wip: continuous backbone floor profile for the cave planner (L134 design 4, untested)

Written by Luna before the Codex usage limit cut the round: one shared distance/floor profile across backbone rooms
and edges, edge cubic floors derived from it, sloped route-room floors, lower-floor backing cap at overlaps, and
validator checks for joint height/slope continuity. Not yet built or audited.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
This commit is contained in:
Fr0zkaandClaude Opus 5.5 committed 2026-10-31 12:03:48 +01:00
1 parent 379093401e
commit 4a97023b24
3 files changed
+294 -17

No files matched your search

@@ -497,8 +497,9 @@ namespace VoxelForgeCore
// Version 226 unifies the passage influence reach in metres and converts it to core
// voxels for every broadphase and proof bound. Version 227 uses the exact smooth-max
// support width/6 when deriving that reach. Version 228 composes deterministic cave
// graphs from fixed passage mouths and the owner Tunnel Network settings.
inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 229;
// graphs from fixed passage mouths and the owner Tunnel Network settings. Version 229
// budgets cave floor support; 230 records a shared backbone floor profile and joint cap.
inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 230;
enum class FieldChannel : std::uint32_t
{
@@ -893,6 +894,7 @@ namespace VoxelForgeCore
inline constexpr std::uint32_t ResolvedLayoutStampVocabularyVersionV2 = 2;
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 ResolvedLayoutOreVeinVocabularyVersionV1 = 1;
enum class ResolvedLayoutValidationModeV3 : std::uint8_t { compatibility, strict };
@@ -957,6 +959,11 @@ namespace VoxelForgeCore
std::array<std::int64_t, 3> centerMicrometres{};
std::array<std::int64_t, 3> halfExtentsMicrometres{};
std::int64_t floorMicrometres = 0;
// Optional shared backbone profile reference. The room floor plane is
// floorMicrometres plus this Q16 XY gradient from centerMicrometres.
std::string floorProfileStableId;
std::int64_t floorProfileDistanceMicrometres = -1;
std::array<std::int32_t, 2> floorGradientQ16{};
std::int64_t smoothWidthMicrometres = 0;
// Optional feature-to-feature union width. Zero preserves the historical
// behavior where smoothWidthMicrometres also controls the graph union.
@@ -976,6 +983,11 @@ namespace VoxelForgeCore
std::int32_t strateIndex = -1;
// A single cubic Bezier in XY. Its four Z values are the cubic floor profile.
std::array<std::array<std::int64_t, 3>, 4> controlPointsMicrometres{};
// Edges with the same profile ID are adjacent pieces of one shared h(s).
// Distances are signed along that profile, so a terminal edge may be reversed.
std::string floorProfileStableId;
std::int64_t floorProfileStartDistanceMicrometres = -1;
std::int64_t floorProfileEndDistanceMicrometres = -1;
// Resolve-time, error-bounded approximation of the cubic centerline. Each
// consecutive pair is a physical segment used by the native cave field.
std::vector<std::array<std::int64_t, 3>> polylineMicrometres;
+233 -9
View File
@@ -621,6 +621,10 @@ namespace
<<"],\"half_extents_um\":["<<room.halfExtentsMicrometres[0]<<','
<<room.halfExtentsMicrometres[1]<<','<<room.halfExtentsMicrometres[2]
<<"],\"floor_um\":"<<room.floorMicrometres
<<",\"floor_profile_id\":"<<LayoutEscape(room.floorProfileStableId)
<<",\"floor_profile_distance_um\":"<<room.floorProfileDistanceMicrometres
<<",\"floor_gradient_q16\":["<<room.floorGradientQ16[0]<<','
<<room.floorGradientQ16[1]<<']'
<<",\"smooth_width_um\":"<<room.smoothWidthMicrometres
;
if(room.unionWidthMicrometres>0)
@@ -647,6 +651,11 @@ namespace
out<<'['<<p[0]<<','<<p[1]<<','<<p[2]<<']';
}
out<<']';
out<<",\"floor_profile_id\":"<<LayoutEscape(edge.floorProfileStableId)
<<",\"floor_profile_start_distance_um\":"
<<edge.floorProfileStartDistanceMicrometres
<<",\"floor_profile_end_distance_um\":"
<<edge.floorProfileEndDistanceMicrometres;
if(!edge.polylineMicrometres.empty())
{
out<<",\"polyline_um\":[";
@@ -1235,7 +1244,8 @@ bool ResolvedLayoutSnapshotV3::validate(ResolvedLayoutValidationModeV3 mode,
add("cavern_graph_vocabulary_version",
"cavern graph vocabulary version is set without graph records");
}
else if(cavernGraphVocabularyVersion!=ResolvedLayoutCavernGraphVocabularyVersionV1)
else if(cavernGraphVocabularyVersion!=ResolvedLayoutCavernGraphVocabularyVersionV1
&&cavernGraphVocabularyVersion!=ResolvedLayoutCavernGraphVocabularyVersionV2)
add("cavern_graph_vocabulary_version",
"cavern graph records require vocabulary version 1");
if(cavernGraphVocabularyVersion!=0)
@@ -1268,6 +1278,16 @@ bool ResolvedLayoutSnapshotV3::validate(ResolvedLayoutValidationModeV3 mode,
||(room.irregularityAmplitudeMicrometres>0
&&room.irregularityWavelengthMicrometres<=0))
add("cavern_room_parameters",room.stableId+" has invalid or unbounded parameters");
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];
if((room.floorProfileStableId.empty()
&&room.floorProfileDistanceMicrometres!=-1)
||(!room.floorProfileStableId.empty()
&&room.floorProfileDistanceMicrometres<0)
||gradientSquared>static_cast<std::int64_t>(13108)*13108)
add("cavern_room_floor_profile",room.stableId+
" has an invalid shared profile reference or floor grade");
const std::int64_t pad=room.smoothWidthMicrometres
+room.unionWidthMicrometres+room.irregularityAmplitudeMicrometres;
for(std::size_t axis=0;axis<3;++axis)
@@ -1308,6 +1328,15 @@ bool ResolvedLayoutSnapshotV3::validate(ResolvedLayoutValidationModeV3 mode,
||(edge.irregularityAmplitudeMicrometres>0
&&edge.irregularityWavelengthMicrometres<=0))
add("cavern_edge_parameters",edge.stableId+" has invalid or unbounded parameters");
const bool hasFloorProfile=!edge.floorProfileStableId.empty();
if((hasFloorProfile&&(edge.floorProfileStartDistanceMicrometres<0
||edge.floorProfileEndDistanceMicrometres<0
||edge.floorProfileStartDistanceMicrometres
==edge.floorProfileEndDistanceMicrometres))
||(!hasFloorProfile&&(edge.floorProfileStartDistanceMicrometres!=-1
||edge.floorProfileEndDistanceMicrometres!=-1)))
add("cavern_edge_floor_profile",edge.stableId+
" has an invalid shared profile distance interval");
std::array<std::int64_t,3> low=edge.controlPointsMicrometres[0];
std::array<std::int64_t,3> high=low;
if(nativeLattice&&!edge.polylineMicrometres.empty())
@@ -1363,6 +1392,114 @@ bool ResolvedLayoutSnapshotV3::validate(ResolvedLayoutValidationModeV3 mode,
add("cavern_edge_polyline",edge.stableId+
" has no bounded resolve-time polyline with matching endpoints");
}
std::vector<std::string> floorProfileIds;
for(const auto& edge:cavernEdges)
if(!edge.floorProfileStableId.empty()
&&std::find(floorProfileIds.begin(),floorProfileIds.end(),
edge.floorProfileStableId)==floorProfileIds.end())
floorProfileIds.push_back(edge.floorProfileStableId);
for(const std::string& profileId:floorProfileIds)
{
std::vector<const ResolvedCavernEdgeV1*> profileEdges;
for(const auto& edge:cavernEdges)
if(edge.floorProfileStableId==profileId)profileEdges.push_back(&edge);
std::sort(profileEdges.begin(),profileEdges.end(),[](const auto* a,const auto* b)
{
return std::min(a->floorProfileStartDistanceMicrometres,
a->floorProfileEndDistanceMicrometres)
<std::min(b->floorProfileStartDistanceMicrometres,
b->floorProfileEndDistanceMicrometres);
});
const auto endpointZ=[](const ResolvedCavernEdgeV1& edge,bool atMinimum)
{
const bool forward=edge.floorProfileStartDistanceMicrometres
<edge.floorProfileEndDistanceMicrometres;
const std::size_t point=(atMinimum==forward)?0:3;
return edge.controlPointsMicrometres[point][2];
};
const auto endpointSlope=[](const ResolvedCavernEdgeV1& edge,bool atMinimum)
{
const double span=static_cast<double>(edge.floorProfileEndDistanceMicrometres
-edge.floorProfileStartDistanceMicrometres);
if(span==0.0)return std::numeric_limits<double>::infinity();
const bool forward=edge.floorProfileStartDistanceMicrometres
<edge.floorProfileEndDistanceMicrometres;
const std::size_t control=atMinimum==forward?0:2;
return 3.0*(edge.controlPointsMicrometres[control+1][2]
-edge.controlPointsMicrometres[control][2])/span;
};
const ResolvedCavernEdgeV1* previous=nullptr;
for(const auto* edge:profileEdges)
{
const std::int64_t low=std::min(edge->floorProfileStartDistanceMicrometres,
edge->floorProfileEndDistanceMicrometres);
const std::int64_t high=std::max(edge->floorProfileStartDistanceMicrometres,
edge->floorProfileEndDistanceMicrometres);
const double slope0=endpointSlope(*edge,true);
const double slope1=endpointSlope(*edge,false);
if(std::max(std::abs(slope0),std::abs(slope1))>0.200001)
add("cavern_floor_profile_grade",edge->stableId+
" exceeds the shared backbone 1:5 grade limit");
if(previous)
{
const std::int64_t previousHigh=std::max(
previous->floorProfileStartDistanceMicrometres,
previous->floorProfileEndDistanceMicrometres);
if(low!=previousHigh
||std::abs(endpointZ(*previous,false)-endpointZ(*edge,true))>1000
||std::abs(endpointSlope(*previous,false)-slope0)>0.000001)
add("cavern_floor_profile_joint",profileId+
" has a gap, height step, or slope step between adjacent edges");
}
previous=edge;
(void)high;
}
for(const auto& room:cavernRooms)
{
if(room.floorProfileStableId!=profileId)continue;
const ResolvedCavernEdgeV1* containing=nullptr;
for(const auto* edge:profileEdges)
{
const auto low=std::min(edge->floorProfileStartDistanceMicrometres,
edge->floorProfileEndDistanceMicrometres);
const auto high=std::max(edge->floorProfileStartDistanceMicrometres,
edge->floorProfileEndDistanceMicrometres);
if(room.floorProfileDistanceMicrometres>=low
&&room.floorProfileDistanceMicrometres<=high)
{containing=edge;break;}
}
if(!containing)
{
add("cavern_room_floor_profile",room.stableId+
" does not lie on its referenced continuous floor profile");
continue;
}
const auto& edge=*containing;
const double t=static_cast<double>(room.floorProfileDistanceMicrometres
-edge.floorProfileStartDistanceMicrometres)
/(edge.floorProfileEndDistanceMicrometres
-edge.floorProfileStartDistanceMicrometres);
const double one=1.0-t;
const double expected=one*one*one*edge.controlPointsMicrometres[0][2]
+3.0*one*one*t*edge.controlPointsMicrometres[1][2]
+3.0*one*t*t*edge.controlPointsMicrometres[2][2]
+t*t*t*edge.controlPointsMicrometres[3][2];
std::array<double,2> routeXY{};
for(std::size_t axis=0;axis<2;++axis)
routeXY[axis]=one*one*one*edge.controlPointsMicrometres[0][axis]
+3.0*one*one*t*edge.controlPointsMicrometres[1][axis]
+3.0*one*t*t*edge.controlPointsMicrometres[2][axis]
+t*t*t*edge.controlPointsMicrometres[3][axis];
const double roomFloorAtProfile=room.floorMicrometres
+room.floorGradientQ16[0]/65536.0
*(routeXY[0]-room.centerMicrometres[0])
+room.floorGradientQ16[1]/65536.0
*(routeXY[1]-room.centerMicrometres[1]);
if(std::abs(expected-roomFloorAtProfile)>1000.0)
add("cavern_room_floor_profile",room.stableId+
" floor does not match its referenced h(s)");
}
}
}
if(oreVeins.empty())
{
@@ -1526,6 +1663,12 @@ std::optional<ResolvedLayoutSnapshotV3> ResolvedLayoutSnapshotV3::FromJson(
readTriplet("bounds_min_um",room.boundsMinimumMicrometres);
readTriplet("bounds_max_um",room.boundsMaximumMicrometres);
room.floorMicrometres=LayoutInt(v,"floor_um");
room.floorProfileStableId=LayoutString(v,"floor_profile_id");
if(v.Get("floor_profile_distance_um").IsNumber())
room.floorProfileDistanceMicrometres=LayoutInt(v,"floor_profile_distance_um");
const auto& floorGradient=v.Get("floor_gradient_q16").AsArray();
for(std::size_t axis=0;axis<2&&axis<floorGradient.size();++axis)
room.floorGradientQ16[axis]=static_cast<std::int32_t>(floorGradient[axis].Integer());
room.smoothWidthMicrometres=LayoutInt(v,"smooth_width_um");
room.unionWidthMicrometres=LayoutInt(v,"union_width_um");
room.irregularityAmplitudeMicrometres=LayoutInt(v,"irregularity_amplitude_um");
@@ -1539,6 +1682,13 @@ std::optional<ResolvedLayoutSnapshotV3> ResolvedLayoutSnapshotV3::FromJson(
edge.startAnchorStableId=LayoutString(v,"start_anchor_stable_id");
edge.endRoomStableId=LayoutString(v,"end_room_stable_id");
edge.strateIndex=static_cast<std::int32_t>(LayoutInt(v,"strate_index"));
edge.floorProfileStableId=LayoutString(v,"floor_profile_id");
if(v.Get("floor_profile_start_distance_um").IsNumber())
edge.floorProfileStartDistanceMicrometres=
LayoutInt(v,"floor_profile_start_distance_um");
if(v.Get("floor_profile_end_distance_um").IsNumber())
edge.floorProfileEndDistanceMicrometres=
LayoutInt(v,"floor_profile_end_distance_um");
const auto& points=v.Get("control_points_um").AsArray();
for(std::size_t i=0;i<edge.controlPointsMicrometres.size()&&i<points.size();++i)
{
@@ -1879,7 +2029,7 @@ bool ResolveResolvedCavernGraphV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
if(!candidate.finalize(error))return false;
layout=std::move(candidate);return true;
}
candidate.cavernGraphVocabularyVersion=ResolvedLayoutCavernGraphVocabularyVersionV1;
candidate.cavernGraphVocabularyVersion=ResolvedLayoutCavernGraphVocabularyVersionV2;
candidate.cavernRooms.assign(rooms.begin(),rooms.end());
candidate.cavernEdges.assign(edges.begin(),edges.end());
const auto isNativeCavesStrate=[&candidate](std::int32_t strateIndex)
@@ -2195,6 +2345,42 @@ 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 auto routeFloorAt=[&](std::int64_t distance)
{
distance=std::clamp<std::int64_t>(distance,0,totalRouteLength);
return routeStart[2]+dz*distance/totalRouteLength;
};
const auto routeFloorGradientAt=[&](std::int64_t distance)
{
distance=std::clamp<std::int64_t>(distance,0,totalRouteLength);
std::size_t segment=1;
while(segment<cumulative.size()-1&&distance>cumulative[segment])++segment;
const auto unitTangent=[&](std::size_t index)
{
const double tx=static_cast<double>(routePoints[index][0]
-routePoints[index-1][0]);
const double ty=static_cast<double>(routePoints[index][1]
-routePoints[index-1][1]);
const double length=std::hypot(tx,ty);
return length>0.0?std::array<double,2>{tx/length,ty/length}
:std::array<double,2>{0.0,0.0};
};
auto tangent=unitTangent(segment);
if(segment+1<cumulative.size()&&distance==cumulative[segment])
{
const auto next=unitTangent(segment+1);
tangent[0]+=next[0];tangent[1]+=next[1];
const double length=std::hypot(tangent[0],tangent[1]);
if(length>1.0e-12){tangent[0]/=length;tangent[1]/=length;}
}
constexpr double Q16=65536.0;
const double profileSlope=static_cast<double>(dz)/totalRouteLength;
return std::array<std::int32_t,2>{
static_cast<std::int32_t>(std::llround(tangent[0]*profileSlope*Q16)),
static_cast<std::int32_t>(std::llround(tangent[1]*profileSlope*Q16))};
};
const auto pointAt=[&](std::int64_t distance)
{
distance=std::clamp<std::int64_t>(distance,0,totalRouteLength);
@@ -2211,7 +2397,7 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
-routePoints[segment-1][axis];
point[axis]=routePoints[segment-1][axis]+delta*local/segmentLength;
}
point[2]=routeStart[2]+dz*distance/totalRouteLength;
point[2]=routeFloorAt(distance);
return point;
};
std::int64_t spacing=std::min(std::max<std::int64_t>(settings.roomSpacingMicrometres,
@@ -2281,6 +2467,9 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
{
std::string id;
FPoint point{};
std::string floorProfileStableId;
std::int64_t floorProfileDistanceMicrometres=-1;
std::array<std::int32_t,2> floorGradientQ16{};
std::int64_t radiusMicrometres=0;
bool destination=false;
};
@@ -2298,6 +2487,9 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
{
const std::int64_t distance=nodeDistances[nodeIndex];
FRoomNode node;node.point=pointAt(distance);
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);
@@ -2347,7 +2539,10 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
pathAxisX*pathAxisX+pathAxisY*pathAxisY))));
const std::int64_t centerOffset=(component%2==0?1:-1)*offsetMagnitude;
ResolvedCavernRoomV1 room;room.stableId=stableId;
room.strateIndex=strateIndex;room.floorMicrometres=node.point[2];
room.strateIndex=strateIndex;
room.floorProfileStableId=node.floorProfileStableId;
room.floorProfileDistanceMicrometres=node.floorProfileDistanceMicrometres;
room.floorGradientQ16=node.floorGradientQ16;
room.halfExtentsMicrometres={halfX,halfY,halfZ};
room.centerMicrometres={node.point[0]+(-pathAxisY)*centerOffset/routeLengthXY,
node.point[1]+pathAxisX*centerOffset/routeLengthXY,
@@ -2362,6 +2557,11 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
%static_cast<std::uint64_t>(jitterRange+1)):0;
room.centerMicrometres[2]+=ceilingJitter;
}
room.floorMicrometres=node.point[2]
-(static_cast<std::int64_t>(room.floorGradientQ16[0])
*(room.centerMicrometres[0]-node.point[0])
+static_cast<std::int64_t>(room.floorGradientQ16[1])
*(room.centerMicrometres[1]-node.point[1]))/65536;
room.smoothWidthMicrometres=smooth;
room.irregularityAmplitudeMicrometres=irregularity;
room.irregularityWavelengthMicrometres=settings.irregularityWavelengthMicrometres;
@@ -2374,7 +2574,8 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
const auto componentRoomId=[](const FRoomNode& node)
{return node.id+"/primitive-0";};
const auto makeEdge=[&](std::string id,std::string startAnchor,
const FPoint& start,const FRoomNode& endNode,std::int64_t boreRadius)
const FPoint& start,const FRoomNode& endNode,std::int64_t boreRadius,
std::int64_t profileStartDistance,std::int64_t profileEndDistance)
{
ResolvedCavernEdgeV1 edge;edge.stableId=std::move(id);
edge.startAnchorStableId=std::move(startAnchor);
@@ -2382,6 +2583,12 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
edge.boreRadiusMicrometres=boreRadius;
edge.boreHeightMicrometres=std::max(PlannedBoreHeight,MinimumBoreHeight);
edge.floorSupportDepthMicrometres=settings.floorSupportDepthMicrometres;
if(profileStartDistance>=0&&profileEndDistance>=0)
{
edge.floorProfileStableId=backboneFloorProfileId;
edge.floorProfileStartDistanceMicrometres=profileStartDistance;
edge.floorProfileEndDistanceMicrometres=profileEndDistance;
}
edge.smoothWidthMicrometres=smooth;
edge.irregularityAmplitudeMicrometres=irregularity;
edge.irregularityWavelengthMicrometres=settings.irregularityWavelengthMicrometres;
@@ -2394,10 +2601,21 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
edge.controlPointsMicrometres[2][axis]=start[axis]+(delta*2)/3;
edge.controlPointsMicrometres[3][axis]=end[axis];
}
if(profileStartDistance>=0&&profileEndDistance>=0)
{
const std::int64_t floorStart=routeFloorAt(profileStartDistance);
const std::int64_t floorEnd=routeFloorAt(profileEndDistance);
const std::int64_t floorDelta=floorEnd-floorStart;
edge.controlPointsMicrometres[0][2]=floorStart;
edge.controlPointsMicrometres[1][2]=floorStart+floorDelta/3;
edge.controlPointsMicrometres[2][2]=floorStart+(floorDelta*2)/3;
edge.controlPointsMicrometres[3][2]=floorEnd;
}
return edge;
};
const auto arrivalPoint=routeStart;
FPoint previousPoint=arrivalPoint;
std::int64_t previousProfileDistance=0;
std::string previousAnchor=arrivalPassage->stableId+"/lower-mouth";
for(std::size_t nodeIndex=0;nodeIndex<nodes.size();++nodeIndex)
{
@@ -2406,8 +2624,10 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
const std::string edgeId="tunnel-network/strate-"+std::to_string(strateIndex)
+"/route-edge-"+std::to_string(nodeIndex);
edges.push_back(makeEdge(edgeId,previousAnchor,previousPoint,node,
portal?passageBoreRadius(*arrivalPassage,true):selectedBoreRadius(edgeId)));
portal?passageBoreRadius(*arrivalPassage,true):selectedBoreRadius(edgeId),
previousProfileDistance,node.floorProfileDistanceMicrometres));
previousPoint={node.point[0],node.point[1],node.point[2]};
previousProfileDistance=node.floorProfileDistanceMicrometres;
previousAnchor=componentRoomId(node);
}
if(!nodes.empty())
@@ -2415,7 +2635,8 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
const std::string departureEdgeId="tunnel-network/strate-"
+std::to_string(strateIndex)+"/departure-edge";
edges.push_back(makeEdge(departureEdgeId,departurePassage->stableId+"/upper-mouth",
routeEnd,nodes.back(),passageBoreRadius(*departurePassage,false)));
routeEnd,nodes.back(),passageBoreRadius(*departurePassage,false),
totalRouteLength,nodes.back().floorProfileDistanceMicrometres));
}
const std::int64_t roomDensity=clampQ16(settings.roomDensityQ16);
@@ -2444,16 +2665,19 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
branch.point={parent.point[0]+(-pathAxisY)*branchLength/pathAxisLength*direction,
parent.point[1]+pathAxisX*branchLength/pathAxisLength*direction,
parent.point[2]};
branch.floorProfileStableId=parent.floorProfileStableId;
branch.floorProfileDistanceMicrometres=parent.floorProfileDistanceMicrometres;
branch.floorGradientQ16=parent.floorGradientQ16;
makeCompound(branch,false,rooms);
const std::string branchEdgeId="tunnel-network/strate-"
+std::to_string(strateIndex)+"/side-edge-"+std::to_string(branchCount);
ResolvedCavernEdgeV1 branchEdge;
if(settings.tunnelsFlowTowardOrigin)
branchEdge=makeEdge(branchEdgeId,componentRoomId(branch),
branch.point,parent,selectedBoreRadius(branchEdgeId));
branch.point,parent,selectedBoreRadius(branchEdgeId),-1,-1);
else
branchEdge=makeEdge(branchEdgeId,componentRoomId(parent),
parent.point,branch,selectedBoreRadius(branchEdgeId));
parent.point,branch,selectedBoreRadius(branchEdgeId),-1,-1);
// Side branches retain their cave floor SDF, but they do not cap the
// descending backbone when their tubes overlap it near a junction.
branchEdge.floorSupportDepthMicrometres=0;
+47 -6
View File
@@ -3583,6 +3583,7 @@ private:
double minimumFloorMetres=std::numeric_limits<double>::infinity();
double maximumFloorMetres=-std::numeric_limits<double>::infinity();
double maximumSegmentGrade=0.0;
std::array<double,2> floorGradient{};
std::array<float,3> centerVoxels{},halfExtentsVoxels{};
std::array<std::array<float,3>,4> controlPointsVoxels{};
float floorVoxels=0.0f;
@@ -4011,8 +4012,10 @@ private:
{
CavernRuntime.clear();CavernSpatialNodes.clear();CavernSpatialOrder.clear();
if(!ResolvedLayout||!Owner||Owner->CoreVoxelSizeMicrons==0
||ResolvedLayout->cavernGraphVocabularyVersion
!=VoxelForgeCore::ResolvedLayoutCavernGraphVocabularyVersionV1)return;
||(ResolvedLayout->cavernGraphVocabularyVersion
!=VoxelForgeCore::ResolvedLayoutCavernGraphVocabularyVersionV1
&&ResolvedLayout->cavernGraphVocabularyVersion
!=VoxelForgeCore::ResolvedLayoutCavernGraphVocabularyVersionV2))return;
const auto isNativeGraphStrate=[this](std::int32_t strateIndex)
{
const auto found=std::find_if(ResolvedLayout->strates.begin(),
@@ -4042,6 +4045,8 @@ private:
}
runtime.floorVoxels=static_cast<float>(
static_cast<double>(room.floorMicrometres)/micronsPerVoxel);
runtime.floorGradient={room.floorGradientQ16[0]/65536.0,
room.floorGradientQ16[1]/65536.0};
runtime.smoothWidthVoxels=static_cast<float>(
static_cast<double>(room.smoothWidthMicrometres)/micronsPerVoxel);
runtime.irregularityAmplitudeVoxels=static_cast<float>(
@@ -5314,7 +5319,18 @@ public:
(std::sqrt(normalizedMinimumSquared)-1.0)*meanRadius,
(std::sqrt(normalizedMaximumSquared)-1.0)*meanRadius,true};
const double floorZ=feature.room->floorMicrometres/1000000.0;
const FMetricInterval floor={floorZ-hi[2],floorZ-lo[2],true};
const double centerX=feature.room->centerMicrometres[0]/1000000.0;
const double centerY=feature.room->centerMicrometres[1]/1000000.0;
const double gx=feature.room->floorGradientQ16[0]/65536.0;
const double gy=feature.room->floorGradientQ16[1]/65536.0;
const double x0=lo[0]-centerX,x1=hi[0]-centerX;
const double y0=lo[1]-centerY,y1=hi[1]-centerY;
const double gradientMinimum=(gx>=0.0?gx*x0:gx*x1)
+(gy>=0.0?gy*y0:gy*y1);
const double gradientMaximum=(gx>=0.0?gx*x1:gx*x0)
+(gy>=0.0?gy*y1:gy*y0);
const FMetricInterval floor={floorZ-hi[2]+gradientMinimum,
floorZ-lo[2]+gradientMaximum,true};
const double width=feature.room->smoothWidthMicrometres/1000000.0;
featureVoid={SmoothMaxMetres(ellipsoid.minimum,floor.minimum,width),
SmoothMaxMetres(ellipsoid.maximum,floor.maximum,width),true};
@@ -7463,6 +7479,7 @@ private:
double floorSupportScore=std::numeric_limits<double>::infinity();
double floorSupportDepth=0.0,floorSupportFloor=0.0,floorSupportGrade=0.0;
const std::string* floorSupportEdgeId=nullptr;
const std::string* floorSupportProfileId=nullptr;
for(const std::uint32_t index:*candidates)
{
if(index>=CavernRuntime.size())continue;
@@ -7491,7 +7508,9 @@ private:
feature.irregularityWavelengthVoxels,feature.irregularitySeed)
*feature.irregularityAmplitudeVoxels*metresPerVoxel*fade;
}
const double floor=feature.room->floorMicrometres/1000000.0-point[2];
const double floor=feature.room->floorMicrometres/1000000.0-point[2]
+feature.floorGradient[0]*(point[0]-feature.centerMetres[0])
+feature.floorGradient[1]*(point[1]-feature.centerMetres[1]);
const double width=feature.room->smoothWidthMicrometres/1000000.0;
featureVoid=SmoothMaxMetres(ellipsoid,floor,width);
}
@@ -7599,10 +7618,32 @@ private:
{
const double deltaZ=point[2]-nearestFloor;
const double candidateScore=nearestRadial*nearestRadial+deltaZ*deltaZ;
if(candidateScore<floorSupportScore
if(!feature.edge->floorProfileStableId.empty())
{
const bool sameProfile=floorSupportProfileId
&&*floorSupportProfileId==feature.edge->floorProfileStableId;
if(!floorSupportProfileId||sameProfile)
{
// Backbone edges share h(s). At a geometric joint their
// cave volumes overlap, so the backing top is the lower
// of the local floors, never the closer higher segment.
if(!floorSupportProfileId||nearestFloor<floorSupportFloor
||(nearestFloor==floorSupportFloor
&&feature.edge->stableId<*floorSupportEdgeId))
{
floorSupportDepth=candidateSupportDepth;
floorSupportFloor=nearestFloor;
floorSupportGrade=nearestGrade;
floorSupportEdgeId=&feature.edge->stableId;
}
floorSupportProfileId=&feature.edge->floorProfileStableId;
}
}
else if(!floorSupportProfileId
&&(candidateScore<floorSupportScore
||(candidateScore==floorSupportScore
&&(!floorSupportEdgeId
||feature.edge->stableId<*floorSupportEdgeId)))
||feature.edge->stableId<*floorSupportEdgeId))))
{
floorSupportScore=candidateScore;
floorSupportDepth=candidateSupportDepth;