├── .gitignore ├── Config └── FilterPlugin.ini ├── DevOps ├── ShapesVisualizer.template ├── clear-plugin.bat ├── package-all.bat └── package-plugin.bat ├── LICENSE ├── README.md ├── Resources └── Icon128.png ├── ShapesVisualizer.uplugin └── Source └── ShapesVisualizer ├── Private ├── Components │ └── ShapesVisualizerComponent.cpp └── ShapesVisualizer.cpp ├── Public └── Components │ └── ShapesVisualizerComponent.h └── ShapesVisualizer.Build.cs /.gitignore: -------------------------------------------------------------------------------- 1 | # Visual Studio 2015 user specific files 2 | .vs/ 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | 22 | # Compiled Static libraries 23 | *.lai 24 | *.la 25 | *.a 26 | *.lib 27 | 28 | # Executables 29 | *.exe 30 | *.out 31 | *.app 32 | *.ipa 33 | 34 | # These project files can be generated by the engine 35 | *.xcodeproj 36 | *.xcworkspace 37 | *.sln 38 | *.suo 39 | *.opensdf 40 | *.sdf 41 | *.VC.db 42 | *.VC.opendb 43 | 44 | # Precompiled Assets 45 | SourceArt/**/*.png 46 | SourceArt/**/*.tga 47 | 48 | # Binary Files 49 | Binaries/* 50 | Plugins/*/Binaries/* 51 | 52 | # Builds 53 | Build/* 54 | 55 | # Whitelist PakBlacklist-.txt files 56 | !Build/*/ 57 | Build/*/** 58 | !Build/*/PakBlacklist*.txt 59 | 60 | # Don't ignore icon files in Build 61 | !Build/**/*.ico 62 | 63 | # Built data for maps 64 | *_BuiltData.uasset 65 | 66 | # Configuration files generated by the Editor 67 | Saved/* 68 | 69 | # Compiled source files for the engine to use 70 | Intermediate/* 71 | Plugins/*/Intermediate/* 72 | 73 | # Cache files for the editor to use 74 | DerivedDataCache/* 75 | -------------------------------------------------------------------------------- /Config/FilterPlugin.ini: -------------------------------------------------------------------------------- 1 | [FilterPlugin] 2 | ; This section lists additional files which will be packaged along with your plugin. Paths should be listed relative to the root plugin directory, and 3 | ; may include "...", "*", and "?" wildcards to match directories, files, and individual characters respectively. 4 | ; 5 | ; Examples: 6 | ; /README.txt 7 | ; /Extras/... 8 | ; /Binaries/ThirdParty/*.dll 9 | -------------------------------------------------------------------------------- /DevOps/ShapesVisualizer.template: -------------------------------------------------------------------------------- 1 | { 2 | "FileVersion": 3, 3 | "Version": 1, 4 | "VersionName": "1.0", 5 | "FriendlyName": "Shapes Visualizer", 6 | "Description": "This component allows you to visualize simple shapes: spheres, half spheres, boxes, cylinders, cones, capsules, points and polyline in wireframe and solid mode.", 7 | "Category": "Rendering", 8 | "CreatedBy": "rionix", 9 | "CreatedByURL": "https://rionix.github.io", 10 | "DocsURL": "https://github.com/rionix/ShapesVisualizer/wiki", 11 | "MarketplaceURL": "com.epicgames.launcher://ue/marketplace/product/379318dddcd6486aa26fc771df8c4b9a", 12 | "SupportURL": "https://github.com/rionix/ShapesVisualizer/issues", 13 | "CanContainContent": false, 14 | "IsBetaVersion": false, 15 | "IsExperimentalVersion": false, 16 | "Installed": false, 17 | "EngineVersion": "%EngineVersion%.0", 18 | "Modules": [ 19 | { 20 | "Name": "ShapesVisualizer", 21 | "Type": "Runtime", 22 | "LoadingPhase": "Default", 23 | "WhitelistPlatforms": [ "Win64", "Android" ] 24 | } 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /DevOps/clear-plugin.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | REM DOCS: https://ss64.com/nt 3 | 4 | REM --== Configuration ==-- 5 | 6 | SET FILES_TO_REMOVE=*.sln *.ipch 7 | SET FOLDERS_TO_REMOVE=.vs Build Binaries Intermediate 8 | 9 | REM --== Commands ==-- 10 | 11 | PUSHD "%~dp0\.." 12 | 13 | FOR %%f IN (%FOLDERS_TO_REMOVE%) DO ( 14 | RMDIR /S /Q %%f 15 | ) 16 | 17 | FOR %%f IN (%FILES_TO_REMOVE%) DO ( 18 | DEL /Q %%f 19 | ) 20 | 21 | POPD 22 | -------------------------------------------------------------------------------- /DevOps/package-all.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | CALL "%~dp0package-plugin.bat" 4.26 "E:\EpicGames\UE_4.26" 4 | CALL "%~dp0package-plugin.bat" 5.0 "E:\EpicGames\UE_5.0" 5 | CALL "%~dp0package-plugin.bat" 5.1 "E:\EpicGames\UE_5.1" 6 | CALL "%~dp0package-plugin.bat" 5.2 "E:\EpicGames\UE_5.2" 7 | CALL "%~dp0package-plugin.bat" 4.27 "C:\Program Files\Epic Games\UE_4.27" 8 | -------------------------------------------------------------------------------- /DevOps/package-plugin.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM --== Manual Config ==-- 4 | 5 | SET PluginPureName=ShapesVisualizer 6 | 7 | REM --== Script Params ==-- 8 | 9 | SET EngineVersion=%~1 10 | SET EnginePath=%~2 11 | 12 | REM --== Config ==-- 13 | 14 | SET BatchFilesPath=%EnginePath%\Engine\Build\BatchFiles 15 | SET RunUAT=%BatchFilesPath%\RunUAT.bat 16 | 17 | FOR %%I IN ("%~dp0.") DO SET "PluginRoot=%%~dpI" 18 | SET PluginName=%PluginPureName%.uplugin 19 | SET PluginPath=%PluginRoot%%PluginName% 20 | SET TemplateName=%PluginPureName%.template 21 | SET TemplatePath=%~dp0%TemplateName% 22 | 23 | SET PackagePath=%PluginRoot%Build\%EngineVersion% 24 | SET ZipPath=%PluginRoot%Build\%PluginPureName%_%EngineVersion%.zip 25 | 26 | REM --== Check Errors ==-- 27 | 28 | IF [%EngineVersion%] == [] GOTO ErrInvalidParams 29 | IF "%EnginePath%" == "" GOTO ErrInvalidParams 30 | IF NOT EXIST "%RunUAT%" GOTO ErrNoRunUAT 31 | IF NOT EXIST "%TemplatePath%" GOTO ErrNoTemplate 32 | 33 | REM --== Make new *.uplugin ==-- 34 | 35 | DEL /Q "%PluginPath%" 36 | 37 | FOR /F "usebackq delims= eol=" %%a IN ("%TemplatePath%") DO ( 38 | CALL ECHO %%a >> "%PluginPath%" 39 | ) 40 | 41 | REM --== Build Plugin ==-- 42 | 43 | CALL "%RunUAT%" BuildPlugin ^ 44 | -Plugin="%PluginPath%" ^ 45 | -Package="%PackagePath%" ^ 46 | -Rocket -VS2019 47 | 48 | IF NOT %ERRORLEVEL% == 0 GOTO ErrUATFailed 49 | 50 | REM --== Compress ==-- 51 | 52 | CALL tar -cav ^ 53 | -f "%ZipPath%" ^ 54 | -C "%PackagePath%" ^ 55 | * 56 | 57 | REM --== Success ==-- 58 | 59 | GOTO :EOF 60 | 61 | REM --== Errors ==-- 62 | 63 | :ErrInvalidParams 64 | ECHO [SCRIPT]: Invalid input parameters 65 | GOTO :EOF 66 | 67 | :ErrNoRunUAT 68 | ECHO [SCRIPT]: File "%RunUAT%" not found 69 | GOTO :EOF 70 | 71 | :ErrNoTemplate 72 | ECHO [SCRIPT]: File "%TemplatePath%" not found 73 | GOTO :EOF 74 | 75 | :ErrUATFailed 76 | ECHO [SCRIPT]: RunUAT failed 77 | GOTO :EOF 78 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 rionix 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ShapesVisualizer 2 | 3 | This component can draw different shapes: spheres, half spheres, boxes, cylinders, cones, capsules, points and polyline in wireframe and solid mode. It is very convenient for displaying debugging information: target search areas, viewing angles, points of interest and others. You just add the component to the actor, select the shape, set its size, visibility settings and that's it. The shape and its parameters can be changed dynamically. 4 | 5 | ## Marketplace URL: 6 | 7 | https://www.unrealengine.com/marketplace/en-US/product/379318dddcd6486aa26fc771df8c4b9a 8 | 9 | ## Features: 10 | 11 | * Simple :) 12 | * 8 types of shapes are supported 13 | * Wireframe and solid mode 14 | * Different visibility modes: only when selected, editor only or game and editor. 15 | 16 | ## Code Modules: 17 | 18 | * ShapesVisualizer (Runtime) 19 | 20 | ## Technical Information: 21 | 22 | * Number of Blueprints: **0** 23 | * Number of C++ Classes: **1** 24 | * Network Replicated: **No** 25 | * Supported Development Platforms: **Win64** 26 | * Supported Target Build Platforms: **Win64, Android** 27 | * Documentation: https://github.com/rionix/ShapesVisualizer/wiki 28 | * Support: https://github.com/rionix/ShapesVisualizer/issues 29 | -------------------------------------------------------------------------------- /Resources/Icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rionix/ShapesVisualizer/2eb1b637bc15bcc914d2760aede6712ab7fb1342/Resources/Icon128.png -------------------------------------------------------------------------------- /ShapesVisualizer.uplugin: -------------------------------------------------------------------------------- 1 | { 2 | "FileVersion": 3, 3 | "Version": 1, 4 | "VersionName": "1.0", 5 | "FriendlyName": "Shapes Visualizer", 6 | "Description": "This component allows you to visualize simple shapes: spheres, half spheres, boxes, cylinders, cones, capsules, points and polyline in wireframe and solid mode.", 7 | "Category": "Rendering", 8 | "CreatedBy": "rionix", 9 | "CreatedByURL": "https://rionix.github.io", 10 | "DocsURL": "https://github.com/rionix/ShapesVisualizer/wiki", 11 | "MarketplaceURL": "com.epicgames.launcher://ue/marketplace/product/379318dddcd6486aa26fc771df8c4b9a", 12 | "SupportURL": "https://github.com/rionix/ShapesVisualizer/issues", 13 | "CanContainContent": false, 14 | "IsBetaVersion": false, 15 | "IsExperimentalVersion": false, 16 | "Installed": false, 17 | "EngineVersion": "4.27.0", 18 | "Modules": [ 19 | { 20 | "Name": "ShapesVisualizer", 21 | "Type": "Runtime", 22 | "LoadingPhase": "Default", 23 | "WhitelistPlatforms": [ "Win64", "Android" ] 24 | } 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /Source/ShapesVisualizer/Private/Components/ShapesVisualizerComponent.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2003-2022 rionix. All Rights Reserved. 2 | 3 | #include "Components/ShapesVisualizerComponent.h" 4 | #include "Engine/Engine.h" 5 | #include "Engine/CollisionProfile.h" 6 | #include "Materials/Material.h" 7 | #include "Materials/MaterialRenderProxy.h" 8 | #include "PrimitiveSceneProxy.h" 9 | #include "DynamicMeshBuilder.h" 10 | #include "SceneManagement.h" 11 | #include "Runtime/Launch/Resources/Version.h" 12 | 13 | // 14 | // Internal functions 15 | // 16 | 17 | namespace 18 | { 19 | // Engine source 4.27 20 | // .\Engine\Source\Runtime\Engine\Private\PrimitiveDrawingUtils.cpp 21 | // Lines [1134-1152]: DrawWireChoppedCone 22 | void DrawWireCone_Internal(FPrimitiveDrawInterface* PDI, const FVector& Base, 23 | const FVector& X, const FVector& Y, const FVector& Z, 24 | const FLinearColor& Color, float Radius, float TopRadius, float HalfHeight, 25 | int32 NumSides, uint8 DepthPriority, float Thickness) 26 | { 27 | const float AngleDelta = 2.0f * PI / NumSides; 28 | FVector LastVertex = Base + X * Radius; 29 | FVector LastTopVertex = Base + X * TopRadius; 30 | 31 | for (int32 SideIndex = 0; SideIndex < NumSides; SideIndex++) 32 | { 33 | const FVector Vertex = Base + (X * FMath::Cos(AngleDelta * (SideIndex + 1)) + Y * FMath::Sin(AngleDelta * (SideIndex + 1))) * Radius; 34 | const FVector TopVertex = Base + (X * FMath::Cos(AngleDelta * (SideIndex + 1)) + Y * FMath::Sin(AngleDelta * (SideIndex + 1))) * TopRadius; 35 | 36 | PDI->DrawLine(LastVertex - Z * HalfHeight, Vertex - Z * HalfHeight, Color, DepthPriority, Thickness); 37 | PDI->DrawLine(LastTopVertex + Z * HalfHeight, TopVertex + Z * HalfHeight, Color, DepthPriority, Thickness); 38 | PDI->DrawLine(LastVertex - Z * HalfHeight, LastTopVertex + Z * HalfHeight, Color, DepthPriority, Thickness); 39 | 40 | LastVertex = Vertex; 41 | LastTopVertex = TopVertex; 42 | } 43 | } 44 | 45 | // Engine source 4.27 46 | // .\Engine\Source\Runtime\Engine\Private\PrimitiveDrawingUtils.cpp 47 | // Lines [549-650]: BuildCylinderVerts 48 | void BuildConeVerts_Internal(const FVector& Base, 49 | const FVector& XAxis, const FVector& YAxis, const FVector& ZAxis, 50 | float Radius, float HalfHeight, uint32 Sides, 51 | TArray& OutVerts, TArray& OutIndices) 52 | { 53 | const float AngleDelta = 2.0f * PI / Sides; 54 | FVector LastVertex = Base + XAxis * Radius; 55 | 56 | FVector2D TC = FVector2D(0.0f, 0.0f); 57 | float TCStep = 1.0f / Sides; 58 | 59 | FVector TopOffset = HalfHeight * ZAxis; 60 | 61 | int32 BaseVertIndex = OutVerts.Num(); 62 | 63 | //Compute vertices for base circle. 64 | for (uint32 SideIndex = 0; SideIndex < Sides; SideIndex++) 65 | { 66 | const FVector Vertex = Base + (XAxis * FMath::Cos(AngleDelta * (SideIndex + 1)) + YAxis * FMath::Sin(AngleDelta * (SideIndex + 1))) * Radius; 67 | FVector Normal = Vertex - Base; 68 | Normal.Normalize(); 69 | 70 | FDynamicMeshVertex MeshVertex; 71 | 72 | #if ENGINE_MAJOR_VERSION == 5 73 | MeshVertex.Position = FVector3f(Vertex - TopOffset); 74 | MeshVertex.TextureCoordinate[0] = FVector2f(TC); 75 | 76 | MeshVertex.SetTangents( 77 | (FVector3f)-ZAxis, 78 | FVector3f((-ZAxis) ^ Normal), 79 | (FVector3f)Normal 80 | ); 81 | #else 82 | MeshVertex.Position = Vertex - TopOffset; 83 | MeshVertex.TextureCoordinate[0] = TC; 84 | 85 | MeshVertex.SetTangents( 86 | -ZAxis, 87 | (-ZAxis) ^ Normal, 88 | Normal 89 | ); 90 | #endif 91 | 92 | OutVerts.Add(MeshVertex); //Add bottom vertex 93 | 94 | LastVertex = Vertex; 95 | TC.X += TCStep; 96 | } 97 | 98 | LastVertex = Base + XAxis * Radius; 99 | TC = FVector2D(0.0f, 1.0f); 100 | 101 | //Compute vertices for the top circle 102 | for (uint32 SideIndex = 0; SideIndex < Sides; SideIndex++) 103 | { 104 | const FVector Vertex = Base + (XAxis * FMath::Cos(AngleDelta * (SideIndex + 1)) + YAxis * FMath::Sin(AngleDelta * (SideIndex + 1))) * SMALL_NUMBER; 105 | FVector Normal = Vertex - Base; 106 | Normal.Normalize(); 107 | 108 | FDynamicMeshVertex MeshVertex; 109 | 110 | #if ENGINE_MAJOR_VERSION == 5 111 | MeshVertex.Position = FVector3f(Vertex + TopOffset); 112 | MeshVertex.TextureCoordinate[0] = FVector2f(TC); 113 | 114 | MeshVertex.SetTangents( 115 | (FVector3f)-ZAxis, 116 | FVector3f((-ZAxis) ^ Normal), 117 | (FVector3f)Normal 118 | ); 119 | #else 120 | MeshVertex.Position = Vertex + TopOffset; 121 | MeshVertex.TextureCoordinate[0] = TC; 122 | 123 | MeshVertex.SetTangents( 124 | -ZAxis, 125 | (-ZAxis) ^ Normal, 126 | Normal 127 | ); 128 | #endif 129 | 130 | OutVerts.Add(MeshVertex); //Add top vertex 131 | 132 | LastVertex = Vertex; 133 | TC.X += TCStep; 134 | } 135 | 136 | //Add top/bottom triangles, in the style of a fan. 137 | //Note if we wanted nice rendering of the caps then we need to duplicate the vertices and modify 138 | //texture/tangent coordinates. 139 | for (uint32 SideIndex = 1; SideIndex < Sides; SideIndex++) 140 | { 141 | int32 V0 = BaseVertIndex; 142 | int32 V1 = BaseVertIndex + SideIndex; 143 | int32 V2 = BaseVertIndex + ((SideIndex + 1) % Sides); 144 | 145 | //bottom 146 | OutIndices.Add(V0); 147 | OutIndices.Add(V1); 148 | OutIndices.Add(V2); 149 | 150 | // top 151 | // OutIndices.Add(Sides + V2); 152 | // OutIndices.Add(Sides + V1); 153 | // OutIndices.Add(Sides + V0); 154 | } 155 | 156 | //Add sides. 157 | 158 | for (uint32 SideIndex = 0; SideIndex < Sides; SideIndex++) 159 | { 160 | int32 V0 = BaseVertIndex + SideIndex; 161 | int32 V1 = BaseVertIndex + ((SideIndex + 1) % Sides); 162 | int32 V2 = V0 + Sides; 163 | int32 V3 = V1 + Sides; 164 | 165 | OutIndices.Add(V0); 166 | OutIndices.Add(V2); 167 | OutIndices.Add(V1); 168 | 169 | OutIndices.Add(V2); 170 | OutIndices.Add(V3); 171 | OutIndices.Add(V1); 172 | } 173 | 174 | } 175 | 176 | // Engine source 4.27 177 | // .\Engine\Source\Runtime\Engine\Private\PrimitiveDrawingUtils.cpp 178 | // Lines [688-697]: GetConeMesh 179 | void GetConeMesh_Internal(const FMatrix& LocalToWorld, 180 | float Radius, float HalfHeight, uint32 Sides, 181 | const FMaterialRenderProxy* MaterialRenderProxy, uint8 DepthPriority, 182 | int32 ViewIndex, FMeshElementCollector& Collector) 183 | { 184 | TArray MeshVerts; 185 | TArray MeshIndices; 186 | BuildConeVerts_Internal(FVector::ZeroVector, FVector::XAxisVector, FVector::YAxisVector, FVector::ZAxisVector, 187 | Radius, HalfHeight, Sides, MeshVerts, MeshIndices); 188 | FDynamicMeshBuilder MeshBuilder(Collector.GetFeatureLevel()); 189 | MeshBuilder.AddVertices(MeshVerts); 190 | MeshBuilder.AddTriangles(MeshIndices); 191 | MeshBuilder.GetMesh(LocalToWorld, MaterialRenderProxy, DepthPriority, false, false, ViewIndex, Collector); 192 | } 193 | 194 | // Engine source 4.27 195 | // .\Engine\Source\Runtime\Engine\Private\PrimitiveDrawingUtils.cpp 196 | // Lines [699-710]: GetCapsuleMesh 197 | void GetCapsuleMesh_Internal(const FMatrix& LocalToWorld, const FLinearColor& Color, float Radius, float HalfHeight, int32 NumSides, const FMaterialRenderProxy* MaterialRenderProxy, uint8 DepthPriority, bool bDisableBackfaceCulling, int32 ViewIndex, FMeshElementCollector& Collector) 198 | { 199 | const FVector XAxis = LocalToWorld.GetScaledAxis(EAxis::X); 200 | const FVector YAxis = LocalToWorld.GetScaledAxis(EAxis::Y); 201 | const FVector ZAxis = LocalToWorld.GetScaledAxis(EAxis::Z); 202 | const FVector Origin = LocalToWorld.GetOrigin() - ZAxis * HalfHeight; 203 | const FVector ScaledRadius = LocalToWorld.GetScaleVector() * Radius; 204 | const float HalfAxis = FMath::Max(HalfHeight - Radius, 1.f); 205 | const FVector BottomEnd = Origin + Radius * ZAxis; 206 | const FVector TopEnd = BottomEnd + (2 * HalfAxis) * ZAxis; 207 | const float CylinderHalfHeight = (TopEnd - BottomEnd).Size() * 0.5; 208 | const FVector CylinderLocation = BottomEnd + CylinderHalfHeight * ZAxis; 209 | 210 | GetOrientedHalfSphereMesh(TopEnd, FRotationMatrix::MakeFromXY(XAxis, YAxis).Rotator(), ScaledRadius, NumSides, NumSides, 0, PI / 2, MaterialRenderProxy, DepthPriority, bDisableBackfaceCulling, ViewIndex, Collector); 211 | GetCylinderMesh(CylinderLocation, XAxis, YAxis, ZAxis, Radius, CylinderHalfHeight, NumSides, MaterialRenderProxy, DepthPriority, ViewIndex, Collector); 212 | GetOrientedHalfSphereMesh(BottomEnd, FRotationMatrix::MakeFromXY(XAxis, YAxis).Rotator(), ScaledRadius, NumSides, NumSides, PI / 2, PI, MaterialRenderProxy, DepthPriority, bDisableBackfaceCulling, ViewIndex, Collector); 213 | } 214 | } 215 | 216 | // 217 | // FShapesVisualizerSceneProxy 218 | // 219 | 220 | class FShapesVisualizerSceneProxy : public FPrimitiveSceneProxy 221 | { 222 | public: 223 | 224 | FShapesVisualizerSceneProxy(const UShapesVisualizerComponent* InComponent) 225 | : FPrimitiveSceneProxy(InComponent) 226 | , Shape(InComponent->Shape) 227 | , Radii(InComponent->Radii) 228 | , Height(InComponent->Height) 229 | , Extent(InComponent->Extent) 230 | , Points(InComponent->Points) 231 | , BaseColor(InComponent->Color) 232 | , Wireframe(SafeWireframe(InComponent->Shape, InComponent->Wireframe)) 233 | , LineThickness(InComponent->LineThickness) 234 | , NumSides(InComponent->NumSides) 235 | , ShowOnlyWhenSelected(InComponent->ShowOnlyWhenSelected) 236 | { 237 | bWantsSelectionOutline = InComponent->WantsSelectionOutline; 238 | } 239 | 240 | virtual SIZE_T GetTypeHash() const override 241 | { 242 | static size_t UniquePointer; 243 | return reinterpret_cast(&UniquePointer); 244 | } 245 | 246 | virtual void GetDynamicMeshElements(const TArray& Views, 247 | const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, 248 | FMeshElementCollector& Collector) const override 249 | { 250 | const FMatrix& LTW = GetLocalToWorld(); 251 | const FVector WorldOrigin = LTW.GetOrigin(); 252 | const float HalfHeight = Height / 2.f; 253 | 254 | for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++) 255 | { 256 | if (!(VisibilityMap & (1 << ViewIndex))) 257 | continue; 258 | 259 | FPrimitiveDrawInterface* PDI = Wireframe ? Collector.GetPDI(ViewIndex) : nullptr; 260 | 261 | const bool Outline = Wireframe && WantsSelectionOutline(); 262 | const FLinearColor Color = GetViewSelectionColor(BaseColor, *Views[ViewIndex], 263 | Outline ? IsSelected() : false, Outline ? IsHovered() : false, 264 | false, IsIndividuallySelected()); 265 | const FMaterialRenderProxy* const ParentMaterial = Wireframe ? nullptr 266 | : GEngine->DebugMeshMaterial->GetRenderProxy(); 267 | FMaterialRenderProxy* const MeshMaterial = ParentMaterial 268 | ? new FColoredMaterialRenderProxy(ParentMaterial, Color) 269 | : nullptr; 270 | if (MeshMaterial) 271 | Collector.RegisterOneFrameMaterialProxy(MeshMaterial); 272 | 273 | switch (Shape) 274 | { 275 | case EVisualShape::Sphere: 276 | if (Wireframe) 277 | DrawWireSphere(PDI, FTransform{ LTW }, 278 | Color, Radii, NumSides, 279 | SDPG_World, LineThickness); 280 | else 281 | GetOrientedHalfSphereMesh(WorldOrigin, 282 | LTW.Rotator(), LTW.GetScaleVector() * Radii, 283 | NumSides, FMath::Max(3, NumSides / 2), 0.f, PI, 284 | MeshMaterial, SDPG_World, false, ViewIndex, Collector); 285 | break; 286 | 287 | case EVisualShape::HalfSphere: 288 | GetOrientedHalfSphereMesh(WorldOrigin, 289 | LTW.Rotator(), LTW.GetScaleVector() * Radii, 290 | NumSides, NumSides, 0.f, HALF_PI, 291 | MeshMaterial, SDPG_World, false, ViewIndex, Collector); 292 | break; 293 | 294 | case EVisualShape::Box: 295 | if (Wireframe) 296 | DrawOrientedWireBox(PDI, WorldOrigin, 297 | LTW.GetScaledAxis(EAxis::X), 298 | LTW.GetScaledAxis(EAxis::Y), 299 | LTW.GetScaledAxis(EAxis::Z), 300 | Extent, Color, SDPG_World, LineThickness); 301 | else 302 | GetBoxMesh(LTW, Extent, 303 | MeshMaterial, SDPG_World, ViewIndex, Collector); 304 | break; 305 | 306 | case EVisualShape::Cylinder: 307 | if (Wireframe) 308 | { 309 | if (Height > 0.f) 310 | DrawWireCylinder(PDI, WorldOrigin, 311 | LTW.GetScaledAxis(EAxis::X), 312 | LTW.GetScaledAxis(EAxis::Y), 313 | LTW.GetScaledAxis(EAxis::Z), 314 | Color, Radii, HalfHeight, NumSides, 315 | SDPG_World, LineThickness); 316 | else 317 | DrawCircle(PDI, WorldOrigin, 318 | LTW.GetScaledAxis(EAxis::X), 319 | LTW.GetScaledAxis(EAxis::Y), 320 | Color, Radii, NumSides, 321 | SDPG_World, LineThickness); 322 | } 323 | else 324 | GetCylinderMesh(LTW, FVector::ZeroVector, 325 | FVector::XAxisVector, FVector::YAxisVector, FVector::ZAxisVector, 326 | Radii, HalfHeight, NumSides, 327 | MeshMaterial, SDPG_World, ViewIndex, Collector); 328 | break; 329 | 330 | case EVisualShape::Cone: 331 | if (Wireframe) 332 | DrawWireCone_Internal(PDI, WorldOrigin, 333 | LTW.GetScaledAxis(EAxis::X), 334 | LTW.GetScaledAxis(EAxis::Y), 335 | LTW.GetScaledAxis(EAxis::Z), 336 | Color, Radii, 0.f, HalfHeight, NumSides, 337 | SDPG_World, LineThickness); 338 | else 339 | GetConeMesh_Internal(LTW, 340 | Radii, HalfHeight, NumSides, 341 | MeshMaterial, SDPG_World, ViewIndex, Collector); 342 | break; 343 | 344 | case EVisualShape::Capsule: 345 | if (Wireframe) 346 | DrawWireCapsule(PDI, WorldOrigin, 347 | LTW.GetScaledAxis(EAxis::X), 348 | LTW.GetScaledAxis(EAxis::Y), 349 | LTW.GetScaledAxis(EAxis::Z), 350 | Color, Radii, HalfHeight, NumSides, 351 | SDPG_World, LineThickness); 352 | else 353 | GetCapsuleMesh_Internal(LTW, 354 | FLinearColor{ Color }, Radii, HalfHeight, NumSides, 355 | MeshMaterial, SDPG_World, false, ViewIndex, Collector); 356 | break; 357 | 358 | case EVisualShape::Points: 359 | for (const FVector& Pt : Points) 360 | { 361 | if (Wireframe) 362 | DrawWireDiamond(PDI, 363 | FTranslationMatrix{ LTW.TransformPosition(Pt) }, Radii, 364 | Color, SDPG_World, LineThickness); 365 | else 366 | GetSphereMesh(LTW.TransformPosition(Pt), FVector{ Radii }, 367 | NumSides, NumSides, 368 | MeshMaterial, SDPG_World, false, ViewIndex, Collector); 369 | } 370 | break; 371 | 372 | case EVisualShape::Polyline: 373 | for (int32 i = 0; i < Points.Num() - 1; ++i) 374 | { 375 | PDI->DrawLine( 376 | LTW.TransformPosition(Points[i]), 377 | LTW.TransformPosition(Points[i + 1]), 378 | Color, SDPG_World, LineThickness); 379 | } 380 | break; 381 | } // switch (Shape) 382 | } 383 | } 384 | 385 | virtual FPrimitiveViewRelevance GetViewRelevance(const FSceneView* View) const override 386 | { 387 | FPrimitiveViewRelevance Result; 388 | Result.bDrawRelevance = IsShown(View) && (!ShowOnlyWhenSelected || IsSelected()); 389 | Result.bDynamicRelevance = true; 390 | Result.bShadowRelevance = IsShadowCast(View); 391 | Result.bEditorPrimitiveRelevance = UseEditorCompositing(View); 392 | Result.bSeparateTranslucency = Result.bNormalTranslucency = IsShown(View); 393 | return Result; 394 | } 395 | 396 | virtual uint32 GetMemoryFootprint(void) const override 397 | { 398 | return sizeof(*this) + GetAllocatedSize() + Points.GetAllocatedSize(); 399 | } 400 | 401 | private: 402 | 403 | FORCEINLINE static bool SafeWireframe(EVisualShape Shape, bool Wireframe) 404 | { 405 | switch (Shape) 406 | { 407 | case EVisualShape::HalfSphere: 408 | return false; 409 | case EVisualShape::Polyline: 410 | return true; 411 | } 412 | return Wireframe; 413 | } 414 | 415 | private: 416 | 417 | // Shape 418 | EVisualShape Shape; 419 | float Radii; 420 | float Height; 421 | FVector Extent; 422 | TArray Points; 423 | // Appearance 424 | FColor BaseColor; 425 | bool Wireframe; 426 | float LineThickness; 427 | int32 NumSides; 428 | bool ShowOnlyWhenSelected; 429 | }; 430 | 431 | // 432 | // UShapesVisualizerComponent 433 | // 434 | 435 | UShapesVisualizerComponent::UShapesVisualizerComponent(const FObjectInitializer& ObjectInitializer) 436 | : Super(ObjectInitializer) 437 | { 438 | // Tick 439 | PrimaryComponentTick.bCanEverTick = false; 440 | PrimaryComponentTick.bStartWithTickEnabled = false; 441 | 442 | // Collision & Navigation 443 | SetCollisionProfileName(UCollisionProfile::NoCollision_ProfileName); 444 | SetGenerateOverlapEvents(false); 445 | CanCharacterStepUpOn = ECB_No; 446 | SetCanEverAffectNavigation(false); 447 | 448 | // Rendering 449 | SetHiddenInGame(true); 450 | SetCastShadow(false); 451 | SetReceivesDecals(false); 452 | } 453 | 454 | FPrimitiveSceneProxy* UShapesVisualizerComponent::CreateSceneProxy() 455 | { 456 | return new FShapesVisualizerSceneProxy(this); 457 | } 458 | 459 | FBoxSphereBounds UShapesVisualizerComponent::CalcBounds(const FTransform& LocalToWorld) const 460 | { 461 | switch (Shape) 462 | { 463 | case EVisualShape::Sphere: 464 | return FBoxSphereBounds{ LocalToWorld.GetLocation(), FVector{ Radii }, Radii }; 465 | case EVisualShape::HalfSphere: 466 | return FBoxSphereBounds{ FVector{ 0.f, 0.f, Radii / 2.f }, 467 | FVector{ Radii, Radii, Radii / 2.f }, Radii }.TransformBy(LocalToWorld); 468 | case EVisualShape::Box: 469 | return FBoxSphereBounds{ FBox{ -Extent, Extent } }.TransformBy(LocalToWorld); 470 | case EVisualShape::Cylinder: 471 | case EVisualShape::Cone: 472 | case EVisualShape::Capsule: 473 | { 474 | const float HalfHeight = Height / 2.f; 475 | const FVector BoxExtent{ Radii, Radii, HalfHeight }; 476 | return FBoxSphereBounds(FVector::ZeroVector, BoxExtent, HalfHeight).TransformBy(LocalToWorld); 477 | } 478 | case EVisualShape::Points: 479 | case EVisualShape::Polyline: 480 | { 481 | const FBoxSphereBounds PointsBounds{ Points.GetData(), static_cast(Points.Num()) }; 482 | return PointsBounds.ExpandBy(Radii).TransformBy(LocalToWorld); 483 | } 484 | } 485 | return FBoxSphereBounds{ LocalToWorld.GetLocation(), FVector::ZeroVector, 0.f }; 486 | } 487 | 488 | // 489 | // Setters 490 | // 491 | 492 | void UShapesVisualizerComponent::SetSphereShape(float InRadii) 493 | { 494 | Shape = EVisualShape::Sphere; 495 | Radii = InRadii; 496 | UpdateBounds(); 497 | MarkRenderStateDirty(); 498 | } 499 | 500 | void UShapesVisualizerComponent::SetHalfSphereShape(float InRadii) 501 | { 502 | Shape = EVisualShape::HalfSphere; 503 | Radii = InRadii; 504 | UpdateBounds(); 505 | MarkRenderStateDirty(); 506 | } 507 | 508 | void UShapesVisualizerComponent::SetBoxShape(const FVector& InExtent) 509 | { 510 | Shape = EVisualShape::Box; 511 | Extent = InExtent; 512 | UpdateBounds(); 513 | MarkRenderStateDirty(); 514 | } 515 | 516 | void UShapesVisualizerComponent::SetCylinderShape(float InRadii, float InHeight) 517 | { 518 | Shape = EVisualShape::Cylinder; 519 | Radii = InRadii; 520 | Height = InHeight; 521 | UpdateBounds(); 522 | MarkRenderStateDirty(); 523 | } 524 | 525 | void UShapesVisualizerComponent::SetConeShape(float InRadii, float InHeight) 526 | { 527 | Shape = EVisualShape::Cone; 528 | Radii = InRadii; 529 | Height = InHeight; 530 | UpdateBounds(); 531 | MarkRenderStateDirty(); 532 | } 533 | 534 | void UShapesVisualizerComponent::SetCapsuleShape(float InRadii, float InHeight) 535 | { 536 | Shape = EVisualShape::Capsule; 537 | Radii = InRadii; 538 | Height = InHeight; 539 | UpdateBounds(); 540 | MarkRenderStateDirty(); 541 | } 542 | 543 | void UShapesVisualizerComponent::SetPointsShape(const TArray& InPoints) 544 | { 545 | Shape = EVisualShape::Points; 546 | Points = InPoints; 547 | UpdateBounds(); 548 | MarkRenderStateDirty(); 549 | } 550 | 551 | void UShapesVisualizerComponent::SetPolylineShape(const TArray& InPoints) 552 | { 553 | Shape = EVisualShape::Polyline; 554 | Points = InPoints; 555 | UpdateBounds(); 556 | MarkRenderStateDirty(); 557 | } 558 | 559 | void UShapesVisualizerComponent::SetColor(const FColor& InColor) 560 | { 561 | Color = InColor; 562 | MarkRenderStateDirty(); 563 | } 564 | 565 | void UShapesVisualizerComponent::SetWireframe(bool InWireframe, float InLineThickness) 566 | { 567 | Wireframe = InWireframe; 568 | LineThickness = FMath::Max(0.f, InLineThickness); 569 | MarkRenderStateDirty(); 570 | } 571 | 572 | void UShapesVisualizerComponent::SetNumSides(int32 InNumSides) 573 | { 574 | NumSides = FMath::Clamp(InNumSides, 8, 64); 575 | MarkRenderStateDirty(); 576 | } 577 | -------------------------------------------------------------------------------- /Source/ShapesVisualizer/Private/ShapesVisualizer.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2003-2022 rionix. All Rights Reserved. 2 | 3 | #include "Modules/ModuleManager.h" 4 | 5 | IMPLEMENT_MODULE(FDefaultModuleImpl, ShapesVisualizer) -------------------------------------------------------------------------------- /Source/ShapesVisualizer/Public/Components/ShapesVisualizerComponent.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2003-2022 rionix. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "Components/PrimitiveComponent.h" 6 | #include "ShapesVisualizerComponent.generated.h" 7 | 8 | // 9 | // EVisualShape - enum of all available shapes 10 | // 11 | 12 | UENUM(BlueprintType) 13 | enum class EVisualShape : uint8 14 | { 15 | Sphere, 16 | HalfSphere, 17 | Box, 18 | Cylinder, 19 | Cone, 20 | Capsule, 21 | // Drawing an array of points with specified radii 22 | Points, 23 | // Drawing a polyline by specified array of points 24 | Polyline 25 | }; 26 | 27 | // 28 | // USimpleShapeComponent 29 | // 30 | 31 | UCLASS(Blueprintable, ClassGroup=Utility, 32 | hideCategories = (Activation, Lighting, Navigation, Physics, Collision, Tags, Cooking), 33 | meta=(BlueprintSpawnableComponent)) 34 | class UShapesVisualizerComponent : public UPrimitiveComponent 35 | { 36 | GENERATED_BODY() 37 | 38 | public: 39 | 40 | UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Shape") 41 | EVisualShape Shape = EVisualShape::Sphere; 42 | 43 | UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Shape", meta = (ClampMin = "0.0", EditConditionHides, EditCondition = "Shape != EVisualShape::Box && Shape != EVisualShape::Polyline")) 44 | float Radii = 50.f; 45 | 46 | UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Shape", meta = (EditConditionHides, EditCondition = "Shape == EVisualShape::Cylinder || Shape == EVisualShape::Cone || Shape == EVisualShape::Capsule")) 47 | float Height = 100.f; 48 | 49 | UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Shape", meta = (EditConditionHides, EditCondition = "Shape == EVisualShape::Box")) 50 | FVector Extent { 50.f, 50.f, 50.f }; 51 | 52 | UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Shape", meta = (EditConditionHides, EditCondition = "Shape == EVisualShape::Points || Shape == EVisualShape::Polyline")) 53 | TArray Points; 54 | 55 | UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Appearance") 56 | FColor Color { 223, 149, 157 }; 57 | 58 | UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Appearance") 59 | bool Wireframe = false; 60 | 61 | UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Appearance", AdvancedDisplay, meta = (ClampMin = "0.0", EditCondition = "Wireframe || Shape == EVisualShape::Polyline")) 62 | float LineThickness = 0.f; 63 | 64 | UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Appearance", AdvancedDisplay, meta = (ClampMin = "8", ClampMax = "64")) 65 | int32 NumSides = 24; 66 | 67 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Rendering") 68 | bool ShowOnlyWhenSelected = false; 69 | 70 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Rendering") 71 | bool WantsSelectionOutline = true; 72 | 73 | public: 74 | 75 | UShapesVisualizerComponent(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); 76 | 77 | // UPrimitiveComponent Interface 78 | 79 | virtual FPrimitiveSceneProxy* CreateSceneProxy() override; 80 | virtual FBoxSphereBounds CalcBounds(const FTransform& LocalToWorld) const override; 81 | 82 | public: 83 | 84 | UFUNCTION(BlueprintCallable, Category = "Components|ShapesVisualizer") 85 | void SetSphereShape(float InRadii = 50.f); 86 | 87 | UFUNCTION(BlueprintCallable, Category = "Components|ShapesVisualizer") 88 | void SetHalfSphereShape(float InRadii = 50.f); 89 | 90 | UFUNCTION(BlueprintCallable, Category = "Components|ShapesVisualizer") 91 | void SetBoxShape(const FVector& InExtent); 92 | 93 | UFUNCTION(BlueprintCallable, Category = "Components|ShapesVisualizer") 94 | void SetCylinderShape(float InRadii = 50.f, float InHeight = 100.f); 95 | 96 | UFUNCTION(BlueprintCallable, Category = "Components|ShapesVisualizer") 97 | void SetConeShape(float InRadii = 50.f, float InHeight = 100.f); 98 | 99 | UFUNCTION(BlueprintCallable, Category = "Components|ShapesVisualizer") 100 | void SetCapsuleShape(float InRadii = 50.f, float InHeight = 100.f); 101 | 102 | UFUNCTION(BlueprintCallable, Category = "Components|ShapesVisualizer") 103 | void SetPointsShape(const TArray& InPoints); 104 | 105 | UFUNCTION(BlueprintCallable, Category = "Components|ShapesVisualizer") 106 | void SetPolylineShape(const TArray& InPoints); 107 | 108 | UFUNCTION(BlueprintCallable, Category = "Components|ShapesVisualizer") 109 | void SetColor(const FColor& InColor = FColor::White); 110 | 111 | UFUNCTION(BlueprintCallable, Category = "Components|ShapesVisualizer") 112 | void SetWireframe(bool InWireframe, float InLineThickness = 0.f); 113 | 114 | UFUNCTION(BlueprintCallable, Category = "Components|ShapesVisualizer") 115 | void SetNumSides(int32 InNumSides = 24); 116 | }; 117 | -------------------------------------------------------------------------------- /Source/ShapesVisualizer/ShapesVisualizer.Build.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2003-2022 rionix. All Rights Reserved. 2 | 3 | using UnrealBuildTool; 4 | 5 | public class ShapesVisualizer : ModuleRules 6 | { 7 | public ShapesVisualizer(ReadOnlyTargetRules Target) : base(Target) 8 | { 9 | PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; 10 | 11 | PublicDependencyModuleNames.AddRange(new string[] { "Core" }); 12 | PrivateDependencyModuleNames.AddRange(new string[] { "CoreUObject", "RenderCore", "Engine" }); 13 | } 14 | } 15 | --------------------------------------------------------------------------------