Files
VoxelForge/Standalone/Source/FarSurvivalAudit.h
T
Fr0zkaandClaude Opus 5.5 a40b0454b3 standalone: far-survival audit + LOD8 default with explicit far shells; neighbourhood-first viewer
- FarSurvivalAudit: checks every stratum top/bottom boundary (4x4 columns per far tile) and every
  in-view far connector (sections <=128 voxels, 4 radial rays) against the emitted mesh. Falsified:
  removing one tile's shell triangles -> 16 missing, 16 holes, exit 14. Passes at LOD6/7/8.
- Far connectors follow source radial crossings; strata patches emitted at LOD7/8. Default LOD8.
- Viewer: separate near ShapeWorld for the 27 player tiles, full world built on its own thread;
  27/27 tiles hash-identical to the direct build. W1/W8 60 hash fields equal; routes 5642/5642.
Cost, measured by me head to head (8 workers, cold, BelowNormal 0xFF00): LOD8 without the shells
1.17-1.25 s, LOD8 with the shells 1.80 s; payload 92.6 -> 276.7 MiB. The shells are far too
expensive, and the pipelined benchmark whole view is 2.92 s (shape compiled twice): both to fix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
2026-10-04 22:51:44 +02:00

524 lines
25 KiB
C++

#pragma once
// Standalone-only proof that far meshes retain the owner's strata-boundary shells and
// generated far-parent connectors. This deliberately compares the dense source field with
// vertical/radial intersections against the triangles that were actually emitted.
struct FFarSurvivalAudit
{
struct FBoundary
{
double Z=0.0;
double HalfWidth=16.0;
};
struct FConnectorSample
{
size_t ConnectorIndex=0;
V3 Center{},U{},V{};
float Radius=0.0f;
uint8 DirectionMask=0;
};
struct FCounts
{
uint64 BoundaryColumns=0,BoundaryReferenceSurfaces=0;
uint64 BoundaryMissing=0,BoundaryExtra=0,BoundaryHoles=0;
uint64 ConnectorsInView=0,ConnectorSamples=0,ConnectorRays=0;
uint64 ConnectorReferenceSurfaces=0,ConnectorRayMissing=0,ConnectorGoodSamples=0,ConnectorMissing=0;
uint64 ConnectorVisibleSamples=0,ConnectorNoSurfaceSamples=0,ConnectorSampleMissing=0;
uint64 ConnectorExtra=0,ConnectorHoles=0,ConnectorUnsampled=0;
uint64 CorruptedTriangles=0;
};
FFarSurvivalAudit(FStandaloneOwnerWorld& InWorld,const FStandaloneShapeWorld& InShape,
const std::vector<FVoxelTileKey>& DesiredTiles,bool bInDropOneShell)
:World(InWorld),Shape(InShape),bDropOneShell(bInDropOneShell)
{
const auto& Layout=World.Manager->GetLayout();
for(const FStrateSlot& Slot:Layout)
{
const FIntVector Mid(0,0,(Slot.TopChunkZ+Slot.BottomChunkZ)/2);
const FStrateGenerationParams Params=World.Manager->GetGenerationParams(Mid);
const double HalfWidth=std::max(8.0,static_cast<double>(Params.BoundarySealThickness)+8.0);
AddBoundary(static_cast<double>(Slot.BottomChunkZ)*CHUNK_SIZE,HalfWidth);
AddBoundary(static_cast<double>(Slot.TopChunkZ+1)*CHUNK_SIZE,HalfWidth);
}
std::sort(Boundaries.begin(),Boundaries.end(),[](const FBoundary& A,const FBoundary& B)
{return A.Z<B.Z;});
BuildConnectorSamples(DesiredTiles);
}
bool AuditTile(FStandaloneCpuTile& Tile)
{
if(Tile.Key.Level<AuditMinLevel)return true;
const FIntVector Origin=Tile.Key.OriginVoxels();
const int32 Extent=Tile.Key.ExtentVoxels();
const int32 Cells=std::max(1,World.CoarseTileCells);
const double Step=static_cast<double>(std::max(1,Extent/Cells));
if(bDropOneShell&&!bCorruptionApplied)
TryDropShellInTile(Tile,Origin,Extent,Step);
FMeshColumnIndex MeshIndex(Tile,Origin,Extent,Cells,Step);
static constexpr int32 ColumnOffsets[4]={1,5,9,13};
for(const FBoundary& Boundary:Boundaries)
{
if(Boundary.Z+Boundary.HalfWidth<Origin.Z
||Boundary.Z-Boundary.HalfWidth>Origin.Z+Extent)continue;
for(const int32 CY:ColumnOffsets)for(const int32 CX:ColumnOffsets)
{
const double X=Origin.X+(CX+0.371)*Step;
const double Y=Origin.Y+(CY+0.619)*Step;
const std::vector<double> Source=FindVerticalCrossings(X,Y,
Boundary.Z-Boundary.HalfWidth,Boundary.Z+Boundary.HalfWidth,1.0);
if(Source.empty())continue;
++Counts.BoundaryColumns;
Counts.BoundaryReferenceSurfaces+=Source.size();
CompareVerticalColumn(MeshIndex,X,Y,Boundary.Z-Boundary.HalfWidth,
Boundary.Z+Boundary.HalfWidth,Source,Step,
Counts.BoundaryMissing,Counts.BoundaryExtra,Counts.BoundaryHoles);
}
}
const auto Samples=ConnectorSamples.find(Tile.Key);
if(Samples!=ConnectorSamples.end())
{
for(const FConnectorSample& Sample:Samples->second)
AuditConnectorSample(Tile,Sample,Step,MeshIndex);
}
return true;
}
bool Passing() const
{
return bCorruptionApplied==bDropOneShell
&&Counts.BoundaryReferenceSurfaces>0
&&Counts.ConnectorsInView>0&&Counts.ConnectorSamples>0
&&Counts.BoundaryMissing==0&&Counts.BoundaryHoles==0
&&Counts.ConnectorMissing==0&&Counts.ConnectorSampleMissing==0
&&Counts.ConnectorHoles==0
&&Counts.ConnectorUnsampled==0;
}
void Print() const
{
std::printf("[FarSurvivalAudit] boundaries=%zu boundary_columns=%llu boundary_surfaces=%llu boundary_missing=%llu boundary_extra=%llu boundary_holes=%llu far_connectors=%llu connector_samples=%llu connector_visible_samples=%llu connector_no_surface_samples=%llu connector_sample_missing=%llu connector_good_connectors=%llu connector_rays=%llu connector_surfaces=%llu connector_ray_missing=%llu connector_missing=%llu connector_extra=%llu connector_holes=%llu connector_unsampled=%llu bad_shell_removed_triangles=%llu status=%s\n",
Boundaries.size(),static_cast<unsigned long long>(Counts.BoundaryColumns),
static_cast<unsigned long long>(Counts.BoundaryReferenceSurfaces),
static_cast<unsigned long long>(Counts.BoundaryMissing),
static_cast<unsigned long long>(Counts.BoundaryExtra),
static_cast<unsigned long long>(Counts.BoundaryHoles),
static_cast<unsigned long long>(Counts.ConnectorsInView),
static_cast<unsigned long long>(Counts.ConnectorSamples),
static_cast<unsigned long long>(Counts.ConnectorVisibleSamples),
static_cast<unsigned long long>(Counts.ConnectorNoSurfaceSamples),
static_cast<unsigned long long>(Counts.ConnectorSampleMissing),
static_cast<unsigned long long>(Counts.ConnectorGoodSamples),
static_cast<unsigned long long>(Counts.ConnectorRays),
static_cast<unsigned long long>(Counts.ConnectorReferenceSurfaces),
static_cast<unsigned long long>(Counts.ConnectorRayMissing),
static_cast<unsigned long long>(Counts.ConnectorMissing),
static_cast<unsigned long long>(Counts.ConnectorExtra),
static_cast<unsigned long long>(Counts.ConnectorHoles),
static_cast<unsigned long long>(Counts.ConnectorUnsampled),
static_cast<unsigned long long>(Counts.CorruptedTriangles),Passing()?"PASS":"FAIL");
std::fflush(stdout);
}
private:
struct FMeshColumnIndex
{
FMeshColumnIndex(const FStandaloneCpuTile& InTile,const FIntVector& InOrigin,
int32 InExtent,int32 InCells,double InStep)
:Tile(InTile),Origin(InOrigin),Extent(InExtent),Cells(InCells),Step(InStep),
ByColumn(static_cast<size_t>(InCells)*InCells)
{
const size_t TriangleCount=Tile.Indices.size()/3;
for(size_t Triangle=0;Triangle<TriangleCount;++Triangle)
{
uint32 A=Tile.Indices[Triangle*3],B=Tile.Indices[Triangle*3+1],C=Tile.Indices[Triangle*3+2];
if(A>=Tile.Vertices.size()||B>=Tile.Vertices.size()||C>=Tile.Vertices.size())continue;
const double MinX=std::min({PositionAxis(A,0),PositionAxis(B,0),PositionAxis(C,0)});
const double MaxX=std::max({PositionAxis(A,0),PositionAxis(B,0),PositionAxis(C,0)});
const double MinY=std::min({PositionAxis(A,1),PositionAxis(B,1),PositionAxis(C,1)});
const double MaxY=std::max({PositionAxis(A,1),PositionAxis(B,1),PositionAxis(C,1)});
const int32 X0=std::clamp(static_cast<int32>(std::floor((MinX-Origin.X)/Step)),0,Cells-1);
const int32 X1=std::clamp(static_cast<int32>(std::floor((MaxX-Origin.X)/Step)),0,Cells-1);
const int32 Y0=std::clamp(static_cast<int32>(std::floor((MinY-Origin.Y)/Step)),0,Cells-1);
const int32 Y1=std::clamp(static_cast<int32>(std::floor((MaxY-Origin.Y)/Step)),0,Cells-1);
for(int32 Y=Y0;Y<=Y1;++Y)for(int32 X=X0;X<=X1;++X)
ByColumn[static_cast<size_t>(Y)*Cells+X].push_back(static_cast<uint32>(Triangle));
}
}
std::vector<double> VerticalHits(double X,double Y,double ZMin,double ZMax) const
{
const int32 CX=std::clamp(static_cast<int32>(std::floor((X-Origin.X)/Step)),0,Cells-1);
const int32 CY=std::clamp(static_cast<int32>(std::floor((Y-Origin.Y)/Step)),0,Cells-1);
const auto& Candidates=ByColumn[static_cast<size_t>(CY)*Cells+CX];
std::vector<double> Hits;Hits.reserve(8);
for(const uint32 Triangle:Candidates)
{
const uint32 IA=Tile.Indices[static_cast<size_t>(Triangle)*3];
const uint32 IB=Tile.Indices[static_cast<size_t>(Triangle)*3+1];
const uint32 IC=Tile.Indices[static_cast<size_t>(Triangle)*3+2];
const double AX=PositionAxis(IA,0),AY=PositionAxis(IA,1),AZ=PositionAxis(IA,2);
const double BX=PositionAxis(IB,0),BY=PositionAxis(IB,1),BZ=PositionAxis(IB,2);
const double CXv=PositionAxis(IC,0),CYv=PositionAxis(IC,1),CZ=PositionAxis(IC,2);
const double Den=(BX-AX)*(CYv-AY)-(BY-AY)*(CXv-AX);
if(std::abs(Den)<1.0e-10)continue;
const double U=((X-AX)*(CYv-AY)-(Y-AY)*(CXv-AX))/Den;
const double V=((BX-AX)*(Y-AY)-(BY-AY)*(X-AX))/Den;
const double W=1.0-U-V;
if(U<-1.0e-7||V<-1.0e-7||W<-1.0e-7)continue;
const double Z=AZ+U*(BZ-AZ)+V*(CZ-AZ);
if(Z>=ZMin-1.0e-5&&Z<=ZMax+1.0e-5)Hits.push_back(Z);
}
SortUnique(Hits,0.25);
return Hits;
}
std::vector<double> SegmentHits(V3 Start,V3 Direction,double Length) const
{
std::vector<double> Hits;Hits.reserve(8);
const size_t TriangleCount=Tile.Indices.size()/3;
for(size_t Triangle=0;Triangle<TriangleCount;++Triangle)
{
const uint32 IA=Tile.Indices[Triangle*3],IB=Tile.Indices[Triangle*3+1],IC=Tile.Indices[Triangle*3+2];
if(IA>=Tile.Vertices.size()||IB>=Tile.Vertices.size()||IC>=Tile.Vertices.size())continue;
const V3 A=PositionVector(IA),B=PositionVector(IB),C=PositionVector(IC);
const V3 E1=Sub(B,A),E2=Sub(C,A),P=Cross(Direction,E2);
const float Determinant=Dot(E1,P);
if(std::abs(Determinant)<1.0e-8f)continue;
const float Inv=1.0f/Determinant;
const V3 T=Sub(Start,A);
const float U=Dot(T,P)*Inv;
if(U<-1.0e-5f||U>1.00001f)continue;
const V3 Q=Cross(T,E1);
const float V=Dot(Direction,Q)*Inv;
if(V<-1.0e-5f||U+V>1.00001f)continue;
const float Distance=Dot(E2,Q)*Inv;
if(Distance>=-1.0e-4f&&Distance<=Length+1.0e-4f)
Hits.push_back(std::clamp(static_cast<double>(Distance),0.0,Length));
}
SortUnique(Hits,0.25);
return Hits;
}
private:
V3 PositionVector(uint32 Index) const
{return {Tile.Vertices[Index].Position[0]*4.0f,Tile.Vertices[Index].Position[1]*4.0f,
Tile.Vertices[Index].Position[2]*4.0f};}
double PositionAxis(uint32 Index,int32 Axis) const
{return static_cast<double>(Tile.Vertices[Index].Position[Axis])*4.0;}
static void SortUnique(std::vector<double>& Values,double Epsilon)
{
std::sort(Values.begin(),Values.end());
Values.erase(std::unique(Values.begin(),Values.end(),[&](double A,double B)
{return std::abs(A-B)<=Epsilon;}),Values.end());
}
const FStandaloneCpuTile& Tile;
FIntVector Origin;
int32 Extent=0,Cells=0;
double Step=1.0;
std::vector<std::vector<uint32>> ByColumn;
};
static V3 AddV(V3 A,V3 B){return {A.X+B.X,A.Y+B.Y,A.Z+B.Z};}
static V3 MulV(V3 A,float S){return {A.X*S,A.Y*S,A.Z*S};}
static float LengthV(V3 A){return std::sqrt(std::max(0.0f,Dot(A,A)));}
static void SortUnique(std::vector<double>& Values,double Epsilon)
{
std::sort(Values.begin(),Values.end());
Values.erase(std::unique(Values.begin(),Values.end(),[&](double A,double B)
{return std::abs(A-B)<=Epsilon;}),Values.end());
}
void AddBoundary(double Z,double HalfWidth)
{
for(FBoundary& Existing:Boundaries)
if(std::abs(Existing.Z-Z)<0.01)
{Existing.HalfWidth=std::max(Existing.HalfWidth,HalfWidth);return;}
Boundaries.push_back({Z,HalfWidth});
}
static V3 ToV3(const FVector& P)
{return {static_cast<float>(P.X),static_cast<float>(P.Y),static_cast<float>(P.Z)};}
static bool Contains(const FVoxelTileKey& Key,V3 P,double Margin=0.0)
{
const FIntVector Origin=Key.OriginVoxels();
const int32 Extent=Key.ExtentVoxels();
return P.X>=Origin.X+Margin&&P.X<=Origin.X+Extent-Margin
&&P.Y>=Origin.Y+Margin&&P.Y<=Origin.Y+Extent-Margin
&&P.Z>=Origin.Z+Margin&&P.Z<=Origin.Z+Extent-Margin;
}
void BuildConnectorSamples(const std::vector<FVoxelTileKey>& DesiredTiles)
{
const auto& Connectors=Shape.GetFarConnectorSegments();
ConnectorInView.assign(Connectors.size(),0);
ConnectorHasSamples.assign(Connectors.size(),0);
ConnectorHasGoodSample.assign(Connectors.size(),0);
std::vector<FVoxelTileKey> FarTiles;
for(const FVoxelTileKey& Key:DesiredTiles)
if(Key.Level>=AuditMinLevel)FarTiles.push_back(Key);
for(size_t I=0;I<Connectors.size();++I)
{
const auto& Connector=Connectors[I];
const V3 A=ToV3(Connector.Start),B=ToV3(Connector.End);
const V3 Delta=Sub(B,A);const float Length=LengthV(Delta);
if(Length<=2.0f*Connector.Radius)continue;
const V3 Axis=MulV(Delta,1.0f/Length);
V3 U=Cross(Axis,{0,0,1});
if(LengthV(U)<0.1f)U=Cross(Axis,{0,1,0});
U=Normalize(U);const V3 V=Normalize(Cross(Axis,U));
bool bInsideView=false;
const int32 SampleCount=std::max(1,static_cast<int32>(std::ceil(Length/128.0f)));
for(int32 S=0;S<SampleCount;++S)
{
const float T=(static_cast<float>(S)+0.5f)/SampleCount;
const V3 Center=AddV(A,MulV(Delta,T));
for(const FVoxelTileKey& Key:FarTiles)
{
if(!Contains(Key,Center))continue;
bInsideView=true;
uint8 Mask=0;
const V3 Directions[4]={U,MulV(U,-1.0f),V,MulV(V,-1.0f)};
const double RayLength=Connector.Radius*2.0+32.0;
for(int32 D=0;D<4;++D)
if(Contains(Key,AddV(Center,MulV(Directions[D],static_cast<float>(RayLength)))))
Mask|=static_cast<uint8>(1u<<D);
if(!Mask)continue;
ConnectorSamples[Key].push_back({I,Center,U,V,Connector.Radius,Mask});
ConnectorHasSamples[I]=1;break;
}
}
ConnectorInView[I]=bInsideView?1:0;
}
}
public:
void Finalize()
{
Counts.ConnectorsInView=0;
Counts.ConnectorUnsampled=0;
Counts.ConnectorMissing=0;
Counts.ConnectorGoodSamples=0;
for(size_t I=0;I<ConnectorInView.size();++I)
{
if(!ConnectorInView[I])continue;
++Counts.ConnectorsInView;
if(!ConnectorHasSamples[I])++Counts.ConnectorUnsampled;
if(!ConnectorHasGoodSample[I])++Counts.ConnectorMissing;
else ++Counts.ConnectorGoodSamples;
}
}
private:
std::vector<double> FindVerticalCrossings(double X,double Y,double ZMin,double ZMax,
double Stride) const
{
std::vector<double> Crossings;
if(ZMax<=ZMin)return Crossings;
double PreviousZ=ZMin;
bool PreviousSolid=Shape.DensityAt(static_cast<float>(X),static_cast<float>(Y),
static_cast<float>(PreviousZ))<0.0f;
for(double Z=std::min(ZMin+Stride,ZMax);Z<=ZMax+1.0e-6;Z=std::min(Z+Stride,ZMax))
{
const bool Solid=Shape.DensityAt(static_cast<float>(X),static_cast<float>(Y),
static_cast<float>(Z))<0.0f;
if(Solid!=PreviousSolid)
{
double Lo=PreviousZ,Hi=Z;const bool LoSolid=PreviousSolid;
for(int32 I=0;I<6;++I)
{
const double Mid=(Lo+Hi)*0.5;
const bool MidSolid=Shape.DensityAt(static_cast<float>(X),static_cast<float>(Y),
static_cast<float>(Mid))<0.0f;
if(MidSolid==LoSolid)Lo=Mid;else Hi=Mid;
}
Crossings.push_back((Lo+Hi)*0.5);
}
if(Z>=ZMax-1.0e-6)break;
PreviousZ=Z;PreviousSolid=Solid;
}
return Crossings;
}
void CompareVerticalColumn(const FMeshColumnIndex& MeshIndex,double X,double Y,
double ZMin,double ZMax,const std::vector<double>& Source,double Step,
uint64& Missing,uint64& Extra,uint64& Holes)
{
std::vector<double> Mesh=MeshIndex.VerticalHits(X,Y,ZMin,ZMax);
const double Tolerance=std::max(4.0,Step*1.5);
std::vector<uint8> Used(Mesh.size(),0);
uint64 ColumnMissing=0,ColumnExtra=0;
for(const double Crossing:Source)
{
size_t Best=Mesh.size();double BestDistance=std::numeric_limits<double>::infinity();
for(size_t I=0;I<Mesh.size();++I)
if(!Used[I]&&std::abs(Mesh[I]-Crossing)<BestDistance)
{Best=I;BestDistance=std::abs(Mesh[I]-Crossing);}
if(Best==Mesh.size()||BestDistance>Tolerance)++ColumnMissing;
else Used[Best]=1;
}
for(const uint8 WasUsed:Used)if(!WasUsed)++ColumnExtra;
Missing+=ColumnMissing;Extra+=ColumnExtra;
if(ColumnMissing)
{
if((ColumnMissing&1u)!=0)++Holes;
if(DebugColumns<6)
{
std::printf("[FarAuditColumnMismatch] xy=(%.1f,%.1f) z=(%.1f..%.1f) source=%zu mesh=%zu first=(%.1f,%.1f) counts=(%llu,%llu)\n",
X,Y,ZMin,ZMax,Source.size(),Mesh.size(),Source.empty()?0.0:Source.front(),
Mesh.empty()?0.0:Mesh.front(),static_cast<unsigned long long>(ColumnMissing),
static_cast<unsigned long long>(ColumnExtra));
++DebugColumns;
}
}
}
void AuditConnectorSample(const FStandaloneCpuTile& Tile,const FConnectorSample& Sample,
double Step,const FMeshColumnIndex& MeshIndex)
{
++Counts.ConnectorSamples;
const V3 Directions[4]={Sample.U,MulV(Sample.U,-1.0f),Sample.V,MulV(Sample.V,-1.0f)};
const float RayLength=Sample.Radius*2.0f+32.0f;
const double SourceRadiusBand=std::max(16.0,static_cast<double>(Sample.Radius)*0.75);
uint32 MatchedDirections=0,ReferenceDirections=0;
uint8 ReferenceMask=0,MatchedMask=0;
for(int32 D=0;D<4;++D)
{
if((Sample.DirectionMask&(1u<<D))==0)continue;
++Counts.ConnectorRays;
const V3 Direction=Directions[D];
const std::vector<double> Source=FindRadialCrossings(Sample.Center,Direction,RayLength);
if(Source.empty())continue;
const auto SourceHit=std::min_element(Source.begin(),Source.end(),[&](double A,double B)
{return std::abs(A-Sample.Radius)<std::abs(B-Sample.Radius);});
if(SourceHit==Source.end()||std::abs(*SourceHit-Sample.Radius)>SourceRadiusBand)continue;
++ReferenceDirections;
ReferenceMask|=static_cast<uint8>(1u<<D);
++Counts.ConnectorReferenceSurfaces;
const std::vector<double> Mesh=MeshIndex.SegmentHits(Sample.Center,Direction,RayLength);
const double Tolerance=std::max(8.0,static_cast<double>(Sample.Radius)*0.35);
double BestDistance=std::numeric_limits<double>::infinity();
for(const double Hit:Mesh)
{
const double Distance=std::abs(Hit-*SourceHit);
if(Distance<BestDistance)BestDistance=Distance;
}
if(BestDistance<=Tolerance)
{++MatchedDirections;MatchedMask|=static_cast<uint8>(1u<<D);}
else
{
++Counts.ConnectorRayMissing;
if(DebugConnectorMismatches<24)
{
std::printf("[FarConnectorRayMismatch] connector=%zu tile_lod=%d center=(%.1f,%.1f,%.1f) radius=%.1f direction=%d source=%.2f nearest_mesh=%.2f hits=%zu step=%.1f tolerance=%.1f\n",
Sample.ConnectorIndex,Tile.Key.Level,Sample.Center.X,Sample.Center.Y,Sample.Center.Z,
Sample.Radius,D,*SourceHit,BestDistance,Mesh.size(),Step,Tolerance);
++DebugConnectorMismatches;
}
}
if(Mesh.size()>1)Counts.ConnectorExtra+=Mesh.size()-1;
}
// The source field can expose only one or two radial walls where a link
// joins a parent room. Preserve every source wall at every sampled section;
// require both sides whenever the source itself exposes an opposing pair.
const bool bSourcePair=(ReferenceMask&0x03u)==0x03u||(ReferenceMask&0x0Cu)==0x0Cu;
const bool bMatchedPair=(MatchedMask&0x03u)==0x03u||(MatchedMask&0x0Cu)==0x0Cu;
if(ReferenceDirections==0)
{++Counts.ConnectorNoSurfaceSamples;return;}
++Counts.ConnectorVisibleSamples;
const bool bSectionPassing=ReferenceDirections>0
&&MatchedDirections==ReferenceDirections
&&(!bSourcePair||bMatchedPair);
if(bSectionPassing&&!ConnectorHasGoodSample[Sample.ConnectorIndex])
ConnectorHasGoodSample[Sample.ConnectorIndex]=1;
if(!bSectionPassing)
{
++Counts.ConnectorSampleMissing;
if(MatchedDirections<ReferenceDirections||(bSourcePair&&!bMatchedPair))
++Counts.ConnectorHoles;
}
}
std::vector<double> FindRadialCrossings(V3 Start,V3 Direction,double Length) const
{
std::vector<double> Crossings;
double PreviousDistance=0.0;
bool PreviousSolid=Shape.DensityAt(Start.X,Start.Y,Start.Z)<0.0f;
constexpr double Stride=2.0;
for(double Distance=Stride;Distance<=Length+1.0e-6;Distance=std::min(Distance+Stride,Length))
{
const V3 P=AddV(Start,MulV(Direction,static_cast<float>(Distance)));
const bool Solid=Shape.DensityAt(P.X,P.Y,P.Z)<0.0f;
if(Solid!=PreviousSolid)
{
double Lo=PreviousDistance,Hi=Distance;
for(int32 I=0;I<6;++I)
{
const double Mid=(Lo+Hi)*0.5;
const V3 M=AddV(Start,MulV(Direction,static_cast<float>(Mid)));
const bool MidSolid=Shape.DensityAt(M.X,M.Y,M.Z)<0.0f;
if(MidSolid==PreviousSolid)Lo=Mid;else Hi=Mid;
}
Crossings.push_back((Lo+Hi)*0.5);
}
if(Distance>=Length-1.0e-6)break;
PreviousDistance=Distance;PreviousSolid=Solid;
}
return Crossings;
}
void TryDropShellInTile(FStandaloneCpuTile& Tile,const FIntVector& Origin,int32 Extent,double Step)
{
static constexpr int32 Offsets[4]={1,5,9,13};
for(const FBoundary& Boundary:Boundaries)
{
if(Boundary.Z+Boundary.HalfWidth<Origin.Z
||Boundary.Z-Boundary.HalfWidth>Origin.Z+Extent)continue;
for(const int32 CY:Offsets)for(const int32 CX:Offsets)
{
const double X=Origin.X+(CX+0.371)*Step;
const double Y=Origin.Y+(CY+0.619)*Step;
if(FindVerticalCrossings(X,Y,Boundary.Z-Boundary.HalfWidth,
Boundary.Z+Boundary.HalfWidth,1.0).empty())continue;
const double DropHalfWidth=std::max(Boundary.HalfWidth+4.0,Step*0.75);
std::vector<uint32> Kept;Kept.reserve(Tile.Indices.size());
for(size_t T=0;T+2<Tile.Indices.size();T+=3)
{
const uint32 A=Tile.Indices[T],B=Tile.Indices[T+1],C=Tile.Indices[T+2];
if(A>=Tile.Vertices.size()||B>=Tile.Vertices.size()||C>=Tile.Vertices.size())continue;
const double CenterZ=(Tile.Vertices[A].Position[2]+Tile.Vertices[B].Position[2]
+Tile.Vertices[C].Position[2])*4.0/3.0;
if(std::abs(CenterZ-Boundary.Z)<=DropHalfWidth)
{++Counts.CorruptedTriangles;continue;}
Kept.push_back(A);Kept.push_back(B);Kept.push_back(C);
}
Tile.Indices.swap(Kept);
bCorruptionApplied=Counts.CorruptedTriangles>0;
std::printf("[FarAuditBadSetting] removed stratum shell triangles at z=%.1f vox tile_lod=%d removed=%llu\n",
Boundary.Z,Tile.Key.Level,static_cast<unsigned long long>(Counts.CorruptedTriangles));
std::fflush(stdout);
return;
}
}
}
FStandaloneOwnerWorld& World;
const FStandaloneShapeWorld& Shape;
static constexpr int32 AuditMinLevel=6;
bool bDropOneShell=false,bCorruptionApplied=false;
std::vector<FBoundary> Boundaries;
std::unordered_map<FVoxelTileKey,std::vector<FConnectorSample>,FStandaloneTileKeyHash> ConnectorSamples;
FCounts Counts;
std::vector<uint8> ConnectorInView,ConnectorHasSamples,ConnectorHasGoodSample;
uint32 DebugColumns=0;
uint32 DebugConnectorMismatches=0;
};