core: Vertical Shafts on the shared Core cave planner (L139, v237; 15/16 seeds)
Shaft mode reuses PlanResolvedCavernNetworkV1 with a capped full-height cylinder primitive (shapeKind=1), ledges, access bores and cross links; 11 VerticalShaftParams fields mapped. Basal law: arrival 2-3 to a walkable terminal landing; drops/climbs stay reporting tiers. Seed 2 fails shell/seams at the 2-3 arrival mouth (3 variants tried, stopped per AGENTS.md). Whole view faster than assembler/world in 8/8 cells; near-ready slower in 7/8. Source/: VoxelForgeCoreAdapter.cpp. 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
fa4417aad0
commit
318c5cf4f2
31 files changed
+1719
-106
No files matched your search
@@ -510,7 +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.
|
||||
inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 236;
|
||||
// v237 adds the shared Core cave-graph shaft primitive and basal terminal route.
|
||||
inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 237;
|
||||
|
||||
enum class FieldChannel : std::uint32_t
|
||||
{
|
||||
@@ -906,6 +907,7 @@ namespace VoxelForgeCore
|
||||
inline constexpr std::uint32_t ResolvedLayoutStampVocabularyVersionV3 = 3;
|
||||
inline constexpr std::uint32_t ResolvedLayoutCavernGraphVocabularyVersionV1 = 1;
|
||||
inline constexpr std::uint32_t ResolvedLayoutCavernGraphVocabularyVersionV2 = 2;
|
||||
inline constexpr std::uint32_t ResolvedLayoutCavernGraphVocabularyVersionV3 = 3;
|
||||
inline constexpr std::uint32_t ResolvedLayoutOreVeinVocabularyVersionV1 = 1;
|
||||
enum class ResolvedLayoutValidationModeV3 : std::uint8_t { compatibility, strict };
|
||||
|
||||
@@ -967,6 +969,10 @@ namespace VoxelForgeCore
|
||||
{
|
||||
std::string stableId;
|
||||
std::int32_t strateIndex = -1;
|
||||
// 0 = floor-backed room; 1 = a true vertical cylindrical shaft. The
|
||||
// shaft form is part of the shared cave graph so other recipes can reuse
|
||||
// the same graph, indexing, interval and density machinery.
|
||||
std::uint8_t shapeKind = 0;
|
||||
std::array<std::int64_t, 3> centerMicrometres{};
|
||||
std::array<std::int64_t, 3> halfExtentsMicrometres{};
|
||||
std::int64_t floorMicrometres = 0;
|
||||
@@ -982,6 +988,11 @@ namespace VoxelForgeCore
|
||||
std::int64_t irregularityAmplitudeMicrometres = 0;
|
||||
std::int64_t irregularityWavelengthMicrometres = 0;
|
||||
std::uint64_t irregularitySeed = 0;
|
||||
// Used only by shapeKind=1. floorMicrometres is the shaft top; the
|
||||
// bottom is center.z - halfExtents.z. Ledges face one cardinal side.
|
||||
std::int64_t ledgeSpacingMicrometres = 0;
|
||||
std::int64_t ledgeDepthMicrometres = 0;
|
||||
std::uint8_t ledgeSide = 0;
|
||||
std::array<std::int64_t, 3> boundsMinimumMicrometres{};
|
||||
std::array<std::int64_t, 3> boundsMaximumMicrometres{};
|
||||
};
|
||||
@@ -1044,6 +1055,12 @@ namespace VoxelForgeCore
|
||||
std::int32_t tunnelHorizontalBiasQ16 = 32768;
|
||||
std::int32_t originRoomMaximumConnections = 4;
|
||||
bool tunnelsFlowTowardOrigin = true;
|
||||
// Vertical Shafts uses this same planner and graph field. It binds its
|
||||
// incoming passage and resolves a bedrock terminal when no departure
|
||||
// mouth exists, then adds shaft records beside the walk-only backbone.
|
||||
bool verticalShaftMode = false;
|
||||
std::int64_t ledgeSpacingMicrometres = 8000000;
|
||||
std::int64_t ledgeDepthMicrometres = 1000000;
|
||||
};
|
||||
|
||||
// Immutable material-only deposits. The capsule and its conservative bounds are in
|
||||
@@ -1150,7 +1167,8 @@ namespace VoxelForgeCore
|
||||
|
||||
inline bool IsNativeCaveFieldStratum(const ResolvedLayoutStratumV2& stratum) noexcept
|
||||
{
|
||||
return (stratum.generatorKind=="Caves"||stratum.generatorKind=="TunnelNetwork")
|
||||
return (stratum.generatorKind=="Caves"||stratum.generatorKind=="TunnelNetwork"
|
||||
||stratum.generatorKind=="VerticalShafts")
|
||||
&&stratum.recipeId=="Core.Caves.PrimitiveField"
|
||||
&&stratum.recipeVersion==1;
|
||||
}
|
||||
|
||||
@@ -626,8 +626,12 @@ namespace
|
||||
<<",\"floor_profile_distance_um\":"<<room.floorProfileDistanceMicrometres
|
||||
<<",\"floor_gradient_q16\":["<<room.floorGradientQ16[0]<<','
|
||||
<<room.floorGradientQ16[1]<<']'
|
||||
<<",\"smooth_width_um\":"<<room.smoothWidthMicrometres
|
||||
;
|
||||
<<",\"smooth_width_um\":"<<room.smoothWidthMicrometres;
|
||||
if(room.shapeKind!=0)
|
||||
out<<",\"shape_kind\":"<<static_cast<unsigned>(room.shapeKind)
|
||||
<<",\"ledge_spacing_um\":"<<room.ledgeSpacingMicrometres
|
||||
<<",\"ledge_depth_um\":"<<room.ledgeDepthMicrometres
|
||||
<<",\"ledge_side\":"<<static_cast<unsigned>(room.ledgeSide);
|
||||
if(room.unionWidthMicrometres>0)
|
||||
out<<",\"union_width_um\":"<<room.unionWidthMicrometres;
|
||||
out<<",\"irregularity_amplitude_um\":"<<room.irregularityAmplitudeMicrometres
|
||||
@@ -1294,7 +1298,8 @@ bool ResolvedLayoutSnapshotV3::validate(ResolvedLayoutValidationModeV3 mode,
|
||||
"cavern graph vocabulary version is set without graph records");
|
||||
}
|
||||
else if(cavernGraphVocabularyVersion!=ResolvedLayoutCavernGraphVocabularyVersionV1
|
||||
&&cavernGraphVocabularyVersion!=ResolvedLayoutCavernGraphVocabularyVersionV2)
|
||||
&&cavernGraphVocabularyVersion!=ResolvedLayoutCavernGraphVocabularyVersionV2
|
||||
&&cavernGraphVocabularyVersion!=ResolvedLayoutCavernGraphVocabularyVersionV3)
|
||||
add("cavern_graph_vocabulary_version",
|
||||
"cavern graph records require a supported vocabulary version");
|
||||
if(cavernGraphVocabularyVersion!=0)
|
||||
@@ -1327,6 +1332,33 @@ bool ResolvedLayoutSnapshotV3::validate(ResolvedLayoutValidationModeV3 mode,
|
||||
||(room.irregularityAmplitudeMicrometres>0
|
||||
&&room.irregularityWavelengthMicrometres<=0))
|
||||
add("cavern_room_parameters",room.stableId+" has invalid or unbounded parameters");
|
||||
const auto* roomStratum=findStratum(room.strateIndex);
|
||||
if(room.shapeKind>1)
|
||||
add("cavern_room_shape",room.stableId+" has an unknown shape kind");
|
||||
if(room.shapeKind==1)
|
||||
{
|
||||
const bool verticalShaftStratum=roomStratum
|
||||
&&roomStratum->generatorKind=="VerticalShafts"
|
||||
&&IsNativeCaveFieldStratum(*roomStratum);
|
||||
if(cavernGraphVocabularyVersion<ResolvedLayoutCavernGraphVocabularyVersionV3
|
||||
||!verticalShaftStratum
|
||||
||room.halfExtentsMicrometres[0]!=room.halfExtentsMicrometres[1]
|
||||
||room.ledgeSpacingMicrometres<0
|
||||
||room.ledgeSpacingMicrometres>100000000
|
||||
||room.ledgeDepthMicrometres<0
|
||||
||room.ledgeDepthMicrometres>=room.halfExtentsMicrometres[0]
|
||||
||room.ledgeSide>3
|
||||
||room.floorMicrometres
|
||||
!=room.centerMicrometres[2]+room.halfExtentsMicrometres[2]
|
||||
||!room.floorProfileStableId.empty()
|
||||
||room.floorGradientQ16[0]!=0||room.floorGradientQ16[1]!=0)
|
||||
add("cavern_shaft_parameters",room.stableId+
|
||||
" has invalid native shaft bounds, ledges, or recipe binding");
|
||||
}
|
||||
else if(room.ledgeSpacingMicrometres!=0||room.ledgeDepthMicrometres!=0
|
||||
||room.ledgeSide!=0)
|
||||
add("cavern_room_shape",room.stableId+
|
||||
" carries shaft parameters on an ordinary room");
|
||||
const std::int64_t gradientSquared=static_cast<std::int64_t>(room.floorGradientQ16[0])
|
||||
*room.floorGradientQ16[0]+static_cast<std::int64_t>(room.floorGradientQ16[1])
|
||||
*room.floorGradientQ16[1];
|
||||
@@ -1415,11 +1447,37 @@ bool ResolvedLayoutSnapshotV3::validate(ResolvedLayoutValidationModeV3 mode,
|
||||
if(room!=cavernRooms.end())
|
||||
{
|
||||
const auto& endpoint=edge.controlPointsMicrometres.back();
|
||||
bool validRoomEndpoint=false;
|
||||
if(room->shapeKind==1)
|
||||
{
|
||||
const std::int64_t shaftBottom=room->centerMicrometres[2]
|
||||
-room->halfExtentsMicrometres[2];
|
||||
const std::int64_t shaftTop=room->centerMicrometres[2]
|
||||
+room->halfExtentsMicrometres[2];
|
||||
const bool insideShaft=endpoint[2]>=shaftBottom
|
||||
&&endpoint[2]<=shaftTop;
|
||||
bool onLanding=room->ledgeSpacingMicrometres==0;
|
||||
if(room->ledgeSpacingMicrometres>0)
|
||||
{
|
||||
const std::int64_t ledgeIndex=(shaftTop-endpoint[2]
|
||||
+room->ledgeSpacingMicrometres/2)
|
||||
/room->ledgeSpacingMicrometres;
|
||||
const std::int64_t ledgeZ=shaftTop
|
||||
-ledgeIndex*room->ledgeSpacingMicrometres;
|
||||
const std::int64_t maximumLedge=(shaftTop-shaftBottom-1000000)
|
||||
/room->ledgeSpacingMicrometres;
|
||||
onLanding=ledgeIndex>=1&&ledgeIndex<=maximumLedge
|
||||
&&std::abs(endpoint[2]-ledgeZ)<=500000;
|
||||
}
|
||||
validRoomEndpoint=insideShaft&&onLanding;
|
||||
}
|
||||
else validRoomEndpoint=
|
||||
std::abs(endpoint[2]-room->floorMicrometres)<=500000;
|
||||
if(endpoint[0]<room->centerMicrometres[0]-room->halfExtentsMicrometres[0]
|
||||
||endpoint[0]>room->centerMicrometres[0]+room->halfExtentsMicrometres[0]
|
||||
||endpoint[1]<room->centerMicrometres[1]-room->halfExtentsMicrometres[1]
|
||||
||endpoint[1]>room->centerMicrometres[1]+room->halfExtentsMicrometres[1]
|
||||
||std::abs(endpoint[2]-room->floorMicrometres)>500000)
|
||||
||!validRoomEndpoint)
|
||||
{
|
||||
add("cavern_edge_portal",edge.stableId+" endpoint_um=("
|
||||
+std::to_string(endpoint[0])+","+std::to_string(endpoint[1])+","+
|
||||
@@ -1733,6 +1791,10 @@ std::optional<ResolvedLayoutSnapshotV3> ResolvedLayoutSnapshotV3::FromJson(
|
||||
room.irregularityAmplitudeMicrometres=LayoutInt(v,"irregularity_amplitude_um");
|
||||
room.irregularityWavelengthMicrometres=LayoutInt(v,"irregularity_wavelength_um");
|
||||
room.irregularitySeed=static_cast<std::uint64_t>(LayoutInt(v,"irregularity_seed"));
|
||||
room.shapeKind=static_cast<std::uint8_t>(LayoutInt(v,"shape_kind"));
|
||||
room.ledgeSpacingMicrometres=LayoutInt(v,"ledge_spacing_um");
|
||||
room.ledgeDepthMicrometres=LayoutInt(v,"ledge_depth_um");
|
||||
room.ledgeSide=static_cast<std::uint8_t>(LayoutInt(v,"ledge_side"));
|
||||
s.cavernRooms.push_back(std::move(room));
|
||||
}
|
||||
for(const auto& v:root.Get("cavern_edges").AsArray())
|
||||
@@ -2088,7 +2150,11 @@ bool ResolveResolvedCavernGraphV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
||||
if(!candidate.finalize(error))return false;
|
||||
layout=std::move(candidate);return true;
|
||||
}
|
||||
candidate.cavernGraphVocabularyVersion=ResolvedLayoutCavernGraphVocabularyVersionV2;
|
||||
const bool hasVerticalShaft=std::any_of(rooms.begin(),rooms.end(),
|
||||
[](const auto& room){return room.shapeKind==1;});
|
||||
candidate.cavernGraphVocabularyVersion=hasVerticalShaft
|
||||
?ResolvedLayoutCavernGraphVocabularyVersionV3
|
||||
:ResolvedLayoutCavernGraphVocabularyVersionV2;
|
||||
candidate.cavernRooms.assign(rooms.begin(),rooms.end());
|
||||
candidate.cavernEdges.assign(edges.begin(),edges.end());
|
||||
const auto isNativeCavesStrate=[&candidate](std::int32_t strateIndex)
|
||||
@@ -2268,7 +2334,11 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
|
||||
||settings.irregularityAmplitudeMicrometres<0
|
||||
||settings.irregularityAmplitudeMicrometres>10000000
|
||||
||(settings.irregularityAmplitudeMicrometres>0
|
||||
&&settings.irregularityWavelengthMicrometres<=0))
|
||||
&&settings.irregularityWavelengthMicrometres<=0)
|
||||
||(settings.ledgeSpacingMicrometres<0
|
||||
||settings.ledgeSpacingMicrometres>100000000)
|
||||
||settings.ledgeDepthMicrometres<0
|
||||
||settings.ledgeDepthMicrometres>5000000)
|
||||
{error="cavern planner settings are outside the bounded room, tunnel or relief ranges";return false;}
|
||||
|
||||
const ResolvedLayoutPassageV1* arrivalPassage=nullptr;
|
||||
@@ -2282,12 +2352,17 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
|
||||
&&(!departurePassage||passage.stableId<departurePassage->stableId))
|
||||
departurePassage=&passage;
|
||||
}
|
||||
if(!arrivalPassage||!departurePassage)
|
||||
{error="cavern planner needs an incoming lower mouth and an outgoing upper mouth in the target strate";return false;}
|
||||
const bool terminalShaftRoute=settings.verticalShaftMode
|
||||
&&stratum->generatorKind=="VerticalShafts"&&!departurePassage;
|
||||
if(!arrivalPassage||(!departurePassage&&!terminalShaftRoute)
|
||||
||(settings.verticalShaftMode&&!terminalShaftRoute))
|
||||
{error="cavern planner needs an incoming mouth and either an outgoing mouth or a Vertical Shafts basal terminal";return false;}
|
||||
if(layout.coreVoxelSizeMicrometres==0)
|
||||
{error="cavern planner requires a nonzero Core voxel size";return false;}
|
||||
|
||||
using FPoint=std::array<std::int64_t,3>;
|
||||
const std::string routePrefix=(terminalShaftRoute?"vertical-shafts/strate-"
|
||||
:"tunnel-network/strate-")+std::to_string(strateIndex)+"/";
|
||||
constexpr std::int64_t MicrometresPerMetre=1000000;
|
||||
constexpr std::int64_t MinimumBoreRadius=1750000;
|
||||
constexpr std::int64_t MinimumBoreHeight=2200000;
|
||||
@@ -2490,19 +2565,47 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
|
||||
std::array<std::int64_t,2> arrivalTangentQ16{},departureTangentQ16{};
|
||||
std::int64_t arrivalFloorSlopeQ16=0,departureFloorSlopeQ16=0;
|
||||
if(!mouthInnerPoint(*arrivalPassage,true,routeStart,arrivalTangentQ16,
|
||||
arrivalFloorSlopeQ16)
|
||||
||!mouthInnerPoint(*departurePassage,false,routeEnd,departureTangentQ16,
|
||||
arrivalFloorSlopeQ16))
|
||||
{error="cavern passage mouth is missing a usable horizontal route tangent";return false;}
|
||||
if(!terminalShaftRoute
|
||||
&&!mouthInnerPoint(*departurePassage,false,routeEnd,departureTangentQ16,
|
||||
departureFloorSlopeQ16))
|
||||
{error="cavern passage mouth is missing a usable horizontal route tangent";return false;}
|
||||
if(terminalShaftRoute)
|
||||
{
|
||||
departureTangentQ16=arrivalTangentQ16;
|
||||
routeEnd[2]=stratum->content.bottomMicrometres+3000000;
|
||||
departureFloorSlopeQ16=0;
|
||||
}
|
||||
|
||||
const std::int64_t dx=routeEnd[0]-routeStart[0];
|
||||
const std::int64_t dy=routeEnd[1]-routeStart[1];
|
||||
std::int64_t dx=routeEnd[0]-routeStart[0];
|
||||
std::int64_t dy=routeEnd[1]-routeStart[1];
|
||||
const std::int64_t dz=routeEnd[2]-routeStart[2];
|
||||
const std::int64_t horizontalBias=std::clamp<std::int64_t>(
|
||||
settings.tunnelHorizontalBiasQ16,0,65536);
|
||||
const std::int64_t planGradeDenominator=MaximumWalkGradeDenominator
|
||||
+horizontalBias/32768;
|
||||
const std::int64_t requiredLength=(std::abs(dz)*planGradeDenominator*130)/100+1000;
|
||||
if(terminalShaftRoute)
|
||||
{
|
||||
// The basal stratum has no next mouth by design. The walk-only spine starts
|
||||
// at its incoming passage and ends on a supported landing above bedrock.
|
||||
// Optional open shafts are side features with separate climb/fall reporting.
|
||||
// Normalize by the truncated Q16 tangent magnitude: dividing by 65536
|
||||
// shortens non-cardinal vectors enough to miss the strict grade-run bound.
|
||||
const std::uint64_t tangentSquared=static_cast<std::uint64_t>(
|
||||
departureTangentQ16[0]*departureTangentQ16[0]
|
||||
+departureTangentQ16[1]*departureTangentQ16[1]);
|
||||
const std::int64_t tangentMagnitudeQ16=static_cast<std::int64_t>(
|
||||
integerSqrt(tangentSquared));
|
||||
if(tangentMagnitudeQ16<=0)
|
||||
{error="cavern planner terminal tangent has zero horizontal length";return false;}
|
||||
const std::int64_t terminalProjectionLength=requiredLength+1000;
|
||||
for(std::size_t axis=0;axis<2;++axis)
|
||||
routeEnd[axis]=routeStart[axis]
|
||||
+departureTangentQ16[axis]*terminalProjectionLength/tangentMagnitudeQ16;
|
||||
dx=routeEnd[0]-routeStart[0];dy=routeEnd[1]-routeStart[1];
|
||||
}
|
||||
std::int64_t pathAxisX=dx,pathAxisY=dy;
|
||||
FPoint routeLeadStart=routeStart,routeLeadEnd=routeEnd;
|
||||
for(std::size_t axis=0;axis<2;++axis)
|
||||
@@ -2648,7 +2751,7 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
|
||||
}
|
||||
};
|
||||
appendPassageClearanceSpans(*arrivalPassage,true);
|
||||
appendPassageClearanceSpans(*departurePassage,false);
|
||||
if(departurePassage)appendPassageClearanceSpans(*departurePassage,false);
|
||||
const auto segmentDistanceMicrometres=[](const std::array<double,3>& p0,
|
||||
const std::array<double,3>& p1,const std::array<double,3>& q0,
|
||||
const std::array<double,3>& q1)
|
||||
@@ -2804,8 +2907,7 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
|
||||
cumulative[i]=cumulative[i-1]+distance2d(routePoints[i-1],routePoints[i]);
|
||||
if(cumulative.back()!=totalRouteLength)
|
||||
{error="cavern planner route length accounting failed";return false;}
|
||||
const std::string backboneFloorProfileId="tunnel-network/strate-"
|
||||
+std::to_string(strateIndex)+"/backbone-floor";
|
||||
const std::string backboneFloorProfileId=routePrefix+"backbone-floor";
|
||||
constexpr std::int64_t ProfileOne=std::int64_t{1}<<30;
|
||||
const auto roundedDivide=[](std::int64_t numerator,std::int64_t denominator)
|
||||
{
|
||||
@@ -3008,7 +3110,7 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
|
||||
std::int64_t radiusMicrometres=0;
|
||||
bool destination=false;
|
||||
};
|
||||
std::vector<FRoomNode> nodes;nodes.reserve(nodeDistances.size());
|
||||
std::vector<FRoomNode> nodes;nodes.reserve(nodeDistances.size()+1);
|
||||
const std::int64_t floorCutMinimum=clampQ16(settings.roomFloorCutMinimumQ16);
|
||||
const std::int64_t floorCutMaximum=std::max(floorCutMinimum,
|
||||
clampQ16(settings.roomFloorCutMaximumQ16));
|
||||
@@ -3025,15 +3127,23 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
|
||||
node.floorProfileStableId=backboneFloorProfileId;
|
||||
node.floorProfileDistanceMicrometres=distance;
|
||||
node.floorGradientQ16=routeFloorGradientAt(distance);
|
||||
node.destination=distance==mainRouteMidpoint;
|
||||
node.id="tunnel-network/strate-"+std::to_string(strateIndex)+"/room-"
|
||||
+std::to_string(nodeIndex);
|
||||
node.destination=!terminalShaftRoute&&distance==mainRouteMidpoint;
|
||||
node.id=routePrefix+"room-"+std::to_string(nodeIndex);
|
||||
const std::uint64_t hash=LayoutStampSeed(seed,strateIndex,node.id);
|
||||
node.radiusMicrometres=node.destination?settings.destinationRadiusMicrometres
|
||||
:settings.minimumRoomRadiusMicrometres
|
||||
+static_cast<std::int64_t>(hash%static_cast<std::uint64_t>(baseRoomSpan+1));
|
||||
nodes.push_back(std::move(node));
|
||||
}
|
||||
if(terminalShaftRoute)
|
||||
{
|
||||
FRoomNode terminal;terminal.id=routePrefix+"basal-landing";
|
||||
terminal.point=routeEnd;terminal.floorProfileStableId=backboneFloorProfileId;
|
||||
terminal.floorProfileDistanceMicrometres=totalRouteLength;
|
||||
terminal.floorGradientQ16=routeFloorGradientAt(totalRouteLength);
|
||||
terminal.radiusMicrometres=settings.destinationRadiusMicrometres;
|
||||
terminal.destination=true;nodes.push_back(std::move(terminal));
|
||||
}
|
||||
const auto makeCompound=[&](const FRoomNode& node,bool destination,
|
||||
std::vector<ResolvedCavernRoomV1>& outRooms)
|
||||
{
|
||||
@@ -3154,6 +3264,130 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
|
||||
}
|
||||
return edge;
|
||||
};
|
||||
struct FPlannedShaftLanding
|
||||
{
|
||||
FRoomNode node;
|
||||
FPoint point{};
|
||||
};
|
||||
std::vector<FPlannedShaftLanding> shaftLandings;
|
||||
std::size_t plannedShaftCount=0;
|
||||
if(terminalShaftRoute&&settings.roomDensityQ16>0)
|
||||
{
|
||||
const std::int64_t ledgeSpacing=std::max<std::int64_t>(0,
|
||||
settings.ledgeSpacingMicrometres);
|
||||
const std::int64_t ledgeDepth=std::clamp<std::int64_t>(
|
||||
settings.ledgeDepthMicrometres,0,5000000);
|
||||
bool emittedShaft=false;
|
||||
const std::int64_t routeLengthXY=std::max<std::int64_t>(1,
|
||||
static_cast<std::int64_t>(integerSqrt(static_cast<std::uint64_t>(
|
||||
pathAxisX*pathAxisX+pathAxisY*pathAxisY))));
|
||||
const std::int64_t sideX=-pathAxisY*65536/routeLengthXY;
|
||||
const std::int64_t sideY=pathAxisX*65536/routeLengthXY;
|
||||
const auto selectedRadius=[&](const std::string& stableId)
|
||||
{
|
||||
const std::int64_t span=settings.maximumRoomRadiusMicrometres
|
||||
-settings.minimumRoomRadiusMicrometres;
|
||||
return settings.minimumRoomRadiusMicrometres+(span==0?0:
|
||||
static_cast<std::int64_t>(LayoutStampSeed(seed,strateIndex,stableId)
|
||||
%static_cast<std::uint64_t>(span+1)));
|
||||
};
|
||||
for(std::size_t nodeIndex=1;nodeIndex+1<nodes.size()&&rooms.size()<2048;
|
||||
++nodeIndex)
|
||||
{
|
||||
const auto& parent=nodes[nodeIndex];
|
||||
const std::uint64_t hash=LayoutStampSeed(seed,strateIndex,
|
||||
parent.id+"/shaft");
|
||||
if(emittedShaft&&static_cast<std::int64_t>(hash&0xffffu)
|
||||
>=settings.roomDensityQ16)continue;
|
||||
const std::int64_t radius=selectedRadius(parent.id+"/shaft-radius");
|
||||
const std::int64_t top=parent.point[2]
|
||||
+(ledgeSpacing>0?ledgeSpacing:1000000);
|
||||
const std::int64_t minimumTop=stratum->content.bottomMicrometres+4000000;
|
||||
if(top>stratum->content.topMicrometres-1000000
|
||||
||top<minimumTop)continue;
|
||||
// Shaft length has no independent owner parameter: use as much content
|
||||
// as available, with the graph's 200 m half-extent bound as a hard cap.
|
||||
const std::int64_t bottom=std::max(
|
||||
stratum->content.bottomMicrometres+1000000,top-200000000);
|
||||
if(ledgeSpacing>0&&top-bottom<ledgeSpacing+2000000)continue;
|
||||
const bool positiveSide=(hash&0x10000u)!=0;
|
||||
const std::int64_t sideXSign=(sideX>=0?1:-1)*(positiveSide?1:-1);
|
||||
const std::int64_t sideYSign=(sideY>=0?1:-1)*(positiveSide?1:-1);
|
||||
const bool useX=std::abs(sideX)>=std::abs(sideY);
|
||||
const std::uint8_t outwardSide=useX
|
||||
?static_cast<std::uint8_t>(sideXSign>0?0:2)
|
||||
:static_cast<std::uint8_t>(sideYSign>0?1:3);
|
||||
const std::array<std::array<std::int64_t,2>,4> cardinal={{{1,0},{0,1},
|
||||
{-1,0},{0,-1}}};
|
||||
const auto outward=cardinal[outwardSide];
|
||||
const std::int64_t offset=std::max<std::int64_t>(
|
||||
parent.radiusMicrometres+radius+3000000,
|
||||
settings.roomSpacingMicrometres/2);
|
||||
FPlannedShaftLanding landing;landing.node.id=routePrefix+"shaft-"
|
||||
+std::to_string(shaftLandings.size());
|
||||
landing.node.point={parent.point[0]+outward[0]*offset,
|
||||
parent.point[1]+outward[1]*offset,parent.point[2]};
|
||||
landing.node.radiusMicrometres=radius;
|
||||
const std::int64_t landingZ=top-(ledgeSpacing>0?ledgeSpacing:1000000);
|
||||
const std::array<std::int64_t,2> toParent={-outward[0],-outward[1]};
|
||||
const std::uint8_t ledgeSide=static_cast<std::uint8_t>(
|
||||
(outwardSide+2)%4);
|
||||
ResolvedCavernRoomV1 shaft;shaft.stableId=landing.node.id+"/primitive-0";
|
||||
shaft.strateIndex=strateIndex;shaft.shapeKind=1;
|
||||
shaft.centerMicrometres={landing.node.point[0],landing.node.point[1],
|
||||
(top+bottom)/2};
|
||||
shaft.halfExtentsMicrometres={radius,radius,(top-bottom)/2};
|
||||
shaft.floorMicrometres=top;shaft.smoothWidthMicrometres=smooth;
|
||||
shaft.ledgeSpacingMicrometres=ledgeSpacing;
|
||||
shaft.ledgeDepthMicrometres=ledgeDepth;shaft.ledgeSide=ledgeSide;
|
||||
shaft.irregularityAmplitudeMicrometres=irregularity;
|
||||
shaft.irregularityWavelengthMicrometres=
|
||||
settings.irregularityWavelengthMicrometres;
|
||||
shaft.irregularitySeed=LayoutStampSeed(seed,strateIndex,shaft.stableId);
|
||||
rooms.push_back(std::move(shaft));
|
||||
landing.point={landing.node.point[0]
|
||||
+toParent[0]*(radius-ledgeDepth/2),
|
||||
landing.node.point[1]+toParent[1]*(radius-ledgeDepth/2),landingZ};
|
||||
landing.node.point=landing.point;
|
||||
const std::string accessId=routePrefix+"shaft-access-"
|
||||
+std::to_string(shaftLandings.size());
|
||||
auto access=makeEdge(accessId,componentRoomId(parent),parent.point,
|
||||
landing.node,routeBoreRadius,-1,-1);
|
||||
// The connector's own floor SDF retains its tread. It must not enter
|
||||
// the descending backbone's finite floor-cap arbitration, where its
|
||||
// nearby but independent floor could suppress backbone support.
|
||||
access.floorSupportDepthMicrometres=0;
|
||||
const std::int64_t run=distance2d(parent.point,landing.point);
|
||||
const std::int64_t rise=std::abs(parent.point[2]-landing.point[2]);
|
||||
if(run<=0||rise*MaximumWalkGradeDenominator>run)
|
||||
{
|
||||
rooms.pop_back();
|
||||
continue;
|
||||
}
|
||||
edges.push_back(std::move(access));
|
||||
shaftLandings.push_back(std::move(landing));
|
||||
emittedShaft=true;
|
||||
}
|
||||
plannedShaftCount=shaftLandings.size();
|
||||
for(std::size_t index=1;index<shaftLandings.size();++index)
|
||||
{
|
||||
const std::uint64_t hash=LayoutStampSeed(seed,strateIndex,
|
||||
shaftLandings[index-1].node.id+"/cross-"+std::to_string(index));
|
||||
if(static_cast<std::int64_t>(hash&0xffffu)>=settings.tunnelDensityQ16)
|
||||
continue;
|
||||
const auto& from=shaftLandings[index-1];
|
||||
const auto& to=shaftLandings[index];
|
||||
const std::int64_t run=distance2d(from.point,to.point);
|
||||
const std::int64_t rise=std::abs(from.point[2]-to.point[2]);
|
||||
if(run<=0||run>settings.maximumTunnelLengthMicrometres
|
||||
||rise*MaximumWalkGradeDenominator>run)continue;
|
||||
auto link=makeEdge(routePrefix+"shaft-cross-"+std::to_string(index-1),
|
||||
componentRoomId(from.node),from.point,to.node,routeBoreRadius,-1,-1);
|
||||
link.floorSupportDepthMicrometres=0;
|
||||
edges.push_back(std::move(link));
|
||||
}
|
||||
}
|
||||
|
||||
const auto arrivalPoint=routeStart;
|
||||
FPoint previousPoint=arrivalPoint;
|
||||
std::int64_t previousProfileDistance=0;
|
||||
@@ -3161,8 +3395,7 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
|
||||
for(std::size_t nodeIndex=0;nodeIndex<nodes.size();++nodeIndex)
|
||||
{
|
||||
const auto& node=nodes[nodeIndex];
|
||||
const std::string edgeId="tunnel-network/strate-"+std::to_string(strateIndex)
|
||||
+"/route-edge-"+std::to_string(nodeIndex);
|
||||
const std::string edgeId=routePrefix+"route-edge-"+std::to_string(nodeIndex);
|
||||
auto edge=makeEdge(edgeId,previousAnchor,previousPoint,node,
|
||||
selectedBoreRadius(edgeId),
|
||||
previousProfileDistance,node.floorProfileDistanceMicrometres);
|
||||
@@ -3171,10 +3404,9 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
|
||||
previousProfileDistance=node.floorProfileDistanceMicrometres;
|
||||
previousAnchor=componentRoomId(node);
|
||||
}
|
||||
if(!nodes.empty())
|
||||
if(!nodes.empty()&&departurePassage)
|
||||
{
|
||||
const std::string departureEdgeId="tunnel-network/strate-"
|
||||
+std::to_string(strateIndex)+"/departure-edge";
|
||||
const std::string departureEdgeId=routePrefix+"departure-edge";
|
||||
auto edge=makeEdge(departureEdgeId,departurePassage->stableId+"/upper-mouth",
|
||||
routeEnd,nodes.back(),selectedBoreRadius(departureEdgeId),
|
||||
totalRouteLength,nodes.back().floorProfileDistanceMicrometres);
|
||||
@@ -3187,7 +3419,7 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
|
||||
const std::size_t branchLimit=settings.originRoomMaximumConnections<=0
|
||||
?32u:static_cast<std::size_t>(std::clamp(settings.originRoomMaximumConnections,0,32));
|
||||
std::size_t branchCount=0;
|
||||
for(std::size_t nodeIndex=1;nodeIndex+1<nodes.size()
|
||||
for(std::size_t nodeIndex=1;!terminalShaftRoute&&nodeIndex+1<nodes.size()
|
||||
&&branchCount<branchLimit&&rooms.size()<2048;++nodeIndex)
|
||||
{
|
||||
const auto& parent=nodes[nodeIndex];
|
||||
@@ -3210,8 +3442,7 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
|
||||
// A side branch leaves the backbone laterally, so it has its own local flat
|
||||
// floor and must not reference the parent's one-dimensional h(s) profile.
|
||||
makeCompound(branch,false,rooms);
|
||||
const std::string branchEdgeId="tunnel-network/strate-"
|
||||
+std::to_string(strateIndex)+"/side-edge-"+std::to_string(branchCount);
|
||||
const std::string branchEdgeId=routePrefix+"side-edge-"+std::to_string(branchCount);
|
||||
ResolvedCavernEdgeV1 branchEdge;
|
||||
if(settings.tunnelsFlowTowardOrigin)
|
||||
branchEdge=makeEdge(branchEdgeId,componentRoomId(branch),
|
||||
@@ -3226,7 +3457,7 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
|
||||
++branchCount;
|
||||
}
|
||||
if(nodes.empty()||rooms.empty()||edges.empty())
|
||||
{error="cavern planner emitted an empty arrival-to-departure graph";return false;}
|
||||
{error="cavern planner emitted an empty mouth-to-terminal graph";return false;}
|
||||
|
||||
double maximumGradeDegrees=0.0;
|
||||
for(const auto& edge:edges)
|
||||
@@ -3255,9 +3486,10 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
|
||||
}
|
||||
const double plannerMilliseconds=std::chrono::duration<double,std::milli>(
|
||||
std::chrono::steady_clock::now()-plannerStarted).count();
|
||||
std::printf("[CavernPlannerV1] strate=%d seed=%d mouths=%s->%s route_nodes=%zu room_primitives=%zu edges=%zu branches=%zu route_horizontal_m=%.3f floor_delta_m=%.3f max_grade_deg=%.3f mouth_tangent_min_dot=%.3f grade_limit_deg=11.310 clearance_radius_m=%.3f clearance_height_m=%.3f destination_radius_m=%.3f plan_ms=%.3f status=PASS\n",
|
||||
strateIndex,seed,arrivalPassage->stableId.c_str(),departurePassage->stableId.c_str(),
|
||||
nodes.size(),rooms.size(),edges.size(),branchCount,totalRouteLength/1.0e6,
|
||||
std::printf("[CavernPlannerV1] strate=%d seed=%d route=%s->%s route_nodes=%zu room_primitives=%zu shafts=%zu edges=%zu branches=%zu route_horizontal_m=%.3f floor_delta_m=%.3f max_grade_deg=%.3f mouth_tangent_min_dot=%.3f grade_limit_deg=11.310 clearance_radius_m=%.3f clearance_height_m=%.3f destination_radius_m=%.3f plan_ms=%.3f status=PASS\n",
|
||||
strateIndex,seed,arrivalPassage->stableId.c_str(),
|
||||
departurePassage?departurePassage->stableId.c_str():"basal-terminal",
|
||||
nodes.size(),rooms.size(),plannedShaftCount,edges.size(),branchCount,totalRouteLength/1.0e6,
|
||||
std::abs(dz)/1.0e6,maximumGradeDegrees,plannedMouthTangentAlignment,
|
||||
routeBoreRadius/1.0e6,PlannedBoreHeight/1.0e6,
|
||||
settings.destinationRadiusMicrometres/1.0e6,plannerMilliseconds);
|
||||
|
||||
@@ -1284,7 +1284,8 @@ public:
|
||||
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=="TunnelNetwork"
|
||||
||queryStratum->Resolved->generatorKind=="VerticalShafts")
|
||||
&&queryStratum->Resolved->recipeId=="Core.Caves.PrimitiveField"
|
||||
&&queryStratum->Resolved->recipeVersion==1;
|
||||
const auto* routePassageIndices=passageTileScopeMatches
|
||||
@@ -1617,9 +1618,12 @@ public:
|
||||
const auto plannedPortalWalkCorridorContains=[&]()
|
||||
{
|
||||
if(!queryStratum||!queryStratum->Resolved
|
||||
||queryStratum->Resolved->generatorKind!="TunnelNetwork"
|
||||
||(queryStratum->Resolved->generatorKind!="TunnelNetwork"
|
||||
&&queryStratum->Resolved->generatorKind!="VerticalShafts")
|
||||
||queryStratum->Resolved->recipeId!="Core.Caves.PrimitiveField"
|
||||
||queryStratum->Resolved->recipeVersion!=1)return false;
|
||||
const bool terminalShaftRoute=
|
||||
queryStratum->Resolved->generatorKind=="VerticalShafts";
|
||||
const VoxelForgeCore::ResolvedLayoutAnchorV2* first=nullptr;
|
||||
const VoxelForgeCore::ResolvedLayoutAnchorV2* second=nullptr;
|
||||
const VoxelForgeCore::ResolvedLayoutAnchorV2* third=nullptr;
|
||||
@@ -1638,8 +1642,12 @@ public:
|
||||
}
|
||||
if(routeCount<6||!first||!second||!third||!lastThree||!lastTwo||!last
|
||||
||!first->stableId.ends_with("000-arrival")
|
||||
||!second->stableId.ends_with("000-edge-start")
|
||||
||!lastTwo->stableId.ends_with("998-departure-inner")
|
||||
||!second->stableId.ends_with("000-edge-start"))return false;
|
||||
if(terminalShaftRoute)
|
||||
{
|
||||
if(!last->stableId.ends_with("999-basal-terminal"))return false;
|
||||
}
|
||||
else if(!lastTwo->stableId.ends_with("998-departure-inner")
|
||||
||!last->stableId.ends_with("999-departure"))return false;
|
||||
const double metresPerSubvoxel=1.0/qPerMetre;
|
||||
const auto position=[&](const VoxelForgeCore::ResolvedLayoutAnchorV2& anchor)
|
||||
@@ -1677,6 +1685,7 @@ public:
|
||||
if(nearRouteSegment(*first,*second,PortalWalkLeadMetres,false,0.0)
|
||||
||nearRouteSegment(*second,*third,PortalWalkLeadMetres,false,
|
||||
arrivalInnerDistance))return true;
|
||||
if(terminalShaftRoute)return false;
|
||||
const auto departureInner=position(*lastTwo),departure=position(*last);
|
||||
const double departureTailDistance=std::hypot(
|
||||
departure[0]-departureInner[0],departure[1]-departureInner[1]);
|
||||
@@ -3835,8 +3844,11 @@ private:
|
||||
const VoxelForgeCore::ResolvedCavernRoomV1* room=nullptr;
|
||||
const VoxelForgeCore::ResolvedCavernEdgeV1* edge=nullptr;
|
||||
bool nativeGraph=false;
|
||||
std::uint8_t roomShapeKind=0;
|
||||
std::uint8_t ledgeSide=0;
|
||||
std::array<std::int64_t,3> boundsMinimumMicrometres{},boundsMaximumMicrometres{};
|
||||
std::array<double,3> centerMetres{},halfExtentsMetres{};
|
||||
double ledgeSpacingMetres=0.0,ledgeDepthMetres=0.0;
|
||||
struct FSegment
|
||||
{
|
||||
std::array<double,3> start{},end{},minimum{},maximum{};
|
||||
@@ -4285,7 +4297,9 @@ private:
|
||||
||(ResolvedLayout->cavernGraphVocabularyVersion
|
||||
!=VoxelForgeCore::ResolvedLayoutCavernGraphVocabularyVersionV1
|
||||
&&ResolvedLayout->cavernGraphVocabularyVersion
|
||||
!=VoxelForgeCore::ResolvedLayoutCavernGraphVocabularyVersionV2))return;
|
||||
!=VoxelForgeCore::ResolvedLayoutCavernGraphVocabularyVersionV2
|
||||
&&ResolvedLayout->cavernGraphVocabularyVersion
|
||||
!=VoxelForgeCore::ResolvedLayoutCavernGraphVocabularyVersionV3))return;
|
||||
const auto isNativeGraphStrate=[this](std::int32_t strateIndex)
|
||||
{
|
||||
const auto found=std::find_if(ResolvedLayout->strates.begin(),
|
||||
@@ -4302,6 +4316,10 @@ private:
|
||||
{
|
||||
FCavernRuntimeFeature runtime;runtime.room=&room;
|
||||
runtime.nativeGraph=isNativeGraphStrate(room.strateIndex);
|
||||
runtime.roomShapeKind=room.shapeKind;
|
||||
runtime.ledgeSide=room.ledgeSide;
|
||||
runtime.ledgeSpacingMetres=room.ledgeSpacingMicrometres/1000000.0;
|
||||
runtime.ledgeDepthMetres=room.ledgeDepthMicrometres/1000000.0;
|
||||
runtime.boundsMinimumMicrometres=room.boundsMinimumMicrometres;
|
||||
runtime.boundsMaximumMicrometres=room.boundsMaximumMicrometres;
|
||||
for(std::size_t axis=0;axis<3;++axis)
|
||||
@@ -4956,7 +4974,8 @@ private:
|
||||
ResolvedLayout->strates.end(),[&](const auto& stratum)
|
||||
{return stratum.strateIndex==passage.lowerStrateIndex;});
|
||||
if(lowerStratum!=ResolvedLayout->strates.end()
|
||||
&&lowerStratum->generatorKind=="TunnelNetwork"
|
||||
&&(lowerStratum->generatorKind=="TunnelNetwork"
|
||||
||lowerStratum->generatorKind=="VerticalShafts")
|
||||
&&routeEnd<controls.size())
|
||||
{
|
||||
double routeHeight=0.0,distanceSquared=0.0;
|
||||
@@ -5667,6 +5686,11 @@ public:
|
||||
FMetricInterval featureVoid;
|
||||
if(feature.room)
|
||||
{
|
||||
// Shaft wall and ledge SDFs are evaluated exactly per sample.
|
||||
// Until a conservative interval bound for their carved shelves is
|
||||
// available, keep intersecting boxes Mixed instead of proving a
|
||||
// potentially false uniform sign.
|
||||
if(feature.roomShapeKind==1)return {0.0,0.0,false};
|
||||
double normalizedMinimumSquared=0.0,normalizedMaximumSquared=0.0;
|
||||
double meanRadius=0.0;
|
||||
for(std::size_t axis=0;axis<3;++axis)
|
||||
@@ -7884,6 +7908,64 @@ private:
|
||||
double edgeFloor=0.0;
|
||||
if(feature.room)
|
||||
{
|
||||
if(feature.roomShapeKind==1)
|
||||
{
|
||||
const double radius=feature.halfExtentsMetres[0];
|
||||
const double bottom=feature.centerMetres[2]-feature.halfExtentsMetres[2];
|
||||
const double top=feature.centerMetres[2]+feature.halfExtentsMetres[2];
|
||||
const double radial=std::hypot(point[0]-feature.centerMetres[0],
|
||||
point[1]-feature.centerMetres[1]);
|
||||
const double radialQ=radial-radius;
|
||||
const double verticalQ=std::max(bottom-point[2],point[2]-top);
|
||||
double shaftDistance=std::min(std::max(radialQ,verticalQ),0.0)
|
||||
+std::hypot(std::max(radialQ,0.0),std::max(verticalQ,0.0));
|
||||
if(feature.irregularityAmplitudeVoxels>0.0f)
|
||||
shaftDistance+=StampValueNoise(
|
||||
X-feature.centerVoxels[0],Y-feature.centerVoxels[1],
|
||||
Z-feature.centerVoxels[2],feature.irregularityWavelengthVoxels,
|
||||
feature.irregularitySeed)
|
||||
*feature.irregularityAmplitudeVoxels*metresPerVoxel;
|
||||
|
||||
// A cardinal ledge faces the walkable backbone. Shelves are
|
||||
// cut out of the otherwise open cylinder, leaving most of the
|
||||
// shaft cross-section clear at every interval.
|
||||
if(feature.ledgeSpacingMetres>0.0&&feature.ledgeDepthMetres>0.0)
|
||||
{
|
||||
const long long nearest=static_cast<long long>(std::llround(
|
||||
(top-point[2])/feature.ledgeSpacingMetres));
|
||||
const long long maximum=static_cast<long long>(std::floor(
|
||||
(top-bottom-1000000.0)/feature.ledgeSpacingMetres));
|
||||
if(nearest>=1&&nearest<=maximum)
|
||||
{
|
||||
const double ledgeZ=top-nearest*feature.ledgeSpacingMetres;
|
||||
constexpr double LedgeThicknessMetres=0.6;
|
||||
constexpr double LedgeFilletMetres=0.1;
|
||||
const double dx=point[0]-feature.centerMetres[0];
|
||||
const double dy=point[1]-feature.centerMetres[1];
|
||||
const std::array<std::array<double,2>,4> sides={{{1.0,0.0},
|
||||
{0.0,1.0},{-1.0,0.0},{0.0,-1.0}}};
|
||||
const auto side=sides[feature.ledgeSide%4];
|
||||
const double radialLocal=dx*side[0]+dy*side[1];
|
||||
const double tangentLocal=-dx*side[1]+dy*side[0];
|
||||
const double halfDepth=feature.ledgeDepthMetres*0.5;
|
||||
const double halfWidth=radius*0.45;
|
||||
const double halfThickness=LedgeThicknessMetres*0.5;
|
||||
const double qx=std::abs(radialLocal-(radius-halfDepth))-halfDepth;
|
||||
const double qy=std::abs(tangentLocal)-halfWidth;
|
||||
const double qz=std::abs(point[2]-(ledgeZ-halfThickness))
|
||||
-halfThickness;
|
||||
const double outside=std::hypot(std::max(qx,0.0),
|
||||
std::max(qy,0.0),std::max(qz,0.0));
|
||||
const double platformDistance=outside
|
||||
+std::min(std::max(qx,std::max(qy,qz)),0.0)
|
||||
-LedgeFilletMetres;
|
||||
shaftDistance=std::max(shaftDistance,-platformDistance);
|
||||
}
|
||||
}
|
||||
featureVoid=shaftDistance;
|
||||
}
|
||||
else
|
||||
{
|
||||
double normalizedSquared=0.0,meanRadius=0.0;
|
||||
for(std::size_t axis=0;axis<3;++axis)
|
||||
{
|
||||
@@ -7907,6 +7989,7 @@ private:
|
||||
+feature.floorGradient[1]*(point[1]-feature.centerMetres[1]);
|
||||
const double width=feature.room->smoothWidthMicrometres/1000000.0;
|
||||
featureVoid=SmoothMaxMetres(ellipsoid,floor,width);
|
||||
}
|
||||
}
|
||||
else if(feature.edge&&!feature.segments.empty())
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user