feat(core): native lattice generator, slice 1 -- helix-ramp spine + small maze (L58); research R27
Core.Lattice.PrimitiveField v1, opt-in per strate (legacy Maze/VerticalShaft stay legacy): resolves the real arrival and departure mouths first, plans a mandatory spine, then maze cells and two dead ends (9 rooms, 9 edges, stable IDs). The spine descends 20 m through four helix turns (6 m radius, 3 m clear width, 5 m pitch, 2.6 m clearance; max grade 10.0 deg); each feature is one filleted void (0.57 m measured). Final-field reachability passes at 883 stations; an orphaned shaft control is rejected; exact intervals (0 misses) and a whole-surface audit with 0 flags. Whole view 1.69 s (above the 1 s target). fast/medium/full PASS. Research R27 (self-checked): static stream-to-pool water design. 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
1e3e04bbac
commit
6c6cc37e1d
25 files changed
+280497
-117
No files matched your search
@@ -378,10 +378,10 @@ public:
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(Stratum.generatorKind=="Caves"
|
||||
&&Stratum.recipeId=="Core.Caves.PrimitiveField"
|
||||
&&Stratum.recipeVersion==1
|
||||
&&Stratum.passageCapabilities.hasNativeCaveField)
|
||||
else if(VoxelForgeCore::HasNativeGraphField(Stratum)
|
||||
&&(VoxelForgeCore::IsNativeCaveFieldStratum(Stratum)
|
||||
?Stratum.passageCapabilities.hasNativeCaveField
|
||||
:Stratum.passageCapabilities.hasNativeLatticeField))
|
||||
{
|
||||
Bound.Kind=FBoundStratumField::EKind::Caves;
|
||||
Bound.Evaluator.emplace<FCavesFieldAdapter>(
|
||||
@@ -1819,14 +1819,28 @@ public:
|
||||
float ApplyResolvedStrateComposition(float density,float X,float Y,float Z) const
|
||||
{
|
||||
const float graphDensity=ApplyResolvedCavernGraph(density,X,Y,Z);
|
||||
const float composed=ApplyResolvedStratePassageCarves(
|
||||
ApplyResolvedStrateBoundaryBand(graphDensity,X,Y,Z),X,Y,Z);
|
||||
const float stamped=StampRuntime.empty()?composed:ApplyResolvedSdfStamps(composed,X,Y,Z);
|
||||
const auto* bound=FindBoundStratumForManagerZ(Z);
|
||||
const bool nativeLattice=bound&&bound->Resolved
|
||||
&&VoxelForgeCore::IsNativeLatticeFieldStratum(*bound->Resolved)
|
||||
&&bound->Resolved->passageCapabilities.hasNativeLatticeField;
|
||||
// The native lattice field is a complete host-rock SDF: it owns both the
|
||||
// voids and the surrounding solid. Replacing its air-side distances with
|
||||
// the generic stratum transition distance creates a discontinuity at a
|
||||
// mouth that crosses a wide transition band. Keep that opt-in field whole;
|
||||
// its host-rock value already supplies the ceiling and bedrock.
|
||||
const float boundary=nativeLattice?graphDensity
|
||||
:ApplyResolvedStrateBoundaryBand(graphDensity,X,Y,Z);
|
||||
// The native lattice owns its resolved arrival/departure portals and their
|
||||
// supported spine. Reapplying legacy passage carves here can erase the
|
||||
// tread immediately above a broad legacy mouth or support column.
|
||||
const float composed=nativeLattice?boundary
|
||||
:ApplyResolvedStratePassageCarves(boundary,X,Y,Z);
|
||||
const float stamped=StampRuntime.empty()?composed:ApplyResolvedSdfStamps(composed,X,Y,Z);
|
||||
if(bound&&bound->Kind==FBoundStratumField::EKind::Caves
|
||||
&&bound->Resolved&&bound->Resolved->recipeId=="Core.Caves.PrimitiveField"
|
||||
&&bound->Resolved->recipeVersion==1
|
||||
&&bound->Resolved->passageCapabilities.hasNativeCaveField)
|
||||
&&bound->Resolved&&VoxelForgeCore::HasNativeGraphField(*bound->Resolved)
|
||||
&&(VoxelForgeCore::IsNativeCaveFieldStratum(*bound->Resolved)
|
||||
?bound->Resolved->passageCapabilities.hasNativeCaveField
|
||||
:bound->Resolved->passageCapabilities.hasNativeLatticeField))
|
||||
return stamped;
|
||||
// The legacy landmark demo retains its compatibility floor cap. Native caves
|
||||
// already combine each floor with its wall before the single void carve above.
|
||||
@@ -3040,7 +3054,9 @@ public:
|
||||
if(NativeFloatingIslandDensityAt(X,Y,Z,Density))return "FloatingIslands-native";
|
||||
const auto* Bound=FindBoundStratumForZ(Z);
|
||||
if(Bound&&Bound->Kind==FBoundStratumField::EKind::Caves
|
||||
&&NativeSurfaceDensityAt(X,Y,Z,Density))return "Caves-native";
|
||||
&&NativeSurfaceDensityAt(X,Y,Z,Density))
|
||||
return Bound->Resolved&&VoxelForgeCore::IsNativeLatticeFieldStratum(*Bound->Resolved)
|
||||
?"Lattice-native":"Caves-native";
|
||||
if(NativeSurfaceDensityAt(X,Y,Z,Density))return "SurfaceWorld-native";
|
||||
if(UsesLegacySurfaceField(X,Y,Z))return "SurfaceWorld-legacy";
|
||||
if(Bound&&Bound->Resolved)
|
||||
@@ -3162,7 +3178,7 @@ public:
|
||||
}
|
||||
bool bHasLegacySurfaceSample=false;
|
||||
bool bHasNativeIslandSample=false;
|
||||
bool bHasNativeCavesSample=false;
|
||||
bool bHasNativeGraphSample=false;
|
||||
std::array<float,8> NativeIslandDensity{};
|
||||
std::array<bool,8> HasNativeIslandSample{};
|
||||
for(int32 Lane=0;Lane<Count;++Lane)
|
||||
@@ -3172,13 +3188,13 @@ public:
|
||||
const float SampleZ=static_cast<float>(Z);
|
||||
bHasLegacySurfaceSample|=UsesLegacySurfaceField(SampleX,SampleY,SampleZ);
|
||||
const auto* bound=FindBoundStratumForZ(SampleZ);
|
||||
bHasNativeCavesSample|=bound
|
||||
bHasNativeGraphSample|=bound
|
||||
&&bound->Kind==FBoundStratumField::EKind::Caves;
|
||||
HasNativeIslandSample[static_cast<std::size_t>(Lane)]=NativeFloatingIslandDensityAt(
|
||||
SampleX,SampleY,SampleZ,NativeIslandDensity[static_cast<std::size_t>(Lane)]);
|
||||
bHasNativeIslandSample|=HasNativeIslandSample[static_cast<std::size_t>(Lane)];
|
||||
}
|
||||
if(bHasLegacySurfaceSample||bHasNativeIslandSample||bHasNativeCavesSample)
|
||||
if(bHasLegacySurfaceSample||bHasNativeIslandSample||bHasNativeGraphSample)
|
||||
{
|
||||
for(int32 Lane=0;Lane<Count;++Lane)
|
||||
{
|
||||
@@ -3315,14 +3331,25 @@ private:
|
||||
{
|
||||
const VoxelForgeCore::ResolvedCavernRoomV1* room=nullptr;
|
||||
const VoxelForgeCore::ResolvedCavernEdgeV1* edge=nullptr;
|
||||
bool nativeCaves=false;
|
||||
bool nativeGraph=false;
|
||||
std::array<std::int64_t,3> boundsMinimumMicrometres{},boundsMaximumMicrometres{};
|
||||
std::array<double,3> centerMetres{},halfExtentsMetres{};
|
||||
struct FSegment
|
||||
{
|
||||
std::array<double,3> start{},end{},minimum{},maximum{};
|
||||
};
|
||||
struct FSegmentNode
|
||||
{
|
||||
std::array<double,3> minimum{},maximum{};
|
||||
std::size_t first=0,end=0;
|
||||
int left=-1,right=-1;
|
||||
};
|
||||
std::vector<FSegment> segments;
|
||||
std::vector<std::uint32_t> segmentOrder;
|
||||
std::vector<FSegmentNode> segmentNodes;
|
||||
double minimumFloorMetres=std::numeric_limits<double>::infinity();
|
||||
double maximumFloorMetres=-std::numeric_limits<double>::infinity();
|
||||
double maximumSegmentGrade=0.0;
|
||||
std::array<float,3> centerVoxels{},halfExtentsVoxels{};
|
||||
std::array<std::array<float,3>,4> controlPointsVoxels{};
|
||||
float floorVoxels=0.0f;
|
||||
@@ -3753,22 +3780,22 @@ private:
|
||||
if(!ResolvedLayout||!Owner||Owner->CoreVoxelSizeMicrons==0
|
||||
||ResolvedLayout->cavernGraphVocabularyVersion
|
||||
!=VoxelForgeCore::ResolvedLayoutCavernGraphVocabularyVersionV1)return;
|
||||
const auto isNativeCavesStrate=[this](std::int32_t strateIndex)
|
||||
const auto isNativeGraphStrate=[this](std::int32_t strateIndex)
|
||||
{
|
||||
const auto found=std::find_if(ResolvedLayout->strates.begin(),
|
||||
ResolvedLayout->strates.end(),[strateIndex](const auto& stratum)
|
||||
{return stratum.strateIndex==strateIndex;});
|
||||
return found!=ResolvedLayout->strates.end()
|
||||
&&found->generatorKind=="Caves"
|
||||
&&found->recipeId=="Core.Caves.PrimitiveField"
|
||||
&&found->recipeVersion==1
|
||||
&&found->passageCapabilities.hasNativeCaveField;
|
||||
&&VoxelForgeCore::HasNativeGraphField(*found)
|
||||
&&(VoxelForgeCore::IsNativeCaveFieldStratum(*found)
|
||||
?found->passageCapabilities.hasNativeCaveField
|
||||
:found->passageCapabilities.hasNativeLatticeField);
|
||||
};
|
||||
const float micronsPerVoxel=static_cast<float>(Owner->CoreVoxelSizeMicrons);
|
||||
for(const auto& room:ResolvedLayout->cavernRooms)
|
||||
{
|
||||
FCavernRuntimeFeature runtime;runtime.room=&room;
|
||||
runtime.nativeCaves=isNativeCavesStrate(room.strateIndex);
|
||||
runtime.nativeGraph=isNativeGraphStrate(room.strateIndex);
|
||||
runtime.boundsMinimumMicrometres=room.boundsMinimumMicrometres;
|
||||
runtime.boundsMaximumMicrometres=room.boundsMaximumMicrometres;
|
||||
for(std::size_t axis=0;axis<3;++axis)
|
||||
@@ -3794,7 +3821,7 @@ private:
|
||||
for(const auto& edge:ResolvedLayout->cavernEdges)
|
||||
{
|
||||
FCavernRuntimeFeature runtime;runtime.edge=&edge;
|
||||
runtime.nativeCaves=isNativeCavesStrate(edge.strateIndex);
|
||||
runtime.nativeGraph=isNativeGraphStrate(edge.strateIndex);
|
||||
runtime.boundsMinimumMicrometres=edge.boundsMinimumMicrometres;
|
||||
runtime.boundsMaximumMicrometres=edge.boundsMaximumMicrometres;
|
||||
for(std::size_t point=0;point<4;++point)
|
||||
@@ -3827,8 +3854,69 @@ private:
|
||||
segment.minimum[axis]=std::min(segment.start[axis],segment.end[axis]);
|
||||
segment.maximum[axis]=std::max(segment.start[axis],segment.end[axis]);
|
||||
}
|
||||
runtime.minimumFloorMetres=std::min(runtime.minimumFloorMetres,
|
||||
std::min(segment.start[2],segment.end[2]));
|
||||
runtime.maximumFloorMetres=std::max(runtime.maximumFloorMetres,
|
||||
std::max(segment.start[2],segment.end[2]));
|
||||
const double horizontal=std::hypot(segment.end[0]-segment.start[0],
|
||||
segment.end[1]-segment.start[1]);
|
||||
if(horizontal>1.0e-9)
|
||||
runtime.maximumSegmentGrade=std::max(runtime.maximumSegmentGrade,
|
||||
std::abs((segment.end[2]-segment.start[2])/horizontal));
|
||||
runtime.segments.push_back(segment);
|
||||
}
|
||||
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);
|
||||
if(!runtime.segmentOrder.empty())
|
||||
{
|
||||
const auto buildSegmentNode=[&](const auto& self,std::size_t first,
|
||||
std::size_t end)->int
|
||||
{
|
||||
FCavernRuntimeFeature::FSegmentNode node;
|
||||
node.first=first;node.end=end;
|
||||
node.minimum.fill(std::numeric_limits<double>::infinity());
|
||||
node.maximum.fill(-std::numeric_limits<double>::infinity());
|
||||
for(std::size_t offset=first;offset<end;++offset)
|
||||
{
|
||||
const auto& segment=runtime.segments[
|
||||
runtime.segmentOrder[offset]];
|
||||
for(std::size_t axis=0;axis<3;++axis)
|
||||
{
|
||||
node.minimum[axis]=std::min(node.minimum[axis],segment.minimum[axis]);
|
||||
node.maximum[axis]=std::max(node.maximum[axis],segment.maximum[axis]);
|
||||
}
|
||||
}
|
||||
const int nodeIndex=static_cast<int>(runtime.segmentNodes.size());
|
||||
runtime.segmentNodes.push_back(node);
|
||||
if(end-first>4)
|
||||
{
|
||||
std::size_t splitAxis=0;
|
||||
for(std::size_t axis=1;axis<3;++axis)
|
||||
if(node.maximum[axis]-node.minimum[axis]
|
||||
>node.maximum[splitAxis]-node.minimum[splitAxis])
|
||||
splitAxis=axis;
|
||||
const std::size_t middle=first+(end-first)/2;
|
||||
std::nth_element(runtime.segmentOrder.begin()+first,
|
||||
runtime.segmentOrder.begin()+middle,
|
||||
runtime.segmentOrder.begin()+end,
|
||||
[&](std::uint32_t a,std::uint32_t b)
|
||||
{
|
||||
const auto& left=runtime.segments[a];
|
||||
const auto& right=runtime.segments[b];
|
||||
const double ca=left.minimum[splitAxis]+left.maximum[splitAxis];
|
||||
const double cb=right.minimum[splitAxis]+right.maximum[splitAxis];
|
||||
return ca==cb?a<b:ca<cb;
|
||||
});
|
||||
const int left=self(self,first,middle);
|
||||
const int right=self(self,middle,end);
|
||||
runtime.segmentNodes[nodeIndex].left=left;
|
||||
runtime.segmentNodes[nodeIndex].right=right;
|
||||
}
|
||||
return nodeIndex;
|
||||
};
|
||||
buildSegmentNode(buildSegmentNode,0,runtime.segmentOrder.size());
|
||||
}
|
||||
CavernRuntime.push_back(runtime);
|
||||
}
|
||||
std::stable_sort(CavernRuntime.begin(),CavernRuntime.end(),[](const auto& a,const auto& b)
|
||||
@@ -3838,8 +3926,8 @@ private:
|
||||
const std::int32_t rightStrate=b.room?b.room->strateIndex
|
||||
:b.edge?b.edge->strateIndex:-1;
|
||||
if(leftStrate!=rightStrate)return leftStrate<rightStrate;
|
||||
if(a.nativeCaves!=b.nativeCaves)return a.nativeCaves;
|
||||
if(!a.nativeCaves)return false;
|
||||
if(a.nativeGraph!=b.nativeGraph)return a.nativeGraph;
|
||||
if(!a.nativeGraph)return false;
|
||||
const std::string& left=a.room?a.room->stableId:a.edge->stableId;
|
||||
const std::string& right=b.room?b.room->stableId:b.edge->stableId;
|
||||
return left<right;
|
||||
@@ -5106,9 +5194,10 @@ public:
|
||||
}
|
||||
else if(Candidate->Kind==FBoundStratumField::EKind::Caves)
|
||||
{
|
||||
if(!Candidate->Resolved||Candidate->Resolved->recipeId!="Core.Caves.PrimitiveField"
|
||||
||Candidate->Resolved->recipeVersion!=1
|
||||
||!Candidate->Resolved->passageCapabilities.hasNativeCaveField
|
||||
if(!Candidate->Resolved||!VoxelForgeCore::HasNativeGraphField(*Candidate->Resolved)
|
||||
||(VoxelForgeCore::IsNativeCaveFieldStratum(*Candidate->Resolved)
|
||||
?!Candidate->Resolved->passageCapabilities.hasNativeCaveField
|
||||
:!Candidate->Resolved->passageCapabilities.hasNativeLatticeField)
|
||||
||!std::get_if<FCavesFieldAdapter>(&Candidate->Evaluator))return false;
|
||||
}
|
||||
else return false;
|
||||
@@ -5144,6 +5233,11 @@ public:
|
||||
FMetricInterval caveVoid{std::numeric_limits<double>::infinity(),
|
||||
std::numeric_limits<double>::infinity(),true};
|
||||
bool haveFeature=false;
|
||||
double latticeFloorSupportLowerBound=std::numeric_limits<double>::infinity();
|
||||
const auto* resolvedStratum=FindResolvedLayoutStratum(strateIndex);
|
||||
const bool nativeLattice=resolvedStratum
|
||||
&&VoxelForgeCore::IsNativeLatticeFieldStratum(*resolvedStratum)
|
||||
&&resolvedStratum->passageCapabilities.hasNativeLatticeField;
|
||||
for(const std::uint32_t index:candidates)
|
||||
{
|
||||
if(index>=CavernRuntime.size())continue;
|
||||
@@ -5185,18 +5279,32 @@ public:
|
||||
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();
|
||||
double floorMinimum=std::numeric_limits<double>::infinity();
|
||||
double floorMaximum=-std::numeric_limits<double>::infinity();
|
||||
double maximumGrade=0.0;
|
||||
for(const auto& segment:feature.segments)
|
||||
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],
|
||||
lo[1]*inverseScale[1],lo[2]*inverseScale[2]};
|
||||
const std::array<double,3> boxMaximum={hi[0]*inverseScale[0],
|
||||
hi[1]*inverseScale[1],hi[2]*inverseScale[2]};
|
||||
const double scale=std::min(radius,height);
|
||||
const auto nodeLowerBound=[&](const FCavernRuntimeFeature::FSegmentNode& node)
|
||||
{
|
||||
const std::array<double,3> inverseScale={1.0/radius,1.0/radius,
|
||||
1.0/height};
|
||||
std::array<double,3> boxMinimum{},boxMaximum{},segmentStart{},segmentEnd{};
|
||||
double gapSquared=0.0;
|
||||
for(std::size_t axis=0;axis<3;++axis)
|
||||
{
|
||||
const double low=node.minimum[axis]*inverseScale[axis];
|
||||
const double high=node.maximum[axis]*inverseScale[axis];
|
||||
const double gap=high<boxMinimum[axis]?boxMinimum[axis]-high
|
||||
:low>boxMaximum[axis]?low-boxMaximum[axis]:0.0;
|
||||
gapSquared+=gap*gap;
|
||||
}
|
||||
return (std::sqrt(gapSquared)-1.0)*scale;
|
||||
};
|
||||
const auto evaluateSegment=[&](std::uint32_t segmentIndex)
|
||||
{
|
||||
const auto& segment=feature.segments[segmentIndex];
|
||||
std::array<double,3> segmentStart{},segmentEnd{};
|
||||
for(std::size_t axis=0;axis<3;++axis)
|
||||
{
|
||||
boxMinimum[axis]=lo[axis]*inverseScale[axis];
|
||||
boxMaximum[axis]=hi[axis]*inverseScale[axis];
|
||||
segmentStart[axis]=segment.start[axis]*inverseScale[axis];
|
||||
segmentEnd[axis]=segment.end[axis]*inverseScale[axis];
|
||||
}
|
||||
@@ -5237,19 +5345,33 @@ public:
|
||||
farthestSquared+=farthest*farthest;
|
||||
}
|
||||
const double distanceUpper=std::sqrt(farthestSquared);
|
||||
const double scale=std::min(radius,height);
|
||||
tubeMinimum=std::min(tubeMinimum,(distanceLower-1.0)*scale);
|
||||
tubeMaximum=std::min(tubeMaximum,(distanceUpper-1.0)*scale);
|
||||
floorMinimum=std::min(floorMinimum,
|
||||
std::min(segment.start[2],segment.end[2]));
|
||||
floorMaximum=std::max(floorMaximum,
|
||||
std::max(segment.start[2],segment.end[2]));
|
||||
const double horizontal=std::hypot(segment.end[0]-segment.start[0],
|
||||
segment.end[1]-segment.start[1]);
|
||||
if(horizontal>1.0e-9)
|
||||
maximumGrade=std::max(maximumGrade,
|
||||
std::abs((segment.end[2]-segment.start[2])/horizontal));
|
||||
}
|
||||
};
|
||||
if(feature.segmentNodes.empty())return {0.0,0.0,false};
|
||||
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)>tubeMaximum)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);
|
||||
const double floorMinimum=feature.minimumFloorMetres;
|
||||
const double floorMaximum=feature.maximumFloorMetres;
|
||||
const double maximumGrade=feature.maximumSegmentGrade;
|
||||
if(!std::isfinite(tubeMinimum)||!std::isfinite(tubeMaximum)
|
||||
||!std::isfinite(floorMinimum)||!std::isfinite(floorMaximum))
|
||||
return {0.0,0.0,false};
|
||||
@@ -5267,6 +5389,23 @@ public:
|
||||
SmoothMaxMetres(tube.maximum,floor.maximum,width),true};
|
||||
}
|
||||
else return {0.0,0.0,false};
|
||||
if(nativeLattice)
|
||||
{
|
||||
const double supportDepth=feature.edge
|
||||
?feature.edge->floorSupportDepthMicrometres/1000000.0:0.0;
|
||||
const double floorMinimum=feature.edge
|
||||
?feature.minimumFloorMetres:std::numeric_limits<double>::infinity();
|
||||
const double floorMaximum=feature.edge
|
||||
?feature.maximumFloorMetres:-std::numeric_limits<double>::infinity();
|
||||
const bool overlapsXY=hi[0]>=feature.boundsMinimumMicrometres[0]/1.0e6
|
||||
&&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
|
||||
&&hi[2]>=floorMinimum-supportDepth&&lo[2]<=floorMaximum)
|
||||
latticeFloorSupportLowerBound=std::min(latticeFloorSupportLowerBound,
|
||||
std::max(-supportDepth,lo[2]-floorMaximum));
|
||||
}
|
||||
if(!std::isfinite(featureVoid.minimum)||!std::isfinite(featureVoid.maximum))
|
||||
return {0.0,0.0,false};
|
||||
if(!haveFeature)
|
||||
@@ -5276,8 +5415,12 @@ public:
|
||||
else
|
||||
{
|
||||
const double width=feature.room
|
||||
?feature.room->smoothWidthMicrometres/1000000.0
|
||||
:feature.edge->smoothWidthMicrometres/1000000.0;
|
||||
?(feature.room->unionWidthMicrometres>0
|
||||
?feature.room->unionWidthMicrometres
|
||||
:feature.room->smoothWidthMicrometres)/1000000.0
|
||||
:(feature.edge->unionWidthMicrometres>0
|
||||
?feature.edge->unionWidthMicrometres
|
||||
:feature.edge->smoothWidthMicrometres)/1000000.0;
|
||||
caveVoid.minimum=SmoothMinMetres(caveVoid.minimum,featureVoid.minimum,width);
|
||||
caveVoid.maximum=SmoothMinMetres(caveVoid.maximum,featureVoid.maximum,width);
|
||||
}
|
||||
@@ -5289,6 +5432,11 @@ public:
|
||||
result.minimum=std::max(HostRockMetres,-caveVoid.maximum);
|
||||
result.maximum=std::max(HostRockMetres,-caveVoid.minimum);
|
||||
}
|
||||
// The final lattice field owns an extra support cap after feature union.
|
||||
// Lower only the interval minimum when a box could touch that cap; the
|
||||
// untouched maximum remains a sound bound when only part of the box does.
|
||||
if(std::isfinite(latticeFloorSupportLowerBound))
|
||||
result.minimum=std::min(result.minimum,latticeFloorSupportLowerBound);
|
||||
// Cover floating-point rounding in interval endpoints and the field's fixed-point
|
||||
// coordinate conversion while keeping the interval tight compared with one micron.
|
||||
constexpr double OutwardPaddingMetres=1.0e-6;
|
||||
@@ -5356,9 +5504,10 @@ public:
|
||||
bIslandOtherStrateOverlap=true;
|
||||
if(CaveBound&&CaveBound!=Bound)bBadPlan=true;
|
||||
CaveBound=Bound;
|
||||
if(!Bound->Resolved||Bound->Resolved->recipeId!="Core.Caves.PrimitiveField"
|
||||
||Bound->Resolved->recipeVersion!=1
|
||||
||!Bound->Resolved->passageCapabilities.hasNativeCaveField
|
||||
if(!Bound->Resolved||!VoxelForgeCore::HasNativeGraphField(*Bound->Resolved)
|
||||
||(VoxelForgeCore::IsNativeCaveFieldStratum(*Bound->Resolved)
|
||||
?!Bound->Resolved->passageCapabilities.hasNativeCaveField
|
||||
:!Bound->Resolved->passageCapabilities.hasNativeLatticeField)
|
||||
||!std::get_if<FCavesFieldAdapter>(&Bound->Evaluator))bBadPlan=true;
|
||||
}
|
||||
else
|
||||
@@ -6768,9 +6917,10 @@ private:
|
||||
const auto* bound=FindBoundStratumForManagerZ(Z);
|
||||
if(!bound)return density;
|
||||
if(bound->Kind==FBoundStratumField::EKind::Caves
|
||||
&&bound->Resolved&&bound->Resolved->recipeId=="Core.Caves.PrimitiveField"
|
||||
&&bound->Resolved->recipeVersion==1
|
||||
&&bound->Resolved->passageCapabilities.hasNativeCaveField)
|
||||
&&bound->Resolved&&VoxelForgeCore::HasNativeGraphField(*bound->Resolved)
|
||||
&&(VoxelForgeCore::IsNativeCaveFieldStratum(*bound->Resolved)
|
||||
?bound->Resolved->passageCapabilities.hasNativeCaveField
|
||||
:bound->Resolved->passageCapabilities.hasNativeLatticeField))
|
||||
return ApplyResolvedNativeCavesField(density,X,Y,Z,*bound,tileQueries,scoped);
|
||||
const double micronsPerVoxel=Owner->CoreVoxelSizeMicrons;
|
||||
const std::array<double,3> worldMicrometres={X*micronsPerVoxel,
|
||||
@@ -6937,6 +7087,10 @@ private:
|
||||
candidates=&localCandidates;
|
||||
}
|
||||
double caveVoid=std::numeric_limits<double>::infinity();
|
||||
double latticeFloorSupportMinimum=std::numeric_limits<double>::infinity();
|
||||
const bool nativeLattice=bound.Resolved
|
||||
&&VoxelForgeCore::IsNativeLatticeFieldStratum(*bound.Resolved)
|
||||
&&bound.Resolved->passageCapabilities.hasNativeLatticeField;
|
||||
for(const std::uint32_t index:*candidates)
|
||||
{
|
||||
if(index>=CavernRuntime.size())continue;
|
||||
@@ -6963,9 +7117,16 @@ private:
|
||||
else if(feature.edge&&!feature.segments.empty())
|
||||
{
|
||||
double tube=std::numeric_limits<double>::infinity();
|
||||
double nearestFloor=0.0,nearestGrade=0.0;
|
||||
for(const auto& segment:feature.segments)
|
||||
double nearestFloor=0.0,nearestGrade=0.0,nearestRadial=0.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(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)
|
||||
{
|
||||
@@ -6983,22 +7144,64 @@ private:
|
||||
if(axis<2)radialSquared+=delta*delta;
|
||||
else vertical=delta;
|
||||
}
|
||||
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 normalized=std::sqrt(radialSquared/(radius*radius)
|
||||
+vertical*vertical/(height*height));
|
||||
const double distance=(normalized-1.0)*std::min(radius,height);
|
||||
if(distance<tube)
|
||||
if(distance<tube||(distance==tube&&segmentIndex<nearestSegment))
|
||||
{
|
||||
tube=distance;
|
||||
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);
|
||||
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);
|
||||
const double width=feature.edge->smoothWidthMicrometres/1000000.0;
|
||||
@@ -7007,8 +7210,13 @@ private:
|
||||
if(!std::isfinite(featureVoid))continue;
|
||||
if(!std::isfinite(caveVoid))caveVoid=featureVoid;
|
||||
else caveVoid=SmoothMinMetres(caveVoid,featureVoid,
|
||||
feature.room?feature.room->smoothWidthMicrometres/1000000.0
|
||||
:feature.edge->smoothWidthMicrometres/1000000.0);
|
||||
feature.room
|
||||
?(feature.room->unionWidthMicrometres>0
|
||||
?feature.room->unionWidthMicrometres
|
||||
:feature.room->smoothWidthMicrometres)/1000000.0
|
||||
:(feature.edge->unionWidthMicrometres>0
|
||||
?feature.edge->unionWidthMicrometres
|
||||
:feature.edge->smoothWidthMicrometres)/1000000.0);
|
||||
}
|
||||
// A versioned native caves recipe owns a solid host-rock field. The
|
||||
// resolved interval oracle uses the same finite -32 m solid value; using
|
||||
@@ -7016,8 +7224,13 @@ private:
|
||||
// terrain detail into the cave floor and invalidate those proofs.
|
||||
constexpr double HostRockMetres=-32.0;
|
||||
const double hostDistance=HostRockMetres;
|
||||
const double composed=std::isfinite(caveVoid)
|
||||
double composed=std::isfinite(caveVoid)
|
||||
?SmoothMaxMetres(hostDistance,-caveVoid,0.0):hostDistance;
|
||||
// Keep the floor support owned by the lattice route after smooth-unioning
|
||||
// neighboring room and corridor voids. This cap is limited to each record's
|
||||
// explicit support depth so separated helix turns remain independent.
|
||||
if(std::isfinite(latticeFloorSupportMinimum))
|
||||
composed=std::min(composed,latticeFloorSupportMinimum);
|
||||
return static_cast<float>(composed/metresPerVoxel);
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user