passages: continuous floor h(s) along the dense route; shell-probe oracle fix (L115, v223)

Floor height from the planner profile h(s) by horizontal projection + arc-length interpolation;
adjacent fine segments share endpoint values. Floor zero-crossing offsets max 7.8 mm (L114: up
to 73 mm); rock-floor close-ups show no ribs. The 2 L114 shell misses were oracle false positives
(points inside an overlapping bore); the oracle now checks every overlapping span; no gate relaxed.
Fast tier PASS; Clang/GCC W1/W8/W12 deterministic. Speed regression grows: near-ready +17..+31% vs
L109 (fine-segment/sample pairs 49.0M vs 19.8M). Not merged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
This commit is contained in:
Fr0zkaandClaude Opus 5.5 committed 2026-10-30 01:48:29 +01:00
1 parent 5e875ae504
commit f403af1fc1
37 files changed
+2741 -183

No files matched your search

@@ -473,8 +473,9 @@ namespace VoxelForgeCore
// Version 219 smooths passage XY while mapping each planned height chord over its sampled horizontal arc.
// Version 220 adaptively samples passage curves and rounds open-air wall crests.
// Version 221 ranks passage segments before floor shaping and preserves their signed exterior distance.
// Version 222 selects the floor profile by centerline position, independent of tube radius.
inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 222;
// Version 223 projects the floor onto one continuous arc-length profile and groups
// dense passage samples into coarse tile candidates.
inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 223;
enum class FieldChannel : std::uint32_t
{
+140 -61
View File
@@ -256,6 +256,7 @@ public:
struct FCandidateSegment
{
std::size_t segmentIndex=0;
std::size_t segmentEndIndex=0;
std::array<std::int64_t,3> minimumSubvoxels{},maximumSubvoxels{};
std::array<std::int64_t,3> sweepMinimumSubvoxels{},sweepMaximumSubvoxels{};
};
@@ -330,6 +331,7 @@ public:
const auto& bounds=shape.PassageFeatureBounds[featureIndex];
FCandidateSegment candidate;
candidate.segmentIndex=feature.segmentIndex;
candidate.segmentEndIndex=feature.segmentEndIndex;
candidate.minimumSubvoxels=bounds.minimumSubvoxels;
candidate.maximumSubvoxels=bounds.maximumSubvoxels;
candidate.sweepMinimumSubvoxels=bounds.minimumSubvoxels;
@@ -1277,6 +1279,7 @@ public:
const std::ptrdiff_t lastIndex=static_cast<std::ptrdiff_t>(
PassageSegmentData[passageIndex].size())-1;
const auto& segments=PassageSegmentData[passageIndex];
double bestDistance=std::numeric_limits<double>::infinity();
double bestFloorCenterDistanceSq=std::numeric_limits<double>::infinity();
const auto visitSegment=[&](std::size_t segmentIndex,int)
@@ -1295,21 +1298,23 @@ public:
const double ex=qx-cx,ey=qy-cy,ez=qz-cz;
const double roundDistance=DeterministicSqrt(
ex*ex+ey*ey+ez*ez)-radius;
const double floorCenterDistanceSq=ex*ex+ey*ey+ez*ez;
const double floorT=segment.horizontalLengthSq>1.0e-12
?std::clamp(((qx-segment.a[0])*segment.delta[0]
?(((qx-segment.a[0])*segment.delta[0]
+(qy-segment.a[1])*segment.delta[1])
/segment.horizontalLengthSq,0.0,1.0):t;
const double floorRadius=segment.radiusA+floorT*segment.radiusDelta;
/segment.horizontalLengthSq):t;
const double floorTClamped=std::clamp(floorT,0.0,1.0);
const double floorCenterDistanceSq=ex*ex+ey*ey+ez*ez;
const double floorRadius=segment.radiusA
+floorTClamped*segment.radiusDelta;
const double floorDepth=PassageFlatFloorDepthSubvoxels(
constants,segmentIndex,floorT,floorRadius,qPerMetre);
constants,segmentIndex,floorTClamped,floorRadius,qPerMetre);
const double floorCenterZ=PassageFlatFloorCenterZSubvoxels(
constants,segmentIndex,floorT,segment,qx,qy,floorDepth,qPerMetre);
constants,segments,segmentIndex,floorT,segment,
qx,qy,floorDepth,qPerMetre);
const double flatDistance=PassageFlatBottomDistance(
static_cast<float>(roundDistance),floorCenterZ,qz,floorDepth,qPerMetre);
// Radius-based tube selection can favor a wider downhill segment
// and carry its lower floor plane backward. Select the floor profile
// by the centerline position, independently of tube radius.
// The profile uses the closest route projection's arc length. Tube
// radius selects the bore shape only; it never selects floor height.
if(floorCenterDistanceSq<bestFloorCenterDistanceSq
||(floorCenterDistanceSq==bestFloorCenterDistanceSq
&&segmentIndex<nearest.floorSegmentIndex))
@@ -1365,14 +1370,18 @@ public:
const double ex=qx-cx,ey=qy-cy,ez=qz-cz;
const double roundDistance=DeterministicSqrt(ex*ex+ey*ey+ez*ez)-radius;
const double floorT=segment.horizontalLengthSq>1.0e-12
?std::clamp(((qx-segment.a[0])*segment.delta[0]
?(((qx-segment.a[0])*segment.delta[0]
+(qy-segment.a[1])*segment.delta[1])
/segment.horizontalLengthSq,0.0,1.0):t;
const double floorRadius=segment.radiusA+floorT*segment.radiusDelta;
/segment.horizontalLengthSq):t;
const double floorTClamped=std::clamp(floorT,0.0,1.0);
const auto& segments=PassageSegmentData[passageIndex];
const double floorRadius=segment.radiusA
+floorTClamped*segment.radiusDelta;
const double floorDepth=PassageFlatFloorDepthSubvoxels(
constants,segmentIndex,floorT,floorRadius,qPerMetre);
constants,segmentIndex,floorTClamped,floorRadius,qPerMetre);
const double floorCenterZ=PassageFlatFloorCenterZSubvoxels(
constants,segmentIndex,floorT,segment,qx,qy,floorDepth,qPerMetre);
constants,segments,segmentIndex,floorT,segment,
qx,qy,floorDepth,qPerMetre);
const double flatDistance=PassageFlatBottomDistance(
static_cast<float>(roundDistance),floorCenterZ,qz,floorDepth,qPerMetre);
const double alongMetres=segment.authoredAlongMetres
@@ -3401,12 +3410,13 @@ public:
private:
struct FPassageTileFeature
{
std::size_t passageIndex=0,segmentIndex=0;
std::size_t passageIndex=0,segmentIndex=0,segmentEndIndex=0;
};
struct FPassageComposerConstants
{
double floorDepthMetres=0.0,totalPathLengthMetres=0.0;
double authoredTotalPathLengthMetres=0.0;
std::size_t routeProfileSegmentCount=0;
double upperMouthX=0.0,upperMouthY=0.0;
double upperMouthPlannedFloor=0.0;
double upperMouthTangentX=0.0,upperMouthTangentY=0.0;
@@ -3436,14 +3446,40 @@ private:
}
static double PassageFlatFloorCenterZSubvoxels(
const FPassageComposerConstants& constants,std::size_t segmentIndex,double floorT,
const FPassageComposerConstants& constants,
const std::vector<FPassageSegmentData>& segments,
std::size_t segmentIndex,double floorT,
const FPassageSegmentData& segment,double queryXSubvoxels,double queryYSubvoxels,
double floorDepthSubvoxels,double qPerMetre)
{
if(!segment.routeSegment||qPerMetre<=0.0)
return segment.a[2]+floorT*segment.delta[2];
const double plannedFloor=segment.authoredFloorZA
return segment.a[2]+std::clamp(floorT,0.0,1.0)*segment.delta[2];
const std::size_t profileCount=std::min(
constants.routeProfileSegmentCount,segments.size());
double plannedFloor=segment.authoredFloorZA
+floorT*segment.authoredFloorZDelta;
if(profileCount>0)
{
if(floorT>=0.0&&floorT<=1.0)
plannedFloor=segment.authoredFloorZA
+floorT*segment.authoredFloorZDelta;
else
{
const double distance=segment.authoredAlongMetres
+floorT*segment.authoredLengthMetres;
const auto profileEnd=segments.begin()+profileCount;
const auto upper=std::lower_bound(segments.begin(),profileEnd,distance,
[](const FPassageSegmentData& candidate,double value)
{return candidate.authoredAlongMetres
+candidate.authoredLengthMetres<value;});
const auto& profileSegment=upper==profileEnd?*(profileEnd-1):*upper;
const double profileT=profileSegment.authoredLengthMetres>1.0e-12
?std::clamp((distance-profileSegment.authoredAlongMetres)
/profileSegment.authoredLengthMetres,0.0,1.0):0.0;
plannedFloor=profileSegment.authoredFloorZA
+profileT*profileSegment.authoredFloorZDelta;
}
}
const double mouthBlendFloor=VoxelForgeCore::PassageSpline::BlendUpperMouthFloorAtXY(
constants.upperMouthReceivingRoute,queryXSubvoxels,queryYSubvoxels,
plannedFloor,constants.upperMouthX,constants.upperMouthY,
@@ -4325,7 +4361,7 @@ private:
template<class FVisitor>
void VisitNearestPassageSegmentRange(std::size_t passageIndex,
std::ptrdiff_t first,std::ptrdiff_t last,double qx,double qy,double qz,
int priority,double& bestDistance,double& bestCenterDistanceSq,
int priority,double& bestDistance,double& bestFloorCenterDistanceSq,
FVisitor&& visitor) const
{
(void)priority;
@@ -4359,8 +4395,9 @@ private:
if(static_cast<std::ptrdiff_t>(node.maximumSegment)<first
||static_cast<std::ptrdiff_t>(node.minimumSegment)>last
||(lowerBound(node)>bestDistance
&&(bestCenterDistanceSq<0.0
||centerDistanceLowerBoundSq(node)>bestCenterDistanceSq)))return;
&&(bestFloorCenterDistanceSq<0.0
||centerDistanceLowerBoundSq(node)
>bestFloorCenterDistanceSq)))return;
if(node.left<0)
{
for(const std::size_t segment:node.segments)
@@ -4740,6 +4777,9 @@ private:
segmentData.push_back(data);
++preparedSegments;
}
constants.routeProfileSegmentCount=static_cast<std::size_t>(std::count_if(
segmentData.begin(),segmentData.end(),
[](const FPassageSegmentData& segment){return segment.routeSegment;}));
PassageSegmentOffsets[passageIndex+1]=preparedSegments;
}
if(tracePreparation)
@@ -4770,6 +4810,9 @@ private:
constexpr double ReliefMetres=0.21;
constexpr double SweepReachMetres=4.0;
constexpr double CornerBlendMetres=0.50;
constexpr std::size_t CandidateFineSamplesPerSpan=5;
const double requiredReach=std::max(SweepReachMetres+ReliefMetres
+CornerBlendMetres*0.25,PassageShellMetres+ReliefMetres);
for(std::size_t passageIndex=0;passageIndex<ResolvedLayout->passages.size();
++passageIndex)
{
@@ -4778,47 +4821,67 @@ private:
std::vector<std::size_t> segmentIds(segmentCount);
for(std::size_t i=0;i<segmentCount;++i)segmentIds[i]=i;
BuildPassageNearestNode(passageIndex,std::move(segmentIds));
for(std::size_t segmentIndex=0;segmentIndex<segmentCount;++segmentIndex)
for(std::size_t segmentIndex=0;segmentIndex<segmentCount;
segmentIndex+=CandidateFineSamplesPerSpan)
{
auto& prepared=PassageSegmentData[passageIndex][segmentIndex];
const double maximumRadius=std::max(prepared.radiusA,
prepared.radiusB)/Q;
std::array<double,3> routeMinimum{},routeMaximum{};
for(std::size_t axis=0;axis<3;++axis)
const std::size_t segmentEndIndex=std::min(
segmentIndex+CandidateFineSamplesPerSpan,segmentCount);
std::array<double,3> routeMinimum{
std::numeric_limits<double>::infinity(),
std::numeric_limits<double>::infinity(),
std::numeric_limits<double>::infinity()};
std::array<double,3> routeMaximum{
-std::numeric_limits<double>::infinity(),
-std::numeric_limits<double>::infinity(),
-std::numeric_limits<double>::infinity()};
double maximumRadiusSubvoxels=0.0;
for(std::size_t localIndex=segmentIndex;
localIndex<segmentEndIndex;++localIndex)
{
routeMinimum[axis]=std::min(prepared.a[axis],prepared.b[axis])/Q;
routeMaximum[axis]=std::max(prepared.a[axis],prepared.b[axis])/Q;
}
const double requiredReach=std::max(SweepReachMetres+ReliefMetres
+CornerBlendMetres*0.25,PassageShellMetres+ReliefMetres);
const double routeExtent=maximumRadius+requiredReach+0.01;
const double tileExtent=routeExtent;
FPassageProofBound proof;
for(std::size_t axis=0;axis<3;++axis)
{
const double extent=routeExtent;
proof.minimum[axis]=routeMinimum[axis]-extent;
proof.maximum[axis]=routeMaximum[axis]+extent;
}
PassageProofBounds.push_back(proof);
auto& prepared=PassageSegmentData[passageIndex][localIndex];
maximumRadiusSubvoxels=std::max({maximumRadiusSubvoxels,
prepared.radiusA,prepared.radiusB});
std::array<double,3> localMinimum{},localMaximum{};
for(std::size_t axis=0;axis<3;++axis)
{
localMinimum[axis]=std::min(prepared.a[axis],prepared.b[axis])/Q;
localMaximum[axis]=std::max(prepared.a[axis],prepared.b[axis])/Q;
routeMinimum[axis]=std::min(routeMinimum[axis],localMinimum[axis]);
routeMaximum[axis]=std::max(routeMaximum[axis],localMaximum[axis]);
}
const double localExtent=requiredReach
+std::max(prepared.radiusA,prepared.radiusB)/Q+0.01;
FPassageProofBound proof;
for(std::size_t axis=0;axis<3;++axis)
{
proof.minimum[axis]=localMinimum[axis]-localExtent;
proof.maximum[axis]=localMaximum[axis]+localExtent;
}
PassageProofBounds.push_back(proof);
for(std::size_t axis=0;axis<3;++axis)
{
prepared.sweepMinimumSubvoxels[axis]=static_cast<std::int64_t>(
std::floor((localMinimum[axis]-localExtent)*Q));
prepared.sweepMaximumSubvoxels[axis]=static_cast<std::int64_t>(
std::ceil((localMaximum[axis]+localExtent)*Q));
}
}
const double routeExtent=maximumRadiusSubvoxels/Q+requiredReach+0.01;
const double tileExtent=routeExtent;
std::array<std::int64_t,3> featureMinimum{},featureMaximum{};
std::array<std::int64_t,3> sweepMinimum{},sweepMaximum{};
for(std::size_t axis=0;axis<3;++axis)
{
featureMinimum[axis]=static_cast<std::int64_t>(std::floor(
(routeMinimum[axis]-tileExtent)*Q));
featureMaximum[axis]=static_cast<std::int64_t>(std::ceil(
(routeMaximum[axis]+tileExtent)*Q));
sweepMinimum[axis]=featureMinimum[axis];
sweepMaximum[axis]=featureMaximum[axis];
prepared.sweepMinimumSubvoxels[axis]=sweepMinimum[axis];
prepared.sweepMaximumSubvoxels[axis]=sweepMaximum[axis];
}
PassageFeatureBounds.push_back({
static_cast<std::uint64_t>(PassageTileFeatures.size()),
featureMinimum,featureMaximum});
PassageTileFeatures.push_back({passageIndex,segmentIndex});
PassageTileFeatures.push_back(
{passageIndex,segmentIndex,segmentEndIndex});
}
}
for(std::size_t i=0;i<PassageFeatureBounds.size();++i)
@@ -11429,17 +11492,19 @@ inline void FStandaloneShapeWorld::FScopedPassageTileQueries::prepareSweptTubeBa
passageSamples.passageIndex=passageIndex;
passageSamples.samples.resize(gridSampleCount);
const auto& constants=shape_->PassageComposerConstants[passageIndex];
const auto& segments=shape_->PassageSegmentData[passageIndex];
for(const auto& candidate:candidates)
{
if(candidate.segmentIndex>=shape_->PassageSegmentData[passageIndex].size())continue;
if(candidate.segmentIndex>=segments.size())continue;
const std::size_t segmentEndIndex=std::min(candidate.segmentEndIndex,
segments.size());
if(candidate.segmentIndex>=segmentEndIndex)continue;
std::array<std::int32_t,3> first{},last{};
for(std::size_t axis=0;axis<3;++axis)
gridRange(axis,candidate.sweepMinimumSubvoxels[axis],
candidate.sweepMaximumSubvoxels[axis],first[axis],last[axis]);
if(first[0]>last[0]||first[1]>last[1]||first[2]>last[2])continue;
const auto& segment=shape_->PassageSegmentData[passageIndex][candidate.segmentIndex];
if(segment.lengthSq<=0.0)continue;
for(std::int32_t gz=first[2];gz<=last[2];++gz)
{
for(std::int32_t gy=first[1];gy<=last[1];++gy)
@@ -11451,6 +11516,17 @@ inline void FStandaloneShapeWorld::FScopedPassageTileQueries::prepareSweptTubeBa
+static_cast<std::int64_t>(gy)*sweep->stepSubvoxels);
const double qz=static_cast<double>(sweep->gridMinimumSubvoxels[2]
+static_cast<std::int64_t>(gz)*sweep->stepSubvoxels);
for(std::size_t segmentIndex=candidate.segmentIndex;
segmentIndex<segmentEndIndex;++segmentIndex)
{
const auto& segment=segments[segmentIndex];
if(segment.lengthSq<=0.0
||qx<segment.sweepMinimumSubvoxels[0]
||qx>segment.sweepMaximumSubvoxels[0]
||qy<segment.sweepMinimumSubvoxels[1]
||qy>segment.sweepMaximumSubvoxels[1]
||qz<segment.sweepMinimumSubvoxels[2]
||qz>segment.sweepMaximumSubvoxels[2])continue;
const double projectionXY=(qx-segment.a[0])*segment.delta[0]
+(qy-segment.a[1])*segment.delta[1];
const double t=std::clamp((projectionXY
@@ -11462,16 +11538,18 @@ inline void FStandaloneShapeWorld::FScopedPassageTileQueries::prepareSweptTubeBa
const double ex=qx-cx,ey=qy-cy,ez=qz-cz;
const double signedDistance=FStandaloneShapeWorld::DeterministicSqrt(
ex*ex+ey*ey+ez*ez)-radius;
const double floorCenterDistanceSq=ex*ex+ey*ey+ez*ez;
const double floorT=segment.horizontalLengthSq>1.0e-12
?std::clamp(projectionXY/segment.horizontalLengthSq,0.0,1.0):t;
const double floorRadius=segment.radiusA+floorT*segment.radiusDelta;
?projectionXY/segment.horizontalLengthSq:t;
const double floorTClamped=std::clamp(floorT,0.0,1.0);
const double floorCenterDistanceSq=ex*ex+ey*ey+ez*ez;
const double floorRadius=segment.radiusA
+floorTClamped*segment.radiusDelta;
const double floorDepth=FStandaloneShapeWorld::PassageFlatFloorDepthSubvoxels(
constants,candidate.segmentIndex,floorT,floorRadius,qPerMetre);
constants,segmentIndex,floorTClamped,floorRadius,qPerMetre);
const double floorCenterZ=
FStandaloneShapeWorld::PassageFlatFloorCenterZSubvoxels(
constants,candidate.segmentIndex,floorT,segment,qx,qy,floorDepth,
qPerMetre);
constants,segments,segmentIndex,floorT,segment,
qx,qy,floorDepth,qPerMetre);
const double flatDistance=FStandaloneShapeWorld::PassageFlatBottomDistance(
static_cast<float>(signedDistance),floorCenterZ,qz,floorDepth,qPerMetre);
if(profile)++profileStats_->passageSweepSegmentSamplePairs;
@@ -11485,14 +11563,14 @@ inline void FStandaloneShapeWorld::FScopedPassageTileQueries::prepareSweptTubeBa
auto& sample=passageSamples.samples[sampleIndex];
if(floorCenterDistanceSq<sample.floorCenterDistanceSq
||(floorCenterDistanceSq==sample.floorCenterDistanceSq
&&candidate.segmentIndex<sample.floorSegmentIndex))
&&segmentIndex<sample.floorSegmentIndex))
{
sample.floorCenterDistanceSq=floorCenterDistanceSq;
sample.floorCenterZSubvoxels=floorCenterZ;
sample.floorDepthSubvoxels=floorDepth;
sample.floorT=floorT;
sample.floorSegmentIndex=static_cast<std::uint32_t>(
candidate.segmentIndex);
segmentIndex);
}
const double alongMetres=segment.authoredAlongMetres
+t*segment.authoredLengthMetres;
@@ -11536,19 +11614,20 @@ inline void FStandaloneShapeWorld::FScopedPassageTileQueries::prepareSweptTubeBa
// independently above from the nearest centerline position.
if(!sample.valid||signedDistance<sample.roundSignedDistance
||(signedDistance==sample.roundSignedDistance
&&candidate.segmentIndex<sample.segmentIndex))
&&segmentIndex<sample.segmentIndex))
{
sample.signedDistance=flatDistance;
sample.roundSignedDistance=signedDistance;
sample.centerZSubvoxels=cz;
sample.radiusSubvoxels=radius;
sample.t=t;
sample.segmentIndex=static_cast<std::uint32_t>(candidate.segmentIndex);
sample.segmentIndex=static_cast<std::uint32_t>(segmentIndex);
sample.valid=true;
sample.hasWallRelief=false;
if(profile)++profileStats_->passageSweepMinimumUpdates;
}
}
}
}
}
}