wip(L27, side branch): passages cross open voids inside shared natural rock columns

Unsupported passage runs > 12.5 m become a solid relief-bearing column (ceiling/island to
ground, flared base) with the helix inside, for every recipe; FI anchor uses the same path.
All 4 styles x both recipes: 0/0/0, 0 open-air, naturalness pass; no-long-bridge audit (L26
fails it). Deterministic, Clang = GCC. Look approved; cost +35% SW / +19% FI blocks merge (L28).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CSFYgsdW8ejZSyiMf4qRSX
This commit is contained in:
Fr0zkaandClaude Opus 5.5 committed 2026-10-21 04:14:14 +02:00
1 parent 37a223046c
commit 07b3fe2852
14 files changed
+772 -201

No files matched your search

+282 -76
View File
@@ -41,27 +41,31 @@ public:
double SealThickness=0.0;
};
const std::vector<FStandalonePassageBridgeRange>& GetPassageBridgeRanges() const
{return PassageBridgeRanges;}
const std::vector<FStandalonePassageVoidCrossing>& GetPassageVoidCrossings() const
{return PassageVoidCrossings;}
bool IsResolvedPassageBridgeSample(std::size_t PassageIndex,std::size_t SegmentIndex,
bool IsResolvedPassageColumnSample(std::size_t PassageIndex,std::size_t SegmentIndex,
const VoxelForgeCore::Vec3d& Position) const
{
if(IsPassageBridgeSegment(PassageIndex,SegmentIndex)
&&PassageBridgeSignedDistance(PassageIndex,SegmentIndex,
static_cast<float>(Position.x),static_cast<float>(Position.y),
static_cast<float>(Position.z))<=0.0)return true;
for(const auto& range:PassageBridgeRanges)
(void)SegmentIndex;
for(const auto& range:PassageVoidCrossings)
{
if(range.passageIndex!=PassageIndex)continue;
for(std::size_t segment=range.firstSegment;segment<=range.lastSegment;++segment)
if(segment!=SegmentIndex&&PassageBridgeSignedDistance(PassageIndex,segment,
static_cast<float>(Position.x),static_cast<float>(Position.y),
static_cast<float>(Position.z))<=0.0)return true;
if(!range.needsColumn||range.passageIndex!=PassageIndex)continue;
if(PassageColumnSignedDistance(range,static_cast<float>(Position.x),
static_cast<float>(Position.y),static_cast<float>(Position.z))<=0.0)return true;
}
return false;
}
bool IsResolvedPassageShortSpanSample(std::size_t PassageIndex,std::size_t SegmentIndex,
const VoxelForgeCore::Vec3d& Position) const
{
return IsPassageShortSpanSegment(PassageIndex,SegmentIndex)
&&PassageBridgeSignedDistance(PassageIndex,SegmentIndex,
static_cast<float>(Position.x),static_cast<float>(Position.y),
static_cast<float>(Position.z))<=0.0;
}
double GetCoreVoxelSizeCm() const
{
return Owner == nullptr || Owner->CoreVoxelSizeMicrons == 0
@@ -197,6 +201,13 @@ public:
-passage.controlPoints.front().radiusSubvoxels+playerCenterAboveFloorQ;
const double lastFloorQ=passage.controlPoints.back().positionSubvoxels[2]
-passage.controlPoints.back().radiusSubvoxels+playerCenterAboveFloorQ;
for(const auto& crossing:PassageVoidCrossings)
{
if(!crossing.needsColumn||crossing.passageIndex!=passageIndex
||qz>crossing.topZSubvoxels||qz<crossing.bottomZSubvoxels)continue;
const double columnDistance=PassageColumnSignedDistance(crossing,X,Y,Z);
composed=std::min(composed,static_cast<float>(columnDistance/Q));
}
if(qz>firstFloorQ+2.0*passage.mouthRadiusSubvoxels
||qz<lastFloorQ-2.0*passage.mouthRadiusSubvoxels)
{
@@ -297,7 +308,7 @@ public:
const double wallReliefWeight=1.0-0.80*verticalWallAlignment;
const double roughSignedDistance=bestSignedDistance
+static_cast<double>(std::clamp(relief,-1.0f,1.0f))*0.24*qPerMetre*wallReliefWeight;
if(IsPassageBridgeSegment(passageIndex,static_cast<std::size_t>(bestIndex)))
if(IsPassageShortSpanSegment(passageIndex,static_cast<std::size_t>(bestIndex)))
{
const double bridgeDistance=PassageBridgeSignedDistance(passageIndex,
static_cast<std::size_t>(bestIndex),X,Y,Z);
@@ -317,8 +328,9 @@ public:
const double radiusB=segmentB.radiusSubvoxels;
// Keep the tread within the probe band and below the capsule's
// lowest sampled point.
const double floorOffset=IsPassageBridgeSegment(passageIndex,
const double floorOffset=IsPassageShortSpanSegment(passageIndex,
static_cast<std::size_t>(index))
||IsPassageColumnSegment(passageIndex,static_cast<std::size_t>(index))
?PassageBridgeDeckOffsetMetres(passageIndex,static_cast<std::size_t>(index))
:0.20;
const double horizontalLengthSq=dx*dx+dy*dy;
@@ -387,11 +399,14 @@ public:
considerSupportSegment(index);
// Keep route treads inside the native rock envelope. Carving may reopen the
// floor above this point, but a support cannot create a new surface in air.
const bool bridgeFloorSupport=hasSupportFloor
&&IsPassageBridgeSegment(passageIndex,
const bool passageColumnFloorSupport=hasSupportFloor
&&IsPassageColumnSegment(passageIndex,
static_cast<std::size_t>(bestSupportSegmentIndex));
const bool shortSpanFloorSupport=hasSupportFloor
&&IsPassageShortSpanSegment(passageIndex,
static_cast<std::size_t>(bestSupportSegmentIndex));
if(hasSupportFloor&&bestPlayerCapsuleSignedDistance>=0.05*qPerMetre
&&(density<0.0f||bridgeFloorSupport))
&&(density<0.0f||passageColumnFloorSupport||shortSpanFloorSupport))
composed=std::min(composed,static_cast<float>(bestSupportDensity));
}
return composed;
@@ -416,9 +431,8 @@ public:
const double radius=static_cast<double>(std::max(a.radiusSubvoxels,b.radiusSubvoxels))/Q;
const double floorThicknessVoxels=Owner&&Owner->CoreVoxelSizeMicrons>0
?1000000.0/Owner->CoreVoxelSizeMicrons:4.0;
// A resolved passage bridge extends to a 3.5 m half-width and almost 5 m
// below the bore floor. Keep those cells shape-dependent for classification
// and streaming even before its bridge ranges have been compiled.
// Short support spans can extend to a 3.5 m half-width and almost 5 m
// below the bore floor. Keep those cells shape-dependent for classification.
const double horizontalExtent=std::max(radius,bridgeHalfWidthVoxels);
const double footDepth=std::max(floorThicknessVoxels,bridgeFootDepthVoxels);
const double low[3]={
@@ -435,6 +449,19 @@ public:
if(overlap)return true;
}
}
for(const auto& crossing:PassageVoidCrossings)
{
if(!crossing.needsColumn)continue;
const double radius=static_cast<double>(crossing.maximumRadiusSubvoxels)/Q;
const double low[3]={crossing.centerXSubvoxels/Q-radius,
crossing.centerYSubvoxels/Q-radius,crossing.bottomZSubvoxels/Q};
const double high[3]={crossing.centerXSubvoxels/Q+radius,
crossing.centerYSubvoxels/Q+radius,crossing.topZSubvoxels/Q};
bool overlap=true;
for(int axis=0;axis<3;++axis)
overlap&=high[axis]>=minimum[axis]&&low[axis]<=maximum[axis];
if(overlap)return true;
}
return false;
}
@@ -785,8 +812,10 @@ public:
if(RoundTunnelsEnabled)
BuildRoundTunnelPrimitives();
BuildSpatialBins(LineBinSize);
BuildPassageBridgePlan();
Stats.LocalLineBinSeconds=std::chrono::duration<double>(Clock::now()-Substage).count();
const auto SharedStage=Clock::now();
RebuildSharedFieldPrimitives();
Stats.SharedFieldSeconds=std::chrono::duration<double>(Clock::now()-SharedStage).count();
Stats.ShapeSeconds=std::chrono::duration<double>(Clock::now()-Stage).count();
Stats.PrimitiveCount=Primitives.size();
if(Primitives.empty())
@@ -797,6 +826,10 @@ public:
RockWords.assign(static_cast<size_t>(Size[0])*Size[1]*WordsPerZ,0);
SurfaceDistanceQ=std::make_unique_for_overwrite<int16[]>(static_cast<size_t>(NodeCount));
BuildPackedRockIntervals(Stats.RockIntervalLines);
// Native density queries for non-heightfield strates use the exact interval
// evaluator. Build those native intervals before scanning passage support;
// the scan itself still completes before any resolved column is composed.
BuildPassageVoidCrossingPlan();
BuildCandidateBands(Stats.CandidateAxisLines,Stats.CandidateMergeLines,
Stats.CandidateMergeSeconds);
if(!BuildGpuVisualDetailNodeSets(InGpuVisualDetailTiles,InCpuVisualDetailTiles,Error))
@@ -820,9 +853,6 @@ public:
return false;
}
Stats.PassageValidationSeconds=std::chrono::duration<double>(Clock::now()-Substage).count();
Substage=Clock::now();
RebuildSharedFieldPrimitives();
Stats.SharedFieldSeconds=std::chrono::duration<double>(Clock::now()-Substage).count();
Built=true;
if(bIncludeFarWorld)BuildFarWorld();
const uint64 WordBytes=static_cast<uint64>(RockWords.size()+CandidateWords.size())*sizeof(uint64);
@@ -1693,23 +1723,27 @@ public:
}
private:
bool IsPassageBridgeSegment(std::size_t PassageIndex,std::size_t SegmentIndex) const
bool IsPassageColumnSegment(std::size_t PassageIndex,std::size_t SegmentIndex) const
{
return std::any_of(PassageBridgeRanges.begin(),PassageBridgeRanges.end(),
[PassageIndex,SegmentIndex](const auto& range)
{return range.passageIndex==PassageIndex
&&SegmentIndex>=range.firstSegment&&SegmentIndex<=range.lastSegment;});
return std::any_of(PassageVoidCrossings.begin(),PassageVoidCrossings.end(),
[PassageIndex,SegmentIndex](const auto& crossing)
{return crossing.needsColumn&&crossing.passageIndex==PassageIndex
&&SegmentIndex>=crossing.firstSegment&&SegmentIndex<=crossing.lastSegment;});
}
bool IsPassageShortSpanSegment(std::size_t PassageIndex,std::size_t SegmentIndex) const
{
return std::any_of(PassageVoidCrossings.begin(),PassageVoidCrossings.end(),
[PassageIndex,SegmentIndex](const auto& crossing)
{return !crossing.needsColumn&&crossing.passageIndex==PassageIndex
&&SegmentIndex>=crossing.firstSegment&&SegmentIndex<=crossing.lastSegment;});
}
double PassageBridgeDeckOffsetMetres(std::size_t PassageIndex,
std::size_t SegmentIndex) const
{
for(const auto& range:PassageBridgeRanges)
{
if(range.passageIndex!=PassageIndex||SegmentIndex>range.lastSegment)continue;
if(SegmentIndex>=range.firstSegment)return 0.13;
}
return 0.20;
(void)PassageIndex;(void)SegmentIndex;
return 0.13;
}
double PassageBridgeSignedDistance(std::size_t PassageIndex,std::size_t SegmentIndex,
@@ -1760,6 +1794,109 @@ private:
return outside+inside;
}
double PassageColumnSignedDistance(const FStandalonePassageVoidCrossing& crossing,
float X,float Y,float Z) const
{
if(crossing.maximumRadiusSubvoxels<=0||crossing.topZSubvoxels
<=crossing.bottomZSubvoxels)return std::numeric_limits<double>::infinity();
constexpr double Q=static_cast<double>(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel);
const double qPerMetre=Q*1000000.0/std::max<std::uint32_t>(1,
ResolvedLayout?ResolvedLayout->coreVoxelSizeMicrometres:250000u);
const double qx=static_cast<double>(X)*Q,qy=static_cast<double>(Y)*Q,
qz=static_cast<double>(Z)*Q;
const double dx=qx-crossing.centerXSubvoxels;
const double dy=qy-crossing.centerYSubvoxels;
const double maximumRadius=static_cast<double>(crossing.maximumRadiusSubvoxels);
const double horizontalSquared=dx*dx+dy*dy;
if(horizontalSquared>maximumRadius*maximumRadius)
return DeterministicSqrt(horizontalSquared)-maximumRadius;
if(qz>crossing.topZSubvoxels)
return qz-crossing.topZSubvoxels;
if(qz<crossing.bottomZSubvoxels)
return crossing.bottomZSubvoxels-qz;
// The lower-strate route is already walkable in its native field and binds
// directly to the passage mouth. Preserve that player capsule as the column
// flares into the ground, so the pillar joins the route instead of sealing it.
if(ResolvedLayout&&crossing.passageIndex<ResolvedLayout->passages.size()
&&qz<crossing.bottomZSubvoxels+18.0*qPerMetre)
{
const auto& passage=ResolvedLayout->passages[crossing.passageIndex];
const auto lower=std::find_if(ResolvedLayout->strates.begin(),
ResolvedLayout->strates.end(),[&](const auto& value)
{return value.strateIndex==passage.lowerStrateIndex;});
if(lower!=ResolvedLayout->strates.end())
{
const double capsuleRadius=0.37*qPerMetre;
const double capsuleHalfHeight=0.86*qPerMetre;
const double capsuleRadiusSquared=capsuleRadius*capsuleRadius;
const VoxelForgeCore::ResolvedLayoutAnchorV2* previous=nullptr;
for(const auto& anchor:lower->anchors)
{
if(anchor.kind!="route_point"||anchor.values.size()<3)continue;
if(previous)
{
const double ax=previous->values[0],ay=previous->values[1];
const double dx=static_cast<double>(anchor.values[0])-ax;
const double dy=static_cast<double>(anchor.values[1])-ay;
const double lengthSquared=dx*dx+dy*dy;
const double t=lengthSquared>0.0?std::clamp(
((qx-ax)*dx+(qy-ay)*dy)/lengthSquared,0.0,1.0):0.0;
const double routeX=ax+t*dx,routeY=ay+t*dy;
const double routeZ=previous->values[2]+t*(
static_cast<double>(anchor.values[2])-previous->values[2]);
const double routeDx=qx-routeX,routeDy=qy-routeY;
if(routeDx*routeDx+routeDy*routeDy<=capsuleRadiusSquared
&&std::abs(qz-routeZ)<=capsuleHalfHeight)
return maximumRadius+qPerMetre;
}
previous=&anchor;
}
}
}
const double height=static_cast<double>(crossing.topZSubvoxels
-crossing.bottomZSubvoxels);
const double t=std::clamp((crossing.topZSubvoxels-qz)/height,0.0,1.0);
const auto smoothStep=[](double value)
{
value=std::clamp(value,0.0,1.0);
return value*value*(3.0-2.0*value);
};
const float z=static_cast<float>(Z);
const float scaledZ=z*0.12f;
const float driftX=VF_FieldSurfaceRelief(0.0f,0.0f,scaledZ,
static_cast<std::uint32_t>(Owner?Owner->Seed:0)^0x434f4c58u);
const float driftY=VF_FieldSurfaceRelief(0.0f,0.0f,scaledZ,
static_cast<std::uint32_t>(Owner?Owner->Seed:0)^0x434f4c59u);
const double axisX=crossing.centerXSubvoxels
+std::clamp(static_cast<double>(driftX),-1.0,1.0)*0.35*qPerMetre;
const double axisY=crossing.centerYSubvoxels
+std::clamp(static_cast<double>(driftY),-1.0,1.0)*0.35*qPerMetre;
const double centerDx=qx-axisX,centerDy=qy-axisY;
const double radialDistance=DeterministicSqrt(centerDx*centerDx+centerDy*centerDy);
const float broadRelief=VF_FieldSurfaceRelief(X*0.12f,Y*0.12f,Z*0.12f,
static_cast<std::uint32_t>(Owner?Owner->Seed:0)^0x434f4c52u);
const float surfaceRelief=VF_FieldSurfaceRelief(X,Y,Z,
static_cast<std::uint32_t>(Owner?Owner->Seed:0)^0x434f4c53u);
const double baseFlare=6.0*qPerMetre*smoothStep((t-0.82)/0.18);
const double ceilingFlare=2.0*qPerMetre*(1.0-smoothStep(t/0.075));
const double irregularity=1.25*qPerMetre
+std::clamp(static_cast<double>(broadRelief),-1.0,1.0)*1.10*qPerMetre
+std::clamp(static_cast<double>(surfaceRelief),-1.0,1.0)*0.35*qPerMetre;
const double radius=static_cast<double>(crossing.minimumRadiusSubvoxels)
+std::max(0.15*qPerMetre,irregularity)+baseFlare+ceilingFlare;
const double radialDistanceField=radialDistance-radius;
const double topDistance=qz-crossing.topZSubvoxels;
const double bottomDistance=crossing.bottomZSubvoxels-qz;
const double verticalDistance=std::max(topDistance,bottomDistance);
const double outside=DeterministicSqrt(
std::max(radialDistanceField,0.0)*std::max(radialDistanceField,0.0)
+std::max(verticalDistance,0.0)*std::max(verticalDistance,0.0));
const double inside=std::min(std::max(radialDistanceField,verticalDistance),0.0);
return outside+inside;
}
float NativePassageDensityAt(float X,float Y,float Z) const
{
float density=0.0f;
@@ -1775,33 +1912,25 @@ private:
return ApplyResolvedStrateBoundaryBand(density,X,Y,Z);
}
void BuildPassageBridgePlan()
void BuildPassageVoidCrossingPlan()
{
PassageBridgeRanges.clear();
PassageVoidCrossings.clear();
if(!Owner||!ResolvedLayout||ResolvedLayout->passages.empty()
||!Owner->PassageBridgeCache)return;
auto& cache=*Owner->PassageBridgeCache;
||!Owner->PassageVoidCrossingCache)return;
auto& cache=*Owner->PassageVoidCrossingCache;
std::lock_guard<std::mutex> lock(cache.mutex);
const auto cached=cache.byLayout.find(ResolvedLayout->stableIdentity);
const std::string cacheKey=ResolvedLayout->stableIdentity+"/passage-void-columns-v1";
const auto cached=cache.byLayout.find(cacheKey);
if(cached!=cache.byLayout.end())
{PassageBridgeRanges=cached->second;return;}
{PassageVoidCrossings=cached->second;return;}
constexpr double Q=static_cast<double>(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel);
const double qPerMetre=Q*1000000.0/std::max<std::uint32_t>(1,
ResolvedLayout->coreVoxelSizeMicrometres);
const auto inSurfaceWorldStratum=[&](double zSubvoxels)
{
const double zMicrometres=zSubvoxels
*ResolvedLayout->coreVoxelSizeMicrometres/Q;
return std::any_of(ResolvedLayout->strates.begin(),ResolvedLayout->strates.end(),
[zMicrometres](const auto& stratum)
{return stratum.generatorKind=="SurfaceWorld"
&&zMicrometres>=stratum.interiorBottomMicrometres
&&zMicrometres<stratum.interiorTopMicrometres;});
};
constexpr std::array<std::array<double,2>,5> supportOffsets{{
{{0.0,0.0}},{{0.45,0.0}},{{-0.45,0.0}},{{0.0,0.45}},{{0.0,-0.45}}}};
constexpr std::array<double,2> supportDepthsMetres{{0.125,0.25}};
constexpr double shortSpanMaximumMetres=12.5; // 2.5 x the 5 m legacy support thickness.
struct FPathSample
{
std::size_t segment=0;
@@ -1818,14 +1947,10 @@ private:
for(const double depth:supportDepthsMetres)
{
const double sampleZ=floorQ-depth*qPerMetre;
// Bridge spans address the measured SurfaceWorld void. Other native
// strata retain their own route/cave composition and are excluded here.
if(!inSurfaceWorldStratum(sampleZ)){hasSolid=true;break;}
float density=1.0f;
if(!NativeSurfaceDensityAt(
const float density=NativePassageDensityAt(
static_cast<float>((point[0]+offset[0]*qPerMetre)/Q),
static_cast<float>((point[1]+offset[1]*qPerMetre)/Q),
static_cast<float>(sampleZ/Q),density))continue;
static_cast<float>(sampleZ/Q));
if(density<0.0f){hasSolid=true;break;}
}
supported+=hasSolid;
@@ -1874,9 +1999,10 @@ private:
const std::size_t gapBegin=cursor;
while(cursor<samples.size()&&!samples[cursor].nativeSupported)++cursor;
const std::size_t gapEnd=cursor-1;
if(gapBegin==0||cursor>=samples.size())continue;
const std::size_t startSample=gapBegin>0?gapBegin-1:gapBegin;
const std::size_t endSample=cursor<samples.size()?cursor:samples.size()-1;
double gapLength=0.0;
for(std::size_t index=gapBegin;index<=gapEnd;++index)
for(std::size_t index=startSample+1;index<=endSample;++index)
{
const auto& a=samples[index-1].point;
const auto& b=samples[index].point;
@@ -1884,25 +2010,105 @@ private:
gapLength+=DeterministicSqrt(dx*dx+dy*dy+dz*dz)/qPerMetre;
}
if(gapLength<1.0)continue;
FStandalonePassageBridgeRange range;
range.passageIndex=passageIndex;
range.firstSegment=samples[gapBegin-1].segment;
range.lastSegment=samples[cursor].segment;
range.unsupportedSamples=gapEnd-gapBegin+1;
range.unsupportedLengthMetres=gapLength;
range.startPoint=samples[gapBegin-1].point;
range.endPoint=samples[cursor].point;
PassageBridgeRanges.push_back(range);
std::printf("[PassageRockBridgePlan] id=%s first_segment=%zu last_segment=%zu unsupported_samples=%llu gap_length_m=%.3f connected_start=1 connected_end=1 width_m=6.000 thickness_m=3.000..5.000 status=PLANNED\n",
passage.stableId.c_str(),range.firstSegment,range.lastSegment,
static_cast<unsigned long long>(range.unsupportedSamples),gapLength);
FStandalonePassageVoidCrossing crossing;
crossing.passageIndex=passageIndex;
crossing.firstSegment=samples[startSample].segment;
crossing.lastSegment=samples[endSample].segment;
crossing.unsupportedSamples=gapEnd-gapBegin+1;
crossing.unsupportedLengthMetres=gapLength;
crossing.startPoint=samples[startSample].point;
crossing.endPoint=samples[endSample].point;
crossing.connectedAtStart=gapBegin>0&&samples[startSample].nativeSupported;
crossing.connectedAtEnd=cursor<samples.size()&&samples[endSample].nativeSupported;
crossing.needsColumn=gapLength>shortSpanMaximumMetres;
if(crossing.needsColumn&&passage.controlPoints.size()>1)
crossing.lastSegment=std::min(crossing.lastSegment+1,
passage.controlPoints.size()-2);
if(crossing.needsColumn)
{
std::int64_t sumX=0,sumY=0;
std::uint64_t centerSamples=0;
for(std::size_t index=gapBegin;index<=gapEnd;++index)
{
sumX+=static_cast<std::int64_t>(std::llround(samples[index].point[0]));
sumY+=static_cast<std::int64_t>(std::llround(samples[index].point[1]));
++centerSamples;
}
crossing.centerXSubvoxels=centerSamples>0
?static_cast<std::int64_t>(std::llround(static_cast<double>(sumX)/centerSamples)):0;
crossing.centerYSubvoxels=centerSamples>0
?static_cast<std::int64_t>(std::llround(static_cast<double>(sumY)/centerSamples)):0;
for(const auto& stratum:ResolvedLayout->strates)
if(stratum.strateIndex==passage.upperStrateIndex
&&stratum.generatorKind=="FloatingIslands")
for(const auto& anchor:stratum.anchors)
if(anchor.kind=="passage_anchor"&&anchor.values.size()>=2)
{
crossing.centerXSubvoxels=anchor.values[0];
crossing.centerYSubvoxels=anchor.values[1];
break;
}
double maximumOffsetSubvoxels=0.0;
double maximumBoreSubvoxels=0.0;
for(std::size_t index=startSample;index<=endSample;++index)
{
const auto& point=samples[index].point;
const double dx=point[0]-crossing.centerXSubvoxels;
const double dy=point[1]-crossing.centerYSubvoxels;
maximumOffsetSubvoxels=std::max(maximumOffsetSubvoxels,
DeterministicSqrt(dx*dx+dy*dy));
maximumBoreSubvoxels=std::max(maximumBoreSubvoxels,point[3]);
}
const double maximumOffsetMetres=maximumOffsetSubvoxels/qPerMetre;
const double maximumBoreMetres=maximumBoreSubvoxels/qPerMetre;
const double minimumRadiusMetres=maximumOffsetMetres+maximumBoreMetres+2.0;
const double nominalRadiusMetres=minimumRadiusMetres+1.25;
crossing.maximumRouteOffsetMetres=maximumOffsetMetres;
crossing.maximumBoreRadiusMetres=maximumBoreMetres;
crossing.minimumRadiusSubvoxels=static_cast<std::int64_t>(std::ceil(
minimumRadiusMetres*qPerMetre));
crossing.nominalRadiusSubvoxels=static_cast<std::int64_t>(std::llround(
nominalRadiusMetres*qPerMetre));
crossing.maximumRadiusSubvoxels=crossing.minimumRadiusSubvoxels
+static_cast<std::int64_t>(std::ceil(11.25*qPerMetre));
const double startZ=crossing.startPoint[2];
const double endZ=crossing.endPoint[2];
crossing.topZSubvoxels=static_cast<std::int64_t>(std::llround(
std::max(startZ,endZ)+5.0*qPerMetre));
crossing.bottomZSubvoxels=static_cast<std::int64_t>(std::llround(
std::min(startZ,endZ)-6.0*qPerMetre));
}
PassageVoidCrossings.push_back(crossing);
if(crossing.needsColumn)
{
const double coreRadius=crossing.nominalRadiusSubvoxels/qPerMetre;
const double minimumWall=coreRadius-crossing.maximumRouteOffsetMetres
-crossing.maximumBoreRadiusMetres;
std::printf("[PassageVoidColumnPlan] id=%s segments=%zu..%zu unsupported_samples=%llu span_m=%.3f threshold_m=%.3f connected=%d/%d axis_m=(%.3f,%.3f) z_m=%.3f..%.3f route_radius_m=%.3f bore_radius_m=%.3f min_radius_m=%.3f nominal_radius_m=%.3f max_radius_m=%.3f nominal_wall_m=%.3f flare_m=6.000 status=%s\n",
passage.stableId.c_str(),crossing.firstSegment,crossing.lastSegment,
static_cast<unsigned long long>(crossing.unsupportedSamples),gapLength,
shortSpanMaximumMetres,static_cast<int>(crossing.connectedAtStart),
static_cast<int>(crossing.connectedAtEnd),
crossing.centerXSubvoxels/qPerMetre,crossing.centerYSubvoxels/qPerMetre,
crossing.topZSubvoxels/qPerMetre,crossing.bottomZSubvoxels/qPerMetre,
crossing.maximumRouteOffsetMetres,crossing.maximumBoreRadiusMetres,
crossing.minimumRadiusSubvoxels/qPerMetre,coreRadius,
crossing.maximumRadiusSubvoxels/qPerMetre,minimumWall,
crossing.connectedAtStart&&crossing.connectedAtEnd?"PLANNED":"UNATTACHED");
}
else
std::printf("[PassageShortGroundedSpanPlan] id=%s segments=%zu..%zu span_m=%.3f threshold_m=%.3f connected=%d/%d width_m=6.000 thickness_m=3.000..5.000 status=%s\n",
passage.stableId.c_str(),crossing.firstSegment,crossing.lastSegment,
gapLength,shortSpanMaximumMetres,static_cast<int>(crossing.connectedAtStart),
static_cast<int>(crossing.connectedAtEnd),
crossing.connectedAtStart&&crossing.connectedAtEnd?"PLANNED":"UNATTACHED");
}
if(std::none_of(PassageBridgeRanges.begin(),PassageBridgeRanges.end(),
if(std::none_of(PassageVoidCrossings.begin(),PassageVoidCrossings.end(),
[passageIndex](const auto& range){return range.passageIndex==passageIndex;}))
std::printf("[PassageRockBridgePlan] id=%s spans=0 status=NOT_REQUIRED\n",
std::printf("[PassageVoidColumnPlan] id=%s crossings=0 status=NOT_REQUIRED\n",
passage.stableId.c_str());
}
cache.byLayout.emplace(ResolvedLayout->stableIdentity,PassageBridgeRanges);
cache.byLayout.emplace(cacheKey,PassageVoidCrossings);
}
static uint64 PackLatticeKey(int32 X,int32 Y,int32 Z)
@@ -6151,7 +6357,7 @@ private:
std::vector<FFarConnectorSegment> FarConnectorSegments;
std::unordered_map<FBinKey,std::vector<uint32>,FBinHash> FarConnectorSpatialBins;
std::vector<FEditBrush> EditBrushes;
std::vector<FStandalonePassageBridgeRange> PassageBridgeRanges;
std::vector<FStandalonePassageVoidCrossing> PassageVoidCrossings;
std::array<FAxisBins,3> AxisBins;
std::unordered_map<FBinKey,FBinItems,FBinHash> SpatialBins;
std::unordered_map<FBinKey,FBinItems,FBinHash> CriticalAirBins;