├── .gitattributes ├── .gitignore ├── Content ├── Icons │ └── icon_Test.png └── Materials │ └── M_BrushDecal.uasset ├── DynamicTerrain.uplugin ├── Resources └── Icon128.png └── Source ├── DynamicTerrain ├── DynamicTerrain.Build.cs ├── Private │ ├── DynamicTerrain.cpp │ ├── Terrain.cpp │ ├── TerrainAlgorithms.cpp │ ├── TerrainBrushDecal.cpp │ ├── TerrainComponent.cpp │ ├── TerrainFoliage.cpp │ ├── TerrainGenerator.cpp │ ├── TerrainHeightMap.cpp │ ├── TerrainRender.cpp │ ├── TerrainRender.h │ ├── TerrainStat.h │ ├── TerrainToolComponent.cpp │ └── TerrainTools.cpp └── Public │ ├── DynamicTerrain.h │ ├── Terrain.h │ ├── TerrainAlgorithms.h │ ├── TerrainBrushDecal.h │ ├── TerrainComponent.h │ ├── TerrainFoliage.h │ ├── TerrainGenerator.h │ ├── TerrainHeightMap.h │ ├── TerrainToolComponent.h │ └── TerrainTools.h └── DynamicTerrainEditor ├── DynamicTerrainEditor.Build.cs ├── Private ├── DynamicTerrainAssets.cpp ├── DynamicTerrainEditor.cpp ├── DynamicTerrainInterface.cpp ├── DynamicTerrainMode.cpp ├── DynamicTerrainModeToolkit.cpp └── DynamicTerrainStyle.cpp └── Public ├── DynamicTerrainAssets.h ├── DynamicTerrainEditor.h ├── DynamicTerrainInterface.h ├── DynamicTerrainMode.h ├── DynamicTerrainModeToolkit.h └── DynamicTerrainStyle.h /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # Unreal project files 7 | Binaries/ 8 | Intermediate/ 9 | 10 | # User-specific files 11 | *.rsuser 12 | *.suo 13 | *.user 14 | *.userosscache 15 | *.sln.docstates 16 | 17 | # User-specific files (MonoDevelop/Xamarin Studio) 18 | *.userprefs 19 | 20 | # Build results 21 | [Dd]ebug/ 22 | [Dd]ebugPublic/ 23 | [Rr]elease/ 24 | [Rr]eleases/ 25 | x64/ 26 | x86/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Ll]og/ 33 | 34 | # Visual Studio 2015/2017 cache/options directory 35 | .vs/ 36 | # Uncomment if you have tasks that create the project's static files in wwwroot 37 | #wwwroot/ 38 | 39 | # Visual Studio 2017 auto generated files 40 | Generated\ Files/ 41 | 42 | # MSTest test Results 43 | [Tt]est[Rr]esult*/ 44 | [Bb]uild[Ll]og.* 45 | 46 | # NUNIT 47 | *.VisualState.xml 48 | TestResult.xml 49 | 50 | # Build Results of an ATL Project 51 | [Dd]ebugPS/ 52 | [Rr]eleasePS/ 53 | dlldata.c 54 | 55 | # Benchmark Results 56 | BenchmarkDotNet.Artifacts/ 57 | 58 | # .NET Core 59 | project.lock.json 60 | project.fragment.lock.json 61 | artifacts/ 62 | 63 | # StyleCop 64 | StyleCopReport.xml 65 | 66 | # Files built by Visual Studio 67 | *_i.c 68 | *_p.c 69 | *_h.h 70 | *.ilk 71 | *.meta 72 | *.obj 73 | *.iobj 74 | *.pch 75 | *.pdb 76 | *.ipdb 77 | *.pgc 78 | *.pgd 79 | *.rsp 80 | *.sbr 81 | *.tlb 82 | *.tli 83 | *.tlh 84 | *.tmp 85 | *.tmp_proj 86 | *_wpftmp.csproj 87 | *.log 88 | *.vspscc 89 | *.vssscc 90 | .builds 91 | *.pidb 92 | *.svclog 93 | *.scc 94 | 95 | # Chutzpah Test files 96 | _Chutzpah* 97 | 98 | # Visual C++ cache files 99 | ipch/ 100 | *.aps 101 | *.ncb 102 | *.opendb 103 | *.opensdf 104 | *.sdf 105 | *.cachefile 106 | *.VC.db 107 | *.VC.VC.opendb 108 | 109 | # Visual Studio profiler 110 | *.psess 111 | *.vsp 112 | *.vspx 113 | *.sap 114 | 115 | # Visual Studio Trace Files 116 | *.e2e 117 | 118 | # TFS 2012 Local Workspace 119 | $tf/ 120 | 121 | # Guidance Automation Toolkit 122 | *.gpState 123 | 124 | # ReSharper is a .NET coding add-in 125 | _ReSharper*/ 126 | *.[Rr]e[Ss]harper 127 | *.DotSettings.user 128 | 129 | # JustCode is a .NET coding add-in 130 | .JustCode 131 | 132 | # TeamCity is a build add-in 133 | _TeamCity* 134 | 135 | # DotCover is a Code Coverage Tool 136 | *.dotCover 137 | 138 | # AxoCover is a Code Coverage Tool 139 | .axoCover/* 140 | !.axoCover/settings.json 141 | 142 | # Visual Studio code coverage results 143 | *.coverage 144 | *.coveragexml 145 | 146 | # NCrunch 147 | _NCrunch_* 148 | .*crunch*.local.xml 149 | nCrunchTemp_* 150 | 151 | # MightyMoose 152 | *.mm.* 153 | AutoTest.Net/ 154 | 155 | # Web workbench (sass) 156 | .sass-cache/ 157 | 158 | # Installshield output folder 159 | [Ee]xpress/ 160 | 161 | # DocProject is a documentation generator add-in 162 | DocProject/buildhelp/ 163 | DocProject/Help/*.HxT 164 | DocProject/Help/*.HxC 165 | DocProject/Help/*.hhc 166 | DocProject/Help/*.hhk 167 | DocProject/Help/*.hhp 168 | DocProject/Help/Html2 169 | DocProject/Help/html 170 | 171 | # Click-Once directory 172 | publish/ 173 | 174 | # Publish Web Output 175 | *.[Pp]ublish.xml 176 | *.azurePubxml 177 | # Note: Comment the next line if you want to checkin your web deploy settings, 178 | # but database connection strings (with potential passwords) will be unencrypted 179 | *.pubxml 180 | *.publishproj 181 | 182 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 183 | # checkin your Azure Web App publish settings, but sensitive information contained 184 | # in these scripts will be unencrypted 185 | PublishScripts/ 186 | 187 | # NuGet Packages 188 | *.nupkg 189 | # The packages folder can be ignored because of Package Restore 190 | **/[Pp]ackages/* 191 | # except build/, which is used as an MSBuild target. 192 | !**/[Pp]ackages/build/ 193 | # Uncomment if necessary however generally it will be regenerated when needed 194 | #!**/[Pp]ackages/repositories.config 195 | # NuGet v3's project.json files produces more ignorable files 196 | *.nuget.props 197 | *.nuget.targets 198 | 199 | # Microsoft Azure Build Output 200 | csx/ 201 | *.build.csdef 202 | 203 | # Microsoft Azure Emulator 204 | ecf/ 205 | rcf/ 206 | 207 | # Windows Store app package directories and files 208 | AppPackages/ 209 | BundleArtifacts/ 210 | Package.StoreAssociation.xml 211 | _pkginfo.txt 212 | *.appx 213 | 214 | # Visual Studio cache files 215 | # files ending in .cache can be ignored 216 | *.[Cc]ache 217 | # but keep track of directories ending in .cache 218 | !?*.[Cc]ache/ 219 | 220 | # Others 221 | ClientBin/ 222 | ~$* 223 | *~ 224 | *.dbmdl 225 | *.dbproj.schemaview 226 | *.jfm 227 | *.pfx 228 | *.publishsettings 229 | orleans.codegen.cs 230 | 231 | # Including strong name files can present a security risk 232 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 233 | #*.snk 234 | 235 | # Since there are multiple workflows, uncomment next line to ignore bower_components 236 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 237 | #bower_components/ 238 | 239 | # RIA/Silverlight projects 240 | Generated_Code/ 241 | 242 | # Backup & report files from converting an old project file 243 | # to a newer Visual Studio version. Backup files are not needed, 244 | # because we have git ;-) 245 | _UpgradeReport_Files/ 246 | Backup*/ 247 | UpgradeLog*.XML 248 | UpgradeLog*.htm 249 | ServiceFabricBackup/ 250 | *.rptproj.bak 251 | 252 | # SQL Server files 253 | *.mdf 254 | *.ldf 255 | *.ndf 256 | 257 | # Business Intelligence projects 258 | *.rdl.data 259 | *.bim.layout 260 | *.bim_*.settings 261 | *.rptproj.rsuser 262 | *- Backup*.rdl 263 | 264 | # Microsoft Fakes 265 | FakesAssemblies/ 266 | 267 | # GhostDoc plugin setting file 268 | *.GhostDoc.xml 269 | 270 | # Node.js Tools for Visual Studio 271 | .ntvs_analysis.dat 272 | node_modules/ 273 | 274 | # Visual Studio 6 build log 275 | *.plg 276 | 277 | # Visual Studio 6 workspace options file 278 | *.opt 279 | 280 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 281 | *.vbw 282 | 283 | # Visual Studio LightSwitch build output 284 | **/*.HTMLClient/GeneratedArtifacts 285 | **/*.DesktopClient/GeneratedArtifacts 286 | **/*.DesktopClient/ModelManifest.xml 287 | **/*.Server/GeneratedArtifacts 288 | **/*.Server/ModelManifest.xml 289 | _Pvt_Extensions 290 | 291 | # Paket dependency manager 292 | .paket/paket.exe 293 | paket-files/ 294 | 295 | # FAKE - F# Make 296 | .fake/ 297 | 298 | # JetBrains Rider 299 | .idea/ 300 | *.sln.iml 301 | 302 | # CodeRush personal settings 303 | .cr/personal 304 | 305 | # Python Tools for Visual Studio (PTVS) 306 | __pycache__/ 307 | *.pyc 308 | 309 | # Cake - Uncomment if you are using it 310 | # tools/** 311 | # !tools/packages.config 312 | 313 | # Tabs Studio 314 | *.tss 315 | 316 | # Telerik's JustMock configuration file 317 | *.jmconfig 318 | 319 | # BizTalk build output 320 | *.btp.cs 321 | *.btm.cs 322 | *.odx.cs 323 | *.xsd.cs 324 | 325 | # OpenCover UI analysis results 326 | OpenCover/ 327 | 328 | # Azure Stream Analytics local run output 329 | ASALocalRun/ 330 | 331 | # MSBuild Binary and Structured Log 332 | *.binlog 333 | 334 | # NVidia Nsight GPU debugger configuration file 335 | *.nvuser 336 | 337 | # MFractors (Xamarin productivity tool) working folder 338 | .mfractor/ 339 | 340 | # Local History for Visual Studio 341 | .localhistory/ 342 | 343 | # BeatPulse healthcheck temp database 344 | healthchecksdb -------------------------------------------------------------------------------- /Content/Icons/icon_Test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UserCodrus/DynamicTerrain/6f7da80d7dd1f3bf5baed83e92ae3d46bb0cbe32/Content/Icons/icon_Test.png -------------------------------------------------------------------------------- /Content/Materials/M_BrushDecal.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UserCodrus/DynamicTerrain/6f7da80d7dd1f3bf5baed83e92ae3d46bb0cbe32/Content/Materials/M_BrushDecal.uasset -------------------------------------------------------------------------------- /DynamicTerrain.uplugin: -------------------------------------------------------------------------------- 1 | { 2 | "FileVersion": 3, 3 | "Version": 1, 4 | "VersionName": "1.0", 5 | "FriendlyName": "DynamicTerrain", 6 | "Description": "Adds a new terrain system that can be edited at runtime.", 7 | "Category": "Other", 8 | "CreatedBy": "Codrus", 9 | "CreatedByURL": "", 10 | "DocsURL": "", 11 | "MarketplaceURL": "", 12 | "SupportURL": "", 13 | "CanContainContent": true, 14 | "IsBetaVersion": false, 15 | "Installed": false, 16 | "Modules": [ 17 | { 18 | "Name": "DynamicTerrain", 19 | "Type": "Runtime", 20 | "LoadingPhase": "Default" 21 | }, 22 | { 23 | "Name": "DynamicTerrainEditor", 24 | "Type": "Editor" 25 | } 26 | ], 27 | "Plugins": [ 28 | { 29 | "Name": "ProceduralMeshComponent", 30 | "Enabled": true 31 | } 32 | ] 33 | } -------------------------------------------------------------------------------- /Resources/Icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UserCodrus/DynamicTerrain/6f7da80d7dd1f3bf5baed83e92ae3d46bb0cbe32/Resources/Icon128.png -------------------------------------------------------------------------------- /Source/DynamicTerrain/DynamicTerrain.Build.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UserCodrus/DynamicTerrain/6f7da80d7dd1f3bf5baed83e92ae3d46bb0cbe32/Source/DynamicTerrain/DynamicTerrain.Build.cs -------------------------------------------------------------------------------- /Source/DynamicTerrain/Private/DynamicTerrain.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UserCodrus/DynamicTerrain/6f7da80d7dd1f3bf5baed83e92ae3d46bb0cbe32/Source/DynamicTerrain/Private/DynamicTerrain.cpp -------------------------------------------------------------------------------- /Source/DynamicTerrain/Private/Terrain.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UserCodrus/DynamicTerrain/6f7da80d7dd1f3bf5baed83e92ae3d46bb0cbe32/Source/DynamicTerrain/Private/Terrain.cpp -------------------------------------------------------------------------------- /Source/DynamicTerrain/Private/TerrainAlgorithms.cpp: -------------------------------------------------------------------------------- 1 | #include "TerrainAlgorithms.h" 2 | #include "TerrainFoliage.h" 3 | 4 | #include 5 | #include 6 | 7 | constexpr float pi = 3.141592f; 8 | 9 | /// Utility Functions /// 10 | 11 | // Linear interpolation 12 | inline float lerp(float t, float a, float b) 13 | { 14 | return a + t * (b - a); 15 | } 16 | 17 | // Cosine interpolation 18 | inline float corp(float t, float a, float b) 19 | { 20 | float u = (1 - std::cos(t * pi)) / 2; 21 | return a * (1 - u) + b * u; 22 | } 23 | 24 | // Cubic interpolation 25 | inline float curp(float t, float a[4]) 26 | { 27 | return a[1] + 0.5f * t * (a[2] - a[0] + t * (2.0f * a[0] - 5.0f * a[1] + 4.0f * a[2] - a[3] + t * (3.0f * (a[1] - a[2]) + a[3] - a[0]))); 28 | } 29 | 30 | // Perlin smoothstep 31 | inline float fade(float t) 32 | { 33 | return t * t * t * (t * (t * 6 - 15) + 10); 34 | } 35 | 36 | /// Base Noise /// 37 | 38 | uint32 Noise::GetWidth() const 39 | { 40 | return Width; 41 | } 42 | 43 | uint32 Noise::GetHeight() const 44 | { 45 | return Height; 46 | } 47 | 48 | /// Gradient Noise /// 49 | 50 | GradientNoise::GradientNoise(uint32 NewWidth, uint32 NewHeight, uint32 Seed) 51 | { 52 | Width = NewWidth; 53 | Height = NewHeight; 54 | Gradient = new FVector2D[Width * Height]; 55 | 56 | // Generate gradient vectors 57 | std::default_random_engine rando(Seed); 58 | std::uniform_real_distribution dist(-pi, pi); 59 | for (unsigned y = 0; y < Height; ++y) 60 | { 61 | for (unsigned x = 0; x < Width; ++x) 62 | { 63 | // Create a unit vector from a random angle 64 | float angle = dist(rando); 65 | Gradient[y * Width + x].X = cos(angle); 66 | Gradient[y * Width + x].Y = sin(angle); 67 | } 68 | } 69 | } 70 | 71 | GradientNoise::GradientNoise(const GradientNoise& Copy) 72 | { 73 | Width = Copy.Width; 74 | Height = Copy.Height; 75 | Gradient = new FVector2D[Width * Height]; 76 | memcpy(Gradient, Copy.Gradient, Width * Height * sizeof(FVector2D)); 77 | } 78 | 79 | GradientNoise::~GradientNoise() 80 | { 81 | if (Gradient != nullptr) 82 | { 83 | delete[] Gradient; 84 | } 85 | } 86 | 87 | void GradientNoise::Scale(uint32 SampleWidth, uint32 SampleHeight) 88 | { 89 | ScaleX = (float)(Width - 1) / SampleWidth; 90 | ScaleY = (float)(Height - 1) / SampleHeight; 91 | } 92 | 93 | FVector2D GradientNoise::GetGradient(uint32 X, uint32 Y) const 94 | { 95 | return Gradient[Y * Width + X]; 96 | } 97 | 98 | float GradientNoise::Perlin(float X, float Y) const 99 | { 100 | // Scale noise values 101 | X *= ScaleX; 102 | Y *= ScaleY; 103 | 104 | // Get the coordinates of the grid cell containing x, y 105 | int32 x = (int32)X; 106 | int32 y = (int32)Y; 107 | 108 | // Subtract the cell coordinates from x and y to get their fractional portion 109 | X -= x; 110 | Y -= y; 111 | 112 | // Get the fade curves of the coordinates 113 | float u = fade(X); 114 | float v = fade(Y); 115 | 116 | // Get the gradients at the corner of the unit cell 117 | FVector2D g00 = Gradient[y * Width + x]; 118 | FVector2D g01 = Gradient[(y + 1) * Width + x]; 119 | FVector2D g10 = Gradient[y * Width + x + 1]; 120 | FVector2D g11 = Gradient[(y + 1) * Width + x + 1]; 121 | 122 | // Interpolate the dot products of each gradient and the cell coordinates 123 | return lerp(u, 124 | lerp(v, g00.X * X + g00.Y * Y, g01.X * X + g01.Y * (Y - 1)), 125 | lerp(v, g10.X * (X - 1) + g10.Y * Y, g11.X * (X - 1) + g11.Y * (Y - 1)) 126 | ); 127 | } 128 | 129 | /// Value Noise /// 130 | 131 | ValueNoise::ValueNoise(uint32 NewWidth, uint32 NewHeight, uint32 Seed) 132 | { 133 | Width = NewWidth; 134 | Height = NewHeight; 135 | Value = new float[Width * Height]; 136 | 137 | // Generate random values at each grid point 138 | std::default_random_engine rando(Seed); 139 | std::uniform_real_distribution dist(-1.0f, 1.0f); 140 | for (unsigned y = 0; y < Height; ++y) 141 | { 142 | for (unsigned x = 0; x < Width; ++x) 143 | { 144 | Value[y * Width + x] = dist(rando); 145 | } 146 | } 147 | } 148 | 149 | ValueNoise::ValueNoise(uint32 Size, uint32 Seed) 150 | { 151 | Width = (unsigned)pow(2, Size) + 1; 152 | Height = Width; 153 | Value = new float[Width * Height]; 154 | 155 | std::default_random_engine rando(Seed); 156 | std::uniform_real_distribution dist(-1.0f, 1.0f); 157 | 158 | // The range of the random values generated 159 | float range = 0.5; 160 | 161 | // Generate corner values 162 | Value[0] = dist(rando) * range; 163 | Value[Width - 1] = dist(rando) * range; 164 | Value[(Height - 1) * Width] = dist(rando) * range; 165 | Value[Height * Width - 1] = dist(rando) * range; 166 | 167 | // The size of the current fractal 168 | unsigned stride = Width - 1; 169 | 170 | unsigned limit_x = Width - 1; 171 | unsigned limit_y = Height - 1; 172 | 173 | // Diamond-square algorithm 174 | while (stride > 1) 175 | { 176 | range *= 0.5; 177 | unsigned half = stride / 2; 178 | 179 | // Diamond step 180 | for (unsigned y = 0; y < limit_y; y += stride) 181 | { 182 | for (unsigned x = 0; x < limit_x; x += stride) 183 | { 184 | // Get the values of the four grid points at the corner of the current grid point 185 | float v00 = Value[y * Width + x]; 186 | float v10 = Value[y * Width + (x + stride)]; 187 | float v01 = Value[(y + stride) * Width + x]; 188 | float v11 = Value[(y + stride) * Width + (x + stride)]; 189 | 190 | // Set the value of the current point to the average of the corners + a random value 191 | Value[(y + half) * Width + (x + half)] = (v00 + v10 + v01 + v11) / 4.0f + dist(rando) * range; 192 | } 193 | } 194 | 195 | // Square step - top / bottom edges 196 | for (unsigned x = half; x < limit_x; x += stride) 197 | { 198 | // Get the values for the points adjacent to the top edge 199 | float v_mid = Value[half * Width + x]; 200 | float v_left = Value[x - half]; 201 | float v_right = Value[x + half]; 202 | 203 | // Set the value for the top edge at the current x coordinate to the average of the adjacent points + a random value 204 | Value[x] = (v_mid + v_left + v_right) / 3.0f + dist(rando) * range; 205 | 206 | // Get the values for the points adjacent to the bottom edge 207 | v_mid = Value[(limit_y - half) * Width + x]; 208 | v_left = Value[limit_y * Width + (x - half)]; 209 | v_right = Value[limit_y * Width + (x + half)]; 210 | 211 | // Set the value for the bottom edge at the current x coordinate to the average of the adjacent points + a random value 212 | Value[limit_y * Width + x] = (v_mid + v_left + v_right) / 3.0f + dist(rando) * range; 213 | } 214 | 215 | // Square step - left / right edges 216 | for (unsigned y = half; y < limit_y; y += stride) 217 | { 218 | // Get the values for the points adjacent to the left edge 219 | float v_mid = Value[y * Width + half]; 220 | float v_top = Value[(y - half) * Width]; 221 | float v_bottom = Value[(y + half) * Width]; 222 | 223 | // Set the value for the left edge at the current y coordinate to the average of the adjacent points + a random value 224 | Value[y * Width] = (v_mid + v_top + v_bottom) / 3.0f + dist(rando) * range; 225 | 226 | // Get the values for the points adjacent to the right edge 227 | v_mid = Value[y * Width + (limit_x - half)]; 228 | v_top = Value[(y - half) * Width + limit_x]; 229 | v_bottom = Value[(y + half) * Width + limit_x]; 230 | 231 | // Set the value for the right edge at the current y coordinate to the average of the adjacent points + a random value 232 | Value[y * Width + limit_x] = (v_mid + v_top + v_bottom) / 3.0f + dist(rando) * range; 233 | } 234 | 235 | // Square step - center points 236 | bool offset = true; 237 | for (unsigned y = half; y < limit_y; y += half) 238 | { 239 | for (unsigned x = offset ? stride : half; x < limit_x; x += stride) 240 | { 241 | // Get the values of the four grid points adjacent the current grid point 242 | float v_top = Value[(y - half) * Width + x]; 243 | float v_bottom = Value[(y + half) * Width + x]; 244 | float v_left = Value[y * Width + (x - half)]; 245 | float v_right = Value[y * Width + (x + half)]; 246 | 247 | // Set the value of the current point to the average of the adjacent points + a random value 248 | Value[y * Width + x] = (v_top + v_bottom + v_left + v_right) / 4.0f + dist(rando) * range; 249 | } 250 | 251 | offset = !offset; 252 | } 253 | 254 | stride /= 2; 255 | } 256 | } 257 | 258 | ValueNoise::ValueNoise(const ValueNoise& Copy) 259 | { 260 | Width = Copy.Width; 261 | Height = Copy.Height; 262 | Value = new float[Width * Height]; 263 | memcpy(Value, Copy.Value, Width * Height * sizeof(float)); 264 | } 265 | 266 | ValueNoise::~ValueNoise() 267 | { 268 | if (Value != nullptr) 269 | { 270 | delete[] Value; 271 | } 272 | } 273 | 274 | void ValueNoise::Scale(uint32 SampleWidth, uint32 SampleHeight) 275 | { 276 | ScaleX = (float)(Width - 1) / SampleWidth; 277 | ScaleY = (float)(Height - 1) / SampleHeight; 278 | } 279 | 280 | float ValueNoise::GetValue(uint32 X, uint32 Y) const 281 | { 282 | return Value[Y * Width + X]; 283 | } 284 | 285 | float ValueNoise::Linear(float X, float Y) const 286 | { 287 | // Scale noise values 288 | X *= ScaleX; 289 | Y *= ScaleY; 290 | 291 | // Get the coordinates of the grid cell containing x, y 292 | int32 x = (int32)X; 293 | int32 y = (int32)Y; 294 | 295 | // Subtract the cell coordinates from x and y to get their fractional portion 296 | X -= x; 297 | Y -= y; 298 | 299 | // Interpolate the noise 300 | return lerp(X, 301 | lerp(Y, Value[y * Width + x], Value[(y + 1) * Width + x]), 302 | lerp(Y, Value[y * Width + (x + 1)], Value[(y + 1) * Width + (x + 1)]) 303 | ); 304 | } 305 | 306 | float ValueNoise::Cosine(float X, float Y) const 307 | { 308 | // Scale noise values 309 | X *= ScaleX; 310 | Y *= ScaleY; 311 | 312 | // Get the coordinates of the grid cell containing x, y 313 | int32 x = (int32)X; 314 | int32 y = (int32)Y; 315 | 316 | // Subtract the cell coordinates from x and y to get their fractional portion 317 | X -= x; 318 | Y -= y; 319 | 320 | // Interpolate the noise 321 | return corp(X, 322 | corp(Y, Value[y * Width + x], Value[(y + 1) * Width + x]), 323 | corp(Y, Value[y * Width + (x + 1)], Value[(y + 1) * Width + (x + 1)]) 324 | ); 325 | } 326 | 327 | float ValueNoise::Cubic(float X, float Y) const 328 | { 329 | // Scale noise values 330 | X *= ScaleX; 331 | Y *= ScaleY; 332 | 333 | // Get the coordinates of the grid cell containing x, y 334 | int32 x = (int32)X; 335 | int32 y = (int32)Y; 336 | 337 | // Subtract the cell coordinates from x and y to get their fractional portion 338 | X -= x; 339 | Y -= y; 340 | 341 | float a[4], p[4]; 342 | 343 | // Second point 344 | p[1] = Value[y * Width + x]; 345 | p[2] = Value[y * Width + (x + 1)]; 346 | if (x > 0) 347 | { 348 | p[0] = Value[y * Width + (x - 1)]; 349 | } 350 | else 351 | { 352 | // Extrapolate the middle points if we are near an edge 353 | p[0] = p[1] - (p[2] - p[1]); 354 | } 355 | if (x < (int)(Width - 2)) 356 | { 357 | p[3] = Value[y * Width + (x + 2)]; 358 | } 359 | else 360 | { 361 | // Extrapolate the middle points if we are near an edge 362 | p[3] = p[2] + (p[2] - p[1]); 363 | } 364 | a[1] = curp(X, p); 365 | 366 | // Third point 367 | p[1] = Value[(y + 1) * Width + x]; 368 | p[2] = Value[(y + 1) * Width + (x + 1)]; 369 | if (x > 0) 370 | { 371 | p[0] = Value[(y + 1) * Width + (x - 1)]; 372 | } 373 | else 374 | { 375 | // Extrapolate the middle points if we are near an edge 376 | p[0] = p[1] - (p[2] - p[1]); 377 | } 378 | if (x < (int)(Width - 2)) 379 | { 380 | p[3] = Value[(y + 1) * Width + (x + 2)]; 381 | } 382 | else 383 | { 384 | // Extrapolate the middle points if we are near an edge 385 | p[3] = p[2] + (p[2] - p[1]); 386 | } 387 | a[2] = curp(X, p); 388 | 389 | // First point 390 | if (y > 0) 391 | { 392 | p[1] = Value[(y - 1) * Width + x]; 393 | p[2] = Value[(y - 1) * Width + (x + 1)]; 394 | if (x > 0) 395 | { 396 | p[0] = Value[(y - 1) * Width + (x - 1)]; 397 | } 398 | else 399 | { 400 | // Extrapolate the middle points if we are near an edge 401 | p[0] = p[1] - (p[2] - p[1]); 402 | } 403 | if (x < (int)(Width - 2)) 404 | { 405 | p[3] = Value[(y - 1) * Width + (x + 2)]; 406 | } 407 | else 408 | { 409 | // Extrapolate the middle points if we are near an edge 410 | p[3] = p[2] + (p[2] - p[1]); 411 | } 412 | a[0] = curp(X, p); 413 | } 414 | else 415 | { 416 | // Extrapolate the middle points if we are near an edge 417 | a[0] = a[1] - (a[2] - a[1]); 418 | } 419 | 420 | // Fourth point 421 | if (y < (int)(Height - 2)) 422 | { 423 | p[1] = Value[(y + 2) * Width + x]; 424 | p[2] = Value[(y + 2) * Width + (x + 1)]; 425 | if (x > 0) 426 | { 427 | p[0] = Value[(y + 2) * Width + (x - 1)]; 428 | } 429 | else 430 | { 431 | // Extrapolate the middle points if we are near an edge 432 | p[0] = p[1] - (p[2] - p[1]); 433 | } 434 | if (x < (int)(Width - 2)) 435 | { 436 | p[3] = Value[(y + 2) * Width + (x + 2)]; 437 | } 438 | else 439 | { 440 | // Extrapolate the middle points if we are near an edge 441 | p[3] = p[2] + (p[2] - p[1]); 442 | } 443 | a[3] = curp(X, p); 444 | } 445 | else 446 | { 447 | // Extrapolate the middle points if we are near an edge 448 | a[3] = a[2] + (a[2] - a[1]); 449 | } 450 | 451 | return std::min(1.0f, std::max(curp(Y, a), -1.0f)); 452 | } 453 | 454 | /// Random Point Noise /// 455 | 456 | PointNoise::PointNoise(uint32 XWidth, uint32 YWidth, uint32 NumPoints, uint32 Seed) 457 | { 458 | Width = XWidth; 459 | Height = YWidth; 460 | 461 | // Generate points using random x and y values 462 | std::default_random_engine rando(Seed); 463 | std::uniform_real_distribution x_dist(0.0f, (float)Width); 464 | std::uniform_real_distribution y_dist(0.0f, (float)Height); 465 | 466 | Points.SetNumUninitialized(NumPoints); 467 | for (uint32 i = 0; i < NumPoints; ++i) 468 | { 469 | // Create a point 470 | Points[i] = FVector2D(x_dist(rando), y_dist(rando)); 471 | } 472 | } 473 | 474 | PointNoise::PointNoise(uint32 Radius, uint32 NumPoints, uint32 Seed) 475 | { 476 | Width = Radius * 2; 477 | Height = Width; 478 | 479 | // Generate random x and y values within a circle 480 | std::default_random_engine rando(Seed); 481 | std::uniform_real_distribution angle(0.0f, 360.0f); 482 | std::uniform_real_distribution dist; 483 | 484 | Points.SetNumUninitialized(NumPoints); 485 | for (uint32 i = 0; i < NumPoints; ++i) 486 | { 487 | // Get a random angle and distance 488 | float a = angle(rando); 489 | float d = Radius * FMath::Sqrt(dist(rando)); 490 | 491 | // Create a point 492 | Points[i] = FVector2D(d * FMath::Cos(a), d * FMath::Sin(a)); 493 | } 494 | } 495 | 496 | void PointNoise::Scale(uint32 SampleWidth, uint32 SampleHeight) 497 | { 498 | ScaleX = (float)Width / SampleWidth; 499 | ScaleY = (float)Height / SampleHeight; 500 | } 501 | 502 | FVector2D PointNoise::GetNearest(FVector2D Location) const 503 | { 504 | // The closest point found so far 505 | FVector2D nearest(0.0f, 0.0f); 506 | // The distance to the closest point 507 | float nearest_distance = 8.0f; 508 | 509 | // Check each point to see which one is closest 510 | for (int32 i = 0; i < Points.Num(); ++i) 511 | { 512 | float distance = FVector2D::DistSquared(Location, Points[i]); 513 | if (distance < nearest_distance) 514 | { 515 | nearest_distance = distance; 516 | nearest = Points[i]; 517 | } 518 | } 519 | 520 | return nearest; 521 | } 522 | 523 | float PointNoise::GetNearestDistance(FVector2D Location) const 524 | { 525 | // The distance to the closest point 526 | float nearest_distance = 8.0f; 527 | 528 | // Check each point to get the distance to the nearest one 529 | for (int32 i = 0; i < Points.Num(); ++i) 530 | { 531 | float distance = FVector2D::DistSquared(Location, Points[i]); 532 | if (distance < nearest_distance) 533 | { 534 | nearest_distance = distance; 535 | } 536 | } 537 | 538 | return FMath::Sqrt(nearest_distance); 539 | } 540 | 541 | float PointNoise::Dot(float X, float Y) const 542 | { 543 | // Scale noise values 544 | X *= ScaleX; 545 | Y *= ScaleY; 546 | 547 | // Get the nearest neighbor to the current point 548 | FVector2D loc(X, Y); 549 | float nearest = GetNearestDistance(loc); 550 | 551 | // Calculate the noise value based on distance 552 | return std::max(0.0f, 1.0f - nearest * 4); 553 | } 554 | 555 | float PointNoise::Worley(float X, float Y) const 556 | { 557 | // Scale noise values 558 | X *= ScaleX; 559 | Y *= ScaleY; 560 | 561 | // Get the nearest neighbor to the current point 562 | FVector2D loc(X, Y); 563 | float nearest = GetNearestDistance(loc); 564 | 565 | // Calculate the noise value based on distance 566 | return std::min(1.0f, nearest); 567 | } 568 | 569 | const TArray& PointNoise::GetPoints() 570 | { 571 | return Points; 572 | } 573 | 574 | /// Grid Aligned Point Noise /// 575 | 576 | UniformPointNoise::UniformPointNoise(uint32 NewWidth, uint32 NewHeight, uint32 Seed) 577 | { 578 | Width = NewWidth; 579 | Height = NewHeight; 580 | 581 | // Generate points at random locations within a unit grid 582 | std::default_random_engine rando(Seed); 583 | std::uniform_real_distribution dist(0.0f, 1.0f); 584 | 585 | Points.SetNumUninitialized(Width * Height); 586 | for (uint32 y = 0; y < Height; ++y) 587 | { 588 | for (uint32 x = 0; x < Width; ++x) 589 | { 590 | uint32 loc = x + y * Width; 591 | Points[loc].X = x + dist(rando); 592 | Points[loc].Y = y + dist(rando); 593 | } 594 | } 595 | } 596 | 597 | inline FVector2D UniformPointNoise::GetNearest(FVector2D Location) const 598 | { 599 | // Get the grid location to the top left of the current point 600 | int32 minx = (int32)Location.X - 1; 601 | int32 miny = (int32)Location.Y - 1; 602 | 603 | // The closest point found so far 604 | FVector2D nearest(0.0f, 0.0f); 605 | // The distance to the closest point 606 | float nearest_distance = 8.0f; 607 | 608 | // Check each grid cell surrounding the cell containing to current point 609 | for (uint32 y = 0; y < 3; ++y) 610 | { 611 | int32 offset_y = (miny + y) * Width; 612 | for (uint32 x = 0; x < 3; ++x) 613 | { 614 | // Get the cell's location in the point array 615 | int32 cell = (minx + x) + offset_y; 616 | if (cell > -1 && cell < Points.Num()) 617 | { 618 | // Check the distance to the point 619 | float dist = FVector2D::DistSquared(Location, Points[cell]); 620 | if (dist < nearest_distance) 621 | { 622 | nearest_distance = dist; 623 | nearest = Points[cell]; 624 | } 625 | } 626 | } 627 | } 628 | 629 | return nearest; 630 | } 631 | 632 | float UniformPointNoise::GetNearestDistance(FVector2D Location) const 633 | { 634 | // Get the grid location to the top left of the current point 635 | int32 minx = (int32)Location.X - 1; 636 | int32 miny = (int32)Location.Y - 1; 637 | 638 | // The distance to the closest point 639 | float nearest_distance = 8.0f; 640 | 641 | // Check each grid cell surrounding the cell containing to current point 642 | for (uint32 y = 0; y < 3; ++y) 643 | { 644 | int32 offset_y = (miny + y) * Width; 645 | for (uint32 x = 0; x < 3; ++x) 646 | { 647 | // Get the cell's location in the point array 648 | int32 cell = (minx + x) + offset_y; 649 | if (cell > -1 && cell < Points.Num()) 650 | { 651 | // Check the distance to the point 652 | float distance = FVector2D::DistSquared(Location, Points[cell]); 653 | if (distance < nearest_distance) 654 | { 655 | nearest_distance = distance; 656 | } 657 | } 658 | } 659 | } 660 | 661 | return FMath::Sqrt(nearest_distance); 662 | } 663 | 664 | /// Poisson Point Noise /// 665 | 666 | // The divisor of the sampling radius used to calculate the size of sorting grids 667 | constexpr float grid_range = 1.42; 668 | // The number of samples to attempt for each algorithm 669 | constexpr uint32 num_samples = 20; 670 | 671 | PoissonPointNoise::PoissonPointNoise(uint32 SpaceWidth, uint32 SpaceHeight, float SampleRadius, uint32 NumPoints, uint32 Seed) 672 | { 673 | Width = SpaceWidth; 674 | Height = SpaceHeight; 675 | 676 | // Resize the sorting grid 677 | InitializeSortingGrid(SampleRadius); 678 | 679 | // Generate points using random x and y values 680 | std::default_random_engine rando(Seed); 681 | std::uniform_real_distribution x_dist(0.0f, (float)Width); 682 | std::uniform_real_distribution y_dist(0.0f, (float)Height); 683 | 684 | // Generate points 685 | Points.Reserve(NumPoints); 686 | for (uint32 i = 0; i < NumPoints; ++i) 687 | { 688 | // Try multiple samples for each point 689 | for (uint32 j = 0; j < num_samples; ++j) 690 | { 691 | // Create a new random point and test it 692 | FVector2D point(x_dist(rando), y_dist(rando)); 693 | if (CheckPoint(point, SampleRadius)) 694 | { 695 | // Add the point 696 | Points.Add(point); 697 | SortPoint(Points.Num() - 1); 698 | 699 | break; 700 | } 701 | } 702 | } 703 | } 704 | 705 | PoissonPointNoise::PoissonPointNoise(uint32 SpaceRadius, float SampleRadius, uint32 NumPoints, uint32 Seed) 706 | { 707 | Width = SpaceRadius * 2; 708 | Height = Width; 709 | 710 | // Resize the sorting grid 711 | InitializeSortingGrid(SampleRadius); 712 | 713 | // Generate points using random x and y values 714 | std::default_random_engine rando(Seed); 715 | std::uniform_real_distribution angle(0.0f, 360.0f); 716 | std::uniform_real_distribution dist; 717 | 718 | // Generate points 719 | Points.Reserve(NumPoints); 720 | for (uint32 i = 0; i < NumPoints; ++i) 721 | { 722 | // Try multiple samples for each point 723 | for (int32 j = 0; j < 20; ++j) 724 | { 725 | // Create a new random point and test it 726 | float a = angle(rando); 727 | float d = SpaceRadius * FMath::Sqrt(dist(rando)); 728 | FVector2D point(d * FMath::Cos(a) + SpaceRadius, d * FMath::Sin(a) + SpaceRadius); 729 | if (CheckPoint(point, SampleRadius)) 730 | { 731 | // Add the point 732 | Points.Add(point); 733 | SortPoint(Points.Num() - 1); 734 | 735 | break; 736 | } 737 | } 738 | } 739 | } 740 | 741 | PoissonPointNoise::PoissonPointNoise(uint32 SpaceWidth, uint32 SpaceHeight, float SampleRadius, uint32 Seed) 742 | { 743 | Width = SpaceWidth; 744 | Height = SpaceHeight; 745 | 746 | // Resize the sorting grid 747 | InitializeSortingGrid(SampleRadius); 748 | 749 | // Generate points using random x and y values 750 | std::default_random_engine rando(Seed); 751 | std::uniform_real_distribution angle(0.0f, 360.0f); 752 | std::uniform_real_distribution dist; 753 | std::uniform_real_distribution x_dist(0.0f, (float)Width); 754 | std::uniform_real_distribution y_dist(0.0f, (float)Height); 755 | 756 | // Create the initial point 757 | FVector2D start(x_dist(rando), y_dist(rando)); 758 | 759 | uint32 num_circles = SpaceWidth * SpaceHeight / (PI * SampleRadius * SampleRadius); 760 | Points.Reserve(num_circles); 761 | Points.Add(start); 762 | 763 | SortingGrid[(uint32)(start.X / GridBound) + (uint32)(start.Y / GridBound) * GridWidth] = Points.Num() - 1; 764 | 765 | // Generate points until we have run out of candidates 766 | for (int32 i = 0; i < Points.Num(); ++i) 767 | { 768 | for (uint32 j = 0; j < num_samples; ++j) 769 | { 770 | // Generate a point near the current candidate 771 | float a = angle(rando); 772 | float d = SampleRadius * dist(rando) + SampleRadius; 773 | FVector2D point(d * FMath::Cos(a) + Points[i].X, d * FMath::Sin(a) + Points[i].Y); 774 | 775 | // Check the point and add it if it is valid 776 | if (point.X > 0 && point.Y > 0 && point.X < Width && point.Y < Height) 777 | { 778 | if (CheckPoint(point, SampleRadius)) 779 | { 780 | Points.Add(point); 781 | SortPoint(Points.Num() - 1); 782 | } 783 | } 784 | } 785 | } 786 | } 787 | 788 | PoissonPointNoise::PoissonPointNoise(uint32 SpaceRadius, float SampleRadius, uint32 Seed) 789 | { 790 | Width = SpaceRadius * 2; 791 | Height = Width; 792 | 793 | // Resize the sorting grid 794 | InitializeSortingGrid(SampleRadius); 795 | 796 | // Generate points using random x and y values 797 | std::default_random_engine rando(Seed); 798 | std::uniform_real_distribution angle(0.0f, 360.0f); 799 | std::uniform_real_distribution dist; 800 | 801 | // Create the initial point 802 | float a = angle(rando); 803 | float d = SpaceRadius * FMath::Sqrt(dist(rando)); 804 | FVector2D start(d * FMath::Cos(a) + SpaceRadius, d * FMath::Sin(a) + SpaceRadius); 805 | 806 | uint32 num_circles = PI * SpaceRadius * SpaceRadius / (PI * SampleRadius * SampleRadius); 807 | Points.Reserve(num_circles); 808 | Points.Add(start); 809 | 810 | SortingGrid[(uint32)(start.X / GridBound) + (uint32)(start.Y / GridBound) * GridWidth] = Points.Num() - 1; 811 | 812 | FVector2D center((float)Width / 2, (float)Height / 2); 813 | 814 | // Generate points until we have run out of candidates 815 | for (int32 i = 0; i < Points.Num(); ++i) 816 | { 817 | for (uint32 j = 0; j < num_samples; ++j) 818 | { 819 | // Generate a point near the current candidate 820 | a = angle(rando); 821 | d = SampleRadius * dist(rando) + SampleRadius; 822 | FVector2D point(d * FMath::Cos(a) + Points[i].X, d * FMath::Sin(a) + Points[i].Y); 823 | 824 | // Check the point and add it if it is valid 825 | if (FVector2D::DistSquared(center, point) < SpaceRadius * SpaceRadius) 826 | { 827 | if (CheckPoint(point, SampleRadius)) 828 | { 829 | Points.Add(point); 830 | SortPoint(Points.Num() - 1); 831 | } 832 | } 833 | } 834 | } 835 | } 836 | 837 | bool PoissonPointNoise::CheckPoint(FVector2D Location, float SearchRadius) const 838 | { 839 | // Calculate the number of grid cells to search 840 | uint32 GridRadius = (uint32)(SearchRadius / GridBound) + 1; 841 | 842 | int32 minx = Location.X / GridBound - GridRadius; 843 | int32 miny = Location.Y / GridBound - GridRadius; 844 | 845 | // Check every cell around the point 846 | for (uint32 y = 0; y < GridRadius * 2 + 1; ++y) 847 | { 848 | for (uint32 x = 0; x < GridRadius * 2 + 1; ++x) 849 | { 850 | // Get the cell's location in the point array 851 | int32 cell = minx + x + (miny + y) * GridWidth; 852 | if (cell > -1 && cell < SortingGrid.Num()) 853 | { 854 | if (SortingGrid[cell] > -1) 855 | { 856 | // Check the distance to the point 857 | float distance = FVector2D::DistSquared(Location, Points[SortingGrid[cell]]); 858 | if (distance < SearchRadius) 859 | { 860 | return false; 861 | } 862 | } 863 | } 864 | } 865 | } 866 | 867 | return true; 868 | } 869 | 870 | FVector2D PoissonPointNoise::GetNearestConstrained(FVector2D Location, float SearchRadius) const 871 | { 872 | // Calculate the number of grid cells to search and the maximum possible distance to return 873 | uint32 GridRadius = (uint32)(SearchRadius / GridBound) + 1; 874 | float nearest_distance = SearchRadius * GridRadius + 1; 875 | nearest_distance *= nearest_distance; 876 | FVector2D nearest_point(-1.0f, -1.0f); 877 | 878 | int32 minx = Location.X / GridBound - GridRadius; 879 | int32 miny = Location.Y / GridBound - GridRadius; 880 | 881 | // Check every cell around the point 882 | for (uint32 y = 0; y < GridRadius * 2 + 1; ++y) 883 | { 884 | for (uint32 x = 0; x < GridRadius * 2 + 1; ++x) 885 | { 886 | // Get the cell's location in the point array 887 | int32 cell = minx + x + (miny + y) * GridWidth; 888 | if (cell > -1 && cell < SortingGrid.Num()) 889 | { 890 | if (SortingGrid[cell] > -1) 891 | { 892 | // Check the distance to the point 893 | float distance = FVector2D::DistSquared(Location, Points[SortingGrid[cell]]); 894 | if (distance < nearest_distance) 895 | { 896 | nearest_distance = distance; 897 | nearest_point = Points[SortingGrid[cell]]; 898 | } 899 | } 900 | } 901 | } 902 | } 903 | 904 | return nearest_point; 905 | } 906 | 907 | float PoissonPointNoise::GetNearestDistanceConstrained(FVector2D Location, float SearchRadius) const 908 | { 909 | // Calculate the number of grid cells to search 910 | uint32 GridRadius = (uint32)(SearchRadius / GridBound) + 1; 911 | float nearest_distance = SearchRadius * SearchRadius; 912 | 913 | int32 minx = Location.X / GridBound - GridRadius; 914 | int32 miny = Location.Y / GridBound - GridRadius; 915 | 916 | // Check every cell around the point 917 | for (uint32 y = 0; y < GridRadius * 2 + 1; ++y) 918 | { 919 | for (uint32 x = 0; x < GridRadius * 2 + 1; ++x) 920 | { 921 | // Get the cell's location in the point array 922 | int32 cell = minx + x + (miny + y) * GridWidth; 923 | if (cell > -1 && cell < SortingGrid.Num()) 924 | { 925 | if (SortingGrid[cell] > -1) 926 | { 927 | // Check the distance to the point 928 | float distance = FVector2D::DistSquared(Location, Points[SortingGrid[cell]]); 929 | if (distance < nearest_distance) 930 | { 931 | nearest_distance = distance; 932 | } 933 | } 934 | } 935 | } 936 | } 937 | 938 | return FMath::Sqrt(nearest_distance); 939 | } 940 | 941 | void PoissonPointNoise::InitializeSortingGrid(float MinRadius) 942 | { 943 | GridBound = MinRadius / grid_range; 944 | GridWidth = (Width / GridBound) + 1; 945 | GridHeight = (Height / GridBound) + 1; 946 | 947 | SortingGrid.SetNumUninitialized(GridWidth * GridHeight); 948 | for (int32 i = 0; i < SortingGrid.Num(); ++i) 949 | { 950 | SortingGrid[i] = -1; 951 | } 952 | } 953 | 954 | void PoissonPointNoise::SortPoint(int32 PointIndex) 955 | { 956 | uint32 x = Points[PointIndex].X / GridBound; 957 | uint32 y = Points[PointIndex].Y / GridBound; 958 | SortingGrid[x + y * GridWidth] = PointIndex; 959 | } 960 | 961 | FoliagePoissonNoise::FoliagePoissonNoise(TArray FoliageSet, uint32 SpaceWidth, uint32 SpaceHeight, uint32 NumPoints, uint32 Seed) 962 | { 963 | Foliage = FoliageSet; 964 | 965 | Width = SpaceWidth; 966 | Height = SpaceHeight; 967 | 968 | // Find the smallest and largest radii in the foliage set 969 | float smallest = 1000000.0f; 970 | for (int32 i = 0; i < Foliage.Num(); ++i) 971 | { 972 | if (Foliage[i].Asset->SafeDistance < smallest) 973 | { 974 | smallest = Foliage[i].Asset->SafeDistance; 975 | } 976 | if (Foliage[i].Asset->SafeDistance > MaxRadius) 977 | { 978 | MaxRadius = Foliage[i].Asset->SafeDistance; 979 | } 980 | if (Foliage[i].Asset->ShadeDistance > MaxShade) 981 | { 982 | MaxShade = Foliage[i].Asset->SafeDistance; 983 | } 984 | } 985 | 986 | // Resize the sorting grid 987 | InitializeSortingGrid(smallest); 988 | 989 | // Set up rng 990 | std::default_random_engine rando(Seed); 991 | std::uniform_real_distribution x_dist(0.0f, (float)Width); 992 | std::uniform_real_distribution y_dist(0.0f, (float)Height); 993 | std::uniform_int_distribution random_seed(0, std::numeric_limits::max()); 994 | 995 | // Create an initial point 996 | FVector2D start(x_dist(rando), y_dist(rando)); 997 | 998 | Points.Reserve(NumPoints); 999 | FoliageInstances.Reserve(NumPoints); 1000 | for (uint32 i = 0; i < NumPoints; ++i) 1001 | { 1002 | // Try multiple samples for each point 1003 | for (uint32 j = 0; j < num_samples; ++j) 1004 | { 1005 | // Choose a random foliage 1006 | UTerrainFoliage* foliage = GetRandomFoliage(random_seed(rando)); 1007 | 1008 | // Create a new random point and test it 1009 | FVector2D point(x_dist(rando), y_dist(rando)); 1010 | if (CheckFoliagePoint(point, foliage)) 1011 | { 1012 | FoliageInstances.Add(foliage); 1013 | Points.Add(point); 1014 | SortPoint(Points.Num() - 1); 1015 | 1016 | break; 1017 | } 1018 | } 1019 | } 1020 | } 1021 | 1022 | bool FoliagePoissonNoise::CheckFoliagePoint(FVector2D Location, UTerrainFoliage* FoliageSample) const 1023 | { 1024 | /// TODO: Make sample object respect the safe space of other foliage objects 1025 | float radius; 1026 | if (FoliageSample->GrowInShade) 1027 | { 1028 | radius = MaxRadius; 1029 | } 1030 | else 1031 | { 1032 | radius = MaxShade; 1033 | } 1034 | 1035 | // Calculate the number of grid cells to search 1036 | uint32 GridRadius = (uint32)(radius / GridBound) + 1; 1037 | 1038 | int32 minx = Location.X / GridBound - GridRadius; 1039 | int32 miny = Location.Y / GridBound - GridRadius; 1040 | 1041 | // Check every cell around the point 1042 | for (uint32 y = 0; y < GridRadius * 2 + 1; ++y) 1043 | { 1044 | for (uint32 x = 0; x < GridRadius * 2 + 1; ++x) 1045 | { 1046 | // Get the cell's location in the point array 1047 | int32 cell = minx + x + (miny + y) * GridWidth; 1048 | if (cell > -1 && cell < SortingGrid.Num()) 1049 | { 1050 | if (SortingGrid[cell] > -1) 1051 | { 1052 | // Check the distance to the point 1053 | float distance = FVector2D::DistSquared(Location, Points[SortingGrid[cell]]); 1054 | if (distance < radius) 1055 | { 1056 | return false; 1057 | } 1058 | } 1059 | } 1060 | } 1061 | } 1062 | 1063 | return true; 1064 | } 1065 | 1066 | UTerrainFoliage* FoliagePoissonNoise::GetRandomFoliage(uint32 Seed) const 1067 | { 1068 | // Calculate the total weight of all the meshes 1069 | int32 total_weight = 0; 1070 | for (int32 i = 0; i < Foliage.Num(); ++i) 1071 | { 1072 | total_weight += Foliage[i].Weight; 1073 | } 1074 | 1075 | if (total_weight > 0) 1076 | { 1077 | // Get a random index value 1078 | std::default_random_engine rando(Seed); 1079 | std::uniform_int_distribution dist(1, total_weight); 1080 | int32 n = dist(rando); 1081 | 1082 | // Find which component corresponds to the random index 1083 | for (int32 i = 0; i < Foliage.Num(); ++i) 1084 | { 1085 | n -= Foliage[i].Weight; 1086 | if (n <= 0) 1087 | { 1088 | return Foliage[i].Asset; 1089 | } 1090 | } 1091 | } 1092 | 1093 | return Foliage.Last().Asset; 1094 | } -------------------------------------------------------------------------------- /Source/DynamicTerrain/Private/TerrainBrushDecal.cpp: -------------------------------------------------------------------------------- 1 | #include "TerrainBrushDecal.h" 2 | 3 | #include "Materials/Material.h" 4 | #include "Materials/MaterialInstanceDynamic.h" 5 | #include "UObject/ConstructorHelpers.h" 6 | 7 | UBrushDecal::UBrushDecal() 8 | { 9 | SetAbsolute(true, true, true); 10 | SetRelativeScale3D(FVector(1.0f, 1.0f, 1.0f)); 11 | 12 | // Get the brush material 13 | static ConstructorHelpers::FObjectFinder Material(TEXT("Material'/DynamicTerrain/Materials/M_BrushDecal.M_BrushDecal'")); 14 | if (Material.Succeeded()) 15 | { 16 | BrushMaterial = Material.Object; 17 | } 18 | } 19 | 20 | void UBrushDecal::BeginPlay() 21 | { 22 | CreateMaterialInstance(); 23 | } 24 | 25 | void UBrushDecal::Resize(FTerrainTool* Tool, ATerrain* Terrain) 26 | { 27 | // Scale the brush size based on the terrain's scale 28 | float Radius = Terrain->GetActorScale3D().X * Tool->Size; 29 | float Falloff = Terrain->GetActorScale3D().X * Tool->Falloff; 30 | 31 | float width = Radius + Falloff + 200.0f; 32 | DecalSize = FVector(width, width, Terrain->GetActorScale3D().Z * 200.0f); 33 | 34 | BrushInstance->SetScalarParameterValue(TEXT("Radius"), Radius); 35 | BrushInstance->SetScalarParameterValue(TEXT("Falloff"), Falloff); 36 | } 37 | 38 | void UBrushDecal::ChangeColor(FColor Color) 39 | { 40 | BrushInstance->SetVectorParameterValue(TEXT("Color"), Color); 41 | } 42 | 43 | void UBrushDecal::CreateMaterialInstance() 44 | { 45 | // Assign the default material if one isn't assigned 46 | if (DecalMaterial == nullptr) 47 | { 48 | SetDecalMaterial(BrushMaterial); 49 | } 50 | BrushInstance = CreateDynamicMaterialInstance(); 51 | } -------------------------------------------------------------------------------- /Source/DynamicTerrain/Private/TerrainComponent.cpp: -------------------------------------------------------------------------------- 1 | #include "TerrainComponent.h" 2 | 3 | #include "Terrain.h" 4 | #include "TerrainRender.h" 5 | #include "TerrainStat.h" 6 | 7 | #include "Engine.h" 8 | #include "PrimitiveSceneProxy.h" 9 | #include "DynamicMeshBuilder.h" 10 | #include "Materials/Material.h" 11 | #include "Engine/CollisionProfile.h" 12 | 13 | DECLARE_CYCLE_STAT(TEXT("Dynamic Terrain - Rebuild Collision"), STAT_DynamicTerrain_RebuildCollision, STATGROUP_DynamicTerrain) 14 | 15 | /// Mesh Component Interface /// 16 | 17 | UTerrainComponent::UTerrainComponent(const FObjectInitializer& ObjectInitializer) 18 | { 19 | Size = 0; 20 | Tiling = 1.0f; 21 | LODs = 1; 22 | LODScale = 0.5; 23 | 24 | // Disable ticking for the component to save some CPU cycles 25 | PrimaryComponentTick.bCanEverTick = false; 26 | 27 | SetCollisionProfileName(UCollisionProfile::BlockAllDynamic_ProfileName); 28 | } 29 | 30 | FPrimitiveSceneProxy* UTerrainComponent::CreateSceneProxy() 31 | { 32 | FPrimitiveSceneProxy* proxy = nullptr; 33 | VerifyMapProxy(); 34 | 35 | if (Vertices.Num() > 0 && IndexBuffer.Num() > 0 && MapProxy.IsValid()) 36 | { 37 | proxy = new FTerrainComponentSceneProxy(this); 38 | } 39 | 40 | return proxy; 41 | } 42 | 43 | UBodySetup* UTerrainComponent::GetBodySetup() 44 | { 45 | if (BodySetup == nullptr) 46 | { 47 | BodySetup = CreateBodySetup(); 48 | } 49 | return BodySetup; 50 | } 51 | 52 | int32 UTerrainComponent::GetNumMaterials() const 53 | { 54 | return 1; 55 | } 56 | 57 | bool UTerrainComponent::GetPhysicsTriMeshData(struct FTriMeshCollisionData* CollisionData, bool InUseAllTriData) 58 | { 59 | // Copy vertex and triangle data 60 | CollisionData->Vertices = Vertices; 61 | int32 num_triangles = IndexBuffer.Num() / 3; 62 | for (int32 i = 0; i < num_triangles; ++i) 63 | { 64 | FTriIndices tris; 65 | tris.v0 = IndexBuffer[i * 3]; 66 | tris.v1 = IndexBuffer[i * 3 + 1]; 67 | tris.v2 = IndexBuffer[i * 3 + 2]; 68 | CollisionData->Indices.Add(tris); 69 | } 70 | 71 | CollisionData->bFlipNormals = true; 72 | CollisionData->bDeformableMesh = true; 73 | CollisionData->bFastCook = true; 74 | 75 | return true; 76 | } 77 | 78 | FBoxSphereBounds UTerrainComponent::CalcBounds(const FTransform& LocalToWorld) const 79 | { 80 | FBox bound(ForceInit); 81 | 82 | for (int32 i = 0; i < Vertices.Num(); ++i) 83 | { 84 | bound += LocalToWorld.TransformPosition(Vertices[i]); 85 | } 86 | 87 | FBoxSphereBounds boxsphere; 88 | boxsphere.BoxExtent = bound.GetExtent(); 89 | boxsphere.Origin = bound.GetCenter(); 90 | boxsphere.SphereRadius = boxsphere.BoxExtent.Size(); 91 | 92 | return boxsphere; 93 | } 94 | 95 | /// Terrain Interface /// 96 | 97 | void UTerrainComponent::Initialize(ATerrain* Terrain, TSharedPtr Proxy, int32 X, int32 Y) 98 | { 99 | XOffset = X; 100 | YOffset = Y; 101 | LODs = Terrain->GetNumLODs(); 102 | LODScale = Terrain->GetLODDistanceScale(); 103 | Tiling = Terrain->GetTiling(); 104 | AsyncCooking = Terrain->GetAsyncCookingEnabled(); 105 | MapProxy = Proxy; 106 | 107 | SetMaterial(0, Terrain->GetMaterials()); 108 | SetSize(Terrain->GetComponentSize()); 109 | } 110 | 111 | void UTerrainComponent::CreateMeshData() 112 | { 113 | // Create vertex data 114 | uint32 width = GetTerrainComponentWidth(Size); 115 | Vertices.Empty(); 116 | Vertices.SetNumUninitialized(width * width); 117 | for (uint32 y = 0; y < width; ++y) 118 | { 119 | for (uint32 x = 0; x < width; ++x) 120 | { 121 | Vertices[y * width + x] = FVector(x, y, 0.0f); 122 | } 123 | } 124 | 125 | // Create triangles 126 | uint32 polygons = width - 1; 127 | IndexBuffer.Empty(); 128 | IndexBuffer.SetNumUninitialized(polygons * polygons * 6); 129 | for (uint32 y = 0; y < polygons; ++y) 130 | { 131 | for (uint32 x = 0; x < polygons; ++x) 132 | { 133 | uint32 i = (y * polygons + x) * 6; 134 | 135 | IndexBuffer[i] = x + (y * width); 136 | IndexBuffer[i + 1] = 1 + x + (y + 1) * width; 137 | IndexBuffer[i + 2] = 1 + x + y * width; 138 | 139 | IndexBuffer[i + 3] = x + (y * width); 140 | IndexBuffer[i + 4] = x + (y + 1) * width; 141 | IndexBuffer[i + 5] = 1 + x + (y + 1) * width; 142 | } 143 | } 144 | } 145 | 146 | void UTerrainComponent::SetSize(uint32 NewSize) 147 | { 148 | if (NewSize > 1 && NewSize != Size) 149 | { 150 | Size = NewSize; 151 | CreateMeshData(); 152 | UpdateCollision(); 153 | MarkRenderStateDirty(); 154 | } 155 | } 156 | 157 | uint32 UTerrainComponent::GetSize() 158 | { 159 | return Size; 160 | } 161 | 162 | void UTerrainComponent::SetTiling(float NewTiling) 163 | { 164 | Tiling = NewTiling; 165 | float x = XOffset; 166 | float y = YOffset; 167 | 168 | // Update UV data in the proxy 169 | FTerrainComponentSceneProxy* proxy = (FTerrainComponentSceneProxy*)SceneProxy; 170 | ENQUEUE_RENDER_COMMAND(FComponentUpdate)([proxy, x, y, NewTiling](FRHICommandListImmediate& RHICmdList) { 171 | proxy->UpdateUVs(x, y, NewTiling); 172 | }); 173 | } 174 | 175 | void UTerrainComponent::SetLODs(int32 NumLODs, float DistanceScale) 176 | { 177 | if ((uint32)NumLODs < Size) 178 | { 179 | LODs = NumLODs; 180 | } 181 | else 182 | { 183 | LODs = Size; 184 | } 185 | LODScale = DistanceScale; 186 | 187 | // Reset the proxy to regenerate LODs 188 | MarkRenderStateDirty(); 189 | } 190 | 191 | void UTerrainComponent::Update(TSharedPtr NewSection) 192 | { 193 | MapProxy = NewSection; 194 | 195 | // Update collision data and bounds 196 | uint32 width = GetTerrainComponentWidth(Size); 197 | for (uint32 y = 0; y < width; ++y) 198 | { 199 | for (uint32 x = 0; x < width; ++x) 200 | { 201 | Vertices[y * width + x].Z = MapProxy->Data[(y + 1) * NewSection->X + x + 1]; 202 | } 203 | } 204 | BodyInstance.UpdateTriMeshVertices(Vertices); 205 | UpdateBounds(); 206 | 207 | // Update the scene proxy 208 | FTerrainComponentSceneProxy* proxy = (FTerrainComponentSceneProxy*)SceneProxy; 209 | ENQUEUE_RENDER_COMMAND(FComponentUpdate)([proxy, NewSection](FRHICommandListImmediate& RHICmdList) { 210 | proxy->UpdateMap(NewSection); 211 | }); 212 | MarkRenderTransformDirty(); 213 | } 214 | 215 | void UTerrainComponent::UpdateCollision() 216 | { 217 | SCOPE_CYCLE_COUNTER(STAT_DynamicTerrain_RebuildCollision); 218 | 219 | if (AsyncCooking) 220 | { 221 | // Abort previous cooks 222 | for (UBodySetup* body : BodySetupQueue) 223 | { 224 | body->AbortPhysicsMeshAsyncCreation(); 225 | } 226 | 227 | // Start cooking a new body 228 | BodySetupQueue.Add(CreateBodySetup()); 229 | BodySetupQueue.Last()->CreatePhysicsMeshesAsync(FOnAsyncPhysicsCookFinished::CreateUObject(this, &UTerrainComponent::FinishCollision, BodySetupQueue.Last())); 230 | } 231 | else 232 | { 233 | // Create a new body setup and clean out the async queue 234 | BodySetupQueue.Empty(); 235 | GetBodySetup(); 236 | 237 | // Change GUID for new collision data 238 | BodySetup->BodySetupGuid = FGuid::NewGuid(); 239 | 240 | // Cook collision data 241 | BodySetup->bHasCookedCollisionData = true; 242 | BodySetup->InvalidatePhysicsData(); 243 | BodySetup->CreatePhysicsMeshes(); 244 | RecreatePhysicsState(); 245 | } 246 | } 247 | 248 | void UTerrainComponent::FinishCollision(bool Success, UBodySetup* NewBodySetup) 249 | { 250 | // Create a new queue for async cooking 251 | TArray new_queue; 252 | new_queue.Reserve(BodySetupQueue.Num()); 253 | 254 | // Find the body setup 255 | int32 location; 256 | if (BodySetupQueue.Find(NewBodySetup, location)) 257 | { 258 | if (Success) 259 | { 260 | // Use the new body setup 261 | BodySetup = NewBodySetup; 262 | RecreatePhysicsState(); 263 | 264 | // Remove any earlier requests 265 | for (int32 i = location + 1; i < BodySetupQueue.Num(); ++i) 266 | { 267 | new_queue.Add(BodySetupQueue[i]); 268 | } 269 | BodySetupQueue = new_queue; 270 | } 271 | else 272 | { 273 | // Remove failed bake 274 | BodySetupQueue.RemoveAt(location); 275 | } 276 | } 277 | } 278 | 279 | UBodySetup* UTerrainComponent::CreateBodySetup() 280 | { 281 | UBodySetup* newbody = NewObject(this, NAME_None, IsTemplate() ? RF_Public : RF_NoFlags); 282 | newbody->BodySetupGuid = FGuid::NewGuid(); 283 | 284 | newbody->bGenerateMirroredCollision = false; 285 | newbody->bDoubleSidedGeometry = true; 286 | newbody->CollisionTraceFlag = CTF_UseComplexAsSimple; 287 | 288 | return newbody; 289 | } 290 | 291 | TSharedPtr UTerrainComponent::GetMapProxy() 292 | { 293 | VerifyMapProxy(); 294 | return MapProxy; 295 | } 296 | 297 | void UTerrainComponent::SetMapProxy(TSharedPtr Proxy) 298 | { 299 | MapProxy = Proxy; 300 | MarkRenderStateDirty(); 301 | } 302 | 303 | void UTerrainComponent::VerifyMapProxy() 304 | { 305 | uint32 width = GetTerrainComponentWidth(Size) + 2; 306 | if (!MapProxy.IsValid()) 307 | { 308 | if (Size > 1) 309 | { 310 | MapProxy = MakeShareable(new FMapSection(width, width)); 311 | } 312 | } 313 | else 314 | { 315 | if (MapProxy->X != width || MapProxy->Y != width) 316 | { 317 | MapProxy = MakeShareable(new FMapSection(width, width)); 318 | } 319 | } 320 | } -------------------------------------------------------------------------------- /Source/DynamicTerrain/Private/TerrainFoliage.cpp: -------------------------------------------------------------------------------- 1 | #include "TerrainFoliage.h" 2 | #include "Terrain.h" 3 | #include "TerrainAlgorithms.h" 4 | 5 | #include "Components/HierarchicalInstancedStaticMeshComponent.h" 6 | #include "Kismet/KismetMathLibrary.h" 7 | 8 | #include 9 | #include 10 | 11 | void UTerrainFoliageSpawner::AddFoliageCluster(ATerrain* Terrain, FVector Location, uint32 Seed) const 12 | { 13 | // Get the bounds of the map 14 | float xbounds = (float)(Terrain->GetMap()->GetWidthX() - 3) / 2.0f * Terrain->GetActorScale3D().X; 15 | float ybounds = (float)(Terrain->GetMap()->GetWidthY() - 3) / 2.0f * Terrain->GetActorScale3D().Y; 16 | FVector center = Terrain->GetActorLocation(); 17 | 18 | FVector2D min(center.X - xbounds, center.Y - ybounds); 19 | FVector2D max(center.X + xbounds, center.Y + ybounds); 20 | 21 | // Get a cluster of points 22 | std::default_random_engine rando(Seed); 23 | std::uniform_int_distribution random_seed(0, std::numeric_limits::max()); 24 | std::uniform_int_distribution cluster(ClusterMin, ClusterMax); 25 | PointNoise noise(Radius, cluster(rando), random_seed(rando)); 26 | 27 | // Add meshes at each point generated 28 | const TArray& points = noise.GetPoints(); 29 | UTerrainFoliage* foliage = nullptr; 30 | for (int32 i = 0; i < points.Num(); ++i) 31 | { 32 | // Set the location of the mesh 33 | FVector location = Location; 34 | location.X += points[i].X; 35 | location.Y += points[i].Y; 36 | 37 | // Make sure the point is within the bounds 38 | if (location.X < max.X && location.X > min.X && location.Y < max.Y && location.Y > min.Y) 39 | { 40 | // Set the height to match the terrain 41 | location.Z = Terrain->GetHeight(location); 42 | 43 | // Pick a new mesh 44 | if (!MatchClusters || foliage == nullptr ) 45 | { 46 | foliage = GetRandomFoliage(random_seed(rando)); 47 | } 48 | 49 | // Set the rotation to match the terrain normal 50 | FRotator rotation; 51 | if (foliage->AlignToNormal) 52 | { 53 | rotation = UKismetMathLibrary::MakeRotFromZ(Terrain->GetNormal(location)); 54 | } 55 | 56 | // Add a mesh 57 | FTransform transform; 58 | transform.SetLocation(location); 59 | transform.SetRotation(rotation.Quaternion()); 60 | Terrain->FindInstancedMesh(foliage->Mesh)->AddInstance(transform); 61 | } 62 | } 63 | } 64 | 65 | void UTerrainFoliageSpawner::AddFoliageUnit(ATerrain* Terrain, FVector Location, uint32 Seed) const 66 | { 67 | UTerrainFoliage* foliage = GetRandomFoliage(Seed); 68 | 69 | if (foliage != nullptr) 70 | { 71 | // Set the rotation 72 | FVector location = Location; 73 | location.Z = Terrain->GetHeight(location); 74 | 75 | // Set the rotation 76 | FRotator rotation; 77 | if (foliage->AlignToNormal) 78 | { 79 | rotation = UKismetMathLibrary::MakeRotFromZ(Terrain->GetNormal(location)); 80 | } 81 | 82 | // Add a mesh 83 | FTransform transform; 84 | transform.SetLocation(location); 85 | transform.SetRotation(rotation.Quaternion()); 86 | Terrain->FindInstancedMesh(foliage->Mesh)->AddInstance(transform); 87 | } 88 | } 89 | 90 | UTerrainFoliage* UTerrainFoliageSpawner::GetRandomFoliage(uint32 Seed) const 91 | { 92 | // Calculate the total weight of all the meshes 93 | int32 total_weight = 0; 94 | for (int32 i = 0; i < Foliage.Num(); ++i) 95 | { 96 | total_weight += Foliage[i].Weight; 97 | } 98 | 99 | if (total_weight > 0) 100 | { 101 | // Get a random index value 102 | std::default_random_engine rando(Seed); 103 | std::uniform_int_distribution dist(1, total_weight); 104 | int32 n = dist(rando); 105 | 106 | // Find which component corresponds to the random index 107 | for (int32 i = 0; i < Foliage.Num(); ++i) 108 | { 109 | n -= Foliage[i].Weight; 110 | if (n <= 0) 111 | { 112 | return Foliage[i].Asset; 113 | } 114 | } 115 | } 116 | 117 | return nullptr; 118 | } -------------------------------------------------------------------------------- /Source/DynamicTerrain/Private/TerrainGenerator.cpp: -------------------------------------------------------------------------------- 1 | #include "TerrainGenerator.h" 2 | #include "Terrain.h" 3 | #include "TerrainAlgorithms.h" 4 | 5 | #include "Kismet/KismetMathLibrary.h" 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | /// Map Generator Functions /// 12 | 13 | void UMapGenerator::NewSeed() 14 | { 15 | Seed = (uint32)std::chrono::steady_clock::now().time_since_epoch().count(); 16 | } 17 | 18 | void UMapGenerator::SetSeed(int32 NewSeed) 19 | { 20 | Seed = (uint32)NewSeed; 21 | } 22 | 23 | void UMapGenerator::Flat(float Height) 24 | { 25 | MapFlat(0.0f); 26 | 27 | // Noise unit test 28 | /*TArray groups; 29 | Terrain->GetFoliageGroups(groups); 30 | std::default_random_engine rando(Seed); 31 | std::uniform_int_distribution random_seed(0, std::numeric_limits::max()); 32 | 33 | for (int32 i = 0; i < groups.Num(); ++i) 34 | { 35 | // Create noise 36 | PoissonPointNoise noise(Terrain->GetMap()->GetWidthX() - 3, Terrain->GetMap()->GetWidthY() - 3, Height, random_seed(rando)); 37 | const TArray& points = noise.GetPoints(); 38 | 39 | // Add foliage objects 40 | FVector origin = Terrain->GetActorLocation(); 41 | origin.X -= (float)(Terrain->GetMap()->GetWidthX() - 3) / 2.0f * Terrain->GetActorScale3D().X; 42 | origin.Y -= (float)(Terrain->GetMap()->GetWidthY() - 3) / 2.0f * Terrain->GetActorScale3D().Y; 43 | for (int32 p = 0; p < points.Num(); ++p) 44 | { 45 | // Get the location of the foliage in world space 46 | FVector location = origin; 47 | location.X += points[p].X * Terrain->GetActorScale3D().X; 48 | location.Y += points[p].Y * Terrain->GetActorScale3D().Y; 49 | 50 | // Place the foliage object 51 | groups[i]->AddFoliageUnit(Terrain, location, random_seed(rando)); 52 | } 53 | }*/ 54 | } 55 | 56 | void UMapGenerator::Plasma(int32 Scale, int32 Foliage, float MaxHeight) 57 | { 58 | MapPlasma(Scale, MaxHeight); 59 | FoliageUniform(Foliage, Foliage); 60 | } 61 | 62 | void UMapGenerator::Perlin(int32 Frequency, int32 Octaves, float Persistence, float MaxHeight) 63 | { 64 | MapPerlin(Frequency, Octaves, Persistence, MaxHeight); 65 | FoliageRandom(Frequency * 10); 66 | } 67 | 68 | void UMapGenerator::TestGenerator(int32 BaseFrequency, int32 ElevationFrequency, int32 DetailFrequency, float MaxHeight) 69 | { 70 | if (MaxHeight < 1.0f) 71 | { 72 | MaxHeight = 1.0f; 73 | } 74 | 75 | std::default_random_engine rng(Seed); 76 | std::uniform_int_distribution random_seed(0, std::numeric_limits::max()); 77 | 78 | UHeightMap* Map = Terrain->GetMap(); 79 | 80 | int32 width_x = Map->GetWidthX(); 81 | int32 width_y = Map->GetWidthY(); 82 | 83 | // Create noise data 84 | GradientNoise base(BaseFrequency, BaseFrequency, random_seed(rng)); 85 | GradientNoise elevation(ElevationFrequency, ElevationFrequency, random_seed(rng)); 86 | GradientNoise detail(DetailFrequency, DetailFrequency, random_seed(rng)); 87 | base.Scale(width_x, width_y); 88 | elevation.Scale(width_x, width_y); 89 | detail.Scale(width_x, width_y); 90 | 91 | // Sample the noise onto the terrain 92 | for (int32 x = 0; x < width_x; ++x) 93 | { 94 | for (int32 y = 0; y < width_y; ++y) 95 | { 96 | float base_height = base.Perlin(x, y); 97 | float mountain = elevation.Perlin(x, y); 98 | 99 | // Start with base islands 100 | float height = base.Perlin(x, y) * 0.05f; 101 | // Add mountains and valleys 102 | height += mountain * mountain * mountain * 0.85f; 103 | // Add rough details 104 | height += detail.Perlin(x, y) * mountain * 0.1f; 105 | 106 | Map->SetHeight(x, y, height * MaxHeight); 107 | } 108 | } 109 | } 110 | 111 | /// Map Generator Components /// 112 | 113 | void UMapGenerator::MapFlat(float Height) 114 | { 115 | UHeightMap* Map = Terrain->GetMap(); 116 | for (int32 i = 0; i < Map->GetWidthX(); ++i) 117 | { 118 | for (int32 j = 0; j < Map->GetWidthY(); ++j) 119 | { 120 | Map->SetHeight(i, j, Height); 121 | } 122 | } 123 | } 124 | 125 | void UMapGenerator::MapPlasma(int32 Scale, float MaxHeight) 126 | { 127 | // Safety check for input values 128 | if (Scale < 1) 129 | { 130 | Scale = 1; 131 | } 132 | 133 | UHeightMap* Map = Terrain->GetMap(); 134 | 135 | int32 width_x = Map->GetWidthX(); 136 | int32 width_y = Map->GetWidthY(); 137 | 138 | // Create the plasma noise 139 | ValueNoise noise(Scale, Seed); 140 | noise.Scale(width_x, width_y); 141 | 142 | // Sample the noise onto the terrain 143 | for (int32 x = 0; x < width_x; ++x) 144 | { 145 | for (int32 y = 0; y < width_y; ++y) 146 | { 147 | Map->SetHeight(x, y, noise.Cubic((float)x, (float)y) * MaxHeight); 148 | } 149 | } 150 | } 151 | 152 | void UMapGenerator::MapPerlin(int32 Frequency, int32 Octaves, float Persistence, float MaxHeight) 153 | { 154 | // Safety check for input values 155 | if (Frequency < 2) 156 | { 157 | Frequency = 2; 158 | } 159 | if (Octaves < 1) 160 | { 161 | Octaves = 1; 162 | } 163 | if (Persistence < 0.0f) 164 | { 165 | Persistence = 0.0f; 166 | } 167 | else if (Persistence > 1.0f) 168 | { 169 | Persistence = 1.0f; 170 | } 171 | 172 | UHeightMap* Map = Terrain->GetMap(); 173 | 174 | int32 width_x = Map->GetWidthX(); 175 | int32 width_y = Map->GetWidthY(); 176 | 177 | // Create noise data 178 | std::vector noise; 179 | for (int32 i = 1; i <= Octaves; ++i) 180 | { 181 | noise.push_back(GradientNoise(Frequency * i, Frequency * i, Seed++)); 182 | noise.back().Scale(width_x, width_y); 183 | } 184 | 185 | // Sample the noise onto the terrain 186 | for (int32 x = 0; x < width_x; ++x) 187 | { 188 | for (int32 y = 0; y < width_y; ++y) 189 | { 190 | float amplitude = 1.0f; 191 | float total = 0.0f; 192 | float height = 0.0f; 193 | for (int32 i = 0; i < Octaves; ++i) 194 | { 195 | height += noise[i].Perlin(x, y) * amplitude; 196 | total += amplitude; 197 | amplitude *= Persistence; 198 | } 199 | 200 | Map->SetHeight(x, y, height * MaxHeight / total); 201 | } 202 | } 203 | } 204 | 205 | void UMapGenerator::FoliageRandom(uint32 NumPoints) 206 | { 207 | if (NumPoints < 1) 208 | return; 209 | 210 | TArray groups; 211 | Terrain->GetFoliageGroups(groups); 212 | std::default_random_engine rando(Seed); 213 | std::uniform_int_distribution random_seed(0, std::numeric_limits::max()); 214 | 215 | for (int32 i = 0; i < groups.Num(); ++i) 216 | { 217 | // Create noise 218 | PointNoise noise(Terrain->GetMap()->GetWidthX() - 3, Terrain->GetMap()->GetWidthY() - 3, NumPoints, random_seed(rando)); 219 | const TArray& points = noise.GetPoints(); 220 | 221 | // Add foliage objects 222 | FVector origin = Terrain->GetActorLocation(); 223 | origin.X -= (float)(Terrain->GetMap()->GetWidthX() - 3) / 2.0f * Terrain->GetActorScale3D().X; 224 | origin.Y -= (float)(Terrain->GetMap()->GetWidthY() - 3) / 2.0f * Terrain->GetActorScale3D().Y; 225 | for (int32 p = 0; p < points.Num(); ++p) 226 | { 227 | // Get the location of the foliage in world space 228 | FVector location = origin; 229 | location.X += points[p].X * Terrain->GetActorScale3D().X; 230 | location.Y += points[p].Y * Terrain->GetActorScale3D().Y; 231 | 232 | // Place the foliage object 233 | groups[i]->AddFoliageCluster(Terrain, location, random_seed(rando)); 234 | } 235 | } 236 | } 237 | 238 | void UMapGenerator::FoliageUniform(uint32 XPoints, uint32 YPoints) 239 | { 240 | if (XPoints < 1 || YPoints < 1) 241 | return; 242 | 243 | TArray groups; 244 | Terrain->GetFoliageGroups(groups); 245 | std::default_random_engine rando(Seed); 246 | std::uniform_int_distribution random_seed(0, std::numeric_limits::max()); 247 | 248 | for (int32 i = 0; i < groups.Num(); ++i) 249 | { 250 | // Create noise 251 | UniformPointNoise noise(XPoints, YPoints, random_seed(rando)); 252 | const TArray& points = noise.GetPoints(); 253 | 254 | // Add foliage objects 255 | FVector origin = Terrain->GetActorLocation(); 256 | origin.X -= (float)(Terrain->GetMap()->GetWidthX() - 3) / 2.0f * Terrain->GetActorScale3D().X; 257 | origin.Y -= (float)(Terrain->GetMap()->GetWidthY() - 3) / 2.0f * Terrain->GetActorScale3D().Y; 258 | 259 | float xscale = (float)(Terrain->GetMap()->GetWidthX() - 3) / noise.GetWidth() * Terrain->GetActorScale3D().X; 260 | float yscale = (float)(Terrain->GetMap()->GetWidthY() - 3) / noise.GetHeight() * Terrain->GetActorScale3D().Y; 261 | 262 | for (int32 p = 0; p < points.Num(); ++p) 263 | { 264 | // Get the location of the foliage in world space 265 | FVector location = origin; 266 | location.X += points[p].X * xscale; 267 | location.Y += points[p].Y * yscale; 268 | 269 | // Place the foliage object 270 | groups[i]->AddFoliageCluster(Terrain, location, random_seed(rando)); 271 | } 272 | } 273 | } -------------------------------------------------------------------------------- /Source/DynamicTerrain/Private/TerrainHeightMap.cpp: -------------------------------------------------------------------------------- 1 | #include "TerrainHeightMap.h" 2 | 3 | /// Blueprint Functions /// 4 | 5 | void UHeightMap::Resize(int32 X, int32 Y) 6 | { 7 | if (X <= 0 || Y <= 0) 8 | { 9 | return; 10 | } 11 | 12 | WidthX = X; 13 | WidthY = Y; 14 | 15 | MapData.Empty(); 16 | MapData.SetNumZeroed(WidthX * WidthY, true); 17 | } 18 | 19 | float UHeightMap::BPGetHeight(int32 X, int32 Y) const 20 | { 21 | if (X < 0 || Y < 0) 22 | { 23 | return 0.0f; 24 | } 25 | 26 | return GetHeight(X, Y); 27 | } 28 | 29 | /// Native Functions /// 30 | 31 | void UHeightMap::GetMapSection(FMapSection* Section, FIntPoint Min) 32 | { 33 | // Check to ensure the section is allocated and won't be outside the bounds of the heightmap 34 | if (Section->X < 2 || Section->Y < 2 || Section->Data.Num() != Section->X * Section->Y) 35 | return; 36 | if (Min.X < 0 || Min.Y < 0 || Min.X + Section->X > WidthX || Min.Y + Section->Y > WidthY) 37 | return; 38 | 39 | // Fill the map data 40 | int32 i = 0; 41 | for (int32 y = Min.Y; y < Min.Y + Section->Y; ++y) 42 | { 43 | for (int32 x = Min.X; x < Min.X + Section->X; ++x) 44 | { 45 | Section->Data[i] = MapData[y * WidthX + x]; 46 | ++i; 47 | } 48 | } 49 | } 50 | 51 | float UHeightMap::GetHeight(uint32 X, uint32 Y) const 52 | { 53 | return MapData[Y * WidthX + X]; 54 | } 55 | 56 | float UHeightMap::GetLinearHeight(float X, float Y) const 57 | { 58 | // Separate the whole number and fractional portions of the coordinates 59 | uint32 _X = X; 60 | uint32 _Y = Y; 61 | X -= _X; 62 | Y -= _Y; 63 | 64 | // Interpolate the heights at the four corners of the cell containing X, Y 65 | return FMath::Lerp( 66 | FMath::Lerp(MapData[_Y * WidthX + _X], MapData[_Y * WidthX + _X + 1], X), 67 | FMath::Lerp(MapData[(_Y + 1) * WidthX + _X], MapData[(_Y + 1) * WidthX + _X + 1], X), 68 | Y); 69 | } 70 | 71 | FVector UHeightMap::GetNormal(uint32 X, uint32 Y) const 72 | { 73 | float s01 = MapData[X - 1 + Y * WidthX]; 74 | float s21 = MapData[X + 1 + Y * WidthX]; 75 | float s10 = MapData[X + (Y - 1) * WidthX]; 76 | float s12 = MapData[X + (Y + 1) * WidthX]; 77 | 78 | // Get tangents in the x and y directions 79 | FVector vx(2.0f, 0.0f, s21 - s01); 80 | FVector vy(0.0f, 2.0f, s12 - s10); 81 | 82 | // Calculate the cross product of the two tangents 83 | vx.Normalize(); 84 | vy.Normalize(); 85 | 86 | return FVector::CrossProduct(vx, vy); 87 | } 88 | 89 | FVector UHeightMap::GetLinearNormal(float X, float Y) const 90 | { 91 | // Separate the whole number and fractional portions of the coordinates 92 | uint32 _X = X; 93 | uint32 _Y = Y; 94 | X -= _X; 95 | Y -= _Y; 96 | 97 | // Get the height on the edges 98 | float s01 = FMath::Lerp(MapData[_Y * WidthX + _X], MapData[(_Y + 1) * WidthX + _X], Y); 99 | float s21 = FMath::Lerp(MapData[_Y * WidthX + _X + 1], MapData[(_Y + 1) * WidthX + _X + 1], Y); 100 | float s10 = FMath::Lerp(MapData[_Y * WidthX + _X], MapData[_Y * WidthX + _X + 1], X); 101 | float s12 = FMath::Lerp(MapData[(_Y + 1) * WidthX + _X], MapData[(_Y + 1) * WidthX + _X + 1], X); 102 | 103 | // Get tangents in the X and Y directions 104 | FVector vx(2.0f, 0, s21 - s01); 105 | FVector vy(0, 2.0f, s12 - s10); 106 | 107 | // Calculate the cross product of the two tangents 108 | vx.Normalize(); 109 | vy.Normalize(); 110 | 111 | return FVector::CrossProduct(vx, vy); 112 | } 113 | 114 | FVector UHeightMap::GetTangent(uint32 X, uint32 Y) const 115 | { 116 | float s01 = MapData[X - 1 + Y * WidthX]; 117 | float s21 = MapData[X + 1 + Y * WidthX]; 118 | float s10 = MapData[X + (Y - 1) * WidthX]; 119 | float s12 = MapData[X + (Y + 1) * WidthX]; 120 | 121 | // Get tangents in the x and y directions 122 | FVector vx(2.0f, 0, s21 - s01); 123 | FVector vy(0, 2.0f, s12 - s10); 124 | 125 | // Return the x tangent 126 | vx.Normalize(); 127 | return vx; 128 | } 129 | 130 | FVector UHeightMap::GetLinearTangent(float X, float Y) const 131 | { 132 | // Separate the whole number and fractional portions of the coordinates 133 | uint32 _X = X; 134 | uint32 _Y = Y; 135 | X -= _X; 136 | Y -= _Y; 137 | 138 | // Get the height on the edges 139 | float s01 = FMath::Lerp(MapData[_Y * WidthX + _X], MapData[(_Y + 1) * WidthX + _X], Y); 140 | float s21 = FMath::Lerp(MapData[_Y * WidthX + _X + 1], MapData[(_Y + 1) * WidthX + _X + 1], Y); 141 | float s10 = FMath::Lerp(MapData[_Y * WidthX + _X], MapData[_Y * WidthX + _X + 1], X); 142 | float s12 = FMath::Lerp(MapData[(_Y + 1) * WidthX + _X], MapData[(_Y + 1) * WidthX + _X + 1], X); 143 | 144 | // Get tangent in the X direction 145 | FVector vx(2.0f, 0, s21 - s01); 146 | vx.Normalize(); 147 | return vx; 148 | } 149 | 150 | void UHeightMap::SetHeight(uint32 X, uint32 Y, float Height) 151 | { 152 | MapData[Y * WidthX + X] = Height; 153 | } 154 | 155 | int32 UHeightMap::GetWidthX() const 156 | { 157 | return WidthX; 158 | } 159 | 160 | int32 UHeightMap::GetWidthY() const 161 | { 162 | return WidthY; 163 | } -------------------------------------------------------------------------------- /Source/DynamicTerrain/Private/TerrainRender.cpp: -------------------------------------------------------------------------------- 1 | #include "TerrainRender.h" 2 | #include "TerrainComponent.h" 3 | #include "Terrain.h" 4 | 5 | #include "Engine.h" 6 | #include "SceneView.h" 7 | #include "Materials/Material.h" 8 | 9 | FTerrainComponentSceneProxy::FTerrainComponentSceneProxy(UTerrainComponent* Component) : FPrimitiveSceneProxy(Component), VertexFactory(GetScene().GetFeatureLevel(), "FTerrainComponentSceneProxy"), MaterialRelevance(Component->GetMaterialRelevance(GetScene().GetFeatureLevel())) 10 | { 11 | // Get map data from the parent component 12 | MapProxy = Component->GetMapProxy(); 13 | Size = Component->Size; 14 | MaxLOD = Component->LODs; 15 | 16 | // Create LOD indices 17 | ScaleLODs(Component->LODScale); 18 | IndexBuffers.SetNum(MaxLOD); 19 | for (uint32 i = 0; i < MaxLOD; ++i) 20 | { 21 | UpdateIndexData(IndexBuffers[i].Indices, i); 22 | } 23 | 24 | // Get the material from the parent or use the engine default 25 | Material = Component->GetMaterial(0); 26 | if (Material == nullptr) 27 | { 28 | Material = UMaterial::GetDefaultMaterial(MD_Surface); 29 | } 30 | 31 | // Initialize the component on the rendering thread 32 | uint32 width = GetTerrainComponentWidth(Size) - 1; 33 | int32 xoffset = Component->XOffset; 34 | int32 yoffset = Component->YOffset; 35 | float tiling = Component->Tiling; 36 | ENQUEUE_RENDER_COMMAND(FComponentFillBuffers)([this, xoffset, yoffset, tiling](FRHICommandListImmediate& RHICmdList) { 37 | Initialize(xoffset, yoffset, tiling); 38 | }); 39 | } 40 | 41 | FTerrainComponentSceneProxy::~FTerrainComponentSceneProxy() 42 | { 43 | VertexBuffers.PositionVertexBuffer.ReleaseResource(); 44 | VertexBuffers.StaticMeshVertexBuffer.ReleaseResource(); 45 | VertexFactory.ReleaseResource(); 46 | for (int32 i = 0; i < IndexBuffers.Num(); ++i) 47 | { 48 | IndexBuffers[i].ReleaseResource(); 49 | } 50 | } 51 | 52 | /// Scene Proxy Interface /// 53 | 54 | void FTerrainComponentSceneProxy::GetDynamicMeshElements(const TArray< const FSceneView* >& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, class FMeshElementCollector& Collector) const 55 | { 56 | // Check to see if wireframe rendering is enabled 57 | const bool wireframe = AllowDebugViewmodes() && ViewFamily.EngineShowFlags.Wireframe; 58 | 59 | // Get the material proxy from either the current material or the wireframe material 60 | FMaterialRenderProxy* material_proxy = nullptr; 61 | if (wireframe) 62 | { 63 | // Get the wireframe material 64 | FColoredMaterialRenderProxy* wireframe_material = new FColoredMaterialRenderProxy(GEngine->WireframeMaterial ? GEngine->WireframeMaterial->GetRenderProxy() : nullptr, FLinearColor(0.0f, 0.5f, 1.0f)); 65 | Collector.RegisterOneFrameMaterialProxy(wireframe_material); 66 | 67 | material_proxy = wireframe_material; 68 | } 69 | else 70 | { 71 | material_proxy = Material->GetRenderProxy(); 72 | } 73 | 74 | // Pass the mesh to every view it is visible to 75 | for (int32 view_index = 0; view_index < Views.Num(); ++view_index) 76 | { 77 | if (VisibilityMap & (1 << view_index)) 78 | { 79 | const FSceneView* view = Views[view_index]; 80 | 81 | // Get the LOD index of the mesh 82 | const FSceneView& lod_view = GetLODView(*view); 83 | const FBoxSphereBounds bounds = GetBounds(); 84 | 85 | FCachedSystemScalabilityCVars cvars = GetCachedScalabilityCVars(); 86 | float screen_scale = cvars.StaticMeshLODDistanceScale != 0.0f ? 1.0f / cvars.StaticMeshLODDistanceScale : 1.0f; 87 | 88 | uint32 LOD = 0; 89 | float radius = ComputeBoundsScreenRadiusSquared(bounds.Origin, bounds.SphereRadius, *view) * screen_scale * screen_scale * lod_view.LODDistanceFactor * lod_view.LODDistanceFactor; 90 | for (uint32 i = 0; i < MaxLOD; ++i) 91 | { 92 | if (FMath::Square(LODScales[i] * 0.5) > radius) 93 | { 94 | LOD = i; 95 | } 96 | else 97 | { 98 | break; 99 | } 100 | } 101 | 102 | // Set up the mesh 103 | FMeshBatch& mesh = Collector.AllocateMesh(); 104 | mesh.bWireframe = wireframe; 105 | mesh.VertexFactory = &VertexFactory; 106 | mesh.MaterialRenderProxy = material_proxy; 107 | mesh.ReverseCulling = IsLocalToWorldDeterminantNegative(); 108 | mesh.Type = PT_TriangleList; 109 | mesh.DepthPriorityGroup = SDPG_World; 110 | mesh.bCanApplyViewModeOverrides = false; 111 | 112 | // Set up the first element of the mesh (we only need one) 113 | FMeshBatchElement& element = mesh.Elements[0]; 114 | element.IndexBuffer = &IndexBuffers[LOD]; 115 | element.FirstIndex = 0; 116 | element.NumPrimitives = IndexBuffers[LOD].Indices.Num() / 3; 117 | element.MinVertexIndex = 0; 118 | element.MaxVertexIndex = VertexBuffers.PositionVertexBuffer.GetNumVertices() - 1; 119 | 120 | // Load uniform buffers 121 | bool bHasPrecomputedVolumetricLightmap; 122 | FMatrix PreviousLocalToWorld; 123 | int32 SingleCaptureIndex; 124 | bool bOutputVelocity; 125 | GetScene().GetPrimitiveUniformShaderParameters_RenderThread(GetPrimitiveSceneInfo(), bHasPrecomputedVolumetricLightmap, PreviousLocalToWorld, SingleCaptureIndex, bOutputVelocity); 126 | 127 | FDynamicPrimitiveUniformBuffer& DynamicPrimitiveUniformBuffer = Collector.AllocateOneFrameResource(); 128 | DynamicPrimitiveUniformBuffer.Set(GetLocalToWorld(), PreviousLocalToWorld, GetBounds(), GetLocalBounds(), true, bHasPrecomputedVolumetricLightmap, DrawsVelocity(), bOutputVelocity); 129 | element.PrimitiveUniformBufferResource = &DynamicPrimitiveUniformBuffer.UniformBuffer; 130 | 131 | // Add the mesh 132 | Collector.AddMesh(view_index, mesh); 133 | } 134 | } 135 | 136 | // Draw bounds and collision in debug builds 137 | #if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) 138 | for (int32 view_index = 0; view_index < Views.Num(); ++view_index) 139 | { 140 | if (VisibilityMap & (1 << view_index)) 141 | { 142 | // Render the object bounds 143 | RenderBounds(Collector.GetPDI(view_index), ViewFamily.EngineShowFlags, GetBounds(), IsSelected()); 144 | } 145 | } 146 | #endif 147 | } 148 | 149 | FPrimitiveViewRelevance FTerrainComponentSceneProxy::GetViewRelevance(const FSceneView* View) const 150 | { 151 | FPrimitiveViewRelevance Result; 152 | Result.bDrawRelevance = IsShown(View); 153 | Result.bShadowRelevance = IsShadowCast(View); 154 | 155 | Result.bDynamicRelevance = true; 156 | 157 | Result.bRenderInMainPass = ShouldRenderInMainPass(); 158 | Result.bUsesLightingChannels = GetLightingChannelMask() != GetDefaultLightingChannelMask(); 159 | Result.bRenderCustomDepth = ShouldRenderCustomDepth(); 160 | 161 | Result.bTranslucentSelfShadow = bCastVolumetricTranslucentShadow; 162 | MaterialRelevance.SetPrimitiveViewRelevance(Result); 163 | Result.bVelocityRelevance = IsMovable() && Result.bOpaque && Result.bRenderInMainPass; 164 | return Result; 165 | } 166 | 167 | void FTerrainComponentSceneProxy::Initialize(int32 X, int32 Y, float Tiling) 168 | { 169 | // Initialize buffers 170 | uint32 width = GetTerrainComponentWidth(Size); 171 | VertexBuffers.PositionVertexBuffer.Init(width * width); 172 | VertexBuffers.StaticMeshVertexBuffer.Init(width * width, 1); 173 | 174 | // Load data for all buffers 175 | UpdateMapData(); 176 | UpdateUVData(X, Y, Tiling); 177 | 178 | // Initialize the buffers 179 | for (int32 i = 0; i < IndexBuffers.Num(); ++i) 180 | { 181 | IndexBuffers[i].InitResource(); 182 | } 183 | VertexBuffers.PositionVertexBuffer.InitResource(); 184 | VertexBuffers.StaticMeshVertexBuffer.InitResource(); 185 | 186 | // Bind vertex factory data 187 | FLocalVertexFactory::FDataType datatype; 188 | VertexBuffers.PositionVertexBuffer.BindPositionVertexBuffer(&VertexFactory, datatype); 189 | VertexBuffers.StaticMeshVertexBuffer.BindTangentVertexBuffer(&VertexFactory, datatype); 190 | VertexBuffers.StaticMeshVertexBuffer.BindPackedTexCoordVertexBuffer(&VertexFactory, datatype); 191 | VertexBuffers.StaticMeshVertexBuffer.BindLightMapVertexBuffer(&VertexFactory, datatype, 0); 192 | FColorVertexBuffer::BindDefaultColorVertexBuffer(&VertexFactory, datatype, FColorVertexBuffer::NullBindStride::ZeroForDefaultBufferBind); 193 | 194 | // Initalize the vertex factory 195 | VertexFactory.SetData(datatype); 196 | VertexFactory.InitResource(); 197 | } 198 | 199 | /// Proxy Update Functions /// 200 | 201 | void FTerrainComponentSceneProxy::UpdateMap(TSharedPtr SectionProxy) 202 | { 203 | // Copy map data to buffers 204 | MapProxy = SectionProxy; 205 | UpdateMapData(); 206 | 207 | // Copy buffers to RHI 208 | { 209 | auto& vertex_buffer = VertexBuffers.PositionVertexBuffer; 210 | void* vertex_data = RHILockVertexBuffer(vertex_buffer.VertexBufferRHI, 0, vertex_buffer.GetNumVertices() * vertex_buffer.GetStride(), RLM_WriteOnly); 211 | FMemory::Memcpy(vertex_data, vertex_buffer.GetVertexData(), vertex_buffer.GetNumVertices() * vertex_buffer.GetStride()); 212 | RHIUnlockVertexBuffer(vertex_buffer.VertexBufferRHI); 213 | } 214 | 215 | { 216 | auto& vertex_buffer = VertexBuffers.StaticMeshVertexBuffer; 217 | void* vertex_data = RHILockVertexBuffer(vertex_buffer.TangentsVertexBuffer.VertexBufferRHI, 0, vertex_buffer.GetTangentSize(), RLM_WriteOnly); 218 | FMemory::Memcpy(vertex_data, vertex_buffer.GetTangentData(), vertex_buffer.GetTangentSize()); 219 | RHIUnlockVertexBuffer(vertex_buffer.TangentsVertexBuffer.VertexBufferRHI); 220 | } 221 | } 222 | 223 | void FTerrainComponentSceneProxy::UpdateUVs(int32 XOffset, int32 YOffset, float Tiling) 224 | { 225 | // Set UV data 226 | UpdateUVData(XOffset, YOffset, Tiling); 227 | 228 | // Copy buffers to RHI 229 | { 230 | auto& vertex_buffer = VertexBuffers.StaticMeshVertexBuffer; 231 | void* vertex_data = RHILockVertexBuffer(vertex_buffer.TexCoordVertexBuffer.VertexBufferRHI, 0, vertex_buffer.GetTexCoordSize(), RLM_WriteOnly); 232 | FMemory::Memcpy(vertex_data, vertex_buffer.GetTexCoordData(), vertex_buffer.GetTexCoordSize()); 233 | RHIUnlockVertexBuffer(vertex_buffer.TexCoordVertexBuffer.VertexBufferRHI); 234 | } 235 | } 236 | 237 | void FTerrainComponentSceneProxy::UpdateMapData() 238 | { 239 | uint32 width = GetTerrainComponentWidth(Size); 240 | for (uint32 y = 0; y < width; ++y) 241 | { 242 | for (uint32 x = 0; x < width; ++x) 243 | { 244 | uint32 i = y * width + x; 245 | 246 | // Position data 247 | VertexBuffers.PositionVertexBuffer.VertexPosition(i) = FVector(x, y, MapProxy->Data[(y + 1) * MapProxy->X + x + 1]); 248 | 249 | // Tangent data 250 | int32 map_offset_x = x + 1; 251 | int32 map_offset_y = y + 1; 252 | float s01 = MapProxy->Data[map_offset_x - 1 + map_offset_y * MapProxy->X]; 253 | float s21 = MapProxy->Data[map_offset_x + 1 + map_offset_y * MapProxy->X]; 254 | float s10 = MapProxy->Data[map_offset_x + (map_offset_y - 1) * MapProxy->X]; 255 | float s12 = MapProxy->Data[map_offset_x + (map_offset_y + 1) * MapProxy->X]; 256 | 257 | // Get tangents in the x and y directions 258 | FVector vx(2.0f, 0, s21 - s01); 259 | FVector vy(0, 2.0f, s12 - s10); 260 | 261 | // Calculate the cross product of the two tangents 262 | vx.Normalize(); 263 | vy.Normalize(); 264 | VertexBuffers.StaticMeshVertexBuffer.SetVertexTangents(i, vx, vy, FVector::CrossProduct(vx, vy)); 265 | } 266 | } 267 | } 268 | 269 | void FTerrainComponentSceneProxy::UpdateUVData(int32 XOffset, int32 YOffset, float Tiling) 270 | { 271 | // Fill UV data 272 | uint32 width = GetTerrainComponentWidth(Size); 273 | XOffset *= width - 1; 274 | YOffset *= width - 1; 275 | for (uint32 y = 0; y < width; ++y) 276 | { 277 | for (uint32 x = 0; x < width; ++x) 278 | { 279 | VertexBuffers.StaticMeshVertexBuffer.SetVertexUV(y * width + x, 0, FVector2D((XOffset + x) * Tiling, (YOffset + y) * Tiling)); 280 | } 281 | } 282 | } 283 | 284 | void FTerrainComponentSceneProxy::UpdateIndexData(TArray& Indices, uint32 Stride) 285 | { 286 | Stride = FMath::Exp2(Stride); 287 | uint32 width = GetTerrainComponentWidth(Size); 288 | uint32 polygons = (width - 1) / Stride; 289 | 290 | Indices.Empty(); 291 | Indices.SetNumUninitialized(polygons * polygons * 6); 292 | for (uint32 y = 0; y < polygons; y++) 293 | { 294 | for (uint32 x = 0; x < polygons; x++) 295 | { 296 | uint32 i = (y * polygons + x) * 6; 297 | 298 | Indices[i] = x * Stride + y * Stride * width; 299 | Indices[i + 1] = (1 + x) * Stride + (y + 1) * Stride * width; 300 | Indices[i + 2] = (1 + x) * Stride + y * Stride * width; 301 | 302 | Indices[i + 3] = x * Stride + y * Stride * width; 303 | Indices[i + 4] = x * Stride + (y + 1) * Stride * width; 304 | Indices[i + 5] = (1 + x) * Stride + (y + 1) * Stride * width; 305 | } 306 | } 307 | } 308 | 309 | void FTerrainComponentSceneProxy::ScaleLODs(float Scale) 310 | { 311 | LODScales.Empty(); 312 | LODScales.SetNumUninitialized(MaxLOD); 313 | 314 | for (uint32 i = 0; i < MaxLOD; ++i) 315 | { 316 | LODScales[i] = FMath::Pow(Scale, i); 317 | } 318 | } -------------------------------------------------------------------------------- /Source/DynamicTerrain/Private/TerrainRender.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "CoreMinimal.h" 4 | #include "PrimitiveSceneProxy.h" 5 | 6 | #include "DynamicMeshBuilder.h" 7 | 8 | class UTerrainComponent; 9 | struct FMapSection; 10 | 11 | // A rendering proxy which stores rendering data for a single terrain component 12 | // Functions for the proxy should only be called on the rendering thread (with the exception of the constructor) 13 | // Use functions in UTerrainComponent to change proxies on the game thread 14 | class FTerrainComponentSceneProxy : public FPrimitiveSceneProxy 15 | { 16 | public: 17 | FTerrainComponentSceneProxy(UTerrainComponent* Component); 18 | virtual ~FTerrainComponentSceneProxy(); 19 | 20 | /// Scene Proxy Interface /// 21 | 22 | SIZE_T GetTypeHash() const override 23 | { 24 | static size_t unique_pointer; 25 | return reinterpret_cast(&unique_pointer); 26 | } 27 | 28 | virtual uint32 GetMemoryFootprint() const override 29 | { 30 | return (sizeof(*this) + GetAllocatedSize()); 31 | } 32 | 33 | uint32 GetAllocatedSize() const 34 | { 35 | return(FPrimitiveSceneProxy::GetAllocatedSize()); 36 | } 37 | 38 | virtual bool CanBeOccluded() const override 39 | { 40 | return !MaterialRelevance.bDisableDepthTest; 41 | } 42 | 43 | virtual void GetDynamicMeshElements(const TArray< const FSceneView* >& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, class FMeshElementCollector& Collector) const override; 44 | virtual FPrimitiveViewRelevance GetViewRelevance(const FSceneView* View) const override; 45 | 46 | /// Proxy Update Functions /// 47 | 48 | // Update rending data using the provided proxy 49 | void UpdateMap(TSharedPtr SectionProxy); 50 | // Update UV tiling 51 | void UpdateUVs(int32 XOffset, int32 YOffset, float Tiling); 52 | 53 | protected: 54 | // Initialize vertex buffers 55 | void Initialize(int32 X, int32 Y, float Tiling); 56 | // Update rendering data using the current map proxy data 57 | void UpdateMapData(); 58 | // Update mesh UVs using the provided offsets and tiling 59 | void UpdateUVData(int32 XOffset, int32 YOffset, float Tiling); 60 | // Fill index buffers 61 | void UpdateIndexData(TArray& Indices, uint32 Stride); 62 | // Set LOD scales for each lod 63 | void ScaleLODs(float Scale); 64 | 65 | // The heightmap data the component needs to render 66 | TSharedPtr MapProxy = nullptr; 67 | // The width of the component, the number of vertices is Size * Size + 1 68 | uint32 Size; 69 | 70 | // The vertex buffers containing mesh data 71 | FStaticMeshVertexBuffers VertexBuffers; 72 | // The triangles used by the component's mesh 73 | TArray IndexBuffers; 74 | // The vertex factory for storing vertex type data 75 | FLocalVertexFactory VertexFactory; 76 | 77 | // The material used to render the component 78 | UMaterialInterface* Material; 79 | FMaterialRelevance MaterialRelevance; 80 | 81 | // The number of LODs to create for the mesh 82 | uint32 MaxLOD; 83 | // LOD scales for each individual LOD 84 | TArray LODScales; 85 | }; -------------------------------------------------------------------------------- /Source/DynamicTerrain/Private/TerrainStat.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | DECLARE_STATS_GROUP(TEXT("Dynamic Terrain Plugin"), STATGROUP_DynamicTerrain, STATCAT_Advanced) -------------------------------------------------------------------------------- /Source/DynamicTerrain/Private/TerrainToolComponent.cpp: -------------------------------------------------------------------------------- 1 | #include "TerrainToolComponent.h" 2 | 3 | #include "Kismet/GameplayStatics.h" 4 | 5 | UTerrainToolComponent::UTerrainToolComponent() 6 | { 7 | BrushDecal = CreateDefaultSubobject(TEXT("TerrainGameplayBrushDecal")); 8 | BrushDecal->SetupAttachment(this); 9 | BrushDecal->SetVisibility(false); 10 | 11 | PrimaryComponentTick.bCanEverTick = true; 12 | PrimaryComponentTick.bStartWithTickEnabled = true; 13 | } 14 | 15 | /// USceneComponent Interface /// 16 | 17 | void UTerrainToolComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) 18 | { 19 | if (!ToolEnabled) 20 | { 21 | // Hide the brush when the tool is disabled 22 | BrushDecal->SetVisibility(false); 23 | return; 24 | } 25 | 26 | // Get the controller of the pawn that owns this component 27 | APawn* owner = Cast(GetOwner()); 28 | if (owner != nullptr) 29 | { 30 | APlayerController* controller = Cast(owner->Controller); 31 | if (controller != nullptr) 32 | { 33 | // Project the mouse into the world 34 | FVector2D Mouse; 35 | UWorld* world = GetWorld(); 36 | UGameViewportClient* viewport = world->GetGameViewport(); 37 | if (viewport->GetMousePosition(Mouse)) 38 | { 39 | FVector WorldOrigin; 40 | FVector WorldDirection; 41 | if (UGameplayStatics::DeprojectScreenToWorld(controller, Mouse, WorldOrigin, WorldDirection)) 42 | { 43 | // Trace from the viewport outward under the cursor 44 | FHitResult hit; 45 | world->LineTraceSingleByChannel(hit, WorldOrigin, WorldOrigin + WorldDirection * MaxBrushDistance, ECollisionChannel::ECC_WorldDynamic); 46 | 47 | if (hit.IsValidBlockingHit()) 48 | { 49 | // Move the brush 50 | BrushDecal->SetRelativeLocation(hit.Location); 51 | 52 | ATerrain* terrain = Cast(hit.GetActor()); 53 | if (terrain != nullptr) 54 | { 55 | BrushDecal->SetVisibility(true); 56 | BrushDecal->ChangeColor(BrushColor); 57 | BrushDecal->Resize(TerrainTools.GetTool(), terrain); 58 | if (ToolActive) 59 | { 60 | // Apply the terrain tool 61 | TerrainTools.GetTool()->Invert = ToolInvert; 62 | TerrainTools.GetTool()->Apply(terrain, hit.Location, DeltaTime); 63 | } 64 | } 65 | else 66 | { 67 | // Hide the brush when it isn't over terrain 68 | BrushDecal->SetVisibility(!HideInvalidBrush); 69 | BrushDecal->ChangeColor(BrushInvalidColor); 70 | } 71 | } 72 | else 73 | { 74 | // Hide the brush when it's out of range 75 | BrushDecal->SetVisibility(!HideInvalidBrush); 76 | BrushDecal->ChangeColor(BrushInvalidColor); 77 | } 78 | } 79 | } 80 | } 81 | } 82 | 83 | } 84 | 85 | /// Terrain Tool Component Interface /// 86 | 87 | void UTerrainToolComponent::SelectTool(TerrainToolID ToolID) 88 | { 89 | TerrainTools.SetTool(ToolID); 90 | } 91 | 92 | void UTerrainToolComponent::SelectBrush(TerrainBrushID BrushID) 93 | { 94 | TerrainTools.SetBrush(BrushID); 95 | } 96 | 97 | void UTerrainToolComponent::NextTool() 98 | { 99 | // Iterate the tool ID of the the current tool 100 | int tool = (int)TerrainTools.ToolID(); 101 | tool++; 102 | 103 | // Set the tool 104 | if (tool == (int)TerrainToolID::NUM) 105 | { 106 | TerrainTools.SetTool(TerrainToolID::SCULPT); 107 | } 108 | else 109 | { 110 | TerrainTools.SetTool((TerrainToolID)tool); 111 | } 112 | } 113 | 114 | void UTerrainToolComponent::PreviousTool() 115 | { 116 | // Decrement the tool ID of the current tool 117 | int tool = (int)TerrainTools.ToolID(); 118 | tool--; 119 | 120 | // Set the tool 121 | if (tool == -1) 122 | { 123 | tool = (int)TerrainToolID::NUM; 124 | TerrainTools.SetTool((TerrainToolID)(tool - 1)); 125 | } 126 | else 127 | { 128 | TerrainTools.SetTool((TerrainToolID)tool); 129 | } 130 | } 131 | 132 | void UTerrainToolComponent::NextBrush() 133 | { 134 | int brush = (int)TerrainTools.BrushID(); 135 | brush++; 136 | 137 | if (brush == (int)TerrainBrushID::NUM) 138 | { 139 | TerrainTools.SetBrush(TerrainBrushID::LINEAR); 140 | } 141 | else 142 | { 143 | TerrainTools.SetBrush((TerrainBrushID)brush); 144 | } 145 | } 146 | 147 | void UTerrainToolComponent::PreviousBrush() 148 | { 149 | int brush = (int)TerrainTools.BrushID(); 150 | brush++; 151 | 152 | if (brush == -1) 153 | { 154 | brush = (int)TerrainBrushID::NUM; 155 | TerrainTools.SetBrush((TerrainBrushID)(brush - 1)); 156 | } 157 | else 158 | { 159 | TerrainTools.SetBrush((TerrainBrushID)brush); 160 | } 161 | } 162 | 163 | FText UTerrainToolComponent::GetToolName() 164 | { 165 | return TerrainTools.GetTool()->GetName(); 166 | } 167 | 168 | FText UTerrainToolComponent::GetBrushName() 169 | { 170 | return TerrainTools.GetBrush()->GetName(); 171 | } 172 | 173 | UBrushDecal* UTerrainToolComponent::GetBrushDecal() 174 | { 175 | return BrushDecal; 176 | } -------------------------------------------------------------------------------- /Source/DynamicTerrain/Private/TerrainTools.cpp: -------------------------------------------------------------------------------- 1 | #include "TerrainTools.h" 2 | 3 | #include "Kismet/GameplayStatics.h" 4 | 5 | #define LOCTEXT_NAMESPACE "TerrainTools" 6 | 7 | const FName FSculptTool::ToolID = TEXT("SculptTool"); 8 | const FName FSmoothTool::ToolID = TEXT("SmoothTool"); 9 | const FName FFlattenTool::ToolID = TEXT("FlattenTool"); 10 | 11 | /// Terrain Brushes /// 12 | 13 | FText FBrushLinear::GetName() const 14 | { 15 | return LOCTEXT("TerrianBrushLinearName", "Linear Brush"); 16 | } 17 | 18 | TerrainBrushID FBrushLinear::GetID() const 19 | { 20 | return TerrainBrushID::LINEAR; 21 | } 22 | 23 | float FBrushLinear::GetStrength(float Distance, float Radius, float Falloff) const 24 | { 25 | return Distance < Radius ? 1.0f : Falloff > 0.0f ? FMath::Max(0.0f, 1.0f - (Distance - Radius) / Falloff) : 0.0f; 26 | } 27 | 28 | FText FBrushSmooth::GetName() const 29 | { 30 | return LOCTEXT("TerrianBrushSmoothName", "Smooth Brush"); 31 | } 32 | 33 | TerrainBrushID FBrushSmooth::GetID() const 34 | { 35 | return TerrainBrushID::SMOOTH; 36 | } 37 | 38 | float FBrushSmooth::GetStrength(float Distance, float Radius, float Falloff) const 39 | { 40 | float s = Distance < Radius ? 1.0f : Falloff > 0.0f ? FMath::Max(0.0f, 1.0f - (Distance - Radius) / Falloff) : 0.0f; 41 | return s * s * (3 - 2 * s); 42 | } 43 | 44 | FText FBrushRound::GetName() const 45 | { 46 | return LOCTEXT("TerrianBrushRoundName", "Round Brush"); 47 | } 48 | 49 | TerrainBrushID FBrushRound::GetID() const 50 | { 51 | return TerrainBrushID::ROUND; 52 | } 53 | 54 | float FBrushRound::GetStrength(float Distance, float Radius, float Falloff) const 55 | { 56 | float s = Distance < Radius ? 0.0f : Falloff > 0.0f ? FMath::Min(1.0f, (Distance - Radius) / Falloff) : 1.0f; 57 | return 1.0f - s * s; 58 | } 59 | 60 | FText FBrushSphere::GetName() const 61 | { 62 | return LOCTEXT("TerrianBrushSphereName", "Sphere Brush"); 63 | } 64 | 65 | TerrainBrushID FBrushSphere::GetID() const 66 | { 67 | return TerrainBrushID::SPHERE; 68 | } 69 | 70 | float FBrushSphere::GetStrength(float Distance, float Radius, float Falloff) const 71 | { 72 | float s = Distance < Radius ? 0.0f : Falloff > 0.0f ? FMath::Min(1.0f, (Distance - Radius) / Falloff) : 1.0f; 73 | return FMath::Sqrt(1.0f - s * s); 74 | } 75 | 76 | /// Terrain Tools /// 77 | 78 | float FTerrainTool::TraceDistance = 50000; 79 | 80 | void FTerrainTool::SetBrush(FTerrainBrush* NewBrush) 81 | { 82 | Brush = NewBrush; 83 | } 84 | 85 | void FTerrainTool::Apply(ATerrain* Terrain, FVector Center, float Delta) const 86 | { 87 | FVector2D MapCenter = WorldVectorToMapVector(Terrain, Center); 88 | 89 | int32 width_x = Terrain->GetMap()->GetWidthX(); 90 | int32 width_y = Terrain->GetMap()->GetWidthY(); 91 | if (MapCenter.X < 0 || MapCenter.Y < 0 || MapCenter.X > width_x || MapCenter.Y > width_y) 92 | { 93 | return; 94 | } 95 | 96 | // Get the mask for the tool 97 | FBrushStroke mask = GetStroke(Terrain->GetMap(), MapCenter); 98 | FIntRect bounds = mask.GetBounds(); 99 | 100 | // Apply the mask to the heightmap 101 | for (int x = bounds.Min.X; x < bounds.Max.X; ++x) 102 | { 103 | for (int y = bounds.Min.Y; y < bounds.Max.Y; ++y) 104 | { 105 | // Change the vertex height 106 | float height = Terrain->GetMap()->GetHeight(x, y); 107 | Terrain->GetMap()->SetHeight(x, y, height + mask.GetData(x, y) * Delta * Strength); 108 | } 109 | } 110 | 111 | // Update the terrain sections covered 112 | Terrain->UpdateRange(bounds); 113 | } 114 | 115 | void FTerrainTool::Apply(UHeightMap* Map, FVector2D Center, float Delta) const 116 | { 117 | int32 width_x = Map->GetWidthX(); 118 | int32 width_y = Map->GetWidthY(); 119 | if (Center.X < 0 || Center.Y < 0 || Center.X > width_x || Center.Y > width_y) 120 | { 121 | return; 122 | } 123 | 124 | // Get the mask for the tool 125 | FBrushStroke mask = GetStroke(Map, Center); 126 | FIntRect bounds = mask.GetBounds(); 127 | 128 | // Apply the mask to the heightmap 129 | for (int x = bounds.Min.X; x < bounds.Max.X; ++x) 130 | { 131 | for (int y = bounds.Min.Y; y < bounds.Max.Y; ++y) 132 | { 133 | // Change the vertex height 134 | float height = Map->GetHeight(x, y); 135 | Map->SetHeight(x, y, height + mask.GetData(x, y) * Delta * Strength); 136 | } 137 | } 138 | } 139 | 140 | bool FTerrainTool::MouseToTerrainPosition(ATerrain* Terrain, const FSceneView* View, FHitResult& Result) const 141 | { 142 | if (Terrain != nullptr && View != nullptr) 143 | { 144 | UWorld* world = Terrain->GetWorld(); 145 | 146 | // Get the location of the mouse in the active game viewport 147 | UGameViewportClient* viewport = world->GetGameViewport(); 148 | 149 | FVector2D Mouse; 150 | if (viewport->GetMousePosition(Mouse)) 151 | { 152 | // Project the mouse into the world 153 | FVector WorldOrigin; 154 | FVector WorldDirection; 155 | 156 | FSceneView::DeprojectScreenToWorld(Mouse, View->UnconstrainedViewRect, View->ViewMatrices.GetInvViewProjectionMatrix(), WorldOrigin, WorldDirection); 157 | 158 | // Trace from the viewport outward under the cursor 159 | FHitResult hit; 160 | world->LineTraceSingleByChannel(hit, WorldOrigin, WorldOrigin + WorldDirection * TraceDistance, ECollisionChannel::ECC_WorldDynamic); 161 | 162 | AActor* actor = hit.GetActor(); 163 | if (actor != nullptr) 164 | { 165 | if (actor->IsA()) 166 | { 167 | Result = hit; 168 | return true; 169 | } 170 | } 171 | } 172 | } 173 | 174 | // Return no result if anything failed 175 | Result = FHitResult(); 176 | return false; 177 | } 178 | 179 | bool FTerrainTool::MouseToTerrainPosition(ATerrain* Terrain, const APlayerController* Controller, FHitResult& Result) const 180 | { 181 | if (Terrain != nullptr && Controller != nullptr) 182 | { 183 | UWorld* world = Terrain->GetWorld(); 184 | 185 | // Get the location of the mouse in the active game viewport 186 | UGameViewportClient* viewport = world->GetGameViewport(); 187 | 188 | FVector2D Mouse; 189 | if (viewport->GetMousePosition(Mouse)) 190 | { 191 | // Project the mouse into the world 192 | FVector WorldOrigin; 193 | FVector WorldDirection; 194 | 195 | if (UGameplayStatics::DeprojectScreenToWorld(Controller, Mouse, WorldOrigin, WorldDirection)) 196 | { 197 | // Trace from the viewport outward under the cursor 198 | FHitResult hit; 199 | world->LineTraceSingleByChannel(hit, WorldOrigin, WorldOrigin + WorldDirection * TraceDistance, ECollisionChannel::ECC_WorldDynamic); 200 | 201 | Result = hit; 202 | return true; 203 | } 204 | } 205 | } 206 | 207 | // Return no result if anything failed 208 | Result = FHitResult(); 209 | return false; 210 | } 211 | 212 | FVector2D FTerrainTool::WorldVectorToMapVector(ATerrain* Terrain, FVector WorldPosition) const 213 | { 214 | if (Terrain == nullptr) 215 | return FVector2D(); 216 | 217 | // Get the corner of the terrain corresponding to 0, 0 on the heightmap 218 | float xwidth = (float)(Terrain->GetMap()->GetWidthX() - 3) / 2.0f; 219 | float ywidth = (float)(Terrain->GetMap()->GetWidthY() - 3) / 2.0f; 220 | FVector scale = Terrain->GetActorScale(); 221 | 222 | FVector corner = Terrain->GetActorLocation(); 223 | corner.X -= xwidth * scale.X; 224 | corner.Y -= ywidth * scale.Y; 225 | 226 | // Get the heightmap coordinates of the point 227 | FVector2D loc; 228 | loc.X = (WorldPosition.X - corner.X) / scale.X + 1.0f; 229 | loc.Y = (WorldPosition.Y - corner.Y) / scale.Y + 1.0f; 230 | 231 | return loc; 232 | } 233 | 234 | /// Sculpt Tool /// 235 | 236 | FBrushStroke FSculptTool::GetStroke(UHeightMap* Map, FVector2D Center) const 237 | { 238 | // Get the outer bounds of the circle 239 | FIntRect bounds; 240 | bounds.Min.X = FMath::FloorToInt(Center.X - Size - Falloff); 241 | bounds.Max.X = FMath::CeilToInt(Center.X + Size + Falloff); 242 | bounds.Min.Y = FMath::FloorToInt(Center.Y - Size - Falloff); 243 | bounds.Max.Y = FMath::CeilToInt(Center.Y + Size + Falloff); 244 | 245 | // Keep the circle within the bounds of the heightmap 246 | if (bounds.Min.X < 0) 247 | { 248 | bounds.Min.X = 0; 249 | } 250 | if (bounds.Min.Y < 0) 251 | { 252 | bounds.Min.Y = 0; 253 | } 254 | if (bounds.Max.X > Map->GetWidthX()) 255 | { 256 | bounds.Max.X = Map->GetWidthX(); 257 | } 258 | if (bounds.Max.Y > Map->GetWidthY()) 259 | { 260 | bounds.Max.Y = Map->GetWidthY(); 261 | } 262 | 263 | // Invert the mask 264 | float inversion = 1.0f; 265 | if (Invert) 266 | { 267 | inversion = -1.0f; 268 | } 269 | 270 | // Calculate the brusk mask using the current brush 271 | FBrushStroke stroke(bounds); 272 | for (int x = bounds.Min.X; x < bounds.Max.X; ++x) 273 | { 274 | for (int y = bounds.Min.Y; y < bounds.Max.Y; ++y) 275 | { 276 | // Set the mask based on the current brush 277 | float dist = FVector2D::Distance(Center, FVector2D(x, y)); 278 | stroke.GetData(x, y) = Brush->GetStrength(dist, Size, Falloff) * inversion; 279 | } 280 | } 281 | 282 | return stroke; 283 | } 284 | 285 | FText FSculptTool::GetName() const 286 | { 287 | return LOCTEXT("TerrianToolSculptName", "Sculpt Terrain"); 288 | } 289 | 290 | FName FSculptTool::GetToolID() const 291 | { 292 | return ToolID; 293 | } 294 | 295 | TerrainToolID FSculptTool::GetID() const 296 | { 297 | return TerrainToolID::SCULPT; 298 | } 299 | 300 | /// Smooth Tool /// 301 | 302 | FBrushStroke FSmoothTool::GetStroke(UHeightMap* Map, FVector2D Center) const 303 | { 304 | // Get the outer bounds of the circle 305 | FIntRect bounds; 306 | bounds.Min.X = FMath::FloorToInt(Center.X - Size - Falloff); 307 | bounds.Max.X = FMath::CeilToInt(Center.X + Size + Falloff); 308 | bounds.Min.Y = FMath::FloorToInt(Center.Y - Size - Falloff); 309 | bounds.Max.Y = FMath::CeilToInt(Center.Y + Size + Falloff); 310 | 311 | // Keep the circle within the bounds of the heightmap 312 | if (bounds.Min.X < 1) 313 | { 314 | bounds.Min.X = 1; 315 | } 316 | if (bounds.Min.Y < 1) 317 | { 318 | bounds.Min.Y = 1; 319 | } 320 | if (bounds.Max.X > Map->GetWidthX() - 1) 321 | { 322 | bounds.Max.X = Map->GetWidthX() - 1; 323 | } 324 | if (bounds.Max.Y > Map->GetWidthY() - 1) 325 | { 326 | bounds.Max.Y = Map->GetWidthY() - 1; 327 | } 328 | 329 | // Invert the mask 330 | float inversion = 1.0f; 331 | if (Invert) 332 | { 333 | inversion = -1.0f; 334 | } 335 | 336 | // Calculate the brusk mask using the current brush 337 | FBrushStroke stroke(bounds); 338 | for (int x = bounds.Min.X; x < bounds.Max.X; ++x) 339 | { 340 | for (int y = bounds.Min.Y; y < bounds.Max.Y; ++y) 341 | { 342 | float dist = FVector2D::Distance(Center, FVector2D(x, y)); 343 | 344 | // Set the mask to the difference between the average height of the surrounding area and the current point 345 | float h00 = Map->GetHeight(x, y); 346 | float h01 = Map->GetHeight(x - 1, y); 347 | float h21 = Map->GetHeight(x + 1, y); 348 | float h10 = Map->GetHeight(x, y - 1); 349 | float h12 = Map->GetHeight(x, y + 1); 350 | 351 | float hdiff = (h01 + h21 + h10 + h12) / 4.0f - h00; 352 | 353 | stroke.GetData(x, y) = 20.0f * hdiff * Brush->GetStrength(dist, Size, Falloff) * inversion; 354 | } 355 | } 356 | 357 | return stroke; 358 | } 359 | 360 | FText FSmoothTool::GetName() const 361 | { 362 | return LOCTEXT("TerrianToolSmoothName", "Smooth Terrain"); 363 | } 364 | 365 | FName FSmoothTool::GetToolID() const 366 | { 367 | return ToolID; 368 | } 369 | 370 | TerrainToolID FSmoothTool::GetID() const 371 | { 372 | return TerrainToolID::SMOOTH; 373 | } 374 | 375 | /// Flatten Tool /// 376 | 377 | FBrushStroke FFlattenTool::GetStroke(UHeightMap* Map, FVector2D Center) const 378 | { 379 | // Get the outer bounds of the circle 380 | FIntRect bounds; 381 | bounds.Min.X = FMath::FloorToInt(Center.X - Size - Falloff); 382 | bounds.Max.X = FMath::CeilToInt(Center.X + Size + Falloff); 383 | bounds.Min.Y = FMath::FloorToInt(Center.Y - Size - Falloff); 384 | bounds.Max.Y = FMath::CeilToInt(Center.Y + Size + Falloff); 385 | 386 | // Keep the circle within the bounds of the heightmap 387 | if (bounds.Min.X < 0) 388 | { 389 | bounds.Min.X = 0; 390 | } 391 | if (bounds.Min.Y < 0) 392 | { 393 | bounds.Min.Y = 0; 394 | } 395 | if (bounds.Max.X > Map->GetWidthX()) 396 | { 397 | bounds.Max.X = Map->GetWidthX(); 398 | } 399 | if (bounds.Max.Y > Map->GetWidthY()) 400 | { 401 | bounds.Max.Y = Map->GetWidthY(); 402 | } 403 | 404 | // Invert the mask 405 | float inversion = 1.0f; 406 | if (Invert) 407 | { 408 | inversion = -1.0f; 409 | } 410 | 411 | // Get the height at the center point 412 | int X = (int)Center.X; 413 | int Y = (int)Center.Y; 414 | float DX = Center.X - X; 415 | float DY = Center.Y - Y; 416 | float height = FMath::Lerp( 417 | FMath::Lerp(Map->GetHeight(X, Y), Map->GetHeight(X + 1, Y), DX), 418 | FMath::Lerp(Map->GetHeight(X, Y + 1), Map->GetHeight(X + 1, Y + 1), DX), 419 | DY); 420 | 421 | // Calculate the brusk mask using the current brush 422 | FBrushStroke stroke(bounds); 423 | for (int x = bounds.Min.X; x < bounds.Max.X; ++x) 424 | { 425 | for (int y = bounds.Min.Y; y < bounds.Max.Y; ++y) 426 | { 427 | float dist = FVector2D::Distance(Center, FVector2D(x, y)); 428 | 429 | // Set the mask to the difference between the center height and the current point 430 | stroke.GetData(x, y) = (height - Map->GetHeight(x, y)) * Brush->GetStrength(dist, Size, Falloff) * inversion; 431 | } 432 | } 433 | 434 | return stroke; 435 | } 436 | 437 | FText FFlattenTool::GetName() const 438 | { 439 | return LOCTEXT("TerrianToolFlattenName", "Flatten Terrain"); 440 | } 441 | 442 | FName FFlattenTool::GetToolID() const 443 | { 444 | return ToolID; 445 | } 446 | 447 | TerrainToolID FFlattenTool::GetID() const 448 | { 449 | return TerrainToolID::FLATTEN; 450 | } 451 | 452 | /// Tool and Brush Sets /// 453 | 454 | FToolSet::FToolSet() 455 | { 456 | // Create tools 457 | Tools.SetNum((int)TerrainToolID::NUM); 458 | Tools[(int)TerrainToolID::SCULPT] = new FSculptTool; 459 | Tools[(int)TerrainToolID::SMOOTH] = new FSmoothTool; 460 | Tools[(int)TerrainToolID::FLATTEN] = new FFlattenTool; 461 | 462 | ActiveTool = Tools[0]; 463 | 464 | // Create brushes 465 | Brushes.SetNum((int)TerrainBrushID::NUM); 466 | Brushes[(int)TerrainBrushID::LINEAR] = new FBrushLinear; 467 | Brushes[(int)TerrainBrushID::SMOOTH] = new FBrushSmooth; 468 | Brushes[(int)TerrainBrushID::ROUND] = new FBrushRound; 469 | Brushes[(int)TerrainBrushID::SPHERE] = new FBrushSphere; 470 | 471 | ActiveBrush = Brushes[0]; 472 | ActiveTool->SetBrush(ActiveBrush); 473 | } 474 | 475 | FToolSet::~FToolSet() 476 | { 477 | for (int32 i = 0; i < Tools.Num(); ++i) 478 | { 479 | delete Tools[i]; 480 | } 481 | Tools.Empty(); 482 | 483 | for (int32 i = 0; i < Brushes.Num(); ++i) 484 | { 485 | delete Brushes[i]; 486 | } 487 | Brushes.Empty(); 488 | } 489 | 490 | void FToolSet::SetTool(TerrainToolID Tool) 491 | { 492 | if (Tool != TerrainToolID::NUM) 493 | { 494 | ActiveTool = Tools[(int)Tool]; 495 | ActiveTool->SetBrush(ActiveBrush); 496 | } 497 | } 498 | 499 | FTerrainTool* FToolSet::GetTool() 500 | { 501 | return ActiveTool; 502 | } 503 | 504 | TerrainToolID FToolSet::ToolID() 505 | { 506 | return ActiveTool->GetID(); 507 | } 508 | 509 | // Set the active brush 510 | void FToolSet::SetBrush(TerrainBrushID Brush) 511 | { 512 | if (Brush != TerrainBrushID::NUM) 513 | { 514 | ActiveBrush = Brushes[(int)Brush]; 515 | ActiveTool->SetBrush(ActiveBrush); 516 | } 517 | } 518 | // Get the active brush 519 | FTerrainBrush* FToolSet::GetBrush() 520 | { 521 | return ActiveBrush; 522 | } 523 | // Get the active brush's ID 524 | TerrainBrushID FToolSet::BrushID() 525 | { 526 | return ActiveBrush->GetID(); 527 | } 528 | 529 | #undef LOCTEXT_NAMESPACE -------------------------------------------------------------------------------- /Source/DynamicTerrain/Public/DynamicTerrain.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UserCodrus/DynamicTerrain/6f7da80d7dd1f3bf5baed83e92ae3d46bb0cbe32/Source/DynamicTerrain/Public/DynamicTerrain.h -------------------------------------------------------------------------------- /Source/DynamicTerrain/Public/Terrain.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UserCodrus/DynamicTerrain/6f7da80d7dd1f3bf5baed83e92ae3d46bb0cbe32/Source/DynamicTerrain/Public/Terrain.h -------------------------------------------------------------------------------- /Source/DynamicTerrain/Public/TerrainAlgorithms.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "CoreMinimal.h" 4 | 5 | // The base class for noise data 6 | class Noise 7 | { 8 | public: 9 | uint32 GetWidth() const; 10 | uint32 GetHeight() const; 11 | 12 | virtual void Scale(uint32 SampleWidth, uint32 SampleHeight) = 0; 13 | 14 | protected: 15 | uint32 Width = 0; 16 | uint32 Height = 0; 17 | 18 | float ScaleX = 0.0f; 19 | float ScaleY = 0.0f; 20 | }; 21 | 22 | // Noise generated by creating a grid of gradient vectors 23 | class GradientNoise : public Noise 24 | { 25 | public: 26 | GradientNoise(uint32 NewWidth, uint32 NewHeight, uint32 Seed); 27 | GradientNoise(const GradientNoise& Copy); 28 | virtual ~GradientNoise(); 29 | 30 | virtual void Scale(uint32 SampleWidth, uint32 SampleHeight) override; 31 | 32 | // Get the gradient at a given grid point 33 | FVector2D GetGradient(uint32 X, uint32 Y) const; 34 | 35 | // Get Perlin noise at the specified coordinate 36 | float Perlin(float X, float Y) const; 37 | 38 | protected: 39 | FVector2D* Gradient = nullptr; 40 | }; 41 | 42 | // Noise generated by creating a grid of random values 43 | class ValueNoise : public Noise 44 | { 45 | public: 46 | ValueNoise() {}; 47 | // Random value noise 48 | ValueNoise(uint32 NewWidth, uint32 NewHeight, uint32 Seed); 49 | // Generate noise with the diamond-square algorithm 50 | ValueNoise(uint32 Size, uint32 Seed); 51 | ValueNoise(const ValueNoise& Copy); 52 | virtual ~ValueNoise(); 53 | 54 | virtual void Scale(uint32 SampleWidth, uint32 SampleHeight) override; 55 | 56 | // Get the value at a given grid point 57 | float GetValue(uint32 X, uint32 Y) const; 58 | 59 | // Bilinear interpolated noise 60 | virtual float Linear(float X, float Y) const; 61 | // Cosine interpolated noise 62 | virtual float Cosine(float X, float Y) const; 63 | // Cubic interpolated noise 64 | virtual float Cubic(float X, float Y) const; 65 | 66 | protected: 67 | float* Value = nullptr; 68 | }; 69 | 70 | // Random points generated in a 2D space 71 | class PointNoise : public Noise 72 | { 73 | public: 74 | PointNoise() {}; 75 | // Generate noise in a rectangle 76 | PointNoise(uint32 XWidth, uint32 YWidth, uint32 NumPoints, uint32 Seed); 77 | // Generate noise in a cirlce 78 | PointNoise(uint32 Radius, uint32 NumPoints, uint32 Seed); 79 | virtual ~PointNoise() {}; 80 | 81 | virtual void Scale(uint32 SampleWidth, uint32 SampleHeight) override; 82 | 83 | // Get the nearest point to a given location 84 | virtual inline FVector2D GetNearest(FVector2D Location) const; 85 | // Get the distance from a given point to the nearest point 86 | virtual inline float GetNearestDistance(FVector2D Location) const; 87 | 88 | // Sample point noise at the given coordinates 89 | virtual float Dot(float X, float Y) const; 90 | // Sample raw Worley noise at the given coordinates 91 | virtual float Worley(float X, float Y) const; 92 | 93 | virtual const TArray& GetPoints(); 94 | 95 | protected: 96 | TArray Points; 97 | }; 98 | 99 | // Noise generated by plotting random points within each cell of a unit grid 100 | class UniformPointNoise : public PointNoise 101 | { 102 | public: 103 | UniformPointNoise(uint32 NewWidth, uint32 NewHeight, uint32 Seed); 104 | virtual ~UniformPointNoise() {}; 105 | 106 | virtual inline FVector2D GetNearest(FVector2D Location) const override; 107 | virtual inline float GetNearestDistance(FVector2D Location) const override; 108 | }; 109 | 110 | // Random points generated with Poisson disk sampling 111 | class PoissonPointNoise : public PointNoise 112 | { 113 | public: 114 | PoissonPointNoise() {}; 115 | // Generate random noise in a rectangle 116 | PoissonPointNoise(uint32 SpaceWidth, uint32 SpaceHeight, float SampleRadius, uint32 NumPoints, uint32 Seed); 117 | // Generate random noise in a circle 118 | PoissonPointNoise(uint32 SpaceRadius, float SampleRadius, uint32 NumPoints, uint32 Seed); 119 | // Fill a rectangle with random points 120 | PoissonPointNoise(uint32 SpaceWidth, uint32 SpaceHeight, float SampleRadius, uint32 Seed); 121 | // Fill a circle with random points 122 | PoissonPointNoise(uint32 SpaceRadius, float SampleRadius, uint32 Seed); 123 | virtual ~PoissonPointNoise() {}; 124 | 125 | // Check to see if the given point is far enough away from other points 126 | virtual inline bool CheckPoint(FVector2D Location, float SearchRadius) const; 127 | // Get the nearest point to a given point within a limited distance, returns an invalid point if no point is found 128 | virtual inline FVector2D GetNearestConstrained(FVector2D Location, float SearchRadius) const; 129 | // Get the distance from the nearest point to the given point within a limited radius 130 | virtual inline float GetNearestDistanceConstrained(FVector2D Location, float SearchRadius) const; 131 | 132 | protected: 133 | // Create the sorting grid 134 | void InitializeSortingGrid(float MinRadius); 135 | // Add a point to the sorting grid 136 | inline void SortPoint(int32 PointIndex); 137 | 138 | // The size of each cell in the sorting grid 139 | float GridBound; 140 | // The dimensions of the sorting grid 141 | uint32 GridWidth, GridHeight; 142 | // A grid to store points for nearest neighbor searches 143 | TArray SortingGrid; 144 | }; 145 | 146 | class UTerrainFoliage; 147 | struct FWeightedFoliage; 148 | 149 | // Poisson sampled points sized according to a set of foliage 150 | class FoliagePoissonNoise : public PoissonPointNoise 151 | { 152 | public: 153 | // Generate random points in a rectangle 154 | FoliagePoissonNoise(TArray FoliageSet, uint32 SpaceWidth, uint32 SpaceHeight, uint32 NumPoints, uint32 Seed); 155 | virtual ~FoliagePoissonNoise() {}; 156 | 157 | // Check the placement of a random foliage 158 | virtual inline bool CheckFoliagePoint(FVector2D Location, UTerrainFoliage* FoliageSample) const; 159 | 160 | protected: 161 | // Pick a random foliage asset 162 | inline UTerrainFoliage* GetRandomFoliage(uint32 Seed) const; 163 | 164 | // The largest shade radius 165 | float MaxShade; 166 | // The largest safe radius 167 | float MaxRadius; 168 | // The foliage used to generate foliage 169 | TArray Foliage; 170 | // The foliage used for each point 171 | TArray FoliageInstances; 172 | }; -------------------------------------------------------------------------------- /Source/DynamicTerrain/Public/TerrainBrushDecal.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "TerrainTools.h" 4 | 5 | #include "CoreMinimal.h" 6 | #include "Components/DecalComponent.h" 7 | 8 | #include "TerrainBrushDecal.generated.h" 9 | 10 | UCLASS(BlueprintType, Blueprintable, ClassGroup = (Custom), meta = (BlueprintSpawnableComponent)) 11 | class DYNAMICTERRAIN_API UBrushDecal : public UDecalComponent 12 | { 13 | GENERATED_BODY() 14 | 15 | public: 16 | UBrushDecal(); 17 | 18 | virtual void BeginPlay() override; 19 | 20 | // Change the size of the cursor 21 | void Resize(FTerrainTool* Tool, ATerrain* Terrain); 22 | // Change the color of the cursor 23 | void ChangeColor(FColor Color); 24 | // Create a material instance for the decal 25 | void CreateMaterialInstance(); 26 | 27 | protected: 28 | // The default material used for the tool brushes 29 | UPROPERTY(Transient) 30 | UMaterial* BrushMaterial; 31 | // The material instance for the brush material 32 | UPROPERTY(Transient) 33 | UMaterialInstanceDynamic* BrushInstance; 34 | }; -------------------------------------------------------------------------------- /Source/DynamicTerrain/Public/TerrainComponent.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "TerrainHeightMap.h" 4 | 5 | #include "CoreMinimal.h" 6 | #include "Components/MeshComponent.h" 7 | #include "Interfaces/Interface_CollisionDataProvider.h" 8 | 9 | #include "TerrainComponent.generated.h" 10 | 11 | class ATerrain; 12 | 13 | UCLASS(HideCategories = (Object, LOD, Physics), EditInlineNew, ClassGroup = Rendering) 14 | class DYNAMICTERRAIN_API UTerrainComponent : public UMeshComponent, public IInterface_CollisionDataProvider 15 | { 16 | GENERATED_BODY() 17 | 18 | /// Mesh Component Interface /// 19 | 20 | public: 21 | UTerrainComponent(const FObjectInitializer& ObjectInitializer); 22 | 23 | virtual FPrimitiveSceneProxy* CreateSceneProxy() override; 24 | virtual UBodySetup* GetBodySetup() override; 25 | virtual int32 GetNumMaterials() const override; 26 | 27 | virtual bool GetPhysicsTriMeshData(struct FTriMeshCollisionData* CollisionData, bool InUseAllTriData) override; 28 | virtual bool ContainsPhysicsTriMeshData(bool InUseAllTriData) const override { return true; } 29 | virtual bool WantsNegXTriMesh() override { return false; } 30 | 31 | private: 32 | virtual FBoxSphereBounds CalcBounds(const FTransform& LocalToWorld) const override; 33 | 34 | /// Terrain Interface /// 35 | 36 | public: 37 | // Initialize the component 38 | void Initialize(ATerrain* Terrain, TSharedPtr Proxy, int32 X, int32 Y); 39 | // Initialize mesh data 40 | void CreateMeshData(); 41 | 42 | // Set the size of the component 43 | void SetSize(uint32 NewSize); 44 | // Get the current component size 45 | inline uint32 GetSize(); 46 | // Set component tiling 47 | void SetTiling(float NewTiling); 48 | // Set LOD levels and scaling 49 | void SetLODs(int32 NumLODs, float DistanceScale); 50 | // Update rendering data from a heightmap section 51 | void Update(TSharedPtr NewSection); 52 | 53 | // Get the map data for this section 54 | TSharedPtr GetMapProxy(); 55 | // Set the map data for this section 56 | void SetMapProxy(TSharedPtr Proxy); 57 | 58 | // Set to true to cook collision off the main thread 59 | UPROPERTY() 60 | bool AsyncCooking; 61 | 62 | private: 63 | // Verify that the map proxy exists 64 | void VerifyMapProxy(); 65 | 66 | // Update collision data 67 | void UpdateCollision(); 68 | // Finish asynchronous collision cooking 69 | void FinishCollision(bool Success, UBodySetup* NewBodySetup); 70 | // Create a collision body 71 | UBodySetup* CreateBodySetup(); 72 | 73 | // The mesh indices 74 | UPROPERTY(VisibleAnywhere) 75 | TArray IndexBuffer; 76 | // The mesh vertices 77 | UPROPERTY(VisibleAnywhere) 78 | TArray Vertices; 79 | 80 | // The size of the component 81 | UPROPERTY(VisibleAnywhere) 82 | uint32 Size; 83 | // The offset of the component on the X axis 84 | UPROPERTY(VisibleAnywhere) 85 | int32 XOffset; 86 | // The offset of the component on the Y axis 87 | UPROPERTY(VisibleAnywhere) 88 | int32 YOffset; 89 | // The UV Tiling of the component 90 | UPROPERTY(VisibleAnywhere) 91 | float Tiling; 92 | // The number of LODs the component uses 93 | UPROPERTY(VisibleAnywhere) 94 | uint32 LODs; 95 | // The scaling factor for LOD transitions 96 | UPROPERTY(VisibleAnywhere) 97 | float LODScale; 98 | 99 | // The collision body for the object 100 | UPROPERTY(Instanced) 101 | UBodySetup* BodySetup; 102 | // Queue of body setups that are being cooked asynchronously 103 | UPROPERTY(Transient) 104 | TArray BodySetupQueue; 105 | 106 | // The render data for the terrain component 107 | TSharedPtr MapProxy; 108 | 109 | friend class FTerrainComponentSceneProxy; 110 | }; -------------------------------------------------------------------------------- /Source/DynamicTerrain/Public/TerrainFoliage.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "CoreMinimal.h" 4 | 5 | #include "TerrainFoliage.generated.h" 6 | 7 | class ATerrain; 8 | class UHierarchicalInstancedStaticMeshComponent; 9 | 10 | UCLASS(BlueprintType) 11 | class DYNAMICTERRAIN_API UTerrainFoliage : public UObject 12 | { 13 | GENERATED_BODY() 14 | 15 | public: 16 | // The mesh that will be generated 17 | UPROPERTY(EditAnywhere, Category = "Mesh") 18 | UStaticMesh* Mesh = nullptr; 19 | 20 | // If set to true this foliage will ignore the shade distance and use safe distance 21 | UPROPERTY(EditAnywhere, Category = "Spacing") 22 | bool GrowInShade = false; 23 | // The minimum safe space between this mesh and other meshes 24 | UPROPERTY(EditAnywhere, Category = "Spacing") 25 | float SafeDistance = 100.0f; 26 | // The safe distance between this mesh and meshes that grow outside shade 27 | UPROPERTY(EditAnywhere, Category = "Spacing") 28 | float ShadeDistance = 200.0f; 29 | 30 | // The minimum elevation this mesh will generate at 31 | UPROPERTY(EditAnywhere, Category = "Placement") 32 | float MinElevation = -200000.0f; 33 | // The maximum elevation this mesh will generate at 34 | UPROPERTY(EditAnywhere, Category = "Placement") 35 | float MaxElevation = 200000.0f; 36 | 37 | // If true the foliage will line up with the slope of the terrain 38 | UPROPERTY(EditAnywhere, Category = "Placement") 39 | bool AlignToNormal = true; 40 | // Set to true to rotate the mesh randomly 41 | UPROPERTY(EditAnywhere, Category = "Placement") 42 | bool RandomRotation = true; 43 | }; 44 | 45 | USTRUCT(BlueprintType) 46 | struct DYNAMICTERRAIN_API FWeightedFoliage 47 | { 48 | GENERATED_BODY() 49 | 50 | // The foliage asset that will be generated 51 | UPROPERTY(EditAnywhere) 52 | UTerrainFoliage* Asset = nullptr; 53 | // The odds that this foliage will appear in a cluster 54 | UPROPERTY(EditAnywhere) 55 | uint32 Weight = 1; 56 | }; 57 | 58 | UCLASS(BlueprintType) 59 | class DYNAMICTERRAIN_API UTerrainFoliageSpawner : public UObject 60 | { 61 | GENERATED_BODY() 62 | 63 | public: 64 | // The static meshes used by this foliage group 65 | UPROPERTY(EditAnywhere, Category = "Foliage") 66 | TArray Foliage; 67 | 68 | // The minimum number of meshes in each cluster 69 | UPROPERTY(EditAnywhere, Category = "Clustering") 70 | uint32 ClusterMin = 1; 71 | // The maximum number of meshes in each cluster 72 | UPROPERTY(EditAnywhere, Category = "Clustering") 73 | uint32 ClusterMax = 1; 74 | // The radius of clusters in world space units 75 | UPROPERTY(EditAnywhere, Category = "Clustering") 76 | float Radius = 500.0f; 77 | // If true each mesh in a given cluster will all be the same, otherwise they will be random 78 | UPROPERTY(EditAnywhere, Category = "Clustering") 79 | bool MatchClusters = true; 80 | 81 | // Create a cluster of foliage at the designated location in world space 82 | void AddFoliageCluster(ATerrain* Terrain, FVector Location, uint32 Seed) const; 83 | // Create a single piece of foliage at the designated location in world space 84 | void AddFoliageUnit(ATerrain* Terrain, FVector Location, uint32 Seed) const; 85 | 86 | protected: 87 | // Pick a random foliage asset from the spawner 88 | UTerrainFoliage* GetRandomFoliage(uint32 Seed) const; 89 | }; -------------------------------------------------------------------------------- /Source/DynamicTerrain/Public/TerrainGenerator.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "TerrainHeightMap.h" 4 | 5 | #include "CoreMinimal.h" 6 | 7 | #include "TerrainGenerator.generated.h" 8 | 9 | class ATerrain; 10 | 11 | UCLASS() 12 | class DYNAMICTERRAIN_API UMapGenerator : public UObject 13 | { 14 | GENERATED_BODY() 15 | 16 | public: 17 | /// Map Generator Functions /// 18 | 19 | UPROPERTY() 20 | ATerrain* Terrain = nullptr; 21 | UPROPERTY(EditAnywhere) 22 | uint32 Seed = 0; 23 | 24 | // Generate a new seed for RNG 25 | void NewSeed(); 26 | // Set the RNG seed 27 | void SetSeed(int32 NewSeed); 28 | 29 | // Flatten the heightmap 30 | UFUNCTION(BlueprintCallable) 31 | void Flat(float Height); 32 | 33 | // Generate a map using plasma noise 34 | UFUNCTION(BlueprintCallable) 35 | void Plasma( 36 | UPARAM(meta = (Default = 4)) int32 Scale, 37 | UPARAM(meta = (Default = 5)) int32 Foliage, 38 | UPARAM(meta = (Default = 256)) float MaxHeight); 39 | 40 | // Generate a map using multiple layers of perlin noise 41 | UFUNCTION(BlueprintCallable) 42 | void Perlin( 43 | UPARAM(meta = (Default = 2)) int32 Frequency, 44 | UPARAM(meta = (Default = 3)) int32 Octaves, 45 | UPARAM(meta = (Default = 0.5f)) float Persistence, 46 | UPARAM(meta = (Default = 256)) float MaxHeight); 47 | 48 | UFUNCTION(BlueprintCallable) 49 | void TestGenerator( 50 | UPARAM(meta = (Default = 2)) int32 BaseFrequency, 51 | UPARAM(meta = (Default = 8)) int32 ElevationFrequency, 52 | UPARAM(meta = (Default = 50)) int32 DetailFrequency, 53 | UPARAM(meta = (Default = 256)) float MaxHeight); 54 | 55 | protected: 56 | /// Map Generator Components /// 57 | 58 | // Generate a flat map at a fixed height 59 | void MapFlat(float Height); 60 | // Generate a heightmap using plasma noise 61 | void MapPlasma(int32 Scale, float MaxHeight); 62 | // Generate a heightmap using perlin noise 63 | void MapPerlin(int32 Frequency, int32 Octaves, float Persistence, float MaxHeight); 64 | 65 | // Generate random foliage on the terrain 66 | void FoliageRandom(uint32 NumPoints); 67 | // Generate foliage evenly distributed around the map 68 | void FoliageUniform(uint32 XPoints, uint32 YPoints); 69 | }; -------------------------------------------------------------------------------- /Source/DynamicTerrain/Public/TerrainHeightMap.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "CoreMinimal.h" 4 | 5 | #include "TerrainHeightMap.generated.h" 6 | 7 | struct FMapSection 8 | { 9 | TArray Data; 10 | int32 X = 0; 11 | int32 Y = 0; 12 | 13 | FMapSection() {}; 14 | FMapSection(int32 XWidth, int32 YWidth) 15 | { 16 | X = XWidth; 17 | Y = YWidth; 18 | Data.SetNumZeroed(X * Y); 19 | } 20 | }; 21 | 22 | UCLASS() 23 | class DYNAMICTERRAIN_API UHeightMap : public UObject 24 | { 25 | GENERATED_BODY() 26 | 27 | public: 28 | /// Blueprint Functions /// 29 | 30 | // Resize the heightmap 31 | // X, Y = The width of the heightmap 32 | UFUNCTION(BlueprintCallable) 33 | void Resize(int32 X, int32 Y); 34 | 35 | // Get the value of the heightmap at the specified coordinates 36 | UFUNCTION(BlueprintPure) 37 | float BPGetHeight(int32 X, int32 Y) const; 38 | 39 | /// Native Functions /// 40 | 41 | // Get a copy of a portion of the map 42 | inline void GetMapSection(FMapSection* Section, FIntPoint Min); 43 | 44 | // Get the height at a given vertex 45 | inline float GetHeight(uint32 X, uint32 Y) const; 46 | // Get the height of the map at a given point 47 | inline float GetLinearHeight(float X, float Y) const; 48 | // Get the normal of a given vertex 49 | inline FVector GetNormal(uint32 X, uint32 Y) const; 50 | // Get the normal of the map at a given point 51 | inline FVector GetLinearNormal(float X, float Y) const; 52 | // Get the X tangent at a given vertex 53 | inline FVector GetTangent(uint32 X, uint32 Y) const; 54 | // Get the X tangent of the map at a given point 55 | inline FVector GetLinearTangent(float X, float Y) const; 56 | // Set the height of the heightmap at the given vertex 57 | inline void SetHeight(uint32 X, uint32 Y, float Height); 58 | 59 | inline int32 GetWidthX() const; 60 | inline int32 GetWidthY() const; 61 | 62 | protected: 63 | // The height data for the map 64 | UPROPERTY(VisibleAnywhere) 65 | TArray MapData; 66 | 67 | // The dimensions of the heightmap 68 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly) 69 | int32 WidthX = 0; 70 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly) 71 | int32 WidthY = 0; 72 | }; -------------------------------------------------------------------------------- /Source/DynamicTerrain/Public/TerrainToolComponent.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "TerrainTools.h" 4 | #include "TerrainBrushDecal.h" 5 | 6 | #include "Components/DecalComponent.h" 7 | 8 | #include "TerrainToolComponent.generated.h" 9 | 10 | UCLASS(BlueprintType, Blueprintable, ClassGroup = (Custom), meta = (BlueprintSpawnableComponent)) 11 | class DYNAMICTERRAIN_API UTerrainToolComponent : public USceneComponent 12 | { 13 | GENERATED_BODY() 14 | 15 | public: 16 | UTerrainToolComponent(); 17 | 18 | /// USceneComponent Interface /// 19 | 20 | virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; 21 | 22 | /// Terrain Tool Component Interface /// 23 | 24 | // Change the current tool 25 | UFUNCTION(BlueprintCallable) 26 | void SelectTool(TerrainToolID ToolID); 27 | // Change the current brush 28 | UFUNCTION(BlueprintCallable) 29 | void SelectBrush(TerrainBrushID BrushID); 30 | 31 | // Switch to the next tool in the toolkit 32 | UFUNCTION(BlueprintCallable) 33 | void NextTool(); 34 | // Switch to the previous brush in the toolkit 35 | UFUNCTION(BlueprintCallable) 36 | void PreviousTool(); 37 | // Switch to the next brush in the toolkit 38 | UFUNCTION(BlueprintCallable) 39 | void NextBrush(); 40 | // Switch to the previous brush in the toolkit 41 | UFUNCTION(BlueprintCallable) 42 | void PreviousBrush(); 43 | 44 | // Get the name of the currently selected tool 45 | UFUNCTION(BlueprintPure) 46 | FText GetToolName(); 47 | // Get the name of the currently selected brush 48 | UFUNCTION(BlueprintPure) 49 | FText GetBrushName(); 50 | // Get the brush decal 51 | UFUNCTION(BlueprintPure) 52 | UBrushDecal* GetBrushDecal(); 53 | 54 | // Set to true when the current tool is in use 55 | UPROPERTY(EditInstanceOnly, BlueprintReadWrite, Category = "Status") 56 | bool ToolActive = false; 57 | // Set to true to invert the terrain tool 58 | UPROPERTY(EditInstanceOnly, BlueprintReadWrite, Category = "Status") 59 | bool ToolInvert = false; 60 | // Set to true to enable the tool and show the brush 61 | UPROPERTY(EditInstanceOnly, BlueprintReadWrite, Category = "Status") 62 | bool ToolEnabled = true; 63 | 64 | // The maximum distance that the brush will be usable from 65 | UPROPERTY(EditAnywhere, Category = "Settings") 66 | float MaxBrushDistance = 50000.0f; 67 | // If set to true the brush cursor will disappear when it isn't over a valid terrain 68 | // If false the brush color will change when its position is invalid 69 | UPROPERTY(EditAnywhere, Category = "Settings") 70 | bool HideInvalidBrush = false; 71 | // The color of the brush when it is usable 72 | UPROPERTY(EditAnywhere, Category = "Settings") 73 | FColor BrushColor; 74 | // The color of the brush when it isn't touching a valid terrain 75 | UPROPERTY(EditAnywhere, Category = "Settings") 76 | FColor BrushInvalidColor; 77 | 78 | protected: 79 | // The decal component that shows the terrain tool's brush 80 | UPROPERTY(EditAnywhere, Category = "Brush") 81 | UBrushDecal* BrushDecal; 82 | 83 | // The tools used to manipulate terrain 84 | FToolSet TerrainTools; 85 | }; -------------------------------------------------------------------------------- /Source/DynamicTerrain/Public/TerrainTools.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "CoreMinimal.h" 4 | #include "Terrain.h" 5 | 6 | UENUM(BlueprintType) 7 | enum class TerrainBrushID : uint8 8 | { 9 | LINEAR, 10 | SMOOTH, 11 | ROUND, 12 | SPHERE, 13 | NUM 14 | }; 15 | 16 | UENUM(BlueprintType) 17 | enum class TerrainToolID : uint8 18 | { 19 | SCULPT, 20 | SMOOTH, 21 | FLATTEN, 22 | NUM 23 | }; 24 | 25 | /// Terrain Brushes /// 26 | 27 | class DYNAMICTERRAIN_API FTerrainBrush 28 | { 29 | public: 30 | virtual ~FTerrainBrush() {}; 31 | 32 | // Get the name of the brush for the editor UI 33 | virtual FText GetName() const = 0; 34 | 35 | // Get the ID value of the brush to identify it easily 36 | virtual TerrainBrushID GetID() const = 0; 37 | 38 | // Get the falloff of the brush 39 | virtual float GetStrength(float Distance, float Radius, float Falloff) const = 0; 40 | }; 41 | 42 | // A brush with a sharp, linear shape 43 | class DYNAMICTERRAIN_API FBrushLinear : public FTerrainBrush 44 | { 45 | public: 46 | virtual FText GetName() const override; 47 | virtual TerrainBrushID GetID() const; 48 | virtual float GetStrength(float Distance, float Radius, float Falloff) const override; 49 | }; 50 | 51 | // A brush with a steep but smooth shape 52 | class DYNAMICTERRAIN_API FBrushSmooth : public FTerrainBrush 53 | { 54 | public: 55 | virtual FText GetName() const override; 56 | virtual TerrainBrushID GetID() const; 57 | virtual float GetStrength(float Distance, float Radius, float Falloff) const override; 58 | }; 59 | 60 | // A brush with a smooth, gradually rounded shape 61 | class DYNAMICTERRAIN_API FBrushRound : public FTerrainBrush 62 | { 63 | public: 64 | virtual FText GetName() const override; 65 | virtual TerrainBrushID GetID() const; 66 | virtual float GetStrength(float Distance, float Radius, float Falloff) const override; 67 | }; 68 | 69 | // A brush with a spherical shape 70 | class DYNAMICTERRAIN_API FBrushSphere : public FTerrainBrush 71 | { 72 | public: 73 | virtual FText GetName() const override; 74 | virtual TerrainBrushID GetID() const; 75 | virtual float GetStrength(float Distance, float Radius, float Falloff) const override; 76 | }; 77 | 78 | class DYNAMICTERRAIN_API FBrushStroke 79 | { 80 | public: 81 | FBrushStroke() : Bounds() {} 82 | FBrushStroke(FIntRect StrokeBounds) : Bounds(StrokeBounds) 83 | { 84 | Mask.SetNumZeroed(Bounds.Area()); 85 | } 86 | 87 | FIntRect GetBounds() 88 | { 89 | return Bounds; 90 | } 91 | 92 | float& GetData(int X, int Y) 93 | { 94 | return Mask[(Y - Bounds.Min.Y) * Bounds.Width() + (X - Bounds.Min.X)]; 95 | } 96 | 97 | protected: 98 | FIntRect Bounds; // The boundaries of the mask within its parent heightmap 99 | TArray Mask; // The alpha mask of the brush 100 | }; 101 | 102 | /// Terrain Tools /// 103 | 104 | class DYNAMICTERRAIN_API FTerrainTool 105 | { 106 | public: 107 | virtual ~FTerrainTool() {}; 108 | 109 | // Retrive the name of the tool for the editor UI 110 | virtual FText GetName() const = 0; 111 | // Retrieve the tool's internal name 112 | virtual FName GetToolID() const = 0; 113 | // Retrieve the tool's ID value 114 | virtual TerrainToolID GetID() const = 0; 115 | 116 | // Select a brush 117 | virtual void SetBrush(FTerrainBrush* NewBrush); 118 | 119 | // Apply the tool to a terrain 120 | virtual void Apply(ATerrain* Terrain, FVector Center, float Delta) const; 121 | // Apply the tool directly to a heightmap 122 | virtual void Apply(UHeightMap* Map, FVector2D Center, float Delta) const; 123 | 124 | // Get the location of the mouse cursor on the terrain 125 | bool MouseToTerrainPosition(ATerrain* Terrain, const FSceneView* View, FHitResult& Result) const; 126 | bool MouseToTerrainPosition(ATerrain* Terrain, const APlayerController* Controller, FHitResult& Result) const; 127 | 128 | // Convert a world vector to heightmap coordinates 129 | FVector2D WorldVectorToMapVector(ATerrain* Terrain, FVector WorldPosition) const; 130 | 131 | float Size = 10.0f; // The radius of the tool circle 132 | float Strength = 5.0f; // The strength of the tool 133 | float Falloff = 5.0f; // The distance from the center that the strength begins to fall 134 | 135 | bool Invert = false; // Set to true to invert the effect of the tool 136 | 137 | static float TraceDistance; // The distance to check for the mouse cursor touching the terrain 138 | 139 | protected: 140 | // Calculate a brush mask using the currently selected brush 141 | virtual FBrushStroke GetStroke(UHeightMap* Map, FVector2D Center) const = 0; 142 | 143 | FTerrainBrush* Brush = nullptr; // The currently selected brush 144 | }; 145 | 146 | // A tool for sculpting the terrain 147 | class DYNAMICTERRAIN_API FSculptTool : public FTerrainTool 148 | { 149 | public: 150 | virtual FBrushStroke GetStroke(UHeightMap* Map, FVector2D Center) const override; 151 | 152 | virtual FText GetName() const override; 153 | virtual FName GetToolID() const; 154 | virtual TerrainToolID GetID() const; 155 | 156 | const static FName ToolID; 157 | }; 158 | 159 | // A tool for smoothing terrain 160 | class DYNAMICTERRAIN_API FSmoothTool : public FTerrainTool 161 | { 162 | public: 163 | virtual FBrushStroke GetStroke(UHeightMap* Map, FVector2D Center) const override; 164 | 165 | virtual FText GetName() const override; 166 | virtual FName GetToolID() const; 167 | virtual TerrainToolID GetID() const; 168 | 169 | const static FName ToolID; 170 | }; 171 | 172 | // A tool for flattening terrain 173 | class DYNAMICTERRAIN_API FFlattenTool : public FTerrainTool 174 | { 175 | public: 176 | virtual FBrushStroke GetStroke(UHeightMap* Map, FVector2D Center) const override; 177 | 178 | virtual FText GetName() const override; 179 | virtual FName GetToolID() const; 180 | virtual TerrainToolID GetID() const; 181 | 182 | const static FName ToolID; 183 | }; 184 | 185 | /// Tool and Brush Sets /// 186 | 187 | class DYNAMICTERRAIN_API FToolSet 188 | { 189 | public: 190 | FToolSet(); 191 | ~FToolSet(); 192 | 193 | // Set the active tool 194 | void SetTool(TerrainToolID Tool); 195 | // Get the active tool 196 | FTerrainTool* GetTool(); 197 | // Get the active tool's ID 198 | TerrainToolID ToolID(); 199 | 200 | // Set the active brush 201 | void SetBrush(TerrainBrushID Brush); 202 | // Get the active brush 203 | FTerrainBrush* GetBrush(); 204 | // Get the active brush's ID 205 | TerrainBrushID BrushID(); 206 | 207 | private: 208 | TArray Tools; 209 | FTerrainTool* ActiveTool; 210 | 211 | TArray Brushes; 212 | FTerrainBrush* ActiveBrush; 213 | }; -------------------------------------------------------------------------------- /Source/DynamicTerrainEditor/DynamicTerrainEditor.Build.cs: -------------------------------------------------------------------------------- 1 | using UnrealBuildTool; 2 | 3 | public class DynamicTerrainEditor : ModuleRules 4 | { 5 | public DynamicTerrainEditor(ReadOnlyTargetRules Target) : base(Target) 6 | { 7 | PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; 8 | 9 | PublicIncludePaths.AddRange( 10 | new string[] { 11 | // ... add public include paths required here ... 12 | } 13 | ); 14 | 15 | 16 | PrivateIncludePaths.AddRange( 17 | new string[] { 18 | // ... add other private include paths required here ... 19 | } 20 | ); 21 | 22 | 23 | PublicDependencyModuleNames.AddRange( 24 | new string[] 25 | { 26 | "Core", 27 | "InputCore", 28 | "PropertyEditor", 29 | "DynamicTerrain" 30 | // ... add other public dependencies that you statically link with here ... 31 | } 32 | ); 33 | 34 | 35 | PrivateDependencyModuleNames.AddRange( 36 | new string[] 37 | { 38 | "CoreUObject", 39 | "Engine", 40 | "UnrealEd", 41 | "Slate", 42 | "SlateCore", 43 | "Projects" 44 | // ... add private dependencies that you statically link with here ... 45 | } 46 | ); 47 | 48 | 49 | DynamicallyLoadedModuleNames.AddRange( 50 | new string[] 51 | { 52 | // ... add any modules that your module loads dynamically here ... 53 | } 54 | ); 55 | } 56 | } -------------------------------------------------------------------------------- /Source/DynamicTerrainEditor/Private/DynamicTerrainAssets.cpp: -------------------------------------------------------------------------------- 1 | #include "DynamicTerrainAssets.h" 2 | 3 | #include "TerrainFoliage.h" 4 | 5 | #define LOCTEXT_NAMESPACE "DynamicTerrainAssets" 6 | 7 | /// UTerrainFoliageSpawnerFactory /// 8 | 9 | UTerrainFoliageSpawnerFactory::UTerrainFoliageSpawnerFactory(const FObjectInitializer& ObjectInitializer) 10 | { 11 | bCreateNew = true; 12 | bEditAfterNew = true; 13 | SupportedClass = UTerrainFoliageSpawner::StaticClass(); 14 | } 15 | 16 | UObject* UTerrainFoliageSpawnerFactory::FactoryCreateNew(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) 17 | { 18 | return NewObject(InParent, InClass, InName, Flags); 19 | } 20 | 21 | /// UTerrainFoliageFactory /// 22 | 23 | UTerrainFoliageFactory::UTerrainFoliageFactory(const FObjectInitializer& ObjectInitializer) 24 | { 25 | bCreateNew = true; 26 | bEditAfterNew = true; 27 | SupportedClass = UTerrainFoliage::StaticClass(); 28 | } 29 | 30 | UObject* UTerrainFoliageFactory::FactoryCreateNew(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) 31 | { 32 | return NewObject(InParent, InClass, InName, Flags); 33 | } 34 | 35 | /// FAssetTypeActions_TerrainFoliageSpawnerFactory /// 36 | 37 | FText FAssetTypeActions_TerrainFoliageSpawnerFactory::GetName() const 38 | { 39 | return LOCTEXT("DynamicTerrainFoliageSpawnerAssetName", "Dynamic Terrain Foliage Spawner"); 40 | } 41 | 42 | FColor FAssetTypeActions_TerrainFoliageSpawnerFactory::GetTypeColor() const 43 | { 44 | return FColor::Green; 45 | } 46 | 47 | UClass* FAssetTypeActions_TerrainFoliageSpawnerFactory::GetSupportedClass() const 48 | { 49 | return UTerrainFoliageSpawner::StaticClass(); 50 | } 51 | 52 | uint32 FAssetTypeActions_TerrainFoliageSpawnerFactory::GetCategories() 53 | { 54 | return TerrainAssetCategory; 55 | } 56 | 57 | /// FAssetTypeActions_TerrainFoliageFactory /// 58 | 59 | FText FAssetTypeActions_TerrainFoliageFactory::GetName() const 60 | { 61 | return LOCTEXT("DynamicTerrainFoliageAssetName", "Dynamic Terrain Foliage"); 62 | } 63 | 64 | FColor FAssetTypeActions_TerrainFoliageFactory::GetTypeColor() const 65 | { 66 | return FColor::Green; 67 | } 68 | 69 | UClass* FAssetTypeActions_TerrainFoliageFactory::GetSupportedClass() const 70 | { 71 | return UTerrainFoliage::StaticClass(); 72 | } 73 | 74 | uint32 FAssetTypeActions_TerrainFoliageFactory::GetCategories() 75 | { 76 | return TerrainAssetCategory; 77 | } 78 | 79 | #undef LOCTEXT_NAMESPACE -------------------------------------------------------------------------------- /Source/DynamicTerrainEditor/Private/DynamicTerrainEditor.cpp: -------------------------------------------------------------------------------- 1 | #include "DynamicTerrainEditor.h" 2 | #include "DynamicTerrainMode.h" 3 | #include "DynamicTerrainStyle.h" 4 | #include "DynamicTerrainInterface.h" 5 | #include "DynamicTerrainAssets.h" 6 | 7 | IMPLEMENT_MODULE(FDynamicTerrainEditorModule, DynamicTerrainEditor); 8 | 9 | #define LOCTEXT_NAMESPACE "DynamicTerrainEditorModule" 10 | 11 | void FDynamicTerrainEditorModule::StartupModule() 12 | { 13 | FDynamicTerrainStyle::Initialize(); 14 | 15 | // Register the custom class layout for the terrain settings panel 16 | FPropertyEditorModule& property_editor = FModuleManager::LoadModuleChecked("PropertyEditor"); 17 | property_editor.RegisterCustomClassLayout("DynamicTerrainSettings", FOnGetDetailCustomizationInstance::CreateStatic(&FDynamicTerrainDetails::CreateInstance)); 18 | property_editor.NotifyCustomizationModuleChanged(); 19 | 20 | // Register the foliage asset factory 21 | IAssetTools& asset_tools = FModuleManager::LoadModuleChecked("AssetTools").Get(); 22 | TerrainAssetCategory = asset_tools.RegisterAdvancedAssetCategory(FName("TerrainAssetCategory"), LOCTEXT("AssetCategoryName", "Terrain")); 23 | TSharedRef action = MakeShareable(new FAssetTypeActions_TerrainFoliageSpawnerFactory()); 24 | asset_tools.RegisterAssetTypeActions(action); 25 | action = MakeShareable(new FAssetTypeActions_TerrainFoliageFactory()); 26 | asset_tools.RegisterAssetTypeActions(action); 27 | 28 | // Register the terrain editor mode 29 | FEditorModeRegistry::Get().RegisterMode(FDynamicTerrainMode::DynamicTerrainModeID, LOCTEXT("DynamicTerrainModeName", "Terrain Editor"), FSlateIcon(FDynamicTerrainStyle::Get()->GetStyleSetName(), "Plugins.Tab"), true); 30 | } 31 | 32 | void FDynamicTerrainEditorModule::ShutdownModule() 33 | { 34 | FEditorModeRegistry::Get().UnregisterMode(FDynamicTerrainMode::DynamicTerrainModeID); 35 | 36 | // Remove the custom class layout 37 | FPropertyEditorModule& property_editor = FModuleManager::LoadModuleChecked("PropertyEditor"); 38 | property_editor.UnregisterCustomClassLayout("DynamicTerrainSettings"); 39 | 40 | // Remove new asset types 41 | /*IAssetTools& asset_tools = FModuleManager::LoadModuleChecked("AssetTools").Get(); 42 | TWeakPtr action = asset_tools.GetAssetTypeActionsForClass(UTerrainFoliageSpawner::StaticClass()); 43 | if (action.IsValid()) 44 | { 45 | asset_tools.UnregisterAssetTypeActions(action.Pin().ToSharedRef()); 46 | }*/ 47 | 48 | FDynamicTerrainStyle::Shutdown(); 49 | } 50 | 51 | #undef LOCTEXT_NAMESPACE -------------------------------------------------------------------------------- /Source/DynamicTerrainEditor/Private/DynamicTerrainInterface.cpp: -------------------------------------------------------------------------------- 1 | #include "DynamicTerrainInterface.h" 2 | 3 | #include "DynamicTerrainStyle.h" 4 | #include "TerrainTools.h" 5 | 6 | #include "Editor.h" 7 | #include "EditorModeManager.h" 8 | #include "DetailLayoutBuilder.h" 9 | #include "DetailCategoryBuilder.h" 10 | #include "DetailWidgetRow.h" 11 | #include "Framework/MultiBox/MultiBoxBuilder.h" 12 | #include "Widgets/Input/SButton.h" 13 | #include "PropertyCustomizationHelpers.h" 14 | 15 | #define LOCTEXT_NAMESPACE "TerrainInterface" 16 | 17 | /// Generator Widget /// 18 | 19 | void SGeneratorBox::Construct(const FArguments& InArgs) 20 | { 21 | // Create the combobox widget 22 | FDynamicTerrainMode* mode = (FDynamicTerrainMode*)GLevelEditorModeTools().GetActiveMode(FDynamicTerrainMode::DynamicTerrainModeID); 23 | ChildSlot 24 | [ 25 | SNew(SComboBox>) 26 | .OptionsSource(&mode->Generators) 27 | .OnGenerateWidget(this, &SGeneratorBox::OptionWidget) 28 | .OnSelectionChanged(this, &SGeneratorBox::OptionSelect) 29 | .InitiallySelectedItem(mode->GetGenerator()) 30 | [ 31 | SNew(STextBlock).Text(FText::FromName(mode->GetGenerator()->Name)) 32 | ] 33 | ]; 34 | } 35 | 36 | TSharedRef SGeneratorBox::OptionWidget(TSharedPtr Option) 37 | { 38 | return SNew(STextBlock).Text(FText::FromName((*Option).Name)); 39 | } 40 | 41 | void SGeneratorBox::OptionSelect(TSharedPtr SelectOption, ESelectInfo::Type) 42 | { 43 | ((FDynamicTerrainMode*)GLevelEditorModeTools().GetActiveMode(FDynamicTerrainMode::DynamicTerrainModeID))->SelectGenerator(SelectOption); 44 | } 45 | 46 | /// Editor Commands /// 47 | 48 | FDynamicTerrainEditorCommands::FDynamicTerrainEditorCommands() : TCommands("DynamicTerrainCommands", NSLOCTEXT("TerrainContexts", "DynamicTerrainEditorMode", "Dynamic Terrain Edit Mode"), NAME_None, FDynamicTerrainStyle::GetName()) 49 | { 50 | // Nothing 51 | } 52 | 53 | void FDynamicTerrainEditorCommands::RegisterCommands() 54 | { 55 | // Initialize each command 56 | UI_COMMAND(CreateMode, "Create Mode", "Create terrain objects", EUserInterfaceActionType::RadioButton, FInputChord()); 57 | UI_COMMAND(ManageMode, "Manage Mode", "Select and resize terrain objects", EUserInterfaceActionType::RadioButton, FInputChord()); 58 | UI_COMMAND(GenerateMode, "Generate Mode", "Generate new terrain", EUserInterfaceActionType::RadioButton, FInputChord()); 59 | UI_COMMAND(SculptMode, "Sculpt Mode", "Sculpt the terrain", EUserInterfaceActionType::RadioButton, FInputChord()); 60 | UI_COMMAND(FoliageMode, "Foliage Mode", "Change terrain foliage", EUserInterfaceActionType::RadioButton, FInputChord()); 61 | 62 | UI_COMMAND(SculptTool, "Sculpt Tool", "Raise or lower the terrain", EUserInterfaceActionType::RadioButton, FInputChord()); 63 | UI_COMMAND(SmoothTool, "Smooth Tool", "Smooth bumpy terrain", EUserInterfaceActionType::RadioButton, FInputChord()); 64 | UI_COMMAND(FlattenTool, "Flatten Tool", "Flatten the terrain", EUserInterfaceActionType::RadioButton, FInputChord()); 65 | 66 | UI_COMMAND(LinearBrush, "Linear Brush", "Linear falloff", EUserInterfaceActionType::RadioButton, FInputChord()); 67 | UI_COMMAND(SmoothBrush, "Smooth Brush", "Smooth falloff", EUserInterfaceActionType::RadioButton, FInputChord()); 68 | UI_COMMAND(RoundBrush, "Round Brush", "Exponential falloff", EUserInterfaceActionType::RadioButton, FInputChord()); 69 | UI_COMMAND(SphereBrush, "Sphere Brush", "Spherical falloff", EUserInterfaceActionType::RadioButton, FInputChord()); 70 | } 71 | 72 | void FDynamicTerrainEditorCommands::MapCommands(FDynamicTerrainModeToolkit* Toolkit) const 73 | { 74 | // Map commands to toolkit callbacks 75 | MapCommandToMode(Toolkit, CreateMode, TerrainModeID::CREATE); 76 | MapCommandToMode(Toolkit, ManageMode, TerrainModeID::MANAGE); 77 | MapCommandToMode(Toolkit, GenerateMode, TerrainModeID::GENERATE); 78 | MapCommandToMode(Toolkit, SculptMode, TerrainModeID::SCULPT); 79 | MapCommandToMode(Toolkit, FoliageMode, TerrainModeID::FOLIAGE); 80 | 81 | MapCommandToTool(Toolkit, SculptTool, TerrainToolID::SCULPT); 82 | MapCommandToTool(Toolkit, SmoothTool, TerrainToolID::SMOOTH); 83 | MapCommandToTool(Toolkit, FlattenTool, TerrainToolID::FLATTEN); 84 | 85 | MapCommandToBrush(Toolkit, LinearBrush, TerrainBrushID::LINEAR); 86 | MapCommandToBrush(Toolkit, SmoothBrush, TerrainBrushID::SMOOTH); 87 | MapCommandToBrush(Toolkit, RoundBrush, TerrainBrushID::ROUND); 88 | MapCommandToBrush(Toolkit, SphereBrush, TerrainBrushID::SPHERE); 89 | } 90 | 91 | void FDynamicTerrainEditorCommands::MapCommandToMode(FDynamicTerrainModeToolkit* Toolkit, TSharedPtr Command, TerrainModeID ModeID) const 92 | { 93 | TSharedRef commands = Toolkit->GetToolkitCommands(); 94 | 95 | // Map the registered command to toolkit callbacks 96 | commands->MapAction(Command, 97 | FUIAction( 98 | // Select the mode when the command is executed 99 | FExecuteAction::CreateRaw(Toolkit, &FDynamicTerrainModeToolkit::ChangeMode, ModeID), 100 | // Check to see if the mode is available 101 | FCanExecuteAction::CreateRaw(Toolkit, &FDynamicTerrainModeToolkit::IsModeEnabled, ModeID), 102 | // Check to see if the mode is already active 103 | FIsActionChecked::CreateRaw(Toolkit, &FDynamicTerrainModeToolkit::IsModeActive, ModeID)) 104 | ); 105 | } 106 | 107 | void FDynamicTerrainEditorCommands::MapCommandToTool(FDynamicTerrainModeToolkit* Toolkit, TSharedPtr Command, TerrainToolID ToolID) const 108 | { 109 | TSharedRef commands = Toolkit->GetToolkitCommands(); 110 | 111 | commands->MapAction(Command, 112 | FUIAction( 113 | FExecuteAction::CreateRaw(Toolkit, &FDynamicTerrainModeToolkit::ChangeTool, ToolID), 114 | FCanExecuteAction::CreateRaw(Toolkit, &FDynamicTerrainModeToolkit::IsToolEnabled, ToolID), 115 | FIsActionChecked::CreateRaw(Toolkit, &FDynamicTerrainModeToolkit::IsToolActive, ToolID)) 116 | ); 117 | } 118 | 119 | void FDynamicTerrainEditorCommands::MapCommandToBrush(FDynamicTerrainModeToolkit* Toolkit, TSharedPtr Command, TerrainBrushID BrushID) const 120 | { 121 | TSharedRef commands = Toolkit->GetToolkitCommands(); 122 | 123 | commands->MapAction(Command, 124 | FUIAction( 125 | FExecuteAction::CreateRaw(Toolkit, &FDynamicTerrainModeToolkit::ChangeBrush, BrushID), 126 | FCanExecuteAction::CreateRaw(Toolkit, &FDynamicTerrainModeToolkit::IsBrushEnabled, BrushID), 127 | FIsActionChecked::CreateRaw(Toolkit, &FDynamicTerrainModeToolkit::IsBrushActive, BrushID)) 128 | ); 129 | } 130 | 131 | /// Details Panel /// 132 | 133 | TSharedRef FDynamicTerrainDetails::CreateInstance() 134 | { 135 | return MakeShareable(new FDynamicTerrainDetails); 136 | } 137 | 138 | void FDynamicTerrainDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) 139 | { 140 | FDynamicTerrainMode* mode = GetMode(); 141 | 142 | if (mode != nullptr) 143 | { 144 | TSharedPtr command_list = mode->GetCommandList(); 145 | TerrainModeID current_mode = mode->GetMode(); 146 | 147 | DetailBuilder.EditCategory("Generator", FText::GetEmpty(), ECategoryPriority::Important).SetCategoryVisibility(false); 148 | 149 | if (current_mode == TerrainModeID::MANAGE) 150 | { 151 | if (mode->GetSelected() != nullptr) 152 | { 153 | // Add a button to edit terrain 154 | IDetailCategoryBuilder& category_manage = DetailBuilder.EditCategory("Terrain Settings", FText::GetEmpty(), ECategoryPriority::Default); 155 | category_manage.AddCustomRow(FText::GetEmpty()) 156 | [ 157 | SNew(SButton).Text(LOCTEXT("ChangeTerrainButton", "Change")).OnClicked_Static(&FDynamicTerrainDetails::ResizeButton) 158 | ]; 159 | } 160 | else 161 | { 162 | // Add a text box with instructions for selecting terrain if none are selected 163 | IDetailCategoryBuilder& category_text = DetailBuilder.EditCategory("Select", FText::GetEmpty(), ECategoryPriority::Default); 164 | category_text.AddCustomRow(FText::GetEmpty()) 165 | [ 166 | SNew(STextBlock).Text(LOCTEXT("NoSelectionManage", "Click on a terrain to select it, or click 'Create' to create a new terrain if none exist")) 167 | ]; 168 | 169 | DetailBuilder.EditCategory("Terrain Settings", FText::GetEmpty(), ECategoryPriority::Important).SetCategoryVisibility(false); 170 | } 171 | 172 | // Hide categories 173 | DetailBuilder.EditCategory("Foliage Settings", FText::GetEmpty(), ECategoryPriority::Important).SetCategoryVisibility(false); 174 | DetailBuilder.EditCategory("Brush Settings", FText::GetEmpty(), ECategoryPriority::Important).SetCategoryVisibility(false); 175 | } 176 | else if (current_mode == TerrainModeID::CREATE) 177 | { 178 | // Add a button to create a new terrain 179 | IDetailCategoryBuilder& category_create = DetailBuilder.EditCategory("Terrain Settings", FText::GetEmpty(), ECategoryPriority::Default); 180 | category_create.AddCustomRow(FText::GetEmpty()) 181 | [ 182 | SNew(SButton).Text(LOCTEXT("NewTerrainButton", "Create")).OnClicked_Static(&FDynamicTerrainDetails::CreateButton) 183 | ]; 184 | 185 | // Hide categories 186 | DetailBuilder.EditCategory("Foliage Settings", FText::GetEmpty(), ECategoryPriority::Important).SetCategoryVisibility(false); 187 | DetailBuilder.EditCategory("Brush Settings", FText::GetEmpty(), ECategoryPriority::Important).SetCategoryVisibility(false); 188 | } 189 | else if (current_mode == TerrainModeID::GENERATE) 190 | { 191 | IDetailCategoryBuilder& category_generate = DetailBuilder.EditCategory("Terrain Generator", FText::GetEmpty(), ECategoryPriority::Default); 192 | 193 | // Generator selection combo box 194 | category_generate.AddCustomRow(FText::GetEmpty()) 195 | [ 196 | SNew(SGeneratorBox) 197 | ]; 198 | // Generate button 199 | category_generate.AddCustomRow(FText::GetEmpty()) 200 | [ 201 | SNew(SButton).Text(LOCTEXT("GenerateButton", "Generate")).OnClicked_Static(&FDynamicTerrainDetails::GenerateButton) 202 | ]; 203 | 204 | // Add seed settings 205 | TSharedRef prop = DetailBuilder.GetProperty("UseRandomSeed"); 206 | category_generate.AddProperty(prop); 207 | prop = DetailBuilder.GetProperty("Seed"); 208 | category_generate.AddProperty(prop); 209 | 210 | // Add parameters for the current generator 211 | FTerrainGenerator* generator = mode->GetGenerator().Get(); 212 | for (int32 i = 0; i < generator->Parameters.Num(); ++i) 213 | { 214 | // Create the name of the property 215 | FString param_name; 216 | if (generator->Parameters[i].IsFloat) 217 | { 218 | param_name = "FloatProperties["; 219 | } 220 | else 221 | { 222 | param_name = "IntProperties["; 223 | } 224 | param_name.AppendInt(i); 225 | param_name.Append("]"); 226 | 227 | // Find the property and set its default value 228 | prop = DetailBuilder.GetProperty(*param_name); 229 | prop->SetPropertyDisplayName(generator->Parameters[i].Name); 230 | if (generator->Parameters[i].IsFloat) 231 | { 232 | prop->SetValue(generator->Parameters[i].Default); 233 | } 234 | else 235 | { 236 | prop->SetValue((int32)generator->Parameters[i].Default); 237 | } 238 | 239 | // Add the property to the details panel 240 | category_generate.AddProperty(prop); 241 | } 242 | 243 | // Hide categories 244 | DetailBuilder.EditCategory("Foliage Settings", FText::GetEmpty(), ECategoryPriority::Important).SetCategoryVisibility(false); 245 | DetailBuilder.EditCategory("Brush Settings", FText::GetEmpty(), ECategoryPriority::Important).SetCategoryVisibility(false); 246 | DetailBuilder.EditCategory("Terrain Settings", FText::GetEmpty(), ECategoryPriority::Important).SetCategoryVisibility(false); 247 | } 248 | else if (current_mode == TerrainModeID::SCULPT) 249 | { 250 | // Create the tool selection widget 251 | IDetailCategoryBuilder& category_tools = DetailBuilder.EditCategory("Tools", FText::GetEmpty(), ECategoryPriority::Important); 252 | 253 | FToolBarBuilder ToolButtons(command_list, FMultiBoxCustomization::None); 254 | ToolButtons.AddToolBarButton(FDynamicTerrainEditorCommands::Get().SculptTool, NAME_None, 255 | LOCTEXT("SculptToolName", "Sculpt"), 256 | LOCTEXT("SculptToolDesc", "Shape terrain by raising or lowering it"), 257 | FSlateIcon(FDynamicTerrainStyle::GetName(), "Plugins.Tool.Sculpt")); 258 | ToolButtons.AddToolBarButton(FDynamicTerrainEditorCommands::Get().SmoothTool, NAME_None, 259 | LOCTEXT("SmoothToolName", "Smooth"), 260 | LOCTEXT("SmoothToolDesc", "Smooth out bumpy terrain"), 261 | FSlateIcon(FDynamicTerrainStyle::GetName(), "Plugins.Tool.Smooth")); 262 | ToolButtons.AddToolBarButton(FDynamicTerrainEditorCommands::Get().FlattenTool, NAME_None, 263 | LOCTEXT("FlattenToolName", "Flatten"), 264 | LOCTEXT("FlattenToolDesc", "Level the terrain around the cursor"), 265 | FSlateIcon(FDynamicTerrainStyle::GetName(), "Plugins.Tool.Flatten")); 266 | 267 | category_tools.AddCustomRow(FText::GetEmpty()) 268 | [ 269 | SNew(SBox).Padding(5).HAlign(HAlign_Center) 270 | [ 271 | ToolButtons.MakeWidget() 272 | ] 273 | ]; 274 | 275 | // Create the brush widget 276 | IDetailCategoryBuilder& category_brushes = DetailBuilder.EditCategory("Brushes", FText::GetEmpty(), ECategoryPriority::Important); 277 | 278 | FToolBarBuilder BrushButtons(command_list, FMultiBoxCustomization::None); 279 | BrushButtons.AddToolBarButton(FDynamicTerrainEditorCommands::Get().LinearBrush, NAME_None, 280 | LOCTEXT("LinearBrushName", "Linear"), 281 | LOCTEXT("LinearBrushDesc", "Linear falloff"), 282 | FSlateIcon(FDynamicTerrainStyle::GetName(), "Plugins.Brush.Linear")); 283 | BrushButtons.AddToolBarButton(FDynamicTerrainEditorCommands::Get().SmoothBrush, NAME_None, 284 | LOCTEXT("SmoothBrushName", "Smooth"), 285 | LOCTEXT("SmoothBrushDesc", "Smooth falloff"), 286 | FSlateIcon(FDynamicTerrainStyle::GetName(), "Plugins.Brush.Smooth")); 287 | BrushButtons.AddToolBarButton(FDynamicTerrainEditorCommands::Get().RoundBrush, NAME_None, 288 | LOCTEXT("RoundBrushName", "Round"), 289 | LOCTEXT("RoundBrushDesc", "Round falloff"), 290 | FSlateIcon(FDynamicTerrainStyle::GetName(), "Plugins.Brush.Round")); 291 | BrushButtons.AddToolBarButton(FDynamicTerrainEditorCommands::Get().SphereBrush, NAME_None, 292 | LOCTEXT("SphereBrushName", "Sphere"), 293 | LOCTEXT("SphereBrushDesc", "Sphere falloff"), 294 | FSlateIcon(FDynamicTerrainStyle::GetName(), "Plugins.Brush.Sphere")); 295 | 296 | category_brushes.AddCustomRow(FText::GetEmpty()) 297 | [ 298 | SNew(SBox).Padding(5).HAlign(HAlign_Center) 299 | [ 300 | BrushButtons.MakeWidget() 301 | ] 302 | ]; 303 | 304 | // Hide categories 305 | DetailBuilder.EditCategory("Foliage Settings", FText::GetEmpty(), ECategoryPriority::Important).SetCategoryVisibility(false); 306 | DetailBuilder.EditCategory("Terrain Settings", FText::GetEmpty(), ECategoryPriority::Important).SetCategoryVisibility(false); 307 | } 308 | else if (current_mode == TerrainModeID::FOLIAGE) 309 | { 310 | // Add the foliage widget 311 | IDetailCategoryBuilder& category_foliage = DetailBuilder.EditCategory("Foliage Settings", FText::GetEmpty(), ECategoryPriority::Default); 312 | category_foliage.AddCustomRow(FText::GetEmpty()) 313 | [ 314 | SNew(SButton).Text(LOCTEXT("ChangeFoliageButton", "Change")).OnClicked_Static(&FDynamicTerrainDetails::FoliageButton) 315 | ]; 316 | 317 | // Hide categories 318 | DetailBuilder.EditCategory("Brush Settings", FText::GetEmpty(), ECategoryPriority::Important).SetCategoryVisibility(false); 319 | DetailBuilder.EditCategory("Terrain Settings", FText::GetEmpty(), ECategoryPriority::Important).SetCategoryVisibility(false); 320 | } 321 | 322 | DetailBuilder.GetProperty("Strength")->SetOnPropertyValueChanged(FSimpleDelegate::CreateSP(this, &FDynamicTerrainDetails::UpdateBrush)); 323 | DetailBuilder.GetProperty("Size")->SetOnPropertyValueChanged(FSimpleDelegate::CreateSP(this, &FDynamicTerrainDetails::UpdateBrush)); 324 | DetailBuilder.GetProperty("Falloff")->SetOnPropertyValueChanged(FSimpleDelegate::CreateSP(this, &FDynamicTerrainDetails::UpdateBrush)); 325 | } 326 | } 327 | 328 | FReply FDynamicTerrainDetails::ResizeButton() 329 | { 330 | GetMode()->ResizeTerrain(); 331 | return FReply::Handled(); 332 | } 333 | 334 | FReply FDynamicTerrainDetails::CreateButton() 335 | { 336 | GetMode()->CreateTerrain(); 337 | return FReply::Handled(); 338 | } 339 | 340 | FReply FDynamicTerrainDetails::GenerateButton() 341 | { 342 | GetMode()->ProcessGenerateCommand(); 343 | return FReply::Handled(); 344 | } 345 | 346 | FReply FDynamicTerrainDetails::FoliageButton() 347 | { 348 | GetMode()->ChangeFoliage(); 349 | return FReply::Handled(); 350 | } 351 | 352 | FDynamicTerrainMode* FDynamicTerrainDetails::GetMode() 353 | { 354 | return (FDynamicTerrainMode*)GLevelEditorModeTools().GetActiveMode(FDynamicTerrainMode::DynamicTerrainModeID); 355 | } 356 | 357 | void FDynamicTerrainDetails::UpdateBrush() 358 | { 359 | FDynamicTerrainMode* mode = GetMode(); 360 | if (mode != nullptr) 361 | { 362 | FTerrainTool* tool = mode->GetTools()->GetTool(); 363 | tool->Strength = mode->Settings->Strength; 364 | tool->Size = mode->Settings->Size; 365 | tool->Falloff = mode->Settings->Falloff; 366 | } 367 | } 368 | 369 | TSharedRef FDynamicTerrainDetails::GetGeneratorParameter(IDetailLayoutBuilder& DetailBuilder, int32 Ref) 370 | { 371 | return DetailBuilder.GetProperty("asdf", nullptr, "asf"); 372 | } 373 | 374 | #undef LOCTEXT_NAMESPACE -------------------------------------------------------------------------------- /Source/DynamicTerrainEditor/Private/DynamicTerrainMode.cpp: -------------------------------------------------------------------------------- 1 | #include "DynamicTerrainMode.h" 2 | #include "DynamicTerrainModeToolkit.h" 3 | 4 | #include "EngineUtils.h" 5 | #include "EditorViewportClient.h" 6 | 7 | #include "EditorModeManager.h" 8 | 9 | #define LOCTEXT_NAMESPACE "TerrainMode" 10 | 11 | const FEditorModeID FDynamicTerrainMode::DynamicTerrainModeID = TEXT("DynamicTerrainModeID"); 12 | 13 | ABrushProxy::ABrushProxy() 14 | { 15 | // Create the decal 16 | Decal = CreateDefaultSubobject(TEXT("TerrainEditorBrushDecal")); 17 | Decal->SetVisibility(false); 18 | 19 | SetActorLabel("BrushProxy"); 20 | } 21 | 22 | bool ABrushProxy::IsSelectable() const 23 | { 24 | return false; 25 | } 26 | 27 | void ABrushProxy::Initialize() 28 | { 29 | Decal->CreateMaterialInstance(); 30 | } 31 | 32 | void ABrushProxy::ShowBrush(bool Visible) 33 | { 34 | Decal->SetVisibility(Visible); 35 | } 36 | 37 | void ABrushProxy::SetBrush(FVector Location, FTerrainTool* Tool, ATerrain* Terrain) 38 | { 39 | Decal->SetRelativeLocation(Location); 40 | Decal->Resize(Tool, Terrain); 41 | } 42 | 43 | FDynamicTerrainMode::FDynamicTerrainMode() 44 | { 45 | Settings = NewObject(GetTransientPackage(), TEXT("DynamicTerrainSettings")); 46 | Settings->AddToRoot(); 47 | 48 | MapGen = NewObject(GetTransientPackage(), TEXT("DynamicTerrainMapGenerator")); 49 | MapGen->AddToRoot(); 50 | 51 | // Create editor modes 52 | Modes.SetNum((int)TerrainModeID::NUM); 53 | Modes[(int)TerrainModeID::CREATE] = new FDynamicTerrainToolMode("Create", TerrainModeID::CREATE); 54 | Modes[(int)TerrainModeID::SCULPT] = new FDynamicTerrainToolMode("Sculpt", TerrainModeID::SCULPT); 55 | Modes[(int)TerrainModeID::MANAGE] = new FDynamicTerrainToolMode("Manage", TerrainModeID::MANAGE); 56 | Modes[(int)TerrainModeID::GENERATE] = new FDynamicTerrainToolMode("Generate", TerrainModeID::GENERATE); 57 | Modes[(int)TerrainModeID::FOLIAGE] = new FDynamicTerrainToolMode("Foliage", TerrainModeID::FOLIAGE); 58 | 59 | CurrentMode = Modes[1]; 60 | 61 | // Create terrain generator data 62 | for (int32 i = 0; i < 10; ++i) 63 | { 64 | Settings->IntProperties[i] = 0; 65 | Settings->FloatProperties[i] = 0.0f; 66 | } 67 | 68 | // Get the names of all the generator functions 69 | TArray fnames; 70 | UMapGenerator::StaticClass()->GenerateFunctionList(fnames); 71 | 72 | for (int32 i = 0; i < fnames.Num(); ++i) 73 | { 74 | Generators.Add(MakeShareable(new FTerrainGenerator(fnames[i]))); 75 | 76 | // Get the function parameters for the generator function 77 | UFunction* funct = UMapGenerator::StaticClass()->FindFunctionByName(fnames[i]); 78 | if (funct != nullptr) 79 | { 80 | for (TFieldIterator it = TFieldIterator(funct); it; ++it) 81 | { 82 | FProperty* prop = *it; 83 | 84 | // Make sure the property is a numeric type 85 | if (FNumericProperty* nprop = CastField(prop)) 86 | { 87 | // Add a new parameter 88 | TSharedRef generator = Generators.Last().ToSharedRef(); 89 | generator->Parameters.Emplace(); 90 | 91 | // Get the name and type of the parameter 92 | generator->Parameters.Last().Name = nprop->GetDisplayNameText(); 93 | generator->Parameters.Last().IsFloat = nprop->IsFloatingPoint(); 94 | if (nprop->HasMetaData("Default")) 95 | { 96 | generator->Parameters.Last().Default = nprop->GetFloatMetaData("Default"); 97 | } 98 | } 99 | else 100 | { 101 | // Ignore the generator if it has non-numeric properties 102 | Generators.Pop(); 103 | break; 104 | } 105 | } 106 | } 107 | } 108 | 109 | CurrentGenerator = Generators.Last(); 110 | } 111 | 112 | FDynamicTerrainMode::~FDynamicTerrainMode() 113 | { 114 | for (int32 i = 0; i < Modes.Num(); ++i) 115 | { 116 | delete Modes[i]; 117 | } 118 | Modes.Empty(); 119 | 120 | Settings->RemoveFromRoot(); 121 | MapGen->RemoveFromRoot(); 122 | MapGen->Terrain = nullptr; 123 | } 124 | 125 | /// Engine Functions /// 126 | 127 | void FDynamicTerrainMode::Enter() 128 | { 129 | FEdMode::Enter(); 130 | 131 | // Initialize the toolkit 132 | if (!Toolkit.IsValid() && UsesToolkits()) 133 | { 134 | Toolkit = MakeShareable(new FDynamicTerrainModeToolkit); 135 | Toolkit->Init(Owner->GetToolkitHost()); 136 | } 137 | 138 | // Create the brush display 139 | FActorSpawnParameters spawn_params; 140 | spawn_params.ObjectFlags = EObjectFlags::RF_Transient; 141 | Brush = GetWorld()->SpawnActor(spawn_params); 142 | Brush->Initialize(); 143 | 144 | // Select a terrain from the level, preferably one matching the name of the last terrain selected in this mode 145 | for (TActorIterator itr(GetWorld()); itr; ++itr) 146 | { 147 | SelectedTerrain = *itr; 148 | MapGen->Terrain = SelectedTerrain; 149 | if (SelectedTerrain->GetName() == TerrainName) 150 | { 151 | break; 152 | } 153 | } 154 | 155 | if (SelectedTerrain == nullptr) 156 | { 157 | // If no terrain is found, switch to create mode 158 | SetMode(TerrainModeID::CREATE); 159 | TerrainName = ""; 160 | } 161 | else 162 | { 163 | if (CurrentMode->ModeID == TerrainModeID::SCULPT) 164 | { 165 | Brush->ShowBrush(true); 166 | } 167 | 168 | TerrainName = SelectedTerrain->GetName(); 169 | } 170 | 171 | ModeUpdate(); 172 | ToolUpdate(); 173 | } 174 | 175 | void FDynamicTerrainMode::Exit() 176 | { 177 | // Deselect the terrain to prevent dangling pointerse 178 | SelectedTerrain = nullptr; 179 | MapGen->Terrain = nullptr; 180 | 181 | // Destroy the brush proxy 182 | Brush->Destroy(); 183 | Brush = nullptr; 184 | 185 | FEdMode::Exit(); 186 | } 187 | 188 | void FDynamicTerrainMode::Tick(FEditorViewportClient* ViewportClient, float DeltaTime) 189 | { 190 | // Get the scene view 191 | FSceneViewFamilyContext context(FSceneViewFamily::ConstructionValues(ViewportClient->Viewport, ViewportClient->GetScene(), ViewportClient->EngineShowFlags)); 192 | FSceneView* View = ViewportClient->CalcSceneView(&context); 193 | 194 | // Get the mouse location 195 | FVector2D Mouse(ViewportClient->Viewport->GetMouseX(), ViewportClient->Viewport->GetMouseY()); 196 | 197 | // Project the mouse into the world 198 | FVector WorldOrigin; 199 | FVector WorldDirection; 200 | 201 | FSceneView::DeprojectScreenToWorld(Mouse, View->UnconstrainedViewRect, View->ViewMatrices.GetInvViewProjectionMatrix(), WorldOrigin, WorldDirection); 202 | 203 | // Trace from the viewport outward under the mouse 204 | FHitResult hit; 205 | ViewportClient->GetWorld()->LineTraceSingleByChannel(hit, WorldOrigin, WorldOrigin + WorldDirection * 50000.0f, ECollisionChannel::ECC_WorldDynamic); 206 | 207 | if (CurrentMode->ModeID == TerrainModeID::SCULPT) 208 | { 209 | ATerrain* terrain = Cast(hit.Actor); 210 | if (terrain != nullptr && terrain != SelectedTerrain) 211 | { 212 | // Select a terrain when the mouse hovers over it 213 | SelectTerrain(terrain); 214 | } 215 | 216 | if (SelectedTerrain != nullptr) 217 | { 218 | FTerrainTool* tool = Tools.GetTool(); 219 | 220 | // Adjust the brush display 221 | Brush->SetBrush(hit.Location, tool, SelectedTerrain); 222 | 223 | if (MouseClick) 224 | { 225 | // Apply the tool 226 | tool->Invert = InvertTool; 227 | tool->Apply(SelectedTerrain, hit.Location, DeltaTime); 228 | 229 | // Force the terrain to update 230 | SelectedTerrain->Update(); 231 | } 232 | } 233 | } 234 | else if (CurrentMode->ModeID == TerrainModeID::MANAGE || CurrentMode->ModeID == TerrainModeID::GENERATE) 235 | { 236 | // Select a terrain when clicked in manage mode 237 | if (MouseClick) 238 | { 239 | ATerrain* newterrain = Cast(hit.Actor); 240 | if (newterrain != nullptr && newterrain != SelectedTerrain) 241 | { 242 | SelectTerrain(newterrain); 243 | } 244 | } 245 | } 246 | } 247 | 248 | void FDynamicTerrainMode::Render(const FSceneView* View, FViewport* Viewport, FPrimitiveDrawInterface* PDI) 249 | { 250 | if (CurrentMode == nullptr || Settings == nullptr) 251 | return; 252 | 253 | FLinearColor color_edge = FLinearColor::Green; 254 | FLinearColor color_poly = FLinearColor::Green; 255 | color_poly.G /= 2.0f; 256 | 257 | FVector scale(100.0f); 258 | FVector center(0.0f); 259 | 260 | if (CurrentMode->ModeID == TerrainModeID::MANAGE || CurrentMode->ModeID == TerrainModeID::CREATE) 261 | { 262 | // Draw gridlines on the selected terrain or at the location of a potential new terrain 263 | if (CurrentMode->ModeID == TerrainModeID::MANAGE) 264 | { 265 | if (SelectedTerrain != nullptr) 266 | { 267 | scale = SelectedTerrain->GetActorScale3D(); 268 | center = SelectedTerrain->GetActorLocation(); 269 | } 270 | else 271 | { 272 | return; 273 | } 274 | } 275 | 276 | int32 polygons = GetTerrainComponentWidth(Settings->ComponentSize) - 1; 277 | float offset_x = (float)(Settings->WidthX * polygons) * scale.X / 2.0f; 278 | float offset_y = (float)(Settings->WidthY * polygons) * scale.Y / 2.0f; 279 | 280 | // Draw X axis lines 281 | for (int32 x = 0; x <= Settings->WidthX * polygons; ++x) 282 | { 283 | FVector start = center; 284 | start.X -= offset_x - x * scale.X; 285 | FVector end = start; 286 | start.Y -= offset_y; 287 | end.Y += offset_y; 288 | 289 | // Draw thick lines on component edges and thin lines on polygons 290 | if (x % polygons == 0) 291 | { 292 | PDI->DrawLine(start, end, color_edge, ESceneDepthPriorityGroup::SDPG_MAX, 10.0f); 293 | } 294 | else 295 | { 296 | PDI->DrawLine(start, end, color_poly, ESceneDepthPriorityGroup::SDPG_MAX, 3.0f); 297 | } 298 | } 299 | // Draw Y axis lines 300 | for (int32 y = 0; y <= Settings->WidthY * polygons; ++y) 301 | { 302 | FVector start = center; 303 | start.Y -= offset_y - y * scale.Y; 304 | FVector end = start; 305 | start.X -= offset_x; 306 | end.X += offset_x; 307 | 308 | // Draw thick lines on component edges and thin lines on polygons 309 | if (y % polygons == 0) 310 | { 311 | PDI->DrawLine(start, end, color_edge, ESceneDepthPriorityGroup::SDPG_MAX, 10.0f); 312 | } 313 | else 314 | { 315 | PDI->DrawLine(start, end, color_poly, ESceneDepthPriorityGroup::SDPG_MAX, 3.0f); 316 | } 317 | } 318 | } 319 | else 320 | { 321 | // Highlight the selected terrain 322 | if (SelectedTerrain != nullptr) 323 | { 324 | scale = SelectedTerrain->GetActorScale3D(); 325 | center = SelectedTerrain->GetActorLocation(); 326 | 327 | uint32 width = GetTerrainComponentWidth(SelectedTerrain->GetComponentSize()) - 1; 328 | float offset_x = (float)(SelectedTerrain->GetXWidth() * width) * scale.X / 2.0f; 329 | float offset_y = (float)(SelectedTerrain->GetYWidth() * width) * scale.Y / 2.0f; 330 | 331 | FVector p00(center.X - offset_x, center.Y - offset_y, center.Z); 332 | FVector p10(center.X + offset_x, center.Y - offset_y, center.Z); 333 | FVector p01(center.X - offset_x, center.Y + offset_y, center.Z); 334 | FVector p11(center.X + offset_x, center.Y + offset_y, center.Z); 335 | 336 | PDI->DrawLine(p00, p10, color_edge, ESceneDepthPriorityGroup::SDPG_World, 10.0f); 337 | PDI->DrawLine(p10, p11, color_edge, ESceneDepthPriorityGroup::SDPG_World, 10.0f); 338 | PDI->DrawLine(p11, p01, color_edge, ESceneDepthPriorityGroup::SDPG_World, 10.0f); 339 | PDI->DrawLine(p01, p00, color_edge, ESceneDepthPriorityGroup::SDPG_World, 10.0f); 340 | } 341 | } 342 | } 343 | 344 | bool FDynamicTerrainMode::DisallowMouseDeltaTracking() const 345 | { 346 | return false; 347 | } 348 | 349 | bool FDynamicTerrainMode::HandleClick(FEditorViewportClient* ViewportClient, HHitProxy* HitProxy, const FViewportClick& Click) 350 | { 351 | // Notify the engine that mouse clicks were handled to prevent actors from being selected when in terrain mode 352 | return true; 353 | } 354 | 355 | bool FDynamicTerrainMode::InputKey(FEditorViewportClient* ViewportClient, FViewport* Viewport, FKey Key, EInputEvent Event) 356 | { 357 | // Handle left clicks 358 | if (Key == EKeys::LeftMouseButton) 359 | { 360 | if (Event == EInputEvent::IE_Pressed) 361 | { 362 | Viewport->CaptureMouse(true); 363 | 364 | MouseClick = true; 365 | } 366 | else if (Event == EInputEvent::IE_Released) 367 | { 368 | Viewport->CaptureMouse(false); 369 | 370 | MouseClick = false; 371 | } 372 | 373 | return true; 374 | } 375 | 376 | // Handle modifier keys 377 | if (Key == EKeys::LeftShift) 378 | { 379 | if (Event == EInputEvent::IE_Pressed) 380 | { 381 | InvertTool = true; 382 | } 383 | else if (Event == EInputEvent::IE_Released) 384 | { 385 | InvertTool = false; 386 | } 387 | 388 | return true; 389 | } 390 | 391 | return false; 392 | } 393 | 394 | bool FDynamicTerrainMode::UsesToolkits() const 395 | { 396 | return true; 397 | } 398 | 399 | /// Command Functions /// 400 | 401 | TSharedRef FDynamicTerrainMode::GetCommandList() const 402 | { 403 | check(Toolkit.IsValid()); 404 | return Toolkit->GetToolkitCommands(); 405 | } 406 | 407 | TerrainModeID FDynamicTerrainMode::GetMode() 408 | { 409 | return CurrentMode->ModeID; 410 | } 411 | 412 | const FName FDynamicTerrainMode::GetModeName() 413 | { 414 | return CurrentMode->ModeName; 415 | } 416 | 417 | void FDynamicTerrainMode::SetMode(TerrainModeID ModeID) 418 | { 419 | if (ModeID != TerrainModeID::NUM) 420 | { 421 | CurrentMode = Modes[(int)ModeID]; 422 | if (ModeID == TerrainModeID::SCULPT) 423 | { 424 | // Enable the brush decal 425 | if (SelectedTerrain != nullptr) 426 | { 427 | Brush->ShowBrush(true); 428 | } 429 | } 430 | else 431 | { 432 | if (SelectedTerrain != nullptr) 433 | { 434 | Brush->ShowBrush(false); 435 | 436 | if (ModeID == TerrainModeID::MANAGE || ModeID == TerrainModeID::FOLIAGE) 437 | { 438 | ModeUpdate(); 439 | } 440 | } 441 | 442 | if (ModeID == TerrainModeID::CREATE) 443 | { 444 | ModeUpdate(); 445 | } 446 | } 447 | } 448 | 449 | ((FDynamicTerrainModeToolkit*)GetToolkit().Get())->RefreshDetails(); 450 | } 451 | 452 | FToolSet* FDynamicTerrainMode::GetTools() 453 | { 454 | return &Tools; 455 | } 456 | 457 | ATerrain* FDynamicTerrainMode::GetSelected() 458 | { 459 | return SelectedTerrain; 460 | } 461 | 462 | void FDynamicTerrainMode::ToolUpdate() 463 | { 464 | // Update settings panel 465 | Settings->Strength = Tools.GetTool()->Strength; 466 | Settings->Size = Tools.GetTool()->Size; 467 | Settings->Falloff = Tools.GetTool()->Falloff; 468 | 469 | ((FDynamicTerrainModeToolkit*)GetToolkit().Get())->RefreshDetails(); 470 | } 471 | 472 | void FDynamicTerrainMode::ModeUpdate() 473 | { 474 | if (SelectedTerrain != nullptr && CurrentMode->ModeID != TerrainModeID::CREATE) 475 | { 476 | if (CurrentMode->ModeID == TerrainModeID::MANAGE) 477 | { 478 | // Copy the current terrain's attributes to the settings panel 479 | Settings->ComponentSize = SelectedTerrain->GetComponentSize(); 480 | Settings->WidthX = SelectedTerrain->GetXWidth(); 481 | Settings->WidthY = SelectedTerrain->GetYWidth(); 482 | Settings->UVTiling = SelectedTerrain->GetTiling(); 483 | Settings->LODLevels = SelectedTerrain->GetNumLODs(); 484 | Settings->LODScale = SelectedTerrain->GetLODDistanceScale(); 485 | } 486 | else if (CurrentMode->ModeID == TerrainModeID::FOLIAGE) 487 | { 488 | // Copy the foliage groups from the selected terrain to the settings panel 489 | SelectedTerrain->GetFoliageGroups(Settings->Foliage); 490 | } 491 | } 492 | else 493 | { 494 | // Change the settings to default 495 | Settings->ComponentSize = 6; 496 | Settings->WidthX = 3; 497 | Settings->WidthY = 3; 498 | Settings->UVTiling = 1.0f; 499 | Settings->LODLevels = 5; 500 | Settings->LODScale = 0.5; 501 | } 502 | 503 | ((FDynamicTerrainModeToolkit*)GetToolkit().Get())->RefreshDetails(); 504 | } 505 | 506 | void FDynamicTerrainMode::ResizeTerrain() 507 | { 508 | if (SelectedTerrain != nullptr) 509 | { 510 | if (Settings->ComponentSize != SelectedTerrain->GetComponentSize() || Settings->WidthX != SelectedTerrain->GetXWidth() || Settings->WidthY != SelectedTerrain->GetYWidth()) 511 | { 512 | // Update everything 513 | SelectedTerrain->SetTiling(Settings->UVTiling); 514 | SelectedTerrain->SetLODs(Settings->LODLevels, Settings->LODScale); 515 | SelectedTerrain->Resize(Settings->ComponentSize, Settings->WidthX, Settings->WidthY); 516 | } 517 | else 518 | { 519 | // Update LODs 520 | if (Settings->LODLevels != SelectedTerrain->GetNumLODs() || Settings->LODScale != SelectedTerrain->GetLODDistanceScale()) 521 | { 522 | SelectedTerrain->SetLODs(Settings->LODLevels, Settings->LODScale); 523 | } 524 | // Update UV settings 525 | if (Settings->UVTiling != SelectedTerrain->GetTiling()) 526 | { 527 | SelectedTerrain->SetTiling(Settings->UVTiling); 528 | } 529 | } 530 | } 531 | } 532 | 533 | void FDynamicTerrainMode::CreateTerrain() 534 | { 535 | GEditor->BeginTransaction(LOCTEXT("CreateTerrainTransaction", "New Terrain")); 536 | 537 | // Spawn the terrain 538 | ATerrain* new_terrain = Cast(GetWorld()->SpawnActor(ATerrain::StaticClass())); 539 | 540 | // Resize the new terrain 541 | new_terrain->SetTiling(Settings->UVTiling); 542 | new_terrain->SetLODs(Settings->LODLevels, Settings->LODScale); 543 | new_terrain->Resize(Settings->ComponentSize, Settings->WidthX, Settings->WidthY); 544 | 545 | SelectTerrain(new_terrain); 546 | 547 | GEditor->EndTransaction(); 548 | } 549 | 550 | void FDynamicTerrainMode::ChangeFoliage() 551 | { 552 | if (SelectedTerrain != nullptr) 553 | { 554 | // Change the foliage on the terrain 555 | SelectedTerrain->SetFoliageGroups(Settings->Foliage); 556 | } 557 | } 558 | 559 | void FDynamicTerrainMode::SelectTerrain(ATerrain* Terrain) 560 | { 561 | SelectedTerrain = Terrain; 562 | TerrainName = SelectedTerrain->GetName(); 563 | MapGen->Terrain = SelectedTerrain; 564 | 565 | ModeUpdate(); 566 | } 567 | 568 | void FDynamicTerrainMode::ProcessGenerateCommand(/*const TCHAR* Command*/) 569 | { 570 | if (MapGen->Terrain == nullptr || CurrentGenerator == nullptr) 571 | return; 572 | 573 | // Get a new seed for the map generator 574 | if (Settings->UseRandomSeed) 575 | { 576 | MapGen->NewSeed(); 577 | Settings->Seed = (int32)MapGen->Seed; 578 | } 579 | else 580 | { 581 | MapGen->SetSeed(Settings->Seed); 582 | } 583 | 584 | 585 | SelectedTerrain->DeleteFoliage(); 586 | 587 | // Create a console command using parameters from the settings panel 588 | FString command(CurrentGenerator->Name.ToString()); 589 | 590 | for (int32 i = 0; i < CurrentGenerator->Parameters.Num(); ++i) 591 | { 592 | command.Append(" "); 593 | 594 | if (CurrentGenerator->Parameters[i].IsFloat) 595 | { 596 | command.Append(FString::SanitizeFloat(Settings->FloatProperties[i])); 597 | } 598 | else 599 | { 600 | command.AppendInt(Settings->IntProperties[i]); 601 | } 602 | } 603 | 604 | // Run the script command passed into the function 605 | MapGen->CallFunctionByNameWithArguments(*command, *GetGlobalLogSingleton(), MapGen, true); 606 | GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Yellow, command); 607 | SelectedTerrain->Refresh(); 608 | } 609 | 610 | void FDynamicTerrainMode::SelectGenerator(TSharedPtr Generator) 611 | { 612 | if (Generator != nullptr) 613 | { 614 | CurrentGenerator = Generator; 615 | ((FDynamicTerrainModeToolkit*)GetToolkit().Get())->RefreshDetails(); 616 | } 617 | } 618 | 619 | TSharedPtr FDynamicTerrainMode::GetGenerator() 620 | { 621 | return CurrentGenerator; 622 | } 623 | 624 | #undef LOCTEXT_NAMESPACE -------------------------------------------------------------------------------- /Source/DynamicTerrainEditor/Private/DynamicTerrainModeToolkit.cpp: -------------------------------------------------------------------------------- 1 | #include "DynamicTerrainModeToolkit.h" 2 | 3 | #include "DynamicTerrainMode.h" 4 | #include "DynamicTerrainStyle.h" 5 | #include "DynamicTerrainInterface.h" 6 | 7 | #include "EditorModeManager.h" 8 | #include "Modules/ModuleManager.h" 9 | #include "Widgets/Layout/SScrollBox.h" 10 | #include "Framework/MultiBox/MultiBoxBuilder.h" 11 | 12 | #define LOCTEXT_NAMESPACE "TerrainTools" 13 | 14 | /// Engine Functions /// 15 | 16 | void FDynamicTerrainModeToolkit::Init(const TSharedPtr< class IToolkitHost >& InitToolkitHost) 17 | { 18 | // Register toolkit commands 19 | FDynamicTerrainEditorCommands::Register(); 20 | FDynamicTerrainEditorCommands::Get().MapCommands(this); 21 | 22 | // Create the mode buttons 23 | FToolBarBuilder ModeButtons(GetToolkitCommands(), FMultiBoxCustomization::None); 24 | ModeButtons.AddToolBarButton(FDynamicTerrainEditorCommands::Get().CreateMode, NAME_None, 25 | LOCTEXT("CreateModeName", "Create"), 26 | LOCTEXT("ManageModeDesc", "Create a new terrain"), 27 | FSlateIcon(FDynamicTerrainStyle::GetName(), "Plugins.Mode.Create")); 28 | ModeButtons.AddToolBarButton(FDynamicTerrainEditorCommands::Get().ManageMode, NAME_None, 29 | LOCTEXT("ManageModeName", "Manage"), 30 | LOCTEXT("ManageModeDesc", "Select and resize terrain objects"), 31 | FSlateIcon(FDynamicTerrainStyle::GetName(), "Plugins.Mode.Manage")); 32 | ModeButtons.AddToolBarButton(FDynamicTerrainEditorCommands::Get().GenerateMode, NAME_None, 33 | LOCTEXT("GenerateModeName", "Generate"), 34 | LOCTEXT("GenerateModeDesc", "Procedurally generate a new terrain"), 35 | FSlateIcon(FDynamicTerrainStyle::GetName(), "Plugins.Mode.Generate")); 36 | ModeButtons.AddToolBarButton(FDynamicTerrainEditorCommands::Get().SculptMode, NAME_None, 37 | LOCTEXT("SculptModeName", "Sculpt"), 38 | LOCTEXT("SculptModeDesc", "Sculpt and reshape the surface of a terrain"), 39 | FSlateIcon(FDynamicTerrainStyle::GetName(), "Plugins.Mode.Sculpt")); 40 | ModeButtons.AddToolBarButton(FDynamicTerrainEditorCommands::Get().FoliageMode, NAME_None, 41 | LOCTEXT("FoliageModeName", "Foliage"), 42 | LOCTEXT("FoliageModeDesc", "Change the foliage generated by a terrain"), 43 | FSlateIcon(FDynamicTerrainStyle::GetName(), "Plugins.Mode.Foliage")); 44 | 45 | // Create the details panel 46 | FPropertyEditorModule& property_editor = FModuleManager::LoadModuleChecked("PropertyEditor"); 47 | FDetailsViewArgs details_args(false, false, false, FDetailsViewArgs::HideNameArea); 48 | DetailsPanel = property_editor.CreateDetailView(details_args); 49 | //DetailsPanel->SetIsPropertyVisibleDelegate(FIsPropertyVisible::CreateRaw(this, &FDynamicTerrainModeToolkit::PropertyVisible)); 50 | 51 | // Put the editor mode settings in the detail panel 52 | FDynamicTerrainMode* mode = (FDynamicTerrainMode*)GetEditorMode(); 53 | if (mode) 54 | { 55 | DetailsPanel->SetObject(mode->Settings, true); 56 | } 57 | 58 | // Add the mode buttons and details panel 59 | SAssignNew(ToolkitWidget, SScrollBox) 60 | + SScrollBox::Slot().Padding(5).HAlign(HAlign_Center) 61 | [ 62 | SNew(SBox) 63 | [ 64 | ModeButtons.MakeWidget() 65 | ] 66 | ] 67 | + SScrollBox::Slot() 68 | [ 69 | SNew(SVerticalBox) + 70 | SVerticalBox::Slot().Padding(0) 71 | [ 72 | DetailsPanel.ToSharedRef() 73 | ] 74 | ]; 75 | 76 | FModeToolkit::Init(InitToolkitHost); 77 | } 78 | 79 | FName FDynamicTerrainModeToolkit::GetToolkitFName() const 80 | { 81 | return FName("DynamicTerrainToolkit"); 82 | } 83 | 84 | FText FDynamicTerrainModeToolkit::GetBaseToolkitName() const 85 | { 86 | return LOCTEXT("TerrainToolkitName", "Dynamic Terrain Toolkit"); 87 | } 88 | 89 | FEdMode* FDynamicTerrainModeToolkit::GetEditorMode() const 90 | { 91 | return GLevelEditorModeTools().GetActiveMode(FDynamicTerrainMode::DynamicTerrainModeID); 92 | } 93 | 94 | TSharedPtr FDynamicTerrainModeToolkit::GetInlineContent() const 95 | { 96 | return ToolkitWidget; 97 | } 98 | 99 | /// Command List Delegates /// 100 | 101 | void FDynamicTerrainModeToolkit::ChangeMode(TerrainModeID ModeID) 102 | { 103 | FDynamicTerrainMode* mode = (FDynamicTerrainMode*)GetEditorMode(); 104 | if (mode != nullptr) 105 | { 106 | // Change the mode 107 | mode->SetMode(ModeID); 108 | } 109 | } 110 | 111 | bool FDynamicTerrainModeToolkit::IsModeEnabled(TerrainModeID ModeID) 112 | { 113 | if (ModeID == TerrainModeID::MANAGE || ModeID == TerrainModeID::CREATE) 114 | { 115 | // Manage mode is always available 116 | return true; 117 | } 118 | else 119 | { 120 | // Other modes are only available if a terrain is selected 121 | FDynamicTerrainMode* mode = (FDynamicTerrainMode*)GetEditorMode(); 122 | if (mode != nullptr) 123 | { 124 | if (mode->GetSelected() != nullptr) 125 | { 126 | return true; 127 | } 128 | } 129 | } 130 | return false; 131 | } 132 | 133 | bool FDynamicTerrainModeToolkit::IsModeActive(TerrainModeID ModeID) 134 | { 135 | FDynamicTerrainMode* mode = (FDynamicTerrainMode*)GetEditorMode(); 136 | if (mode != nullptr) 137 | { 138 | return mode->GetMode() == ModeID; 139 | } 140 | return false; 141 | } 142 | 143 | void FDynamicTerrainModeToolkit::ChangeTool(TerrainToolID ToolID) 144 | { 145 | FDynamicTerrainMode* mode = (FDynamicTerrainMode*)GetEditorMode(); 146 | if (mode != nullptr) 147 | { 148 | // Change the tool 149 | mode->GetTools()->SetTool(ToolID); 150 | 151 | // Refresh the details pane 152 | mode->ToolUpdate(); 153 | } 154 | } 155 | 156 | bool FDynamicTerrainModeToolkit::IsToolEnabled(TerrainToolID ToolID) 157 | { 158 | // Tools are always available if their buttons are clickable 159 | FDynamicTerrainMode* mode = (FDynamicTerrainMode*)GetEditorMode(); 160 | if (mode != nullptr) 161 | { 162 | return true; 163 | } 164 | return false; 165 | } 166 | 167 | bool FDynamicTerrainModeToolkit::IsToolActive(TerrainToolID ToolID) 168 | { 169 | FDynamicTerrainMode* mode = (FDynamicTerrainMode*)GetEditorMode(); 170 | if (mode != nullptr) 171 | { 172 | return mode->GetTools()->ToolID() == ToolID; 173 | } 174 | return false; 175 | } 176 | 177 | void FDynamicTerrainModeToolkit::ChangeBrush(TerrainBrushID BrushID) 178 | { 179 | FDynamicTerrainMode* mode = (FDynamicTerrainMode*)GetEditorMode(); 180 | if (mode != nullptr) 181 | { 182 | mode->GetTools()->SetBrush(BrushID); 183 | } 184 | } 185 | 186 | bool FDynamicTerrainModeToolkit::IsBrushEnabled(TerrainBrushID BrushID) 187 | { 188 | // Brushes are always available if their buttons are clickable 189 | FDynamicTerrainMode* mode = (FDynamicTerrainMode*)GetEditorMode(); 190 | if (mode != nullptr) 191 | { 192 | return true; 193 | } 194 | return false; 195 | } 196 | 197 | bool FDynamicTerrainModeToolkit::IsBrushActive(TerrainBrushID BrushID) 198 | { 199 | FDynamicTerrainMode* mode = (FDynamicTerrainMode*)GetEditorMode(); 200 | if (mode != nullptr) 201 | { 202 | return mode->GetTools()->BrushID() == BrushID; 203 | } 204 | return false; 205 | } 206 | 207 | void FDynamicTerrainModeToolkit::RefreshDetails() 208 | { 209 | DetailsPanel->ForceRefresh(); 210 | } 211 | 212 | #undef LOCTEXT_NAMESPACE -------------------------------------------------------------------------------- /Source/DynamicTerrainEditor/Private/DynamicTerrainStyle.cpp: -------------------------------------------------------------------------------- 1 | #include "DynamicTerrainStyle.h" 2 | 3 | #include "Styling/SlateStyleRegistry.h" 4 | #include "Interfaces/IPluginManager.h" 5 | 6 | TSharedPtr FDynamicTerrainStyle::StyleSet = nullptr; 7 | 8 | void FDynamicTerrainStyle::Initialize() 9 | { 10 | // Initialize only once 11 | if (StyleSet.IsValid()) 12 | { 13 | return; 14 | } 15 | 16 | // Create the style set 17 | StyleSet = MakeShareable(new FSlateStyleSet("DynamicTerrainStyle")); 18 | StyleSet->SetCoreContentRoot(FPaths::EngineContentDir() / TEXT("Slate")); 19 | 20 | // Set the style icons 21 | const FString content = IPluginManager::Get().FindPlugin(TEXT("DynamicTerrain"))->GetContentDir() / "Icons"; 22 | const FVector2D size(40.0f, 40.0f); 23 | 24 | StyleSet->Set("Plugins.Tab", new FSlateImageBrush(content / "icon_Test.png", size)); 25 | 26 | StyleSet->Set("Plugins.Mode.Manage", new FSlateImageBrush(content / "icon_Test.png", size)); 27 | StyleSet->Set("Plugins.Mode.Create", new FSlateImageBrush(content / "icon_Test.png", size)); 28 | StyleSet->Set("Plugins.Mode.Generate", new FSlateImageBrush(content / "icon_Test.png", size)); 29 | StyleSet->Set("Plugins.Mode.Sculpt", new FSlateImageBrush(content / "icon_Test.png", size)); 30 | StyleSet->Set("Plugins.Mode.Foliage", new FSlateImageBrush(content / "icon_Test.png", size)); 31 | 32 | StyleSet->Set("Plugins.Tool.Sculpt", new FSlateImageBrush(content / "icon_Test.png", size)); 33 | StyleSet->Set("Plugins.Tool.Smooth", new FSlateImageBrush(content / "icon_Test.png", size)); 34 | StyleSet->Set("Plugins.Tool.Flatten", new FSlateImageBrush(content / "icon_Test.png", size)); 35 | 36 | StyleSet->Set("Plugins.Brush.Linear", new FSlateImageBrush(content / "icon_Test.png", size)); 37 | StyleSet->Set("Plugins.Brush.Smooth", new FSlateImageBrush(content / "icon_Test.png", size)); 38 | StyleSet->Set("Plugins.Brush.Round", new FSlateImageBrush(content / "icon_Test.png", size)); 39 | StyleSet->Set("Plugins.Brush.Sphere", new FSlateImageBrush(content / "icon_Test.png", size)); 40 | 41 | FSlateStyleRegistry::RegisterSlateStyle(*StyleSet.Get()); 42 | } 43 | 44 | void FDynamicTerrainStyle::Shutdown() 45 | { 46 | if (StyleSet.IsValid()) 47 | { 48 | FSlateStyleRegistry::UnRegisterSlateStyle(*StyleSet.Get()); 49 | ensure(StyleSet.IsUnique()); 50 | StyleSet.Reset(); 51 | } 52 | } 53 | 54 | TSharedPtr FDynamicTerrainStyle::Get() 55 | { 56 | return StyleSet; 57 | } 58 | 59 | const FName FDynamicTerrainStyle::GetName() 60 | { 61 | return StyleSet->GetStyleSetName(); 62 | } -------------------------------------------------------------------------------- /Source/DynamicTerrainEditor/Public/DynamicTerrainAssets.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Factories/Factory.h" 4 | #include "AssetTypeActions_Base.h" 5 | 6 | #include "DynamicTerrainAssets.generated.h" 7 | 8 | static EAssetTypeCategories::Type TerrainAssetCategory; 9 | 10 | // The asset factory for creating terrain foliage spawners in the editor 11 | UCLASS() 12 | class UTerrainFoliageSpawnerFactory : public UFactory 13 | { 14 | GENERATED_BODY() 15 | 16 | public: 17 | UTerrainFoliageSpawnerFactory(const FObjectInitializer& ObjectInitializer); 18 | 19 | virtual UObject* FactoryCreateNew(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override; 20 | }; 21 | 22 | // The asset factory for creating terrain foliage in the editor 23 | UCLASS() 24 | class UTerrainFoliageFactory : public UFactory 25 | { 26 | GENERATED_BODY() 27 | 28 | public: 29 | UTerrainFoliageFactory(const FObjectInitializer& ObjectInitializer); 30 | 31 | virtual UObject* FactoryCreateNew(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override; 32 | }; 33 | 34 | // Asset actions to make the foliage spawner factory work in the editor 35 | class FAssetTypeActions_TerrainFoliageSpawnerFactory : public FAssetTypeActions_Base 36 | { 37 | public: 38 | virtual FText GetName() const override; 39 | virtual FColor GetTypeColor() const override; 40 | virtual UClass* GetSupportedClass() const override; 41 | virtual uint32 GetCategories() override; 42 | }; 43 | 44 | // Asset actions to make the foliage factory work in the editor 45 | class FAssetTypeActions_TerrainFoliageFactory : public FAssetTypeActions_Base 46 | { 47 | public: 48 | virtual FText GetName() const override; 49 | virtual FColor GetTypeColor() const override; 50 | virtual UClass* GetSupportedClass() const override; 51 | virtual uint32 GetCategories() override; 52 | }; -------------------------------------------------------------------------------- /Source/DynamicTerrainEditor/Public/DynamicTerrainEditor.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Modules/ModuleManager.h" 4 | 5 | class FDynamicTerrainEditorModule : public IModuleInterface 6 | { 7 | public: 8 | virtual void StartupModule() override; 9 | virtual void ShutdownModule() override; 10 | }; -------------------------------------------------------------------------------- /Source/DynamicTerrainEditor/Public/DynamicTerrainInterface.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "DynamicTerrainMode.h" 4 | #include "DynamicTerrainModeToolkit.h" 5 | 6 | #include "Framework/Commands/Commands.h" 7 | #include "IDetailCustomization.h" 8 | 9 | class SGeneratorBox : public SCompoundWidget 10 | { 11 | public: 12 | SLATE_BEGIN_ARGS(SGeneratorBox) 13 | {} 14 | SLATE_END_ARGS() 15 | 16 | // Construct the widget 17 | void Construct(const FArguments& InArgs); 18 | // Called when widgets are generated for the combobox list 19 | TSharedRef OptionWidget(TSharedPtr Option); 20 | // Called when a combobox button is selected 21 | void OptionSelect(TSharedPtr SelectOption, ESelectInfo::Type); 22 | }; 23 | 24 | class FDynamicTerrainEditorCommands : public TCommands 25 | { 26 | public: 27 | FDynamicTerrainEditorCommands(); 28 | 29 | // Register the names of the commands 30 | virtual void RegisterCommands() override; 31 | // Map commands to toolkit functions 32 | void MapCommands(FDynamicTerrainModeToolkit* Toolkit) const; 33 | 34 | // UI commands 35 | TSharedPtr CreateMode; 36 | TSharedPtr ManageMode; 37 | TSharedPtr GenerateMode; 38 | TSharedPtr SculptMode; 39 | TSharedPtr FoliageMode; 40 | 41 | TSharedPtr SculptTool; 42 | TSharedPtr SmoothTool; 43 | TSharedPtr FlattenTool; 44 | 45 | TSharedPtr LinearBrush; 46 | TSharedPtr SmoothBrush; 47 | TSharedPtr RoundBrush; 48 | TSharedPtr SphereBrush; 49 | 50 | private: 51 | // Map a command to an interface mode 52 | void MapCommandToMode(FDynamicTerrainModeToolkit* Toolkit, TSharedPtr Command, TerrainModeID ModeID) const; 53 | // Map a command to an interface tool 54 | void MapCommandToTool(FDynamicTerrainModeToolkit* Toolkit, TSharedPtr Command, TerrainToolID ToolID) const; 55 | // Map a command to an interface tool 56 | void MapCommandToBrush(FDynamicTerrainModeToolkit* Toolkit, TSharedPtr Command, TerrainBrushID BrushID) const; 57 | }; 58 | 59 | class FDynamicTerrainDetails : public IDetailCustomization 60 | { 61 | public: 62 | // Create an instance of this detail class 63 | static TSharedRef CreateInstance(); 64 | // Called by the engine when details are ready to be customized 65 | virtual void CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) override; 66 | 67 | // Called when the resize button is clicked 68 | static FReply ResizeButton(); 69 | // Called when the create button is clicked 70 | static FReply CreateButton(); 71 | // Called when the generate button is clicked 72 | static FReply GenerateButton(); 73 | // Called when the change foliage button is clicked 74 | static FReply FoliageButton(); 75 | 76 | protected: 77 | // Get the editor mode using these details 78 | static FDynamicTerrainMode* GetMode(); 79 | // Update brush settings 80 | void UpdateBrush(); 81 | // Get the property handle of a terrain generator parameter 82 | TSharedRef GetGeneratorParameter(IDetailLayoutBuilder& DetailBuilder, int32 Ref); 83 | }; -------------------------------------------------------------------------------- /Source/DynamicTerrainEditor/Public/DynamicTerrainMode.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Terrain.h" 4 | #include "TerrainGenerator.h" 5 | #include "TerrainTools.h" 6 | #include "TerrainBrushDecal.h" 7 | #include "TerrainFoliage.h" 8 | 9 | #include "EdMode.h" 10 | #include "EditorModeRegistry.h" 11 | #include "DynamicTerrainMode.generated.h" 12 | 13 | enum class TerrainModeID 14 | { 15 | CREATE, 16 | MANAGE, 17 | GENERATE, 18 | SCULPT, 19 | FOLIAGE, 20 | NUM 21 | }; 22 | 23 | struct FDynamicTerrainToolMode 24 | { 25 | FDynamicTerrainToolMode(FName Name, TerrainModeID ID) : ModeName(Name), ModeID(ID) {}; 26 | 27 | const FName ModeName; 28 | const TerrainModeID ModeID; 29 | }; 30 | 31 | struct FGeneratorParam 32 | { 33 | FText Name; 34 | bool IsFloat; 35 | float Default = 0.0f; 36 | }; 37 | 38 | struct FTerrainGenerator 39 | { 40 | FTerrainGenerator(FName GennyName) 41 | { 42 | Name = GennyName; 43 | } 44 | 45 | FName Name; 46 | TArray Parameters; 47 | }; 48 | 49 | UCLASS() 50 | class ABrushProxy : public AActor 51 | { 52 | GENERATED_BODY() 53 | 54 | public: 55 | ABrushProxy(); 56 | 57 | virtual bool IsSelectable() const override; 58 | 59 | // Initialize the decal materials 60 | void Initialize(); 61 | // Show or hide the brush decal 62 | void ShowBrush(bool Visible); 63 | // Set the location and size of the brush decal 64 | void SetBrush(FVector Location, FTerrainTool* Tool, ATerrain* Terrain); 65 | 66 | protected: 67 | UPROPERTY() 68 | UBrushDecal* Decal = nullptr; 69 | }; 70 | 71 | constexpr unsigned NUM_PROPERTIES = 10; 72 | 73 | UCLASS() 74 | class UDynamicTerrainSettings : public UObject 75 | { 76 | GENERATED_BODY() 77 | public: 78 | UPROPERTY(EditAnywhere, Category = "Terrain Settings|Terrain") 79 | int32 ComponentSize = 6; 80 | UPROPERTY(EditAnywhere, Category = "Terrain Settings|Terrain") 81 | int32 WidthX = 3; 82 | UPROPERTY(EditAnywhere, Category = "Terrain Settings|Terrain") 83 | int32 WidthY = 3; 84 | UPROPERTY(EditAnywhere, Category = "Terrain Settings|Terrain") 85 | float UVTiling = 1.0f; 86 | 87 | UPROPERTY(EditAnywhere, Category = "Terrain Settings|LOD") 88 | int32 LODLevels = 5; 89 | UPROPERTY(EditAnywhere, Category = "Terrain Settings|LOD") 90 | float LODScale = 0.5; 91 | 92 | UPROPERTY(EditAnywhere, Category = "Brush Settings") 93 | float Size = 10.0f; 94 | UPROPERTY(EditAnywhere, Category = "Brush Settings") 95 | float Falloff = 5.0f; 96 | UPROPERTY(EditAnywhere, Category = "Brush Settings") 97 | float Strength = 1.0f; 98 | 99 | UPROPERTY(EditAnywhere, Category = "Foliage Settings") 100 | TArray Foliage; 101 | 102 | UPROPERTY(EditAnywhere, Category = "Generator") 103 | FString FunctionName; 104 | UPROPERTY(EditAnywhere, Category = "Generator") 105 | int32 IntProperties[NUM_PROPERTIES]; 106 | UPROPERTY(EditAnywhere, Category = "Generator") 107 | float FloatProperties[NUM_PROPERTIES]; 108 | UPROPERTY(EditAnywhere, Category = "Generator") 109 | bool UseRandomSeed = true; 110 | UPROPERTY(EditAnywhere, Category = "Generator") 111 | int32 Seed = 0; 112 | }; 113 | 114 | class FDynamicTerrainMode : public FEdMode 115 | { 116 | public: 117 | FDynamicTerrainMode(); 118 | ~FDynamicTerrainMode(); 119 | 120 | /// Engine Functions /// 121 | 122 | virtual void Enter() override; 123 | virtual void Exit() override; 124 | virtual void Tick(FEditorViewportClient* ViewportClient, float DeltaTime) override; 125 | 126 | // Draw lines around terrain 127 | virtual void Render(const FSceneView* View, FViewport* Viewport, FPrimitiveDrawInterface* PDI) override; 128 | 129 | // Disable delta tracking for a smoother experience when using tools 130 | virtual bool DisallowMouseDeltaTracking() const override; 131 | // Handle clicks in the viewport 132 | virtual bool HandleClick(FEditorViewportClient* ViewportClient, HHitProxy* HitProxy, const FViewportClick& Click) override; 133 | // Handle key input 134 | virtual bool InputKey(FEditorViewportClient* ViewportClient, FViewport* Viewport, FKey Key, EInputEvent Event) override; 135 | 136 | // Always returns true because this mode uses toolkits 137 | bool UsesToolkits() const override; 138 | 139 | /// Command Functions /// 140 | 141 | // Get the command list from the mode toolkit 142 | TSharedRef GetCommandList() const; 143 | 144 | // Get the tools for this mode 145 | FToolSet* GetTools(); 146 | // Get the selected terrain 147 | ATerrain* GetSelected(); 148 | 149 | // Get the ID of the current mode 150 | TerrainModeID GetMode(); 151 | // Get the name of the current mode 152 | const FName GetModeName(); 153 | // Change the current mode 154 | void SetMode(TerrainModeID ModeID); 155 | 156 | // Update the tool settings when the toolkit changes a tool 157 | void ToolUpdate(); 158 | // Update the terrain settings when the mode changes 159 | void ModeUpdate(); 160 | // Rebuild the terrain to match the chosen settings 161 | void ResizeTerrain(); 162 | // Create a new terrain 163 | void CreateTerrain(); 164 | // Change the foliage for a terrain 165 | void ChangeFoliage(); 166 | // Select a terrain object 167 | void SelectTerrain(ATerrain* Terrain); 168 | 169 | // Process a generator command 170 | void ProcessGenerateCommand(); 171 | // Select a different generator 172 | void SelectGenerator(TSharedPtr Generator); 173 | // Get the currently selected generator 174 | TSharedPtr GetGenerator(); 175 | 176 | // The identifier string for this editor mode 177 | const static FEditorModeID DynamicTerrainModeID; 178 | // The settings data used to display settings in the editor via detail customization 179 | UDynamicTerrainSettings* Settings; 180 | // Terrain generator data for creating menus and processing commands 181 | TArray> Generators; 182 | 183 | protected: 184 | // Set to true when clicking the left mouse button 185 | bool MouseClick = false; 186 | // Inverts the tool when shift is held 187 | bool InvertTool = false; 188 | 189 | // Terrain tools for sculpting terrain 190 | FToolSet Tools; 191 | // The map generator object used to generate heightmaps 192 | UMapGenerator* MapGen; 193 | 194 | // Tool modes 195 | TArray Modes; 196 | // The currently selected mode 197 | FDynamicTerrainToolMode* CurrentMode = nullptr; 198 | 199 | // The brush decal used to display the range of sculpting brushes 200 | ABrushProxy* Brush = nullptr; 201 | 202 | // The terrain object being edited 203 | ATerrain* SelectedTerrain = nullptr; 204 | // The name of the last terrain selected 205 | FString TerrainName; 206 | 207 | // The current generator to use when the generate button is clicked in the editor 208 | TSharedPtr CurrentGenerator; 209 | }; -------------------------------------------------------------------------------- /Source/DynamicTerrainEditor/Public/DynamicTerrainModeToolkit.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "TerrainTools.h" 4 | 5 | #include "Toolkits/BaseToolkit.h" 6 | #include "IDetailsView.h" 7 | 8 | class FDynamicTerrainModeToolkit : public FModeToolkit 9 | { 10 | public: 11 | /// Engine Functions /// 12 | 13 | virtual void Init(const TSharedPtr< class IToolkitHost >& InitToolkitHost) override; 14 | 15 | virtual FName GetToolkitFName() const override; 16 | virtual FText GetBaseToolkitName() const override; 17 | virtual FEdMode* GetEditorMode() const override; 18 | virtual TSharedPtr GetInlineContent() const override; 19 | 20 | /// Command List Delegates /// 21 | 22 | void ChangeMode(TerrainModeID ModeID); 23 | bool IsModeEnabled(TerrainModeID ModeID); 24 | bool IsModeActive(TerrainModeID ModeID); 25 | 26 | void ChangeTool(TerrainToolID ToolID); 27 | bool IsToolEnabled(TerrainToolID ToolID); 28 | bool IsToolActive(TerrainToolID ToolID); 29 | 30 | void ChangeBrush(TerrainBrushID BrushID); 31 | bool IsBrushEnabled(TerrainBrushID BrushID); 32 | bool IsBrushActive(TerrainBrushID BrushID); 33 | 34 | void RefreshDetails(); 35 | 36 | protected: 37 | TSharedPtr DetailsPanel; 38 | TSharedPtr ToolkitWidget; 39 | }; -------------------------------------------------------------------------------- /Source/DynamicTerrainEditor/Public/DynamicTerrainStyle.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "CoreMinimal.h" 4 | #include "Styling/SlateStyle.h" 5 | 6 | class FDynamicTerrainStyle 7 | { 8 | public: 9 | // Create the style singleton 10 | static void Initialize(); 11 | // Delete the style singleton 12 | static void Shutdown(); 13 | 14 | // Get the style set 15 | static TSharedPtr Get(); 16 | // Get the name of the style set 17 | static const FName GetName(); 18 | 19 | private: 20 | // The style set singleton instance 21 | static TSharedPtr StyleSet; 22 | }; --------------------------------------------------------------------------------