wip(L6, side branch): screen-space far gate; no-op cutoff removed; cost within noise vs L4
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ED33VnqepD4YY6gDAAA832
This commit is contained in:
1 parent
b21a19d561
commit
aa67bd31f5
13 files changed
+474
-615
No files matched your search
@@ -341,11 +341,12 @@ int main(int argc,char** argv)
|
||||
{
|
||||
if(argc<3)
|
||||
{
|
||||
std::fprintf(stderr,"Usage: %s scene.vfmesh output.png [--size=WIDTHxHEIGHT] [--fov-deg=90] [--yaw-deg=N] [--pitch-deg=N]\n",argv[0]);
|
||||
std::fprintf(stderr,"Usage: %s scene.vfmesh output.png [--size=WIDTHxHEIGHT] [--fov-deg=90] [--camera-m=X,Y,Z] [--yaw-deg=N] [--pitch-deg=N]\n",argv[0]);
|
||||
return 2;
|
||||
}
|
||||
int Width=2560,Height=1440;double Fov=90.0;
|
||||
bool HasYaw=false,HasPitch=false;double Yaw=0.0,Pitch=0.0;
|
||||
bool HasCamera=false;std::array<double,3> Camera{};
|
||||
for(int I=3;I<argc;++I)
|
||||
{
|
||||
const std::string_view Arg(argv[I]);
|
||||
@@ -361,6 +362,14 @@ int main(int argc,char** argv)
|
||||
if(End==Value.c_str()||*End!='\0'||Fov<=1.0||Fov>=179.0)
|
||||
{std::fprintf(stderr,"Invalid horizontal FOV\n");return 2;}
|
||||
}
|
||||
else if(Arg.starts_with("--camera-m="))
|
||||
{
|
||||
const std::string Value(Arg.substr(11));
|
||||
if(std::sscanf(Value.c_str(),"%lf,%lf,%lf",&Camera[0],&Camera[1],&Camera[2])!=3
|
||||
||!std::isfinite(Camera[0])||!std::isfinite(Camera[1])||!std::isfinite(Camera[2]))
|
||||
{std::fprintf(stderr,"Invalid camera position\n");return 2;}
|
||||
HasCamera=true;
|
||||
}
|
||||
else if(Arg.starts_with("--yaw-deg=")||Arg.starts_with("--pitch-deg="))
|
||||
{
|
||||
const bool IsYaw=Arg.starts_with("--yaw-deg=");
|
||||
@@ -376,6 +385,7 @@ int main(int argc,char** argv)
|
||||
FScene Scene;std::string Error;
|
||||
if(!LoadScene(argv[1],Scene,Error))
|
||||
{std::fprintf(stderr,"[OffscreenCapture] FAIL: %s\n",Error.c_str());return 2;}
|
||||
if(HasCamera)Scene.CameraMeters=Camera;
|
||||
if(HasYaw)Scene.YawDegrees=Yaw;
|
||||
if(HasPitch)Scene.PitchDegrees=Pitch;
|
||||
std::printf("[OffscreenScene] voxels_per_meter=%.6f camera_m=(%.3f,%.3f,%.3f) yaw=%.2f pitch=%.2f vertices=%zu triangles=%llu\n",
|
||||
|
||||
@@ -22,6 +22,12 @@ namespace StandaloneScreenSpaceGate
|
||||
int Lod=0;
|
||||
std::uint64_t Samples=0,Missing=0,Floating=0;
|
||||
double P50=0.0,P99=0.0,Maximum=0.0;
|
||||
double VerticalP50=0.0,VerticalP99=0.0,VerticalMaximum=0.0;
|
||||
double MaximumNormalDistanceMeters=0.0,MaximumX=0.0,MaximumY=0.0;
|
||||
double MaximumReferenceZ=0.0,MaximumDisplayedZ=0.0;
|
||||
double MaximumSlopeDegrees=0.0;
|
||||
std::array<std::uint64_t,4> SlopeSamples{};
|
||||
std::array<double,4> SlopeP99{},SlopeMaximum{};
|
||||
bool Valid=false;
|
||||
};
|
||||
|
||||
@@ -30,9 +36,128 @@ namespace StandaloneScreenSpaceGate
|
||||
int CoarseLod=0;
|
||||
std::uint64_t Samples=0,Cracks=0,Floating=0;
|
||||
double P50=0.0,P99=0.0,Maximum=0.0;
|
||||
double VerticalP50=0.0,VerticalP99=0.0,VerticalMaximum=0.0;
|
||||
double MaximumNormalDistanceMeters=0.0,MaximumX=0.0,MaximumY=0.0;
|
||||
double MaximumReferenceZ=0.0,MaximumDisplayedZ=0.0;
|
||||
double MaximumSlopeDegrees=0.0;
|
||||
std::array<std::uint64_t,4> SlopeSamples{};
|
||||
std::array<double,4> SlopeP99{},SlopeMaximum{};
|
||||
bool Valid=false;
|
||||
};
|
||||
|
||||
struct FSurfaceRoot
|
||||
{
|
||||
double ZMeters=0.0;
|
||||
double SlopeX=0.0;
|
||||
double SlopeY=0.0;
|
||||
};
|
||||
|
||||
struct FViewBasis
|
||||
{
|
||||
std::array<double,3> Right{};
|
||||
std::array<double,3> Up{};
|
||||
};
|
||||
|
||||
struct FErrorAccumulator
|
||||
{
|
||||
std::vector<double> Pixels,VerticalPixels;
|
||||
std::array<std::vector<double>,4> BySlope;
|
||||
double Maximum=-1.0,MaximumVertical=0.0,MaximumNormalDistanceMeters=0.0;
|
||||
double MaximumX=0.0,MaximumY=0.0,MaximumSlopeDegrees=0.0;
|
||||
double MaximumReferenceZ=0.0,MaximumDisplayedZ=0.0;
|
||||
std::array<std::uint64_t,4> SlopeSamples{};
|
||||
};
|
||||
|
||||
inline double Percentile(std::vector<double>& Values,double P);
|
||||
|
||||
inline FViewBasis MakeViewBasis(double YawDegrees,double PitchDegrees)
|
||||
{
|
||||
constexpr double Pi=3.14159265358979323846;
|
||||
const double Yaw=YawDegrees*Pi/180.0,Pitch=PitchDegrees*Pi/180.0;
|
||||
const std::array<double,3> Forward={std::sin(Yaw)*std::cos(Pitch),
|
||||
std::cos(Yaw)*std::cos(Pitch),std::sin(Pitch)};
|
||||
FViewBasis Basis;
|
||||
Basis.Right={std::cos(Yaw),-std::sin(Yaw),0.0};
|
||||
Basis.Up={Basis.Right[1]*Forward[2]-Basis.Right[2]*Forward[1],
|
||||
Basis.Right[2]*Forward[0]-Basis.Right[0]*Forward[2],
|
||||
Basis.Right[0]*Forward[1]-Basis.Right[1]*Forward[0]};
|
||||
return Basis;
|
||||
}
|
||||
|
||||
inline std::size_t SlopeBin(double Degrees)
|
||||
{
|
||||
if(Degrees<30.0)return 0;
|
||||
if(Degrees<45.0)return 1;
|
||||
if(Degrees<60.0)return 2;
|
||||
return 3;
|
||||
}
|
||||
|
||||
inline void AddError(FErrorAccumulator& Accumulator,double DeltaZMeters,
|
||||
const FSurfaceRoot& Root,double PixelBudgetMeters,const FViewBasis& View,
|
||||
double X,double Y)
|
||||
{
|
||||
constexpr double Pi=3.14159265358979323846;
|
||||
const double GradientSquared=Root.SlopeX*Root.SlopeX+Root.SlopeY*Root.SlopeY;
|
||||
const double NormalLength=std::sqrt(1.0+GradientSquared);
|
||||
const double NormalX=-Root.SlopeX/NormalLength;
|
||||
const double NormalY=-Root.SlopeY/NormalLength;
|
||||
const double NormalZ=1.0/NormalLength;
|
||||
const double NormalDistance=std::abs(DeltaZMeters)/NormalLength;
|
||||
const double ProjectedNormal=std::hypot(
|
||||
NormalX*View.Right[0]+NormalY*View.Right[1]+NormalZ*View.Right[2],
|
||||
NormalX*View.Up[0]+NormalY*View.Up[1]+NormalZ*View.Up[2]);
|
||||
const double ProjectedVertical=std::hypot(View.Right[2],View.Up[2]);
|
||||
const double NormalPixels=NormalDistance*ProjectedNormal/PixelBudgetMeters;
|
||||
const double VerticalPixels=std::abs(DeltaZMeters)*ProjectedVertical/PixelBudgetMeters;
|
||||
const double SlopeDegrees=std::atan(std::sqrt(GradientSquared))*180.0/Pi;
|
||||
Accumulator.Pixels.push_back(NormalPixels);
|
||||
Accumulator.VerticalPixels.push_back(VerticalPixels);
|
||||
const std::size_t Bin=SlopeBin(SlopeDegrees);
|
||||
Accumulator.BySlope[Bin].push_back(NormalPixels);
|
||||
++Accumulator.SlopeSamples[Bin];
|
||||
if(NormalPixels>Accumulator.Maximum)
|
||||
{
|
||||
Accumulator.Maximum=NormalPixels;
|
||||
Accumulator.MaximumVertical=VerticalPixels;
|
||||
Accumulator.MaximumNormalDistanceMeters=NormalDistance;
|
||||
Accumulator.MaximumX=X;Accumulator.MaximumY=Y;
|
||||
Accumulator.MaximumSlopeDegrees=SlopeDegrees;
|
||||
Accumulator.MaximumReferenceZ=Root.ZMeters;
|
||||
Accumulator.MaximumDisplayedZ=Root.ZMeters+DeltaZMeters;
|
||||
}
|
||||
}
|
||||
|
||||
inline void AddMissing(FErrorAccumulator& Accumulator)
|
||||
{
|
||||
Accumulator.Pixels.push_back(2.0);
|
||||
Accumulator.VerticalPixels.push_back(2.0);
|
||||
}
|
||||
|
||||
inline void FinalizeError(FErrorRow& Row,FErrorAccumulator& Accumulator)
|
||||
{
|
||||
Row.Samples=Accumulator.Pixels.size();
|
||||
Row.P50=Percentile(Accumulator.Pixels,0.50);
|
||||
Row.P99=Percentile(Accumulator.Pixels,0.99);
|
||||
Row.Maximum=Accumulator.Pixels.empty()?0.0
|
||||
:*std::max_element(Accumulator.Pixels.begin(),Accumulator.Pixels.end());
|
||||
Row.VerticalP50=Percentile(Accumulator.VerticalPixels,0.50);
|
||||
Row.VerticalP99=Percentile(Accumulator.VerticalPixels,0.99);
|
||||
Row.VerticalMaximum=Accumulator.VerticalPixels.empty()?0.0
|
||||
:*std::max_element(Accumulator.VerticalPixels.begin(),Accumulator.VerticalPixels.end());
|
||||
Row.MaximumNormalDistanceMeters=Accumulator.MaximumNormalDistanceMeters;
|
||||
Row.MaximumX=Accumulator.MaximumX;Row.MaximumY=Accumulator.MaximumY;
|
||||
Row.MaximumReferenceZ=Accumulator.MaximumReferenceZ;
|
||||
Row.MaximumDisplayedZ=Accumulator.MaximumDisplayedZ;
|
||||
Row.MaximumSlopeDegrees=Accumulator.MaximumSlopeDegrees;
|
||||
Row.SlopeSamples=Accumulator.SlopeSamples;
|
||||
for(std::size_t Bin=0;Bin<Accumulator.BySlope.size();++Bin)
|
||||
{
|
||||
Row.SlopeP99[Bin]=Percentile(Accumulator.BySlope[Bin],0.99);
|
||||
Row.SlopeMaximum[Bin]=Accumulator.BySlope[Bin].empty()?0.0
|
||||
:*std::max_element(Accumulator.BySlope[Bin].begin(),Accumulator.BySlope[Bin].end());
|
||||
}
|
||||
}
|
||||
|
||||
struct FHeightGrid
|
||||
{
|
||||
int Side=65;
|
||||
@@ -162,38 +287,51 @@ namespace StandaloneScreenSpaceGate
|
||||
return Grid;
|
||||
}
|
||||
|
||||
inline std::vector<double> ExactHeightRoots(const FHeightGrid& Grid,int Ix,int Iy,
|
||||
inline std::vector<FSurfaceRoot> ExactHeightRoots(const FHeightGrid& Grid,int Ix,int Iy,
|
||||
const FVoxelTileKey& Patch,double VoxelMeters,
|
||||
const VoxelForgeCore::WorldAssemblyManifest& Assembly)
|
||||
{
|
||||
std::vector<double> Roots;
|
||||
std::vector<FSurfaceRoot> Roots;
|
||||
const double XSubvoxels=Grid.X(Ix)/VoxelMeters*256.0;
|
||||
const double YSubvoxels=Grid.Y(Iy)/VoxelMeters*256.0;
|
||||
const auto X=static_cast<std::int64_t>(std::llround(XSubvoxels));
|
||||
const auto Y=static_cast<std::int64_t>(std::llround(YSubvoxels));
|
||||
const double MinZ=static_cast<double>(Patch.OriginVoxels().Z);
|
||||
const double MaxZ=MinZ+Patch.ExtentVoxels();
|
||||
constexpr std::int64_t NormalProbeSubvoxels=256;
|
||||
for(const auto& Plan:Assembly.plan.strates)
|
||||
{
|
||||
VoxelForgeCore::Heightfield2D Field(Assembly.recipe,Plan);
|
||||
if(!Field.isValid())continue;
|
||||
const auto Column=Field.point(static_cast<std::int64_t>(std::llround(XSubvoxels)),
|
||||
static_cast<std::int64_t>(std::llround(YSubvoxels)),0);
|
||||
const auto Column=Field.point(X,Y,0);
|
||||
if(Column.groundHeightSubvoxels>=Column.capHeightSubvoxels)continue;
|
||||
const double Ground=static_cast<double>(Column.groundHeightSubvoxels)/(256.0)*VoxelMeters;
|
||||
const double Cap=static_cast<double>(Column.capHeightSubvoxels)/(256.0)*VoxelMeters;
|
||||
const auto XMinus=Field.point(X-NormalProbeSubvoxels,Y,0);
|
||||
const auto XPlus=Field.point(X+NormalProbeSubvoxels,Y,0);
|
||||
const auto YMinus=Field.point(X,Y-NormalProbeSubvoxels,0);
|
||||
const auto YPlus=Field.point(X,Y+NormalProbeSubvoxels,0);
|
||||
const auto Slope=[&](std::int64_t Minus,std::int64_t Plus)
|
||||
{return static_cast<double>(Plus-Minus)/(2.0*NormalProbeSubvoxels);};
|
||||
const double Ground=static_cast<double>(Column.groundHeightSubvoxels)/256.0*VoxelMeters;
|
||||
const double Cap=static_cast<double>(Column.capHeightSubvoxels)/256.0*VoxelMeters;
|
||||
const double PlanMinZ=static_cast<double>(Plan.bottomChunkZ)*32.0*VoxelMeters;
|
||||
const double PlanMaxZ=static_cast<double>(Plan.topChunkZ+1)*32.0*VoxelMeters;
|
||||
if(Ground>=MinZ*VoxelMeters-0.25*VoxelMeters
|
||||
&&Ground<=MaxZ*VoxelMeters+0.25*VoxelMeters
|
||||
&&Ground>=PlanMinZ-0.25*VoxelMeters&&Ground<=PlanMaxZ+0.25*VoxelMeters)
|
||||
Roots.push_back(Ground);
|
||||
Roots.push_back({Ground,Slope(XMinus.groundHeightSubvoxels,
|
||||
XPlus.groundHeightSubvoxels),Slope(YMinus.groundHeightSubvoxels,
|
||||
YPlus.groundHeightSubvoxels)});
|
||||
if(Cap>=MinZ*VoxelMeters-0.25*VoxelMeters
|
||||
&&Cap<=MaxZ*VoxelMeters+0.25*VoxelMeters
|
||||
&&Cap>=PlanMinZ-0.25*VoxelMeters&&Cap<=PlanMaxZ+0.25*VoxelMeters)
|
||||
Roots.push_back(Cap);
|
||||
Roots.push_back({Cap,Slope(XMinus.capHeightSubvoxels,
|
||||
XPlus.capHeightSubvoxels),Slope(YMinus.capHeightSubvoxels,
|
||||
YPlus.capHeightSubvoxels)});
|
||||
}
|
||||
std::sort(Roots.begin(),Roots.end());
|
||||
Roots.erase(std::unique(Roots.begin(),Roots.end(),[](double A,double B)
|
||||
{return std::abs(A-B)<0.01;}),Roots.end());
|
||||
std::sort(Roots.begin(),Roots.end(),[](const FSurfaceRoot& A,const FSurfaceRoot& B)
|
||||
{return A.ZMeters<B.ZMeters;});
|
||||
Roots.erase(std::unique(Roots.begin(),Roots.end(),[](const FSurfaceRoot& A,
|
||||
const FSurfaceRoot& B){return std::abs(A.ZMeters-B.ZMeters)<0.01;}),Roots.end());
|
||||
return Roots;
|
||||
}
|
||||
|
||||
@@ -213,75 +351,87 @@ namespace StandaloneScreenSpaceGate
|
||||
|
||||
inline FErrorRow CompareToExact(const FHeightGrid& MeshGrid,const FVoxelTileKey& Patch,
|
||||
double VoxelMeters,double PixelBudgetMeters,
|
||||
const VoxelForgeCore::WorldAssemblyManifest& Assembly)
|
||||
const VoxelForgeCore::WorldAssemblyManifest& Assembly,const FViewBasis& View)
|
||||
{
|
||||
FErrorRow Row;Row.Lod=Patch.Level;Row.Valid=true;
|
||||
std::vector<double> Errors;
|
||||
FErrorAccumulator Accumulator;
|
||||
for(int Iy=0;Iy<MeshGrid.Side;++Iy)
|
||||
for(int Ix=0;Ix<MeshGrid.Side;++Ix)
|
||||
{
|
||||
const auto Roots=ExactHeightRoots(MeshGrid,Ix,Iy,Patch,VoxelMeters,Assembly);
|
||||
if(Roots.empty()&&MeshGrid.At(Ix,Iy).empty())continue;
|
||||
std::vector<bool> Used(MeshGrid.At(Ix,Iy).size(),false);
|
||||
for(double Root:Roots)
|
||||
for(const FSurfaceRoot& Root:Roots)
|
||||
{
|
||||
std::size_t HitIndex=0;double ErrorMeters=0.0;
|
||||
if(!MatchNearest(MeshGrid.At(Ix,Iy),Used,Root,HitIndex,ErrorMeters))
|
||||
if(!MatchNearest(MeshGrid.At(Ix,Iy),Used,Root.ZMeters,HitIndex,ErrorMeters))
|
||||
{
|
||||
++Row.Missing;
|
||||
Errors.push_back(2.0);
|
||||
AddMissing(Accumulator);
|
||||
continue;
|
||||
}
|
||||
Used[HitIndex]=true;
|
||||
Errors.push_back(ErrorMeters/PixelBudgetMeters);
|
||||
AddError(Accumulator,MeshGrid.At(Ix,Iy)[HitIndex]-Root.ZMeters,
|
||||
Root,PixelBudgetMeters,View,MeshGrid.X(Ix),MeshGrid.Y(Iy));
|
||||
++Row.Samples;
|
||||
}
|
||||
// Heightfield2D declares ground and cap roots only. Other mesh intersections may
|
||||
// be valid tunnel geometry; the exact-mesh comparison checks extra pieces.
|
||||
}
|
||||
Row.P50=Percentile(Errors,0.50);Row.P99=Percentile(Errors,0.99);
|
||||
Row.Maximum=Errors.empty()?0.0:*std::max_element(Errors.begin(),Errors.end());
|
||||
Row.Samples=Errors.size();
|
||||
FinalizeError(Row,Accumulator);
|
||||
return Row;
|
||||
}
|
||||
|
||||
inline FErrorRow CompareToReference(const FHeightGrid& MeshGrid,
|
||||
const FHeightGrid& ReferenceGrid,int Lod,double PixelBudgetMeters)
|
||||
const FHeightGrid& ReferenceGrid,const FVoxelTileKey& Patch,double VoxelMeters,
|
||||
double PixelBudgetMeters,const VoxelForgeCore::WorldAssemblyManifest& Assembly,
|
||||
const FViewBasis& View)
|
||||
{
|
||||
FErrorRow Row;Row.Lod=Lod;Row.Valid=true;
|
||||
std::vector<double> Errors;
|
||||
FErrorRow Row;Row.Lod=Patch.Level;Row.Valid=true;
|
||||
FErrorAccumulator Accumulator;
|
||||
for(int Iy=0;Iy<ReferenceGrid.Side;++Iy)
|
||||
for(int Ix=0;Ix<ReferenceGrid.Side;++Ix)
|
||||
{
|
||||
const auto& Reference=ReferenceGrid.At(Ix,Iy);
|
||||
const auto& Mesh=MeshGrid.At(Ix,Iy);
|
||||
if(Reference.empty()&&Mesh.empty())continue;
|
||||
std::vector<bool> Used(Mesh.size(),false);
|
||||
for(double ReferenceZ:Reference)
|
||||
const auto Roots=ExactHeightRoots(ReferenceGrid,Ix,Iy,Patch,VoxelMeters,Assembly);
|
||||
std::vector<bool> UsedReference(Reference.size(),false),UsedMesh(Mesh.size(),false);
|
||||
for(std::size_t ReferenceIndex=0;ReferenceIndex<Reference.size();++ReferenceIndex)
|
||||
{
|
||||
std::size_t HitIndex=0;double ErrorMeters=0.0;
|
||||
if(!MatchNearest(Mesh,Used,ReferenceZ,HitIndex,ErrorMeters))
|
||||
const double ReferenceZ=Reference[ReferenceIndex];
|
||||
UsedReference[ReferenceIndex]=true;
|
||||
std::size_t MeshIndex=0;double MeshError=0.0;
|
||||
if(!MatchNearest(Mesh,UsedMesh,ReferenceZ,MeshIndex,MeshError))
|
||||
{
|
||||
++Row.Missing;Errors.push_back(2.0);continue;
|
||||
++Row.Missing;AddMissing(Accumulator);continue;
|
||||
}
|
||||
Used[HitIndex]=true;
|
||||
Errors.push_back(ErrorMeters/PixelBudgetMeters);
|
||||
UsedMesh[MeshIndex]=true;
|
||||
FSurfaceRoot Root{ReferenceZ,0.0,0.0};
|
||||
if(!Roots.empty())
|
||||
{
|
||||
const auto BestRoot=std::min_element(Roots.begin(),Roots.end(),
|
||||
[ReferenceZ](const FSurfaceRoot& A,const FSurfaceRoot& B)
|
||||
{return std::abs(A.ZMeters-ReferenceZ)<std::abs(B.ZMeters-ReferenceZ);});
|
||||
if(BestRoot!=Roots.end()&&std::abs(BestRoot->ZMeters-ReferenceZ)<1.0)
|
||||
Root=*BestRoot;
|
||||
}
|
||||
AddError(Accumulator,Mesh[MeshIndex]-Reference[ReferenceIndex],
|
||||
Root,PixelBudgetMeters,View,ReferenceGrid.X(Ix),ReferenceGrid.Y(Iy));
|
||||
}
|
||||
for(std::size_t I=0;I<Mesh.size();++I)
|
||||
if(!Used[I]){++Row.Floating;Errors.push_back(2.0);}
|
||||
if(!UsedMesh[I]){++Row.Floating;AddMissing(Accumulator);}
|
||||
}
|
||||
Row.P50=Percentile(Errors,0.50);Row.P99=Percentile(Errors,0.99);
|
||||
Row.Maximum=Errors.empty()?0.0:*std::max_element(Errors.begin(),Errors.end());
|
||||
Row.Samples=Errors.size();
|
||||
FinalizeError(Row,Accumulator);
|
||||
return Row;
|
||||
}
|
||||
|
||||
inline FPopRow ComparePop(const FHeightGrid& Coarse,const FHeightGrid& Fine,
|
||||
const FVoxelTileKey& Patch,double VoxelMeters,double PixelBudgetMeters,
|
||||
const VoxelForgeCore::WorldAssemblyManifest& Assembly)
|
||||
const VoxelForgeCore::WorldAssemblyManifest& Assembly,const FViewBasis& View)
|
||||
{
|
||||
FPopRow Row;Row.CoarseLod=Patch.Level;Row.Valid=true;
|
||||
std::vector<double> Errors;
|
||||
FErrorAccumulator Accumulator;
|
||||
for(int Iy=0;Iy<Coarse.Side;++Iy)
|
||||
for(int Ix=0;Ix<Coarse.Side;++Ix)
|
||||
{
|
||||
@@ -291,25 +441,34 @@ namespace StandaloneScreenSpaceGate
|
||||
if(Roots.empty()&&CoarseHits.empty()&&FineHits.empty())continue;
|
||||
std::vector<bool> CoarseUsed(CoarseHits.size(),false);
|
||||
std::vector<bool> FineUsed(FineHits.size(),false);
|
||||
for(double Root:Roots)
|
||||
for(const FSurfaceRoot& Root:Roots)
|
||||
{
|
||||
std::size_t CoarseIndex=0,FineIndex=0;
|
||||
double CoarseError=0.0,FineError=0.0;
|
||||
if(!MatchNearest(CoarseHits,CoarseUsed,Root,CoarseIndex,CoarseError)
|
||||
||!MatchNearest(FineHits,FineUsed,Root,FineIndex,FineError))
|
||||
if(!MatchNearest(CoarseHits,CoarseUsed,Root.ZMeters,CoarseIndex,CoarseError)
|
||||
||!MatchNearest(FineHits,FineUsed,Root.ZMeters,FineIndex,FineError))
|
||||
{
|
||||
++Row.Cracks;Errors.push_back(2.0);continue;
|
||||
++Row.Cracks;AddMissing(Accumulator);continue;
|
||||
}
|
||||
CoarseUsed[CoarseIndex]=true;
|
||||
FineUsed[FineIndex]=true;
|
||||
Errors.push_back(std::abs(CoarseHits[CoarseIndex]-FineHits[FineIndex])
|
||||
/PixelBudgetMeters);
|
||||
AddError(Accumulator,CoarseHits[CoarseIndex]-FineHits[FineIndex],
|
||||
Root,PixelBudgetMeters,View,Coarse.X(Ix),Coarse.Y(Iy));
|
||||
++Row.Samples;
|
||||
}
|
||||
}
|
||||
Row.P50=Percentile(Errors,0.50);Row.P99=Percentile(Errors,0.99);
|
||||
Row.Maximum=Errors.empty()?0.0:*std::max_element(Errors.begin(),Errors.end());
|
||||
Row.Samples=Errors.size();
|
||||
FErrorRow Stats;Stats.Lod=Patch.Level;Stats.Valid=true;
|
||||
FinalizeError(Stats,Accumulator);
|
||||
Row.Samples=Stats.Samples;Row.P50=Stats.P50;Row.P99=Stats.P99;Row.Maximum=Stats.Maximum;
|
||||
Row.VerticalP50=Stats.VerticalP50;Row.VerticalP99=Stats.VerticalP99;
|
||||
Row.VerticalMaximum=Stats.VerticalMaximum;
|
||||
Row.MaximumNormalDistanceMeters=Stats.MaximumNormalDistanceMeters;
|
||||
Row.MaximumX=Stats.MaximumX;Row.MaximumY=Stats.MaximumY;
|
||||
Row.MaximumReferenceZ=Stats.MaximumReferenceZ;
|
||||
Row.MaximumDisplayedZ=Stats.MaximumDisplayedZ;
|
||||
Row.MaximumSlopeDegrees=Stats.MaximumSlopeDegrees;
|
||||
Row.SlopeSamples=Stats.SlopeSamples;Row.SlopeP99=Stats.SlopeP99;
|
||||
Row.SlopeMaximum=Stats.SlopeMaximum;
|
||||
return Row;
|
||||
}
|
||||
|
||||
@@ -352,19 +511,52 @@ namespace StandaloneScreenSpaceGate
|
||||
return Distances;
|
||||
}
|
||||
|
||||
inline void PrintSlopeTail(const char* Kind,int Lod,const std::array<std::uint64_t,4>& Counts,
|
||||
const std::array<double,4>& P99,const std::array<double,4>& Maximum)
|
||||
{
|
||||
constexpr const char* Labels[4]={"lt30","30to45","45to60","ge60"};
|
||||
for(std::size_t Bin=0;Bin<Counts.size();++Bin)
|
||||
std::printf("[ScreenSpaceSlopeTail] kind=%s lod=%d slope_deg=%s samples=%llu p99_px=%.4f max_px=%.4f\n",
|
||||
Kind,Lod,Labels[Bin],static_cast<unsigned long long>(Counts[Bin]),P99[Bin],Maximum[Bin]);
|
||||
}
|
||||
|
||||
inline void PrintKnownFailureControl(double CellMeters,double DisplayDistanceMeters,
|
||||
const FViewBasis& View)
|
||||
{
|
||||
constexpr double Slope=100.0;
|
||||
const double LateralMeters=CellMeters/200.0;
|
||||
const double VerticalMeters=Slope*LateralMeters;
|
||||
const double NormalLength=std::sqrt(1.0+Slope*Slope);
|
||||
const double NormalDistance=VerticalMeters/NormalLength;
|
||||
const std::array<double,3> Normal={-Slope/NormalLength,0.0,1.0/NormalLength};
|
||||
const double ProjectedNormal=std::hypot(
|
||||
Normal[0]*View.Right[0]+Normal[1]*View.Right[1]+Normal[2]*View.Right[2],
|
||||
Normal[0]*View.Up[0]+Normal[1]*View.Up[1]+Normal[2]*View.Up[2]);
|
||||
const double PixelBudgetMeters=DisplayDistanceMeters*OnePixelRadians;
|
||||
const double VerticalPixels=VerticalMeters/PixelBudgetMeters;
|
||||
const double NormalPixels=NormalDistance*ProjectedNormal/PixelBudgetMeters;
|
||||
const bool Passed=VerticalPixels>4.0&&NormalPixels<1.0;
|
||||
std::printf("[ScreenSpaceKnownFailureControl] case=steep-plane-lateral-offset slope_deg=%.4f cell_m=%.6f lateral_m=%.6f cell_fraction=%.6f vertical_error_m=%.6f vertical_height_px=%.4f closest_normal_m=%.6f projected_normal_px=%.4f status=%s\n",
|
||||
std::atan(Slope)*180.0/3.14159265358979323846,CellMeters,LateralMeters,
|
||||
LateralMeters/CellMeters,VerticalMeters,VerticalPixels,NormalDistance,NormalPixels,
|
||||
Passed?"EXPECTED_VERTICAL_FALSE_POSITIVE":"CONTROL_FAILED");
|
||||
}
|
||||
|
||||
inline bool Run(const std::array<std::optional<FStandaloneCpuTile>,9>& SampleTiles,
|
||||
FStandaloneStreamingGenerator& Streamer,
|
||||
const VoxelForgeCore::WorldAssemblyManifest& Assembly,
|
||||
const std::array<double,9>& Distances,std::uint32_t VoxelSizeMicrons,int MaxLod,
|
||||
const std::array<std::int64_t,9>& PixelBudgetsSubvoxels)
|
||||
const std::array<std::int64_t,9>& PixelBudgetsSubvoxels,
|
||||
double CameraYawDegrees,double CameraPitchDegrees)
|
||||
{
|
||||
const double VoxelMeters=static_cast<double>(VoxelSizeMicrons)/1000000.0;
|
||||
const FViewBasis View=MakeViewBasis(CameraYawDegrees,CameraPitchDegrees);
|
||||
std::array<double,9> Budgets{};
|
||||
std::array<FErrorRow,9> ErrorRows{};
|
||||
std::array<FPopRow,9> PopRows{};
|
||||
bool GatePass=true;
|
||||
std::printf("[ScreenSpaceGate] angular_pixel=%.9f rad (90deg/2560) lod>=3_limit=1.0px sampling=65x65 skirts=excluded metric=vertical_height_error field_reference=direct-Heightfield2D-roots\n",
|
||||
OnePixelRadians);
|
||||
std::printf("[ScreenSpaceGate] angular_pixel=%.9f rad (horizontal_fov=90deg,width=2560) lod>=3_limit=1.0px sampling=65x65 skirts=excluded metric=local_surface_normal_projected field_reference=direct-Heightfield2D-roots normal_probe_vox=1 view_yaw_deg=%.3f view_pitch_deg=%.3f\n",
|
||||
OnePixelRadians,CameraYawDegrees,CameraPitchDegrees);
|
||||
for(int Lod=0;Lod<=MaxLod&&Lod<9;++Lod)
|
||||
{
|
||||
Budgets[Lod]=Distances[Lod]*OnePixelRadians;
|
||||
@@ -372,6 +564,8 @@ namespace StandaloneScreenSpaceGate
|
||||
Lod,Distances[Lod],Budgets[Lod],Budgets[Lod]*100.0,
|
||||
static_cast<long long>(PixelBudgetsSubvoxels[Lod]),Lod<3?"exact-2cm":"1px");
|
||||
}
|
||||
const double LOD3CellMeters=static_cast<double>(CHUNK_SIZE*(1<<3))*VoxelMeters/64.0;
|
||||
PrintKnownFailureControl(LOD3CellMeters,Distances[3],View);
|
||||
VoxelForgeCore::Heightfield2D::resetThreadColumnCacheStats(false,true,0);
|
||||
for(int Lod=3;Lod<=MaxLod&&Lod<9;++Lod)
|
||||
{
|
||||
@@ -389,32 +583,43 @@ namespace StandaloneScreenSpaceGate
|
||||
const FStandaloneCpuTile* ExactPointer=&ExactTile;
|
||||
const std::span<const FStandaloneCpuTile* const> ExactSpan(&ExactPointer,1);
|
||||
const FHeightGrid ExactGrid=Rasterize(ExactSpan,ExactTile.Key,VoxelMeters);
|
||||
ErrorRows[Lod]=CompareToReference(CoarseGrid,ExactGrid,Lod,Budgets[Lod]);
|
||||
ErrorRows[Lod]=CompareToReference(CoarseGrid,ExactGrid,CoarseTile.Key,VoxelMeters,
|
||||
Budgets[Lod],Assembly,View);
|
||||
const FErrorRow& Error=ErrorRows[Lod];
|
||||
const bool LodPass=Error.Valid&&Error.Missing==0&&Error.Floating==0
|
||||
&&Error.Samples>=16&&Error.Maximum<=1.0;
|
||||
GatePass=GatePass&&LodPass;
|
||||
std::printf("[ScreenSpaceGateLOD] lod=%d key=(%d,%d,%d) samples=%llu p50_px=%.4f p99_px=%.4f max_px=%.4f missing=%llu floating=%llu status=%s\n",
|
||||
std::printf("[ScreenSpaceGateLOD] lod=%d key=(%d,%d,%d) samples=%llu normal_px_p50=%.4f p99=%.4f max=%.4f vertical_px_p50=%.4f p99=%.4f max=%.4f max_normal_m=%.6f max_xy_m=(%.3f,%.3f) max_slope_deg=%.2f missing=%llu floating=%llu status=%s\n",
|
||||
Lod,CoarseTile.Key.Coord.X,CoarseTile.Key.Coord.Y,CoarseTile.Key.Coord.Z,
|
||||
static_cast<unsigned long long>(Error.Samples),Error.P50,Error.P99,Error.Maximum,
|
||||
Error.VerticalP50,Error.VerticalP99,Error.VerticalMaximum,
|
||||
Error.MaximumNormalDistanceMeters,Error.MaximumX,Error.MaximumY,
|
||||
Error.MaximumSlopeDegrees,
|
||||
static_cast<unsigned long long>(Error.Missing),static_cast<unsigned long long>(Error.Floating),
|
||||
LodPass?"PASS":"REJECT");
|
||||
PrintSlopeTail("same_lod",Lod,Error.SlopeSamples,Error.SlopeP99,Error.SlopeMaximum);
|
||||
|
||||
const FErrorRow FieldError=CompareToExact(CoarseGrid,CoarseTile.Key,VoxelMeters,
|
||||
Budgets[Lod],Assembly);
|
||||
Budgets[Lod],Assembly,View);
|
||||
const bool FieldPass=FieldError.Valid&&FieldError.Missing==0&&FieldError.Floating==0
|
||||
&&FieldError.Samples>=16&&FieldError.Maximum<=1.0;
|
||||
GatePass=GatePass&&FieldPass;
|
||||
std::printf("[ScreenSpaceGateFieldLOD] lod=%d samples=%llu p50_px=%.4f p99_px=%.4f max_px=%.4f missing=%llu floating=%llu status=%s\n",
|
||||
std::printf("[ScreenSpaceGateFieldLOD] lod=%d samples=%llu normal_px_p50=%.4f p99=%.4f max=%.4f vertical_px_p50=%.4f p99=%.4f max=%.4f max_normal_m=%.6f max_xyz_m=(%.3f,%.3f,%.3f) max_slope_deg=%.2f missing=%llu floating=%llu status=%s\n",
|
||||
Lod,static_cast<unsigned long long>(FieldError.Samples),FieldError.P50,
|
||||
FieldError.P99,FieldError.Maximum,static_cast<unsigned long long>(FieldError.Missing),
|
||||
FieldError.P99,FieldError.Maximum,FieldError.VerticalP50,FieldError.VerticalP99,
|
||||
FieldError.VerticalMaximum,FieldError.MaximumNormalDistanceMeters,
|
||||
FieldError.MaximumX,FieldError.MaximumY,FieldError.MaximumDisplayedZ,
|
||||
FieldError.MaximumSlopeDegrees,
|
||||
static_cast<unsigned long long>(FieldError.Missing),
|
||||
static_cast<unsigned long long>(FieldError.Floating),FieldPass?"PASS":"REJECT");
|
||||
PrintSlopeTail("field",Lod,FieldError.SlopeSamples,FieldError.SlopeP99,
|
||||
FieldError.SlopeMaximum);
|
||||
|
||||
bool FaultControlsPass=true;
|
||||
const FHeightGrid MissingGrid(ExactGrid.Side,ExactGrid.MinimumX,ExactGrid.MaximumX,
|
||||
ExactGrid.MinimumY,ExactGrid.MaximumY);
|
||||
const FErrorRow MissingError=CompareToReference(MissingGrid,ExactGrid,Lod,
|
||||
Budgets[Lod]);
|
||||
const FErrorRow MissingError=CompareToReference(MissingGrid,ExactGrid,
|
||||
CoarseTile.Key,VoxelMeters,Budgets[Lod],Assembly,View);
|
||||
const bool MissingRejected=MissingError.Missing>0&&MissingError.Maximum>1.0;
|
||||
FaultControlsPass=FaultControlsPass&&MissingRejected;
|
||||
std::printf("[ScreenSpaceFaultInjection] lod=%d case=missing-displayed-surface missing=%llu status=%s\n",
|
||||
@@ -435,8 +640,8 @@ namespace StandaloneScreenSpaceGate
|
||||
const FStandaloneCpuTile* FloatingPointer=&FloatingTile;
|
||||
const std::span<const FStandaloneCpuTile* const> FloatingSpan(&FloatingPointer,1);
|
||||
const FHeightGrid FloatingGrid=Rasterize(FloatingSpan,CoarseTile.Key,VoxelMeters);
|
||||
const FErrorRow FloatingError=CompareToReference(FloatingGrid,ExactGrid,Lod,
|
||||
Budgets[Lod]);
|
||||
const FErrorRow FloatingError=CompareToReference(FloatingGrid,ExactGrid,
|
||||
CoarseTile.Key,VoxelMeters,Budgets[Lod],Assembly,View);
|
||||
const bool FloatingRejected=FloatingError.Floating>0||FloatingError.Maximum>1.0;
|
||||
FaultControlsPass=FaultControlsPass&&FloatingRejected;
|
||||
std::printf("[ScreenSpaceFaultInjection] lod=%d case=2m-floating-surface floating=%llu max_px=%.4f status=%s\n",
|
||||
@@ -461,20 +666,24 @@ namespace StandaloneScreenSpaceGate
|
||||
FinePointers.data(),FinePointers.size());
|
||||
const FHeightGrid FineGrid=Rasterize(FineSpan,CoarseTile.Key,VoxelMeters);
|
||||
PopRows[Lod]=ComparePop(CoarseGrid,FineGrid,CoarseTile.Key,VoxelMeters,
|
||||
Budgets[Lod],Assembly);
|
||||
Budgets[Lod],Assembly,View);
|
||||
const FPopRow& Pop=PopRows[Lod];
|
||||
const bool PopPass=Pop.Valid&&Pop.Cracks==0&&Pop.Floating==0&&Pop.Maximum<=1.0;
|
||||
GatePass=GatePass&&PopPass;
|
||||
std::printf("[ScreenSpaceSwap] lod=%d_to_%d samples=%llu p50_px=%.4f p99_px=%.4f max_px=%.4f cracks=%llu floating=%llu status=%s\n",
|
||||
std::printf("[ScreenSpaceSwap] lod=%d_to_%d samples=%llu normal_px_p50=%.4f p99=%.4f max=%.4f vertical_px_p50=%.4f p99=%.4f max=%.4f max_normal_m=%.6f max_xyz_m=(%.3f,%.3f,%.3f) max_slope_deg=%.2f cracks=%llu floating=%llu status=%s\n",
|
||||
Lod,Lod-1,static_cast<unsigned long long>(Pop.Samples),
|
||||
Pop.P50,Pop.P99,Pop.Maximum,static_cast<unsigned long long>(Pop.Cracks),
|
||||
Pop.P50,Pop.P99,Pop.Maximum,Pop.VerticalP50,Pop.VerticalP99,Pop.VerticalMaximum,
|
||||
Pop.MaximumNormalDistanceMeters,Pop.MaximumX,Pop.MaximumY,
|
||||
Pop.MaximumDisplayedZ,
|
||||
Pop.MaximumSlopeDegrees,static_cast<unsigned long long>(Pop.Cracks),
|
||||
static_cast<unsigned long long>(Pop.Floating),
|
||||
PopPass?"PASS":"REJECT");
|
||||
PrintSlopeTail("swap",Lod,Pop.SlopeSamples,Pop.SlopeP99,Pop.SlopeMaximum);
|
||||
|
||||
const FHeightGrid MissingFineGrid(CoarseGrid.Side,CoarseGrid.MinimumX,
|
||||
CoarseGrid.MaximumX,CoarseGrid.MinimumY,CoarseGrid.MaximumY);
|
||||
const FPopRow MissingFineError=ComparePop(CoarseGrid,MissingFineGrid,
|
||||
CoarseTile.Key,VoxelMeters,Budgets[Lod],Assembly);
|
||||
CoarseTile.Key,VoxelMeters,Budgets[Lod],Assembly,View);
|
||||
const bool CrackRejected=MissingFineError.Cracks>0&&MissingFineError.Maximum>1.0;
|
||||
FaultControlsPass=FaultControlsPass&&CrackRejected;
|
||||
GatePass=GatePass&&FaultControlsPass;
|
||||
@@ -490,8 +699,8 @@ namespace StandaloneScreenSpaceGate
|
||||
const FStandaloneCpuTile* BadPointer=&Bad;
|
||||
const std::span<const FStandaloneCpuTile* const> BadSpan(&BadPointer,1);
|
||||
const FHeightGrid BadGrid=Rasterize(BadSpan,Bad.Key,VoxelMeters);
|
||||
const FErrorRow BadError=CompareToReference(BadGrid,ExactGrid,Lod,
|
||||
Budgets[Lod]);
|
||||
const FErrorRow BadError=CompareToReference(BadGrid,ExactGrid,
|
||||
CoarseTile.Key,VoxelMeters,Budgets[Lod],Assembly,View);
|
||||
const bool Rejected=BadError.Missing>0||BadError.Floating>0
|
||||
||BadError.Samples<16||BadError.Maximum>1.0;
|
||||
NegativeControlsPass=NegativeControlsPass&&Rejected;
|
||||
|
||||
@@ -38,7 +38,6 @@ namespace
|
||||
bool ForceGenerate=false;
|
||||
bool MeshCoverage=false;
|
||||
bool ScreenSpaceGate=false;
|
||||
bool VisualDetailCutoff=false;
|
||||
bool HasSeedOverride=false;
|
||||
int SeedOverride=0;
|
||||
bool HasRenderDistanceOverride=false;
|
||||
@@ -98,7 +97,6 @@ namespace
|
||||
" --auto-surface-camera (place the 27-tile cube on this seed's surface)\n"
|
||||
" --mesh-coverage (report non-empty tiles and indexed triangles)\n"
|
||||
" --screen-space-gate (measure display error and historical negative controls)\n"
|
||||
" --visual-detail-cutoff (enable the declared far visual octave cutoff)\n"
|
||||
" --render-distance-chunks=N (0 disables the extra far ring)\n"
|
||||
" --tls-probe=N (Linux worker TLS/stack measurement; N=0..12)\n"
|
||||
" --tls-probe-touch=none|surface-world|floating-islands (first-use probe)\n"
|
||||
@@ -114,7 +112,6 @@ namespace
|
||||
else if(arg=="--force-generate")options.ForceGenerate=true;
|
||||
else if(arg=="--mesh-coverage")options.MeshCoverage=true;
|
||||
else if(arg=="--screen-space-gate")options.ScreenSpaceGate=true;
|
||||
else if(arg=="--visual-detail-cutoff")options.VisualDetailCutoff=true;
|
||||
else if(arg=="--auto-surface-camera")options.AutoSurfaceCamera=true;
|
||||
else if(arg.starts_with("--recipe="))
|
||||
{
|
||||
@@ -317,5 +314,5 @@ int main(int argc,char** argv)
|
||||
scheduler,options.StreamHash,false,false,false,false,coldStart,
|
||||
options.ProfileColumnOverlap,options.FarHorizonLOD8InteriorStepVoxels,
|
||||
options.MeshCoverage,options.MeshExportPath,options.CameraYawDegrees,
|
||||
options.CameraPitchDegrees,options.ScreenSpaceGate,options.VisualDetailCutoff);
|
||||
options.CameraPitchDegrees,options.ScreenSpaceGate);
|
||||
}
|
||||
@@ -137,22 +137,17 @@ namespace StandaloneStreamBenchmark
|
||||
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 bApplyVisualDetailCutoff=false)
|
||||
float CameraPitchDegrees=0.0f,bool bScreenSpaceGate=false)
|
||||
{
|
||||
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{};
|
||||
std::array<std::int64_t,9> AppliedPixelBudgetsSubvoxels{};
|
||||
if(bScreenSpaceGate||bApplyVisualDetailCutoff)
|
||||
if(bScreenSpaceGate)
|
||||
{
|
||||
ClosestDisplayDistances=bScreenSpaceGate
|
||||
?StandaloneScreenSpaceGate::ClosestDisplayedDistancesMeters(
|
||||
World,CameraPositionVoxels,World.CoreVoxelSizeMicrons)
|
||||
:StandaloneScreenSpaceGate::ClipmapClosestDisplayedDistancesMeters(
|
||||
World,World.CoreVoxelSizeMicrons);
|
||||
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(
|
||||
@@ -160,67 +155,6 @@ namespace StandaloneStreamBenchmark
|
||||
*StandaloneScreenSpaceGate::OnePixelRadians/VoxelMeters
|
||||
*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel));
|
||||
}
|
||||
AppliedPixelBudgetsSubvoxels=PixelBudgetsSubvoxels;
|
||||
if(Assembly&&(bScreenSpaceGate||bApplyVisualDetailCutoff))
|
||||
{
|
||||
for(int32 Lod=3;Lod<=World.MaxClipLevel&&Lod<9;++Lod)
|
||||
{
|
||||
const std::int32_t Cells=FMath::Max(1,World.CoarseTileCells);
|
||||
const std::int32_t Step=FMath::Max(1,(CHUNK_SIZE<<Lod)/Cells);
|
||||
VoxelForgeCore::VisualDetailCutoffV1 Probe;
|
||||
Probe.pixelBudgetSubvoxels=PixelBudgetsSubvoxels[static_cast<std::size_t>(Lod)];
|
||||
Probe.sampleStepSubvoxels=static_cast<std::int64_t>(Step)
|
||||
*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
|
||||
std::uint64_t TermCount=0,EligibleTermCount=0,PotentialSkippedOctaves=0;
|
||||
for(const auto& Plan:Assembly->plan.strates)
|
||||
{
|
||||
const VoxelForgeCore::Heightfield2D Field(Assembly->recipe,Plan);
|
||||
for(const auto& Term:Field.visualDetailContract().terms)
|
||||
{
|
||||
++TermCount;
|
||||
const auto Result=VoxelForgeCore::FindVisualDetailTailCutoffV1(Term,Probe);
|
||||
if(Result.firstSkippedOctave==0)continue;
|
||||
++EligibleTermCount;
|
||||
PotentialSkippedOctaves+=Term.octaveCount-Result.firstSkippedOctave;
|
||||
}
|
||||
}
|
||||
std::printf("[WorldAssemblerVisualDetailCutoffPlan] lod=%d terms=%llu eligible_terms=%llu potential_skipped_octaves=%llu per_call_budget_divisor=%u sample_step_vox=%d status=%s\n",
|
||||
Lod,static_cast<unsigned long long>(TermCount),
|
||||
static_cast<unsigned long long>(EligibleTermCount),
|
||||
static_cast<unsigned long long>(PotentialSkippedOctaves),
|
||||
Probe.callBudgetDivisor,Step,
|
||||
EligibleTermCount==0?"exact-no-eligible-tail":"candidate");
|
||||
if(bApplyVisualDetailCutoff&&EligibleTermCount==0)
|
||||
AppliedPixelBudgetsSubvoxels[static_cast<std::size_t>(Lod)]=0;
|
||||
}
|
||||
}
|
||||
if(bScreenSpaceGate&&Assembly)
|
||||
{
|
||||
for(const auto& Plan:Assembly->plan.strates)
|
||||
{
|
||||
const VoxelForgeCore::Heightfield2D Field(Assembly->recipe,Plan);
|
||||
for(const auto& Term:Field.visualDetailContract().terms)
|
||||
for(std::uint32_t Octave=0;Octave<Term.octaveCount;++Octave)
|
||||
{
|
||||
const std::int64_t Wavelength=Term.baseWavelengthSubvoxels
|
||||
/static_cast<std::int64_t>(1ull<<Octave);
|
||||
const long double Amplitude=static_cast<long double>(Term.maximumDisplacementSubvoxels)
|
||||
*Term.maximumNoiseAbsQ16/65536.0L
|
||||
/static_cast<long double>(1ull<<Octave);
|
||||
std::printf("[WorldAssemblerVisualDetailTerm] stratum=%d id=%llu octave=%u wavelength_vox=%.3f max_abs_surface_vox=%.3f declared=1\n",
|
||||
Plan.strateIndex,static_cast<unsigned long long>(Term.stableId),Octave,
|
||||
static_cast<double>(Wavelength)/256.0,static_cast<double>(Amplitude));
|
||||
}
|
||||
}
|
||||
for(const auto& Island:Assembly->floatingIslands)
|
||||
{
|
||||
const VoxelForgeCore::FloatingIslandFieldV1 Field(
|
||||
Island.manifest.recipe,Island.manifest.plan);
|
||||
const auto Contract=Field.visualDetailContract();
|
||||
std::printf("[WorldAssemblerVisualDetailContract] recipe=floating-islands strate=%d declared_terms=%zu exact_path=%d\n",
|
||||
Island.strateIndex,Contract.terms.size(),Contract.terms.empty()?1:0);
|
||||
}
|
||||
}
|
||||
const bool bRoundTunnels=World.Generator->bStandaloneResearchRoundTunnels;
|
||||
const bool bEnhancedTunnelWalls=World.Generator->bStandaloneResearchEnhancedWalls;
|
||||
VoxelForgeCore::World Shape(CoreSnapshot);
|
||||
@@ -309,12 +243,7 @@ namespace StandaloneStreamBenchmark
|
||||
FStandaloneStreamingGenerator Streamer(World,WorkerCount,bHashDensity,nullptr,MaxLevel,
|
||||
bSkipUvAndColors,bSkipMaterialGroups,bSkipUV,bSkipColors,&Shape,bProfileColumnOverlap,
|
||||
FarHorizonLOD8InteriorStepVoxels);
|
||||
if(bScreenSpaceGate||bApplyVisualDetailCutoff)
|
||||
{
|
||||
Streamer.SetVisualDetailCutoffBudgets(AppliedPixelBudgetsSubvoxels,
|
||||
bApplyVisualDetailCutoff);
|
||||
Streamer.SetVisualDetailProfilingEnabled(bScreenSpaceGate);
|
||||
}
|
||||
if(bScreenSpaceGate)Streamer.SetMesherStageProfilingEnabled(true);
|
||||
const auto StreamStart=Clock::now();
|
||||
const FStandaloneStreamingUpdate Update=Streamer.StartWithDesired(Desired);
|
||||
if(Update.DesiredTiles!=static_cast<int32>(Desired.Tiles.size()))
|
||||
@@ -370,14 +299,11 @@ namespace StandaloneStreamBenchmark
|
||||
std::array<bool,9> ColumnCaptureCompleteByLOD{};
|
||||
ColumnCaptureCompleteByLOD.fill(true);
|
||||
std::array<double,9> WorkerSecondsByLOD{};
|
||||
std::array<std::array<std::uint64_t,8>,9> DetailOctaveEvaluationsByLOD{};
|
||||
std::array<std::array<std::uint64_t,8>,9> DetailOctaveEligibleByLOD{};
|
||||
std::array<std::array<std::uint64_t,8>,9> DetailOctaveSkipsByLOD{};
|
||||
std::array<std::array<double,8>,9> DetailOctaveMillisecondsByLOD{};
|
||||
std::array<std::array<double,8>,9> DetailOctaveEligibleMillisecondsByLOD{};
|
||||
std::array<double,9> ColumnEvalMsByLOD{},MarchingExclusiveMsByLOD{};
|
||||
std::array<double,9> GroundRefineExclusiveMsByLOD{},CapRefineExclusiveMsByLOD{};
|
||||
std::array<double,9> MeshAssemblyMsByLOD{};
|
||||
std::array<double,9> SkirtMsByLOD{};
|
||||
std::array<std::uint64_t,9> SkirtTrianglesByLOD{};
|
||||
std::array<std::uint64_t,9> TileCountByLOD{};
|
||||
for(const FVoxelTileKey& Tile:Desired.Tiles)
|
||||
if(Tile.Level>=0&&Tile.Level<static_cast<int32>(ExpectedTilesByLOD.size()))
|
||||
@@ -425,19 +351,8 @@ namespace StandaloneStreamBenchmark
|
||||
GroundRefineExclusiveMsByLOD[LOD]+=Tile.CoreDiagnostics.groundRefinementExclusiveMilliseconds;
|
||||
CapRefineExclusiveMsByLOD[LOD]+=Tile.CoreDiagnostics.capRefinementExclusiveMilliseconds;
|
||||
MeshAssemblyMsByLOD[LOD]+=Tile.CoreDiagnostics.meshAssemblyMilliseconds;
|
||||
for(std::size_t Octave=0;Octave<8;++Octave)
|
||||
{
|
||||
DetailOctaveEvaluationsByLOD[LOD][Octave]+=
|
||||
Tile.CoreDiagnostics.visualDetailOctaveEvaluations[Octave];
|
||||
DetailOctaveEligibleByLOD[LOD][Octave]+=
|
||||
Tile.CoreDiagnostics.visualDetailOctaveBudgetEligible[Octave];
|
||||
DetailOctaveSkipsByLOD[LOD][Octave]+=
|
||||
Tile.CoreDiagnostics.visualDetailOctaveSkips[Octave];
|
||||
DetailOctaveMillisecondsByLOD[LOD][Octave]+=
|
||||
Tile.CoreDiagnostics.visualDetailOctaveMilliseconds[Octave];
|
||||
DetailOctaveEligibleMillisecondsByLOD[LOD][Octave]+=
|
||||
Tile.CoreDiagnostics.visualDetailOctaveEligibleMilliseconds[Octave];
|
||||
}
|
||||
SkirtMsByLOD[LOD]+=Tile.CoreDiagnostics.marchingCubesSkirtMilliseconds;
|
||||
SkirtTrianglesByLOD[LOD]+=Tile.SkirtTriangleCount;
|
||||
if(bScreenSpaceGate&&Assembly&&Tile.Key.Level>=3&&Tile.Key.Level<9
|
||||
&&!Tile.Vertices.empty()
|
||||
&&StandaloneScreenSpaceGate::TileHasNativeSurface(Tile.Key,*Assembly,
|
||||
@@ -541,7 +456,8 @@ namespace StandaloneStreamBenchmark
|
||||
if(Assembly)
|
||||
bScreenSpaceGatePassing=StandaloneScreenSpaceGate::Run(GateSampleTiles,
|
||||
Streamer,*Assembly,ClosestDisplayDistances,World.CoreVoxelSizeMicrons,
|
||||
World.MaxClipLevel,PixelBudgetsSubvoxels);
|
||||
World.MaxClipLevel,PixelBudgetsSubvoxels,CameraYawDegrees,
|
||||
CameraPitchDegrees);
|
||||
else
|
||||
{std::fprintf(stderr,"[ScreenSpaceGateResult] no-world-assembly status=REJECT\n");bScreenSpaceGatePassing=false;}
|
||||
}
|
||||
@@ -576,19 +492,12 @@ namespace StandaloneStreamBenchmark
|
||||
}
|
||||
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 mesh_assembly_ms=%.3f\n",
|
||||
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],MeshAssemblyMsByLOD[LOD]);
|
||||
if(bScreenSpaceGate)
|
||||
for(std::size_t Octave=0;Octave<8;++Octave)
|
||||
std::printf("[WorldAssemblerLODDetailOctave] lod=%zu octave=%zu evaluated=%llu budget_eligible=%llu skipped=%llu eval_ms=%.6f eligible_ms=%.6f\n",
|
||||
LOD,Octave,
|
||||
static_cast<unsigned long long>(DetailOctaveEvaluationsByLOD[LOD][Octave]),
|
||||
static_cast<unsigned long long>(DetailOctaveEligibleByLOD[LOD][Octave]),
|
||||
static_cast<unsigned long long>(DetailOctaveSkipsByLOD[LOD][Octave]),
|
||||
DetailOctaveMillisecondsByLOD[LOD][Octave],
|
||||
DetailOctaveEligibleMillisecondsByLOD[LOD][Octave]);
|
||||
GroundRefineExclusiveMsByLOD[LOD],CapRefineExclusiveMsByLOD[LOD],
|
||||
SkirtMsByLOD[LOD],static_cast<unsigned long long>(SkirtTrianglesByLOD[LOD]),
|
||||
MeshAssemblyMsByLOD[LOD]);
|
||||
}
|
||||
const double NeighborhoodSeconds=std::chrono::duration<double>(NeighborhoodReady-ColdStart).count();
|
||||
const double NeighborhoodMeshSeconds=std::chrono::duration<double>(NeighborhoodReady-ShapeReady).count();
|
||||
|
||||
@@ -646,16 +646,14 @@ public:
|
||||
int32 GetWorkerCount()const{return WorkerCount;}
|
||||
uint64 GetCacheHits()const{return CacheHitCount.load(std::memory_order_relaxed);}
|
||||
|
||||
void SetVisualDetailCutoffBudgets(const std::array<std::int64_t,9>& Budgets,bool Apply)
|
||||
{VisualDetailBudgetSubvoxels=Budgets;bEnableVisualDetailCutoff=true;bApplyVisualDetailCutoff=Apply;}
|
||||
void SetVisualDetailProfilingEnabled(bool Enabled)
|
||||
{bProfileVisualDetailOctaves=Enabled;}
|
||||
void SetMesherStageProfilingEnabled(bool Enabled)
|
||||
{bProfileMesherStages=Enabled;}
|
||||
FStandaloneCpuTile GenerateDiagnostic(const FVoxelTileKey& Key,
|
||||
std::uint32_t GlobalOctaveDrop=0,std::uint32_t CellStride=1,
|
||||
bool ForceExactVisualDetail=false)
|
||||
bool ForceGenerate=false)
|
||||
{
|
||||
FStandaloneTileJob Job;Job.Key=Key;Job.StartedAt=FClock::now();Job.QueuedAt=Job.StartedAt;
|
||||
return Generate(Job,GlobalOctaveDrop,CellStride,0,ForceExactVisualDetail);
|
||||
return Generate(Job,GlobalOctaveDrop,CellStride,ForceGenerate);
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -775,13 +773,12 @@ private:
|
||||
}
|
||||
FStandaloneCpuTile Generate(const FStandaloneTileJob& Job,
|
||||
std::uint32_t DiagnosticGlobalOctaveDrop=0,std::uint32_t DiagnosticCellStride=1,
|
||||
std::int64_t DiagnosticVisualBudgetSubvoxels=0,
|
||||
bool ForceExactVisualDetail=false)
|
||||
bool ForceGenerate=false)
|
||||
{
|
||||
const auto StartTime=Job.StartedAt;
|
||||
FStandaloneCpuTile Out;Out.Key=Job.Key;Out.RequestedCrossing=Job.RequestedCrossing;
|
||||
Out.QueuedAt=Job.QueuedAt;Out.StartedAt=Job.StartedAt;
|
||||
if(MeshCache&&!ForceExactVisualDetail)
|
||||
if(MeshCache&&!ForceGenerate)
|
||||
{
|
||||
if(const FStandaloneCpuTile* Cached=MeshCache->Find(Job.Key))
|
||||
{
|
||||
@@ -820,19 +817,9 @@ private:
|
||||
Options.skipMaterialGroups=bSkipMaterialGroups;Options.skipUv=bSkipUV;Options.skipColors=bSkipColors;
|
||||
Options.captureColumnCoordinates=bCaptureColumnCoordinates;
|
||||
Options.lod8HorizonInteriorStepVoxels=Lod8HorizonInteriorStepVoxels;
|
||||
Options.visualDetailPixelBudgetSubvoxels=DiagnosticVisualBudgetSubvoxels;
|
||||
if(DiagnosticVisualBudgetSubvoxels==0&&bEnableVisualDetailCutoff
|
||||
&&Level>=3&&Level<static_cast<int32>(VisualDetailBudgetSubvoxels.size()))
|
||||
Options.visualDetailPixelBudgetSubvoxels=
|
||||
VisualDetailBudgetSubvoxels[static_cast<std::size_t>(Level)];
|
||||
Options.visualDetailSampleStepVoxels=FMath::Max(1,Extent/Cells);
|
||||
Options.profileVisualDetailOctaves=bProfileVisualDetailOctaves&&!ForceExactVisualDetail;
|
||||
Options.visualDetailCutoffEnabled=bApplyVisualDetailCutoff
|
||||
&&!ForceExactVisualDetail&&Level>=3
|
||||
&&Options.visualDetailPixelBudgetSubvoxels>0;
|
||||
Options.visualDetailContractVersion=Options.visualDetailCutoffEnabled
|
||||
?VoxelForgeCore::CurrentVisualDetailCutoffContractVersion:0;
|
||||
Options.profileMesherStages=bProfileMesherStages&&!ForceGenerate;
|
||||
Options.diagnosticGlobalOctaveDrop=DiagnosticGlobalOctaveDrop;
|
||||
Options.diagnosticCellStride=std::max<std::uint32_t>(1,DiagnosticCellStride);
|
||||
Options.diagnosticColumnInterpolationStride=
|
||||
std::max<std::uint32_t>(1,DiagnosticCellStride);
|
||||
std::string Error;
|
||||
@@ -1007,10 +994,7 @@ private:
|
||||
bool bSkipColors=false;
|
||||
bool bCaptureColumnCoordinates=false;
|
||||
int32 Lod8HorizonInteriorStepVoxels=0;
|
||||
std::array<std::int64_t,9> VisualDetailBudgetSubvoxels{};
|
||||
bool bEnableVisualDetailCutoff=false;
|
||||
bool bApplyVisualDetailCutoff=false;
|
||||
bool bProfileVisualDetailOctaves=false;
|
||||
bool bProfileMesherStages=false;
|
||||
std::atomic<uint64> CacheHitCount{0};
|
||||
mutable std::mutex Mutex;
|
||||
std::condition_variable Wake;
|
||||
|
||||
@@ -1768,6 +1768,7 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
||||
const int32 GroundSurfaceTriangleCount=GroundTris.Num()/3;
|
||||
const int32 CapSurfaceTriangleCount=CapTris.Num()/3;
|
||||
TArray<uint8> GroundSkirtFaceMasks,CapSkirtFaceMasks;
|
||||
const uint64 SkirtStageStartCycles=OutStageTimings?FPlatformTime::Cycles64():0;
|
||||
if (bGenerateSkirts && (GroundTris.Num() + CapTris.Num()) > 0)
|
||||
{
|
||||
const float ExtentCm = (float)(CellsPerAxis * Step) * GeometryVoxelSize;
|
||||
@@ -2007,6 +2008,10 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
||||
EmitSkirts(GroundTris,GroundSkirtFaceMasks);
|
||||
EmitSkirts(CapTris,CapSkirtFaceMasks);
|
||||
}
|
||||
if(OutStageTimings)
|
||||
OutStageTimings->SkirtMilliseconds=static_cast<double>(
|
||||
FPlatformTime::Cycles64()-SkirtStageStartCycles)
|
||||
*FPlatformTime::GetSecondsPerCycle64()*1000.0;
|
||||
|
||||
// F17 — concatène sol PUIS sky-cap : un run d'indices contigu par polygroup (voir
|
||||
// FVoxelMeshData::NumCeilingTriangles). Mêmes triangles qu'avant, seul l'ordre change.
|
||||
@@ -2059,6 +2064,8 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
||||
AuditInterpolationCycles,AuditInterpolationSamples,SurfaceCellCalls);
|
||||
OutStageTimings->VertexDedupStreamMilliseconds=ScaleSampledCycles(
|
||||
AuditDedupStreamCycles,AuditDedupStreamSamples,SurfaceCellCalls);
|
||||
OutStageTimings->SkirtMilliseconds=std::max(0.0,
|
||||
OutStageTimings->SkirtMilliseconds);
|
||||
const double TotalMilliseconds=static_cast<double>(
|
||||
FPlatformTime::Cycles64()-StageMesherStartCycles)*CycleToMs;
|
||||
OutStageTimings->OtherMilliseconds=FMath::Max(0.0,TotalMilliseconds
|
||||
@@ -2066,7 +2073,8 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
||||
-OutStageTimings->CellClassificationMilliseconds
|
||||
-OutStageTimings->GradientNormalsMilliseconds
|
||||
-OutStageTimings->EdgeInterpolationMilliseconds
|
||||
-OutStageTimings->VertexDedupStreamMilliseconds);
|
||||
-OutStageTimings->VertexDedupStreamMilliseconds
|
||||
-OutStageTimings->SkirtMilliseconds);
|
||||
}
|
||||
|
||||
return MeshData;
|
||||
|
||||
@@ -50,6 +50,7 @@ struct FVoxelMarchingCubesStageTimings
|
||||
double GradientNormalsMilliseconds = 0.0;
|
||||
double EdgeInterpolationMilliseconds = 0.0;
|
||||
double VertexDedupStreamMilliseconds = 0.0;
|
||||
double SkirtMilliseconds = 0.0;
|
||||
double OtherMilliseconds = 0.0;
|
||||
uint64 GradientFallbackDensityCalls = 0;
|
||||
uint64 EdgeMidpointDensityCalls = 0;
|
||||
|
||||
Reference in new issue
Block a user