standalone: 64 m full-detail override, detailed round tunnels, optional mesh cache -- generation itself NOT faster
ClipRadius override 3 (64 m at 25 cm). Uncached full detail 20.3 s on 8 workers (512 tiles at ~51 ms serial each should be ~3-4 s: the in-stream inefficiency is unsolved); full clipmap 170.6 s. The 1 s / 5 s targets were met only from an on-disk mesh cache (.vfmesh, not committed) of already-generated tiles: a revisit speed-up, not generation speed. Round tunnels gained deterministic multi-scale wall relief and lost the larger mouth wedges (1.54% of lattice signs changed, mean 0.20 m). Hashes stable across runs and 1 vs 6 workers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
This commit is contained in:
1 parent
81b7d6d0ff
commit
5441c7da5c
181 files changed
+1674
-51
No files matched your search
@@ -27,6 +27,7 @@
|
||||
#include <iostream>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <numeric>
|
||||
#include <sstream>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
@@ -120,12 +121,49 @@ namespace
|
||||
bool bReported=false;
|
||||
};
|
||||
|
||||
uint64 HashStandaloneFile(const std::string& Path)
|
||||
{
|
||||
std::ifstream File(Path,std::ios::binary);
|
||||
if(!File)return 0;
|
||||
uint64 Hash=14695981039346656037ull;
|
||||
char Buffer[65536];
|
||||
while(File)
|
||||
{
|
||||
File.read(Buffer,sizeof(Buffer));
|
||||
const std::streamsize Count=File.gcount();
|
||||
for(std::streamsize I=0;I<Count;++I)
|
||||
{
|
||||
Hash^=static_cast<uint8>(Buffer[I]);
|
||||
Hash*=1099511628211ull;
|
||||
}
|
||||
}
|
||||
return Hash;
|
||||
}
|
||||
|
||||
FStandaloneBatchKey BatchForTile(const FVoxelTileKey& Tile)
|
||||
{
|
||||
return {Tile.Level,VoxelClipmapDesiredTiles::FloorDiv(Tile.Coord.X,8),
|
||||
VoxelClipmapDesiredTiles::FloorDiv(Tile.Coord.Y,8),Tile.Coord.Z};
|
||||
}
|
||||
|
||||
bool PrepareStandaloneDensityWorld(FStandaloneOwnerWorld& World,int32 Seed,
|
||||
bool bRoundTunnels,bool bEnhancedWalls,std::string& Error)
|
||||
{
|
||||
World.Generator.reset();World.Manager.reset();
|
||||
World.Settings->Seed=Seed;World.Seed=Seed;
|
||||
// Keep the owner's walkable rooms, tunnels, and strate passages intact. The research
|
||||
// round field composes as additional air after the owner field, so it can widen/connect
|
||||
// spaces but cannot remove the authored entrance-to-exit route.
|
||||
FStandaloneResearchTunnels::Set(bRoundTunnels,bEnhancedWalls);
|
||||
World.Manager=std::make_unique<UVoxelStrateManager>();
|
||||
if(!World.Manager->Initialize(World.Settings.get(),Seed))
|
||||
{Error="standalone copied strate manager failed while applying viewer settings";return false;}
|
||||
World.Generator=std::make_unique<UVoxelGenerator>();
|
||||
World.Generator->InitializeSettings(World.Settings.get());
|
||||
World.Generator->SetStrateManager(World.Manager.get());
|
||||
return true;
|
||||
}
|
||||
|
||||
struct FApp
|
||||
{
|
||||
HWND Window=nullptr;
|
||||
@@ -142,6 +180,7 @@ namespace
|
||||
ID3D11RasterizerState* WireRasterizer=nullptr;
|
||||
std::unique_ptr<FStandaloneOwnerWorld> World;
|
||||
std::unique_ptr<FStandaloneStreamingGenerator> Streamer;
|
||||
FStandaloneMeshCache MeshCache;
|
||||
std::string DataPath;
|
||||
bool bHasCameraOverride=false;
|
||||
V3 CameraPositionOverride{};
|
||||
@@ -163,6 +202,7 @@ namespace
|
||||
bool bRebuildPending=false;
|
||||
bool bParityChecked=false;
|
||||
bool bRoundTunnels=false;
|
||||
bool bRoundTunnelEnhanced=true;
|
||||
bool bWireframe=false;
|
||||
bool bCaptured=false;
|
||||
bool bAutoCaptureWritten=false;
|
||||
@@ -179,12 +219,16 @@ namespace
|
||||
std::chrono::steady_clock::time_point CrossingMoveStopTime{};
|
||||
bool bCaptureAfterClipmap=false;
|
||||
bool bQuitAfterClipmap=false;
|
||||
bool bPrintedClipOverride=false;
|
||||
bool bForceGenerate=false;
|
||||
std::string CapturePath;
|
||||
std::string MeshCachePath;
|
||||
std::chrono::steady_clock::time_point GenerationStart{};
|
||||
bool bShowLevel[MaxLevels+1]={true,true,true,true,true,true,true,true,true};
|
||||
bool Keys[256]{};
|
||||
int32 Seed=0;
|
||||
int32 WorkerCount=6;
|
||||
int32 WorkerCount=8;
|
||||
int32 ClipRadius=3;
|
||||
int32 Width=1280,Height=800;
|
||||
float Yaw=0.0f,Pitch=0.0f,MoveSpeed=8.0f;
|
||||
V3 Position{};
|
||||
@@ -423,32 +467,64 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
||||
World=std::make_unique<FStandaloneOwnerWorld>();
|
||||
if(!StandaloneOwnerLoader::Load(DataPath,*World,Error))return false;
|
||||
if(!bParityChecked&&Seed==World->Seed&&!bRoundTunnels)PrintParity();
|
||||
World->Generator.reset();World->Manager.reset();
|
||||
World->Settings->Seed=Seed;World->Seed=Seed;
|
||||
if(bRoundTunnels)
|
||||
const int32 ExportClipRadius=World->ClipRadius;
|
||||
World->ClipRadius=ClipRadius;
|
||||
World->Settings->ClipRadius=ClipRadius;
|
||||
if(!bPrintedClipOverride)
|
||||
{
|
||||
// The research tunnel graph replaces the owner's first-strate corridor graph.
|
||||
for(auto& Pair:World->Settings->FixedStrates)
|
||||
{
|
||||
UVoxelStrateDefinition* Definition=Pair.Value.Get();
|
||||
if(Pair.Key==0&&Definition&&Definition->GeneratorType==ECaveGeneratorType::TunnelNetwork)
|
||||
{
|
||||
Definition->GenerationParams.TunnelDensity=0.0f;
|
||||
Definition->GenerationParams.TunnelWarpStrength=0.0f;
|
||||
}
|
||||
}
|
||||
FStandaloneResearchTunnels::Set(true,Position.Z*4.0f);
|
||||
bPrintedClipOverride=true;
|
||||
const double FullDetailWidth=static_cast<double>(2*ClipRadius+2)
|
||||
*CHUNK_SIZE*VOXEL_SIZE/100.0;
|
||||
std::printf("Standalone clip override: exported radius=%d (112 m cube) -> radius=%d (%.0f m LOD0 cube), voxel size=25 cm.\n",
|
||||
ExportClipRadius,ClipRadius,FullDetailWidth);
|
||||
}
|
||||
else FStandaloneResearchTunnels::Set(false,0.0f);
|
||||
World->Manager=std::make_unique<UVoxelStrateManager>();
|
||||
if(!World->Manager->Initialize(World->Settings.get(),Seed))
|
||||
{Error="standalone copied strate manager failed while applying viewer seed";return false;}
|
||||
World->Generator=std::make_unique<UVoxelGenerator>();
|
||||
World->Generator->InitializeSettings(World->Settings.get());
|
||||
World->Generator->SetStrateManager(World->Manager.get());
|
||||
if(!PrepareStandaloneDensityWorld(*World,Seed,bRoundTunnels,
|
||||
bRoundTunnelEnhanced,Error))return false;
|
||||
LoadMeshCache();
|
||||
return true;
|
||||
}
|
||||
|
||||
FStandaloneMeshCacheMetadata MakeMeshCacheMetadata()const
|
||||
{
|
||||
FStandaloneMeshCacheMetadata Metadata;
|
||||
Metadata.SourceDataHash=HashStandaloneFile(DataPath);
|
||||
Metadata.Seed=Seed;Metadata.ClipRadius=ClipRadius;
|
||||
Metadata.FullResClipLevels=World->FullResClipLevels;
|
||||
Metadata.CoarseTileCells=World->CoarseTileCells;
|
||||
Metadata.StrateContentCutMinLevel=World->StrateContentCutMinLevel;
|
||||
Metadata.SkirtCells=World->SkirtCells;
|
||||
Metadata.LODOctaveDrop=World->LODOctaveDrop;
|
||||
Metadata.bGenerateSkirts=World->bGenerateSkirts?1:0;
|
||||
Metadata.AlgorithmVersion=FStandaloneMeshCache::CurrentVersion;
|
||||
Metadata.TunnelMode=bRoundTunnels?(bRoundTunnelEnhanced?2u:1u):0u;
|
||||
return Metadata;
|
||||
}
|
||||
|
||||
void LoadMeshCache()
|
||||
{
|
||||
MeshCache.Clear();
|
||||
if(bForceGenerate||!World)return;
|
||||
const char* ModeSuffix=!bRoundTunnels?"_base":
|
||||
(bRoundTunnelEnhanced?"_round_detailed":"_round_legacy");
|
||||
const std::filesystem::path CachePath=std::filesystem::path(DataPath).parent_path()
|
||||
/(std::string("VoxelForge_seed")+std::to_string(Seed)+"_r"
|
||||
+std::to_string(ClipRadius)+ModeSuffix+".vfmesh");
|
||||
MeshCachePath=CachePath.string();
|
||||
const auto Begin=std::chrono::steady_clock::now();
|
||||
std::string CacheError;
|
||||
if(MeshCache.Load(MeshCachePath,MakeMeshCacheMetadata(),CacheError))
|
||||
{
|
||||
const double Seconds=std::chrono::duration<double>(
|
||||
std::chrono::steady_clock::now()-Begin).count();
|
||||
std::printf("[MeshCache] loaded %zu tiles, %.2f MiB in %.3f s from %s\n",
|
||||
MeshCache.Num(),static_cast<double>(MeshCache.GetBytesLoaded())/1048576.0,
|
||||
Seconds,MeshCachePath.c_str());
|
||||
std::fflush(stdout);
|
||||
}
|
||||
else if(CacheError!="cache file is missing or unreadable")
|
||||
std::printf("[MeshCache] ignored %s: %s\n",MeshCachePath.c_str(),CacheError.c_str());
|
||||
}
|
||||
|
||||
void StartGeneration()
|
||||
{
|
||||
StopGeneration();
|
||||
@@ -471,14 +547,17 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
||||
DesiredTiles=Desired.Tiles;
|
||||
for(int32 I=0;I<=MaxLevels;++I)DesiredByLevel[I]=0;
|
||||
for(const FVoxelTileKey& Tile:DesiredTiles)++DesiredByLevel[Tile.Level];
|
||||
Streamer=std::make_unique<FStandaloneStreamingGenerator>(*World,WorkerCount);
|
||||
Streamer=std::make_unique<FStandaloneStreamingGenerator>(*World,WorkerCount,false,
|
||||
MeshCache.IsLoaded()?&MeshCache:nullptr);
|
||||
const FStandaloneStreamingUpdate Update=Streamer->Start(CameraVoxels,{},CrossingIndex);
|
||||
FCrossingWork& InitialWork=CrossingWork[CrossingIndex];
|
||||
InitialWork.SubmittedJobs=Update.NewJobs;InitialWork.PendingJobs=Update.NewJobs;
|
||||
InitialWork.StartTime=GenerationStart;
|
||||
bGenerationRunning=true;bRebuildPending=false;
|
||||
const char* TunnelMode=!bRoundTunnels?"":(bRoundTunnelEnhanced
|
||||
?" with detailed round/winding tunnels":" with legacy round/winding tunnels");
|
||||
std::printf("Streaming seed %d%s: %d desired tiles, %d full-detail tiles, %d workers; upload budget %d batches / %.1f ms per frame.\n",
|
||||
Seed,bRoundTunnels?" with research round/winding tunnels":"",Update.DesiredTiles,
|
||||
Seed,TunnelMode,Update.DesiredTiles,
|
||||
Update.FullResTiles,Streamer->GetWorkerCount(),MaxBatchUploadsPerFrame,BatchUploadBudgetMilliseconds);
|
||||
std::fflush(stdout);
|
||||
}
|
||||
@@ -795,6 +874,7 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
||||
{
|
||||
uint64 VisibleIndices=0;for(const auto& Pair:StreamingBatches)if(bShowLevel[Pair.first.Level])VisibleIndices+=Pair.second.IndexCount;
|
||||
std::ostringstream S;S<<"VoxelForge standalone | seed "<<Seed<<" | "<<(bRoundTunnels?"round/winding research tunnels":"base world")
|
||||
<<(bRoundTunnels?(bRoundTunnelEnhanced?" (detailed walls)":" (legacy walls)"):"")
|
||||
<<" | "<<VisibleIndices/3<<" tris | "<<FrameMs<<" ms | "<<(bGenerationRunning?"generating":"ready")
|
||||
<<" | pos "<<std::fixed<<std::setprecision(1)<<Position.X<<", "<<Position.Y<<", "<<Position.Z<<" m";
|
||||
SetWindowTextA(Window,S.str().c_str());
|
||||
@@ -865,6 +945,13 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
||||
if(W==VK_ESCAPE){if(GApp->bCaptured)GApp->Capture(false);else DestroyWindow(H);}
|
||||
if(!bWasDown&&W=='F')GApp->bWireframe=!GApp->bWireframe;
|
||||
if(!bWasDown&&W=='T'){GApp->bRoundTunnels=!GApp->bRoundTunnels;GApp->bRebuildPending=true;}
|
||||
if(!bWasDown&&W=='G'&&GApp->bRoundTunnels)
|
||||
{
|
||||
GApp->bRoundTunnelEnhanced=!GApp->bRoundTunnelEnhanced;
|
||||
GApp->bRebuildPending=true;
|
||||
std::printf("Round-tunnel comparison: %s wall field.\n",
|
||||
GApp->bRoundTunnelEnhanced?"detailed/smoothed":"legacy smooth tube");
|
||||
}
|
||||
if(!bWasDown&&(W=='N'||W=='P'))GApp->RequestSeed(W=='N'?1:-1);
|
||||
if(!bWasDown&&W>='1'&&W<='8'){const int32 I=static_cast<int32>(W-'1');GApp->bShowLevel[I]=!GApp->bShowLevel[I];}
|
||||
if(!bWasDown&&W=='9')GApp->bShowLevel[8]=!GApp->bShowLevel[8];
|
||||
@@ -1063,6 +1150,293 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
||||
std::fflush(stdout);
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct FComparePoint
|
||||
{
|
||||
double X=0.0,Y=0.0,Z=0.0;
|
||||
};
|
||||
|
||||
struct FCompareBucketHash
|
||||
{
|
||||
size_t operator()(const FIntVector& K)const noexcept
|
||||
{
|
||||
uint64 H=static_cast<uint32>(K.X)*0x9E3779B185EBCA87ull;
|
||||
H^=static_cast<uint64>(static_cast<uint32>(K.Y))*0xC2B2AE3D27D4EB4Full;
|
||||
H^=static_cast<uint64>(static_cast<uint32>(K.Z))*0x165667B19E3779F9ull;
|
||||
return static_cast<size_t>(H^(H>>32));
|
||||
}
|
||||
};
|
||||
|
||||
struct FDirectedSurfaceDistance
|
||||
{
|
||||
double Sum=0.0,Maximum=0.0;
|
||||
uint64 Count=0,Unmatched=0;
|
||||
};
|
||||
|
||||
FDirectedSurfaceDistance DirectedSurfaceDistance(
|
||||
const std::vector<FComparePoint>& From,const std::vector<FComparePoint>& To)
|
||||
{
|
||||
FDirectedSurfaceDistance Result;
|
||||
if(From.empty()||To.empty()){Result.Unmatched=From.size();return Result;}
|
||||
constexpr double BucketWidth=1.0;
|
||||
std::unordered_map<FIntVector,std::vector<const FComparePoint*>,FCompareBucketHash> Buckets;
|
||||
Buckets.reserve(To.size());
|
||||
const auto BucketOf=[=](const FComparePoint& P)
|
||||
{
|
||||
return FIntVector(static_cast<int32>(std::floor(P.X/BucketWidth)),
|
||||
static_cast<int32>(std::floor(P.Y/BucketWidth)),
|
||||
static_cast<int32>(std::floor(P.Z/BucketWidth)));
|
||||
};
|
||||
for(const FComparePoint& P:To)Buckets[BucketOf(P)].push_back(&P);
|
||||
for(const FComparePoint& P:From)
|
||||
{
|
||||
const FIntVector Center=BucketOf(P);
|
||||
double BestSquared=std::numeric_limits<double>::infinity();
|
||||
for(int32 Z=-3;Z<=3;++Z)for(int32 Y=-3;Y<=3;++Y)for(int32 X=-3;X<=3;++X)
|
||||
{
|
||||
const auto It=Buckets.find(Center+FIntVector(X,Y,Z));
|
||||
if(It==Buckets.end())continue;
|
||||
for(const FComparePoint* Candidate:It->second)
|
||||
{
|
||||
const double DX=P.X-Candidate->X,DY=P.Y-Candidate->Y,DZ=P.Z-Candidate->Z;
|
||||
BestSquared=std::min(BestSquared,DX*DX+DY*DY+DZ*DZ);
|
||||
}
|
||||
}
|
||||
if(std::isfinite(BestSquared))
|
||||
{
|
||||
const double Distance=std::sqrt(BestSquared);
|
||||
Result.Sum+=Distance;Result.Maximum=std::max(Result.Maximum,Distance);++Result.Count;
|
||||
}
|
||||
else ++Result.Unmatched;
|
||||
}
|
||||
return Result;
|
||||
}
|
||||
|
||||
int RunRoundWallComparison(FStandaloneOwnerWorld& World,V3 CameraPosition,int32 TileCount)
|
||||
{
|
||||
const FVector CameraVoxels(CameraPosition.X*4.0,CameraPosition.Y*4.0,CameraPosition.Z*4.0);
|
||||
const FStandaloneDesiredTileSet Desired=StandaloneWorldMesher::BuildDesiredSet(World,CameraVoxels);
|
||||
std::vector<FVoxelTileKey> Tiles;Tiles.reserve(static_cast<size_t>(TileCount));
|
||||
for(const FVoxelTileKey& Tile:Desired.Tiles)
|
||||
if(Tile.Level==0&&static_cast<int32>(Tiles.size())<TileCount)Tiles.push_back(Tile);
|
||||
if(Tiles.empty()){std::fprintf(stderr,"No LOD0 tiles are available for the round-tunnel comparison.\n");return 9;}
|
||||
struct FComparisonTile
|
||||
{
|
||||
std::vector<float> Densities;
|
||||
std::vector<FComparePoint> Surface;
|
||||
};
|
||||
std::vector<FComparisonTile> Legacy(Tiles.size());
|
||||
std::vector<FComparisonTile> Detailed(Tiles.size());
|
||||
UVoxelMarchingCubesMesher Mesher;
|
||||
Mesher.SetGenerator(World.Generator.get());Mesher.bGenerateSkirts=World.bGenerateSkirts;
|
||||
Mesher.SkirtCells=static_cast<float>(World.SkirtCells);Mesher.LODOctaveDrop=World.LODOctaveDrop;
|
||||
int32 MinChunkZ=MIN_int32,MaxChunkZ=MAX_int32,TopChunkZ=0,BottomChunkZ=0;
|
||||
if(World.Settings->bClampViewToStrate&&World.Manager->GetStrateChunkZBounds(
|
||||
StandaloneWorldMesher::CameraChunk(CameraVoxels).Z,TopChunkZ,BottomChunkZ))
|
||||
{
|
||||
MinChunkZ=BottomChunkZ*CHUNK_SIZE;
|
||||
MaxChunkZ=(TopChunkZ+1)*CHUNK_SIZE;
|
||||
}
|
||||
uint64 TotalSamples=0,ChangedSigns=0;
|
||||
uint64 LegacyHash=14695981039346656037ull,DetailedHash=14695981039346656037ull;
|
||||
const auto Evaluate=[&](bool bEnhanced,std::vector<FComparisonTile>& Out)
|
||||
{
|
||||
FStandaloneResearchTunnels::Set(true,bEnhanced);
|
||||
for(size_t I=0;I<Tiles.size();++I)
|
||||
{
|
||||
const FVoxelTileKey& Tile=Tiles[I];
|
||||
const int32 Step=1,Cells=CHUNK_SIZE;
|
||||
FVoxelMeshData Mesh=Mesher.GenerateMesh(Tile.OriginVoxels(),Step,Cells,nullptr,
|
||||
MinChunkZ,MaxChunkZ,nullptr,nullptr,nullptr,&Out[I].Densities);
|
||||
Out[I].Surface.reserve(static_cast<size_t>(Mesh.Vertices.Num()));
|
||||
for(const FVector& P:Mesh.Vertices)
|
||||
Out[I].Surface.push_back({P.X*0.01,P.Y*0.01,P.Z*0.01});
|
||||
}
|
||||
};
|
||||
Evaluate(false,Legacy);
|
||||
Evaluate(true,Detailed);
|
||||
FDirectedSurfaceDistance LegacyToDetailed,DetailedToLegacy;
|
||||
std::vector<FComparePoint> AllLegacySurface,AllDetailedSurface;
|
||||
for(size_t TileIndex=0;TileIndex<Tiles.size();++TileIndex)
|
||||
{
|
||||
const auto& A=Legacy[TileIndex];const auto& B=Detailed[TileIndex];
|
||||
const size_t Shared=std::min(A.Densities.size(),B.Densities.size());
|
||||
TotalSamples+=Shared;
|
||||
for(size_t I=0;I<Shared;++I)
|
||||
{
|
||||
if((A.Densities[I]<0.0f)!=(B.Densities[I]<0.0f))++ChangedSigns;
|
||||
LegacyHash^=std::bit_cast<uint32>(A.Densities[I]);LegacyHash*=1099511628211ull;
|
||||
DetailedHash^=std::bit_cast<uint32>(B.Densities[I]);DetailedHash*=1099511628211ull;
|
||||
}
|
||||
AllLegacySurface.insert(AllLegacySurface.end(),A.Surface.begin(),A.Surface.end());
|
||||
AllDetailedSurface.insert(AllDetailedSurface.end(),B.Surface.begin(),B.Surface.end());
|
||||
}
|
||||
LegacyToDetailed=DirectedSurfaceDistance(AllLegacySurface,AllDetailedSurface);
|
||||
DetailedToLegacy=DirectedSurfaceDistance(AllDetailedSurface,AllLegacySurface);
|
||||
const uint64 DistanceSamples=LegacyToDetailed.Count+DetailedToLegacy.Count;
|
||||
const double MeanDistance=DistanceSamples>0
|
||||
?(LegacyToDetailed.Sum+DetailedToLegacy.Sum)/static_cast<double>(DistanceSamples):0.0;
|
||||
const double Hausdorff=std::max(LegacyToDetailed.Maximum,DetailedToLegacy.Maximum);
|
||||
std::printf("[RoundTunnelCompare] tiles=%zu lattice_samples=%llu changed_signs=%llu changed_share=%.6f%% legacy_density_hash=0x%016llx detailed_density_hash=0x%016llx old_vertices=%zu new_vertices=%zu symmetric_vertex_mean_m=%.4f symmetric_vertex_max_m=%.4f unmatched_vertices=%llu\n",
|
||||
Tiles.size(),static_cast<unsigned long long>(TotalSamples),
|
||||
static_cast<unsigned long long>(ChangedSigns),TotalSamples?100.0*ChangedSigns/TotalSamples:0.0,
|
||||
static_cast<unsigned long long>(LegacyHash),static_cast<unsigned long long>(DetailedHash),
|
||||
AllLegacySurface.size(),AllDetailedSurface.size(),MeanDistance,Hausdorff,
|
||||
static_cast<unsigned long long>(LegacyToDetailed.Unmatched+DetailedToLegacy.Unmatched));
|
||||
FStandaloneResearchTunnels::Set(true,true);
|
||||
return TotalSamples>0&&LegacyToDetailed.Unmatched+DetailedToLegacy.Unmatched==0?0:10;
|
||||
}
|
||||
|
||||
void HashWord(uint64& Hash,uint64 Word,int32 Bytes=8)
|
||||
{
|
||||
for(int32 I=0;I<Bytes;++I)
|
||||
{
|
||||
Hash^=static_cast<uint8>(Word>>(I*8));
|
||||
Hash*=1099511628211ull;
|
||||
}
|
||||
}
|
||||
|
||||
struct FHashedTile
|
||||
{
|
||||
FVoxelTileKey Key;
|
||||
uint64 Hash=14695981039346656037ull;
|
||||
};
|
||||
|
||||
bool TileKeyLessForHash(const FVoxelTileKey& A,const FVoxelTileKey& B)
|
||||
{
|
||||
if(A.Level!=B.Level)return A.Level<B.Level;
|
||||
if(A.Coord.Z!=B.Coord.Z)return A.Coord.Z<B.Coord.Z;
|
||||
if(A.Coord.Y!=B.Coord.Y)return A.Coord.Y<B.Coord.Y;
|
||||
return A.Coord.X<B.Coord.X;
|
||||
}
|
||||
|
||||
uint64 HashTileRows(std::vector<FHashedTile>& Rows)
|
||||
{
|
||||
std::sort(Rows.begin(),Rows.end(),[](const FHashedTile& A,const FHashedTile& B)
|
||||
{return TileKeyLessForHash(A.Key,B.Key);});
|
||||
uint64 Hash=14695981039346656037ull;
|
||||
for(const FHashedTile& Row:Rows)
|
||||
{
|
||||
HashWord(Hash,static_cast<uint32>(Row.Key.Level),4);
|
||||
HashWord(Hash,static_cast<uint32>(Row.Key.Coord.X),4);
|
||||
HashWord(Hash,static_cast<uint32>(Row.Key.Coord.Y),4);
|
||||
HashWord(Hash,static_cast<uint32>(Row.Key.Coord.Z),4);
|
||||
HashWord(Hash,Row.Hash);
|
||||
}
|
||||
return Hash;
|
||||
}
|
||||
|
||||
int RunStreamingBenchmark(FStandaloneOwnerWorld& World,const FVector& CameraPositionVoxels,
|
||||
int32 WorkerCount,int32 MaxLevel,bool bRoundTunnels,
|
||||
bool bEnhancedWalls,const FStandaloneMeshCache* Cache,
|
||||
const std::string& BakeCachePath,
|
||||
const FStandaloneMeshCacheMetadata& CacheMetadata)
|
||||
{
|
||||
FStandaloneDesiredTileSet Desired=StandaloneWorldMesher::BuildDesiredSet(World,CameraPositionVoxels);
|
||||
if(MaxLevel>=0)
|
||||
Desired.Tiles.erase(std::remove_if(Desired.Tiles.begin(),Desired.Tiles.end(),
|
||||
[&](const FVoxelTileKey& Tile){return Tile.Level>MaxLevel;}),Desired.Tiles.end());
|
||||
if(Desired.Tiles.size()>1000000)
|
||||
{
|
||||
std::fprintf(stderr,"Stream benchmark rejected an unsafe desired set of %zu tiles.\n",
|
||||
Desired.Tiles.size());
|
||||
return 7;
|
||||
}
|
||||
const int32 FullDetailExpected=static_cast<int32>(std::count_if(
|
||||
Desired.Tiles.begin(),Desired.Tiles.end(),[&](const FVoxelTileKey& Tile)
|
||||
{return Tile.Level<World.FullResClipLevels;}));
|
||||
std::array<std::vector<FHashedTile>,MaxLevels+1> HashRows;
|
||||
std::array<uint64,MaxLevels+1> SamplesByLevel{};
|
||||
std::array<uint64,MaxLevels+1> VerticesByLevel{};
|
||||
std::array<uint64,MaxLevels+1> IndicesByLevel{};
|
||||
std::array<double,MaxLevels+1> WorkerSecondsByLevel{};
|
||||
std::vector<FStandaloneCpuTile> CacheTiles;
|
||||
if(!BakeCachePath.empty())CacheTiles.reserve(Desired.Tiles.size());
|
||||
const auto Start=std::chrono::steady_clock::now();
|
||||
FStandaloneStreamingGenerator Streamer(World,WorkerCount,true,Cache,MaxLevel);
|
||||
const FStandaloneStreamingUpdate Update=Streamer.Start(CameraPositionVoxels);
|
||||
const double LOD0Width=static_cast<double>(2*World.ClipRadius+2)
|
||||
*CHUNK_SIZE*VOXEL_SIZE/100.0;
|
||||
std::printf("[StreamBenchmarkConfig] clip_radius=%d lod0_cube=%.0fm voxel=25cm fullres_levels=%d max_level_filter=%d workers=%d full_detail_tiles=%d desired_tiles=%d variant=%s\n",
|
||||
World.ClipRadius,LOD0Width,World.FullResClipLevels,MaxLevel<0?World.MaxClipLevel:MaxLevel,
|
||||
Streamer.GetWorkerCount(),FullDetailExpected,Update.DesiredTiles,
|
||||
bRoundTunnels?(bEnhancedWalls?"round-detailed":"round-legacy"):"base");
|
||||
std::fflush(stdout);
|
||||
int32 Finished=0,FullDetailFinished=0;
|
||||
bool bFullDetailTimeReported=false;
|
||||
double FullDetailSeconds=0.0;
|
||||
const int32 DesiredTotal=Update.DesiredTiles;
|
||||
while(Finished<DesiredTotal)
|
||||
{
|
||||
std::vector<FStandaloneCpuTile> Ready=Streamer.TakeReady();
|
||||
if(Ready.empty())
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
continue;
|
||||
}
|
||||
for(FStandaloneCpuTile& Tile:Ready)
|
||||
{
|
||||
++Finished;
|
||||
const int32 Level=Tile.Key.Level;
|
||||
if(Level>=0&&Level<=MaxLevels)
|
||||
{
|
||||
SamplesByLevel[Level]+=Tile.DensitySamples;
|
||||
VerticesByLevel[Level]+=Tile.Vertices.size();
|
||||
IndicesByLevel[Level]+=Tile.Indices.size();
|
||||
WorkerSecondsByLevel[Level]+=Tile.WorkerSeconds;
|
||||
if(Tile.bHasDensityHash)HashRows[Level].push_back({Tile.Key,Tile.DensityHash});
|
||||
}
|
||||
if(Level<World.FullResClipLevels)++FullDetailFinished;
|
||||
if(!BakeCachePath.empty())CacheTiles.push_back(std::move(Tile));
|
||||
}
|
||||
if(!bFullDetailTimeReported&&FullDetailFinished>=FullDetailExpected)
|
||||
{
|
||||
bFullDetailTimeReported=true;
|
||||
FullDetailSeconds=std::chrono::duration<double>(
|
||||
std::chrono::steady_clock::now()-Start).count();
|
||||
std::vector<FHashedTile> FullRows;
|
||||
for(int32 Level=0;Level<World.FullResClipLevels&&Level<=MaxLevels;++Level)
|
||||
FullRows.insert(FullRows.end(),HashRows[Level].begin(),HashRows[Level].end());
|
||||
std::printf("[StreamFullDetail] wall=%.3fs generated=%d/%d resident_tiles_cpu_only=1 density_hash=0x%016llx\n",
|
||||
FullDetailSeconds,FullDetailFinished,FullDetailExpected,
|
||||
static_cast<unsigned long long>(HashTileRows(FullRows)));
|
||||
std::fflush(stdout);
|
||||
}
|
||||
}
|
||||
const double FullClipSeconds=std::chrono::duration<double>(
|
||||
std::chrono::steady_clock::now()-Start).count();
|
||||
std::vector<FHashedTile> AllRows;
|
||||
for(auto& LevelRows:HashRows)AllRows.insert(AllRows.end(),LevelRows.begin(),LevelRows.end());
|
||||
std::printf("[StreamFullClipmap] wall=%.3fs generated=%d/%d worker_sum=%.3fs tiles=%d density_hash=0x%016llx\n",
|
||||
FullClipSeconds,Finished,DesiredTotal,
|
||||
std::accumulate(WorkerSecondsByLevel.begin(),WorkerSecondsByLevel.end(),0.0),
|
||||
DesiredTotal,static_cast<unsigned long long>(HashTileRows(AllRows)));
|
||||
std::printf("[StreamCacheUse] hits=%llu requested=%d\n",
|
||||
static_cast<unsigned long long>(Streamer.GetCacheHits()),DesiredTotal);
|
||||
for(int32 Level=0;Level<=MaxLevels;++Level)
|
||||
{
|
||||
if(HashRows[Level].empty())continue;
|
||||
const uint64 LevelHash=HashTileRows(HashRows[Level]);
|
||||
std::printf("[StreamLevel] lod=%d tiles=%zu samples=%.3fM worker_sum=%.3fs vertices=%llu indices=%llu density_hash=0x%016llx\n",
|
||||
Level,HashRows[Level].size(),static_cast<double>(SamplesByLevel[Level])/1.0e6,
|
||||
WorkerSecondsByLevel[Level],static_cast<unsigned long long>(VerticesByLevel[Level]),
|
||||
static_cast<unsigned long long>(IndicesByLevel[Level]),
|
||||
static_cast<unsigned long long>(LevelHash));
|
||||
}
|
||||
if(!BakeCachePath.empty())
|
||||
{
|
||||
std::string CacheError;
|
||||
if(!FStandaloneMeshCache::Save(BakeCachePath,CacheMetadata,CacheTiles,CacheError))
|
||||
{
|
||||
std::fprintf(stderr,"[MeshCacheBake] failed: %s\n",CacheError.c_str());
|
||||
return 8;
|
||||
}
|
||||
const uint64 Bytes=std::filesystem::file_size(BakeCachePath);
|
||||
std::printf("[MeshCacheBake] saved %zu tiles, %.2f MiB to %s\n",CacheTiles.size(),
|
||||
static_cast<double>(Bytes)/1048576.0,BakeCachePath.c_str());
|
||||
}
|
||||
return (Finished==DesiredTotal&&FullDetailFinished==FullDetailExpected)?0:6;
|
||||
}
|
||||
}
|
||||
|
||||
int main(int Argc,char** Argv)
|
||||
@@ -1072,7 +1446,11 @@ int main(int Argc,char** Argv)
|
||||
for(int32 I=1;I<Argc;++I)
|
||||
{if(!JoinedCommandLine.empty())JoinedCommandLine.push_back(' ');JoinedCommandLine+=Argv[I];}
|
||||
FCommandLine::Set(JoinedCommandLine);
|
||||
int32 BenchmarkLevel=-1,BenchmarkCount=64;
|
||||
int32 BenchmarkLevel=-1,BenchmarkCount=64,StreamBenchmarkMaxLevel=-1;
|
||||
bool bStreamBenchmark=false;
|
||||
bool bRoundWallCompare=false;
|
||||
int32 RoundCompareTileCount=16;
|
||||
std::string BakeMeshCachePath;
|
||||
for(int32 I=1;I<Argc;++I)
|
||||
{
|
||||
const std::string_view Arg=Argv[I];
|
||||
@@ -1082,14 +1460,28 @@ int main(int Argc,char** Argv)
|
||||
else if(Arg=="--parity-only")App.bParityOnly=true;
|
||||
else if(Arg=="--density-hash")App.bDensityHash=true;
|
||||
else if(Arg=="--round-tunnels")App.bRoundTunnels=true;
|
||||
else if(Arg=="--round-tunnels-legacy")
|
||||
{App.bRoundTunnels=true;App.bRoundTunnelEnhanced=false;}
|
||||
else if(Arg=="--stream-benchmark")bStreamBenchmark=true;
|
||||
else if(Arg=="--compare-round-walls")
|
||||
{bRoundWallCompare=true;App.bRoundTunnels=true;}
|
||||
else if(Arg.starts_with("--round-compare-tiles="))
|
||||
RoundCompareTileCount=std::max(1,std::atoi(std::string(Arg.substr(22)).c_str()));
|
||||
else if(Arg=="--force-generate")App.bForceGenerate=true;
|
||||
else if(Arg.starts_with("--bake-mesh-cache="))
|
||||
{BakeMeshCachePath=std::string(Arg.substr(18));App.bForceGenerate=true;}
|
||||
else if(Arg.starts_with("--stream-max-level="))
|
||||
StreamBenchmarkMaxLevel=std::atoi(std::string(Arg.substr(19)).c_str());
|
||||
else if(Arg=="--test-one-chunk-crossing")App.bTestOneChunkCrossing=true;
|
||||
else if(Arg=="--capture-after-clipmap")App.bCaptureAfterClipmap=true;
|
||||
else if(Arg=="--quit-after-clipmap")App.bQuitAfterClipmap=true;
|
||||
else if(Arg.starts_with("--workers="))
|
||||
{
|
||||
const int32 Parsed=std::atoi(std::string(Arg.substr(10)).c_str());
|
||||
App.WorkerCount=std::clamp(Parsed,1,32);
|
||||
App.WorkerCount=std::clamp(Parsed,1,8);
|
||||
}
|
||||
else if(Arg.starts_with("--clip-radius="))
|
||||
App.ClipRadius=std::clamp(std::atoi(std::string(Arg.substr(14)).c_str()),1,64);
|
||||
else if(Arg.starts_with("--benchmark-level="))
|
||||
BenchmarkLevel=std::atoi(std::string(Arg.substr(18)).c_str());
|
||||
else if(Arg.starts_with("--benchmark-count="))
|
||||
@@ -1120,6 +1512,27 @@ int main(int Argc,char** Argv)
|
||||
App.Position={static_cast<float>(App.World->ViewerStartVoxels.X*0.25),
|
||||
static_cast<float>(App.World->ViewerStartVoxels.Y*0.25),static_cast<float>(App.World->ViewerStartVoxels.Z*0.25)};
|
||||
if(App.bHasCameraOverride){App.Position=App.CameraPositionOverride;App.Yaw=App.CameraYawOverride;App.Pitch=App.CameraPitchOverride;}
|
||||
const int32 ExportClipRadius=App.World->ClipRadius;
|
||||
App.World->ClipRadius=App.ClipRadius;
|
||||
App.World->Settings->ClipRadius=App.ClipRadius;
|
||||
if(App.bRoundTunnels&&!PrepareStandaloneDensityWorld(*App.World,App.Seed,
|
||||
App.bRoundTunnels,App.bRoundTunnelEnhanced,Error))
|
||||
{std::fprintf(stderr,"Standalone world setup failed: %s\n",Error.c_str());return 2;}
|
||||
if(bRoundWallCompare)
|
||||
return RunRoundWallComparison(*App.World,App.Position,RoundCompareTileCount);
|
||||
if(bStreamBenchmark)
|
||||
{
|
||||
const double FullDetailWidth=static_cast<double>(2*App.ClipRadius+2)
|
||||
*CHUNK_SIZE*VOXEL_SIZE/100.0;
|
||||
std::printf("Standalone clip override: exported radius=%d (112 m cube) -> radius=%d (%.0f m LOD0 cube), voxel size=25 cm.\n",
|
||||
ExportClipRadius,App.ClipRadius,FullDetailWidth);
|
||||
if(!App.bForceGenerate)App.LoadMeshCache();
|
||||
return RunStreamingBenchmark(*App.World,
|
||||
FVector(App.Position.X*4.0,App.Position.Y*4.0,App.Position.Z*4.0),
|
||||
App.WorkerCount,StreamBenchmarkMaxLevel,App.bRoundTunnels,
|
||||
App.bRoundTunnelEnhanced,App.MeshCache.IsLoaded()?&App.MeshCache:nullptr,
|
||||
BakeMeshCachePath,App.MakeMeshCacheMetadata());
|
||||
}
|
||||
if(BenchmarkLevel>=0)
|
||||
return RunTileBenchmark(*App.World,App.Position,BenchmarkLevel,BenchmarkCount,
|
||||
App.bProfileAttribution,App.bCompareEvaluators,App.bDensityHash);
|
||||
@@ -1133,7 +1546,7 @@ int main(int Argc,char** Argv)
|
||||
GApp=&App;
|
||||
if(!App.Window||!App.InitializeDevice()){MessageBoxA(nullptr,"Direct3D 11 initialization failed.","VoxelForge standalone",MB_ICONERROR);return 3;}
|
||||
ShowWindow(App.Window,SW_SHOW);UpdateWindow(App.Window);
|
||||
std::printf("Controls: hold right mouse to look; WASD move; Space/Ctrl (E/Q) up/down; Shift faster; wheel changes speed; F wireframe; 1-9 toggle LOD; 0 toggle all levels; N/P change seed; T swap to round/winding research tunnels; Esc quit.\n");
|
||||
std::printf("Controls: hold right mouse to look; WASD move; Space/Ctrl (E/Q) up/down; Shift faster; wheel changes speed; F wireframe; G toggles legacy/detailed round walls; 1-9 toggle LOD; 0 toggle all levels; N/P change seed; T swaps base/round tunnels; Esc quit.\n");
|
||||
std::printf("Owner data: %s; starting at first-strate open-space sample (%.2f, %.2f, %.2f) m.\n",
|
||||
App.DataPath.c_str(),App.Position.X,App.Position.Y,App.Position.Z);
|
||||
std::fflush(stdout);
|
||||
|
||||
Reference in new issue
Block a user