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
+749
-439
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 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 217 replaces passage supports with flat-floor hollow-shell geometry.
|
||||
inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 217;
|
||||
// Version 218 resamples passages on a smooth spline and uses open-air half-pipe shells.
|
||||
// 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
|
||||
{
|
||||
|
||||
@@ -1196,6 +1196,7 @@ public:
|
||||
constexpr double Q=static_cast<double>(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel);
|
||||
constexpr double FloorShellThicknessMetres=0.75;
|
||||
constexpr double WallReliefMetres=0.21;
|
||||
constexpr double OpenAirSideWallHeightMetres=1.0;
|
||||
constexpr double MouthShellBlendMetres=5.0;
|
||||
const double qPerMetre=Q*1000000.0/
|
||||
std::max<std::uint32_t>(1,ResolvedLayout->coreVoxelSizeMicrometres);
|
||||
@@ -1264,7 +1265,7 @@ public:
|
||||
}
|
||||
|
||||
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();
|
||||
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 smoothFloorBlend=floorBlend*floorBlend
|
||||
*(3.0-2.0*floorBlend);
|
||||
const double mouthWallThickness=segmentIndex
|
||||
<constants.routeEndControlIndex
|
||||
const double wallElevationMetres=(qz-(floorCenterZ-floorDepth))
|
||||
/qPerMetre;
|
||||
const double mouthWallThickness=segment.routeSegment
|
||||
&&wallElevationMetres>=0.0
|
||||
&&wallElevationMetres<=OpenAirSideWallHeightMetres
|
||||
?smoothShellFade*shellThickness:0.0;
|
||||
double floorExtensionFade=1.0;
|
||||
if(segmentIndex>=constants.routeEndControlIndex)
|
||||
if(!segment.routeSegment)
|
||||
{
|
||||
const double pastMouthMetres=std::max(0.0,alongMetres
|
||||
-constants.totalPathLengthMetres);
|
||||
@@ -1417,11 +1421,14 @@ 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 mouthWallThickness=nearest.segmentIndex
|
||||
<constants.routeEndControlIndex
|
||||
const double wallElevationMetres=(qz-(nearest.floorCenterZ-nearest.floorDepth))
|
||||
/qPerMetre;
|
||||
const double mouthWallThickness=segment.routeSegment
|
||||
&&wallElevationMetres>=0.0
|
||||
&&wallElevationMetres<=OpenAirSideWallHeightMetres
|
||||
?smoothShellFade*shellThickness:0.0;
|
||||
double floorExtensionFade=1.0;
|
||||
if(nearest.segmentIndex>=constants.routeEndControlIndex)
|
||||
if(!segment.routeSegment)
|
||||
{
|
||||
const double pastMouthMetres=std::max(0.0,alongMetres
|
||||
-constants.totalPathLengthMetres);
|
||||
@@ -1446,15 +1453,11 @@ public:
|
||||
// solid only in air, so it leaves host rock unchanged. Taking the union
|
||||
// before writing either polarity makes every tunnel interior beat every
|
||||
// overlapping tunnel shell.
|
||||
// Positive native graph distance is authored tunnel interior. Blend it
|
||||
// into the same air union before any passage shell can change its sign.
|
||||
// This also preserves the cave interior across a shared-layer boundary
|
||||
// blend, which may be solid on the passage's side of that interface.
|
||||
if(nativeTunnelFieldDensity>=0.0f)
|
||||
// Preserve native graph air inside the passage interior before any shell
|
||||
// can change its sign. Outside that interior, the shell may cover host air.
|
||||
if(nativeTunnelFieldDensity>=0.0f&&unionDistance<=0.0)
|
||||
{
|
||||
const float passageAir=unionDistance<=0.0
|
||||
?static_cast<float>(-unionDistance/Q)
|
||||
:std::numeric_limits<float>::lowest();
|
||||
const float passageAir=static_cast<float>(-unionDistance/Q);
|
||||
return std::max(nativeTunnelFieldDensity,passageAir);
|
||||
}
|
||||
if(nativeFloorSupport!=std::numeric_limits<float>::lowest()
|
||||
@@ -3377,7 +3380,38 @@ private:
|
||||
double radiusA=0.0,radiusB=0.0,radiusDelta=0.0;
|
||||
double lengthSq=0.0,horizontalLengthSq=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(
|
||||
const FPassageComposerConstants& constants,std::size_t segmentIndex,double t,
|
||||
double radiusSubvoxels,double qPerMetre)
|
||||
@@ -3411,7 +3445,7 @@ private:
|
||||
const FPassageComposerConstants& constants,std::size_t segmentIndex,double floorT,
|
||||
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];
|
||||
const double radiusMetres=(segment.radiusA+floorT*segment.radiusDelta)/qPerMetre;
|
||||
const double alongMetres=segment.authoredAlongMetres
|
||||
@@ -4222,13 +4256,11 @@ private:
|
||||
|
||||
int BuildPassageNearestNode(std::size_t passageIndex,std::vector<std::size_t> segments)
|
||||
{
|
||||
const auto& points=ResolvedLayout->passages[passageIndex].controlPoints;
|
||||
FPassageNearestNode node;
|
||||
node.minimum.fill(std::numeric_limits<double>::infinity());
|
||||
node.maximum.fill(-std::numeric_limits<double>::infinity());
|
||||
for(const std::size_t segment:segments)
|
||||
{
|
||||
const auto& a=points[segment];const auto& b=points[segment+1];
|
||||
const auto& prepared=PassageSegmentData[passageIndex][segment];
|
||||
for(std::size_t axis=0;axis<3;++axis)
|
||||
{
|
||||
@@ -4238,7 +4270,7 @@ private:
|
||||
prepared.a[axis],prepared.b[axis]});
|
||||
}
|
||||
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.maximumSegment=*std::max_element(segments.begin(),segments.end());
|
||||
@@ -4258,8 +4290,8 @@ private:
|
||||
-PassageNearestNodes[passageIndex][nodeIndex].minimum[splitAxis])splitAxis=axis;
|
||||
const std::size_t middle=segments.size()/2;
|
||||
const auto center=[&](std::size_t segment)
|
||||
{return static_cast<double>(points[segment].positionSubvoxels[splitAxis])
|
||||
+static_cast<double>(points[segment+1].positionSubvoxels[splitAxis]);};
|
||||
{const auto& prepared=PassageSegmentData[passageIndex][segment];
|
||||
return prepared.a[splitAxis]+prepared.b[splitAxis];};
|
||||
std::nth_element(segments.begin(),segments.begin()+middle,segments.end(),
|
||||
[&](std::size_t a,std::size_t b)
|
||||
{const double ca=center(a),cb=center(b);return ca==cb?a<b:ca<cb;});
|
||||
@@ -4497,7 +4529,7 @@ private:
|
||||
if(!ResolvedLayout)
|
||||
{
|
||||
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::steady_clock::now()-preparationStart).count());
|
||||
return;
|
||||
@@ -4508,7 +4540,9 @@ private:
|
||||
constexpr double Q=static_cast<double>(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel);
|
||||
const double qPerMetre=Q*1000000.0/
|
||||
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();
|
||||
++passageIndex)
|
||||
{
|
||||
@@ -4519,50 +4553,104 @@ private:
|
||||
PassageSegmentOffsets[passageIndex+1]=preparedSegments;
|
||||
continue;
|
||||
}
|
||||
auto& segmentData=PassageSegmentData[passageIndex];
|
||||
segmentData.resize(passage.controlPoints.size()-1);
|
||||
const std::size_t routeEnd=std::clamp<std::size_t>(
|
||||
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];
|
||||
constants.routeEndControlIndex=routeEnd;
|
||||
std::vector<double> authoredPathDistanceMetres(
|
||||
passage.controlPoints.size(),0.0);
|
||||
for(std::size_t pointIndex=1;pointIndex<passage.controlPoints.size();
|
||||
++pointIndex)
|
||||
|
||||
struct FRawPassageSegment
|
||||
{
|
||||
const auto& a=passage.controlPoints[pointIndex-1].positionSubvoxels;
|
||||
const auto& b=passage.controlPoints[pointIndex].positionSubvoxels;
|
||||
const double dx=static_cast<double>(b[0]-a[0])/qPerMetre;
|
||||
const double dy=static_cast<double>(b[1]-a[1])/qPerMetre;
|
||||
const double dz=static_cast<double>(b[2]-a[2])/qPerMetre;
|
||||
authoredPathDistanceMetres[pointIndex]=
|
||||
authoredPathDistanceMetres[pointIndex-1]
|
||||
+DeterministicSqrt(dx*dx+dy*dy+dz*dz);
|
||||
std::array<double,4> a{},b{};
|
||||
double alongMetres=0.0,lengthMetres=0.0;
|
||||
std::size_t span=0;
|
||||
double tA=0.0,tB=1.0;
|
||||
bool routeSegment=false;
|
||||
};
|
||||
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);
|
||||
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;
|
||||
double minimumBodyRadius=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,radius);
|
||||
const double along=authoredPathDistanceMetres[pointIndex];
|
||||
if(along>=10.0&&authoredTotalPathLengthMetres-along>=10.0)
|
||||
minimumBodyRadius=std::min(minimumBodyRadius,radius);
|
||||
minimumRadius=std::min(minimumRadius,sample[1]);
|
||||
if(sample[0]>=10.0&&authoredTotalPathLengthMetres-sample[0]>=10.0)
|
||||
minimumBodyRadius=std::min(minimumBodyRadius,sample[1]);
|
||||
}
|
||||
if(!std::isfinite(minimumBodyRadius))minimumBodyRadius=minimumRadius;
|
||||
// One fixed flat-floor depth through the body. The authored grade
|
||||
// constraint follows centreline-radius+0.23 m; shift the swept centreline
|
||||
// where a taper changes radius so this floor keeps that same route.
|
||||
// Keep one flat-floor depth through the body and ease it into both mouths.
|
||||
constants.floorDepthMetres=std::max(0.5,minimumBodyRadius-0.23);
|
||||
constants.upperMouthFloorDepthMetres=std::max(0.0,
|
||||
static_cast<double>(passage.controlPoints.front().positionSubvoxels[2]
|
||||
@@ -4586,76 +4674,135 @@ private:
|
||||
constants.lowerMouthFloorDepthMetres=std::max(0.0,
|
||||
routeFloorMetres-nativeFloorMetres);
|
||||
}
|
||||
const auto routeFloorDepth=[&](std::size_t pointIndex,double radius)
|
||||
|
||||
struct FRouteFloorSample
|
||||
{
|
||||
const double localRadiusDepth=std::max(0.0,radius-0.23);
|
||||
const double along=authoredPathDistanceMetres[pointIndex];
|
||||
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::array<double,4> point{};
|
||||
double alongMetres=0.0,desiredFloorZMetres=0.0;
|
||||
};
|
||||
std::vector<double> effectiveCenterZSubvoxels(
|
||||
passage.controlPoints.size(),0.0);
|
||||
for(std::size_t pointIndex=0;pointIndex<passage.controlPoints.size();
|
||||
++pointIndex)
|
||||
std::vector<FRouteFloorSample> routeFloorSamples;
|
||||
routeFloorSamples.reserve(rawSegments.size()+1);
|
||||
const auto appendRouteFloorSample=[&](const std::array<double,4>& point,
|
||||
double along)
|
||||
{
|
||||
const double radius=pointIndex<=routeEnd
|
||||
?radiiMetres[pointIndex]
|
||||
:static_cast<double>(passage.controlPoints[pointIndex]
|
||||
.radiusSubvoxels)/qPerMetre;
|
||||
const double localTubeDepth=std::min(constants.floorDepthMetres,
|
||||
std::max(0.0,radius-0.23));
|
||||
const double routeDepth=pointIndex<=routeEnd
|
||||
?routeFloorDepth(pointIndex,radius):std::max(0.0,radius-0.23);
|
||||
effectiveCenterZSubvoxels[pointIndex]=
|
||||
static_cast<double>(passage.controlPoints[pointIndex].positionSubvoxels[2])
|
||||
+(-routeDepth+localTubeDepth)*qPerMetre;
|
||||
const double radiusMetres=point[3]/qPerMetre;
|
||||
const double routeDepth=PassageRouteFloorDepthMetres(constants,along,
|
||||
radiusMetres);
|
||||
routeFloorSamples.push_back({point,along,point[2]/qPerMetre-routeDepth});
|
||||
};
|
||||
for(const auto& raw:rawSegments)
|
||||
{
|
||||
if(!raw.routeSegment)break;
|
||||
if(routeFloorSamples.empty())appendRouteFloorSample(raw.a,raw.alongMetres);
|
||||
appendRouteFloorSample(raw.b,raw.alongMetres+raw.lengthMetres);
|
||||
}
|
||||
for(std::size_t segmentIndex=0;segmentIndex<segmentData.size();++segmentIndex)
|
||||
if(routeFloorSamples.size()>1)
|
||||
{
|
||||
const auto& a=passage.controlPoints[segmentIndex];
|
||||
const auto& b=passage.controlPoints[segmentIndex+1];
|
||||
FPassageSegmentData& data=segmentData[segmentIndex];
|
||||
data.authoredCenterZA=static_cast<double>(a.positionSubvoxels[2]);
|
||||
data.authoredCenterZDelta=static_cast<double>(
|
||||
b.positionSubvoxels[2]-a.positionSubvoxels[2]);
|
||||
data.authoredAlongMetres=authoredPathDistanceMetres[segmentIndex];
|
||||
data.authoredLengthMetres=authoredPathDistanceMetres[segmentIndex+1]
|
||||
-authoredPathDistanceMetres[segmentIndex];
|
||||
constexpr double MaximumWalkGradeTangent=0.2586407821;
|
||||
std::vector<double> horizontalGradeLimits(routeFloorSamples.size()-1);
|
||||
std::vector<double> remainingGradeAllowance(routeFloorSamples.size(),0.0);
|
||||
for(std::size_t i=0;i+1<routeFloorSamples.size();++i)
|
||||
{
|
||||
const double dx=(routeFloorSamples[i+1].point[0]
|
||||
-routeFloorSamples[i].point[0])/qPerMetre;
|
||||
const double dy=(routeFloorSamples[i+1].point[1]
|
||||
-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)
|
||||
{
|
||||
data.a[axis]=static_cast<double>(a.positionSubvoxels[axis]);
|
||||
data.b[axis]=static_cast<double>(b.positionSubvoxels[axis]);
|
||||
data.a[axis]=raw.a[axis];
|
||||
data.b[axis]=raw.b[axis];
|
||||
if(axis==2)
|
||||
{
|
||||
data.a[axis]=effectiveCenterZSubvoxels[segmentIndex];
|
||||
data.b[axis]=effectiveCenterZSubvoxels[segmentIndex+1];
|
||||
data.a[axis]=effectiveCenterZ(raw.a[2],raw.a[3],raw.alongMetres);
|
||||
data.b[axis]=effectiveCenterZ(raw.b[2],raw.b[3],alongB);
|
||||
}
|
||||
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];
|
||||
data.lengthSq=dx*dx+dy*dy+dz*dz;
|
||||
data.horizontalLengthSq=dx*dx+dy*dy;
|
||||
data.lengthSubvoxels=DeterministicSqrt(data.lengthSq);
|
||||
data.horizontalLength=DeterministicSqrt(data.horizontalLengthSq);
|
||||
segmentData.push_back(data);
|
||||
++preparedSegments;
|
||||
}
|
||||
constants.totalPathLengthMetres=authoredTotalPathLengthMetres;
|
||||
PassageSegmentOffsets[passageIndex+1]=preparedSegments;
|
||||
}
|
||||
if(tracePreparation)
|
||||
std::printf("[L109PassagePrepare] passages=%zu segments=%zu bytes=%zu prepare_ms=%.3f\n",
|
||||
PassageSegmentData.size(),preparedSegments,
|
||||
preparedSegments*sizeof(FPassageSegmentData),
|
||||
std::printf("[L111PassagePrepare] passages=%zu control_spans=%zu segments=%zu spacing_m=%.2f bytes=%zu prepare_ms=%.3f\n",
|
||||
PassageSegmentData.size(),controlSpans,preparedSegments,
|
||||
PassageSegmentSpacingMetres,preparedSegments*sizeof(FPassageSegmentData),
|
||||
std::chrono::duration<double,std::milli>(
|
||||
std::chrono::steady_clock::now()-preparationStart).count());
|
||||
}
|
||||
@@ -4681,20 +4828,16 @@ private:
|
||||
for(std::size_t passageIndex=0;passageIndex<ResolvedLayout->passages.size();
|
||||
++passageIndex)
|
||||
{
|
||||
const auto& passage=ResolvedLayout->passages[passageIndex];
|
||||
const std::size_t segmentCount=passage.controlPoints.size()>1
|
||||
?passage.controlPoints.size()-1:0;
|
||||
const std::size_t segmentCount=PassageSegmentData[passageIndex].size();
|
||||
if(segmentCount==0)continue;
|
||||
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)
|
||||
{
|
||||
const auto& a=passage.controlPoints[segmentIndex];
|
||||
const auto& b=passage.controlPoints[segmentIndex+1];
|
||||
auto& prepared=PassageSegmentData[passageIndex][segmentIndex];
|
||||
double maximumRadius=std::max(static_cast<double>(a.radiusSubvoxels),
|
||||
static_cast<double>(b.radiusSubvoxels))/Q;
|
||||
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)
|
||||
{
|
||||
@@ -11286,6 +11429,7 @@ inline void FStandaloneShapeWorld::FScopedPassageTileQueries::prepareSweptTubeBa
|
||||
{
|
||||
constexpr std::int64_t Q=VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
|
||||
constexpr double InfluenceMetres=4.0;
|
||||
constexpr double OpenAirSideWallHeightMetres=1.0;
|
||||
if(!shape_||step<=0||cells<=0||!std::isfinite(passageCarveReachMetres_)
|
||||
||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 smoothFloorBlend=floorBlend*floorBlend
|
||||
*(3.0-2.0*floorBlend);
|
||||
const double mouthWallThickness=candidate.segmentIndex
|
||||
<constants.routeEndControlIndex
|
||||
const double wallElevationMetres=(qz-(floorCenterZ-floorDepth))
|
||||
/qPerMetre;
|
||||
const double mouthWallThickness=segment.routeSegment
|
||||
&&wallElevationMetres>=0.0
|
||||
&&wallElevationMetres<=OpenAirSideWallHeightMetres
|
||||
?smoothShellFade*shellThickness:0.0;
|
||||
double floorExtensionFade=1.0;
|
||||
if(candidate.segmentIndex>=constants.routeEndControlIndex)
|
||||
if(!segment.routeSegment)
|
||||
{
|
||||
const double pastMouthMetres=std::max(0.0,alongMetres
|
||||
-constants.totalPathLengthMetres);
|
||||
|
||||
Reference in new issue
Block a user