wip(l59): open helix shaft (ledge open to the core) -- PARKED: jagged inner ramp edge, audit ROI missed it
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
6c6cc37e1d
commit
54ba1b49ab
17 files changed
+1167
-172
No files matched your search
@@ -1004,11 +1004,13 @@ namespace VoxelForgeCore
|
||||
&&stratum.recipeVersion==1;
|
||||
}
|
||||
|
||||
inline constexpr std::uint32_t NativeLatticeFieldRecipeVersion=2;
|
||||
|
||||
inline bool IsNativeLatticeFieldStratum(const ResolvedLayoutStratumV2& stratum) noexcept
|
||||
{
|
||||
return (stratum.generatorKind=="Maze"||stratum.generatorKind=="VerticalShafts")
|
||||
&&stratum.recipeId=="Core.Lattice.PrimitiveField"
|
||||
&&stratum.recipeVersion==1;
|
||||
&&stratum.recipeVersion==NativeLatticeFieldRecipeVersion;
|
||||
}
|
||||
|
||||
inline bool HasNativeGraphField(const ResolvedLayoutStratumV2& stratum) noexcept
|
||||
|
||||
@@ -1901,7 +1901,8 @@ bool ResolveResolvedCavernGraphV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
||||
// The shared JSON value type stores numbers as IEEE-754 doubles. Keep the
|
||||
// serialized seed in the exact integer range, as the stamp vocabulary does.
|
||||
room.irregularitySeed=(isNativeLatticeStrate(room.strateIndex)
|
||||
?LayoutLatticeFeatureSeed(seed,room.strateIndex,1,room.stableId)
|
||||
?LayoutLatticeFeatureSeed(seed,room.strateIndex,
|
||||
NativeLatticeFieldRecipeVersion,room.stableId)
|
||||
:LayoutStampSeed(seed,room.strateIndex,room.stableId))
|
||||
&0x001fffffffffffffull;
|
||||
const std::int64_t pad=room.smoothWidthMicrometres
|
||||
@@ -1918,7 +1919,8 @@ bool ResolveResolvedCavernGraphV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
||||
{
|
||||
const bool nativeLattice=isNativeLatticeStrate(edge.strateIndex);
|
||||
edge.irregularitySeed=(nativeLattice
|
||||
?LayoutLatticeFeatureSeed(seed,edge.strateIndex,1,edge.stableId)
|
||||
?LayoutLatticeFeatureSeed(seed,edge.strateIndex,
|
||||
NativeLatticeFieldRecipeVersion,edge.stableId)
|
||||
:LayoutStampSeed(seed,edge.strateIndex,edge.stableId))
|
||||
&0x001fffffffffffffull;
|
||||
using FCurvePoint=std::array<double,3>;
|
||||
|
||||
@@ -3332,6 +3332,14 @@ private:
|
||||
const VoxelForgeCore::ResolvedCavernRoomV1* room=nullptr;
|
||||
const VoxelForgeCore::ResolvedCavernEdgeV1* edge=nullptr;
|
||||
bool nativeGraph=false;
|
||||
bool openShaftRamp=false;
|
||||
std::array<double,2> openShaftCenterMetres{};
|
||||
std::array<double,2> openShaftStartDirection{1.0,0.0};
|
||||
double openShaftCenterlineRadiusMetres=0.0;
|
||||
double openShaftStartFloorMetres=0.0;
|
||||
double openShaftPitchPerTurnMetres=0.0;
|
||||
double openShaftTurnCount=0.0;
|
||||
double openShaftRotationSign=1.0;
|
||||
std::array<std::int64_t,3> boundsMinimumMicrometres{},boundsMaximumMicrometres{};
|
||||
std::array<double,3> centerMetres{},halfExtentsMetres{};
|
||||
struct FSegment
|
||||
@@ -3792,6 +3800,15 @@ private:
|
||||
:found->passageCapabilities.hasNativeLatticeField);
|
||||
};
|
||||
const float micronsPerVoxel=static_cast<float>(Owner->CoreVoxelSizeMicrons);
|
||||
const auto shaftCoreRoom=std::find_if(ResolvedLayout->cavernRooms.begin(),
|
||||
ResolvedLayout->cavernRooms.end(),[](const auto& room)
|
||||
{return room.stableId=="lattice/spine/shaft-core-v2";});
|
||||
std::array<double,2> shaftCoreCenter{};
|
||||
if(shaftCoreRoom!=ResolvedLayout->cavernRooms.end())
|
||||
{
|
||||
shaftCoreCenter={shaftCoreRoom->centerMicrometres[0]/1.0e6,
|
||||
shaftCoreRoom->centerMicrometres[1]/1.0e6};
|
||||
}
|
||||
for(const auto& room:ResolvedLayout->cavernRooms)
|
||||
{
|
||||
FCavernRuntimeFeature runtime;runtime.room=&room;
|
||||
@@ -3822,6 +3839,11 @@ private:
|
||||
{
|
||||
FCavernRuntimeFeature runtime;runtime.edge=&edge;
|
||||
runtime.nativeGraph=isNativeGraphStrate(edge.strateIndex);
|
||||
runtime.openShaftRamp=runtime.nativeGraph
|
||||
&&edge.stableId=="lattice/spine/01-helix-ramp-v1"
|
||||
&&shaftCoreRoom!=ResolvedLayout->cavernRooms.end();
|
||||
if(runtime.openShaftRamp)
|
||||
runtime.openShaftCenterMetres=shaftCoreCenter;
|
||||
runtime.boundsMinimumMicrometres=edge.boundsMinimumMicrometres;
|
||||
runtime.boundsMaximumMicrometres=edge.boundsMaximumMicrometres;
|
||||
for(std::size_t point=0;point<4;++point)
|
||||
@@ -3865,6 +3887,37 @@ private:
|
||||
std::abs((segment.end[2]-segment.start[2])/horizontal));
|
||||
runtime.segments.push_back(segment);
|
||||
}
|
||||
if(runtime.openShaftRamp&&!runtime.segments.empty())
|
||||
{
|
||||
const auto& first=runtime.segments.front().start;
|
||||
const auto& last=runtime.segments.back().end;
|
||||
const double startX=first[0]-shaftCoreCenter[0];
|
||||
const double startY=first[1]-shaftCoreCenter[1];
|
||||
runtime.openShaftCenterlineRadiusMetres=std::hypot(startX,startY);
|
||||
if(runtime.openShaftCenterlineRadiusMetres>1.0e-9)
|
||||
runtime.openShaftStartDirection={
|
||||
startX/runtime.openShaftCenterlineRadiusMetres,
|
||||
startY/runtime.openShaftCenterlineRadiusMetres};
|
||||
runtime.openShaftStartFloorMetres=first[2];
|
||||
double totalAngle=0.0;
|
||||
for(std::size_t segment=0;segment<runtime.segments.size();++segment)
|
||||
{
|
||||
const auto& previous=runtime.segments[segment].start;
|
||||
const auto& current=runtime.segments[segment].end;
|
||||
const double previousX=previous[0]-shaftCoreCenter[0];
|
||||
const double previousY=previous[1]-shaftCoreCenter[1];
|
||||
const double currentX=current[0]-shaftCoreCenter[0];
|
||||
const double currentY=current[1]-shaftCoreCenter[1];
|
||||
totalAngle+=std::atan2(previousX*currentY-previousY*currentX,
|
||||
previousX*currentX+previousY*currentY);
|
||||
}
|
||||
constexpr double TwoPi=6.283185307179586476925286766559;
|
||||
runtime.openShaftTurnCount=std::max(1.0,
|
||||
std::round(std::abs(totalAngle)/TwoPi));
|
||||
runtime.openShaftRotationSign=totalAngle<0.0?-1.0:1.0;
|
||||
runtime.openShaftPitchPerTurnMetres=(first[2]-last[2])
|
||||
/runtime.openShaftTurnCount;
|
||||
}
|
||||
runtime.segmentOrder.resize(runtime.segments.size());
|
||||
for(std::size_t index=0;index<runtime.segmentOrder.size();++index)
|
||||
runtime.segmentOrder[index]=static_cast<std::uint32_t>(index);
|
||||
@@ -5245,9 +5298,50 @@ public:
|
||||
const std::int32_t featureStrate=feature.room?feature.room->strateIndex
|
||||
:feature.edge?feature.edge->strateIndex:-1;
|
||||
if(featureStrate!=strateIndex)continue;
|
||||
bool featureOverlapsBox=true,boxWithinFeatureBounds=true;
|
||||
for(std::size_t axis=0;axis<3;++axis)
|
||||
{
|
||||
const double featureMinimum=feature.boundsMinimumMicrometres[axis]/1.0e6;
|
||||
const double featureMaximum=feature.boundsMaximumMicrometres[axis]/1.0e6;
|
||||
if(hi[axis]<featureMinimum||lo[axis]>featureMaximum)
|
||||
{featureOverlapsBox=false;break;}
|
||||
boxWithinFeatureBounds=boxWithinFeatureBounds
|
||||
&&lo[axis]>=featureMinimum&&hi[axis]<=featureMaximum;
|
||||
}
|
||||
if(!featureOverlapsBox)continue;
|
||||
// The point field omits a record outside its resolved bounds. A partial
|
||||
// overlap cannot reuse the record's unbounded SDF for an all-air proof.
|
||||
if(!boxWithinFeatureBounds)return {0.0,0.0,false};
|
||||
FMetricInterval featureVoid;
|
||||
bool openHelixSupportCouldApply=true;
|
||||
double openHelixSupportLowerBound=std::numeric_limits<double>::infinity();
|
||||
if(feature.room)
|
||||
{
|
||||
if(nativeLattice
|
||||
&&feature.room->stableId=="lattice/spine/shaft-core-v2")
|
||||
{
|
||||
const double radius=feature.halfExtentsMetres[0];
|
||||
if(!(radius>0.0))return {0.0,0.0,false};
|
||||
double radialMinimumSquared=0.0,radialMaximumSquared=0.0;
|
||||
for(std::size_t axis=0;axis<2;++axis)
|
||||
{
|
||||
const double center=feature.centerMetres[axis];
|
||||
const double deltaMinimum=center<lo[axis]?lo[axis]-center
|
||||
:center>hi[axis]?center-hi[axis]:0.0;
|
||||
const double deltaMaximum=std::max(std::abs(lo[axis]-center),
|
||||
std::abs(hi[axis]-center));
|
||||
radialMinimumSquared+=deltaMinimum*deltaMinimum;
|
||||
radialMaximumSquared+=deltaMaximum*deltaMaximum;
|
||||
}
|
||||
const double radialMinimum=std::sqrt(radialMinimumSquared)-radius;
|
||||
const double radialMaximum=std::sqrt(radialMaximumSquared)-radius;
|
||||
const double floorZ=feature.room->floorMicrometres/1000000.0;
|
||||
const FMetricInterval bottom={floorZ-hi[2],floorZ-lo[2],true};
|
||||
featureVoid={std::max(radialMinimum,bottom.minimum),
|
||||
std::max(radialMaximum,bottom.maximum),true};
|
||||
}
|
||||
else
|
||||
{
|
||||
double normalizedMinimumSquared=0.0,normalizedMaximumSquared=0.0;
|
||||
double meanRadius=0.0;
|
||||
for(std::size_t axis=0;axis<3;++axis)
|
||||
@@ -5271,14 +5365,135 @@ public:
|
||||
const double width=feature.room->smoothWidthMicrometres/1000000.0;
|
||||
featureVoid={SmoothMaxMetres(ellipsoid.minimum,floor.minimum,width),
|
||||
SmoothMaxMetres(ellipsoid.maximum,floor.maximum,width),true};
|
||||
}
|
||||
}
|
||||
else if(feature.edge&&!feature.segments.empty())
|
||||
{
|
||||
const bool openHelixRamp=nativeLattice&&feature.openShaftRamp;
|
||||
const double radius=feature.edge->boreRadiusMicrometres/1000000.0;
|
||||
const double height=feature.edge->boreHeightMicrometres/1000000.0;
|
||||
if(!(radius>0.0&&height>0.0))return {0.0,0.0,false};
|
||||
double tubeMinimum=std::numeric_limits<double>::infinity();
|
||||
double tubeMaximum=std::numeric_limits<double>::infinity();
|
||||
if(openHelixRamp)
|
||||
{
|
||||
double radialMinimumSquared=0.0,radialMaximumSquared=0.0;
|
||||
for(std::size_t axis=0;axis<2;++axis)
|
||||
{
|
||||
const double center=feature.openShaftCenterMetres[axis];
|
||||
const double deltaMinimum=center<lo[axis]?lo[axis]-center
|
||||
:center>hi[axis]?center-hi[axis]:0.0;
|
||||
const double deltaMaximum=std::max(std::abs(lo[axis]-center),
|
||||
std::abs(hi[axis]-center));
|
||||
radialMinimumSquared+=deltaMinimum*deltaMinimum;
|
||||
radialMaximumSquared+=deltaMaximum*deltaMaximum;
|
||||
}
|
||||
const double radialMinimum=std::sqrt(radialMinimumSquared);
|
||||
const double radialMaximum=std::sqrt(radialMaximumSquared);
|
||||
const double centerline=feature.openShaftCenterlineRadiusMetres;
|
||||
const double nearestRingDistance=centerline<radialMinimum
|
||||
?radialMinimum-centerline:centerline>radialMaximum
|
||||
?centerline-radialMaximum:0.0;
|
||||
tubeMinimum=nearestRingDistance-radius;
|
||||
tubeMaximum=std::max(std::abs(radialMinimum-centerline),
|
||||
std::abs(radialMaximum-centerline))-radius;
|
||||
openHelixSupportCouldApply=tubeMinimum<0.0;
|
||||
const double startFloor=feature.openShaftStartFloorMetres;
|
||||
const double pitch=feature.openShaftPitchPerTurnMetres;
|
||||
const double turnCount=feature.openShaftTurnCount;
|
||||
if(!std::isfinite(tubeMinimum)||!std::isfinite(tubeMaximum)
|
||||
||!std::isfinite(startFloor)||!(pitch>0.0)
|
||||
||!(turnCount>=1.0))return {0.0,0.0,false};
|
||||
const FMetricInterval tube={tubeMinimum,tubeMaximum,true};
|
||||
const double grade=pitch/(6.283185307179586476925286766559
|
||||
*feature.openShaftCenterlineRadiusMetres);
|
||||
const double verticalScale=1.0/std::sqrt(1.0+grade*grade);
|
||||
double phaseMinimum=0.0,phaseMaximum=1.0;
|
||||
const double boxCenterX=(lo[0]+hi[0])*0.5;
|
||||
const double boxCenterY=(lo[1]+hi[1])*0.5;
|
||||
const double centerDx=boxCenterX-feature.openShaftCenterMetres[0];
|
||||
const double centerDy=boxCenterY-feature.openShaftCenterMetres[1];
|
||||
const double centerDistance=std::hypot(centerDx,centerDy);
|
||||
const double halfDiagonal=0.5*std::hypot(hi[0]-lo[0],hi[1]-lo[1]);
|
||||
if(centerDistance>halfDiagonal)
|
||||
{
|
||||
const double angle=std::atan2(feature.openShaftRotationSign
|
||||
*(feature.openShaftStartDirection[0]*centerDy
|
||||
-feature.openShaftStartDirection[1]*centerDx),
|
||||
feature.openShaftStartDirection[0]*centerDx
|
||||
+feature.openShaftStartDirection[1]*centerDy);
|
||||
constexpr double TwoPi=6.283185307179586476925286766559;
|
||||
double centerPhase=angle/TwoPi;
|
||||
const double angleRadius=std::asin(std::clamp(
|
||||
halfDiagonal/centerDistance,0.0,1.0));
|
||||
const double phaseRadius=angleRadius/TwoPi;
|
||||
// Point evaluation normalizes phase into [0,1). If this box
|
||||
// crosses the start ray, retain the full phase range; otherwise
|
||||
// unwrap its center into the same range as the point field.
|
||||
if(std::abs(angle)>angleRadius)
|
||||
{
|
||||
if(centerPhase<0.0)centerPhase+=1.0;
|
||||
phaseMinimum=centerPhase-phaseRadius;
|
||||
phaseMaximum=centerPhase+phaseRadius;
|
||||
}
|
||||
}
|
||||
double verticalMinimum=std::numeric_limits<double>::infinity();
|
||||
double verticalMaximum=-std::numeric_limits<double>::infinity();
|
||||
const double coordinateMinimum=(startFloor-hi[2])/pitch-phaseMaximum;
|
||||
const double coordinateMaximum=(startFloor-lo[2])/pitch-phaseMinimum;
|
||||
if(!std::isfinite(coordinateMinimum)||!std::isfinite(coordinateMaximum))
|
||||
return {0.0,0.0,false};
|
||||
const int lastTurn=std::max(0,static_cast<int>(turnCount)-1);
|
||||
const int firstPossibleTurn=std::clamp(
|
||||
static_cast<int>(std::floor(coordinateMinimum)),-1,lastTurn+1);
|
||||
const int lastPossibleTurn=std::clamp(
|
||||
static_cast<int>(std::floor(coordinateMaximum)),-1,lastTurn+1);
|
||||
const auto corridorInterval=[&](int turn)
|
||||
{
|
||||
const double floorMinimum=startFloor-pitch*(turn+phaseMaximum);
|
||||
const double floorMaximum=startFloor-pitch*(turn+phaseMinimum);
|
||||
const double floorToQueryMinimum=(floorMinimum-hi[2])*verticalScale;
|
||||
const double floorToQueryMaximum=(floorMaximum-lo[2])*verticalScale;
|
||||
const double queryToCeilingMinimum=(lo[2]-floorMaximum-height)
|
||||
*verticalScale;
|
||||
const double queryToCeilingMaximum=(hi[2]-floorMinimum-height)
|
||||
*verticalScale;
|
||||
const double turnMinimum=std::max(floorToQueryMinimum,
|
||||
queryToCeilingMinimum);
|
||||
const double turnMaximum=std::max(floorToQueryMaximum,
|
||||
queryToCeilingMaximum);
|
||||
return FMetricInterval{turnMinimum,turnMaximum,true};
|
||||
};
|
||||
for(int turn=firstPossibleTurn;turn<=lastPossibleTurn;++turn)
|
||||
{
|
||||
const int turnA=std::clamp(turn,0,lastTurn);
|
||||
const int turnB=std::clamp(turn+1,0,lastTurn);
|
||||
const FMetricInterval corridorA=corridorInterval(turnA);
|
||||
const FMetricInterval corridorB=corridorInterval(turnB);
|
||||
verticalMinimum=std::min(verticalMinimum,
|
||||
std::min(corridorA.minimum,corridorB.minimum));
|
||||
verticalMaximum=std::max(verticalMaximum,
|
||||
std::min(corridorA.maximum,corridorB.maximum));
|
||||
const double supportDepth=feature.edge->floorSupportDepthMicrometres
|
||||
/1000000.0;
|
||||
const double progressMinimum=turn+phaseMinimum;
|
||||
const double progressMaximum=turn+phaseMaximum;
|
||||
const double floorMinimum=startFloor-pitch*progressMaximum;
|
||||
const double floorMaximum=startFloor-pitch*progressMinimum;
|
||||
const double supportFloorMinimum=(floorMinimum-hi[2])*verticalScale;
|
||||
const double supportUndersideMinimum=(lo[2]-floorMaximum+supportDepth)
|
||||
*verticalScale;
|
||||
openHelixSupportLowerBound=std::min(openHelixSupportLowerBound,
|
||||
std::max({tubeMinimum,supportFloorMinimum,
|
||||
supportUndersideMinimum}));
|
||||
}
|
||||
const FMetricInterval vertical={verticalMinimum,verticalMaximum,true};
|
||||
const double width=feature.edge->smoothWidthMicrometres/1000000.0;
|
||||
featureVoid={SmoothMaxMetres(tube.minimum,vertical.minimum,width),
|
||||
SmoothMaxMetres(tube.maximum,vertical.maximum,width),true};
|
||||
}
|
||||
else
|
||||
{
|
||||
const std::array<double,3> inverseScale={1.0/radius,1.0/radius,
|
||||
1.0/height};
|
||||
const std::array<double,3> boxMinimum={lo[0]*inverseScale[0],
|
||||
@@ -5387,6 +5602,7 @@ public:
|
||||
const double width=feature.edge->smoothWidthMicrometres/1000000.0;
|
||||
featureVoid={SmoothMaxMetres(tube.minimum,floor.minimum,width),
|
||||
SmoothMaxMetres(tube.maximum,floor.maximum,width),true};
|
||||
}
|
||||
}
|
||||
else return {0.0,0.0,false};
|
||||
if(nativeLattice)
|
||||
@@ -5401,10 +5617,17 @@ public:
|
||||
&&lo[0]<=feature.boundsMaximumMicrometres[0]/1.0e6
|
||||
&&hi[1]>=feature.boundsMinimumMicrometres[1]/1.0e6
|
||||
&&lo[1]<=feature.boundsMaximumMicrometres[1]/1.0e6;
|
||||
if(supportDepth>0.0&&overlapsXY
|
||||
const bool supportCouldApply=overlapsXY
|
||||
&&(!feature.openShaftRamp||openHelixSupportCouldApply);
|
||||
if(supportDepth>0.0&&supportCouldApply
|
||||
&&hi[2]>=floorMinimum-supportDepth&&lo[2]<=floorMaximum)
|
||||
{
|
||||
double supportLower=std::max(-supportDepth,lo[2]-floorMaximum);
|
||||
if(feature.openShaftRamp)
|
||||
supportLower=openHelixSupportLowerBound;
|
||||
latticeFloorSupportLowerBound=std::min(latticeFloorSupportLowerBound,
|
||||
std::max(-supportDepth,lo[2]-floorMaximum));
|
||||
supportLower);
|
||||
}
|
||||
}
|
||||
if(!std::isfinite(featureVoid.minimum)||!std::isfinite(featureVoid.maximum))
|
||||
return {0.0,0.0,false};
|
||||
@@ -6962,6 +7185,10 @@ private:
|
||||
return;
|
||||
}
|
||||
if(!feature.edge)return;
|
||||
// The open helix is already carved with its exact cylindrical/turn
|
||||
// field above. Its four serialized control points are a compact record
|
||||
// envelope, not a walkable Bezier centerline for this legacy cap.
|
||||
if(feature.openShaftRamp)return;
|
||||
const auto bezier=[&](float t,std::size_t axis)
|
||||
{
|
||||
const float one=1.0f-t;
|
||||
@@ -7098,114 +7325,209 @@ private:
|
||||
const std::int32_t strateIndex=feature.room?feature.room->strateIndex
|
||||
:feature.edge?feature.edge->strateIndex:-1;
|
||||
if(strateIndex!=bound.StrateIndex)continue;
|
||||
bool insideFeatureBounds=true;
|
||||
for(std::size_t axis=0;axis<3;++axis)
|
||||
insideFeatureBounds=insideFeatureBounds
|
||||
&&point[axis]*1000000.0>=feature.boundsMinimumMicrometres[axis]
|
||||
&&point[axis]*1000000.0<=feature.boundsMaximumMicrometres[axis];
|
||||
if(!insideFeatureBounds)continue;
|
||||
double featureVoid=std::numeric_limits<double>::infinity();
|
||||
if(feature.room)
|
||||
{
|
||||
double normalizedSquared=0.0,meanRadius=0.0;
|
||||
for(std::size_t axis=0;axis<3;++axis)
|
||||
if(nativeLattice
|
||||
&&feature.room->stableId=="lattice/spine/shaft-core-v2")
|
||||
{
|
||||
const double radius=feature.halfExtentsMetres[axis];
|
||||
if(!(radius>0.0)){normalizedSquared=std::numeric_limits<double>::infinity();break;}
|
||||
const double delta=(point[axis]-feature.centerMetres[axis])/radius;
|
||||
normalizedSquared+=delta*delta;meanRadius+=radius/3.0;
|
||||
const double radius=feature.halfExtentsMetres[0];
|
||||
if(!(radius>0.0))continue;
|
||||
const double radial=std::hypot(
|
||||
point[0]-feature.centerMetres[0],
|
||||
point[1]-feature.centerMetres[1])-radius;
|
||||
const double bottom=feature.room->floorMicrometres/1000000.0-point[2];
|
||||
featureVoid=std::max(radial,bottom);
|
||||
}
|
||||
else
|
||||
{
|
||||
double normalizedSquared=0.0,meanRadius=0.0;
|
||||
for(std::size_t axis=0;axis<3;++axis)
|
||||
{
|
||||
const double radius=feature.halfExtentsMetres[axis];
|
||||
if(!(radius>0.0))
|
||||
{normalizedSquared=std::numeric_limits<double>::infinity();break;}
|
||||
const double delta=(point[axis]-feature.centerMetres[axis])/radius;
|
||||
normalizedSquared+=delta*delta;meanRadius+=radius/3.0;
|
||||
}
|
||||
const double ellipsoid=(std::sqrt(normalizedSquared)-1.0)*meanRadius;
|
||||
const double floor=feature.room->floorMicrometres/1000000.0-point[2];
|
||||
const double width=feature.room->smoothWidthMicrometres/1000000.0;
|
||||
featureVoid=SmoothMaxMetres(ellipsoid,floor,width);
|
||||
}
|
||||
const double ellipsoid=(std::sqrt(normalizedSquared)-1.0)*meanRadius;
|
||||
const double floor=feature.room->floorMicrometres/1000000.0-point[2];
|
||||
const double width=feature.room->smoothWidthMicrometres/1000000.0;
|
||||
featureVoid=SmoothMaxMetres(ellipsoid,floor,width);
|
||||
}
|
||||
else if(feature.edge&&!feature.segments.empty())
|
||||
{
|
||||
const bool openHelixRamp=nativeLattice&&feature.openShaftRamp;
|
||||
double tube=std::numeric_limits<double>::infinity();
|
||||
double nearestFloor=0.0,nearestGrade=0.0,nearestRadial=0.0;
|
||||
double openHelixVerticalSdf=std::numeric_limits<double>::infinity();
|
||||
double openHelixPhase=0.0;
|
||||
int openHelixTurnA=0,openHelixTurnB=0;
|
||||
double openHelixVerticalScale=1.0;
|
||||
const double radius=feature.edge->boreRadiusMicrometres/1000000.0;
|
||||
const double height=feature.edge->boreHeightMicrometres/1000000.0;
|
||||
if(!(radius>0.0&&height>0.0))continue;
|
||||
const double minScale=std::min(radius,height);
|
||||
std::uint32_t nearestSegment=std::numeric_limits<std::uint32_t>::max();
|
||||
const auto evaluateSegment=[&](std::uint32_t segmentIndex)
|
||||
if(openHelixRamp)
|
||||
{
|
||||
if(segmentIndex>=feature.segments.size())return;
|
||||
const auto& segment=feature.segments[segmentIndex];
|
||||
double lengthSquared=0.0,dot=0.0;
|
||||
for(std::size_t axis=0;axis<3;++axis)
|
||||
const double dx=point[0]-feature.openShaftCenterMetres[0];
|
||||
const double dy=point[1]-feature.openShaftCenterMetres[1];
|
||||
const double shaftRadial=std::hypot(dx,dy);
|
||||
nearestRadial=std::abs(shaftRadial
|
||||
-feature.openShaftCenterlineRadiusMetres);
|
||||
tube=nearestRadial-radius;
|
||||
const double phase=std::atan2(feature.openShaftRotationSign
|
||||
*(feature.openShaftStartDirection[0]*dy
|
||||
-feature.openShaftStartDirection[1]*dx),
|
||||
feature.openShaftStartDirection[0]*dx
|
||||
+feature.openShaftStartDirection[1]*dy);
|
||||
constexpr double TwoPi=6.283185307179586476925286766559;
|
||||
openHelixPhase=(phase<0.0?phase+TwoPi:phase)/TwoPi;
|
||||
const double pitch=feature.openShaftPitchPerTurnMetres;
|
||||
if(!(pitch>0.0)||!(feature.openShaftTurnCount>=1.0))continue;
|
||||
const double grade=pitch/(TwoPi
|
||||
*feature.openShaftCenterlineRadiusMetres);
|
||||
openHelixVerticalScale=1.0/std::sqrt(1.0+grade*grade);
|
||||
const double turnCoordinate=(feature.openShaftStartFloorMetres-point[2])
|
||||
/pitch-openHelixPhase;
|
||||
const int turnFloor=static_cast<int>(std::floor(turnCoordinate));
|
||||
const int lastTurn=std::max(0,
|
||||
static_cast<int>(feature.openShaftTurnCount)-1);
|
||||
openHelixTurnA=std::clamp(turnFloor,0,lastTurn);
|
||||
openHelixTurnB=std::clamp(turnFloor+1,0,lastTurn);
|
||||
const auto floorForTurn=[&](int turn)
|
||||
{
|
||||
const double delta=segment.end[axis]-segment.start[axis];
|
||||
lengthSquared+=delta*delta;
|
||||
dot+=(point[axis]-segment.start[axis])*delta;
|
||||
}
|
||||
const double t=lengthSquared>0.0
|
||||
?std::clamp(dot/lengthSquared,0.0,1.0):0.0;
|
||||
double radialSquared=0.0,vertical=0.0;
|
||||
for(std::size_t axis=0;axis<3;++axis)
|
||||
{
|
||||
const double delta=point[axis]-(segment.start[axis]
|
||||
+t*(segment.end[axis]-segment.start[axis]));
|
||||
if(axis<2)radialSquared+=delta*delta;
|
||||
else vertical=delta;
|
||||
}
|
||||
const double normalized=std::sqrt(radialSquared/(radius*radius)
|
||||
+vertical*vertical/(height*height));
|
||||
const double distance=(normalized-1.0)*std::min(radius,height);
|
||||
if(distance<tube||(distance==tube&&segmentIndex<nearestSegment))
|
||||
{
|
||||
tube=distance;nearestSegment=segmentIndex;
|
||||
nearestFloor=segment.start[2]+t*(segment.end[2]-segment.start[2]);
|
||||
nearestRadial=std::sqrt(radialSquared);
|
||||
const double horizontal=std::hypot(
|
||||
segment.end[0]-segment.start[0],segment.end[1]-segment.start[1]);
|
||||
nearestGrade=horizontal>1.0e-9
|
||||
?(segment.end[2]-segment.start[2])/horizontal:0.0;
|
||||
}
|
||||
};
|
||||
const auto nodeLowerBound=[&](const FCavernRuntimeFeature::FSegmentNode& node)
|
||||
{
|
||||
double gapSquared=0.0;
|
||||
for(std::size_t axis=0;axis<3;++axis)
|
||||
{
|
||||
const double scale=axis==2?height:radius;
|
||||
const double q=point[axis]/scale;
|
||||
const double low=node.minimum[axis]/scale;
|
||||
const double high=node.maximum[axis]/scale;
|
||||
const double gap=q<low?low-q:q>high?q-high:0.0;
|
||||
gapSquared+=gap*gap;
|
||||
}
|
||||
return (std::sqrt(gapSquared)-1.0)*minScale;
|
||||
};
|
||||
if(!feature.segmentNodes.empty())
|
||||
{
|
||||
const auto visitNode=[&](const auto& self,int nodeIndex)->void
|
||||
{
|
||||
if(nodeIndex<0||static_cast<std::size_t>(nodeIndex)>=feature.segmentNodes.size())return;
|
||||
const auto& node=feature.segmentNodes[static_cast<std::size_t>(nodeIndex)];
|
||||
if(nodeLowerBound(node)>tube)return;
|
||||
if(node.left<0)
|
||||
{
|
||||
for(std::size_t offset=node.first;offset<node.end;++offset)
|
||||
evaluateSegment(feature.segmentOrder[offset]);
|
||||
return;
|
||||
}
|
||||
const double leftBound=nodeLowerBound(feature.segmentNodes[
|
||||
static_cast<std::size_t>(node.left)]);
|
||||
const double rightBound=nodeLowerBound(feature.segmentNodes[
|
||||
static_cast<std::size_t>(node.right)]);
|
||||
if(leftBound<=rightBound)
|
||||
{self(self,node.left);self(self,node.right);}
|
||||
else {self(self,node.right);self(self,node.left);}
|
||||
return feature.openShaftStartFloorMetres
|
||||
-pitch*(turn+openHelixPhase);
|
||||
};
|
||||
visitNode(visitNode,0);
|
||||
const auto corridorSdf=[&](int turn)
|
||||
{
|
||||
const double floorZ=floorForTurn(turn);
|
||||
return std::max((floorZ-point[2])*openHelixVerticalScale,
|
||||
(point[2]-(floorZ+height))*openHelixVerticalScale);
|
||||
};
|
||||
openHelixVerticalSdf=std::min(corridorSdf(openHelixTurnA),
|
||||
corridorSdf(openHelixTurnB));
|
||||
}
|
||||
else
|
||||
{
|
||||
std::uint32_t nearestSegment=std::numeric_limits<std::uint32_t>::max();
|
||||
const auto evaluateSegment=[&](std::uint32_t segmentIndex)
|
||||
{
|
||||
if(segmentIndex>=feature.segments.size())return;
|
||||
const auto& segment=feature.segments[segmentIndex];
|
||||
double lengthSquared=0.0,dot=0.0;
|
||||
for(std::size_t axis=0;axis<3;++axis)
|
||||
{
|
||||
const double delta=segment.end[axis]-segment.start[axis];
|
||||
lengthSquared+=delta*delta;
|
||||
dot+=(point[axis]-segment.start[axis])*delta;
|
||||
}
|
||||
const double t=lengthSquared>0.0
|
||||
?std::clamp(dot/lengthSquared,0.0,1.0):0.0;
|
||||
double radialSquared=0.0,vertical=0.0;
|
||||
for(std::size_t axis=0;axis<3;++axis)
|
||||
{
|
||||
const double delta=point[axis]-(segment.start[axis]
|
||||
+t*(segment.end[axis]-segment.start[axis]));
|
||||
if(axis<2)radialSquared+=delta*delta;
|
||||
else vertical=delta;
|
||||
}
|
||||
const double normalized=std::sqrt(radialSquared/(radius*radius)
|
||||
+vertical*vertical/(height*height));
|
||||
const double distance=(normalized-1.0)*minScale;
|
||||
if(distance<tube||(distance==tube&&segmentIndex<nearestSegment))
|
||||
{
|
||||
tube=distance;nearestSegment=segmentIndex;
|
||||
nearestFloor=segment.start[2]+t*(segment.end[2]-segment.start[2]);
|
||||
nearestRadial=std::sqrt(radialSquared);
|
||||
const double horizontal=std::hypot(
|
||||
segment.end[0]-segment.start[0],segment.end[1]-segment.start[1]);
|
||||
nearestGrade=horizontal>1.0e-9
|
||||
?(segment.end[2]-segment.start[2])/horizontal:0.0;
|
||||
}
|
||||
};
|
||||
const auto nodeLowerBound=[&](const FCavernRuntimeFeature::FSegmentNode& node)
|
||||
{
|
||||
double gapSquared=0.0;
|
||||
for(std::size_t axis=0;axis<3;++axis)
|
||||
{
|
||||
const double scale=axis==2?height:radius;
|
||||
const double q=point[axis]/scale;
|
||||
const double low=node.minimum[axis]/scale;
|
||||
const double high=node.maximum[axis]/scale;
|
||||
const double gap=q<low?low-q:q>high?q-high:0.0;
|
||||
gapSquared+=gap*gap;
|
||||
}
|
||||
return (std::sqrt(gapSquared)-1.0)*minScale;
|
||||
};
|
||||
if(!feature.segmentNodes.empty())
|
||||
{
|
||||
const auto visitNode=[&](const auto& self,int nodeIndex)->void
|
||||
{
|
||||
if(nodeIndex<0||static_cast<std::size_t>(nodeIndex)>=feature.segmentNodes.size())return;
|
||||
const auto& node=feature.segmentNodes[static_cast<std::size_t>(nodeIndex)];
|
||||
if(nodeLowerBound(node)>tube)return;
|
||||
if(node.left<0)
|
||||
{
|
||||
for(std::size_t offset=node.first;offset<node.end;++offset)
|
||||
evaluateSegment(feature.segmentOrder[offset]);
|
||||
return;
|
||||
}
|
||||
const double leftBound=nodeLowerBound(feature.segmentNodes[
|
||||
static_cast<std::size_t>(node.left)]);
|
||||
const double rightBound=nodeLowerBound(feature.segmentNodes[
|
||||
static_cast<std::size_t>(node.right)]);
|
||||
if(leftBound<=rightBound)
|
||||
{self(self,node.left);self(self,node.right);}
|
||||
else {self(self,node.right);self(self,node.left);}
|
||||
};
|
||||
visitNode(visitNode,0);
|
||||
}
|
||||
else for(std::uint32_t segment=0;segment<feature.segments.size();++segment)
|
||||
evaluateSegment(segment);
|
||||
}
|
||||
else for(std::uint32_t segment=0;segment<feature.segments.size();++segment)
|
||||
evaluateSegment(segment);
|
||||
const double supportDepth=feature.edge->floorSupportDepthMicrometres/1000000.0;
|
||||
if(nativeLattice&&supportDepth>0.0&&nearestRadial<radius-0.12
|
||||
&&point[2]<nearestFloor&&nearestFloor-point[2]<=supportDepth)
|
||||
latticeFloorSupportMinimum=std::min(latticeFloorSupportMinimum,
|
||||
(point[2]-nearestFloor)/std::sqrt(1.0+nearestGrade*nearestGrade));
|
||||
const double floor=(nearestFloor-point[2])
|
||||
/std::sqrt(1.0+nearestGrade*nearestGrade);
|
||||
if(nativeLattice&&supportDepth>0.0)
|
||||
{
|
||||
if(openHelixRamp)
|
||||
{
|
||||
const auto supportSdf=[&](int turn)
|
||||
{
|
||||
const double floorZ=feature.openShaftStartFloorMetres
|
||||
-feature.openShaftPitchPerTurnMetres
|
||||
*(turn+openHelixPhase);
|
||||
const double vertical=std::max(
|
||||
(floorZ-point[2])*openHelixVerticalScale,
|
||||
(point[2]-(floorZ-supportDepth))
|
||||
*openHelixVerticalScale);
|
||||
return std::max(nearestRadial-radius,vertical);
|
||||
};
|
||||
latticeFloorSupportMinimum=std::min(latticeFloorSupportMinimum,
|
||||
std::min(supportSdf(openHelixTurnA),
|
||||
supportSdf(openHelixTurnB)));
|
||||
}
|
||||
else if(nearestRadial<radius-0.12&&point[2]<nearestFloor
|
||||
&&nearestFloor-point[2]<=supportDepth)
|
||||
latticeFloorSupportMinimum=std::min(latticeFloorSupportMinimum,
|
||||
(point[2]-nearestFloor)/std::sqrt(1.0+nearestGrade*nearestGrade));
|
||||
}
|
||||
const double width=feature.edge->smoothWidthMicrometres/1000000.0;
|
||||
featureVoid=SmoothMaxMetres(tube,floor,width);
|
||||
if(openHelixRamp)
|
||||
featureVoid=SmoothMaxMetres(tube,openHelixVerticalSdf,width);
|
||||
else
|
||||
{
|
||||
const double floor=(nearestFloor-point[2])
|
||||
/std::sqrt(1.0+nearestGrade*nearestGrade);
|
||||
featureVoid=SmoothMaxMetres(tube,floor,width);
|
||||
}
|
||||
}
|
||||
if(!std::isfinite(featureVoid))continue;
|
||||
if(!std::isfinite(caveVoid))caveVoid=featureVoid;
|
||||
|
||||
Reference in new issue
Block a user