Files
VoxelForge/Standalone/Source/StandaloneViewer.cpp
T
Fr0zkaandClaude Opus 5.5 631dd57eb4 standalone: landing plate removed; passage shaft rings reduced with the rock detail kept
- AddLanding no longer adds a shallow positive ellipsoid (floor plate check 71.3% -> 0%).
- Round 10 had removed all relief on passage walls to pass the ring check (smooth tube; rejected).
  Cause found instead: the owner roughness's 8-voxel Z anchors with linear interpolation. Passage-
  controlled walls now use 2-voxel anchors, blended smoothly into the 8-voxel ones.
- Ring gate (lag-20 correlation) 0.376 -> 0.038 AND shaft relief RMS 1.294 (smooth-tube control
  0.276 fails); passage/cave relief ratio 1.152. Faint rings remain deep in the shaft.
- Whole view 0.984 s cold @8w (interleaved, full payload); W1/W8 44/44 hash fields equal; routes
  5642/5642; look and far-survival audits pass; bad-shell control fails.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
2026-10-05 09:56:11 +02:00

2564 lines
147 KiB
C++

#include "OwnerWorldLoader.h"
#include "StreamingWorld.h"
#include "StandaloneResearchTunnels.h"
#include "StandaloneShapeWorld.h"
#include "VoxelDensityProfile.h"
#ifdef TEXT
#undef TEXT
#endif
#ifndef NOMINMAX
#define NOMINMAX
#endif
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <d3d11.h>
#include <d3dcompiler.h>
#include <dxgi.h>
#include <windowsx.h>
#include <bit>
#include <cstdio>
#include <cstdlib>
#include <filesystem>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <limits>
#include <map>
#include <numeric>
#include <sstream>
#include <unordered_map>
#include <unordered_set>
#pragma comment(lib,"d3d11.lib")
#pragma comment(lib,"dxgi.lib")
#pragma comment(lib,"d3dcompiler.lib")
#pragma comment(lib,"user32.lib")
#pragma comment(lib,"gdi32.lib")
namespace
{
struct V3 { float X=0.0f,Y=0.0f,Z=0.0f; };
V3 Add(V3 A,V3 B){return {A.X+B.X,A.Y+B.Y,A.Z+B.Z};}
V3 Sub(V3 A,V3 B){return {A.X-B.X,A.Y-B.Y,A.Z-B.Z};}
V3 Mul(V3 A,float S){return {A.X*S,A.Y*S,A.Z*S};}
float Dot(V3 A,V3 B){return A.X*B.X+A.Y*B.Y+A.Z*B.Z;}
V3 Cross(V3 A,V3 B){return {A.Y*B.Z-A.Z*B.Y,A.Z*B.X-A.X*B.Z,A.X*B.Y-A.Y*B.X};}
V3 Normalize(V3 A){return Mul(A,1.0f/std::sqrt(std::max(1.0e-20f,Dot(A,A))));}
#include "FarSurvivalAudit.h"
struct FMeshSheetAudit
{
uint64 Components=0;
uint64 SmallComponents=0;
uint64 SmallComponentsInView=0;
uint64 BoundarySmallComponentsInView=0;
uint64 ThinSheets=0;
uint64 TrianglesInThinSheets=0;
double MinimumThicknessVoxels=std::numeric_limits<double>::max();
};
FMeshSheetAudit AuditThinMeshSheets(const FStandaloneCpuTile& Tile,
const FVector& CameraVoxels,float Yaw,float Pitch,bool bPrintCandidates)
{
FMeshSheetAudit Result;
const size_t VertexCount=Tile.Vertices.size();
if(VertexCount==0||Tile.Indices.size()<3)return Result;
std::vector<uint32> Parent(VertexCount);
std::vector<uint8> Rank(VertexCount,0);
std::iota(Parent.begin(),Parent.end(),0u);
auto Find=[&](uint32 Value)
{
uint32 Root=Value;
while(Parent[Root]!=Root)Root=Parent[Root];
while(Parent[Value]!=Value)
{const uint32 Next=Parent[Value];Parent[Value]=Root;Value=Next;}
return Root;
};
auto Union=[&](uint32 A,uint32 B)
{
A=Find(A);B=Find(B);if(A==B)return;
if(Rank[A]<Rank[B])std::swap(A,B);
Parent[B]=A;if(Rank[A]==Rank[B])++Rank[A];
};
const size_t TriangleCount=Tile.Indices.size()/3;
for(size_t T=0;T<TriangleCount;++T)
{
const uint32 A=Tile.Indices[T*3],B=Tile.Indices[T*3+1],C=Tile.Indices[T*3+2];
if(A>=VertexCount||B>=VertexCount||C>=VertexCount)continue;
Union(A,B);Union(B,C);
}
struct FComponent
{
uint64 Triangles=0,Vertices=0;
double Min[3]={std::numeric_limits<double>::max(),std::numeric_limits<double>::max(),std::numeric_limits<double>::max()};
double Max[3]={-std::numeric_limits<double>::max(),-std::numeric_limits<double>::max(),-std::numeric_limits<double>::max()};
double Sum[3]={0.0,0.0,0.0};
double Cov[6]={0.0,0.0,0.0,0.0,0.0,0.0};
};
std::vector<FComponent> Components(VertexCount);
for(size_t I=0;I<VertexCount;++I)
{
const uint32 Root=Find(static_cast<uint32>(I));
FComponent& C=Components[Root];++C.Vertices;
for(int32 A=0;A<3;++A)
{
const double P=Tile.Vertices[I].Position[A];
C.Min[A]=std::min(C.Min[A],P);C.Max[A]=std::max(C.Max[A],P);C.Sum[A]+=P;
}
}
for(size_t T=0;T<TriangleCount;++T)
{
const uint32 A=Tile.Indices[T*3],B=Tile.Indices[T*3+1],C=Tile.Indices[T*3+2];
if(A>=VertexCount||B>=VertexCount||C>=VertexCount)continue;
++Components[Find(A)].Triangles;
}
for(size_t I=0;I<VertexCount;++I)
{
const uint32 Root=Find(static_cast<uint32>(I));
FComponent& C=Components[Root];
if(C.Vertices==0)continue;
const double P[3]={Tile.Vertices[I].Position[0],Tile.Vertices[I].Position[1],Tile.Vertices[I].Position[2]};
const double D[3]={P[0]-C.Sum[0]/C.Vertices,P[1]-C.Sum[1]/C.Vertices,P[2]-C.Sum[2]/C.Vertices};
C.Cov[0]+=D[0]*D[0];C.Cov[1]+=D[1]*D[1];C.Cov[2]+=D[2]*D[2];
C.Cov[3]+=D[0]*D[1];C.Cov[4]+=D[0]*D[2];C.Cov[5]+=D[1]*D[2];
}
const double Camera[3]={CameraVoxels.X*0.25,CameraVoxels.Y*0.25,CameraVoxels.Z*0.25};
const double CP=std::cos(Pitch),Forward[3]={std::sin(Yaw)*CP,std::cos(Yaw)*CP,std::sin(Pitch)};
const double Right[3]={std::cos(Yaw),-std::sin(Yaw),0.0};
const double Up[3]={-std::sin(Yaw)*std::sin(Pitch),-std::cos(Yaw)*std::sin(Pitch),std::cos(Pitch)};
for(size_t Root=0;Root<VertexCount;++Root)
{
FComponent& C=Components[Root];if(C.Triangles==0||C.Vertices==0)continue;
++Result.Components;
const double Extent[3]={C.Max[0]-C.Min[0],C.Max[1]-C.Min[1],C.Max[2]-C.Min[2]};
const double MaxExtent=std::max({Extent[0],Extent[1],Extent[2]})/0.25;
const bool bSmall=C.Triangles<=256&&MaxExtent<=20.0;
if(bSmall)++Result.SmallComponents;
if(!bSmall||C.Vertices<4)continue;
double A[3][3]={{C.Cov[0],C.Cov[3],C.Cov[4]},
{C.Cov[3],C.Cov[1],C.Cov[5]},{C.Cov[4],C.Cov[5],C.Cov[2]}};
for(int32 Sweep=0;Sweep<12;++Sweep)
{
int32 P=0,Q=1;
if(std::abs(A[0][2])>std::abs(A[P][Q])){P=0;Q=2;}
if(std::abs(A[1][2])>std::abs(A[P][Q])){P=1;Q=2;}
if(std::abs(A[P][Q])<1.0e-16)break;
const double Tau=(A[Q][Q]-A[P][P])/(2.0*A[P][Q]);
const double T=std::copysign(1.0,Tau)/(std::abs(Tau)+std::sqrt(1.0+Tau*Tau));
const double Cos=1.0/std::sqrt(1.0+T*T),Sin=T*Cos;
const double App=A[P][P],Aqq=A[Q][Q],Apq=A[P][Q];
A[P][P]=App-T*Apq;A[Q][Q]=Aqq+T*Apq;A[P][Q]=A[Q][P]=0.0;
for(int32 K=0;K<3;++K)if(K!=P&&K!=Q)
{
const double Akp=A[K][P],Akq=A[K][Q];
A[K][P]=A[P][K]=Cos*Akp-Sin*Akq;
A[K][Q]=A[Q][K]=Sin*Akp+Cos*Akq;
}
}
const double Variance=std::max(0.0,std::min({A[0][0],A[1][1],A[2][2]})/C.Vertices);
const double ThicknessVoxels=3.5*std::sqrt(Variance)/0.25;
const double Center[3]={C.Sum[0]/C.Vertices,C.Sum[1]/C.Vertices,C.Sum[2]/C.Vertices};
const double Relative[3]={Center[0]-Camera[0],Center[1]-Camera[1],Center[2]-Camera[2]};
const double Depth=Relative[0]*Forward[0]+Relative[1]*Forward[1]+Relative[2]*Forward[2];
const double ScreenX=Relative[0]*Right[0]+Relative[1]*Right[1]+Relative[2]*Right[2];
const double ScreenY=Relative[0]*Up[0]+Relative[1]*Up[1]+Relative[2]*Up[2];
const bool bInView=Depth>0.0&&Depth<=64.0&&std::abs(ScreenX)<0.924*Depth
&&std::abs(ScreenY)<0.5774*Depth;
if(!bInView)continue;
const FIntVector TileOrigin=Tile.Key.OriginVoxels();
const int32 TileMin[3]={TileOrigin.X,TileOrigin.Y,TileOrigin.Z};
const int32 TileMax[3]={TileOrigin.X+Tile.Key.ExtentVoxels(),
TileOrigin.Y+Tile.Key.ExtentVoxels(),TileOrigin.Z+Tile.Key.ExtentVoxels()};
bool bTouchesTileBoundary=false;
for(int32 Axis=0;Axis<3;++Axis)
{
const double MinVoxels=C.Min[Axis]*4.0,MaxVoxels=C.Max[Axis]*4.0;
bTouchesTileBoundary|=MinVoxels<=TileMin[Axis]+0.5
||MaxVoxels>=TileMax[Axis]-0.5;
}
if(bTouchesTileBoundary)++Result.BoundarySmallComponentsInView;
else ++Result.SmallComponentsInView;
if(bPrintCandidates&&!bTouchesTileBoundary&&Result.SmallComponentsInView<=8)
std::printf("[SmallMeshComponent] level=%d tile=(%d,%d,%d) tris=%llu size=(%.2f,%.2f,%.2f) vox pca_thickness=%.3f vox center=(%.2f,%.2f,%.2f)m\n",
Tile.Key.Level,Tile.Key.Coord.X,Tile.Key.Coord.Y,Tile.Key.Coord.Z,
static_cast<unsigned long long>(C.Triangles),Extent[0]/0.25,Extent[1]/0.25,
Extent[2]/0.25,ThicknessVoxels,Center[0],Center[1],Center[2]);
else if(bPrintCandidates&&bTouchesTileBoundary
&&Result.BoundarySmallComponentsInView<=8)
std::printf("[BoundaryMeshComponent] level=%d tile=(%d,%d,%d) tris=%llu size=(%.2f,%.2f,%.2f) vox pca_thickness=%.3f vox center=(%.2f,%.2f,%.2f)m\n",
Tile.Key.Level,Tile.Key.Coord.X,Tile.Key.Coord.Y,Tile.Key.Coord.Z,
static_cast<unsigned long long>(C.Triangles),Extent[0]/0.25,Extent[1]/0.25,
Extent[2]/0.25,ThicknessVoxels,Center[0],Center[1],Center[2]);
if(ThicknessVoxels>=1.0)continue;
++Result.ThinSheets;Result.MinimumThicknessVoxels=std::min(Result.MinimumThicknessVoxels,ThicknessVoxels);
Result.TrianglesInThinSheets+=C.Triangles;
if(bPrintCandidates&&Result.ThinSheets==1)
std::printf("[ThinMeshSheetCandidate] level=%d tile=(%d,%d,%d) tris=%llu size=(%.2f,%.2f,%.2f) vox pca_thickness=%.3f vox center=(%.2f,%.2f,%.2f)m\n",
Tile.Key.Level,Tile.Key.Coord.X,Tile.Key.Coord.Y,Tile.Key.Coord.Z,
static_cast<unsigned long long>(C.Triangles),Extent[0]/0.25,Extent[1]/0.25,Extent[2]/0.25,
ThicknessVoxels,Center[0],Center[1],Center[2]);
}
return Result;
}
struct M4 { float M[4][4]{}; };
M4 Identity(){M4 R{};for(int I=0;I<4;++I)R.M[I][I]=1.0f;return R;}
M4 Transpose(M4 A){M4 B{};for(int I=0;I<4;++I)for(int J=0;J<4;++J)B.M[I][J]=A.M[J][I];return B;}
M4 Perspective(float Fov,float Aspect,float NearZ,float FarZ)
{
M4 R{};const float Y=1.0f/std::tan(Fov*0.5f);
R.M[0][0]=Y/Aspect;R.M[1][1]=Y;R.M[2][2]=FarZ/(FarZ-NearZ);
R.M[2][3]=1.0f;R.M[3][2]=-NearZ*FarZ/(FarZ-NearZ);return R;
}
M4 View(V3 Position,V3 Forward,V3 Right,V3 Up)
{
M4 R=Identity();
R.M[0][0]=Right.X;R.M[1][0]=Right.Y;R.M[2][0]=Right.Z;R.M[3][0]=-Dot(Right,Position);
R.M[0][1]=Up.X;R.M[1][1]=Up.Y;R.M[2][1]=Up.Z;R.M[3][1]=-Dot(Up,Position);
R.M[0][2]=Forward.X;R.M[1][2]=Forward.Y;R.M[2][2]=Forward.Z;R.M[3][2]=-Dot(Forward,Position);
return R;
}
struct FGpuVertex { float Position[3]; float Normal[3]; };
struct FConstants
{
M4 ViewProjection;
float Camera[3]; float FogStart;
float Forward[3]; float FogEnd;
float LightDirection[3]; float Ambient;
float Padding[4];
};
constexpr int32 MaxLevels=8;
struct FStandaloneBatchKey
{
int32 Level=0,X=0,Y=0,Z=0;
bool operator==(const FStandaloneBatchKey& O)const
{return Level==O.Level&&X==O.X&&Y==O.Y&&Z==O.Z;}
};
struct FStandaloneBatchKeyHash
{
size_t operator()(const FStandaloneBatchKey& K)const noexcept
{
uint64 H=static_cast<uint32>(K.Level);
H=(H*0x9e3779b185ebca87ull)^static_cast<uint32>(K.X);
H=(H*0x9e3779b185ebca87ull)^static_cast<uint32>(K.Y);
H=(H*0x9e3779b185ebca87ull)^static_cast<uint32>(K.Z);
return static_cast<size_t>(H^(H>>32));
}
};
struct FResidentTileMesh
{
std::vector<FStandaloneCpuVertex> Vertices;
std::vector<uint32> Indices;
uint64 Samples=0;
double WorkerSeconds=0.0;
};
struct FStreamingGpuBatch
{
ID3D11Buffer* VertexBuffer=nullptr;
ID3D11Buffer* IndexBuffer=nullptr;
UINT IndexCount=0;
uint64 VertexCount=0;
};
struct FCrossingWork
{
uint64 SubmittedJobs=0;
uint64 PendingJobs=0;
uint64 FinishedJobs=0;
uint64 CancelledQueuedJobs=0;
uint64 StaleResults=0;
uint64 Tiles=0;
uint64 Samples=0;
double WorkerSeconds=0.0;
std::chrono::steady_clock::time_point StartTime{};
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;
ID3D11Device* Device=nullptr;
ID3D11DeviceContext* Context=nullptr;
IDXGISwapChain* SwapChain=nullptr;
ID3D11RenderTargetView* RenderTarget=nullptr;
ID3D11DepthStencilView* DepthView=nullptr;
ID3D11VertexShader* VertexShader=nullptr;
ID3D11PixelShader* PixelShader=nullptr;
ID3D11InputLayout* InputLayout=nullptr;
ID3D11Buffer* Constants=nullptr;
ID3D11RasterizerState* SolidRasterizer=nullptr;
ID3D11RasterizerState* WireRasterizer=nullptr;
std::unique_ptr<FStandaloneOwnerWorld> World;
std::unique_ptr<FStandaloneShapeWorld> ShapeWorld;
std::unique_ptr<FStandaloneShapeWorld> PendingFullShapeWorld;
std::unique_ptr<FStandaloneStreamingGenerator> Streamer;
std::thread FullShapeBuildThread;
std::atomic<bool> bFullShapeBuildFinished{false};
bool bFullShapeBuildSucceeded=false;
std::string FullShapeBuildError;
std::chrono::steady_clock::time_point FullShapeBuildStart{};
FStandaloneMeshCache MeshCache;
std::string DataPath;
bool bHasCameraOverride=false;
V3 CameraPositionOverride{};
float CameraYawOverride=0.0f,CameraPitchOverride=0.0f;
std::unordered_map<FVoxelTileKey,FResidentTileMesh,FStandaloneTileKeyHash> ResidentTiles;
std::unordered_map<FStandaloneBatchKey,std::vector<FVoxelTileKey>,FStandaloneBatchKeyHash> BatchMembers;
std::unordered_map<FStandaloneBatchKey,FStreamingGpuBatch,FStandaloneBatchKeyHash> StreamingBatches;
std::unordered_set<FStandaloneBatchKey,FStandaloneBatchKeyHash> DirtyBatchSet;
std::unordered_map<uint64,FCrossingWork> CrossingWork;
std::vector<FVoxelTileKey> DesiredTiles;
std::vector<FVoxelTileKey> InitialNeighborhoodTiles;
int32 DesiredByLevel[MaxLevels+1]{};
uint64 GeneratedByLevel[MaxLevels+1]{};
uint64 SamplesByLevel[MaxLevels+1]{};
double WorkerSecondsByLevel[MaxLevels+1]{};
uint64 ProfileCyclesByLevel[MaxLevels+1][VoxelDensityProfile::BucketCount]{};
std::vector<double> TileSecondsByLevel[MaxLevels+1];
uint64 CrossingIndex=0;
bool bGenerationRunning=false;
bool bRebuildPending=false;
bool bParityChecked=false;
bool bRoundTunnels=false;
bool bRoundTunnelEnhanced=true;
bool bShapeWorld=false;
bool bShapePipelineActive=false;
bool bNeighborhoodReadyReported=false;
bool bShapeAuditOnly=false;
bool bShapeCompare=false;
bool bFarShapeStagePending=false;
FStandaloneShapeWorld::EDetailVariant ShapeDetailVariant=FStandaloneShapeWorld::EDetailVariant::Coherent;
bool bWireframe=false;
bool bCaptured=false;
bool bAutoCaptureWritten=false;
bool bFullDetailReported=false;
bool bInitialClipmapReported=false;
bool bProfileAttribution=false;
bool bCompareEvaluators=false;
bool bParityOnly=false;
bool bDensityHash=false;
bool bTestOneChunkCrossing=false;
bool bCrossingMoveStarted=false;
bool bCrossingMoveFinished=false;
bool bCrossingClosePosted=false;
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 ColdProcessStart{};
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=8;
int32 ClipRadius=3;
int32 MaxClipLevelOverride=-1;
int32 Width=1280,Height=800;
float Yaw=0.0f,Pitch=0.0f,MoveSpeed=8.0f;
V3 Position{};
POINT LastMouse{};
std::chrono::steady_clock::time_point PreviousFrame=std::chrono::steady_clock::now();
double FrameMs=0.0;
uint64 TotalVertices=0;
uint64 TotalIndices=0;
FIntVector BuildTargetChunk=FIntVector::ZeroValue;
int32 MaxBatchUploadsPerFrame=4;
double BatchUploadBudgetMilliseconds=2.0;
~FApp(){
StopGeneration(true);ReleaseMeshes();
if(SolidRasterizer)SolidRasterizer->Release();if(WireRasterizer)WireRasterizer->Release();
if(Constants)Constants->Release();if(InputLayout)InputLayout->Release();
if(VertexShader)VertexShader->Release();if(PixelShader)PixelShader->Release();
if(RenderTarget)RenderTarget->Release();if(DepthView)DepthView->Release();
if(SwapChain)SwapChain->Release();if(Context)Context->Release();if(Device)Device->Release();
}
void ReleaseMeshes()
{
for(auto& Pair:StreamingBatches)
{
if(Pair.second.VertexBuffer)Pair.second.VertexBuffer->Release();
if(Pair.second.IndexBuffer)Pair.second.IndexBuffer->Release();
}
StreamingBatches.clear();BatchMembers.clear();ResidentTiles.clear();
DirtyBatchSet.clear();DesiredTiles.clear();CrossingWork.clear();
for(int32 I=0;I<=MaxLevels;++I)
{DesiredByLevel[I]=0;GeneratedByLevel[I]=0;SamplesByLevel[I]=0;WorkerSecondsByLevel[I]=0.0;
std::fill(std::begin(ProfileCyclesByLevel[I]),std::end(ProfileCyclesByLevel[I]),0);
TileSecondsByLevel[I].clear();}
TotalVertices=0;TotalIndices=0;
}
void StopGeneration(bool bCancel=false)
{
(void)bCancel;
if(Streamer)Streamer->Stop();
Streamer.reset();
if(FullShapeBuildThread.joinable())FullShapeBuildThread.join();
PendingFullShapeWorld.reset();
bFullShapeBuildFinished.store(false,std::memory_order_relaxed);
bFullShapeBuildSucceeded=false;
FullShapeBuildError.clear();
InitialNeighborhoodTiles.clear();
bShapePipelineActive=false;
bNeighborhoodReadyReported=false;
bFarShapeStagePending=false;
bGenerationRunning=false;
}
V3 Forward() const
{return Normalize({std::sin(Yaw)*std::cos(Pitch),std::cos(Yaw)*std::cos(Pitch),std::sin(Pitch)});}
FIntVector CameraChunk() const
{return StandaloneWorldMesher::CameraChunk(FVector(Position.X*4.0,Position.Y*4.0,Position.Z*4.0));}
bool NeedsRecenter(const FIntVector& Center) const
{
return CameraChunk()!=Center;
}
bool MakeBuffer(ID3D11Buffer** Out,const void* Data,size_t Bytes,UINT Bind)
{
if(Bytes==0||Bytes>std::numeric_limits<UINT>::max())return false;
D3D11_BUFFER_DESC Desc{};Desc.ByteWidth=static_cast<UINT>(Bytes);
Desc.Usage=D3D11_USAGE_DEFAULT;Desc.BindFlags=Bind;
D3D11_SUBRESOURCE_DATA Initial{};Initial.pSysMem=Data;
return SUCCEEDED(Device->CreateBuffer(&Desc,&Initial,Out));
}
static void AppendPngU32(std::vector<uint8>& Out,uint32 Value)
{
Out.push_back(static_cast<uint8>(Value >> 24));
Out.push_back(static_cast<uint8>(Value >> 16));
Out.push_back(static_cast<uint8>(Value >> 8));
Out.push_back(static_cast<uint8>(Value));
}
static uint32 PngCrc32(const uint8* Data,size_t Count)
{
uint32 Crc=0xffffffffu;
for(size_t I=0;I<Count;++I)
{
Crc^=Data[I];
for(int32 Bit=0;Bit<8;++Bit)Crc=(Crc>>1)^((Crc&1)?0xedb88320u:0u);
}
return Crc^0xffffffffu;
}
static void AppendPngChunk(std::vector<uint8>& Png,const char Type[4],const std::vector<uint8>& Data)
{
AppendPngU32(Png,static_cast<uint32>(Data.size()));
const size_t CrcStart=Png.size();
Png.insert(Png.end(),Type,Type+4);
Png.insert(Png.end(),Data.begin(),Data.end());
AppendPngU32(Png,PngCrc32(Png.data()+CrcStart,Png.size()-CrcStart));
}
bool SaveBackBufferPng(const std::string& Path)
{
ID3D11Texture2D* BackBuffer=nullptr;
if(FAILED(SwapChain->GetBuffer(0,IID_PPV_ARGS(&BackBuffer))))return false;
D3D11_TEXTURE2D_DESC Desc{};BackBuffer->GetDesc(&Desc);
Desc.Usage=D3D11_USAGE_STAGING;Desc.BindFlags=0;Desc.CPUAccessFlags=D3D11_CPU_ACCESS_READ;Desc.MiscFlags=0;
ID3D11Texture2D* Staging=nullptr;
const HRESULT CreateResult=Device->CreateTexture2D(&Desc,nullptr,&Staging);
if(FAILED(CreateResult)){BackBuffer->Release();return false;}
Context->CopyResource(Staging,BackBuffer);BackBuffer->Release();
D3D11_MAPPED_SUBRESOURCE Mapped{};
if(FAILED(Context->Map(Staging,0,D3D11_MAP_READ,0,&Mapped))){Staging->Release();return false;}
const size_t RowBytes=static_cast<size_t>(Desc.Width)*4;
std::vector<uint8> Raw((RowBytes+1)*Desc.Height);
for(UINT Y=0;Y<Desc.Height;++Y)
{
uint8* Row=Raw.data()+static_cast<size_t>(Y)*(RowBytes+1);
Row[0]=0;
std::memcpy(Row+1,static_cast<const uint8*>(Mapped.pData)+static_cast<size_t>(Y)*Mapped.RowPitch,RowBytes);
}
Context->Unmap(Staging,0);Staging->Release();
std::vector<uint8> Zlib{0x78,0x01};
uint32 AdlerA=1,AdlerB=0;
for(const uint8 Byte:Raw){AdlerA=(AdlerA+Byte)%65521u;AdlerB=(AdlerB+AdlerA)%65521u;}
for(size_t Offset=0;Offset<Raw.size();)
{
const uint16 Block=static_cast<uint16>(std::min<size_t>(65535,Raw.size()-Offset));
const bool bFinal=Offset+Block==Raw.size();
Zlib.push_back(bFinal?1:0);
Zlib.push_back(static_cast<uint8>(Block));Zlib.push_back(static_cast<uint8>(Block>>8));
const uint16 NotBlock=static_cast<uint16>(~Block);
Zlib.push_back(static_cast<uint8>(NotBlock));Zlib.push_back(static_cast<uint8>(NotBlock>>8));
Zlib.insert(Zlib.end(),Raw.begin()+Offset,Raw.begin()+Offset+Block);
Offset+=Block;
}
AppendPngU32(Zlib,(AdlerB<<16)|AdlerA);
std::vector<uint8> Png{137,80,78,71,13,10,26,10};
std::vector<uint8> Ihdr;AppendPngU32(Ihdr,Desc.Width);AppendPngU32(Ihdr,Desc.Height);
Ihdr.insert(Ihdr.end(),{8,6,0,0,0});AppendPngChunk(Png,"IHDR",Ihdr);
AppendPngChunk(Png,"IDAT",Zlib);AppendPngChunk(Png,"IEND",{});
std::ofstream File(Path,std::ios::binary);
if(!File)return false;
File.write(reinterpret_cast<const char*>(Png.data()),static_cast<std::streamsize>(Png.size()));
return File.good();
}
bool Resize(UINT NewWidth,UINT NewHeight)
{
if(!Device||NewWidth==0||NewHeight==0)return false;
Width=static_cast<int32>(NewWidth);Height=static_cast<int32>(NewHeight);
if(RenderTarget){RenderTarget->Release();RenderTarget=nullptr;}
if(DepthView){DepthView->Release();DepthView=nullptr;}
if(SwapChain->ResizeBuffers(0,NewWidth,NewHeight,DXGI_FORMAT_UNKNOWN,0)!=S_OK)return false;
ID3D11Texture2D* Back=nullptr;
if(FAILED(SwapChain->GetBuffer(0,IID_PPV_ARGS(&Back))))return false;
const HRESULT RtvResult=Device->CreateRenderTargetView(Back,nullptr,&RenderTarget);Back->Release();
if(FAILED(RtvResult))return false;
D3D11_TEXTURE2D_DESC DepthDesc{};DepthDesc.Width=NewWidth;DepthDesc.Height=NewHeight;
DepthDesc.MipLevels=1;DepthDesc.ArraySize=1;DepthDesc.Format=DXGI_FORMAT_D24_UNORM_S8_UINT;
DepthDesc.SampleDesc.Count=1;DepthDesc.Usage=D3D11_USAGE_DEFAULT;DepthDesc.BindFlags=D3D11_BIND_DEPTH_STENCIL;
ID3D11Texture2D* Depth=nullptr;
if(FAILED(Device->CreateTexture2D(&DepthDesc,nullptr,&Depth)))return false;
const HRESULT DsvResult=Device->CreateDepthStencilView(Depth,nullptr,&DepthView);Depth->Release();
return SUCCEEDED(DsvResult);
}
bool InitializeDevice()
{
DXGI_SWAP_CHAIN_DESC Desc{};Desc.BufferCount=1;Desc.BufferDesc.Format=DXGI_FORMAT_R8G8B8A8_UNORM;
Desc.BufferUsage=DXGI_USAGE_RENDER_TARGET_OUTPUT;Desc.OutputWindow=Window;Desc.SampleDesc.Count=1;
Desc.Windowed=TRUE;Desc.SwapEffect=DXGI_SWAP_EFFECT_DISCARD;
D3D_FEATURE_LEVEL Feature{};
if(FAILED(D3D11CreateDeviceAndSwapChain(nullptr,D3D_DRIVER_TYPE_HARDWARE,nullptr,0,nullptr,0,
D3D11_SDK_VERSION,&Desc,&SwapChain,&Device,&Feature,&Context)))return false;
const char* ShaderSource=R"(
cbuffer C:register(b0){float4x4 VP;float3 Camera;float FogStart;float3 Forward;float FogEnd;float3 LightDir;float Ambient;float4 Pad;}
struct I{float3 p:POSITION;float3 n:NORMAL;};
struct O{float4 p:SV_POSITION;float3 world:TEXCOORD0;float3 n:NORMAL;};
O VS(I i){O o;o.p=mul(float4(i.p,1),VP);o.world=i.p;o.n=normalize(i.n);return o;}
float4 PS(O i):SV_TARGET{
float3 N=normalize(i.n);float3 V=normalize(Camera-i.world);float3 L=normalize(-LightDir);
float Diffuse=saturate(dot(N,L));float Head=saturate(dot(N,V));
float Dist=length(Camera-i.world);float Atten=1.0-saturate(Dist/95.0);
float3 Rock=lerp(float3(0.30,0.22,0.16),float3(0.48,0.43,0.34),saturate(N.z*0.5+0.5));
// Keep the camera fill broad and low-amplitude. The old squared, high-intensity
// lobe made the 25 cm density-lattice normal variation read as horizontal bands.
Head=Head*Atten;
float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
float Fog=saturate((Dist-FogStart)/(FogEnd-FogStart));
Lit=lerp(Lit,float3(0.10,0.14,0.19),Fog);return float4(Lit,1);
})";
ID3DBlob* VsCode=nullptr;ID3DBlob* PsCode=nullptr;ID3DBlob* Errors=nullptr;
HRESULT Hr=D3DCompile(ShaderSource,std::strlen(ShaderSource),nullptr,nullptr,nullptr,"VS","vs_5_0",0,0,&VsCode,&Errors);
if(FAILED(Hr)){if(Errors)std::fprintf(stderr,"Vertex shader compile: %s\n",static_cast<const char*>(Errors->GetBufferPointer()));if(Errors)Errors->Release();return false;}
Hr=D3DCompile(ShaderSource,std::strlen(ShaderSource),nullptr,nullptr,nullptr,"PS","ps_5_0",0,0,&PsCode,&Errors);
if(FAILED(Hr)){if(Errors)std::fprintf(stderr,"Pixel shader compile: %s\n",static_cast<const char*>(Errors->GetBufferPointer()));if(Errors)Errors->Release();VsCode->Release();return false;}
Hr=Device->CreateVertexShader(VsCode->GetBufferPointer(),VsCode->GetBufferSize(),nullptr,&VertexShader);
if(SUCCEEDED(Hr))Hr=Device->CreatePixelShader(PsCode->GetBufferPointer(),PsCode->GetBufferSize(),nullptr,&PixelShader);
D3D11_INPUT_ELEMENT_DESC Elements[]={{"POSITION",0,DXGI_FORMAT_R32G32B32_FLOAT,0,0,D3D11_INPUT_PER_VERTEX_DATA,0},
{"NORMAL",0,DXGI_FORMAT_R32G32B32_FLOAT,0,12,D3D11_INPUT_PER_VERTEX_DATA,0}};
if(SUCCEEDED(Hr))Hr=Device->CreateInputLayout(Elements,2,VsCode->GetBufferPointer(),VsCode->GetBufferSize(),&InputLayout);
VsCode->Release();PsCode->Release();if(FAILED(Hr))return false;
D3D11_BUFFER_DESC ConstantDesc{};ConstantDesc.ByteWidth=sizeof(FConstants);ConstantDesc.Usage=D3D11_USAGE_DEFAULT;
ConstantDesc.BindFlags=D3D11_BIND_CONSTANT_BUFFER;
if(FAILED(Device->CreateBuffer(&ConstantDesc,nullptr,&Constants)))return false;
D3D11_RASTERIZER_DESC Raster{};Raster.FillMode=D3D11_FILL_SOLID;Raster.CullMode=D3D11_CULL_NONE;
Raster.DepthClipEnable=TRUE;if(FAILED(Device->CreateRasterizerState(&Raster,&SolidRasterizer)))return false;
Raster.FillMode=D3D11_FILL_WIREFRAME;if(FAILED(Device->CreateRasterizerState(&Raster,&WireRasterizer)))return false;
return Resize(Width,Height);
}
size_t PrintParity()
{
if(bParityChecked||!World)return 0;
bParityChecked=true;size_t MismatchCount=0;
std::map<std::string,size_t> Kinds;
for(size_t I=0;I<World->DensitySamples.size();++I)
{
const FStandaloneParitySample& S=World->DensitySamples[I];
const float Actual=World->Generator->GetDensityAt(static_cast<float>(S.PositionVoxels.X),
static_cast<float>(S.PositionVoxels.Y),static_cast<float>(S.PositionVoxels.Z));
++Kinds[S.Kind];
if(std::bit_cast<uint32>(Actual)!=std::bit_cast<uint32>(S.PluginDensity))
{
++MismatchCount;
std::fprintf(stderr,"PARITY MISMATCH %s strate=%d voxels=(%.6f,%.6f,%.6f) plugin=%.9g standalone=%.9g\n",
S.Kind.c_str(),S.StrateIndex,S.PositionVoxels.X,S.PositionVoxels.Y,S.PositionVoxels.Z,S.PluginDensity,Actual);
}
}
std::printf("Density parity: %zu/%zu bit-identical; mismatches=%zu\n",
World->DensitySamples.size()-MismatchCount,World->DensitySamples.size(),MismatchCount);
for(const auto& [Kind,Count]:Kinds)std::printf(" %-24s %zu\n",Kind.c_str(),Count);
std::fflush(stdout);
std::fflush(stdout);
return MismatchCount;
}
bool ConfigureWorld(std::string& Error)
{
FStandaloneShapeWorldAccess::SetActive(nullptr);
ShapeWorld.reset();
World=std::make_unique<FStandaloneOwnerWorld>();
if(!StandaloneOwnerLoader::Load(DataPath,*World,Error))return false;
if(!bParityChecked&&Seed==World->Seed&&!bRoundTunnels)PrintParity();
const int32 ExportClipRadius=World->ClipRadius;
World->ClipRadius=ClipRadius;
World->Settings->ClipRadius=ClipRadius;
if(!bPrintedClipOverride)
{
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);
}
if(!PrepareStandaloneDensityWorld(*World,Seed,bRoundTunnels,
bRoundTunnelEnhanced,Error))return false;
if(MaxClipLevelOverride>=0)
{
World->MaxClipLevel=MaxClipLevelOverride;
World->Settings->MaxClipLevel=MaxClipLevelOverride;
std::printf("Standalone max clip level override applied after world reload: max_level=%d.\n",
MaxClipLevelOverride);
}
// Compile one world field before streaming. The streamer already prioritizes the
// player's neighborhood, so a second near-only field would only duplicate work.
if(bShapeWorld&&!BuildShapeWorldForCamera(Error,true,false))return false;
LoadMeshCache();
return true;
}
bool BuildShapeWorldForCamera(std::string& Error,bool bIncludeFarWorld=false,
bool bNeighborhoodOnly=false)
{
const FVector CameraVoxels(Position.X*4.0,Position.Y*4.0,Position.Z*4.0);
const FStandaloneDesiredTileSet Desired=StandaloneWorldMesher::BuildDesiredSet(*World,CameraVoxels);
FIntVector Min(INT32_MAX,INT32_MAX,INT32_MAX),Max(INT32_MIN,INT32_MIN,INT32_MIN);
FIntVector ClipMin(INT32_MAX,INT32_MAX,INT32_MAX),ClipMax(INT32_MIN,INT32_MIN,INT32_MIN);
uint64 FineTiles=0;
InitialNeighborhoodTiles.clear();
const FIntVector CenterChunk=Desired.CenterChunk;
for(const FVoxelTileKey& Tile:Desired.Tiles)
{
const FIntVector Origin=Tile.OriginVoxels();
const int32 Extent=Tile.ExtentVoxels();
ClipMin.X=std::min(ClipMin.X,Origin.X);ClipMin.Y=std::min(ClipMin.Y,Origin.Y);ClipMin.Z=std::min(ClipMin.Z,Origin.Z);
ClipMax.X=std::max(ClipMax.X,Origin.X+Extent);ClipMax.Y=std::max(ClipMax.Y,Origin.Y+Extent);ClipMax.Z=std::max(ClipMax.Z,Origin.Z+Extent);
if(bNeighborhoodOnly)
{
if(Tile.Level!=0||std::abs(Tile.Coord.X-CenterChunk.X)>1
||std::abs(Tile.Coord.Y-CenterChunk.Y)>1
||std::abs(Tile.Coord.Z-CenterChunk.Z)>1)continue;
InitialNeighborhoodTiles.push_back(Tile);
}
else if(Tile.Level>=World->FullResClipLevels)continue;
Min.X=std::min(Min.X,Origin.X);Min.Y=std::min(Min.Y,Origin.Y);Min.Z=std::min(Min.Z,Origin.Z);
Max.X=std::max(Max.X,Origin.X+Extent);Max.Y=std::max(Max.Y,Origin.Y+Extent);Max.Z=std::max(Max.Z,Origin.Z+Extent);
++FineTiles;
}
if(!FineTiles){Error="no full-detail tiles are available for shape compilation";return false;}
if(bNeighborhoodOnly)
{
if(InitialNeighborhoodTiles.size()!=27)
{Error="the 3x3x3 player neighborhood does not contain 27 LOD0 tiles";return false;}
constexpr int32 NearBuildPad=64;
Min.X-=NearBuildPad;Min.Y-=NearBuildPad;Min.Z-=NearBuildPad;
Max.X+=NearBuildPad;Max.Y+=NearBuildPad;Max.Z+=NearBuildPad;
}
ShapeWorld=std::make_unique<FStandaloneShapeWorld>();
const int32 ShapeWorkers=bNeighborhoodOnly?(WorkerCount>=8?2:1):WorkerCount;
if(!ShapeWorld->Build(*World,Min,Max,ClipMin,ClipMax,ShapeWorkers,
ShapeDetailVariant,bIncludeFarWorld,Error))
{ShapeWorld.reset();return false;}
FStandaloneShapeWorldAccess::SetActive(ShapeWorld.get());
bShapePipelineActive=bNeighborhoodOnly;
bNeighborhoodReadyReported=false;
bFarShapeStagePending=bShapeWorld&&!bNeighborhoodOnly&&!ShapeWorld->HasFarWorld();
std::printf("[ShapeRegion] fine_tiles=%llu bounds=(%d,%d,%d)..(%d,%d,%d) clip=(%d,%d,%d)..(%d,%d,%d)\n",
static_cast<unsigned long long>(FineTiles),Min.X,Min.Y,Min.Z,Max.X,Max.Y,Max.Z,
ClipMin.X,ClipMin.Y,ClipMin.Z,ClipMax.X,ClipMax.Y,ClipMax.Z);
return true;
}
bool StartFullShapeBuild(const std::vector<FVoxelTileKey>& FullDesired,std::string& Error)
{
FIntVector FineMin(INT32_MAX,INT32_MAX,INT32_MAX),FineMax(INT32_MIN,INT32_MIN,INT32_MIN);
FIntVector ClipMin(INT32_MAX,INT32_MAX,INT32_MAX),ClipMax(INT32_MIN,INT32_MIN,INT32_MIN);
for(const FVoxelTileKey& Tile:FullDesired)
{
const FIntVector Origin=Tile.OriginVoxels();const int32 Extent=Tile.ExtentVoxels();
ClipMin.X=std::min(ClipMin.X,Origin.X);ClipMin.Y=std::min(ClipMin.Y,Origin.Y);ClipMin.Z=std::min(ClipMin.Z,Origin.Z);
ClipMax.X=std::max(ClipMax.X,Origin.X+Extent);ClipMax.Y=std::max(ClipMax.Y,Origin.Y+Extent);ClipMax.Z=std::max(ClipMax.Z,Origin.Z+Extent);
if(Tile.Level>=World->FullResClipLevels)continue;
FineMin.X=std::min(FineMin.X,Origin.X);FineMin.Y=std::min(FineMin.Y,Origin.Y);FineMin.Z=std::min(FineMin.Z,Origin.Z);
FineMax.X=std::max(FineMax.X,Origin.X+Extent);FineMax.Y=std::max(FineMax.Y,Origin.Y+Extent);FineMax.Z=std::max(FineMax.Z,Origin.Z+Extent);
}
if(FineMin.X==INT32_MAX||ClipMin.X==INT32_MAX)
{Error="the full shape build has no desired detail or clipmap tiles";return false;}
PendingFullShapeWorld=std::make_unique<FStandaloneShapeWorld>();
FullShapeBuildError.clear();bFullShapeBuildSucceeded=false;
bFullShapeBuildFinished.store(false,std::memory_order_relaxed);
const int32 FullWorkers=WorkerCount>=8?6:WorkerCount;
FullShapeBuildStart=std::chrono::steady_clock::now();
FullShapeBuildThread=std::thread([this,FineMin,FineMax,ClipMin,ClipMax,FullWorkers]()
{
bFullShapeBuildSucceeded=PendingFullShapeWorld->Build(*World,FineMin,FineMax,
ClipMin,ClipMax,FullWorkers,ShapeDetailVariant,true,FullShapeBuildError);
bFullShapeBuildFinished.store(true,std::memory_order_release);
});
std::printf("[ShapePipelineBuildStart] neighborhood_tiles=%zu near_mesh_workers=%d full_shape_workers=%d\n",
InitialNeighborhoodTiles.size(),WorkerCount>=8?2:1,FullWorkers);
std::fflush(stdout);
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||bShapeWorld||!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();
ReleaseMeshes();
GenerationStart=std::chrono::steady_clock::now();
std::string Error;
if(!ConfigureWorld(Error))
{std::fprintf(stderr,"World load failed: %s\n",Error.c_str());MessageBoxA(Window,Error.c_str(),"VoxelForge viewer",MB_ICONERROR);return;}
const FVector CameraVoxels(Position.X*4.0,Position.Y*4.0,Position.Z*4.0);
BuildTargetChunk=StandaloneWorldMesher::CameraChunk(CameraVoxels);
bFullDetailReported=false;bInitialClipmapReported=false;bAutoCaptureWritten=false;
if(bProfileAttribution)
{
VoxelDensityProfile::SetMode(VoxelDensityProfile::EMode::Attribution,128);
VoxelDensityProfile::CalibrateTimer();
VoxelDensityProfile::Reset();
std::printf("Density stage profiling enabled (Attribution, sample interval 128).\n");
}
const FStandaloneDesiredTileSet Desired=StandaloneWorldMesher::BuildDesiredSet(*World,CameraVoxels);
DesiredTiles=Desired.Tiles;
for(int32 I=0;I<=MaxLevels;++I)DesiredByLevel[I]=0;
for(const FVoxelTileKey& Tile:DesiredTiles)++DesiredByLevel[Tile.Level];
const int32 StreamWorkers=bShapePipelineActive?(WorkerCount>=8?2:1):WorkerCount;
Streamer=std::make_unique<FStandaloneStreamingGenerator>(*World,StreamWorkers,false,
(!bShapeWorld&&MeshCache.IsLoaded())?&MeshCache:nullptr,
bFarShapeStagePending?World->FullResClipLevels-1:-1);
if(bShapePipelineActive)Streamer->SetDesiredTileFilter(InitialNeighborhoodTiles);
const FStandaloneStreamingUpdate Update=Streamer->Start(CameraVoxels,{},CrossingIndex);
if(bShapePipelineActive&&Update.DesiredTiles!=static_cast<int32>(InitialNeighborhoodTiles.size()))
{
StopGeneration();
Error="the initial neighborhood streamer did not accept all 27 tiles";
std::fprintf(stderr,"World load failed: %s\n",Error.c_str());
return;
}
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%s: %d desired tiles, %d full-detail tiles, %d workers; upload budget %d batches / %.1f ms per frame.\n",
Seed,TunnelMode,bShapeWorld?" [shape intervals]":"",Update.DesiredTiles,
Update.FullResTiles,Streamer->GetWorkerCount(),MaxBatchUploadsPerFrame,BatchUploadBudgetMilliseconds);
if(bShapePipelineActive)
{
std::string ShapeError;
if(!StartFullShapeBuild(DesiredTiles,ShapeError))
{
StopGeneration();
std::fprintf(stderr,"World load failed: %s\n",ShapeError.c_str());
return;
}
}
std::fflush(stdout);
}
void RequestSeed(int32 Delta)
{
const uint32 Next=static_cast<uint32>(Seed)+static_cast<uint32>(Delta);
Seed=static_cast<int32>(Next);bRebuildPending=true;
std::printf("Seed changed to %d; replacing the current streaming world.\n",Seed);
}
void StartFarShapeGeneration()
{
if(!bFarShapeStagePending||!ShapeWorld||ShapeWorld->HasFarWorld()||!Streamer)return;
ShapeWorld->BuildFarWorld();
bFarShapeStagePending=false;
Streamer->Stop();
std::vector<FVoxelTileKey> ResidentKeys;ResidentKeys.reserve(ResidentTiles.size());
for(const auto& Pair:ResidentTiles)ResidentKeys.push_back(Pair.first);
Streamer.reset();
Streamer=std::make_unique<FStandaloneStreamingGenerator>(*World,WorkerCount,false,nullptr,-1);
const FVector CameraVoxels(Position.X*4.0,Position.Y*4.0,Position.Z*4.0);
++CrossingIndex;
const FStandaloneStreamingUpdate Update=Streamer->Start(CameraVoxels,ResidentKeys,CrossingIndex);
FCrossingWork& Work=CrossingWork[CrossingIndex];
Work.SubmittedJobs=Update.NewJobs;Work.PendingJobs=Update.NewJobs;
Work.StartTime=std::chrono::steady_clock::now();
bGenerationRunning=true;
std::printf("[FarStreamStart] desired=%d retained=%d new_jobs=%d workers=%d\n",
Update.DesiredTiles,Update.RetainedResidentTiles,Update.NewJobs,WorkerCount);
std::fflush(stdout);
}
void TryCompleteShapePipeline()
{
if(!bShapePipelineActive||InitialNeighborhoodTiles.empty())return;
bool bNeighborhoodUploaded=true;
for(const FVoxelTileKey& Key:InitialNeighborhoodTiles)
if(!ResidentTiles.contains(Key)){bNeighborhoodUploaded=false;break;}
bNeighborhoodUploaded &= DirtyBatchSet.empty();
if(bNeighborhoodUploaded&&!bNeighborhoodReadyReported)
{
bNeighborhoodReadyReported=true;
const double Elapsed=std::chrono::duration<double>(
std::chrono::steady_clock::now()-ColdProcessStart).count();
std::printf("[NeighborhoodReady] %.3fs after cold process start; tiles=%zu/%zu vertices=%llu indices=%llu\n",
Elapsed,InitialNeighborhoodTiles.size(),InitialNeighborhoodTiles.size(),
static_cast<unsigned long long>(TotalVertices),
static_cast<unsigned long long>(TotalIndices));
std::fflush(stdout);
}
if(!bNeighborhoodReadyReported
||!bFullShapeBuildFinished.load(std::memory_order_acquire))return;
if(FullShapeBuildThread.joinable())FullShapeBuildThread.join();
if(!bFullShapeBuildSucceeded||!PendingFullShapeWorld)
{
bShapePipelineActive=false;bGenerationRunning=false;
std::fprintf(stderr,"[ShapePipeline] full shape build failed: %s\n",
FullShapeBuildError.c_str());
PostMessageA(Window,WM_CLOSE,0,0);
return;
}
const double FullShapeSeconds=std::chrono::duration<double>(
std::chrono::steady_clock::now()-FullShapeBuildStart).count();
std::vector<FVoxelTileKey> ResidentKeys;ResidentKeys.reserve(ResidentTiles.size());
for(const auto& Pair:ResidentTiles)ResidentKeys.push_back(Pair.first);
if(Streamer)Streamer->Stop();
Streamer.reset();
ShapeWorld=std::move(PendingFullShapeWorld);
FStandaloneShapeWorldAccess::SetActive(ShapeWorld.get());
bFarShapeStagePending=false;bShapePipelineActive=false;
const FVector CameraVoxels(Position.X*4.0,Position.Y*4.0,Position.Z*4.0);
Streamer=std::make_unique<FStandaloneStreamingGenerator>(*World,WorkerCount,false,nullptr,-1);
++CrossingIndex;
const FStandaloneStreamingUpdate Update=Streamer->Start(CameraVoxels,ResidentKeys,CrossingIndex);
if(Update.DesiredTiles!=static_cast<int32>(DesiredTiles.size()))
{
Streamer->Stop();Streamer.reset();bGenerationRunning=false;
std::fprintf(stderr,"[ShapePipeline] full streamer accepted %d of %zu desired tiles.\n",
Update.DesiredTiles,DesiredTiles.size());
PostMessageA(Window,WM_CLOSE,0,0);
return;
}
FCrossingWork& Work=CrossingWork[CrossingIndex];
Work.SubmittedJobs=Update.NewJobs;Work.PendingJobs=Update.NewJobs;
Work.StartTime=std::chrono::steady_clock::now();
bGenerationRunning=true;
std::printf("[ShapePipelineFullReady] full_shape=%.3fs retained=%d new_jobs=%d workers=%d; filling outward\n",
FullShapeSeconds,Update.RetainedResidentTiles,Update.NewJobs,WorkerCount);
std::fflush(stdout);
}
void MarkBatchDirty(const FStandaloneBatchKey& Key)
{DirtyBatchSet.insert(Key);}
static bool TileKeyLess(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;
}
void RemoveResidentTile(const FVoxelTileKey& Key)
{
auto Resident=ResidentTiles.find(Key);
if(Resident==ResidentTiles.end())return;
const FStandaloneBatchKey Batch=BatchForTile(Key);
auto Members=BatchMembers.find(Batch);
if(Members!=BatchMembers.end())
{
auto& Items=Members->second;
Items.erase(std::remove(Items.begin(),Items.end(),Key),Items.end());
if(Items.empty())BatchMembers.erase(Members);
}
MarkBatchDirty(Batch);
ResidentTiles.erase(Resident);
}
void RecenterStreaming()
{
if(!World||!Streamer)return;
const FVector CameraVoxels(Position.X*4.0,Position.Y*4.0,Position.Z*4.0);
FStandaloneDesiredTileSet NewDesired=StandaloneWorldMesher::BuildDesiredSet(*World,CameraVoxels);
if(NewDesired.CenterChunk==BuildTargetChunk)return;
if(bShapeWorld)
{
bRebuildPending=true;
std::printf("[ShapeRegion] camera crossed its compiled full-detail bounds; rebuilding the interval region.\n");
return;
}
const FIntVector Previous=BuildTargetChunk;
std::unordered_set<FVoxelTileKey,FStandaloneTileKeyHash> NewSet;
NewSet.reserve(NewDesired.Tiles.size()*2+1);
for(const FVoxelTileKey& Key:NewDesired.Tiles)NewSet.insert(Key);
std::vector<FVoxelTileKey> Leavers;
for(const auto& Pair:ResidentTiles)if(!NewSet.contains(Pair.first))Leavers.push_back(Pair.first);
for(const FVoxelTileKey& Key:Leavers)RemoveResidentTile(Key);
DesiredTiles=std::move(NewDesired.Tiles);
for(int32 I=0;I<=MaxLevels;++I)DesiredByLevel[I]=0;
for(const FVoxelTileKey& Tile:DesiredTiles)++DesiredByLevel[Tile.Level];
std::vector<FVoxelTileKey> ResidentKeys;ResidentKeys.reserve(ResidentTiles.size());
for(const auto& Pair:ResidentTiles)ResidentKeys.push_back(Pair.first);
++CrossingIndex;
const FStandaloneStreamingUpdate Update=Streamer->UpdateDesired(CameraVoxels,ResidentKeys,CrossingIndex);
for(const auto& Pair:Update.CancelledByCrossing)
{
FCrossingWork& Work=CrossingWork[Pair.first];
Work.PendingJobs=Work.PendingJobs>=Pair.second?Work.PendingJobs-Pair.second:0;
Work.CancelledQueuedJobs+=Pair.second;
}
FCrossingWork& Work=CrossingWork[CrossingIndex];
Work.SubmittedJobs=Update.NewJobs;Work.PendingJobs=Update.NewJobs;
Work.StartTime=std::chrono::steady_clock::now();
BuildTargetChunk=Update.CenterChunk;bGenerationRunning=true;
std::printf("[ChunkCrossing] id=%llu center=(%d,%d,%d) delta=(%d,%d,%d) desired=%d retained=%d new_jobs=%d dropped_resident=%zu cancelled_queued=%d full_detail=%d\n",
static_cast<unsigned long long>(CrossingIndex),Update.CenterChunk.X,Update.CenterChunk.Y,Update.CenterChunk.Z,
Update.CenterChunk.X-Previous.X,Update.CenterChunk.Y-Previous.Y,Update.CenterChunk.Z-Previous.Z,
Update.DesiredTiles,Update.RetainedResidentTiles,Update.NewJobs,Leavers.size(),
Update.CancelledQueuedJobs,Update.FullResTiles);
std::fflush(stdout);
ReportCrossingWork();
}
bool RebuildBatch(const FStandaloneBatchKey& Key)
{
auto MembersIt=BatchMembers.find(Key);
auto Existing=StreamingBatches.find(Key);
if(MembersIt==BatchMembers.end()||MembersIt->second.empty())
{
if(Existing!=StreamingBatches.end())
{
TotalVertices-=Existing->second.VertexCount;TotalIndices-=Existing->second.IndexCount;
if(Existing->second.VertexBuffer)Existing->second.VertexBuffer->Release();
if(Existing->second.IndexBuffer)Existing->second.IndexBuffer->Release();
StreamingBatches.erase(Existing);
}
return true;
}
std::vector<FVoxelTileKey> Members=MembersIt->second;
std::sort(Members.begin(),Members.end(),TileKeyLess);
size_t VertexCount=0,IndexCount=0;
for(const FVoxelTileKey& Tile:Members)
{
const auto It=ResidentTiles.find(Tile);if(It==ResidentTiles.end())continue;
VertexCount+=It->second.Vertices.size();IndexCount+=It->second.Indices.size();
}
if(VertexCount==0||IndexCount==0)
{
if(Existing!=StreamingBatches.end())
{
TotalVertices-=Existing->second.VertexCount;TotalIndices-=Existing->second.IndexCount;
if(Existing->second.VertexBuffer)Existing->second.VertexBuffer->Release();
if(Existing->second.IndexBuffer)Existing->second.IndexBuffer->Release();
StreamingBatches.erase(Existing);
}
return true;
}
if(VertexCount>std::numeric_limits<uint32>::max()||IndexCount>std::numeric_limits<UINT>::max())
{std::fprintf(stderr,"LOD %d batch exceeded D3D11 limits.\n",Key.Level);return false;}
std::vector<FGpuVertex> Vertices;Vertices.reserve(VertexCount);
std::vector<uint32> Indices;Indices.reserve(IndexCount);
for(const FVoxelTileKey& Tile:Members)
{
const auto It=ResidentTiles.find(Tile);if(It==ResidentTiles.end())continue;
const uint32 Base=static_cast<uint32>(Vertices.size());
for(const FStandaloneCpuVertex& V:It->second.Vertices)
Vertices.push_back({{V.Position[0],V.Position[1],V.Position[2]},{V.Normal[0],V.Normal[1],V.Normal[2]}});
for(const uint32 Index:It->second.Indices)Indices.push_back(Base+Index);
}
FStreamingGpuBatch NewBatch;
const bool bVertex=MakeBuffer(&NewBatch.VertexBuffer,Vertices.data(),Vertices.size()*sizeof(FGpuVertex),D3D11_BIND_VERTEX_BUFFER);
const bool bIndex=bVertex&&MakeBuffer(&NewBatch.IndexBuffer,Indices.data(),Indices.size()*sizeof(uint32),D3D11_BIND_INDEX_BUFFER);
if(!bIndex)
{
if(NewBatch.VertexBuffer)NewBatch.VertexBuffer->Release();
std::fprintf(stderr,"D3D11 upload failed for LOD %d batch (%d,%d,%d).\n",Key.Level,Key.X,Key.Y,Key.Z);
return false;
}
NewBatch.IndexCount=static_cast<UINT>(Indices.size());NewBatch.VertexCount=Vertices.size();
if(Existing!=StreamingBatches.end())
{
TotalVertices-=Existing->second.VertexCount;TotalIndices-=Existing->second.IndexCount;
if(Existing->second.VertexBuffer)Existing->second.VertexBuffer->Release();
if(Existing->second.IndexBuffer)Existing->second.IndexBuffer->Release();
Existing->second=NewBatch;
}
else StreamingBatches.emplace(Key,NewBatch);
TotalVertices+=NewBatch.VertexCount;TotalIndices+=NewBatch.IndexCount;
return true;
}
void UploadDirtyBatches()
{
if(DirtyBatchSet.empty())return;
std::vector<FStandaloneBatchKey> Dirty(DirtyBatchSet.begin(),DirtyBatchSet.end());
const FIntVector Center=Streamer?Streamer->CenterChunk():BuildTargetChunk;
auto Distance=[&](const FStandaloneBatchKey& K)
{
const double Scale=static_cast<double>(1<<K.Level);
const double DX=(K.X*8.0+4.0)*Scale-Center.X;
const double DY=(K.Y*8.0+4.0)*Scale-Center.Y;
const double DZ=(K.Z+0.5)*Scale-Center.Z;
return DX*DX+DY*DY+DZ*DZ;
};
std::sort(Dirty.begin(),Dirty.end(),[&](const auto& A,const auto& B)
{const double DA=Distance(A),DB=Distance(B);return DA==DB?A.Level<B.Level:DA<DB;});
const auto Start=std::chrono::steady_clock::now();int32 Uploaded=0;
for(const FStandaloneBatchKey& Key:Dirty)
{
if(Uploaded>=MaxBatchUploadsPerFrame)break;
if(Uploaded>0&&BatchUploadBudgetMilliseconds>0.0
&&std::chrono::duration<double,std::milli>(std::chrono::steady_clock::now()-Start).count()>=BatchUploadBudgetMilliseconds)break;
if(!DirtyBatchSet.contains(Key))continue;
if(RebuildBatch(Key)){DirtyBatchSet.erase(Key);++Uploaded;}
}
}
bool IsFullDetailUploaded()const
{
if(!Streamer||World->FullResClipLevels<=0)return false;
int32 ResidentFullRes=0;
for(const auto& Pair:ResidentTiles)if(Pair.first.Level<World->FullResClipLevels)++ResidentFullRes;
if(ResidentFullRes!=Streamer->FullResDesiredCount())return false;
for(const FStandaloneBatchKey& Key:DirtyBatchSet)if(Key.Level<World->FullResClipLevels)return false;
return true;
}
void ReportTileTimings()
{
for(int32 L=0;L<=World->MaxClipLevel;++L)
{
if(GeneratedByLevel[L]<static_cast<uint64>(DesiredByLevel[L])||DesiredByLevel[L]==0)continue;
std::vector<double>& Times=TileSecondsByLevel[L];if(Times.empty())continue;
std::sort(Times.begin(),Times.end());
const double Average=WorkerSecondsByLevel[L]/static_cast<double>(GeneratedByLevel[L]);
const double P50=Times[(Times.size()-1)*50/100];
const double P95=Times[(Times.size()-1)*95/100];
std::printf("[TileTiming] LOD=%d generated=%llu target=%d samples=%.2fM worker_sum=%.3fs avg=%.3fms p50=%.3fms p95=%.3fms\n",
L,static_cast<unsigned long long>(GeneratedByLevel[L]),DesiredByLevel[L],
static_cast<double>(SamplesByLevel[L])/1.0e6,WorkerSecondsByLevel[L],Average*1000.0,P50*1000.0,P95*1000.0);
if(bProfileAttribution)
{
std::printf("[TileProfile] LOD=%d",L);
for(size_t I=0;I<VoxelDensityProfile::BucketCount;++I)
{
const double Ms=static_cast<double>(ProfileCyclesByLevel[L][I])
*FPlatformTime::GetSecondsPerCycle64()*1000.0;
if(Ms>0.001)std::printf(" %s=%.3fms",
VoxelDensityProfile::BucketName(static_cast<VoxelDensityProfile::EBucket>(I)),Ms);
}
std::printf("\n");
}
Times.clear();
}
}
void ReportCrossingWork()
{
for(auto& Pair:CrossingWork)
{
FCrossingWork& Work=Pair.second;
if(Work.bReported||Work.PendingJobs!=0)continue;
Work.bReported=true;
const double AverageMs=Work.FinishedJobs>0
?Work.WorkerSeconds*1000.0/static_cast<double>(Work.FinishedJobs):0.0;
const double WallSeconds=Work.StartTime.time_since_epoch().count()!=0
?std::chrono::duration<double>(std::chrono::steady_clock::now()-Work.StartTime).count():0.0;
std::printf("[ChunkCrossingWork] id=%llu submitted=%llu finished=%llu generated=%llu cancelled_queued=%llu stale=%llu samples=%.2fM worker_sum=%.3fs wall=%.3fs avg=%.3fms\n",
static_cast<unsigned long long>(Pair.first),
static_cast<unsigned long long>(Work.SubmittedJobs),
static_cast<unsigned long long>(Work.FinishedJobs),
static_cast<unsigned long long>(Work.Tiles),
static_cast<unsigned long long>(Work.CancelledQueuedJobs),
static_cast<unsigned long long>(Work.StaleResults),
static_cast<double>(Work.Samples)/1.0e6,Work.WorkerSeconds,WallSeconds,AverageMs);
}
}
void PumpGeneration()
{
if(bRebuildPending){StartGeneration();return;}
if(!Streamer)return;
std::vector<FStandaloneCpuTile> Ready=Streamer->TakeReady();
for(FStandaloneCpuTile& Tile:Ready)
{
const int32 Level=Tile.Key.Level;
FCrossingWork& Crossing=CrossingWork[Tile.RequestedCrossing];
if(Crossing.PendingJobs>0)--Crossing.PendingJobs;
++Crossing.FinishedJobs;Crossing.Samples+=Tile.DensitySamples;Crossing.WorkerSeconds+=Tile.WorkerSeconds;
if(!Streamer->IsDesired(Tile.Key)||ResidentTiles.contains(Tile.Key))
{++Crossing.StaleResults;continue;}
++Crossing.Tiles;
++GeneratedByLevel[Level];SamplesByLevel[Level]+=Tile.DensitySamples;WorkerSecondsByLevel[Level]+=Tile.WorkerSeconds;
if(bProfileAttribution)for(size_t I=0;I<Tile.ProfileCycles.size();++I)
ProfileCyclesByLevel[Level][I]+=Tile.ProfileCycles[I];
TileSecondsByLevel[Level].push_back(Tile.WorkerSeconds);
FResidentTileMesh Resident;Resident.Vertices=std::move(Tile.Vertices);Resident.Indices=std::move(Tile.Indices);
Resident.Samples=Tile.DensitySamples;Resident.WorkerSeconds=Tile.WorkerSeconds;
ResidentTiles.emplace(Tile.Key,std::move(Resident));
const FStandaloneBatchKey Batch=BatchForTile(Tile.Key);
BatchMembers[Batch].push_back(Tile.Key);MarkBatchDirty(Batch);
}
ReportCrossingWork();
UploadDirtyBatches();
if(bShapePipelineActive)TryCompleteShapePipeline();
if(bTestOneChunkCrossing&&bCrossingMoveFinished&&!bCrossingClosePosted
&&DirtyBatchSet.empty())
{
const auto Crossing=CrossingWork.find(1);
if(Crossing!=CrossingWork.end()&&Crossing->second.bReported)
{
bCrossingClosePosted=true;
std::printf("[CrossingTestComplete] crossing work and its bounded uploads drained; closing this test viewer.\n");
std::fflush(stdout);
PostMessageA(Window,WM_CLOSE,0,0);
}
}
if(!bShapePipelineActive&&!bFullDetailReported&&IsFullDetailUploaded())
{
bFullDetailReported=true;
const double Elapsed=std::chrono::duration<double>(std::chrono::steady_clock::now()-ColdProcessStart).count();
std::printf("[FullDetailReady] %.3f s after cold process start; resident=%zu/%d tiles through LOD%d.\n",
Elapsed,ResidentTiles.size(),Streamer->FullResDesiredCount(),World->FullResClipLevels-1);
ReportTileTimings();std::fflush(stdout);
StartFarShapeGeneration();
}
if(!bInitialClipmapReported&&ResidentTiles.size()==DesiredTiles.size()&&DirtyBatchSet.empty())
{
bInitialClipmapReported=true;
const double Elapsed=std::chrono::duration<double>(std::chrono::steady_clock::now()-ColdProcessStart).count();
std::printf("[InitialClipmapReady] %.3f s after cold process start; %zu tiles, %llu vertices, %llu indices, %zu GPU batches.\n",
Elapsed,ResidentTiles.size(),static_cast<unsigned long long>(TotalVertices),
static_cast<unsigned long long>(TotalIndices),StreamingBatches.size());
ReportTileTimings();std::fflush(stdout);
if(bQuitAfterClipmap&&CapturePath.empty())PostMessageA(Window,WM_CLOSE,0,0);
}
bGenerationRunning=ResidentTiles.size()!=DesiredTiles.size()||!DirtyBatchSet.empty();
}
void CaptureRenderedFrame()
{
const bool bReadyToCapture=bCaptureAfterClipmap?bInitialClipmapReported:IsFullDetailUploaded();
if(CapturePath.empty()||bAutoCaptureWritten||!bReadyToCapture)return;
bAutoCaptureWritten=SaveBackBufferPng(CapturePath);
if(bAutoCaptureWritten)
{
const double Elapsed=std::chrono::duration<double>(std::chrono::steady_clock::now()-GenerationStart).count();
std::printf("Back-buffer capture saved: %s (%.3f s after generation start; full detail uploaded).\n",CapturePath.c_str(),Elapsed);
std::fflush(stdout);PostMessageA(Window,WM_CLOSE,0,0);
}
else std::fprintf(stderr,"Back-buffer capture failed: %s\n",CapturePath.c_str());
}
void Capture(bool bOn)
{
bCaptured=bOn;
if(bOn){SetCapture(Window);ShowCursor(FALSE);GetCursorPos(&LastMouse);POINT C{Width/2,Height/2};ClientToScreen(Window,&C);SetCursorPos(C.x,C.y);}
else{ReleaseCapture();ShowCursor(TRUE);}
}
void UpdateTitle()
{
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<<" | "<<(bShapeWorld?"shape-interval owner world":(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());
}
void Draw()
{
PumpGeneration();
const auto Now=std::chrono::steady_clock::now();
if(bTestOneChunkCrossing&&bFullDetailReported&&!bCrossingMoveStarted&&!bCrossingMoveFinished)
{
bCrossingMoveStarted=true;
CrossingMoveStopTime=Now+std::chrono::milliseconds(350);
Keys[VK_SHIFT]=true;Keys['W']=true;
std::printf("[CrossingTestMove] moving forward for 350 ms after full detail.\n");
std::fflush(stdout);
}
if(bCrossingMoveStarted&&Now>=CrossingMoveStopTime)
{
Keys[VK_SHIFT]=false;Keys['W']=false;
bCrossingMoveStarted=false;bCrossingMoveFinished=true;
std::printf("[CrossingTestMove] movement stopped; waiting for crossing work and uploads.\n");
std::fflush(stdout);
}
const float Dt=std::clamp(std::chrono::duration<float>(Now-PreviousFrame).count(),0.0f,0.1f);
PreviousFrame=Now;FrameMs=Dt*1000.0;
const V3 F=Forward(),R=Normalize(Cross(F,{0,0,1})),U=Normalize(Cross(R,F));
const float Step=MoveSpeed*Dt*(Keys[VK_SHIFT]?4.0f:1.0f);
if(Keys['W'])Position=Add(Position,Mul(F,Step));if(Keys['S'])Position=Sub(Position,Mul(F,Step));
if(Keys['A'])Position=Sub(Position,Mul(R,Step));if(Keys['D'])Position=Add(Position,Mul(R,Step));
if(Keys[VK_SPACE]||Keys['E'])Position.Z+=Step;if(Keys[VK_CONTROL]||Keys['Q'])Position.Z-=Step;
if(Streamer&&NeedsRecenter(BuildTargetChunk))RecenterStreaming();
const float Clear[4]={0.055f,0.075f,0.105f,1.0f};
Context->OMSetRenderTargets(1,&RenderTarget,DepthView);Context->ClearRenderTargetView(RenderTarget,Clear);
Context->ClearDepthStencilView(DepthView,D3D11_CLEAR_DEPTH,1.0f,0);
D3D11_VIEWPORT Viewport{0,0,static_cast<float>(Width),static_cast<float>(Height),0,1};Context->RSSetViewports(1,&Viewport);
Context->RSSetState(bWireframe?WireRasterizer:SolidRasterizer);Context->IASetInputLayout(InputLayout);
Context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
FConstants C{};const M4 V=View(Position,F,R,U);const M4 P=Perspective(1.04719755f,static_cast<float>(Width)/std::max(1,Height),0.05f,5000.0f);
M4 Combined{};for(int I=0;I<4;++I)for(int J=0;J<4;++J)for(int K=0;K<4;++K)Combined.M[I][J]+=V.M[I][K]*P.M[K][J];
C.ViewProjection=Transpose(Combined);C.Camera[0]=Position.X;C.Camera[1]=Position.Y;C.Camera[2]=Position.Z;
C.Forward[0]=F.X;C.Forward[1]=F.Y;C.Forward[2]=F.Z;C.LightDirection[0]=-0.5f;C.LightDirection[1]=-0.72f;C.LightDirection[2]=-0.48f;
C.FogStart=260.0f;C.FogEnd=2600.0f;C.Ambient=0.36f;
Context->UpdateSubresource(Constants,0,nullptr,&C,0,0);Context->VSSetConstantBuffers(0,1,&Constants);Context->PSSetConstantBuffers(0,1,&Constants);
Context->VSSetShader(VertexShader,nullptr,0);Context->PSSetShader(PixelShader,nullptr,0);
for(const auto& Pair:StreamingBatches)
{
const FStreamingGpuBatch& L=Pair.second;
if(!bShowLevel[Pair.first.Level]||!L.VertexBuffer||!L.IndexBuffer||!L.IndexCount)continue;
const UINT Stride=sizeof(FGpuVertex),Offset=0;Context->IASetVertexBuffers(0,1,&L.VertexBuffer,&Stride,&Offset);
Context->IASetIndexBuffer(L.IndexBuffer,DXGI_FORMAT_R32_UINT,0);Context->DrawIndexed(L.IndexCount,0,0);
}
CaptureRenderedFrame();
SwapChain->Present(1,0);UpdateTitle();
}
};
FApp* GApp=nullptr;
LRESULT CALLBACK WindowProc(HWND H,UINT Message,WPARAM W,LPARAM L)
{
if(!GApp)return DefWindowProc(H,Message,W,L);
switch(Message)
{
case WM_SIZE:if(W!=SIZE_MINIMIZED)GApp->Resize(LOWORD(L),HIWORD(L));return 0;
case WM_KEYDOWN:
{
const bool bWasDown=(L&(1ll<<30))!=0;if(W<256)GApp->Keys[W]=true;
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=='V'){GApp->bShapeWorld=!GApp->bShapeWorld;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];
if(!bWasDown&&W=='0')for(bool& B:GApp->bShowLevel)B=!B;
return 0;
}
case WM_KEYUP:if(W<256)GApp->Keys[W]=false;return 0;
case WM_RBUTTONDOWN:GApp->Capture(true);return 0;
case WM_RBUTTONUP:GApp->Capture(false);return 0;
case WM_MOUSEMOVE:
if(GApp->bCaptured){POINT C{GApp->Width/2,GApp->Height/2};ClientToScreen(H,&C);POINT P{GET_X_LPARAM(L),GET_Y_LPARAM(L)},Screen=P;ClientToScreen(H,&Screen);const int32 DX=Screen.x-C.x,DY=Screen.y-C.y;if(DX||DY){GApp->Yaw+=DX*0.0035f;GApp->Pitch=std::clamp(GApp->Pitch-DY*0.0035f,-1.55f,1.55f);SetCursorPos(C.x,C.y);}}return 0;
case WM_MOUSEWHEEL:GApp->MoveSpeed=std::clamp(GApp->MoveSpeed*(GET_WHEEL_DELTA_WPARAM(W)>0?1.25f:0.8f),0.25f,1000.0f);return 0;
case WM_DESTROY:if(GApp->Streamer)GApp->Streamer->Stop();PostQuitMessage(0);return 0;
}
return DefWindowProc(H,Message,W,L);
}
std::filesystem::path DefaultDataPath()
{
wchar_t Module[MAX_PATH]{};GetModuleFileNameW(nullptr,Module,MAX_PATH);
const std::filesystem::path Exe(Module);
return Exe.parent_path().parent_path()/L"data"/L"owner_world.json";
}
int RunTileBenchmark(FStandaloneOwnerWorld& World,V3 CameraPosition,int32 Level,
int32 Count,bool bProfile,bool bCompareEvaluators,bool bDensityHash)
{
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>(std::max(Count,1)));
for(const FVoxelTileKey& Tile:Desired.Tiles)
if(Tile.Level==Level&&static_cast<int32>(Tiles.size())<Count)Tiles.push_back(Tile);
if(Tiles.empty())
{std::fprintf(stderr,"No LOD %d tiles are in the current desired set.\n",Level);return 4;}
VoxelGenLOD::InitializeConsoleSwitches();
UVoxelMarchingCubesMesher::InitializeConsoleSwitches();
int32 TileCacheWindow=1,TileCacheWindowMinLOD=3,RoomGraphCacheSlots=1;
FParse::Value(FCommandLine::Get(),"voxel.TileCacheWindow=",TileCacheWindow);
FParse::Value(FCommandLine::Get(),"voxel.TileCacheWindowMinLOD=",TileCacheWindowMinLOD);
FParse::Value(FCommandLine::Get(),"voxel.RoomGraphCacheSlots=",RoomGraphCacheSlots);
std::printf("[BenchmarkConfig] tile_cache_window=%d fused_min_lod=%d room_graph_cache_slots=%d command='%s'\n",
TileCacheWindow,TileCacheWindowMinLOD,RoomGraphCacheSlots,FCommandLine::Get());
VoxelDensityProfile::SetMode(bProfile?VoxelDensityProfile::EMode::Attribution
:VoxelDensityProfile::EMode::Disabled,128);
if(bProfile)VoxelDensityProfile::CalibrateTimer();
VoxelDensityProfile::Reset();
const VoxelDensityProfile::FSnapshot Before=VoxelDensityProfile::SnapshotCurrentThread();
UVoxelMarchingCubesMesher Mesher;
Mesher.SetGenerator(World.Generator.get());Mesher.bGenerateSkirts=World.bGenerateSkirts;
Mesher.SkirtCells=static_cast<float>(World.SkirtCells);Mesher.LODOctaveDrop=World.LODOctaveDrop;
const int32 BandMin=World.StrateContentCutMinLevel<9&&Desired.bHasStrate
?Desired.StrateBottomChunkZ*CHUNK_SIZE:MIN_int32;
const int32 BandMax=World.StrateContentCutMinLevel<9&&Desired.bHasStrate
?(Desired.StrateTopChunkZ+1)*CHUNK_SIZE:MAX_int32;
if(bCompareEvaluators)
{
const bool bOriginalFused=VoxelGenLOD::IsUseFusedEvaluatorEnabled();
uint64 Mismatches=0,ComparedSamples=0,MeshCountMismatches=0;
float MaxAbsoluteDifference=0.0f;
size_t FirstMismatch=SIZE_MAX;
FVoxelTileKey FirstMismatchTile{};
VoxelGenLOD::SetUseFusedEvaluatorEnabled(true);
for(const FVoxelTileKey& Tile:Tiles)
{
const int32 Cells=Level<World.FullResClipLevels?CHUNK_SIZE:FMath::Max(1,World.CoarseTileCells);
const int32 Step=FMath::Max(1,Tile.ExtentVoxels()/Cells);
std::vector<float> FusedDensities,OpStackDensities;
int64 FusedSampleCalls=0,OpStackSampleCalls=0;
FVoxelMeshData FusedMesh=Mesher.GenerateMesh(Tile.OriginVoxels(),Step,Cells,nullptr,
BandMin,BandMax,&FusedSampleCalls,nullptr,nullptr,&FusedDensities);
VoxelGenLOD::SetUseFusedEvaluatorEnabled(false);
FVoxelMeshData OpStackMesh=Mesher.GenerateMesh(Tile.OriginVoxels(),Step,Cells,nullptr,
BandMin,BandMax,&OpStackSampleCalls,nullptr,nullptr,&OpStackDensities);
VoxelGenLOD::SetUseFusedEvaluatorEnabled(true);
if(FusedMesh.Vertices.Num()!=OpStackMesh.Vertices.Num()
|| FusedMesh.Triangles.Num()!=OpStackMesh.Triangles.Num())++MeshCountMismatches;
const size_t SharedSamples=std::min(FusedDensities.size(),OpStackDensities.size());
ComparedSamples+=SharedSamples;
for(size_t I=0;I<SharedSamples;++I)
{
if(std::bit_cast<uint32>(FusedDensities[I])!=std::bit_cast<uint32>(OpStackDensities[I]))
{
if(FirstMismatch==SIZE_MAX){FirstMismatch=I;FirstMismatchTile=Tile;}
++Mismatches;
MaxAbsoluteDifference=std::max(MaxAbsoluteDifference,
std::abs(FusedDensities[I]-OpStackDensities[I]));
}
}
Mismatches+=static_cast<uint64>(std::max(FusedDensities.size(),OpStackDensities.size())-SharedSamples);
}
std::printf("[EvaluatorCompare] LOD=%d tiles=%zu compared_samples=%llu density_mismatches=%llu first_tile=(%d,%d,%d) first_index=%llu max_abs=%.9g mesh_count_mismatch_tiles=%llu original_fused=%d\n",
Level,Tiles.size(),static_cast<unsigned long long>(ComparedSamples),
static_cast<unsigned long long>(Mismatches),
FirstMismatchTile.Coord.X,FirstMismatchTile.Coord.Y,FirstMismatchTile.Coord.Z,
static_cast<unsigned long long>(FirstMismatch),MaxAbsoluteDifference,
static_cast<unsigned long long>(MeshCountMismatches),bOriginalFused?1:0);
VoxelGenLOD::SetUseFusedEvaluatorEnabled(bOriginalFused);
std::fflush(stdout);
return Mismatches==0?0:5;
}
std::vector<double> TileSeconds;TileSeconds.reserve(Tiles.size());
uint64 Samples=0,Vertices=0,Indices=0;
uint64 DensityHash=14695981039346656037ull;
const auto TotalStart=std::chrono::steady_clock::now();
for(const FVoxelTileKey& Tile:Tiles)
{
const auto TileStart=std::chrono::steady_clock::now();
const int32 Cells=Level<World.FullResClipLevels?CHUNK_SIZE:FMath::Max(1,World.CoarseTileCells);
const int32 Step=FMath::Max(1,Tile.ExtentVoxels()/Cells);
int64 TileSamples=0;
std::vector<float> DebugDensitySamples;
FVoxelMeshData Mesh=Mesher.GenerateMesh(Tile.OriginVoxels(),Step,Cells,nullptr,
BandMin,BandMax,&TileSamples,nullptr,nullptr,
bDensityHash?&DebugDensitySamples:nullptr);
Samples+=static_cast<uint64>(FMath::Max<int64>(0,TileSamples));
Vertices+=static_cast<uint64>(Mesh.Vertices.Num());
Indices+=static_cast<uint64>(Mesh.Triangles.Num());
if(bDensityHash)
{
for(const float Density:DebugDensitySamples)
{
DensityHash^=std::bit_cast<uint32>(Density);
DensityHash*=1099511628211ull;
}
}
TileSeconds.push_back(std::chrono::duration<double>(
std::chrono::steady_clock::now()-TileStart).count());
}
const double TotalSeconds=std::chrono::duration<double>(
std::chrono::steady_clock::now()-TotalStart).count();
const VoxelDensityProfile::FSnapshot After=VoxelDensityProfile::SnapshotCurrentThread();
std::sort(TileSeconds.begin(),TileSeconds.end());
const double Average=TotalSeconds/static_cast<double>(Tiles.size());
std::printf("[TileBenchmark] LOD=%d generated=%zu samples=%.2fM worker_sum=%.3fs avg=%.3fms p50=%.3fms p95=%.3fms vertices=%llu indices=%llu profiling=%s\n",
Level,Tiles.size(),static_cast<double>(Samples)/1.0e6,TotalSeconds,Average*1000.0,
TileSeconds[(TileSeconds.size()-1)*50/100]*1000.0,
TileSeconds[(TileSeconds.size()-1)*95/100]*1000.0,
static_cast<unsigned long long>(Vertices),static_cast<unsigned long long>(Indices),
bProfile?"Attribution":"off");
if(bDensityHash)
{
std::printf("[TileDensityHash] LOD=%d tiles=%zu hash=0x%016llx\n",Level,Tiles.size(),
static_cast<unsigned long long>(DensityHash));
}
if(bProfile)
{
std::printf("[TileProfile] LOD=%d",Level);
for(int32 I=0;I<VoxelDensityProfile::BucketCount;++I)
{
const uint64 CycleDelta=After.Cycles[I]>=Before.Cycles[I]
?After.Cycles[I]-Before.Cycles[I]:0;
const double Ms=static_cast<double>(CycleDelta)
*FPlatformTime::GetSecondsPerCycle64()*1000.0;
if(Ms>0.001)std::printf(" %s=%.3fms",VoxelDensityProfile::BucketName(
static_cast<VoxelDensityProfile::EBucket>(I)),Ms);
}
std::printf("\n");
const auto CounterDelta=[&](VoxelDensityProfile::ECounter Counter)
{
const size_t I=static_cast<size_t>(Counter);
return After.Counters[I]>=Before.Counters[I]
?After.Counters[I]-Before.Counters[I]:0;
};
std::printf("[TileCounters] sdf_builds=%llu graph_tile_builds=%llu graph_core_builds=%llu op_block_builds=%llu\n",
static_cast<unsigned long long>(CounterDelta(VoxelDensityProfile::ECounter::SdfCacheBuild)),
static_cast<unsigned long long>(CounterDelta(VoxelDensityProfile::ECounter::RoomGraphBuildGeneratorTile)),
static_cast<unsigned long long>(CounterDelta(VoxelDensityProfile::ECounter::RoomGraphBuildGeneratorTunnelCore)),
static_cast<unsigned long long>(CounterDelta(VoxelDensityProfile::ECounter::OpBlockBuilds)));
std::printf("[SdfRebuildCauses] strate=%llu seed=%llu fingerprint=%llu layout=%llu manager=%llu terrain_ops=%llu fused_policy=%llu tile_policy=%llu tile_identity=%llu bounds_x=%llu bounds_y=%llu tile_window_accepted=%llu tile_window_rejected=%llu\n",
static_cast<unsigned long long>(CounterDelta(VoxelDensityProfile::ECounter::SdfRebuildStrate)),
static_cast<unsigned long long>(CounterDelta(VoxelDensityProfile::ECounter::SdfRebuildSeed)),
static_cast<unsigned long long>(CounterDelta(VoxelDensityProfile::ECounter::SdfRebuildFingerprint)),
static_cast<unsigned long long>(CounterDelta(VoxelDensityProfile::ECounter::SdfRebuildLayout)),
static_cast<unsigned long long>(CounterDelta(VoxelDensityProfile::ECounter::SdfRebuildManagerLifetime)),
static_cast<unsigned long long>(CounterDelta(VoxelDensityProfile::ECounter::SdfRebuildTerrainOps)),
static_cast<unsigned long long>(CounterDelta(VoxelDensityProfile::ECounter::SdfRebuildFusedPolicy)),
static_cast<unsigned long long>(CounterDelta(VoxelDensityProfile::ECounter::SdfRebuildTilePolicy)),
static_cast<unsigned long long>(CounterDelta(VoxelDensityProfile::ECounter::SdfRebuildTileIdentity)),
static_cast<unsigned long long>(CounterDelta(VoxelDensityProfile::ECounter::SdfRebuildBoundsX)),
static_cast<unsigned long long>(CounterDelta(VoxelDensityProfile::ECounter::SdfRebuildBoundsY)),
static_cast<unsigned long long>(CounterDelta(VoxelDensityProfile::ECounter::SdfTileWindowAccepted)),
static_cast<unsigned long long>(CounterDelta(VoxelDensityProfile::ECounter::SdfTileWindowRejected)));
std::printf("[TilePolicyKeyChanges] origin=%llu step=%llu cells=%llu fingerprint=%llu layout=%llu manager=%llu terrain_ops=%llu fused_path=%llu context=%llu\n",
static_cast<unsigned long long>(CounterDelta(VoxelDensityProfile::ECounter::TilePolicyKeyOrigin)),
static_cast<unsigned long long>(CounterDelta(VoxelDensityProfile::ECounter::TilePolicyKeyStep)),
static_cast<unsigned long long>(CounterDelta(VoxelDensityProfile::ECounter::TilePolicyKeyCells)),
static_cast<unsigned long long>(CounterDelta(VoxelDensityProfile::ECounter::TilePolicyKeyFingerprint)),
static_cast<unsigned long long>(CounterDelta(VoxelDensityProfile::ECounter::TilePolicyKeyLayout)),
static_cast<unsigned long long>(CounterDelta(VoxelDensityProfile::ECounter::TilePolicyKeyManager)),
static_cast<unsigned long long>(CounterDelta(VoxelDensityProfile::ECounter::TilePolicyKeyTerrainOps)),
static_cast<unsigned long long>(CounterDelta(VoxelDensityProfile::ECounter::TilePolicyKeyFusedPath)),
static_cast<unsigned long long>(CounterDelta(VoxelDensityProfile::ECounter::TilePolicyKeyContext)));
}
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,
double BucketWidth=1.0,int32 SearchCells=3)
{
FDirectedSurfaceDistance Result;
if(From.empty()||To.empty()){Result.Unmatched=From.size();return Result;}
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=-SearchCells;Z<=SearchCells;++Z)
for(int32 Y=-SearchCells;Y<=SearchCells;++Y)
for(int32 X=-SearchCells;X<=SearchCells;++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 RunOwnerShapeComparison(FStandaloneOwnerWorld& World,const FStandaloneShapeWorld& Shape,
V3 CameraPosition,float CameraYaw,float CameraPitch,
int32 Workers,int32 LevelFilter,int32 TileLimit)
{
if(FStandaloneShapeWorldAccess::GetActive()!=&Shape)
{std::fprintf(stderr,"Shape comparison requires the compiled interval world to be active.\n");return 11;}
if(LevelFilter>=World.FullResClipLevels&&!Shape.HasFarWorld())
const_cast<FStandaloneShapeWorld&>(Shape).BuildFarWorld();
const FVector CameraVoxels(CameraPosition.X*4.0,CameraPosition.Y*4.0,CameraPosition.Z*4.0);
Shape.AuditViewSurface(CameraPosition.X,CameraPosition.Y,CameraPosition.Z,
CameraYaw,CameraPitch);
{
const int32 Offsets[][3]={{0,0,0},{4,0,0},{-4,0,0},{0,4,0},{0,-4,0},{0,0,4},{0,0,-4}};
for(const auto& Offset:Offsets)
{
const float X=static_cast<float>(CameraVoxels.X+Offset[0]);
const float Y=static_cast<float>(CameraVoxels.Y+Offset[1]);
const float Z=static_cast<float>(CameraVoxels.Z+Offset[2]);
float OwnerDensity=0.0f;
{FStandaloneShapeWorldAccess::FScopedBypass Bypass;
OwnerDensity=World.Generator->GetDensityAt(X,Y,Z);}
const float ShapeDensity=Shape.DensityAt(X,Y,Z);
std::printf("[ShapeProbe] voxels=(%.1f,%.1f,%.1f) owner=%.5f shape=%.5f owner_air=%d shape_air=%d protected_air=%d solid_overlay=%d\n",
X,Y,Z,OwnerDensity,ShapeDensity,OwnerDensity>=0.0f?1:0,ShapeDensity>=0.0f?1:0,
Shape.DebugCriticalAirAt(X,Y,Z)?1:0,Shape.DebugSolidOverlayAt(X,Y,Z)?1:0);
}
}
const FStandaloneDesiredTileSet Desired=StandaloneWorldMesher::BuildDesiredSet(World,CameraVoxels);
std::vector<FVoxelTileKey> Tiles;
for(const FVoxelTileKey& Tile:Desired.Tiles)
if(LevelFilter<0?Tile.Level<World.FullResClipLevels:Tile.Level==LevelFilter)
Tiles.push_back(Tile);
if(TileLimit>0&&Tiles.size()>static_cast<size_t>(TileLimit))
{
std::sort(Tiles.begin(),Tiles.end(),[&](const FVoxelTileKey& A,const FVoxelTileKey& B)
{
const FIntVector AO=A.OriginVoxels(),BO=B.OriginVoxels();
const double ADX=AO.X+A.ExtentVoxels()*0.5-CameraVoxels.X;
const double ADY=AO.Y+A.ExtentVoxels()*0.5-CameraVoxels.Y;
const double ADZ=AO.Z+A.ExtentVoxels()*0.5-CameraVoxels.Z;
const double BDX=BO.X+B.ExtentVoxels()*0.5-CameraVoxels.X;
const double BDY=BO.Y+B.ExtentVoxels()*0.5-CameraVoxels.Y;
const double BDZ=BO.Z+B.ExtentVoxels()*0.5-CameraVoxels.Z;
return ADX*ADX+ADY*ADY+ADZ*ADZ<BDX*BDX+BDY*BDY+BDZ*BDZ;
});
Tiles.resize(static_cast<size_t>(TileLimit));
}
if(Tiles.empty()){std::fprintf(stderr,"No tiles are available for the requested shape comparison level.\n");return 12;}
struct FTileComparison
{
uint64 Samples=0,Changed=0,MeshMismatch=0;
uint64 OldHash=14695981039346656037ull,NewHash=14695981039346656037ull;
std::vector<FComparePoint> OldSurface,NewSurface;
};
std::vector<FTileComparison> Results(Tiles.size());
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;}
std::atomic<size_t> Next{0};
std::vector<std::thread> Threads;
const int32 ThreadCount=std::clamp(Workers,1,16);
Threads.reserve(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<float>(World.SkirtCells);
Mesher.LODOctaveDrop=World.LODOctaveDrop;
for(;;)
{
const size_t Index=Next.fetch_add(1,std::memory_order_relaxed);
if(Index>=Tiles.size())break;
const FVoxelTileKey& Tile=Tiles[Index];
const int32 Cells=Tile.Level<World.FullResClipLevels?CHUNK_SIZE:World.CoarseTileCells;
const int32 Step=std::max(1,Tile.ExtentVoxels()/Cells);
std::vector<float> NewDensities,OldDensities;
FVoxelMeshData NewMesh=Mesher.GenerateMesh(Tile.OriginVoxels(),Step,Cells,nullptr,
MinChunkZ,MaxChunkZ,nullptr,nullptr,nullptr,&NewDensities);
FVoxelMeshData OldMesh;
{
FStandaloneShapeWorldAccess::FScopedBypass Bypass;
OldMesh=Mesher.GenerateMesh(Tile.OriginVoxels(),Step,Cells,nullptr,
MinChunkZ,MaxChunkZ,nullptr,nullptr,nullptr,&OldDensities);
}
FTileComparison& R=Results[Index];
const size_t Shared=std::min(NewDensities.size(),OldDensities.size());
R.Samples=Shared;
R.MeshMismatch=std::max(NewDensities.size(),OldDensities.size())-Shared;
for(size_t I=0;I<Shared;++I)
{
if((NewDensities[I]<0.0f)!=(OldDensities[I]<0.0f))++R.Changed;
R.NewHash^=std::bit_cast<uint32>(NewDensities[I]);R.NewHash*=1099511628211ull;
R.OldHash^=std::bit_cast<uint32>(OldDensities[I]);R.OldHash*=1099511628211ull;
}
constexpr size_t VertexStride=4;
R.NewSurface.reserve(static_cast<size_t>(NewMesh.Vertices.Num())/VertexStride+1);
for(int32 I=0;I<NewMesh.Vertices.Num();I+=static_cast<int32>(VertexStride))
{const FVector& P=NewMesh.Vertices[I];R.NewSurface.push_back({P.X*0.01,P.Y*0.01,P.Z*0.01});}
R.OldSurface.reserve(static_cast<size_t>(OldMesh.Vertices.Num())/VertexStride+1);
for(int32 I=0;I<OldMesh.Vertices.Num();I+=static_cast<int32>(VertexStride))
{const FVector& P=OldMesh.Vertices[I];R.OldSurface.push_back({P.X*0.01,P.Y*0.01,P.Z*0.01});}
}
});
}
for(std::thread& Thread:Threads)Thread.join();
uint64 Samples=0,Changed=0,MeshMismatch=0;
uint64 OldHash=14695981039346656037ull,NewHash=14695981039346656037ull;
size_t OldVertexCount=0,NewVertexCount=0;
std::vector<FComparePoint> OldSurface,NewSurface;
for(const FTileComparison& R:Results)
{
Samples+=R.Samples;Changed+=R.Changed;MeshMismatch+=R.MeshMismatch;
OldHash^=R.OldHash;OldHash*=1099511628211ull;
NewHash^=R.NewHash;NewHash*=1099511628211ull;
OldVertexCount+=R.OldSurface.size();NewVertexCount+=R.NewSurface.size();
}
OldSurface.reserve(OldVertexCount);NewSurface.reserve(NewVertexCount);
for(FTileComparison& R:Results)
{
OldSurface.insert(OldSurface.end(),R.OldSurface.begin(),R.OldSurface.end());
NewSurface.insert(NewSurface.end(),R.NewSurface.begin(),R.NewSurface.end());
}
const FDirectedSurfaceDistance OldToNew=DirectedSurfaceDistance(OldSurface,NewSurface,1.0,3);
const FDirectedSurfaceDistance NewToOld=DirectedSurfaceDistance(NewSurface,OldSurface,1.0,3);
const uint64 DistanceSamples=OldToNew.Count+NewToOld.Count;
const double MeanDistance=DistanceSamples
?(OldToNew.Sum+NewToOld.Sum)/static_cast<double>(DistanceSamples):0.0;
std::printf("[OwnerShapeCompare] tiles=%zu fullres_levels=%d level_filter=%d lattice_samples=%llu changed_signs=%llu changed_share=%.6f%% old_hash=0x%016llx shape_hash=0x%016llx mesh_sample_mismatch=%llu old_surface_samples=%zu shape_surface_samples=%zu symmetric_vertex_mean_m=%.4f symmetric_vertex_max_m=%.4f unmatched_surface_samples=%llu vertex_stride=4 workers=%d\n",
Tiles.size(),World.FullResClipLevels,LevelFilter,static_cast<unsigned long long>(Samples),
static_cast<unsigned long long>(Changed),Samples?100.0*Changed/Samples:0.0,
static_cast<unsigned long long>(OldHash),static_cast<unsigned long long>(NewHash),
static_cast<unsigned long long>(MeshMismatch),OldSurface.size(),NewSurface.size(),
MeanDistance,std::max(OldToNew.Maximum,NewToOld.Maximum),
static_cast<unsigned long long>(OldToNew.Unmatched+NewToOld.Unmatched),ThreadCount);
std::fflush(stdout);
return Samples>0&&MeshMismatch==0?0:13;
}
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 RunPipelinedStreamingBenchmark(FStandaloneOwnerWorld& World,const FVector& CameraPositionVoxels,
int32 WorkerCount,int32 MaxLevel,FStandaloneShapeWorld::EDetailVariant DetailVariant,
bool bHashDensity,bool bSkipUvAndColors,bool bSkipMaterialGroups,bool bSkipUV,bool bSkipColors,
std::chrono::steady_clock::time_point ColdStart)
{
using Clock=std::chrono::steady_clock;
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());
FIntVector FineMin(INT32_MAX,INT32_MAX,INT32_MAX),FineMax(INT32_MIN,INT32_MIN,INT32_MIN);
FIntVector ClipMin(INT32_MAX,INT32_MAX,INT32_MAX),ClipMax(INT32_MIN,INT32_MIN,INT32_MIN);
for(const FVoxelTileKey& Tile:Desired.Tiles)
{
const FIntVector Origin=Tile.OriginVoxels();const int32 Extent=Tile.ExtentVoxels();
ClipMin.X=std::min(ClipMin.X,Origin.X);ClipMin.Y=std::min(ClipMin.Y,Origin.Y);ClipMin.Z=std::min(ClipMin.Z,Origin.Z);
ClipMax.X=std::max(ClipMax.X,Origin.X+Extent);ClipMax.Y=std::max(ClipMax.Y,Origin.Y+Extent);ClipMax.Z=std::max(ClipMax.Z,Origin.Z+Extent);
if(Tile.Level<World.FullResClipLevels)
{
FineMin.X=std::min(FineMin.X,Origin.X);FineMin.Y=std::min(FineMin.Y,Origin.Y);FineMin.Z=std::min(FineMin.Z,Origin.Z);
FineMax.X=std::max(FineMax.X,Origin.X+Extent);FineMax.Y=std::max(FineMax.Y,Origin.Y+Extent);FineMax.Z=std::max(FineMax.Z,Origin.Z+Extent);
}
}
if(FineMin.X==INT32_MAX||ClipMin.X==INT32_MAX)
{std::fprintf(stderr,"[StreamPipeline] no desired detail or clipmap tiles.\n");return 7;}
const FIntVector CenterChunk=Desired.CenterChunk;
std::unordered_set<FVoxelTileKey,FStandaloneTileKeyHash> Neighborhood;
Neighborhood.reserve(32);
for(const FVoxelTileKey& Tile:Desired.Tiles)
if(Tile.Level==0&&std::abs(Tile.Coord.X-CenterChunk.X)<=1
&&std::abs(Tile.Coord.Y-CenterChunk.Y)<=1&&std::abs(Tile.Coord.Z-CenterChunk.Z)<=1)
Neighborhood.insert(Tile);
if(Neighborhood.size()!=27)
{std::fprintf(stderr,"[StreamPipeline] expected 27 player-neighborhood tiles, found %zu.\n",Neighborhood.size());return 7;}
FStandaloneShapeWorld Shape;
std::string BuildError;
const bool bIncludeFarWorld=MaxLevel<0||MaxLevel>=World.FullResClipLevels;
const auto ShapeBuildStart=Clock::now();
if(!Shape.Build(World,FineMin,FineMax,ClipMin,ClipMax,WorkerCount,
DetailVariant,bIncludeFarWorld,BuildError))
{std::fprintf(stderr,"[StreamPipeline] full shape build failed: %s\n",BuildError.c_str());return 2;}
const auto ShapeReady=Clock::now();
const double ShapeSeconds=std::chrono::duration<double>(ShapeReady-ShapeBuildStart).count();
FStandaloneShapeWorldAccess::SetActive(&Shape);
FStandaloneStreamingGenerator Streamer(World,WorkerCount,bHashDensity,nullptr,MaxLevel,
bSkipUvAndColors,bSkipMaterialGroups,bSkipUV,bSkipColors);
const auto StreamStart=Clock::now();
const FStandaloneStreamingUpdate Update=Streamer.Start(CameraPositionVoxels);
if(Update.DesiredTiles!=static_cast<int32>(Desired.Tiles.size()))
{
Streamer.Stop();FStandaloneShapeWorldAccess::SetActive(nullptr);
std::fprintf(stderr,"[StreamPipeline] expected %zu tiles but queued %d.\n",
Desired.Tiles.size(),Update.DesiredTiles);return 7;
}
std::vector<FHashedTile> DensityRows,MeshRows,PayloadRows,TopologyRows;
int32 Finished=0,NeighborhoodFinished=0,FullDetailFinished=0;
int32 FullDetailExpected=0;
for(const FVoxelTileKey& Tile:Desired.Tiles)
if(Tile.Level<World.FullResClipLevels)++FullDetailExpected;
Clock::time_point NeighborhoodReady{},FullDetailReady{},WholeViewReady{};
bool bNeighborhoodReported=false,bFullDetailReported=false;
while(Finished<Update.DesiredTiles)
{
std::vector<FStandaloneCpuTile> Ready=Streamer.TakeReady();
if(Ready.empty()){std::this_thread::sleep_for(std::chrono::milliseconds(1));continue;}
for(const FStandaloneCpuTile& Tile:Ready)
{
++Finished;
WholeViewReady=std::max(WholeViewReady,Tile.CompletedAt);
if(Neighborhood.contains(Tile.Key))
{
++NeighborhoodFinished;
NeighborhoodReady=std::max(NeighborhoodReady,Tile.CompletedAt);
}
if(Tile.Key.Level<World.FullResClipLevels)
{
++FullDetailFinished;
FullDetailReady=std::max(FullDetailReady,Tile.CompletedAt);
}
if(bHashDensity)
{
if(Tile.bHasDensityHash)DensityRows.push_back({Tile.Key,Tile.DensityHash});
if(Tile.bHasMeshHash)MeshRows.push_back({Tile.Key,Tile.MeshHash});
if(Tile.bHasPayloadHash)PayloadRows.push_back({Tile.Key,Tile.PayloadHash});
if(Tile.bHasTopologyHash)TopologyRows.push_back({Tile.Key,Tile.TopologyHash});
}
}
if(!bNeighborhoodReported&&NeighborhoodFinished==static_cast<int32>(Neighborhood.size()))
{
std::printf("[PipelineMilestone] neighborhood_ready=%.3fs generated=%d/27\n",
std::chrono::duration<double>(NeighborhoodReady-ColdStart).count(),NeighborhoodFinished);
bNeighborhoodReported=true;
}
if(!bFullDetailReported&&FullDetailFinished==FullDetailExpected)
{
std::printf("[PipelineMilestone] full_detail_ready=%.3fs generated=%d/%d\n",
std::chrono::duration<double>(FullDetailReady-ColdStart).count(),
FullDetailFinished,FullDetailExpected);
bFullDetailReported=true;
}
}
Streamer.Stop();
const double NeighborhoodSeconds=std::chrono::duration<double>(NeighborhoodReady-ColdStart).count();
const double NeighborhoodMeshSeconds=std::chrono::duration<double>(NeighborhoodReady-ShapeReady).count();
const double FullDetailSeconds=std::chrono::duration<double>(FullDetailReady-ColdStart).count();
const double WholeViewSeconds=std::chrono::duration<double>(WholeViewReady-ColdStart).count();
const double StreamSeconds=std::chrono::duration<double>(WholeViewReady-StreamStart).count();
std::printf("[StreamPipeline] workers=%d shape=%.3fs neighborhood_mesh=%.3fs neighborhood_ready=%.3fs full_detail_ready=%.3fs whole_view_ready=%.3fs neighborhood_tiles=%d/27 full_detail_tiles=%d/%d whole_tiles=%d/%zu stream_wall=%.3fs cache_hits=0 full_payload=1\n",
WorkerCount,ShapeSeconds,NeighborhoodMeshSeconds,NeighborhoodSeconds,
FullDetailSeconds,WholeViewSeconds,NeighborhoodFinished,
FullDetailFinished,FullDetailExpected,Finished,Desired.Tiles.size(),StreamSeconds);
bool bHashesPassing=true;
if(bHashDensity)
{
bHashesPassing=DensityRows.size()==Desired.Tiles.size()
&&MeshRows.size()==Desired.Tiles.size()
&&PayloadRows.size()==Desired.Tiles.size()
&&TopologyRows.size()==Desired.Tiles.size();
std::vector<FHashedTile> FullDensity,FullMesh,FullPayload,FullTopology;
for(const FHashedTile& Row:DensityRows)if(Row.Key.Level<World.FullResClipLevels)FullDensity.push_back(Row);
for(const FHashedTile& Row:MeshRows)if(Row.Key.Level<World.FullResClipLevels)FullMesh.push_back(Row);
for(const FHashedTile& Row:PayloadRows)if(Row.Key.Level<World.FullResClipLevels)FullPayload.push_back(Row);
for(const FHashedTile& Row:TopologyRows)if(Row.Key.Level<World.FullResClipLevels)FullTopology.push_back(Row);
std::printf("[PipelineFullDetailHash] tiles=%d density=0x%016llx mesh=0x%016llx payload=0x%016llx topology=0x%016llx\n",
FullDetailExpected,static_cast<unsigned long long>(HashTileRows(FullDensity)),
static_cast<unsigned long long>(HashTileRows(FullMesh)),
static_cast<unsigned long long>(HashTileRows(FullPayload)),
static_cast<unsigned long long>(HashTileRows(FullTopology)));
std::printf("[PipelineWholeViewHash] tiles=%zu density=0x%016llx mesh=0x%016llx payload=0x%016llx topology=0x%016llx\n",
Desired.Tiles.size(),static_cast<unsigned long long>(HashTileRows(DensityRows)),
static_cast<unsigned long long>(HashTileRows(MeshRows)),
static_cast<unsigned long long>(HashTileRows(PayloadRows)),
static_cast<unsigned long long>(HashTileRows(TopologyRows)));
}
FStandaloneShapeWorldAccess::SetActive(nullptr);
return NeighborhoodFinished==static_cast<int32>(Neighborhood.size())
&&FullDetailFinished==FullDetailExpected&&Finished==Update.DesiredTiles&&bHashesPassing?0:6;
}
int RunStreamingBenchmark(FStandaloneOwnerWorld& World,const FVector& CameraPositionVoxels,
int32 WorkerCount,int32 MaxLevel,bool bRoundTunnels,
bool bEnhancedWalls,const FStandaloneMeshCache* Cache,
bool bHashDensity,bool bSkipUvAndColors,bool bSkipMaterialGroups,
bool bSkipUV,bool bSkipColors,
bool bShardAudit,bool bFarSurvivalAudit,bool bFarAuditBadShell,
float CameraYaw,float CameraPitch,
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::unique_ptr<FFarSurvivalAudit> FarAudit;
if(bFarSurvivalAudit)
{
const FStandaloneShapeWorld* Shape=FStandaloneShapeWorldAccess::GetActive();
if(!Shape||!Shape->HasFarWorld())
{std::fprintf(stderr,"Far survival audit requires an active shape world with far-parent data.\n");return 15;}
FarAudit=std::make_unique<FFarSurvivalAudit>(World,*Shape,Desired.Tiles,bFarAuditBadShell);
}
std::array<std::vector<FHashedTile>,MaxLevels+1> HashRows;
std::array<std::vector<FHashedTile>,MaxLevels+1> MeshHashRows;
std::array<std::vector<FHashedTile>,MaxLevels+1> PayloadHashRows;
std::array<std::vector<FHashedTile>,MaxLevels+1> TopologyHashRows;
std::array<uint64,MaxLevels+1> SamplesByLevel{};
std::array<uint64,MaxLevels+1> VerticesByLevel{};
std::array<uint64,MaxLevels+1> IndicesByLevel{};
std::array<uint64,MaxLevels+1> TilesByLevel{};
std::array<double,MaxLevels+1> WorkerSecondsByLevel{};
std::array<double,MaxLevels+1> FirstCompletionByLevel{};
std::array<double,MaxLevels+1> LastCompletionByLevel{};
std::array<uint64,MaxLevels+1> OutputBytesByLevel{};
std::array<uint64,MaxLevels+1> MeshPayloadAllocatedBytesByLevel{};
FirstCompletionByLevel.fill(std::numeric_limits<double>::infinity());
std::unordered_set<FVoxelTileKey,FStandaloneTileKeyHash> PlayerNeighborhoodTiles;
for(const FVoxelTileKey& Tile:Desired.Tiles)
if(Tile.Level==0&&std::abs(Tile.Coord.X-Desired.CenterChunk.X)<=1
&&std::abs(Tile.Coord.Y-Desired.CenterChunk.Y)<=1
&&std::abs(Tile.Coord.Z-Desired.CenterChunk.Z)<=1)
PlayerNeighborhoodTiles.insert(Tile);
std::unordered_set<FVoxelTileKey,FStandaloneTileKeyHash> PlayerNeighborhoodReady;
auto PlayerNeighborhoodCompleted=std::chrono::steady_clock::time_point{};
std::vector<FStandaloneCpuTile> CacheTiles;
if(!BakeCachePath.empty())CacheTiles.reserve(Desired.Tiles.size());
const auto Start=std::chrono::steady_clock::now();
FStandaloneStreamingGenerator Streamer(World,WorkerCount,
bHashDensity||!BakeCachePath.empty(),Cache,MaxLevel,
bSkipUvAndColors,bSkipMaterialGroups,bSkipUV,bSkipColors);
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 uv=%s colors=%s material_groups=%s hash=%s\n",
World.ClipRadius,LOD0Width,World.FullResClipLevels,MaxLevel<0?World.MaxClipLevel:MaxLevel,
Streamer.GetWorkerCount(),FullDetailExpected,Update.DesiredTiles,
bRoundTunnels?(bEnhancedWalls?"round-detailed":"round-legacy"):"base",
(bSkipUvAndColors||bSkipUV)?"skipped":"full",
(bSkipUvAndColors||bSkipColors)?"skipped":"full",
bSkipMaterialGroups?"skipped":"full",
(bHashDensity||!BakeCachePath.empty())?"on":"off");
std::fflush(stdout);
int32 Finished=0,FullDetailFinished=0;
bool bFullDetailTimeReported=false;
double FullDetailSeconds=0.0;
const int32 DesiredTotal=Update.DesiredTiles;
FMeshSheetAudit MeshSheetStats;
uint64 MeshAuditedTiles=0;
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)
{
if(FarAudit)FarAudit->AuditTile(Tile);
++Finished;
const int32 Level=Tile.Key.Level;
if(PlayerNeighborhoodTiles.contains(Tile.Key))
{
PlayerNeighborhoodReady.insert(Tile.Key);
if(PlayerNeighborhoodReady.size()==PlayerNeighborhoodTiles.size())
PlayerNeighborhoodCompleted=Tile.CompletedAt;
}
if(Level>=0&&Level<=MaxLevels)
{
SamplesByLevel[Level]+=Tile.DensitySamples;
VerticesByLevel[Level]+=Tile.Vertices.size();
IndicesByLevel[Level]+=Tile.Indices.size();
++TilesByLevel[Level];
WorkerSecondsByLevel[Level]+=Tile.WorkerSeconds;
const double CompletedSeconds=std::chrono::duration<double>(Tile.CompletedAt-Start).count();
FirstCompletionByLevel[Level]=std::min(FirstCompletionByLevel[Level],CompletedSeconds);
LastCompletionByLevel[Level]=std::max(LastCompletionByLevel[Level],CompletedSeconds);
OutputBytesByLevel[Level]+=static_cast<uint64>(Tile.Vertices.capacity())*sizeof(FStandaloneCpuVertex)
+static_cast<uint64>(Tile.Indices.capacity())*sizeof(uint32);
MeshPayloadAllocatedBytesByLevel[Level]+=Tile.MeshPayloadAllocatedBytes;
if(Tile.bHasDensityHash)HashRows[Level].push_back({Tile.Key,Tile.DensityHash});
if(Tile.bHasMeshHash)MeshHashRows[Level].push_back({Tile.Key,Tile.MeshHash});
if(Tile.bHasPayloadHash)PayloadHashRows[Level].push_back({Tile.Key,Tile.PayloadHash});
if(Tile.bHasTopologyHash)TopologyHashRows[Level].push_back({Tile.Key,Tile.TopologyHash});
}
if(Level<World.FullResClipLevels)++FullDetailFinished;
if(bShardAudit&&Level==0)
{
const FMeshSheetAudit TileAudit=AuditThinMeshSheets(Tile,CameraPositionVoxels,
CameraYaw,CameraPitch,
MeshSheetStats.ThinSheets==0
&&MeshSheetStats.SmallComponentsInView
+MeshSheetStats.BoundarySmallComponentsInView<8);
MeshSheetStats.Components+=TileAudit.Components;
MeshSheetStats.SmallComponents+=TileAudit.SmallComponents;
MeshSheetStats.ThinSheets+=TileAudit.ThinSheets;
MeshSheetStats.SmallComponentsInView+=TileAudit.SmallComponentsInView;
MeshSheetStats.BoundarySmallComponentsInView+=TileAudit.BoundarySmallComponentsInView;
MeshSheetStats.TrianglesInThinSheets+=TileAudit.TrianglesInThinSheets;
MeshSheetStats.MinimumThicknessVoxels=std::min(MeshSheetStats.MinimumThicknessVoxels,
TileAudit.MinimumThicknessVoxels);
++MeshAuditedTiles;
}
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;
std::vector<FHashedTile> FullMeshRows;
std::vector<FHashedTile> FullPayloadRows;
std::vector<FHashedTile> FullTopologyRows;
for(int32 Level=0;Level<World.FullResClipLevels&&Level<=MaxLevels;++Level)
{
FullRows.insert(FullRows.end(),HashRows[Level].begin(),HashRows[Level].end());
FullMeshRows.insert(FullMeshRows.end(),MeshHashRows[Level].begin(),MeshHashRows[Level].end());
FullPayloadRows.insert(FullPayloadRows.end(),PayloadHashRows[Level].begin(),PayloadHashRows[Level].end());
FullTopologyRows.insert(FullTopologyRows.end(),TopologyHashRows[Level].begin(),TopologyHashRows[Level].end());
}
if(bHashDensity||!BakeCachePath.empty())
std::printf("[StreamFullDetail] wall=%.3fs generated=%d/%d resident_tiles_cpu_only=1 density_hash=0x%016llx mesh_hash=0x%016llx payload_hash=0x%016llx topology_hash=0x%016llx\n",
FullDetailSeconds,FullDetailFinished,FullDetailExpected,
static_cast<unsigned long long>(HashTileRows(FullRows)),
static_cast<unsigned long long>(HashTileRows(FullMeshRows)),
static_cast<unsigned long long>(HashTileRows(FullPayloadRows)),
static_cast<unsigned long long>(HashTileRows(FullTopologyRows)));
else
std::printf("[StreamFullDetail] wall=%.3fs generated=%d/%d resident_tiles_cpu_only=1 hash=off\n",
FullDetailSeconds,FullDetailFinished,FullDetailExpected);
std::fflush(stdout);
}
}
const double FullClipSeconds=std::chrono::duration<double>(
std::chrono::steady_clock::now()-Start).count();
if(!PlayerNeighborhoodTiles.empty()&&PlayerNeighborhoodReady.size()==PlayerNeighborhoodTiles.size())
std::printf("[PlayerNeighborhood] lod0_tiles=%zu/%zu mesh_ready=%.3fs collision_cook=unavailable_in_standalone\n",
PlayerNeighborhoodReady.size(),PlayerNeighborhoodTiles.size(),
std::chrono::duration<double>(PlayerNeighborhoodCompleted-Start).count());
else
std::printf("[PlayerNeighborhood] lod0_tiles=%zu/%zu mesh_ready=missing collision_cook=unavailable_in_standalone\n",
PlayerNeighborhoodReady.size(),PlayerNeighborhoodTiles.size());
const double TotalWorkerSeconds=std::accumulate(WorkerSecondsByLevel.begin(),WorkerSecondsByLevel.end(),0.0);
const uint64 TotalMeshPayloadAllocatedBytes=std::accumulate(
MeshPayloadAllocatedBytesByLevel.begin(),MeshPayloadAllocatedBytesByLevel.end(),uint64{0});
const uint64 TotalViewerOutputBytes=std::accumulate(
OutputBytesByLevel.begin(),OutputBytesByLevel.end(),uint64{0});
const double WorkerBusyPercent=100.0*TotalWorkerSeconds
/(std::max(1,WorkerCount)*std::max(0.001,FullClipSeconds));
std::vector<FHashedTile> AllRows;
std::vector<FHashedTile> AllMeshRows;
std::vector<FHashedTile> AllPayloadRows;
std::vector<FHashedTile> AllTopologyRows;
if(bHashDensity||!BakeCachePath.empty())
for(int32 Level=0;Level<=MaxLevels;++Level)
{
AllRows.insert(AllRows.end(),HashRows[Level].begin(),HashRows[Level].end());
AllMeshRows.insert(AllMeshRows.end(),MeshHashRows[Level].begin(),MeshHashRows[Level].end());
AllPayloadRows.insert(AllPayloadRows.end(),PayloadHashRows[Level].begin(),PayloadHashRows[Level].end());
AllTopologyRows.insert(AllTopologyRows.end(),TopologyHashRows[Level].begin(),TopologyHashRows[Level].end());
}
if(bHashDensity||!BakeCachePath.empty())
std::printf("[StreamFullClipmap] wall=%.3fs generated=%d/%d worker_sum=%.3fs worker_busy=%.1f%% worker_idle=%.1f%% payload_alloc=%.2fMiB viewer_output=%.2fMiB tiles=%d density_hash=0x%016llx mesh_hash=0x%016llx payload_hash=0x%016llx topology_hash=0x%016llx\n",
FullClipSeconds,Finished,DesiredTotal,TotalWorkerSeconds,WorkerBusyPercent,
std::max(0.0,100.0-WorkerBusyPercent),
static_cast<double>(TotalMeshPayloadAllocatedBytes)/1048576.0,
static_cast<double>(TotalViewerOutputBytes)/1048576.0,
DesiredTotal,static_cast<unsigned long long>(HashTileRows(AllRows)),
static_cast<unsigned long long>(HashTileRows(AllMeshRows)),
static_cast<unsigned long long>(HashTileRows(AllPayloadRows)),
static_cast<unsigned long long>(HashTileRows(AllTopologyRows)));
else
std::printf("[StreamFullClipmap] wall=%.3fs generated=%d/%d worker_sum=%.3fs worker_busy=%.1f%% worker_idle=%.1f%% payload_alloc=%.2fMiB viewer_output=%.2fMiB tiles=%d hash=off\n",
FullClipSeconds,Finished,DesiredTotal,TotalWorkerSeconds,WorkerBusyPercent,
std::max(0.0,100.0-WorkerBusyPercent),
static_cast<double>(TotalMeshPayloadAllocatedBytes)/1048576.0,
static_cast<double>(TotalViewerOutputBytes)/1048576.0,DesiredTotal);
std::printf("[StreamCacheUse] hits=%llu requested=%d\n",
static_cast<unsigned long long>(Streamer.GetCacheHits()),DesiredTotal);
if(bShardAudit)
std::printf("[ThinMeshShardAudit] level=0 tiles=%llu view_components=%llu small_components=%llu isolated_in_view_small=%llu boundary_in_view_small=%llu thin_sheets=%llu sheet_triangles=%llu thickness_lt=1.000 vox pca_scale=3.5x_sigma status=%s\n",
static_cast<unsigned long long>(MeshAuditedTiles),
static_cast<unsigned long long>(MeshSheetStats.Components),
static_cast<unsigned long long>(MeshSheetStats.SmallComponents),
static_cast<unsigned long long>(MeshSheetStats.SmallComponentsInView),
static_cast<unsigned long long>(MeshSheetStats.BoundarySmallComponentsInView),
static_cast<unsigned long long>(MeshSheetStats.ThinSheets),
static_cast<unsigned long long>(MeshSheetStats.TrianglesInThinSheets),
MeshSheetStats.ThinSheets==0&&MeshSheetStats.SmallComponentsInView==0?"PASS":"FAIL");
for(int32 Level=0;Level<=MaxLevels;++Level)
{
if(TilesByLevel[Level]==0)continue;
if(bHashDensity||!BakeCachePath.empty())
std::printf("[StreamLevel] lod=%d tiles=%llu samples=%.3fM worker_sum=%.3fs first_ready=%.3fs complete=%.3fs worker_busy=%.1f%% payload_alloc=%.2fMiB viewer_output=%.2fMiB vertices=%llu indices=%llu density_hash=0x%016llx mesh_hash=0x%016llx payload_hash=0x%016llx topology_hash=0x%016llx\n",
Level,static_cast<unsigned long long>(TilesByLevel[Level]),
static_cast<double>(SamplesByLevel[Level])/1.0e6,WorkerSecondsByLevel[Level],
FirstCompletionByLevel[Level],LastCompletionByLevel[Level],
100.0*WorkerSecondsByLevel[Level]/(WorkerCount*std::max(0.001,LastCompletionByLevel[Level])),
static_cast<double>(MeshPayloadAllocatedBytesByLevel[Level])/1048576.0,
static_cast<double>(OutputBytesByLevel[Level])/1048576.0,
static_cast<unsigned long long>(VerticesByLevel[Level]),
static_cast<unsigned long long>(IndicesByLevel[Level]),
static_cast<unsigned long long>(HashTileRows(HashRows[Level])),
static_cast<unsigned long long>(HashTileRows(MeshHashRows[Level])),
static_cast<unsigned long long>(HashTileRows(PayloadHashRows[Level])),
static_cast<unsigned long long>(HashTileRows(TopologyHashRows[Level])));
else
std::printf("[StreamLevel] lod=%d tiles=%llu samples=%.3fM worker_sum=%.3fs first_ready=%.3fs complete=%.3fs worker_busy=%.1f%% payload_alloc=%.2fMiB viewer_output=%.2fMiB vertices=%llu indices=%llu hash=off\n",
Level,static_cast<unsigned long long>(TilesByLevel[Level]),
static_cast<double>(SamplesByLevel[Level])/1.0e6,WorkerSecondsByLevel[Level],
FirstCompletionByLevel[Level],LastCompletionByLevel[Level],
100.0*WorkerSecondsByLevel[Level]/(WorkerCount*std::max(0.001,LastCompletionByLevel[Level])),
static_cast<double>(MeshPayloadAllocatedBytesByLevel[Level])/1048576.0,
static_cast<double>(OutputBytesByLevel[Level])/1048576.0,
static_cast<unsigned long long>(VerticesByLevel[Level]),
static_cast<unsigned long long>(IndicesByLevel[Level]));
}
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());
}
if(Finished!=DesiredTotal||FullDetailFinished!=FullDetailExpected)return 6;
if(bShardAudit&&(MeshSheetStats.ThinSheets!=0||MeshSheetStats.SmallComponentsInView!=0))return 13;
if(FarAudit)
{
FarAudit->Finalize();
FarAudit->Print();
if(!FarAudit->Passing())return 14;
}
return 0;
}
int RunEditTransactionBenchmark(FStandaloneOwnerWorld& World,FStandaloneShapeWorld& Shape,
const FVector& CameraPositionVoxels,int32 WorkerCount)
{
using Clock=std::chrono::steady_clock;
// Place the spell across the LOD0/LOD1 clipmap seam so one edit exercises every visible
// level it intersects, instead of timing only the center LOD0 tiles.
const double X=CameraPositionVoxels.X+3.0*CHUNK_SIZE;
const double Y=CameraPositionVoxels.Y,Z=CameraPositionVoxels.Z;
std::vector<FStandaloneShapeWorld::FEditBrush> Brushes(3);
Brushes[0].Shape=FStandaloneShapeWorld::FEditBrush::EShape::Sphere;
Brushes[0].Operation=FStandaloneShapeWorld::FEditBrush::EOperation::Carve;
Brushes[0].A={X,Y,Z};Brushes[0].Radius=20.0;
Brushes[1].Shape=FStandaloneShapeWorld::FEditBrush::EShape::RoundedBox;
Brushes[1].Operation=FStandaloneShapeWorld::FEditBrush::EOperation::Carve;
Brushes[1].A={X,Y,Z+12.0};Brushes[1].HalfExtent={11.0,11.0,4.0};Brushes[1].Rounding=3.0;
Brushes[2].Shape=FStandaloneShapeWorld::FEditBrush::EShape::Capsule;
Brushes[2].Operation=FStandaloneShapeWorld::FEditBrush::EOperation::AddRock;
Brushes[2].A={X-22.0,Y,Z-8.0};Brushes[2].B={X+22.0,Y,Z-8.0};Brushes[2].Radius=3.0;
const auto EditStart=Clock::now();
Shape.SetEditTransaction(std::move(Brushes));
FStandaloneDesiredTileSet Desired=StandaloneWorldMesher::BuildDesiredSet(World,CameraPositionVoxels);
const FIntVector EditMin=Shape.GetEditBoundsMin(),EditMax=Shape.GetEditBoundsMax();
std::vector<FVoxelTileKey> Affected;Affected.reserve(128);
std::array<uint64,MaxLevels+1> AffectedByLevel{},FinishedByLevel{};
for(const FVoxelTileKey& Tile:Desired.Tiles)
{
const FIntVector Origin=Tile.OriginVoxels();const int32 Extent=Tile.ExtentVoxels();
const int32 Pad=std::max(2,Tile.StepVoxels());
if(Origin.X-Pad>EditMax.X||Origin.X+Extent+Pad<EditMin.X
||Origin.Y-Pad>EditMax.Y||Origin.Y+Extent+Pad<EditMin.Y
||Origin.Z-Pad>EditMax.Z||Origin.Z+Extent+Pad<EditMin.Z)continue;
Affected.push_back(Tile);
if(Tile.Level>=0&&Tile.Level<=MaxLevels)++AffectedByLevel[Tile.Level];
}
if(Affected.empty())
{
std::fprintf(stderr,"[EditTransaction] failed: no desired tiles intersect the edit bounds.\n");
return 9;
}
FStandaloneStreamingGenerator Streamer(World,WorkerCount,false,nullptr,-1);
Streamer.SetDesiredTileFilter(Affected);
const auto Start=Clock::now();
const FStandaloneStreamingUpdate Update=Streamer.Start(CameraPositionVoxels);
int32 Finished=0;
double WorkerSeconds=0.0;
while(Finished<Update.DesiredTiles)
{
std::vector<FStandaloneCpuTile> Ready=Streamer.TakeReady();
if(Ready.empty()){std::this_thread::sleep_for(std::chrono::milliseconds(1));continue;}
for(const FStandaloneCpuTile& Tile:Ready)
{
++Finished;WorkerSeconds+=Tile.WorkerSeconds;
if(Tile.Key.Level>=0&&Tile.Key.Level<=MaxLevels)++FinishedByLevel[Tile.Key.Level];
}
}
const double RemeshWall=std::chrono::duration<double>(Clock::now()-Start).count();
Streamer.Stop();
std::printf("[EditTransaction] operation=crater_batch transaction_count=1 brushes=3 diameter=10.0m offset_from_player=%.2fm affected_tiles=%d/%zu workers=%d cache_hits=%llu remesh_wall=%.3fs worker_sum=%.3fs edit_to_ready=%.3fs status=%s\n",
3.0*CHUNK_SIZE*VOXEL_SIZE/100.0,
Finished,Affected.size(),WorkerCount,static_cast<unsigned long long>(Streamer.GetCacheHits()),
RemeshWall,WorkerSeconds,std::chrono::duration<double>(Clock::now()-EditStart).count(),
Finished==static_cast<int32>(Affected.size())?"PASS":"FAIL");
for(int32 Level=0;Level<=MaxLevels;++Level)
if(AffectedByLevel[Level])
std::printf("[EditTransactionLevel] lod=%d tiles=%llu remeshed=%llu status=%s\n",Level,
static_cast<unsigned long long>(AffectedByLevel[Level]),
static_cast<unsigned long long>(FinishedByLevel[Level]),
AffectedByLevel[Level]==FinishedByLevel[Level]?"PASS":"FAIL");
return Finished==static_cast<int32>(Affected.size())?0:10;
}
}
int main(int Argc,char** Argv)
{
const auto ColdProcessStart=std::chrono::steady_clock::now();
FApp App;App.DataPath=DefaultDataPath().string();App.ColdProcessStart=ColdProcessStart;
std::string JoinedCommandLine;
for(int32 I=1;I<Argc;++I)
{if(!JoinedCommandLine.empty())JoinedCommandLine.push_back(' ');JoinedCommandLine+=Argv[I];}
FCommandLine::Set(JoinedCommandLine);
int32 BenchmarkLevel=-1,BenchmarkCount=64,StreamBenchmarkMaxLevel=-1;
int32 MaxClipLevelOverride=-1;
int32 ShapeCompareLevel=-1,ShapeCompareTileLimit=0;
bool bStreamBenchmark=false;
bool bStreamPipeline=false;
bool bProfileDetailBand=false;
bool bPassageDetailAuditOnly=false;
bool bShardAudit=false;
bool bFarSurvivalAudit=false;
bool bFarAuditBadShell=false;
bool bStreamHash=false;
bool bSkipUvAndColors=false;
bool bSkipUV=false;
bool bSkipColors=false;
bool bSkipMaterialGroups=false;
bool bEditBenchmark=false;
bool bRoundWallCompare=false;
int32 RoundCompareTileCount=16;
std::string BakeMeshCachePath;
for(int32 I=1;I<Argc;++I)
{
const std::string_view Arg=Argv[I];
if(Arg.starts_with("--capture="))App.CapturePath=std::string(Arg.substr(10));
else if(Arg=="--profile-attribution")App.bProfileAttribution=true;
else if(Arg=="--profile-detail-band")bProfileDetailBand=true;
else if(Arg=="--compare-evaluators")App.bCompareEvaluators=true;
else if(Arg=="--parity-only")App.bParityOnly=true;
else if(Arg=="--density-hash")App.bDensityHash=true;
else if(Arg=="--shape-world")App.bShapeWorld=true;
else if(Arg=="--shape-audit")App.bShapeWorld=true,App.bShapeAuditOnly=true;
else if(Arg=="--passage-wall-detail-audit")
{App.bShapeWorld=true;App.bShapeAuditOnly=true;bPassageDetailAuditOnly=true;}
else if(Arg=="--shape-compare")App.bShapeWorld=true,App.bShapeCompare=true;
else if(Arg.starts_with("--shape-compare-level="))
ShapeCompareLevel=std::atoi(std::string(Arg.substr(std::string_view("--shape-compare-level=").size())).c_str());
else if(Arg.starts_with("--shape-compare-tile-limit="))
ShapeCompareTileLimit=std::max(0,std::atoi(std::string(Arg.substr(std::string_view("--shape-compare-tile-limit=").size())).c_str()));
else if(Arg.starts_with("--shape-detail="))
{
const std::string_view Value=Arg.substr(15);
if(Value=="smooth")App.ShapeDetailVariant=FStandaloneShapeWorld::EDetailVariant::Smooth;
else if(Value=="per-voxel")App.ShapeDetailVariant=FStandaloneShapeWorld::EDetailVariant::PerVoxel;
else if(Value!="coherent"){std::fprintf(stderr,"Invalid --shape-detail value: %.*s\n",static_cast<int>(Value.size()),Value.data());return 2;}
}
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=="--stream-pipeline")
{bStreamPipeline=true;bStreamBenchmark=true;App.bShapeWorld=true;App.bForceGenerate=true;}
else if(Arg=="--edit-benchmark")
{bEditBenchmark=true;bStreamBenchmark=true;App.bShapeWorld=true;}
else if(Arg=="--stream-hash")bStreamHash=true;
else if(Arg=="--skip-uv-colors")bSkipUvAndColors=true;
else if(Arg=="--skip-uv")bSkipUV=true;
else if(Arg=="--skip-colors")bSkipColors=true;
else if(Arg=="--skip-material-groups")bSkipMaterialGroups=true;
else if(Arg=="--shard-audit")bShardAudit=true;
else if(Arg=="--far-survival-audit")bFarSurvivalAudit=true,App.bShapeWorld=true;
else if(Arg=="--far-audit-bad-shell")
{bFarAuditBadShell=true;bFarSurvivalAudit=true;App.bShapeWorld=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.starts_with("--max-clip-level="))
MaxClipLevelOverride=std::clamp(
std::atoi(std::string(Arg.substr(17)).c_str()),0,8);
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,16);
}
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="))
BenchmarkCount=std::max(1,std::atoi(std::string(Arg.substr(18)).c_str()));
else if(Arg.starts_with("--camera="))
{
const std::string Values(Arg.substr(9));
float X=0,Y=0,Z=0,YawDegrees=0,PitchDegrees=0;
if(std::sscanf(Values.c_str(),"%f,%f,%f,%f,%f",&X,&Y,&Z,&YawDegrees,&PitchDegrees)==5)
{
App.bHasCameraOverride=true;App.CameraPositionOverride={X,Y,Z};
App.CameraYawOverride=YawDegrees*0.017453292519943295f;
App.CameraPitchOverride=PitchDegrees*0.017453292519943295f;
}
else {std::fprintf(stderr,"Invalid --camera value: %s\n",Values.c_str());return 2;}
}
else if(!Arg.empty()&&Arg.front()!='-')App.DataPath=std::string(Arg);
}
FStandaloneShapeWorld::SetDetailProfileEnabled(bProfileDetailBand);
std::string Error;
App.World=std::make_unique<FStandaloneOwnerWorld>();
if(!StandaloneOwnerLoader::Load(App.DataPath,*App.World,Error)){std::fprintf(stderr,"Owner data load failed: %s\n",Error.c_str());return 2;}
App.Seed=App.World->Seed;
if(App.bParityOnly)
{
VoxelGenLOD::InitializeConsoleSwitches();
return App.PrintParity()==0?0:1;
}
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;
const int32 ExportMaxClipLevel=App.World->MaxClipLevel;
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(MaxClipLevelOverride>=0)
{
App.MaxClipLevelOverride=MaxClipLevelOverride;
App.World->MaxClipLevel=MaxClipLevelOverride;
App.World->Settings->MaxClipLevel=MaxClipLevelOverride;
std::printf("Standalone max clip level override: exported=%d -> max_level=%d.\n",
ExportMaxClipLevel,MaxClipLevelOverride);
}
const bool bNeedFarShapeWorld=bStreamBenchmark
?(StreamBenchmarkMaxLevel<0||StreamBenchmarkMaxLevel>=App.World->FullResClipLevels)
:(App.bShapeCompare?ShapeCompareLevel>=App.World->FullResClipLevels
:(BenchmarkLevel>=App.World->FullResClipLevels));
if(App.bShapeWorld&&(App.bShapeAuditOnly||App.bShapeCompare||bStreamBenchmark||BenchmarkLevel>=0)
&&!bStreamPipeline
&&!App.BuildShapeWorldForCamera(Error,bNeedFarShapeWorld))
{std::fprintf(stderr,"Shape interval build failed: %s\n",Error.c_str());return 2;}
if(App.bShapeAuditOnly)
{
if(bPassageDetailAuditOnly)
return App.ShapeWorld->AuditSurfaceBandFamilies(true)?0:8;
const bool bSurfacePassing=App.ShapeWorld->AuditViewSurface(App.Position.X,App.Position.Y,
App.Position.Z,App.Yaw,App.Pitch);
return App.ShapeWorld->IsDetailAuditPassing()&&bSurfacePassing?0:7;
}
if(App.bShapeCompare)
return RunOwnerShapeComparison(*App.World,*App.ShapeWorld,App.Position,App.Yaw,App.Pitch,App.WorkerCount,
ShapeCompareLevel,ShapeCompareTileLimit);
if(bRoundWallCompare)
return RunRoundWallComparison(*App.World,App.Position,RoundCompareTileCount);
if(bStreamBenchmark)
{
if(bStreamPipeline)
return RunPipelinedStreamingBenchmark(*App.World,
FVector(App.Position.X*4.0,App.Position.Y*4.0,App.Position.Z*4.0),
App.WorkerCount,StreamBenchmarkMaxLevel,App.ShapeDetailVariant,
bStreamHash||!BakeMeshCachePath.empty(),bSkipUvAndColors,bSkipMaterialGroups,
bSkipUV,bSkipColors,ColdProcessStart);
if(bEditBenchmark)
return RunEditTransactionBenchmark(*App.World,*App.ShapeWorld,
FVector(App.Position.X*4.0,App.Position.Y*4.0,App.Position.Z*4.0),App.WorkerCount);
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.bShapeWorld)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.bShapeWorld&&App.MeshCache.IsLoaded())?&App.MeshCache:nullptr,
bStreamHash,bSkipUvAndColors,bSkipMaterialGroups,bSkipUV,bSkipColors,bShardAudit,
bFarSurvivalAudit,bFarAuditBadShell,App.Yaw,App.Pitch,
BakeMeshCachePath,App.MakeMeshCacheMetadata());
}
if(BenchmarkLevel>=0)
return RunTileBenchmark(*App.World,App.Position,BenchmarkLevel,BenchmarkCount,
App.bProfileAttribution,App.bCompareEvaluators,App.bDensityHash);
App.World.reset();
WNDCLASSA Class{};Class.lpfnWndProc=WindowProc;Class.hInstance=GetModuleHandleA(nullptr);Class.lpszClassName="VoxelForgeStandaloneWorld";
Class.hCursor=LoadCursor(nullptr,IDC_ARROW);RegisterClassA(&Class);
RECT Rect{0,0,App.Width,App.Height};AdjustWindowRect(&Rect,WS_OVERLAPPEDWINDOW,FALSE);
App.Window=CreateWindowA(Class.lpszClassName,"VoxelForge standalone world",WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,CW_USEDEFAULT,
Rect.right-Rect.left,Rect.bottom-Rect.top,nullptr,nullptr,Class.hInstance,nullptr);
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; G toggles legacy/detailed round walls; 1-9 toggle LOD; 0 toggle all levels; N/P change seed; T swaps base/round tunnels; V swaps owner density/shape intervals; 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);
App.StartGeneration();
MSG Message{};
while(Message.message!=WM_QUIT)
{
if(PeekMessage(&Message,nullptr,0,0,PM_REMOVE)){TranslateMessage(&Message);DispatchMessage(&Message);}
else App.Draw();
}
GApp=nullptr;
return 0;
}