passages: XY-smooth spline with planned height profile + open-air half-pipe (L112, v219)
Dense centripetal spline in XY, height piecewise-linear on the planner's certified profile, radius/floor depth blended; open air = floor slab + 1 m side walls, no roof; audits sample the generated centreline. All walk audits pass (grades <= 15 deg, headroom, shell, mouths, ripple); Clang/GCC W1/W8/W12 deterministic; fast tier PASS; Linux UE builds. Chevron rings gone. Open issues: jagged spikes along the half-pipe wall tops, visible step lines on the trough floor; near-ready +7..+10% vs L109 (candidate list p95 4 -> 22). Not merged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
This commit is contained in:
1 parent
5ec09d1ff1
commit
16554cd426
130 files changed
+8626
-624
No files matched your search
@@ -4,6 +4,7 @@
|
||||
#include "StreamingWorld.h"
|
||||
#include "LinuxScreenSpaceGate.h"
|
||||
#include "../Core/src/VoxelForgeFieldShared.h"
|
||||
#include "../Core/include/VoxelForgeCore/PassageSpline.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
@@ -2968,292 +2969,186 @@ namespace StandaloneStreamBenchmark
|
||||
const auto qToMetres=[&](std::int64_t q)
|
||||
{return static_cast<double>(q)*World.CoreVoxelSizeMicrons
|
||||
/(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel*1000000.0);};
|
||||
const auto passageSplineControl=[&](
|
||||
const VoxelForgeCore::ResolvedLayoutPassageV1& passage,std::size_t index)
|
||||
const auto passageUpperMouthFloorDepth=[&](
|
||||
const VoxelForgeCore::ResolvedLayoutPassageV1& passage)
|
||||
{
|
||||
const auto& point=passage.controlPoints[index];
|
||||
return std::array<double,4>{qToMetres(point.positionSubvoxels[0]),
|
||||
qToMetres(point.positionSubvoxels[1]),
|
||||
qToMetres(point.positionSubvoxels[2]),
|
||||
qToMetres(point.radiusSubvoxels)};
|
||||
const double controlZ=qToMetres(
|
||||
passage.controlPoints.front().positionSubvoxels[2]);
|
||||
const double authoredMouthDepth=std::max(0.0,controlZ
|
||||
-qToMetres(passage.upperMouthSubvoxels[2])+1.13);
|
||||
const auto upperStratum=std::find_if(ResolvedLayout->strates.begin(),
|
||||
ResolvedLayout->strates.end(),[&](const auto& stratum)
|
||||
{return stratum.strateIndex==passage.upperStrateIndex;});
|
||||
if(upperStratum==ResolvedLayout->strates.end()
|
||||
||upperStratum->generatorKind!="FloatingIslands")
|
||||
return authoredMouthDepth;
|
||||
std::vector<std::array<double,3>> route;
|
||||
for(const auto& anchor:upperStratum->anchors)
|
||||
if(anchor.kind=="route_point"&&anchor.values.size()>=3)
|
||||
route.push_back({qToMetres(anchor.values[0]),
|
||||
qToMetres(anchor.values[1]),qToMetres(anchor.values[2])});
|
||||
double routeHeight=0.0,distanceSquared=0.0;
|
||||
const double mouthX=qToMetres(passage.controlPoints.front().positionSubvoxels[0]);
|
||||
const double mouthY=qToMetres(passage.controlPoints.front().positionSubvoxels[1]);
|
||||
if(!VoxelForgeCore::PassageSpline::ClosestRouteHeightAtXY(
|
||||
route,mouthX,mouthY,routeHeight,distanceSquared)
|
||||
||distanceSquared>std::pow(
|
||||
qToMetres(passage.controlPoints.front().radiusSubvoxels)+2.0,2.0))
|
||||
return authoredMouthDepth;
|
||||
const double routeAlignedDepth=std::max(0.0,controlZ-routeHeight+1.13);
|
||||
return std::max(authoredMouthDepth,routeAlignedDepth);
|
||||
};
|
||||
const auto passageSplineKnotSpan=[](const std::array<double,4>& a,
|
||||
const std::array<double,4>& b)
|
||||
const auto passageSplineControls=[&](
|
||||
const VoxelForgeCore::ResolvedLayoutPassageV1& passage)
|
||||
{
|
||||
const double dx=b[0]-a[0],dy=b[1]-a[1],dz=b[2]-a[2];
|
||||
return std::max(1.0e-6,std::sqrt(std::sqrt(dx*dx+dy*dy+dz*dz)));
|
||||
std::vector<VoxelForgeCore::PassageSpline::FPoint> controls;
|
||||
controls.reserve(passage.controlPoints.size());
|
||||
for(const auto& point:passage.controlPoints)
|
||||
controls.push_back({qToMetres(point.positionSubvoxels[0]),
|
||||
qToMetres(point.positionSubvoxels[1]),
|
||||
qToMetres(point.positionSubvoxels[2]),
|
||||
qToMetres(point.radiusSubvoxels)});
|
||||
if(!controls.empty())
|
||||
controls.front()[3]=std::max(controls.front()[3],
|
||||
passageUpperMouthFloorDepth(passage));
|
||||
return controls;
|
||||
};
|
||||
const auto passageSplinePoint=[&](
|
||||
const VoxelForgeCore::ResolvedLayoutPassageV1& passage,
|
||||
std::size_t span,double t)
|
||||
const auto passageSplineFloorProfile=[&](
|
||||
const VoxelForgeCore::ResolvedLayoutPassageV1& passage)
|
||||
{
|
||||
const auto p0=passageSplineControl(passage,span==0?0:span-1);
|
||||
const auto p1=passageSplineControl(passage,span);
|
||||
const auto p2=passageSplineControl(passage,span+1);
|
||||
const auto p3=passageSplineControl(passage,
|
||||
std::min(span+2,passage.controlPoints.size()-1));
|
||||
const double dt0=passageSplineKnotSpan(p0,p1);
|
||||
const double dt1=passageSplineKnotSpan(p1,p2);
|
||||
const double dt2=passageSplineKnotSpan(p2,p3);
|
||||
const double t2=t*t,t3=t2*t;
|
||||
std::array<double,4> result{};
|
||||
for(std::size_t axis=0;axis<4;++axis)
|
||||
{
|
||||
const double m1=dt1*((p1[axis]-p0[axis])/dt0
|
||||
-(p2[axis]-p0[axis])/(dt0+dt1)
|
||||
+(p2[axis]-p1[axis])/dt1);
|
||||
const double m2=dt1*((p2[axis]-p1[axis])/dt1
|
||||
-(p3[axis]-p1[axis])/(dt1+dt2)
|
||||
+(p3[axis]-p2[axis])/dt2);
|
||||
result[axis]=(2.0*t3-3.0*t2+1.0)*p1[axis]
|
||||
+(-2.0*t3+3.0*t2)*p2[axis]
|
||||
+(t3-2.0*t2+t)*m1+(t3-t2)*m2;
|
||||
}
|
||||
result[3]=std::clamp(result[3],std::min(p1[3],p2[3]),
|
||||
std::max(p1[3],p2[3]));
|
||||
return result;
|
||||
std::vector<double> profile;
|
||||
profile.reserve(passage.controlPoints.size());
|
||||
for(const auto& point:passage.controlPoints)
|
||||
profile.push_back(qToMetres(point.positionSubvoxels[2]
|
||||
-point.radiusSubvoxels));
|
||||
return profile;
|
||||
};
|
||||
constexpr std::size_t PassageSplineArcSteps=32;
|
||||
const auto passageBodyFloorDepth=[&](
|
||||
const VoxelForgeCore::ResolvedLayoutPassageV1& passage)
|
||||
{
|
||||
if(passage.controlPoints.size()<2)return 0.0;
|
||||
const std::size_t routeEnd=std::clamp<std::size_t>(
|
||||
passage.lowerMouthControlIndex,1,passage.controlPoints.size()-1);
|
||||
std::vector<double> distances(routeEnd+1,0.0);
|
||||
for(std::size_t i=1;i<=routeEnd;++i)
|
||||
{
|
||||
auto previous=passageSplinePoint(passage,i-1,0.0);
|
||||
double spanLength=0.0;
|
||||
for(std::size_t step=1;step<=PassageSplineArcSteps;++step)
|
||||
{
|
||||
const auto current=passageSplinePoint(passage,i-1,
|
||||
static_cast<double>(step)/PassageSplineArcSteps);
|
||||
const double dx=current[0]-previous[0];
|
||||
const double dy=current[1]-previous[1];
|
||||
const double dz=current[2]-previous[2];
|
||||
spanLength+=std::sqrt(dx*dx+dy*dy+dz*dz);
|
||||
previous=current;
|
||||
}
|
||||
distances[i]=distances[i-1]+spanLength;
|
||||
}
|
||||
double minimumRadius=std::numeric_limits<double>::infinity();
|
||||
double minimumBodyRadius=minimumRadius;
|
||||
for(std::size_t i=0;i<=routeEnd;++i)
|
||||
double totalLength=0.0;
|
||||
const auto controls=passageSplineControls(passage);
|
||||
const auto floorProfile=passageSplineFloorProfile(passage);
|
||||
const auto segments=VoxelForgeCore::PassageSpline::BuildSegments(
|
||||
controls,routeEnd,1.0,1.0,floorProfile);
|
||||
const auto consider=[&](double distance,double radius)
|
||||
{
|
||||
const double radius=qToMetres(
|
||||
passage.controlPoints[i].radiusSubvoxels);
|
||||
minimumRadius=std::min(minimumRadius,radius);
|
||||
if(distances[i]>=10.0&&distances[routeEnd]-distances[i]>=10.0)
|
||||
if(distance>=10.0&&totalLength-distance>=10.0)
|
||||
minimumBodyRadius=std::min(minimumBodyRadius,radius);
|
||||
}
|
||||
};
|
||||
for(const auto& segment:segments)totalLength=segment.alongMetres
|
||||
+segment.lengthMetres;
|
||||
consider(0.0,controls.front()[3]);
|
||||
for(const auto& segment:segments)
|
||||
consider(segment.alongMetres+segment.lengthMetres,segment.b[3]);
|
||||
if(!std::isfinite(minimumBodyRadius))minimumBodyRadius=minimumRadius;
|
||||
return std::max(0.5,minimumBodyRadius-0.23);
|
||||
};
|
||||
const auto passageMouthFloorDepths=[&](
|
||||
const VoxelForgeCore::ResolvedLayoutPassageV1& passage)
|
||||
{
|
||||
const std::size_t routeEnd=std::clamp<std::size_t>(
|
||||
passage.lowerMouthControlIndex,1,passage.controlPoints.size()-1);
|
||||
const double upper=qToMetres(
|
||||
passage.controlPoints.front().positionSubvoxels[2]
|
||||
-passage.upperMouthSubvoxels[2])+1.13;
|
||||
const double lower=qToMetres(
|
||||
passage.controlPoints[routeEnd].positionSubvoxels[2]
|
||||
-passage.lowerMouthSubvoxels[2])+1.13;
|
||||
double lowerDepth=std::max(0.0,lower);
|
||||
const auto nativeCaveMouth=std::find_if(ResolvedLayout->cavernEdges.begin(),
|
||||
ResolvedLayout->cavernEdges.end(),[&](const auto& edge)
|
||||
{
|
||||
return edge.strateIndex==passage.lowerStrateIndex
|
||||
&&edge.startAnchorStableId==passage.stableId+"/lower-mouth"
|
||||
&&!edge.polylineMicrometres.empty();
|
||||
});
|
||||
if(nativeCaveMouth!=ResolvedLayout->cavernEdges.end())
|
||||
{
|
||||
const double routeFloor=qToMetres(
|
||||
passage.controlPoints[routeEnd].positionSubvoxels[2]);
|
||||
const double nativeFloor=static_cast<double>(
|
||||
nativeCaveMouth->polylineMicrometres.front()[2])/1000000.0;
|
||||
lowerDepth=std::max(0.0,routeFloor-nativeFloor);
|
||||
}
|
||||
return std::array<double,2>{{std::max(0.0,upper),lowerDepth}};
|
||||
};
|
||||
const auto passageFloorDepthAt=[](double alongMetres,double totalMetres,
|
||||
double radiusMetres,double bodyDepthMetres,double upperMouthDepth,
|
||||
double lowerMouthDepth)
|
||||
{
|
||||
const double fromMouth=std::max(0.0,std::min(alongMetres,
|
||||
totalMetres-alongMetres));
|
||||
const double linear=std::clamp(fromMouth/5.0,0.0,1.0);
|
||||
const double ramp=linear*linear*(3.0-2.0*linear);
|
||||
const double mouthTarget=alongMetres<=totalMetres*0.5
|
||||
?upperMouthDepth:lowerMouthDepth;
|
||||
// The authored route grades its walk floor as centreline-radius+0.23 m.
|
||||
// This is the floor path the derived flat tube must preserve.
|
||||
const double localBodyDepth=std::max(0.0,radiusMetres-0.23);
|
||||
const double mouthDepth=std::clamp(mouthTarget,0.0,
|
||||
std::max(0.0,radiusMetres));
|
||||
(void)bodyDepthMetres;
|
||||
return std::lerp(mouthDepth,localBodyDepth,ramp);
|
||||
};
|
||||
const auto passageTubeDepthAt=[](double radiusMetres,double bodyDepthMetres)
|
||||
{
|
||||
return std::min(bodyDepthMetres,
|
||||
std::max(0.0,radiusMetres-0.23));
|
||||
};
|
||||
const auto passageRouteDistances=[&](
|
||||
const VoxelForgeCore::ResolvedLayoutPassageV1& passage)
|
||||
{
|
||||
std::vector<double> distances(passage.controlPoints.size(),0.0);
|
||||
if(passage.controlPoints.size()<2)return distances;
|
||||
for(std::size_t i=1;i<passage.controlPoints.size();++i)
|
||||
{
|
||||
auto previous=passageSplinePoint(passage,i-1,0.0);
|
||||
double spanLength=0.0;
|
||||
for(std::size_t step=1;step<=PassageSplineArcSteps;++step)
|
||||
{
|
||||
const auto current=passageSplinePoint(passage,i-1,
|
||||
static_cast<double>(step)/PassageSplineArcSteps);
|
||||
const double dx=current[0]-previous[0];
|
||||
const double dy=current[1]-previous[1];
|
||||
const double dz=current[2]-previous[2];
|
||||
spanLength+=std::sqrt(dx*dx+dy*dy+dz*dz);
|
||||
previous=current;
|
||||
}
|
||||
distances[i]=distances[i-1]+spanLength;
|
||||
}
|
||||
return distances;
|
||||
};
|
||||
const auto passageEffectiveCenterZ=[&](
|
||||
const VoxelForgeCore::ResolvedLayoutPassageV1& passage,
|
||||
std::size_t pointIndex,const std::vector<double>& routeDistances,
|
||||
double totalRouteLength,double bodyDepth,
|
||||
const std::array<double,2>& mouthFloorDepths)
|
||||
{
|
||||
const auto& point=passage.controlPoints[pointIndex];
|
||||
const double radius=qToMetres(point.radiusSubvoxels);
|
||||
const double routeFloorDepth=passageFloorDepthAt(
|
||||
routeDistances[pointIndex],totalRouteLength,radius,bodyDepth,
|
||||
mouthFloorDepths[0],mouthFloorDepths[1]);
|
||||
return qToMetres(point.positionSubvoxels[2])-routeFloorDepth
|
||||
+passageTubeDepthAt(radius,bodyDepth);
|
||||
};
|
||||
struct FPassageAuditRoutePoint
|
||||
{
|
||||
double x=0.0,y=0.0,z=0.0,radius=0.0,distance=0.0;
|
||||
double x=0.0,y=0.0,z=0.0,floorZ=0.0,radius=0.0,distance=0.0;
|
||||
std::size_t segmentIndex=0;
|
||||
};
|
||||
const auto passageSmoothRoute=[&](
|
||||
const VoxelForgeCore::ResolvedLayoutPassageV1& passage,
|
||||
double bodyDepth,const std::array<double,2>& mouthFloorDepths)
|
||||
const VoxelForgeCore::ResolvedLayoutPassageV1& passage,double bodyDepth)
|
||||
{
|
||||
std::vector<FPassageAuditRoutePoint> route;
|
||||
if(passage.controlPoints.size()<2)return route;
|
||||
const std::size_t routeEnd=std::clamp<std::size_t>(
|
||||
passage.lowerMouthControlIndex,1,passage.controlPoints.size()-1);
|
||||
constexpr double AuditRouteSpacingMetres=1.0;
|
||||
auto previous=passageSplinePoint(passage,0,0.0);
|
||||
route.push_back({previous[0],previous[1],previous[2],previous[3],0.0,0});
|
||||
double distance=0.0;
|
||||
for(std::size_t span=0;span<routeEnd;++span)
|
||||
const auto controls=passageSplineControls(passage);
|
||||
const auto floorProfile=passageSplineFloorProfile(passage);
|
||||
const auto segments=VoxelForgeCore::PassageSpline::BuildSegments(
|
||||
controls,routeEnd,1.0,1.0,floorProfile);
|
||||
const auto effectiveCenterZ=[&](const auto& point,double plannedFloor)
|
||||
{
|
||||
std::array<double,PassageSplineArcSteps+1> arcLength{};
|
||||
auto arcPrevious=passageSplinePoint(passage,span,0.0);
|
||||
for(std::size_t step=1;step<=PassageSplineArcSteps;++step)
|
||||
{
|
||||
const auto current=passageSplinePoint(passage,span,
|
||||
static_cast<double>(step)/PassageSplineArcSteps);
|
||||
const double dx=current[0]-arcPrevious[0];
|
||||
const double dy=current[1]-arcPrevious[1];
|
||||
const double dz=current[2]-arcPrevious[2];
|
||||
arcLength[step]=arcLength[step-1]
|
||||
+std::sqrt(dx*dx+dy*dy+dz*dz);
|
||||
arcPrevious=current;
|
||||
}
|
||||
const double spanLength=arcLength.back();
|
||||
const std::size_t steps=std::max<std::size_t>(1,
|
||||
static_cast<std::size_t>(std::ceil(
|
||||
spanLength/AuditRouteSpacingMetres)));
|
||||
for(std::size_t sample=1;sample<=steps;++sample)
|
||||
{
|
||||
const double targetArc=spanLength*static_cast<double>(sample)/steps;
|
||||
const auto upper=std::lower_bound(arcLength.begin()+1,
|
||||
arcLength.end(),targetArc);
|
||||
const std::size_t upperIndex=static_cast<std::size_t>(
|
||||
upper-arcLength.begin());
|
||||
const std::size_t lowerIndex=upperIndex-1;
|
||||
const double arcSpan=arcLength[upperIndex]-arcLength[lowerIndex];
|
||||
const double arcT=arcSpan>1.0e-12
|
||||
?std::clamp((targetArc-arcLength[lowerIndex])/arcSpan,0.0,1.0):0.0;
|
||||
const double t=(static_cast<double>(lowerIndex)+arcT)
|
||||
/PassageSplineArcSteps;
|
||||
const auto current=passageSplinePoint(passage,span,t);
|
||||
const double dx=current[0]-previous[0];
|
||||
const double dy=current[1]-previous[1];
|
||||
const double dz=current[2]-previous[2];
|
||||
distance+=std::sqrt(dx*dx+dy*dy+dz*dz);
|
||||
route.push_back({current[0],current[1],current[2],current[3],
|
||||
distance,span+1});
|
||||
previous=current;
|
||||
}
|
||||
}
|
||||
const double totalLength=route.empty()?0.0:route.back().distance;
|
||||
std::vector<double> desiredFloor(route.size());
|
||||
std::vector<double> horizontalGradeLimits(route.size()>0?route.size()-1:0);
|
||||
std::vector<double> remainingGradeAllowance(route.size(),0.0);
|
||||
constexpr double MaximumWalkGradeTangent=0.2586407821;
|
||||
for(std::size_t i=0;i<route.size();++i)
|
||||
return plannedFloor+passageTubeDepthAt(point[3],bodyDepth);
|
||||
};
|
||||
if(!segments.empty())
|
||||
{
|
||||
const double routeDepth=passageFloorDepthAt(route[i].distance,totalLength,
|
||||
route[i].radius,bodyDepth,mouthFloorDepths[0],mouthFloorDepths[1]);
|
||||
desiredFloor[i]=route[i].z-routeDepth;
|
||||
if(i+1<route.size())
|
||||
{
|
||||
const double dx=route[i+1].x-route[i].x;
|
||||
const double dy=route[i+1].y-route[i].y;
|
||||
horizontalGradeLimits[i]=MaximumWalkGradeTangent
|
||||
*std::hypot(dx,dy);
|
||||
}
|
||||
}
|
||||
for(std::size_t i=horizontalGradeLimits.size();i>0;--i)
|
||||
remainingGradeAllowance[i-1]=remainingGradeAllowance[i]
|
||||
+horizontalGradeLimits[i-1];
|
||||
if(route.size()>1)
|
||||
{
|
||||
std::vector<double> walkableFloor(route.size());
|
||||
walkableFloor.front()=desiredFloor.front();
|
||||
walkableFloor.back()=desiredFloor.back();
|
||||
for(std::size_t i=1;i+1<route.size();++i)
|
||||
{
|
||||
const double limit=horizontalGradeLimits[i-1];
|
||||
const double remaining=remainingGradeAllowance[i];
|
||||
const double low=std::max(walkableFloor[i-1]-limit,
|
||||
walkableFloor.back()-remaining);
|
||||
const double high=std::min(walkableFloor[i-1]+limit,
|
||||
walkableFloor.back()+remaining);
|
||||
walkableFloor[i]=std::clamp(desiredFloor[i],low,high);
|
||||
}
|
||||
for(std::size_t i=0;i<route.size();++i)
|
||||
route[i].z=walkableFloor[i]
|
||||
+passageTubeDepthAt(route[i].radius,bodyDepth);
|
||||
const auto& first=segments.front();
|
||||
route.push_back({first.a[0],first.a[1],
|
||||
effectiveCenterZ(first.a,first.plannedFloorA),
|
||||
first.plannedFloorA,first.a[3],
|
||||
first.alongMetres,0});
|
||||
for(const auto& segment:segments)
|
||||
route.push_back({segment.b[0],segment.b[1],
|
||||
effectiveCenterZ(segment.b,segment.plannedFloorB),
|
||||
segment.plannedFloorB,segment.b[3],
|
||||
segment.alongMetres+segment.lengthMetres,
|
||||
segment.controlSpan+1});
|
||||
}
|
||||
return route;
|
||||
};
|
||||
std::vector<std::vector<double>> allPassageRouteDistances;
|
||||
std::vector<double> allPassageBodyFloorDepths;
|
||||
std::vector<std::vector<FPassageAuditRoutePoint>> allPassageSmoothRoutes;
|
||||
allPassageRouteDistances.reserve(ResolvedLayout->passages.size());
|
||||
struct FUpperMouthFloorBlend
|
||||
{
|
||||
std::vector<std::array<double,3>> receivingRoute;
|
||||
double mouthX=0.0,mouthY=0.0,mouthFloor=0.0;
|
||||
double tangentX=0.0,tangentY=0.0;
|
||||
};
|
||||
std::vector<FUpperMouthFloorBlend> allUpperMouthFloorBlends;
|
||||
allPassageBodyFloorDepths.reserve(ResolvedLayout->passages.size());
|
||||
allPassageSmoothRoutes.reserve(ResolvedLayout->passages.size());
|
||||
allUpperMouthFloorBlends.reserve(ResolvedLayout->passages.size());
|
||||
for(const auto& passage:ResolvedLayout->passages)
|
||||
{
|
||||
allPassageRouteDistances.push_back(passageRouteDistances(passage));
|
||||
const double bodyDepth=passageBodyFloorDepth(passage);
|
||||
allPassageBodyFloorDepths.push_back(bodyDepth);
|
||||
allPassageSmoothRoutes.push_back(passageSmoothRoute(passage,bodyDepth,
|
||||
passageMouthFloorDepths(passage)));
|
||||
allPassageSmoothRoutes.push_back(passageSmoothRoute(passage,bodyDepth));
|
||||
FUpperMouthFloorBlend blend;
|
||||
const auto upperStratum=std::find_if(ResolvedLayout->strates.begin(),
|
||||
ResolvedLayout->strates.end(),[&](const auto& stratum)
|
||||
{return stratum.strateIndex==passage.upperStrateIndex;});
|
||||
if(upperStratum!=ResolvedLayout->strates.end()
|
||||
&&upperStratum->generatorKind=="FloatingIslands")
|
||||
{
|
||||
for(const auto& anchor:upperStratum->anchors)
|
||||
if(anchor.kind=="route_point"&&anchor.values.size()>=3)
|
||||
blend.receivingRoute.push_back({
|
||||
qToMetres(anchor.values[0]),qToMetres(anchor.values[1]),
|
||||
qToMetres(anchor.values[2])});
|
||||
const auto controls=passageSplineControls(passage);
|
||||
const auto floorProfile=passageSplineFloorProfile(passage);
|
||||
const std::size_t routeEnd=std::clamp<std::size_t>(
|
||||
passage.lowerMouthControlIndex,1,passage.controlPoints.size()-1);
|
||||
const auto segments=VoxelForgeCore::PassageSpline::BuildSegments(
|
||||
controls,routeEnd,1.0,1.0,floorProfile);
|
||||
if(!segments.empty())
|
||||
{
|
||||
blend.mouthX=segments.front().a[0];
|
||||
blend.mouthY=segments.front().a[1];
|
||||
blend.mouthFloor=floorProfile.front();
|
||||
blend.tangentX=segments.front().b[0]-segments.front().a[0];
|
||||
blend.tangentY=segments.front().b[1]-segments.front().a[1];
|
||||
}
|
||||
}
|
||||
allUpperMouthFloorBlends.push_back(std::move(blend));
|
||||
}
|
||||
const auto passageFloorAtXY=[&](std::size_t passageIndex,double x,double y,
|
||||
double plannedFloor)
|
||||
{
|
||||
if(passageIndex>=allUpperMouthFloorBlends.size())return plannedFloor;
|
||||
const auto& blend=allUpperMouthFloorBlends[passageIndex];
|
||||
return VoxelForgeCore::PassageSpline::BlendUpperMouthFloorAtXY(
|
||||
blend.receivingRoute,x,y,plannedFloor,blend.mouthX,blend.mouthY,
|
||||
blend.mouthFloor,
|
||||
blend.tangentX,blend.tangentY,
|
||||
VoxelForgeCore::PassageSpline::UpperMouthFloorBlendRadiusMetres,
|
||||
1.13);
|
||||
};
|
||||
const auto interpolatePassageRoute=[](
|
||||
const std::vector<FPassageAuditRoutePoint>& route,double distance)
|
||||
{
|
||||
@@ -3268,6 +3163,7 @@ namespace StandaloneStreamBenchmark
|
||||
const double t=span>1.0e-9?(distance-a.distance)/span:0.0;
|
||||
return FPassageAuditRoutePoint{
|
||||
std::lerp(a.x,b.x,t),std::lerp(a.y,b.y,t),std::lerp(a.z,b.z,t),
|
||||
std::lerp(a.floorZ,b.floorZ,t),
|
||||
std::lerp(a.radius,b.radius,t),distance,b.segmentIndex};
|
||||
};
|
||||
const auto passageInteriorWinsAt=[&](double xMetres,double yMetres,
|
||||
@@ -3283,7 +3179,6 @@ namespace StandaloneStreamBenchmark
|
||||
if(passage.controlPoints.size()<2)continue;
|
||||
const auto& route=allPassageSmoothRoutes[passageIndex];
|
||||
if(route.size()<2)continue;
|
||||
const double bodyDepth=allPassageBodyFloorDepths[passageIndex];
|
||||
double bestDistance=std::numeric_limits<double>::infinity();
|
||||
double bestRoundDistance=0.0,bestCenterZ=0.0;
|
||||
std::size_t bestSegment=std::numeric_limits<std::size_t>::max();
|
||||
@@ -3304,14 +3199,12 @@ namespace StandaloneStreamBenchmark
|
||||
const double floorT=horizontalLengthSquared>1.0e-12
|
||||
?std::clamp(((xMetres-a.x)*dx+(yMetres-a.y)*dy)
|
||||
/horizontalLengthSquared,0.0,1.0):t;
|
||||
const double floorCenterZ=a.z+floorT*dz;
|
||||
const double floorRadius=std::lerp(a.radius,b.radius,floorT);
|
||||
const double floorDepth=passageTubeDepthAt(floorRadius,bodyDepth);
|
||||
const double floorCenterZ=passageFloorAtXY(passageIndex,
|
||||
xMetres,yMetres,std::lerp(a.floorZ,b.floorZ,floorT));
|
||||
const double roundDistance=std::sqrt((xMetres-centerX)*(xMetres-centerX)
|
||||
+(yMetres-centerY)*(yMetres-centerY)
|
||||
+(zMetres-centerZ)*(zMetres-centerZ))-radius;
|
||||
const double floorPlaneZ=floorCenterZ-floorDepth;
|
||||
const double floorDistance=floorPlaneZ-zMetres;
|
||||
const double floorDistance=floorCenterZ-zMetres;
|
||||
const double h=std::max(CornerBlendMetres-
|
||||
std::abs(roundDistance-floorDistance),0.0)/CornerBlendMetres;
|
||||
const double flatDistance=std::max(roundDistance,floorDistance)
|
||||
@@ -3353,15 +3246,13 @@ namespace StandaloneStreamBenchmark
|
||||
const auto& bFloor=route[bestSegment];
|
||||
const double floorDx=bFloor.x-aFloor.x;
|
||||
const double floorDy=bFloor.y-aFloor.y;
|
||||
const double floorDz=bFloor.z-aFloor.z;
|
||||
const double horizontalSquared=floorDx*floorDx+floorDy*floorDy;
|
||||
const double floorT=horizontalSquared>1.0e-12
|
||||
?std::clamp(((xMetres-aFloor.x)*floorDx
|
||||
+(yMetres-aFloor.y)*floorDy)/horizontalSquared,0.0,1.0):t;
|
||||
const double floorRadius=std::lerp(aFloor.radius,bFloor.radius,floorT);
|
||||
const double floorDepth=passageTubeDepthAt(floorRadius,bodyDepth);
|
||||
const double floorCenterZ=aFloor.z+floorT*floorDz;
|
||||
const double floorDistance=floorCenterZ-floorDepth-zMetres;
|
||||
const double floorCenterZ=passageFloorAtXY(passageIndex,
|
||||
xMetres,yMetres,std::lerp(aFloor.floorZ,bFloor.floorZ,floorT));
|
||||
const double floorDistance=floorCenterZ-zMetres;
|
||||
const double blend=std::max(CornerBlendMetres-
|
||||
std::abs(roughRoundDistance-floorDistance),0.0)/CornerBlendMetres;
|
||||
const double shapedDistance=std::max(roughRoundDistance,floorDistance)
|
||||
@@ -3441,51 +3332,25 @@ namespace StandaloneStreamBenchmark
|
||||
{
|
||||
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;
|
||||
const auto routeDistances=passageRouteDistances(passage);
|
||||
const double totalRouteLength=routeDistances.empty()?0.0:
|
||||
routeDistances[end];
|
||||
const double bodyFloorDepth=passageBodyFloorDepth(passage);
|
||||
const auto mouthFloorDepths=passageMouthFloorDepths(passage);
|
||||
const std::size_t passageIndex=static_cast<std::size_t>(
|
||||
&passage-ResolvedLayout->passages.data());
|
||||
if(passageIndex>=allPassageSmoothRoutes.size())return audit;
|
||||
const auto& smoothRoute=allPassageSmoothRoutes[passageIndex];
|
||||
if(smoothRoute.size()<3)return audit;
|
||||
struct FProfilePose
|
||||
{
|
||||
double distance=0.0,x=0.0,y=0.0,z=0.0,radius=0.0;
|
||||
double distance=0.0,x=0.0,y=0.0,z=0.0,floorZ=0.0,radius=0.0;
|
||||
std::size_t segmentIndex=0;
|
||||
};
|
||||
std::vector<FProfilePose> path;
|
||||
path.reserve(end*8+1);
|
||||
double distance=0.0;
|
||||
for(std::size_t segment=0;segment<end;++segment)
|
||||
path.reserve(smoothRoute.size());
|
||||
for(const auto& point:smoothRoute)
|
||||
{
|
||||
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=passageEffectiveCenterZ(passage,segment,
|
||||
routeDistances,totalRouteLength,bodyFloorDepth,mouthFloorDepths);
|
||||
const double bx=qToMetres(b.positionSubvoxels[0]);
|
||||
const double by=qToMetres(b.positionSubvoxels[1]);
|
||||
const double bz=passageEffectiveCenterZ(passage,segment+1,
|
||||
routeDistances,totalRouteLength,bodyFloorDepth,mouthFloorDepths);
|
||||
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),
|
||||
segment+1});
|
||||
}
|
||||
distance+=length;
|
||||
path.push_back({point.distance,point.x,point.y,point.z,
|
||||
point.floorZ,point.radius,point.segmentIndex});
|
||||
}
|
||||
if(path.size()<3)return audit;
|
||||
const double lowerMouthDistance=distance;
|
||||
const double lowerMouthDistance=path.back().distance;
|
||||
constexpr double DensitySearchStepMetres=0.0625;
|
||||
const auto densityMetres=[&](double x,double y,double z)
|
||||
{
|
||||
@@ -3495,8 +3360,7 @@ namespace StandaloneStreamBenchmark
|
||||
};
|
||||
const auto firstFloorCrossing=[&](const FProfilePose& pose,double x,double y)->double
|
||||
{
|
||||
const double expectedFloor=pose.z-passageTubeDepthAt(
|
||||
pose.radius,bodyFloorDepth);
|
||||
const double expectedFloor=pose.floorZ;
|
||||
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;
|
||||
@@ -3520,18 +3384,20 @@ namespace StandaloneStreamBenchmark
|
||||
}
|
||||
return highestFloor;
|
||||
};
|
||||
const auto findWallRadius=[&](const FProfilePose& pose,double nx,double ny)
|
||||
const auto findWallRadius=[&](const FProfilePose& pose,double nx,double ny,
|
||||
double wallHeightMetres)
|
||||
{
|
||||
const double maximum=pose.radius+2.0;
|
||||
double previousRadius=0.0;
|
||||
double previous=densityMetres(pose.x,pose.y,pose.z);
|
||||
const double wallZ=pose.floorZ+wallHeightMetres;
|
||||
double previous=densityMetres(pose.x,pose.y,wallZ);
|
||||
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);
|
||||
pose.y+ny*radius,wallZ);
|
||||
if(!std::isfinite(current))return std::numeric_limits<double>::quiet_NaN();
|
||||
if(previous>=0.0&¤t<=0.0)
|
||||
{
|
||||
@@ -3580,8 +3446,7 @@ namespace StandaloneStreamBenchmark
|
||||
const double lateral=lane==0?0.0:lane==1?0.75:-0.75;
|
||||
const double sampleX=pose.x+nx*lateral;
|
||||
const double sampleY=pose.y+ny*lateral;
|
||||
const double expectedFloor=pose.z-passageTubeDepthAt(
|
||||
pose.radius,bodyFloorDepth);
|
||||
const double expectedFloor=pose.floorZ;
|
||||
++audit.expectedFloorSamples;
|
||||
++endpointFloorExpected[endpoint];
|
||||
if(passageInteriorWinsAt(sampleX,sampleY,expectedFloor+0.125,
|
||||
@@ -3596,7 +3461,7 @@ namespace StandaloneStreamBenchmark
|
||||
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,sampleX,sampleY,pose.z-pose.radius,floor);
|
||||
localDistance,lateral,sampleX,sampleY,pose.floorZ,floor);
|
||||
if(std::isfinite(floor))
|
||||
{
|
||||
floorProfiles[endpoint][lane].push_back({localDistance,floor});
|
||||
@@ -3608,7 +3473,11 @@ namespace StandaloneStreamBenchmark
|
||||
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);
|
||||
// The open-air half-pipe has side walls only one metre
|
||||
// above its floor. Probe at mid-wall height so this check
|
||||
// measures those walls instead of the absent roof-height
|
||||
// shell at the centreline.
|
||||
const double radius=findWallRadius(pose,sign*nx,sign*ny,0.50);
|
||||
if(std::isfinite(radius))
|
||||
{
|
||||
wallProfiles[endpoint][side].push_back({localDistance,radius});
|
||||
@@ -3684,8 +3553,8 @@ namespace StandaloneStreamBenchmark
|
||||
const auto& a=path[i-1];
|
||||
const auto& b=path[i];
|
||||
const double dx=b.x-a.x,dy=b.y-a.y;
|
||||
const double floorA=a.z-passageTubeDepthAt(a.radius,bodyFloorDepth)+1.13;
|
||||
const double floorB=b.z-passageTubeDepthAt(b.radius,bodyFloorDepth)+1.13;
|
||||
const double floorA=a.floorZ+1.13;
|
||||
const double floorB=b.floorZ+1.13;
|
||||
const double dz=floorB-floorA;
|
||||
const double horizontal=std::hypot(dx,dy);
|
||||
if(horizontal>1.0e-6&&std::abs(dz)/horizontal
|
||||
@@ -3761,18 +3630,17 @@ namespace StandaloneStreamBenchmark
|
||||
constexpr double FloorRippleAmplitudeLimitMetres=0.025;
|
||||
struct FPathPoint
|
||||
{
|
||||
double x=0.0,y=0.0,z=0.0,radius=0.0,distance=0.0;
|
||||
double x=0.0,y=0.0,z=0.0,floorZ=0.0,radius=0.0,distance=0.0;
|
||||
std::size_t segmentIndex=0;
|
||||
};
|
||||
const auto& passage=ResolvedLayout->passages[passageIndex];
|
||||
const auto& smoothRoute=allPassageSmoothRoutes[passageIndex];
|
||||
if(smoothRoute.size()<2)return audit;
|
||||
const double bodyFloorDepth=allPassageBodyFloorDepths[passageIndex];
|
||||
std::vector<FPathPoint> path;
|
||||
path.reserve(smoothRoute.size());
|
||||
for(const auto& point:smoothRoute)
|
||||
{
|
||||
path.push_back({point.x,point.y,point.z,point.radius,
|
||||
path.push_back({point.x,point.y,point.z,point.floorZ,point.radius,
|
||||
point.distance,point.segmentIndex});
|
||||
}
|
||||
const double total=path.back().distance;
|
||||
@@ -3787,7 +3655,8 @@ namespace StandaloneStreamBenchmark
|
||||
const double length=b.distance-a.distance;
|
||||
const double t=length>1.0e-9?(distance-a.distance)/length:0.0;
|
||||
return FPathPoint{std::lerp(a.x,b.x,t),std::lerp(a.y,b.y,t),
|
||||
std::lerp(a.z,b.z,t),std::lerp(a.radius,b.radius,t),distance,
|
||||
std::lerp(a.z,b.z,t),std::lerp(a.floorZ,b.floorZ,t),
|
||||
std::lerp(a.radius,b.radius,t),distance,
|
||||
b.segmentIndex};
|
||||
};
|
||||
const auto sinusoidAmplitude=[&](const std::vector<std::array<double,2>>& samples,
|
||||
@@ -3979,8 +3848,7 @@ namespace StandaloneStreamBenchmark
|
||||
// The support strip is segment-built and is not itself a true
|
||||
// signed-distance field, so a fixed centerline radius misses
|
||||
// the stepped top that the player actually walks on.
|
||||
const double expectedFloor=pose.z-passageTubeDepthAt(
|
||||
pose.radius,bodyFloorDepth);
|
||||
const double expectedFloor=pose.floorZ;
|
||||
const bool floorInteriorWins=passageInteriorWinsAt(pose.x,pose.y,
|
||||
expectedFloor+0.125,passageIndex,pose.segmentIndex);
|
||||
if(floorInteriorWins)
|
||||
@@ -4175,18 +4043,16 @@ namespace StandaloneStreamBenchmark
|
||||
value=std::clamp(value,0.0,1.0);
|
||||
return value*value*(3.0-2.0*value);
|
||||
};
|
||||
bool ambiguous=false,expectedSolid=false,interiorAir=false,mouthBlend=false;
|
||||
bool ambiguous=false,expectedSolid=false,mouthBlend=false;
|
||||
if(passageInteriorWinsAt(xMetres,yMetres,zMetres))return 0;
|
||||
for(std::size_t passageIndex=0;
|
||||
passageIndex<ResolvedLayout->passages.size();++passageIndex)
|
||||
{
|
||||
const auto& passage=ResolvedLayout->passages[passageIndex];
|
||||
if(passage.controlPoints.size()<2)continue;
|
||||
const double bodyDepth=allPassageBodyFloorDepths[passageIndex];
|
||||
const auto& path=allPassageSmoothRoutes[passageIndex];
|
||||
if(path.size()<2)continue;
|
||||
const double totalLength=path.back().distance;
|
||||
double bestFlatDistance=std::numeric_limits<double>::infinity();
|
||||
double bestFloorDistance=0.0,bestMouthDistance=0.0;
|
||||
for(std::size_t segment=1;segment<path.size();++segment)
|
||||
{
|
||||
const auto& a=path[segment-1];
|
||||
@@ -4203,46 +4069,33 @@ namespace StandaloneStreamBenchmark
|
||||
const double floorT=horizontalSquared>1.0e-12
|
||||
?std::clamp(((xMetres-ax)*dx+(yMetres-ay)*dy)
|
||||
/horizontalSquared,0.0,1.0):t;
|
||||
const double floorRadius=std::lerp(a.radius,b.radius,floorT);
|
||||
const double floorCenterZ=std::lerp(a.z,b.z,floorT);
|
||||
const double floorCenterZ=passageFloorAtXY(passageIndex,
|
||||
xMetres,yMetres,std::lerp(a.floorZ,b.floorZ,floorT));
|
||||
const double along=std::lerp(a.distance,b.distance,t);
|
||||
const double floorDepth=passageTubeDepthAt(floorRadius,bodyDepth);
|
||||
const double roundDistance=std::sqrt((xMetres-centerX)*(xMetres-centerX)
|
||||
+(yMetres-centerY)*(yMetres-centerY)
|
||||
+(zMetres-centerZ)*(zMetres-centerZ))-radius;
|
||||
const double floorDistance=floorCenterZ-floorDepth-zMetres;
|
||||
const double floorDistance=floorCenterZ-zMetres;
|
||||
const double blend=std::max(CornerBlendMetres-
|
||||
std::abs(roundDistance-floorDistance),0.0)/CornerBlendMetres;
|
||||
const double flatDistance=std::max(roundDistance,floorDistance)
|
||||
+blend*blend*blend*(CornerBlendMetres/6.0);
|
||||
const double mouthDistance=std::max(0.0,std::min(along,
|
||||
totalLength-along));
|
||||
if(flatDistance<bestFlatDistance)
|
||||
{
|
||||
bestFlatDistance=flatDistance;
|
||||
bestFloorDistance=floorDistance;
|
||||
bestMouthDistance=mouthDistance;
|
||||
}
|
||||
if(flatDistance<=0.0)continue;
|
||||
const double fade=std::clamp(mouthDistance/5.0,0.0,1.0);
|
||||
const double smoothFade=fade*fade*(3.0-2.0*fade);
|
||||
const double floorBlend=smoothstep(floorDistance/0.25);
|
||||
const double wallElevation=-floorDistance;
|
||||
const double wallThickness=wallElevation>=0.0&&wallElevation<=1.0
|
||||
?smoothFade:0.0;
|
||||
const double thickness=0.75*std::max(wallThickness,
|
||||
smoothFade*floorBlend);
|
||||
if(flatDistance+ReliefBoundMetres<thickness)expectedSolid=true;
|
||||
else if(flatDistance-ReliefBoundMetres<thickness)ambiguous=true;
|
||||
else if(mouthDistance<5.0)mouthBlend=true;
|
||||
}
|
||||
if(!std::isfinite(bestFlatDistance))continue;
|
||||
if(bestFlatDistance<=0.0)
|
||||
{
|
||||
interiorAir=true;
|
||||
continue;
|
||||
}
|
||||
const double fade=std::clamp(bestMouthDistance/5.0,0.0,1.0);
|
||||
const double smoothFade=fade*fade*(3.0-2.0*fade);
|
||||
const double floorBlend=smoothstep(bestFloorDistance/0.25);
|
||||
const double wallElevation=-bestFloorDistance;
|
||||
const double wallThickness=wallElevation>=0.0&&wallElevation<=1.0
|
||||
?smoothFade:0.0;
|
||||
const double floorThickness=smoothFade*floorBlend;
|
||||
const double thickness=0.75*std::max(wallThickness,floorThickness);
|
||||
if(bestFlatDistance+ReliefBoundMetres<thickness)expectedSolid=true;
|
||||
else if(bestFlatDistance-ReliefBoundMetres<thickness)ambiguous=true;
|
||||
else if(bestMouthDistance<5.0)mouthBlend=true;
|
||||
}
|
||||
if(interiorAir)return 0;
|
||||
if(expectedSolid)return 1;
|
||||
return ambiguous||mouthBlend?-1:0;
|
||||
};
|
||||
@@ -4298,12 +4151,8 @@ namespace StandaloneStreamBenchmark
|
||||
const double floorT=horizontalLengthSquared>1.0e-12
|
||||
?std::clamp(((sampleX-a.x)*dx+(sampleY-a.y)*dy)
|
||||
/horizontalLengthSquared,0.0,1.0):t;
|
||||
const double floorAlong=a.distance+alongLength*floorT;
|
||||
const double floorRadius=std::lerp(a.radius,b.radius,floorT);
|
||||
const double floorCenterZ=std::lerp(a.z,b.z,floorT);
|
||||
const double floorDepth=passageTubeDepthAt(floorRadius,
|
||||
bodyFloorDepth);
|
||||
const double floorZ=floorCenterZ-floorDepth;
|
||||
const double floorZ=passageFloorAtXY(passageIndex,
|
||||
sampleX,sampleY,std::lerp(a.floorZ,b.floorZ,floorT));
|
||||
const VoxelForgeCore::Vec3d position{
|
||||
sampleX/coreMetres,sampleY/coreMetres,
|
||||
(floorZ-depth)/coreMetres};
|
||||
@@ -4341,7 +4190,8 @@ namespace StandaloneStreamBenchmark
|
||||
}
|
||||
const double nx=-dy/tangentLength,ny=dx/tangentLength;
|
||||
const double floorDepth=passageTubeDepthAt(radius,bodyFloorDepth);
|
||||
const double floorZ=centerZ-floorDepth;
|
||||
const double floorZ=passageFloorAtXY(passageIndex,centerX,centerY,
|
||||
std::lerp(a.floorZ,b.floorZ,t));
|
||||
struct FWallProbe {double side=0.0,height=0.0;bool roof=false;};
|
||||
const std::array<FWallProbe,5> wallProbes{{
|
||||
{0.0,0.0,true},{-1.0,0.25,false},{1.0,0.25,false},
|
||||
@@ -4419,12 +4269,10 @@ namespace StandaloneStreamBenchmark
|
||||
{
|
||||
const auto& passage=ResolvedLayout->passages[passageIndex];
|
||||
const auto& smoothRoute=allPassageSmoothRoutes[passageIndex];
|
||||
const double bodyFloorDepth=allPassageBodyFloorDepths[passageIndex];
|
||||
std::vector<std::array<double,3>> path;path.reserve(smoothRoute.size());
|
||||
for(const auto& point:smoothRoute)
|
||||
{
|
||||
const double playerZ=point.z
|
||||
-passageTubeDepthAt(point.radius,bodyFloorDepth)+1.13;
|
||||
const double playerZ=point.floorZ+1.13;
|
||||
path.push_back({point.x/coreMetres,point.y/coreMetres,
|
||||
playerZ/coreMetres});
|
||||
}
|
||||
@@ -4581,10 +4429,19 @@ namespace StandaloneStreamBenchmark
|
||||
shellAudit.firstViolations[i][1],shellAudit.firstViolations[i][2],
|
||||
shellAudit.firstViolations[i][3]);
|
||||
for(std::size_t i=0;i<shellAudit.firstWallViolationCount;++i)
|
||||
std::printf("[PassageShellAuditSample] id=%s kind=wall_missing xyz_m=(%.3f,%.3f,%.3f) density=%.6f\n",
|
||||
{
|
||||
const auto& sample=shellAudit.firstWallViolations[i];
|
||||
const int expected=expectedPassageShellAt(sample[0],sample[1],sample[2]);
|
||||
const bool passageInterior=passageInteriorWinsAt(
|
||||
sample[0],sample[1],sample[2]);
|
||||
std::printf("[PassageShellAuditSample] id=%s kind=wall_missing xyz_m=(%.3f,%.3f,%.3f) density=%.6f expected=%d passage_interior=%s native_density=%.6f\n",
|
||||
passage.stableId.c_str(),shellAudit.firstWallViolations[i][0],
|
||||
shellAudit.firstWallViolations[i][1],shellAudit.firstWallViolations[i][2],
|
||||
shellAudit.firstWallViolations[i][3]);
|
||||
shellAudit.firstWallViolations[i][3],expected,
|
||||
passageInterior?"true":"false",
|
||||
blockedWorld.densityAt({sample[0]/coreMetres,
|
||||
sample[1]/coreMetres,sample[2]/coreMetres}));
|
||||
}
|
||||
for(std::size_t i=0;i<passageAudit.firstBlockedCount;++i)
|
||||
{
|
||||
const auto& sample=passageAudit.firstBlocked[i];
|
||||
@@ -4609,10 +4466,13 @@ namespace StandaloneStreamBenchmark
|
||||
const VoxelForgeCore::Vec3d nativePosition{sample[0]/coreMetres,
|
||||
sample[1]/coreMetres,sample[2]/coreMetres};
|
||||
const float nativeDensity=blockedWorld.densityAt(nativePosition);
|
||||
std::printf("[ResolvedPassageAuditSample] id=%s kind=blocked xyz_m=(%.3f,%.3f,%.3f) density=%.6f native_density=%.6f route_segment=%zu t=%.3f native_path=%s\n",
|
||||
const bool passageInterior=passageInteriorWinsAt(sample[0],sample[1],
|
||||
sample[2]);
|
||||
std::printf("[ResolvedPassageAuditSample] id=%s kind=blocked xyz_m=(%.3f,%.3f,%.3f) density=%.6f native_density=%.6f route_segment=%zu t=%.3f native_path=%s passage_interior=%s\n",
|
||||
passage.stableId.c_str(),sample[0],sample[1],sample[2],sample[3],
|
||||
nativeDensity,nearestSegment,nearestT,
|
||||
blockedWorld.densityDispatchPathAt(nativePosition).c_str());
|
||||
blockedWorld.densityDispatchPathAt(nativePosition).c_str(),
|
||||
passageInterior?"true":"false");
|
||||
}
|
||||
for(std::size_t i=0;i<passageAudit.firstUnsupportedCount;++i)
|
||||
{
|
||||
@@ -4729,36 +4589,35 @@ namespace StandaloneStreamBenchmark
|
||||
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]);
|
||||
{
|
||||
const auto& sample=routeAudit.firstBlocked[i];
|
||||
const VoxelForgeCore::Vec3d position{sample[0]/coreMetres,
|
||||
sample[1]/coreMetres,sample[2]/coreMetres};
|
||||
const float nativeDensity=blockedWorld.densityAt(position);
|
||||
const bool passageInterior=passageInteriorWinsAt(sample[0],sample[1],sample[2]);
|
||||
std::printf("[ResolvedStrateRouteAuditSample] strate=%d kind=blocked xyz_m=(%.3f,%.3f,%.3f) density=%.6f native_density=%.6f native_path=%s passage_interior=%s\n",
|
||||
stratum.strateIndex,sample[0],sample[1],sample[2],sample[3],
|
||||
nativeDensity,blockedWorld.densityDispatchPathAt(position).c_str(),
|
||||
passageInterior?"true":"false");
|
||||
}
|
||||
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)
|
||||
for(std::size_t passageIndex=0;
|
||||
passageIndex<ResolvedLayout->passages.size();++passageIndex)
|
||||
{
|
||||
const auto& passage=ResolvedLayout->passages[passageIndex];
|
||||
const auto& smoothRoute=allPassageSmoothRoutes[passageIndex];
|
||||
std::vector<std::array<double,3>> path;path.reserve(passage.controlPoints.size());
|
||||
const auto routeDistances=passageRouteDistances(passage);
|
||||
const double routeLength=routeDistances.empty()?0.0:routeDistances[
|
||||
std::clamp<std::size_t>(passage.lowerMouthControlIndex,1,
|
||||
passage.controlPoints.size()-1)];
|
||||
const double bodyFloorDepth=passageBodyFloorDepth(passage);
|
||||
const auto mouthFloorDepths=passageMouthFloorDepths(passage);
|
||||
const std::size_t routeEnd=std::clamp<std::size_t>(
|
||||
passage.lowerMouthControlIndex,1,passage.controlPoints.size()-1);
|
||||
for(std::size_t pointIndex=0;pointIndex<=routeEnd;++pointIndex)
|
||||
path.reserve(smoothRoute.size());
|
||||
for(const auto& point:smoothRoute)
|
||||
{
|
||||
const auto& point=passage.controlPoints[pointIndex];
|
||||
const double centerZ=qToMetres(point.positionSubvoxels[2]);
|
||||
const double floorDepth=passageFloorDepthAt(routeDistances[pointIndex],
|
||||
routeLength,qToMetres(point.radiusSubvoxels),bodyFloorDepth,
|
||||
mouthFloorDepths[0],mouthFloorDepths[1]);
|
||||
const double playerZ=centerZ-floorDepth+1.13;
|
||||
path.push_back({qToMetres(point.positionSubvoxels[0])/coreMetres,
|
||||
qToMetres(point.positionSubvoxels[1])/coreMetres,playerZ/coreMetres});
|
||||
const double playerZ=point.floorZ+1.13;
|
||||
path.push_back({point.x/coreMetres,point.y/coreMetres,
|
||||
playerZ/coreMetres});
|
||||
}
|
||||
const FWalkAudit blockedAudit=auditPlayerPath(blockedWorld,path,
|
||||
0.125,0.125,0.25,3,0.45);
|
||||
|
||||
Reference in new issue
Block a user