#pragma once #include "StreamingWorld.h" #include #include #include #include #include #include #include #include #include #include namespace StandaloneScreenSpaceGate { inline constexpr double OnePixelRadians=1.57079632679489661923/2560.0; struct FErrorRow { int Lod=0; std::uint64_t Samples=0,Missing=0,Floating=0; double P50=0.0,P99=0.0,Maximum=0.0; bool Valid=false; }; struct FPopRow { int CoarseLod=0; std::uint64_t Samples=0,Cracks=0,Floating=0; double P50=0.0,P99=0.0,Maximum=0.0; bool Valid=false; }; struct FHeightGrid { int Side=65; double MinimumX=0.0,MaximumX=0.0,MinimumY=0.0,MaximumY=0.0; std::vector> Hits; FHeightGrid()=default; FHeightGrid(int InSide,double MinX,double MaxX,double MinY,double MaxY) :Side(InSide),MinimumX(MinX),MaximumX(MaxX),MinimumY(MinY),MaximumY(MaxY), Hits(static_cast(InSide)*InSide) {} double X(int Ix) const {return MinimumX+(MaximumX-MinimumX)*Ix/(Side-1);} double Y(int Iy) const {return MinimumY+(MaximumY-MinimumY)*Iy/(Side-1);} void Add(int Ix,int Iy,double Z) { auto& Values=Hits[static_cast(Iy)*Side+Ix]; Values.push_back(Z); } const std::vector& At(int Ix,int Iy) const {return Hits[static_cast(Iy)*Side+Ix];} }; inline double Percentile(std::vector& Values,double P) { if(Values.empty())return 0.0; std::sort(Values.begin(),Values.end()); const std::size_t Index=std::min(Values.size()-1, static_cast(std::ceil(P*Values.size())-1)); return Values[Index]; } inline double TileAabbDistanceMeters(const FVoxelTileKey& Key, const FVector& CameraVoxels,double VoxelMeters) { const double Extent=static_cast(Key.ExtentVoxels()); const double Min[3]={static_cast(Key.OriginVoxels().X), static_cast(Key.OriginVoxels().Y),static_cast(Key.OriginVoxels().Z)}; const double Cam[3]={CameraVoxels.X,CameraVoxels.Y,CameraVoxels.Z}; double Squared=0.0; for(int Axis=0;Axis<3;++Axis) { const double Delta=Cam[Axis]Min[Axis]+Extent?Cam[Axis]-(Min[Axis]+Extent):0.0; Squared+=Delta*Delta; } return std::sqrt(Squared)*VoxelMeters; } inline bool TileHasNativeSurface(const FVoxelTileKey& Key, const VoxelForgeCore::WorldAssemblyManifest& Assembly, double VoxelMeters) { const double Extent=static_cast(Key.ExtentVoxels()); const double X=(static_cast(Key.OriginVoxels().X)+Extent*0.5)*256.0; const double Y=(static_cast(Key.OriginVoxels().Y)+Extent*0.5)*256.0; const double MinZ=static_cast(Key.OriginVoxels().Z); const double MaxZ=MinZ+Extent; for(const auto& Plan:Assembly.plan.strates) { VoxelForgeCore::Heightfield2D Field(Assembly.recipe,Plan); if(!Field.isValid())continue; const auto Column=Field.point(static_cast(std::llround(X)), static_cast(std::llround(Y)),0); const double Ground=static_cast(Column.groundHeightSubvoxels)/256.0; const double Cap=static_cast(Column.capHeightSubvoxels)/256.0; const double Margin=std::max(2.0,0.25/VoxelMeters); if(Ground=MinZ-Margin&&Ground<=MaxZ+Margin) ||(Cap>=MinZ-Margin&&Cap<=MaxZ+Margin)))return true; } return false; } inline FHeightGrid Rasterize(std::span Tiles, const FVoxelTileKey& Patch,double VoxelMeters,int Side=65) { const double Extent=static_cast(Patch.ExtentVoxels())*VoxelMeters; const double MinX=static_cast(Patch.OriginVoxels().X)*VoxelMeters; const double MinY=static_cast(Patch.OriginVoxels().Y)*VoxelMeters; FHeightGrid Grid(Side,MinX,MinX+Extent,MinY,MinY+Extent); for(const FStandaloneCpuTile* Tile:Tiles) { if(!Tile||Tile->Indices.size()<3)continue; const std::size_t TriangleCount=Tile->Indices.size()/3; for(std::size_t Triangle=0;TriangleTriangleSkirtFaceMasks.size() &&Tile->TriangleSkirtFaceMasks[Triangle]!=0)continue; const std::uint32_t I0=Tile->Indices[Triangle*3]; const std::uint32_t I1=Tile->Indices[Triangle*3+1]; const std::uint32_t I2=Tile->Indices[Triangle*3+2]; if(I0>=Tile->Vertices.size()||I1>=Tile->Vertices.size()||I2>=Tile->Vertices.size())continue; const auto& A=Tile->Vertices[I0].Position; const auto& B=Tile->Vertices[I1].Position; const auto& C=Tile->Vertices[I2].Position; const double Denominator=(B[1]-C[1])*(A[0]-C[0])+(C[0]-B[0])*(A[1]-C[1]); if(std::abs(Denominator)<1e-10)continue; const double MinTriX=std::min({A[0],B[0],C[0]}),MaxTriX=std::max({A[0],B[0],C[0]}); const double MinTriY=std::min({A[1],B[1],C[1]}),MaxTriY=std::max({A[1],B[1],C[1]}); const double StepX=Extent/(Side-1),StepY=Extent/(Side-1); const int Ix0=std::clamp(static_cast(std::ceil((MinTriX-MinX)/StepX)),0,Side-1); const int Ix1=std::clamp(static_cast(std::floor((MaxTriX-MinX)/StepX)),0,Side-1); const int Iy0=std::clamp(static_cast(std::ceil((MinTriY-MinY)/StepY)),0,Side-1); const int Iy1=std::clamp(static_cast(std::floor((MaxTriY-MinY)/StepY)),0,Side-1); for(int Iy=Iy0;Iy<=Iy1;++Iy) for(int Ix=Ix0;Ix<=Ix1;++Ix) { const double X=Grid.X(Ix),Y=Grid.Y(Iy); const double W0=((B[1]-C[1])*(X-C[0])+(C[0]-B[0])*(Y-C[1]))/Denominator; const double W1=((C[1]-A[1])*(X-C[0])+(A[0]-C[0])*(Y-C[1]))/Denominator; const double W2=1.0-W0-W1; if(W0<-1e-6||W1<-1e-6||W2<-1e-6)continue; Grid.Add(Ix,Iy,W0*A[2]+W1*B[2]+W2*C[2]); } } } for(auto& Values:Grid.Hits) { std::sort(Values.begin(),Values.end()); Values.erase(std::unique(Values.begin(),Values.end(),[](double A,double B) {return std::abs(A-B)<0.02;}),Values.end()); } return Grid; } inline std::vector ExactHeightRoots(const FHeightGrid& Grid,int Ix,int Iy, const FVoxelTileKey& Patch,double VoxelMeters, const VoxelForgeCore::WorldAssemblyManifest& Assembly) { std::vector Roots; const double XSubvoxels=Grid.X(Ix)/VoxelMeters*256.0; const double YSubvoxels=Grid.Y(Iy)/VoxelMeters*256.0; const double MinZ=static_cast(Patch.OriginVoxels().Z); const double MaxZ=MinZ+Patch.ExtentVoxels(); for(const auto& Plan:Assembly.plan.strates) { VoxelForgeCore::Heightfield2D Field(Assembly.recipe,Plan); if(!Field.isValid())continue; const auto Column=Field.point(static_cast(std::llround(XSubvoxels)), static_cast(std::llround(YSubvoxels)),0); if(Column.groundHeightSubvoxels>=Column.capHeightSubvoxels)continue; const double Ground=static_cast(Column.groundHeightSubvoxels)/(256.0)*VoxelMeters; const double Cap=static_cast(Column.capHeightSubvoxels)/(256.0)*VoxelMeters; const double PlanMinZ=static_cast(Plan.bottomChunkZ)*32.0*VoxelMeters; const double PlanMaxZ=static_cast(Plan.topChunkZ+1)*32.0*VoxelMeters; if(Ground>=MinZ*VoxelMeters-0.25*VoxelMeters &&Ground<=MaxZ*VoxelMeters+0.25*VoxelMeters &&Ground>=PlanMinZ-0.25*VoxelMeters&&Ground<=PlanMaxZ+0.25*VoxelMeters) Roots.push_back(Ground); if(Cap>=MinZ*VoxelMeters-0.25*VoxelMeters &&Cap<=MaxZ*VoxelMeters+0.25*VoxelMeters &&Cap>=PlanMinZ-0.25*VoxelMeters&&Cap<=PlanMaxZ+0.25*VoxelMeters) Roots.push_back(Cap); } std::sort(Roots.begin(),Roots.end()); Roots.erase(std::unique(Roots.begin(),Roots.end(),[](double A,double B) {return std::abs(A-B)<0.01;}),Roots.end()); return Roots; } inline bool MatchNearest(const std::vector& Hits,const std::vector& Used, double Root,std::size_t& OutIndex,double& OutError) { double Best=std::numeric_limits::infinity();std::size_t BestIndex=Hits.size(); for(std::size_t I=0;I=Used.size()||!Used[I]) { const double Error=std::abs(Hits[I]-Root); if(Error Errors; for(int Iy=0;Iy Used(MeshGrid.At(Ix,Iy).size(),false); for(double Root:Roots) { std::size_t HitIndex=0;double ErrorMeters=0.0; if(!MatchNearest(MeshGrid.At(Ix,Iy),Used,Root,HitIndex,ErrorMeters)) { ++Row.Missing; Errors.push_back(2.0); continue; } Used[HitIndex]=true; Errors.push_back(ErrorMeters/PixelBudgetMeters); ++Row.Samples; } // Heightfield2D declares ground and cap roots only. Other mesh intersections may // be valid tunnel geometry; the exact-mesh comparison checks extra pieces. } Row.P50=Percentile(Errors,0.50);Row.P99=Percentile(Errors,0.99); Row.Maximum=Errors.empty()?0.0:*std::max_element(Errors.begin(),Errors.end()); Row.Samples=Errors.size(); return Row; } inline FErrorRow CompareToReference(const FHeightGrid& MeshGrid, const FHeightGrid& ReferenceGrid,int Lod,double PixelBudgetMeters) { FErrorRow Row;Row.Lod=Lod;Row.Valid=true; std::vector Errors; for(int Iy=0;Iy Used(Mesh.size(),false); for(double ReferenceZ:Reference) { std::size_t HitIndex=0;double ErrorMeters=0.0; if(!MatchNearest(Mesh,Used,ReferenceZ,HitIndex,ErrorMeters)) { ++Row.Missing;Errors.push_back(2.0);continue; } Used[HitIndex]=true; Errors.push_back(ErrorMeters/PixelBudgetMeters); } for(std::size_t I=0;I Errors; for(int Iy=0;Iy CoarseUsed(CoarseHits.size(),false); std::vector FineUsed(FineHits.size(),false); for(double Root:Roots) { std::size_t CoarseIndex=0,FineIndex=0; double CoarseError=0.0,FineError=0.0; if(!MatchNearest(CoarseHits,CoarseUsed,Root,CoarseIndex,CoarseError) ||!MatchNearest(FineHits,FineUsed,Root,FineIndex,FineError)) { ++Row.Cracks;Errors.push_back(2.0);continue; } CoarseUsed[CoarseIndex]=true; FineUsed[FineIndex]=true; Errors.push_back(std::abs(CoarseHits[CoarseIndex]-FineHits[FineIndex]) /PixelBudgetMeters); ++Row.Samples; } } Row.P50=Percentile(Errors,0.50);Row.P99=Percentile(Errors,0.99); Row.Maximum=Errors.empty()?0.0:*std::max_element(Errors.begin(),Errors.end()); Row.Samples=Errors.size(); return Row; } inline std::array ClosestDisplayedDistancesMeters( const FStandaloneOwnerWorld& World,const FVector& CameraVoxels, std::uint32_t VoxelSizeMicrons) { const double VoxelMeters=static_cast(VoxelSizeMicrons)/1000000.0; std::array Minimums{}; Minimums.fill(std::numeric_limits::infinity()); for(int Lod=0;Lod<=8;++Lod) { const int PhaseCount=1<(Phase*32.0+32.0-1.0/256.0); const FStandaloneDesiredTileSet Desired=StandaloneWorldMesher::BuildDesiredSet(World,Probe); for(const FVoxelTileKey& Tile:Desired.Tiles) { if(Tile.Level!=Lod)continue; Minimums[Lod]=std::min(Minimums[Lod], TileAabbDistanceMeters(Tile,Probe,VoxelMeters)); } } } for(double& Distance:Minimums) if(!std::isfinite(Distance))Distance=0.0; return Minimums; } inline std::array ClipmapClosestDisplayedDistancesMeters( const FStandaloneOwnerWorld& World,std::uint32_t VoxelSizeMicrons) { const double VoxelMeters=static_cast(VoxelSizeMicrons)/1000000.0; std::array Distances{}; for(int Lod=1;Lod<=8;++Lod) Distances[Lod]=static_cast(World.ClipRadius) *static_cast(CHUNK_SIZE)*(1ull<<(Lod-1))*VoxelMeters; return Distances; } inline bool Run(const std::array,9>& SampleTiles, FStandaloneStreamingGenerator& Streamer, const VoxelForgeCore::WorldAssemblyManifest& Assembly, const std::array& Distances,std::uint32_t VoxelSizeMicrons,int MaxLod, const std::array& PixelBudgetsSubvoxels) { const double VoxelMeters=static_cast(VoxelSizeMicrons)/1000000.0; std::array Budgets{}; std::array ErrorRows{}; std::array PopRows{}; bool GatePass=true; std::printf("[ScreenSpaceGate] angular_pixel=%.9f rad (90deg/2560) lod>=3_limit=1.0px sampling=65x65 skirts=excluded metric=vertical_height_error field_reference=direct-Heightfield2D-roots\n", OnePixelRadians); for(int Lod=0;Lod<=MaxLod&&Lod<9;++Lod) { Budgets[Lod]=Distances[Lod]*OnePixelRadians; std::printf("[ScreenSpaceDistance] lod=%d closest_m=%.3f pixel_budget_m=%.6f pixel_budget_cm=%.3f pixel_budget_subvoxels=%lld rule=%s\n", Lod,Distances[Lod],Budgets[Lod],Budgets[Lod]*100.0, static_cast(PixelBudgetsSubvoxels[Lod]),Lod<3?"exact-2cm":"1px"); } VoxelForgeCore::Heightfield2D::resetThreadColumnCacheStats(false,true,0); for(int Lod=3;Lod<=MaxLod&&Lod<9;++Lod) { if(!SampleTiles[Lod]||SampleTiles[Lod]->Vertices.empty()) { std::printf("[ScreenSpaceGateLOD] lod=%d status=NO_SURFACE_TILE\n",Lod); GatePass=false;continue; } const FStandaloneCpuTile& CoarseTile=*SampleTiles[Lod]; const FStandaloneCpuTile* CoarsePtr=&CoarseTile; const std::span CoarseSpan(&CoarsePtr,1); const FHeightGrid CoarseGrid=Rasterize(CoarseSpan,CoarseTile.Key,VoxelMeters); const FStandaloneCpuTile ExactTile=Streamer.GenerateDiagnostic( CoarseTile.Key,0,1,true); const FStandaloneCpuTile* ExactPointer=&ExactTile; const std::span ExactSpan(&ExactPointer,1); const FHeightGrid ExactGrid=Rasterize(ExactSpan,ExactTile.Key,VoxelMeters); ErrorRows[Lod]=CompareToReference(CoarseGrid,ExactGrid,Lod,Budgets[Lod]); const FErrorRow& Error=ErrorRows[Lod]; const bool LodPass=Error.Valid&&Error.Missing==0&&Error.Floating==0 &&Error.Samples>=16&&Error.Maximum<=1.0; GatePass=GatePass&&LodPass; std::printf("[ScreenSpaceGateLOD] lod=%d key=(%d,%d,%d) samples=%llu p50_px=%.4f p99_px=%.4f max_px=%.4f missing=%llu floating=%llu status=%s\n", Lod,CoarseTile.Key.Coord.X,CoarseTile.Key.Coord.Y,CoarseTile.Key.Coord.Z, static_cast(Error.Samples),Error.P50,Error.P99,Error.Maximum, static_cast(Error.Missing),static_cast(Error.Floating), LodPass?"PASS":"REJECT"); const FErrorRow FieldError=CompareToExact(CoarseGrid,CoarseTile.Key,VoxelMeters, Budgets[Lod],Assembly); const bool FieldPass=FieldError.Valid&&FieldError.Missing==0&&FieldError.Floating==0 &&FieldError.Samples>=16&&FieldError.Maximum<=1.0; GatePass=GatePass&&FieldPass; std::printf("[ScreenSpaceGateFieldLOD] lod=%d samples=%llu p50_px=%.4f p99_px=%.4f max_px=%.4f missing=%llu floating=%llu status=%s\n", Lod,static_cast(FieldError.Samples),FieldError.P50, FieldError.P99,FieldError.Maximum,static_cast(FieldError.Missing), static_cast(FieldError.Floating),FieldPass?"PASS":"REJECT"); bool FaultControlsPass=true; const FHeightGrid MissingGrid(ExactGrid.Side,ExactGrid.MinimumX,ExactGrid.MaximumX, ExactGrid.MinimumY,ExactGrid.MaximumY); const FErrorRow MissingError=CompareToReference(MissingGrid,ExactGrid,Lod, Budgets[Lod]); const bool MissingRejected=MissingError.Missing>0&&MissingError.Maximum>1.0; FaultControlsPass=FaultControlsPass&&MissingRejected; std::printf("[ScreenSpaceFaultInjection] lod=%d case=missing-displayed-surface missing=%llu status=%s\n", Lod,static_cast(MissingError.Missing), MissingRejected?"REJECTED_AS_REQUIRED":"GATE_FALSE_NEGATIVE"); FStandaloneCpuTile FloatingTile=CoarseTile; const std::uint32_t FloatingVertexBase=static_cast(FloatingTile.Vertices.size()); for(FStandaloneCpuVertex Vertex:CoarseTile.Vertices) { Vertex.Position[2]+=2.0f; FloatingTile.Vertices.push_back(Vertex); } for(const std::uint32_t Index:CoarseTile.Indices) FloatingTile.Indices.push_back(FloatingVertexBase+Index); FloatingTile.TriangleSkirtFaceMasks.insert(FloatingTile.TriangleSkirtFaceMasks.end(), CoarseTile.TriangleSkirtFaceMasks.begin(),CoarseTile.TriangleSkirtFaceMasks.end()); const FStandaloneCpuTile* FloatingPointer=&FloatingTile; const std::span FloatingSpan(&FloatingPointer,1); const FHeightGrid FloatingGrid=Rasterize(FloatingSpan,CoarseTile.Key,VoxelMeters); const FErrorRow FloatingError=CompareToReference(FloatingGrid,ExactGrid,Lod, Budgets[Lod]); const bool FloatingRejected=FloatingError.Floating>0||FloatingError.Maximum>1.0; FaultControlsPass=FaultControlsPass&&FloatingRejected; std::printf("[ScreenSpaceFaultInjection] lod=%d case=2m-floating-surface floating=%llu max_px=%.4f status=%s\n", Lod,static_cast(FloatingError.Floating),FloatingError.Maximum, FloatingRejected?"REJECTED_AS_REQUIRED":"GATE_FALSE_NEGATIVE"); std::vector FineTiles; FineTiles.reserve(8); for(int Dz=0;Dz<=1;++Dz) for(int Dy=0;Dy<=1;++Dy) for(int Dx=0;Dx<=1;++Dx) { const FVoxelTileKey FineKey(FIntVector(2*CoarseTile.Key.Coord.X+Dx, 2*CoarseTile.Key.Coord.Y+Dy,2*CoarseTile.Key.Coord.Z+Dz),Lod-1); FStandaloneCpuTile FineTile=Streamer.GenerateDiagnostic(FineKey); if(!FineTile.Vertices.empty())FineTiles.push_back(std::move(FineTile)); } std::vector FinePointers; FinePointers.reserve(FineTiles.size()); for(const auto& Tile:FineTiles)FinePointers.push_back(&Tile); const auto FineSpan=std::span( FinePointers.data(),FinePointers.size()); const FHeightGrid FineGrid=Rasterize(FineSpan,CoarseTile.Key,VoxelMeters); PopRows[Lod]=ComparePop(CoarseGrid,FineGrid,CoarseTile.Key,VoxelMeters, Budgets[Lod],Assembly); const FPopRow& Pop=PopRows[Lod]; const bool PopPass=Pop.Valid&&Pop.Cracks==0&&Pop.Floating==0&&Pop.Maximum<=1.0; GatePass=GatePass&&PopPass; std::printf("[ScreenSpaceSwap] lod=%d_to_%d samples=%llu p50_px=%.4f p99_px=%.4f max_px=%.4f cracks=%llu floating=%llu status=%s\n", Lod,Lod-1,static_cast(Pop.Samples), Pop.P50,Pop.P99,Pop.Maximum,static_cast(Pop.Cracks), static_cast(Pop.Floating), PopPass?"PASS":"REJECT"); const FHeightGrid MissingFineGrid(CoarseGrid.Side,CoarseGrid.MinimumX, CoarseGrid.MaximumX,CoarseGrid.MinimumY,CoarseGrid.MaximumY); const FPopRow MissingFineError=ComparePop(CoarseGrid,MissingFineGrid, CoarseTile.Key,VoxelMeters,Budgets[Lod],Assembly); const bool CrackRejected=MissingFineError.Cracks>0&&MissingFineError.Maximum>1.0; FaultControlsPass=FaultControlsPass&&CrackRejected; GatePass=GatePass&&FaultControlsPass; std::printf("[ScreenSpaceFaultInjection] lod=%d case=missing-fine-ring-surface cracks=%llu max_px=%.4f status=%s\n", Lod,static_cast(MissingFineError.Cracks), MissingFineError.Maximum,CrackRejected?"REJECTED_AS_REQUIRED":"GATE_FALSE_NEGATIVE"); bool NegativeControlsPass=true; const auto CheckNegative=[&](const char* Name,std::uint32_t Drop,std::uint32_t Stride) { FStandaloneCpuTile Bad=Streamer.GenerateDiagnostic( CoarseTile.Key,Drop,Stride,true); const FStandaloneCpuTile* BadPointer=&Bad; const std::span BadSpan(&BadPointer,1); const FHeightGrid BadGrid=Rasterize(BadSpan,Bad.Key,VoxelMeters); const FErrorRow BadError=CompareToReference(BadGrid,ExactGrid,Lod, Budgets[Lod]); const bool Rejected=BadError.Missing>0||BadError.Floating>0 ||BadError.Samples<16||BadError.Maximum>1.0; NegativeControlsPass=NegativeControlsPass&&Rejected; std::printf("[ScreenSpaceNegative] lod=%d case=%s p50_px=%.4f p99_px=%.4f max_px=%.4f missing=%llu floating=%llu status=%s\n", Lod,Name,BadError.P50,BadError.P99,BadError.Maximum, static_cast(BadError.Missing), static_cast(BadError.Floating), Rejected?"REJECTED_AS_REQUIRED":"GATE_FALSE_NEGATIVE"); }; CheckNegative("s13-global-octave-drop",1,1); CheckNegative("stride-2-column-interpolation",0,2); GatePass=GatePass&&NegativeControlsPass; } std::printf("[ScreenSpaceGateResult] status=%s\n",GatePass?"PASS":"REJECT"); return GatePass; } }