#pragma once #include "OwnerWorldLoader.h" #include "VoxelClipmapDesiredTiles.h" #include "VoxelMarchingCubesMesher.h" #include #include #include struct FStandaloneCpuVertex { float Position[3]; float Normal[3]; }; struct FStandaloneCpuLevel { std::vector Vertices; std::vector Indices; uint64 VertexCount = 0; uint64 IndexCount = 0; uint64 TileCount = 0; uint64 NonEmptyTiles = 0; uint64 DensitySamples = 0; double Seconds = 0.0; }; struct FStandaloneCpuClipmap { std::array Levels; FIntVector CenterChunk = FIntVector::ZeroValue; int32 MinimumChunkZ = MIN_int32; int32 MaximumChunkZ = MAX_int32; int32 WorkerCount = 6; double TotalSeconds = 0.0; bool bCancelled = false; }; namespace StandaloneWorldMesher { using FClock = std::chrono::steady_clock; inline FIntVector CameraChunk(const FVector& PositionVoxels) { return FIntVector( FMath::FloorToInt(PositionVoxels.X / CHUNK_SIZE), FMath::FloorToInt(PositionVoxels.Y / CHUNK_SIZE), FMath::FloorToInt(PositionVoxels.Z / CHUNK_SIZE)); } inline FStandaloneCpuClipmap Build( FStandaloneOwnerWorld& World, const FVector& CameraPositionVoxels, int32 WorkerCount = 6, const std::function& OnLevelReady = {}, bool bRetainMeshData = true, std::atomic* Cancel = nullptr) { FStandaloneCpuClipmap Output; Output.WorkerCount = FMath::Clamp(WorkerCount,1,32); Output.CenterChunk = CameraChunk(CameraPositionVoxels); bool bHasStrate = false; int32 TopChunkZ = 0, BottomChunkZ = 0; if (World.Settings->bClampViewToStrate) bHasStrate = World.Manager->GetStrateChunkZBounds( Output.CenterChunk.Z,TopChunkZ,BottomChunkZ); VoxelClipmapDesiredTiles::ResolveVerticalBounds( Output.CenterChunk.Z,World.Settings->ViewDistanceUp,World.Settings->ViewDistanceDown, World.Settings->StrateViewMarginChunks,World.Settings->bClampViewToStrate,bHasStrate, TopChunkZ,BottomChunkZ,Output.MinimumChunkZ,Output.MaximumChunkZ); VoxelClipmapDesiredTiles::FParameters Parameters; Parameters.ClipRadius = World.ClipRadius; Parameters.MaxClipLevel = FMath::Clamp(World.MaxClipLevel,0,8); Parameters.RenderDistanceChunks = World.RenderDistanceChunks; TArray AllTiles; VoxelClipmapDesiredTiles::Build( Output.CenterChunk,Output.MinimumChunkZ,Output.MaximumChunkZ,Parameters,AllTiles); std::array,9> TilesByLevel; for (const FVoxelTileKey& Tile : AllTiles) TilesByLevel[static_cast(Tile.Level)].push_back(Tile); UVoxelMarchingCubesMesher::InitializeConsoleSwitches(); const auto TotalStart = FClock::now(); for (int32 Level = 0; Level <= Parameters.MaxClipLevel; ++Level) { if (Cancel && Cancel->load(std::memory_order_relaxed)) { Output.bCancelled = true; break; } const auto LevelStart = FClock::now(); const std::vector& Tiles = TilesByLevel[static_cast(Level)]; FStandaloneCpuLevel& LevelOutput = Output.Levels[static_cast(Level)]; LevelOutput.TileCount = Tiles.size(); struct FTileResult { FVoxelMeshData Mesh; uint64 Samples = 0; }; std::vector Results(Tiles.size()); std::atomic Next{0}; const int32 ThreadCount = FMath::Min(Output.WorkerCount, FMath::Max(1,static_cast(Tiles.size()))); std::vector Threads; Threads.reserve(static_cast(ThreadCount)); for (int32 Worker = 0; Worker < ThreadCount; ++Worker) { Threads.emplace_back([&]() { UVoxelMarchingCubesMesher Mesher; Mesher.SetGenerator(World.Generator.get()); Mesher.bGenerateSkirts = World.bGenerateSkirts; Mesher.SkirtCells = static_cast(World.SkirtCells); Mesher.LODOctaveDrop = World.LODOctaveDrop; for (;;) { if (Cancel && Cancel->load(std::memory_order_relaxed)) break; const size_t Index = Next.fetch_add(1,std::memory_order_relaxed); if (Index >= Tiles.size()) break; const FVoxelTileKey& Tile = Tiles[Index]; const int32 Extent = Tile.ExtentVoxels(); const int32 Cells = Level < World.FullResClipLevels ? CHUNK_SIZE : FMath::Max(1,World.CoarseTileCells); const int32 Step = FMath::Max(1,Extent / Cells); int32 BandMin = MIN_int32, BandMax = MAX_int32; if (World.StrateContentCutMinLevel < 9 && Level >= World.StrateContentCutMinLevel && bHasStrate) { BandMin = BottomChunkZ * CHUNK_SIZE; BandMax = (TopChunkZ + 1) * CHUNK_SIZE; } int64 Samples = 0; Results[Index].Mesh = Mesher.GenerateMesh( Tile.OriginVoxels(),Step,Cells,nullptr,BandMin,BandMax,&Samples); Results[Index].Samples = static_cast(FMath::Max(0,Samples)); } }); } for (std::thread& Thread : Threads) Thread.join(); if (Cancel && Cancel->load(std::memory_order_relaxed)) { Output.bCancelled = true; break; } size_t VertexCount = 0, IndexCount = 0; for (const FTileResult& Result : Results) { if (!Result.Mesh.Triangles.IsEmpty()) ++LevelOutput.NonEmptyTiles; VertexCount += static_cast(Result.Mesh.Vertices.Num()); IndexCount += static_cast(Result.Mesh.Triangles.Num()); LevelOutput.DensitySamples += Result.Samples; } LevelOutput.Vertices.reserve(VertexCount); LevelOutput.Indices.reserve(IndexCount); for (FTileResult& Result : Results) { const uint32 BaseVertex = static_cast(LevelOutput.Vertices.size()); for (int32 I = 0; I < Result.Mesh.Vertices.Num(); ++I) { const FVector& P = Result.Mesh.Vertices[I]; const FVector& N = Result.Mesh.Normals[I]; LevelOutput.Vertices.push_back({ {static_cast(P.X*0.01),static_cast(P.Y*0.01),static_cast(P.Z*0.01)}, {static_cast(N.X),static_cast(N.Y),static_cast(N.Z)}}); } for (int32 Index : Result.Mesh.Triangles) LevelOutput.Indices.push_back(BaseVertex + static_cast(Index)); } LevelOutput.Seconds = std::chrono::duration(FClock::now()-LevelStart).count(); LevelOutput.VertexCount = LevelOutput.Vertices.size(); LevelOutput.IndexCount = LevelOutput.Indices.size(); std::printf("clip level %d: %.3f s, %llu tiles (%llu nonempty), %llu vertices, %.2f M density samples\n", Level,LevelOutput.Seconds, static_cast(LevelOutput.TileCount), static_cast(LevelOutput.NonEmptyTiles), static_cast(LevelOutput.VertexCount), static_cast(LevelOutput.DensitySamples)/1.0e6); std::fflush(stdout); if (OnLevelReady) OnLevelReady(Level,std::move(LevelOutput)); if (!bRetainMeshData) { FStandaloneCpuLevel& Metadata = Output.Levels[static_cast(Level)]; Metadata.Vertices.clear(); Metadata.Indices.clear(); Metadata.Vertices.shrink_to_fit(); Metadata.Indices.shrink_to_fit(); } } Output.TotalSeconds = std::chrono::duration(FClock::now()-TotalStart).count(); std::printf("clipmap total: %.3f s on %d workers, %zu tiles\n",Output.TotalSeconds, Output.WorkerCount,static_cast(AllTiles.Num())); return Output; } }