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
@@ -159,6 +159,16 @@ namespace
|
||||
for(unsigned char c:stableId){hash^=c;hash*=1099511628211ull;}
|
||||
return LayoutStampMix(hash);
|
||||
}
|
||||
std::uint64_t LayoutLatticeFeatureSeed(std::int32_t seed,std::int32_t strate,
|
||||
std::uint32_t recipeVersion,std::string_view stableId)
|
||||
{
|
||||
std::uint64_t hash=14695981039346656037ull;
|
||||
TransitionHashWord(hash,static_cast<std::uint32_t>(seed));
|
||||
TransitionHashWord(hash,static_cast<std::uint32_t>(strate));
|
||||
TransitionHashWord(hash,recipeVersion);
|
||||
for(unsigned char c:stableId){hash^=c;hash*=1099511628211ull;}
|
||||
return LayoutStampMix(hash);
|
||||
}
|
||||
std::uint64_t LayoutStampNext(std::uint64_t& state)
|
||||
{state=LayoutStampMix(state+0x9e3779b97f4a7c15ull);return state;}
|
||||
bool LayoutStampKindValid(ResolvedSdfStampKindV1 kind)
|
||||
@@ -611,7 +621,10 @@ namespace
|
||||
<<room.halfExtentsMicrometres[1]<<','<<room.halfExtentsMicrometres[2]
|
||||
<<"],\"floor_um\":"<<room.floorMicrometres
|
||||
<<",\"smooth_width_um\":"<<room.smoothWidthMicrometres
|
||||
<<",\"irregularity_amplitude_um\":"<<room.irregularityAmplitudeMicrometres
|
||||
;
|
||||
if(room.unionWidthMicrometres>0)
|
||||
out<<",\"union_width_um\":"<<room.unionWidthMicrometres;
|
||||
out<<",\"irregularity_amplitude_um\":"<<room.irregularityAmplitudeMicrometres
|
||||
<<",\"irregularity_wavelength_um\":"<<room.irregularityWavelengthMicrometres
|
||||
<<",\"irregularity_seed\":"<<room.irregularitySeed
|
||||
<<",\"bounds_min_um\":["<<room.boundsMinimumMicrometres[0]<<','
|
||||
@@ -646,8 +659,10 @@ namespace
|
||||
out<<",\"bore_radius_um\":"<<edge.boreRadiusMicrometres
|
||||
<<",\"bore_height_um\":"<<edge.boreHeightMicrometres
|
||||
<<",\"floor_support_depth_um\":"<<edge.floorSupportDepthMicrometres
|
||||
<<",\"smooth_width_um\":"<<edge.smoothWidthMicrometres
|
||||
<<",\"irregularity_amplitude_um\":"<<edge.irregularityAmplitudeMicrometres
|
||||
<<",\"smooth_width_um\":"<<edge.smoothWidthMicrometres;
|
||||
if(edge.unionWidthMicrometres>0)
|
||||
out<<",\"union_width_um\":"<<edge.unionWidthMicrometres;
|
||||
out<<",\"irregularity_amplitude_um\":"<<edge.irregularityAmplitudeMicrometres
|
||||
<<",\"irregularity_wavelength_um\":"<<edge.irregularityWavelengthMicrometres
|
||||
<<",\"irregularity_seed\":"<<edge.irregularitySeed
|
||||
<<",\"bounds_min_um\":["<<edge.boundsMinimumMicrometres[0]<<','
|
||||
@@ -867,8 +882,10 @@ bool ResolvedLayoutSnapshotV3::finalize(std::string& error)
|
||||
x.lowerInterfaceCapabilities={};
|
||||
if(x.generatorKind=="SurfaceWorld")
|
||||
x.passageCapabilities.hasSurfaceLanding=true;
|
||||
else if(x.generatorKind=="Caves")
|
||||
else if(IsNativeCaveFieldStratum(x))
|
||||
x.passageCapabilities.hasNativeCaveField=true;
|
||||
else if(IsNativeLatticeFieldStratum(x))
|
||||
x.passageCapabilities.hasNativeLatticeField=true;
|
||||
else if(x.generatorKind=="FloatingIslands")
|
||||
{
|
||||
x.passageCapabilities.hasFloatingIslandLanding=true;
|
||||
@@ -1245,6 +1262,7 @@ bool ResolvedLayoutSnapshotV3::validate(ResolvedLayoutValidationModeV3 mode,
|
||||
validExtents=validExtents&&extent>=500000&&extent<=100000000;
|
||||
if(!validExtents||room.smoothWidthMicrometres<0
|
||||
||room.smoothWidthMicrometres>10000000
|
||||
||room.unionWidthMicrometres<0||room.unionWidthMicrometres>10000000
|
||||
||room.irregularityAmplitudeMicrometres<0
|
||||
||room.irregularityAmplitudeMicrometres>10000000
|
||||
||room.irregularityWavelengthMicrometres<0
|
||||
@@ -1253,7 +1271,7 @@ bool ResolvedLayoutSnapshotV3::validate(ResolvedLayoutValidationModeV3 mode,
|
||||
&&room.irregularityWavelengthMicrometres<=0))
|
||||
add("cavern_room_parameters",room.stableId+" has invalid or unbounded parameters");
|
||||
const std::int64_t pad=room.smoothWidthMicrometres
|
||||
+room.irregularityAmplitudeMicrometres;
|
||||
+room.unionWidthMicrometres+room.irregularityAmplitudeMicrometres;
|
||||
for(std::size_t axis=0;axis<3;++axis)
|
||||
if(room.boundsMinimumMicrometres[axis]
|
||||
!=room.centerMicrometres[axis]-room.halfExtentsMicrometres[axis]-pad
|
||||
@@ -1276,11 +1294,15 @@ bool ResolvedLayoutSnapshotV3::validate(ResolvedLayoutValidationModeV3 mode,
|
||||
add("cavern_edge_binding",edge.stableId+" does not terminate at a room in its strate");
|
||||
if(!findStratum(edge.strateIndex))
|
||||
add("cavern_graph_strate",edge.stableId+" names an unknown strate");
|
||||
const auto* edgeStratum=findStratum(edge.strateIndex);
|
||||
const bool nativeLattice=edgeStratum
|
||||
&&IsNativeLatticeFieldStratum(*edgeStratum);
|
||||
if(edge.boreRadiusMicrometres<500000||edge.boreRadiusMicrometres>10000000
|
||||
||edge.boreHeightMicrometres<1000000||edge.boreHeightMicrometres>20000000
|
||||
||edge.floorSupportDepthMicrometres<0
|
||||
||edge.floorSupportDepthMicrometres>2000000
|
||||
||edge.smoothWidthMicrometres<0||edge.smoothWidthMicrometres>10000000
|
||||
||edge.unionWidthMicrometres<0||edge.unionWidthMicrometres>10000000
|
||||
||edge.irregularityAmplitudeMicrometres<0
|
||||
||edge.irregularityAmplitudeMicrometres>10000000
|
||||
||edge.irregularityWavelengthMicrometres<0
|
||||
@@ -1290,13 +1312,21 @@ bool ResolvedLayoutSnapshotV3::validate(ResolvedLayoutValidationModeV3 mode,
|
||||
add("cavern_edge_parameters",edge.stableId+" has invalid or unbounded parameters");
|
||||
std::array<std::int64_t,3> low=edge.controlPointsMicrometres[0];
|
||||
std::array<std::int64_t,3> high=low;
|
||||
for(const auto& point:edge.controlPointsMicrometres)
|
||||
if(nativeLattice&&!edge.polylineMicrometres.empty())
|
||||
{low=edge.polylineMicrometres.front();high=low;}
|
||||
const auto includeBoundPoint=[&](const auto& point)
|
||||
{
|
||||
for(std::size_t axis=0;axis<3;++axis)
|
||||
{low[axis]=std::min(low[axis],point[axis]);high[axis]=std::max(high[axis],point[axis]);}
|
||||
};
|
||||
if(nativeLattice&&!edge.polylineMicrometres.empty())
|
||||
for(const auto& point:edge.polylineMicrometres)includeBoundPoint(point);
|
||||
else for(const auto& point:edge.controlPointsMicrometres)includeBoundPoint(point);
|
||||
const std::int64_t xyPad=edge.boreRadiusMicrometres
|
||||
+edge.smoothWidthMicrometres+edge.irregularityAmplitudeMicrometres;
|
||||
const std::int64_t zPad=edge.smoothWidthMicrometres
|
||||
+edge.smoothWidthMicrometres+edge.unionWidthMicrometres
|
||||
+edge.irregularityAmplitudeMicrometres;
|
||||
const std::int64_t zPad=edge.smoothWidthMicrometres
|
||||
+edge.unionWidthMicrometres+edge.irregularityAmplitudeMicrometres;
|
||||
for(std::size_t axis=0;axis<3;++axis)
|
||||
{
|
||||
const std::int64_t pad=axis==2?zPad:xyPad;
|
||||
@@ -1314,17 +1344,26 @@ bool ResolvedLayoutSnapshotV3::validate(ResolvedLayoutValidationModeV3 mode,
|
||||
||endpoint[1]<room->centerMicrometres[1]-room->halfExtentsMicrometres[1]
|
||||
||endpoint[1]>room->centerMicrometres[1]+room->halfExtentsMicrometres[1]
|
||||
||std::abs(endpoint[2]-room->floorMicrometres)>500000)
|
||||
add("cavern_edge_portal",edge.stableId+" endpoint does not meet its room floor portal");
|
||||
{
|
||||
add("cavern_edge_portal",edge.stableId+" endpoint_um=("
|
||||
+std::to_string(endpoint[0])+","+std::to_string(endpoint[1])+","+
|
||||
std::to_string(endpoint[2])+") room="+room->stableId+" center_um=("
|
||||
+std::to_string(room->centerMicrometres[0])+","+
|
||||
std::to_string(room->centerMicrometres[1])+","+
|
||||
std::to_string(room->centerMicrometres[2])+") floor_um="+
|
||||
std::to_string(room->floorMicrometres)+" half_um=("
|
||||
+std::to_string(room->halfExtentsMicrometres[0])+","+
|
||||
std::to_string(room->halfExtentsMicrometres[1])+","+
|
||||
std::to_string(room->halfExtentsMicrometres[2])+")");
|
||||
}
|
||||
}
|
||||
const auto* edgeStratum=findStratum(edge.strateIndex);
|
||||
if(edgeStratum&&edgeStratum->generatorKind=="Caves"
|
||||
&&edgeStratum->recipeId=="Core.Caves.PrimitiveField"
|
||||
&&edgeStratum->recipeVersion==1
|
||||
if(edgeStratum&&HasNativeGraphField(*edgeStratum)
|
||||
&&(edge.polylineMicrometres.size()<2
|
||||
||edge.polylineMicrometres.front()!=edge.controlPointsMicrometres.front()
|
||||
||edge.polylineMicrometres.back()!=edge.controlPointsMicrometres.back()))
|
||||
||edge.polylineMicrometres.back()!=edge.controlPointsMicrometres.back()
|
||||
||edge.polylineMicrometres.size()>65536))
|
||||
add("cavern_edge_polyline",edge.stableId+
|
||||
" has no resolve-time polyline with the cubic endpoints");
|
||||
" has no bounded resolve-time polyline with matching endpoints");
|
||||
}
|
||||
}
|
||||
if(oreVeins.empty())
|
||||
@@ -1376,7 +1415,12 @@ bool ResolvedLayoutSnapshotV3::validate(ResolvedLayoutValidationModeV3 mode,
|
||||
if(!LayoutOreVeinSafe(*this,vein,&safetyReason))
|
||||
add("ore_vein_placement",vein.stableId+" is unsafe: "+safetyReason);
|
||||
}
|
||||
if(strict&&!issues.empty()){error="strict resolved-layout validation rejected "+issues.front().code;return false;}
|
||||
if(strict&&!issues.empty())
|
||||
{
|
||||
error="strict resolved-layout validation rejected "+issues.front().code+": "
|
||||
+issues.front().detail;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1485,6 +1529,7 @@ std::optional<ResolvedLayoutSnapshotV3> ResolvedLayoutSnapshotV3::FromJson(
|
||||
readTriplet("bounds_max_um",room.boundsMaximumMicrometres);
|
||||
room.floorMicrometres=LayoutInt(v,"floor_um");
|
||||
room.smoothWidthMicrometres=LayoutInt(v,"smooth_width_um");
|
||||
room.unionWidthMicrometres=LayoutInt(v,"union_width_um");
|
||||
room.irregularityAmplitudeMicrometres=LayoutInt(v,"irregularity_amplitude_um");
|
||||
room.irregularityWavelengthMicrometres=LayoutInt(v,"irregularity_wavelength_um");
|
||||
room.irregularitySeed=static_cast<std::uint64_t>(LayoutInt(v,"irregularity_seed"));
|
||||
@@ -1522,6 +1567,7 @@ std::optional<ResolvedLayoutSnapshotV3> ResolvedLayoutSnapshotV3::FromJson(
|
||||
edge.boreHeightMicrometres=LayoutInt(v,"bore_height_um");
|
||||
edge.floorSupportDepthMicrometres=LayoutInt(v,"floor_support_depth_um");
|
||||
edge.smoothWidthMicrometres=LayoutInt(v,"smooth_width_um");
|
||||
edge.unionWidthMicrometres=LayoutInt(v,"union_width_um");
|
||||
edge.irregularityAmplitudeMicrometres=LayoutInt(v,"irregularity_amplitude_um");
|
||||
edge.irregularityWavelengthMicrometres=LayoutInt(v,"irregularity_wavelength_um");
|
||||
edge.irregularitySeed=static_cast<std::uint64_t>(LayoutInt(v,"irregularity_seed"));
|
||||
@@ -1842,17 +1888,24 @@ bool ResolveResolvedCavernGraphV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
||||
{
|
||||
const auto found=std::find_if(candidate.strates.begin(),candidate.strates.end(),
|
||||
[strateIndex](const auto& stratum){return stratum.strateIndex==strateIndex;});
|
||||
return found!=candidate.strates.end()&&found->generatorKind=="Caves"
|
||||
&&found->recipeId=="Core.Caves.PrimitiveField"&&found->recipeVersion==1;
|
||||
return found!=candidate.strates.end()&&IsNativeCaveFieldStratum(*found);
|
||||
};
|
||||
const auto isNativeLatticeStrate=[&candidate](std::int32_t strateIndex)
|
||||
{
|
||||
const auto found=std::find_if(candidate.strates.begin(),candidate.strates.end(),
|
||||
[strateIndex](const auto& stratum){return stratum.strateIndex==strateIndex;});
|
||||
return found!=candidate.strates.end()&&IsNativeLatticeFieldStratum(*found);
|
||||
};
|
||||
for(auto& room:candidate.cavernRooms)
|
||||
{
|
||||
// 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=LayoutStampSeed(seed,room.strateIndex,room.stableId)
|
||||
room.irregularitySeed=(isNativeLatticeStrate(room.strateIndex)
|
||||
?LayoutLatticeFeatureSeed(seed,room.strateIndex,1,room.stableId)
|
||||
:LayoutStampSeed(seed,room.strateIndex,room.stableId))
|
||||
&0x001fffffffffffffull;
|
||||
const std::int64_t pad=room.smoothWidthMicrometres
|
||||
+room.irregularityAmplitudeMicrometres;
|
||||
+room.unionWidthMicrometres+room.irregularityAmplitudeMicrometres;
|
||||
for(std::size_t axis=0;axis<3;++axis)
|
||||
{
|
||||
room.boundsMinimumMicrometres[axis]=room.centerMicrometres[axis]
|
||||
@@ -1863,7 +1916,10 @@ bool ResolveResolvedCavernGraphV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
||||
}
|
||||
for(auto& edge:candidate.cavernEdges)
|
||||
{
|
||||
edge.irregularitySeed=LayoutStampSeed(seed,edge.strateIndex,edge.stableId)
|
||||
const bool nativeLattice=isNativeLatticeStrate(edge.strateIndex);
|
||||
edge.irregularitySeed=(nativeLattice
|
||||
?LayoutLatticeFeatureSeed(seed,edge.strateIndex,1,edge.stableId)
|
||||
:LayoutStampSeed(seed,edge.strateIndex,edge.stableId))
|
||||
&0x001fffffffffffffull;
|
||||
using FCurvePoint=std::array<double,3>;
|
||||
std::array<FCurvePoint,4> curve{};
|
||||
@@ -1889,6 +1945,8 @@ bool ResolveResolvedCavernGraphV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
||||
}
|
||||
return std::sqrt(distanceSquared);
|
||||
};
|
||||
std::vector<std::array<std::int64_t,3>> authoredPolyline;
|
||||
if(nativeLattice)authoredPolyline=std::move(edge.polylineMicrometres);
|
||||
edge.polylineMicrometres.clear();
|
||||
if(isNativeCavesStrate(edge.strateIndex))
|
||||
{
|
||||
@@ -1934,15 +1992,31 @@ bool ResolveResolvedCavernGraphV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
||||
if(!polylineValid)
|
||||
{error="cavern edge "+edge.stableId+" exceeds the bounded polyline subdivision budget";return false;}
|
||||
}
|
||||
else if(nativeLattice)
|
||||
{
|
||||
if(authoredPolyline.size()<2||authoredPolyline.size()>65536
|
||||
||authoredPolyline.front()!=edge.controlPointsMicrometres.front()
|
||||
||authoredPolyline.back()!=edge.controlPointsMicrometres.back())
|
||||
{error="lattice edge "+edge.stableId+
|
||||
" needs a bounded explicit path with matching endpoints";return false;}
|
||||
edge.polylineMicrometres=std::move(authoredPolyline);
|
||||
}
|
||||
std::array<std::int64_t,3> low=edge.controlPointsMicrometres[0];
|
||||
std::array<std::int64_t,3> high=low;
|
||||
for(const auto& point:edge.controlPointsMicrometres)
|
||||
const auto includeBoundPoint=[&](const auto& point)
|
||||
{
|
||||
for(std::size_t axis=0;axis<3;++axis)
|
||||
{low[axis]=std::min(low[axis],point[axis]);high[axis]=std::max(high[axis],point[axis]);}
|
||||
};
|
||||
if(nativeLattice&&!edge.polylineMicrometres.empty())
|
||||
{low=edge.polylineMicrometres.front();high=low;
|
||||
for(const auto& point:edge.polylineMicrometres)includeBoundPoint(point);}
|
||||
else for(const auto& point:edge.controlPointsMicrometres)includeBoundPoint(point);
|
||||
const std::int64_t xyPad=edge.boreRadiusMicrometres
|
||||
+edge.smoothWidthMicrometres+edge.irregularityAmplitudeMicrometres;
|
||||
const std::int64_t zPad=edge.smoothWidthMicrometres
|
||||
+edge.smoothWidthMicrometres+edge.unionWidthMicrometres
|
||||
+edge.irregularityAmplitudeMicrometres;
|
||||
const std::int64_t zPad=edge.smoothWidthMicrometres
|
||||
+edge.unionWidthMicrometres+edge.irregularityAmplitudeMicrometres;
|
||||
for(std::size_t axis=0;axis<3;++axis)
|
||||
{
|
||||
const std::int64_t pad=axis==2?zPad:xyPad;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -1435,7 +1435,7 @@ namespace VoxelForgeCore
|
||||
const int32 firstSurfaceChunk=FloorDiv(boundsMin.Z,LayoutChunkWidth);
|
||||
const int32 lastSurfaceChunk=FloorDiv(boundsMax.Z,LayoutChunkWidth);
|
||||
const WorldAssemblyStratumPlanV1* selectedPlan=nullptr;
|
||||
const ResolvedLayoutStratumV2* selectedCaveStratum=nullptr;
|
||||
const ResolvedLayoutStratumV2* selectedGraphStratum=nullptr;
|
||||
for(int32 chunkZ=firstSurfaceChunk;chunkZ<=lastSurfaceChunk;++chunkZ)
|
||||
{
|
||||
const int32 strateIndex=impl_->owner->Manager->GetStrateIndex(
|
||||
@@ -1444,22 +1444,22 @@ namespace VoxelForgeCore
|
||||
const auto resolved=std::find_if(impl_->resolvedLayout->strates.begin(),
|
||||
impl_->resolvedLayout->strates.end(),[strateIndex](const auto& stratum)
|
||||
{return stratum.strateIndex==strateIndex;});
|
||||
const bool nativeCaves=resolved!=impl_->resolvedLayout->strates.end()
|
||||
&&resolved->generatorKind=="Caves"
|
||||
&&resolved->recipeId=="Core.Caves.PrimitiveField"
|
||||
&&resolved->recipeVersion==1
|
||||
&&resolved->passageCapabilities.hasNativeCaveField;
|
||||
const bool nativeGraph=resolved!=impl_->resolvedLayout->strates.end()
|
||||
&&HasNativeGraphField(*resolved)
|
||||
&&(IsNativeCaveFieldStratum(*resolved)
|
||||
?resolved->passageCapabilities.hasNativeCaveField
|
||||
:resolved->passageCapabilities.hasNativeLatticeField);
|
||||
const auto managerType=impl_->owner->Manager->GetGeneratorTypeForChunk(
|
||||
FIntVector(0,0,chunkZ));
|
||||
if(!nativeCaves&&managerType!=ECaveGeneratorType::SurfaceWorld)return false;
|
||||
if(nativeCaves)
|
||||
if(!nativeGraph&&managerType!=ECaveGeneratorType::SurfaceWorld)return false;
|
||||
if(nativeGraph)
|
||||
{
|
||||
if(selectedCaveStratum&&selectedCaveStratum!=&*resolved)return false;
|
||||
selectedCaveStratum=&*resolved;
|
||||
if(selectedGraphStratum&&selectedGraphStratum!=&*resolved)return false;
|
||||
selectedGraphStratum=&*resolved;
|
||||
if(selectedPlan)return false;
|
||||
continue;
|
||||
}
|
||||
if(selectedCaveStratum)return false;
|
||||
if(selectedGraphStratum)return false;
|
||||
const auto found=std::find_if(impl_->worldAssembly->plan.strates.begin(),
|
||||
impl_->worldAssembly->plan.strates.end(),[strateIndex,chunkZ](const auto& plan)
|
||||
{return plan.strateIndex==strateIndex&&chunkZ<=plan.topChunkZ
|
||||
@@ -1468,9 +1468,9 @@ namespace VoxelForgeCore
|
||||
||(selectedPlan&&selectedPlan!=&*found))return false;
|
||||
selectedPlan=&*found;
|
||||
}
|
||||
if((!selectedCaveStratum&&(!selectedPlan
|
||||
if((!selectedGraphStratum&&(!selectedPlan
|
||||
||!Heightfield2D(impl_->worldAssembly->recipe,*selectedPlan).isValid()))
|
||||
||(selectedCaveStratum&&selectedPlan))
|
||||
||(selectedGraphStratum&&selectedPlan))
|
||||
return false;
|
||||
|
||||
const double coreVoxelsPerOwnerChunk=static_cast<double>(
|
||||
|
||||
Reference in new issue
Block a user