├── .clang-format ├── .gitignore ├── CashGen.uplugin ├── LICENSE.txt ├── README.md ├── Resources └── Icon128.png └── Source └── CashGen ├── Private ├── CGMCQueue.cpp ├── CGObjectPool.cpp ├── CGTerrainGeneratorWorker.cpp ├── CGTerrainManager.cpp ├── CGTerrainTrackerComponent.cpp ├── CGTile.cpp ├── CashGen.cpp └── cashgenPrivatePCH.h ├── Public ├── CGMCQueue.h ├── CGObjectPool.h ├── CGSettings.h ├── CGTerrainGeneratorWorker.h ├── CGTerrainManager.h ├── CGTerrainTrackerComponent.h ├── CGTile.h ├── CashGen.h └── Struct │ ├── CGIntVector2.h │ ├── CGJob.h │ ├── CGLODConfig.h │ ├── CGLODMeshData.h │ ├── CGMeshData.h │ ├── CGSector.h │ ├── CGTerrainConfig.h │ └── CGTileHandle.h └── cashgen.Build.cs /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | BasedOnStyle: LLVM 4 | IndentWidth: 4 5 | TabWidth: 4 6 | UseTab: Always 7 | Standard: Cpp11 8 | AccessModifierOffset: -4 9 | AlignAfterOpenBracket: DontAlign 10 | AlignEscapedNewlines: Right 11 | AlignTrailingComments: true 12 | AllowShortCaseLabelsOnASingleLine: true 13 | AllowShortFunctionsOnASingleLine: InlineOnly 14 | BreakBeforeBraces: Allman 15 | BreakConstructorInitializersBeforeComma: true 16 | ColumnLimit: 0 17 | PointerAlignment: Left 18 | SpacesInAngles: false 19 | --- 20 | Language: ObjC 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Visual Studio 2015 user specific files 2 | .vs/ 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | 22 | # Compiled Static libraries 23 | *.lai 24 | *.la 25 | *.a 26 | *.lib 27 | 28 | # Executables 29 | *.exe 30 | *.out 31 | *.app 32 | *.ipa 33 | 34 | # These project files can be generated by the engine 35 | *.xcodeproj 36 | *.sln 37 | *.suo 38 | *.opensdf 39 | *.sdf 40 | *.VC.opendb 41 | *.VC.db 42 | 43 | # Precompiled Assets 44 | SourceArt/**/*.png 45 | SourceArt/**/*.tga 46 | 47 | # Binary Files 48 | **/Binaries/* 49 | 50 | # Builds 51 | **/Build/* 52 | 53 | # Don't ignore icon files in Build 54 | !Build/**/*.ico 55 | 56 | # Configuration files generated by the Editor 57 | **/Saved/* 58 | 59 | # Compiled source files for the engine to use 60 | **/Intermediate/* 61 | 62 | 63 | # Cache files for the editor to use 64 | **/DerivedDataCache/* 65 | -------------------------------------------------------------------------------- /CashGen.uplugin: -------------------------------------------------------------------------------- 1 | { 2 | "FileVersion": 3, 3 | "Version": 2, 4 | "VersionName": "0.5", 5 | "FriendlyName": "CashGen", 6 | "Description": "An Infinite Procedural World Generator", 7 | "Category": "Procedural Generation", 8 | "CreatedBy": "Chris Ashworth", 9 | "CreatedByURL": "http://www.cashworth.net", 10 | "DocsURL": "", 11 | "MarketplaceURL": "", 12 | "SupportURL": "", 13 | "EnabledByDefault": false, 14 | "CanContainContent": true, 15 | "IsBetaVersion": false, 16 | "Installed": false, 17 | "Modules": [ 18 | { 19 | "Name": "CashGen", 20 | "Type": "Runtime", 21 | "LoadingPhase": "PostConfigInit" 22 | } 23 | ], 24 | "Plugins": [ 25 | { 26 | "Name": "ProceduralMeshComponent", 27 | "Enabled": true 28 | }, 29 | { 30 | "Name": "UnrealFastNoisePlugin", 31 | "Enabled": true 32 | } 33 | ] 34 | } -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Chris Ashworth 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cashgenUE 2 | Procedural Terrain Generator for UnrealEngine 4.25 3 | 4 | This plugin generates heightmap-based terrain tiles in realtime, and move the tiles around to track a player pawn. 5 | 6 | [![CashGen Demo](http://img.youtube.com/vi/r68mpFKRAbA/0.jpg)](http://www.youtube.com/watch?v=r68mpFKRAbA "Video Title") 7 | 8 | Features: 9 | 10 | * Multithreaded heightmap, erosion and geometry generation 11 | * A simple hydraulic erosion algorithm 12 | * Multiple tile LODs with per-LOD collision, tesselation and subdivision 13 | * Dithered LOD transitions (when using a suitable material instance) 14 | * Slope scalar in vertex colour channel 15 | * Depth map texture generation for water material 16 | 17 | It has dependencies on : 18 | 19 | * UnrealFastNoise by myself, a modular noise generation plugin 20 | 21 | 1. Create a new C++ project 22 | 2. Checkout UnrealFastNoisePlugin into your engine or project plugins folder ( https://github.com/midgen/UnrealFastNoise ) 23 | 3. Checkout this repository into your engine or project plugins folder 24 | 4. Add "CashGen", "UnrealFastNoisePlugin" to your project Build.cs (required to package project) 25 | ```csharp 26 | PrivateDependencyModuleNames.AddRange(new string[] { "CashGen", "UnrealFastNoisePlugin" }); 27 | ``` 28 | 6. Create a new Blueprint based on CGTerrainManager 29 | 7. OnBeginPlay in the blueprint, call SetupTerrain() and fill out all required parameters 30 | 8. Add a CGTerrainTrackerComponent to any actors you wish to have terrain formed around 31 | 9. You can optionally tell the tracker component to hide/disable gravity on the spawned actors until terrain generation is complete 32 | 10. Vertex Colours - Red = slope. Green = the biome mask specified in terrain config 33 | 34 | For samples, please check : 35 | 36 | https://github.com/midgen/CashDemo 37 | 38 | 39 | -------------------------------------------------------------------------------- /Resources/Icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/midgen/cashgenUE/13bfe6436c89e0798587747a1911c458524faf53/Resources/Icon128.png -------------------------------------------------------------------------------- /Source/CashGen/Private/CGMCQueue.cpp: -------------------------------------------------------------------------------- 1 | #include "CGMcQueue.h" 2 | -------------------------------------------------------------------------------- /Source/CashGen/Private/CGObjectPool.cpp: -------------------------------------------------------------------------------- 1 | #include "CGObjectPool.h" 2 | -------------------------------------------------------------------------------- /Source/CashGen/Private/CGTerrainGeneratorWorker.cpp: -------------------------------------------------------------------------------- 1 | #include "CashGen/Public/CGTerrainGeneratorWorker.h" 2 | #include "CashGen/Public/CGTile.h" 3 | 4 | #include "ProceduralMeshComponent.h" 5 | 6 | #include 7 | 8 | #include 9 | 10 | DECLARE_CYCLE_STAT(TEXT("CashGenStat ~ HeightMap"), STAT_HeightMap, STATGROUP_CashGenStat); 11 | DECLARE_CYCLE_STAT(TEXT("CashGenStat ~ Normals"), STAT_Normals, STATGROUP_CashGenStat); 12 | DECLARE_CYCLE_STAT(TEXT("CashGenStat ~ Erosion"), STAT_Erosion, STATGROUP_CashGenStat); 13 | 14 | FCGTerrainGeneratorWorker::FCGTerrainGeneratorWorker(ACGTerrainManager& aTerrainManager, FCGTerrainConfig& aTerrainConfig, TArray>& meshDataPoolPerLOD) 15 | : pTerrainManager(aTerrainManager) 16 | , pTerrainConfig(aTerrainConfig) 17 | , pMeshDataPoolsPerLOD(meshDataPoolPerLOD) 18 | { 19 | } 20 | 21 | FCGTerrainGeneratorWorker::~FCGTerrainGeneratorWorker() 22 | { 23 | } 24 | 25 | bool FCGTerrainGeneratorWorker::Init() 26 | { 27 | IsThreadFinished = false; 28 | return true; 29 | } 30 | 31 | uint32 FCGTerrainGeneratorWorker::Run() 32 | { 33 | // Here's the loop 34 | while (!IsThreadFinished) 35 | { 36 | if (pTerrainManager.myPendingJobQueue.Dequeue(workJob)) 37 | { 38 | workLOD = workJob.LOD; 39 | 40 | workJob.Data = pMeshDataPoolsPerLOD[workLOD].Borrow([&] { return !IsThreadFinished; }); 41 | if (!workJob.Data.IsValid() && IsThreadFinished) 42 | { 43 | // seems borrowing aborted because IsThreadFinished got true. Let's just return 44 | return 1; 45 | } 46 | 47 | pMeshData = workJob.Data.Get(); 48 | 49 | std::chrono::milliseconds startMs = std::chrono::duration_cast( 50 | std::chrono::system_clock::now().time_since_epoch()); 51 | 52 | prepMaps(); 53 | ProcessTerrainMap(); 54 | 55 | workJob.HeightmapGenerationDuration = (std::chrono::duration_cast( 56 | std::chrono::system_clock::now().time_since_epoch()) - 57 | startMs) 58 | .count(); 59 | 60 | startMs = std::chrono::duration_cast( 61 | std::chrono::system_clock::now().time_since_epoch()); 62 | 63 | if (workLOD == 0) 64 | { 65 | { 66 | SCOPE_CYCLE_COUNTER(STAT_Erosion); 67 | 68 | for (int32 i = 0; i < pTerrainConfig.DropletAmount; ++i) 69 | { 70 | ProcessSingleDropletErosion(); 71 | } 72 | } 73 | } 74 | 75 | workJob.ErosionGenerationDuration = (std::chrono::duration_cast( 76 | std::chrono::system_clock::now().time_since_epoch()) - 77 | startMs) 78 | .count(); 79 | 80 | ProcessPerBlockGeometry(); 81 | ProcessPerVertexTasks(); 82 | ProcessSkirtGeometry(); 83 | 84 | pTerrainManager.myUpdateJobQueue.Enqueue(workJob); 85 | } 86 | // Otherwise, take a nap 87 | else 88 | { 89 | FPlatformProcess::Sleep(0.01f); 90 | } 91 | } 92 | 93 | return 1; 94 | } 95 | 96 | void FCGTerrainGeneratorWorker::Stop() 97 | { 98 | IsThreadFinished = true; 99 | } 100 | 101 | void FCGTerrainGeneratorWorker::Exit() 102 | { 103 | } 104 | 105 | void FCGTerrainGeneratorWorker::prepMaps() 106 | { 107 | // TODO : VERTEX COLORS 108 | for (int32 i = 0; i < pMeshData->MyColours.Num(); ++i) 109 | { 110 | pMeshData->MyColours[i].R = 0; 111 | pMeshData->MyColours[i].G = 0; 112 | pMeshData->MyColours[i].B = 0; 113 | pMeshData->MyColours[i].A = 0; 114 | } 115 | } 116 | 117 | void FCGTerrainGeneratorWorker::ProcessTerrainMap() 118 | { 119 | SCOPE_CYCLE_COUNTER(STAT_HeightMap); 120 | // Size of the noise sampling (larger than the actual mesh so we can have seamless normals) 121 | int32 exX = GetNumberOfNoiseSamplePoints(); 122 | int32 exY = exX; 123 | 124 | const int32 XYunits = workLOD == 0 ? pTerrainConfig.TileXUnits : pTerrainConfig.TileXUnits / pTerrainConfig.LODs[workLOD].ResolutionDivisor; 125 | const int32 exUnitSize = workLOD == 0 ? pTerrainConfig.UnitSize : pTerrainConfig.UnitSize * pTerrainConfig.LODs[workLOD].ResolutionDivisor; 126 | 127 | // Calculate the new noisemap 128 | for (int y = 0; y < exY; ++y) 129 | { 130 | for (int x = 0; x < exX; ++x) 131 | { 132 | int32 worldX = (((workJob.mySector.X * XYunits) + x) * exUnitSize) - exUnitSize; 133 | int32 worldY = (((workJob.mySector.Y * XYunits) + y) * exUnitSize) - exUnitSize; 134 | 135 | pMeshData->HeightMap[x + (exX * y)] = pTerrainConfig.NoiseGenerator->GetNoise2D(worldX, worldY); 136 | } 137 | } 138 | // Put heightmap into Red channel 139 | 140 | if (pTerrainConfig.GenerateSplatMap && workLOD == 0) 141 | { 142 | int i = 0; 143 | for (int y = 0; y < pTerrainConfig.TileYUnits; ++y) 144 | { 145 | for (int x = 0; x < pTerrainConfig.TileXUnits; ++x) 146 | { 147 | float& noiseValue = pMeshData->HeightMap[(x + 1) + (exX * (y + 1))]; 148 | 149 | pMeshData->myTextureData[i].R = (uint8)FMath::GetMappedRangeValueClamped(FVector2D(0.0f, 1.0f), FVector2D(0.0f, 255.0f), noiseValue); 150 | 151 | pMeshData->myTextureData[i].G = (uint8)FMath::GetMappedRangeValueClamped(FVector2D(-1.0f, 0.0f), FVector2D(0.0f, 255.0f), noiseValue); 152 | 153 | pMeshData->myTextureData[i].B = i; 154 | 155 | pMeshData->myTextureData[i].A = 0; 156 | 157 | i++; 158 | } 159 | } 160 | } 161 | 162 | // Then put the biome map into the Green vertex colour channel 163 | if (pTerrainConfig.BiomeBlendGenerator) 164 | { 165 | exX -= 2; 166 | exY -= 2; 167 | for (int y = 0; y < exY; ++y) 168 | { 169 | for (int x = 0; x < exX; ++x) 170 | { 171 | int32 worldX = (((workJob.mySector.X * (exX - 1)) + x) * exUnitSize); 172 | int32 worldY = (((workJob.mySector.Y * (exX - 1)) + y) * exUnitSize); 173 | float val = pTerrainConfig.BiomeBlendGenerator->GetNoise2D(worldX, worldY); 174 | 175 | pMeshData->MyColours[x + (exX * y)].G = FMath::Clamp(FMath::RoundToInt(((val + 1.0f) / 2.0f) * 256), 0, 255); 176 | } 177 | } 178 | } 179 | } 180 | 181 | void FCGTerrainGeneratorWorker::AddDepositionToHeightMap() 182 | { 183 | int32 index = 0; 184 | for (float& heightPoint : pMeshData->HeightMap) 185 | { 186 | //heightPoint.Z += (*pDepositionMap)[index]; 187 | ++index; 188 | } 189 | } 190 | 191 | void FCGTerrainGeneratorWorker::erodeHeightMapAtIndex(int32 aX, int32 aY, float aAmount) 192 | { 193 | int32 XUnits = GetNumberOfNoiseSamplePoints(); 194 | float mod1 = 0.5f; 195 | float mod2 = 0.4f; 196 | 197 | pMeshData->HeightMap[aX + (XUnits * aY)] -= aAmount; 198 | pMeshData->HeightMap[aX + (XUnits * (aY + 1))] -= aAmount * mod1; 199 | pMeshData->HeightMap[aX + (XUnits * (aY - 1))] -= aAmount * mod1; 200 | pMeshData->HeightMap[aX + 1 + (XUnits * (aY))] -= aAmount * mod1; 201 | pMeshData->HeightMap[aX - 1 + (XUnits * (aY))] -= aAmount * mod1; 202 | 203 | pMeshData->HeightMap[aX + 1 + (XUnits * (aY + 1))] -= aAmount * mod1; 204 | pMeshData->HeightMap[aX + 1 + (XUnits * (aY - 1))] -= aAmount * mod1; 205 | pMeshData->HeightMap[aX - 1 + (XUnits * (aY + 1))] -= aAmount * mod1; 206 | pMeshData->HeightMap[aX - 1 + (XUnits * (aY - 1))] -= aAmount * mod1; 207 | 208 | // Add to the Red channel for deposition 209 | if (aAmount > 0.0f) 210 | { 211 | //pMeshData->MyVertexData[aX - 1 + ((XUnits - 2) * (aY - 1))].Color.R = FMath::Clamp(pMeshData->MyVertexData[aX - 1 + ((XUnits - 2) * (aY - 1))].Color.R + FMath::RoundToInt(aAmount), 0, 255); 212 | } 213 | // Add to the blue channel for erosion 214 | if (aAmount <= 0.0f) 215 | { 216 | //pMeshData->MyVertexData[aX - 1 + ((XUnits - 2) * (aY - 1))].Color.B = FMath::Clamp(pMeshData->MyVertexData[aX - 1 + ((XUnits - 2) * (aY - 1))].Color.B + FMath::RoundToInt(aAmount * 0.01f), 0, 255); 217 | } 218 | } 219 | 220 | void FCGTerrainGeneratorWorker::ProcessSingleDropletErosion() 221 | { 222 | int32 XUnits = GetNumberOfNoiseSamplePoints(); 223 | int32 YUnits = XUnits; 224 | 225 | // Pick a random start point that isn't on an edge 226 | int32 cX = FMath::RandRange(1, XUnits - 1); 227 | int32 cY = FMath::RandRange(1, YUnits - 1); 228 | 229 | float sedimentAmount = 0.0f; 230 | float waterAmount = 1.0f; 231 | FVector velocity = FVector(0.0f, 0.0f, 1.0f); 232 | 233 | //while (waterAmount > 0.0f && cX > 0 && cX < XUnits - 1 && cY > 0 && cY < YUnits - 1) 234 | //{ 235 | // FVector origin = pMeshData->HeightMap[cX + (XUnits * cY)]; 236 | // if (origin.Z < pTerrainConfig->DropletErosionFloor) 237 | // { 238 | // // Don't care about underwater erosion 239 | // break; 240 | // } 241 | // FVector up = (pMeshData->HeightMap[cX + (XUnits * (cY + 1))] - origin).GetSafeNormal(); 242 | // FVector down = (pMeshData->HeightMap[cX + (XUnits * (cY - 1))] - origin).GetSafeNormal(); 243 | // FVector left = (pMeshData->HeightMap[cX + 1 + (XUnits * (cY))] - origin).GetSafeNormal(); 244 | // FVector right = (pMeshData->HeightMap[cX - 1 + (XUnits * (cY))] - origin).GetSafeNormal(); 245 | 246 | // FVector upleft = (pMeshData->HeightMap[cX + 1 + (XUnits * (cY + 1))] - origin).GetSafeNormal(); 247 | // FVector downleft = (pMeshData->HeightMap[cX + 1 + (XUnits * (cY - 1))] - origin).GetSafeNormal(); 248 | // FVector upright = (pMeshData->HeightMap[cX - 1 + (XUnits * (cY + 1))] - origin).GetSafeNormal(); 249 | // FVector downright = (pMeshData->HeightMap[cX - 1 + (XUnits * (cY - 1))] - origin).GetSafeNormal(); 250 | 251 | // FVector lowestRoute = FVector(0.0f); 252 | 253 | // int32 newCx = cX; 254 | // int32 newCy = cY; 255 | 256 | // if (up.Z < lowestRoute.Z) { lowestRoute = up; newCy++; } 257 | // if (down.Z < lowestRoute.Z) { lowestRoute = down; newCy--; } 258 | // if (left.Z < lowestRoute.Z) { lowestRoute = left; newCx++; } 259 | // if (right.Z < lowestRoute.Z) { lowestRoute = right; newCx--; } 260 | // if (upleft.Z < lowestRoute.Z) { lowestRoute = upleft; newCy++; newCx++; } 261 | // if (upright.Z < lowestRoute.Z) { lowestRoute = upright; newCy++; newCx--; } 262 | // if (downleft.Z < lowestRoute.Z) { lowestRoute = downleft; newCy--; newCx++; } 263 | // if (downright.Z < lowestRoute.Z) { lowestRoute = downright; newCy--; newCx--; } 264 | 265 | // // The amount of sediment to pick up depends on if we are hitting an obstacle 266 | // float sedimentUptake = pTerrainConfig->DropletErosionMultiplier * FVector::DotProduct(velocity, lowestRoute); 267 | // if (sedimentUptake < 0.0f) { sedimentUptake = 0.0f; } 268 | 269 | // sedimentAmount += sedimentUptake; 270 | 271 | // float sedimentDeposit = 0.0f; 272 | // // Deposit sediment if we are carrying too much 273 | // if (sedimentAmount > pTerrainConfig->DropletSedimentCapacity) 274 | // { 275 | // sedimentDeposit = (sedimentAmount - pTerrainConfig->DropletSedimentCapacity) * pTerrainConfig->DropletDespositionMultiplier; 276 | // } 277 | 278 | // // Deposit based on slope 279 | // sedimentDeposit += sedimentAmount * FMath::Clamp(1.0f + lowestRoute.Z, 0.0f, 1.0f); 280 | 281 | // sedimentAmount -= sedimentDeposit; 282 | 283 | // velocity = lowestRoute; 284 | 285 | // erodeHeightMapAtIndex(cX, cY, (sedimentUptake + (sedimentDeposit * -1.0f))); 286 | 287 | // waterAmount -= pTerrainConfig->DropletEvaporationRate; 288 | 289 | // cX = newCx; 290 | // cY = newCy; 291 | } 292 | 293 | void FCGTerrainGeneratorWorker::ProcessPerBlockGeometry() 294 | { 295 | int32 vertCounter = 0; 296 | int32 triCounter = 0; 297 | 298 | int32 xUnits = workLOD == 0 ? pTerrainConfig.TileXUnits : (pTerrainConfig.TileXUnits / pTerrainConfig.LODs[workLOD].ResolutionDivisor); 299 | int32 yUnits = workLOD == 0 ? pTerrainConfig.TileYUnits : (pTerrainConfig.TileYUnits / pTerrainConfig.LODs[workLOD].ResolutionDivisor); 300 | 301 | // Generate the mesh data for each block 302 | for (int32 y = 0; y < yUnits; ++y) 303 | { 304 | for (int32 x = 0; x < xUnits; ++x) 305 | { 306 | UpdateOneBlockGeometry(x, y, vertCounter, triCounter); 307 | } 308 | } 309 | } 310 | 311 | void FCGTerrainGeneratorWorker::ProcessPerVertexTasks() 312 | { 313 | SCOPE_CYCLE_COUNTER(STAT_Normals); 314 | int32 xUnits = workLOD == 0 ? pTerrainConfig.TileXUnits : (pTerrainConfig.TileXUnits / pTerrainConfig.LODs[workLOD].ResolutionDivisor); 315 | int32 yUnits = workLOD == 0 ? pTerrainConfig.TileYUnits : (pTerrainConfig.TileYUnits / pTerrainConfig.LODs[workLOD].ResolutionDivisor); 316 | 317 | int32 rowLength = workLOD == 0 ? pTerrainConfig.TileXUnits + 1 : (pTerrainConfig.TileXUnits / (pTerrainConfig.LODs[workLOD].ResolutionDivisor) + 1); 318 | 319 | for (int32 y = 0; y < yUnits + 1; ++y) 320 | { 321 | for (int32 x = 0; x < xUnits + 1; ++x) 322 | { 323 | FVector normal; 324 | FProcMeshTangent tangent(0.0f, 1.0f, 0.f); 325 | 326 | GetNormalFromHeightMapForVertex(x, y, normal); 327 | 328 | uint8 slopeChan = FMath::RoundToInt((1.0f - FMath::Abs(FVector::DotProduct(normal, FVector::UpVector))) * 256); 329 | pMeshData->MyColours[x + (y * rowLength)].R = slopeChan; 330 | pMeshData->MyNormals[x + (y * rowLength)] = normal; 331 | pMeshData->MyTangents[x + (y * rowLength)] = tangent; 332 | } 333 | } 334 | } 335 | 336 | // Generates the 'skirt' geometry that falls down from the edges of each tile 337 | void FCGTerrainGeneratorWorker::ProcessSkirtGeometry() 338 | { 339 | // Going to do this the simple way, keep code easy to understand! 340 | 341 | int32 numXVerts = workLOD == 0 ? pTerrainConfig.TileXUnits + 1 : (pTerrainConfig.TileXUnits / pTerrainConfig.LODs[workLOD].ResolutionDivisor) + 1; 342 | int32 numYVerts = workLOD == 0 ? pTerrainConfig.TileYUnits + 1 : (pTerrainConfig.TileYUnits / pTerrainConfig.LODs[workLOD].ResolutionDivisor) + 1; 343 | 344 | int32 startIndex = numXVerts * numYVerts; 345 | int32 triStartIndex = ((numXVerts - 1) * (numYVerts - 1) * 6); 346 | 347 | // Bottom Edge verts 348 | for (int i = 0; i < numXVerts; ++i) 349 | { 350 | pMeshData->MyPositions[startIndex + i].X = pMeshData->MyPositions[i].X; 351 | pMeshData->MyPositions[startIndex + i].Y = pMeshData->MyPositions[i].Y; 352 | pMeshData->MyPositions[startIndex + i].Z = -30000.0f; 353 | 354 | pMeshData->MyNormals[startIndex + i] = pMeshData->MyNormals[i]; 355 | } 356 | // bottom edge triangles 357 | for (int i = 0; i < ((numXVerts - 1)); ++i) 358 | { 359 | pMeshData->MyTriangles[triStartIndex + (i * 6)] = i; 360 | pMeshData->MyTriangles[triStartIndex + (i * 6) + 1] = startIndex + i + 1; 361 | pMeshData->MyTriangles[triStartIndex + (i * 6) + 2] = startIndex + i; 362 | 363 | pMeshData->MyTriangles[triStartIndex + (i * 6) + 3] = i + 1; 364 | pMeshData->MyTriangles[triStartIndex + (i * 6) + 4] = startIndex + i + 1; 365 | pMeshData->MyTriangles[triStartIndex + (i * 6) + 5] = i; 366 | } 367 | triStartIndex += ((numXVerts - 1) * 6); 368 | 369 | startIndex = ((numXVerts) * (numYVerts + 1)); 370 | // Top Edge verts 371 | for (int i = 0; i < numXVerts; ++i) 372 | { 373 | pMeshData->MyPositions[startIndex + i].X = pMeshData->MyPositions[i + startIndex - (numXVerts * 2)].X; 374 | pMeshData->MyPositions[startIndex + i].Y = pMeshData->MyPositions[i + startIndex - (numXVerts * 2)].Y; 375 | pMeshData->MyPositions[startIndex + i].Z = -30000.0f; 376 | 377 | pMeshData->MyNormals[startIndex + i] = pMeshData->MyNormals[i + startIndex - (numXVerts * 2)]; 378 | } 379 | // top edge triangles 380 | 381 | for (int i = 0; i < ((numXVerts - 1)); ++i) 382 | { 383 | pMeshData->MyTriangles[triStartIndex + (i * 6)] = i + startIndex - (numXVerts * 2); 384 | pMeshData->MyTriangles[triStartIndex + (i * 6) + 1] = startIndex + i; 385 | pMeshData->MyTriangles[triStartIndex + (i * 6) + 2] = i + startIndex - (numXVerts * 2) + 1; 386 | 387 | pMeshData->MyTriangles[triStartIndex + (i * 6) + 3] = i + startIndex - (numXVerts * 2) + 1; 388 | pMeshData->MyTriangles[triStartIndex + (i * 6) + 4] = startIndex + i; 389 | pMeshData->MyTriangles[triStartIndex + (i * 6) + 5] = startIndex + i + 1; 390 | } 391 | triStartIndex += ((numXVerts - 1) * 6); 392 | 393 | startIndex = numXVerts * (numYVerts + 2); 394 | // Right edge - bit different 395 | for (int i = 0; i < numYVerts - 2; ++i) 396 | { 397 | pMeshData->MyPositions[startIndex + i].X = pMeshData->MyPositions[(i + 1) * numXVerts].X; 398 | pMeshData->MyPositions[startIndex + i].Y = pMeshData->MyPositions[(i + 1) * numXVerts].Y; 399 | pMeshData->MyPositions[startIndex + i].Z = -30000.0f; 400 | 401 | pMeshData->MyNormals[startIndex + i] = pMeshData->MyNormals[(i + 1) * numXVerts]; 402 | } 403 | // Bottom right corner 404 | 405 | pMeshData->MyTriangles[triStartIndex] = 0; 406 | pMeshData->MyTriangles[triStartIndex + 1] = numXVerts * numYVerts; 407 | pMeshData->MyTriangles[triStartIndex + 2] = numXVerts; 408 | 409 | pMeshData->MyTriangles[triStartIndex + 3] = numXVerts; 410 | pMeshData->MyTriangles[triStartIndex + 4] = numXVerts * numYVerts; 411 | pMeshData->MyTriangles[triStartIndex + 5] = numXVerts * (numYVerts + 2); 412 | 413 | // Top right corner 414 | triStartIndex += 6; 415 | 416 | pMeshData->MyTriangles[triStartIndex] = numXVerts * (numYVerts - 1); 417 | pMeshData->MyTriangles[triStartIndex + 1] = (numXVerts * (numYVerts + 2)) + numYVerts - 3; 418 | pMeshData->MyTriangles[triStartIndex + 2] = numXVerts * (numYVerts + 1); 419 | 420 | pMeshData->MyTriangles[triStartIndex + 3] = numXVerts * (numYVerts - 1); 421 | pMeshData->MyTriangles[triStartIndex + 4] = numXVerts * (numYVerts - 2); 422 | pMeshData->MyTriangles[triStartIndex + 5] = (numXVerts * (numYVerts + 2)) + numYVerts - 3; 423 | 424 | // Middle right part! 425 | startIndex = numXVerts * (numYVerts + 2); 426 | triStartIndex += 6; 427 | 428 | for (int i = 0; i < numYVerts - 3; ++i) 429 | { 430 | pMeshData->MyTriangles[triStartIndex + (i * 6)] = numXVerts * (i + 1); 431 | pMeshData->MyTriangles[triStartIndex + (i * 6) + 1] = startIndex + i; 432 | pMeshData->MyTriangles[triStartIndex + (i * 6) + 2] = numXVerts * (i + 2); 433 | 434 | pMeshData->MyTriangles[triStartIndex + (i * 6) + 3] = numXVerts * (i + 2); 435 | pMeshData->MyTriangles[triStartIndex + (i * 6) + 4] = startIndex + i; 436 | pMeshData->MyTriangles[triStartIndex + (i * 6) + 5] = startIndex + i + 1; 437 | } 438 | triStartIndex += ((numYVerts - 3) * 6); 439 | 440 | startIndex += (numYVerts - 2); 441 | // Left edge - bit different 442 | for (int i = 0; i < numYVerts - 2; ++i) 443 | { 444 | pMeshData->MyPositions[startIndex + i].X = pMeshData->MyPositions[((i + 1) * numXVerts) + numXVerts - 1].X; 445 | pMeshData->MyPositions[startIndex + i].Y = pMeshData->MyPositions[((i + 1) * numXVerts) + numXVerts - 1].Y; 446 | pMeshData->MyPositions[startIndex + i].Z = -30000.0f; 447 | 448 | pMeshData->MyNormals[startIndex + i] = pMeshData->MyNormals[((i + 1) * numXVerts) + numXVerts - 1]; 449 | } 450 | // Bottom left corner 451 | 452 | pMeshData->MyTriangles[triStartIndex] = numXVerts - 1; 453 | pMeshData->MyTriangles[triStartIndex + 1] = (numXVerts * 2) - 1; 454 | pMeshData->MyTriangles[triStartIndex + 2] = startIndex; 455 | 456 | pMeshData->MyTriangles[triStartIndex + 3] = startIndex; 457 | pMeshData->MyTriangles[triStartIndex + 4] = (numXVerts * numYVerts) + numXVerts - 1; 458 | pMeshData->MyTriangles[triStartIndex + 5] = numXVerts - 1; 459 | 460 | // Top left corner 461 | triStartIndex += 6; 462 | 463 | pMeshData->MyTriangles[triStartIndex] = (numXVerts * numYVerts) - 1; 464 | pMeshData->MyTriangles[triStartIndex + 1] = (numXVerts * (numYVerts + 2)) - 1; 465 | pMeshData->MyTriangles[triStartIndex + 2] = (numXVerts * (numYVerts + 2)) + ((numYVerts - 2) * 2) - 1; 466 | 467 | pMeshData->MyTriangles[triStartIndex + 3] = (numXVerts * numYVerts) - 1; 468 | pMeshData->MyTriangles[triStartIndex + 4] = (numXVerts * (numYVerts + 2)) + ((numYVerts - 2) * 2) - 1; 469 | pMeshData->MyTriangles[triStartIndex + 5] = (numXVerts * (numYVerts - 2)) + numXVerts - 1; 470 | 471 | // Middle left part! 472 | 473 | triStartIndex += 6; 474 | 475 | for (int i = 0; i < numYVerts - 3; ++i) 476 | { 477 | pMeshData->MyTriangles[triStartIndex + (i * 6)] = (numXVerts * (i + 1)) + numXVerts - 1; 478 | pMeshData->MyTriangles[triStartIndex + (i * 6) + 1] = (numXVerts * (i + 2)) + numXVerts - 1; 479 | pMeshData->MyTriangles[triStartIndex + (i * 6) + 2] = startIndex + i + 1; 480 | 481 | pMeshData->MyTriangles[triStartIndex + (i * 6) + 3] = (numXVerts * (i + 1)) + numXVerts - 1; 482 | pMeshData->MyTriangles[triStartIndex + (i * 6) + 4] = startIndex + i + 1; 483 | pMeshData->MyTriangles[triStartIndex + (i * 6) + 5] = startIndex + i; 484 | } 485 | } 486 | 487 | void FCGTerrainGeneratorWorker::GetNormalFromHeightMapForVertex(const int32& vertexX, const int32& vertexY, FVector& aOutNormal) //, FVector& aOutTangent) 488 | { 489 | FVector result; 490 | 491 | const int32 rowLength = workLOD == 0 ? pTerrainConfig.TileXUnits + 1 : (pTerrainConfig.TileXUnits / (pTerrainConfig.LODs[workLOD].ResolutionDivisor) + 1); 492 | const int32 heightMapRowLength = rowLength + 2; 493 | 494 | // the heightmapIndex for this vertex index 495 | const int32 heightMapIndex = vertexX + 1 + ((vertexY + 1) * heightMapRowLength); 496 | const float worldTileX = workJob.mySector.X * pTerrainConfig.TileXUnits; 497 | const float worldTileY = workJob.mySector.Y * pTerrainConfig.TileYUnits; 498 | const float& unitSize = workLOD == 0 ? pTerrainConfig.UnitSize : pTerrainConfig.UnitSize * pTerrainConfig.LODs[workLOD].ResolutionDivisor; 499 | const float& ampl = pTerrainConfig.Amplitude; 500 | 501 | FVector origin = FVector((worldTileX + vertexX) * unitSize, (worldTileY + vertexY) * unitSize, pMeshData->HeightMap[heightMapIndex] * ampl); 502 | 503 | // Get the 4 neighbouring points 504 | FVector up, down, left, right; 505 | 506 | up = FVector((worldTileX + vertexX) * unitSize, (worldTileY + vertexY + 1) * unitSize, pMeshData->HeightMap[heightMapIndex + heightMapRowLength] * ampl) - origin; 507 | down = FVector((worldTileX + vertexX) * unitSize, (worldTileY + vertexY - 1) * unitSize, pMeshData->HeightMap[heightMapIndex - heightMapRowLength] * ampl) - origin; 508 | left = FVector((worldTileX + vertexX + 1) * unitSize, (worldTileY + vertexY) * unitSize, pMeshData->HeightMap[heightMapIndex + 1] * ampl) - origin; 509 | right = FVector((worldTileX + vertexX - 1) * unitSize, (worldTileY + vertexY) * unitSize, pMeshData->HeightMap[heightMapIndex - 1] * ampl) - origin; 510 | 511 | FVector n1, n2, n3, n4; 512 | 513 | n1 = FVector::CrossProduct(left, up); 514 | n2 = FVector::CrossProduct(up, right); 515 | n3 = FVector::CrossProduct(right, down); 516 | n4 = FVector::CrossProduct(down, left); 517 | 518 | result = n1 + n2 + n3 + n4; 519 | 520 | aOutNormal = result.GetSafeNormal(); 521 | 522 | // We can mega cheap out here as we're dealing with a simple flat grid 523 | //aOutTangent = FRuntimeMeshTangent(left.GetSafeNormal(), false); 524 | } 525 | 526 | void FCGTerrainGeneratorWorker::UpdateOneBlockGeometry(const int32& aX, const int32& aY, int32& aVertCounter, int32& triCounter) 527 | { 528 | int32 thisX = aX; 529 | int32 thisY = aY; 530 | int32 heightMapX = thisX + 1; 531 | int32 heightMapY = thisY + 1; 532 | // LOD adjusted dimensions 533 | int32 rowLength = workLOD == 0 ? pTerrainConfig.TileXUnits + 1 : (pTerrainConfig.TileXUnits / (pTerrainConfig.LODs[workLOD].ResolutionDivisor) + 1); 534 | int32 heightMapRowLength = rowLength + 2; 535 | // LOD adjusted unit size 536 | int32 exUnitSize = workLOD == 0 ? pTerrainConfig.UnitSize : pTerrainConfig.UnitSize * (pTerrainConfig.LODs[workLOD].ResolutionDivisor); 537 | 538 | const int blockX = 0; 539 | const int blockY = 0; 540 | const float& unitSize = pTerrainConfig.UnitSize; 541 | const float& ampl = pTerrainConfig.Amplitude; 542 | 543 | FVector heightMapToWorldOffset = FVector(0.0f, 0.0f, 0.0f); 544 | 545 | // TL 546 | pMeshData->MyPositions[thisX + (thisY * rowLength)] = FVector((blockX + thisX) * exUnitSize, (blockY + thisY) * exUnitSize, pMeshData->HeightMap[heightMapX + (heightMapY * heightMapRowLength)] * ampl) - heightMapToWorldOffset; 547 | // TR 548 | pMeshData->MyPositions[thisX + ((thisY + 1) * rowLength)] = FVector((blockX + thisX) * exUnitSize, (blockY + thisY + 1) * exUnitSize, pMeshData->HeightMap[heightMapX + ((heightMapY + 1) * heightMapRowLength)] * ampl) - heightMapToWorldOffset; 549 | // BL 550 | pMeshData->MyPositions[(thisX + 1) + (thisY * rowLength)] = FVector((blockX + thisX + 1) * exUnitSize, (blockY + thisY) * exUnitSize, pMeshData->HeightMap[(heightMapX + 1) + (heightMapY * heightMapRowLength)] * ampl) - heightMapToWorldOffset; 551 | // BR 552 | pMeshData->MyPositions[(thisX + 1) + ((thisY + 1) * rowLength)] = FVector((blockX + thisX + 1) * exUnitSize, (blockY + thisY + 1) * exUnitSize, pMeshData->HeightMap[(heightMapX + 1) + ((heightMapY + 1) * heightMapRowLength)] * ampl) - heightMapToWorldOffset; 553 | } 554 | 555 | int32 FCGTerrainGeneratorWorker::GetNumberOfNoiseSamplePoints() 556 | { 557 | return workLOD == 0 ? pTerrainConfig.TileXUnits + 3 : (pTerrainConfig.TileXUnits / (pTerrainConfig.LODs[workLOD].ResolutionDivisor)) + 3; 558 | } 559 | -------------------------------------------------------------------------------- /Source/CashGen/Private/CGTerrainManager.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "CashGen/Public/CGTerrainManager.h" 3 | #include "CashGen/Public/CGTerrainGeneratorWorker.h" 4 | #include "CashGen/Public/CGTile.h" 5 | #include "CashGen/Public/Struct/CGJob.h" 6 | #include "CashGen/Public/Struct/CGTileHandle.h" 7 | 8 | #include 9 | 10 | using namespace std::chrono; 11 | 12 | DECLARE_CYCLE_STAT(TEXT("CashGenStat ~ ActorSectorSweeps"), STAT_ActorSectorSweeps, STATGROUP_CashGenStat); 13 | DECLARE_CYCLE_STAT(TEXT("CashGenStat ~ SectorExpirySweeps"), STAT_SectorExpirySweeps, STATGROUP_CashGenStat); 14 | 15 | ACGTerrainManager::ACGTerrainManager() 16 | { 17 | PrimaryActorTick.bCanEverTick = true; 18 | 19 | MyWaterMeshComponent = CreateDefaultSubobject(TEXT("MyWaterComponent")); 20 | MyWaterMeshComponent->SetupAttachment(RootComponent); 21 | } 22 | 23 | ACGTerrainManager::~ACGTerrainManager() 24 | { 25 | } 26 | 27 | void ACGTerrainManager::BeginPlay() 28 | { 29 | Super::BeginPlay(); 30 | 31 | FString threadName = "TerrainWorkerThread"; 32 | 33 | for (int i = 0; i < myTerrainConfig.NumberOfThreads; i++) 34 | { 35 | myWorkerThreads.Add(FRunnableThread::Create(new FCGTerrainGeneratorWorker(*this, myTerrainConfig, myFreeMeshData), 36 | *threadName, 37 | 0, EThreadPriority::TPri_Normal, FPlatformAffinity::GetNoAffinityMask())); 38 | } 39 | } 40 | 41 | void ACGTerrainManager::BeginDestroy() 42 | { 43 | for (auto& thread : myWorkerThreads) 44 | { 45 | if (thread != nullptr) 46 | { 47 | thread->Kill(); 48 | delete thread; 49 | thread = nullptr; 50 | } 51 | } 52 | 53 | Super::BeginDestroy(); 54 | } 55 | 56 | void ACGTerrainManager::Tick(float DeltaSeconds) 57 | { 58 | Super::Tick(DeltaSeconds); 59 | 60 | myTimeSinceLastSweep += DeltaSeconds; 61 | 62 | // Make sure there's no daft number of threads. Unlikely you'll want more than one anyway. 63 | if (myTerrainConfig.NumberOfThreads > FPlatformMisc::NumberOfCores()) 64 | { 65 | myTerrainConfig.NumberOfThreads = FPlatformMisc::NumberOfCores(); 66 | } 67 | 68 | // Now check for Update jobs 69 | for (uint8 i = 0; i < myTerrainConfig.MeshUpdatesPerFrame; i++) 70 | { 71 | FCGJob updateJob; 72 | if (myUpdateJobQueue.Dequeue(updateJob)) 73 | { 74 | milliseconds startMs = duration_cast( 75 | system_clock::now().time_since_epoch()); 76 | 77 | updateJob.myTileHandle.myHandle->UpdateMesh(updateJob.LOD, 78 | updateJob.IsInPlaceUpdate, 79 | updateJob.Data->MyPositions, 80 | updateJob.Data->MyNormals, 81 | updateJob.Data->MyTangents, 82 | updateJob.Data->MyUV0, 83 | updateJob.Data->MyColours, 84 | updateJob.Data->MyTriangles, 85 | updateJob.Data->myTextureData); 86 | 87 | if (myTerrainConfig.UseInstancedWaterMesh) 88 | { 89 | FTransform waterTransform = FTransform(FRotator(0.0f), updateJob.myTileHandle.myHandle->GetActorLocation() + FVector(myTerrainConfig.TileXUnits * myTerrainConfig.UnitSize * 0.5f, myTerrainConfig.TileYUnits * myTerrainConfig.UnitSize * 0.5f, 0.0f), FVector(myTerrainConfig.TileXUnits * myTerrainConfig.UnitSize * 0.01f, myTerrainConfig.TileYUnits * myTerrainConfig.UnitSize * 0.01f, 1.0f)); 90 | MyWaterMeshComponent->UpdateInstanceTransform(updateJob.myTileHandle.myWaterISMIndex, waterTransform, true, true, true); 91 | } 92 | 93 | updateJob.myTileHandle.myHandle->SetActorHiddenInGame(false); 94 | int32 updateMS = (duration_cast( 95 | system_clock::now().time_since_epoch()) - 96 | startMs) 97 | .count(); 98 | 99 | #ifdef UE_BUILD_DEBUG 100 | if (Settings->ShowTimings && updateJob.LOD == 0) 101 | { 102 | GEngine->AddOnScreenDebugMessage(0, 5.f, FColor::Red, TEXT("Heightmap gen " + FString::FromInt(updateJob.HeightmapGenerationDuration) + "ms")); 103 | GEngine->AddOnScreenDebugMessage(1, 5.f, FColor::Red, TEXT("Erosion gen " + FString::FromInt(updateJob.ErosionGenerationDuration) + "ms")); 104 | GEngine->AddOnScreenDebugMessage(2, 5.f, FColor::Red, TEXT("MeshUpdate " + FString::FromInt(updateMS) + "ms")); 105 | } 106 | #endif 107 | 108 | updateJob.Data.Release(); 109 | OnAfterTileCreated(updateJob.myTileHandle.myHandle); 110 | myQueuedSectors.Remove(updateJob.mySector); 111 | } 112 | } 113 | 114 | if (myActorIndex >= myTrackedActors.Num()) 115 | { 116 | myActorIndex = FMath::Min(0, myTrackedActors.Num() - 1); 117 | } 118 | 119 | // Time based sweep of actors to see if any have moved sectors 120 | if (myTimeSinceLastSweep > myTerrainConfig.TileSweepTime && myTrackedActors.Num() > 0) 121 | { 122 | SCOPE_CYCLE_COUNTER(STAT_ActorSectorSweeps); 123 | 124 | // Compare current location to previous 125 | FCGIntVector2 oldSector = myActorLocationMap[myTrackedActors[myActorIndex]]; 126 | FCGIntVector2 newSector = GetSector(myTrackedActors[myActorIndex]->GetActorLocation()); 127 | if (oldSector != newSector) 128 | { 129 | // Take care of spawning new sectors if necessary 130 | SetActorSector(myTrackedActors[myActorIndex], newSector); 131 | 132 | ProcessTilesForActor(myTrackedActors[myActorIndex]); 133 | } 134 | else 135 | { 136 | for (FCGSector& sector : GetRelevantSectorsForActor(myTrackedActors[myActorIndex])) 137 | { 138 | if (myTileHandleMap.Contains(sector.mySector)) 139 | { 140 | myTileHandleMap[sector.mySector].myLastRequiredTimestamp = FDateTime::Now(); 141 | } 142 | } 143 | } 144 | 145 | if (myActorIndex < myTrackedActors.Num() - 1) 146 | { 147 | myActorIndex++; 148 | } 149 | else 150 | { 151 | myActorIndex = 0; 152 | myTimeSinceLastSweep = 0.0f; 153 | } 154 | } 155 | 156 | // TODO: this sucks, don't wanna be iterating over a big map like this 157 | // But the cost is negligible compared to the mesh updates/collision cooking we're doing sooooooo 158 | // Better than all the tiles ticking themselves 159 | 160 | TArray TilesToDelete; 161 | 162 | { 163 | SCOPE_CYCLE_COUNTER(STAT_SectorExpirySweeps); 164 | 165 | for (auto& elem : myTileHandleMap) 166 | { 167 | // The tile hasn't been required free it 168 | if (elem.Value.myLastRequiredTimestamp + myTerrainConfig.TileReleaseDelay < FDateTime::Now()) 169 | { 170 | FreeTile(elem.Value.myHandle, elem.Value.myWaterISMIndex); 171 | TilesToDelete.Push(elem.Key); 172 | } 173 | else if (myTerrainConfig.DitheringLODTransitions) 174 | { 175 | elem.Value.myHandle->TickTransition(DeltaSeconds); 176 | } 177 | } 178 | 179 | for (auto& key : TilesToDelete) 180 | { 181 | myTileHandleMap.Remove(key); 182 | } 183 | } 184 | if (!myIsTerrainComplete && 185 | myTrackedActors.Num() > 0 && 186 | myPendingJobQueue.IsEmpty() && 187 | myUpdateJobQueue.IsEmpty()) 188 | { 189 | BroadcastTerrainComplete(); 190 | myIsTerrainComplete = true; 191 | } 192 | } 193 | 194 | TPair ACGTerrainManager::GetAvailableTile() 195 | { 196 | TPair result; 197 | 198 | if (myFreeTiles.Num()) 199 | { 200 | result.Key = myFreeTiles.Pop(); 201 | if (myTerrainConfig.UseInstancedWaterMesh) 202 | { 203 | result.Value = myFreeWaterMeshIndices.Pop(); 204 | } 205 | } 206 | 207 | if (!result.Key) 208 | { 209 | result.Key = GetWorld()->SpawnActor(ACGTile::StaticClass(), FVector(0.0f, 0.0f, -10000.0f), FRotator(0.0f)); 210 | if (myTerrainConfig.UseInstancedWaterMesh) 211 | { 212 | result.Value = MyWaterMeshComponent->AddInstance(FTransform(FRotator(0.0f), FVector(0.0f, 0.0f, -10000.0f), FVector::OneVector)); 213 | } 214 | } 215 | 216 | return result; 217 | } 218 | 219 | void ACGTerrainManager::FreeTile(ACGTile* aTile, const int32& waterMeshIndex) 220 | { 221 | if (myTerrainConfig.UseInstancedWaterMesh) 222 | { 223 | MyWaterMeshComponent->UpdateInstanceTransform(waterMeshIndex, FTransform(FRotator(0.0f), FVector(0.0f, 0.0f, -100000.0f), FVector(0.1f)), true, true, true); 224 | myFreeWaterMeshIndices.Push(waterMeshIndex); 225 | } 226 | aTile->SetActorHiddenInGame(true); 227 | myFreeTiles.Push(aTile); 228 | } 229 | 230 | void ACGTerrainManager::SetActorSector(const AActor* aActor, const FCGIntVector2& aNewSector) 231 | { 232 | myActorLocationMap[aActor] = aNewSector; 233 | } 234 | 235 | FCGIntVector2 ACGTerrainManager::GetSector(const FVector& aLocation) 236 | { 237 | FCGIntVector2 sector; 238 | 239 | sector.X = FMath::RoundToInt(aLocation.X / (myTerrainConfig.TileXUnits * myTerrainConfig.UnitSize)); 240 | sector.Y = FMath::RoundToInt(aLocation.Y / (myTerrainConfig.TileYUnits * myTerrainConfig.UnitSize)); 241 | 242 | return sector; 243 | } 244 | 245 | TArray ACGTerrainManager::GetRelevantSectorsForActor(const AActor* aActor) 246 | { 247 | TArray result; 248 | 249 | FCGIntVector2 rootSector = GetSector(aActor->GetActorLocation()); 250 | 251 | if (myTerrainConfig.LODs.Num() < 1) 252 | { 253 | return result; 254 | } 255 | 256 | // Always include the sector the pawn is in 257 | result.Add(rootSector); 258 | 259 | const int sweepRange = myTerrainConfig.LODs[myTerrainConfig.LODs.Num() - 1].SectorRadius; 260 | const int sweepRange2 = sweepRange * sweepRange; 261 | 262 | for (int x = 0; x < sweepRange * 2; x++) 263 | { 264 | for (int y = 0; y < sweepRange * 2; y++) 265 | { 266 | FCGSector newSector = FCGSector(rootSector.X - sweepRange + x, rootSector.Y - sweepRange + y, 0); 267 | FCGIntVector2 diff = newSector.mySector - rootSector; 268 | int thisRange = (diff.X * diff.X + diff.Y * diff.Y); 269 | int lod = GetLODForRange(thisRange); 270 | if (newSector != rootSector && lod > -1) 271 | { 272 | newSector.myLOD = lod; 273 | result.Add(newSector); 274 | } 275 | } 276 | } 277 | 278 | return result; 279 | } 280 | 281 | int ACGTerrainManager::GetLODForRange(const int32 aRange) 282 | { 283 | int lowestLOD = 999; 284 | for (int i = myTerrainConfig.LODs.Num() - 1; i >= 0; i--) 285 | { 286 | if (aRange < (myTerrainConfig.LODs[i].SectorRadius * myTerrainConfig.LODs[i].SectorRadius) && lowestLOD > i) 287 | { 288 | lowestLOD = i; 289 | } 290 | } 291 | 292 | return lowestLOD != 999 ? lowestLOD : -1; 293 | } 294 | 295 | void ACGTerrainManager::SetupTerrainGenerator(UUFNNoiseGenerator* aHeightmapGenerator, UUFNNoiseGenerator* aBiomeGenerator) 296 | { 297 | // myTerrainConfig = aTerrainConfig; 298 | 299 | myTerrainConfig.NoiseGenerator = aHeightmapGenerator; 300 | myTerrainConfig.BiomeBlendGenerator = aBiomeGenerator; 301 | 302 | myTerrainConfig.TileOffset = FVector(myTerrainConfig.UnitSize * myTerrainConfig.TileXUnits * 0.5f, myTerrainConfig.UnitSize * myTerrainConfig.TileYUnits * 0.5f, 0.0f); 303 | 304 | AllocateAllMeshDataStructures(); 305 | 306 | isReady = true; 307 | } 308 | 309 | void ACGTerrainManager::AddActorToTrack(AActor* aPawn) 310 | { 311 | myTrackedActors.Add(aPawn); 312 | FCGIntVector2 pawnSector = GetSector(aPawn->GetActorLocation()); 313 | myActorLocationMap.Add(aPawn, pawnSector); 314 | 315 | ProcessTilesForActor(aPawn); 316 | } 317 | 318 | void ACGTerrainManager::RemoveActorToTrack(AActor* aPawn) 319 | { 320 | myTrackedActors.Remove(aPawn); 321 | 322 | myActorLocationMap.Remove(aPawn); 323 | } 324 | 325 | void ACGTerrainManager::CreateTileRefreshJob(FCGJob aJob) 326 | { 327 | if (aJob.LOD != 10) 328 | { 329 | myQueuedSectors.Add(aJob.mySector); 330 | myPendingJobQueue.Enqueue(std::move(aJob)); 331 | } 332 | } 333 | 334 | void ACGTerrainManager::ProcessTilesForActor(const AActor* anActor) 335 | { 336 | for (FCGSector& sector : GetRelevantSectorsForActor(anActor)) 337 | { 338 | bool isExistsAtLowerLOD = myTileHandleMap.Contains(sector.mySector) && myTileHandleMap[sector.mySector].myLOD > sector.myLOD; 339 | // If the sector doesn't have a tile already, or the tile that does exist is a higher LOD 340 | if (!myTileHandleMap.Contains(sector.mySector) || isExistsAtLowerLOD) 341 | { 342 | 343 | FCGTileHandle tileHandle; 344 | // We have to create the tile for this sector 345 | if (!isExistsAtLowerLOD) 346 | { 347 | TPair tile = GetAvailableTile(); 348 | tileHandle.myHandle = tile.Key; 349 | //tileHandle.myHandle->SetActorLocation(FVector(myTerrainConfig.TileXUnits * myTerrainConfig.UnitSize * sector.mySector.X, myTerrainConfig.TileYUnits * myTerrainConfig.UnitSize * sector.mySector.Y, 0.0f)); 350 | 351 | if (myTerrainConfig.UseInstancedWaterMesh) 352 | { 353 | FTransform waterTransform = FTransform(FRotator(0.0f), FVector(myTerrainConfig.TileXUnits * myTerrainConfig.UnitSize * (sector.mySector.X - 0.5f), myTerrainConfig.TileYUnits * myTerrainConfig.UnitSize * (sector.mySector.Y - 0.5f), 0.0f), FVector(myTerrainConfig.TileXUnits * myTerrainConfig.UnitSize * 0.01f, myTerrainConfig.TileYUnits * myTerrainConfig.UnitSize * 0.01f, 1.0f)); 354 | MyWaterMeshComponent->UpdateInstanceTransform(tile.Value, waterTransform, true, true, true); 355 | } 356 | 357 | tileHandle.myWaterISMIndex = tile.Value; 358 | tileHandle.myStatus = ETileStatus::SPAWNED; 359 | tileHandle.myLOD = sector.myLOD; 360 | tileHandle.myLastRequiredTimestamp = FDateTime::Now(); 361 | 362 | // Add it to our sector map 363 | myTileHandleMap.Add(sector.mySector, tileHandle); 364 | } 365 | else 366 | { 367 | myTileHandleMap[sector.mySector].myLOD = sector.myLOD; 368 | tileHandle = myTileHandleMap[sector.mySector]; 369 | } 370 | 371 | // Create the job to generate the new geometry and update the terrain tile 372 | FCGJob job; 373 | job.mySector = sector.mySector; 374 | job.myTileHandle = tileHandle; 375 | job.LOD = sector.myLOD; 376 | job.IsInPlaceUpdate = isExistsAtLowerLOD; 377 | 378 | // TODO: this method needs renaming 379 | tileHandle.myHandle->UpdateSettings(sector.mySector, &myTerrainConfig, FVector(0.f)); 380 | 381 | if (!isExistsAtLowerLOD) 382 | { 383 | tileHandle.myHandle->RepositionAndHide(10); 384 | if (myTerrainConfig.UseInstancedWaterMesh) 385 | { 386 | MyWaterMeshComponent->UpdateInstanceTransform(tileHandle.myWaterISMIndex, FTransform(FRotator(0.0f), tileHandle.myHandle->GetActorLocation(), FVector(myTerrainConfig.TileXUnits * myTerrainConfig.UnitSize * 0.01f, myTerrainConfig.TileYUnits * myTerrainConfig.UnitSize * 0.01f, 1.0f)), true, true, true); 387 | } 388 | } 389 | 390 | CreateTileRefreshJob(std::move(job)); 391 | } 392 | } 393 | } 394 | 395 | /** Allocates data structures and pointers for mesh data **/ 396 | void ACGTerrainManager::AllocateAllMeshDataStructures() 397 | { 398 | for (uint8 lod = 0; lod < myTerrainConfig.LODs.Num(); ++lod) 399 | { 400 | myMeshData.Add(FCGLODMeshData()); 401 | myFreeMeshData.Emplace(); 402 | 403 | myMeshData[lod].Data.Reserve(myTerrainConfig.MeshDataPoolSize); 404 | 405 | for (int j = 0; j < myTerrainConfig.MeshDataPoolSize; ++j) 406 | { 407 | myMeshData[lod].Data.Add(FCGMeshData()); 408 | AllocateDataStructuresForLOD(&myMeshData[lod].Data[j], &myTerrainConfig, lod); 409 | } 410 | } 411 | 412 | for (uint8 lod = 0; lod < myTerrainConfig.LODs.Num(); ++lod) 413 | { 414 | for (int j = 0; j < myTerrainConfig.MeshDataPoolSize; ++j) 415 | { 416 | myFreeMeshData[lod].Add(&myMeshData[lod].Data[j]); 417 | } 418 | } 419 | } 420 | 421 | /************************************************************************ 422 | Allocates all the data structures for a single LOD mesh data 423 | Includes setting up triangles etc. 424 | ************************************************************************/ 425 | bool ACGTerrainManager::AllocateDataStructuresForLOD(FCGMeshData* aData, FCGTerrainConfig* aConfig, const uint8 aLOD) 426 | { 427 | int32 numXVerts = aLOD == 0 ? aConfig->TileXUnits + 1 : (aConfig->TileXUnits / myTerrainConfig.LODs[aLOD].ResolutionDivisor) + 1; 428 | int32 numYVerts = aLOD == 0 ? aConfig->TileYUnits + 1 : (aConfig->TileYUnits / myTerrainConfig.LODs[aLOD].ResolutionDivisor) + 1; 429 | 430 | int32 numTotalVertices = numXVerts * numYVerts + ((numXVerts - 1) * 2) + ((numXVerts - 1) * 2); 431 | 432 | aData->MyPositions.Reserve(numTotalVertices); 433 | aData->MyNormals.Reserve(numTotalVertices); 434 | aData->MyTangents.Reserve(numTotalVertices); 435 | aData->MyColours.Reserve(numTotalVertices); 436 | aData->MyUV0.Reserve(numTotalVertices); 437 | if (myTerrainConfig.GenerateSplatMap) 438 | { 439 | aData->myTextureData.Reserve(aConfig->TileXUnits * aConfig->TileYUnits); 440 | } 441 | 442 | // Generate the per vertex data sets 443 | aData->MyPositions.AddDefaulted(numTotalVertices); 444 | aData->MyNormals.AddDefaulted(numTotalVertices); 445 | aData->MyTangents.AddDefaulted(numTotalVertices); 446 | aData->MyColours.AddDefaulted(numTotalVertices); 447 | aData->MyUV0.AddDefaulted(numTotalVertices); 448 | 449 | if (myTerrainConfig.GenerateSplatMap) 450 | { 451 | for (int32 i = 0; i < (aConfig->TileXUnits * aConfig->TileYUnits); ++i) 452 | { 453 | aData->myTextureData.Emplace(); 454 | } 455 | } 456 | 457 | // Heightmap needs to be larger than the mesh 458 | // Using vectors here is a lot wasteful, but it does make normal/tangent or any other 459 | // Geometric calculations based on the heightmap a bit easier. Easy enough to change to floats 460 | 461 | aData->HeightMap.Reserve((numXVerts + 2) * (numYVerts + 2)); 462 | for (int32 i = 0; i < (numXVerts + 2) * (numYVerts + 2); ++i) 463 | { 464 | aData->HeightMap.Emplace(0.0f); 465 | } 466 | 467 | // Triangle indexes 468 | int32 terrainTris = ((numXVerts - 1) * (numYVerts - 1) * 6); 469 | int32 skirtTris = (((numXVerts - 1) * 2) + ((numYVerts - 1) * 2)) * 6; 470 | int32 numTris = terrainTris + skirtTris; 471 | aData->MyTriangles.Reserve(numTris); 472 | for (int32 i = 0; i < numTris; ++i) 473 | { 474 | aData->MyTriangles.Add(i); 475 | } 476 | 477 | // Now calculate triangles and UVs 478 | int32 triCounter = 0; 479 | int32 thisX, thisY; 480 | int32 rowLength; 481 | 482 | rowLength = aLOD == 0 ? aConfig->TileXUnits + 1 : (aConfig->TileXUnits / myTerrainConfig.LODs[aLOD].ResolutionDivisor + 1); 483 | float maxUV = aLOD == 0 ? 1.0f : 1.0f / aLOD; 484 | 485 | int32 exX = aLOD == 0 ? aConfig->TileXUnits : (aConfig->TileXUnits / myTerrainConfig.LODs[aLOD].ResolutionDivisor); 486 | int32 exY = aLOD == 0 ? aConfig->TileYUnits : (aConfig->TileYUnits / myTerrainConfig.LODs[aLOD].ResolutionDivisor); 487 | 488 | for (int32 y = 0; y < exY; ++y) 489 | { 490 | for (int32 x = 0; x < exX; ++x) 491 | { 492 | 493 | thisX = x; 494 | thisY = y; 495 | //TR 496 | aData->MyTriangles[triCounter] = thisX + ((thisY + 1) * (rowLength)); 497 | triCounter++; 498 | //BL 499 | aData->MyTriangles[triCounter] = (thisX + 1) + (thisY * (rowLength)); 500 | triCounter++; 501 | //BR 502 | aData->MyTriangles[triCounter] = thisX + (thisY * (rowLength)); 503 | triCounter++; 504 | 505 | //BL 506 | aData->MyTriangles[triCounter] = (thisX + 1) + (thisY * (rowLength)); 507 | triCounter++; 508 | //TR 509 | aData->MyTriangles[triCounter] = thisX + ((thisY + 1) * (rowLength)); 510 | triCounter++; 511 | // TL 512 | aData->MyTriangles[triCounter] = (thisX + 1) + ((thisY + 1) * (rowLength)); 513 | triCounter++; 514 | 515 | ////TR 516 | //aData->MyVertexData[thisX + ((thisY + 1) * (rowLength))].UV0 = FVector2D((thisX / rowLength) * maxUV, ((thisY / rowLength) + 1.0f) * maxUV); 517 | ////BR 518 | //aData->MyVertexData[thisX + (thisY * (rowLength))].UV0 = FVector2D((thisX / rowLength) * maxUV, (thisY / rowLength) * maxUV); 519 | ////BL 520 | //aData->MyVertexData[(thisX + 1) + (thisY * (rowLength))].UV0 = FVector2D(((thisX / rowLength) + 1.0f) * maxUV, (thisY / rowLength) * maxUV); 521 | ////TL 522 | //aData->MyVertexData[(thisX + 1) + ((thisY + 1) * (rowLength))].UV0 = FVector2D(((thisX / rowLength) + 1.0f)* maxUV, ((thisY / rowLength) + 1.0f) * maxUV); 523 | 524 | //TR 525 | aData->MyUV0[thisX + ((thisY + 1) * (rowLength))] = FVector2D(thisX * 1.0f / rowLength, (thisY + 1.0f) / rowLength); 526 | //BR 527 | aData->MyUV0[thisX + (thisY * (rowLength))] = FVector2D(thisX * 1.0f / rowLength, thisY * 1.0f / rowLength); 528 | //BL 529 | aData->MyUV0[(thisX + 1) + (thisY * (rowLength))] = FVector2D((thisX + 1.0f) / rowLength, thisY * 1.0f / rowLength); 530 | //TL 531 | aData->MyUV0[(thisX + 1) + ((thisY + 1) * (rowLength))] = FVector2D((thisX + 1.0f) / rowLength, (thisY + 1.0f) / rowLength); 532 | } 533 | } 534 | 535 | return true; 536 | } 537 | -------------------------------------------------------------------------------- /Source/CashGen/Private/CGTerrainTrackerComponent.cpp: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | #include "CGTerrainTrackerComponent.h" 3 | #include "CashGen.h" 4 | #include "CGTerrainManager.h" 5 | #include "Runtime/Engine/Classes/GameFramework/CharacterMovementComponent.h" 6 | #include "Runtime/Engine/Classes/Kismet/GameplayStatics.h" 7 | #include "GameFramework/Character.h" 8 | 9 | 10 | 11 | 12 | // Sets default values for this component's properties 13 | UCGTerrainTrackerComponent::UCGTerrainTrackerComponent() 14 | { 15 | 16 | PrimaryComponentTick.bCanEverTick = true; 17 | 18 | } 19 | 20 | 21 | void UCGTerrainTrackerComponent::OnTerrainComplete() 22 | { 23 | if (HideActorUntilTerrainComplete && !TeleportToSurfaceOnTerrainComplete) 24 | { 25 | GetOwner()->SetActorHiddenInGame(false); 26 | } 27 | 28 | if (DisableCharacterGravityUntilComplete && !TeleportToSurfaceOnTerrainComplete) 29 | { 30 | ACharacter* character = Cast(GetOwner()); 31 | if (character) 32 | { 33 | character->GetCharacterMovement()->GravityScale = 1.0f; 34 | } 35 | } 36 | 37 | isTerrainComplete = true; 38 | 39 | } 40 | 41 | // Called when the game starts 42 | void UCGTerrainTrackerComponent::BeginPlay() 43 | { 44 | Super::BeginPlay(); 45 | 46 | // ... 47 | 48 | } 49 | 50 | 51 | // Called every frame 52 | void UCGTerrainTrackerComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) 53 | { 54 | Super::TickComponent(DeltaTime, TickType, ThisTickFunction); 55 | 56 | if (!MyTerrainManager && !isSetup) 57 | { 58 | TArray results; 59 | UGameplayStatics::GetAllActorsOfClass(GetWorld(), ACGTerrainManager::StaticClass(), results); 60 | 61 | //Should only be one 62 | for (auto& result : results) 63 | { 64 | ACGTerrainManager* thisTM = Cast(result); 65 | if (thisTM && thisTM->isReady) 66 | { 67 | isSetup = true; 68 | thisTM->AddActorToTrack(GetOwner()); 69 | MyTerrainManager = thisTM; 70 | 71 | MyTerrainManager->OnTerrainComplete().AddUObject(this, &UCGTerrainTrackerComponent::OnTerrainComplete); 72 | if (HideActorUntilTerrainComplete) 73 | { 74 | GetOwner()->SetActorHiddenInGame(true); 75 | } 76 | 77 | if (DisableCharacterGravityUntilComplete) 78 | { 79 | ACharacter* character = Cast(GetOwner()); 80 | if (character) 81 | { 82 | character->GetCharacterMovement()->GravityScale = 0.0f; 83 | } 84 | } 85 | if (TeleportToSurfaceOnTerrainComplete) 86 | { 87 | mySpawnLocation = GetOwner()->GetActorLocation(); 88 | } 89 | 90 | 91 | 92 | } 93 | break; 94 | 95 | } 96 | 97 | } 98 | 99 | if (!isSpawnPointFound && isTerrainComplete && TeleportToSurfaceOnTerrainComplete) 100 | { 101 | int32 raycastsRemainingThisFrame = SpawnRayCastsPerFrame; 102 | 103 | while (raycastsRemainingThisFrame > 0) 104 | { 105 | 106 | 107 | FVector traceStart = mySpawnLocation + FVector(FMath::RandRange(-10000.0f, 10000.0f), FMath::RandRange(-100000.0f, 100000.0f), 5000.0f); 108 | FVector traceEnd = traceStart + FVector(0.f, 0.f, -50000.0f); 109 | FCollisionQueryParams traceParams; 110 | 111 | traceParams.bTraceComplex = true; 112 | traceParams.bReturnPhysicalMaterial = true; 113 | 114 | /* const FName TraceTag("SpawnTraceTag"); 115 | 116 | GetWorld()->DebugDrawTraceTag = TraceTag; 117 | 118 | traceParams.TraceTag = TraceTag;*/ 119 | 120 | FHitResult hitResult; 121 | 122 | if (GetWorld()->LineTraceSingleByChannel(hitResult, traceStart, traceEnd, ECC_GameTraceChannel1, traceParams) 123 | && hitResult.Location.Z > 10.0f) 124 | { 125 | 126 | GetOwner()->SetActorLocation(hitResult.Location + FVector(0.0f, 0.0f, 10.0f)); 127 | ACharacter* character = Cast(GetOwner()); 128 | if (character) 129 | { 130 | character->GetCharacterMovement()->GravityScale = 1.0f; 131 | } 132 | GetOwner()->SetActorHiddenInGame(false); 133 | isSpawnPointFound = true; 134 | break; 135 | } 136 | 137 | raycastsRemainingThisFrame--; 138 | } 139 | 140 | } 141 | 142 | 143 | 144 | 145 | } 146 | 147 | void UCGTerrainTrackerComponent::OnUnregister() 148 | { 149 | if (MyTerrainManager) 150 | { 151 | MyTerrainManager->RemoveActorToTrack(GetOwner()); 152 | } 153 | Super::OnUnregister(); 154 | } 155 | 156 | -------------------------------------------------------------------------------- /Source/CashGen/Private/CGTile.cpp: -------------------------------------------------------------------------------- 1 | #include "CGTile.h" 2 | #include "Components/StaticMeshComponent.h" 3 | #include "Struct/CGTerrainConfig.h" 4 | 5 | #include "ProceduralMeshComponent.h" 6 | 7 | DECLARE_CYCLE_STAT(TEXT("CashGenStat ~ RMCUpdate"), STAT_RMCUpdate, STATGROUP_CashGenStat); 8 | 9 | ACGTile::ACGTile() 10 | { 11 | PrimaryActorTick.bCanEverTick = false; 12 | 13 | SphereComponent = CreateDefaultSubobject(TEXT("RootComponent")); 14 | RootComponent = SphereComponent; 15 | 16 | CurrentLOD = 10; 17 | PreviousLOD = 10; 18 | 19 | mySector = FCGIntVector2(0, 0); 20 | } 21 | 22 | ACGTile::~ACGTile() 23 | { 24 | if (myRegion) 25 | { 26 | delete myRegion; 27 | myRegion = nullptr; 28 | } 29 | } 30 | 31 | bool ACGTile::TickTransition(float DeltaSeconds) 32 | { 33 | for (auto& lod : LODStatus) 34 | { 35 | if (lod.Value == ELODStatus::TRANSITION && MaterialInstances.Num() > 0) 36 | { 37 | if (LODTransitionOpacity >= -1.0f) 38 | { 39 | LODTransitionOpacity -= DeltaSeconds; 40 | 41 | if (LODTransitionOpacity > 0.0f) 42 | { 43 | MaterialInstances[lod.Key]->SetScalarParameterValue(FName("TerrainOpacity"), 1.0f - LODTransitionOpacity); 44 | } 45 | else if (PreviousLOD != 10 && PreviousLOD != CurrentLOD) 46 | { 47 | MaterialInstances[PreviousLOD]->SetScalarParameterValue(FName("TerrainOpacity"), LODTransitionOpacity + 1.0f); 48 | } 49 | } 50 | else 51 | { 52 | if (PreviousLOD != 10 && PreviousLOD != CurrentLOD) 53 | { 54 | MeshComponents[PreviousLOD]->SetVisibility(false); 55 | } 56 | 57 | LODTransitionOpacity = 1.0f; 58 | lod.Value = ELODStatus::CREATED; 59 | return true; 60 | } 61 | } 62 | } 63 | return false; 64 | } 65 | 66 | /************************************************************************ 67 | * Move the tile and make it hidden pending a redraw 68 | ************************************************************************/ 69 | void ACGTile::RepositionAndHide(uint8 aNewLOD) 70 | { 71 | SetActorLocation(FVector((TerrainConfigMaster->TileXUnits * TerrainConfigMaster->UnitSize * mySector.X) - TerrainConfigMaster->TileOffset.X, (TerrainConfigMaster->TileYUnits * TerrainConfigMaster->UnitSize * mySector.Y) - TerrainConfigMaster->TileOffset.Y, 0.0f)); 72 | 73 | SetActorHiddenInGame(true); 74 | 75 | CurrentLOD = aNewLOD; 76 | } 77 | 78 | void ACGTile::BeginPlay() 79 | { 80 | Super::BeginPlay(); 81 | } 82 | 83 | void ACGTile::Tick(float DeltaSeconds) 84 | { 85 | } 86 | 87 | /************************************************************************ 88 | * Initial setup of the tile, creates components and material instance 89 | ************************************************************************/ 90 | void ACGTile::UpdateSettings(FCGIntVector2 aOffset, FCGTerrainConfig* aTerrainConfig, FVector aWorldOffset) 91 | { 92 | mySector.X = aOffset.X; 93 | mySector.Y = aOffset.Y; 94 | 95 | if (!IsInitalized) 96 | { 97 | WorldOffset = aWorldOffset; 98 | TerrainConfigMaster = aTerrainConfig; 99 | 100 | // Disable tick if we're not doing lod transitions 101 | 102 | SetActorTickEnabled(TerrainConfigMaster->DitheringLODTransitions && aTerrainConfig->LODs.Num() > 1); 103 | 104 | FString waterCompName = "WaterSMC"; 105 | FTransform waterTransform = FTransform(FRotator::ZeroRotator, FVector(TerrainConfigMaster->TileXUnits * TerrainConfigMaster->UnitSize * 0.5f, TerrainConfigMaster->TileXUnits * TerrainConfigMaster->UnitSize * 0.5f, 0.0f), FVector(TerrainConfigMaster->TileXUnits * TerrainConfigMaster->UnitSize * 0.01f, TerrainConfigMaster->TileYUnits * TerrainConfigMaster->UnitSize * 0.01f, 1.0f)); 106 | MyWaterMeshComponent = NewObject(this, UStaticMeshComponent::StaticClass(), *waterCompName); 107 | MyWaterMeshComponent->SetStaticMesh(TerrainConfigMaster->WaterMesh); 108 | MyWaterMeshComponent->SetRelativeTransform(waterTransform); 109 | MyWaterMeshComponent->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepRelativeTransform); 110 | MyWaterMeshComponent->RegisterComponent(); 111 | 112 | myWaterMaterialInstance = UMaterialInstanceDynamic::Create(TerrainConfigMaster->WaterMaterialInstance, this); 113 | MyWaterMeshComponent->SetMaterial(0, myWaterMaterialInstance); 114 | 115 | for (int32 i = 0; i < aTerrainConfig->LODs.Num(); ++i) 116 | { 117 | 118 | FString compName = "RMC" + FString::FromInt(i); 119 | MeshComponents.Add(i, NewObject(this, UProceduralMeshComponent::StaticClass(), *compName)); 120 | MeshComponents[i]->SetRelativeTransform(FTransform()); 121 | MeshComponents[i]->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepRelativeTransform); 122 | 123 | MeshComponents[i]->BodyInstance.SetResponseToAllChannels(ECR_Block); 124 | MeshComponents[i]->BodyInstance.SetResponseToChannel(ECC_GameTraceChannel1, ECR_Block); 125 | 126 | MeshComponents[i]->bCastDynamicShadow = i == 0 ? TerrainConfigMaster->CastShadows : false; 127 | MeshComponents[i]->bCastStaticShadow = i == 0 ? TerrainConfigMaster->CastShadows : false; 128 | 129 | LODStatus.Add(i, ELODStatus::NOT_CREATED); 130 | 131 | // Create material instances 132 | if (TerrainConfigMaster->TerrainMaterialInstance && !TerrainConfigMaster->MakeDynamicMaterialInstance) 133 | { 134 | MaterialInstance = TerrainConfigMaster->TerrainMaterialInstance; 135 | MeshComponents[i]->SetMaterial(0, MaterialInstance); 136 | } 137 | else if (TerrainConfigMaster->TerrainMaterialInstance && TerrainConfigMaster->MakeDynamicMaterialInstance) 138 | { 139 | MaterialInstances.Add(i, UMaterialInstanceDynamic::Create(TerrainConfigMaster->TerrainMaterialInstance, this)); 140 | MeshComponents[i]->SetMaterial(0, MaterialInstances[i]); 141 | } 142 | } 143 | 144 | if (TerrainConfigMaster->GenerateSplatMap) 145 | { 146 | myTexture = UTexture2D::CreateTransient(TerrainConfigMaster->TileXUnits, TerrainConfigMaster->TileYUnits, EPixelFormat::PF_B8G8R8A8); 147 | myTexture->AddressX = TA_Clamp; 148 | myTexture->AddressY = TA_Clamp; 149 | 150 | myTexture->UpdateResource(); 151 | 152 | myRegion = new FUpdateTextureRegion2D(); 153 | myRegion->Height = TerrainConfigMaster->TileYUnits; 154 | myRegion->Width = TerrainConfigMaster->TileXUnits; 155 | myRegion->SrcX = 0; 156 | myRegion->SrcY = 0; 157 | myRegion->DestX = 0; 158 | myRegion->DestY = 0; 159 | } 160 | 161 | IsInitalized = true; 162 | } 163 | } 164 | 165 | /************************************************************************ 166 | * Draw a simple quad to use as the water plane 167 | ************************************************************************/ 168 | bool ACGTile::CreateWaterMesh() 169 | { 170 | 171 | if (MeshComponents.Num() > 0) 172 | { 173 | TArray myPositions; 174 | TArray myNormals; 175 | TArray myTangents; 176 | TArray myUV0; 177 | FVector normal; 178 | normal = FVector(0.0f, 0.0f, 1.0f); 179 | FProcMeshTangent tangent, tangentX; 180 | 181 | myPositions.Reserve(4); 182 | myNormals.Reserve(4); 183 | myUV0.Reserve(4); 184 | 185 | int32 i = 0; 186 | myPositions.Emplace(); 187 | myNormals.Emplace(); 188 | myTangents.Emplace(); 189 | myUV0.Emplace(); 190 | myPositions[i].X = 0.0f; 191 | myPositions[i].Y = 0.0f; 192 | myPositions[i].Z = 0.0f; 193 | myUV0[i] = FVector2D(0.0f, 0.0f); 194 | tangent = FProcMeshTangent(0.0f, 1.0f, 0.0f); 195 | myNormals[i] = normal; 196 | myTangents[i] = tangent; 197 | 198 | ++i; 199 | 200 | myPositions.Emplace(); 201 | myNormals.Emplace(); 202 | myTangents.Emplace(); 203 | myUV0.Emplace(); 204 | myPositions[i].X = 0.0f; 205 | myPositions[i].Y = TerrainConfigMaster->TileYUnits * TerrainConfigMaster->UnitSize; 206 | myPositions[i].Z = 0.0f; 207 | myUV0[i] = FVector2D(1.0f, 0.0f); 208 | tangentX = FProcMeshTangent(0.0f, 1.0f, 0.0f); 209 | myNormals[i] = normal; 210 | myTangents[i] = tangentX; 211 | 212 | ++i; 213 | 214 | myPositions.Emplace(); 215 | myNormals.Emplace(); 216 | myTangents.Emplace(); 217 | myUV0.Emplace(); 218 | myPositions[i].X = TerrainConfigMaster->TileXUnits * TerrainConfigMaster->UnitSize; 219 | myPositions[i].Y = TerrainConfigMaster->TileYUnits * TerrainConfigMaster->UnitSize; 220 | myPositions[i].Z = 0.0f; 221 | myUV0[i] = FVector2D(1.0f, 1.0f); 222 | 223 | tangentX = FProcMeshTangent(0.0f, 1.0f, 0.0f); 224 | myNormals[i] = normal; 225 | myTangents[i] = tangentX; 226 | ++i; 227 | 228 | myPositions.Emplace(); 229 | myNormals.Emplace(); 230 | myTangents.Emplace(); 231 | myUV0.Emplace(); 232 | myPositions[i].X = TerrainConfigMaster->TileXUnits * TerrainConfigMaster->UnitSize; 233 | myPositions[i].Y = 0.0f; 234 | myPositions[i].Z = 0.0f; 235 | myUV0[i] = FVector2D(0.0f, 1.0f); 236 | tangentX = FProcMeshTangent(0.0f, 1.0f, 0.0f); 237 | myNormals[i] = normal; 238 | myTangents[i] = tangentX; 239 | 240 | TArray myIndices; 241 | myIndices.Reserve(6); 242 | myIndices.Emplace(0); 243 | myIndices.Emplace(1); 244 | myIndices.Emplace(2); 245 | myIndices.Emplace(2); 246 | myIndices.Emplace(3); 247 | myIndices.Emplace(0); 248 | 249 | MeshComponents[0]->CreateMeshSection(1, myPositions, myIndices, myNormals, myUV0, TArray(), myTangents, true); 250 | 251 | myWaterMaterialInstance = UMaterialInstanceDynamic::Create(TerrainConfigMaster->WaterMaterialInstance, this); 252 | MeshComponents[0]->SetMaterial(1, myWaterMaterialInstance); 253 | 254 | return true; 255 | } 256 | return false; 257 | } 258 | 259 | /************************************************************************ 260 | * Updates the mesh for a given LOD and starts the transition effects 261 | ************************************************************************/ 262 | void ACGTile::UpdateMesh(uint8 aLOD, bool aIsInPlaceUpdate, 263 | TArray& aPositions, TArray& aNormals, TArray& aTangents, TArray& aUV0s, TArray& aColours, TArray& aTriangles, TArray& aTextureData) 264 | { 265 | SCOPE_CYCLE_COUNTER(STAT_RMCUpdate); 266 | SetActorHiddenInGame(false); 267 | 268 | PreviousLOD = CurrentLOD; 269 | CurrentLOD = aLOD; 270 | LODTransitionOpacity = 1.0f; 271 | 272 | for (int32 i = 0; i < TerrainConfigMaster->LODs.Num(); ++i) 273 | { 274 | if (i == aLOD) 275 | { 276 | if (LODStatus[i] == ELODStatus::NOT_CREATED) 277 | { 278 | MeshComponents[i]->CreateMeshSection(0, aPositions, aTriangles, aNormals, aUV0s, aColours, aTangents, TerrainConfigMaster->LODs[aLOD].isCollisionEnabled); 279 | MeshComponents[i]->RegisterComponent(); 280 | LODStatus.Add(i, ELODStatus::TRANSITION); 281 | } 282 | else 283 | { 284 | MeshComponents[i]->UpdateMeshSection(0, aPositions, aNormals, aUV0s, aColours, aTangents); 285 | LODStatus.Add(i, ELODStatus::TRANSITION); 286 | } 287 | 288 | MeshComponents[i]->SetVisibility(true); 289 | } 290 | else if (!aIsInPlaceUpdate) 291 | { 292 | MeshComponents[i]->SetVisibility(false); 293 | } 294 | } 295 | 296 | if (aLOD == 0 && TerrainConfigMaster->GenerateSplatMap && TerrainConfigMaster->MakeDynamicMaterialInstance && MaterialInstances.Num() > 0) 297 | { 298 | 299 | myTexture->UpdateTextureRegions(0, 1, myRegion, 4 * TerrainConfigMaster->TileXUnits, 4, (uint8*)aTextureData.GetData()); 300 | 301 | MaterialInstances[0]->SetTextureParameterValue("SplatMap", myTexture); 302 | myWaterMaterialInstance->SetTextureParameterValue("SplatMap", myTexture); 303 | } 304 | 305 | if (TerrainConfigMaster->LODs[aLOD].isCollisionEnabled) 306 | { 307 | MyWaterMeshComponent->SetCollisionEnabled(TerrainConfigMaster->WaterCollision); 308 | } 309 | else 310 | { 311 | MyWaterMeshComponent->SetCollisionEnabled(ECollisionEnabled::NoCollision); 312 | } 313 | } 314 | 315 | UMaterialInstanceDynamic* ACGTile::GetMaterialInstanceDynamic(const uint8 aLOD) 316 | { 317 | if (aLOD < MaterialInstances.Num() - 1) 318 | { 319 | return MaterialInstances[aLOD]; 320 | } 321 | 322 | return nullptr; 323 | } 324 | -------------------------------------------------------------------------------- /Source/CashGen/Private/CashGen.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. 2 | 3 | #include "CashGen/Public/CashGen.h" 4 | #include "CashGen/Public/CGSettings.h" 5 | #include 6 | #include 7 | 8 | //#include "cashgenPrivatePCH.h" 9 | 10 | #define LOCTEXT_NAMESPACE "FCashGen" 11 | 12 | void FCashGen::StartupModule() 13 | { 14 | // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module 15 | 16 | // register settings 17 | ISettingsModule* SettingsModule = FModuleManager::GetModulePtr("Settings"); 18 | 19 | if (SettingsModule != nullptr) 20 | { 21 | ISettingsSectionPtr SettingsSection = SettingsModule->RegisterSettings("Project", "Plugins", "CashGen", 22 | LOCTEXT("CashGenSettingsName", "CashGen"), 23 | LOCTEXT("CashGenSettingsDescription", "Configure the CashGen Plugin"), 24 | GetMutableDefault() 25 | ); 26 | 27 | if (SettingsSection.IsValid()) 28 | { 29 | SettingsSection->OnModified().BindRaw(this, &FCashGen::HandleSettingsSaved); 30 | } 31 | } 32 | } 33 | 34 | void FCashGen::ShutdownModule() 35 | { 36 | // This function may be called during shutdown to clean up your module. For modules that support dynamic reloading, 37 | // we call this function before unloading the module. 38 | 39 | // unregister settings 40 | ISettingsModule* SettingsModule = FModuleManager::GetModulePtr("Settings"); 41 | 42 | if (SettingsModule != nullptr) 43 | { 44 | SettingsModule->UnregisterSettings("Project", "Plugins", "CashGen"); 45 | } 46 | } 47 | 48 | bool FCashGen::HandleSettingsSaved() 49 | { 50 | //RestartServices(); 51 | 52 | return true; 53 | } 54 | 55 | #undef LOCTEXT_NAMESPACE 56 | 57 | IMPLEMENT_MODULE(FCashGen, CashGen) -------------------------------------------------------------------------------- /Source/CashGen/Private/cashgenPrivatePCH.h: -------------------------------------------------------------------------------- 1 | // Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. 2 | 3 | #include "CashGen.h" 4 | 5 | // You should place include statements to your module's private header files here. You only need to 6 | // add includes for headers that are used in most of your module's source files though. -------------------------------------------------------------------------------- /Source/CashGen/Public/CGMCQueue.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | /** 6 | * A multi consumer threadsafe queue 7 | */ 8 | template 9 | class TCGMcQueueBase final { 10 | public: 11 | void Enqueue(T&& job) { 12 | bool success = queue_.Enqueue(std::move(job)); 13 | check(success); 14 | } 15 | 16 | bool Dequeue(T& job) { 17 | // TQueue is not threadsafe for multiple consumers. We need to serialize consumers using a lock. 18 | // There is more performant implementations of MC queues, but we don't plan to use this queue 19 | // on a perf critical path. 20 | std::lock_guard lock(consumerMutex_); 21 | return queue_.Dequeue(job); 22 | } 23 | 24 | bool IsEmpty() const { 25 | return queue_.IsEmpty(); 26 | } 27 | 28 | private: 29 | std::mutex consumerMutex_; 30 | TQueue queue_; 31 | }; 32 | 33 | /** 34 | * A multi producer multi consumer threadsafe queue. 35 | */ 36 | template using TCGMpmcQueue = TCGMcQueueBase; 37 | 38 | /** 39 | * A single producer multi consumer threadsafe queue. 40 | */ 41 | template using TCGSpmcQueue = TCGMcQueueBase; 42 | -------------------------------------------------------------------------------- /Source/CashGen/Public/CGObjectPool.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | template class TCGBorrowedObject; 8 | 9 | /** 10 | * A pool of objects that can be borrowed and returned. 11 | * 12 | * This class is threadsafe. 13 | * You can add and borrow objects from different threads concurrently. 14 | */ 15 | template 16 | class TCGObjectPool final { 17 | public: 18 | /** 19 | * Borrow an object from the pool. It will be removed from the pool 20 | * and you can use it. Once the TCGBorrowedObject is destructed, 21 | * it will automatically be put back into the object pool. 22 | * 23 | * If there is no object available in the pool, this will block 24 | * until there is one available. 25 | */ 26 | TCGBorrowedObject Borrow(std::function shouldContinueToBlock) { 27 | return TCGBorrowedObject(impl_, impl_->Borrow(std::move(shouldContinueToBlock))); 28 | } 29 | 30 | /** 31 | * Add a new object from the pool. After adding it, it can be borrowed. 32 | */ 33 | void Add(T* object) { 34 | impl_->Add(object); 35 | } 36 | 37 | // Copying and moving is forbidden. Because of Impl, this would 38 | // follow reference semantics and could be confusing. 39 | TCGObjectPool(const TCGObjectPool&) = delete; 40 | TCGObjectPool(TCGObjectPool&&) noexcept = delete; 41 | TCGObjectPool& operator=(const TCGObjectPool&) = delete; 42 | TCGObjectPool& operator=(TCGObjectPool&&) noexcept = delete; 43 | 44 | TCGObjectPool() 45 | : impl_(MakeShared()) {} 46 | 47 | private: 48 | class Impl final { 49 | public: 50 | Impl() = default; 51 | 52 | // copying and moving is forbidden because there might be objects 53 | // borrowed from this pool that would then be put back into the wrong pool 54 | Impl(const Impl&) = delete; 55 | Impl(Impl&&) noexcept = delete; 56 | Impl& operator=(const Impl&) = delete; 57 | Impl& operator=(Impl&&) noexcept = delete; 58 | 59 | void Add(T* object) { 60 | check(nullptr != object); 61 | 62 | std::lock_guard lock(mutex_); 63 | freeObjects_.Push(object); 64 | cv_.notify_one(); 65 | } 66 | 67 | T* Borrow(std::function shouldContinueToBlock) { 68 | std::unique_lock lock(mutex_); 69 | do { 70 | // Block until an object becomes available. 71 | // Every 100ms, we check if shouldContinueToBlock still returns true. If not, we abort. 72 | if (cv_.wait_for(lock, std::chrono::milliseconds(100), [&]() { return freeObjects_.Num() > 0; })) { 73 | // We found an object. Borrow and return it. 74 | return freeObjects_.Pop(EAllowShrinking::No); 75 | } 76 | } while (shouldContinueToBlock()); 77 | 78 | return nullptr; 79 | } 80 | private: 81 | std::mutex mutex_; 82 | std::condition_variable cv_; 83 | TArray freeObjects_; 84 | }; 85 | 86 | friend class TCGBorrowedObject; 87 | 88 | TSharedRef impl_; 89 | }; 90 | 91 | /** 92 | * A handle to an object borrowed from the object pool. 93 | * This class is *not* threadsafe. You cannot share TCGBorrowedObjects between threads. 94 | */ 95 | template 96 | class TCGBorrowedObject final { 97 | public: 98 | /** 99 | * Get a pointer to the borrowed object. 100 | */ 101 | T* Get() { 102 | T* result = impl_->Get(); 103 | check(nullptr != result && "TCGBorrowedObject instance does not contain a borrowed object"); 104 | return result; 105 | } 106 | 107 | T* operator->() { 108 | return Get(); 109 | } 110 | 111 | /** 112 | * Return true if this instance contains a valid object. 113 | */ 114 | bool IsValid() const { 115 | return nullptr != impl_->Get(); 116 | } 117 | 118 | /** 119 | * Return the borrowed object to the pool. 120 | */ 121 | void Release() { 122 | impl_->Release(); 123 | } 124 | 125 | TCGBorrowedObject() 126 | : impl_(MakeShared(TWeakPtr::Impl, ESPMode::ThreadSafe>(), nullptr)) { 127 | } 128 | 129 | private: 130 | using ObjectPoolImpl = typename TCGObjectPool::Impl; 131 | 132 | explicit TCGBorrowedObject(TWeakPtr pool, T* object) 133 | : impl_(MakeShared(std::move(pool), object)) { 134 | } 135 | 136 | class BorrowedObjectImpl final { 137 | private: 138 | TWeakPtr pool_; 139 | T* object_; 140 | public: 141 | T* Get() { 142 | return object_; 143 | } 144 | 145 | explicit BorrowedObjectImpl(TWeakPtr pool, T* object) 146 | : pool_(std::move(pool)), object_(object) { 147 | } 148 | 149 | /** 150 | * Return the borrowed object to the pool. 151 | */ 152 | void Release() { 153 | if (nullptr != object_) { 154 | if (auto pool = pool_.Pin()) { 155 | pool->Add(object_); 156 | } 157 | pool_ = nullptr; 158 | object_ = nullptr; 159 | } 160 | check(!pool_.IsValid() && "Class invariant: If object_ is nullptr, so must be pool_."); 161 | } 162 | 163 | /** 164 | * On destruction, we put the object back into the pool. 165 | */ 166 | ~BorrowedObjectImpl() { 167 | Release(); 168 | } 169 | 170 | // copying is forbidden because it would break the RAII pattern of putting 171 | // objects back into the pool. 172 | BorrowedObjectImpl(const BorrowedObjectImpl&) = delete; 173 | BorrowedObjectImpl& operator=(const BorrowedObjectImpl&) = delete; 174 | 175 | BorrowedObjectImpl(BorrowedObjectImpl&& rhs) noexcept 176 | : pool_(std::move(rhs.pool_)), object_(rhs.object_) { 177 | // make sure the old BorrowedObjectImpl doesn't put anything back into the pool 178 | rhs.pool_ = nullptr; 179 | rhs.object_ = nullptr; 180 | } 181 | 182 | BorrowedObjectImpl& operator=(BorrowedObjectImpl&& rhs) noexcept { 183 | pool_ = std::move(rhs.pool_); 184 | object_ = rhs.object_; 185 | // make sure the old BorrowedObjectImpl doesn't put anything back into the pool 186 | rhs.pool_ = nullptr; 187 | rhs.object_ = nullptr; 188 | } 189 | }; 190 | 191 | // We use shared_ptr to get refcounting when TCGBorrowedObjects are copied around 192 | TSharedRef impl_; 193 | 194 | friend class TCGObjectPool; 195 | }; 196 | -------------------------------------------------------------------------------- /Source/CashGen/Public/CGSettings.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "CGSettings.generated.h" 4 | 5 | /** 6 | * Implements the settings for the CashGen Plugin 7 | */ 8 | UCLASS(config = CashGen, BlueprintType) 9 | class CASHGEN_API UCGSettings : public UObject 10 | { 11 | GENERATED_BODY() 12 | 13 | public: 14 | UPROPERTY(config, EditAnywhere, BlueprintReadOnly, Category = Debug) 15 | bool ShowTimings = false; 16 | }; 17 | -------------------------------------------------------------------------------- /Source/CashGen/Public/CGTerrainGeneratorWorker.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "CashGen/Public/CGTerrainManager.h" 3 | #include "CashGen/Public/Struct/CGMeshData.h" 4 | #include "CashGen/Public/Struct/CGTerrainConfig.h" 5 | 6 | struct FCGJob; 7 | 8 | class CASHGEN_API FCGTerrainGeneratorWorker : public FRunnable 9 | { 10 | public: 11 | FCGTerrainGeneratorWorker(ACGTerrainManager& aTerrainManager, 12 | FCGTerrainConfig& aTerrainConfig, TArray>& meshDataPoolPerLOD); 13 | 14 | virtual ~FCGTerrainGeneratorWorker(); 15 | 16 | virtual bool Init(); 17 | virtual uint32 Run(); 18 | virtual void Stop(); 19 | virtual void Exit(); 20 | 21 | private: 22 | ACGTerrainManager& pTerrainManager; 23 | FCGTerrainConfig& pTerrainConfig; 24 | TArray>& pMeshDataPoolsPerLOD; 25 | FCGJob workJob; 26 | uint8 workLOD; 27 | 28 | FCGMeshData* pMeshData; 29 | 30 | bool IsThreadFinished; 31 | 32 | void prepMaps(); 33 | void ProcessTerrainMap(); 34 | void AddDepositionToHeightMap(); 35 | void ProcessSingleDropletErosion(); 36 | void ProcessPerBlockGeometry(); 37 | void ProcessPerVertexTasks(); 38 | void ProcessSkirtGeometry(); 39 | TCGBorrowedObject BorrowMeshData(); 40 | 41 | void erodeHeightMapAtIndex(int32 aX, int32 aY, float aAmount); 42 | void GetNormalFromHeightMapForVertex(const int32& vertexX, const int32& vertexY, FVector& aOutNormal); // , FVector& aOutTangent); 43 | 44 | void UpdateOneBlockGeometry(const int32& aX, const int32& aY, int32& aVertCounter, int32& triCounter); 45 | 46 | int32 GetNumberOfNoiseSamplePoints(); 47 | }; 48 | -------------------------------------------------------------------------------- /Source/CashGen/Public/CGTerrainManager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "CashGen/Public/CGMcQueue.h" 4 | #include "CashGen/Public/CGObjectPool.h" 5 | #include "CashGen/Public/CGSettings.h" 6 | #include "CashGen/Public/Struct/CGJob.h" 7 | #include "CashGen/Public/Struct/CGLODMeshData.h" 8 | #include "CashGen/Public/Struct/CGMeshData.h" 9 | #include "CashGen/Public/Struct/CGSector.h" 10 | #include "CashGen/Public/Struct/CGTerrainConfig.h" 11 | #include "CashGen/Public/Struct/CGTileHandle.h" 12 | #include "CashGen/Public/Struct/CGIntVector2.h" 13 | 14 | #include 15 | #include 16 | 17 | #include "CGTerrainManager.generated.h" 18 | 19 | class ACGTile; 20 | 21 | UCLASS(BlueprintType, Blueprintable) 22 | class CASHGEN_API ACGTerrainManager : public AActor 23 | { 24 | GENERATED_BODY() 25 | 26 | public: 27 | ACGTerrainManager(); 28 | ~ACGTerrainManager(); 29 | 30 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "CashGen") 31 | UCGSettings* Settings = GetMutableDefault(); 32 | 33 | /* Event called when initial terrain generation is complete */ 34 | DECLARE_EVENT(ACGTerrainManager, FTerrainCompleteEvent) 35 | FTerrainCompleteEvent& OnTerrainComplete() { return TerrainCompleteEvent; } 36 | 37 | /* Returns true once terrain has been configured */ 38 | bool isReady = false; 39 | 40 | /* Main entry point for starting terrain generation */ 41 | UFUNCTION(BlueprintCallable, Category = "CashGen") 42 | void SetupTerrainGenerator(UUFNNoiseGenerator* aHeightmapGenerator, UUFNNoiseGenerator* aBiomeGenerator /*FCGTerrainConfig aTerrainConfig*/); 43 | 44 | /* Add a new actor to track and generate terrain tiles around */ 45 | UFUNCTION(BlueprintCallable, Category = "CashGen") 46 | void AddActorToTrack(AActor* aActor); 47 | 48 | /* Add a new actor to track and generate terrain tiles around */ 49 | UFUNCTION(BlueprintCallable, Category = "CashGen") 50 | void RemoveActorToTrack(AActor* aActor); 51 | 52 | // Pending job queue, worker threads take jobs from here 53 | TCGSpmcQueue myPendingJobQueue; 54 | 55 | // Update queue, jobs get sent here from the worker thread 56 | TQueue myUpdateJobQueue; 57 | 58 | virtual void BeginPlay() override; 59 | virtual void Tick(float DeltaSeconds) override; 60 | void BeginDestroy() override; 61 | 62 | UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "CashGen") 63 | UHierarchicalInstancedStaticMeshComponent* MyWaterMeshComponent; 64 | 65 | UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "CashGen") 66 | FCGTerrainConfig myTerrainConfig; 67 | 68 | UFUNCTION(BlueprintImplementableEvent, Category = "CashGen|Events") 69 | void OnAfterTileCreated(ACGTile* tile); 70 | 71 | protected: 72 | void BroadcastTerrainComplete() 73 | { 74 | TerrainCompleteEvent.Broadcast(); 75 | } 76 | 77 | private: 78 | void SetActorSector(const AActor* aActor, const FCGIntVector2& aNewSector); 79 | void AllocateAllMeshDataStructures(); 80 | bool AllocateDataStructuresForLOD(FCGMeshData* aData, FCGTerrainConfig* aConfig, const uint8 aLOD); 81 | int GetLODForRange(const int32 aRange); 82 | void CreateTileRefreshJob(FCGJob aJob); 83 | void ProcessTilesForActor(const AActor* anActor); 84 | TPair GetAvailableTile(); 85 | void FreeTile(ACGTile* aTile, const int32& aWaterMeshIndex); 86 | FCGIntVector2 GetSector(const FVector& aLocation); 87 | TArray GetRelevantSectorsForActor(const AActor* aActor); 88 | 89 | FTerrainCompleteEvent TerrainCompleteEvent; 90 | 91 | // Threads 92 | TArray myWorkerThreads; 93 | 94 | // Geometry data storage 95 | UPROPERTY() 96 | TArray myMeshData; 97 | TArray> myFreeMeshData; 98 | 99 | // Tile/Sector tracking 100 | TArray myFreeTiles; 101 | TArray myFreeWaterMeshIndices; 102 | UPROPERTY() 103 | TMap myTileHandleMap; 104 | TSet myQueuedSectors; 105 | 106 | // Actor tracking 107 | TArray myTrackedActors; 108 | TMap myActorLocationMap; 109 | 110 | // Sweep tracking 111 | float myTimeSinceLastSweep = 0.0f; 112 | const float mySweepTime = 2.0f; 113 | uint8 myActorIndex = 0; 114 | 115 | bool myIsTerrainComplete = false; 116 | }; -------------------------------------------------------------------------------- /Source/CashGen/Public/CGTerrainTrackerComponent.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "CashGen/Public/CGTerrainManager.h" 4 | 5 | #include 6 | 7 | #include "CGTerrainTrackerComponent.generated.h" 8 | 9 | UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) 10 | class CASHGEN_API UCGTerrainTrackerComponent : public UActorComponent 11 | { 12 | GENERATED_BODY() 13 | 14 | bool isSetup = false; 15 | public: 16 | // Sets default values for this component's properties 17 | UCGTerrainTrackerComponent(); 18 | 19 | // Called every frame 20 | virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; 21 | 22 | 23 | 24 | /* Sets actor invisible until inital terrain generation is complete */ 25 | UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Cashgen") 26 | bool HideActorUntilTerrainComplete = false; 27 | 28 | /* Attempts to disable gravity on character until terrain generation is complete */ 29 | UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Cashgen") 30 | bool DisableCharacterGravityUntilComplete = false; 31 | 32 | /* Attempts to teleport character to terrain surface when terrain generation is complete */ 33 | UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Cashgen") 34 | bool TeleportToSurfaceOnTerrainComplete = false; 35 | 36 | void OnTerrainComplete(); 37 | 38 | FVector mySpawnLocation; 39 | ACGTerrainManager* MyTerrainManager; 40 | 41 | /* Attempts to teleport character to terrain surface when terrain generation is complete */ 42 | UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Cashgen") 43 | int32 SpawnRayCastsPerFrame = 10; 44 | 45 | protected: 46 | // Called when the game starts 47 | virtual void BeginPlay() override; 48 | 49 | virtual void OnUnregister() override; 50 | 51 | bool isTerrainComplete = false; 52 | bool isSpawnPointFound = false; 53 | 54 | }; 55 | -------------------------------------------------------------------------------- /Source/CashGen/Public/CGTile.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Cashgen/Public/Struct/CGIntVector2.h" 4 | 5 | #include 6 | #include "ProceduralMeshComponent.h" 7 | 8 | #include "CGTile.generated.h" 9 | 10 | class UStaticMeshComponent; 11 | struct FCGTerrainConfig; 12 | 13 | UENUM(BlueprintType) 14 | enum class ELODStatus : uint8 15 | { 16 | NOT_CREATED, 17 | CREATED, 18 | TRANSITION 19 | }; 20 | 21 | UCLASS() 22 | class CASHGEN_API ACGTile : public AActor 23 | { 24 | GENERATED_BODY() 25 | 26 | TMap MeshComponents; 27 | TMap MaterialInstances; 28 | UStaticMeshComponent* MyWaterMeshComponent; 29 | UMaterialInstance* MaterialInstance; 30 | UMaterialInstanceDynamic* myWaterMaterialInstance; 31 | UMaterial* Material; 32 | TMap LODStatus; 33 | 34 | float LODTransitionOpacity = 0.0f; 35 | 36 | uint8 CurrentLOD; 37 | uint8 PreviousLOD; 38 | 39 | USphereComponent* SphereComponent; 40 | 41 | bool IsInitalized = false; 42 | 43 | FCGIntVector2 mySector; 44 | 45 | FVector WorldOffset; 46 | FCGTerrainConfig* TerrainConfigMaster; 47 | 48 | UPROPERTY() 49 | UTexture2D* myTexture; 50 | 51 | FUpdateTextureRegion2D* myRegion; 52 | 53 | 54 | public: 55 | ACGTile(); 56 | ~ACGTile(); 57 | 58 | public: 59 | 60 | bool TickTransition(float DeltaSeconds); 61 | 62 | virtual void BeginPlay() override; 63 | virtual void Tick(float DeltaSeconds) override; 64 | 65 | void UpdateSettings(FCGIntVector2 aOffset, FCGTerrainConfig* aTerrainConfig, FVector aWorldOffset); 66 | void UpdateMesh(uint8 aLOD, bool aIsInPlaceUpdate, TArray& aPosition, TArray& aNormals, TArray& aTangents, TArray& aUV0s, TArray& aColours, TArray& aTriangles, TArray& aTextureData); 67 | void RepositionAndHide(uint8 aNewLOD); 68 | 69 | bool CreateWaterMesh(); 70 | 71 | 72 | 73 | UMaterialInstanceDynamic* GetMaterialInstanceDynamic(const uint8 aLOD); 74 | 75 | }; 76 | -------------------------------------------------------------------------------- /Source/CashGen/Public/CashGen.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | #pragma once 3 | 4 | #include 5 | 6 | #define Msg(Text) if(GEngine) GEngine->AddOnScreenDebugMessage(-1, 1, FColor::Green, TEXT(Text)); 7 | 8 | 9 | DECLARE_STATS_GROUP(TEXT("CashGen"), STATGROUP_CashGenStat, STATCAT_Advanced); 10 | 11 | class CASHGEN_API FCashGen : public IModuleInterface 12 | { 13 | public: 14 | 15 | /** IModuleInterface implementation */ 16 | virtual void StartupModule() override; 17 | virtual void ShutdownModule() override; 18 | bool HandleSettingsSaved(); 19 | }; 20 | -------------------------------------------------------------------------------- /Source/CashGen/Public/Struct/CGIntVector2.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "CashGen.h" 3 | #include "CGIntVector2.generated.h" 4 | 5 | 6 | 7 | /** Struct defines all applicable attributes for managing generation of a single zone */ 8 | USTRUCT() 9 | struct FCGIntVector2 10 | { 11 | GENERATED_USTRUCT_BODY() 12 | 13 | int32 X, Y; 14 | FCGIntVector2(int32 aX, int32 aY) 15 | { 16 | X = aX; 17 | Y = aY; 18 | } 19 | FCGIntVector2() 20 | { 21 | X = 0; Y = 0; 22 | } 23 | 24 | FORCEINLINE bool operator==(const FCGIntVector2& Src) const 25 | { 26 | return (X == Src.X) && (Y == Src.Y); 27 | } 28 | 29 | FORCEINLINE bool operator!=(const FCGIntVector2& Src) const 30 | { 31 | return X != Src.X || Y != Src.Y; 32 | } 33 | 34 | FCGIntVector2 operator-(const FCGIntVector2& Src) const 35 | { 36 | return FCGIntVector2(X - Src.X, Y - Src.Y); 37 | } 38 | 39 | FString ToString() 40 | { 41 | return "[" + FString::FromInt(X) + ":" + FString::FromInt(Y) + "]"; 42 | } 43 | 44 | friend FORCEINLINE uint32 GetTypeHash(const FCGIntVector2& point) 45 | { 46 | return FCrc::MemCrc32(&point, sizeof(FCGIntVector2)); 47 | } 48 | 49 | }; 50 | -------------------------------------------------------------------------------- /Source/CashGen/Public/Struct/CGJob.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "CashGen/Public/Struct/CGIntVector2.h" 4 | #include "CashGen/Public/Struct/CGTileHandle.h" 5 | #include "CashGen/Public/CGObjectPool.h" 6 | 7 | #include "CGJob.generated.h" 8 | 9 | struct FCGMeshData; 10 | 11 | USTRUCT(BlueprintType) 12 | struct FCGJob 13 | { 14 | GENERATED_BODY() 15 | 16 | FCGJob() 17 | : mySector(0,0) 18 | , HeightmapGenerationDuration(0) 19 | , ErosionGenerationDuration(0) 20 | , LOD(0) 21 | , IsInPlaceUpdate(false) 22 | { 23 | } 24 | 25 | FCGIntVector2 mySector; 26 | FCGTileHandle myTileHandle; 27 | TCGBorrowedObject Data; 28 | int32 HeightmapGenerationDuration; 29 | int32 ErosionGenerationDuration; 30 | 31 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "CashGen") 32 | uint8 LOD; 33 | 34 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "CashGen") 35 | bool IsInPlaceUpdate; 36 | }; -------------------------------------------------------------------------------- /Source/CashGen/Public/Struct/CGLODConfig.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "CGLODConfig.generated.h" 4 | 5 | USTRUCT(BlueprintType) 6 | struct FCGLODConfig 7 | { 8 | GENERATED_BODY() 9 | 10 | FCGLODConfig() 11 | : SectorRadius(0) 12 | , ResolutionDivisor(0) 13 | , isCollisionEnabled(true) 14 | { 15 | } 16 | 17 | /** Radius in sectors to spawn terrain around an actor */ 18 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "CashGen") 19 | int SectorRadius; 20 | /** Factor to reduce sector mesh resolution by from base value */ 21 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "CashGen") 22 | uint8 ResolutionDivisor; 23 | /** Cook collision */ 24 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "CashGen") 25 | bool isCollisionEnabled; 26 | }; -------------------------------------------------------------------------------- /Source/CashGen/Public/Struct/CGLODMeshData.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "CashGen/Public/Struct/CGMeshData.h" 4 | 5 | #include "CGLODMeshData.generated.h" 6 | 7 | USTRUCT(BlueprintType) 8 | struct FCGLODMeshData 9 | { 10 | GENERATED_BODY() 11 | 12 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "CashGen") 13 | TArray Data; 14 | 15 | }; -------------------------------------------------------------------------------- /Source/CashGen/Public/Struct/CGMeshData.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ProceduralMeshComponent.h" 4 | 5 | #include "CGMeshData.generated.h" 6 | 7 | /** Defines the data required for a single procedural mesh section */ 8 | USTRUCT(BlueprintType) 9 | struct FCGMeshData 10 | { 11 | GENERATED_BODY() 12 | 13 | UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Mesh Data Struct") 14 | TArray MyPositions; 15 | UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Mesh Data Struct") 16 | TArray MyNormals; 17 | UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Mesh Data Struct") 18 | TArray MyTangents; 19 | UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Mesh Data Struct") 20 | TArray MyColours; 21 | UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Mesh Data Struct") 22 | TArray MyUV0; 23 | UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Mesh Data Struct") 24 | TArray MyTriangles; 25 | UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Mesh Data Struct") 26 | TArray HeightMap; 27 | UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Mesh Data Struct") 28 | TArray myTextureData; 29 | }; -------------------------------------------------------------------------------- /Source/CashGen/Public/Struct/CGSector.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "CashGen/Public/Struct/CGIntVector2.h" 4 | 5 | #include "CGSector.generated.h" 6 | 7 | USTRUCT(BlueprintType) 8 | struct FCGSector 9 | { 10 | GENERATED_BODY() 11 | 12 | public: 13 | 14 | FCGSector(const int32 aX, const int32 aY, const uint8 aLOD) 15 | : mySector(aX, aY) 16 | , myLOD(aLOD) 17 | { 18 | } 19 | 20 | FCGSector(const FCGIntVector2 aIntVector, const uint8 aLOD = 0) 21 | : mySector(aIntVector) 22 | , myLOD(aLOD) 23 | { 24 | } 25 | 26 | FCGSector() 27 | : mySector(0, 0) 28 | , myLOD(0) 29 | { 30 | } 31 | 32 | FORCEINLINE bool operator==(const FCGSector& Src) const 33 | { 34 | return (mySector.X == Src.mySector.X) && (mySector.Y == Src.mySector.Y); 35 | } 36 | 37 | FORCEINLINE bool operator!=(const FCGSector& Src) const 38 | { 39 | return mySector.X != Src.mySector.X || mySector.Y != Src.mySector.Y; 40 | } 41 | 42 | friend FORCEINLINE uint32 GetTypeHash(const FCGSector& point) 43 | { 44 | return FCrc::MemCrc32(&point.mySector, sizeof(FCGIntVector2)); 45 | } 46 | 47 | FCGIntVector2 mySector; 48 | uint8 myLOD; 49 | }; -------------------------------------------------------------------------------- /Source/CashGen/Public/Struct/CGTerrainConfig.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "CashGen/Public/Struct/CGLODConfig.h" 4 | 5 | #include 6 | #include 7 | 8 | #include "CGTerrainConfig.generated.h" 9 | 10 | /** Struct defines all applicable attributes for managing generation of a single zone */ 11 | USTRUCT(BlueprintType) 12 | struct FCGTerrainConfig 13 | { 14 | GENERATED_BODY() 15 | 16 | FCGTerrainConfig() 17 | : NoiseGenerator(nullptr) 18 | , BiomeBlendGenerator(nullptr) 19 | , WaterMaterialInstance(nullptr) 20 | { 21 | } 22 | 23 | /** Noise Generator configuration struct */ 24 | UPROPERTY() 25 | UUFNNoiseGenerator* NoiseGenerator = nullptr; 26 | UPROPERTY() 27 | UUFNNoiseGenerator* BiomeBlendGenerator = nullptr; 28 | /** Use ASync collision cooking for terrain mesh (Recommended) */ 29 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "CashGen|System") 30 | bool UseAsyncCollision = true; 31 | /** Size of MeshData pool */ 32 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "CashGen|System") 33 | uint8 MeshDataPoolSize = 5; 34 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "CashGen|System") 35 | uint8 NumberOfThreads = 1; 36 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "CashGen|System") 37 | uint8 MeshUpdatesPerFrame = 1; 38 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "CashGen|System") 39 | FTimespan TileReleaseDelay = 5.0f; 40 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "CashGen|System") 41 | float TileSweepTime = 1.0f; 42 | /** Number of blocks along a zone's X axis */ 43 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "CashGen|Scale") 44 | int32 TileXUnits = 32; 45 | /** Number of blocks along a zone's Y axis */ 46 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "CashGen|Scale") 47 | int32 TileYUnits = 32; 48 | /** Size of a single block in world units */ 49 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "CashGen|Scale") 50 | float UnitSize = 300.0f; 51 | /** Multiplier for heightmap*/ 52 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "cashGen|Scale") 53 | float Amplitude = 5000.0f; 54 | /** Droplet erosion droplet amount *EXPERIMENTAL* **/ 55 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "CashGen|Erosion") 56 | int32 DropletAmount = 0; 57 | /** Droplet erosion deposition rate *EXPERIMENTAL* **/ 58 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "CashGen|Erosion") 59 | float DropletErosionMultiplier = 1.0f; 60 | /** Droplet erosion deposition rate *EXPERIMENTAL* **/ 61 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "CashGen|Erosion") 62 | float DropletDespositionMultiplier = 1.0f; 63 | /** Droplet erosion deposition Theta *EXPERIMENTAL* **/ 64 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "CashGen|Erosion") 65 | float DropletSedimentCapacity = 10.0f; 66 | /** Droplet erosion evaporation rate *EXPERIMENTAL* **/ 67 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "CashGen|Erosion") 68 | float DropletEvaporationRate = 0.1f; 69 | /** Erosion floor cutoff **/ 70 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "CashGen|Erosion") 71 | float DropletErosionFloor = 0.0f; 72 | 73 | /** Material for the terrain mesh */ 74 | //UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "CashGen|Rendering") 75 | //UMaterial* TerrainMaterial; 76 | /** Material for the water mesh (will be instanced)*/ 77 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "CashGen|Rendering") 78 | UMaterialInstance* WaterMaterialInstance = nullptr; 79 | /** Cast Shadows */ 80 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "CashGen|Rendering") 81 | bool CastShadows = false; 82 | /* Generate a texture including heightmap and other information */ 83 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "CashGen|Rendering") 84 | bool GenerateSplatMap = false; 85 | /** If checked and numLODs > 1, material will be instanced and TerrainOpacity parameters used to dither LOD transitions */ 86 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "CashGen|Rendering") 87 | bool DitheringLODTransitions = false; 88 | /** If no TerrainMaterial and LOD transitions disabled, just use the same static instance for all LODs **/ 89 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "CashGen|Rendering") 90 | UMaterialInstance* TerrainMaterialInstance = nullptr; 91 | /** Make a dynamic material instance */ 92 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "CashGen|Water") 93 | bool MakeDynamicMaterialInstance = false; 94 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "CashGen|Water") 95 | /** If checked, will use a single instanced mesh for water, otherwise a procmesh section with dynamic texture will be used */ 96 | bool UseInstancedWaterMesh = true; 97 | 98 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "CashGen|Water") 99 | /** If checked, will use a single instanced mesh for water, otherwise a procmesh section with dynamic texture will be used */ 100 | UStaticMesh* WaterMesh = nullptr; 101 | 102 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "CashGen|Water") 103 | TEnumAsByte WaterCollision = ECollisionEnabled::Type::QueryAndPhysics; 104 | 105 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "CashGen|LODs") 106 | TArray LODs; 107 | 108 | FVector TileOffset = FVector::ZeroVector; 109 | }; 110 | -------------------------------------------------------------------------------- /Source/CashGen/Public/Struct/CGTileHandle.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "CashGen.h" 3 | #include "CGTileHandle.generated.h" 4 | 5 | class ACGTile; 6 | 7 | UENUM(BlueprintType) 8 | enum class ETileStatus : uint8 9 | { 10 | NOT_SPAWNED, SPAWNED, REQUESTED, TRANSITION, IDLE 11 | }; 12 | 13 | /** Handle for tracking a single tile */ 14 | USTRUCT() 15 | struct FCGTileHandle 16 | { 17 | GENERATED_USTRUCT_BODY() 18 | // Current rendering status of the sector 19 | ETileStatus myStatus{ETileStatus::NOT_SPAWNED}; 20 | uint8 myLOD{0}; 21 | int32 myWaterISMIndex{-1}; 22 | // Handle to the tile actor 23 | UPROPERTY() 24 | ACGTile* myHandle{nullptr}; 25 | // Bitmask to indicate which players require this sector 26 | FDateTime myLastRequiredTimestamp{}; 27 | }; -------------------------------------------------------------------------------- /Source/CashGen/cashgen.Build.cs: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | using UnrealBuildTool; 4 | 5 | public class CashGen : ModuleRules 6 | { 7 | public CashGen(ReadOnlyTargetRules Target) : base(Target) 8 | { 9 | 10 | PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "RenderCore", "RHI", "UnrealFastNoisePlugin", "ProceduralMeshComponent" }); 11 | 12 | PrivateDependencyModuleNames.AddRange(new string[] { }); 13 | 14 | PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; 15 | } 16 | } 17 | --------------------------------------------------------------------------------