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:
1 parent
5ea01988c4
commit
5ec09d1ff1
5 files changed
+720
-410
No files matched your search
@@ -468,8 +468,9 @@ namespace VoxelForgeCore
|
|||||||
// Version 210 keeps bulk native-cave sampling on the scalar field path at strata edges.
|
// Version 210 keeps bulk native-cave sampling on the scalar field path at strata edges.
|
||||||
// Version 211 makes coordinate biasing in the shared cell hash unsigned, removing signed-overflow UB.
|
// Version 211 makes coordinate biasing in the shared cell hash unsigned, removing signed-overflow UB.
|
||||||
// Version 212 balances generated entry routes against existing walk lanes and preserves their shared treads.
|
// Version 212 balances generated entry routes against existing walk lanes and preserves their shared treads.
|
||||||
// Version 217 replaces passage supports with flat-floor hollow-shell geometry.
|
// Version 218 resamples passages on a smooth spline and uses open-air half-pipe shells.
|
||||||
inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 217;
|
// Version 218 sweeps passages around a dense smooth spline and limits exposed shells to a trough.
|
||||||
|
inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 218;
|
||||||
|
|
||||||
enum class FieldChannel : std::uint32_t
|
enum class FieldChannel : std::uint32_t
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1196,6 +1196,7 @@ public:
|
|||||||
constexpr double Q=static_cast<double>(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel);
|
constexpr double Q=static_cast<double>(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel);
|
||||||
constexpr double FloorShellThicknessMetres=0.75;
|
constexpr double FloorShellThicknessMetres=0.75;
|
||||||
constexpr double WallReliefMetres=0.21;
|
constexpr double WallReliefMetres=0.21;
|
||||||
|
constexpr double OpenAirSideWallHeightMetres=1.0;
|
||||||
constexpr double MouthShellBlendMetres=5.0;
|
constexpr double MouthShellBlendMetres=5.0;
|
||||||
const double qPerMetre=Q*1000000.0/
|
const double qPerMetre=Q*1000000.0/
|
||||||
std::max<std::uint32_t>(1,ResolvedLayout->coreVoxelSizeMicrometres);
|
std::max<std::uint32_t>(1,ResolvedLayout->coreVoxelSizeMicrometres);
|
||||||
@@ -1264,7 +1265,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
const std::ptrdiff_t lastIndex=static_cast<std::ptrdiff_t>(
|
const std::ptrdiff_t lastIndex=static_cast<std::ptrdiff_t>(
|
||||||
passage.controlPoints.size())-1;
|
PassageSegmentData[passageIndex].size())-1;
|
||||||
double bestDistance=std::numeric_limits<double>::infinity();
|
double bestDistance=std::numeric_limits<double>::infinity();
|
||||||
const auto visitSegment=[&](std::size_t segmentIndex,int)
|
const auto visitSegment=[&](std::size_t segmentIndex,int)
|
||||||
{
|
{
|
||||||
@@ -1355,11 +1356,14 @@ public:
|
|||||||
const double floorBlend=std::clamp(floorDistanceMetres/0.25,0.0,1.0);
|
const double floorBlend=std::clamp(floorDistanceMetres/0.25,0.0,1.0);
|
||||||
const double smoothFloorBlend=floorBlend*floorBlend
|
const double smoothFloorBlend=floorBlend*floorBlend
|
||||||
*(3.0-2.0*floorBlend);
|
*(3.0-2.0*floorBlend);
|
||||||
const double mouthWallThickness=segmentIndex
|
const double wallElevationMetres=(qz-(floorCenterZ-floorDepth))
|
||||||
<constants.routeEndControlIndex
|
/qPerMetre;
|
||||||
|
const double mouthWallThickness=segment.routeSegment
|
||||||
|
&&wallElevationMetres>=0.0
|
||||||
|
&&wallElevationMetres<=OpenAirSideWallHeightMetres
|
||||||
?smoothShellFade*shellThickness:0.0;
|
?smoothShellFade*shellThickness:0.0;
|
||||||
double floorExtensionFade=1.0;
|
double floorExtensionFade=1.0;
|
||||||
if(segmentIndex>=constants.routeEndControlIndex)
|
if(!segment.routeSegment)
|
||||||
{
|
{
|
||||||
const double pastMouthMetres=std::max(0.0,alongMetres
|
const double pastMouthMetres=std::max(0.0,alongMetres
|
||||||
-constants.totalPathLengthMetres);
|
-constants.totalPathLengthMetres);
|
||||||
@@ -1417,11 +1421,14 @@ public:
|
|||||||
const double floorBlend=std::clamp(floorDistanceMetres/0.25,0.0,1.0);
|
const double floorBlend=std::clamp(floorDistanceMetres/0.25,0.0,1.0);
|
||||||
const double smoothFloorBlend=floorBlend*floorBlend
|
const double smoothFloorBlend=floorBlend*floorBlend
|
||||||
*(3.0-2.0*floorBlend);
|
*(3.0-2.0*floorBlend);
|
||||||
const double mouthWallThickness=nearest.segmentIndex
|
const double wallElevationMetres=(qz-(nearest.floorCenterZ-nearest.floorDepth))
|
||||||
<constants.routeEndControlIndex
|
/qPerMetre;
|
||||||
|
const double mouthWallThickness=segment.routeSegment
|
||||||
|
&&wallElevationMetres>=0.0
|
||||||
|
&&wallElevationMetres<=OpenAirSideWallHeightMetres
|
||||||
?smoothShellFade*shellThickness:0.0;
|
?smoothShellFade*shellThickness:0.0;
|
||||||
double floorExtensionFade=1.0;
|
double floorExtensionFade=1.0;
|
||||||
if(nearest.segmentIndex>=constants.routeEndControlIndex)
|
if(!segment.routeSegment)
|
||||||
{
|
{
|
||||||
const double pastMouthMetres=std::max(0.0,alongMetres
|
const double pastMouthMetres=std::max(0.0,alongMetres
|
||||||
-constants.totalPathLengthMetres);
|
-constants.totalPathLengthMetres);
|
||||||
@@ -1446,15 +1453,11 @@ public:
|
|||||||
// solid only in air, so it leaves host rock unchanged. Taking the union
|
// solid only in air, so it leaves host rock unchanged. Taking the union
|
||||||
// before writing either polarity makes every tunnel interior beat every
|
// before writing either polarity makes every tunnel interior beat every
|
||||||
// overlapping tunnel shell.
|
// overlapping tunnel shell.
|
||||||
// Positive native graph distance is authored tunnel interior. Blend it
|
// Preserve native graph air inside the passage interior before any shell
|
||||||
// into the same air union before any passage shell can change its sign.
|
// can change its sign. Outside that interior, the shell may cover host air.
|
||||||
// This also preserves the cave interior across a shared-layer boundary
|
if(nativeTunnelFieldDensity>=0.0f&&unionDistance<=0.0)
|
||||||
// blend, which may be solid on the passage's side of that interface.
|
|
||||||
if(nativeTunnelFieldDensity>=0.0f)
|
|
||||||
{
|
{
|
||||||
const float passageAir=unionDistance<=0.0
|
const float passageAir=static_cast<float>(-unionDistance/Q);
|
||||||
?static_cast<float>(-unionDistance/Q)
|
|
||||||
:std::numeric_limits<float>::lowest();
|
|
||||||
return std::max(nativeTunnelFieldDensity,passageAir);
|
return std::max(nativeTunnelFieldDensity,passageAir);
|
||||||
}
|
}
|
||||||
if(nativeFloorSupport!=std::numeric_limits<float>::lowest()
|
if(nativeFloorSupport!=std::numeric_limits<float>::lowest()
|
||||||
@@ -3377,7 +3380,38 @@ private:
|
|||||||
double radiusA=0.0,radiusB=0.0,radiusDelta=0.0;
|
double radiusA=0.0,radiusB=0.0,radiusDelta=0.0;
|
||||||
double lengthSq=0.0,horizontalLengthSq=0.0;
|
double lengthSq=0.0,horizontalLengthSq=0.0;
|
||||||
double lengthSubvoxels=0.0,horizontalLength=0.0;
|
double lengthSubvoxels=0.0,horizontalLength=0.0;
|
||||||
|
bool routeSegment=false;
|
||||||
};
|
};
|
||||||
|
static double PassageCentripetalKnotSpan(
|
||||||
|
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,DeterministicSqrt(DeterministicSqrt(
|
||||||
|
dx*dx+dy*dy+dz*dz)));
|
||||||
|
}
|
||||||
|
static std::array<double,4> PassageCentripetalCatmullRom(
|
||||||
|
const std::array<double,4>& p0,const std::array<double,4>& p1,
|
||||||
|
const std::array<double,4>& p2,const std::array<double,4>& p3,
|
||||||
|
double t,double dt0,double dt1,double dt2)
|
||||||
|
{
|
||||||
|
std::array<double,4> result{};
|
||||||
|
const double t2=t*t,t3=t2*t;
|
||||||
|
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;
|
||||||
|
}
|
||||||
static double PassageFlatFloorDepthSubvoxels(
|
static double PassageFlatFloorDepthSubvoxels(
|
||||||
const FPassageComposerConstants& constants,std::size_t segmentIndex,double t,
|
const FPassageComposerConstants& constants,std::size_t segmentIndex,double t,
|
||||||
double radiusSubvoxels,double qPerMetre)
|
double radiusSubvoxels,double qPerMetre)
|
||||||
@@ -3411,7 +3445,7 @@ private:
|
|||||||
const FPassageComposerConstants& constants,std::size_t segmentIndex,double floorT,
|
const FPassageComposerConstants& constants,std::size_t segmentIndex,double floorT,
|
||||||
const FPassageSegmentData& segment,double floorDepthSubvoxels,double qPerMetre)
|
const FPassageSegmentData& segment,double floorDepthSubvoxels,double qPerMetre)
|
||||||
{
|
{
|
||||||
if(segmentIndex>=constants.routeEndControlIndex||qPerMetre<=0.0)
|
if(!segment.routeSegment||qPerMetre<=0.0)
|
||||||
return segment.a[2]+floorT*segment.delta[2];
|
return segment.a[2]+floorT*segment.delta[2];
|
||||||
const double radiusMetres=(segment.radiusA+floorT*segment.radiusDelta)/qPerMetre;
|
const double radiusMetres=(segment.radiusA+floorT*segment.radiusDelta)/qPerMetre;
|
||||||
const double alongMetres=segment.authoredAlongMetres
|
const double alongMetres=segment.authoredAlongMetres
|
||||||
@@ -4222,13 +4256,11 @@ private:
|
|||||||
|
|
||||||
int BuildPassageNearestNode(std::size_t passageIndex,std::vector<std::size_t> segments)
|
int BuildPassageNearestNode(std::size_t passageIndex,std::vector<std::size_t> segments)
|
||||||
{
|
{
|
||||||
const auto& points=ResolvedLayout->passages[passageIndex].controlPoints;
|
|
||||||
FPassageNearestNode node;
|
FPassageNearestNode node;
|
||||||
node.minimum.fill(std::numeric_limits<double>::infinity());
|
node.minimum.fill(std::numeric_limits<double>::infinity());
|
||||||
node.maximum.fill(-std::numeric_limits<double>::infinity());
|
node.maximum.fill(-std::numeric_limits<double>::infinity());
|
||||||
for(const std::size_t segment:segments)
|
for(const std::size_t segment:segments)
|
||||||
{
|
{
|
||||||
const auto& a=points[segment];const auto& b=points[segment+1];
|
|
||||||
const auto& prepared=PassageSegmentData[passageIndex][segment];
|
const auto& prepared=PassageSegmentData[passageIndex][segment];
|
||||||
for(std::size_t axis=0;axis<3;++axis)
|
for(std::size_t axis=0;axis<3;++axis)
|
||||||
{
|
{
|
||||||
@@ -4238,7 +4270,7 @@ private:
|
|||||||
prepared.a[axis],prepared.b[axis]});
|
prepared.a[axis],prepared.b[axis]});
|
||||||
}
|
}
|
||||||
node.maximumRadius=std::max({node.maximumRadius,
|
node.maximumRadius=std::max({node.maximumRadius,
|
||||||
static_cast<double>(a.radiusSubvoxels),static_cast<double>(b.radiusSubvoxels)});
|
prepared.radiusA,prepared.radiusB});
|
||||||
}
|
}
|
||||||
node.minimumSegment=*std::min_element(segments.begin(),segments.end());
|
node.minimumSegment=*std::min_element(segments.begin(),segments.end());
|
||||||
node.maximumSegment=*std::max_element(segments.begin(),segments.end());
|
node.maximumSegment=*std::max_element(segments.begin(),segments.end());
|
||||||
@@ -4258,8 +4290,8 @@ private:
|
|||||||
-PassageNearestNodes[passageIndex][nodeIndex].minimum[splitAxis])splitAxis=axis;
|
-PassageNearestNodes[passageIndex][nodeIndex].minimum[splitAxis])splitAxis=axis;
|
||||||
const std::size_t middle=segments.size()/2;
|
const std::size_t middle=segments.size()/2;
|
||||||
const auto center=[&](std::size_t segment)
|
const auto center=[&](std::size_t segment)
|
||||||
{return static_cast<double>(points[segment].positionSubvoxels[splitAxis])
|
{const auto& prepared=PassageSegmentData[passageIndex][segment];
|
||||||
+static_cast<double>(points[segment+1].positionSubvoxels[splitAxis]);};
|
return prepared.a[splitAxis]+prepared.b[splitAxis];};
|
||||||
std::nth_element(segments.begin(),segments.begin()+middle,segments.end(),
|
std::nth_element(segments.begin(),segments.begin()+middle,segments.end(),
|
||||||
[&](std::size_t a,std::size_t b)
|
[&](std::size_t a,std::size_t b)
|
||||||
{const double ca=center(a),cb=center(b);return ca==cb?a<b:ca<cb;});
|
{const double ca=center(a),cb=center(b);return ca==cb?a<b:ca<cb;});
|
||||||
@@ -4497,7 +4529,7 @@ private:
|
|||||||
if(!ResolvedLayout)
|
if(!ResolvedLayout)
|
||||||
{
|
{
|
||||||
if(tracePreparation)
|
if(tracePreparation)
|
||||||
std::printf("[L98PassagePrepare] passages=0 segments=0 bytes=0 prepare_ms=%.3f\n",
|
std::printf("[L111PassagePrepare] passages=0 control_spans=0 segments=0 spacing_m=1.00 bytes=0 prepare_ms=%.3f\n",
|
||||||
std::chrono::duration<double,std::milli>(
|
std::chrono::duration<double,std::milli>(
|
||||||
std::chrono::steady_clock::now()-preparationStart).count());
|
std::chrono::steady_clock::now()-preparationStart).count());
|
||||||
return;
|
return;
|
||||||
@@ -4508,7 +4540,9 @@ private:
|
|||||||
constexpr double Q=static_cast<double>(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel);
|
constexpr double Q=static_cast<double>(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel);
|
||||||
const double qPerMetre=Q*1000000.0/
|
const double qPerMetre=Q*1000000.0/
|
||||||
std::max<std::uint32_t>(1,ResolvedLayout->coreVoxelSizeMicrometres);
|
std::max<std::uint32_t>(1,ResolvedLayout->coreVoxelSizeMicrometres);
|
||||||
std::size_t preparedSegments=0;
|
constexpr double PassageSegmentSpacingMetres=1.0;
|
||||||
|
constexpr std::size_t ArcLengthTableSteps=32;
|
||||||
|
std::size_t preparedSegments=0,controlSpans=0;
|
||||||
for(std::size_t passageIndex=0;passageIndex<ResolvedLayout->passages.size();
|
for(std::size_t passageIndex=0;passageIndex<ResolvedLayout->passages.size();
|
||||||
++passageIndex)
|
++passageIndex)
|
||||||
{
|
{
|
||||||
@@ -4519,50 +4553,104 @@ private:
|
|||||||
PassageSegmentOffsets[passageIndex+1]=preparedSegments;
|
PassageSegmentOffsets[passageIndex+1]=preparedSegments;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
auto& segmentData=PassageSegmentData[passageIndex];
|
|
||||||
segmentData.resize(passage.controlPoints.size()-1);
|
|
||||||
const std::size_t routeEnd=std::clamp<std::size_t>(
|
const std::size_t routeEnd=std::clamp<std::size_t>(
|
||||||
passage.lowerMouthControlIndex,1,passage.controlPoints.size()-1);
|
passage.lowerMouthControlIndex,1,passage.controlPoints.size()-1);
|
||||||
std::vector<double> radiiMetres;
|
|
||||||
radiiMetres.reserve(routeEnd+1);
|
|
||||||
for(std::size_t pointIndex=0;pointIndex<=routeEnd;++pointIndex)
|
|
||||||
{
|
|
||||||
radiiMetres.push_back(static_cast<double>(
|
|
||||||
passage.controlPoints[pointIndex].radiusSubvoxels)/qPerMetre);
|
|
||||||
}
|
|
||||||
auto& constants=PassageComposerConstants[passageIndex];
|
auto& constants=PassageComposerConstants[passageIndex];
|
||||||
constants.routeEndControlIndex=routeEnd;
|
constants.routeEndControlIndex=routeEnd;
|
||||||
std::vector<double> authoredPathDistanceMetres(
|
|
||||||
passage.controlPoints.size(),0.0);
|
struct FRawPassageSegment
|
||||||
for(std::size_t pointIndex=1;pointIndex<passage.controlPoints.size();
|
|
||||||
++pointIndex)
|
|
||||||
{
|
{
|
||||||
const auto& a=passage.controlPoints[pointIndex-1].positionSubvoxels;
|
std::array<double,4> a{},b{};
|
||||||
const auto& b=passage.controlPoints[pointIndex].positionSubvoxels;
|
double alongMetres=0.0,lengthMetres=0.0;
|
||||||
const double dx=static_cast<double>(b[0]-a[0])/qPerMetre;
|
std::size_t span=0;
|
||||||
const double dy=static_cast<double>(b[1]-a[1])/qPerMetre;
|
double tA=0.0,tB=1.0;
|
||||||
const double dz=static_cast<double>(b[2]-a[2])/qPerMetre;
|
bool routeSegment=false;
|
||||||
authoredPathDistanceMetres[pointIndex]=
|
};
|
||||||
authoredPathDistanceMetres[pointIndex-1]
|
std::vector<std::array<double,4>> controls;
|
||||||
|
controls.reserve(passage.controlPoints.size());
|
||||||
|
for(const auto& point:passage.controlPoints)
|
||||||
|
controls.push_back({static_cast<double>(point.positionSubvoxels[0]),
|
||||||
|
static_cast<double>(point.positionSubvoxels[1]),
|
||||||
|
static_cast<double>(point.positionSubvoxels[2]),
|
||||||
|
static_cast<double>(point.radiusSubvoxels)});
|
||||||
|
std::vector<FRawPassageSegment> rawSegments;
|
||||||
|
rawSegments.reserve((passage.controlPoints.size()-1)*10);
|
||||||
|
std::vector<std::array<double,2>> routeRadiusSamples;
|
||||||
|
routeRadiusSamples.push_back({0.0,controls.front()[3]/qPerMetre});
|
||||||
|
double alongMetres=0.0,authoredTotalPathLengthMetres=0.0;
|
||||||
|
const auto curvePoint=[&](std::size_t span,double t)
|
||||||
|
{
|
||||||
|
const auto& p1=controls[span];
|
||||||
|
const auto& p2=controls[span+1];
|
||||||
|
const auto& p0=controls[span==0?0:span-1];
|
||||||
|
const auto& p3=controls[std::min(span+2,controls.size()-1)];
|
||||||
|
return PassageCentripetalCatmullRom(p0,p1,p2,p3,t,
|
||||||
|
PassageCentripetalKnotSpan(p0,p1),
|
||||||
|
PassageCentripetalKnotSpan(p1,p2),
|
||||||
|
PassageCentripetalKnotSpan(p2,p3));
|
||||||
|
};
|
||||||
|
for(std::size_t span=0;span+1<controls.size();++span)
|
||||||
|
{
|
||||||
|
++controlSpans;
|
||||||
|
std::array<double,ArcLengthTableSteps+1> arcLength{};
|
||||||
|
auto previous=curvePoint(span,0.0);
|
||||||
|
for(std::size_t step=1;step<=ArcLengthTableSteps;++step)
|
||||||
|
{
|
||||||
|
const auto current=curvePoint(span,
|
||||||
|
static_cast<double>(step)/ArcLengthTableSteps);
|
||||||
|
const double dx=(current[0]-previous[0])/qPerMetre;
|
||||||
|
const double dy=(current[1]-previous[1])/qPerMetre;
|
||||||
|
const double dz=(current[2]-previous[2])/qPerMetre;
|
||||||
|
arcLength[step]=arcLength[step-1]
|
||||||
+DeterministicSqrt(dx*dx+dy*dy+dz*dz);
|
+DeterministicSqrt(dx*dx+dy*dy+dz*dz);
|
||||||
|
previous=current;
|
||||||
|
}
|
||||||
|
const double spanLength=arcLength.back();
|
||||||
|
const std::size_t segmentCount=std::max<std::size_t>(1,
|
||||||
|
static_cast<std::size_t>(std::ceil(
|
||||||
|
spanLength/PassageSegmentSpacingMetres)));
|
||||||
|
auto a=curvePoint(span,0.0);
|
||||||
|
double aT=0.0;
|
||||||
|
for(std::size_t segment=1;segment<=segmentCount;++segment)
|
||||||
|
{
|
||||||
|
const double targetArc=spanLength*static_cast<double>(segment)
|
||||||
|
/segmentCount;
|
||||||
|
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)
|
||||||
|
/ArcLengthTableSteps;
|
||||||
|
const auto b=curvePoint(span,t);
|
||||||
|
const double dx=(b[0]-a[0])/qPerMetre;
|
||||||
|
const double dy=(b[1]-a[1])/qPerMetre;
|
||||||
|
const double dz=(b[2]-a[2])/qPerMetre;
|
||||||
|
const double length=DeterministicSqrt(dx*dx+dy*dy+dz*dz);
|
||||||
|
rawSegments.push_back({a,b,alongMetres,length,span,aT,t,span<routeEnd});
|
||||||
|
alongMetres+=length;
|
||||||
|
if(span<routeEnd)
|
||||||
|
routeRadiusSamples.push_back({alongMetres,b[3]/qPerMetre});
|
||||||
|
a=b;
|
||||||
|
aT=t;
|
||||||
|
}
|
||||||
|
if(span+1==routeEnd)
|
||||||
|
authoredTotalPathLengthMetres=alongMetres;
|
||||||
}
|
}
|
||||||
const double authoredTotalPathLengthMetres=
|
|
||||||
authoredPathDistanceMetres[routeEnd];
|
|
||||||
constants.authoredTotalPathLengthMetres=authoredTotalPathLengthMetres;
|
constants.authoredTotalPathLengthMetres=authoredTotalPathLengthMetres;
|
||||||
double minimumBodyRadius=std::numeric_limits<double>::infinity();
|
double minimumBodyRadius=std::numeric_limits<double>::infinity();
|
||||||
double minimumRadius=std::numeric_limits<double>::infinity();
|
double minimumRadius=std::numeric_limits<double>::infinity();
|
||||||
for(std::size_t pointIndex=0;pointIndex<=routeEnd;++pointIndex)
|
for(const auto& sample:routeRadiusSamples)
|
||||||
{
|
{
|
||||||
const double radius=radiiMetres[pointIndex];
|
minimumRadius=std::min(minimumRadius,sample[1]);
|
||||||
minimumRadius=std::min(minimumRadius,radius);
|
if(sample[0]>=10.0&&authoredTotalPathLengthMetres-sample[0]>=10.0)
|
||||||
const double along=authoredPathDistanceMetres[pointIndex];
|
minimumBodyRadius=std::min(minimumBodyRadius,sample[1]);
|
||||||
if(along>=10.0&&authoredTotalPathLengthMetres-along>=10.0)
|
|
||||||
minimumBodyRadius=std::min(minimumBodyRadius,radius);
|
|
||||||
}
|
}
|
||||||
if(!std::isfinite(minimumBodyRadius))minimumBodyRadius=minimumRadius;
|
if(!std::isfinite(minimumBodyRadius))minimumBodyRadius=minimumRadius;
|
||||||
// One fixed flat-floor depth through the body. The authored grade
|
// Keep one flat-floor depth through the body and ease it into both mouths.
|
||||||
// constraint follows centreline-radius+0.23 m; shift the swept centreline
|
|
||||||
// where a taper changes radius so this floor keeps that same route.
|
|
||||||
constants.floorDepthMetres=std::max(0.5,minimumBodyRadius-0.23);
|
constants.floorDepthMetres=std::max(0.5,minimumBodyRadius-0.23);
|
||||||
constants.upperMouthFloorDepthMetres=std::max(0.0,
|
constants.upperMouthFloorDepthMetres=std::max(0.0,
|
||||||
static_cast<double>(passage.controlPoints.front().positionSubvoxels[2]
|
static_cast<double>(passage.controlPoints.front().positionSubvoxels[2]
|
||||||
@@ -4586,76 +4674,135 @@ private:
|
|||||||
constants.lowerMouthFloorDepthMetres=std::max(0.0,
|
constants.lowerMouthFloorDepthMetres=std::max(0.0,
|
||||||
routeFloorMetres-nativeFloorMetres);
|
routeFloorMetres-nativeFloorMetres);
|
||||||
}
|
}
|
||||||
const auto routeFloorDepth=[&](std::size_t pointIndex,double radius)
|
|
||||||
|
struct FRouteFloorSample
|
||||||
{
|
{
|
||||||
const double localRadiusDepth=std::max(0.0,radius-0.23);
|
std::array<double,4> point{};
|
||||||
const double along=authoredPathDistanceMetres[pointIndex];
|
double alongMetres=0.0,desiredFloorZMetres=0.0;
|
||||||
const double mouthDistance=std::max(0.0,std::min(along,
|
|
||||||
authoredTotalPathLengthMetres-along));
|
|
||||||
const double linear=std::clamp(mouthDistance/5.0,0.0,1.0);
|
|
||||||
const double ramp=linear*linear*(3.0-2.0*linear);
|
|
||||||
const double target=along<=authoredTotalPathLengthMetres*0.5
|
|
||||||
?constants.upperMouthFloorDepthMetres
|
|
||||||
:constants.lowerMouthFloorDepthMetres;
|
|
||||||
const double mouthDepth=std::clamp(target,0.0,std::max(0.0,radius));
|
|
||||||
return std::lerp(mouthDepth,localRadiusDepth,ramp);
|
|
||||||
};
|
};
|
||||||
std::vector<double> effectiveCenterZSubvoxels(
|
std::vector<FRouteFloorSample> routeFloorSamples;
|
||||||
passage.controlPoints.size(),0.0);
|
routeFloorSamples.reserve(rawSegments.size()+1);
|
||||||
for(std::size_t pointIndex=0;pointIndex<passage.controlPoints.size();
|
const auto appendRouteFloorSample=[&](const std::array<double,4>& point,
|
||||||
++pointIndex)
|
double along)
|
||||||
{
|
{
|
||||||
const double radius=pointIndex<=routeEnd
|
const double radiusMetres=point[3]/qPerMetre;
|
||||||
?radiiMetres[pointIndex]
|
const double routeDepth=PassageRouteFloorDepthMetres(constants,along,
|
||||||
:static_cast<double>(passage.controlPoints[pointIndex]
|
radiusMetres);
|
||||||
.radiusSubvoxels)/qPerMetre;
|
routeFloorSamples.push_back({point,along,point[2]/qPerMetre-routeDepth});
|
||||||
const double localTubeDepth=std::min(constants.floorDepthMetres,
|
};
|
||||||
std::max(0.0,radius-0.23));
|
for(const auto& raw:rawSegments)
|
||||||
const double routeDepth=pointIndex<=routeEnd
|
{
|
||||||
?routeFloorDepth(pointIndex,radius):std::max(0.0,radius-0.23);
|
if(!raw.routeSegment)break;
|
||||||
effectiveCenterZSubvoxels[pointIndex]=
|
if(routeFloorSamples.empty())appendRouteFloorSample(raw.a,raw.alongMetres);
|
||||||
static_cast<double>(passage.controlPoints[pointIndex].positionSubvoxels[2])
|
appendRouteFloorSample(raw.b,raw.alongMetres+raw.lengthMetres);
|
||||||
+(-routeDepth+localTubeDepth)*qPerMetre;
|
|
||||||
}
|
}
|
||||||
for(std::size_t segmentIndex=0;segmentIndex<segmentData.size();++segmentIndex)
|
if(routeFloorSamples.size()>1)
|
||||||
{
|
{
|
||||||
const auto& a=passage.controlPoints[segmentIndex];
|
constexpr double MaximumWalkGradeTangent=0.2586407821;
|
||||||
const auto& b=passage.controlPoints[segmentIndex+1];
|
std::vector<double> horizontalGradeLimits(routeFloorSamples.size()-1);
|
||||||
FPassageSegmentData& data=segmentData[segmentIndex];
|
std::vector<double> remainingGradeAllowance(routeFloorSamples.size(),0.0);
|
||||||
data.authoredCenterZA=static_cast<double>(a.positionSubvoxels[2]);
|
for(std::size_t i=0;i+1<routeFloorSamples.size();++i)
|
||||||
data.authoredCenterZDelta=static_cast<double>(
|
{
|
||||||
b.positionSubvoxels[2]-a.positionSubvoxels[2]);
|
const double dx=(routeFloorSamples[i+1].point[0]
|
||||||
data.authoredAlongMetres=authoredPathDistanceMetres[segmentIndex];
|
-routeFloorSamples[i].point[0])/qPerMetre;
|
||||||
data.authoredLengthMetres=authoredPathDistanceMetres[segmentIndex+1]
|
const double dy=(routeFloorSamples[i+1].point[1]
|
||||||
-authoredPathDistanceMetres[segmentIndex];
|
-routeFloorSamples[i].point[1])/qPerMetre;
|
||||||
|
horizontalGradeLimits[i]=MaximumWalkGradeTangent
|
||||||
|
*DeterministicSqrt(dx*dx+dy*dy);
|
||||||
|
}
|
||||||
|
for(std::size_t i=horizontalGradeLimits.size();i>0;--i)
|
||||||
|
remainingGradeAllowance[i-1]=remainingGradeAllowance[i]
|
||||||
|
+horizontalGradeLimits[i-1];
|
||||||
|
std::vector<double> walkableFloor(routeFloorSamples.size());
|
||||||
|
walkableFloor.front()=routeFloorSamples.front().desiredFloorZMetres;
|
||||||
|
walkableFloor.back()=routeFloorSamples.back().desiredFloorZMetres;
|
||||||
|
for(std::size_t i=1;i+1<routeFloorSamples.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(
|
||||||
|
routeFloorSamples[i].desiredFloorZMetres,low,high);
|
||||||
|
}
|
||||||
|
for(std::size_t i=0;i+1<rawSegments.size();++i)
|
||||||
|
{
|
||||||
|
auto& raw=rawSegments[i];
|
||||||
|
if(!raw.routeSegment)break;
|
||||||
|
const auto& floorA=routeFloorSamples[i];
|
||||||
|
const auto& floorB=routeFloorSamples[i+1];
|
||||||
|
const double radiusA=floorA.point[3]/qPerMetre;
|
||||||
|
const double radiusB=floorB.point[3]/qPerMetre;
|
||||||
|
raw.a[2]=(walkableFloor[i]
|
||||||
|
+PassageRouteFloorDepthMetres(constants,floorA.alongMetres,radiusA))
|
||||||
|
*qPerMetre;
|
||||||
|
raw.b[2]=(walkableFloor[i+1]
|
||||||
|
+PassageRouteFloorDepthMetres(constants,floorB.alongMetres,radiusB))
|
||||||
|
*qPerMetre;
|
||||||
|
}
|
||||||
|
const std::size_t routeSegmentCount=routeFloorSamples.size()-1;
|
||||||
|
if(routeSegmentCount<rawSegments.size())
|
||||||
|
{
|
||||||
|
auto& firstCap=rawSegments[routeSegmentCount];
|
||||||
|
const double localDepth=std::max(0.0,
|
||||||
|
firstCap.a[3]/qPerMetre-0.23);
|
||||||
|
firstCap.a[2]=(walkableFloor.back()+localDepth)*qPerMetre;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& segmentData=PassageSegmentData[passageIndex];
|
||||||
|
segmentData.clear();
|
||||||
|
segmentData.reserve(rawSegments.size());
|
||||||
|
for(const auto& raw:rawSegments)
|
||||||
|
{
|
||||||
|
FPassageSegmentData data;
|
||||||
|
data.authoredCenterZA=raw.a[2];
|
||||||
|
data.authoredCenterZDelta=raw.b[2]-raw.a[2];
|
||||||
|
data.authoredAlongMetres=raw.alongMetres;
|
||||||
|
data.authoredLengthMetres=raw.lengthMetres;
|
||||||
|
data.routeSegment=raw.routeSegment;
|
||||||
|
data.radiusA=raw.a[3];
|
||||||
|
data.radiusB=raw.b[3];
|
||||||
|
data.radiusDelta=raw.b[3]-raw.a[3];
|
||||||
|
const double alongB=raw.alongMetres+raw.lengthMetres;
|
||||||
|
const auto effectiveCenterZ=[&](double authoredZ,double radiusQ,double along)
|
||||||
|
{
|
||||||
|
const double radiusMetres=radiusQ/qPerMetre;
|
||||||
|
const double localTubeDepth=std::min(constants.floorDepthMetres,
|
||||||
|
std::max(0.0,radiusMetres-0.23));
|
||||||
|
const double routeDepth=raw.routeSegment
|
||||||
|
?PassageRouteFloorDepthMetres(constants,along,radiusMetres)
|
||||||
|
:std::max(0.0,radiusMetres-0.23);
|
||||||
|
return authoredZ+(-routeDepth+localTubeDepth)*qPerMetre;
|
||||||
|
};
|
||||||
for(std::size_t axis=0;axis<3;++axis)
|
for(std::size_t axis=0;axis<3;++axis)
|
||||||
{
|
{
|
||||||
data.a[axis]=static_cast<double>(a.positionSubvoxels[axis]);
|
data.a[axis]=raw.a[axis];
|
||||||
data.b[axis]=static_cast<double>(b.positionSubvoxels[axis]);
|
data.b[axis]=raw.b[axis];
|
||||||
if(axis==2)
|
if(axis==2)
|
||||||
{
|
{
|
||||||
data.a[axis]=effectiveCenterZSubvoxels[segmentIndex];
|
data.a[axis]=effectiveCenterZ(raw.a[2],raw.a[3],raw.alongMetres);
|
||||||
data.b[axis]=effectiveCenterZSubvoxels[segmentIndex+1];
|
data.b[axis]=effectiveCenterZ(raw.b[2],raw.b[3],alongB);
|
||||||
}
|
}
|
||||||
data.delta[axis]=data.b[axis]-data.a[axis];
|
data.delta[axis]=data.b[axis]-data.a[axis];
|
||||||
}
|
}
|
||||||
data.radiusA=static_cast<double>(a.radiusSubvoxels);
|
|
||||||
data.radiusB=static_cast<double>(b.radiusSubvoxels);
|
|
||||||
data.radiusDelta=static_cast<double>(b.radiusSubvoxels)-a.radiusSubvoxels;
|
|
||||||
const double dx=data.delta[0],dy=data.delta[1],dz=data.delta[2];
|
const double dx=data.delta[0],dy=data.delta[1],dz=data.delta[2];
|
||||||
data.lengthSq=dx*dx+dy*dy+dz*dz;
|
data.lengthSq=dx*dx+dy*dy+dz*dz;
|
||||||
data.horizontalLengthSq=dx*dx+dy*dy;
|
data.horizontalLengthSq=dx*dx+dy*dy;
|
||||||
data.lengthSubvoxels=DeterministicSqrt(data.lengthSq);
|
data.lengthSubvoxels=DeterministicSqrt(data.lengthSq);
|
||||||
data.horizontalLength=DeterministicSqrt(data.horizontalLengthSq);
|
data.horizontalLength=DeterministicSqrt(data.horizontalLengthSq);
|
||||||
|
segmentData.push_back(data);
|
||||||
++preparedSegments;
|
++preparedSegments;
|
||||||
}
|
}
|
||||||
constants.totalPathLengthMetres=authoredTotalPathLengthMetres;
|
constants.totalPathLengthMetres=authoredTotalPathLengthMetres;
|
||||||
PassageSegmentOffsets[passageIndex+1]=preparedSegments;
|
PassageSegmentOffsets[passageIndex+1]=preparedSegments;
|
||||||
}
|
}
|
||||||
if(tracePreparation)
|
if(tracePreparation)
|
||||||
std::printf("[L109PassagePrepare] passages=%zu segments=%zu bytes=%zu prepare_ms=%.3f\n",
|
std::printf("[L111PassagePrepare] passages=%zu control_spans=%zu segments=%zu spacing_m=%.2f bytes=%zu prepare_ms=%.3f\n",
|
||||||
PassageSegmentData.size(),preparedSegments,
|
PassageSegmentData.size(),controlSpans,preparedSegments,
|
||||||
preparedSegments*sizeof(FPassageSegmentData),
|
PassageSegmentSpacingMetres,preparedSegments*sizeof(FPassageSegmentData),
|
||||||
std::chrono::duration<double,std::milli>(
|
std::chrono::duration<double,std::milli>(
|
||||||
std::chrono::steady_clock::now()-preparationStart).count());
|
std::chrono::steady_clock::now()-preparationStart).count());
|
||||||
}
|
}
|
||||||
@@ -4681,20 +4828,16 @@ private:
|
|||||||
for(std::size_t passageIndex=0;passageIndex<ResolvedLayout->passages.size();
|
for(std::size_t passageIndex=0;passageIndex<ResolvedLayout->passages.size();
|
||||||
++passageIndex)
|
++passageIndex)
|
||||||
{
|
{
|
||||||
const auto& passage=ResolvedLayout->passages[passageIndex];
|
const std::size_t segmentCount=PassageSegmentData[passageIndex].size();
|
||||||
const std::size_t segmentCount=passage.controlPoints.size()>1
|
|
||||||
?passage.controlPoints.size()-1:0;
|
|
||||||
if(segmentCount==0)continue;
|
if(segmentCount==0)continue;
|
||||||
std::vector<std::size_t> segmentIds(segmentCount);
|
std::vector<std::size_t> segmentIds(segmentCount);
|
||||||
for(std::size_t i=0;i<segmentCount;++i)segmentIds[i]=i;
|
for(std::size_t i=0;i<segmentCount;++i)segmentIds[i]=i;
|
||||||
BuildPassageNearestNode(passageIndex,std::move(segmentIds));
|
BuildPassageNearestNode(passageIndex,std::move(segmentIds));
|
||||||
for(std::size_t segmentIndex=0;segmentIndex<segmentCount;++segmentIndex)
|
for(std::size_t segmentIndex=0;segmentIndex<segmentCount;++segmentIndex)
|
||||||
{
|
{
|
||||||
const auto& a=passage.controlPoints[segmentIndex];
|
|
||||||
const auto& b=passage.controlPoints[segmentIndex+1];
|
|
||||||
auto& prepared=PassageSegmentData[passageIndex][segmentIndex];
|
auto& prepared=PassageSegmentData[passageIndex][segmentIndex];
|
||||||
double maximumRadius=std::max(static_cast<double>(a.radiusSubvoxels),
|
const double maximumRadius=std::max(prepared.radiusA,
|
||||||
static_cast<double>(b.radiusSubvoxels))/Q;
|
prepared.radiusB)/Q;
|
||||||
std::array<double,3> routeMinimum{},routeMaximum{};
|
std::array<double,3> routeMinimum{},routeMaximum{};
|
||||||
for(std::size_t axis=0;axis<3;++axis)
|
for(std::size_t axis=0;axis<3;++axis)
|
||||||
{
|
{
|
||||||
@@ -11286,6 +11429,7 @@ inline void FStandaloneShapeWorld::FScopedPassageTileQueries::prepareSweptTubeBa
|
|||||||
{
|
{
|
||||||
constexpr std::int64_t Q=VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
|
constexpr std::int64_t Q=VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
|
||||||
constexpr double InfluenceMetres=4.0;
|
constexpr double InfluenceMetres=4.0;
|
||||||
|
constexpr double OpenAirSideWallHeightMetres=1.0;
|
||||||
if(!shape_||step<=0||cells<=0||!std::isfinite(passageCarveReachMetres_)
|
if(!shape_||step<=0||cells<=0||!std::isfinite(passageCarveReachMetres_)
|
||||||
||std::abs(passageCarveReachMetres_-InfluenceMetres)>1.0e-9)return;
|
||std::abs(passageCarveReachMetres_-InfluenceMetres)>1.0e-9)return;
|
||||||
|
|
||||||
@@ -11403,11 +11547,14 @@ inline void FStandaloneShapeWorld::FScopedPassageTileQueries::prepareSweptTubeBa
|
|||||||
const double floorBlend=std::clamp(floorDistanceMetres/0.25,0.0,1.0);
|
const double floorBlend=std::clamp(floorDistanceMetres/0.25,0.0,1.0);
|
||||||
const double smoothFloorBlend=floorBlend*floorBlend
|
const double smoothFloorBlend=floorBlend*floorBlend
|
||||||
*(3.0-2.0*floorBlend);
|
*(3.0-2.0*floorBlend);
|
||||||
const double mouthWallThickness=candidate.segmentIndex
|
const double wallElevationMetres=(qz-(floorCenterZ-floorDepth))
|
||||||
<constants.routeEndControlIndex
|
/qPerMetre;
|
||||||
|
const double mouthWallThickness=segment.routeSegment
|
||||||
|
&&wallElevationMetres>=0.0
|
||||||
|
&&wallElevationMetres<=OpenAirSideWallHeightMetres
|
||||||
?smoothShellFade*shellThickness:0.0;
|
?smoothShellFade*shellThickness:0.0;
|
||||||
double floorExtensionFade=1.0;
|
double floorExtensionFade=1.0;
|
||||||
if(candidate.segmentIndex>=constants.routeEndControlIndex)
|
if(!segment.routeSegment)
|
||||||
{
|
{
|
||||||
const double pastMouthMetres=std::max(0.0,alongMetres
|
const double pastMouthMetres=std::max(0.0,alongMetres
|
||||||
-constants.totalPathLengthMetres);
|
-constants.totalPathLengthMetres);
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
# Linux111 report
|
||||||
|
|
||||||
|
## Design
|
||||||
|
|
||||||
|
Version 218 resamples passage controls with a centripetal Catmull–Rom curve at about 1 m spacing (310 control spans became 2,790 prepared segments). Open-air shells use a 0.75 m floor slab and side walls up to 1.0 m above the floor; there is no roof. Passage interior remains higher priority than shell. The second design constrained the floor route to a 14.5° grade ceiling.
|
||||||
|
|
||||||
|
The per-tile candidate lists were not measured. The 310→2,790 figures are prepared route segment counts, not tile candidate list sizes.
|
||||||
|
|
||||||
|
## Speed
|
||||||
|
|
||||||
|
L109 medians below are the candidate column from the saved five-pair timing matrix. L111 timing was not run after the audits failed.
|
||||||
|
|
||||||
|
| Stratum | L109 W8 near-ready (s) | L109 W12 near-ready (s) | L111 W8/W12 |
|
||||||
|
|---|---:|---:|---|
|
||||||
|
| SurfaceWorld | 0.737 | 0.761 | Not run |
|
||||||
|
| FloatingIslands | 0.911 | 0.863 | Not run |
|
||||||
|
| TunnelNetwork | 0.956 | 0.895 | Not run |
|
||||||
|
| VerticalShafts | 0.789 | 0.738 | Not run |
|
||||||
|
|
||||||
|
## Audits
|
||||||
|
|
||||||
|
- Clang standalone build completed. The second design removed all grade failures. Passage 0–1 still had 2 unsupported walk samples; passages 1–2 and 2–3 passed. The combined resolved-passage audit failed.
|
||||||
|
- Mouth-profile audits passed 3/3. Naturalness and ripple audits passed 2/3.
|
||||||
|
- The half-pipe shell audit passed 2/3; passage 0–1 reported 11,544 floor and 6,945 wall violations. A targeted trace showed at least one reported floor sample was 0.101 m inside the passage after relief, where interior correctly overrides the shell. The audit's smooth-route overlap model still disagreed at that point, so the remaining shell counts are not conclusive.
|
||||||
|
- Fast tier, Clang/GCC W1/W8/W12 hash matrix, readiness timing, and Unreal Linux build were not run. Canonical v218 hashes were not generated.
|
||||||
|
|
||||||
|
## Captures
|
||||||
|
|
||||||
|
No Linux111 capture images were generated. Mouths, 25/50/75% stations, open-air sections, and programmatically verified 4 m outside/interior close-ups remain outstanding.
|
||||||
|
|
||||||
|
The candidate audit found these open-air sections:
|
||||||
|
|
||||||
|
- passage 0–1: 6.408–16.782 m; 19.500–24.439 m; 1,044.046–1,498.708 m
|
||||||
|
- passage 1–2: 81.654–85.646 m; 104.092–104.829 m
|
||||||
|
- passage 2–3: 7.701–26.008 m; 372.697–377.363 m
|
||||||
|
|
||||||
|
## Failed attempts
|
||||||
|
|
||||||
|
1. Dense spline plus half-pipe: walk grades exceeded 15° and passage 0–1 failed shell, naturalness, and ripple checks.
|
||||||
|
2. Dense spline plus a grade-limited floor: grade failures cleared, but passage 0–1 retained 2 unsupported walk samples and its shell/naturalness/ripple audits still failed. Work stopped after this second design as requested.
|
||||||
|
|
||||||
|
Logs: `Saved/Linux/l111/build-clang.log` and `Saved/Linux/l111/audit-owner.log`.
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
#include "OwnerWorldLoader.h"
|
#include "OwnerWorldLoader.h"
|
||||||
#include "StreamingWorld.h"
|
#include "StreamingWorld.h"
|
||||||
#include "LinuxScreenSpaceGate.h"
|
#include "LinuxScreenSpaceGate.h"
|
||||||
|
#include "../Core/src/VoxelForgeFieldShared.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <array>
|
#include <array>
|
||||||
@@ -2967,6 +2968,52 @@ namespace StandaloneStreamBenchmark
|
|||||||
const auto qToMetres=[&](std::int64_t q)
|
const auto qToMetres=[&](std::int64_t q)
|
||||||
{return static_cast<double>(q)*World.CoreVoxelSizeMicrons
|
{return static_cast<double>(q)*World.CoreVoxelSizeMicrons
|
||||||
/(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel*1000000.0);};
|
/(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 auto passageBodyFloorDepth=[&](
|
||||||
const VoxelForgeCore::ResolvedLayoutPassageV1& passage)
|
const VoxelForgeCore::ResolvedLayoutPassageV1& passage)
|
||||||
{
|
{
|
||||||
@@ -2976,12 +3023,19 @@ namespace StandaloneStreamBenchmark
|
|||||||
std::vector<double> distances(routeEnd+1,0.0);
|
std::vector<double> distances(routeEnd+1,0.0);
|
||||||
for(std::size_t i=1;i<=routeEnd;++i)
|
for(std::size_t i=1;i<=routeEnd;++i)
|
||||||
{
|
{
|
||||||
const auto& a=passage.controlPoints[i-1].positionSubvoxels;
|
auto previous=passageSplinePoint(passage,i-1,0.0);
|
||||||
const auto& b=passage.controlPoints[i].positionSubvoxels;
|
double spanLength=0.0;
|
||||||
const double dx=qToMetres(b[0]-a[0]);
|
for(std::size_t step=1;step<=PassageSplineArcSteps;++step)
|
||||||
const double dy=qToMetres(b[1]-a[1]);
|
{
|
||||||
const double dz=qToMetres(b[2]-a[2]);
|
const auto current=passageSplinePoint(passage,i-1,
|
||||||
distances[i]=distances[i-1]+std::sqrt(dx*dx+dy*dy+dz*dz);
|
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 minimumRadius=std::numeric_limits<double>::infinity();
|
||||||
double minimumBodyRadius=minimumRadius;
|
double minimumBodyRadius=minimumRadius;
|
||||||
@@ -3007,8 +3061,23 @@ namespace StandaloneStreamBenchmark
|
|||||||
const double lower=qToMetres(
|
const double lower=qToMetres(
|
||||||
passage.controlPoints[routeEnd].positionSubvoxels[2]
|
passage.controlPoints[routeEnd].positionSubvoxels[2]
|
||||||
-passage.lowerMouthSubvoxels[2])+1.13;
|
-passage.lowerMouthSubvoxels[2])+1.13;
|
||||||
return std::array<double,2>{{std::max(0.0,upper),
|
double lowerDepth=std::max(0.0,lower);
|
||||||
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,
|
const auto passageFloorDepthAt=[](double alongMetres,double totalMetres,
|
||||||
double radiusMetres,double bodyDepthMetres,double upperMouthDepth,
|
double radiusMetres,double bodyDepthMetres,double upperMouthDepth,
|
||||||
@@ -3040,12 +3109,19 @@ namespace StandaloneStreamBenchmark
|
|||||||
if(passage.controlPoints.size()<2)return distances;
|
if(passage.controlPoints.size()<2)return distances;
|
||||||
for(std::size_t i=1;i<passage.controlPoints.size();++i)
|
for(std::size_t i=1;i<passage.controlPoints.size();++i)
|
||||||
{
|
{
|
||||||
const auto& a=passage.controlPoints[i-1].positionSubvoxels;
|
auto previous=passageSplinePoint(passage,i-1,0.0);
|
||||||
const auto& b=passage.controlPoints[i].positionSubvoxels;
|
double spanLength=0.0;
|
||||||
const double dx=qToMetres(b[0]-a[0]);
|
for(std::size_t step=1;step<=PassageSplineArcSteps;++step)
|
||||||
const double dy=qToMetres(b[1]-a[1]);
|
{
|
||||||
const double dz=qToMetres(b[2]-a[2]);
|
const auto current=passageSplinePoint(passage,i-1,
|
||||||
distances[i]=distances[i-1]+std::sqrt(dx*dx+dy*dy+dz*dz);
|
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;
|
return distances;
|
||||||
};
|
};
|
||||||
@@ -3063,77 +3139,235 @@ namespace StandaloneStreamBenchmark
|
|||||||
return qToMetres(point.positionSubvoxels[2])-routeFloorDepth
|
return qToMetres(point.positionSubvoxels[2])-routeFloorDepth
|
||||||
+passageTubeDepthAt(radius,bodyDepth);
|
+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<std::vector<double>> allPassageRouteDistances;
|
||||||
std::vector<double> allPassageBodyFloorDepths;
|
std::vector<double> allPassageBodyFloorDepths;
|
||||||
|
std::vector<std::vector<FPassageAuditRoutePoint>> allPassageSmoothRoutes;
|
||||||
allPassageRouteDistances.reserve(ResolvedLayout->passages.size());
|
allPassageRouteDistances.reserve(ResolvedLayout->passages.size());
|
||||||
allPassageBodyFloorDepths.reserve(ResolvedLayout->passages.size());
|
allPassageBodyFloorDepths.reserve(ResolvedLayout->passages.size());
|
||||||
|
allPassageSmoothRoutes.reserve(ResolvedLayout->passages.size());
|
||||||
for(const auto& passage:ResolvedLayout->passages)
|
for(const auto& passage:ResolvedLayout->passages)
|
||||||
{
|
{
|
||||||
allPassageRouteDistances.push_back(passageRouteDistances(passage));
|
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,
|
const auto passageInteriorWinsAt=[&](double xMetres,double yMetres,
|
||||||
double zMetres,std::size_t ignoredPassage=std::numeric_limits<std::size_t>::max(),
|
double zMetres,std::size_t ignoredPassage=std::numeric_limits<std::size_t>::max(),
|
||||||
std::size_t ignoredSegment=std::numeric_limits<std::size_t>::max())
|
std::size_t ignoredSegment=std::numeric_limits<std::size_t>::max())
|
||||||
{
|
{
|
||||||
constexpr double CornerBlendMetres=0.50;
|
constexpr double CornerBlendMetres=0.50;
|
||||||
// Relief moves the curved surface by up to 0.21 m. Samples in this
|
constexpr double WallReliefMetres=0.21;
|
||||||
// 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;
|
|
||||||
for(std::size_t passageIndex=0;
|
for(std::size_t passageIndex=0;
|
||||||
passageIndex<ResolvedLayout->passages.size();++passageIndex)
|
passageIndex<ResolvedLayout->passages.size();++passageIndex)
|
||||||
{
|
{
|
||||||
const auto& passage=ResolvedLayout->passages[passageIndex];
|
const auto& passage=ResolvedLayout->passages[passageIndex];
|
||||||
if(passage.controlPoints.size()<2)continue;
|
if(passage.controlPoints.size()<2)continue;
|
||||||
const std::size_t routeEnd=passage.controlPoints.size()-1;
|
const auto& route=allPassageSmoothRoutes[passageIndex];
|
||||||
const auto& distances=allPassageRouteDistances[passageIndex];
|
if(route.size()<2)continue;
|
||||||
const double totalLength=distances[std::clamp<std::size_t>(
|
|
||||||
passage.lowerMouthControlIndex,1,passage.controlPoints.size()-1)];
|
|
||||||
const double bodyDepth=allPassageBodyFloorDepths[passageIndex];
|
const double bodyDepth=allPassageBodyFloorDepths[passageIndex];
|
||||||
const auto mouthFloorDepths=passageMouthFloorDepths(passage);
|
double bestDistance=std::numeric_limits<double>::infinity();
|
||||||
for(std::size_t segment=1;segment<=routeEnd;++segment)
|
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=route[segment-1];
|
||||||
const auto& a=passage.controlPoints[segment-1];
|
const auto& b=route[segment];
|
||||||
const auto& b=passage.controlPoints[segment];
|
if(passageIndex==ignoredPassage
|
||||||
const double ax=qToMetres(a.positionSubvoxels[0]);
|
&&b.segmentIndex==ignoredSegment)continue;
|
||||||
const double ay=qToMetres(a.positionSubvoxels[1]);
|
const double dx=b.x-a.x,dy=b.y-a.y,dz=b.z-a.z;
|
||||||
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 double lengthSquared=dx*dx+dy*dy+dz*dz;
|
const double lengthSquared=dx*dx+dy*dy+dz*dz;
|
||||||
if(lengthSquared<=1.0e-12)continue;
|
if(lengthSquared<=1.0e-12)continue;
|
||||||
const double t=std::clamp(((xMetres-ax)*dx+(yMetres-ay)*dy
|
const double t=std::clamp(((xMetres-a.x)*dx+(yMetres-a.y)*dy
|
||||||
+(zMetres-az)*dz)/lengthSquared,0.0,1.0);
|
+(zMetres-a.z)*dz)/lengthSquared,0.0,1.0);
|
||||||
const double centerX=ax+t*dx,centerY=ay+t*dy,centerZ=az+t*dz;
|
const double centerX=a.x+t*dx,centerY=a.y+t*dy,centerZ=a.z+t*dz;
|
||||||
const double radius=std::lerp(qToMetres(a.radiusSubvoxels),
|
const double radius=std::lerp(a.radius,b.radius,t);
|
||||||
qToMetres(b.radiusSubvoxels),t);
|
|
||||||
const double horizontalLengthSquared=dx*dx+dy*dy;
|
const double horizontalLengthSquared=dx*dx+dy*dy;
|
||||||
const double floorT=horizontalLengthSquared>1.0e-12
|
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;
|
/horizontalLengthSquared,0.0,1.0):t;
|
||||||
const double floorCenterZ=az+floorT*dz;
|
const double floorCenterZ=a.z+floorT*dz;
|
||||||
const double floorRadius=std::lerp(qToMetres(a.radiusSubvoxels),
|
const double floorRadius=std::lerp(a.radius,b.radius,floorT);
|
||||||
qToMetres(b.radiusSubvoxels),floorT);
|
|
||||||
const double along=distances[segment-1]
|
|
||||||
+std::sqrt(lengthSquared)*floorT;
|
|
||||||
const double floorDepth=passageTubeDepthAt(floorRadius,bodyDepth);
|
const double floorDepth=passageTubeDepthAt(floorRadius,bodyDepth);
|
||||||
const double roundDistance=std::sqrt((xMetres-centerX)*(xMetres-centerX)
|
const double roundDistance=std::sqrt((xMetres-centerX)*(xMetres-centerX)
|
||||||
+(yMetres-centerY)*(yMetres-centerY)
|
+(yMetres-centerY)*(yMetres-centerY)
|
||||||
+(zMetres-centerZ)*(zMetres-centerZ))-radius;
|
+(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-
|
const double h=std::max(CornerBlendMetres-
|
||||||
std::abs(roundDistance-floorDistance),0.0)/CornerBlendMetres;
|
std::abs(roundDistance-floorDistance),0.0)/CornerBlendMetres;
|
||||||
const double flatDistance=std::max(roundDistance,floorDistance)
|
const double flatDistance=std::max(roundDistance,floorDistance)
|
||||||
+h*h*h*(CornerBlendMetres/6.0);
|
+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;
|
return false;
|
||||||
};
|
};
|
||||||
struct FPassageNaturalnessAudit
|
struct FPassageNaturalnessAudit
|
||||||
@@ -3438,31 +3672,20 @@ namespace StandaloneStreamBenchmark
|
|||||||
>=PassageProfilePlateauMinimumMetres
|
>=PassageProfilePlateauMinimumMetres
|
||||||
&&l23TerraceControlResult.second>=PassageProfileStepThresholdMetres;
|
&&l23TerraceControlResult.second>=PassageProfileStepThresholdMetres;
|
||||||
const auto auditPassageNaturalness=[&](const VoxelForgeCore::World& Field,
|
const auto auditPassageNaturalness=[&](const VoxelForgeCore::World& Field,
|
||||||
const VoxelForgeCore::ResolvedLayoutPassageV1& passage)
|
std::size_t passageIndex)
|
||||||
{
|
{
|
||||||
FPassageNaturalnessAudit audit;
|
FPassageNaturalnessAudit audit;
|
||||||
const std::size_t end=std::min<std::size_t>(passage.lowerMouthControlIndex,
|
const auto& path=allPassageSmoothRoutes[passageIndex];
|
||||||
passage.controlPoints.size()-1);
|
if(path.size()<3)return audit;
|
||||||
const auto routeDistances=passageRouteDistances(passage);
|
const double bodyFloorDepth=allPassageBodyFloorDepths[passageIndex];
|
||||||
const double totalRouteLength=routeDistances.empty()?0.0:
|
|
||||||
routeDistances[end];
|
|
||||||
const double bodyFloorDepth=passageBodyFloorDepth(passage);
|
|
||||||
const auto mouthFloorDepths=passageMouthFloorDepths(passage);
|
|
||||||
double flatRun=0.0;
|
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& a=path[i-1];
|
||||||
const auto& b=passage.controlPoints[i];
|
const auto& b=path[i];
|
||||||
const double dx=qToMetres(b.positionSubvoxels[0]-a.positionSubvoxels[0]);
|
const double dx=b.x-a.x,dy=b.y-a.y;
|
||||||
const double dy=qToMetres(b.positionSubvoxels[1]-a.positionSubvoxels[1]);
|
const double floorA=a.z-passageTubeDepthAt(a.radius,bodyFloorDepth)+1.13;
|
||||||
const double floorA=qToMetres(a.positionSubvoxels[2])
|
const double floorB=b.z-passageTubeDepthAt(b.radius,bodyFloorDepth)+1.13;
|
||||||
-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 double dz=floorB-floorA;
|
const double dz=floorB-floorA;
|
||||||
const double horizontal=std::hypot(dx,dy);
|
const double horizontal=std::hypot(dx,dy);
|
||||||
if(horizontal>1.0e-6&&std::abs(dz)/horizontal
|
if(horizontal>1.0e-6&&std::abs(dz)/horizontal
|
||||||
@@ -3471,28 +3694,31 @@ namespace StandaloneStreamBenchmark
|
|||||||
audit.maximumFlatRunMetres,flatRun);}
|
audit.maximumFlatRunMetres,flatRun);}
|
||||||
else flatRun=0.0;
|
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 minimumRadius=std::numeric_limits<double>::infinity();
|
||||||
double maximumRadius=-std::numeric_limits<double>::infinity();
|
double maximumRadius=-std::numeric_limits<double>::infinity();
|
||||||
for(std::size_t sample=0;sample<sampleCount;++sample)
|
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);
|
/std::max<std::size_t>(1,sampleCount-1);
|
||||||
const auto& point=passage.controlPoints[index];
|
const auto& point=path[index];
|
||||||
const auto& before=passage.controlPoints[index-1];
|
const auto& before=path[index-1];
|
||||||
const auto& after=passage.controlPoints[index+1];
|
const auto& after=path[index+1];
|
||||||
const double tangentX=qToMetres(after.positionSubvoxels[0]-before.positionSubvoxels[0]);
|
const double tangentX=after.x-before.x;
|
||||||
const double tangentY=qToMetres(after.positionSubvoxels[1]-before.positionSubvoxels[1]);
|
const double tangentY=after.y-before.y;
|
||||||
const double tangentLength=std::hypot(tangentX,tangentY);
|
const double tangentLength=std::hypot(tangentX,tangentY);
|
||||||
if(tangentLength<=1.0e-6){++audit.missingWallSamples;continue;}
|
if(tangentLength<=1.0e-6){++audit.missingWallSamples;continue;}
|
||||||
const double radiusMetres=qToMetres(point.radiusSubvoxels);
|
constexpr double WallProbeHeightMetres=0.75;
|
||||||
const double sampleRadius=std::max(0.20,radiusMetres-0.18);
|
const double floorDepth=passageTubeDepthAt(point.radius,bodyFloorDepth);
|
||||||
const double centerX=qToMetres(point.positionSubvoxels[0]);
|
const double vertical=WallProbeHeightMetres-floorDepth;
|
||||||
const double centerY=qToMetres(point.positionSubvoxels[1]);
|
const double wallRadiusSquared=point.radius*point.radius-vertical*vertical;
|
||||||
const double centerZ=passageEffectiveCenterZ(passage,index,
|
if(wallRadiusSquared<=0.0){audit.missingWallSamples+=2;continue;}
|
||||||
routeDistances,totalRouteLength,bodyFloorDepth,mouthFloorDepths);
|
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;
|
const double nx=-tangentY/tangentLength,ny=tangentX/tangentLength;
|
||||||
for(const double side:{-1.0,1.0})
|
for(const double side:{-1.0,1.0})
|
||||||
{
|
{
|
||||||
@@ -3523,7 +3749,6 @@ namespace StandaloneStreamBenchmark
|
|||||||
return audit;
|
return audit;
|
||||||
};
|
};
|
||||||
const auto auditPassageRipples=[&](const VoxelForgeCore::World& Field,
|
const auto auditPassageRipples=[&](const VoxelForgeCore::World& Field,
|
||||||
const VoxelForgeCore::ResolvedLayoutPassageV1& passage,
|
|
||||||
std::size_t passageIndex)
|
std::size_t passageIndex)
|
||||||
{
|
{
|
||||||
FPassageRippleAudit audit;
|
FPassageRippleAudit audit;
|
||||||
@@ -3539,33 +3764,16 @@ namespace StandaloneStreamBenchmark
|
|||||||
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,radius=0.0,distance=0.0;
|
||||||
std::size_t segmentIndex=0;
|
std::size_t segmentIndex=0;
|
||||||
};
|
};
|
||||||
const std::size_t end=std::min<std::size_t>(passage.lowerMouthControlIndex,
|
const auto& passage=ResolvedLayout->passages[passageIndex];
|
||||||
passage.controlPoints.size()-1);
|
const auto& smoothRoute=allPassageSmoothRoutes[passageIndex];
|
||||||
if(end<2)return audit;
|
if(smoothRoute.size()<2)return audit;
|
||||||
const auto routeDistances=passageRouteDistances(passage);
|
const double bodyFloorDepth=allPassageBodyFloorDepths[passageIndex];
|
||||||
const double totalRouteLength=routeDistances.empty()?0.0:
|
|
||||||
routeDistances[end];
|
|
||||||
const double bodyFloorDepth=passageBodyFloorDepth(passage);
|
|
||||||
const auto mouthFloorDepths=passageMouthFloorDepths(passage);
|
|
||||||
std::vector<FPathPoint> path;
|
std::vector<FPathPoint> path;
|
||||||
path.reserve(end+1);
|
path.reserve(smoothRoute.size());
|
||||||
for(std::size_t i=0;i<=end;++i)
|
for(const auto& point:smoothRoute)
|
||||||
{
|
{
|
||||||
const auto& point=passage.controlPoints[i];
|
path.push_back({point.x,point.y,point.z,point.radius,
|
||||||
FPathPoint value{qToMetres(point.positionSubvoxels[0]),
|
point.distance,point.segmentIndex});
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
const double total=path.back().distance;
|
const double total=path.back().distance;
|
||||||
if(total<WindowLengthMetres)return audit;
|
if(total<WindowLengthMetres)return audit;
|
||||||
@@ -3973,51 +4181,31 @@ namespace StandaloneStreamBenchmark
|
|||||||
{
|
{
|
||||||
const auto& passage=ResolvedLayout->passages[passageIndex];
|
const auto& passage=ResolvedLayout->passages[passageIndex];
|
||||||
if(passage.controlPoints.size()<2)continue;
|
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 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 bestFlatDistance=std::numeric_limits<double>::infinity();
|
||||||
double bestFloorDistance=0.0,bestMouthDistance=0.0;
|
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& a=path[segment-1];
|
||||||
const auto& b=passage.controlPoints[segment+1];
|
const auto& b=path[segment];
|
||||||
const double ax=qToMetres(a.positionSubvoxels[0]);
|
const double ax=a.x,ay=a.y,az=a.z;
|
||||||
const double ay=qToMetres(a.positionSubvoxels[1]);
|
const double dx=b.x-a.x,dy=b.y-a.y,dz=b.z-a.z;
|
||||||
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 double lengthSquared=dx*dx+dy*dy+dz*dz;
|
const double lengthSquared=dx*dx+dy*dy+dz*dz;
|
||||||
if(lengthSquared<=1.0e-12)continue;
|
if(lengthSquared<=1.0e-12)continue;
|
||||||
const double t=std::clamp(((xMetres-ax)*dx+(yMetres-ay)*dy
|
const double t=std::clamp(((xMetres-ax)*dx+(yMetres-ay)*dy
|
||||||
+(zMetres-az)*dz)/lengthSquared,0.0,1.0);
|
+(zMetres-az)*dz)/lengthSquared,0.0,1.0);
|
||||||
const double centerX=ax+t*dx,centerY=ay+t*dy,centerZ=az+t*dz;
|
const double centerX=ax+t*dx,centerY=ay+t*dy,centerZ=az+t*dz;
|
||||||
const double radius=std::lerp(qToMetres(a.radiusSubvoxels),
|
const double radius=std::lerp(a.radius,b.radius,t);
|
||||||
qToMetres(b.radiusSubvoxels),t);
|
|
||||||
const double horizontalSquared=dx*dx+dy*dy;
|
const double horizontalSquared=dx*dx+dy*dy;
|
||||||
const double floorT=horizontalSquared>1.0e-12
|
const double floorT=horizontalSquared>1.0e-12
|
||||||
?std::clamp(((xMetres-ax)*dx+(yMetres-ay)*dy)
|
?std::clamp(((xMetres-ax)*dx+(yMetres-ay)*dy)
|
||||||
/horizontalSquared,0.0,1.0):t;
|
/horizontalSquared,0.0,1.0):t;
|
||||||
const double floorRadius=std::lerp(qToMetres(a.radiusSubvoxels),
|
const double floorRadius=std::lerp(a.radius,b.radius,floorT);
|
||||||
qToMetres(b.radiusSubvoxels),floorT);
|
const double floorCenterZ=std::lerp(a.z,b.z,floorT);
|
||||||
const double floorAlong=distances[segment]
|
const double along=std::lerp(a.distance,b.distance,t);
|
||||||
+(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 floorDepth=passageTubeDepthAt(floorRadius,bodyDepth);
|
const double floorDepth=passageTubeDepthAt(floorRadius,bodyDepth);
|
||||||
const double roundDistance=std::sqrt((xMetres-centerX)*(xMetres-centerX)
|
const double roundDistance=std::sqrt((xMetres-centerX)*(xMetres-centerX)
|
||||||
+(yMetres-centerY)*(yMetres-centerY)
|
+(yMetres-centerY)*(yMetres-centerY)
|
||||||
@@ -4045,7 +4233,11 @@ namespace StandaloneStreamBenchmark
|
|||||||
const double fade=std::clamp(bestMouthDistance/5.0,0.0,1.0);
|
const double fade=std::clamp(bestMouthDistance/5.0,0.0,1.0);
|
||||||
const double smoothFade=fade*fade*(3.0-2.0*fade);
|
const double smoothFade=fade*fade*(3.0-2.0*fade);
|
||||||
const double floorBlend=smoothstep(bestFloorDistance/0.25);
|
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;
|
if(bestFlatDistance+ReliefBoundMetres<thickness)expectedSolid=true;
|
||||||
else if(bestFlatDistance-ReliefBoundMetres<thickness)ambiguous=true;
|
else if(bestFlatDistance-ReliefBoundMetres<thickness)ambiguous=true;
|
||||||
else if(bestMouthDistance<5.0)mouthBlend=true;
|
else if(bestMouthDistance<5.0)mouthBlend=true;
|
||||||
@@ -4056,19 +4248,18 @@ namespace StandaloneStreamBenchmark
|
|||||||
};
|
};
|
||||||
const auto auditPassageShell=[&](const VoxelForgeCore::World& Field,
|
const auto auditPassageShell=[&](const VoxelForgeCore::World& Field,
|
||||||
const VoxelForgeCore::World& Native,
|
const VoxelForgeCore::World& Native,
|
||||||
const VoxelForgeCore::ResolvedLayoutPassageV1& passage)
|
const VoxelForgeCore::ResolvedLayoutPassageV1& passage,
|
||||||
|
std::size_t passageIndex)
|
||||||
{
|
{
|
||||||
FPassageShellAudit audit;
|
FPassageShellAudit audit;
|
||||||
if(passage.controlPoints.size()<2)return audit;
|
const auto& route=allPassageSmoothRoutes[passageIndex];
|
||||||
const std::size_t routeEnd=std::clamp<std::size_t>(
|
if(route.size()<2)return audit;
|
||||||
passage.lowerMouthControlIndex,1,passage.controlPoints.size()-1);
|
const double totalRouteLength=route.back().distance;
|
||||||
const std::size_t geometryEnd=passage.controlPoints.size()-1;
|
const double bodyFloorDepth=allPassageBodyFloorDepths[passageIndex];
|
||||||
const auto routeDistances=passageRouteDistances(passage);
|
constexpr std::array<std::array<double,2>,5> floorOffsets{{
|
||||||
const double totalRouteLength=routeDistances[std::clamp<std::size_t>(
|
{{0.0,0.0}},{{0.30,0.0}},{{-0.30,0.0}},
|
||||||
passage.lowerMouthControlIndex,1,passage.controlPoints.size()-1)];
|
{{0.0,0.30}},{{0.0,-0.30}}}};
|
||||||
const double bodyFloorDepth=passageBodyFloorDepth(passage);
|
const auto expectedFloorThickness=[&](double along,double floorDistance)
|
||||||
const auto mouthFloorDepths=passageMouthFloorDepths(passage);
|
|
||||||
const auto expectedShellThickness=[&](double along,double floorDistance)
|
|
||||||
{
|
{
|
||||||
const double mouthDistance=std::max(0.0,std::min(along,
|
const double mouthDistance=std::max(0.0,std::min(along,
|
||||||
totalRouteLength-along));
|
totalRouteLength-along));
|
||||||
@@ -4077,43 +4268,27 @@ namespace StandaloneStreamBenchmark
|
|||||||
const double floorBlend=std::clamp(floorDistance/0.25,0.0,1.0);
|
const double floorBlend=std::clamp(floorDistance/0.25,0.0,1.0);
|
||||||
const double smoothFloorBlend=floorBlend*floorBlend
|
const double smoothFloorBlend=floorBlend*floorBlend
|
||||||
*(3.0-2.0*floorBlend);
|
*(3.0-2.0*floorBlend);
|
||||||
const double extensionFade=std::clamp(1.0
|
return 0.75*smoothFade*smoothFloorBlend;
|
||||||
-(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);
|
|
||||||
};
|
};
|
||||||
constexpr std::array<std::array<double,2>,5> floorOffsets{{
|
for(std::size_t segment=1;segment<route.size();++segment)
|
||||||
{{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)
|
|
||||||
{
|
{
|
||||||
const auto& a=passage.controlPoints[segment-1];
|
const auto& a=route[segment-1];
|
||||||
const auto& b=passage.controlPoints[segment];
|
const auto& b=route[segment];
|
||||||
const double ax=qToMetres(a.positionSubvoxels[0]);
|
const double dx=b.x-a.x,dy=b.y-a.y,dz=b.z-a.z;
|
||||||
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 double length=std::sqrt(dx*dx+dy*dy+dz*dz);
|
const double length=std::sqrt(dx*dx+dy*dy+dz*dz);
|
||||||
const double authoredSegmentLength=routeDistances[segment]
|
const double alongLength=b.distance-a.distance;
|
||||||
-routeDistances[segment-1];
|
|
||||||
const std::size_t intervals=std::max<std::size_t>(1,
|
const std::size_t intervals=std::max<std::size_t>(1,
|
||||||
static_cast<std::size_t>(std::ceil(length/PassageShellAuditSpacingMetres)));
|
static_cast<std::size_t>(std::ceil(
|
||||||
for(std::size_t sampleIndex=0;sampleIndex<=intervals;++sampleIndex)
|
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 t=static_cast<double>(sampleIndex)/intervals;
|
||||||
const double centerX=ax+dx*t,centerY=ay+dy*t,centerZ=az+dz*t;
|
const double centerX=a.x+dx*t,centerY=a.y+dy*t,centerZ=a.z+dz*t;
|
||||||
const double along=routeDistances[segment-1]
|
const double along=a.distance+alongLength*t;
|
||||||
+authoredSegmentLength*t;
|
const double radius=std::lerp(a.radius,b.radius,t);
|
||||||
const double radius=std::lerp(qToMetres(a.radiusSubvoxels),
|
|
||||||
qToMetres(b.radiusSubvoxels),t);
|
|
||||||
if(std::min(along,totalRouteLength-along)>0.0)
|
if(std::min(along,totalRouteLength-along)>0.0)
|
||||||
|
{
|
||||||
for(const auto& offset:floorOffsets)
|
for(const auto& offset:floorOffsets)
|
||||||
for(const double depth:PassageShellAuditDepthsMetres)
|
for(const double depth:PassageShellAuditDepthsMetres)
|
||||||
{
|
{
|
||||||
@@ -4121,44 +4296,30 @@ namespace StandaloneStreamBenchmark
|
|||||||
const double sampleX=centerX+offset[0];
|
const double sampleX=centerX+offset[0];
|
||||||
const double sampleY=centerY+offset[1];
|
const double sampleY=centerY+offset[1];
|
||||||
const double floorT=horizontalLengthSquared>1.0e-12
|
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;
|
/horizontalLengthSquared,0.0,1.0):t;
|
||||||
const double floorAlong=routeDistances[segment-1]
|
const double floorAlong=a.distance+alongLength*floorT;
|
||||||
+authoredSegmentLength*floorT;
|
const double floorRadius=std::lerp(a.radius,b.radius,floorT);
|
||||||
const double floorRadius=std::lerp(
|
const double floorCenterZ=std::lerp(a.z,b.z,floorT);
|
||||||
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 floorDepth=passageTubeDepthAt(floorRadius,
|
const double floorDepth=passageTubeDepthAt(floorRadius,
|
||||||
bodyFloorDepth);
|
bodyFloorDepth);
|
||||||
const double floorZ=floorCenterZ-floorDepth;
|
const double floorZ=floorCenterZ-floorDepth;
|
||||||
const VoxelForgeCore::Vec3d position{
|
const VoxelForgeCore::Vec3d position{
|
||||||
sampleX/coreMetres,
|
sampleX/coreMetres,sampleY/coreMetres,
|
||||||
sampleY/coreMetres,
|
|
||||||
(floorZ-depth)/coreMetres};
|
(floorZ-depth)/coreMetres};
|
||||||
const double sampleZ=floorZ-depth;
|
const double sampleZ=floorZ-depth;
|
||||||
if(passageInteriorWinsAt(sampleX,sampleY,sampleZ,
|
if(passageInteriorWinsAt(sampleX,sampleY,sampleZ))
|
||||||
static_cast<std::size_t>(&passage-
|
|
||||||
ResolvedLayout->passages.data()),segment))
|
|
||||||
{
|
{
|
||||||
++audit.floorInteriorWins;
|
++audit.floorInteriorWins;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
++audit.floorSamples;
|
++audit.floorSamples;
|
||||||
const float nativeFloor=Native.densityAt(position);
|
if(Native.densityAt(position)>=0.0f)
|
||||||
if(nativeFloor>=0.0f)
|
|
||||||
{
|
{
|
||||||
++audit.openAirSamples;
|
++audit.openAirSamples;
|
||||||
audit.openAirDistancesMetres.push_back(along);
|
audit.openAirDistancesMetres.push_back(along);
|
||||||
const bool shellExpected=depth+0.011
|
const bool shellExpected=depth+0.011
|
||||||
<expectedShellThickness(along,depth);
|
<expectedFloorThickness(along,depth);
|
||||||
const float density=Field.densityAt(position);
|
const float density=Field.densityAt(position);
|
||||||
if((shellExpected&&density>=0.0f)
|
if((shellExpected&&density>=0.0f)
|
||||||
||(!shellExpected&&density<0.0f))
|
||(!shellExpected&&density<0.0f))
|
||||||
@@ -4166,46 +4327,53 @@ namespace StandaloneStreamBenchmark
|
|||||||
++audit.floorViolations;
|
++audit.floorViolations;
|
||||||
if(audit.firstViolationCount<audit.firstViolations.size())
|
if(audit.firstViolationCount<audit.firstViolations.size())
|
||||||
audit.firstViolations[audit.firstViolationCount++]={
|
audit.firstViolations[audit.firstViolationCount++]={
|
||||||
centerX+offset[0],centerY+offset[1],floorZ-depth,density};
|
sampleX,sampleY,sampleZ,density};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const double tangentLength=std::hypot(dx,dy);
|
const double tangentLength=std::hypot(dx,dy);
|
||||||
if(tangentLength>1.0e-6)
|
if(tangentLength<=1.0e-6)continue;
|
||||||
{
|
|
||||||
if(std::min(along,totalRouteLength-along)<5.0)
|
if(std::min(along,totalRouteLength-along)<5.0)
|
||||||
{
|
{
|
||||||
audit.wallMouthBlendSkips+=5;
|
audit.wallMouthBlendSkips+=5;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const double nx=-dy/tangentLength,ny=dx/tangentLength;
|
const double nx=-dy/tangentLength,ny=dx/tangentLength;
|
||||||
const std::array<std::array<double,3>,5> wallDirections{{
|
const double floorDepth=passageTubeDepthAt(radius,bodyFloorDepth);
|
||||||
{{0.0,0.0,1.0}},
|
const double floorZ=centerZ-floorDepth;
|
||||||
{{-0.7071067811865475*nx,-0.7071067811865475*ny,
|
struct FWallProbe {double side=0.0,height=0.0;bool roof=false;};
|
||||||
0.7071067811865475}},
|
const std::array<FWallProbe,5> wallProbes{{
|
||||||
{{0.7071067811865475*nx,0.7071067811865475*ny,
|
{0.0,0.0,true},{-1.0,0.25,false},{1.0,0.25,false},
|
||||||
0.7071067811865475}},
|
{-1.0,0.75,false},{1.0,0.75,false}}};
|
||||||
{{-nx,-ny,0.0}},{{nx,ny,0.0}}}};
|
for(const auto& probe:wallProbes)
|
||||||
for(const auto& direction:wallDirections)
|
|
||||||
{
|
{
|
||||||
const double sampleX=centerX+direction[0]*(radius+0.375);
|
double sampleX=centerX,sampleY=centerY,sampleZ=0.0;
|
||||||
const double sampleY=centerY+direction[1]*(radius+0.375);
|
if(probe.roof)
|
||||||
const double sampleZ=centerZ+direction[2]*(radius+0.375);
|
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,
|
const VoxelForgeCore::Vec3d wallPosition{sampleX/coreMetres,
|
||||||
sampleY/coreMetres,sampleZ/coreMetres};
|
sampleY/coreMetres,sampleZ/coreMetres};
|
||||||
const bool wallInteriorWins=passageInteriorWinsAt(sampleX,
|
if(passageInteriorWinsAt(sampleX,sampleY,sampleZ,
|
||||||
sampleY,sampleZ,static_cast<std::size_t>(&passage-
|
passageIndex,b.segmentIndex))
|
||||||
ResolvedLayout->passages.data()),segment);
|
|
||||||
if(wallInteriorWins)++audit.wallInteriorWins;
|
|
||||||
else if(Native.densityAt(wallPosition)>=0.0f)
|
|
||||||
{
|
{
|
||||||
|
++audit.wallInteriorWins;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if(Native.densityAt(wallPosition)<0.0f)continue;
|
||||||
++audit.openAirSamples;
|
++audit.openAirSamples;
|
||||||
++audit.wallSamples;
|
++audit.wallSamples;
|
||||||
audit.openAirDistancesMetres.push_back(along);
|
audit.openAirDistancesMetres.push_back(along);
|
||||||
const float density=Field.densityAt(wallPosition);
|
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(
|
const int shellExpected=expectedPassageShellAt(
|
||||||
sampleX,sampleY,sampleZ);
|
sampleX,sampleY,sampleZ);
|
||||||
if(shellExpected>=0
|
if(shellExpected>=0
|
||||||
@@ -4215,15 +4383,12 @@ namespace StandaloneStreamBenchmark
|
|||||||
++audit.wallViolations;
|
++audit.wallViolations;
|
||||||
if(audit.firstWallViolationCount
|
if(audit.firstWallViolationCount
|
||||||
<audit.firstWallViolations.size())
|
<audit.firstWallViolations.size())
|
||||||
audit.firstWallViolations[
|
audit.firstWallViolations[audit.firstWallViolationCount++]={
|
||||||
audit.firstWallViolationCount++]={
|
|
||||||
sampleX,sampleY,sampleZ,density};
|
sampleX,sampleY,sampleZ,density};
|
||||||
}
|
}
|
||||||
else if(shellExpected<0)
|
else if(shellExpected<0)
|
||||||
++audit.wallMouthBlendSkips;
|
++audit.wallMouthBlendSkips;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
++audit.interiorSamples;
|
++audit.interiorSamples;
|
||||||
const float interior=Field.densityAt({centerX/coreMetres,
|
const float interior=Field.densityAt({centerX/coreMetres,
|
||||||
centerY/coreMetres,centerZ/coreMetres});
|
centerY/coreMetres,centerZ/coreMetres});
|
||||||
@@ -4253,42 +4418,15 @@ namespace StandaloneStreamBenchmark
|
|||||||
for(std::size_t passageIndex=0;passageIndex<ResolvedLayout->passages.size();++passageIndex)
|
for(std::size_t passageIndex=0;passageIndex<ResolvedLayout->passages.size();++passageIndex)
|
||||||
{
|
{
|
||||||
const auto& passage=ResolvedLayout->passages[passageIndex];
|
const auto& passage=ResolvedLayout->passages[passageIndex];
|
||||||
std::vector<std::array<double,3>> path;path.reserve(passage.controlPoints.size());
|
const auto& smoothRoute=allPassageSmoothRoutes[passageIndex];
|
||||||
const auto routeDistances=passageRouteDistances(passage);
|
const double bodyFloorDepth=allPassageBodyFloorDepths[passageIndex];
|
||||||
const double routeLength=routeDistances.empty()?0.0:routeDistances[
|
std::vector<std::array<double,3>> path;path.reserve(smoothRoute.size());
|
||||||
std::clamp<std::size_t>(passage.lowerMouthControlIndex,1,
|
for(const auto& point:smoothRoute)
|
||||||
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& a=passage.controlPoints[segment-1];
|
const double playerZ=point.z
|
||||||
const auto& b=passage.controlPoints[segment];
|
-passageTubeDepthAt(point.radius,bodyFloorDepth)+1.13;
|
||||||
const double ax=qToMetres(a.positionSubvoxels[0]);
|
path.push_back({point.x/coreMetres,point.y/coreMetres,
|
||||||
const double ay=qToMetres(a.positionSubvoxels[1]);
|
playerZ/coreMetres});
|
||||||
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});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// Probe below the capsule feet far enough to enter the new 0.75 m
|
// 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
|
// floor shell; the old quarter-metre search only checked the air
|
||||||
@@ -4298,7 +4436,8 @@ namespace StandaloneStreamBenchmark
|
|||||||
{return passageInteriorWinsAt(x,y,z-0.25);};
|
{return passageInteriorWinsAt(x,y,z-0.25);};
|
||||||
const FWalkAudit passageAudit=auditPlayerPath(Shape,path,0.375,0.125,
|
const FWalkAudit passageAudit=auditPlayerPath(Shape,path,0.375,0.125,
|
||||||
1.125,3,0.45,passageSupportExemption);
|
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(),
|
const auto lowerProfileStratum=std::find_if(ResolvedLayout->strates.begin(),
|
||||||
ResolvedLayout->strates.end(),[&](const auto& stratum)
|
ResolvedLayout->strates.end(),[&](const auto& stratum)
|
||||||
{return stratum.strateIndex==passage.lowerStrateIndex;});
|
{return stratum.strateIndex==passage.lowerStrateIndex;});
|
||||||
@@ -4309,10 +4448,10 @@ namespace StandaloneStreamBenchmark
|
|||||||
{return anchor.kind=="heightfield_profile";});
|
{return anchor.kind=="heightfield_profile";});
|
||||||
const FPassageMouthProfileAudit mouthProfileAudit=
|
const FPassageMouthProfileAudit mouthProfileAudit=
|
||||||
auditPassageMouthProfiles(Shape,passage,profiledLowerSurface);
|
auditPassageMouthProfiles(Shape,passage,profiledLowerSurface);
|
||||||
const FPassageRippleAudit rippleAudit=auditPassageRipples(Shape,passage,
|
const FPassageRippleAudit rippleAudit=
|
||||||
passageIndex);
|
auditPassageRipples(Shape,passageIndex);
|
||||||
const FPassageShellAudit shellAudit=auditPassageShell(Shape,blockedWorld,
|
const FPassageShellAudit shellAudit=auditPassageShell(Shape,blockedWorld,
|
||||||
passage);
|
passage,passageIndex);
|
||||||
std::vector<double> openAirDistances=shellAudit.openAirDistancesMetres;
|
std::vector<double> openAirDistances=shellAudit.openAirDistancesMetres;
|
||||||
std::sort(openAirDistances.begin(),openAirDistances.end());
|
std::sort(openAirDistances.begin(),openAirDistances.end());
|
||||||
openAirDistances.erase(std::unique(openAirDistances.begin(),
|
openAirDistances.erase(std::unique(openAirDistances.begin(),
|
||||||
@@ -4326,29 +4465,10 @@ namespace StandaloneStreamBenchmark
|
|||||||
const double startAlong=openAirDistances[first];
|
const double startAlong=openAirDistances[first];
|
||||||
const double endAlong=openAirDistances[end-1];
|
const double endAlong=openAirDistances[end-1];
|
||||||
const double midpoint=(startAlong+endAlong)*0.5;
|
const double midpoint=(startAlong+endAlong)*0.5;
|
||||||
const auto upper=std::lower_bound(routeDistances.begin()+1,
|
const auto center=interpolatePassageRoute(smoothRoute,midpoint);
|
||||||
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);
|
|
||||||
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",
|
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,
|
passage.stableId.c_str(),section+1,startAlong,endAlong,end-first,
|
||||||
std::lerp(qToMetres(a.positionSubvoxels[0]),
|
center.x,center.y,center.z,center.radius);
|
||||||
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));
|
|
||||||
first=end;
|
first=end;
|
||||||
}
|
}
|
||||||
shellAudits+=1;
|
shellAudits+=1;
|
||||||
|
|||||||
@@ -122,11 +122,11 @@ RECIPE_RE = re.compile(
|
|||||||
|
|
||||||
def owner_hash_baseline(recipe: str, style: int) -> tuple[Path, str]:
|
def owner_hash_baseline(recipe: str, style: int) -> tuple[Path, str]:
|
||||||
if recipe == "surface-world" and style == 1:
|
if recipe == "surface-world" and style == 1:
|
||||||
return (BASELINES / "owner-hash/canonical/owner-hash-surface-world-style1-v217.baseline",
|
return (BASELINES / "owner-hash/canonical/owner-hash-surface-world-style1-v218.baseline",
|
||||||
"L109-v217-flat-floor-hollow-shell")
|
"L111-v218-smooth-spline-half-pipe")
|
||||||
if recipe == "floating-islands" and style == 2:
|
if recipe == "floating-islands" and style == 2:
|
||||||
return (BASELINES / "owner-hash/canonical/owner-hash-floating-islands-style2-v217.baseline",
|
return (BASELINES / "owner-hash/canonical/owner-hash-floating-islands-style2-v218.baseline",
|
||||||
"L109-v217-flat-floor-hollow-shell")
|
"L111-v218-smooth-spline-half-pipe")
|
||||||
raise VerificationFailure(f"no pinned owner hash baseline for {recipe} style {style}")
|
raise VerificationFailure(f"no pinned owner hash baseline for {recipe} style {style}")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user