Support under chambers and 9 m approaches either refilled the bore or left the same 5 Surface World route + 2 upper chamber probes unsupported in all three variants (identical counts with or without support), so those probes are likely not under the support footprint. Also exposed an FI chamber content-band validation failure. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
303 lines
17 KiB
Diff
303 lines
17 KiB
Diff
diff --git a/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h b/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h
|
|
index 4799b9e..e3ecf5e 100644
|
|
--- a/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h
|
|
+++ b/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h
|
|
@@ -512,7 +512,8 @@ namespace VoxelForgeCore
|
|
// boundary-blended host rock from widening the authored passage wall.
|
|
// v238 plans one resolved passage spline with explicit landing chambers.
|
|
// v239 plans their floor planes as the passage's continuous floor profile.
|
|
- inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 239;
|
|
+ // v240 gives landing chambers and their route approaches tapered floor support.
|
|
+ inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 240;
|
|
|
|
enum class FieldChannel : std::uint32_t
|
|
{
|
|
diff --git a/Standalone/Core/src/ShapeWorldInternal.h b/Standalone/Core/src/ShapeWorldInternal.h
|
|
index 6b1a6af..f0e0da4 100644
|
|
--- a/Standalone/Core/src/ShapeWorldInternal.h
|
|
+++ b/Standalone/Core/src/ShapeWorldInternal.h
|
|
@@ -57,6 +57,7 @@ static constexpr double VF_PassageShellThicknessMetres=0.75;
|
|
static constexpr double VF_PassageWallReliefMetres=0.21;
|
|
static constexpr double VF_PassageCornerBlendMetres=0.50;
|
|
static constexpr double VF_PassageReachMarginMetres=0.01;
|
|
+static constexpr double VF_LandingSupportBoundsDepthMetres=2.20;
|
|
// Radial support in metres: shell + maximum relief + the smooth-max support
|
|
// (corner-blend width / 6) + a 1 cm guard for float and bound rounding.
|
|
static constexpr double VF_PassageInfluenceReachMetres=
|
|
@@ -1260,11 +1261,92 @@ public:
|
|
constexpr double WallReliefMetres=VF_PassageWallReliefMetres;
|
|
constexpr double OpenAirSideWallHeightMetres=1.0;
|
|
constexpr double MouthShellBlendMetres=5.0;
|
|
+ // Landing floors carry a tapered footing below their walk plane. Surface
|
|
+ // landings use local native ground within a 2 m budget; cave and shaft
|
|
+ // landings reuse the cave planner's 0.75 m floor-support depth.
|
|
+ constexpr double LandingSupportMaximumDepthMetres=2.0;
|
|
+ constexpr double LandingSupportFallbackDepthMetres=0.75;
|
|
+ constexpr double LandingSupportTaperMetres=1.0;
|
|
+ constexpr double LandingSupportGroundEmbedMetres=0.20;
|
|
+ constexpr double LandingSupportTopBiasSubvoxels=2.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 auto* queryStratum=FindBoundStratumForManagerZ(Z);
|
|
+ double landingSupportDistanceVoxels=std::numeric_limits<double>::infinity();
|
|
+ double landingSupportAirClipVoxels=-std::numeric_limits<double>::infinity();
|
|
+ double routeUnionDistance=std::numeric_limits<double>::infinity();
|
|
+ bool surfaceGroundQueried=false,surfaceGroundValid=false;
|
|
+ double surfaceGroundVoxels=0.0;
|
|
+ const bool surfaceLandingStratum=queryStratum&&queryStratum->Resolved
|
|
+ &&queryStratum->Resolved->generatorKind=="SurfaceWorld";
|
|
+ const auto surfaceGroundAtQuery=[&]()
|
|
+ {
|
|
+ if(!surfaceGroundQueried)
|
|
+ {
|
|
+ surfaceGroundQueried=true;
|
|
+ const double layoutWidth=GetResolvedLayoutChunkWidthVoxels();
|
|
+ if(layoutWidth>0.0)
|
|
+ {
|
|
+ const int32 chunkZ=FMath::FloorToInt(
|
|
+ static_cast<float>(static_cast<double>(Z)/layoutWidth));
|
|
+ float ground=0.0f,ceiling=0.0f;
|
|
+ surfaceGroundValid=GetNativeSurfaceHeightsAt(
|
|
+ X,Y,chunkZ,ground,ceiling);
|
|
+ if(surfaceGroundValid)surfaceGroundVoxels=ground;
|
|
+ }
|
|
+ }
|
|
+ return surfaceGroundValid?surfaceGroundVoxels
|
|
+ :std::numeric_limits<double>::quiet_NaN();
|
|
+ };
|
|
+ const auto includeLandingSupport=[&](double floorSubvoxels,
|
|
+ double centerDistanceSubvoxels,double footprintRadiusSubvoxels)
|
|
+ {
|
|
+ if(!(footprintRadiusSubvoxels>0.0))return;
|
|
+ const double floorVoxels=floorSubvoxels/Q;
|
|
+ const double centerDistanceVoxels=centerDistanceSubvoxels/Q;
|
|
+ const double radiusVoxels=footprintRadiusSubvoxels/Q;
|
|
+ const double supportDepthVoxels=(surfaceLandingStratum
|
|
+ ?LandingSupportMaximumDepthMetres:LandingSupportFallbackDepthMetres)
|
|
+ *qPerMetre/Q;
|
|
+ const double taperVoxels=LandingSupportTaperMetres*qPerMetre/Q;
|
|
+ const double embedVoxels=LandingSupportGroundEmbedMetres*qPerMetre/Q;
|
|
+ const double topBiasVoxels=LandingSupportTopBiasSubvoxels/Q;
|
|
+ if(centerDistanceVoxels>=radiusVoxels+taperVoxels
|
|
+ ||static_cast<double>(Z)<floorVoxels-supportDepthVoxels-embedVoxels
|
|
+ ||static_cast<double>(Z)>floorVoxels+topBiasVoxels)return;
|
|
+
|
|
+ double groundAnchorVoxels=floorVoxels-supportDepthVoxels;
|
|
+ if(surfaceLandingStratum)
|
|
+ {
|
|
+ const double ground=surfaceGroundAtQuery();
|
|
+ if(std::isfinite(ground))
|
|
+ groundAnchorVoxels=std::max(groundAnchorVoxels,
|
|
+ std::min(ground,floorVoxels));
|
|
+ }
|
|
+ const double radialFade=std::clamp(
|
|
+ (radiusVoxels+taperVoxels-centerDistanceVoxels)/taperVoxels,
|
|
+ 0.0,1.0);
|
|
+ const double smoothFade=radialFade*radialFade*(3.0-2.0*radialFade);
|
|
+ const double supportTop=groundAnchorVoxels
|
|
+ +(floorVoxels+topBiasVoxels-groundAnchorVoxels)*smoothFade;
|
|
+ const double supportBottom=groundAnchorVoxels-embedVoxels*smoothFade;
|
|
+ const double supportDistance=std::max(
|
|
+ centerDistanceVoxels-(radiusVoxels+taperVoxels),
|
|
+ std::max(static_cast<double>(Z)-supportTop,
|
|
+ supportBottom-static_cast<double>(Z)));
|
|
+ landingSupportDistanceVoxels=std::min(landingSupportDistanceVoxels,
|
|
+ supportDistance);
|
|
+ };
|
|
+ const auto applyLandingSupport=[&](float composed)
|
|
+ {
|
|
+ if(!std::isfinite(landingSupportDistanceVoxels))return composed;
|
|
+ const double clippedSupportDistance=std::max(landingSupportDistanceVoxels,
|
|
+ landingSupportAirClipVoxels);
|
|
+ return std::min(composed,static_cast<float>(clippedSupportDistance));
|
|
+ };
|
|
struct FNearestTubeSample
|
|
{
|
|
double signedDistance=std::numeric_limits<double>::infinity();
|
|
@@ -1284,7 +1366,6 @@ public:
|
|
std::numeric_limits<double>::infinity();
|
|
double plannedTunnelPassageShellDistance=std::numeric_limits<double>::infinity();
|
|
const std::uint32_t reliefSeed=static_cast<std::uint32_t>(Owner?Owner->Seed:0);
|
|
- const auto* queryStratum=FindBoundStratumForManagerZ(Z);
|
|
const bool plannedTunnelStratum=queryStratum&&queryStratum->Resolved
|
|
&&(queryStratum->Resolved->generatorKind=="TunnelNetwork"
|
|
||queryStratum->Resolved->generatorKind=="VerticalShafts")
|
|
@@ -1346,6 +1427,9 @@ public:
|
|
const double radialDistance=DeterministicSqrt(
|
|
(qx-centerX)*(qx-centerX)+(qy-centerY)*(qy-centerY))
|
|
-landingChamber->radiusSubvoxels;
|
|
+ includeLandingSupport(floorCenterZ,
|
|
+ radialDistance+landingChamber->radiusSubvoxels,
|
|
+ landingChamber->radiusSubvoxels);
|
|
const double floorDistance=floorCenterZ-qz;
|
|
const double ceilingDistance=qz-(floorCenterZ
|
|
+landingChamber->clearHeightSubvoxels);
|
|
@@ -1578,6 +1662,42 @@ public:
|
|
visitShellSegment);
|
|
}
|
|
if(!nearest.valid)continue;
|
|
+ if(queryStratum&&queryStratum->Resolved
|
|
+ &&(queryStratum->StrateIndex==passage.upperStrateIndex
|
|
+ ||queryStratum->StrateIndex==passage.lowerStrateIndex)
|
|
+ &&nearest.floorSegmentIndex<PassageSegmentData[passageIndex].size())
|
|
+ {
|
|
+ constexpr double LandingApproachLengthMetres=9.0;
|
|
+ const auto& floorSegment=
|
|
+ PassageSegmentData[passageIndex][nearest.floorSegmentIndex];
|
|
+ const double floorAlongMetres=floorSegment.authoredAlongMetres
|
|
+ +nearest.floorT*floorSegment.authoredLengthMetres;
|
|
+ const bool upperApproach=queryStratum->StrateIndex
|
|
+ ==passage.upperStrateIndex
|
|
+ &&floorAlongMetres>=-LandingSupportTaperMetres
|
|
+ &&floorAlongMetres<=LandingApproachLengthMetres;
|
|
+ const bool lowerApproach=queryStratum->StrateIndex
|
|
+ ==passage.lowerStrateIndex
|
|
+ &&constants.totalPathLengthMetres-floorAlongMetres
|
|
+ <=LandingApproachLengthMetres
|
|
+ &&constants.totalPathLengthMetres-floorAlongMetres
|
|
+ >=-LandingSupportTaperMetres;
|
|
+ if(floorSegment.routeSegment&&(upperApproach||lowerApproach))
|
|
+ {
|
|
+ const double routeX=floorSegment.a[0]
|
|
+ +nearest.floorT*floorSegment.delta[0];
|
|
+ const double routeY=floorSegment.a[1]
|
|
+ +nearest.floorT*floorSegment.delta[1];
|
|
+ const double routeDistance=DeterministicSqrt(
|
|
+ (qx-routeX)*(qx-routeX)+(qy-routeY)*(qy-routeY));
|
|
+ const double floorRadius=floorSegment.radiusA
|
|
+ +std::clamp(nearest.floorT,0.0,1.0)*floorSegment.radiusDelta;
|
|
+ const double supportRadius=std::max(0.0,
|
|
+ floorRadius-0.23*qPerMetre);
|
|
+ includeLandingSupport(nearest.floorCenterZ-nearest.floorDepth,
|
|
+ routeDistance,supportRadius);
|
|
+ }
|
|
+ }
|
|
const double wallElevationMetres=(qz-(nearest.floorCenterZ
|
|
-nearest.floorDepth))/qPerMetre;
|
|
double distanceToCenter=std::max(1.0e-9,
|
|
@@ -1648,6 +1768,8 @@ public:
|
|
static_cast<double>(shapedDistance)-floorShellThickness);
|
|
nearest.floorShellSignedDistance=std::min(
|
|
nearest.floorShellSignedDistance,floorShellDistance);
|
|
+ routeUnionDistance=std::min(routeUnionDistance,
|
|
+ static_cast<double>(shapedDistance));
|
|
unionDistance=std::min(unionDistance,static_cast<double>(shapedDistance));
|
|
unionFloorShellDistance=std::min(unionFloorShellDistance,
|
|
nearest.floorShellSignedDistance);
|
|
@@ -1657,13 +1779,18 @@ public:
|
|
std::min(localShellDistance,nearest.shellSignedDistance));
|
|
}
|
|
|
|
- if(!std::isfinite(unionDistance))return density;
|
|
+ if(!std::isfinite(unionDistance))return applyLandingSupport(density);
|
|
const float passageAirDistance=static_cast<float>(-unionDistance/Q);
|
|
+ // Keep support out of the passage bore while allowing it under the
|
|
+ // chamber's own floor, where the chamber volume is naturally open.
|
|
+ if(std::isfinite(routeUnionDistance))
|
|
+ landingSupportAirClipVoxels=-routeUnionDistance/Q;
|
|
// Keep the landing floor shell where it is exposed to the chamber. At
|
|
// the tunnel joint, the passage void must be able to open through that
|
|
// floor shell so its own continuous floor and shell define the throat.
|
|
if(unionLandingChamberFloorShellDistance<0.0&&unionDistance>0.0)
|
|
- return static_cast<float>(unionLandingChamberFloorShellDistance/Q);
|
|
+ return applyLandingSupport(
|
|
+ static_cast<float>(unionLandingChamberFloorShellDistance/Q));
|
|
const auto plannedPortalWalkCorridorContains=[&]()
|
|
{
|
|
if(!queryStratum||!queryStratum->Resolved
|
|
@@ -1855,7 +1982,7 @@ public:
|
|
// otherwise the neighboring field can widen its wall by a whole
|
|
// shell thickness. Native graph air still wins above via the
|
|
// positive nativeTunnelFieldDensity path.
|
|
- return passageAirDistance;
|
|
+ return applyLandingSupport(passageAirDistance);
|
|
}
|
|
// Air is the union of the native graph and passage voids. Keeping the
|
|
// signed passage distance outside the void as well gives marching cubes
|
|
@@ -1865,9 +1992,10 @@ public:
|
|
&&plannedTunnelPassageShellDistance<0.0&&unionDistance>0.0)
|
|
{
|
|
if(nativeTunnelFieldDensity>=0.0f&&plannedPortalWalkCorridorContains())
|
|
- return std::max(nativeTunnelFieldDensity,passageAirDistance);
|
|
- return std::min(density,
|
|
- static_cast<float>(plannedTunnelPassageShellDistance/Q));
|
|
+ return applyLandingSupport(
|
|
+ std::max(nativeTunnelFieldDensity,passageAirDistance));
|
|
+ return applyLandingSupport(std::min(density,
|
|
+ static_cast<float>(plannedTunnelPassageShellDistance/Q)));
|
|
}
|
|
if(nativeTunnelFieldDensity>=0.0f)
|
|
{
|
|
@@ -1875,29 +2003,30 @@ public:
|
|
// cave voids after the explicit mouth fade has ended. Keep this scoped
|
|
// to the planner-crossing passage shell so nearby portal joints stay open.
|
|
if(plannedTunnelPassageShellDistance<0.0&&unionDistance>0.0)
|
|
- return std::min(nativeTunnelFieldDensity,
|
|
- static_cast<float>(plannedTunnelPassageShellDistance/Q));
|
|
+ return applyLandingSupport(std::min(nativeTunnelFieldDensity,
|
|
+ static_cast<float>(plannedTunnelPassageShellDistance/Q)));
|
|
constexpr float NativeCaveAirMarginVoxels=0.75f;
|
|
if(unionDistance>0.0
|
|
&&nativeTunnelFieldDensity<NativeCaveAirMarginVoxels
|
|
&&unionFloorShellDistance<0.0)
|
|
- return std::min(nativeTunnelFieldDensity,
|
|
- static_cast<float>(unionFloorShellDistance/Q));
|
|
- return std::max(nativeTunnelFieldDensity,passageAirDistance);
|
|
+ return applyLandingSupport(std::min(nativeTunnelFieldDensity,
|
|
+ static_cast<float>(unionFloorShellDistance/Q)));
|
|
+ return applyLandingSupport(
|
|
+ std::max(nativeTunnelFieldDensity,passageAirDistance));
|
|
}
|
|
if(nativeFloorSupport!=std::numeric_limits<float>::lowest()
|
|
&&nativeFloorSupport<0.0f)
|
|
- return std::min(nativeFloorSupport,unionDistance<=0.0
|
|
- ?passageAirDistance:density);
|
|
+ return applyLandingSupport(std::min(nativeFloorSupport,unionDistance<=0.0
|
|
+ ?passageAirDistance:density));
|
|
if(nativeTunnelFieldDensity!=std::numeric_limits<float>::lowest())
|
|
- return std::max(density,passageAirDistance);
|
|
+ return applyLandingSupport(std::max(density,passageAirDistance));
|
|
if(unionDistance>0.0&&unionShellDistance<0.0&&density>=0.0f)
|
|
{
|
|
if(connectedSurfaceLandingShellContains())
|
|
- return std::max(density,passageAirDistance);
|
|
- return static_cast<float>(unionShellDistance/Q);
|
|
+ return applyLandingSupport(std::max(density,passageAirDistance));
|
|
+ return applyLandingSupport(static_cast<float>(unionShellDistance/Q));
|
|
}
|
|
- return std::max(density,passageAirDistance);
|
|
+ return applyLandingSupport(std::max(density,passageAirDistance));
|
|
}
|
|
|
|
float ApplyResolvedStrateComposition(float density,float X,float Y,float Z) const
|
|
@@ -2044,8 +2173,11 @@ public:
|
|
static_cast<double>(chamber.floorGradientQ16[0])/65536.0,
|
|
static_cast<double>(chamber.floorGradientQ16[1])/65536.0);
|
|
const double extent=reachCoreVoxels+0.01;
|
|
+ const double supportDepth=(VF_LandingSupportBoundsDepthMetres*1000000.0/
|
|
+ std::max<std::uint32_t>(1,
|
|
+ ResolvedLayout->coreVoxelSizeMicrometres));
|
|
const double low[3]={cx-radius-extent,cy-radius-extent,
|
|
- cz-floorExtent-extent};
|
|
+ cz-floorExtent-supportDepth-extent};
|
|
const double high[3]={cx+radius+extent,cy+radius+extent,
|
|
cz+floorExtent+static_cast<double>(
|
|
chamber.clearHeightSubvoxels)/Q+extent};
|
|
@@ -5484,8 +5616,12 @@ private:
|
|
const double height=static_cast<double>(
|
|
chamber.clearHeightSubvoxels)/Q;
|
|
const double extent=requiredReachCoreVoxels+0.01;
|
|
+ const double supportDepth=(VF_LandingSupportBoundsDepthMetres*1000000.0/
|
|
+ std::max<std::uint32_t>(1,
|
|
+ ResolvedLayout->coreVoxelSizeMicrometres));
|
|
const std::array<double,3> minimum{
|
|
- cx-radius-extent,cy-radius-extent,cz-floorExtent-extent};
|
|
+ cx-radius-extent,cy-radius-extent,
|
|
+ cz-floorExtent-supportDepth-extent};
|
|
const std::array<double,3> maximum{
|
|
cx+radius+extent,cy+radius+extent,
|
|
cz+floorExtent+height+extent};
|