All 4 styles x both recipes pass capsule, open-air and naturalness audits, and full-view hashes are deterministic. But the SW passage crosses 355 m of open SurfaceWorld sky as a ~2 km fragmented rock ramp, which is not natural. L27 replaces long bridges with a shared ceiling-to-ground rock column. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CSFYgsdW8ejZSyiMf4qRSX
2870 lines
183 KiB
C++
2870 lines
183 KiB
C++
#pragma once
|
|
|
|
#include "OwnerWorldLoader.h"
|
|
#include "StreamingWorld.h"
|
|
#include "LinuxScreenSpaceGate.h"
|
|
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <bit>
|
|
#include <chrono>
|
|
#include <cmath>
|
|
#include <cstdint>
|
|
#include <ctime>
|
|
#include <cstdio>
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <future>
|
|
#include <limits>
|
|
#include <numeric>
|
|
#include <thread>
|
|
#include <unordered_map>
|
|
#include <unordered_set>
|
|
#include <vector>
|
|
|
|
namespace StandaloneStreamBenchmark
|
|
{
|
|
struct FRouteDensityComparison
|
|
{
|
|
std::uint64_t samples=0;
|
|
std::uint64_t signMismatches=0;
|
|
std::uint64_t crossingEdgeMismatches=0;
|
|
std::uint64_t sharedCrossingEdges=0;
|
|
std::uint64_t exactQuerySamples=0;
|
|
std::uint64_t exactQueryMismatches=0;
|
|
double maxAbsoluteDelta=0.0;
|
|
double sumAbsoluteDelta=0.0;
|
|
double maxSurfaceDisplacementVoxels=0.0;
|
|
double sumSurfaceDisplacementVoxels=0.0;
|
|
bool valid=false;
|
|
};
|
|
|
|
inline FRouteDensityComparison CompareRouteDensities(
|
|
const VoxelForgeCore::TileMesh& A,const VoxelForgeCore::TileMesh& B,
|
|
const VoxelForgeCore::World& World)
|
|
{
|
|
FRouteDensityComparison Result;
|
|
if(A.debugDensitySamples.empty()
|
|
||A.debugDensitySamples.size()!=B.debugDensitySamples.size())return Result;
|
|
const double Root=std::cbrt(static_cast<double>(A.debugDensitySamples.size()));
|
|
const std::int32_t Dim=static_cast<std::int32_t>(std::llround(Root));
|
|
if(Dim<4||static_cast<std::size_t>(Dim)*Dim*Dim!=A.debugDensitySamples.size())return Result;
|
|
const std::int32_t GridDim=Dim-3;
|
|
const std::int32_t Extent=CHUNK_SIZE<<A.key.lod;
|
|
if(GridDim<=0||Extent%GridDim!=0)return Result;
|
|
const std::int32_t Step=Extent/GridDim;
|
|
const FVoxelTileKey NativeKey(
|
|
FIntVector(A.key.coordinate.x,A.key.coordinate.y,A.key.coordinate.z),A.key.lod);
|
|
const FIntVector Origin=NativeKey.OriginVoxels();
|
|
const auto Index=[Dim](std::int32_t X,std::int32_t Y,std::int32_t Z)
|
|
{return (static_cast<std::size_t>(Z)*Dim+Y)*Dim+X;};
|
|
const auto IsSolid=[](float Density){return Density<0.0f;};
|
|
const auto EdgeCrosses=[&](const std::vector<float>& Values,
|
|
std::int32_t X0,std::int32_t Y0,std::int32_t Z0,
|
|
std::int32_t X1,std::int32_t Y1,std::int32_t Z1)
|
|
{
|
|
return IsSolid(Values[Index(X0,Y0,Z0)])!=IsSolid(Values[Index(X1,Y1,Z1)]);
|
|
};
|
|
for(std::int32_t Z=0;Z<Dim;++Z)
|
|
for(std::int32_t Y=0;Y<Dim;++Y)
|
|
for(std::int32_t X=0;X<Dim;++X)
|
|
{
|
|
const std::size_t I=Index(X,Y,Z);
|
|
const float DA=A.debugDensitySamples[I],DB=B.debugDensitySamples[I];
|
|
if(!std::isfinite(DA)||!std::isfinite(DB))return Result;
|
|
const double Delta=std::abs(static_cast<double>(DA)-DB);
|
|
Result.maxAbsoluteDelta=std::max(Result.maxAbsoluteDelta,Delta);
|
|
Result.sumAbsoluteDelta+=Delta;
|
|
Result.signMismatches+=IsSolid(DA)!=IsSolid(DB)?1u:0u;
|
|
++Result.samples;
|
|
|
|
const std::int32_t WorldX=Origin.X+(X-1)*Step;
|
|
const std::int32_t WorldY=Origin.Y+(Y-1)*Step;
|
|
const std::int32_t WorldZ=Origin.Z+(Z-1)*Step;
|
|
const VoxelForgeCore::Vec3i Lattice{WorldX,WorldY,WorldZ};
|
|
const float ExactLattice=World.densityAtLattice(Lattice);
|
|
const float ExactPoint=World.densityAt({static_cast<double>(WorldX),
|
|
static_cast<double>(WorldY),static_cast<double>(WorldZ)});
|
|
++Result.exactQuerySamples;
|
|
if(std::bit_cast<std::uint32_t>(DA)!=std::bit_cast<std::uint32_t>(ExactLattice)
|
|
||std::bit_cast<std::uint32_t>(DA)!=std::bit_cast<std::uint32_t>(ExactPoint))
|
|
++Result.exactQueryMismatches;
|
|
}
|
|
|
|
const auto CompareEdge=[&](std::int32_t X0,std::int32_t Y0,std::int32_t Z0,
|
|
std::int32_t X1,std::int32_t Y1,std::int32_t Z1)
|
|
{
|
|
const bool CrossA=EdgeCrosses(A.debugDensitySamples,X0,Y0,Z0,X1,Y1,Z1);
|
|
const bool CrossB=EdgeCrosses(B.debugDensitySamples,X0,Y0,Z0,X1,Y1,Z1);
|
|
if(CrossA!=CrossB)++Result.crossingEdgeMismatches;
|
|
if(!CrossA||!CrossB)return;
|
|
const float A0=A.debugDensitySamples[Index(X0,Y0,Z0)];
|
|
const float A1=A.debugDensitySamples[Index(X1,Y1,Z1)];
|
|
const float B0=B.debugDensitySamples[Index(X0,Y0,Z0)];
|
|
const float B1=B.debugDensitySamples[Index(X1,Y1,Z1)];
|
|
const double TA=static_cast<double>(A0)/(static_cast<double>(A0)-A1);
|
|
const double TB=static_cast<double>(B0)/(static_cast<double>(B0)-B1);
|
|
const double Displacement=std::abs(TA-TB)*Step;
|
|
Result.maxSurfaceDisplacementVoxels=std::max(
|
|
Result.maxSurfaceDisplacementVoxels,Displacement);
|
|
Result.sumSurfaceDisplacementVoxels+=Displacement;
|
|
++Result.sharedCrossingEdges;
|
|
};
|
|
for(std::int32_t Z=0;Z<Dim;++Z)
|
|
for(std::int32_t Y=0;Y<Dim;++Y)
|
|
for(std::int32_t X=0;X<Dim;++X)
|
|
{
|
|
if(X+1<Dim)CompareEdge(X,Y,Z,X+1,Y,Z);
|
|
if(Y+1<Dim)CompareEdge(X,Y,Z,X,Y+1,Z);
|
|
if(Z+1<Dim)CompareEdge(X,Y,Z,X,Y,Z+1);
|
|
}
|
|
Result.valid=true;
|
|
return Result;
|
|
}
|
|
|
|
inline bool SameTileOutput(const VoxelForgeCore::TileMesh& A,
|
|
const VoxelForgeCore::TileMesh& B)
|
|
{
|
|
return A.hasDensityHash&&B.hasDensityHash&&A.hasMeshHash&&B.hasMeshHash
|
|
&&A.hasPayloadHash&&B.hasPayloadHash&&A.hasTopologyHash&&B.hasTopologyHash
|
|
&&A.densityHash==B.densityHash&&A.meshHash==B.meshHash
|
|
&&A.payloadHash==B.payloadHash&&A.topologyHash==B.topologyHash;
|
|
}
|
|
|
|
struct FSceneExportWriter
|
|
{
|
|
std::ofstream File;
|
|
bool bGood=true;
|
|
std::uint32_t TileCount=0;
|
|
|
|
template<typename T> void Write(const T& Value)
|
|
{
|
|
File.write(reinterpret_cast<const char*>(&Value),sizeof(Value));
|
|
bGood=bGood&&static_cast<bool>(File);
|
|
}
|
|
|
|
bool Open(const std::filesystem::path& Path,std::uint32_t VoxelSizeMicrons,
|
|
const FVector& CameraVoxels,float YawDegrees,float PitchDegrees,
|
|
std::uint32_t ExpectedTiles)
|
|
{
|
|
std::error_code Error;
|
|
if(!Path.parent_path().empty())
|
|
std::filesystem::create_directories(Path.parent_path(),Error);
|
|
if(Error)return false;
|
|
File.open(Path,std::ios::binary|std::ios::trunc);
|
|
if(!File)return false;
|
|
constexpr char Magic[8]={'V','F','M','S','H','0','0','1'};
|
|
File.write(Magic,sizeof(Magic));
|
|
const std::uint32_t Version=1;
|
|
Write(Version);Write(VoxelSizeMicrons);Write(ExpectedTiles);
|
|
const double MicronsToMeters=static_cast<double>(VoxelSizeMicrons)/1000000.0;
|
|
const double Camera[3]={CameraVoxels.X*MicronsToMeters,
|
|
CameraVoxels.Y*MicronsToMeters,CameraVoxels.Z*MicronsToMeters};
|
|
for(const double Value:Camera)Write(Value);
|
|
const double Yaw=YawDegrees,Pitch=PitchDegrees;
|
|
Write(Yaw);Write(Pitch);
|
|
return bGood;
|
|
}
|
|
|
|
void Add(const FStandaloneCpuTile& Tile)
|
|
{
|
|
const std::int32_t Key[4]={Tile.Key.Coord.X,Tile.Key.Coord.Y,
|
|
Tile.Key.Coord.Z,Tile.Key.Level};
|
|
for(const auto Value:Key)Write(Value);
|
|
const std::uint32_t SurfaceVertexCount=Tile.SurfaceVertexCount;
|
|
const std::uint32_t SkirtTriangleCount=Tile.SkirtTriangleCount;
|
|
const std::uint32_t VertexCount=static_cast<std::uint32_t>(Tile.Vertices.size());
|
|
const std::uint32_t IndexCount=static_cast<std::uint32_t>(Tile.Indices.size());
|
|
const std::uint32_t MaskCount=static_cast<std::uint32_t>(Tile.TriangleSkirtFaceMasks.size());
|
|
Write(SurfaceVertexCount);Write(SkirtTriangleCount);
|
|
Write(VertexCount);Write(IndexCount);Write(MaskCount);
|
|
for(const FStandaloneCpuVertex& Vertex:Tile.Vertices)
|
|
for(const float Value:Vertex.Position)Write(Value);
|
|
for(const FStandaloneCpuVertex& Vertex:Tile.Vertices)
|
|
for(const float Value:Vertex.Normal)Write(Value);
|
|
if(IndexCount)
|
|
File.write(reinterpret_cast<const char*>(Tile.Indices.data()),
|
|
static_cast<std::streamsize>(sizeof(std::uint32_t)*Tile.Indices.size()));
|
|
if(MaskCount)
|
|
File.write(reinterpret_cast<const char*>(Tile.TriangleSkirtFaceMasks.data()),
|
|
static_cast<std::streamsize>(Tile.TriangleSkirtFaceMasks.size()));
|
|
bGood=bGood&&static_cast<bool>(File);
|
|
++TileCount;
|
|
}
|
|
|
|
bool Close(std::uint32_t ExpectedTiles)
|
|
{
|
|
File.flush();
|
|
const bool bComplete=bGood&&static_cast<bool>(File)&&TileCount==ExpectedTiles;
|
|
File.close();
|
|
return bComplete;
|
|
}
|
|
};
|
|
|
|
inline 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;
|
|
};
|
|
|
|
inline 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;
|
|
}
|
|
|
|
inline 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;
|
|
}
|
|
|
|
inline int RunPipelinedStreamingBenchmark(std::shared_ptr<FStandaloneOwnerWorld> OwnerWorld,
|
|
const VoxelForgeCore::ResolvedWorldSnapshot& CoreSnapshot,
|
|
const FVector& CameraPositionVoxels,int32 WorkerCount,int32 MaxLevel,
|
|
VoxelForgeCore::DetailVariant DetailVariant,const VoxelForgeCore::JobScheduler& Scheduler,
|
|
bool bHashDensity,bool bSkipUvAndColors,bool bSkipMaterialGroups,bool bSkipUV,bool bSkipColors,
|
|
std::chrono::steady_clock::time_point ColdStart,bool bProfileColumnOverlap,
|
|
int32 FarHorizonLOD8InteriorStepVoxels,bool bReportMeshCoverage=false,
|
|
const std::filesystem::path& MeshExportPath={},float CameraYawDegrees=0.0f,
|
|
float CameraPitchDegrees=0.0f,bool bScreenSpaceGate=false,
|
|
bool bUseSharedColumnCache=true,FVector CameraStepVoxels=FVector::ZeroVector,
|
|
bool bAuditUniformBlockProofs=false,bool bReportLodWorkProfile=false,
|
|
bool bRegionCrossing=false,
|
|
bool bAuditTileShapeReadiness=false,int DiagnosticSlowShapeCompileMs=0,
|
|
bool bAuditBasePageEquivalence=false,bool bAuditRouteParity=false,
|
|
bool bAuditResolvedLayoutBoundaries=false,
|
|
bool bAuditResolvedLayoutPassages=false,
|
|
const VoxelForgeCore::ResolvedLayoutSnapshotV3* ResolvedLayout=nullptr)
|
|
{
|
|
using Clock=std::chrono::steady_clock;
|
|
FStandaloneOwnerWorld& World=*OwnerWorld;
|
|
const VoxelForgeCore::WorldAssemblyManifest* Assembly=CoreSnapshot.worldAssembly();
|
|
std::array<double,9> ClosestDisplayDistances{};
|
|
std::array<std::int64_t,9> PixelBudgetsSubvoxels{};
|
|
if(bScreenSpaceGate)
|
|
{
|
|
ClosestDisplayDistances=StandaloneScreenSpaceGate::ClosestDisplayedDistancesMeters(
|
|
World,CameraPositionVoxels,World.CoreVoxelSizeMicrons);
|
|
const double VoxelMeters=static_cast<double>(World.CoreVoxelSizeMicrons)/1000000.0;
|
|
for(int32 Lod=3;Lod<=World.MaxClipLevel&&Lod<9;++Lod)
|
|
PixelBudgetsSubvoxels[static_cast<std::size_t>(Lod)]=static_cast<std::int64_t>(std::floor(
|
|
ClosestDisplayDistances[static_cast<std::size_t>(Lod)]
|
|
*StandaloneScreenSpaceGate::OnePixelRadians/VoxelMeters
|
|
*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel));
|
|
}
|
|
const bool bRoundTunnels=World.Generator->bStandaloneResearchRoundTunnels;
|
|
const bool bEnhancedTunnelWalls=World.Generator->bStandaloneResearchEnhancedWalls;
|
|
VoxelForgeCore::World Shape(CoreSnapshot);
|
|
FStandaloneDesiredTileSet Desired=StandaloneWorldMesher::BuildDesiredSet(World,CameraPositionVoxels);
|
|
const auto CoreTiles=Shape.desiredTiles(
|
|
{CameraPositionVoxels.X,CameraPositionVoxels.Y,CameraPositionVoxels.Z});
|
|
Desired.Tiles.clear();Desired.Tiles.reserve(CoreTiles.size());
|
|
for(const auto& Key:CoreTiles)
|
|
Desired.Tiles.emplace_back(FIntVector(Key.coordinate.x,Key.coordinate.y,Key.coordinate.z),Key.lod);
|
|
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());
|
|
const bool bHasCameraStep=std::abs(CameraStepVoxels.X)+std::abs(CameraStepVoxels.Y)
|
|
+std::abs(CameraStepVoxels.Z)>1.0e-6f;
|
|
FStandaloneDesiredTileSet MovedDesired;
|
|
if(bHasCameraStep)
|
|
{
|
|
const FVector MovedCamera=CameraPositionVoxels+CameraStepVoxels;
|
|
MovedDesired=StandaloneWorldMesher::BuildDesiredSet(World,MovedCamera);
|
|
const auto MovedCoreTiles=Shape.desiredTiles({MovedCamera.X,MovedCamera.Y,MovedCamera.Z});
|
|
MovedDesired.Tiles.clear();MovedDesired.Tiles.reserve(MovedCoreTiles.size());
|
|
for(const auto& Key:MovedCoreTiles)
|
|
MovedDesired.Tiles.emplace_back(FIntVector(Key.coordinate.x,Key.coordinate.y,Key.coordinate.z),Key.lod);
|
|
if(MaxLevel>=0)
|
|
MovedDesired.Tiles.erase(std::remove_if(MovedDesired.Tiles.begin(),MovedDesired.Tiles.end(),
|
|
[&](const FVoxelTileKey& Tile){return Tile.Level>MaxLevel;}),MovedDesired.Tiles.end());
|
|
}
|
|
if(!Desired.Tiles.empty())
|
|
{
|
|
FIntVector volumeMin(INT32_MAX,INT32_MAX,INT32_MAX);
|
|
FIntVector volumeMax(INT32_MIN,INT32_MIN,INT32_MIN);
|
|
int32 minimumLod=INT32_MAX,maximumLod=INT32_MIN;
|
|
for(const FVoxelTileKey& tile:Desired.Tiles)
|
|
{
|
|
const FIntVector origin=tile.OriginVoxels();
|
|
const int32 extent=tile.ExtentVoxels();
|
|
volumeMin.X=std::min(volumeMin.X,origin.X);
|
|
volumeMin.Y=std::min(volumeMin.Y,origin.Y);
|
|
volumeMin.Z=std::min(volumeMin.Z,origin.Z);
|
|
volumeMax.X=std::max(volumeMax.X,origin.X+extent);
|
|
volumeMax.Y=std::max(volumeMax.Y,origin.Y+extent);
|
|
volumeMax.Z=std::max(volumeMax.Z,origin.Z+extent);
|
|
minimumLod=std::min(minimumLod,tile.Level);
|
|
maximumLod=std::max(maximumLod,tile.Level);
|
|
}
|
|
std::printf("[PipelineVolume] tiles=%zu lod=%d..%d min_voxel=(%d,%d,%d) max_voxel=(%d,%d,%d)\n",
|
|
Desired.Tiles.size(),minimumLod,maximumLod,volumeMin.X,volumeMin.Y,volumeMin.Z,
|
|
volumeMax.X,volumeMax.Y,volumeMax.Z);
|
|
}
|
|
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);
|
|
const auto AccumulateBounds=[&](const std::vector<FVoxelTileKey>& Tiles)
|
|
{
|
|
for(const FVoxelTileKey& Tile: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);
|
|
}
|
|
}
|
|
};
|
|
AccumulateBounds(Desired.Tiles);
|
|
if(bHasCameraStep&&!bRegionCrossing)AccumulateBounds(MovedDesired.Tiles);
|
|
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;}
|
|
|
|
FStandaloneDesiredTileSet ShapeFreeDesired=Desired;
|
|
ShapeFreeDesired.Tiles.clear();
|
|
std::vector<FVoxelTileKey> ShapeFreeTiles;
|
|
ShapeFreeTiles.reserve(Neighborhood.size());
|
|
int32 ShapeFreeNeighborhood=0;
|
|
for(const FVoxelTileKey& Tile:Desired.Tiles)
|
|
{
|
|
if(!Neighborhood.contains(Tile))continue;
|
|
const VoxelForgeCore::TileKey CoreKey{{Tile.Coord.X,Tile.Coord.Y,Tile.Coord.Z},Tile.Level};
|
|
if(!Shape.isTileShapeFree(CoreKey))continue;
|
|
ShapeFreeDesired.Tiles.push_back(Tile);
|
|
ShapeFreeTiles.push_back(Tile);
|
|
if(Neighborhood.contains(Tile))++ShapeFreeNeighborhood;
|
|
}
|
|
if(bAuditTileShapeReadiness)
|
|
{
|
|
FVoxelTileKey DeliberatelyWrong(FIntVector(0,0,31250),0);
|
|
for(int32 Z=100000;Z<100100;++Z)
|
|
{
|
|
DeliberatelyWrong=FVoxelTileKey(FIntVector(0,0,Z),0);
|
|
if(!Shape.isTileShapeFree({{0,0,Z},0}))break;
|
|
}
|
|
VoxelForgeCore::TileGenerationOptions NegativeControlOptions;
|
|
NegativeControlOptions.diagnosticForceNativeOnly=true;
|
|
std::string NegativeControlError;
|
|
const auto MisMarked=Shape.generateTile(
|
|
{{DeliberatelyWrong.Coord.X,DeliberatelyWrong.Coord.Y,DeliberatelyWrong.Coord.Z},0},
|
|
NegativeControlOptions,NegativeControlError);
|
|
const bool bGuardCaught=!MisMarked
|
|
&&NegativeControlError.find("shape-free guard")!=std::string::npos;
|
|
std::printf("[ShapeReadinessNegativeControl] deliberately_mismarked=(%d,%d,%d,lod0) result=%s guard_caught=%d error=%s\n",
|
|
DeliberatelyWrong.Coord.X,DeliberatelyWrong.Coord.Y,DeliberatelyWrong.Coord.Z,
|
|
MisMarked?"generated":"rejected",bGuardCaught?1:0,
|
|
NegativeControlError.c_str());
|
|
if(!bGuardCaught)
|
|
{std::fprintf(stderr,"[StreamPipeline] shape-free guard did not reject the deliberately mis-marked tile.\n");return 9;}
|
|
}
|
|
std::printf("[ShapeReadiness] shape_free_desired=%zu/%zu near_ring=%d/27 proof=single_SurfaceWorld_plan_no_features_routes_passages_edits_or_owner_primitive_overlap\n",
|
|
ShapeFreeTiles.size(),Desired.Tiles.size(),ShapeFreeNeighborhood);
|
|
|
|
// The initial desired set now owns all clipmap metadata needed by this Core-only run.
|
|
// Release the separate compatibility-world generation graph before compiling Core.
|
|
World.Generator.reset();
|
|
World.Manager.reset();
|
|
World.DefinitionStorage.clear();
|
|
World.BiomeStorage.clear();
|
|
World.OperationStorage.clear();
|
|
World.DensitySamples.clear();
|
|
const bool bIncludeFarWorld=MaxLevel<0||MaxLevel>=World.FullResClipLevels;
|
|
VoxelForgeCore::ShapeBuildRequest ShapeRequest;
|
|
ShapeRequest.minVoxel={FineMin.X,FineMin.Y,FineMin.Z};
|
|
ShapeRequest.maxVoxel={FineMax.X,FineMax.Y,FineMax.Z};
|
|
ShapeRequest.clipMinVoxel={ClipMin.X,ClipMin.Y,ClipMin.Z};
|
|
ShapeRequest.clipMaxVoxel={ClipMax.X,ClipMax.Y,ClipMax.Z};
|
|
ShapeRequest.workerLimit=static_cast<uint32>(std::clamp(WorkerCount,1,16));
|
|
ShapeRequest.detail=DetailVariant;ShapeRequest.includeFarWorld=bIncludeFarWorld;
|
|
ShapeRequest.roundTunnels=bRoundTunnels;
|
|
ShapeRequest.enhancedTunnelWalls=bEnhancedTunnelWalls;
|
|
FStandaloneStreamingGenerator Streamer(World,WorkerCount,bHashDensity,nullptr,MaxLevel,
|
|
bSkipUvAndColors,bSkipMaterialGroups,bSkipUV,bSkipColors,&Shape,bProfileColumnOverlap,
|
|
FarHorizonLOD8InteriorStepVoxels,bUseSharedColumnCache);
|
|
Streamer.SetUniformBlockProofAuditEnabled(bAuditUniformBlockProofs);
|
|
if(bScreenSpaceGate||bReportLodWorkProfile)Streamer.SetMesherStageProfilingEnabled(true);
|
|
const auto StreamStart=Clock::now();
|
|
const FStandaloneStreamingUpdate EarlyUpdate=Streamer.StartWithDesired(ShapeFreeDesired);
|
|
if(EarlyUpdate.DesiredTiles!=static_cast<int32>(ShapeFreeDesired.Tiles.size()))
|
|
{
|
|
Streamer.Stop();
|
|
std::fprintf(stderr,"[StreamPipeline] expected %zu shape-free tiles but queued %d.\n",
|
|
ShapeFreeDesired.Tiles.size(),EarlyUpdate.DesiredTiles);return 7;
|
|
}
|
|
const auto ShapeBuildStart=Clock::now();
|
|
auto ShapeBuildFuture=std::async(std::launch::async,
|
|
[&Shape,&Scheduler,ShapeRequest,DiagnosticSlowShapeCompileMs]() mutable
|
|
{
|
|
if(DiagnosticSlowShapeCompileMs>0)
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(DiagnosticSlowShapeCompileMs));
|
|
std::string Error;
|
|
const bool bCompiled=Shape.compile(ShapeRequest,Scheduler,Error);
|
|
return std::pair<bool,std::string>{bCompiled,std::move(Error)};
|
|
});
|
|
const auto [bShapeCompiled,BuildError]=ShapeBuildFuture.get();
|
|
if(!bShapeCompiled)
|
|
{
|
|
Streamer.Stop();
|
|
std::fprintf(stderr,"[StreamPipeline] full shape build failed: %s\n",BuildError.c_str());return 2;
|
|
}
|
|
if(bAuditResolvedLayoutPassages)
|
|
{
|
|
if(ResolvedLayout==nullptr||!CoreSnapshot.resolvedLayout())
|
|
{Streamer.Stop();std::fprintf(stderr,"[ResolvedPassageAudit] missing resolved-layout snapshot\n");return 2;}
|
|
struct FWalkAudit
|
|
{
|
|
std::uint64_t segments=0,centers=0,capsuleSamples=0,blocked=0;
|
|
std::uint64_t supportSamples=0,unsupported=0,gradeFailures=0;
|
|
std::array<std::array<double,4>,8> firstBlocked{};
|
|
std::array<std::array<double,4>,8> firstUnsupported{};
|
|
std::array<std::array<bool,5>,8> firstSupportMasks{};
|
|
std::size_t firstBlockedCount=0,firstUnsupportedCount=0;
|
|
std::array<double,4> firstDeepUnsupported{};
|
|
std::array<bool,5> firstDeepSupportMask{};
|
|
bool hasDeepUnsupported=false;
|
|
bool passed=false;
|
|
};
|
|
struct FPassageSegmentSupportAudit
|
|
{
|
|
std::uint64_t centers=0,unsupported=0,nativeUnsupported=0;
|
|
std::array<double,3> firstUnsupported{};
|
|
std::array<double,4> firstNativeDensities{};
|
|
std::uint32_t firstNativeSupportedProbes=0;
|
|
bool hasUnsupported=false;
|
|
};
|
|
const double coreMetres=static_cast<double>(World.CoreVoxelSizeMicrons)/1000000.0;
|
|
const auto auditPlayerPath=[&](const VoxelForgeCore::World& Field,
|
|
const std::vector<std::array<double,3>>& path,
|
|
double supportStartMetres,double supportStepMetres,
|
|
double supportMaximumMetres,std::uint32_t minimumSupportProbes,
|
|
double supportProbeRadiusMetres,
|
|
const VoxelForgeCore::World* nativeField=nullptr,
|
|
std::vector<FPassageSegmentSupportAudit>* segmentSupport=nullptr)
|
|
{
|
|
FWalkAudit audit;
|
|
if(path.size()<2)return audit;
|
|
if(segmentSupport)segmentSupport->assign(path.size()-1,{});
|
|
constexpr std::array<double,7> heights{{-0.84,-0.60,-0.30,0.0,0.30,0.60,0.84}};
|
|
constexpr std::array<std::array<double,2>,8> directions{{
|
|
{{1.0,0.0}},{{0.7071067811865475,0.7071067811865475}},{{0.0,1.0}},
|
|
{{-0.7071067811865475,0.7071067811865475}},{{-1.0,0.0}},
|
|
{{-0.7071067811865475,-0.7071067811865475}},{{0.0,-1.0}},
|
|
{{0.7071067811865475,-0.7071067811865475}}}};
|
|
const auto solidAt=[&](double xMetres,double yMetres,double zMetres)
|
|
{
|
|
++audit.capsuleSamples;
|
|
const VoxelForgeCore::Vec3d position{xMetres/coreMetres,yMetres/coreMetres,zMetres/coreMetres};
|
|
const float density=Field.densityAt(position);
|
|
if(density<0.0f)
|
|
{
|
|
if(audit.firstBlockedCount<audit.firstBlocked.size())
|
|
{
|
|
audit.firstBlocked[audit.firstBlockedCount++]={xMetres,yMetres,zMetres,density};
|
|
std::printf("[ResolvedWalkDispatch] kind=blocked xyz_m=(%.3f,%.3f,%.3f) path=%s\n",
|
|
xMetres,yMetres,zMetres,Field.densityDispatchPathAt(position).c_str());
|
|
}
|
|
++audit.blocked;return true;
|
|
}
|
|
return false;
|
|
};
|
|
for(std::size_t segment=1;segment<path.size();++segment)
|
|
{
|
|
++audit.segments;
|
|
const auto& a=path[segment-1];const auto& b=path[segment];
|
|
const double dx=b[0]-a[0],dy=b[1]-a[1],dz=b[2]-a[2];
|
|
const double horizontal=std::hypot(dx,dy)*coreMetres;
|
|
const double vertical=std::abs(dz)*coreMetres;
|
|
if(horizontal<=1.0e-6||vertical>horizontal*0.2679491924311227)
|
|
++audit.gradeFailures;
|
|
const double length=std::sqrt(dx*dx+dy*dy+dz*dz)*coreMetres;
|
|
const std::uint64_t intervals=std::max<std::uint64_t>(1,
|
|
static_cast<std::uint64_t>(std::ceil(length/0.5)));
|
|
for(std::uint64_t step=0;step<=intervals;++step)
|
|
{
|
|
if(segment>1&&step==0)continue;
|
|
const double t=static_cast<double>(step)/intervals;
|
|
const double x=(a[0]+dx*t)*coreMetres;
|
|
const double y=(a[1]+dy*t)*coreMetres;
|
|
const double z=(a[2]+dz*t)*coreMetres;
|
|
++audit.centers;
|
|
FPassageSegmentSupportAudit* segmentAudit=segmentSupport
|
|
?&(*segmentSupport)[segment-1]:nullptr;
|
|
if(segmentAudit)++segmentAudit->centers;
|
|
for(const double h:heights)
|
|
{
|
|
const double capOffset=std::max(0.0,std::abs(h)-0.54);
|
|
const double radial=std::abs(h)<=0.54?0.34
|
|
:std::sqrt(std::max(0.0,0.34*0.34-capOffset*capOffset));
|
|
solidAt(x,y,z+h);
|
|
for(const auto& direction:directions)
|
|
solidAt(x+direction[0]*radial,y+direction[1]*radial,z+h);
|
|
}
|
|
const double footZ=z-0.88;
|
|
const std::array<std::array<double,2>,5> supportOffsets{{
|
|
{{0.0,0.0}},{{supportProbeRadiusMetres,0.0}},
|
|
{{-supportProbeRadiusMetres,0.0}},
|
|
{{0.0,supportProbeRadiusMetres}},
|
|
{{0.0,-supportProbeRadiusMetres}}}};
|
|
std::uint32_t supportedProbes=0;
|
|
std::uint32_t nativeSupportedProbes=0;
|
|
double firstUnsupportedX=x,firstUnsupportedY=y,lastUnsupportedDensity=0.0;
|
|
std::array<bool,5> supportMask{};
|
|
for(std::size_t offsetIndex=0;offsetIndex<supportOffsets.size();++offsetIndex)
|
|
{
|
|
const auto& offset=supportOffsets[offsetIndex];
|
|
const double supportX=x+offset[0],supportY=y+offset[1];
|
|
bool supported=false;
|
|
for(double depth=supportStartMetres;
|
|
depth<=supportMaximumMetres+1.0e-6;depth+=supportStepMetres)
|
|
{
|
|
++audit.supportSamples;
|
|
lastUnsupportedDensity=Field.densityAt({supportX/coreMetres,
|
|
supportY/coreMetres,(footZ-depth)/coreMetres});
|
|
if(lastUnsupportedDensity<0.0)
|
|
{
|
|
supported=true;break;
|
|
}
|
|
}
|
|
if(nativeField)
|
|
{
|
|
for(double depth=supportStartMetres;
|
|
depth<=supportMaximumMetres+1.0e-6;depth+=supportStepMetres)
|
|
if(nativeField->densityAt({supportX/coreMetres,
|
|
supportY/coreMetres,(footZ-depth)/coreMetres})<0.0f)
|
|
{++nativeSupportedProbes;break;}
|
|
}
|
|
if(supported){++supportedProbes;supportMask[offsetIndex]=true;}
|
|
else{firstUnsupportedX=supportX;firstUnsupportedY=supportY;}
|
|
}
|
|
if(nativeField&&segmentAudit
|
|
&&nativeSupportedProbes<minimumSupportProbes)
|
|
++segmentAudit->nativeUnsupported;
|
|
if(supportedProbes<minimumSupportProbes)
|
|
{
|
|
++audit.unsupported;
|
|
if(segmentAudit)
|
|
{
|
|
++segmentAudit->unsupported;
|
|
if(!segmentAudit->hasUnsupported)
|
|
{
|
|
segmentAudit->hasUnsupported=true;
|
|
segmentAudit->firstUnsupported={x,y,footZ};
|
|
segmentAudit->firstNativeSupportedProbes=nativeSupportedProbes;
|
|
if(nativeField)
|
|
{
|
|
constexpr std::array<double,4> depths{{0.125,1.0,2.0,4.0}};
|
|
for(std::size_t depthIndex=0;depthIndex<depths.size();++depthIndex)
|
|
segmentAudit->firstNativeDensities[depthIndex]=nativeField->densityAt({
|
|
x/coreMetres,y/coreMetres,
|
|
(footZ-depths[depthIndex])/coreMetres});
|
|
}
|
|
}
|
|
}
|
|
if(!audit.hasDeepUnsupported&&footZ<-80.0)
|
|
{
|
|
audit.hasDeepUnsupported=true;
|
|
audit.firstDeepUnsupported={firstUnsupportedX,firstUnsupportedY,
|
|
footZ,lastUnsupportedDensity};
|
|
audit.firstDeepSupportMask=supportMask;
|
|
}
|
|
if(audit.firstUnsupportedCount<audit.firstUnsupported.size())
|
|
{
|
|
const std::size_t sample=audit.firstUnsupportedCount++;
|
|
audit.firstUnsupported[sample]={
|
|
firstUnsupportedX,firstUnsupportedY,footZ,lastUnsupportedDensity};
|
|
audit.firstSupportMasks[sample]=supportMask;
|
|
std::printf("[ResolvedWalkDispatch] kind=unsupported xyz_m=(%.3f,%.3f,%.3f) support_probes=%u/5 mask=%d%d%d%d%d path=%s\n",
|
|
firstUnsupportedX,firstUnsupportedY,footZ,supportedProbes,
|
|
supportMask[0],supportMask[1],supportMask[2],supportMask[3],supportMask[4],
|
|
Field.densityDispatchPathAt({firstUnsupportedX/coreMetres,
|
|
firstUnsupportedY/coreMetres,footZ/coreMetres}).c_str());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
audit.passed=audit.centers>0&&audit.blocked==0&&audit.unsupported==0
|
|
&&audit.gradeFailures==0;
|
|
return audit;
|
|
};
|
|
const auto qToMetres=[&](std::int64_t q)
|
|
{return static_cast<double>(q)*World.CoreVoxelSizeMicrons
|
|
/(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel*1000000.0);};
|
|
struct FPassageNaturalnessAudit
|
|
{
|
|
double maximumFlatRunMetres=0.0;
|
|
double wallReliefPeakToPeakMetres=0.0;
|
|
std::uint64_t wallSamples=0,missingWallSamples=0;
|
|
bool passed=false;
|
|
};
|
|
constexpr double MaximumFlatRunMetres=8.0;
|
|
constexpr double MinimumWallReliefMetres=0.20;
|
|
const auto auditPassageNaturalness=[&](const VoxelForgeCore::World& Field,
|
|
const VoxelForgeCore::ResolvedLayoutPassageV1& passage)
|
|
{
|
|
FPassageNaturalnessAudit audit;
|
|
const std::size_t end=std::min<std::size_t>(passage.lowerMouthControlIndex,
|
|
passage.controlPoints.size()-1);
|
|
double flatRun=0.0;
|
|
for(std::size_t i=1;i<=end;++i)
|
|
{
|
|
const auto& a=passage.controlPoints[i-1];
|
|
const auto& b=passage.controlPoints[i];
|
|
const double dx=qToMetres(b.positionSubvoxels[0]-a.positionSubvoxels[0]);
|
|
const double dy=qToMetres(b.positionSubvoxels[1]-a.positionSubvoxels[1]);
|
|
const double dz=qToMetres(b.positionSubvoxels[2]-a.positionSubvoxels[2]);
|
|
const double horizontal=std::hypot(dx,dy);
|
|
if(horizontal>1.0e-6&&std::abs(dz)/horizontal
|
|
<std::tan(0.5*3.14159265358979323846/180.0))
|
|
{flatRun+=horizontal;audit.maximumFlatRunMetres=std::max(
|
|
audit.maximumFlatRunMetres,flatRun);}
|
|
else flatRun=0.0;
|
|
}
|
|
if(end>=6)
|
|
{
|
|
const std::size_t sampleCount=std::min<std::size_t>(64,end-4);
|
|
double minimumRadius=std::numeric_limits<double>::infinity();
|
|
double maximumRadius=-std::numeric_limits<double>::infinity();
|
|
for(std::size_t sample=0;sample<sampleCount;++sample)
|
|
{
|
|
const std::size_t index=2+(sample*(end-4))
|
|
/std::max<std::size_t>(1,sampleCount-1);
|
|
const auto& point=passage.controlPoints[index];
|
|
const auto& before=passage.controlPoints[index-1];
|
|
const auto& after=passage.controlPoints[index+1];
|
|
const double tangentX=qToMetres(after.positionSubvoxels[0]-before.positionSubvoxels[0]);
|
|
const double tangentY=qToMetres(after.positionSubvoxels[1]-before.positionSubvoxels[1]);
|
|
const double tangentLength=std::hypot(tangentX,tangentY);
|
|
if(tangentLength<=1.0e-6){++audit.missingWallSamples;continue;}
|
|
const double radiusMetres=qToMetres(point.radiusSubvoxels);
|
|
const double sampleRadius=std::max(0.20,radiusMetres-0.18);
|
|
const double centerX=qToMetres(point.positionSubvoxels[0]);
|
|
const double centerY=qToMetres(point.positionSubvoxels[1]);
|
|
const double centerZ=qToMetres(point.positionSubvoxels[2]);
|
|
const double nx=-tangentY/tangentLength,ny=tangentX/tangentLength;
|
|
for(const double side:{-1.0,1.0})
|
|
{
|
|
const VoxelForgeCore::Vec3d probe{
|
|
(centerX+side*nx*sampleRadius)/coreMetres,
|
|
(centerY+side*ny*sampleRadius)/coreMetres,
|
|
centerZ/coreMetres};
|
|
const double density=Field.densityAt(probe);
|
|
if(!std::isfinite(density)){++audit.missingWallSamples;continue;}
|
|
const double densityMetres=density*coreMetres;
|
|
if(densityMetres< -0.75||densityMetres>0.75)
|
|
{++audit.missingWallSamples;continue;}
|
|
// For the carved SDF, r + density is the wall radius. Its
|
|
// variation along the passage measures the applied rock relief.
|
|
const double wallRadius=sampleRadius+densityMetres;
|
|
minimumRadius=std::min(minimumRadius,wallRadius);
|
|
maximumRadius=std::max(maximumRadius,wallRadius);
|
|
++audit.wallSamples;
|
|
}
|
|
}
|
|
if(audit.wallSamples>0&&std::isfinite(minimumRadius)
|
|
&&std::isfinite(maximumRadius))
|
|
audit.wallReliefPeakToPeakMetres=maximumRadius-minimumRadius;
|
|
}
|
|
audit.passed=audit.maximumFlatRunMetres<=MaximumFlatRunMetres
|
|
&&audit.wallReliefPeakToPeakMetres>=MinimumWallReliefMetres
|
|
&&audit.wallSamples>=8;
|
|
return audit;
|
|
};
|
|
const auto looksNatural=[&](double flatRun,double reliefPeakToPeak)
|
|
{return flatRun<=MaximumFlatRunMetres
|
|
&&reliefPeakToPeak>=MinimumWallReliefMetres;};
|
|
const auto findStratum=[&](std::int32_t index)->const VoxelForgeCore::ResolvedLayoutStratumV2*
|
|
{
|
|
const auto it=std::find_if(ResolvedLayout->strates.begin(),ResolvedLayout->strates.end(),
|
|
[index](const auto& s){return s.strateIndex==index;});
|
|
return it==ResolvedLayout->strates.end()?nullptr:&*it;
|
|
};
|
|
const auto controlSnapshot=CoreSnapshot.withoutResolvedLayoutPassages();
|
|
VoxelForgeCore::World blockedWorld(controlSnapshot);
|
|
std::string blockedCompileError;
|
|
const bool blockedCompiled=controlSnapshot
|
|
&&blockedWorld.compile(ShapeRequest,Scheduler,blockedCompileError);
|
|
if(!blockedCompiled)
|
|
{
|
|
Streamer.Stop();
|
|
std::fprintf(stderr,"[ResolvedPassageBlockedControl] compile_failed=%s\n",
|
|
blockedCompileError.c_str());
|
|
return 10;
|
|
}
|
|
struct FPassageOpenAirAudit
|
|
{
|
|
std::uint64_t supportSamples=0,violations=0,legacyViolations=0,bridgeSamples=0;
|
|
std::uint64_t routeSupportSamples=0,routeSupportViolations=0;
|
|
std::array<std::array<double,4>,8> firstViolations{};
|
|
std::size_t firstViolationCount=0;
|
|
};
|
|
constexpr double PassageSupportAuditSpacingMetres=0.25;
|
|
constexpr std::array<double,2> PassageSupportAuditDepthsMetres{{0.125,0.375}};
|
|
constexpr std::array<std::array<double,2>,16> PassageSupportAuditDirections{{
|
|
{{1.0000000000000000,0.0000000000000000}},
|
|
{{0.9238795325112867,0.3826834323650898}},
|
|
{{0.7071067811865476,0.7071067811865476}},
|
|
{{0.3826834323650898,0.9238795325112867}},
|
|
{{0.0000000000000000,1.0000000000000000}},
|
|
{{-0.3826834323650898,0.9238795325112867}},
|
|
{{-0.7071067811865476,0.7071067811865476}},
|
|
{{-0.9238795325112867,0.3826834323650898}},
|
|
{{-1.0000000000000000,0.0000000000000000}},
|
|
{{-0.9238795325112867,-0.3826834323650898}},
|
|
{{-0.7071067811865476,-0.7071067811865476}},
|
|
{{-0.3826834323650898,-0.9238795325112867}},
|
|
{{0.0000000000000000,-1.0000000000000000}},
|
|
{{0.3826834323650898,-0.9238795325112867}},
|
|
{{0.7071067811865476,-0.7071067811865476}},
|
|
{{0.9238795325112867,-0.3826834323650898}}}};
|
|
const auto auditPassageOpenAir=[&](const VoxelForgeCore::World& Field,
|
|
const VoxelForgeCore::World& Native,
|
|
const VoxelForgeCore::ResolvedLayoutPassageV1& passage,
|
|
std::size_t passageIndex)
|
|
{
|
|
FPassageOpenAirAudit audit;
|
|
const auto inspect=[&](double xMetres,double yMetres,double floorMetres,
|
|
double depthMetres,double supportThicknessMetres,bool routeSupport=false,
|
|
std::size_t passageSegment=std::numeric_limits<std::size_t>::max())
|
|
{
|
|
if(depthMetres<=0.0||depthMetres>supportThicknessMetres+1.0e-6)return;
|
|
++audit.supportSamples;
|
|
audit.routeSupportSamples+=routeSupport;
|
|
const VoxelForgeCore::Vec3d sample{xMetres/coreMetres,yMetres/coreMetres,
|
|
(floorMetres-depthMetres)/coreMetres};
|
|
const float nativeDensity=Native.densityAt(sample);
|
|
const float passageDensity=Field.densityAt(sample);
|
|
const float legacySupportDensity=std::min(nativeDensity,
|
|
static_cast<float>(-depthMetres/coreMetres));
|
|
if(nativeDensity>=0.0f&&legacySupportDensity<0.0f)
|
|
++audit.legacyViolations;
|
|
if(nativeDensity>=0.0f&&passageDensity<0.0f)
|
|
{
|
|
const bool connectedBridge=!routeSupport
|
|
&&passageSegment!=std::numeric_limits<std::size_t>::max()
|
|
&&Field.isPassageBridgeSample(passageIndex,passageSegment,sample);
|
|
if(connectedBridge){++audit.bridgeSamples;return;}
|
|
audit.routeSupportViolations+=routeSupport;
|
|
if(audit.firstViolationCount<audit.firstViolations.size())
|
|
audit.firstViolations[audit.firstViolationCount++]={xMetres,yMetres,
|
|
floorMetres-depthMetres,passageDensity};
|
|
++audit.violations;
|
|
}
|
|
};
|
|
const auto inspectFloorDisc=[&](double xMetres,double yMetres,double floorMetres,
|
|
double supportRadiusMetres,double depthMetres,double supportThicknessMetres,
|
|
bool routeSupport=false,
|
|
std::size_t passageSegment=std::numeric_limits<std::size_t>::max())
|
|
{
|
|
const std::size_t radialSteps=static_cast<std::size_t>(std::ceil(
|
|
supportRadiusMetres/PassageSupportAuditSpacingMetres));
|
|
for(std::size_t radialStep=0;radialStep<=radialSteps;++radialStep)
|
|
{
|
|
const double radial=std::min(supportRadiusMetres,
|
|
radialStep*PassageSupportAuditSpacingMetres);
|
|
for(const auto& direction:PassageSupportAuditDirections)
|
|
inspect(xMetres+direction[0]*radial,yMetres+direction[1]*radial,
|
|
floorMetres,depthMetres,supportThicknessMetres,routeSupport,
|
|
passageSegment);
|
|
}
|
|
};
|
|
for(std::size_t segment=1;segment<passage.controlPoints.size();++segment)
|
|
{
|
|
const auto& a=passage.controlPoints[segment-1];
|
|
const auto& b=passage.controlPoints[segment];
|
|
const double ax=qToMetres(a.positionSubvoxels[0]);
|
|
const double ay=qToMetres(a.positionSubvoxels[1]);
|
|
const double az=qToMetres(a.positionSubvoxels[2]);
|
|
const double bx=qToMetres(b.positionSubvoxels[0]);
|
|
const double by=qToMetres(b.positionSubvoxels[1]);
|
|
const double bz=qToMetres(b.positionSubvoxels[2]);
|
|
const double length=std::sqrt((bx-ax)*(bx-ax)+(by-ay)*(by-ay)+(bz-az)*(bz-az));
|
|
const std::size_t intervals=std::max<std::size_t>(1,static_cast<std::size_t>(
|
|
std::ceil(length/PassageSupportAuditSpacingMetres)));
|
|
for(std::size_t sample=0;sample<=intervals;++sample)
|
|
{
|
|
if(segment>1&&sample==0)continue;
|
|
const double t=static_cast<double>(sample)/intervals;
|
|
const double centerX=ax+(bx-ax)*t,centerY=ay+(by-ay)*t;
|
|
const double centerZ=az+(bz-az)*t;
|
|
const double radius=qToMetres(a.radiusSubvoxels)
|
|
+(qToMetres(b.radiusSubvoxels)-qToMetres(a.radiusSubvoxels))*t;
|
|
const double floorZ=centerZ-radius+0.20;
|
|
for(const double depth:PassageSupportAuditDepthsMetres)
|
|
inspectFloorDisc(centerX,centerY,floorZ,radius,depth,1.0,
|
|
false,segment-1);
|
|
}
|
|
}
|
|
const auto upperStratum=std::find_if(ResolvedLayout->strates.begin(),
|
|
ResolvedLayout->strates.end(),[&](const auto& value)
|
|
{return value.strateIndex==passage.upperStrateIndex;});
|
|
if(upperStratum!=ResolvedLayout->strates.end())
|
|
{
|
|
std::array<const VoxelForgeCore::ResolvedLayoutAnchorV2*,5> route{};
|
|
std::size_t routeCount=0;
|
|
for(const auto& anchor:upperStratum->anchors)
|
|
if(anchor.kind=="route_point"&&anchor.values.size()>=3)
|
|
{
|
|
if(routeCount<route.size())route[routeCount++]=&anchor;
|
|
else
|
|
{
|
|
std::move(route.begin()+1,route.end(),route.begin());
|
|
route.back()=&anchor;
|
|
}
|
|
}
|
|
const double mouthX=qToMetres(passage.upperMouthSubvoxels[0]);
|
|
const double mouthY=qToMetres(passage.upperMouthSubvoxels[1]);
|
|
constexpr double RouteSupportReachMetres=13.0;
|
|
constexpr double RouteFloorOffsetMetres=1.20;
|
|
constexpr double RouteSupportThicknessMetres=1.0;
|
|
constexpr double RouteSupportDepthStepMetres=0.125;
|
|
for(std::size_t segment=1;segment<routeCount;++segment)
|
|
{
|
|
const auto& a=*route[segment-1];
|
|
const auto& b=*route[segment];
|
|
const double ax=qToMetres(a.values[0]),ay=qToMetres(a.values[1]);
|
|
const double az=qToMetres(a.values[2]);
|
|
const double bx=qToMetres(b.values[0]),by=qToMetres(b.values[1]);
|
|
const double bz=qToMetres(b.values[2]);
|
|
const double dx=bx-ax,dy=by-ay,dz=bz-az;
|
|
const double length=std::sqrt(dx*dx+dy*dy+dz*dz);
|
|
const std::size_t intervals=std::max<std::size_t>(1,
|
|
static_cast<std::size_t>(std::ceil(length
|
|
/PassageSupportAuditSpacingMetres)));
|
|
const double routeRadius=a.values.size()>=4
|
|
?std::max(0.75,qToMetres(a.values[3])):0.75;
|
|
for(std::size_t sample=0;sample<=intervals;++sample)
|
|
{
|
|
if(segment>1&&sample==0)continue;
|
|
const double t=static_cast<double>(sample)/intervals;
|
|
const double centerX=ax+dx*t,centerY=ay+dy*t;
|
|
const double centerZ=az+dz*t;
|
|
for(std::size_t radialStep=0;radialStep<=static_cast<std::size_t>(
|
|
std::ceil(routeRadius/PassageSupportAuditSpacingMetres));++radialStep)
|
|
{
|
|
const double radial=std::min(routeRadius,
|
|
radialStep*PassageSupportAuditSpacingMetres);
|
|
for(const auto& direction:PassageSupportAuditDirections)
|
|
{
|
|
const double x=centerX+direction[0]*radial;
|
|
const double y=centerY+direction[1]*radial;
|
|
if(std::hypot(x-mouthX,y-mouthY)>RouteSupportReachMetres)continue;
|
|
const double floorZ=centerZ-RouteFloorOffsetMetres;
|
|
for(double depth=RouteSupportDepthStepMetres;
|
|
depth<=RouteSupportThicknessMetres+1.0e-6;
|
|
depth+=RouteSupportDepthStepMetres)
|
|
inspect(x,y,floorZ,depth,RouteSupportThicknessMetres,true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return audit;
|
|
};
|
|
struct FPassageBridgeAudit
|
|
{
|
|
std::uint64_t spans=0,connectedEnds=0,crossSections=0,crossSectionFailures=0;
|
|
double minimumThicknessMetres=std::numeric_limits<double>::infinity();
|
|
double maximumThicknessMetres=0.0;
|
|
double thicknessReliefPeakToPeakMetres=0.0;
|
|
bool passed=true;
|
|
};
|
|
const auto auditPassageBridges=[&](const VoxelForgeCore::World& Field,
|
|
const VoxelForgeCore::World& Native,
|
|
const VoxelForgeCore::ResolvedLayoutPassageV1& passage,
|
|
std::size_t passageIndex)
|
|
{
|
|
FPassageBridgeAudit audit;
|
|
const double qMetres=coreMetres
|
|
/VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
|
|
constexpr std::array<std::array<double,2>,5> supportOffsets{{
|
|
{{0.0,0.0}},{{0.45,0.0}},{{-0.45,0.0}},{{0.0,0.45}},{{0.0,-0.45}}}};
|
|
const auto endpointConnected=[&](const std::array<double,4>& point)
|
|
{
|
|
const double x=point[0]*qMetres,y=point[1]*qMetres;
|
|
const double floor=point[2]*qMetres-point[3]*qMetres+0.13;
|
|
std::uint32_t supported=0;
|
|
for(const auto& offset:supportOffsets)
|
|
{
|
|
bool solid=false;
|
|
for(const double depth:{0.125,0.25})
|
|
if(Native.densityAt({(x+offset[0])/coreMetres,
|
|
(y+offset[1])/coreMetres,(floor-depth)/coreMetres})<0.0f)
|
|
{solid=true;break;}
|
|
supported+=solid;
|
|
}
|
|
return supported>=3;
|
|
};
|
|
const auto bridgeSolidAt=[&](std::size_t segment,double x,double y,double z)
|
|
{
|
|
return Field.isPassageBridgeSample(passageIndex,segment,
|
|
{x/coreMetres,y/coreMetres,z/coreMetres});
|
|
};
|
|
const auto thicknessAt=[&](std::size_t segment,double x,double y,double floor)
|
|
{
|
|
double thickness=0.0;
|
|
for(double depth=0.125;depth<=6.0+1.0e-6;depth+=0.125)
|
|
{
|
|
if(!bridgeSolidAt(segment,x,y,floor-depth))break;
|
|
thickness=depth;
|
|
}
|
|
return thickness;
|
|
};
|
|
for(const auto& range:Field.passageBridgeRanges())
|
|
{
|
|
if(range.passageIndex!=passageIndex
|
|
||range.lastSegment+1>=passage.controlPoints.size())continue;
|
|
++audit.spans;
|
|
const bool connectedStart=endpointConnected(range.startPointSubvoxels);
|
|
const bool connectedEnd=endpointConnected(range.endPointSubvoxels);
|
|
audit.connectedEnds+=connectedStart+connectedEnd;
|
|
const std::size_t segmentCount=range.lastSegment-range.firstSegment+1;
|
|
const std::size_t probeCount=std::min<std::size_t>(256,segmentCount);
|
|
double spanMinimumThickness=std::numeric_limits<double>::infinity();
|
|
double spanMaximumThickness=0.0;
|
|
std::uint64_t spanCrossSections=0,spanCrossSectionFailures=0;
|
|
for(std::size_t probe=0;probe<probeCount;++probe)
|
|
{
|
|
const std::size_t segment=range.firstSegment
|
|
+std::min(segmentCount-1,(probe*segmentCount)/probeCount);
|
|
const auto& a=passage.controlPoints[segment];
|
|
const auto& b=passage.controlPoints[segment+1];
|
|
const double ax=qToMetres(a.positionSubvoxels[0]);
|
|
const double ay=qToMetres(a.positionSubvoxels[1]);
|
|
const double az=qToMetres(a.positionSubvoxels[2]);
|
|
const double bx=qToMetres(b.positionSubvoxels[0]);
|
|
const double by=qToMetres(b.positionSubvoxels[1]);
|
|
const double bz=qToMetres(b.positionSubvoxels[2]);
|
|
const double x=(ax+bx)*0.5,y=(ay+by)*0.5;
|
|
const double z=(az+bz)*0.5;
|
|
const double radius=(qToMetres(a.radiusSubvoxels)
|
|
+qToMetres(b.radiusSubvoxels))*0.5;
|
|
const double floor=z-radius+0.13;
|
|
const double thickness=thicknessAt(segment,x,y,floor);
|
|
spanMinimumThickness=std::min(spanMinimumThickness,thickness);
|
|
spanMaximumThickness=std::max(spanMaximumThickness,thickness);
|
|
audit.minimumThicknessMetres=std::min(audit.minimumThicknessMetres,thickness);
|
|
audit.maximumThicknessMetres=std::max(audit.maximumThicknessMetres,thickness);
|
|
const double dx=bx-ax,dy=by-ay,horizontal=std::hypot(dx,dy);
|
|
if(horizontal<=1.0e-6)continue;
|
|
const double nx=dy/horizontal,ny=-dx/horizontal;
|
|
bool crossSectionSolid=true;
|
|
for(const double offset:{-2.5,0.0,2.5})
|
|
crossSectionSolid&=bridgeSolidAt(segment,x+nx*offset,
|
|
y+ny*offset,floor-2.0);
|
|
++audit.crossSections;
|
|
++spanCrossSections;
|
|
spanCrossSectionFailures+=!crossSectionSolid;
|
|
audit.crossSectionFailures+=!crossSectionSolid;
|
|
}
|
|
const double relief=std::isfinite(spanMinimumThickness)
|
|
?spanMaximumThickness-spanMinimumThickness:0.0;
|
|
audit.thicknessReliefPeakToPeakMetres=std::max(
|
|
audit.thicknessReliefPeakToPeakMetres,relief);
|
|
const bool spanPass=connectedStart&&connectedEnd
|
|
&&spanCrossSections>=3&&spanCrossSectionFailures==0
|
|
&&spanMinimumThickness>=2.75
|
|
&&spanMaximumThickness<=6.5
|
|
&&audit.thicknessReliefPeakToPeakMetres>=0.20;
|
|
audit.passed&=spanPass;
|
|
std::printf("[PassageRockBridgeAudit] id=%s segments=%zu..%zu gap_m=%.3f connected_ends=%d/2 width_at_least_m=5.000 cross_sections=%llu/%llu thickness_m=%.3f..%.3f relief_p2p_m=%.3f status=%s\n",
|
|
passage.stableId.c_str(),range.firstSegment,range.lastSegment,
|
|
range.unsupportedLengthMetres,static_cast<int>(connectedStart)
|
|
+static_cast<int>(connectedEnd),
|
|
static_cast<unsigned long long>(spanCrossSections-spanCrossSectionFailures),
|
|
static_cast<unsigned long long>(spanCrossSections),
|
|
std::isfinite(spanMinimumThickness)?spanMinimumThickness:0.0,
|
|
spanMaximumThickness,relief,
|
|
spanPass?"PASS":"FAIL");
|
|
}
|
|
return audit;
|
|
};
|
|
if(Assembly)
|
|
for(const auto& stratum:ResolvedLayout->strates)
|
|
{
|
|
if(stratum.generatorKind!="FloatingIslands")continue;
|
|
const auto anchor=std::find_if(stratum.anchors.begin(),stratum.anchors.end(),
|
|
[](const auto& value){return value.kind=="passage_anchor"&&value.values.size()>=7;});
|
|
const auto islandPlan=std::find_if(Assembly->floatingIslands.begin(),
|
|
Assembly->floatingIslands.end(),[&](const auto& value)
|
|
{return value.strateIndex==stratum.strateIndex;});
|
|
if(anchor==stratum.anchors.end()||islandPlan==Assembly->floatingIslands.end())continue;
|
|
const auto ordinal=islandPlan->manifest.recipe.anchorIslandOrdinal;
|
|
if(ordinal>=islandPlan->manifest.plan.islands.size())continue;
|
|
const auto& anchorIsland=islandPlan->manifest.plan.islands[ordinal];
|
|
const double axisX=qToMetres(anchor->values[0]);
|
|
const double axisY=qToMetres(anchor->values[1]);
|
|
const double rootBottom=qToMetres(anchor->values[5]);
|
|
const auto& islandRecipe=islandPlan->manifest.recipe;
|
|
const std::int64_t crustRatioQ16=islandRecipe.thicknessRatioQ16
|
|
+islandRecipe.crustDepthBonusQ16;
|
|
const std::int64_t crustDepthQ=(anchorIsland.radiusSubvoxels*crustRatioQ16
|
|
+32768)/65536;
|
|
const double rootTop=qToMetres(anchorIsland.topZSubvoxels-crustDepthQ);
|
|
const double nominalRootRadius=qToMetres(anchor->values[2]);
|
|
double helixMinimumRadius=std::numeric_limits<double>::infinity();
|
|
double helixMaximumRadius=0.0;
|
|
for(const auto& passage:ResolvedLayout->passages)
|
|
if(passage.upperStrateIndex==stratum.strateIndex)
|
|
for(const auto& point:passage.controlPoints)
|
|
{
|
|
const double z=qToMetres(point.positionSubvoxels[2]);
|
|
if(z<rootBottom||z>rootTop)continue;
|
|
const double radial=std::hypot(
|
|
qToMetres(point.positionSubvoxels[0]-anchor->values[0]),
|
|
qToMetres(point.positionSubvoxels[1]-anchor->values[1]));
|
|
helixMinimumRadius=std::min(helixMinimumRadius,radial);
|
|
helixMaximumRadius=std::max(helixMaximumRadius,radial);
|
|
}
|
|
if(!std::isfinite(helixMinimumRadius))continue;
|
|
double minimumMeasuredRootRadius=std::numeric_limits<double>::infinity();
|
|
std::uint64_t rootRaySamples=0;
|
|
constexpr std::array<double,5> RootHeightFractions{{0.01,0.25,0.5,0.75,0.99}};
|
|
for(const double fraction:RootHeightFractions)
|
|
{
|
|
const double z=rootTop+(rootBottom-rootTop)*fraction;
|
|
double minimumRadius=std::numeric_limits<double>::infinity();
|
|
double maximumRadius=0.0,meanRadius=0.0;
|
|
constexpr std::size_t DirectionCount=32;
|
|
constexpr double RayStepMetres=0.125;
|
|
const std::size_t raySteps=static_cast<std::size_t>(std::ceil(
|
|
(nominalRootRadius+2.0)/RayStepMetres));
|
|
for(std::size_t direction=0;direction<DirectionCount;++direction)
|
|
{
|
|
const double angle=2.0*3.14159265358979323846*direction/DirectionCount;
|
|
double radius=0.0;
|
|
for(std::size_t step=0;step<=raySteps;++step)
|
|
{
|
|
const double probeRadius=step*RayStepMetres;
|
|
const VoxelForgeCore::Vec3d probe{
|
|
(axisX+probeRadius*std::cos(angle))/coreMetres,
|
|
(axisY+probeRadius*std::sin(angle))/coreMetres,z/coreMetres};
|
|
if(blockedWorld.densityAt(probe)>=0.0f)break;
|
|
radius=probeRadius;
|
|
}
|
|
minimumRadius=std::min(minimumRadius,radius);
|
|
maximumRadius=std::max(maximumRadius,radius);
|
|
meanRadius+=radius;
|
|
++rootRaySamples;
|
|
}
|
|
meanRadius/=DirectionCount;
|
|
minimumMeasuredRootRadius=std::min(minimumMeasuredRootRadius,minimumRadius);
|
|
std::printf("[PassageRootRadiusMeasure] strate=%d z_m=%.3f solid_radius_min_m=%.3f mean_m=%.3f max_m=%.3f nominal_root_m=%.3f rays=%zu\n",
|
|
stratum.strateIndex,z,minimumRadius,meanRadius,maximumRadius,
|
|
nominalRootRadius,DirectionCount);
|
|
}
|
|
const auto firstRootPassage=std::find_if(ResolvedLayout->passages.begin(),
|
|
ResolvedLayout->passages.end(),[&](const auto& passage)
|
|
{return passage.upperStrateIndex==stratum.strateIndex;});
|
|
const double mouthBoreRadius=firstRootPassage==ResolvedLayout->passages.end()?0.0
|
|
:qToMetres(firstRootPassage->mouthRadiusSubvoxels);
|
|
const double midBoreRadius=firstRootPassage==ResolvedLayout->passages.end()?0.0
|
|
:qToMetres(firstRootPassage->midRadiusSubvoxels);
|
|
const double maximumBoreRadius=std::max(mouthBoreRadius,midBoreRadius);
|
|
std::printf("[PassageRootMeasure] strate=%d axis_m=(%.3f,%.3f) helix_centerline_radius_min_m=%.3f max_m=%.3f mouth_bore_radius_m=%.3f mid_bore_radius_m=%.3f nominal_root_m=%.3f measured_min_root_m=%.3f minimum_wall_m=%.3f samples=%llu\n",
|
|
stratum.strateIndex,axisX,axisY,helixMinimumRadius,helixMaximumRadius,
|
|
mouthBoreRadius,midBoreRadius,nominalRootRadius,minimumMeasuredRootRadius,
|
|
minimumMeasuredRootRadius-helixMaximumRadius-maximumBoreRadius,
|
|
static_cast<unsigned long long>(rootRaySamples));
|
|
}
|
|
const auto mouthInContent=[&](std::int32_t index,const std::array<std::int64_t,3>& mouth)
|
|
{
|
|
const auto* stratum=findStratum(index);if(!stratum)return false;
|
|
const std::int64_t zUm=static_cast<std::int64_t>(std::llround(
|
|
static_cast<double>(mouth[2])*World.CoreVoxelSizeMicrons
|
|
/VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel));
|
|
return zUm>=stratum->content.bottomMicrometres&&zUm<stratum->content.topMicrometres;
|
|
};
|
|
std::uint64_t passageCount=0,passagePasses=0,blockedControls=0,naturalPasses=0;
|
|
std::uint64_t openAirViolations=0,openAirLegacyViolations=0,openAirSamples=0;
|
|
std::uint64_t openAirAudits=0,openAirBridgeSamples=0,bridgeSpans=0,
|
|
bridgeConnectedEnds=0;
|
|
bool bridgeAuditsPassed=true;
|
|
for(std::size_t passageIndex=0;passageIndex<ResolvedLayout->passages.size();++passageIndex)
|
|
{
|
|
const auto& passage=ResolvedLayout->passages[passageIndex];
|
|
std::vector<std::array<double,3>> path;path.reserve(passage.controlPoints.size());
|
|
for(const auto& point:passage.controlPoints)
|
|
{
|
|
const double playerZ=static_cast<double>(point.positionSubvoxels[2]
|
|
-point.radiusSubvoxels)+1.13*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel
|
|
*1000000.0/World.CoreVoxelSizeMicrons;
|
|
path.push_back({qToMetres(point.positionSubvoxels[0])/coreMetres,
|
|
qToMetres(point.positionSubvoxels[1])/coreMetres,playerZ/256.0});
|
|
}
|
|
std::vector<FPassageSegmentSupportAudit> segmentSupport;
|
|
const FWalkAudit passageAudit=auditPlayerPath(Shape,path,0.125,0.125,0.25,3,0.45,
|
|
&blockedWorld,&segmentSupport);
|
|
const FPassageNaturalnessAudit naturalAudit=auditPassageNaturalness(Shape,passage);
|
|
const FPassageOpenAirAudit openAirAudit=auditPassageOpenAir(Shape,blockedWorld,
|
|
passage,passageIndex);
|
|
const FPassageBridgeAudit bridgeAudit=auditPassageBridges(Shape,blockedWorld,
|
|
passage,passageIndex);
|
|
openAirSamples+=openAirAudit.supportSamples;
|
|
openAirViolations+=openAirAudit.violations;
|
|
openAirLegacyViolations+=openAirAudit.legacyViolations;
|
|
openAirBridgeSamples+=openAirAudit.bridgeSamples;
|
|
bridgeSpans+=bridgeAudit.spans;
|
|
bridgeConnectedEnds+=bridgeAudit.connectedEnds;
|
|
bridgeAuditsPassed&=bridgeAudit.passed;
|
|
++openAirAudits;
|
|
const auto radialSquared=[](const std::array<std::int64_t,3>& p)
|
|
{return p[0]*p[0]+p[1]*p[1];};
|
|
const bool mouthsInRadius=radialSquared(passage.upperMouthSubvoxels)
|
|
<=passage.placementRadiusSubvoxels*passage.placementRadiusSubvoxels
|
|
&&radialSquared(passage.lowerMouthSubvoxels)
|
|
<=passage.placementRadiusSubvoxels*passage.placementRadiusSubvoxels;
|
|
const bool mouthsInContent=mouthInContent(passage.upperStrateIndex,passage.upperMouthSubvoxels)
|
|
&&mouthInContent(passage.lowerStrateIndex,passage.lowerMouthSubvoxels);
|
|
++passageCount;naturalPasses+=naturalAudit.passed;
|
|
passagePasses+=passageAudit.passed&&mouthsInRadius&&mouthsInContent
|
|
&&naturalAudit.passed;
|
|
std::printf("[ResolvedPassageAudit] id=%s interface=%d-%d control_points=%zu center_samples=%llu capsule_samples=%llu blocked=%llu support_samples=%llu unsupported=%llu grade_failures=%llu mouth_content=%d/2 mouth_radius=%d/2 walkability=%s\n",
|
|
passage.stableId.c_str(),passage.upperStrateIndex,passage.lowerStrateIndex,
|
|
passage.controlPoints.size(),static_cast<unsigned long long>(passageAudit.centers),
|
|
static_cast<unsigned long long>(passageAudit.capsuleSamples),
|
|
static_cast<unsigned long long>(passageAudit.blocked),
|
|
static_cast<unsigned long long>(passageAudit.supportSamples),
|
|
static_cast<unsigned long long>(passageAudit.unsupported),
|
|
static_cast<unsigned long long>(passageAudit.gradeFailures),mouthsInContent?2:0,
|
|
mouthsInRadius?2:0,passageAudit.passed?"PASS":"FAIL");
|
|
std::printf("[PassageNaturalnessAudit] id=%s flat_run_max_m=%.3f limit_m=%.3f wall_relief_p2p_m=%.3f minimum_m=%.3f samples=%llu missing=%llu status=%s\n",
|
|
passage.stableId.c_str(),naturalAudit.maximumFlatRunMetres,MaximumFlatRunMetres,
|
|
naturalAudit.wallReliefPeakToPeakMetres,MinimumWallReliefMetres,
|
|
static_cast<unsigned long long>(naturalAudit.wallSamples),
|
|
static_cast<unsigned long long>(naturalAudit.missingWallSamples),
|
|
naturalAudit.passed?"PASS":"FAIL");
|
|
std::printf("[PassageOpenAirAudit] id=%s support_samples=%llu violations=%llu bridge_samples=%llu l24_ungated_violations=%llu route_support_samples=%llu route_support_violations=%llu spacing_m=%.3f status=%s\n",
|
|
passage.stableId.c_str(),static_cast<unsigned long long>(openAirAudit.supportSamples),
|
|
static_cast<unsigned long long>(openAirAudit.violations),
|
|
static_cast<unsigned long long>(openAirAudit.bridgeSamples),
|
|
static_cast<unsigned long long>(openAirAudit.legacyViolations),
|
|
static_cast<unsigned long long>(openAirAudit.routeSupportSamples),
|
|
static_cast<unsigned long long>(openAirAudit.routeSupportViolations),
|
|
PassageSupportAuditSpacingMetres,
|
|
openAirAudit.violations==0?"PASS":"FAIL");
|
|
for(std::size_t i=0;i<openAirAudit.firstViolationCount;++i)
|
|
std::printf("[PassageOpenAirAuditSample] id=%s kind=unsupported_support xyz_m=(%.3f,%.3f,%.3f) density=%.6f\n",
|
|
passage.stableId.c_str(),openAirAudit.firstViolations[i][0],
|
|
openAirAudit.firstViolations[i][1],openAirAudit.firstViolations[i][2],
|
|
openAirAudit.firstViolations[i][3]);
|
|
for(std::size_t i=0;i<passageAudit.firstBlockedCount;++i)
|
|
{
|
|
const auto& sample=passageAudit.firstBlocked[i];
|
|
double nearestDistanceSquared=std::numeric_limits<double>::infinity();
|
|
std::size_t nearestSegment=0;
|
|
double nearestT=0.0;
|
|
double nearestPassageSdf=std::numeric_limits<double>::infinity();
|
|
std::size_t nearestPassageSegment=0;
|
|
for(std::size_t segment=0;segment+1<path.size();++segment)
|
|
{
|
|
const auto& a=path[segment];const auto& b=path[segment+1];
|
|
const double dx=b[0]-a[0],dy=b[1]-a[1],dz=b[2]-a[2];
|
|
const double lengthSquared=dx*dx+dy*dy+dz*dz;
|
|
const double t=lengthSquared>0.0?std::clamp(
|
|
((sample[0]/coreMetres-a[0])*dx+(sample[1]/coreMetres-a[1])*dy
|
|
+(sample[2]/coreMetres-a[2])*dz)/lengthSquared,0.0,1.0):0.0;
|
|
const double ex=sample[0]/coreMetres-(a[0]+t*dx);
|
|
const double ey=sample[1]/coreMetres-(a[1]+t*dy);
|
|
const double ez=sample[2]/coreMetres-(a[2]+t*dz);
|
|
const double distanceSquared=ex*ex+ey*ey+ez*ez;
|
|
if(distanceSquared<nearestDistanceSquared)
|
|
{nearestDistanceSquared=distanceSquared;nearestSegment=segment;nearestT=t;}
|
|
}
|
|
for(std::size_t segment=0;segment+1<passage.controlPoints.size();++segment)
|
|
{
|
|
const auto& a=passage.controlPoints[segment];
|
|
const auto& b=passage.controlPoints[segment+1];
|
|
const double ax=qToMetres(a.positionSubvoxels[0]);
|
|
const double ay=qToMetres(a.positionSubvoxels[1]);
|
|
const double az=qToMetres(a.positionSubvoxels[2]);
|
|
const double dx=qToMetres(b.positionSubvoxels[0]-a.positionSubvoxels[0]);
|
|
const double dy=qToMetres(b.positionSubvoxels[1]-a.positionSubvoxels[1]);
|
|
const double dz=qToMetres(b.positionSubvoxels[2]-a.positionSubvoxels[2]);
|
|
const double lengthSquared=dx*dx+dy*dy+dz*dz;
|
|
const double t=lengthSquared>0.0?std::clamp(
|
|
((sample[0]-ax)*dx+(sample[1]-ay)*dy+(sample[2]-az)*dz)
|
|
/lengthSquared,0.0,1.0):0.0;
|
|
const double ex=sample[0]-(ax+t*dx),ey=sample[1]-(ay+t*dy),
|
|
ez=sample[2]-(az+t*dz);
|
|
const double radius=qToMetres(a.radiusSubvoxels)
|
|
+(qToMetres(b.radiusSubvoxels)-qToMetres(a.radiusSubvoxels))*t;
|
|
const double signedDistance=std::sqrt(ex*ex+ey*ey+ez*ez)-radius;
|
|
if(signedDistance<nearestPassageSdf)
|
|
{nearestPassageSdf=signedDistance;nearestPassageSegment=segment;}
|
|
}
|
|
const VoxelForgeCore::Vec3d nativePosition{sample[0]/coreMetres,
|
|
sample[1]/coreMetres,sample[2]/coreMetres};
|
|
const float nativeDensity=blockedWorld.densityAt(nativePosition);
|
|
const bool bridgeVolume=Shape.isPassageBridgeSample(passageIndex,
|
|
nearestSegment,nativePosition);
|
|
std::printf("[ResolvedPassageAuditSample] id=%s kind=blocked xyz_m=(%.3f,%.3f,%.3f) density=%.6f native_density=%.6f segment=%zu t=%.3f passage_segment=%zu passage_sdf_m=%.3f bridge_volume=%d native_path=%s\n",
|
|
passage.stableId.c_str(),sample[0],sample[1],sample[2],sample[3],
|
|
nativeDensity,nearestSegment,nearestT,nearestPassageSegment,
|
|
nearestPassageSdf,bridgeVolume?1:0,
|
|
blockedWorld.densityDispatchPathAt(nativePosition).c_str());
|
|
}
|
|
for(std::size_t i=0;i<passageAudit.firstUnsupportedCount;++i)
|
|
{
|
|
std::printf("[ResolvedPassageAuditSample] id=%s kind=unsupported xyz_m=(%.3f,%.3f,%.3f) density=%.6f\n",
|
|
passage.stableId.c_str(),passageAudit.firstUnsupported[i][0],
|
|
passageAudit.firstUnsupported[i][1],passageAudit.firstUnsupported[i][2],
|
|
passageAudit.firstUnsupported[i][3]);
|
|
}
|
|
for(std::size_t segment=0;segment<segmentSupport.size();++segment)
|
|
{
|
|
const auto& item=segmentSupport[segment];
|
|
if(!item.hasUnsupported)continue;
|
|
const auto& a=path[segment];const auto& b=path[segment+1];
|
|
const auto& sample=item.firstUnsupported;
|
|
const double firstX=sample[0]/coreMetres;
|
|
const double firstY=sample[1]/coreMetres;
|
|
const double firstZ=(sample[2]-0.125)/coreMetres;
|
|
const VoxelForgeCore::Vec3d bridgeProbe{sample[0]/coreMetres,
|
|
sample[1]/coreMetres,(sample[2]-0.25)/coreMetres};
|
|
const bool bridgeAtSupport=Shape.isPassageBridgeSample(passageIndex,
|
|
segment,bridgeProbe);
|
|
std::printf("[ResolvedPassageSupportSegment] id=%s segment=%zu route_z_m=%.3f..%.3f centers=%llu unsupported=%llu native_unsupported=%llu first_foot_m=(%.3f,%.3f,%.3f) bridge_at_d025=%d native_support_probes=%u/5 native_density_d0125_d1_d2_d4=(%.6f,%.6f,%.6f,%.6f) native_path=%s\n",
|
|
passage.stableId.c_str(),segment,
|
|
std::min(a[2],b[2])*coreMetres,std::max(a[2],b[2])*coreMetres,
|
|
static_cast<unsigned long long>(item.centers),
|
|
static_cast<unsigned long long>(item.unsupported),
|
|
static_cast<unsigned long long>(item.nativeUnsupported),
|
|
sample[0],sample[1],sample[2],bridgeAtSupport?1:0,
|
|
item.firstNativeSupportedProbes,item.firstNativeDensities[0],item.firstNativeDensities[1],
|
|
item.firstNativeDensities[2],item.firstNativeDensities[3],
|
|
blockedWorld.densityDispatchPathAt({firstX,firstY,firstZ}).c_str());
|
|
}
|
|
}
|
|
std::uint64_t routePoints=0,routePasses=0,routeWalkPasses=0,routeBindings=0,routeBindingFailures=0;
|
|
struct FRouteAuditInput
|
|
{
|
|
std::int32_t strateIndex=0;
|
|
std::vector<std::array<double,3>> path;
|
|
double supportStart=0.125,supportStep=0.125,supportMaximum=0.25,
|
|
supportProbeRadius=0.45;
|
|
std::uint32_t minimumSupportProbes=3;
|
|
};
|
|
std::vector<FRouteAuditInput> routeAuditInputs;
|
|
for(const auto& stratum:ResolvedLayout->strates)
|
|
{
|
|
std::vector<const VoxelForgeCore::ResolvedLayoutAnchorV2*> anchors;
|
|
for(const auto& anchor:stratum.anchors)
|
|
if(anchor.kind=="route_point"&&anchor.values.size()>=3)anchors.push_back(&anchor);
|
|
if(anchors.size()<2)continue;
|
|
std::vector<std::array<double,3>> route;route.reserve(anchors.size());
|
|
for(const auto* anchor:anchors)
|
|
{
|
|
route.push_back({qToMetres(anchor->values[0])/coreMetres,
|
|
qToMetres(anchor->values[1])/coreMetres,
|
|
qToMetres(anchor->values[2])/coreMetres});
|
|
++routePoints;
|
|
}
|
|
double supportStartMetres=0.125,supportStepMetres=0.125,
|
|
supportMaximumMetres=0.25,supportProbeRadiusMetres=0.45;
|
|
std::uint32_t minimumSupportProbes=3;
|
|
const VoxelForgeCore::WorldAssemblyRouteConstraintsV1* routeConstraints=nullptr;
|
|
if(Assembly)
|
|
{
|
|
const auto worldRoute=std::find_if(Assembly->plan.strates.begin(),
|
|
Assembly->plan.strates.end(),[&](const auto& candidate)
|
|
{return candidate.strateIndex==stratum.strateIndex;});
|
|
if(worldRoute!=Assembly->plan.strates.end()
|
|
&&worldRoute->routeConstraints.version==1)
|
|
routeConstraints=&worldRoute->routeConstraints;
|
|
const auto islandRoute=std::find_if(Assembly->floatingIslands.begin(),
|
|
Assembly->floatingIslands.end(),[&](const auto& candidate)
|
|
{return candidate.strateIndex==stratum.strateIndex;});
|
|
if(islandRoute!=Assembly->floatingIslands.end()
|
|
&&islandRoute->manifest.recipe.routeConstraints.version==1)
|
|
routeConstraints=&islandRoute->manifest.recipe.routeConstraints;
|
|
}
|
|
if(routeConstraints)
|
|
{
|
|
const double qMetres=coreMetres
|
|
/VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
|
|
minimumSupportProbes=std::clamp<std::uint32_t>(
|
|
routeConstraints->minimumSupportProbes,1,5);
|
|
supportProbeRadiusMetres=routeConstraints->supportProbeRadiusSubvoxels*qMetres;
|
|
if(stratum.generatorKind=="FloatingIslands")
|
|
{
|
|
supportStartMetres=(routeConstraints->minimumClearanceSubvoxels
|
|
+VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel/2)*qMetres;
|
|
supportStepMetres=VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel
|
|
*0.5*qMetres;
|
|
supportMaximumMetres=(routeConstraints->minimumClearanceSubvoxels
|
|
+routeConstraints->supportSearchDepthSubvoxels)*qMetres;
|
|
}
|
|
else
|
|
{
|
|
const std::int64_t spacing=std::min<std::int64_t>(
|
|
routeConstraints->sweepSpacingSubvoxels,
|
|
VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel/4);
|
|
supportStepMetres=std::max<std::int64_t>(1,spacing)*qMetres;
|
|
supportStartMetres=supportStepMetres;
|
|
supportMaximumMetres=routeConstraints->supportSearchDepthSubvoxels*qMetres;
|
|
}
|
|
}
|
|
const FWalkAudit routeAudit=auditPlayerPath(Shape,route,supportStartMetres,
|
|
supportStepMetres,supportMaximumMetres,minimumSupportProbes,
|
|
supportProbeRadiusMetres,nullptr,nullptr);
|
|
routeAuditInputs.push_back({stratum.strateIndex,route,supportStartMetres,
|
|
supportStepMetres,supportMaximumMetres,supportProbeRadiusMetres,
|
|
minimumSupportProbes});
|
|
bool binding=true;
|
|
for(const auto& passage:ResolvedLayout->passages)
|
|
{
|
|
const auto equalPosition=[](const auto& a,const auto& b)
|
|
{return a[0]==b[0]&&a[1]==b[1]&&a[2]==b[2];};
|
|
if(passage.upperStrateIndex==stratum.strateIndex)
|
|
{
|
|
const auto rootedMouth=std::find_if(stratum.anchors.begin(),
|
|
stratum.anchors.end(),[](const auto& anchor)
|
|
{return anchor.kind=="passage_anchor"&&anchor.values.size()>=10;});
|
|
const std::array<std::int64_t,3> departure=
|
|
rootedMouth!=stratum.anchors.end()
|
|
?std::array<std::int64_t,3>{{rootedMouth->values[7],
|
|
rootedMouth->values[8],rootedMouth->values[9]}}
|
|
:std::array<std::int64_t,3>{{anchors.back()->values[0],
|
|
anchors.back()->values[1],anchors.back()->values[2]}};
|
|
binding&=equalPosition(passage.upperMouthSubvoxels,
|
|
departure);
|
|
}
|
|
if(passage.lowerStrateIndex==stratum.strateIndex)
|
|
{
|
|
const auto passageArrival=std::find_if(stratum.anchors.begin(),
|
|
stratum.anchors.end(),[](const auto& anchor)
|
|
{return anchor.kind=="passage_arrival"&&anchor.values.size()>=3;});
|
|
const std::array<std::int64_t,3> arrival=passageArrival!=stratum.anchors.end()
|
|
?std::array<std::int64_t,3>{{passageArrival->values[0],
|
|
passageArrival->values[1],passageArrival->values[2]}}
|
|
:std::array<std::int64_t,3>{{anchors.front()->values[0],
|
|
anchors.front()->values[1],anchors.front()->values[2]}};
|
|
binding&=equalPosition(passage.lowerMouthSubvoxels,
|
|
arrival);
|
|
}
|
|
}
|
|
++routePasses;routeWalkPasses+=routeAudit.passed;
|
|
routeBindings+=binding;routeBindingFailures+=!binding;
|
|
std::printf("[ResolvedStrateRouteAudit] strate=%d route_points=%zu center_samples=%llu capsule_samples=%llu blocked=%llu support_samples=%llu unsupported=%llu grade_failures=%llu mouth_binding=%s status=%s\n",
|
|
stratum.strateIndex,anchors.size(),static_cast<unsigned long long>(routeAudit.centers),
|
|
static_cast<unsigned long long>(routeAudit.capsuleSamples),
|
|
static_cast<unsigned long long>(routeAudit.blocked),
|
|
static_cast<unsigned long long>(routeAudit.supportSamples),
|
|
static_cast<unsigned long long>(routeAudit.unsupported),
|
|
static_cast<unsigned long long>(routeAudit.gradeFailures),binding?"PASS":"FAIL",
|
|
routeAudit.passed&&binding?"PASS":"FAIL");
|
|
for(std::size_t i=0;i<routeAudit.firstBlockedCount;++i)
|
|
std::printf("[ResolvedStrateRouteAuditSample] strate=%d kind=blocked xyz_m=(%.3f,%.3f,%.3f) density=%.6f\n",
|
|
stratum.strateIndex,routeAudit.firstBlocked[i][0],routeAudit.firstBlocked[i][1],
|
|
routeAudit.firstBlocked[i][2],routeAudit.firstBlocked[i][3]);
|
|
for(std::size_t i=0;i<routeAudit.firstUnsupportedCount;++i)
|
|
std::printf("[ResolvedStrateRouteAuditSample] strate=%d kind=unsupported xyz_m=(%.3f,%.3f,%.3f) density=%.6f\n",
|
|
stratum.strateIndex,routeAudit.firstUnsupported[i][0],
|
|
routeAudit.firstUnsupported[i][1],routeAudit.firstUnsupported[i][2],
|
|
routeAudit.firstUnsupported[i][3]);
|
|
}
|
|
for(const auto& passage:ResolvedLayout->passages)
|
|
{
|
|
std::vector<std::array<double,3>> path;path.reserve(passage.controlPoints.size());
|
|
for(const auto& point:passage.controlPoints)
|
|
{
|
|
const double playerZ=static_cast<double>(point.positionSubvoxels[2]
|
|
-point.radiusSubvoxels)+1.13*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel
|
|
*1000000.0/World.CoreVoxelSizeMicrons;
|
|
path.push_back({qToMetres(point.positionSubvoxels[0])/coreMetres,
|
|
qToMetres(point.positionSubvoxels[1])/coreMetres,playerZ/256.0});
|
|
}
|
|
const FWalkAudit blockedAudit=auditPlayerPath(blockedWorld,path,
|
|
0.125,0.125,0.25,3,0.45,nullptr,nullptr);
|
|
blockedControls+=blockedAudit.blocked>0&&!blockedAudit.passed;
|
|
std::printf("[ResolvedPassageBlockedControl] id=%s blocked_capsule_samples=%llu detected=%s\n",
|
|
passage.stableId.c_str(),static_cast<unsigned long long>(blockedAudit.blocked),
|
|
blockedAudit.blocked>0&&!blockedAudit.passed?"true":"false");
|
|
}
|
|
for(const auto& input:routeAuditInputs)
|
|
{
|
|
const FWalkAudit controlRoute=auditPlayerPath(blockedWorld,input.path,
|
|
input.supportStart,input.supportStep,input.supportMaximum,
|
|
input.minimumSupportProbes,input.supportProbeRadius,nullptr,nullptr);
|
|
std::printf("[ResolvedStrateRouteBlockedControl] strate=%d blocked=%llu unsupported=%llu status=%s\n",
|
|
input.strateIndex,static_cast<unsigned long long>(controlRoute.blocked),
|
|
static_cast<unsigned long long>(controlRoute.unsupported),
|
|
controlRoute.passed?"PASS":"FAIL");
|
|
}
|
|
const bool l24OpenAirControlDetected=openAirLegacyViolations>0;
|
|
std::printf("[PassageOpenAirNegativeControl] source=L24_ungated_support unsupported_samples=%llu detected=%s\n",
|
|
static_cast<unsigned long long>(openAirLegacyViolations),
|
|
l24OpenAirControlDetected?"true":"false");
|
|
std::printf("[PassageOpenAirAuditSummary] interfaces=%llu samples=%llu violations=%llu bridge_samples=%llu l24_negative_control=%s status=%s\n",
|
|
static_cast<unsigned long long>(openAirAudits),
|
|
static_cast<unsigned long long>(openAirSamples),
|
|
static_cast<unsigned long long>(openAirViolations),
|
|
static_cast<unsigned long long>(openAirBridgeSamples),
|
|
l24OpenAirControlDetected?"DETECTED":"MISSED",
|
|
openAirAudits==passageCount&&openAirViolations==0&&l24OpenAirControlDetected
|
|
?"PASS":"FAIL");
|
|
std::printf("[PassageRockBridgeAuditSummary] spans=%llu connected_ends=%llu/%llu status=%s\n",
|
|
static_cast<unsigned long long>(bridgeSpans),
|
|
static_cast<unsigned long long>(bridgeConnectedEnds),
|
|
static_cast<unsigned long long>(bridgeSpans*2),
|
|
bridgeAuditsPassed?"PASS":"FAIL");
|
|
const bool l23SmoothWallDetected=!looksNatural(0.0,0.0);
|
|
std::printf("[PassageNaturalnessNegativeControl] source=L23_smooth_unrelieved_wall relief_p2p_m=0.000 threshold_m=%.3f detected=%s\n",
|
|
MinimumWallReliefMetres,l23SmoothWallDetected?"true":"false");
|
|
std::printf("[PassageNaturalnessSummary] passed=%llu/%llu l23_negative_control=%s flat_run_limit_m=%.3f wall_relief_min_m=%.3f status=%s\n",
|
|
static_cast<unsigned long long>(naturalPasses),static_cast<unsigned long long>(passageCount),
|
|
l23SmoothWallDetected?"DETECTED":"MISSED",MaximumFlatRunMetres,
|
|
MinimumWallReliefMetres,naturalPasses==passageCount&&l23SmoothWallDetected?"PASS":"FAIL");
|
|
const bool auditPassed=passageCount>0&&passagePasses==passageCount
|
|
&&naturalPasses==passageCount&&l23SmoothWallDetected
|
|
&&openAirAudits==passageCount&&openAirViolations==0&&l24OpenAirControlDetected
|
|
&&bridgeAuditsPassed
|
|
&&routePasses>0&&routePasses==routeWalkPasses&&routePasses==routeBindings
|
|
&&routeBindingFailures==0
|
|
&&blockedCompiled&&blockedControls==passageCount;
|
|
std::printf("[ResolvedPassageAuditSummary] interfaces=%llu passed=%llu natural=%llu/%llu strate_routes=%llu route_walkable=%llu route_points=%llu route_bindings=%llu route_binding_failures=%llu blocked_controls=%llu/%llu status=%s\n",
|
|
static_cast<unsigned long long>(passageCount),static_cast<unsigned long long>(passagePasses),
|
|
static_cast<unsigned long long>(naturalPasses),static_cast<unsigned long long>(passageCount),
|
|
static_cast<unsigned long long>(routePasses),static_cast<unsigned long long>(routeWalkPasses),
|
|
static_cast<unsigned long long>(routePoints),
|
|
static_cast<unsigned long long>(routeBindings),static_cast<unsigned long long>(routeBindingFailures),
|
|
static_cast<unsigned long long>(blockedControls),static_cast<unsigned long long>(passageCount),
|
|
auditPassed?"PASS":"FAIL");
|
|
if(!auditPassed)
|
|
{Streamer.Stop();return 10;}
|
|
}
|
|
if(bAuditResolvedLayoutBoundaries)
|
|
{
|
|
if(ResolvedLayout==nullptr)
|
|
{Streamer.Stop();std::fprintf(stderr,"[ResolvedLayoutProbe] missing plan-time snapshot\n");return 2;}
|
|
std::vector<std::int64_t> endpoints;
|
|
for(const auto& Stratum:ResolvedLayout->strates)
|
|
{
|
|
endpoints.push_back(Stratum.interiorBottomMicrometres);
|
|
endpoints.push_back(Stratum.interiorTopMicrometres);
|
|
endpoints.push_back(Stratum.ceiling.bottomMicrometres);
|
|
endpoints.push_back(Stratum.ceiling.topMicrometres);
|
|
endpoints.push_back(Stratum.bedrock.bottomMicrometres);
|
|
endpoints.push_back(Stratum.bedrock.topMicrometres);
|
|
}
|
|
for(std::size_t StratumIndex=0;StratumIndex+1<ResolvedLayout->strates.size();++StratumIndex)
|
|
{
|
|
const auto& Upper=ResolvedLayout->strates[StratumIndex];
|
|
if(Upper.transitionMode=="Hard"||Upper.transitionWidthMicrometres<=0)continue;
|
|
endpoints.push_back(Upper.interiorBottomMicrometres-Upper.transitionWidthMicrometres);
|
|
endpoints.push_back(Upper.interiorBottomMicrometres+Upper.transitionWidthMicrometres);
|
|
}
|
|
std::sort(endpoints.begin(),endpoints.end());
|
|
endpoints.erase(std::unique(endpoints.begin(),endpoints.end()),endpoints.end());
|
|
std::uint64_t probes=0,disagreements=0;
|
|
const double CoreVoxelMicrometres=std::max<std::uint32_t>(1,World.CoreVoxelSizeMicrons);
|
|
const auto expectedAt=[&](std::int64_t zMicrometres)
|
|
{
|
|
std::vector<std::string> candidates;
|
|
for(const auto& Stratum:ResolvedLayout->strates)
|
|
{
|
|
const auto contains=[zMicrometres](const VoxelForgeCore::ResolvedLayoutBandV2& Band)
|
|
{return Band.presentInCurrentField&&zMicrometres>=Band.bottomMicrometres
|
|
&&zMicrometres<Band.topMicrometres;};
|
|
if(contains(Stratum.ceiling))candidates.push_back(Stratum.generatorKind+"/ceiling");
|
|
if(contains(Stratum.content))candidates.push_back(Stratum.generatorKind+"/content");
|
|
if(contains(Stratum.bedrock))candidates.push_back(Stratum.generatorKind+"/bedrock");
|
|
}
|
|
return candidates;
|
|
};
|
|
const std::array<std::int64_t,3> offsets{{
|
|
-static_cast<std::int64_t>(World.CoreVoxelSizeMicrons),0,
|
|
static_cast<std::int64_t>(World.CoreVoxelSizeMicrons)}};
|
|
const std::int64_t stackMinimum=std::min_element(ResolvedLayout->strates.begin(),
|
|
ResolvedLayout->strates.end(),[](const auto& A,const auto& B)
|
|
{return A.bedrock.bottomMicrometres<B.bedrock.bottomMicrometres;})
|
|
->bedrock.bottomMicrometres;
|
|
const std::int64_t stackMaximum=std::max_element(ResolvedLayout->strates.begin(),
|
|
ResolvedLayout->strates.end(),[](const auto& A,const auto& B)
|
|
{return A.ceiling.topMicrometres<B.ceiling.topMicrometres;})
|
|
->ceiling.topMicrometres;
|
|
for(const std::int64_t Endpoint:endpoints)
|
|
for(const std::int64_t Offset:offsets)
|
|
{
|
|
const std::int64_t zMicrometres=Endpoint+Offset;
|
|
const double zVoxel=static_cast<double>(zMicrometres)/CoreVoxelMicrometres;
|
|
const VoxelForgeCore::Vec3d position{0.0,0.0,zVoxel};
|
|
const std::string actual=Shape.densityDispatchPathAt(position);
|
|
const auto expected=expectedAt(zMicrometres);
|
|
std::string expectedLabel;
|
|
std::vector<std::string> expectedGenerators;
|
|
for(const auto& Candidate:expected)
|
|
{
|
|
if(!expectedLabel.empty())expectedLabel+=',';
|
|
expectedLabel+=Candidate;
|
|
const auto Separator=Candidate.find('/');
|
|
const std::string Kind=Candidate.substr(0,Separator);
|
|
if(std::find(expectedGenerators.begin(),expectedGenerators.end(),Kind)==expectedGenerators.end())
|
|
expectedGenerators.push_back(Kind);
|
|
}
|
|
const float density=Shape.densityAt(position);
|
|
const bool solid=density<0.0f;
|
|
const bool outsideStack=zMicrometres<stackMinimum||zMicrometres>=stackMaximum;
|
|
if(expected.empty())expectedLabel=outsideStack?"outside-stack/air":"uncovered";
|
|
std::string actualGenerator;
|
|
if(actual=="SurfaceWorld-native"||actual=="SurfaceWorld-legacy")actualGenerator="SurfaceWorld";
|
|
else if(actual=="FloatingIslands-native")actualGenerator="FloatingIslands";
|
|
else if(actual.rfind("compiled-analytic/",0)==0)actualGenerator=actual.substr(18);
|
|
const bool mismatch=expected.empty()
|
|
?(!outsideStack||solid)
|
|
:std::find(expectedGenerators.begin(),expectedGenerators.end(),actualGenerator)==expectedGenerators.end();
|
|
++probes;disagreements+=mismatch?1u:0u;
|
|
std::printf("[ResolvedLayoutProbe] endpoint_um=%lld offset_um=%lld z_m=%.3f expected=%s actual=%s density=%.7g occupancy=%s disagreement=%s\n",
|
|
static_cast<long long>(Endpoint),static_cast<long long>(Offset),
|
|
static_cast<double>(zMicrometres)/1000000.0,expectedLabel.c_str(),actual.c_str(),density,
|
|
solid?"solid":"air",mismatch?"YES":"no");
|
|
}
|
|
std::printf("[ResolvedLayoutProbeSummary] probes=%llu disagreements=%llu status=%s\n",
|
|
static_cast<unsigned long long>(probes),static_cast<unsigned long long>(disagreements),
|
|
disagreements==0?"PASS":"DEVIATIONS_RECORDED");
|
|
Streamer.Stop();
|
|
return 0;
|
|
}
|
|
const auto ShapeReady=Clock::now();
|
|
const double ShapeSeconds=std::chrono::duration<double>(ShapeReady-ShapeBuildStart).count();
|
|
if(bAuditTileShapeReadiness)
|
|
{
|
|
VoxelForgeCore::TileGenerationOptions CompiledOptions;
|
|
VoxelForgeCore::TileGenerationOptions NativeOnlyOptions;
|
|
CompiledOptions.hashDensity=true;
|
|
NativeOnlyOptions.hashDensity=true;
|
|
NativeOnlyOptions.diagnosticForceNativeOnly=true;
|
|
int32 Compared=0,Mismatched=0;
|
|
for(const FVoxelTileKey& Tile:ShapeFreeTiles)
|
|
{
|
|
if(!Neighborhood.contains(Tile))continue;
|
|
const VoxelForgeCore::TileKey CoreKey{{Tile.Coord.X,Tile.Coord.Y,Tile.Coord.Z},Tile.Level};
|
|
std::string CompiledError,NativeOnlyError;
|
|
const auto Compiled=Shape.generateTile(CoreKey,CompiledOptions,CompiledError);
|
|
const auto NativeOnly=Shape.generateTile(CoreKey,NativeOnlyOptions,NativeOnlyError);
|
|
++Compared;
|
|
const bool bSame=Compiled&&NativeOnly
|
|
&&Compiled->densityHash==NativeOnly->densityHash
|
|
&&Compiled->meshHash==NativeOnly->meshHash
|
|
&&Compiled->payloadHash==NativeOnly->payloadHash
|
|
&&Compiled->topologyHash==NativeOnly->topologyHash;
|
|
if(!bSame)
|
|
{
|
|
++Mismatched;
|
|
std::printf("[ShapeReadinessEquivalence] key=(%d,%d,%d,lod%d) match=0 compiled=%s native_only=%s density=%s mesh=%s\n",
|
|
Tile.Coord.X,Tile.Coord.Y,Tile.Coord.Z,Tile.Level,
|
|
Compiled?"ok":CompiledError.c_str(),NativeOnly?"ok":NativeOnlyError.c_str(),
|
|
Compiled&&NativeOnly&&Compiled->densityHash==NativeOnly->densityHash?"same":"different",
|
|
Compiled&&NativeOnly&&Compiled->meshHash==NativeOnly->meshHash?"same":"different");
|
|
}
|
|
}
|
|
std::printf("[ShapeReadinessEquivalence] compared_near_ring=%d mismatches=%d status=%s\n",
|
|
Compared,Mismatched,Mismatched==0?"PASS":"FAIL");
|
|
if(Mismatched!=0)
|
|
{
|
|
Streamer.Stop();
|
|
std::fprintf(stderr,"[StreamPipeline] native-only tile generation differs from compiled generation.\n");
|
|
return 9;
|
|
}
|
|
}
|
|
if(bAuditBasePageEquivalence)
|
|
{
|
|
std::vector<VoxelForgeCore::TileKey> AuditKeys;
|
|
AuditKeys.reserve(48);
|
|
const auto AddAuditCandidates=[&](int32 PreferredLOD)
|
|
{
|
|
const std::size_t ExistingCount=AuditKeys.size();
|
|
for(const FVoxelTileKey& Tile:Desired.Tiles)
|
|
{
|
|
if(Tile.Level!=PreferredLOD)continue;
|
|
const VoxelForgeCore::TileKey Key{{Tile.Coord.X,Tile.Coord.Y,Tile.Coord.Z},
|
|
Tile.Level};
|
|
if(Shape.isTileShapeFree(Key)||Shape.needsTileRegion(Key))continue;
|
|
if(std::find(AuditKeys.begin(),AuditKeys.end(),Key)==AuditKeys.end())
|
|
AuditKeys.push_back(Key);
|
|
if(AuditKeys.size()>=48||AuditKeys.size()-ExistingCount>=4)return;
|
|
}
|
|
};
|
|
for(int32 PreferredLOD:{1,0,2,3,4,5,6,7,8})
|
|
{
|
|
AddAuditCandidates(PreferredLOD);
|
|
if(AuditKeys.size()>=48)break;
|
|
}
|
|
if(AuditKeys.empty())
|
|
{
|
|
std::printf("[BasePageTileEquivalence] status=UNAVAILABLE reason=no_tile_reachable_through_both_routes\n");
|
|
Streamer.Stop();
|
|
return 9;
|
|
}
|
|
VoxelForgeCore::TileGenerationOptions HashOptions;
|
|
HashOptions.hashDensity=true;
|
|
HashOptions.profileMesherStages=true;
|
|
bool bFoundEquivalentTile=false;
|
|
std::size_t CandidatesTried=0;
|
|
VoxelForgeCore::TileKey EquivalentKey{};
|
|
for(const VoxelForgeCore::TileKey& AuditKey:AuditKeys)
|
|
{
|
|
++CandidatesTried;
|
|
std::string BaseError,PageError,CompileError;
|
|
const auto BaseGenerationStart=Clock::now();
|
|
const auto BaseTile=Shape.generateTile(AuditKey,HashOptions,BaseError);
|
|
const double BaseGenerationSeconds=std::chrono::duration<double>(
|
|
Clock::now()-BaseGenerationStart).count();
|
|
if(!BaseTile)
|
|
{
|
|
std::printf("[BasePageTileEquivalenceCandidate] key=(%d,%d,%d,lod%d) base=FAIL reason=%s\n",
|
|
AuditKey.coordinate.x,AuditKey.coordinate.y,AuditKey.coordinate.z,
|
|
AuditKey.lod,BaseError.c_str());
|
|
continue;
|
|
}
|
|
const std::array<VoxelForgeCore::TileKey,1> AuditTiles={AuditKey};
|
|
VoxelForgeCore::TileRegionCompileStats PageBuildStats;
|
|
std::size_t PageTileCount=0;
|
|
const auto PageCompileStart=Clock::now();
|
|
const bool bPageBuilt=Shape.compileTileRegion(AuditTiles,
|
|
static_cast<std::uint32_t>(std::clamp(WorkerCount,1,16)),Scheduler,
|
|
CompileError,&PageTileCount,&PageBuildStats,true,true);
|
|
const double PageCompileSeconds=std::chrono::duration<double>(
|
|
Clock::now()-PageCompileStart).count();
|
|
const auto PageGenerationStart=Clock::now();
|
|
const auto PageTile=bPageBuilt?Shape.generateTile(AuditKey,HashOptions,PageError)
|
|
:std::optional<VoxelForgeCore::TileMesh>{};
|
|
const double PageGenerationSeconds=std::chrono::duration<double>(
|
|
Clock::now()-PageGenerationStart).count();
|
|
const bool bDensitySame=PageTile&&BaseTile->densityHash==PageTile->densityHash;
|
|
const bool bMeshSame=PageTile&&BaseTile->meshHash==PageTile->meshHash;
|
|
const bool bPayloadSame=PageTile&&BaseTile->payloadHash==PageTile->payloadHash;
|
|
const bool bTopologySame=PageTile&&BaseTile->topologyHash==PageTile->topologyHash;
|
|
const bool bActualPage=PageTile&&PageTile->diagnostics.analyticRegionPageRoute;
|
|
const int32 Matches=static_cast<int32>(bDensitySame)+static_cast<int32>(bMeshSame)
|
|
+static_cast<int32>(bPayloadSame)+static_cast<int32>(bTopologySame);
|
|
const bool bSame=bActualPage&&Matches==4;
|
|
std::printf("[BasePageTileEquivalenceCandidate] key=(%d,%d,%d,lod%d) page_built=%d page_route_verified=%d page_keys=%zu page_nodes=%llu base_route=base-shape page_route=analytic-page base_generation_ms=%.3f page_compile_ms=%.3f page_generation_ms=%.3f density=%s mesh=%s payload=%s topology=%s base_density=0x%016llx page_density=0x%016llx status=%s error=%s\n",
|
|
AuditKey.coordinate.x,AuditKey.coordinate.y,AuditKey.coordinate.z,AuditKey.lod,
|
|
bPageBuilt?1:0,bActualPage?1:0,PageTileCount,
|
|
static_cast<unsigned long long>(PageBuildStats.latticeNodes),
|
|
BaseGenerationSeconds*1000.0,PageCompileSeconds*1000.0,
|
|
PageGenerationSeconds*1000.0,
|
|
bDensitySame?"same":"DIFF",bMeshSame?"same":"DIFF",
|
|
bPayloadSame?"same":"DIFF",bTopologySame?"same":"DIFF",
|
|
static_cast<unsigned long long>(BaseTile->densityHash),
|
|
static_cast<unsigned long long>(PageTile?PageTile->densityHash:0),
|
|
bSame?"PASS":"DIFF",bPageBuilt?PageError.c_str():CompileError.c_str());
|
|
if(bSame)
|
|
{
|
|
bFoundEquivalentTile=true;
|
|
EquivalentKey=AuditKey;
|
|
break;
|
|
}
|
|
}
|
|
std::printf("[BasePageTileEquivalence] candidates=%zu tried=%zu matching_key=(%d,%d,%d,lod%d) status=%s\n",
|
|
AuditKeys.size(),CandidatesTried,EquivalentKey.coordinate.x,
|
|
EquivalentKey.coordinate.y,EquivalentKey.coordinate.z,EquivalentKey.lod,
|
|
bFoundEquivalentTile?"PASS":"FAIL");
|
|
if(!bFoundEquivalentTile)
|
|
{
|
|
Streamer.Stop();
|
|
return 9;
|
|
}
|
|
}
|
|
if(bAuditRouteParity)
|
|
{
|
|
struct FRouteAuditCandidate
|
|
{
|
|
VoxelForgeCore::TileKey Key;
|
|
bool bWouldRequestPage=false;
|
|
};
|
|
struct FRouteAuditRow
|
|
{
|
|
FRouteAuditCandidate Candidate;
|
|
std::optional<VoxelForgeCore::TileMesh> Base;
|
|
};
|
|
VoxelForgeCore::TileGenerationOptions CaptureOptions;
|
|
CaptureOptions.hashDensity=true;
|
|
CaptureOptions.captureDensitySamples=true;
|
|
CaptureOptions.forceFullDensityCapture=true;
|
|
CaptureOptions.profileMesherStages=true;
|
|
std::vector<FRouteAuditRow> Rows;
|
|
Rows.reserve(static_cast<std::size_t>(World.FullResClipLevels)*8);
|
|
std::vector<VoxelForgeCore::TileKey> PageKeys;
|
|
PageKeys.reserve(Rows.capacity());
|
|
const int32 AuditPageLODs=std::min(World.FullResClipLevels,3);
|
|
std::vector<const FStandaloneDesiredTileSet*> CandidateSets{&Desired};
|
|
if(bHasCameraStep)CandidateSets.push_back(&MovedDesired);
|
|
for(int32 LOD=0;LOD<AuditPageLODs;++LOD)
|
|
{
|
|
for(const bool bPageRequested:{true,false})
|
|
{
|
|
int32 SurfaceCandidates=0;
|
|
bool bHaveFlatCandidate=false;
|
|
int32 Inspected=0;
|
|
for(const FStandaloneDesiredTileSet* CandidateSet:CandidateSets)
|
|
{
|
|
for(const FVoxelTileKey& Tile:CandidateSet->Tiles)
|
|
{
|
|
if(Tile.Level!=LOD)continue;
|
|
const VoxelForgeCore::TileKey Key{{Tile.Coord.X,Tile.Coord.Y,Tile.Coord.Z},Tile.Level};
|
|
if(Shape.isTileShapeFree(Key)
|
|
||Shape.needsTileRegion(Key)!=bPageRequested)continue;
|
|
if(Inspected++>=256)break;
|
|
std::string BaseError;
|
|
auto Base=Shape.generateTile(Key,CaptureOptions,BaseError);
|
|
if(!Base)
|
|
{
|
|
std::printf("[RouteParityCandidate] route=%s key=(%d,%d,%d,lod%d) base=FAIL error=%s\n",
|
|
bPageRequested?"actual-page":"base-covered-page",Key.coordinate.x,
|
|
Key.coordinate.y,Key.coordinate.z,Key.lod,BaseError.c_str());
|
|
continue;
|
|
}
|
|
const bool bHasSurface=!Base->vertices.empty()&&!Base->indices.empty();
|
|
if(bHasSurface&&SurfaceCandidates<3)
|
|
{
|
|
Rows.push_back({{Key,bPageRequested},std::move(Base)});
|
|
PageKeys.push_back(Key);
|
|
++SurfaceCandidates;
|
|
}
|
|
else if(!bHasSurface&&!bHaveFlatCandidate)
|
|
{
|
|
Rows.push_back({{Key,bPageRequested},std::move(Base)});
|
|
PageKeys.push_back(Key);
|
|
bHaveFlatCandidate=true;
|
|
}
|
|
if(SurfaceCandidates>=3&&bHaveFlatCandidate)break;
|
|
}
|
|
if(SurfaceCandidates>=3&&bHaveFlatCandidate)break;
|
|
}
|
|
}
|
|
}
|
|
if(Rows.empty())
|
|
{
|
|
std::printf("[RouteParityAudit] status=UNAVAILABLE reason=no_page_route_candidates\n");
|
|
Streamer.Stop();
|
|
return 9;
|
|
}
|
|
|
|
VoxelForgeCore::TileGenerationOptions PageOptions=CaptureOptions;
|
|
PageOptions.auditUniformBlockProofs=true;
|
|
int32 BasePageMismatches=0,PageRoutesVerified=0;
|
|
std::uint64_t PageProofBlocks=0,PageProofSamples=0,PageProofFallbacks=0,
|
|
PageProofUnknown=0,PageProofViolations=0;
|
|
std::uint64_t PageExactSamples=0,PageExactMismatches=0;
|
|
double MaxDensityDelta=0.0,SumDensityDelta=0.0;
|
|
std::uint64_t ComparedDensitySamples=0,SignMismatches=0,
|
|
CrossingEdgeMismatches=0,SharedCrossingEdges=0;
|
|
double MaxSurfaceDisplacement=0.0,SumSurfaceDisplacement=0.0;
|
|
for(const FRouteAuditRow& Row:Rows)
|
|
if(Row.Base)
|
|
{
|
|
const FRouteDensityComparison Query=CompareRouteDensities(
|
|
*Row.Base,*Row.Base,Shape);
|
|
PageExactSamples+=Query.exactQuerySamples;
|
|
PageExactMismatches+=Query.exactQueryMismatches;
|
|
}
|
|
else ++BasePageMismatches;
|
|
VoxelForgeCore::TileRegionCompileStats PageStats;
|
|
std::size_t CoveredPageKeys=0;
|
|
std::string PageCompileError;
|
|
const bool bPagesCompiled=Shape.compileTileRegion(PageKeys,
|
|
static_cast<std::uint32_t>(std::clamp(WorkerCount,1,16)),Scheduler,
|
|
PageCompileError,&CoveredPageKeys,&PageStats,true,true);
|
|
if(!bPagesCompiled)
|
|
{
|
|
std::printf("[RouteParityAudit] page_compile=FAIL error=%s\n",PageCompileError.c_str());
|
|
Streamer.Stop();
|
|
return 9;
|
|
}
|
|
for(FRouteAuditRow& Row:Rows)
|
|
{
|
|
std::string Error;
|
|
const auto Page=Shape.generateTile(Row.Candidate.Key,PageOptions,Error);
|
|
const bool bVerifiedPage=Page&&Page->diagnostics.analyticRegionPageRoute;
|
|
PageRoutesVerified+=bVerifiedPage?1:0;
|
|
const bool bSame=Row.Base&&bVerifiedPage&&SameTileOutput(*Row.Base,*Page);
|
|
const FRouteDensityComparison Comparison=Page&&Row.Base
|
|
?CompareRouteDensities(*Page,*Row.Base,Shape):FRouteDensityComparison{};
|
|
if(!bSame||!Comparison.valid||Comparison.exactQueryMismatches!=0)
|
|
++BasePageMismatches;
|
|
PageExactSamples+=Comparison.exactQuerySamples;
|
|
PageExactMismatches+=Comparison.exactQueryMismatches;
|
|
MaxDensityDelta=std::max(MaxDensityDelta,Comparison.maxAbsoluteDelta);
|
|
SumDensityDelta+=Comparison.sumAbsoluteDelta;
|
|
ComparedDensitySamples+=Comparison.samples;
|
|
SignMismatches+=Comparison.signMismatches;
|
|
CrossingEdgeMismatches+=Comparison.crossingEdgeMismatches;
|
|
SharedCrossingEdges+=Comparison.sharedCrossingEdges;
|
|
MaxSurfaceDisplacement=std::max(MaxSurfaceDisplacement,
|
|
Comparison.maxSurfaceDisplacementVoxels);
|
|
SumSurfaceDisplacement+=Comparison.sumSurfaceDisplacementVoxels;
|
|
if(Page)
|
|
{
|
|
PageProofBlocks+=Page->diagnostics.uniformBlockProofsChecked;
|
|
PageProofSamples+=Page->diagnostics.uniformBlockProofSamplesChecked;
|
|
PageProofFallbacks+=Page->diagnostics.uniformBlockProofFallbackBlocks;
|
|
PageProofUnknown+=Page->diagnostics.uniformBlockProofUnknownBlocks;
|
|
PageProofViolations+=Page->diagnostics.uniformBlockProofViolations;
|
|
}
|
|
std::printf("[RouteParityPair] route=base/page key=(%d,%d,%d,lod%d) page_requested=%d page_verified=%d same_density=%d same_mesh=%d same_payload=%d same_topology=%d samples=%llu max_abs_density_delta=%.9g sign_mismatches=%llu edge_crossing_mismatches=%llu shared_surface_edges=%llu max_surface_delta_vox=%.9g exact_query_mismatches=%llu base_native_float=%llu page_native_float=%llu first_density=(%.9g,%.9g) status=%s error=%s\n",
|
|
Row.Candidate.Key.coordinate.x,Row.Candidate.Key.coordinate.y,
|
|
Row.Candidate.Key.coordinate.z,Row.Candidate.Key.lod,
|
|
Row.Candidate.bWouldRequestPage?1:0,bVerifiedPage?1:0,
|
|
Row.Base&&Page&&Row.Base->densityHash==Page->densityHash?1:0,
|
|
Row.Base&&Page&&Row.Base->meshHash==Page->meshHash?1:0,
|
|
Row.Base&&Page&&Row.Base->payloadHash==Page->payloadHash?1:0,
|
|
Row.Base&&Page&&Row.Base->topologyHash==Page->topologyHash?1:0,
|
|
static_cast<unsigned long long>(Comparison.samples),
|
|
Comparison.maxAbsoluteDelta,
|
|
static_cast<unsigned long long>(Comparison.signMismatches),
|
|
static_cast<unsigned long long>(Comparison.crossingEdgeMismatches),
|
|
static_cast<unsigned long long>(Comparison.sharedCrossingEdges),
|
|
Comparison.maxSurfaceDisplacementVoxels,
|
|
static_cast<unsigned long long>(Comparison.exactQueryMismatches),
|
|
static_cast<unsigned long long>(Row.Base?Row.Base->diagnostics.floatFieldCalls:0),
|
|
static_cast<unsigned long long>(Page?Page->diagnostics.floatFieldCalls:0),
|
|
Row.Base&&!Row.Base->debugDensitySamples.empty()
|
|
?Row.Base->debugDensitySamples.front():0.0f,
|
|
Page&&!Page->debugDensitySamples.empty()?Page->debugDensitySamples.front():0.0f,
|
|
bSame&&Comparison.valid&&Comparison.exactQueryMismatches==0?"PASS":"FAIL",
|
|
Page?Error.c_str():Error.c_str());
|
|
}
|
|
|
|
int32 NativeMismatches=0,NativeRoutesVerified=0;
|
|
std::uint64_t NativeExactSamples=0,NativeExactMismatches=0;
|
|
int32 NativePairs=0;
|
|
VoxelForgeCore::TileGenerationOptions NativeOptions=CaptureOptions;
|
|
NativeOptions.diagnosticForceNativeOnly=true;
|
|
for(const FVoxelTileKey& Tile:ShapeFreeTiles)
|
|
{
|
|
if(NativePairs>=9)break;
|
|
if(!Neighborhood.contains(Tile))continue;
|
|
const VoxelForgeCore::TileKey Key{{Tile.Coord.X,Tile.Coord.Y,Tile.Coord.Z},Tile.Level};
|
|
std::string BaseError,NativeError;
|
|
const auto Base=Shape.generateTile(Key,CaptureOptions,BaseError);
|
|
const auto Native=Shape.generateTile(Key,NativeOptions,NativeError);
|
|
++NativePairs;
|
|
const bool bVerifiedNative=Native&&Native->diagnostics.nativeOnlyRoute;
|
|
NativeRoutesVerified+=bVerifiedNative?1:0;
|
|
const bool bSame=Base&&bVerifiedNative&&SameTileOutput(*Base,*Native);
|
|
const FRouteDensityComparison Comparison=Base&&Native
|
|
?CompareRouteDensities(*Native,*Base,Shape):FRouteDensityComparison{};
|
|
NativeExactSamples+=Comparison.exactQuerySamples;
|
|
NativeExactMismatches+=Comparison.exactQueryMismatches;
|
|
if(!bSame||!Comparison.valid||Comparison.exactQueryMismatches!=0)
|
|
++NativeMismatches;
|
|
std::printf("[RouteParityPair] route=compiled/native-only key=(%d,%d,%d,lod%d) native_verified=%d same_density=%d same_mesh=%d same_payload=%d same_topology=%d samples=%llu exact_query_mismatches=%llu status=%s error=%s\n",
|
|
Key.coordinate.x,Key.coordinate.y,Key.coordinate.z,Key.lod,
|
|
bVerifiedNative?1:0,Base&&Native&&Base->densityHash==Native->densityHash?1:0,
|
|
Base&&Native&&Base->meshHash==Native->meshHash?1:0,
|
|
Base&&Native&&Base->payloadHash==Native->payloadHash?1:0,
|
|
Base&&Native&&Base->topologyHash==Native->topologyHash?1:0,
|
|
static_cast<unsigned long long>(Comparison.samples),
|
|
static_cast<unsigned long long>(Comparison.exactQueryMismatches),
|
|
bSame&&Comparison.valid&&Comparison.exactQueryMismatches==0?"PASS":"FAIL",
|
|
Native?NativeError.c_str():NativeError.c_str());
|
|
}
|
|
|
|
int32 ExactQueryPairs=0,ExactQueryMismatches=0;
|
|
std::uint64_t ExactQuerySamples=0;
|
|
for(int32 LOD=World.FullResClipLevels;LOD<=World.MaxClipLevel&&LOD<9;++LOD)
|
|
{
|
|
int32 Added=0;
|
|
for(const FVoxelTileKey& Tile:Desired.Tiles)
|
|
{
|
|
if(Tile.Level!=LOD||Added>=2)continue;
|
|
const VoxelForgeCore::TileKey Key{{Tile.Coord.X,Tile.Coord.Y,Tile.Coord.Z},Tile.Level};
|
|
std::string Error;
|
|
const auto Mesh=Shape.generateTile(Key,CaptureOptions,Error);
|
|
const FRouteDensityComparison Comparison=Mesh
|
|
?CompareRouteDensities(*Mesh,*Mesh,Shape):FRouteDensityComparison{};
|
|
++ExactQueryPairs;++Added;
|
|
ExactQuerySamples+=Comparison.exactQuerySamples;
|
|
if(!Mesh||!Comparison.valid||Comparison.exactQueryMismatches!=0)
|
|
++ExactQueryMismatches;
|
|
std::printf("[RouteParityPair] route=tile/exact-query key=(%d,%d,%d,lod%d) samples=%llu exact_query_mismatches=%llu status=%s error=%s\n",
|
|
Key.coordinate.x,Key.coordinate.y,Key.coordinate.z,Key.lod,
|
|
static_cast<unsigned long long>(Comparison.exactQuerySamples),
|
|
static_cast<unsigned long long>(Comparison.exactQueryMismatches),
|
|
Mesh&&Comparison.valid&&Comparison.exactQueryMismatches==0?"PASS":"FAIL",
|
|
Error.c_str());
|
|
}
|
|
}
|
|
int32 EditPairs=0,EditMismatches=0;
|
|
const auto EditRow=std::find_if(Rows.begin(),Rows.end(),[](const FRouteAuditRow& Row)
|
|
{
|
|
return Row.Base&&!Row.Candidate.bWouldRequestPage&&Row.Candidate.Key.lod<=1
|
|
&&!Row.Base->vertices.empty()&&!Row.Base->indices.empty();
|
|
});
|
|
if(EditRow!=Rows.end())
|
|
{
|
|
VoxelForgeCore::World EditWorld(CoreSnapshot);
|
|
std::string EditCompileError;
|
|
if(!EditWorld.compile(ShapeRequest,Scheduler,EditCompileError))
|
|
{
|
|
++EditMismatches;
|
|
std::printf("[RouteParityEdit] status=FAIL stage=compile error=%s\n",
|
|
EditCompileError.c_str());
|
|
}
|
|
else
|
|
{
|
|
const VoxelForgeCore::TileKey Key=EditRow->Candidate.Key;
|
|
const std::int32_t Extent=CHUNK_SIZE<<Key.lod;
|
|
const std::array<VoxelForgeCore::TileKey,1> EditKeys={Key};
|
|
VoxelForgeCore::TileGenerationOptions EditOptions=CaptureOptions;
|
|
EditOptions.auditUniformBlockProofs=true;
|
|
std::string BeforeError,ApplyError,BaseError,PageCompileError,PageError;
|
|
const auto Before=EditWorld.generateTile(Key,EditOptions,BeforeError);
|
|
VoxelForgeCore::EditTransaction Transaction;
|
|
Transaction.sequence=1;
|
|
VoxelForgeCore::EditBrush Brush;
|
|
Brush.shape=VoxelForgeCore::EditBrush::Shape::sphere;
|
|
Brush.operation=VoxelForgeCore::EditBrush::Operation::densityOffset;
|
|
bool bSurfaceCenterFound=false;
|
|
if(Before&&!Before->debugDensitySamples.empty())
|
|
{
|
|
const std::int32_t Dim=static_cast<std::int32_t>(std::llround(
|
|
std::cbrt(static_cast<double>(Before->debugDensitySamples.size()))));
|
|
const std::int32_t GridDim=Dim-3;
|
|
if(Dim>=4&&static_cast<std::size_t>(Dim)*Dim*Dim
|
|
==Before->debugDensitySamples.size()&&GridDim>0&&Extent%GridDim==0)
|
|
{
|
|
const std::int32_t Step=Extent/GridDim;
|
|
const std::int32_t OriginX=Key.coordinate.x*Extent;
|
|
const std::int32_t OriginY=Key.coordinate.y*Extent;
|
|
const std::int32_t OriginZ=Key.coordinate.z*Extent;
|
|
const auto Index=[Dim](std::int32_t X,std::int32_t Y,std::int32_t Z)
|
|
{return (static_cast<std::size_t>(Z)*Dim+Y)*Dim+X;};
|
|
for(std::int32_t Z=0;Z<Dim&&!bSurfaceCenterFound;++Z)
|
|
for(std::int32_t Y=0;Y<Dim&&!bSurfaceCenterFound;++Y)
|
|
for(std::int32_t X=0;X<Dim&&!bSurfaceCenterFound;++X)
|
|
{
|
|
for(const std::array<std::int32_t,3>& Delta:
|
|
{std::array<std::int32_t,3>{1,0,0},
|
|
std::array<std::int32_t,3>{0,1,0},
|
|
std::array<std::int32_t,3>{0,0,1}})
|
|
{
|
|
const std::int32_t NX=X+Delta[0],NY=Y+Delta[1],NZ=Z+Delta[2];
|
|
if(NX>=Dim||NY>=Dim||NZ>=Dim)continue;
|
|
const float D0=Before->debugDensitySamples[Index(X,Y,Z)];
|
|
const float D1=Before->debugDensitySamples[Index(NX,NY,NZ)];
|
|
if((D0<0.0f)==(D1<0.0f))continue;
|
|
const double T=static_cast<double>(D0)
|
|
/(static_cast<double>(D0)-D1);
|
|
Brush.a={OriginX+(X-1+T*Delta[0])*Step,
|
|
OriginY+(Y-1+T*Delta[1])*Step,
|
|
OriginZ+(Z-1+T*Delta[2])*Step};
|
|
bSurfaceCenterFound=true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if(!bSurfaceCenterFound)
|
|
Brush.a={static_cast<double>(Key.coordinate.x*Extent)+Extent*0.5,
|
|
static_cast<double>(Key.coordinate.y*Extent)+Extent*0.5,
|
|
static_cast<double>(Key.coordinate.z*Extent)+Extent*0.5};
|
|
Brush.radius=10.0;
|
|
Brush.strength=-48.0f;
|
|
Transaction.brushes.push_back(Brush);
|
|
const auto Applied=Before?EditWorld.applyEditTransaction(Transaction,
|
|
EditKeys,ApplyError):std::optional<VoxelForgeCore::EditResult>{};
|
|
const auto EditedBase=Applied
|
|
?EditWorld.generateTile(Key,EditOptions,BaseError)
|
|
:std::optional<VoxelForgeCore::TileMesh>{};
|
|
VoxelForgeCore::TileRegionCompileStats EditPageStats;
|
|
std::size_t EditPageKeys=0;
|
|
const bool bEditPageBuilt=EditedBase&&EditWorld.compileTileRegion(EditKeys,
|
|
static_cast<std::uint32_t>(std::clamp(WorkerCount,1,16)),Scheduler,
|
|
PageCompileError,&EditPageKeys,&EditPageStats,true,true);
|
|
const auto EditedPage=bEditPageBuilt
|
|
?EditWorld.generateTile(Key,EditOptions,PageError)
|
|
:std::optional<VoxelForgeCore::TileMesh>{};
|
|
const FRouteDensityComparison EditComparison=EditedBase&&EditedPage
|
|
?CompareRouteDensities(*EditedPage,*EditedBase,EditWorld)
|
|
:FRouteDensityComparison{};
|
|
const bool bEditChanged=Before&&EditedBase
|
|
&&Before->densityHash!=EditedBase->densityHash;
|
|
const bool bEditPageVerified=EditedPage
|
|
&&EditedPage->diagnostics.analyticRegionPageRoute;
|
|
const bool bEditSame=EditedBase&&EditedPage&&bEditPageVerified
|
|
&&SameTileOutput(*EditedBase,*EditedPage);
|
|
const bool bEditPass=Before&&Applied&&EditedBase&&bEditPageBuilt
|
|
&&bEditPageVerified&&bEditChanged&&bEditSame&&EditComparison.valid
|
|
&&EditComparison.exactQueryMismatches==0;
|
|
++EditPairs;
|
|
if(!bEditPass)++EditMismatches;
|
|
if(EditedPage)
|
|
{
|
|
PageProofBlocks+=EditedPage->diagnostics.uniformBlockProofsChecked;
|
|
PageProofSamples+=EditedPage->diagnostics.uniformBlockProofSamplesChecked;
|
|
PageProofFallbacks+=EditedPage->diagnostics.uniformBlockProofFallbackBlocks;
|
|
PageProofUnknown+=EditedPage->diagnostics.uniformBlockProofUnknownBlocks;
|
|
PageProofViolations+=EditedPage->diagnostics.uniformBlockProofViolations;
|
|
}
|
|
std::printf("[RouteParityEdit] key=(%d,%d,%d,lod%d) sequence=%llu before_hash=0x%016llx edited_base_hash=0x%016llx edited_page_hash=0x%016llx edit_changed=%d page_verified=%d same_mesh=%d same_payload=%d same_topology=%d samples=%llu exact_query_mismatches=%llu status=%s error=%s\n",
|
|
Key.coordinate.x,Key.coordinate.y,Key.coordinate.z,Key.lod,
|
|
static_cast<unsigned long long>(Transaction.sequence),
|
|
static_cast<unsigned long long>(Before?Before->densityHash:0),
|
|
static_cast<unsigned long long>(EditedBase?EditedBase->densityHash:0),
|
|
static_cast<unsigned long long>(EditedPage?EditedPage->densityHash:0),
|
|
bEditChanged?1:0,bEditPageVerified?1:0,
|
|
EditedBase&&EditedPage&&EditedBase->meshHash==EditedPage->meshHash?1:0,
|
|
EditedBase&&EditedPage&&EditedBase->payloadHash==EditedPage->payloadHash?1:0,
|
|
EditedBase&&EditedPage&&EditedBase->topologyHash==EditedPage->topologyHash?1:0,
|
|
static_cast<unsigned long long>(EditComparison.samples),
|
|
static_cast<unsigned long long>(EditComparison.exactQueryMismatches),
|
|
bEditPass?"PASS":"FAIL",
|
|
!Before?BeforeError.c_str():!Applied?ApplyError.c_str():
|
|
!EditedBase?BaseError.c_str():!bEditPageBuilt?PageCompileError.c_str():
|
|
PageError.c_str());
|
|
}
|
|
}
|
|
int32 HistoryPairs=0,HistoryMismatches=0;
|
|
if(bHasCameraStep&&bRegionCrossing)
|
|
{
|
|
const auto MovedAnchor=std::find_if(MovedDesired.Tiles.begin(),MovedDesired.Tiles.end(),
|
|
[](const FVoxelTileKey& Tile){return Tile.Level==0;});
|
|
const auto HistoryRow=std::find_if(Rows.begin(),Rows.end(),
|
|
[&](const FRouteAuditRow& Row)
|
|
{
|
|
if(!Row.Base||Row.Candidate.bWouldRequestPage)return false;
|
|
return std::none_of(MovedDesired.Tiles.begin(),MovedDesired.Tiles.end(),
|
|
[&](const FVoxelTileKey& Tile)
|
|
{
|
|
return Tile.Level==Row.Candidate.Key.lod
|
|
&&Tile.Coord.X==Row.Candidate.Key.coordinate.x
|
|
&&Tile.Coord.Y==Row.Candidate.Key.coordinate.y
|
|
&&Tile.Coord.Z==Row.Candidate.Key.coordinate.z;
|
|
});
|
|
});
|
|
if(HistoryRow!=Rows.end()&&MovedAnchor!=MovedDesired.Tiles.end())
|
|
{
|
|
VoxelForgeCore::ShapeBuildRequest MovedShapeRequest=ShapeRequest;
|
|
const FIntVector MovedOrigin=MovedAnchor->OriginVoxels();
|
|
const std::int32_t MovedExtent=MovedAnchor->ExtentVoxels();
|
|
MovedShapeRequest.minVoxel={MovedOrigin.X,MovedOrigin.Y,MovedOrigin.Z};
|
|
MovedShapeRequest.maxVoxel={MovedOrigin.X+MovedExtent,
|
|
MovedOrigin.Y+MovedExtent,MovedOrigin.Z+MovedExtent};
|
|
MovedShapeRequest.clipMinVoxel=MovedShapeRequest.minVoxel;
|
|
MovedShapeRequest.clipMaxVoxel=MovedShapeRequest.maxVoxel;
|
|
VoxelForgeCore::World MovedWorld(CoreSnapshot);
|
|
std::string MovedShapeError,MovedTileError;
|
|
const bool bMovedCompiled=MovedWorld.compile(MovedShapeRequest,Scheduler,
|
|
MovedShapeError);
|
|
const auto MovedTile=bMovedCompiled
|
|
?MovedWorld.generateTile(HistoryRow->Candidate.Key,CaptureOptions,MovedTileError)
|
|
:std::optional<VoxelForgeCore::TileMesh>{};
|
|
const FRouteDensityComparison HistoryComparison=MovedTile
|
|
?CompareRouteDensities(*MovedTile,*HistoryRow->Base,MovedWorld)
|
|
:FRouteDensityComparison{};
|
|
const bool bHistorySame=MovedTile
|
|
&&SameTileOutput(*MovedTile,*HistoryRow->Base)
|
|
&&HistoryComparison.valid&&HistoryComparison.exactQueryMismatches==0;
|
|
++HistoryPairs;
|
|
if(!bHistorySame)++HistoryMismatches;
|
|
std::printf("[RouteParityHistory] from=initial_base to=moved_world key=(%d,%d,%d,lod%d) same_density=%d same_mesh=%d same_payload=%d same_topology=%d samples=%llu max_abs_density_delta=%.9g exact_query_mismatches=%llu status=%s error=%s\n",
|
|
HistoryRow->Candidate.Key.coordinate.x,HistoryRow->Candidate.Key.coordinate.y,
|
|
HistoryRow->Candidate.Key.coordinate.z,HistoryRow->Candidate.Key.lod,
|
|
MovedTile&&MovedTile->densityHash==HistoryRow->Base->densityHash?1:0,
|
|
MovedTile&&MovedTile->meshHash==HistoryRow->Base->meshHash?1:0,
|
|
MovedTile&&MovedTile->payloadHash==HistoryRow->Base->payloadHash?1:0,
|
|
MovedTile&&MovedTile->topologyHash==HistoryRow->Base->topologyHash?1:0,
|
|
static_cast<unsigned long long>(HistoryComparison.samples),
|
|
HistoryComparison.maxAbsoluteDelta,
|
|
static_cast<unsigned long long>(HistoryComparison.exactQueryMismatches),
|
|
bHistorySame?"PASS":"FAIL",
|
|
!bMovedCompiled?MovedShapeError.c_str():MovedTileError.c_str());
|
|
}
|
|
}
|
|
const double MeanDensityDelta=ComparedDensitySamples
|
|
?SumDensityDelta/static_cast<double>(ComparedDensitySamples):0.0;
|
|
const double MeanSurfaceDisplacement=SharedCrossingEdges
|
|
?SumSurfaceDisplacement/static_cast<double>(SharedCrossingEdges):0.0;
|
|
const bool bRouteParityPassing=BasePageMismatches==0&&NativeMismatches==0
|
|
&&ExactQueryMismatches==0&&EditPairs==1&&EditMismatches==0
|
|
&&HistoryPairs==(bHasCameraStep&&bRegionCrossing?1:0)&&HistoryMismatches==0
|
|
&&PageRoutesVerified==static_cast<int32>(Rows.size())
|
|
&&NativeRoutesVerified==NativePairs&&PageProofViolations==0
|
|
&&PageExactMismatches==0&&NativeExactMismatches==0;
|
|
std::printf("[RouteParityAudit] seed=%d page_pairs=%zu page_verified=%d page_mismatches=%d native_only_pairs=%d native_verified=%d native_mismatches=%d higher_lod_exact_pairs=%d higher_lod_exact_mismatches=%d edit_pairs=%d edit_mismatches=%d history_pairs=%d history_mismatches=%d exact_query_samples=%llu exact_query_mismatches=%llu page_density_samples=%llu max_abs_density_delta=%.9g mean_abs_density_delta=%.9g sign_mismatches=%llu edge_crossing_mismatches=%llu shared_surface_edges=%llu max_surface_delta_vox=%.9g mean_surface_delta_vox=%.9g page_proof_blocks=%llu page_proof_samples=%llu page_proof_fallbacks=%llu page_proof_unknown=%llu page_proof_violations=%llu status=%s\n",
|
|
World.Seed,Rows.size(),PageRoutesVerified,BasePageMismatches,
|
|
NativePairs,NativeRoutesVerified,NativeMismatches,ExactQueryPairs,
|
|
ExactQueryMismatches,EditPairs,EditMismatches,HistoryPairs,HistoryMismatches,
|
|
static_cast<unsigned long long>(PageExactSamples
|
|
+NativeExactSamples+ExactQuerySamples),
|
|
static_cast<unsigned long long>(PageExactMismatches
|
|
+NativeExactMismatches+ExactQueryMismatches),
|
|
static_cast<unsigned long long>(ComparedDensitySamples),MaxDensityDelta,
|
|
MeanDensityDelta,static_cast<unsigned long long>(SignMismatches),
|
|
static_cast<unsigned long long>(CrossingEdgeMismatches),
|
|
static_cast<unsigned long long>(SharedCrossingEdges),MaxSurfaceDisplacement,
|
|
MeanSurfaceDisplacement,static_cast<unsigned long long>(PageProofBlocks),
|
|
static_cast<unsigned long long>(PageProofSamples),
|
|
static_cast<unsigned long long>(PageProofFallbacks),
|
|
static_cast<unsigned long long>(PageProofUnknown),
|
|
static_cast<unsigned long long>(PageProofViolations),
|
|
bRouteParityPassing?"PASS":"FAIL");
|
|
if(!bRouteParityPassing)
|
|
{
|
|
Streamer.Stop();
|
|
return 9;
|
|
}
|
|
}
|
|
const FStandaloneStreamingUpdate Update=Streamer.UpdateDesiredWithSet(Desired,{},0);
|
|
if(Update.DesiredTiles!=static_cast<int32>(Desired.Tiles.size()))
|
|
{
|
|
Streamer.Stop();
|
|
std::fprintf(stderr,"[StreamPipeline] expected %zu tiles after shape publication but queued %d.\n",
|
|
Desired.Tiles.size(),Update.DesiredTiles);return 7;
|
|
}
|
|
FSceneExportWriter SceneExport;
|
|
const bool bExportRequested=!MeshExportPath.empty();
|
|
bool bSceneExportPassing=true;
|
|
if(bExportRequested)
|
|
{
|
|
bSceneExportPassing=SceneExport.Open(MeshExportPath,World.CoreVoxelSizeMicrons,
|
|
CameraPositionVoxels,CameraYawDegrees,CameraPitchDegrees,
|
|
static_cast<std::uint32_t>(Update.DesiredTiles));
|
|
if(!bSceneExportPassing)
|
|
{
|
|
Streamer.Stop();
|
|
std::fprintf(stderr,"[MeshSceneExport] FAIL: could not open %s\n",
|
|
MeshExportPath.string().c_str());
|
|
return 8;
|
|
}
|
|
}
|
|
|
|
std::vector<FHashedTile> DensityRows,MeshRows,PayloadRows,TopologyRows;
|
|
int32 Finished=0,NeighborhoodFinished=0,FullDetailFinished=0;
|
|
int32 FullDetailExpected=0;
|
|
uint64 WholeNonEmptyMeshTiles=0,WholeTriangleCount=0;
|
|
struct FColumnXY
|
|
{
|
|
std::int64_t X=0,Y=0;
|
|
bool operator==(const FColumnXY&)const=default;
|
|
};
|
|
struct FColumnXYHash
|
|
{
|
|
std::size_t operator()(const FColumnXY& Value)const noexcept
|
|
{
|
|
const std::size_t X=std::hash<std::int64_t>{}(Value.X);
|
|
const std::size_t Y=std::hash<std::int64_t>{}(Value.Y);
|
|
return X^(Y+static_cast<std::size_t>(0x9E3779B9u)+(X<<6)+(X>>2));
|
|
}
|
|
};
|
|
struct FColumnEvaluationEvent
|
|
{
|
|
FColumnXY XY;
|
|
FVoxelTileKey Tile;
|
|
std::uint64_t WorkerHash=0;
|
|
std::uint64_t Sequence=0;
|
|
bool bOutsideTileFootprint=false;
|
|
};
|
|
std::array<std::vector<FColumnEvaluationEvent>,9> ColumnEventsByLOD;
|
|
std::array<std::uint64_t,9> ColumnEvaluationEventsByLOD{},UniqueColumnCoordinatesByLOD{};
|
|
std::array<std::uint64_t,9> ColumnDuplicateEventsByLOD{},SameWorkerEvictedRepeatsByLOD{};
|
|
std::array<std::uint64_t,9> DifferentWorkerRepeatsByLOD{},HaloNormalRepeatsByLOD{};
|
|
std::array<std::uint64_t,9> HaloNormalEvaluationsByLOD{},NeighbourDuplicateEventsByLOD{};
|
|
std::array<std::uint64_t,9> CrossWorkerNeighbourDuplicateEventsByLOD{};
|
|
std::array<std::uint64_t,9> ColumnCaptureTilesByLOD{},ExpectedTilesByLOD{};
|
|
std::array<bool,9> ColumnCaptureCompleteByLOD{};
|
|
ColumnCaptureCompleteByLOD.fill(true);
|
|
std::array<double,9> WorkerSecondsByLOD{};
|
|
std::array<double,9> BaseRouteWorkerSecondsByLOD{},PageRouteWorkerSecondsByLOD{};
|
|
std::array<double,9> ColumnEvalMsByLOD{},MarchingExclusiveMsByLOD{};
|
|
std::array<double,9> GroundRefineExclusiveMsByLOD{},CapRefineExclusiveMsByLOD{};
|
|
std::array<double,9> MeshAssemblyMsByLOD{};
|
|
std::array<double,9> SkirtMsByLOD{};
|
|
struct FFarLODProfile
|
|
{
|
|
std::array<std::uint64_t,9> ColumnCalls{},ColumnHits{},ColumnMisses{};
|
|
std::array<std::uint64_t,9> ColumnConflictMisses{},SharedColumnHits{},ColumnEvaluations{};
|
|
std::array<double,9> McColumnMs{},GroundColumnMs{},CapColumnMs{};
|
|
std::array<double,9> McGridMs{},McCellClassMs{},McGradientMs{};
|
|
std::array<double,9> McInterpolationMs{},McDedupMs{},McOtherMs{};
|
|
std::array<double,9> StratumShellMs{},ConnectorShellMs{};
|
|
std::array<std::uint64_t,9> StratumFieldCalls{},ConnectorFieldCalls{},FarShellTriangles{};
|
|
std::array<std::uint64_t,9> StratumColumnPointCalls{},StratumColumnHits{};
|
|
std::array<std::uint64_t,9> StratumColumnMisses{},StratumSharedColumnHits{},StratumColumnEvaluations{};
|
|
std::array<std::uint64_t,9> ConnectorColumnPointCalls{},ConnectorColumnHits{};
|
|
std::array<std::uint64_t,9> ConnectorColumnMisses{},ConnectorSharedColumnHits{},ConnectorColumnEvaluations{};
|
|
std::array<double,9> StratumColumnComputeMs{},ConnectorColumnComputeMs{};
|
|
};
|
|
std::unique_ptr<FFarLODProfile> FarLODProfile;
|
|
if(bReportLodWorkProfile)FarLODProfile=std::make_unique<FFarLODProfile>();
|
|
std::array<std::uint64_t,9> DensitySamplesByLOD{};
|
|
std::array<std::uint64_t,9> SkirtTrianglesByLOD{};
|
|
std::array<std::uint64_t,9> TileCountByLOD{};
|
|
std::array<std::uint64_t,9> BaseRouteTilesByLOD{},PageRouteTilesByLOD{};
|
|
for(const FVoxelTileKey& Tile:Desired.Tiles)
|
|
if(Tile.Level>=0&&Tile.Level<static_cast<int32>(ExpectedTilesByLOD.size()))
|
|
++ExpectedTilesByLOD[static_cast<std::size_t>(Tile.Level)];
|
|
const auto AreSameLODNeighbours=[](const FVoxelTileKey& A,const FVoxelTileKey& B)
|
|
{
|
|
if(A.Level!=B.Level)return false;
|
|
const std::int64_t DX=std::abs(static_cast<std::int64_t>(A.Coord.X)-B.Coord.X);
|
|
const std::int64_t DY=std::abs(static_cast<std::int64_t>(A.Coord.Y)-B.Coord.Y);
|
|
const std::int64_t DZ=std::abs(static_cast<std::int64_t>(A.Coord.Z)-B.Coord.Z);
|
|
return std::max({DX,DY,DZ})<=1&&(DX+DY+DZ)>0;
|
|
};
|
|
uint64 NativeSolidBoxes=0,NativeAirBoxes=0,NativeMixedBoxes=0,NativeUnknownBoxes=0;
|
|
uint64 NativeFieldCalls=0,NativeColumnHits=0,NativeColumnMisses=0,NativeMeshingSkips=0;
|
|
double NativeClassificationMilliseconds=0.0,NativeColumnComputeMilliseconds=0.0;
|
|
double NativeMeshingMilliseconds=0.0;
|
|
for(const FVoxelTileKey& Tile:Desired.Tiles)
|
|
if(Tile.Level<World.FullResClipLevels)++FullDetailExpected;
|
|
std::array<std::optional<FStandaloneCpuTile>,9> GateSampleTiles{};
|
|
std::array<double,9> GateSampleDistances{};
|
|
GateSampleDistances.fill(std::numeric_limits<double>::infinity());
|
|
Clock::time_point FirstTileReady{},NeighborhoodReady{},FullDetailReady{},WholeViewReady{};
|
|
bool bFirstTileReported=false,bNeighborhoodReported=false,bFullDetailReported=false;
|
|
uint64 BlockProofBlocksChecked=0,BlockProofSamplesChecked=0;
|
|
uint64 BlockProofFallbackBlocks=0,BlockProofUnknownBlocks=0,BlockProofViolations=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)
|
|
{
|
|
if(Tile.bGenerationFailed)
|
|
{
|
|
Streamer.Stop();
|
|
std::fprintf(stderr,"[StreamPipeline] a tile failed generation; refusing a partial result.\n");
|
|
return 9;
|
|
}
|
|
if(bExportRequested)SceneExport.Add(Tile);
|
|
++Finished;
|
|
if(bReportMeshCoverage)
|
|
{
|
|
const uint64 TileTriangleCount=static_cast<uint64>(Tile.Indices.size()/3);
|
|
WholeTriangleCount+=TileTriangleCount;
|
|
if(TileTriangleCount>0)++WholeNonEmptyMeshTiles;
|
|
}
|
|
if(Tile.Key.Level>=0&&Tile.Key.Level<static_cast<int32>(TileCountByLOD.size()))
|
|
{
|
|
const std::size_t LOD=static_cast<std::size_t>(Tile.Key.Level);
|
|
++TileCountByLOD[LOD];
|
|
WorkerSecondsByLOD[LOD]+=Tile.WorkerSeconds;
|
|
if(bReportLodWorkProfile)
|
|
{
|
|
if(Tile.CoreDiagnostics.analyticRegionPageRoute)
|
|
{
|
|
++PageRouteTilesByLOD[LOD];
|
|
PageRouteWorkerSecondsByLOD[LOD]+=Tile.WorkerSeconds;
|
|
}
|
|
else
|
|
{
|
|
++BaseRouteTilesByLOD[LOD];
|
|
BaseRouteWorkerSecondsByLOD[LOD]+=Tile.WorkerSeconds;
|
|
}
|
|
}
|
|
ColumnEvalMsByLOD[LOD]+=Tile.CoreDiagnostics.columnEvaluationExclusiveMilliseconds;
|
|
MarchingExclusiveMsByLOD[LOD]+=Tile.CoreDiagnostics.marchingCubesExclusiveMilliseconds;
|
|
GroundRefineExclusiveMsByLOD[LOD]+=Tile.CoreDiagnostics.groundRefinementExclusiveMilliseconds;
|
|
CapRefineExclusiveMsByLOD[LOD]+=Tile.CoreDiagnostics.capRefinementExclusiveMilliseconds;
|
|
MeshAssemblyMsByLOD[LOD]+=Tile.CoreDiagnostics.meshAssemblyMilliseconds;
|
|
SkirtMsByLOD[LOD]+=Tile.CoreDiagnostics.marchingCubesSkirtMilliseconds;
|
|
SkirtTrianglesByLOD[LOD]+=Tile.SkirtTriangleCount;
|
|
if(bReportLodWorkProfile)
|
|
{
|
|
DensitySamplesByLOD[LOD]+=Tile.DensitySamples;
|
|
const auto& D=Tile.CoreDiagnostics;
|
|
FarLODProfile->ColumnCalls[LOD]+=D.fieldCalls;
|
|
FarLODProfile->ColumnHits[LOD]+=D.columnCacheHits;
|
|
FarLODProfile->ColumnMisses[LOD]+=D.columnCacheMisses;
|
|
FarLODProfile->ColumnConflictMisses[LOD]+=D.columnCacheConflictMisses;
|
|
FarLODProfile->SharedColumnHits[LOD]+=D.sharedColumnCacheHits;
|
|
FarLODProfile->ColumnEvaluations[LOD]+=D.columnEvaluations;
|
|
FarLODProfile->McColumnMs[LOD]+=D.marchingCubesColumnComputeMilliseconds;
|
|
FarLODProfile->GroundColumnMs[LOD]+=D.groundRefinementColumnComputeMilliseconds;
|
|
FarLODProfile->CapColumnMs[LOD]+=D.capRefinementColumnComputeMilliseconds;
|
|
FarLODProfile->McGridMs[LOD]+=D.marchingCubesDensityGridMilliseconds;
|
|
FarLODProfile->McCellClassMs[LOD]+=D.marchingCubesCellClassificationMilliseconds;
|
|
FarLODProfile->McGradientMs[LOD]+=D.marchingCubesGradientNormalsMilliseconds;
|
|
FarLODProfile->McInterpolationMs[LOD]+=D.marchingCubesEdgeInterpolationMilliseconds;
|
|
FarLODProfile->McDedupMs[LOD]+=D.marchingCubesVertexDedupStreamMilliseconds;
|
|
FarLODProfile->McOtherMs[LOD]+=D.marchingCubesOtherMilliseconds;
|
|
FarLODProfile->StratumShellMs[LOD]+=D.farStratumShellMilliseconds;
|
|
FarLODProfile->ConnectorShellMs[LOD]+=D.farConnectorShellMilliseconds;
|
|
FarLODProfile->StratumFieldCalls[LOD]+=D.farStratumFieldCalls;
|
|
FarLODProfile->ConnectorFieldCalls[LOD]+=D.farConnectorFieldCalls;
|
|
FarLODProfile->FarShellTriangles[LOD]+=D.farShellTrianglesAdded;
|
|
FarLODProfile->StratumColumnPointCalls[LOD]+=D.farStratumColumnPointCalls;
|
|
FarLODProfile->StratumColumnHits[LOD]+=D.farStratumColumnCacheHits;
|
|
FarLODProfile->StratumColumnMisses[LOD]+=D.farStratumColumnCacheMisses;
|
|
FarLODProfile->StratumSharedColumnHits[LOD]+=D.farStratumSharedColumnCacheHits;
|
|
FarLODProfile->StratumColumnEvaluations[LOD]+=D.farStratumColumnEvaluations;
|
|
FarLODProfile->ConnectorColumnPointCalls[LOD]+=D.farConnectorColumnPointCalls;
|
|
FarLODProfile->ConnectorColumnHits[LOD]+=D.farConnectorColumnCacheHits;
|
|
FarLODProfile->ConnectorColumnMisses[LOD]+=D.farConnectorColumnCacheMisses;
|
|
FarLODProfile->ConnectorSharedColumnHits[LOD]+=D.farConnectorSharedColumnCacheHits;
|
|
FarLODProfile->ConnectorColumnEvaluations[LOD]+=D.farConnectorColumnEvaluations;
|
|
FarLODProfile->StratumColumnComputeMs[LOD]+=D.farStratumColumnComputeMilliseconds;
|
|
FarLODProfile->ConnectorColumnComputeMs[LOD]+=D.farConnectorColumnComputeMilliseconds;
|
|
}
|
|
if(bAuditUniformBlockProofs)
|
|
{
|
|
BlockProofBlocksChecked+=Tile.CoreDiagnostics.uniformBlockProofsChecked;
|
|
BlockProofSamplesChecked+=Tile.CoreDiagnostics.uniformBlockProofSamplesChecked;
|
|
BlockProofFallbackBlocks+=Tile.CoreDiagnostics.uniformBlockProofFallbackBlocks;
|
|
BlockProofUnknownBlocks+=Tile.CoreDiagnostics.uniformBlockProofUnknownBlocks;
|
|
BlockProofViolations+=Tile.CoreDiagnostics.uniformBlockProofViolations;
|
|
}
|
|
if(bScreenSpaceGate&&Assembly&&Tile.Key.Level>=3&&Tile.Key.Level<9
|
|
&&!Tile.Vertices.empty()
|
|
&&StandaloneScreenSpaceGate::TileHasNativeSurface(Tile.Key,*Assembly,
|
|
static_cast<double>(World.CoreVoxelSizeMicrons)/1000000.0))
|
|
{
|
|
const double Distance=StandaloneScreenSpaceGate::TileAabbDistanceMeters(
|
|
Tile.Key,CameraPositionVoxels,
|
|
static_cast<double>(World.CoreVoxelSizeMicrons)/1000000.0);
|
|
const bool bCloser=Distance<GateSampleDistances[LOD]-1e-9;
|
|
const bool bTied=std::abs(Distance-GateSampleDistances[LOD])<=1e-9;
|
|
const bool bKeyLess=!GateSampleTiles[LOD]
|
|
||TileKeyLessForHash(Tile.Key,GateSampleTiles[LOD]->Key);
|
|
if(bCloser||(bTied&&bKeyLess))
|
|
{GateSampleDistances[LOD]=Distance;GateSampleTiles[LOD]=Tile;}
|
|
}
|
|
}
|
|
if(bProfileColumnOverlap&&Tile.Key.Level>=0
|
|
&&Tile.Key.Level<static_cast<int32>(ColumnCaptureTilesByLOD.size()))
|
|
{
|
|
const std::size_t LOD=static_cast<std::size_t>(Tile.Key.Level);
|
|
++ColumnCaptureTilesByLOD[LOD];
|
|
ColumnCaptureCompleteByLOD[LOD]=ColumnCaptureCompleteByLOD[LOD]
|
|
&&Tile.CoreDiagnostics.columnCoordinateCaptureComplete;
|
|
auto& Events=ColumnEventsByLOD[LOD];
|
|
for(const auto& Coordinate:Tile.CoreDiagnostics.evaluatedColumnCoordinates)
|
|
{
|
|
Events.push_back({{Coordinate.x,Coordinate.y},Tile.Key,
|
|
Tile.CoreDiagnostics.columnEvaluationWorkerHash,Coordinate.sequence,
|
|
Coordinate.outsideTileFootprint});
|
|
}
|
|
}
|
|
WholeViewReady=std::max(WholeViewReady,Tile.CompletedAt);
|
|
if(FirstTileReady.time_since_epoch().count()==0||Tile.CompletedAt<FirstTileReady)
|
|
FirstTileReady=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);
|
|
NativeSolidBoxes+=Tile.CoreDiagnostics.solidBoxes;
|
|
NativeAirBoxes+=Tile.CoreDiagnostics.airBoxes;
|
|
NativeMixedBoxes+=Tile.CoreDiagnostics.mixedBoxes;
|
|
NativeUnknownBoxes+=Tile.CoreDiagnostics.unknownBoxes;
|
|
NativeFieldCalls+=Tile.CoreDiagnostics.fieldCalls;
|
|
NativeColumnHits+=Tile.CoreDiagnostics.columnCacheHits;
|
|
NativeColumnMisses+=Tile.CoreDiagnostics.columnCacheMisses;
|
|
NativeMeshingSkips+=Tile.CoreDiagnostics.meshingSkipped?1u:0u;
|
|
NativeClassificationMilliseconds+=Tile.CoreDiagnostics.classificationMilliseconds;
|
|
NativeColumnComputeMilliseconds+=Tile.CoreDiagnostics.columnComputeMilliseconds;
|
|
NativeMeshingMilliseconds+=Tile.CoreDiagnostics.meshingMilliseconds;
|
|
}
|
|
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(!bFirstTileReported&&FirstTileReady.time_since_epoch().count()!=0)
|
|
{
|
|
std::printf("[PipelineMilestone] first_tile_ready=%.3fs\n",
|
|
std::chrono::duration<double>(FirstTileReady-ColdStart).count());
|
|
bFirstTileReported=true;
|
|
}
|
|
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;
|
|
}
|
|
}
|
|
if(bHasCameraStep)
|
|
{
|
|
const auto MovingStart=Clock::now();
|
|
double RegionCompileWallSeconds=0.0;
|
|
double RegionCompileCpuSeconds=0.0;
|
|
std::size_t RegionCompileRequested=0,RegionCompileCovered=0;
|
|
VoxelForgeCore::TileRegionCompileStats RegionCompileStats;
|
|
if(bRegionCrossing)
|
|
{
|
|
std::unordered_set<FVoxelTileKey,FStandaloneTileKeyHash> InitialKeys;
|
|
InitialKeys.reserve(Desired.Tiles.size()*2+1);
|
|
for(const FVoxelTileKey& Key:Desired.Tiles)InitialKeys.insert(Key);
|
|
std::vector<VoxelForgeCore::TileKey> EnteringCoreTiles;
|
|
EnteringCoreTiles.reserve(MovedDesired.Tiles.size());
|
|
for(const FVoxelTileKey& Key:MovedDesired.Tiles)
|
|
{
|
|
if(InitialKeys.contains(Key))continue;
|
|
EnteringCoreTiles.push_back({{Key.Coord.X,Key.Coord.Y,Key.Coord.Z},Key.Level});
|
|
}
|
|
RegionCompileRequested=EnteringCoreTiles.size();
|
|
const auto RegionCompileStart=Clock::now();
|
|
const std::clock_t RegionCompileCpuStart=std::clock();
|
|
std::string RegionCompileError;
|
|
if(!Shape.compileTileRegion(EnteringCoreTiles,
|
|
static_cast<uint32>(std::clamp(WorkerCount,1,16)),Scheduler,
|
|
RegionCompileError,&RegionCompileCovered,&RegionCompileStats,true))
|
|
{
|
|
Streamer.Stop();
|
|
std::fprintf(stderr,"[MovingRegionCompile] failed: %s\n",RegionCompileError.c_str());
|
|
return 2;
|
|
}
|
|
RegionCompileWallSeconds=std::chrono::duration<double>(
|
|
Clock::now()-RegionCompileStart).count();
|
|
RegionCompileCpuSeconds=static_cast<double>(std::clock()-RegionCompileCpuStart)
|
|
/CLOCKS_PER_SEC;
|
|
const std::size_t ParallelWorkers=RegionCompileStats.regionsBuilt>1
|
|
?std::min<std::size_t>(8,RegionCompileStats.regionsBuilt):0;
|
|
std::printf("[MovingRegionCompile] distance_m=%.3f entering_tiles=%zu covered_tiles=%zu regions=%zu lattice_nodes=%llu wall_s=%.6f cpu_s=%.6f worker_threads=%zu mode=analytic-shape-pages\n",
|
|
std::sqrt(static_cast<double>(CameraStepVoxels.X)*CameraStepVoxels.X
|
|
+static_cast<double>(CameraStepVoxels.Y)*CameraStepVoxels.Y
|
|
+static_cast<double>(CameraStepVoxels.Z)*CameraStepVoxels.Z)
|
|
*static_cast<double>(World.CoreVoxelSizeMicrons)/1000000.0,
|
|
RegionCompileRequested,RegionCompileCovered,RegionCompileStats.regionsBuilt,
|
|
static_cast<unsigned long long>(RegionCompileStats.latticeNodes),
|
|
RegionCompileWallSeconds,RegionCompileCpuSeconds,ParallelWorkers);
|
|
}
|
|
const auto MovingTileStart=Clock::now();
|
|
const auto MovingUpdate=Streamer.UpdateDesiredWithSet(MovedDesired,Desired.Tiles,1);
|
|
std::array<std::uint64_t,9> MovingTilesByLOD{};
|
|
std::array<double,9> MovingWorkerSecondsByLOD{};
|
|
std::array<std::uint64_t,9> MovingSamplesByLOD{},MovingNativeFloatCallsByLOD{},
|
|
MovingNativeLatticeCallsByLOD{},MovingNativeOnlyTilesByLOD{},
|
|
MovingAnalyticPageTilesByLOD{},MovingNativeSurfaceTilesByLOD{};
|
|
std::array<std::uint64_t,9> MovingBaseRouteTilesByLOD{};
|
|
std::array<double,9> MovingBaseRouteWorkerSecondsByLOD{},
|
|
MovingPageRouteWorkerSecondsByLOD{};
|
|
std::array<double,9> MovingColumnComputeMsByLOD{},MovingClassificationMsByLOD{},
|
|
MovingMcExclusiveMsByLOD{},MovingMcDensityGridMsByLOD{},
|
|
MovingMcCellClassMsByLOD{},MovingMcGradientMsByLOD{},
|
|
MovingMcInterpolationMsByLOD{},MovingMcDedupMsByLOD{},MovingMcOtherMsByLOD{};
|
|
std::vector<FHashedTile> MovingDensityRows,MovingMeshRows,
|
|
MovingPayloadRows,MovingTopologyRows;
|
|
int32 MovingFinished=0;
|
|
while(MovingFinished<MovingUpdate.NewJobs)
|
|
{
|
|
std::vector<FStandaloneCpuTile> Ready=Streamer.TakeReady();
|
|
if(Ready.empty())
|
|
{std::this_thread::sleep_for(std::chrono::milliseconds(1));continue;}
|
|
for(const FStandaloneCpuTile& Tile:Ready)
|
|
{
|
|
++MovingFinished;
|
|
if(Tile.Key.Level>=0&&Tile.Key.Level<static_cast<int32>(MovingTilesByLOD.size()))
|
|
{
|
|
const std::size_t LOD=static_cast<std::size_t>(Tile.Key.Level);
|
|
++MovingTilesByLOD[LOD];
|
|
MovingWorkerSecondsByLOD[LOD]+=Tile.WorkerSeconds;
|
|
if(bReportLodWorkProfile)
|
|
{
|
|
const auto& D=Tile.CoreDiagnostics;
|
|
MovingSamplesByLOD[LOD]+=Tile.DensitySamples;
|
|
MovingNativeFloatCallsByLOD[LOD]+=D.floatFieldCalls;
|
|
MovingNativeLatticeCallsByLOD[LOD]+=D.latticeFieldCalls;
|
|
MovingNativeOnlyTilesByLOD[LOD]+=D.nativeOnlyRoute?1u:0u;
|
|
MovingAnalyticPageTilesByLOD[LOD]+=D.analyticRegionPageRoute?1u:0u;
|
|
MovingNativeSurfaceTilesByLOD[LOD]+=D.nativeSurfaceTile?1u:0u;
|
|
if(D.analyticRegionPageRoute)
|
|
MovingPageRouteWorkerSecondsByLOD[LOD]+=Tile.WorkerSeconds;
|
|
else
|
|
{
|
|
++MovingBaseRouteTilesByLOD[LOD];
|
|
MovingBaseRouteWorkerSecondsByLOD[LOD]+=Tile.WorkerSeconds;
|
|
}
|
|
MovingColumnComputeMsByLOD[LOD]+=D.columnEvaluationExclusiveMilliseconds;
|
|
MovingClassificationMsByLOD[LOD]+=D.classificationExclusiveMilliseconds;
|
|
MovingMcExclusiveMsByLOD[LOD]+=D.marchingCubesExclusiveMilliseconds;
|
|
MovingMcDensityGridMsByLOD[LOD]+=D.marchingCubesDensityGridMilliseconds;
|
|
MovingMcCellClassMsByLOD[LOD]+=D.marchingCubesCellClassificationMilliseconds;
|
|
MovingMcGradientMsByLOD[LOD]+=D.marchingCubesGradientNormalsMilliseconds;
|
|
MovingMcInterpolationMsByLOD[LOD]+=D.marchingCubesEdgeInterpolationMilliseconds;
|
|
MovingMcDedupMsByLOD[LOD]+=D.marchingCubesVertexDedupStreamMilliseconds;
|
|
MovingMcOtherMsByLOD[LOD]+=D.marchingCubesOtherMilliseconds;
|
|
}
|
|
}
|
|
if(bHashDensity)
|
|
{
|
|
if(Tile.bHasDensityHash)MovingDensityRows.push_back({Tile.Key,Tile.DensityHash});
|
|
if(Tile.bHasMeshHash)MovingMeshRows.push_back({Tile.Key,Tile.MeshHash});
|
|
if(Tile.bHasPayloadHash)MovingPayloadRows.push_back({Tile.Key,Tile.PayloadHash});
|
|
if(Tile.bHasTopologyHash)MovingTopologyRows.push_back({Tile.Key,Tile.TopologyHash});
|
|
}
|
|
}
|
|
}
|
|
const double MovingTileWallSeconds=std::chrono::duration<double>(
|
|
Clock::now()-MovingTileStart).count();
|
|
const double MovingWallSeconds=std::chrono::duration<double>(Clock::now()-MovingStart).count();
|
|
const double StepDistanceMeters=std::sqrt(
|
|
static_cast<double>(CameraStepVoxels.X)*CameraStepVoxels.X
|
|
+static_cast<double>(CameraStepVoxels.Y)*CameraStepVoxels.Y
|
|
+static_cast<double>(CameraStepVoxels.Z)*CameraStepVoxels.Z)
|
|
*static_cast<double>(World.CoreVoxelSizeMicrons)/1000000.0;
|
|
std::printf("[MovingCameraStep] step_voxels=(%.3f,%.3f,%.3f) distance_m=%.3f desired_tiles=%zu new_tiles=%d completed=%d wall_s=%.6f shared_column_cache=%s\n",
|
|
CameraStepVoxels.X,CameraStepVoxels.Y,CameraStepVoxels.Z,StepDistanceMeters,
|
|
MovedDesired.Tiles.size(),MovingUpdate.NewJobs,MovingFinished,MovingWallSeconds,
|
|
bUseSharedColumnCache?"on":"off");
|
|
std::printf("[MovingTileWork] tiles=%d wall_s=%.6f worker_sum_s=%.6f region_compile_wall_s=%.6f crossing_total_s=%.6f\n",
|
|
MovingFinished,MovingTileWallSeconds,
|
|
std::accumulate(MovingWorkerSecondsByLOD.begin(),MovingWorkerSecondsByLOD.end(),0.0),
|
|
RegionCompileWallSeconds,MovingWallSeconds);
|
|
if(bHashDensity)
|
|
std::printf("[MovingTileHash] tiles=%d rows=(%zu,%zu,%zu,%zu) density=0x%016llx mesh=0x%016llx payload=0x%016llx topology=0x%016llx status=%s\n",
|
|
MovingFinished,MovingDensityRows.size(),MovingMeshRows.size(),
|
|
MovingPayloadRows.size(),MovingTopologyRows.size(),
|
|
static_cast<unsigned long long>(HashTileRows(MovingDensityRows)),
|
|
static_cast<unsigned long long>(HashTileRows(MovingMeshRows)),
|
|
static_cast<unsigned long long>(HashTileRows(MovingPayloadRows)),
|
|
static_cast<unsigned long long>(HashTileRows(MovingTopologyRows)),
|
|
MovingDensityRows.size()==static_cast<std::size_t>(MovingFinished)
|
|
&&MovingMeshRows.size()==static_cast<std::size_t>(MovingFinished)
|
|
&&MovingPayloadRows.size()==static_cast<std::size_t>(MovingFinished)
|
|
&&MovingTopologyRows.size()==static_cast<std::size_t>(MovingFinished)
|
|
?"PASS":"FAIL");
|
|
for(std::size_t LOD=0;LOD<MovingTilesByLOD.size();++LOD)
|
|
if(MovingTilesByLOD[LOD]!=0)
|
|
{
|
|
std::printf("[MovingCameraStepLOD] lod=%zu tiles=%llu worker_sum_s=%.6f\n",
|
|
LOD,static_cast<unsigned long long>(MovingTilesByLOD[LOD]),
|
|
MovingWorkerSecondsByLOD[LOD]);
|
|
if(bReportLodWorkProfile)
|
|
std::printf("[MovingCameraStepLODProfile] lod=%zu density_samples=%llu samples_per_tile=%.1f native_float_calls=%llu native_lattice_calls=%llu native_surface_tiles=%llu native_only_tiles=%llu analytic_page_tiles=%llu base_route_tiles=%llu base_route_avg_ms=%.3f page_route_avg_ms=%.3f column_compute_ms=%.3f classification_ms=%.3f mc_exclusive_ms=%.3f mc_density_grid_ms=%.3f mc_cell_class_ms=%.3f mc_gradient_ms=%.3f mc_interpolation_ms=%.3f mc_dedup_ms=%.3f mc_other_ms=%.3f\n",
|
|
LOD,static_cast<unsigned long long>(MovingSamplesByLOD[LOD]),
|
|
static_cast<double>(MovingSamplesByLOD[LOD])/MovingTilesByLOD[LOD],
|
|
static_cast<unsigned long long>(MovingNativeFloatCallsByLOD[LOD]),
|
|
static_cast<unsigned long long>(MovingNativeLatticeCallsByLOD[LOD]),
|
|
static_cast<unsigned long long>(MovingNativeSurfaceTilesByLOD[LOD]),
|
|
static_cast<unsigned long long>(MovingNativeOnlyTilesByLOD[LOD]),
|
|
static_cast<unsigned long long>(MovingAnalyticPageTilesByLOD[LOD]),
|
|
static_cast<unsigned long long>(MovingBaseRouteTilesByLOD[LOD]),
|
|
MovingBaseRouteTilesByLOD[LOD]
|
|
?MovingBaseRouteWorkerSecondsByLOD[LOD]*1000.0
|
|
/MovingBaseRouteTilesByLOD[LOD]:0.0,
|
|
MovingAnalyticPageTilesByLOD[LOD]
|
|
?MovingPageRouteWorkerSecondsByLOD[LOD]*1000.0
|
|
/MovingAnalyticPageTilesByLOD[LOD]:0.0,
|
|
MovingColumnComputeMsByLOD[LOD],MovingClassificationMsByLOD[LOD],
|
|
MovingMcExclusiveMsByLOD[LOD],MovingMcDensityGridMsByLOD[LOD],
|
|
MovingMcCellClassMsByLOD[LOD],MovingMcGradientMsByLOD[LOD],
|
|
MovingMcInterpolationMsByLOD[LOD],MovingMcDedupMsByLOD[LOD],
|
|
MovingMcOtherMsByLOD[LOD]);
|
|
}
|
|
}
|
|
Streamer.Stop();
|
|
bool bScreenSpaceGatePassing=true;
|
|
if(bScreenSpaceGate)
|
|
{
|
|
if(Assembly)
|
|
bScreenSpaceGatePassing=StandaloneScreenSpaceGate::Run(GateSampleTiles,
|
|
Streamer,*Assembly,ClosestDisplayDistances,World.CoreVoxelSizeMicrons,
|
|
World.MaxClipLevel,PixelBudgetsSubvoxels,CameraYawDegrees,
|
|
CameraPitchDegrees);
|
|
else
|
|
{std::fprintf(stderr,"[ScreenSpaceGateResult] no-world-assembly status=REJECT\n");bScreenSpaceGatePassing=false;}
|
|
}
|
|
if(bExportRequested)
|
|
{
|
|
bSceneExportPassing=SceneExport.Close(static_cast<std::uint32_t>(Update.DesiredTiles));
|
|
std::error_code FileSizeError;
|
|
const std::uintmax_t ExportBytes=std::filesystem::file_size(MeshExportPath,FileSizeError);
|
|
std::printf("[MeshSceneExport] tiles=%u expected=%d bytes=%llu status=%s path=%s\n",
|
|
SceneExport.TileCount,Update.DesiredTiles,
|
|
static_cast<unsigned long long>(FileSizeError?0:ExportBytes),
|
|
bSceneExportPassing?"PASS":"FAIL",MeshExportPath.string().c_str());
|
|
bSceneExportPassing=bSceneExportPassing&&!FileSizeError;
|
|
}
|
|
if(bProfileColumnOverlap)
|
|
{
|
|
struct FColumnPriorUse
|
|
{
|
|
FVoxelTileKey Tile;
|
|
std::uint64_t WorkerHash=0;
|
|
};
|
|
for(std::size_t LOD=0;LOD<ColumnEventsByLOD.size();++LOD)
|
|
{
|
|
auto& Events=ColumnEventsByLOD[LOD];
|
|
std::sort(Events.begin(),Events.end(),[](const FColumnEvaluationEvent& A,
|
|
const FColumnEvaluationEvent& B){return A.Sequence<B.Sequence;});
|
|
std::unordered_map<FColumnXY,std::vector<FColumnPriorUse>,FColumnXYHash> UsesByXY;
|
|
UsesByXY.reserve(Events.size());
|
|
for(const FColumnEvaluationEvent& Event:Events)
|
|
{
|
|
++ColumnEvaluationEventsByLOD[LOD];
|
|
if(Event.bOutsideTileFootprint)++HaloNormalEvaluationsByLOD[LOD];
|
|
auto [Found,Inserted]=UsesByXY.try_emplace(Event.XY);
|
|
auto& PriorUses=Found->second;
|
|
if(Inserted)
|
|
{
|
|
++UniqueColumnCoordinatesByLOD[LOD];
|
|
PriorUses.push_back({Event.Tile,Event.WorkerHash});
|
|
continue;
|
|
}
|
|
++ColumnDuplicateEventsByLOD[LOD];
|
|
if(Event.bOutsideTileFootprint)
|
|
++HaloNormalRepeatsByLOD[LOD];
|
|
else
|
|
{
|
|
const bool bSameWorkerSeen=std::any_of(PriorUses.begin(),PriorUses.end(),
|
|
[&](const FColumnPriorUse& Prior)
|
|
{return Prior.WorkerHash==Event.WorkerHash;});
|
|
if(bSameWorkerSeen)++SameWorkerEvictedRepeatsByLOD[LOD];
|
|
else ++DifferentWorkerRepeatsByLOD[LOD];
|
|
}
|
|
bool bHasNeighbourDuplicate=false,bHasCrossWorkerNeighbourDuplicate=false;
|
|
for(const FColumnPriorUse& Prior:PriorUses)
|
|
{
|
|
if(!AreSameLODNeighbours(Prior.Tile,Event.Tile))continue;
|
|
bHasNeighbourDuplicate=true;
|
|
bHasCrossWorkerNeighbourDuplicate=bHasCrossWorkerNeighbourDuplicate
|
|
||Prior.WorkerHash!=Event.WorkerHash;
|
|
}
|
|
if(bHasNeighbourDuplicate)++NeighbourDuplicateEventsByLOD[LOD];
|
|
if(bHasCrossWorkerNeighbourDuplicate)
|
|
++CrossWorkerNeighbourDuplicateEventsByLOD[LOD];
|
|
PriorUses.push_back({Event.Tile,Event.WorkerHash});
|
|
}
|
|
}
|
|
}
|
|
bool bColumnOverlapCapturePassing=true;
|
|
if(bProfileColumnOverlap)
|
|
{
|
|
for(std::size_t LOD=0;LOD<ColumnCaptureTilesByLOD.size();++LOD)
|
|
{
|
|
const bool bLODComplete=ColumnCaptureCompleteByLOD[LOD]
|
|
&&ColumnCaptureTilesByLOD[LOD]==ExpectedTilesByLOD[LOD];
|
|
bColumnOverlapCapturePassing=bColumnOverlapCapturePassing&&bLODComplete;
|
|
std::printf("[WorldAssemblerLODColumnOverlap] lod=%zu tiles=%llu expected_tiles=%llu total_column_evaluations=%llu unique_xy_columns=%llu repeated_column_evaluations=%llu same_worker_evicted_repeats=%llu different_worker_repeats=%llu halo_normal_repeats=%llu halo_normal_evaluations=%llu neighbouring_duplicate_evaluations=%llu cross_worker_neighbouring_duplicates=%llu capture_complete=%d\n",
|
|
LOD,static_cast<unsigned long long>(ColumnCaptureTilesByLOD[LOD]),
|
|
static_cast<unsigned long long>(ExpectedTilesByLOD[LOD]),
|
|
static_cast<unsigned long long>(ColumnEvaluationEventsByLOD[LOD]),
|
|
static_cast<unsigned long long>(UniqueColumnCoordinatesByLOD[LOD]),
|
|
static_cast<unsigned long long>(ColumnDuplicateEventsByLOD[LOD]),
|
|
static_cast<unsigned long long>(SameWorkerEvictedRepeatsByLOD[LOD]),
|
|
static_cast<unsigned long long>(DifferentWorkerRepeatsByLOD[LOD]),
|
|
static_cast<unsigned long long>(HaloNormalRepeatsByLOD[LOD]),
|
|
static_cast<unsigned long long>(HaloNormalEvaluationsByLOD[LOD]),
|
|
static_cast<unsigned long long>(NeighbourDuplicateEventsByLOD[LOD]),
|
|
static_cast<unsigned long long>(CrossWorkerNeighbourDuplicateEventsByLOD[LOD]),
|
|
bLODComplete?1:0);
|
|
}
|
|
}
|
|
for(std::size_t LOD=0;LOD<TileCountByLOD.size();++LOD)
|
|
{
|
|
std::printf("[WorldAssemblerLODWorker] lod=%zu tiles=%llu worker_sum_s=%.6f column_eval_exclusive_ms=%.3f mc_exclusive_ms=%.3f ground_refine_exclusive_ms=%.3f cap_refine_exclusive_ms=%.3f skirt_ms=%.3f skirt_triangles=%llu mesh_assembly_ms=%.3f\n",
|
|
LOD,static_cast<unsigned long long>(TileCountByLOD[LOD]),WorkerSecondsByLOD[LOD],
|
|
ColumnEvalMsByLOD[LOD],MarchingExclusiveMsByLOD[LOD],
|
|
GroundRefineExclusiveMsByLOD[LOD],CapRefineExclusiveMsByLOD[LOD],
|
|
SkirtMsByLOD[LOD],static_cast<unsigned long long>(SkirtTrianglesByLOD[LOD]),
|
|
MeshAssemblyMsByLOD[LOD]);
|
|
if(bReportLodWorkProfile)
|
|
{
|
|
const auto& Detail=*FarLODProfile;
|
|
const double SamplesPerTile=TileCountByLOD[LOD]
|
|
?static_cast<double>(DensitySamplesByLOD[LOD])/TileCountByLOD[LOD]:0.0;
|
|
std::printf("[WorldAssemblerLODSamples] lod=%zu samples=%llu samples_per_tile=%.1f\n",
|
|
LOD,static_cast<unsigned long long>(DensitySamplesByLOD[LOD]),SamplesPerTile);
|
|
std::printf("[WorldAssemblerLODRouteProfile] lod=%zu base_shape_tiles=%llu base_shape_avg_ms=%.3f analytic_page_tiles=%llu analytic_page_avg_ms=%.3f\n",
|
|
LOD,static_cast<unsigned long long>(BaseRouteTilesByLOD[LOD]),
|
|
BaseRouteTilesByLOD[LOD]
|
|
?BaseRouteWorkerSecondsByLOD[LOD]*1000.0/BaseRouteTilesByLOD[LOD]:0.0,
|
|
static_cast<unsigned long long>(PageRouteTilesByLOD[LOD]),
|
|
PageRouteTilesByLOD[LOD]
|
|
?PageRouteWorkerSecondsByLOD[LOD]*1000.0/PageRouteTilesByLOD[LOD]:0.0);
|
|
const double TileDivisor=TileCountByLOD[LOD]
|
|
?static_cast<double>(TileCountByLOD[LOD]):1.0;
|
|
std::printf("[WorldAssemblerLODCostDetail] lod=%zu tiles=%llu density_samples=%llu column_point_calls=%llu column_local_hits=%llu column_local_misses=%llu column_conflict_misses=%llu column_shared_hits=%llu column_formula_evaluations=%llu column_compute_ms=%.3f mc_column_ms=%.3f mc_exclusive_ms=%.3f mc_grid_ms=%.3f mc_cell_class_ms=%.3f mc_gradient_ms=%.3f mc_interpolation_ms=%.3f mc_dedup_stream_ms=%.3f mc_other_ms=%.3f ground_column_ms=%.3f ground_exclusive_ms=%.3f cap_column_ms=%.3f cap_exclusive_ms=%.3f stratum_shell_ms=%.3f stratum_field_calls=%llu stratum_column_calls=%llu stratum_column_hits=%llu stratum_column_misses=%llu stratum_shared_hits=%llu stratum_column_evaluations=%llu stratum_column_compute_ms=%.3f connector_shell_ms=%.3f connector_field_calls=%llu connector_column_calls=%llu connector_column_hits=%llu connector_column_misses=%llu connector_shared_hits=%llu connector_column_evaluations=%llu connector_column_compute_ms=%.3f far_shell_triangles=%llu skirt_ms=%.3f mesh_assembly_ms=%.3f worker_ms_per_tile=%.3f column_formula_evals_per_tile=%.1f connector_calls_per_tile=%.1f\n",
|
|
LOD,static_cast<unsigned long long>(TileCountByLOD[LOD]),
|
|
static_cast<unsigned long long>(DensitySamplesByLOD[LOD]),
|
|
static_cast<unsigned long long>(Detail.ColumnCalls[LOD]),
|
|
static_cast<unsigned long long>(Detail.ColumnHits[LOD]),
|
|
static_cast<unsigned long long>(Detail.ColumnMisses[LOD]),
|
|
static_cast<unsigned long long>(Detail.ColumnConflictMisses[LOD]),
|
|
static_cast<unsigned long long>(Detail.SharedColumnHits[LOD]),
|
|
static_cast<unsigned long long>(Detail.ColumnEvaluations[LOD]),
|
|
ColumnEvalMsByLOD[LOD],Detail.McColumnMs[LOD],MarchingExclusiveMsByLOD[LOD],
|
|
Detail.McGridMs[LOD],Detail.McCellClassMs[LOD],Detail.McGradientMs[LOD],
|
|
Detail.McInterpolationMs[LOD],Detail.McDedupMs[LOD],Detail.McOtherMs[LOD],
|
|
Detail.GroundColumnMs[LOD],GroundRefineExclusiveMsByLOD[LOD],
|
|
Detail.CapColumnMs[LOD],CapRefineExclusiveMsByLOD[LOD],
|
|
Detail.StratumShellMs[LOD],
|
|
static_cast<unsigned long long>(Detail.StratumFieldCalls[LOD]),
|
|
static_cast<unsigned long long>(Detail.StratumColumnPointCalls[LOD]),
|
|
static_cast<unsigned long long>(Detail.StratumColumnHits[LOD]),
|
|
static_cast<unsigned long long>(Detail.StratumColumnMisses[LOD]),
|
|
static_cast<unsigned long long>(Detail.StratumSharedColumnHits[LOD]),
|
|
static_cast<unsigned long long>(Detail.StratumColumnEvaluations[LOD]),
|
|
Detail.StratumColumnComputeMs[LOD],
|
|
Detail.ConnectorShellMs[LOD],
|
|
static_cast<unsigned long long>(Detail.ConnectorFieldCalls[LOD]),
|
|
static_cast<unsigned long long>(Detail.ConnectorColumnPointCalls[LOD]),
|
|
static_cast<unsigned long long>(Detail.ConnectorColumnHits[LOD]),
|
|
static_cast<unsigned long long>(Detail.ConnectorColumnMisses[LOD]),
|
|
static_cast<unsigned long long>(Detail.ConnectorSharedColumnHits[LOD]),
|
|
static_cast<unsigned long long>(Detail.ConnectorColumnEvaluations[LOD]),
|
|
Detail.ConnectorColumnComputeMs[LOD],
|
|
static_cast<unsigned long long>(Detail.FarShellTriangles[LOD]),
|
|
SkirtMsByLOD[LOD],MeshAssemblyMsByLOD[LOD],
|
|
WorkerSecondsByLOD[LOD]*1000.0/TileDivisor,
|
|
static_cast<double>(Detail.ColumnEvaluations[LOD])/TileDivisor,
|
|
static_cast<double>(Detail.ConnectorFieldCalls[LOD])/TileDivisor);
|
|
}
|
|
}
|
|
const double NeighborhoodSeconds=std::chrono::duration<double>(NeighborhoodReady-ColdStart).count();
|
|
const double NeighborhoodMeshSeconds=std::max(0.0,
|
|
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();
|
|
const double FirstTileSeconds=std::chrono::duration<double>(FirstTileReady-ColdStart).count();
|
|
std::printf("[StreamPipeline] workers=%d shape=%.3fs first_tile_ready=%.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,FirstTileSeconds,NeighborhoodMeshSeconds,NeighborhoodSeconds,
|
|
FullDetailSeconds,WholeViewSeconds,NeighborhoodFinished,
|
|
FullDetailFinished,FullDetailExpected,Finished,Desired.Tiles.size(),StreamSeconds);
|
|
if(bReportMeshCoverage)
|
|
std::printf("[PipelineMeshCoverage] tiles=%d nonempty_mesh_tiles=%llu indexed_triangles=%llu\n",
|
|
Finished,static_cast<unsigned long long>(WholeNonEmptyMeshTiles),
|
|
static_cast<unsigned long long>(WholeTriangleCount));
|
|
std::printf("[WorldAssemblerTilePipeline] full_detail_tiles=%d solid_boxes=%llu air_boxes=%llu mixed_boxes=%llu unknown_boxes=%llu whole_tile_skips=%llu field_calls=%llu column_cache_hits=%llu column_cache_misses=%llu column_compute_worker_ms=%.3f classification_worker_ms=%.3f meshing_worker_ms=%.3f\n",
|
|
FullDetailExpected,static_cast<unsigned long long>(NativeSolidBoxes),
|
|
static_cast<unsigned long long>(NativeAirBoxes),
|
|
static_cast<unsigned long long>(NativeMixedBoxes),
|
|
static_cast<unsigned long long>(NativeUnknownBoxes),
|
|
static_cast<unsigned long long>(NativeMeshingSkips),
|
|
static_cast<unsigned long long>(NativeFieldCalls),
|
|
static_cast<unsigned long long>(NativeColumnHits),
|
|
static_cast<unsigned long long>(NativeColumnMisses),
|
|
NativeColumnComputeMilliseconds,NativeClassificationMilliseconds,
|
|
NativeMeshingMilliseconds);
|
|
if(bAuditUniformBlockProofs)
|
|
std::printf("[UniformBlockProofAudit] checked_blocks=%llu checked_samples=%llu fallback_blocks=%llu unknown_blocks=%llu violations=%llu\n",
|
|
static_cast<unsigned long long>(BlockProofBlocksChecked),
|
|
static_cast<unsigned long long>(BlockProofSamplesChecked),
|
|
static_cast<unsigned long long>(BlockProofFallbackBlocks),
|
|
static_cast<unsigned long long>(BlockProofUnknownBlocks),
|
|
static_cast<unsigned long long>(BlockProofViolations));
|
|
|
|
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)));
|
|
for(std::size_t LOD=0;LOD<TileCountByLOD.size();++LOD)
|
|
{
|
|
const auto FilterLOD=[LOD](const std::vector<FHashedTile>& Rows)
|
|
{
|
|
std::vector<FHashedTile> Selected;
|
|
for(const FHashedTile& Row:Rows)
|
|
if(Row.Key.Level==static_cast<int32>(LOD))Selected.push_back(Row);
|
|
return Selected;
|
|
};
|
|
auto LodDensity=FilterLOD(DensityRows),LodMesh=FilterLOD(MeshRows);
|
|
auto LodPayload=FilterLOD(PayloadRows),LodTopology=FilterLOD(TopologyRows);
|
|
std::printf("[PipelineLODHash] lod=%zu tiles=%llu density=0x%016llx mesh=0x%016llx payload=0x%016llx topology=0x%016llx\n",
|
|
LOD,static_cast<unsigned long long>(TileCountByLOD[LOD]),
|
|
static_cast<unsigned long long>(HashTileRows(LodDensity)),
|
|
static_cast<unsigned long long>(HashTileRows(LodMesh)),
|
|
static_cast<unsigned long long>(HashTileRows(LodPayload)),
|
|
static_cast<unsigned long long>(HashTileRows(LodTopology)));
|
|
}
|
|
const std::array<VoxelForgeCore::Vec3d,9> QueryPoints={{
|
|
{CameraPositionVoxels.X,CameraPositionVoxels.Y,CameraPositionVoxels.Z},
|
|
{CameraPositionVoxels.X+8.0,CameraPositionVoxels.Y,CameraPositionVoxels.Z},
|
|
{CameraPositionVoxels.X-8.0,CameraPositionVoxels.Y,CameraPositionVoxels.Z},
|
|
{CameraPositionVoxels.X,CameraPositionVoxels.Y+8.0,CameraPositionVoxels.Z},
|
|
{CameraPositionVoxels.X,CameraPositionVoxels.Y-8.0,CameraPositionVoxels.Z},
|
|
{CameraPositionVoxels.X,CameraPositionVoxels.Y,CameraPositionVoxels.Z+8.0},
|
|
{CameraPositionVoxels.X,CameraPositionVoxels.Y,CameraPositionVoxels.Z-8.0},
|
|
{CameraPositionVoxels.X+64.0,CameraPositionVoxels.Y-32.0,CameraPositionVoxels.Z+16.0},
|
|
{CameraPositionVoxels.X-128.0,CameraPositionVoxels.Y+96.0,CameraPositionVoxels.Z-64.0}}};
|
|
uint64 ExactDensityHash=14695981039346656037ull;
|
|
uint64 ExactUnclampedHash=14695981039346656037ull;
|
|
uint64 ExactDetailHash=14695981039346656037ull;
|
|
uint64 ExactCollisionHash=14695981039346656037ull;
|
|
for(const auto& Point:QueryPoints)
|
|
{
|
|
const float Density=Shape.densityAt(Point);
|
|
const float Unclamped=Shape.unclampedDensityAt(Point);
|
|
const auto Detail=Shape.detailSampleAt(Point);
|
|
HashWord(ExactDensityHash,std::bit_cast<uint32>(Density),4);
|
|
HashWord(ExactUnclampedHash,std::bit_cast<uint32>(Unclamped),4);
|
|
HashWord(ExactDetailHash,std::bit_cast<uint32>(Detail.displacement),4);
|
|
HashWord(ExactDetailHash,std::bit_cast<uint32>(Detail.caveInfluence),4);
|
|
HashWord(ExactDetailHash,std::bit_cast<uint32>(Detail.floorInfluence),4);
|
|
HashWord(ExactCollisionHash,Shape.isSolidAt(Point)?1u:0u,1);
|
|
}
|
|
std::printf("[PipelineExactQueryHash] probes=%zu density=0x%016llx unclamped=0x%016llx detail=0x%016llx collision=0x%016llx path=direct-Core-queries\n",
|
|
QueryPoints.size(),static_cast<unsigned long long>(ExactDensityHash),
|
|
static_cast<unsigned long long>(ExactUnclampedHash),
|
|
static_cast<unsigned long long>(ExactDetailHash),
|
|
static_cast<unsigned long long>(ExactCollisionHash));
|
|
}
|
|
return NeighborhoodFinished==static_cast<int32>(Neighborhood.size())
|
|
&&FullDetailFinished==FullDetailExpected&&Finished==Update.DesiredTiles&&bHashesPassing
|
|
&&(!bProfileColumnOverlap||bColumnOverlapCapturePassing)
|
|
&&(!bExportRequested||bSceneExportPassing)&&bScreenSpaceGatePassing
|
|
&&(!bAuditUniformBlockProofs||BlockProofViolations==0)?0:6;
|
|
}
|
|
} // namespace StandaloneStreamBenchmark
|