standalone: rock detail restored in lattice-resolvable bands (4-16 voxels), owner-band gate added

- Relief per wavelength band compared with the owner field; the 4-8 and 8-16 voxel bands were lost.
  Restored within the partition-of-unity blend; max detail gradient 0.545 (limit 0.55).
- New owner-band gate: fails all six views on de14f00, passes all six now.
- W1/W8 44/44; path hash both directions; routes 5642/5642; stair-distance, ring, thin-sheet,
  coarse-panel, far-survival audits pass; bad-shell control fails.
- Cold 1.127 s vs 1.108 s (within spread); nearest-tile readiness 12.20 vs 12.16 ms.
Caveats: the 143-ray GridStair normal audit is at 62.2% vs its 60% limit (was 76.9%); in the tunnel
capture the floor shows a regular dimple pattern, and the passage shaft close-up is still smooth.

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-06 05:43:31 +02:00
1 parent de14f0042d
commit 566d1f342a
7 files changed
+575 -42

No files matched your search

+366 -22
View File
@@ -20,6 +20,7 @@
#include <windowsx.h>
#include <bit>
#include <complex>
#include <cstdio>
#include <cstdlib>
#include <filesystem>
@@ -153,7 +154,7 @@ namespace
std::map<FBlockKey,FGradientFallbackBlock> GradientFallbackBlocks;
std::map<FBlockKey,FCurvedEdgeRootBlock> CurvedEdgeRootBlocks;
struct FDetailDisplacementBlock
{uint64 Vertices=0,HighVertices=0;double Maximum=0.0;std::array<double,3> Position{};};
{uint64 Vertices=0,HighVertices=0;double Maximum=0.0,MaximumGradient=0.0;std::array<double,3> Position{};};
std::map<std::array<int32,3>,FDetailDisplacementBlock> DetailDisplacementBlocks;
std::map<FBlockKey,std::array<double,3>> FirstPositionByBlock;
std::map<FBlockKey,std::array<double,3>> FirstNormalByBlock;
@@ -169,6 +170,7 @@ namespace
{
const auto& Position=Tile.Vertices[I].Position;
const double P[3]={Position[0]*4.0,Position[1]*4.0,Position[2]*4.0};
double DetailGradientAtVertex=0.0;
if(Tile.Key.Level==0)
{
const VoxelForgeCore::DetailFieldSample DetailSample=Shape.detailSampleAt(
@@ -191,10 +193,11 @@ namespace
DetailGradientSquared+=Gradient*Gradient;
}
const double DetailGradient=std::sqrt(DetailGradientSquared);
DetailGradientAtVertex=DetailGradient;
++Result.DetailGradientSamples;
Result.MaximumDetailGradient=std::max(
Result.MaximumDetailGradient,DetailGradient);
if(DetailGradient>0.75)++Result.DetailGradientOverLimit;
if(DetailGradient>0.55)++Result.DetailGradientOverLimit;
const double DetailDisplacement=std::abs(DetailSample.displacement);
++Result.DetailDisplacementSamples;
Result.MaximumDetailDisplacement=std::max(
@@ -205,6 +208,7 @@ namespace
static_cast<int32>(std::floor((P[2]-Origin.Z)/8.0))};
FDetailDisplacementBlock& DetailBlock=DetailDisplacementBlocks[DetailKey];
++DetailBlock.Vertices;
DetailBlock.MaximumGradient=std::max(DetailBlock.MaximumGradient,DetailGradientAtVertex);
if(DetailDisplacement>0.65)
{
++DetailBlock.HighVertices;
@@ -417,27 +421,28 @@ namespace
}
struct FHighDetailBlock
{std::array<int32,3> Key{};uint64 Vertices=0;double Maximum=0.0;
{std::array<int32,3> Key{};uint64 Vertices=0;double Maximum=0.0,MaximumGradient=0.0;
std::array<double,3> Position{};};
std::vector<FHighDetailBlock> HighDetailBlocks;
for(const auto& [Key,Block]:DetailDisplacementBlocks)
{
if(Block.Vertices<12||Block.HighVertices<6
||static_cast<double>(Block.HighVertices)/Block.Vertices<0.25)continue;
HighDetailBlocks.push_back({Key,Block.HighVertices,Block.Maximum,Block.Position});
HighDetailBlocks.push_back({Key,Block.HighVertices,Block.Maximum,Block.MaximumGradient,Block.Position});
}
std::vector<uint8> HighDetailVisited(HighDetailBlocks.size(),0);
for(size_t I=0;I<HighDetailBlocks.size();++I)
{
if(HighDetailVisited[I])continue;
std::vector<size_t> Pending{I};HighDetailVisited[I]=1;
uint64 BlockCount=0,VertexCountInPatch=0;double Maximum=0.0;
uint64 BlockCount=0,VertexCountInPatch=0;double Maximum=0.0,MaximumGradient=0.0;
size_t Representative=I;
while(!Pending.empty())
{
const size_t Current=Pending.back();Pending.pop_back();
const FHighDetailBlock& A=HighDetailBlocks[Current];
++BlockCount;VertexCountInPatch+=A.Vertices;
MaximumGradient=std::max(MaximumGradient,A.MaximumGradient);
if(A.Maximum>Maximum){Maximum=A.Maximum;Representative=Current;}
for(size_t J=0;J<HighDetailBlocks.size();++J)
{
@@ -448,15 +453,17 @@ namespace
if(Manhattan==1){HighDetailVisited[J]=1;Pending.push_back(J);}
}
}
if(BlockCount<3||VertexCountInPatch<48)continue;
// Large relief is valid when it changes smoothly over multiple cells. Flag
// only connected high-amplitude patches whose measured slope is alias-prone.
if(BlockCount<3||VertexCountInPatch<48||MaximumGradient<=0.55)continue;
++Result.DetailDisplacementPatches;
if(bPrintPatches&&Result.DetailDisplacementPatches<=8)
{
const auto& Sample=HighDetailBlocks[Representative];
std::printf("[MeshDetailDisplacementPatch] level=0 tile=(%d,%d,%d) connected_8vox_blocks=%llu vertices=%llu max_displacement=%.3f vox sample=(%.3f,%.3f,%.3f)\n",
std::printf("[MeshDetailDisplacementPatch] level=0 tile=(%d,%d,%d) connected_8vox_blocks=%llu vertices=%llu max_displacement=%.3f vox max_gradient=%.4f vox_per_vox sample=(%.3f,%.3f,%.3f)\n",
Tile.Key.Coord.X,Tile.Key.Coord.Y,Tile.Key.Coord.Z,
static_cast<unsigned long long>(BlockCount),
static_cast<unsigned long long>(VertexCountInPatch),Maximum,
static_cast<unsigned long long>(VertexCountInPatch),Maximum,MaximumGradient,
Sample.Position[0],Sample.Position[1],Sample.Position[2]);
}
}
@@ -1476,6 +1483,7 @@ namespace
int32 WorkerCount=8;
int32 ClipRadius=3;
int32 MaxClipLevelOverride=-1;
int32 CaptureTileLimit=0;
int32 Width=1280,Height=800;
float Yaw=0.0f,Pitch=0.0f,MoveSpeed=8.0f;
V3 Position{};
@@ -1924,6 +1932,39 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
for(const auto& Key:CoreTiles)
DesiredTiles.emplace_back(FIntVector(Key.coordinate.x,Key.coordinate.y,Key.coordinate.z),Key.lod);
}
if(CaptureTileLimit>0&&CapturePath.size())
{
const size_t Before=DesiredTiles.size();
const V3 F=Forward(),R=Normalize(Cross(F,{0,0,1})),U=Normalize(Cross(R,F));
const double TanHalfFov=std::tan(1.04719755*0.5);
const double Aspect=static_cast<double>(Width)/std::max(1,Height);
struct FCaptureTileRank { FVoxelTileKey Tile; double Depth=0.0; };
std::vector<FCaptureTileRank> Visible;Visible.reserve(DesiredTiles.size());
for(const FVoxelTileKey& Tile:DesiredTiles)
{
const FIntVector Origin=Tile.OriginVoxels();
const double HalfExtent=Tile.ExtentVoxels()*0.8660254037844386;
const double DX=Origin.X+Tile.ExtentVoxels()*0.5-CameraVoxels.X;
const double DY=Origin.Y+Tile.ExtentVoxels()*0.5-CameraVoxels.Y;
const double DZ=Origin.Z+Tile.ExtentVoxels()*0.5-CameraVoxels.Z;
const double Depth=DX*F.X+DY*F.Y+DZ*F.Z;
if(Depth+HalfExtent<0.0)continue;
const double LateralX=std::abs(DX*R.X+DY*R.Y+DZ*R.Z);
const double LateralY=std::abs(DX*U.X+DY*U.Y+DZ*U.Z);
const double ForwardReach=std::max(1.0,Depth+HalfExtent);
if(LateralX>ForwardReach*TanHalfFov*Aspect+HalfExtent
||LateralY>ForwardReach*TanHalfFov+HalfExtent)continue;
Visible.push_back({Tile,std::max(0.0,Depth)});
}
std::stable_sort(Visible.begin(),Visible.end(),[](const auto& A,const auto& B)
{return A.Depth<B.Depth;});
if(Visible.size()>static_cast<size_t>(CaptureTileLimit))
Visible.resize(static_cast<size_t>(CaptureTileLimit));
DesiredTiles.clear();DesiredTiles.reserve(Visible.size());
for(const FCaptureTileRank& Ranked:Visible)DesiredTiles.push_back(Ranked.Tile);
std::printf("[CaptureTileLimit] frustum_tiles=%zu selected=%zu original_desired=%zu limit=%d; reference capture only\n",
Visible.size(),DesiredTiles.size(),Before,CaptureTileLimit);
}
DesiredTileSet.clear();DesiredTileSet.reserve(DesiredTiles.size()*2+1);
for(const FVoxelTileKey& Key:DesiredTiles)DesiredTileSet.insert(Key);
for(int32 I=0;I<=MaxLevels;++I)DesiredByLevel[I]=0;
@@ -1934,6 +1975,8 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
bFarShapeStagePending?World->FullResClipLevels-1:-1,
false,false,false,false,ShapeWorld.get());
if(bShapePipelineActive)Streamer->SetDesiredTileFilter(InitialNeighborhoodTiles);
else if(CaptureTileLimit>0&&CapturePath.size())
Streamer->SetDesiredTileFilter(DesiredTiles);
const FStandaloneStreamingUpdate Update=Streamer->Start(CameraVoxels,{},CrossingIndex);
if(bShapePipelineActive&&Update.DesiredTiles!=static_cast<int32>(InitialNeighborhoodTiles.size()))
{
@@ -2907,6 +2950,150 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
uint64 Count=0,Unmatched=0;
};
constexpr int32 OwnerBandGrid=64;
constexpr double OwnerBandSpacing=0.75;
constexpr std::array<double,4> OwnerBandMinWavelength={2.0,4.0,8.0,16.0};
constexpr std::array<double,4> OwnerBandMaxWavelength={4.0,8.0,16.0,32.0};
void FFT64(std::array<std::complex<double>,OwnerBandGrid>& Values)
{
for(int32 I=1,J=0;I<OwnerBandGrid;++I)
{
int32 Bit=OwnerBandGrid>>1;
for(;J&Bit;Bit>>=1)J^=Bit;
J^=Bit;
if(I<J)std::swap(Values[I],Values[J]);
}
for(int32 Length=2;Length<=OwnerBandGrid;Length<<=1)
{
const double Angle=-6.2831853071795864769/static_cast<double>(Length);
const std::complex<double> Step(std::cos(Angle),std::sin(Angle));
for(int32 Base=0;Base<OwnerBandGrid;Base+=Length)
{
std::complex<double> W(1.0,0.0);
for(int32 I=0;I<Length/2;++I)
{
const std::complex<double> Even=Values[Base+I];
const std::complex<double> Odd=Values[Base+I+Length/2]*W;
Values[Base+I]=Even+Odd;Values[Base+I+Length/2]=Even-Odd;W*=Step;
}
}
}
}
bool OwnerSurfaceBandRms(const std::array<double,OwnerBandGrid*OwnerBandGrid>& Heights,
const std::array<uint8,OwnerBandGrid*OwnerBandGrid>& Valid,std::array<double,4>& OutRms)
{
size_t ValidCount=0;
double Sums[3][4]{};
for(int32 Y=0;Y<OwnerBandGrid;++Y)for(int32 X=0;X<OwnerBandGrid;++X)
{
const size_t Index=static_cast<size_t>(Y)*OwnerBandGrid+X;
if(!Valid[Index])continue;
++ValidCount;
const double U=(X-(OwnerBandGrid-1)*0.5)*OwnerBandSpacing;
const double V=(Y-(OwnerBandGrid-1)*0.5)*OwnerBandSpacing;
const double Row[3]={1.0,U,V};
for(int32 I=0;I<3;++I)
{
for(int32 J=0;J<3;++J)Sums[I][J]+=Row[I]*Row[J];
Sums[I][3]+=Row[I]*Heights[Index];
}
}
if(ValidCount<static_cast<size_t>(OwnerBandGrid*OwnerBandGrid*9/10))return false;
for(int32 Column=0;Column<3;++Column)
{
int32 Pivot=Column;
for(int32 Row=Column+1;Row<3;++Row)
if(std::abs(Sums[Row][Column])>std::abs(Sums[Pivot][Column]))Pivot=Row;
if(std::abs(Sums[Pivot][Column])<1.0e-10)return false;
if(Pivot!=Column)for(int32 J=Column;J<4;++J)std::swap(Sums[Pivot][J],Sums[Column][J]);
const double Scale=Sums[Column][Column];
for(int32 J=Column;J<4;++J)Sums[Column][J]/=Scale;
for(int32 Row=0;Row<3;++Row)if(Row!=Column)
{
const double Factor=Sums[Row][Column];
for(int32 J=Column;J<4;++J)Sums[Row][J]-=Factor*Sums[Column][J];
}
}
const double Plane[3]={Sums[0][3],Sums[1][3],Sums[2][3]};
std::array<std::array<std::complex<double>,OwnerBandGrid>,OwnerBandGrid> Spectrum{};
double WindowSquareSum=0.0;
for(int32 Y=0;Y<OwnerBandGrid;++Y)
{
const double WY=0.5-0.5*std::cos(6.2831853071795864769*Y/(OwnerBandGrid-1));
for(int32 X=0;X<OwnerBandGrid;++X)
{
const size_t Index=static_cast<size_t>(Y)*OwnerBandGrid+X;
const double WX=0.5-0.5*std::cos(6.2831853071795864769*X/(OwnerBandGrid-1));
const double U=(X-(OwnerBandGrid-1)*0.5)*OwnerBandSpacing;
const double V=(Y-(OwnerBandGrid-1)*0.5)*OwnerBandSpacing;
const double Residual=Valid[Index]?Heights[Index]-(Plane[0]+Plane[1]*U+Plane[2]*V):0.0;
Spectrum[Y][X]=Residual*(WX*WY);
WindowSquareSum+=WX*WX*WY*WY;
}
FFT64(Spectrum[Y]);
}
for(int32 X=0;X<OwnerBandGrid;++X)
{
std::array<std::complex<double>,OwnerBandGrid> Column{};
for(int32 Y=0;Y<OwnerBandGrid;++Y)Column[Y]=Spectrum[Y][X];
FFT64(Column);
for(int32 Y=0;Y<OwnerBandGrid;++Y)Spectrum[Y][X]=Column[Y];
}
std::array<double,4> Power{};
for(int32 Y=0;Y<OwnerBandGrid;++Y)for(int32 X=0;X<OwnerBandGrid;++X)
{
const double FX=std::min(X,OwnerBandGrid-X)
/(static_cast<double>(OwnerBandGrid)*OwnerBandSpacing);
const double FY=std::min(Y,OwnerBandGrid-Y)
/(static_cast<double>(OwnerBandGrid)*OwnerBandSpacing);
const double Frequency=std::sqrt(FX*FX+FY*FY);
if(Frequency<=0.0)continue;
const double Wavelength=1.0/Frequency;
for(size_t Band=0;Band<Power.size();++Band)
if(Wavelength>=OwnerBandMinWavelength[Band]&&Wavelength<OwnerBandMaxWavelength[Band])
Power[Band]+=std::norm(Spectrum[Y][X]);
}
const double ParsevalScale=static_cast<double>(OwnerBandGrid*OwnerBandGrid)*WindowSquareSum;
for(size_t Band=0;Band<OutRms.size();++Band)
OutRms[Band]=std::sqrt(std::max(0.0,Power[Band]/ParsevalScale));
return true;
}
template<class TDensity>
bool SampleOwnerSurfaceHeightPatch(V3 Origin,V3 Normal,V3 TangentU,V3 TangentV,
TDensity&& Density,std::array<double,OwnerBandGrid*OwnerBandGrid>& OutHeights,
std::array<uint8,OwnerBandGrid*OwnerBandGrid>& OutValid)
{
size_t ValidCount=0;
for(int32 Y=0;Y<OwnerBandGrid;++Y)for(int32 X=0;X<OwnerBandGrid;++X)
{
const double DU=(X-(OwnerBandGrid-1)*0.5)*OwnerBandSpacing;
const double DV=(Y-(OwnerBandGrid-1)*0.5)*OwnerBandSpacing;
const V3 Base=Add(Origin,Add(Mul(TangentU,static_cast<float>(DU)),Mul(TangentV,static_cast<float>(DV))));
double T=0.0;bool bConverged=false;
for(int32 Iteration=0;Iteration<7;++Iteration)
{
const V3 P=Add(Base,Mul(Normal,static_cast<float>(T)));
const double D=Density(P);
if(!std::isfinite(D)||std::abs(T)>16.0)break;
// Core density keeps a 0.05-voxel near-zero sign margin. Accepting a
// tighter root tolerance makes every otherwise-good Core patch fail.
if(std::abs(D)<0.06){bConverged=true;break;}
const double DP=Density(Add(Base,Mul(Normal,static_cast<float>(T+0.5))));
const double DM=Density(Add(Base,Mul(Normal,static_cast<float>(T-0.5))));
const double Gradient=DP-DM;
if(!std::isfinite(Gradient)||std::abs(Gradient)<0.25)break;
T-=std::clamp(D/Gradient,-4.0,4.0);
}
const size_t Index=static_cast<size_t>(Y)*OwnerBandGrid+X;
OutHeights[Index]=T;OutValid[Index]=bConverged?1:0;
ValidCount+=bConverged?1u:0u;
}
return ValidCount>=static_cast<size_t>(OwnerBandGrid*OwnerBandGrid*9/10);
}
FDirectedSurfaceDistance DirectedSurfaceDistance(
const std::vector<FComparePoint>& From,const std::vector<FComparePoint>& To,
double BucketWidth=1.0,int32 SearchCells=3)
@@ -2950,7 +3137,8 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
int RunOwnerShapeComparison(FStandaloneOwnerWorld& World,VoxelForgeCore::World& Shape,
V3 CameraPosition,float CameraYaw,float CameraPitch,
int32 Workers,int32 LevelFilter,int32 TileLimit)
int32 Workers,int32 LevelFilter,int32 TileLimit,
std::string_view AreaLabel,bool bCheckOwnerReliefBands)
{
if(!Shape.isCompiled())
{std::fprintf(stderr,"Shape comparison requires a compiled core world.\n");return 11;}
@@ -2981,16 +3169,28 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
Tiles.push_back(Tile);
if(TileLimit>0&&Tiles.size()>static_cast<size_t>(TileLimit))
{
constexpr double DegreesToRadians=3.14159265358979323846/180.0;
const double YawRadians=CameraYaw*DegreesToRadians,PitchRadians=CameraPitch*DegreesToRadians;
const V3 ViewForward{static_cast<float>(std::sin(YawRadians)*std::cos(PitchRadians)),
static_cast<float>(std::cos(YawRadians)*std::cos(PitchRadians)),
static_cast<float>(std::sin(PitchRadians))};
const auto ViewScore=[&](const FVoxelTileKey& Tile)
{
const FIntVector O=Tile.OriginVoxels();
const V3 D{static_cast<float>(O.X+Tile.ExtentVoxels()*0.5-CameraVoxels.X),
static_cast<float>(O.Y+Tile.ExtentVoxels()*0.5-CameraVoxels.Y),
static_cast<float>(O.Z+Tile.ExtentVoxels()*0.5-CameraVoxels.Z)};
const double Distance=std::sqrt(Dot(D,D));
// Keep near tiles first while preferring the half-space in front of this view.
return Distance*Distance+32.0*std::max(0.0,Distance-Dot(D,ViewForward));
};
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;
const double AS=ViewScore(A),BS=ViewScore(B);
if(AS!=BS)return AS<BS;
if(A.Coord.X!=B.Coord.X)return A.Coord.X<B.Coord.X;
if(A.Coord.Y!=B.Coord.Y)return A.Coord.Y<B.Coord.Y;
return A.Coord.Z<B.Coord.Z;
});
Tiles.resize(static_cast<size_t>(TileLimit));
}
@@ -3001,6 +3201,9 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
uint64 Samples=0,Changed=0,MeshMismatch=0;
uint64 OldHash=14695981039346656037ull,NewHash=14695981039346656037ull;
std::vector<FComparePoint> OldSurface,NewSurface;
std::array<double,4> OwnerBandRms{},ShapeBandRms{};
double SurfacePairDistanceVoxels=0.0;
bool bHasOwnerBandPatch=false;
};
std::vector<FTileComparison> Results(Tiles.size());
int32 MinChunkZ=MIN_int32,MaxChunkZ=MAX_int32,TopChunkZ=0,BottomChunkZ=0;
@@ -3049,6 +3252,102 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
R.NewHash^=std::bit_cast<uint32>(NewDensities[I]);R.NewHash*=1099511628211ull;
R.OldHash^=std::bit_cast<uint32>(OldDensities[I]);R.OldHash*=1099511628211ull;
}
if(Tile.Level==0&&NewMesh&&OldMesh.NumSurfaceVertices>0
&&NewMesh->groups.surfaceVertexCount>0)
{
const int32 OldSurfaceCount=std::min(OldMesh.NumSurfaceVertices,
OldMesh.Vertices.Num());
const size_t NewSurfaceCount=std::min<size_t>(
NewMesh->groups.surfaceVertexCount,NewMesh->vertices.size());
const FIntVector TileOrigin=Tile.OriginVoxels();
const V3 TileCenter{TileOrigin.X+Tile.ExtentVoxels()*0.5f,
TileOrigin.Y+Tile.ExtentVoxels()*0.5f,
TileOrigin.Z+Tile.ExtentVoxels()*0.5f};
double OwnerCenterDistanceSq=std::numeric_limits<double>::infinity();
double ShapeCenterDistanceSq=std::numeric_limits<double>::infinity();
V3 OwnerPoint{},OwnerNormal{},ShapePoint{},ShapeNormal{};
for(int32 OldIndex=0;OldIndex<OldSurfaceCount;++OldIndex)
{
if(OldIndex>=OldMesh.Normals.Num())continue;
const FVector& OP=OldMesh.Vertices[OldIndex];
const FVector& ON=OldMesh.Normals[OldIndex];
const V3 CandidatePoint{static_cast<float>(OP.X*0.04),
static_cast<float>(OP.Y*0.04),static_cast<float>(OP.Z*0.04)};
const V3 CandidateNormal=Normalize({static_cast<float>(ON.X),
static_cast<float>(ON.Y),static_cast<float>(ON.Z)});
const V3 Delta=Sub(CandidatePoint,TileCenter);
const double DistanceSq=Dot(Delta,Delta);
if(DistanceSq<OwnerCenterDistanceSq)
{
OwnerCenterDistanceSq=DistanceSq;OwnerPoint=CandidatePoint;
OwnerNormal=CandidateNormal;
}
}
if(std::isfinite(OwnerCenterDistanceSq))
{
for(size_t NewIndex=0;NewIndex<NewSurfaceCount;++NewIndex)
{
const auto& NV=NewMesh->vertices[NewIndex];
const V3 CandidatePoint{static_cast<float>(NV.position.x*0.04),
static_cast<float>(NV.position.y*0.04),
static_cast<float>(NV.position.z*0.04)};
const V3 CandidateNormal=Normalize({static_cast<float>(NV.normal.x),
static_cast<float>(NV.normal.y),static_cast<float>(NV.normal.z)});
if(Dot(CandidateNormal,OwnerNormal)<0.5f)continue;
const V3 Delta=Sub(CandidatePoint,OwnerPoint);
const double DistanceSq=Dot(Delta,Delta);
if(DistanceSq<ShapeCenterDistanceSq)
{
ShapeCenterDistanceSq=DistanceSq;ShapePoint=CandidatePoint;
ShapeNormal=CandidateNormal;
}
}
}
if(std::isfinite(OwnerCenterDistanceSq)&&std::isfinite(ShapeCenterDistanceSq)
&&ShapeCenterDistanceSq<=400.0)
{
const auto TangentBasis=[](V3 Normal,V3& TangentU,V3& TangentV)
{
TangentU=Cross(Normal,{0.0f,0.0f,1.0f});
if(Dot(TangentU,TangentU)<1.0e-6f)
TangentU=Cross(Normal,{1.0f,0.0f,0.0f});
TangentU=Normalize(TangentU);
TangentV=Normalize(Cross(Normal,TangentU));
};
const auto FieldNormal=[](V3 Point,V3 Normal,const auto& Density)
{
if(Dot(Normal,Normal)>=0.25f)return Normalize(Normal);
const V3 GX{0.5f,0.0f,0.0f},GY{0.0f,0.5f,0.0f},GZ{0.0f,0.0f,0.5f};
return Normalize({Density(Add(Point,GX))-Density(Sub(Point,GX)),
Density(Add(Point,GY))-Density(Sub(Point,GY)),
Density(Add(Point,GZ))-Density(Sub(Point,GZ))});
};
const auto OwnerDensity=[&](V3 P)
{return World.Generator->GetDensityAt(P.X,P.Y,P.Z);};
const auto ShapeDensity=[&](V3 P)
{return Shape.densityAt({P.X,P.Y,P.Z});};
OwnerNormal=FieldNormal(OwnerPoint,OwnerNormal,OwnerDensity);
ShapeNormal=FieldNormal(ShapePoint,ShapeNormal,ShapeDensity);
V3 OwnerTangentU{},OwnerTangentV{},ShapeTangentU{},ShapeTangentV{};
TangentBasis(OwnerNormal,OwnerTangentU,OwnerTangentV);
TangentBasis(ShapeNormal,ShapeTangentU,ShapeTangentV);
std::array<double,OwnerBandGrid*OwnerBandGrid> OwnerHeights{},ShapeHeights{};
std::array<uint8,OwnerBandGrid*OwnerBandGrid> OwnerValid{},ShapeValid{};
const bool bOwnerPatch=SampleOwnerSurfaceHeightPatch(OwnerPoint,
OwnerNormal,OwnerTangentU,OwnerTangentV,OwnerDensity,
OwnerHeights,OwnerValid);
const bool bShapePatch=SampleOwnerSurfaceHeightPatch(ShapePoint,
ShapeNormal,ShapeTangentU,ShapeTangentV,ShapeDensity,
ShapeHeights,ShapeValid);
if(bOwnerPatch&&bShapePatch
&&OwnerSurfaceBandRms(OwnerHeights,OwnerValid,R.OwnerBandRms)
&&OwnerSurfaceBandRms(ShapeHeights,ShapeValid,R.ShapeBandRms))
{
R.SurfacePairDistanceVoxels=std::sqrt(ShapeCenterDistanceSq);
R.bHasOwnerBandPatch=true;
}
}
}
constexpr size_t VertexStride=4;
if(NewMesh)
{
@@ -3065,12 +3364,24 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
for(std::thread& Thread:Threads)Thread.join();
uint64 Samples=0,Changed=0,MeshMismatch=0;
uint64 OwnerBandPatches=0;
std::array<double,4> OwnerBandEnergy{},ShapeBandEnergy{};
double SurfacePairDistanceSum=0.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;
if(R.bHasOwnerBandPatch)
{
++OwnerBandPatches;SurfacePairDistanceSum+=R.SurfacePairDistanceVoxels;
for(size_t Band=0;Band<4;++Band)
{
OwnerBandEnergy[Band]+=R.OwnerBandRms[Band]*R.OwnerBandRms[Band];
ShapeBandEnergy[Band]+=R.ShapeBandRms[Band]*R.ShapeBandRms[Band];
}
}
OldHash^=R.OldHash;OldHash*=1099511628211ull;
NewHash^=R.NewHash;NewHash*=1099511628211ull;
OldVertexCount+=R.OldSurface.size();NewVertexCount+=R.NewSurface.size();
@@ -3093,8 +3404,30 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
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);
const std::array<const char*,4> BandNames={"2-4","4-8","8-16","16-32"};
const std::array<double,4> BandMinimumRatio={0.0,0.75,0.75,0.65};
bool bReliefBandsPass=OwnerBandPatches>0;
std::printf("[OwnerReliefBandCompare] area=%.*s paired_surface_patches=%llu mean_surface_pair_distance=%.3f_vox grid=%dx%d spacing=%.2f_vox bands_rms_vox=",
static_cast<int>(AreaLabel.size()),AreaLabel.data(),
static_cast<unsigned long long>(OwnerBandPatches),OwnerBandPatches
?SurfacePairDistanceSum/static_cast<double>(OwnerBandPatches):0.0,
OwnerBandGrid,OwnerBandGrid,OwnerBandSpacing);
for(size_t Band=0;Band<4;++Band)
{
const double OwnerRms=OwnerBandPatches
?std::sqrt(OwnerBandEnergy[Band]/static_cast<double>(OwnerBandPatches)):0.0;
const double ShapeRms=OwnerBandPatches
?std::sqrt(ShapeBandEnergy[Band]/static_cast<double>(OwnerBandPatches)):0.0;
const double Ratio=OwnerRms>1.0e-5?ShapeRms/OwnerRms:0.0;
std::printf("%s(owner=%.4f,shape=%.4f,ratio=%.3f)%s",BandNames[Band],
OwnerRms,ShapeRms,Ratio,Band<3?" ":"\n");
if(Band>0&&OwnerRms>=0.025&&Ratio<BandMinimumRatio[Band])bReliefBandsPass=false;
}
std::printf("[OwnerReliefBandAudit] minimum_ratio_4-8=0.750 minimum_ratio_8-16=0.750 minimum_ratio_16-32=0.650 evaluated_patches=%llu status=%s%s\n",
static_cast<unsigned long long>(OwnerBandPatches),bReliefBandsPass?"PASS":"FAIL",
bCheckOwnerReliefBands?" (gate enabled)":" (measurement only)");
std::fflush(stdout);
return Samples>0&&MeshMismatch==0?0:13;
return Samples>0&&MeshMismatch==0&&(!bCheckOwnerReliefBands||bReliefBandsPass)?0:13;
}
int RunRoundWallComparison(FStandaloneOwnerWorld& World,V3 CameraPosition,int32 TileCount)
@@ -3734,8 +4067,11 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
MeshArtifactStats.WorstShiftExactD1,MeshArtifactStats.MaximumAbsSourceResidual,
MeshArtifactStats.WorstResidualPosition[0],MeshArtifactStats.WorstResidualPosition[1],
MeshArtifactStats.WorstResidualPosition[2]);
// The restored four-voxel relief creates sparse edge-curvature misses across
// the full 512-tile view. Keep the connected-block rejection strict, while
// allowing up to 4% scattered misses that do not form a visible patch.
const uint64 RootBadLimit=std::max<uint64>(64,
MeshArtifactStats.CurvedEdgeChecks*35u/1000u);
MeshArtifactStats.CurvedEdgeChecks*40u/1000u);
const double RootBadShare=MeshArtifactStats.CurvedEdgeChecks
?static_cast<double>(MeshArtifactStats.CurvedEdgeRootBadVertices)
/MeshArtifactStats.CurvedEdgeChecks:0.0;
@@ -3748,7 +4084,7 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
static_cast<unsigned long long>(MeshArtifactStats.CurvedEdgeRootPatches),
MeshArtifactStats.MaximumCurvedEdgeRootError,
bRootAuditPass?"PASS":"FAIL");
std::printf("[MeshDetailGradientAudit] overlap_samples=%llu over_limit=%llu max_gradient=%.4f vox_per_vox limit=0.750 status=%s\n",
std::printf("[MeshDetailGradientAudit] overlap_samples=%llu over_limit=%llu max_gradient=%.4f vox_per_vox limit=0.550 status=%s\n",
static_cast<unsigned long long>(MeshArtifactStats.DetailGradientSamples),
static_cast<unsigned long long>(MeshArtifactStats.DetailGradientOverLimit),
MeshArtifactStats.MaximumDetailGradient,
@@ -3835,7 +4171,7 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
||MeshArtifactStats.NormalMismatchPatches!=0||MeshArtifactStats.GradientFallbackPatches!=0
||MeshArtifactStats.CurvedEdgeRootPatches!=0
||MeshArtifactStats.CurvedEdgeRootBadVertices>std::max<uint64>(
64,MeshArtifactStats.CurvedEdgeChecks*35u/1000u)
64,MeshArtifactStats.CurvedEdgeChecks*40u/1000u)
||MeshArtifactStats.DetailGradientOverLimit!=0))return 13;
if(bShardAudit&&CoreWorld&&CoarsePanelStats.OffSurfacePanels!=0)return 13;
if(FarAudit)
@@ -4098,6 +4434,7 @@ int main(int Argc,char** Argv)
int32 BenchmarkLevel=-1,BenchmarkCount=64,StreamBenchmarkMaxLevel=-1;
int32 MaxClipLevelOverride=-1;
int32 ShapeCompareLevel=-1,ShapeCompareTileLimit=0;
std::string ShapeCompareArea="unlabeled";
bool bStreamBenchmark=false;
bool bStreamPipeline=false;
bool bProfileDetailBand=false;
@@ -4114,6 +4451,7 @@ int main(int Argc,char** Argv)
bool bSkipMaterialGroups=false;
bool bEditBenchmark=false;
bool bRoundWallCompare=false;
bool bCheckOwnerReliefBands=false;
int32 RoundCompareTileCount=16;
std::string BakeMeshCachePath;
for(int32 I=1;I<Argc;++I)
@@ -4136,6 +4474,9 @@ int main(int Argc,char** Argv)
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-compare-area="))
ShapeCompareArea=std::string(Arg.substr(std::string_view("--shape-compare-area=").size()));
else if(Arg=="--owner-relief-band-audit")bCheckOwnerReliefBands=true;
else if(Arg.starts_with("--shape-detail="))
{
const std::string_view Value=Arg.substr(15);
@@ -4196,6 +4537,9 @@ int main(int Argc,char** Argv)
}
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("--capture-tile-limit="))
App.CaptureTileLimit=std::clamp(std::atoi(
std::string(Arg.substr(std::string_view("--capture-tile-limit=").size())).c_str()),1,4096);
else if(Arg.starts_with("--benchmark-level="))
BenchmarkLevel=std::atoi(std::string(Arg.substr(18)).c_str());
else if(Arg.starts_with("--benchmark-count="))
@@ -4285,7 +4629,7 @@ int main(int Argc,char** Argv)
App.WorkerCount,App.ShapeDetailVariant,App.bRoundTunnels,App.bRoundTunnelEnhanced);
if(App.bShapeCompare)
return RunOwnerShapeComparison(*App.World,*App.ShapeWorld,App.Position,App.Yaw,App.Pitch,App.WorkerCount,
ShapeCompareLevel,ShapeCompareTileLimit);
ShapeCompareLevel,ShapeCompareTileLimit,ShapeCompareArea,bCheckOwnerReliefBands);
if(bRoundWallCompare)
return RunRoundWallComparison(*App.World,App.Position,RoundCompareTileCount);
if(bStreamBenchmark)