#pragma once #include "WorldMesher.h" #include #include #include struct FStandaloneTileKeyHash { std::size_t operator()(const FVoxelTileKey& Key) const noexcept { return static_cast(GetTypeHash(Key)); } }; struct FStandaloneDesiredTileSet { std::vector Tiles; FIntVector CenterChunk=FIntVector::ZeroValue; int32 MinimumChunkZ=MIN_int32; int32 MaximumChunkZ=MAX_int32; int32 StrateTopChunkZ=0; int32 StrateBottomChunkZ=0; bool bHasStrate=false; }; struct FStandaloneCpuTile { FVoxelTileKey Key; uint64 RequestedCrossing=0; std::vector Vertices; std::vector Indices; uint64 DensitySamples=0; double WorkerSeconds=0.0; std::array ProfileCycles{}; }; struct FStandaloneStreamingUpdate { FIntVector CenterChunk=FIntVector::ZeroValue; int32 DesiredTiles=0; int32 RetainedResidentTiles=0; int32 NewJobs=0; int32 CancelledQueuedJobs=0; int32 FullResTiles=0; std::vector> CancelledByCrossing; }; struct FStandaloneTileJob { FVoxelTileKey Key; int32 BandZMin=MIN_int32; int32 BandZMax=MAX_int32; uint64 RequestedCrossing=0; double PriorityDistanceSquared=0.0; }; struct FStandaloneTileJobHeapOrder { bool operator()(const FStandaloneTileJob& A,const FStandaloneTileJob& B)const { if(A.PriorityDistanceSquared!=B.PriorityDistanceSquared) return A.PriorityDistanceSquared>B.PriorityDistanceSquared; if(A.Key.Level!=B.Key.Level)return A.Key.Level>B.Key.Level; if(A.Key.Coord.Z!=B.Key.Coord.Z)return A.Key.Coord.Z>B.Key.Coord.Z; if(A.Key.Coord.Y!=B.Key.Coord.Y)return A.Key.Coord.Y>B.Key.Coord.Y; return A.Key.Coord.X>B.Key.Coord.X; } }; namespace StandaloneWorldMesher { inline double TileDistanceSquaredChunks(const FVoxelTileKey& Tile,const FIntVector& Center) { const double Scale=static_cast(1<(Tile.Coord.X)+0.5)*Scale-Center.X; const double DY=(static_cast(Tile.Coord.Y)+0.5)*Scale-Center.Y; const double DZ=(static_cast(Tile.Coord.Z)+0.5)*Scale-Center.Z; return DX*DX+DY*DY+DZ*DZ; } inline FStandaloneDesiredTileSet BuildDesiredSet( const FStandaloneOwnerWorld& World,const FVector& CameraPositionVoxels) { FStandaloneDesiredTileSet Desired; Desired.CenterChunk=CameraChunk(CameraPositionVoxels); if(World.Settings->bClampViewToStrate) Desired.bHasStrate=World.Manager->GetStrateChunkZBounds( Desired.CenterChunk.Z,Desired.StrateTopChunkZ,Desired.StrateBottomChunkZ); VoxelClipmapDesiredTiles::ResolveVerticalBounds( Desired.CenterChunk.Z,World.Settings->ViewDistanceUp,World.Settings->ViewDistanceDown, World.Settings->StrateViewMarginChunks,World.Settings->bClampViewToStrate, Desired.bHasStrate,Desired.StrateTopChunkZ,Desired.StrateBottomChunkZ, Desired.MinimumChunkZ,Desired.MaximumChunkZ); VoxelClipmapDesiredTiles::FParameters Parameters; Parameters.ClipRadius=World.ClipRadius; Parameters.MaxClipLevel=FMath::Clamp(World.MaxClipLevel,0,8); Parameters.RenderDistanceChunks=World.RenderDistanceChunks; TArray Tiles; VoxelClipmapDesiredTiles::Build(Desired.CenterChunk,Desired.MinimumChunkZ, Desired.MaximumChunkZ,Parameters,Tiles); Desired.Tiles.reserve(static_cast(Tiles.Num())); for(const FVoxelTileKey& Tile:Tiles)Desired.Tiles.push_back(Tile); std::sort(Desired.Tiles.begin(),Desired.Tiles.end(),[&](const FVoxelTileKey& A,const FVoxelTileKey& B) { const double DA=TileDistanceSquaredChunks(A,Desired.CenterChunk); const double DB=TileDistanceSquaredChunks(B,Desired.CenterChunk); return DA==DB?A.Level& ResidentKeys={}, uint64 CrossingIndex=0) { Stop(); { std::lock_guard Lock(Mutex); bStop=false; } for(int32 I=0;I Lock(Mutex); bStop=true; Queue.clear(); } Wake.notify_all(); for(std::thread& Worker:Workers)if(Worker.joinable())Worker.join(); Workers.clear(); std::lock_guard Lock(Mutex); Ready.clear();Outstanding.clear();Desired.clear(); } FStandaloneStreamingUpdate UpdateDesired( const FVector& CameraPositionVoxels,const std::vector& ResidentKeys, uint64 CrossingIndex=0) { FStandaloneDesiredTileSet NewDesired=StandaloneWorldMesher::BuildDesiredSet(*World,CameraPositionVoxels); FStandaloneStreamingUpdate Stats; Stats.CenterChunk=NewDesired.CenterChunk; Stats.DesiredTiles=static_cast(NewDesired.Tiles.size()); std::unordered_set Resident; Resident.reserve(ResidentKeys.size()*2+1); for(const FVoxelTileKey& Key:ResidentKeys)Resident.insert(Key); for(const FVoxelTileKey& Key:NewDesired.Tiles) { if(Key.LevelFullResClipLevels)++Stats.FullResTiles; if(Resident.contains(Key))++Stats.RetainedResidentTiles; } std::lock_guard Lock(Mutex); Desired.clear();Desired.reserve(NewDesired.Tiles.size()*2+1); for(const FVoxelTileKey& Key:NewDesired.Tiles)Desired.insert(Key); FullResTileCount=Stats.FullResTiles; std::unordered_map CancelledByCrossing; auto QueueEnd=std::remove_if(Queue.begin(),Queue.end(),[&](const FStandaloneTileJob& Job) { if(Desired.contains(Job.Key))return false; Outstanding.erase(Job.Key);++Stats.CancelledQueuedJobs; ++CancelledByCrossing[Job.RequestedCrossing];return true; }); Queue.erase(QueueEnd,Queue.end()); Stats.CancelledByCrossing.reserve(CancelledByCrossing.size()); for(const auto& Pair:CancelledByCrossing)Stats.CancelledByCrossing.push_back(Pair); for(FStandaloneTileJob& Job:Queue) Job.PriorityDistanceSquared=StandaloneWorldMesher::TileDistanceSquaredChunks( Job.Key,NewDesired.CenterChunk); const int32 BandMin=World->StrateContentCutMinLevel<9&&NewDesired.bHasStrate ? NewDesired.StrateBottomChunkZ*CHUNK_SIZE:MIN_int32; const int32 BandMax=World->StrateContentCutMinLevel<9&&NewDesired.bHasStrate ? (NewDesired.StrateTopChunkZ+1)*CHUNK_SIZE:MAX_int32; for(const FVoxelTileKey& Key:NewDesired.Tiles) { if(Resident.contains(Key)||Outstanding.contains(Key))continue; Queue.push_back({Key,BandMin,BandMax,CrossingIndex, StandaloneWorldMesher::TileDistanceSquaredChunks(Key,NewDesired.CenterChunk)}); Outstanding.insert(Key);++Stats.NewJobs; } CurrentDesired=std::move(NewDesired); std::make_heap(Queue.begin(),Queue.end(),FStandaloneTileJobHeapOrder{}); Wake.notify_all(); return Stats; } std::vector TakeReady() { std::vector Out; std::lock_guard Lock(Mutex); Out.swap(Ready); for(const FStandaloneCpuTile& Tile:Out)Outstanding.erase(Tile.Key); return Out; } bool IsDesired(const FVoxelTileKey& Key)const {std::lock_guard Lock(Mutex);return Desired.contains(Key);} int32 DesiredCount()const {std::lock_guard Lock(Mutex);return static_cast(Desired.size());} int32 FullResDesiredCount()const {std::lock_guard Lock(Mutex);return FullResTileCount;} FIntVector CenterChunk()const {std::lock_guard Lock(Mutex);return CurrentDesired.CenterChunk;} int32 GetWorkerCount()const{return WorkerCount;} private: FStandaloneCpuTile Generate(const FStandaloneTileJob& Job) { const auto StartTime=FClock::now(); FStandaloneCpuTile Out;Out.Key=Job.Key;Out.RequestedCrossing=Job.RequestedCrossing; UVoxelMarchingCubesMesher Mesher; Mesher.SetGenerator(World->Generator.get()); Mesher.bGenerateSkirts=World->bGenerateSkirts; Mesher.SkirtCells=static_cast(World->SkirtCells); Mesher.LODOctaveDrop=World->LODOctaveDrop; const int32 Level=Job.Key.Level; const int32 Extent=Job.Key.ExtentVoxels(); const int32 Cells=LevelFullResClipLevels?CHUNK_SIZE:FMath::Max(1,World->CoarseTileCells); const int32 Step=FMath::Max(1,Extent/Cells); int64 SampleCount=0; const bool bProfile=VoxelDensityProfile::GetMode()!=VoxelDensityProfile::EMode::Disabled; const VoxelDensityProfile::FSnapshot Before=bProfile ? VoxelDensityProfile::SnapshotCurrentThread():VoxelDensityProfile::FSnapshot{}; FVoxelMeshData Mesh=Mesher.GenerateMesh(Job.Key.OriginVoxels(),Step,Cells,nullptr, Job.BandZMin,Job.BandZMax,&SampleCount); if(bProfile) { const VoxelDensityProfile::FSnapshot After=VoxelDensityProfile::SnapshotCurrentThread(); for(size_t I=0;I=Before.Cycles[Bucket] ?After.Cycles[Bucket]-Before.Cycles[Bucket]:0; } } Out.DensitySamples=static_cast(FMath::Max(0,SampleCount)); Out.Vertices.reserve(static_cast(Mesh.Vertices.Num())); for(int32 I=0;I(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)}}); } Out.Indices.reserve(static_cast(Mesh.Triangles.Num())); for(const int32 Index:Mesh.Triangles)Out.Indices.push_back(static_cast(Index)); Out.WorkerSeconds=std::chrono::duration(FClock::now()-StartTime).count(); return Out; } void WorkerLoop() { for(;;) { FStandaloneTileJob Job; { std::unique_lock Lock(Mutex); Wake.wait(Lock,[&](){return bStop||!Queue.empty();}); if(bStop)return; std::pop_heap(Queue.begin(),Queue.end(),FStandaloneTileJobHeapOrder{}); Job=Queue.back();Queue.pop_back(); } FStandaloneCpuTile Tile=Generate(Job); { std::lock_guard Lock(Mutex); if(bStop)return; Ready.push_back(std::move(Tile)); } } } using FClock=std::chrono::steady_clock; FStandaloneOwnerWorld* World=nullptr; int32 WorkerCount=6; mutable std::mutex Mutex; std::condition_variable Wake; bool bStop=true; std::vector Workers; std::vector Queue; std::vector Ready; std::unordered_set Desired; std::unordered_set Outstanding; FStandaloneDesiredTileSet CurrentDesired; int32 FullResTileCount=0; };