standalone: shape-world look round 2 -- shards gone, calmer layered relief, still 0.72 s / 1.6 s cold

- Removed the thin automatic room-support slabs that left floating caps (in-view detached thin
  sheets 9 -> 0; the audit now also fails on isolated small components).
- Bounded 16-voxel floor/wall blend (18 rejected: it created a sub-voxel sheet), 3-voxel
  strata/cave blend, C1 tunnel radius profile (0 radius/slope steps at joins).
- Surface relief: broad octave with amplitude modulation instead of uniform bumps.
- Cold, no cache: 64 m full detail 0.924 s (8 workers) / 0.715 s (16); whole view 2.016 / 1.605 s.
- 1-worker and 6-worker hashes match; 5,642/5,642 passage samples open.
Not solved: regular bands in the narrow passage shafts, a few slivers on tile boundaries, faint
wall/floor plates; close-view mean surface distance to the owner field got worse (see REPORT.md).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
This commit is contained in:
Fr0zkaandClaude Opus 5.5 committed 2026-10-04 04:09:59 +02:00
1 parent 7e3f926352
commit 97c0455939
12 files changed
+919 -108

No files matched your search

+194 -5
View File
@@ -48,6 +48,162 @@ namespace
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))));}
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;}
@@ -1348,7 +1504,7 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
std::atomic<size_t> Next{0};
std::vector<std::thread> Threads;
const int32 ThreadCount=std::clamp(Workers,1,8);
const int32 ThreadCount=std::clamp(Workers,1,16);
Threads.reserve(ThreadCount);
for(int32 Worker=0;Worker<ThreadCount;++Worker)
{
@@ -1547,7 +1703,7 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
int RunStreamingBenchmark(FStandaloneOwnerWorld& World,const FVector& CameraPositionVoxels,
int32 WorkerCount,int32 MaxLevel,bool bRoundTunnels,
bool bEnhancedWalls,const FStandaloneMeshCache* Cache,
bool bHashDensity,
bool bHashDensity,bool bShardAudit,float CameraYaw,float CameraPitch,
const std::string& BakeCachePath,
const FStandaloneMeshCacheMetadata& CacheMetadata)
{
@@ -1588,6 +1744,8 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
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();
@@ -1610,6 +1768,23 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
if(Tile.bHasDensityHash)HashRows[Level].push_back({Tile.Key,Tile.DensityHash});
}
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)
@@ -1646,6 +1821,16 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
std::accumulate(WorkerSecondsByLevel.begin(),WorkerSecondsByLevel.end(),0.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;
@@ -1675,7 +1860,9 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
std::printf("[MeshCacheBake] saved %zu tiles, %.2f MiB to %s\n",CacheTiles.size(),
static_cast<double>(Bytes)/1048576.0,BakeCachePath.c_str());
}
return (Finished==DesiredTotal&&FullDetailFinished==FullDetailExpected)?0:6;
if(Finished!=DesiredTotal||FullDetailFinished!=FullDetailExpected)return 6;
if(bShardAudit&&(MeshSheetStats.ThinSheets!=0||MeshSheetStats.SmallComponentsInView!=0))return 13;
return 0;
}
}
@@ -1689,6 +1876,7 @@ int main(int Argc,char** Argv)
int32 BenchmarkLevel=-1,BenchmarkCount=64,StreamBenchmarkMaxLevel=-1;
int32 ShapeCompareLevel=-1,ShapeCompareTileLimit=0;
bool bStreamBenchmark=false;
bool bShardAudit=false;
bool bStreamHash=false;
bool bRoundWallCompare=false;
int32 RoundCompareTileCount=16;
@@ -1720,6 +1908,7 @@ int main(int Argc,char** Argv)
{App.bRoundTunnels=true;App.bRoundTunnelEnhanced=false;}
else if(Arg=="--stream-benchmark")bStreamBenchmark=true;
else if(Arg=="--stream-hash")bStreamHash=true;
else if(Arg=="--shard-audit")bShardAudit=true;
else if(Arg=="--compare-round-walls")
{bRoundWallCompare=true;App.bRoundTunnels=true;}
else if(Arg.starts_with("--round-compare-tiles="))
@@ -1735,7 +1924,7 @@ int main(int Argc,char** Argv)
else if(Arg.starts_with("--workers="))
{
const int32 Parsed=std::atoi(std::string(Arg.substr(10)).c_str());
App.WorkerCount=std::clamp(Parsed,1,8);
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);
@@ -1804,7 +1993,7 @@ int main(int Argc,char** Argv)
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,BakeMeshCachePath,App.MakeMeshCacheMetadata());
bStreamHash,bShardAudit,App.Yaw,App.Pitch,BakeMeshCachePath,App.MakeMeshCacheMetadata());
}
if(BenchmarkLevel>=0)
return RunTileBenchmark(*App.World,App.Position,BenchmarkLevel,BenchmarkCount,