core: one tunnel for two strates with landing chambers (L151, owner design; v238, grade open)

Each passage records upper and lower landing chambers (floor, gradient, radius, 4.5 m clear height) composed with the
tunnel shell; cave route endpoints bind to the chambers; envelope audit permits only the named chamber joints.
Seed 2: 0 solid in 319,579 passage+chamber capsule samples, 0 shell violations, 0 envelope conflicts, controls fire.
Open: tunnel-to-chamber floor blend leaves 19.1/19.0 deg grades (limit 15); sweeps/timing not run.

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-11-01 18:41:40 +01:00
1 parent 9ac60d5d18
commit 5bd158d4eb
21 files changed
+1949 -105

No files matched your search

@@ -510,8 +510,8 @@ namespace VoxelForgeCore
// preserves open cave lanes through passage shell overlaps; 236 binds passage mouths to
// the planned graph route, preserves its walk corridor through shell overlaps, and keeps
// boundary-blended host rock from widening the authored passage wall.
// v237 adds the shared Core cave-graph shaft primitive and basal terminal route.
inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 237;
// v238 plans one resolved passage spline with explicit landing chambers.
inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 238;
enum class FieldChannel : std::uint32_t
{
@@ -1209,6 +1209,16 @@ namespace VoxelForgeCore
std::int64_t radiusSubvoxels = 0;
};
struct ResolvedLayoutPassageLandingChamberV1
{
// The chamber floor plane passes through this point. Gradient is dz/dx,
// dz/dy in Q16 so the chamber continues the passage's authored h(s) grade.
std::array<std::int64_t, 3> floorCenterSubvoxels{};
std::array<std::int64_t, 2> floorGradientQ16{};
std::int64_t radiusSubvoxels = 0;
std::int64_t clearHeightSubvoxels = 0;
};
struct ResolvedLayoutPassageV1
{
std::string stableId;
@@ -1223,6 +1233,8 @@ namespace VoxelForgeCore
std::int64_t midRadiusSubvoxels = 0;
// Derived from the mouth binding; the main descent is monotone through this point.
std::size_t lowerMouthControlIndex = 0;
ResolvedLayoutPassageLandingChamberV1 upperLandingChamber;
ResolvedLayoutPassageLandingChamberV1 lowerLandingChamber;
std::vector<ResolvedLayoutPassagePointV1> controlPoints;
};
+324 -73
View File
@@ -565,7 +565,21 @@ namespace
<<"],\"lower_mouth_q\":["<<p.lowerMouthSubvoxels[0]<<','
<<p.lowerMouthSubvoxels[1]<<','<<p.lowerMouthSubvoxels[2]
<<"],\"mouth_radius_q\":"<<p.mouthRadiusSubvoxels
<<",\"mid_radius_q\":"<<p.midRadiusSubvoxels<<",\"control_points\":[";
<<",\"mid_radius_q\":"<<p.midRadiusSubvoxels;
const auto writeLandingChamber=[&](const char* name,
const ResolvedLayoutPassageLandingChamberV1& chamber)
{
out<<",\""<<name<<"\":{\"floor_center_q\":["
<<chamber.floorCenterSubvoxels[0]<<','
<<chamber.floorCenterSubvoxels[1]<<','
<<chamber.floorCenterSubvoxels[2]<<"],\"floor_gradient_q16\":["
<<chamber.floorGradientQ16[0]<<','<<chamber.floorGradientQ16[1]
<<"],\"radius_q\":"<<chamber.radiusSubvoxels
<<",\"clear_height_q\":"<<chamber.clearHeightSubvoxels<<'}';
};
writeLandingChamber("upper_landing_chamber",p.upperLandingChamber);
writeLandingChamber("lower_landing_chamber",p.lowerLandingChamber);
out<<",\"control_points\":[";
for(std::size_t j=0;j<p.controlPoints.size();++j)
{
if(j)out<<',';const auto& point=p.controlPoints[j];
@@ -1095,6 +1109,62 @@ bool ResolvedLayoutSnapshotV3::validate(ResolvedLayoutValidationModeV3 mode,
if(!insideContent(*upper,passage.upperMouthSubvoxels)
||!insideContent(*lower,passage.lowerMouthSubvoxels))
add("passage_mouth_content",passage.stableId+" mouth is outside its owned content band");
const auto validateLandingChamber=[&](
const ResolvedLayoutPassageLandingChamberV1& chamber,
const ResolvedLayoutStratumV2& host,
const std::array<std::int64_t,3>& mouth,const char* side)
{
const double playerCenterHeight=(side==std::string_view("upper")
&&host.generatorKind=="SurfaceWorld")?0.98:1.13;
const std::int64_t playerCenterHeightQ=static_cast<std::int64_t>(
std::llround(playerCenterHeight*WorldAssemblySubvoxelsPerVoxel
*1000000.0/std::max<std::uint32_t>(1,coreVoxelSizeMicrometres)));
if(chamber.floorCenterSubvoxels[0]!=mouth[0]
||chamber.floorCenterSubvoxels[1]!=mouth[1]
||chamber.floorCenterSubvoxels[2]+playerCenterHeightQ!=mouth[2]
||chamber.radiusSubvoxels<=0
||chamber.clearHeightSubvoxels<=0
||chamber.radiusSubvoxels<passage.mouthRadiusSubvoxels
||chamber.clearHeightSubvoxels<static_cast<std::int64_t>(
std::llround(2.2*WorldAssemblySubvoxelsPerVoxel*1000000.0
/std::max<std::uint32_t>(1,coreVoxelSizeMicrometres))))
{
add("passage_landing_chamber",passage.stableId+" "+side
+" landing chamber has invalid floor, radius, or capsule clearance");
return;
}
const std::uint64_t gradientSquared=static_cast<std::uint64_t>(
chamber.floorGradientQ16[0]*chamber.floorGradientQ16[0]
+chamber.floorGradientQ16[1]*chamber.floorGradientQ16[1]);
const std::int64_t gradientMagnitude=static_cast<std::int64_t>(
PassageIsqrt(gradientSquared));
if(gradientMagnitude>17560)
add("passage_landing_grade",passage.stableId+" "+side
+" landing chamber floor exceeds 15 degrees");
const std::int64_t planeReachQ=static_cast<std::int64_t>(
(chamber.radiusSubvoxels*gradientMagnitude+65535)/65536);
const std::int64_t shellQ=static_cast<std::int64_t>(std::llround(
0.75*WorldAssemblySubvoxelsPerVoxel*1000000.0
/std::max<std::uint32_t>(1,coreVoxelSizeMicrometres)));
const auto qToUm=[&](std::int64_t q)
{return q*static_cast<std::int64_t>(coreVoxelSizeMicrometres)
/WorldAssemblySubvoxelsPerVoxel;};
if(!host.content.presentInCurrentField
||qToUm(chamber.floorCenterSubvoxels[2]-planeReachQ-shellQ)
<host.content.bottomMicrometres
||qToUm(chamber.floorCenterSubvoxels[2]+planeReachQ
+chamber.clearHeightSubvoxels+shellQ)
>host.content.topMicrometres)
add("passage_landing_content",passage.stableId+" "+side
+" landing chamber or plain shell leaves its host content band");
};
if(upper&&lower)
{
validateLandingChamber(passage.upperLandingChamber,*upper,
passage.upperMouthSubvoxels,"upper");
validateLandingChamber(passage.lowerLandingChamber,*lower,
passage.lowerMouthSubvoxels,"lower");
}
if(passage.controlPoints.size()<2)continue;
const std::int64_t PlayerCenterAboveFloorQ=coreVoxelSizeMicrometres>0
?static_cast<std::int64_t>(std::llround(1.13*WorldAssemblySubvoxelsPerVoxel
@@ -1730,6 +1800,21 @@ std::optional<ResolvedLayoutSnapshotV3> ResolvedLayoutSnapshotV3::FromJson(
const auto& lower=v.Get("lower_mouth_q").AsArray();
for(std::size_t axis=0;axis<3&&axis<upper.size();++axis)p.upperMouthSubvoxels[axis]=upper[axis].Integer();
for(std::size_t axis=0;axis<3&&axis<lower.size();++axis)p.lowerMouthSubvoxels[axis]=lower[axis].Integer();
const auto readLandingChamber=[&](const char* key,
ResolvedLayoutPassageLandingChamberV1& chamber)
{
const auto& value=v.Get(key);
const auto& center=value.Get("floor_center_q").AsArray();
const auto& gradient=value.Get("floor_gradient_q16").AsArray();
for(std::size_t axis=0;axis<3&&axis<center.size();++axis)
chamber.floorCenterSubvoxels[axis]=center[axis].Integer();
for(std::size_t axis=0;axis<2&&axis<gradient.size();++axis)
chamber.floorGradientQ16[axis]=gradient[axis].Integer();
chamber.radiusSubvoxels=LayoutInt(value,"radius_q");
chamber.clearHeightSubvoxels=LayoutInt(value,"clear_height_q");
};
readLandingChamber("upper_landing_chamber",p.upperLandingChamber);
readLandingChamber("lower_landing_chamber",p.lowerLandingChamber);
for(const auto& q:v.Get("control_points").AsArray())
{
ResolvedLayoutPassagePointV1 point;const auto& position=q.Get("position_q").AsArray();
@@ -2367,7 +2452,6 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
constexpr std::int64_t MinimumBoreRadius=1750000;
constexpr std::int64_t MinimumBoreHeight=2200000;
constexpr std::int64_t PlannedBoreHeight=4500000;
constexpr std::int64_t MouthOverlap=2000000;
constexpr std::int64_t PortalLeadLength=6000000;
constexpr std::int64_t MaximumBoreRadius=10000000;
constexpr std::int64_t MaximumWalkGradeDenominator=5; // 1:5 = 11.31 degrees.
@@ -2394,13 +2478,13 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
const auto qToMicrometres=[&](std::int64_t q)
{return q*static_cast<std::int64_t>(layout.coreVoxelSizeMicrometres)
/WorldAssemblySubvoxelsPerVoxel;};
const std::int64_t mouthBoreRadius=std::clamp<std::int64_t>(
std::max(MinimumBoreRadius,settings.minimumTunnelRadiusMicrometres),
MinimumBoreRadius,MaximumBoreRadius);
constexpr std::int64_t LandingConnectorBoreRadius=2000000;
const std::int64_t mouthBoreRadius=LandingConnectorBoreRadius;
const auto chooseMouthRouteTangent=[&](const ResolvedLayoutPassageV1& passage,
std::size_t mouthIndex,bool lowerSide,
const std::array<std::int64_t,2>& authoredRouteTangentQ16,
std::int64_t authoredFloorSlopeQ16,const FPoint& mouthFloorPoint)
std::int64_t authoredFloorSlopeQ16,const FPoint& mouthFloorPoint,
std::int64_t chamberRadiusMicrometres)
{
constexpr std::array<std::array<std::int64_t,2>,16> DirectionsQ16={{
{{65536,0}},{{60547,25080}},{{46341,46341}},{{25080,60547}},
@@ -2418,7 +2502,9 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
+outwardQ16[1]*authoredRouteTangentQ16[1])
/(65536ll*65536ll));
constexpr std::int64_t MouthClearanceProbeExtent=48000000;
for(std::int64_t distance=2000000;
const std::int64_t firstClearanceProbe=std::max<std::int64_t>(
2000000,chamberRadiusMicrometres+1000000);
for(std::int64_t distance=firstClearanceProbe;
distance<=MouthClearanceProbeExtent;distance+=2000000)
{
const std::int64_t x=mouthFloorPoint[0]
@@ -2515,9 +2601,10 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
bool lowerSide,FPoint& point,std::array<std::int64_t,2>& routeTangentQ16,
std::int64_t& floorSlopeQ16)
{
const auto& chamber=lowerSide?passage.lowerLandingChamber
:passage.upperLandingChamber;
for(std::size_t axis=0;axis<3;++axis)
point[axis]=qToMicrometres(lowerSide?passage.lowerMouthSubvoxels[axis]
:passage.upperMouthSubvoxels[axis]);
point[axis]=qToMicrometres(chamber.floorCenterSubvoxels[axis]);
if(passage.controlPoints.size()<2)return false;
const std::size_t mouthIndex=lowerSide
?std::min(passage.lowerMouthControlIndex,passage.controlPoints.size()-1):0;
@@ -2537,28 +2624,26 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
if(tangentLength<=0)return false;
routeTangentQ16={tx*65536/tangentLength,ty*65536/tangentLength};
const auto& mouthControl=passage.controlPoints[mouthIndex];
const auto& tangentControl=passage.controlPoints[tangentIndex];
const std::int64_t mouthFloor=mouthControl.positionSubvoxels[2]
-mouthControl.radiusSubvoxels;
const std::int64_t tangentFloor=tangentControl.positionSubvoxels[2]
-tangentControl.radiusSubvoxels;
// Resolved passage mouths already store the player center at floor + 1.13 m.
// Cave edge control points store the floor itself, so start from the passage
// control's floor rather than reusing the player-center mouth height.
const std::int64_t mouthFloor=chamber.floorCenterSubvoxels[2];
point[2]=qToMicrometres(mouthFloor);
const std::int64_t passageFloorDelta=direction*(mouthFloor-tangentFloor);
floorSlopeQ16=passageFloorDelta*65536/tangentLength;
floorSlopeQ16=(chamber.floorGradientQ16[0]*routeTangentQ16[0]
+chamber.floorGradientQ16[1]*routeTangentQ16[1])/65536;
const auto authoredRouteTangentQ16=routeTangentQ16;
routeTangentQ16=chooseMouthRouteTangent(passage,mouthIndex,lowerSide,
authoredRouteTangentQ16,floorSlopeQ16,point);
const std::int64_t tangentAlignment=routeTangentQ16[0]
*authoredRouteTangentQ16[0]+routeTangentQ16[1]
*authoredRouteTangentQ16[1];
floorSlopeQ16=floorSlopeQ16*tangentAlignment/(65536ll*65536ll);
authoredRouteTangentQ16,floorSlopeQ16,point,
qToMicrometres(chamber.radiusSubvoxels));
floorSlopeQ16=(chamber.floorGradientQ16[0]*routeTangentQ16[0]
+chamber.floorGradientQ16[1]*routeTangentQ16[1])/65536;
const std::int64_t offsetDirection=lowerSide?1:-1;
point[0]+=offsetDirection*routeTangentQ16[0]*MouthOverlap/65536;
point[1]+=offsetDirection*routeTangentQ16[1]*MouthOverlap/65536;
point[2]+=offsetDirection*floorSlopeQ16*MouthOverlap/65536;
const std::int64_t portOffset=std::max<std::int64_t>(0,
qToMicrometres(chamber.radiusSubvoxels)-LandingConnectorBoreRadius);
point[0]+=offsetDirection*routeTangentQ16[0]*portOffset/65536;
point[1]+=offsetDirection*routeTangentQ16[1]*portOffset/65536;
point[2]=qToMicrometres(mouthFloor)
+(chamber.floorGradientQ16[0]*(point[0]
-qToMicrometres(chamber.floorCenterSubvoxels[0]))
+chamber.floorGradientQ16[1]*(point[1]
-qToMicrometres(chamber.floorCenterSubvoxels[1])))/65536;
return true;
};
FPoint routeStart{},routeEnd{};
@@ -3391,13 +3476,14 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
const auto arrivalPoint=routeStart;
FPoint previousPoint=arrivalPoint;
std::int64_t previousProfileDistance=0;
std::string previousAnchor=arrivalPassage->stableId+"/lower-mouth";
std::string previousAnchor=arrivalPassage->stableId+"/lower-landing-chamber";
for(std::size_t nodeIndex=0;nodeIndex<nodes.size();++nodeIndex)
{
const auto& node=nodes[nodeIndex];
const std::string edgeId=routePrefix+"route-edge-"+std::to_string(nodeIndex);
auto edge=makeEdge(edgeId,previousAnchor,previousPoint,node,
selectedBoreRadius(edgeId),
nodeIndex==0?std::min(selectedBoreRadius(edgeId),LandingConnectorBoreRadius)
:selectedBoreRadius(edgeId),
previousProfileDistance,node.floorProfileDistanceMicrometres);
edges.push_back(std::move(edge));
previousPoint={node.point[0],node.point[1],node.point[2]};
@@ -3407,8 +3493,10 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
if(!nodes.empty()&&departurePassage)
{
const std::string departureEdgeId=routePrefix+"departure-edge";
auto edge=makeEdge(departureEdgeId,departurePassage->stableId+"/upper-mouth",
routeEnd,nodes.back(),selectedBoreRadius(departureEdgeId),
auto edge=makeEdge(departureEdgeId,
departurePassage->stableId+"/upper-landing-chamber",
routeEnd,nodes.back(),
std::min(selectedBoreRadius(departureEdgeId),LandingConnectorBoreRadius),
totalRouteLength,nodes.back().floorProfileDistanceMicrometres);
edges.push_back(std::move(edge));
}
@@ -3873,7 +3961,6 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
{error=passage.stableId+" route mouth is outside the resolved PassageConfig.DistanceMax radius";return false;}
passage.upperMouthSubvoxels=upperMouth;passage.lowerMouthSubvoxels=lowerMouth;
constexpr std::int64_t LandingExtensionQ=8;
std::array<std::int64_t,2> upperTangent{},lowerTangent{};
const bool hasUpperTangent=hasUpperRoute
&&routeTangent(upper,true,upperTangent);
@@ -4643,39 +4730,6 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
}
pushXY({lowerMouth[0],lowerMouth[1]});totalHorizontal=measurePath();
std::size_t lowerMouthIndex=xy.size()-1;
std::vector<std::array<std::int64_t,3>> lowerLandingPoints;
if(hasLowerTangent)
{
std::array<std::int64_t,3> previousRoutePoint=lowerMouth;
std::int64_t remainingQ=LandingExtensionQ*qPerMetre;
bool skippedMouth=false;
for(const auto& anchor:lower.anchors)
if(anchor.kind=="route_point"&&anchor.values.size()>=3)
{
const std::array<std::int64_t,3> routePoint{{anchor.values[0],
anchor.values[1],anchor.values[2]}};
if(!skippedMouth){skippedMouth=true;continue;}
if(remainingQ<=0)break;
const std::int64_t dx=routePoint[0]-previousRoutePoint[0];
const std::int64_t dy=routePoint[1]-previousRoutePoint[1];
const std::uint64_t segmentLength=distanceQ(dx,dy);
if(segmentLength==0)continue;
const std::int64_t advance=std::min<std::int64_t>(remainingQ,
static_cast<std::int64_t>(segmentLength));
std::array<std::int64_t,3> landingPoint{};
for(std::size_t axis=0;axis<3;++axis)
landingPoint[axis]=previousRoutePoint[axis]+PassageRoundRatio(
routePoint[axis]-previousRoutePoint[axis],advance,
static_cast<std::int64_t>(segmentLength));
if(!radialWithin(landingPoint,placementRadius))break;
lowerLandingPoints.push_back(landingPoint);
pushXY({landingPoint[0],landingPoint[1]});
previousRoutePoint=landingPoint;remainingQ-=advance;
if(advance<static_cast<std::int64_t>(segmentLength))break;
}
if(lowerLandingPoints.empty())
{error=passage.stableId+" lower route has no landing length inside PassageConfig.DistanceMax";return false;}
}
if(VoxelForgeCore::WorldAssemblyEvaluatorVersion>=188
&&style==0&&anchoredIslandPassage&&!anchoredSpiral&&xy.size()>4)
{
@@ -4718,8 +4772,7 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
std::max<std::int64_t>(8*qPerMetre,2*mouthRadius));
for(std::size_t i=0;i<xy.size();++i)
{
std::int64_t radius=(i==0||i==lowerMouthIndex
||(i>lowerMouthIndex&&i+1>=xy.size()-1))?mouthRadius:midRadius;
std::int64_t radius=(i==0||i==lowerMouthIndex)?mouthRadius:midRadius;
if(surfaceGroundDeparture&&i<lowerMouthIndex)
{
const double t=std::clamp(static_cast<double>(cumulative[i])
@@ -4761,9 +4814,7 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
}
}
std::int64_t playerZ=0;
if(i>lowerMouthIndex)
playerZ=lowerLandingPoints[i-lowerMouthIndex-1][2];
else if(hasAnchorHelix&&i<=helixEndIndex)
if(hasAnchorHelix&&i<=helixEndIndex)
playerZ=upperMouth[2]+PassageRoundRatio(helixBedrockZ-upperMouth[2],
static_cast<std::int64_t>(cumulative[i]),
static_cast<std::int64_t>(helixHorizontal));
@@ -4815,6 +4866,201 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
passage.controlPoints[lowerMouthIndex].positionSubvoxels[2]=lowerMouth[2]
+passage.controlPoints[lowerMouthIndex].radiusSubvoxels
-playerCenterAboveFloorQ;
std::vector<VoxelForgeCore::PassageSpline::FPoint> chamberFloorControls;
std::vector<double> chamberFloorProfile;
chamberFloorControls.reserve(passage.controlPoints.size());
chamberFloorProfile.reserve(passage.controlPoints.size());
for(const auto& point:passage.controlPoints)
{
chamberFloorControls.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)});
chamberFloorProfile.push_back(static_cast<double>(
point.positionSubvoxels[2]-point.radiusSubvoxels));
}
const auto routePoints=[&](const ResolvedLayoutStratumV2& host)
{
std::vector<std::array<double,3>> route;
for(const auto& anchor:host.anchors)
if(anchor.kind=="route_point"&&anchor.values.size()>=3)
route.push_back({static_cast<double>(anchor.values[0]),
static_cast<double>(anchor.values[1]),
static_cast<double>(anchor.values[2])});
return route;
};
if(upper.generatorKind=="TunnelNetwork"
||upper.generatorKind=="SurfaceWorld")
{
auto route=routePoints(upper);
if(upper.generatorKind=="TunnelNetwork"&&route.size()>2)
route.erase(route.begin(),route.end()-2);
double routeHeight=0.0,distanceSquared=0.0;
if(VoxelForgeCore::PassageSpline::ClosestRouteHeightAtXY(route,
chamberFloorControls.front()[0],chamberFloorControls.front()[1],
routeHeight,distanceSquared)
&&distanceSquared<=std::pow(chamberFloorControls.front()[3]
+2.0*qPerMetre,2.0))
VoxelForgeCore::PassageSpline::BlendEndpointFloorToRoute(
chamberFloorControls,chamberFloorProfile,0,route,qPerMetre,
upper.generatorKind=="SurfaceWorld"?0.98:1.13);
}
if(lower.generatorKind=="TunnelNetwork"
||lower.generatorKind=="VerticalShafts")
{
auto route=routePoints(lower);
if(route.size()>2)route.resize(2);
double routeHeight=0.0,distanceSquared=0.0;
if(VoxelForgeCore::PassageSpline::ClosestRouteHeightAtXY(route,
chamberFloorControls[lowerMouthIndex][0],
chamberFloorControls[lowerMouthIndex][1],
routeHeight,distanceSquared)
&&distanceSquared<=std::pow(
chamberFloorControls[lowerMouthIndex][3]
+2.0*qPerMetre,2.0))
VoxelForgeCore::PassageSpline::BlendEndpointFloorToRoute(
chamberFloorControls,chamberFloorProfile,
lowerMouthIndex,route,qPerMetre);
}
const auto resolveLandingChamber=[&](
ResolvedLayoutPassageLandingChamberV1& chamber,
const std::array<std::int64_t,3>& mouth,std::size_t endpointIndex,
std::size_t neighborIndex,const ResolvedLayoutStratumV2& host,
const char* side)
{
chamber.floorCenterSubvoxels=mouth;
chamber.radiusSubvoxels=mouthRadius+3*qPerMetre;
chamber.clearHeightSubvoxels=static_cast<std::int64_t>(
std::llround(4.5*qPerMetre));
const auto& endpoint=passage.controlPoints[endpointIndex];
const auto& neighbor=passage.controlPoints[neighborIndex];
const std::int64_t endpointFloor=static_cast<std::int64_t>(
std::llround(chamberFloorProfile[endpointIndex]));
const std::int64_t neighborFloor=static_cast<std::int64_t>(
std::llround(chamberFloorProfile[neighborIndex]));
chamber.floorCenterSubvoxels[2]=endpointFloor;
const std::int64_t dx=endpoint.positionSubvoxels[0]
-neighbor.positionSubvoxels[0];
const std::int64_t dy=endpoint.positionSubvoxels[1]
-neighbor.positionSubvoxels[1];
const std::int64_t dz=endpointFloor-neighborFloor;
const std::int64_t horizontalSquared=dx*dx+dy*dy;
if(horizontalSquared<=0)
{error=passage.stableId+" "+side+" landing chamber has no endpoint tangent";return false;}
chamber.floorGradientQ16={
PassageRoundRatio(dx<0?-dz:dz,std::abs(dx)*65536,
horizontalSquared),
PassageRoundRatio(dy<0?-dz:dz,std::abs(dy)*65536,
horizontalSquared)};
if(host.generatorKind=="SurfaceWorld")
{
const auto surfaceRoute=routePoints(host);
std::vector<std::array<double,3>> nearbyRoute;
// Fit the landing floor only to route anchors inside its
// chamber footprint; samples beyond the chamber can pull
// the floor away from the route at the joint.
const double supportRadius=static_cast<double>(
chamber.radiusSubvoxels);
for(const auto& point:surfaceRoute)
{
const double routeDx=point[0]-endpoint.positionSubvoxels[0];
const double routeDy=point[1]-endpoint.positionSubvoxels[1];
if(routeDx*routeDx+routeDy*routeDy<=supportRadius*supportRadius)
nearbyRoute.push_back(point);
}
if(nearbyRoute.size()>=2)
{
std::array<double,3> mean{};
for(const auto& point:nearbyRoute)
for(std::size_t axis=0;axis<3;++axis)
mean[axis]+=point[axis]/nearbyRoute.size();
double xx=0.0,xy=0.0,yy=0.0,xz=0.0,yz=0.0;
for(const auto& point:nearbyRoute)
{
const double routeDx=point[0]-mean[0];
const double routeDy=point[1]-mean[1];
const double routeDz=point[2]-mean[2];
xx+=routeDx*routeDx;xy+=routeDx*routeDy;
yy+=routeDy*routeDy;xz+=routeDx*routeDz;
yz+=routeDy*routeDz;
}
const double determinant=xx*yy-xy*xy;
double gradientX=0.0,gradientY=0.0;
if(determinant>1.0e-9)
{
gradientX=(xz*yy-yz*xy)/determinant;
gradientY=(yz*xx-xz*xy)/determinant;
}
else
{
// Route anchors can be collinear inside the chamber.
// Fit their dominant horizontal direction by least
// squares instead of extrapolating from one far point.
const double trace=xx+yy;
const double discriminant=std::sqrt(std::max(0.0,
(xx-yy)*(xx-yy)+4.0*xy*xy));
const double variance=0.5*(trace+discriminant);
if(variance>1.0e-9)
{
double axisX=1.0,axisY=0.0;
if(std::abs(xy)>1.0e-12)
{axisX=xy;axisY=variance-xx;}
else if(yy>xx)
{axisX=0.0;axisY=1.0;}
const double axisLength=std::hypot(axisX,axisY);
axisX/=axisLength;axisY/=axisLength;
const double routeSlope=(axisX*xz+axisY*yz)/variance;
gradientX=routeSlope*axisX;
gradientY=routeSlope*axisY;
}
}
chamber.floorGradientQ16={
static_cast<std::int32_t>(std::llround(gradientX*65536.0)),
static_cast<std::int32_t>(std::llround(gradientY*65536.0))};
}
}
const std::uint64_t gradientSquared=static_cast<std::uint64_t>(
chamber.floorGradientQ16[0]*chamber.floorGradientQ16[0]
+chamber.floorGradientQ16[1]*chamber.floorGradientQ16[1]);
const std::int64_t gradientMagnitude=static_cast<std::int64_t>(
PassageIsqrt(gradientSquared));
constexpr std::int64_t MaximumLandingGradeQ16=17560;
if(gradientMagnitude>MaximumLandingGradeQ16)
{error=passage.stableId+" "+side+" landing chamber floor exceeds 15 degrees";return false;}
const std::int64_t planeReachQ=PassageRoundRatio(
chamber.radiusSubvoxels,gradientMagnitude,65536);
const std::int64_t shellQ=static_cast<std::int64_t>(
std::llround(0.75*qPerMetre));
const auto qToUm=[&](std::int64_t q)
{return q*static_cast<std::int64_t>(layout.coreVoxelSizeMicrometres)
/WorldAssemblySubvoxelsPerVoxel;};
if(!host.content.presentInCurrentField
||qToUm(chamber.floorCenterSubvoxels[2]-planeReachQ-shellQ)
<host.content.bottomMicrometres
||qToUm(chamber.floorCenterSubvoxels[2]+planeReachQ
+chamber.clearHeightSubvoxels+shellQ)
>host.content.topMicrometres)
{
error=passage.stableId+" "+side
+" landing chamber does not fit inside its host content band";
return false;
}
return true;
};
if(!resolveLandingChamber(passage.upperLandingChamber,upperMouth,0,1,
upper,"upper")
||!resolveLandingChamber(passage.lowerLandingChamber,lowerMouth,
lowerMouthIndex,lowerMouthIndex-1,lower,"lower"))
return false;
std::printf("[PassageLandingChamberPlan] id=%s upper_radius_m=%.3f lower_radius_m=%.3f clear_height_m=4.500 upper_floor_grade_q16=(%lld,%lld) lower_floor_grade_q16=(%lld,%lld) status=PASS\n",
passage.stableId.c_str(),
passage.upperLandingChamber.radiusSubvoxels/static_cast<double>(qPerMetre),
passage.lowerLandingChamber.radiusSubvoxels/static_cast<double>(qPerMetre),
static_cast<long long>(passage.upperLandingChamber.floorGradientQ16[0]),
static_cast<long long>(passage.upperLandingChamber.floorGradientQ16[1]),
static_cast<long long>(passage.lowerLandingChamber.floorGradientQ16[0]),
static_cast<long long>(passage.lowerLandingChamber.floorGradientQ16[1]));
layout.passages.push_back(std::move(passage));
}
}
@@ -4842,17 +5088,22 @@ ResolvedLayoutValidatorAuditV3 AuditResolvedLayoutValidatorV3()
else result.failures.push_back({"negative_control_accepted",std::move(name)+" was accepted by strict validator"});
};
auto base=ResolvedLayoutSnapshotV3{};base.worldIdentity="synthetic";base.ownerVoxelSizeMicrometres=125000;
base.coreVoxelSizeMicrometres=250000;base.strates={make("a",0,10000000,0),make("b",1,-2000000,-12000000)};
base.coreVoxelSizeMicrometres=250000;base.strates={make("a",0,30000000,0),make("b",1,-2000000,-40000000)};
ResolvedLayoutPassageConfigV1 passageConfig;passageConfig.upperStrateIndex=0;passageConfig.connections=1;
passageConfig.mouthRadiusSubvoxels=1280;passageConfig.midRadiusSubvoxels=1280;
passageConfig.reachMinimumSubvoxels=2048;passageConfig.reachMaximumSubvoxels=2048;
passageConfig.reachMinimumSubvoxels=9216;passageConfig.reachMaximumSubvoxels=9216;
passageConfig.distanceMinimumSubvoxels=1024;passageConfig.distanceMaximumSubvoxels=10240;
std::string planError;
if(!PlanResolvedLayoutPassagesV1(base,19,{passageConfig},planError))
result.failures.push_back({"synthetic_passage_plan_failed",planError});
{std::vector<ResolvedLayoutIssueV2> issues;std::string error;
if(!base.validate(ResolvedLayoutValidationModeV3::strict,issues,error))
result.failures.push_back({"positive_control_rejected","valid ceiling/content/bedrock layout was rejected"});}
{
for(const auto& issue:issues)
std::printf("[ResolvedLayoutValidatorPositiveControlIssue] code=%s detail=%s\n",
issue.code.c_str(),issue.detail.c_str());
result.failures.push_back({"positive_control_rejected","valid ceiling/content/bedrock layout was rejected"});
}}
auto missingPassage=base;missingPassage.passages.clear();expectReject("missing interface passage",missingPassage);
auto outsidePassage=base;
if(!outsidePassage.passages.empty())
+280 -13
View File
@@ -1280,6 +1280,8 @@ public:
double unionDistance=std::numeric_limits<double>::infinity();
double unionShellDistance=std::numeric_limits<double>::infinity();
double unionFloorShellDistance=std::numeric_limits<double>::infinity();
double unionLandingChamberFloorShellDistance=
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);
@@ -1317,14 +1319,56 @@ public:
const bool passageTargetsPlannedTunnelNetwork=plannedTunnelStratum;
const double mouthShellBlendMetres=passageTargetsPlannedTunnelNetwork
?2.0:MouthShellBlendMetres;
// Legacy native cave fields own the floor and mouth carve on their
// lower-strate boundary. The planner's TunnelNetwork field instead
// needs the shared passage composer to carry its incoming mouth open.
if(passageTargetsNativeCaveField&&!passageTargetsPlannedTunnelNetwork)
continue;
// Legacy native cave fields own the tunnel bore on their lower-strate
// boundary. The shared passage composer still owns its landing chamber.
if(passageProfile)++passageProfile->passageIterations;
const auto& constants=PassageComposerConstants[passageIndex];
const VoxelForgeCore::ResolvedLayoutPassageLandingChamberV1* landingChamber=nullptr;
if(queryStratum&&queryStratum->Resolved)
{
if(queryStratum->StrateIndex==passage.upperStrateIndex)
landingChamber=&passage.upperLandingChamber;
else if(queryStratum->StrateIndex==passage.lowerStrateIndex)
landingChamber=&passage.lowerLandingChamber;
}
if(landingChamber&&landingChamber->radiusSubvoxels>0
&&landingChamber->clearHeightSubvoxels>0)
{
const double centerX=static_cast<double>(
landingChamber->floorCenterSubvoxels[0]);
const double centerY=static_cast<double>(
landingChamber->floorCenterSubvoxels[1]);
const double floorCenterZ=static_cast<double>(
landingChamber->floorCenterSubvoxels[2])
+(landingChamber->floorGradientQ16[0]*(qx-centerX)
+landingChamber->floorGradientQ16[1]*(qy-centerY))/65536.0;
const double radialDistance=DeterministicSqrt(
(qx-centerX)*(qx-centerX)+(qy-centerY)*(qy-centerY))
-landingChamber->radiusSubvoxels;
const double floorDistance=floorCenterZ-qz;
const double ceilingDistance=qz-(floorCenterZ
+landingChamber->clearHeightSubvoxels);
const double chamberDistance=std::max({radialDistance,
floorDistance,ceilingDistance});
const double chamberShellDistance=std::max(-chamberDistance,
chamberDistance-shellThickness);
const double chamberFloorShellDistance=std::max(
std::max(-floorDistance,floorDistance-shellThickness),
radialDistance);
unionDistance=std::min(unionDistance,chamberDistance);
unionShellDistance=std::min(unionShellDistance,chamberShellDistance);
unionFloorShellDistance=std::min(unionFloorShellDistance,
chamberFloorShellDistance);
unionLandingChamberFloorShellDistance=std::min(
unionLandingChamberFloorShellDistance,chamberFloorShellDistance);
if(passageTargetsPlannedTunnelNetwork)
plannedTunnelPassageShellDistance=std::min(
plannedTunnelPassageShellDistance,chamberShellDistance);
}
if(passageTargetsNativeCaveField&&!passageTargetsPlannedTunnelNetwork)
continue;
FNearestTubeSample nearest;
const bool usePreparedSweep=passageTileScopeMatches
&&passageTileQueries->hasSweptTubeBase();
@@ -1615,6 +1659,11 @@ public:
if(!std::isfinite(unionDistance))return density;
const float passageAirDistance=static_cast<float>(-unionDistance/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);
const auto plannedPortalWalkCorridorContains=[&]()
{
if(!queryStratum||!queryStratum->Resolved
@@ -1678,7 +1727,11 @@ public:
const double routeFloor=std::lerp(a[2],b[2],t)-1.13;
return queryZ>=routeFloor-0.30&&queryZ<=routeFloor+2.25;
};
constexpr double PortalWalkLeadMetres=8.0;
// The connected layer route begins inside the landing chamber. Let
// its air open the chamber's plain side shell through the chamber
// radius plus the shell thickness; the condition below remains
// limited to samples where the tunnel/chamber shell is present.
constexpr double PortalWalkLeadMetres=9.0;
const auto arrival=position(*first),arrivalInner=position(*second);
const double arrivalInnerDistance=std::hypot(
arrivalInner[0]-arrival[0],arrivalInner[1]-arrival[1]);
@@ -1693,6 +1746,90 @@ public:
departureTailDistance)
||nearRouteSegment(*lastTwo,*last,PortalWalkLeadMetres,true,0.0);
};
const auto connectedSurfaceLandingShellContains=[&]()
{
if(!queryStratum||!queryStratum->Resolved
||queryStratum->Resolved->generatorKind!="SurfaceWorld")return false;
const double queryX=qx/qPerMetre,queryY=qy/qPerMetre,queryZ=qz/qPerMetre;
constexpr double ChamberShellThicknessMetres=0.75;
constexpr double RouteClearanceMetres=1.25;
constexpr double RouteHeightClearanceMetres=1.5;
constexpr double PortalWalkLeadMetres=9.0;
for(const auto& passage:ResolvedLayout->passages)
{
if(passage.upperStrateIndex!=queryStratum->StrateIndex)continue;
const auto& chamber=passage.upperLandingChamber;
if(chamber.radiusSubvoxels<=0||chamber.clearHeightSubvoxels<=0)continue;
const double cx=static_cast<double>(chamber.floorCenterSubvoxels[0])/qPerMetre;
const double cy=static_cast<double>(chamber.floorCenterSubvoxels[1])/qPerMetre;
const double floorZ=static_cast<double>(chamber.floorCenterSubvoxels[2])/qPerMetre
+chamber.floorGradientQ16[0]/65536.0*(queryX-cx)
+chamber.floorGradientQ16[1]/65536.0*(queryY-cy);
const double radialDistance=std::hypot(queryX-cx,queryY-cy)
-static_cast<double>(chamber.radiusSubvoxels)/qPerMetre;
const double floorDistance=floorZ-queryZ;
const double ceilingDistance=queryZ-(floorZ
+static_cast<double>(chamber.clearHeightSubvoxels)/qPerMetre);
const double chamberDistance=std::max({radialDistance,
floorDistance,ceilingDistance});
const double chamberShellDistance=std::max(-chamberDistance,
chamberDistance-ChamberShellThicknessMetres);
if(chamberDistance<=0.0||chamberShellDistance>=0.0)continue;
std::vector<const VoxelForgeCore::ResolvedLayoutAnchorV2*> route;
for(const auto& anchor:queryStratum->Resolved->anchors)
if(anchor.kind=="route_point"&&anchor.values.size()>=3)
route.push_back(&anchor);
if(route.size()<2)continue;
const double mouthZ=static_cast<double>(passage.upperMouthSubvoxels[2])
/qPerMetre;
std::size_t endpoint=route.size();
double endpointDistanceSquared=1.0;
for(std::size_t index=0;index<route.size();++index)
{
const double x=route[index]->values[0]/qPerMetre;
const double y=route[index]->values[1]/qPerMetre;
const double z=route[index]->values[2]/qPerMetre;
const double dx=x-cx,dy=y-cy;
const double distanceSquared=dx*dx+dy*dy;
if(distanceSquared<endpointDistanceSquared
&&std::abs(z-mouthZ)<=1.5)
{endpoint=index;endpointDistanceSquared=distanceSquared;}
}
if(endpoint==route.size())continue;
std::vector<double> routeDistance(route.size(),0.0);
for(std::size_t index=1;index<route.size();++index)
{
const double dx=(route[index]->values[0]-route[index-1]->values[0])
/qPerMetre;
const double dy=(route[index]->values[1]-route[index-1]->values[1])
/qPerMetre;
routeDistance[index]=routeDistance[index-1]+std::hypot(dx,dy);
}
for(std::size_t index=1;index<route.size();++index)
{
const auto& a=*route[index-1];
const auto& b=*route[index];
const double ax=a.values[0]/qPerMetre,ay=a.values[1]/qPerMetre;
const double az=a.values[2]/qPerMetre;
const double dx=(b.values[0]-a.values[0])/qPerMetre;
const double dy=(b.values[1]-a.values[1])/qPerMetre;
const double horizontalLengthSquared=dx*dx+dy*dy;
if(horizontalLengthSquared<=1.0e-12)continue;
const double t=std::clamp(((queryX-ax)*dx+(queryY-ay)*dy)
/horizontalLengthSquared,0.0,1.0);
const double alongFromEndpoint=std::abs(
std::lerp(routeDistance[index-1],routeDistance[index],t)
-routeDistance[endpoint]);
if(alongFromEndpoint>PortalWalkLeadMetres)continue;
const double routeX=ax+t*dx,routeY=ay+t*dy;
const double routeZ=az+t*(b.values[2]-a.values[2])/qPerMetre;
if(std::hypot(queryX-routeX,queryY-routeY)<=RouteClearanceMetres
&&std::abs(queryZ-routeZ)<=RouteHeightClearanceMetres)return true;
}
}
return false;
};
if(plannedTunnelStratum
&&nativeTunnelFieldDensity!=std::numeric_limits<float>::lowest()
&&nativeTunnelFieldDensity<0.0f)
@@ -1755,7 +1892,11 @@ public:
if(nativeTunnelFieldDensity!=std::numeric_limits<float>::lowest())
return 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 std::max(density,passageAirDistance);
}
@@ -1892,6 +2033,26 @@ public:
}
if(overlaps(low,high))return true;
}
const auto chamberOverlaps=[&](
const VoxelForgeCore::ResolvedLayoutPassageLandingChamberV1& chamber)
{
const double cx=static_cast<double>(chamber.floorCenterSubvoxels[0])/Q;
const double cy=static_cast<double>(chamber.floorCenterSubvoxels[1])/Q;
const double cz=static_cast<double>(chamber.floorCenterSubvoxels[2])/Q;
const double radius=static_cast<double>(chamber.radiusSubvoxels)/Q;
const double floorExtent=radius*std::hypot(
static_cast<double>(chamber.floorGradientQ16[0])/65536.0,
static_cast<double>(chamber.floorGradientQ16[1])/65536.0);
const double extent=reachCoreVoxels+0.01;
const double low[3]={cx-radius-extent,cy-radius-extent,
cz-floorExtent-extent};
const double high[3]={cx+radius+extent,cy+radius+extent,
cz+floorExtent+static_cast<double>(
chamber.clearHeightSubvoxels)/Q+extent};
return overlaps(low,high);
};
if(chamberOverlaps(passage.upperLandingChamber)
||chamberOverlaps(passage.lowerLandingChamber))return true;
}
return false;
}
@@ -3668,6 +3829,12 @@ private:
double lowerMouthPlannedFloor=0.0;
double lowerMouthTangentX=0.0,lowerMouthTangentY=0.0;
std::vector<std::array<double,3>> lowerMouthReceivingRoute;
std::array<double,3> upperLandingFloorCenterSubvoxels{};
std::array<double,2> upperLandingFloorGradient{};
double upperLandingRadiusSubvoxels=0.0;
std::array<double,3> lowerLandingFloorCenterSubvoxels{};
std::array<double,2> lowerLandingFloorGradient{};
double lowerLandingRadiusSubvoxels=0.0;
std::size_t routeEndControlIndex=0;
};
struct FPassageSegmentData
@@ -3692,6 +3859,49 @@ private:
std::max(0.0,radiusSubvoxels-0.23*qPerMetre));
}
static double BlendPassageFloorToLandingChambers(
const FPassageComposerConstants& constants,
const FPassageSegmentData& segment,double floorT,
double queryXSubvoxels,double queryYSubvoxels,double plannedFloor,
double qPerMetre)
{
if(qPerMetre<=0.0||!(constants.totalPathLengthMetres>0.0))
return plannedFloor;
const double alongMetres=segment.authoredAlongMetres
+floorT*segment.authoredLengthMetres;
constexpr double LandingFloorBlendMetres=12.0;
const auto floorWeight=[&](double distanceMetres,double radiusSubvoxels)
{
if(!(radiusSubvoxels>0.0))return 0.0;
const double radiusMetres=radiusSubvoxels/qPerMetre;
if(distanceMetres<=radiusMetres)return 1.0;
if(distanceMetres>=radiusMetres+LandingFloorBlendMetres)return 0.0;
const double t=(distanceMetres-radiusMetres)/LandingFloorBlendMetres;
return 1.0-t*t*(3.0-2.0*t);
};
const double upperWeight=floorWeight(std::max(0.0,alongMetres),
constants.upperLandingRadiusSubvoxels);
const double lowerWeight=floorWeight(std::max(0.0,
constants.totalPathLengthMetres-alongMetres),
constants.lowerLandingRadiusSubvoxels);
if(upperWeight<=0.0&&lowerWeight<=0.0)return plannedFloor;
const auto floorAt=[&](const std::array<double,3>& center,
const std::array<double,2>& gradient)
{
return center[2]+gradient[0]*(queryXSubvoxels-center[0])
+gradient[1]*(queryYSubvoxels-center[1]);
};
const double upperFloor=floorAt(constants.upperLandingFloorCenterSubvoxels,
constants.upperLandingFloorGradient);
const double lowerFloor=floorAt(constants.lowerLandingFloorCenterSubvoxels,
constants.lowerLandingFloorGradient);
const double totalWeight=upperWeight+lowerWeight;
if(totalWeight<=1.0)
return plannedFloor*(1.0-totalWeight)
+upperFloor*upperWeight+lowerFloor*lowerWeight;
return (upperFloor*upperWeight+lowerFloor*lowerWeight)/totalWeight;
}
static double PassageFlatFloorCenterZSubvoxels(
const FPassageComposerConstants& constants,
const std::vector<FPassageSegmentData>& segments,
@@ -3742,7 +3952,9 @@ private:
-constants.lowerMouthTangentX,-constants.lowerMouthTangentY,
VoxelForgeCore::PassageSpline::UpperMouthFloorBlendRadiusMetres,1.13,
qPerMetre);
return lowerBlendFloor+floorDepthSubvoxels;
const double landingFloor=BlendPassageFloorToLandingChambers(constants,
segment,floorT,queryXSubvoxels,queryYSubvoxels,lowerBlendFloor,qPerMetre);
return landingFloor+floorDepthSubvoxels;
}
static float PassageFlatBottomDistance(float roundDistanceSubvoxels,
@@ -4900,6 +5112,23 @@ private:
passage.lowerMouthControlIndex,1,passage.controlPoints.size()-1);
auto& constants=PassageComposerConstants[passageIndex];
constants.routeEndControlIndex=routeEnd;
for(std::size_t axis=0;axis<3;++axis)
{
constants.upperLandingFloorCenterSubvoxels[axis]=static_cast<double>(
passage.upperLandingChamber.floorCenterSubvoxels[axis]);
constants.lowerLandingFloorCenterSubvoxels[axis]=static_cast<double>(
passage.lowerLandingChamber.floorCenterSubvoxels[axis]);
}
constants.upperLandingFloorGradient={
passage.upperLandingChamber.floorGradientQ16[0]/65536.0,
passage.upperLandingChamber.floorGradientQ16[1]/65536.0};
constants.lowerLandingFloorGradient={
passage.lowerLandingChamber.floorGradientQ16[0]/65536.0,
passage.lowerLandingChamber.floorGradientQ16[1]/65536.0};
constants.upperLandingRadiusSubvoxels=static_cast<double>(
passage.upperLandingChamber.radiusSubvoxels);
constants.lowerLandingRadiusSubvoxels=static_cast<double>(
passage.lowerLandingChamber.radiusSubvoxels);
const auto upperStratum=std::find_if(ResolvedLayout->strates.begin(),
ResolvedLayout->strates.end(),[&](const auto& stratum)
{return stratum.strateIndex==passage.upperStrateIndex;});
@@ -4928,7 +5157,8 @@ private:
double upperMouthFloorDepth=authoredUpperMouthFloorDepth;
if(upperStratum!=ResolvedLayout->strates.end()
&&(upperStratum->generatorKind=="FloatingIslands"
||upperStratum->generatorKind=="TunnelNetwork"))
||upperStratum->generatorKind=="TunnelNetwork"
||upperStratum->generatorKind=="SurfaceWorld"))
{
std::vector<std::array<double,3>> route;
for(const auto& anchor:upperStratum->anchors)
@@ -4947,16 +5177,19 @@ private:
upperMouthFloorDepth=std::max(upperMouthFloorDepth,
std::max(0.0,(controls.front()[2]-routeHeight)/qPerMetre
+constants.upperMouthPlayerCenterHeightMetres));
if(upperStratum->generatorKind=="TunnelNetwork")
if(upperStratum->generatorKind=="TunnelNetwork"
||upperStratum->generatorKind=="SurfaceWorld")
VoxelForgeCore::PassageSpline::BlendEndpointFloorToRoute(
controls,plannedFloorProfile,0,route,qPerMetre);
controls,plannedFloorProfile,0,route,qPerMetre,
constants.upperMouthPlayerCenterHeightMetres);
}
}
controls.front()[3]=std::max(controls.front()[3],
upperMouthFloorDepth*qPerMetre);
if(upperStratum!=ResolvedLayout->strates.end()
&&(upperStratum->generatorKind=="FloatingIslands"
||upperStratum->generatorKind=="TunnelNetwork"))
||upperStratum->generatorKind=="TunnelNetwork"
||upperStratum->generatorKind=="SurfaceWorld"))
for(const auto& anchor:upperStratum->anchors)
if(anchor.kind=="route_point"&&anchor.values.size()>=3)
constants.upperMouthReceivingRoute.push_back({
@@ -5044,8 +5277,8 @@ private:
constants.upperMouthTangentY=sampledSegments.front().b[1]
-sampledSegments.front().a[1];
}
const std::string lowerMouthAnchor=passage.stableId+"/lower-mouth";
const std::string upperMouthAnchor=passage.stableId+"/upper-mouth";
const std::string lowerMouthAnchor=passage.stableId+"/lower-landing-chamber";
const std::string upperMouthAnchor=passage.stableId+"/upper-landing-chamber";
for(const auto& edge:ResolvedLayout->cavernEdges)
{
if(edge.controlPointsMicrometres.size()<2)continue;
@@ -5230,6 +5463,40 @@ private:
std::ceil((localMaximum[axis]+localExtent)*Q));
}
}
const auto includeLandingChamber=[&](
const VoxelForgeCore::ResolvedLayoutPassageLandingChamberV1& chamber)
{
const double cx=static_cast<double>(chamber.floorCenterSubvoxels[0])/Q;
const double cy=static_cast<double>(chamber.floorCenterSubvoxels[1])/Q;
const double cz=static_cast<double>(chamber.floorCenterSubvoxels[2])/Q;
const double radius=static_cast<double>(chamber.radiusSubvoxels)/Q;
const double gx=static_cast<double>(chamber.floorGradientQ16[0])/65536.0;
const double gy=static_cast<double>(chamber.floorGradientQ16[1])/65536.0;
const double floorExtent=radius*std::hypot(gx,gy);
const double height=static_cast<double>(
chamber.clearHeightSubvoxels)/Q;
const double extent=requiredReachCoreVoxels+0.01;
const std::array<double,3> minimum{
cx-radius-extent,cy-radius-extent,cz-floorExtent-extent};
const std::array<double,3> maximum{
cx+radius+extent,cy+radius+extent,
cz+floorExtent+height+extent};
FPassageProofBound proof;
for(std::size_t axis=0;axis<3;++axis)
{
routeMinimum[axis]=std::min(routeMinimum[axis],minimum[axis]);
routeMaximum[axis]=std::max(routeMaximum[axis],maximum[axis]);
proof.minimum[axis]=minimum[axis];
proof.maximum[axis]=maximum[axis];
}
PassageProofBounds.push_back(proof);
};
if(segmentIndex==0)
includeLandingChamber(
ResolvedLayout->passages[passageIndex].upperLandingChamber);
if(segmentEndIndex==segmentCount)
includeLandingChamber(
ResolvedLayout->passages[passageIndex].lowerLandingChamber);
const double routeExtent=maximumRadiusSubvoxels/Q
+requiredReachCoreVoxels+0.01;
const double tileExtent=routeExtent;