wip(assembler): round L34 -- passage mouth profile audit and standard capture set
Adds a density-field floor/wall profile audit along passages near mouths (0.125 m spacing; L23 terraced negative control detected) and a reusable 12-view passage capture set (80 deg FOV, eye height, LOD0, skirts off, capped clip planes). The old blurry mouth shot was camera framing; the L33 entry ledges were tile skirts. Owner world hashes unchanged; 24/24 passage rows, 30/30 determinism, 16/16 invariants; cost within noise. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CSFYgsdW8ejZSyiMf4qRSX
This commit is contained in:
1 parent
b7a47bef95
commit
49e29fcd36
24 files changed
+1863
-3
No files matched your search
@@ -14,8 +14,11 @@
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
@@ -39,6 +42,7 @@ namespace
|
||||
float MaximumHeight=-std::numeric_limits<float>::infinity();
|
||||
std::vector<FVertex> Vertices;
|
||||
std::vector<std::uint32_t> Indices;
|
||||
std::vector<std::uint32_t> SurfaceIndices;
|
||||
struct FTileRange
|
||||
{
|
||||
std::array<std::int32_t,4> Key{};
|
||||
@@ -198,6 +202,8 @@ namespace
|
||||
for(const auto& Tile:Scene.Tiles)Desired.insert(Tile.Key);
|
||||
std::vector<std::uint32_t> VisibleIndices;
|
||||
VisibleIndices.reserve(Scene.Indices.size());
|
||||
Scene.SurfaceIndices.reserve(Scene.Indices.size()-
|
||||
static_cast<std::size_t>(Scene.SkirtTriangles)*3);
|
||||
for(const auto& Tile:Scene.Tiles)
|
||||
{
|
||||
const std::uint8_t VisibleMask=ComputeVisibleSkirtFaceMask(Tile.Key,Desired);
|
||||
@@ -212,6 +218,9 @@ namespace
|
||||
const std::size_t Index=Tile.IndexBegin+static_cast<std::size_t>(Triangle)*3;
|
||||
VisibleIndices.insert(VisibleIndices.end(),Scene.Indices.begin()+Index,
|
||||
Scene.Indices.begin()+Index+3);
|
||||
if(FaceMask==0)
|
||||
Scene.SurfaceIndices.insert(Scene.SurfaceIndices.end(),
|
||||
Scene.Indices.begin()+Index,Scene.Indices.begin()+Index+3);
|
||||
}
|
||||
}
|
||||
Scene.Indices=std::move(VisibleIndices);
|
||||
@@ -219,6 +228,272 @@ namespace
|
||||
return true;
|
||||
}
|
||||
|
||||
struct FClipCapResult
|
||||
{
|
||||
std::uint64_t Intersections=0,Loops=0,OpenContours=0,Triangles=0;
|
||||
std::uint64_t DegreeOneNodes=0,BranchNodes=0;
|
||||
std::uint64_t ClosedOpenContours=0;
|
||||
double OpenGapMetres=0.0,NearestOpenGapMetres=0.0;
|
||||
};
|
||||
|
||||
FClipCapResult AddClipPlaneCaps(FScene& Scene,const std::array<double,4>& InputPlane)
|
||||
{
|
||||
FClipCapResult Result;
|
||||
const double NormalLength=std::hypot(InputPlane[0],InputPlane[1],InputPlane[2]);
|
||||
if(!(NormalLength>0.0))return Result;
|
||||
const std::array<double,3> N={InputPlane[0]/NormalLength,
|
||||
InputPlane[1]/NormalLength,InputPlane[2]/NormalLength};
|
||||
const double D=InputPlane[3]/NormalLength;
|
||||
const std::array<double,3> Reference=std::abs(N[2])<0.85
|
||||
?std::array<double,3>{0.0,0.0,1.0}:std::array<double,3>{0.0,1.0,0.0};
|
||||
const auto Cross=[](const std::array<double,3>& A,const std::array<double,3>& B)
|
||||
{
|
||||
return std::array<double,3>{A[1]*B[2]-A[2]*B[1],
|
||||
A[2]*B[0]-A[0]*B[2],A[0]*B[1]-A[1]*B[0]};
|
||||
};
|
||||
const auto Normalize=[](const std::array<double,3>& A)
|
||||
{
|
||||
const double Length=std::hypot(A[0],A[1],A[2]);
|
||||
return Length>0.0?std::array<double,3>{A[0]/Length,A[1]/Length,A[2]/Length}
|
||||
:std::array<double,3>{1.0,0.0,0.0};
|
||||
};
|
||||
const auto Dot=[](const std::array<double,3>& A,const std::array<double,3>& B)
|
||||
{return A[0]*B[0]+A[1]*B[1]+A[2]*B[2];};
|
||||
const std::array<double,3> U=Normalize(Cross(N,Reference));
|
||||
const std::array<double,3> V=Normalize(Cross(N,U));
|
||||
const std::array<double,3> Origin={-D*N[0],-D*N[1],-D*N[2]};
|
||||
using FPointKey=std::pair<std::int64_t,std::int64_t>;
|
||||
std::map<FPointKey,std::size_t> NodeForPoint;
|
||||
std::vector<std::array<double,2>> PlanePoints;
|
||||
std::vector<std::array<double,3>> WorldPoints;
|
||||
std::set<std::pair<std::size_t,std::size_t>> Segments;
|
||||
const auto AddPoint=[&](const std::array<double,3>& Point)
|
||||
{
|
||||
const std::array<double,3> Relative={Point[0]-Origin[0],
|
||||
Point[1]-Origin[1],Point[2]-Origin[2]};
|
||||
const std::array<double,2> PlanePoint={Dot(Relative,U),Dot(Relative,V)};
|
||||
constexpr double Quantization=1000.0;
|
||||
const FPointKey Key{static_cast<std::int64_t>(std::llround(PlanePoint[0]*Quantization)),
|
||||
static_cast<std::int64_t>(std::llround(PlanePoint[1]*Quantization))};
|
||||
const auto Found=NodeForPoint.find(Key);
|
||||
if(Found!=NodeForPoint.end())return Found->second;
|
||||
const std::size_t Index=PlanePoints.size();
|
||||
NodeForPoint.emplace(Key,Index);PlanePoints.push_back(PlanePoint);
|
||||
WorldPoints.push_back(Point);return Index;
|
||||
};
|
||||
for(std::size_t Triangle=0;Triangle+2<Scene.Indices.size();Triangle+=3)
|
||||
{
|
||||
std::array<std::array<double,3>,3> Points{};
|
||||
std::array<double,3> Distances{};
|
||||
for(std::size_t Vertex=0;Vertex<3;++Vertex)
|
||||
{
|
||||
const auto& Source=Scene.Vertices[Scene.Indices[Triangle+Vertex]];
|
||||
Points[Vertex]={Source.WorldPosition[0],Source.WorldPosition[1],Source.WorldPosition[2]};
|
||||
Distances[Vertex]=Dot(N,Points[Vertex])+D;
|
||||
}
|
||||
std::array<std::array<double,3>,3> Hits{};std::size_t HitCount=0;
|
||||
for(std::size_t Edge=0;Edge<3;++Edge)
|
||||
{
|
||||
const std::size_t A=Edge,B=(Edge+1)%3;
|
||||
const double DA=Distances[A],DB=Distances[B];
|
||||
if(std::abs(DA)<=1.0e-9)
|
||||
{
|
||||
bool Duplicate=false;
|
||||
for(std::size_t I=0;I<HitCount;++I)
|
||||
Duplicate=Duplicate||std::hypot(Hits[I][0]-Points[A][0],
|
||||
Hits[I][1]-Points[A][1],Hits[I][2]-Points[A][2])<1.0e-7;
|
||||
if(!Duplicate&&HitCount<Hits.size())Hits[HitCount++]=Points[A];
|
||||
}
|
||||
if((DA< -1.0e-9&&DB>1.0e-9)||(DA>1.0e-9&&DB< -1.0e-9))
|
||||
{
|
||||
const double T=DA/(DA-DB);
|
||||
const std::array<double,3> Hit={
|
||||
std::lerp(Points[A][0],Points[B][0],T),
|
||||
std::lerp(Points[A][1],Points[B][1],T),
|
||||
std::lerp(Points[A][2],Points[B][2],T)};
|
||||
bool Duplicate=false;
|
||||
for(std::size_t I=0;I<HitCount;++I)
|
||||
Duplicate=Duplicate||std::hypot(Hits[I][0]-Hit[0],
|
||||
Hits[I][1]-Hit[1],Hits[I][2]-Hit[2])<1.0e-7;
|
||||
if(!Duplicate&&HitCount<Hits.size())Hits[HitCount++]=Hit;
|
||||
}
|
||||
}
|
||||
if(HitCount!=2)continue;
|
||||
const std::size_t A=AddPoint(Hits[0]),B=AddPoint(Hits[1]);
|
||||
if(A==B)continue;
|
||||
Segments.emplace(std::min(A,B),std::max(A,B));
|
||||
}
|
||||
Result.Intersections=Segments.size();
|
||||
if(Segments.empty())return Result;
|
||||
std::vector<std::vector<std::size_t>> Adjacency(PlanePoints.size());
|
||||
for(const auto& [A,B]:Segments)
|
||||
{Adjacency[A].push_back(B);Adjacency[B].push_back(A);}
|
||||
std::vector<std::size_t> DegreeOne;
|
||||
for(std::size_t Node=0;Node<Adjacency.size();++Node)
|
||||
{
|
||||
const auto& Neighbours=Adjacency[Node];
|
||||
if(Neighbours.size()==1){++Result.DegreeOneNodes;DegreeOne.push_back(Node);}
|
||||
else if(Neighbours.size()>2)++Result.BranchNodes;
|
||||
}
|
||||
struct FEndpointPair {double Distance=0.0;std::size_t A=0,B=0;};
|
||||
std::vector<FEndpointPair> EndpointPairs;
|
||||
for(std::size_t I=0;I<DegreeOne.size();++I)
|
||||
for(std::size_t J=I+1;J<DegreeOne.size();++J)
|
||||
{
|
||||
const std::size_t A=DegreeOne[I],B=DegreeOne[J];
|
||||
EndpointPairs.push_back({std::hypot(PlanePoints[A][0]-PlanePoints[B][0],
|
||||
PlanePoints[A][1]-PlanePoints[B][1]),A,B});
|
||||
}
|
||||
std::sort(EndpointPairs.begin(),EndpointPairs.end(),[](const auto& A,const auto& B)
|
||||
{return A.Distance<B.Distance;});
|
||||
if(!EndpointPairs.empty())Result.NearestOpenGapMetres=EndpointPairs.front().Distance;
|
||||
std::set<std::size_t> PairedEndpoints;
|
||||
for(const FEndpointPair& Pair:EndpointPairs)
|
||||
{
|
||||
if(Pair.Distance>1.0)break;
|
||||
if(PairedEndpoints.contains(Pair.A)||PairedEndpoints.contains(Pair.B))continue;
|
||||
const auto ClosingEdge=std::minmax(Pair.A,Pair.B);
|
||||
if(!Segments.emplace(ClosingEdge.first,ClosingEdge.second).second)continue;
|
||||
Adjacency[Pair.A].push_back(Pair.B);Adjacency[Pair.B].push_back(Pair.A);
|
||||
PairedEndpoints.insert(Pair.A);PairedEndpoints.insert(Pair.B);
|
||||
Result.OpenGapMetres=std::max(Result.OpenGapMetres,Pair.Distance);
|
||||
++Result.ClosedOpenContours;
|
||||
}
|
||||
std::vector<std::map<std::size_t,std::size_t>> Continuation(Adjacency.size());
|
||||
for(std::size_t Node=0;Node<Adjacency.size();++Node)
|
||||
{
|
||||
const auto& Neighbours=Adjacency[Node];
|
||||
if(Neighbours.size()==2)
|
||||
{
|
||||
Continuation[Node][Neighbours[0]]=Neighbours[1];
|
||||
Continuation[Node][Neighbours[1]]=Neighbours[0];
|
||||
continue;
|
||||
}
|
||||
if(Neighbours.size()<3)continue;
|
||||
struct FTurnPair {double Dot=1.0;std::size_t A=0,B=0;};
|
||||
std::vector<FTurnPair> Pairs;
|
||||
for(std::size_t I=0;I<Neighbours.size();++I)
|
||||
for(std::size_t J=I+1;J<Neighbours.size();++J)
|
||||
{
|
||||
const auto& A=PlanePoints[Neighbours[I]];
|
||||
const auto& B=PlanePoints[Neighbours[J]];
|
||||
const double AX=A[0]-PlanePoints[Node][0];
|
||||
const double AY=A[1]-PlanePoints[Node][1];
|
||||
const double BX=B[0]-PlanePoints[Node][0];
|
||||
const double BY=B[1]-PlanePoints[Node][1];
|
||||
const double LengthA=std::hypot(AX,AY),LengthB=std::hypot(BX,BY);
|
||||
if(LengthA<=1.0e-9||LengthB<=1.0e-9)continue;
|
||||
Pairs.push_back({(AX*BX+AY*BY)/(LengthA*LengthB),
|
||||
Neighbours[I],Neighbours[J]});
|
||||
}
|
||||
std::sort(Pairs.begin(),Pairs.end(),[](const auto& A,const auto& B)
|
||||
{return A.Dot<B.Dot;});
|
||||
std::set<std::size_t> Paired;
|
||||
for(const FTurnPair& Pair:Pairs)
|
||||
{
|
||||
if(Pair.Dot>0.25||Paired.contains(Pair.A)||Paired.contains(Pair.B))continue;
|
||||
Continuation[Node][Pair.A]=Pair.B;Continuation[Node][Pair.B]=Pair.A;
|
||||
Paired.insert(Pair.A);Paired.insert(Pair.B);
|
||||
}
|
||||
}
|
||||
std::set<std::pair<std::size_t,std::size_t>> UsedSegments;
|
||||
std::vector<std::vector<std::size_t>> Loops;
|
||||
for(const auto& Segment:Segments)
|
||||
{
|
||||
if(UsedSegments.contains(Segment))continue;
|
||||
std::vector<std::size_t> Loop{Segment.first};
|
||||
std::size_t Previous=Segment.first,Current=Segment.second;
|
||||
UsedSegments.insert(Segment);bool Closed=false,Branched=false;
|
||||
for(std::size_t Guard=0;Guard<=Segments.size();++Guard)
|
||||
{
|
||||
if(Current==Loop.front()){Closed=true;break;}
|
||||
Loop.push_back(Current);
|
||||
const auto ContinuationIt=Continuation[Current].find(Previous);
|
||||
if(ContinuationIt==Continuation[Current].end())
|
||||
{Branched=true;break;}
|
||||
const std::size_t Next=ContinuationIt->second;
|
||||
const auto NextEdge=std::minmax(Current,Next);
|
||||
if(UsedSegments.contains(NextEdge))
|
||||
{Branched=true;break;}
|
||||
UsedSegments.insert(NextEdge);Previous=Current;Current=Next;
|
||||
}
|
||||
if(Closed&&Loop.size()>=3)Loops.push_back(std::move(Loop));
|
||||
else if(Branched||!Closed)++Result.OpenContours;
|
||||
}
|
||||
Result.Loops=Loops.size();
|
||||
if(Loops.empty())return Result;
|
||||
|
||||
struct FPlaneEdge {std::array<double,2> A{},B{};};
|
||||
std::vector<FPlaneEdge> Edges;
|
||||
std::vector<double> Ys;
|
||||
for(const auto& Loop:Loops)
|
||||
for(std::size_t I=0;I<Loop.size();++I)
|
||||
{
|
||||
const auto& A=PlanePoints[Loop[I]];
|
||||
const auto& B=PlanePoints[Loop[(I+1)%Loop.size()]];
|
||||
if(std::hypot(A[0]-B[0],A[1]-B[1])<1.0e-6)continue;
|
||||
Edges.push_back({A,B});Ys.push_back(A[1]);Ys.push_back(B[1]);
|
||||
}
|
||||
std::sort(Ys.begin(),Ys.end());
|
||||
Ys.erase(std::unique(Ys.begin(),Ys.end(),[](double A,double B)
|
||||
{return std::abs(A-B)<1.0e-6;}),Ys.end());
|
||||
const auto AppendTriangle=[&](const std::array<double,2>& A,
|
||||
const std::array<double,2>& B,const std::array<double,2>& C)
|
||||
{
|
||||
const std::array<std::array<double,2>,3> PlaneVertices={A,B,C};
|
||||
const std::uint32_t Base=static_cast<std::uint32_t>(Scene.Vertices.size());
|
||||
for(const auto& PlaneVertex:PlaneVertices)
|
||||
{
|
||||
FVertex Vertex{};
|
||||
for(std::size_t Axis=0;Axis<3;++Axis)
|
||||
{
|
||||
const double World=Origin[Axis]+U[Axis]*PlaneVertex[0]
|
||||
+V[Axis]*PlaneVertex[1]+N[Axis]*0.001;
|
||||
Vertex.Position[Axis]=static_cast<float>(World);
|
||||
Vertex.WorldPosition[Axis]=static_cast<float>(World);
|
||||
Vertex.Normal[Axis]=static_cast<float>(-N[Axis]);
|
||||
}
|
||||
Vertex.Height=Vertex.Position[2];
|
||||
Scene.Vertices.push_back(Vertex);
|
||||
}
|
||||
Scene.Indices.insert(Scene.Indices.end(),{Base,Base+1,Base+2});
|
||||
++Result.Triangles;
|
||||
};
|
||||
for(std::size_t Slab=0;Slab+1<Ys.size();++Slab)
|
||||
{
|
||||
const double Y0=Ys[Slab],Y1=Ys[Slab+1];
|
||||
if(Y1-Y0<1.0e-7)continue;
|
||||
const double Mid=(Y0+Y1)*0.5;
|
||||
struct FCrossing {double X=0.0;const FPlaneEdge* Edge=nullptr;};
|
||||
std::vector<FCrossing> Crossings;
|
||||
for(const FPlaneEdge& Edge:Edges)
|
||||
{
|
||||
const double Low=std::min(Edge.A[1],Edge.B[1]);
|
||||
const double High=std::max(Edge.A[1],Edge.B[1]);
|
||||
if(Mid<=Low||Mid>=High)continue;
|
||||
const double T=(Mid-Edge.A[1])/(Edge.B[1]-Edge.A[1]);
|
||||
Crossings.push_back({std::lerp(Edge.A[0],Edge.B[0],T),&Edge});
|
||||
}
|
||||
std::sort(Crossings.begin(),Crossings.end(),[](const auto& A,const auto& B)
|
||||
{return A.X<B.X;});
|
||||
const auto XAt=[](const FPlaneEdge& Edge,double Y)
|
||||
{return std::lerp(Edge.A[0],Edge.B[0],(Y-Edge.A[1])/(Edge.B[1]-Edge.A[1]));};
|
||||
for(std::size_t I=0;I+1<Crossings.size();I+=2)
|
||||
{
|
||||
const FPlaneEdge& Left=*Crossings[I].Edge;
|
||||
const FPlaneEdge& Right=*Crossings[I+1].Edge;
|
||||
const std::array<double,2> A={XAt(Left,Y0),Y0};
|
||||
const std::array<double,2> B={XAt(Right,Y0),Y0};
|
||||
const std::array<double,2> C={XAt(Right,Y1),Y1};
|
||||
const std::array<double,2> E={XAt(Left,Y1),Y1};
|
||||
if(std::abs(A[0]-B[0])+std::abs(E[0]-C[0])<1.0e-7)continue;
|
||||
AppendTriangle(A,B,C);AppendTriangle(A,C,E);
|
||||
}
|
||||
}
|
||||
Scene.DrawnTriangles=Scene.Indices.size()/3;
|
||||
return Result;
|
||||
}
|
||||
|
||||
EGLDisplay MakeDisplay()
|
||||
{
|
||||
using TGetPlatformDisplay=EGLDisplay(*)(EGLenum,void*,const EGLint*);
|
||||
@@ -537,7 +812,7 @@ int main(int argc,char** argv)
|
||||
{
|
||||
if(argc<3)
|
||||
{
|
||||
std::fprintf(stderr,"Usage: %s scene.vfmesh output.png [--size=WIDTHxHEIGHT] [--fov-deg=90] [--height-range-m=N] [--height-center-m=N] [--camera-m=X,Y,Z] [--yaw-deg=N] [--pitch-deg=N] [--shade=readable|normal] [--height-tint=on|off] [--headlamp=on|off] [--light-direction=X,Y,Z] [--tile-edges=on|off] [--tile-edge-width-m=N] [--clip-plane=NX,NY,NZ,D]\n",argv[0]);
|
||||
std::fprintf(stderr,"Usage: %s scene.vfmesh output.png [--size=WIDTHxHEIGHT] [--fov-deg=90] [--height-range-m=N] [--height-center-m=N] [--camera-m=X,Y,Z] [--yaw-deg=N] [--pitch-deg=N] [--shade=readable|normal] [--height-tint=on|off] [--headlamp=on|off] [--light-direction=X,Y,Z] [--tile-edges=on|off] [--tile-skirts=on|off] [--tile-edge-width-m=N] [--clip-plane=NX,NY,NZ,D] [--focus-region=X,Y,Z,R]\n",argv[0]);
|
||||
return 2;
|
||||
}
|
||||
int Width=2560,Height=1440;double Fov=90.0,HeightRangeMeters=64.0;
|
||||
@@ -545,8 +820,10 @@ int main(int argc,char** argv)
|
||||
bool HasYaw=false,HasPitch=false;double Yaw=0.0,Pitch=0.0;
|
||||
bool HasCamera=false;std::array<double,3> Camera{};
|
||||
bool HasLightDirection=false;std::array<double,3> LightDirection{};
|
||||
bool ReadableMode=true,HeightTint=true,TileEdges=false,Headlamp=false;double TileEdgeWidthMeters=0.15;
|
||||
bool ReadableMode=true,HeightTint=true,TileEdges=false,TileSkirts=true,Headlamp=false;
|
||||
double TileEdgeWidthMeters=0.15;
|
||||
bool ClipPlaneEnabled=false;std::array<double,4> ClipPlane{};
|
||||
bool FocusRegionEnabled=false;std::array<double,3> FocusCenter{};double FocusRadius=0.0;
|
||||
for(int I=3;I<argc;++I)
|
||||
{
|
||||
const std::string_view Arg(argv[I]);
|
||||
@@ -586,6 +863,17 @@ int main(int argc,char** argv)
|
||||
{std::fprintf(stderr,"Invalid camera position\n");return 2;}
|
||||
HasCamera=true;
|
||||
}
|
||||
else if(Arg.starts_with("--focus-region="))
|
||||
{
|
||||
const std::string Value(Arg.substr(15));
|
||||
if(std::sscanf(Value.c_str(),"%lf,%lf,%lf,%lf",&FocusCenter[0],&FocusCenter[1],
|
||||
&FocusCenter[2],&FocusRadius)!=4
|
||||
||!std::isfinite(FocusCenter[0])||!std::isfinite(FocusCenter[1])
|
||||
||!std::isfinite(FocusCenter[2])||!std::isfinite(FocusRadius)
|
||||
||FocusRadius<=0.0)
|
||||
{std::fprintf(stderr,"Invalid focus region\n");return 2;}
|
||||
FocusRegionEnabled=true;
|
||||
}
|
||||
else if(Arg.starts_with("--yaw-deg=")||Arg.starts_with("--pitch-deg="))
|
||||
{
|
||||
const bool IsYaw=Arg.starts_with("--yaw-deg=");
|
||||
@@ -635,6 +923,13 @@ int main(int argc,char** argv)
|
||||
else if(Mode=="off")TileEdges=false;
|
||||
else {std::fprintf(stderr,"Invalid tile-edge mode\n");return 2;}
|
||||
}
|
||||
else if(Arg.starts_with("--tile-skirts="))
|
||||
{
|
||||
const std::string_view Mode=Arg.substr(14);
|
||||
if(Mode=="on")TileSkirts=true;
|
||||
else if(Mode=="off")TileSkirts=false;
|
||||
else {std::fprintf(stderr,"Invalid tile-skirt mode\n");return 2;}
|
||||
}
|
||||
else if(Arg.starts_with("--tile-edge-width-m="))
|
||||
{
|
||||
char* End=nullptr;const std::string Value(Arg.substr(20));
|
||||
@@ -663,6 +958,74 @@ int main(int argc,char** argv)
|
||||
if(HasYaw)Scene.YawDegrees=Yaw;
|
||||
if(HasPitch)Scene.PitchDegrees=Pitch;
|
||||
if(!HasHeightCenter)HeightCenterMeters=Scene.CameraMeters[2];
|
||||
const std::size_t CapIndexBegin=Scene.Indices.size();
|
||||
FClipCapResult ClipCap;
|
||||
if(ClipPlaneEnabled)
|
||||
{
|
||||
ClipCap=AddClipPlaneCaps(Scene,ClipPlane);
|
||||
const bool Capped=ClipCap.Loops>0&&ClipCap.Triangles>0;
|
||||
std::printf("[OffscreenClipCap] segments=%llu loops=%llu open_contours=%llu closed_open_contours=%llu degree_one=%llu branch_nodes=%llu open_gap_m=%.3f nearest_open_gap_m=%.3f cap_triangles=%llu status=%s\n",
|
||||
static_cast<unsigned long long>(ClipCap.Intersections),
|
||||
static_cast<unsigned long long>(ClipCap.Loops),
|
||||
static_cast<unsigned long long>(ClipCap.OpenContours),
|
||||
static_cast<unsigned long long>(ClipCap.ClosedOpenContours),
|
||||
static_cast<unsigned long long>(ClipCap.DegreeOneNodes),
|
||||
static_cast<unsigned long long>(ClipCap.BranchNodes),
|
||||
ClipCap.OpenGapMetres,ClipCap.NearestOpenGapMetres,
|
||||
static_cast<unsigned long long>(ClipCap.Triangles),Capped?"PASS":"FAIL");
|
||||
if(!Capped)return 3;
|
||||
}
|
||||
if(!TileSkirts)
|
||||
{
|
||||
const std::size_t SurfaceTriangleCount=Scene.SurfaceIndices.size()/3;
|
||||
std::vector<std::uint32_t> SurfaceAndCaps=std::move(Scene.SurfaceIndices);
|
||||
// Caps were appended after the original mesh indices. Keep them while dropping
|
||||
// the LOD seam skirts, which are useful for streamed play but obscure close captures.
|
||||
if(ClipPlaneEnabled&&Scene.Indices.size()>CapIndexBegin)
|
||||
SurfaceAndCaps.insert(SurfaceAndCaps.end(),Scene.Indices.begin()+CapIndexBegin,
|
||||
Scene.Indices.end());
|
||||
Scene.Indices=std::move(SurfaceAndCaps);
|
||||
Scene.DrawnTriangles=Scene.Indices.size()/3;
|
||||
std::printf("[OffscreenMeshDisplay] tile_skirts=off kept_surface_triangles=%llu "
|
||||
"cap_triangles=%llu status=PASS\n",
|
||||
static_cast<unsigned long long>(SurfaceTriangleCount),
|
||||
static_cast<unsigned long long>(Scene.Indices.size()/3-
|
||||
SurfaceTriangleCount));
|
||||
}
|
||||
if(FocusRegionEnabled)
|
||||
{
|
||||
std::vector<std::uint32_t> FocusedIndices;
|
||||
FocusedIndices.reserve(Scene.Indices.size());
|
||||
const double RadiusSquared=FocusRadius*FocusRadius;
|
||||
for(std::size_t Triangle=0;Triangle+2<Scene.Indices.size();Triangle+=3)
|
||||
{
|
||||
std::array<double,3> Center{};
|
||||
for(std::size_t Corner=0;Corner<3;++Corner)
|
||||
{
|
||||
const auto& Vertex=Scene.Vertices[Scene.Indices[Triangle+Corner]];
|
||||
for(std::size_t Axis=0;Axis<3;++Axis)
|
||||
Center[Axis]+=Vertex.WorldPosition[Axis]/3.0;
|
||||
}
|
||||
const double DX=Center[0]-FocusCenter[0],DY=Center[1]-FocusCenter[1],
|
||||
DZ=Center[2]-FocusCenter[2];
|
||||
if(DX*DX+DY*DY+DZ*DZ>RadiusSquared)continue;
|
||||
FocusedIndices.insert(FocusedIndices.end(),Scene.Indices.begin()+Triangle,
|
||||
Scene.Indices.begin()+Triangle+3);
|
||||
}
|
||||
if(FocusedIndices.empty())
|
||||
std::printf("[OffscreenFocusRegion] center_m=(%.3f,%.3f,%.3f) radius_m=%.3f "
|
||||
"triangles=0 status=EMPTY_FALLBACK full_scene_triangles=%llu\n",
|
||||
FocusCenter[0],FocusCenter[1],FocusCenter[2],FocusRadius,
|
||||
static_cast<unsigned long long>(Scene.DrawnTriangles));
|
||||
else
|
||||
{
|
||||
Scene.Indices=std::move(FocusedIndices);
|
||||
Scene.DrawnTriangles=Scene.Indices.size()/3;
|
||||
std::printf("[OffscreenFocusRegion] center_m=(%.3f,%.3f,%.3f) radius_m=%.3f "
|
||||
"triangles=%llu status=PASS\n",FocusCenter[0],FocusCenter[1],FocusCenter[2],
|
||||
FocusRadius,static_cast<unsigned long long>(Scene.DrawnTriangles));
|
||||
}
|
||||
}
|
||||
std::printf("[OffscreenScene] voxels_per_meter=%.6f camera_m=(%.3f,%.3f,%.3f) yaw=%.2f pitch=%.2f height_m=%.3f..%.3f height_center_m=%.3f shade=%s height_tint=%s tile_edges=%s vertices=%zu triangles=%llu\n",
|
||||
1000000.0/Scene.VoxelSizeMicrons,Scene.CameraMeters[0],Scene.CameraMeters[1],Scene.CameraMeters[2],
|
||||
Scene.YawDegrees,Scene.PitchDegrees,Scene.MinimumHeight,Scene.MaximumHeight,
|
||||
|
||||
@@ -805,8 +805,241 @@ namespace StandaloneStreamBenchmark
|
||||
std::uint64_t wallSamples=0,missingWallSamples=0;
|
||||
bool passed=false;
|
||||
};
|
||||
struct FPassageMouthProfileAudit
|
||||
{
|
||||
double floorFlatStepRunMetres=0.0,floorStepMetres=0.0;
|
||||
double wallFlatStepRunMetres=0.0,wallStepMetres=0.0;
|
||||
std::uint64_t floorSamples=0,wallSamples=0,missingFloorSamples=0,missingWallSamples=0;
|
||||
bool passed=false;
|
||||
};
|
||||
constexpr double MaximumFlatRunMetres=8.0;
|
||||
constexpr double MinimumWallReliefMetres=0.20;
|
||||
constexpr double PassageProfileSpacingMetres=0.125;
|
||||
constexpr double PassageProfilePlateauMinimumMetres=0.50;
|
||||
constexpr double PassageProfilePlateauEpsilonMetres=0.020;
|
||||
constexpr double PassageProfileStepThresholdMetres=0.100;
|
||||
constexpr double PassageMouthProfileReachMetres=12.0;
|
||||
const auto auditProfilePlateaus=[&](const std::vector<std::array<double,2>>& profile)
|
||||
{
|
||||
std::pair<double,double> result{};
|
||||
if(profile.size()<2)return result;
|
||||
std::size_t runStart=0;
|
||||
for(std::size_t i=1;i<profile.size();++i)
|
||||
{
|
||||
if(profile[i][0]-profile[i-1][0]
|
||||
>PassageProfileSpacingMetres*1.5)
|
||||
{runStart=i;continue;}
|
||||
if(std::abs(profile[i][1]-profile[runStart][1])
|
||||
<=PassageProfilePlateauEpsilonMetres)continue;
|
||||
const double flatLength=profile[i-1][0]-profile[runStart][0];
|
||||
const double step=std::abs(profile[i][1]-profile[i-1][1]);
|
||||
if(flatLength>=PassageProfilePlateauMinimumMetres
|
||||
&&step>=PassageProfileStepThresholdMetres)
|
||||
{
|
||||
result.first=std::max(result.first,flatLength);
|
||||
result.second=std::max(result.second,step);
|
||||
}
|
||||
runStart=i;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
const auto auditPassageMouthProfiles=[&](const VoxelForgeCore::World& Field,
|
||||
const VoxelForgeCore::ResolvedLayoutPassageV1& passage)
|
||||
{
|
||||
FPassageMouthProfileAudit audit;
|
||||
if(passage.controlPoints.size()<2)return audit;
|
||||
const std::size_t end=std::min(passage.lowerMouthControlIndex,
|
||||
passage.controlPoints.size()-1);
|
||||
if(end<1)return audit;
|
||||
struct FProfilePose
|
||||
{
|
||||
double distance=0.0,x=0.0,y=0.0,z=0.0,radius=0.0;
|
||||
};
|
||||
std::vector<FProfilePose> path;
|
||||
path.reserve(end*8+1);
|
||||
double distance=0.0;
|
||||
for(std::size_t segment=0;segment<end;++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 bx=qToMetres(b.positionSubvoxels[0]);
|
||||
const double by=qToMetres(b.positionSubvoxels[1]);
|
||||
const double bz=qToMetres(b.positionSubvoxels[2]);
|
||||
const double ar=qToMetres(a.radiusSubvoxels);
|
||||
const double br=qToMetres(b.radiusSubvoxels);
|
||||
const double length=std::sqrt((bx-ax)*(bx-ax)+(by-ay)*(by-ay)+(bz-az)*(bz-az));
|
||||
if(!(length>1.0e-8))continue;
|
||||
const std::size_t steps=std::max<std::size_t>(1,
|
||||
static_cast<std::size_t>(std::ceil(length/PassageProfileSpacingMetres)));
|
||||
for(std::size_t step=segment==0?0:1;step<=steps;++step)
|
||||
{
|
||||
const double t=static_cast<double>(step)/steps;
|
||||
path.push_back({distance+length*t,std::lerp(ax,bx,t),
|
||||
std::lerp(ay,by,t),std::lerp(az,bz,t),std::lerp(ar,br,t)});
|
||||
}
|
||||
distance+=length;
|
||||
}
|
||||
if(path.size()<3)return audit;
|
||||
const double lowerMouthDistance=distance;
|
||||
constexpr double DensitySearchStepMetres=0.0625;
|
||||
const auto densityMetres=[&](double x,double y,double z)
|
||||
{
|
||||
const float value=Field.densityAt({x/coreMetres,y/coreMetres,z/coreMetres});
|
||||
return std::isfinite(value)?static_cast<double>(value)*coreMetres
|
||||
:std::numeric_limits<double>::quiet_NaN();
|
||||
};
|
||||
const auto firstFloorCrossing=[&](const FProfilePose& pose,double x,double y)->double
|
||||
{
|
||||
const double expectedFloor=pose.z-pose.radius;
|
||||
const double maximumRise=std::min(2.0*pose.radius-0.25,
|
||||
std::max(0.75,pose.radius-1.20+0.50));
|
||||
double previousZ=expectedFloor-1.50;
|
||||
double previous=densityMetres(x,y,previousZ);
|
||||
if(!std::isfinite(previous))return std::numeric_limits<double>::quiet_NaN();
|
||||
double highestFloor=std::numeric_limits<double>::quiet_NaN();
|
||||
for(double z=previousZ+DensitySearchStepMetres;
|
||||
z<=expectedFloor+maximumRise+DensitySearchStepMetres*0.25;
|
||||
z+=DensitySearchStepMetres)
|
||||
{
|
||||
const double current=densityMetres(x,y,z);
|
||||
if(!std::isfinite(current))return std::numeric_limits<double>::quiet_NaN();
|
||||
if(previous<=0.0&¤t>=0.0)
|
||||
{
|
||||
const double denominator=current-previous;
|
||||
const double t=std::abs(denominator)>1.0e-12
|
||||
?std::clamp(-previous/denominator,0.0,1.0):0.0;
|
||||
highestFloor=std::lerp(z-DensitySearchStepMetres,z,t);
|
||||
}
|
||||
previous=current;
|
||||
}
|
||||
return highestFloor;
|
||||
};
|
||||
const auto findWallRadius=[&](const FProfilePose& pose,double nx,double ny)
|
||||
{
|
||||
const double maximum=pose.radius+2.0;
|
||||
double previousRadius=0.0;
|
||||
double previous=densityMetres(pose.x,pose.y,pose.z);
|
||||
if(!std::isfinite(previous)||previous<0.0)
|
||||
return std::numeric_limits<double>::quiet_NaN();
|
||||
for(double radius=DensitySearchStepMetres;radius<=maximum;
|
||||
radius+=DensitySearchStepMetres)
|
||||
{
|
||||
const double current=densityMetres(pose.x+nx*radius,
|
||||
pose.y+ny*radius,pose.z);
|
||||
if(!std::isfinite(current))return std::numeric_limits<double>::quiet_NaN();
|
||||
if(previous>=0.0&¤t<=0.0)
|
||||
{
|
||||
const double denominator=previous-current;
|
||||
const double t=std::abs(denominator)>1.0e-12
|
||||
?std::clamp(previous/denominator,0.0,1.0):0.0;
|
||||
return std::lerp(previousRadius,radius,t);
|
||||
}
|
||||
previous=current;previousRadius=radius;
|
||||
}
|
||||
return std::numeric_limits<double>::quiet_NaN();
|
||||
};
|
||||
std::array<std::array<std::vector<std::array<double,2>>,3>,2> floorProfiles;
|
||||
std::array<std::array<std::vector<std::array<double,2>>,2>,2> wallProfiles;
|
||||
std::array<double,2> endpointFloorCounts{};
|
||||
for(std::size_t i=0;i<path.size();++i)
|
||||
{
|
||||
const auto& pose=path[i];
|
||||
const bool inUpperWindow=pose.distance<=PassageMouthProfileReachMetres;
|
||||
const bool inLowerWindow=lowerMouthDistance-pose.distance
|
||||
<=PassageMouthProfileReachMetres;
|
||||
if(!inUpperWindow&&!inLowerWindow)continue;
|
||||
for(std::size_t endpoint=0;endpoint<2;++endpoint)
|
||||
{
|
||||
if((endpoint==0&&!inUpperWindow)||(endpoint==1&&!inLowerWindow))continue;
|
||||
const double localDistance=endpoint==0?pose.distance
|
||||
:lowerMouthDistance-pose.distance;
|
||||
const double tangentX=i==0?path[i+1].x-path[i].x
|
||||
:i+1>=path.size()?path[i].x-path[i-1].x
|
||||
:path[i+1].x-path[i-1].x;
|
||||
const double tangentY=i==0?path[i+1].y-path[i].y
|
||||
:i+1>=path.size()?path[i].y-path[i-1].y
|
||||
:path[i+1].y-path[i-1].y;
|
||||
const double tangentLength=std::hypot(tangentX,tangentY);
|
||||
if(tangentLength<=1.0e-6)
|
||||
{audit.missingFloorSamples+=3;audit.missingWallSamples+=2;continue;}
|
||||
const double nx=-tangentY/tangentLength,ny=tangentX/tangentLength;
|
||||
for(std::size_t lane=0;lane<3;++lane)
|
||||
{
|
||||
const double lateral=lane==0?0.0:lane==1?0.75:-0.75;
|
||||
const double floor=firstFloorCrossing(pose,
|
||||
pose.x+nx*lateral,pose.y+ny*lateral);
|
||||
if(lane!=0&&localDistance>=4.0&&localDistance<=5.5)
|
||||
std::printf("[PassageMouthProfileProbe] id=%s endpoint=%s d_m=%.3f lane_m=%.2f x_m=%.3f y_m=%.3f route_floor_m=%.3f field_floor_m=%.3f\n",
|
||||
passage.stableId.c_str(),endpoint==0?"upper":"lower",
|
||||
localDistance,lateral,pose.x+nx*lateral,
|
||||
pose.y+ny*lateral,pose.z-pose.radius,floor);
|
||||
if(std::isfinite(floor))
|
||||
{
|
||||
floorProfiles[endpoint][lane].push_back({localDistance,floor});
|
||||
++endpointFloorCounts[endpoint];++audit.floorSamples;
|
||||
}
|
||||
else ++audit.missingFloorSamples;
|
||||
}
|
||||
if(i%4!=0)continue;
|
||||
for(std::size_t side=0;side<2;++side)
|
||||
{
|
||||
const double sign=side==0?-1.0:1.0;
|
||||
const double radius=findWallRadius(pose,sign*nx,sign*ny);
|
||||
if(std::isfinite(radius))
|
||||
{
|
||||
wallProfiles[endpoint][side].push_back({localDistance,radius});
|
||||
++audit.wallSamples;
|
||||
}
|
||||
else ++audit.missingWallSamples;
|
||||
}
|
||||
}
|
||||
}
|
||||
double worstEndpointFloorCoverage=1.0;
|
||||
for(std::size_t endpoint=0;endpoint<2;++endpoint)
|
||||
{
|
||||
for(std::size_t lane=0;lane<3;++lane)
|
||||
std::sort(floorProfiles[endpoint][lane].begin(),
|
||||
floorProfiles[endpoint][lane].end(),
|
||||
[](const auto& a,const auto& b){return a[0]<b[0];});
|
||||
for(std::size_t side=0;side<2;++side)
|
||||
std::sort(wallProfiles[endpoint][side].begin(),
|
||||
wallProfiles[endpoint][side].end(),
|
||||
[](const auto& a,const auto& b){return a[0]<b[0];});
|
||||
const double expectedFloor=std::min<double>(path.size(),
|
||||
PassageMouthProfileReachMetres/PassageProfileSpacingMetres)+1.0;
|
||||
worstEndpointFloorCoverage=std::min(worstEndpointFloorCoverage,
|
||||
expectedFloor>0?endpointFloorCounts[endpoint]/expectedFloor:0.0);
|
||||
// Lateral probes confirm floor coverage, but do not define a longitudinal
|
||||
// floor profile: the normal rotates around tight bends and can jump across
|
||||
// unrelated terrain. The centerline is the stable passage-floor trace.
|
||||
const auto floorStep=auditProfilePlateaus(floorProfiles[endpoint][0]);
|
||||
audit.floorFlatStepRunMetres=std::max(audit.floorFlatStepRunMetres,
|
||||
floorStep.first);
|
||||
audit.floorStepMetres=std::max(audit.floorStepMetres,floorStep.second);
|
||||
for(std::size_t side=0;side<2;++side)
|
||||
{
|
||||
const auto wallStep=auditProfilePlateaus(wallProfiles[endpoint][side]);
|
||||
audit.wallFlatStepRunMetres=std::max(audit.wallFlatStepRunMetres,wallStep.first);
|
||||
audit.wallStepMetres=std::max(audit.wallStepMetres,wallStep.second);
|
||||
}
|
||||
}
|
||||
audit.passed=worstEndpointFloorCoverage>=0.75
|
||||
&&audit.wallSamples>=8
|
||||
&&audit.floorFlatStepRunMetres==0.0&&audit.wallFlatStepRunMetres==0.0;
|
||||
return audit;
|
||||
};
|
||||
std::vector<std::array<double,2>> l23TerracedControl;
|
||||
for(std::size_t tread=0;tread<8;++tread)
|
||||
for(std::size_t sample=0;sample<=16;++sample)
|
||||
l23TerracedControl.push_back({(tread*16+sample)*PassageProfileSpacingMetres,
|
||||
static_cast<double>(tread)*0.25});
|
||||
const auto l23TerraceControlResult=auditProfilePlateaus(l23TerracedControl);
|
||||
const bool l23TerraceControlDetected=l23TerraceControlResult.first
|
||||
>=PassageProfilePlateauMinimumMetres
|
||||
&&l23TerraceControlResult.second>=PassageProfileStepThresholdMetres;
|
||||
const auto auditPassageNaturalness=[&](const VoxelForgeCore::World& Field,
|
||||
const VoxelForgeCore::ResolvedLayoutPassageV1& passage)
|
||||
{
|
||||
@@ -1498,6 +1731,7 @@ namespace StandaloneStreamBenchmark
|
||||
return zUm>=stratum->content.bottomMicrometres&&zUm<stratum->content.topMicrometres;
|
||||
};
|
||||
std::uint64_t passageCount=0,passagePasses=0,blockedControls=0,naturalPasses=0;
|
||||
std::uint64_t mouthProfilePasses=0;
|
||||
std::uint64_t openAirViolations=0,openAirLegacyViolations=0,openAirSamples=0;
|
||||
std::uint64_t openAirAudits=0,openAirColumnSamples=0,openAirShortSpanSamples=0;
|
||||
std::uint64_t columnCrossings=0,shortGroundedSpans=0,columnConnectedEnds=0;
|
||||
@@ -1545,6 +1779,8 @@ namespace StandaloneStreamBenchmark
|
||||
const FWalkAudit passageAudit=auditPlayerPath(Shape,path,0.125,0.125,0.25,3,0.45,
|
||||
&blockedWorld,&segmentSupport);
|
||||
const FPassageNaturalnessAudit naturalAudit=auditPassageNaturalness(Shape,passage);
|
||||
const FPassageMouthProfileAudit mouthProfileAudit=
|
||||
auditPassageMouthProfiles(Shape,passage);
|
||||
const FPassageOpenAirAudit openAirAudit=auditPassageOpenAir(Shape,blockedWorld,
|
||||
passage,passageIndex);
|
||||
const FPassageColumnAudit columnAudit=auditPassageColumns(Shape,blockedWorld,
|
||||
@@ -1574,8 +1810,9 @@ namespace StandaloneStreamBenchmark
|
||||
const bool mouthsInContent=mouthInContent(passage.upperStrateIndex,passage.upperMouthSubvoxels)
|
||||
&&mouthInContent(passage.lowerStrateIndex,passage.lowerMouthSubvoxels);
|
||||
++passageCount;naturalPasses+=naturalAudit.passed;
|
||||
mouthProfilePasses+=mouthProfileAudit.passed;
|
||||
passagePasses+=passageAudit.passed&&mouthsInRadius&&mouthsInContent
|
||||
&&naturalAudit.passed;
|
||||
&&naturalAudit.passed&&mouthProfileAudit.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),
|
||||
@@ -1591,6 +1828,15 @@ namespace StandaloneStreamBenchmark
|
||||
static_cast<unsigned long long>(naturalAudit.wallSamples),
|
||||
static_cast<unsigned long long>(naturalAudit.missingWallSamples),
|
||||
naturalAudit.passed?"PASS":"FAIL");
|
||||
std::printf("[PassageMouthProfileAudit] id=%s floor_samples=%llu floor_missing=%llu wall_samples=%llu wall_missing=%llu floor_step_scan=centerline floor_flat_step_run_m=%.3f floor_step_m=%.3f wall_flat_step_run_m=%.3f wall_step_m=%.3f spacing_m=%.3f plateau=(%.3f,%.3f) status=%s\n",
|
||||
passage.stableId.c_str(),static_cast<unsigned long long>(mouthProfileAudit.floorSamples),
|
||||
static_cast<unsigned long long>(mouthProfileAudit.missingFloorSamples),
|
||||
static_cast<unsigned long long>(mouthProfileAudit.wallSamples),
|
||||
static_cast<unsigned long long>(mouthProfileAudit.missingWallSamples),
|
||||
mouthProfileAudit.floorFlatStepRunMetres,mouthProfileAudit.floorStepMetres,
|
||||
mouthProfileAudit.wallFlatStepRunMetres,mouthProfileAudit.wallStepMetres,
|
||||
PassageProfileSpacingMetres,PassageProfilePlateauMinimumMetres,
|
||||
PassageProfileStepThresholdMetres,mouthProfileAudit.passed?"PASS":"FAIL");
|
||||
std::printf("[PassageOpenAirAudit] id=%s support_samples=%llu violations=%llu column_samples=%llu short_span_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),
|
||||
@@ -1891,12 +2137,23 @@ namespace StandaloneStreamBenchmark
|
||||
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("[PassageMouthProfileNegativeControl] source=L23_terraced_geometry flat_run_m=%.3f following_step_m=%.3f thresholds=(%.3f,%.3f) detected=%s\n",
|
||||
l23TerraceControlResult.first,l23TerraceControlResult.second,
|
||||
PassageProfilePlateauMinimumMetres,PassageProfileStepThresholdMetres,
|
||||
l23TerraceControlDetected?"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");
|
||||
std::printf("[PassageMouthProfileSummary] passed=%llu/%llu spacing_m=%.3f plateau_min_m=%.3f following_step_min_m=%.3f l23_terrace_negative_control=%s status=%s\n",
|
||||
static_cast<unsigned long long>(mouthProfilePasses),
|
||||
static_cast<unsigned long long>(passageCount),PassageProfileSpacingMetres,
|
||||
PassageProfilePlateauMinimumMetres,PassageProfileStepThresholdMetres,
|
||||
l23TerraceControlDetected?"DETECTED":"MISSED",
|
||||
mouthProfilePasses==passageCount&&l23TerraceControlDetected?"PASS":"FAIL");
|
||||
const bool auditPassed=passageCount>0&&passagePasses==passageCount
|
||||
&&naturalPasses==passageCount&&l23SmoothWallDetected
|
||||
&&mouthProfilePasses==passageCount&&l23TerraceControlDetected
|
||||
&&openAirAudits==passageCount&&openAirViolations==0&&l24OpenAirControlDetected
|
||||
&&columnAuditsPassed&&noLongBridgeAuditPassed
|
||||
&&routePasses>0&&routePasses==routeWalkPasses&&routePasses==routeBindings
|
||||
|
||||
Reference in new issue
Block a user