passages: smoothed open-air shell joins (L110, v257) - partial, not merged

Bands = end profiles of per-control-span shell capsules (~9.35 m pitch = span spacing).
Shell now smooth-unions adjacent spans over 6 m and reuses the interior chord distance; a 0.60 m
clearance fades in 5-20 m from each mouth. Close-ups: bands reduced but still visible on part of
the exposed shell. Cost vs L109: near-ready +8..+12% (FI 0.88 -> 1.00, TN 0.92 -> 1.01 s @W12).
Audits, Clang/GCC determinism, fast tier and Linux UE build pass. Kept on its own branch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
This commit is contained in:
Fr0zkaandClaude Opus 5.5 committed 2026-10-29 16:07:52 +01:00
1 parent 5ea01988c4
commit bf39712792
397 files changed
+7754 -128

No files matched your search

+441 -122
View File
@@ -261,14 +261,17 @@ public:
struct FPassageSweepSample
{
double signedDistance=std::numeric_limits<double>::infinity();
double shellSignedDistance=std::numeric_limits<double>::infinity();
double roundSignedDistance=std::numeric_limits<double>::infinity();
double centerZSubvoxels=0.0,floorCenterZSubvoxels=0.0;
double radiusSubvoxels=0.0,floorDepthSubvoxels=0.0;
double t=0.0;
std::uint32_t segmentIndex=std::numeric_limits<std::uint32_t>::max();
double shellSignedDistance=std::numeric_limits<double>::infinity();
std::uint32_t shellSegmentIndex=std::numeric_limits<std::uint32_t>::max();
double previousShellDistance=std::numeric_limits<double>::infinity();
double previousShellBlendWeight=0.0;
float wallRelief=0.0f;
bool valid=false,hasWallRelief=false;
bool valid=false,shellValid=false,hasWallRelief=false;
};
struct FPassageTileSweepData
{
@@ -1195,13 +1198,16 @@ public:
||!std::isfinite(Y)||!std::isfinite(Z))return density;
constexpr double Q=static_cast<double>(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel);
constexpr double FloorShellThicknessMetres=0.75;
constexpr double WallShellThicknessMetres=0.75;
constexpr double ShellClearanceMetres=0.60;
constexpr double WallReliefMetres=0.21;
constexpr double MouthShellBlendMetres=5.0;
const double qPerMetre=Q*1000000.0/
std::max<std::uint32_t>(1,ResolvedLayout->coreVoxelSizeMicrometres);
const double qx=static_cast<double>(X)*Q,qy=static_cast<double>(Y)*Q,
qz=static_cast<double>(Z)*Q;
const double shellThickness=FloorShellThicknessMetres*qPerMetre;
const double floorShellThicknessBase=FloorShellThicknessMetres*qPerMetre;
const double wallShellThickness=WallShellThicknessMetres*qPerMetre;
const auto* passageTileQueries=ActivePassageTileQueries;
const bool passageTileScopeMatches=passageTileQueries
&&passageTileQueries->matches(*this);
@@ -1212,7 +1218,6 @@ public:
struct FNearestTubeSample
{
double signedDistance=std::numeric_limits<double>::infinity();
double shellSignedDistance=std::numeric_limits<double>::infinity();
double roundSignedDistance=std::numeric_limits<double>::infinity();
double centerZ=0.0,floorCenterZ=0.0,radius=0.0,floorDepth=0.0,t=0.0;
std::size_t segmentIndex=0;
@@ -1232,7 +1237,7 @@ public:
continue;
const auto& constants=PassageComposerConstants[passageIndex];
FNearestTubeSample nearest;
FNearestTubeSample nearest,nearestShell;
const bool usePreparedSweep=passageTileScopeMatches
&&passageTileQueries->hasSweptTubeBase();
const auto* swept=usePreparedSweep
@@ -1240,7 +1245,6 @@ public:
if(swept)
{
nearest.signedDistance=swept->signedDistance;
nearest.shellSignedDistance=swept->shellSignedDistance;
nearest.roundSignedDistance=swept->roundSignedDistance;
nearest.segmentIndex=swept->segmentIndex;
nearest.t=swept->t;
@@ -1250,6 +1254,10 @@ public:
nearest.floorDepth=swept->floorDepthSubvoxels;
nearest.valid=true;
nearest.swept=swept;
nearestShell.signedDistance=swept->shellSignedDistance;
nearestShell.segmentIndex=swept->shellSegmentIndex;
nearestShell.valid=swept->shellValid;
nearestShell.swept=swept;
}
else
{
@@ -1265,7 +1273,17 @@ public:
const std::ptrdiff_t lastIndex=static_cast<std::ptrdiff_t>(
passage.controlPoints.size())-1;
constexpr double ShellJoinBlendMetres=6.00;
double bestDistance=std::numeric_limits<double>::infinity();
double bestShellSearchBound=std::numeric_limits<double>::infinity();
struct FShellCandidate
{
double distance=std::numeric_limits<double>::infinity();
std::size_t segmentIndex=0;
double blendWeight=1.0;
};
std::array<FShellCandidate,8> shellCandidates{};
std::size_t shellCandidateCount=0;
const auto visitSegment=[&](std::size_t segmentIndex,int)
{
if(passageProfile)++passageProfile->passageCarveSegmentVisits;
@@ -1275,13 +1293,12 @@ public:
+(qy-segment.a[1])*segment.delta[1]
+(qz-segment.a[2])*segment.delta[2];
const double t=std::clamp(projection/segment.lengthSq,0.0,1.0);
double cx=segment.a[0]+t*segment.delta[0];
double cy=segment.a[1]+t*segment.delta[1];
double cz=segment.a[2]+t*segment.delta[2];
const double cx=segment.a[0]+t*segment.delta[0];
const double cy=segment.a[1]+t*segment.delta[1];
const double cz=segment.a[2]+t*segment.delta[2];
const double radius=segment.radiusA+t*segment.radiusDelta;
const double ex=qx-cx,ey=qy-cy,ez=qz-cz;
const double roundDistance=DeterministicSqrt(
ex*ex+ey*ey+ez*ez)-radius;
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]
+(qy-segment.a[1])*segment.delta[1])
@@ -1308,15 +1325,11 @@ public:
nearest.floorDepth=floorDepth;
nearest.valid=true;
}
};
VisitNearestPassageSegmentRange(passageIndex,0,lastIndex-1,
qx,qy,qz,0,bestDistance,visitSegment);
// Shells form a union over nearby route segments, even when another
// segment supplies the nearest interior distance. The hierarchy bound
// is deliberately wider than the shell so its radius and corner blend
// cannot prune a segment that contributes a boundary shell.
double shellSearchBound=2.0*qPerMetre;
const auto visitShellSegment=[&](std::size_t segmentIndex,int)
{
const auto& segment=PassageSegmentData[passageIndex][segmentIndex];
@@ -1329,8 +1342,9 @@ public:
const double cy=segment.a[1]+t*segment.delta[1];
const double cz=segment.a[2]+t*segment.delta[2];
const double radius=segment.radiusA+t*segment.radiusDelta;
const double ex=qx-cx,ey=qy-cy,ez=qz-cz;
const double roundDistance=DeterministicSqrt(ex*ex+ey*ey+ez*ez)-radius;
const double dx=qx-cx,dy=qy-cy,dz=qz-cz;
const double roundDistance=DeterministicSqrt(dx*dx+dy*dy+dz*dz)
-radius;
const double floorT=segment.horizontalLengthSq>1.0e-12
?std::clamp(((qx-segment.a[0])*segment.delta[0]
+(qy-segment.a[1])*segment.delta[1])
@@ -1341,13 +1355,14 @@ public:
const double floorCenterZ=PassageFlatFloorCenterZSubvoxels(
constants,segmentIndex,floorT,segment,floorDepth,qPerMetre);
const double flatDistance=PassageFlatBottomDistance(
static_cast<float>(roundDistance),floorCenterZ,qz,floorDepth,qPerMetre);
static_cast<float>(roundDistance),floorCenterZ,qz,floorDepth,
qPerMetre);
const double alongMetres=segment.authoredAlongMetres
+t*segment.authoredLengthMetres;
const double mouthDistanceMetres=std::max(0.0,std::min(alongMetres,
constants.totalPathLengthMetres-alongMetres));
const double shellFade=std::clamp(mouthDistanceMetres/
MouthShellBlendMetres,0.0,1.0);
const double shellFade=std::clamp(mouthDistanceMetres
/MouthShellBlendMetres,0.0,1.0);
const double smoothShellFade=shellFade*shellFade
*(3.0-2.0*shellFade);
const double floorDistanceMetres=(floorCenterZ-floorDepth-qz)
@@ -1355,32 +1370,91 @@ public:
const double floorBlend=std::clamp(floorDistanceMetres/0.25,0.0,1.0);
const double smoothFloorBlend=floorBlend*floorBlend
*(3.0-2.0*floorBlend);
const double floorAlongMetres=segment.authoredAlongMetres
+floorT*segment.authoredLengthMetres;
const double floorMouthDistanceMetres=std::max(0.0,
std::min(floorAlongMetres,constants.totalPathLengthMetres
-floorAlongMetres));
const double floorShellFade=std::clamp(floorMouthDistanceMetres
/MouthShellBlendMetres,0.0,1.0);
const double smoothFloorShellFade=floorShellFade*floorShellFade
*(3.0-2.0*floorShellFade);
const double mouthWallThickness=segmentIndex
<constants.routeEndControlIndex
?smoothShellFade*shellThickness:0.0;
?smoothShellFade*wallShellThickness:0.0;
double floorExtensionFade=1.0;
if(segmentIndex>=constants.routeEndControlIndex)
{
const double pastMouthMetres=std::max(0.0,alongMetres
const double pastMouthMetres=std::max(0.0,floorAlongMetres
-constants.totalPathLengthMetres);
const double extensionFade=std::clamp(1.0
-pastMouthMetres/MouthShellBlendMetres,0.0,1.0);
floorExtensionFade=extensionFade*extensionFade
*(3.0-2.0*extensionFade);
}
const double floorShellThickness=smoothFloorBlend*shellThickness
*floorExtensionFade*smoothShellFade;
const double floorShellThickness=smoothFloorBlend
*floorShellThicknessBase*floorExtensionFade
*smoothFloorShellFade;
const double shellClearanceT=std::clamp(
(mouthDistanceMetres-5.0)/15.0,0.0,1.0);
const double shellClearanceFade=shellClearanceT*shellClearanceT
*(3.0-2.0*shellClearanceT);
const double localThickness=std::max(mouthWallThickness,
floorShellThickness);
const double localShellDistance=std::max(-static_cast<double>(flatDistance),
static_cast<double>(flatDistance)-localThickness);
nearest.shellSignedDistance=std::min(nearest.shellSignedDistance,
localShellDistance);
floorShellThickness)+ShellClearanceMetres*qPerMetre
*shellClearanceFade;
const double joinBlendT=std::clamp(mouthDistanceMetres/15.0,
0.0,1.0);
const double joinBlendWeight=joinBlendT*joinBlendT
*(3.0-2.0*joinBlendT);
const double localOuterDistance=flatDistance-localThickness;
FShellCandidate candidate{localOuterDistance,segmentIndex,
joinBlendWeight};
std::size_t insertAt=0;
while(insertAt<shellCandidateCount
&&(shellCandidates[insertAt].distance<candidate.distance
||(shellCandidates[insertAt].distance==candidate.distance
&&shellCandidates[insertAt].segmentIndex
<candidate.segmentIndex)))++insertAt;
if(insertAt<shellCandidates.size())
{
const std::size_t newCount=std::min(shellCandidateCount+1,
shellCandidates.size());
for(std::size_t move=newCount-1;move>insertAt;--move)
shellCandidates[move]=shellCandidates[move-1];
shellCandidates[insertAt]=candidate;
shellCandidateCount=newCount;
}
bestShellSearchBound=std::min(bestShellSearchBound,
localOuterDistance+wallShellThickness
+ShellClearanceMetres*qPerMetre
+(0.25+ShellJoinBlendMetres)*qPerMetre);
};
VisitNearestPassageSegmentRange(passageIndex,0,lastIndex-1,
qx,qy,qz,0,shellSearchBound,visitShellSegment);
qx,qy,qz,0,bestShellSearchBound,visitShellSegment);
if(shellCandidateCount>0)
{
nearestShell.signedDistance=shellCandidates[0].distance;
nearestShell.segmentIndex=shellCandidates[0].segmentIndex;
for(std::size_t left=0;left<shellCandidateCount;++left)
for(std::size_t right=left+1;right<shellCandidateCount;++right)
{
if(shellCandidates[right].segmentIndex
!=shellCandidates[left].segmentIndex+1)continue;
nearestShell.signedDistance=std::min(
nearestShell.signedDistance,PassageSmoothMinDistance(
shellCandidates[left].distance,
shellCandidates[right].distance,
ShellJoinBlendMetres*qPerMetre*std::min(
shellCandidates[left].blendWeight,
shellCandidates[right].blendWeight)));
}
nearestShell.valid=true;
}
}
if(!nearest.valid)continue;
if(!nearest.valid||!nearestShell.valid)continue;
nearestShell.signedDistance=std::max(
-nearest.signedDistance,nearestShell.signedDistance);
double distanceToCenter=std::max(1.0e-9,
nearest.roundSignedDistance+nearest.radius);
const double verticalWallAlignment=distanceToCenter>1.0e-9
@@ -1402,43 +1476,9 @@ public:
const double shapedDistance=PassageFlatBottomDistance(
static_cast<float>(roughRoundDistance),nearest.floorCenterZ,qz,
nearest.floorDepth,qPerMetre);
const auto& segment=PassageSegmentData[passageIndex][nearest.segmentIndex];
const double alongMetres=segment.authoredAlongMetres
+nearest.t*segment.authoredLengthMetres;
const double mouthDistanceMetres=std::max(0.0,std::min(alongMetres,
constants.totalPathLengthMetres-alongMetres));
const double shellFade=std::clamp(mouthDistanceMetres/
MouthShellBlendMetres,0.0,1.0);
const double smoothShellFade=shellFade*shellFade*(3.0-2.0*shellFade);
// Ease both wall and floor shell into the host ground over five metres.
// The floor shell remains below the floor plane throughout that blend.
const double floorDistanceMetres=(nearest.floorCenterZ
-nearest.floorDepth-qz)/qPerMetre;
const double floorBlend=std::clamp(floorDistanceMetres/0.25,0.0,1.0);
const double smoothFloorBlend=floorBlend*floorBlend
*(3.0-2.0*floorBlend);
const double mouthWallThickness=nearest.segmentIndex
<constants.routeEndControlIndex
?smoothShellFade*shellThickness:0.0;
double floorExtensionFade=1.0;
if(nearest.segmentIndex>=constants.routeEndControlIndex)
{
const double pastMouthMetres=std::max(0.0,alongMetres
-constants.totalPathLengthMetres);
const double extensionFade=std::clamp(1.0
-pastMouthMetres/MouthShellBlendMetres,0.0,1.0);
floorExtensionFade=extensionFade*extensionFade
*(3.0-2.0*extensionFade);
}
const double floorShellThickness=smoothFloorBlend*shellThickness
*floorExtensionFade*smoothShellFade;
const double localShellThickness=std::max(mouthWallThickness,
floorShellThickness);
unionDistance=std::min(unionDistance,static_cast<double>(shapedDistance));
const double localShellDistance=std::max(-static_cast<double>(shapedDistance),
static_cast<double>(shapedDistance)-localShellThickness);
unionShellDistance=std::min(unionShellDistance,
std::min(localShellDistance,nearest.shellSignedDistance));
nearestShell.signedDistance);
}
if(!std::isfinite(unionDistance))return density;
@@ -1592,7 +1632,7 @@ public:
const auto& p3=passage.controlPoints[
std::min(segment+2,passage.controlPoints.size()-1)];
const double radius=std::max(static_cast<double>(a.radiusSubvoxels),
static_cast<double>(b.radiusSubvoxels))/Q+1.0;
static_cast<double>(b.radiusSubvoxels))/Q+2.36;
double low[3]{},high[3]{};
for(int axis=0;axis<3;++axis)
{
@@ -3371,6 +3411,8 @@ private:
struct FPassageSegmentData
{
std::array<double,3> a{},b{},delta{};
std::array<std::array<double,4>,3> shellCurveCoefficients{};
std::array<double,3> shellCurveMinimum{},shellCurveMaximum{};
std::array<std::int64_t,3> sweepMinimumSubvoxels{},sweepMaximumSubvoxels{};
double authoredCenterZA=0.0,authoredCenterZDelta=0.0;
double authoredAlongMetres=0.0,authoredLengthMetres=0.0;
@@ -3432,6 +3474,128 @@ private:
return SmoothMaxDistance(roundDistanceSubvoxels,floorDistance,
static_cast<float>(CornerBlendMetres*qPerMetre));
}
static double PassageSmoothMinDistance(double a,double b,double width)
{
if(!(width>0.0))return std::min(a,b);
const double h=std::max(width-std::abs(a-b),0.0)/width;
return std::min(a,b)-(h*h*h)*(width/6.0);
}
static double PassageCurveCoordinate(
const std::array<double,4>& coefficients,double t)
{
return ((coefficients[3]*t+coefficients[2])*t+coefficients[1])*t
+coefficients[0];
}
static double PassageCurveCoordinateExtended(
const std::array<double,4>& coefficients,double t)
{
if(t<0.0)return coefficients[0]+coefficients[1]*t;
if(t>1.0)
{
const double end=PassageCurveCoordinate(coefficients,1.0);
const double tangent=3.0*coefficients[3]+2.0*coefficients[2]
+coefficients[1];
return end+(t-1.0)*tangent;
}
return PassageCurveCoordinate(coefficients,t);
}
static void PassageCurveEvaluateExtended(
const std::array<double,4>& coefficients,double t,
double& coordinate,double& tangent,double& curvature)
{
if(t<0.0)
{
coordinate=coefficients[0]+coefficients[1]*t;
tangent=coefficients[1];
curvature=0.0;
}
else if(t>1.0)
{
const double end=PassageCurveCoordinate(coefficients,1.0);
tangent=3.0*coefficients[3]+2.0*coefficients[2]+coefficients[1];
coordinate=end+(t-1.0)*tangent;
curvature=0.0;
}
else
{
coordinate=PassageCurveCoordinate(coefficients,t);
tangent=(3.0*coefficients[3]*t+2.0*coefficients[2])*t
+coefficients[1];
curvature=6.0*coefficients[3]*t+2.0*coefficients[2];
}
}
static void PassageCurveAxisBounds(const std::array<double,4>& coefficients,
double& minimum,double& maximum,double tMinimum=0.0,double tMaximum=1.0)
{
minimum=std::min(PassageCurveCoordinate(coefficients,tMinimum),
PassageCurveCoordinate(coefficients,tMaximum));
maximum=std::max(PassageCurveCoordinate(coefficients,tMinimum),
PassageCurveCoordinate(coefficients,tMaximum));
const double a=3.0*coefficients[3],b=2.0*coefficients[2],c=coefficients[1];
const auto include=[&](double t)
{
if(t<=tMinimum||t>=tMaximum)return;
const double value=PassageCurveCoordinate(coefficients,t);
minimum=std::min(minimum,value);
maximum=std::max(maximum,value);
};
if(std::abs(a)<=1.0e-18)
{
if(std::abs(b)>1.0e-18)include(-c/b);
return;
}
const double discriminant=b*b-4.0*a*c;
if(discriminant<0.0)return;
const double root=DeterministicSqrt(discriminant);
include((-b-root)/(2.0*a));
include((-b+root)/(2.0*a));
}
static double PassageCatmullRoundDistanceSubvoxels(
const std::vector<FPassageSegmentData>& segments,std::size_t segmentIndex,
double qx,double qy,double qz,double extensionSubvoxels=0.0,
double* outT=nullptr)
{
if(segmentIndex>=segments.size())return std::numeric_limits<double>::infinity();
const auto& segment=segments[segmentIndex];
if(segment.lengthSq<=0.0)return std::numeric_limits<double>::infinity();
const double projection=(qx-segment.a[0])*segment.delta[0]
+(qy-segment.a[1])*segment.delta[1]
+(qz-segment.a[2])*segment.delta[2];
const double tExtension=std::max(0.0,extensionSubvoxels)
/std::max(1.0,segment.lengthSubvoxels);
const double tMinimum=-tExtension,tMaximum=1.0+tExtension;
double t=std::clamp(projection/segment.lengthSq,tMinimum,tMaximum);
for(int iteration=0;iteration<4;++iteration)
{
double firstDerivative=0.0,secondDerivative=0.0;
const double query[3]={qx,qy,qz};
for(std::size_t axis=0;axis<3;++axis)
{
const auto& c=segment.shellCurveCoefficients[axis];
double center=0.0,tangent=0.0,curvature=0.0;
PassageCurveEvaluateExtended(c,t,center,tangent,curvature);
const double difference=center-query[axis];
firstDerivative+=difference*tangent;
secondDerivative+=tangent*tangent+difference*curvature;
}
if(std::abs(secondDerivative)<=1.0e-12)break;
const double next=std::clamp(t-firstDerivative/secondDerivative,
tMinimum,tMaximum);
if(std::abs(next-t)<=1.0e-9){t=next;break;}
t=next;
}
double distanceSq=0.0;
const double query[3]={qx,qy,qz};
for(std::size_t axis=0;axis<3;++axis)
{
const auto& c=segment.shellCurveCoefficients[axis];
const double center=PassageCurveCoordinateExtended(c,t);
const double difference=query[axis]-center;
distanceSq+=difference*difference;
}
if(outT)*outT=t;
return DeterministicSqrt(distanceSq)-(segment.radiusA+t*segment.radiusDelta);
}
struct FPassageNearestNode
{
std::array<double,3> minimum{},maximum{};
@@ -4232,10 +4396,10 @@ private:
const auto& prepared=PassageSegmentData[passageIndex][segment];
for(std::size_t axis=0;axis<3;++axis)
{
node.minimum[axis]=std::min({node.minimum[axis],
prepared.a[axis],prepared.b[axis]});
node.maximum[axis]=std::max({node.maximum[axis],
prepared.a[axis],prepared.b[axis]});
node.minimum[axis]=std::min(node.minimum[axis],
prepared.shellCurveMinimum[axis]);
node.maximum[axis]=std::max(node.maximum[axis],
prepared.shellCurveMaximum[axis]);
}
node.maximumRadius=std::max({node.maximumRadius,
static_cast<double>(a.radiusSubvoxels),static_cast<double>(b.radiusSubvoxels)});
@@ -4617,6 +4781,78 @@ private:
static_cast<double>(passage.controlPoints[pointIndex].positionSubvoxels[2])
+(-routeDepth+localTubeDepth)*qPerMetre;
}
std::vector<std::array<double,3>> shellControlPoints(
segmentData.size()+1);
for(std::size_t pointIndex=0;pointIndex<shellControlPoints.size();++pointIndex)
{
for(std::size_t axis=0;axis<3;++axis)
shellControlPoints[pointIndex][axis]=axis==2
?effectiveCenterZSubvoxels[pointIndex]
:static_cast<double>(passage.controlPoints[pointIndex]
.positionSubvoxels[axis]);
}
const auto authoredShellControlPoints=shellControlPoints;
for(std::size_t pointIndex=1;pointIndex+1<shellControlPoints.size();
++pointIndex)
{
const double previousSpan=authoredPathDistanceMetres[pointIndex]
-authoredPathDistanceMetres[pointIndex-1];
const double nextSpan=authoredPathDistanceMetres[pointIndex+1]
-authoredPathDistanceMetres[pointIndex];
if(previousSpan>20.0||nextSpan>20.0)continue;
const double alongMetres=authoredPathDistanceMetres[pointIndex];
const double mouthDistance=std::max(0.0,std::min(alongMetres,
authoredTotalPathLengthMetres-alongMetres));
const double fadeT=std::clamp((mouthDistance-80.0)/40.0,0.0,1.0);
const double fade=fadeT*fadeT*(3.0-2.0*fadeT);
const double neighborWeight=0.10*fade;
for(std::size_t axis=0;axis<2;++axis)
shellControlPoints[pointIndex][axis]=
(1.0-2.0*neighborWeight)
*authoredShellControlPoints[pointIndex][axis]
+neighborWeight*authoredShellControlPoints[pointIndex-1][axis]
+neighborWeight*authoredShellControlPoints[pointIndex+1][axis];
}
std::vector<std::array<double,3>> shellSecondDerivatives(
shellControlPoints.size());
const std::size_t interiorPointCount=shellControlPoints.size()-2;
for(std::size_t axis=0;axis<3;++axis)
{
std::vector<double> diagonal(interiorPointCount);
std::vector<double> upper(interiorPointCount);
std::vector<double> lower(interiorPointCount);
std::vector<double> rhs(interiorPointCount);
for(std::size_t row=0;row<interiorPointCount;++row)
{
const std::size_t pointIndex=row+1;
const double previousSpan=authoredPathDistanceMetres[pointIndex]
-authoredPathDistanceMetres[pointIndex-1];
const double nextSpan=authoredPathDistanceMetres[pointIndex+1]
-authoredPathDistanceMetres[pointIndex];
lower[row]=previousSpan;
diagonal[row]=2.0*(previousSpan+nextSpan);
upper[row]=nextSpan;
rhs[row]=6.0*((shellControlPoints[pointIndex+1][axis]
-shellControlPoints[pointIndex][axis])/nextSpan
-(shellControlPoints[pointIndex][axis]
-shellControlPoints[pointIndex-1][axis])/previousSpan);
}
for(std::size_t row=1;row<interiorPointCount;++row)
{
const double factor=lower[row]/diagonal[row-1];
diagonal[row]-=factor*upper[row-1];
rhs[row]-=factor*rhs[row-1];
}
if(interiorPointCount>0)
{
shellSecondDerivatives[interiorPointCount][axis]=
rhs[interiorPointCount-1]/diagonal[interiorPointCount-1];
for(std::size_t row=interiorPointCount-1;row>0;--row)
shellSecondDerivatives[row][axis]=(rhs[row-1]
-upper[row-1]*shellSecondDerivatives[row+1][axis])
/diagonal[row-1];
}
}
for(std::size_t segmentIndex=0;segmentIndex<segmentData.size();++segmentIndex)
{
const auto& a=passage.controlPoints[segmentIndex];
@@ -4649,6 +4885,44 @@ private:
data.horizontalLength=DeterministicSqrt(data.horizontalLengthSq);
++preparedSegments;
}
for(std::size_t segmentIndex=0;segmentIndex<segmentData.size();++segmentIndex)
{
auto& data=segmentData[segmentIndex];
const bool hasLongNeighbor=(segmentIndex>0
&&segmentData[segmentIndex-1].authoredLengthMetres>20.0)
||(segmentIndex+1<segmentData.size()
&&segmentData[segmentIndex+1].authoredLengthMetres>20.0);
const bool useCurvedSpan=data.authoredLengthMetres<=20.0
&&!hasLongNeighbor;
for(std::size_t axis=0;axis<3;++axis)
{
if(!useCurvedSpan)
{
data.shellCurveCoefficients[axis]={data.a[axis],
data.delta[axis],0.0,0.0};
}
else
{
const double span=data.authoredLengthMetres;
const double p0=shellControlPoints[segmentIndex][axis];
const double p1=shellControlPoints[segmentIndex+1][axis];
const double m0=shellSecondDerivatives[segmentIndex][axis];
const double m1=shellSecondDerivatives[segmentIndex+1][axis];
data.shellCurveCoefficients[axis]={
p0,p1-p0-span*span*(2.0*m0+m1)/6.0,
span*span*m0/2.0,span*span*(m1-m0)/6.0};
}
PassageCurveAxisBounds(data.shellCurveCoefficients[axis],
data.shellCurveMinimum[axis],data.shellCurveMaximum[axis]);
// This tree also culls chord-based interior queries. Keep
// the authored chord inside broad-phase bounds after the
// shell-only centerline is smoothed.
data.shellCurveMinimum[axis]=std::min(data.shellCurveMinimum[axis],
std::min(data.a[axis],data.b[axis]));
data.shellCurveMaximum[axis]=std::max(data.shellCurveMaximum[axis],
std::max(data.a[axis],data.b[axis]));
}
}
constants.totalPathLengthMetres=authoredTotalPathLengthMetres;
PassageSegmentOffsets[passageIndex+1]=preparedSegments;
}
@@ -4675,7 +4949,7 @@ private:
PassageNearestNodes.resize(ResolvedLayout?ResolvedLayout->passages.size():0);
if(!ResolvedLayout||ResolvedLayout->passages.empty())return;
constexpr double Q=static_cast<double>(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel);
constexpr double PassageShellMetres=0.75;
constexpr double PassageShellMetres=2.35;
constexpr double ReliefMetres=0.21;
constexpr double SweepReachMetres=4.0;
for(std::size_t passageIndex=0;passageIndex<ResolvedLayout->passages.size();
@@ -4698,8 +4972,8 @@ private:
std::array<double,3> routeMinimum{},routeMaximum{};
for(std::size_t axis=0;axis<3;++axis)
{
routeMinimum[axis]=std::min(prepared.a[axis],prepared.b[axis])/Q;
routeMaximum[axis]=std::max(prepared.a[axis],prepared.b[axis])/Q;
routeMinimum[axis]=prepared.shellCurveMinimum[axis]/Q;
routeMaximum[axis]=prepared.shellCurveMaximum[axis]/Q;
}
const double routeExtent=maximumRadius+PassageShellMetres
+ReliefMetres+0.01;
@@ -11286,6 +11560,11 @@ inline void FStandaloneShapeWorld::FScopedPassageTileQueries::prepareSweptTubeBa
{
constexpr std::int64_t Q=VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
constexpr double InfluenceMetres=4.0;
constexpr double FloorShellThicknessMetres=0.75;
constexpr double WallShellThicknessMetres=0.75;
constexpr double ShellClearanceMetres=0.60;
constexpr double MouthShellBlendMetres=5.0;
constexpr double ShellJoinBlendMetres=6.00;
if(!shape_||step<=0||cells<=0||!std::isfinite(passageCarveReachMetres_)
||std::abs(passageCarveReachMetres_-InfluenceMetres)>1.0e-9)return;
@@ -11306,8 +11585,6 @@ inline void FStandaloneShapeWorld::FScopedPassageTileQueries::prepareSweptTubeBa
const double qPerMetre=static_cast<double>(Q)*1000000.0/
std::max<std::uint32_t>(1,shape_->ResolvedLayout
?shape_->ResolvedLayout->coreVoxelSizeMicrometres:250000);
const double shellThickness=0.75*qPerMetre;
constexpr double MouthShellBlendMetres=5.0;
const double wallReliefShell=0.41*qPerMetre;
const double floorDetailShell=1.0*qPerMetre;
const bool profile=profileStats_!=nullptr;
@@ -11363,17 +11640,16 @@ inline void FStandaloneShapeWorld::FScopedPassageTileQueries::prepareSweptTubeBa
+static_cast<std::int64_t>(gz)*sweep->stepSubvoxels);
const double projectionXY=(qx-segment.a[0])*segment.delta[0]
+(qy-segment.a[1])*segment.delta[1];
const double t=std::clamp((projectionXY
const double chordT=std::clamp((projectionXY
+(qz-segment.a[2])*segment.delta[2])/segment.lengthSq,0.0,1.0);
double cx=segment.a[0]+t*segment.delta[0];
double cy=segment.a[1]+t*segment.delta[1];
double cz=segment.a[2]+t*segment.delta[2];
const double radius=segment.radiusA+t*segment.radiusDelta;
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 cx=segment.a[0]+chordT*segment.delta[0];
const double cy=segment.a[1]+chordT*segment.delta[1];
const double cz=segment.a[2]+chordT*segment.delta[2];
const double radius=segment.radiusA+chordT*segment.radiusDelta;
const double dx=qx-cx,dy=qy-cy,dz=qz-cz;
const double signedDistance=DeterministicSqrt(dx*dx+dy*dy+dz*dz)-radius;
const double floorT=segment.horizontalLengthSq>1.0e-12
?std::clamp(projectionXY/segment.horizontalLengthSq,0.0,1.0):t;
?std::clamp(projectionXY/segment.horizontalLengthSq,0.0,1.0):chordT;
const double floorRadius=segment.radiusA+floorT*segment.radiusDelta;
const double floorDepth=FStandaloneShapeWorld::PassageFlatFloorDepthSubvoxels(
constants,candidate.segmentIndex,floorT,floorRadius,qPerMetre);
@@ -11382,6 +11658,54 @@ inline void FStandaloneShapeWorld::FScopedPassageTileQueries::prepareSweptTubeBa
constants,candidate.segmentIndex,floorT,segment,floorDepth,qPerMetre);
const double flatDistance=FStandaloneShapeWorld::PassageFlatBottomDistance(
static_cast<float>(signedDistance),floorCenterZ,qz,floorDepth,qPerMetre);
const double shellAlongMetres=segment.authoredAlongMetres
+chordT*segment.authoredLengthMetres;
const double mouthDistanceMetres=std::max(0.0,std::min(shellAlongMetres,
constants.totalPathLengthMetres-shellAlongMetres));
const double shellFade=std::clamp(mouthDistanceMetres/
MouthShellBlendMetres,0.0,1.0);
const double smoothShellFade=shellFade*shellFade*(3.0-2.0*shellFade);
const double floorDistanceMetres=(floorCenterZ-floorDepth-qz)
/qPerMetre;
const double floorBlend=std::clamp(floorDistanceMetres/0.25,0.0,1.0);
const double smoothFloorBlend=floorBlend*floorBlend
*(3.0-2.0*floorBlend);
const double floorAlongMetres=segment.authoredAlongMetres
+floorT*segment.authoredLengthMetres;
const double floorMouthDistanceMetres=std::max(0.0,
std::min(floorAlongMetres,constants.totalPathLengthMetres
-floorAlongMetres));
const double floorShellFade=std::clamp(floorMouthDistanceMetres
/MouthShellBlendMetres,0.0,1.0);
const double smoothFloorShellFade=floorShellFade*floorShellFade
*(3.0-2.0*floorShellFade);
const double mouthWallThickness=candidate.segmentIndex
<constants.routeEndControlIndex
?smoothShellFade*WallShellThicknessMetres*qPerMetre:0.0;
double floorExtensionFade=1.0;
if(candidate.segmentIndex>=constants.routeEndControlIndex)
{
const double pastMouthMetres=std::max(0.0,floorAlongMetres
-constants.totalPathLengthMetres);
const double extensionFade=std::clamp(1.0
-pastMouthMetres/MouthShellBlendMetres,0.0,1.0);
floorExtensionFade=extensionFade*extensionFade
*(3.0-2.0*extensionFade);
}
const double floorShellThickness=smoothFloorBlend
*FloorShellThicknessMetres*qPerMetre*floorExtensionFade
*smoothFloorShellFade;
const double shellClearanceT=std::clamp(
(mouthDistanceMetres-5.0)/15.0,0.0,1.0);
const double shellClearanceFade=shellClearanceT*shellClearanceT
*(3.0-2.0*shellClearanceT);
const double shellThickness=std::max(mouthWallThickness,
floorShellThickness)+(ShellClearanceMetres*qPerMetre)
*shellClearanceFade;
const double joinBlendT=std::clamp(mouthDistanceMetres/15.0,0.0,1.0);
const double joinBlendWeight=joinBlendT*joinBlendT
*(3.0-2.0*joinBlendT);
const double localShellDistance=flatDistance-shellThickness;
if(profile)++profileStats_->passageSweepSegmentSamplePairs;
if(signedDistance>sweep->influenceSubvoxels)
{
@@ -11391,39 +11715,6 @@ inline void FStandaloneShapeWorld::FScopedPassageTileQueries::prepareSweptTubeBa
*static_cast<std::size_t>(sweep->dimension)+static_cast<std::size_t>(gy))
*static_cast<std::size_t>(sweep->dimension)+static_cast<std::size_t>(gx);
auto& sample=passageSamples.samples[sampleIndex];
const double alongMetres=segment.authoredAlongMetres
+t*segment.authoredLengthMetres;
const double mouthDistanceMetres=std::max(0.0,std::min(alongMetres,
constants.totalPathLengthMetres-alongMetres));
const double shellFade=std::clamp(mouthDistanceMetres/
MouthShellBlendMetres,0.0,1.0);
const double smoothShellFade=shellFade*shellFade
*(3.0-2.0*shellFade);
const double floorDistanceMetres=(floorCenterZ-floorDepth-qz)/qPerMetre;
const double floorBlend=std::clamp(floorDistanceMetres/0.25,0.0,1.0);
const double smoothFloorBlend=floorBlend*floorBlend
*(3.0-2.0*floorBlend);
const double mouthWallThickness=candidate.segmentIndex
<constants.routeEndControlIndex
?smoothShellFade*shellThickness:0.0;
double floorExtensionFade=1.0;
if(candidate.segmentIndex>=constants.routeEndControlIndex)
{
const double pastMouthMetres=std::max(0.0,alongMetres
-constants.totalPathLengthMetres);
const double extensionFade=std::clamp(1.0
-pastMouthMetres/MouthShellBlendMetres,0.0,1.0);
floorExtensionFade=extensionFade*extensionFade
*(3.0-2.0*extensionFade);
}
const double floorShellThickness=smoothFloorBlend*shellThickness
*floorExtensionFade*smoothShellFade;
const double localThickness=std::max(mouthWallThickness,
floorShellThickness);
const double localShellDistance=std::max(-static_cast<double>(flatDistance),
static_cast<double>(flatDistance)-localThickness);
sample.shellSignedDistance=std::min(sample.shellSignedDistance,
localShellDistance);
if(!sample.valid||flatDistance<sample.signedDistance
||(flatDistance==sample.signedDistance
&&candidate.segmentIndex<sample.segmentIndex))
@@ -11434,12 +11725,38 @@ inline void FStandaloneShapeWorld::FScopedPassageTileQueries::prepareSweptTubeBa
sample.floorCenterZSubvoxels=floorCenterZ;
sample.radiusSubvoxels=radius;
sample.floorDepthSubvoxels=floorDepth;
sample.t=t;
sample.t=chordT;
sample.segmentIndex=static_cast<std::uint32_t>(candidate.segmentIndex);
sample.valid=true;
sample.hasWallRelief=false;
if(profile)++profileStats_->passageSweepMinimumUpdates;
}
if(!sample.shellValid)
{
sample.shellSignedDistance=localShellDistance;
sample.shellSegmentIndex=static_cast<std::uint32_t>(
candidate.segmentIndex);
sample.previousShellDistance=localShellDistance;
sample.previousShellBlendWeight=joinBlendWeight;
sample.shellValid=true;
}
else
{
if(candidate.segmentIndex==sample.shellSegmentIndex+1)
sample.shellSignedDistance=std::min(
sample.shellSignedDistance,
FStandaloneShapeWorld::PassageSmoothMinDistance(
sample.previousShellDistance,localShellDistance,
ShellJoinBlendMetres*qPerMetre*std::min(
sample.previousShellBlendWeight,
joinBlendWeight)));
sample.shellSignedDistance=std::min(sample.shellSignedDistance,
localShellDistance);
sample.shellSegmentIndex=static_cast<std::uint32_t>(
candidate.segmentIndex);
sample.previousShellDistance=localShellDistance;
sample.previousShellBlendWeight=joinBlendWeight;
}
}
}
}
@@ -11448,8 +11765,6 @@ inline void FStandaloneShapeWorld::FScopedPassageTileQueries::prepareSweptTubeBa
for(auto& passageSamples:sweep->passages)
{
const std::size_t passageIndex=passageSamples.passageIndex;
const auto& constants=shape_->PassageComposerConstants[passageIndex];
const auto& segments=shape_->PassageSegmentData[passageIndex];
for(std::size_t sampleIndex=0;sampleIndex<passageSamples.samples.size();++sampleIndex)
{
auto& sample=passageSamples.samples[sampleIndex];
@@ -11468,8 +11783,12 @@ 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);
const double wallReliefShell=0.41*qPerMetre;
const double radius=sample.radiusSubvoxels;
const double centerZ=sample.centerZSubvoxels;
if(sample.shellValid)
sample.shellSignedDistance=std::max(-sample.signedDistance,
sample.shellSignedDistance);
if(sample.signedDistance<=sweep->influenceSubvoxels)
{
anyPassageInfluence[sampleIndex]=1;