passages: dense centripetal spline + open-air half-pipe attempt (L111, failed audits)

Route resampled to ~1 m (310 -> 2,790 segments); open air writes a 0.75 m floor slab plus 1.0 m
side walls, no roof. The 3D spline overshoots between planned route points, raising floor grades
above 15 deg; a grade-limited variant left 2 unsupported samples on passage 0-1, and the shell/
ripple audits still model the old chord route. Stopped after two designs. 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:
Fr0zkaandClaude Opus 5.5 committed 2026-10-29 18:09:46 +01:00
1 parent 5ea01988c4
commit 5ec09d1ff1
5 files changed
+749 -439

No files matched your search

+441 -321
View File
@@ -3,6 +3,7 @@
#include "OwnerWorldLoader.h"
#include "StreamingWorld.h"
#include "LinuxScreenSpaceGate.h"
#include "../Core/src/VoxelForgeFieldShared.h"
#include <algorithm>
#include <array>
@@ -2967,6 +2968,52 @@ 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& 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 auto passageSplineKnotSpan=[](const std::array<double,4>& a,
const std::array<double,4>& b)
{
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)));
};
const auto passageSplinePoint=[&](
const VoxelForgeCore::ResolvedLayoutPassageV1& passage,
std::size_t span,double t)
{
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;
};
constexpr std::size_t PassageSplineArcSteps=32;
const auto passageBodyFloorDepth=[&](
const VoxelForgeCore::ResolvedLayoutPassageV1& passage)
{
@@ -2976,12 +3023,19 @@ namespace StandaloneStreamBenchmark
std::vector<double> distances(routeEnd+1,0.0);
for(std::size_t i=1;i<=routeEnd;++i)
{
const auto& a=passage.controlPoints[i-1].positionSubvoxels;
const auto& b=passage.controlPoints[i].positionSubvoxels;
const double dx=qToMetres(b[0]-a[0]);
const double dy=qToMetres(b[1]-a[1]);
const double dz=qToMetres(b[2]-a[2]);
distances[i]=distances[i-1]+std::sqrt(dx*dx+dy*dy+dz*dz);
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;
@@ -3007,8 +3061,23 @@ namespace StandaloneStreamBenchmark
const double lower=qToMetres(
passage.controlPoints[routeEnd].positionSubvoxels[2]
-passage.lowerMouthSubvoxels[2])+1.13;
return std::array<double,2>{{std::max(0.0,upper),
std::max(0.0,lower)}};
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,
@@ -3040,12 +3109,19 @@ namespace StandaloneStreamBenchmark
if(passage.controlPoints.size()<2)return distances;
for(std::size_t i=1;i<passage.controlPoints.size();++i)
{
const auto& a=passage.controlPoints[i-1].positionSubvoxels;
const auto& b=passage.controlPoints[i].positionSubvoxels;
const double dx=qToMetres(b[0]-a[0]);
const double dy=qToMetres(b[1]-a[1]);
const double dz=qToMetres(b[2]-a[2]);
distances[i]=distances[i-1]+std::sqrt(dx*dx+dy*dy+dz*dz);
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;
};
@@ -3063,76 +3139,234 @@ namespace StandaloneStreamBenchmark
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;
std::size_t segmentIndex=0;
};
const auto passageSmoothRoute=[&](
const VoxelForgeCore::ResolvedLayoutPassageV1& passage,
double bodyDepth,const std::array<double,2>& mouthFloorDepths)
{
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)
{
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)
{
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);
}
return route;
};
std::vector<std::vector<double>> allPassageRouteDistances;
std::vector<double> allPassageBodyFloorDepths;
std::vector<std::vector<FPassageAuditRoutePoint>> allPassageSmoothRoutes;
allPassageRouteDistances.reserve(ResolvedLayout->passages.size());
allPassageBodyFloorDepths.reserve(ResolvedLayout->passages.size());
allPassageSmoothRoutes.reserve(ResolvedLayout->passages.size());
for(const auto& passage:ResolvedLayout->passages)
{
allPassageRouteDistances.push_back(passageRouteDistances(passage));
allPassageBodyFloorDepths.push_back(passageBodyFloorDepth(passage));
const double bodyDepth=passageBodyFloorDepth(passage);
allPassageBodyFloorDepths.push_back(bodyDepth);
allPassageSmoothRoutes.push_back(passageSmoothRoute(passage,bodyDepth,
passageMouthFloorDepths(passage)));
}
const auto interpolatePassageRoute=[](
const std::vector<FPassageAuditRoutePoint>& route,double distance)
{
if(route.empty())return FPassageAuditRoutePoint{};
distance=std::clamp(distance,0.0,route.back().distance);
const auto upper=std::lower_bound(route.begin()+1,route.end(),distance,
[](const FPassageAuditRoutePoint& point,double value)
{return point.distance<value;});
const auto& b=*upper;
const auto& a=*(upper-1);
const double span=b.distance-a.distance;
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.radius,b.radius,t),distance,b.segmentIndex};
};
const auto passageInteriorWinsAt=[&](double xMetres,double yMetres,
double zMetres,std::size_t ignoredPassage=std::numeric_limits<std::size_t>::max(),
std::size_t ignoredSegment=std::numeric_limits<std::size_t>::max())
{
constexpr double CornerBlendMetres=0.50;
// Relief moves the curved surface by up to 0.21 m. Samples in this
// band around another segment's boundary are ambiguous shell overlaps;
// the composed union resolves them locally, with interior air winning.
constexpr double OverlapBandMetres=0.21;
constexpr double WallReliefMetres=0.21;
for(std::size_t passageIndex=0;
passageIndex<ResolvedLayout->passages.size();++passageIndex)
{
const auto& passage=ResolvedLayout->passages[passageIndex];
if(passage.controlPoints.size()<2)continue;
const std::size_t routeEnd=passage.controlPoints.size()-1;
const auto& distances=allPassageRouteDistances[passageIndex];
const double totalLength=distances[std::clamp<std::size_t>(
passage.lowerMouthControlIndex,1,passage.controlPoints.size()-1)];
const auto& route=allPassageSmoothRoutes[passageIndex];
if(route.size()<2)continue;
const double bodyDepth=allPassageBodyFloorDepths[passageIndex];
const auto mouthFloorDepths=passageMouthFloorDepths(passage);
for(std::size_t segment=1;segment<=routeEnd;++segment)
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();
for(std::size_t segment=1;segment<route.size();++segment)
{
if(passageIndex==ignoredPassage&&segment==ignoredSegment)continue;
const auto& a=passage.controlPoints[segment-1];
const auto& b=passage.controlPoints[segment];
const double ax=qToMetres(a.positionSubvoxels[0]);
const double ay=qToMetres(a.positionSubvoxels[1]);
const double az=passageEffectiveCenterZ(passage,segment-1,
distances,totalLength,bodyDepth,mouthFloorDepths);
const double dx=qToMetres(b.positionSubvoxels[0]-a.positionSubvoxels[0]);
const double dy=qToMetres(b.positionSubvoxels[1]-a.positionSubvoxels[1]);
const double bz=passageEffectiveCenterZ(passage,segment,
distances,totalLength,bodyDepth,mouthFloorDepths);
const double dz=bz-az;
const auto& a=route[segment-1];
const auto& b=route[segment];
if(passageIndex==ignoredPassage
&&b.segmentIndex==ignoredSegment)continue;
const double dx=b.x-a.x,dy=b.y-a.y,dz=b.z-a.z;
const double lengthSquared=dx*dx+dy*dy+dz*dz;
if(lengthSquared<=1.0e-12)continue;
const double t=std::clamp(((xMetres-ax)*dx+(yMetres-ay)*dy
+(zMetres-az)*dz)/lengthSquared,0.0,1.0);
const double centerX=ax+t*dx,centerY=ay+t*dy,centerZ=az+t*dz;
const double radius=std::lerp(qToMetres(a.radiusSubvoxels),
qToMetres(b.radiusSubvoxels),t);
const double t=std::clamp(((xMetres-a.x)*dx+(yMetres-a.y)*dy
+(zMetres-a.z)*dz)/lengthSquared,0.0,1.0);
const double centerX=a.x+t*dx,centerY=a.y+t*dy,centerZ=a.z+t*dz;
const double radius=std::lerp(a.radius,b.radius,t);
const double horizontalLengthSquared=dx*dx+dy*dy;
const double floorT=horizontalLengthSquared>1.0e-12
?std::clamp(((xMetres-ax)*dx+(yMetres-ay)*dy)
?std::clamp(((xMetres-a.x)*dx+(yMetres-a.y)*dy)
/horizontalLengthSquared,0.0,1.0):t;
const double floorCenterZ=az+floorT*dz;
const double floorRadius=std::lerp(qToMetres(a.radiusSubvoxels),
qToMetres(b.radiusSubvoxels),floorT);
const double along=distances[segment-1]
+std::sqrt(lengthSquared)*floorT;
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 roundDistance=std::sqrt((xMetres-centerX)*(xMetres-centerX)
+(yMetres-centerY)*(yMetres-centerY)
+(zMetres-centerZ)*(zMetres-centerZ))-radius;
const double floorDistance=floorCenterZ-floorDepth-zMetres;
const double floorPlaneZ=floorCenterZ-floorDepth;
const double floorDistance=floorPlaneZ-zMetres;
const double h=std::max(CornerBlendMetres-
std::abs(roundDistance-floorDistance),0.0)/CornerBlendMetres;
const double flatDistance=std::max(roundDistance,floorDistance)
+h*h*h*(CornerBlendMetres/6.0);
if(flatDistance<OverlapBandMetres)return true;
if(flatDistance<bestDistance)
{
bestDistance=flatDistance;
bestRoundDistance=roundDistance;
bestCenterZ=centerZ;
bestSegment=segment;
}
}
if(bestSegment==std::numeric_limits<std::size_t>::max())continue;
const auto& a=route[bestSegment-1];
const auto& b=route[bestSegment];
const double dx=b.x-a.x,dy=b.y-a.y,dz=b.z-a.z;
const double lengthSquared=dx*dx+dy*dy+dz*dz;
if(lengthSquared<=1.0e-12)continue;
const double t=std::clamp(((xMetres-a.x)*dx+(yMetres-a.y)*dy
+(zMetres-a.z)*dz)/lengthSquared,0.0,1.0);
const double radius=std::lerp(a.radius,b.radius,t);
const double centerX=a.x+t*dx,centerY=a.y+t*dy;
const double centerZ=a.z+t*dz;
const double distanceToCenter=std::max(1.0e-9,
bestRoundDistance+radius);
const double verticalWallAlignment=std::clamp(
-(zMetres-centerZ)/distanceToCenter,0.0,1.0);
const double wallReliefWeight=0.05+0.95
*(1.0-verticalWallAlignment);
const float relief=VF_FieldPassageWallRelief(
static_cast<float>(xMetres/coreMetres),
static_cast<float>(yMetres/coreMetres),
static_cast<float>(zMetres/coreMetres),
static_cast<std::uint32_t>(World.Seed));
const double roughRoundDistance=bestRoundDistance
+static_cast<double>(std::clamp(relief,-1.0f,1.0f))
*WallReliefMetres*wallReliefWeight;
const auto& aFloor=route[bestSegment-1];
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 blend=std::max(CornerBlendMetres-
std::abs(roughRoundDistance-floorDistance),0.0)/CornerBlendMetres;
const double shapedDistance=std::max(roughRoundDistance,floorDistance)
+blend*blend*blend*(CornerBlendMetres/6.0);
if(shapedDistance<=0.0)return true;
}
return false;
};
@@ -3438,31 +3672,20 @@ namespace StandaloneStreamBenchmark
>=PassageProfilePlateauMinimumMetres
&&l23TerraceControlResult.second>=PassageProfileStepThresholdMetres;
const auto auditPassageNaturalness=[&](const VoxelForgeCore::World& Field,
const VoxelForgeCore::ResolvedLayoutPassageV1& passage)
std::size_t passageIndex)
{
FPassageNaturalnessAudit audit;
const std::size_t end=std::min<std::size_t>(passage.lowerMouthControlIndex,
passage.controlPoints.size()-1);
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 auto& path=allPassageSmoothRoutes[passageIndex];
if(path.size()<3)return audit;
const double bodyFloorDepth=allPassageBodyFloorDepths[passageIndex];
double flatRun=0.0;
for(std::size_t i=1;i<=end;++i)
for(std::size_t i=1;i<path.size();++i)
{
const auto& a=passage.controlPoints[i-1];
const auto& b=passage.controlPoints[i];
const double dx=qToMetres(b.positionSubvoxels[0]-a.positionSubvoxels[0]);
const double dy=qToMetres(b.positionSubvoxels[1]-a.positionSubvoxels[1]);
const double floorA=qToMetres(a.positionSubvoxels[2])
-passageFloorDepthAt(routeDistances[i-1],totalRouteLength,
qToMetres(a.radiusSubvoxels),bodyFloorDepth,
mouthFloorDepths[0],mouthFloorDepths[1])+1.13;
const double floorB=qToMetres(b.positionSubvoxels[2])
-passageFloorDepthAt(routeDistances[i],totalRouteLength,
qToMetres(b.radiusSubvoxels),bodyFloorDepth,
mouthFloorDepths[0],mouthFloorDepths[1])+1.13;
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 dz=floorB-floorA;
const double horizontal=std::hypot(dx,dy);
if(horizontal>1.0e-6&&std::abs(dz)/horizontal
@@ -3471,28 +3694,31 @@ namespace StandaloneStreamBenchmark
audit.maximumFlatRunMetres,flatRun);}
else flatRun=0.0;
}
if(end>=6)
if(path.size()>=8)
{
const std::size_t sampleCount=std::min<std::size_t>(64,end-4);
const std::size_t sampleCount=std::min<std::size_t>(64,path.size()-4);
double minimumRadius=std::numeric_limits<double>::infinity();
double maximumRadius=-std::numeric_limits<double>::infinity();
for(std::size_t sample=0;sample<sampleCount;++sample)
{
const std::size_t index=2+(sample*(end-4))
const std::size_t index=2+(sample*(path.size()-5))
/std::max<std::size_t>(1,sampleCount-1);
const auto& point=passage.controlPoints[index];
const auto& before=passage.controlPoints[index-1];
const auto& after=passage.controlPoints[index+1];
const double tangentX=qToMetres(after.positionSubvoxels[0]-before.positionSubvoxels[0]);
const double tangentY=qToMetres(after.positionSubvoxels[1]-before.positionSubvoxels[1]);
const auto& point=path[index];
const auto& before=path[index-1];
const auto& after=path[index+1];
const double tangentX=after.x-before.x;
const double tangentY=after.y-before.y;
const double tangentLength=std::hypot(tangentX,tangentY);
if(tangentLength<=1.0e-6){++audit.missingWallSamples;continue;}
const double radiusMetres=qToMetres(point.radiusSubvoxels);
const double sampleRadius=std::max(0.20,radiusMetres-0.18);
const double centerX=qToMetres(point.positionSubvoxels[0]);
const double centerY=qToMetres(point.positionSubvoxels[1]);
const double centerZ=passageEffectiveCenterZ(passage,index,
routeDistances,totalRouteLength,bodyFloorDepth,mouthFloorDepths);
constexpr double WallProbeHeightMetres=0.75;
const double floorDepth=passageTubeDepthAt(point.radius,bodyFloorDepth);
const double vertical=WallProbeHeightMetres-floorDepth;
const double wallRadiusSquared=point.radius*point.radius-vertical*vertical;
if(wallRadiusSquared<=0.0){audit.missingWallSamples+=2;continue;}
const double sampleRadius=std::max(0.20,
std::sqrt(wallRadiusSquared)-0.18);
const double centerX=point.x,centerY=point.y;
const double centerZ=point.z-floorDepth+WallProbeHeightMetres;
const double nx=-tangentY/tangentLength,ny=tangentX/tangentLength;
for(const double side:{-1.0,1.0})
{
@@ -3523,7 +3749,6 @@ namespace StandaloneStreamBenchmark
return audit;
};
const auto auditPassageRipples=[&](const VoxelForgeCore::World& Field,
const VoxelForgeCore::ResolvedLayoutPassageV1& passage,
std::size_t passageIndex)
{
FPassageRippleAudit audit;
@@ -3539,33 +3764,16 @@ namespace StandaloneStreamBenchmark
double x=0.0,y=0.0,z=0.0,radius=0.0,distance=0.0;
std::size_t segmentIndex=0;
};
const std::size_t end=std::min<std::size_t>(passage.lowerMouthControlIndex,
passage.controlPoints.size()-1);
if(end<2)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 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(end+1);
for(std::size_t i=0;i<=end;++i)
path.reserve(smoothRoute.size());
for(const auto& point:smoothRoute)
{
const auto& point=passage.controlPoints[i];
FPathPoint value{qToMetres(point.positionSubvoxels[0]),
qToMetres(point.positionSubvoxels[1]),
passageEffectiveCenterZ(passage,i,routeDistances,
totalRouteLength,bodyFloorDepth,mouthFloorDepths),
qToMetres(point.radiusSubvoxels),0.0,i==0?1:i};
if(!path.empty())
{
const auto& previous=path.back();
value.distance=previous.distance+std::sqrt(
(value.x-previous.x)*(value.x-previous.x)
+(value.y-previous.y)*(value.y-previous.y)
+(value.z-previous.z)*(value.z-previous.z));
}
path.push_back(value);
path.push_back({point.x,point.y,point.z,point.radius,
point.distance,point.segmentIndex});
}
const double total=path.back().distance;
if(total<WindowLengthMetres)return audit;
@@ -3973,51 +4181,31 @@ namespace StandaloneStreamBenchmark
{
const auto& passage=ResolvedLayout->passages[passageIndex];
if(passage.controlPoints.size()<2)continue;
const std::size_t routeEnd=std::clamp<std::size_t>(
passage.lowerMouthControlIndex,1,passage.controlPoints.size()-1);
const auto& distances=allPassageRouteDistances[passageIndex];
const double totalLength=distances[std::clamp<std::size_t>(
passage.lowerMouthControlIndex,1,passage.controlPoints.size()-1)];
const double bodyDepth=allPassageBodyFloorDepths[passageIndex];
const auto mouthFloorDepths=passageMouthFloorDepths(passage);
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=0;segment<routeEnd;++segment)
for(std::size_t segment=1;segment<path.size();++segment)
{
const auto& a=passage.controlPoints[segment];
const auto& b=passage.controlPoints[segment+1];
const double ax=qToMetres(a.positionSubvoxels[0]);
const double ay=qToMetres(a.positionSubvoxels[1]);
const double az=passageEffectiveCenterZ(passage,segment,
distances,totalLength,bodyDepth,mouthFloorDepths);
const double dx=qToMetres(b.positionSubvoxels[0]-a.positionSubvoxels[0]);
const double dy=qToMetres(b.positionSubvoxels[1]-a.positionSubvoxels[1]);
const double bz=passageEffectiveCenterZ(passage,segment+1,
distances,totalLength,bodyDepth,mouthFloorDepths);
const double dz=bz-az;
const auto& a=path[segment-1];
const auto& b=path[segment];
const double ax=a.x,ay=a.y,az=a.z;
const double dx=b.x-a.x,dy=b.y-a.y,dz=b.z-a.z;
const double lengthSquared=dx*dx+dy*dy+dz*dz;
if(lengthSquared<=1.0e-12)continue;
const double t=std::clamp(((xMetres-ax)*dx+(yMetres-ay)*dy
+(zMetres-az)*dz)/lengthSquared,0.0,1.0);
const double centerX=ax+t*dx,centerY=ay+t*dy,centerZ=az+t*dz;
const double radius=std::lerp(qToMetres(a.radiusSubvoxels),
qToMetres(b.radiusSubvoxels),t);
const double radius=std::lerp(a.radius,b.radius,t);
const double horizontalSquared=dx*dx+dy*dy;
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(qToMetres(a.radiusSubvoxels),
qToMetres(b.radiusSubvoxels),floorT);
const double floorAlong=distances[segment]
+(distances[segment+1]-distances[segment])*floorT;
const double floorCenterZ=std::lerp(
qToMetres(a.positionSubvoxels[2]),
qToMetres(b.positionSubvoxels[2]),floorT)
-passageFloorDepthAt(floorAlong,totalLength,floorRadius,
bodyDepth,mouthFloorDepths[0],mouthFloorDepths[1])
+passageTubeDepthAt(floorRadius,bodyDepth);
const double along=distances[segment]
+(distances[segment+1]-distances[segment])*t;
const double floorRadius=std::lerp(a.radius,b.radius,floorT);
const double floorCenterZ=std::lerp(a.z,b.z,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)
@@ -4045,7 +4233,11 @@ namespace StandaloneStreamBenchmark
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 thickness=0.75*std::max(smoothFade,floorBlend);
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;
@@ -4056,19 +4248,18 @@ namespace StandaloneStreamBenchmark
};
const auto auditPassageShell=[&](const VoxelForgeCore::World& Field,
const VoxelForgeCore::World& Native,
const VoxelForgeCore::ResolvedLayoutPassageV1& passage)
const VoxelForgeCore::ResolvedLayoutPassageV1& passage,
std::size_t passageIndex)
{
FPassageShellAudit audit;
if(passage.controlPoints.size()<2)return audit;
const std::size_t routeEnd=std::clamp<std::size_t>(
passage.lowerMouthControlIndex,1,passage.controlPoints.size()-1);
const std::size_t geometryEnd=passage.controlPoints.size()-1;
const auto routeDistances=passageRouteDistances(passage);
const double totalRouteLength=routeDistances[std::clamp<std::size_t>(
passage.lowerMouthControlIndex,1,passage.controlPoints.size()-1)];
const double bodyFloorDepth=passageBodyFloorDepth(passage);
const auto mouthFloorDepths=passageMouthFloorDepths(passage);
const auto expectedShellThickness=[&](double along,double floorDistance)
const auto& route=allPassageSmoothRoutes[passageIndex];
if(route.size()<2)return audit;
const double totalRouteLength=route.back().distance;
const double bodyFloorDepth=allPassageBodyFloorDepths[passageIndex];
constexpr std::array<std::array<double,2>,5> floorOffsets{{
{{0.0,0.0}},{{0.30,0.0}},{{-0.30,0.0}},
{{0.0,0.30}},{{0.0,-0.30}}}};
const auto expectedFloorThickness=[&](double along,double floorDistance)
{
const double mouthDistance=std::max(0.0,std::min(along,
totalRouteLength-along));
@@ -4077,43 +4268,27 @@ namespace StandaloneStreamBenchmark
const double floorBlend=std::clamp(floorDistance/0.25,0.0,1.0);
const double smoothFloorBlend=floorBlend*floorBlend
*(3.0-2.0*floorBlend);
const double extensionFade=std::clamp(1.0
-(along-totalRouteLength)/5.0,0.0,1.0);
const double smoothExtensionFade=extensionFade*extensionFade
*(3.0-2.0*extensionFade);
return 0.75*std::max(smoothFade,
smoothFloorBlend*smoothExtensionFade);
return 0.75*smoothFade*smoothFloorBlend;
};
constexpr std::array<std::array<double,2>,5> floorOffsets{{
{{0.0,0.0}},{{0.30,0.0}},{{-0.30,0.0}},{{0.0,0.30}},{{0.0,-0.30}}}};
for(std::size_t segment=1;segment<=geometryEnd;++segment)
for(std::size_t segment=1;segment<route.size();++segment)
{
const auto& a=passage.controlPoints[segment-1];
const auto& b=passage.controlPoints[segment];
const double ax=qToMetres(a.positionSubvoxels[0]);
const double ay=qToMetres(a.positionSubvoxels[1]);
const double az=passageEffectiveCenterZ(passage,segment-1,
routeDistances,totalRouteLength,bodyFloorDepth,mouthFloorDepths);
const double bx=qToMetres(b.positionSubvoxels[0]);
const double by=qToMetres(b.positionSubvoxels[1]);
const double bz=passageEffectiveCenterZ(passage,segment,
routeDistances,totalRouteLength,bodyFloorDepth,mouthFloorDepths);
const double dx=bx-ax,dy=by-ay,dz=bz-az;
const auto& a=route[segment-1];
const auto& b=route[segment];
const double dx=b.x-a.x,dy=b.y-a.y,dz=b.z-a.z;
const double length=std::sqrt(dx*dx+dy*dy+dz*dz);
const double authoredSegmentLength=routeDistances[segment]
-routeDistances[segment-1];
const double alongLength=b.distance-a.distance;
const std::size_t intervals=std::max<std::size_t>(1,
static_cast<std::size_t>(std::ceil(length/PassageShellAuditSpacingMetres)));
for(std::size_t sampleIndex=0;sampleIndex<=intervals;++sampleIndex)
static_cast<std::size_t>(std::ceil(
length/PassageShellAuditSpacingMetres)));
for(std::size_t sampleIndex=segment==1?0:1;
sampleIndex<=intervals;++sampleIndex)
{
if(segment>1&&sampleIndex==0)continue;
const double t=static_cast<double>(sampleIndex)/intervals;
const double centerX=ax+dx*t,centerY=ay+dy*t,centerZ=az+dz*t;
const double along=routeDistances[segment-1]
+authoredSegmentLength*t;
const double radius=std::lerp(qToMetres(a.radiusSubvoxels),
qToMetres(b.radiusSubvoxels),t);
if(std::min(along,totalRouteLength-along)>0.0)
const double centerX=a.x+dx*t,centerY=a.y+dy*t,centerZ=a.z+dz*t;
const double along=a.distance+alongLength*t;
const double radius=std::lerp(a.radius,b.radius,t);
if(std::min(along,totalRouteLength-along)>0.0)
{
for(const auto& offset:floorOffsets)
for(const double depth:PassageShellAuditDepthsMetres)
{
@@ -4121,44 +4296,30 @@ namespace StandaloneStreamBenchmark
const double sampleX=centerX+offset[0];
const double sampleY=centerY+offset[1];
const double floorT=horizontalLengthSquared>1.0e-12
?std::clamp(((sampleX-ax)*dx+(sampleY-ay)*dy)
?std::clamp(((sampleX-a.x)*dx+(sampleY-a.y)*dy)
/horizontalLengthSquared,0.0,1.0):t;
const double floorAlong=routeDistances[segment-1]
+authoredSegmentLength*floorT;
const double floorRadius=std::lerp(
qToMetres(a.radiusSubvoxels),
qToMetres(b.radiusSubvoxels),floorT);
const double floorCenterZ=segment<=routeEnd
?std::lerp(qToMetres(a.positionSubvoxels[2]),
qToMetres(b.positionSubvoxels[2]),floorT)
-passageFloorDepthAt(floorAlong,totalRouteLength,
floorRadius,bodyFloorDepth,mouthFloorDepths[0],
mouthFloorDepths[1])
+passageTubeDepthAt(floorRadius,bodyFloorDepth)
:az+floorT*dz;
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 VoxelForgeCore::Vec3d position{
sampleX/coreMetres,
sampleY/coreMetres,
sampleX/coreMetres,sampleY/coreMetres,
(floorZ-depth)/coreMetres};
const double sampleZ=floorZ-depth;
if(passageInteriorWinsAt(sampleX,sampleY,sampleZ,
static_cast<std::size_t>(&passage-
ResolvedLayout->passages.data()),segment))
if(passageInteriorWinsAt(sampleX,sampleY,sampleZ))
{
++audit.floorInteriorWins;
continue;
}
++audit.floorSamples;
const float nativeFloor=Native.densityAt(position);
if(nativeFloor>=0.0f)
if(Native.densityAt(position)>=0.0f)
{
++audit.openAirSamples;
audit.openAirDistancesMetres.push_back(along);
const bool shellExpected=depth+0.011
<expectedShellThickness(along,depth);
<expectedFloorThickness(along,depth);
const float density=Field.densityAt(position);
if((shellExpected&&density>=0.0f)
||(!shellExpected&&density<0.0f))
@@ -4166,64 +4327,68 @@ namespace StandaloneStreamBenchmark
++audit.floorViolations;
if(audit.firstViolationCount<audit.firstViolations.size())
audit.firstViolations[audit.firstViolationCount++]={
centerX+offset[0],centerY+offset[1],floorZ-depth,density};
}
}
}
const double tangentLength=std::hypot(dx,dy);
if(tangentLength>1.0e-6)
{
if(std::min(along,totalRouteLength-along)<5.0)
{
audit.wallMouthBlendSkips+=5;
continue;
}
const double nx=-dy/tangentLength,ny=dx/tangentLength;
const std::array<std::array<double,3>,5> wallDirections{{
{{0.0,0.0,1.0}},
{{-0.7071067811865475*nx,-0.7071067811865475*ny,
0.7071067811865475}},
{{0.7071067811865475*nx,0.7071067811865475*ny,
0.7071067811865475}},
{{-nx,-ny,0.0}},{{nx,ny,0.0}}}};
for(const auto& direction:wallDirections)
{
const double sampleX=centerX+direction[0]*(radius+0.375);
const double sampleY=centerY+direction[1]*(radius+0.375);
const double sampleZ=centerZ+direction[2]*(radius+0.375);
const VoxelForgeCore::Vec3d wallPosition{sampleX/coreMetres,
sampleY/coreMetres,sampleZ/coreMetres};
const bool wallInteriorWins=passageInteriorWinsAt(sampleX,
sampleY,sampleZ,static_cast<std::size_t>(&passage-
ResolvedLayout->passages.data()),segment);
if(wallInteriorWins)++audit.wallInteriorWins;
else if(Native.densityAt(wallPosition)>=0.0f)
{
++audit.openAirSamples;
++audit.wallSamples;
audit.openAirDistancesMetres.push_back(along);
const float density=Field.densityAt(wallPosition);
// The largest authored wall-relief excursion is 0.21 m;
// test only where the shell must remain solid for every
// relief phase.
const int shellExpected=expectedPassageShellAt(
sampleX,sampleY,sampleZ);
if(shellExpected>=0
&&((shellExpected==1&&density>=0.0f)
||(shellExpected==0&&density<0.0f)))
{
++audit.wallViolations;
if(audit.firstWallViolationCount
<audit.firstWallViolations.size())
audit.firstWallViolations[
audit.firstWallViolationCount++]={
sampleX,sampleY,sampleZ,density};
}
else if(shellExpected<0)
++audit.wallMouthBlendSkips;
}
}
}
const double tangentLength=std::hypot(dx,dy);
if(tangentLength<=1.0e-6)continue;
if(std::min(along,totalRouteLength-along)<5.0)
{
audit.wallMouthBlendSkips+=5;
continue;
}
const double nx=-dy/tangentLength,ny=dx/tangentLength;
const double floorDepth=passageTubeDepthAt(radius,bodyFloorDepth);
const double floorZ=centerZ-floorDepth;
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},
{-1.0,0.75,false},{1.0,0.75,false}}};
for(const auto& probe:wallProbes)
{
double sampleX=centerX,sampleY=centerY,sampleZ=0.0;
if(probe.roof)
sampleZ=centerZ+radius+0.375;
else
{
const double vertical=probe.height-floorDepth;
const double wallRadiusSquared=radius*radius-vertical*vertical;
if(wallRadiusSquared<=0.0)continue;
const double wallRadius=std::sqrt(wallRadiusSquared);
sampleX+=nx*probe.side*(wallRadius+0.375);
sampleY+=ny*probe.side*(wallRadius+0.375);
sampleZ=floorZ+probe.height;
}
const VoxelForgeCore::Vec3d wallPosition{sampleX/coreMetres,
sampleY/coreMetres,sampleZ/coreMetres};
if(passageInteriorWinsAt(sampleX,sampleY,sampleZ,
passageIndex,b.segmentIndex))
{
++audit.wallInteriorWins;
continue;
}
if(Native.densityAt(wallPosition)<0.0f)continue;
++audit.openAirSamples;
++audit.wallSamples;
audit.openAirDistancesMetres.push_back(along);
const float density=Field.densityAt(wallPosition);
const int shellExpected=expectedPassageShellAt(
sampleX,sampleY,sampleZ);
if(shellExpected>=0
&&((shellExpected==1&&density>=0.0f)
||(shellExpected==0&&density<0.0f)))
{
++audit.wallViolations;
if(audit.firstWallViolationCount
<audit.firstWallViolations.size())
audit.firstWallViolations[audit.firstWallViolationCount++]={
sampleX,sampleY,sampleZ,density};
}
else if(shellExpected<0)
++audit.wallMouthBlendSkips;
}
++audit.interiorSamples;
const float interior=Field.densityAt({centerX/coreMetres,
centerY/coreMetres,centerZ/coreMetres});
@@ -4253,42 +4418,15 @@ namespace StandaloneStreamBenchmark
for(std::size_t passageIndex=0;passageIndex<ResolvedLayout->passages.size();++passageIndex)
{
const auto& passage=ResolvedLayout->passages[passageIndex];
std::vector<std::array<double,3>> path;path.reserve(passage.controlPoints.size());
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 segment=1;segment<=routeEnd;++segment)
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 auto& a=passage.controlPoints[segment-1];
const auto& b=passage.controlPoints[segment];
const double ax=qToMetres(a.positionSubvoxels[0]);
const double ay=qToMetres(a.positionSubvoxels[1]);
const double az=qToMetres(a.positionSubvoxels[2]);
const double bx=qToMetres(b.positionSubvoxels[0]);
const double by=qToMetres(b.positionSubvoxels[1]);
const double bz=qToMetres(b.positionSubvoxels[2]);
const double length=routeDistances[segment]-routeDistances[segment-1];
const std::size_t intervals=std::max<std::size_t>(1,
static_cast<std::size_t>(std::ceil(length/0.5)));
for(std::size_t step=segment==1?0:1;step<=intervals;++step)
{
const double t=static_cast<double>(step)/intervals;
const double along=routeDistances[segment-1]+length*t;
const double centerZ=std::lerp(az,bz,t);
const double radius=std::lerp(qToMetres(a.radiusSubvoxels),
qToMetres(b.radiusSubvoxels),t);
const double floorDepth=passageFloorDepthAt(along,routeLength,
radius,bodyFloorDepth,mouthFloorDepths[0],
mouthFloorDepths[1]);
const double playerZ=centerZ-floorDepth+1.13;
path.push_back({std::lerp(ax,bx,t)/coreMetres,
std::lerp(ay,by,t)/coreMetres,playerZ/coreMetres});
}
const double playerZ=point.z
-passageTubeDepthAt(point.radius,bodyFloorDepth)+1.13;
path.push_back({point.x/coreMetres,point.y/coreMetres,
playerZ/coreMetres});
}
// Probe below the capsule feet far enough to enter the new 0.75 m
// floor shell; the old quarter-metre search only checked the air
@@ -4298,7 +4436,8 @@ namespace StandaloneStreamBenchmark
{return passageInteriorWinsAt(x,y,z-0.25);};
const FWalkAudit passageAudit=auditPlayerPath(Shape,path,0.375,0.125,
1.125,3,0.45,passageSupportExemption);
const FPassageNaturalnessAudit naturalAudit=auditPassageNaturalness(Shape,passage);
const FPassageNaturalnessAudit naturalAudit=
auditPassageNaturalness(Shape,passageIndex);
const auto lowerProfileStratum=std::find_if(ResolvedLayout->strates.begin(),
ResolvedLayout->strates.end(),[&](const auto& stratum)
{return stratum.strateIndex==passage.lowerStrateIndex;});
@@ -4309,10 +4448,10 @@ namespace StandaloneStreamBenchmark
{return anchor.kind=="heightfield_profile";});
const FPassageMouthProfileAudit mouthProfileAudit=
auditPassageMouthProfiles(Shape,passage,profiledLowerSurface);
const FPassageRippleAudit rippleAudit=auditPassageRipples(Shape,passage,
passageIndex);
const FPassageRippleAudit rippleAudit=
auditPassageRipples(Shape,passageIndex);
const FPassageShellAudit shellAudit=auditPassageShell(Shape,blockedWorld,
passage);
passage,passageIndex);
std::vector<double> openAirDistances=shellAudit.openAirDistancesMetres;
std::sort(openAirDistances.begin(),openAirDistances.end());
openAirDistances.erase(std::unique(openAirDistances.begin(),
@@ -4326,29 +4465,10 @@ namespace StandaloneStreamBenchmark
const double startAlong=openAirDistances[first];
const double endAlong=openAirDistances[end-1];
const double midpoint=(startAlong+endAlong)*0.5;
const auto upper=std::lower_bound(routeDistances.begin()+1,
routeDistances.end(),midpoint);
const std::size_t segment=std::min<std::size_t>(
static_cast<std::size_t>(upper-routeDistances.begin())-1,
passage.controlPoints.size()-2);
const double segmentLength=routeDistances[segment+1]-routeDistances[segment];
const double t=segmentLength>1.0e-9
?std::clamp((midpoint-routeDistances[segment])/segmentLength,0.0,1.0):0.0;
const auto& a=passage.controlPoints[segment];
const auto& b=passage.controlPoints[segment+1];
const double effectiveAz=passageEffectiveCenterZ(passage,segment,
routeDistances,routeLength,bodyFloorDepth,mouthFloorDepths);
const double effectiveBz=passageEffectiveCenterZ(passage,segment+1,
routeDistances,routeLength,bodyFloorDepth,mouthFloorDepths);
const auto center=interpolatePassageRoute(smoothRoute,midpoint);
std::printf("[PassageOpenAirSection] id=%s section=%zu start_m=%.3f end_m=%.3f samples=%zu midpoint_xyz_m=(%.3f,%.3f,%.3f) midpoint_radius_m=%.3f capture_views_required=3\n",
passage.stableId.c_str(),section+1,startAlong,endAlong,end-first,
std::lerp(qToMetres(a.positionSubvoxels[0]),
qToMetres(b.positionSubvoxels[0]),t),
std::lerp(qToMetres(a.positionSubvoxels[1]),
qToMetres(b.positionSubvoxels[1]),t),
std::lerp(effectiveAz,effectiveBz,t),
std::lerp(qToMetres(a.radiusSubvoxels),
qToMetres(b.radiusSubvoxels),t));
center.x,center.y,center.z,center.radius);
first=end;
}
shellAudits+=1;