wip: L176 step 3 in progress (unverified, saved after machine crash)
Bore carves air only, stamp last, chambers/exemptions/plane fit removed. Seed 0: seams 0, no solid over host air (control detected); pad audit 0/6 (surface landing lacks support - step 4). 16-seed sweep interrupted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011g9Ez42ZNqQGjigc4FWDih
This commit is contained in:
1 parent
72f2609507
commit
98835952a2
8 files changed
+1230
-2268
No files matched your search
@@ -517,7 +517,8 @@ namespace VoxelForgeCore
|
||||
// v239 plans their floor planes as the passage's continuous floor profile.
|
||||
// v240 adds level end runs and rejects passage tube overlaps and hairpins.
|
||||
// v241 keeps passage composition sample-local and out of base-only X8 lanes.
|
||||
inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 241;
|
||||
// v242 composes layer content, air-only passage bores, and bounded landing pads.
|
||||
inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 242;
|
||||
|
||||
enum class FieldChannel : std::uint32_t
|
||||
{
|
||||
@@ -1331,16 +1332,6 @@ namespace VoxelForgeCore
|
||||
std::int64_t radiusSubvoxels = 0;
|
||||
};
|
||||
|
||||
struct ResolvedLayoutPassageLandingChamberV1
|
||||
{
|
||||
// The chamber floor plane passes through this point. Gradient is dz/dx,
|
||||
// dz/dy in Q16 so the chamber continues the passage's authored h(s) grade.
|
||||
std::array<std::int64_t, 3> floorCenterSubvoxels{};
|
||||
std::array<std::int64_t, 2> floorGradientQ16{};
|
||||
std::int64_t radiusSubvoxels = 0;
|
||||
std::int64_t clearHeightSubvoxels = 0;
|
||||
};
|
||||
|
||||
inline std::int64_t ResolvedLayoutPassageLandingDiscRadiusSubvoxels(
|
||||
std::int64_t mouthRadiusSubvoxels,
|
||||
std::uint32_t coreVoxelSizeMicrometres) noexcept
|
||||
@@ -1376,34 +1367,6 @@ namespace VoxelForgeCore
|
||||
<=static_cast<double>(halfWidthSubvoxels);
|
||||
}
|
||||
|
||||
// Shared landing-volume predicate for planners, the passage composer, and audits.
|
||||
// Query coordinates use the same world subvoxel units as the resolved passage.
|
||||
inline double ResolvedLayoutPassageLandingFloorAtXYSubvoxels(
|
||||
const ResolvedLayoutPassageLandingChamberV1& chamber,
|
||||
double xSubvoxels,double ySubvoxels) noexcept
|
||||
{
|
||||
return static_cast<double>(chamber.floorCenterSubvoxels[2])
|
||||
+static_cast<double>(chamber.floorGradientQ16[0])/65536.0
|
||||
*(xSubvoxels-static_cast<double>(chamber.floorCenterSubvoxels[0]))
|
||||
+static_cast<double>(chamber.floorGradientQ16[1])/65536.0
|
||||
*(ySubvoxels-static_cast<double>(chamber.floorCenterSubvoxels[1]));
|
||||
}
|
||||
|
||||
inline bool IsInsideResolvedLayoutPassageLandingChamberV1(
|
||||
const ResolvedLayoutPassageLandingChamberV1& chamber,
|
||||
double xSubvoxels,double ySubvoxels,double zSubvoxels) noexcept
|
||||
{
|
||||
if(chamber.radiusSubvoxels<=0||chamber.clearHeightSubvoxels<=0)return false;
|
||||
const double dx=xSubvoxels-static_cast<double>(chamber.floorCenterSubvoxels[0]);
|
||||
const double dy=ySubvoxels-static_cast<double>(chamber.floorCenterSubvoxels[1]);
|
||||
const double radius=static_cast<double>(chamber.radiusSubvoxels);
|
||||
const double floor=ResolvedLayoutPassageLandingFloorAtXYSubvoxels(
|
||||
chamber,xSubvoxels,ySubvoxels);
|
||||
return dx*dx+dy*dy<=radius*radius
|
||||
&&zSubvoxels>=floor
|
||||
&&zSubvoxels<=floor+static_cast<double>(chamber.clearHeightSubvoxels);
|
||||
}
|
||||
|
||||
struct ResolvedLayoutPassageV1
|
||||
{
|
||||
std::string stableId;
|
||||
@@ -1420,15 +1383,22 @@ namespace VoxelForgeCore
|
||||
std::int64_t endRunSubvoxels = 0;
|
||||
// Derived from the mouth binding; the main descent is monotone through this point.
|
||||
std::size_t lowerMouthControlIndex = 0;
|
||||
ResolvedLayoutPassageLandingChamberV1 upperLandingChamber;
|
||||
ResolvedLayoutPassageLandingChamberV1 lowerLandingChamber;
|
||||
// When true, each control point's position.z - radius is the planned
|
||||
// floor h(s), and both chamber planes continue that same endpoint profile.
|
||||
// False preserves older resolved snapshots' runtime floor composition.
|
||||
bool floorProfileIncludesLandingChambers = false;
|
||||
std::vector<ResolvedLayoutPassagePointV1> controlPoints;
|
||||
};
|
||||
|
||||
inline std::array<std::int64_t,3> ResolvedLayoutPassageLandingPadCenterSubvoxels(
|
||||
const ResolvedLayoutPassageV1& passage,bool upper) noexcept
|
||||
{
|
||||
const auto& mouth=upper?passage.upperMouthSubvoxels:passage.lowerMouthSubvoxels;
|
||||
std::array<std::int64_t,3> center=mouth;
|
||||
if(passage.controlPoints.empty())return center;
|
||||
const std::size_t endIndex=upper?0:
|
||||
std::min(passage.lowerMouthControlIndex,passage.controlPoints.size()-1);
|
||||
const auto& endpoint=passage.controlPoints[endIndex];
|
||||
center[2]=endpoint.positionSubvoxels[2]-endpoint.radiusSubvoxels;
|
||||
return center;
|
||||
}
|
||||
|
||||
struct ResolvedLayoutPassageConfigV1
|
||||
{
|
||||
std::int32_t upperStrateIndex = -1;
|
||||
@@ -2257,6 +2227,12 @@ namespace VoxelForgeCore
|
||||
float unclampedDensityAt(Vec3d voxelPosition) const;
|
||||
// Research-only source field entering passage composition, with passage carving omitted.
|
||||
float passageSourceDensityAt(Vec3d voxelPosition) const;
|
||||
// Research-only signed distance to the passage floor shell, without other composition.
|
||||
float passageFloorShellDistanceAt(Vec3d voxelPosition) const;
|
||||
// Audit-only influence bounds in Core voxel coordinates, shared with tile routing.
|
||||
bool resolvedPassageLandingPadInfluenceBounds(std::size_t passageIndex,bool upper,
|
||||
std::array<double,3>& minimumCoreVoxels,
|
||||
std::array<double,3>& maximumCoreVoxels) const;
|
||||
DetailFieldSample detailSampleAt(Vec3d voxelPosition) const;
|
||||
float densityAtLattice(Vec3i voxelPosition) const;
|
||||
bool isSolidAt(Vec3d voxelPosition) const;
|
||||
|
||||
@@ -653,22 +653,7 @@ namespace
|
||||
<<"],\"mouth_radius_q\":"<<p.mouthRadiusSubvoxels
|
||||
<<",\"mid_radius_q\":"<<p.midRadiusSubvoxels
|
||||
<<",\"end_run_q\":"<<p.endRunSubvoxels
|
||||
<<",\"floor_profile_includes_landing_chambers\":"
|
||||
<<(p.floorProfileIncludesLandingChambers?"true":"false");
|
||||
const auto writeLandingChamber=[&](const char* name,
|
||||
const ResolvedLayoutPassageLandingChamberV1& chamber)
|
||||
{
|
||||
out<<",\""<<name<<"\":{\"floor_center_q\":["
|
||||
<<chamber.floorCenterSubvoxels[0]<<','
|
||||
<<chamber.floorCenterSubvoxels[1]<<','
|
||||
<<chamber.floorCenterSubvoxels[2]<<"],\"floor_gradient_q16\":["
|
||||
<<chamber.floorGradientQ16[0]<<','<<chamber.floorGradientQ16[1]
|
||||
<<"],\"radius_q\":"<<chamber.radiusSubvoxels
|
||||
<<",\"clear_height_q\":"<<chamber.clearHeightSubvoxels<<'}';
|
||||
};
|
||||
writeLandingChamber("upper_landing_chamber",p.upperLandingChamber);
|
||||
writeLandingChamber("lower_landing_chamber",p.lowerLandingChamber);
|
||||
out<<",\"control_points\":[";
|
||||
<<",\"control_points\":[";
|
||||
for(std::size_t j=0;j<p.controlPoints.size();++j)
|
||||
{
|
||||
if(j)out<<',';const auto& point=p.controlPoints[j];
|
||||
@@ -1269,62 +1254,6 @@ bool ResolvedLayoutSnapshotV3::validate(ResolvedLayoutValidationModeV3 mode,
|
||||
if(!insideContent(*upper,passage.upperMouthSubvoxels)
|
||||
||!insideContent(*lower,passage.lowerMouthSubvoxels))
|
||||
add("passage_mouth_content",passage.stableId+" mouth is outside its owned content band");
|
||||
const auto validateLandingChamber=[&](
|
||||
const ResolvedLayoutPassageLandingChamberV1& chamber,
|
||||
const ResolvedLayoutStratumV2& host,
|
||||
const std::array<std::int64_t,3>& mouth,const char* side)
|
||||
{
|
||||
const double playerCenterHeight=(side==std::string_view("upper")
|
||||
&&host.generatorKind=="SurfaceWorld")?0.98:1.13;
|
||||
const std::int64_t playerCenterHeightQ=static_cast<std::int64_t>(
|
||||
std::llround(playerCenterHeight*WorldAssemblySubvoxelsPerVoxel
|
||||
*1000000.0/std::max<std::uint32_t>(1,coreVoxelSizeMicrometres)));
|
||||
if(chamber.floorCenterSubvoxels[0]!=mouth[0]
|
||||
||chamber.floorCenterSubvoxels[1]!=mouth[1]
|
||||
||chamber.floorCenterSubvoxels[2]+playerCenterHeightQ!=mouth[2]
|
||||
||chamber.radiusSubvoxels<=0
|
||||
||chamber.clearHeightSubvoxels<=0
|
||||
||chamber.radiusSubvoxels<passage.mouthRadiusSubvoxels
|
||||
||chamber.clearHeightSubvoxels<static_cast<std::int64_t>(
|
||||
std::llround(2.2*WorldAssemblySubvoxelsPerVoxel*1000000.0
|
||||
/std::max<std::uint32_t>(1,coreVoxelSizeMicrometres))))
|
||||
{
|
||||
add("passage_landing_chamber",passage.stableId+" "+side
|
||||
+" landing chamber has invalid floor, radius, or capsule clearance");
|
||||
return;
|
||||
}
|
||||
const std::uint64_t gradientSquared=static_cast<std::uint64_t>(
|
||||
chamber.floorGradientQ16[0]*chamber.floorGradientQ16[0]
|
||||
+chamber.floorGradientQ16[1]*chamber.floorGradientQ16[1]);
|
||||
const std::int64_t gradientMagnitude=static_cast<std::int64_t>(
|
||||
PassageIsqrt(gradientSquared));
|
||||
if(gradientMagnitude>17560)
|
||||
add("passage_landing_grade",passage.stableId+" "+side
|
||||
+" landing chamber floor exceeds 15 degrees");
|
||||
const std::int64_t planeReachQ=static_cast<std::int64_t>(
|
||||
(chamber.radiusSubvoxels*gradientMagnitude+65535)/65536);
|
||||
const std::int64_t shellQ=static_cast<std::int64_t>(std::llround(
|
||||
0.75*WorldAssemblySubvoxelsPerVoxel*1000000.0
|
||||
/std::max<std::uint32_t>(1,coreVoxelSizeMicrometres)));
|
||||
const auto qToUm=[&](std::int64_t q)
|
||||
{return q*static_cast<std::int64_t>(coreVoxelSizeMicrometres)
|
||||
/WorldAssemblySubvoxelsPerVoxel;};
|
||||
if(!host.content.presentInCurrentField
|
||||
||qToUm(chamber.floorCenterSubvoxels[2]-planeReachQ-shellQ)
|
||||
<host.content.bottomMicrometres
|
||||
||qToUm(chamber.floorCenterSubvoxels[2]+planeReachQ
|
||||
+chamber.clearHeightSubvoxels+shellQ)
|
||||
>host.content.topMicrometres)
|
||||
add("passage_landing_content",passage.stableId+" "+side
|
||||
+" landing chamber or plain shell leaves its host content band");
|
||||
};
|
||||
if(upper&&lower)
|
||||
{
|
||||
validateLandingChamber(passage.upperLandingChamber,*upper,
|
||||
passage.upperMouthSubvoxels,"upper");
|
||||
validateLandingChamber(passage.lowerLandingChamber,*lower,
|
||||
passage.lowerMouthSubvoxels,"lower");
|
||||
}
|
||||
if(passage.controlPoints.size()<2)continue;
|
||||
const std::int64_t PlayerCenterAboveFloorQ=coreVoxelSizeMicrometres>0
|
||||
?static_cast<std::int64_t>(std::llround(1.13*WorldAssemblySubvoxelsPerVoxel
|
||||
@@ -2113,27 +2042,10 @@ std::optional<ResolvedLayoutSnapshotV3> ResolvedLayoutSnapshotV3::FromJson(
|
||||
p.placementRadiusSubvoxels=LayoutInt(v,"placement_radius_q");
|
||||
p.mouthRadiusSubvoxels=LayoutInt(v,"mouth_radius_q");p.midRadiusSubvoxels=LayoutInt(v,"mid_radius_q");
|
||||
p.endRunSubvoxels=LayoutInt(v,"end_run_q");
|
||||
p.floorProfileIncludesLandingChambers=
|
||||
LayoutBool(v,"floor_profile_includes_landing_chambers");
|
||||
const auto& upper=v.Get("upper_mouth_q").AsArray();
|
||||
const auto& lower=v.Get("lower_mouth_q").AsArray();
|
||||
for(std::size_t axis=0;axis<3&&axis<upper.size();++axis)p.upperMouthSubvoxels[axis]=upper[axis].Integer();
|
||||
for(std::size_t axis=0;axis<3&&axis<lower.size();++axis)p.lowerMouthSubvoxels[axis]=lower[axis].Integer();
|
||||
const auto readLandingChamber=[&](const char* key,
|
||||
ResolvedLayoutPassageLandingChamberV1& chamber)
|
||||
{
|
||||
const auto& value=v.Get(key);
|
||||
const auto& center=value.Get("floor_center_q").AsArray();
|
||||
const auto& gradient=value.Get("floor_gradient_q16").AsArray();
|
||||
for(std::size_t axis=0;axis<3&&axis<center.size();++axis)
|
||||
chamber.floorCenterSubvoxels[axis]=center[axis].Integer();
|
||||
for(std::size_t axis=0;axis<2&&axis<gradient.size();++axis)
|
||||
chamber.floorGradientQ16[axis]=gradient[axis].Integer();
|
||||
chamber.radiusSubvoxels=LayoutInt(value,"radius_q");
|
||||
chamber.clearHeightSubvoxels=LayoutInt(value,"clear_height_q");
|
||||
};
|
||||
readLandingChamber("upper_landing_chamber",p.upperLandingChamber);
|
||||
readLandingChamber("lower_landing_chamber",p.lowerLandingChamber);
|
||||
for(const auto& q:v.Get("control_points").AsArray())
|
||||
{
|
||||
ResolvedLayoutPassagePointV1 point;const auto& position=q.Get("position_q").AsArray();
|
||||
@@ -2940,7 +2852,7 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
|
||||
std::size_t mouthIndex,bool lowerSide,
|
||||
const std::array<std::int64_t,2>& authoredRouteTangentQ16,
|
||||
std::int64_t authoredFloorSlopeQ16,const FPoint& mouthFloorPoint,
|
||||
std::int64_t chamberRadiusMicrometres)
|
||||
std::int64_t landingRadiusMicrometres)
|
||||
{
|
||||
constexpr std::array<std::array<std::int64_t,2>,16> DirectionsQ16={{
|
||||
{{65536,0}},{{60547,25080}},{{46341,46341}},{{25080,60547}},
|
||||
@@ -2959,7 +2871,7 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
|
||||
/(65536ll*65536ll));
|
||||
constexpr std::int64_t MouthClearanceProbeExtent=48000000;
|
||||
const std::int64_t firstClearanceProbe=std::max<std::int64_t>(
|
||||
2000000,chamberRadiusMicrometres+1000000);
|
||||
2000000,landingRadiusMicrometres+1000000);
|
||||
for(std::int64_t distance=firstClearanceProbe;
|
||||
distance<=MouthClearanceProbeExtent;distance+=2000000)
|
||||
{
|
||||
@@ -3057,13 +2969,13 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
|
||||
bool lowerSide,FPoint& point,std::array<std::int64_t,2>& routeTangentQ16,
|
||||
std::int64_t& floorSlopeQ16)
|
||||
{
|
||||
const auto& chamber=lowerSide?passage.lowerLandingChamber
|
||||
:passage.upperLandingChamber;
|
||||
for(std::size_t axis=0;axis<3;++axis)
|
||||
point[axis]=qToMicrometres(chamber.floorCenterSubvoxels[axis]);
|
||||
if(passage.controlPoints.size()<2)return false;
|
||||
const std::size_t mouthIndex=lowerSide
|
||||
?std::min(passage.lowerMouthControlIndex,passage.controlPoints.size()-1):0;
|
||||
const auto& mouth=lowerSide?passage.lowerMouthSubvoxels
|
||||
:passage.upperMouthSubvoxels;
|
||||
point[0]=qToMicrometres(mouth[0]);
|
||||
point[1]=qToMicrometres(mouth[1]);
|
||||
const std::size_t tangentIndex=lowerSide
|
||||
?(mouthIndex>0?mouthIndex-1:1):1;
|
||||
// The lower arrival continues from the previous passage point through its
|
||||
@@ -3080,26 +2992,23 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
|
||||
if(tangentLength<=0)return false;
|
||||
routeTangentQ16={tx*65536/tangentLength,ty*65536/tangentLength};
|
||||
const auto& mouthControl=passage.controlPoints[mouthIndex];
|
||||
const std::int64_t mouthFloor=chamber.floorCenterSubvoxels[2];
|
||||
const std::int64_t mouthFloor=mouthControl.positionSubvoxels[2]
|
||||
-mouthControl.radiusSubvoxels;
|
||||
point[2]=qToMicrometres(mouthFloor);
|
||||
floorSlopeQ16=(chamber.floorGradientQ16[0]*routeTangentQ16[0]
|
||||
+chamber.floorGradientQ16[1]*routeTangentQ16[1])/65536;
|
||||
floorSlopeQ16=0;
|
||||
const auto authoredRouteTangentQ16=routeTangentQ16;
|
||||
routeTangentQ16=chooseMouthRouteTangent(passage,mouthIndex,lowerSide,
|
||||
authoredRouteTangentQ16,floorSlopeQ16,point,
|
||||
qToMicrometres(chamber.radiusSubvoxels));
|
||||
floorSlopeQ16=(chamber.floorGradientQ16[0]*routeTangentQ16[0]
|
||||
+chamber.floorGradientQ16[1]*routeTangentQ16[1])/65536;
|
||||
qToMicrometres(ResolvedLayoutPassageLandingDiscRadiusSubvoxels(
|
||||
passage.mouthRadiusSubvoxels,layout.coreVoxelSizeMicrometres)));
|
||||
const std::int64_t offsetDirection=lowerSide?1:-1;
|
||||
const std::int64_t portOffset=std::max<std::int64_t>(0,
|
||||
qToMicrometres(chamber.radiusSubvoxels)-LandingConnectorBoreRadius);
|
||||
qToMicrometres(ResolvedLayoutPassageLandingDiscRadiusSubvoxels(
|
||||
passage.mouthRadiusSubvoxels,layout.coreVoxelSizeMicrometres))
|
||||
-LandingConnectorBoreRadius);
|
||||
point[0]+=offsetDirection*routeTangentQ16[0]*portOffset/65536;
|
||||
point[1]+=offsetDirection*routeTangentQ16[1]*portOffset/65536;
|
||||
point[2]=qToMicrometres(mouthFloor)
|
||||
+(chamber.floorGradientQ16[0]*(point[0]
|
||||
-qToMicrometres(chamber.floorCenterSubvoxels[0]))
|
||||
+chamber.floorGradientQ16[1]*(point[1]
|
||||
-qToMicrometres(chamber.floorCenterSubvoxels[1])))/65536;
|
||||
point[2]=qToMicrometres(mouthFloor);
|
||||
return true;
|
||||
};
|
||||
FPoint routeStart{},routeEnd{};
|
||||
@@ -3932,7 +3841,7 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
|
||||
const auto arrivalPoint=routeStart;
|
||||
FPoint previousPoint=arrivalPoint;
|
||||
std::int64_t previousProfileDistance=0;
|
||||
std::string previousAnchor=arrivalPassage->stableId+"/lower-landing-chamber";
|
||||
std::string previousAnchor=arrivalPassage->stableId+"/lower-landing-pad";
|
||||
for(std::size_t nodeIndex=0;nodeIndex<nodes.size();++nodeIndex)
|
||||
{
|
||||
const auto& node=nodes[nodeIndex];
|
||||
@@ -3950,7 +3859,7 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
|
||||
{
|
||||
const std::string departureEdgeId=routePrefix+"departure-edge";
|
||||
auto edge=makeEdge(departureEdgeId,
|
||||
departurePassage->stableId+"/upper-landing-chamber",
|
||||
departurePassage->stableId+"/upper-landing-pad",
|
||||
routeEnd,nodes.back(),
|
||||
std::min(selectedBoreRadius(departureEdgeId),LandingConnectorBoreRadius),
|
||||
totalRouteLength,nodes.back().floorProfileDistanceMicrometres);
|
||||
@@ -4375,17 +4284,21 @@ static FPassageTubeSelfOverlapCensus CensusPassageTubeSelfOverlaps(
|
||||
passage.mouthRadiusSubvoxels+qPerMetre)+qPerMetre);
|
||||
const std::int64_t landingDiscRadius=ResolvedLayoutPassageLandingDiscRadiusSubvoxels(
|
||||
passage.mouthRadiusSubvoxels,coreVoxelSizeMicrometres);
|
||||
const auto overlapsLandingColumn=[&](
|
||||
const ResolvedLayoutPassageLandingChamberV1& chamber,
|
||||
const std::array<std::int64_t,3>& landingMouth,
|
||||
const std::array<double,3>& center)
|
||||
const std::int64_t landingHeadroom=static_cast<std::int64_t>(
|
||||
std::llround(4.5*qPerMetre));
|
||||
const auto overlapsLandingColumn=[&](bool upper,const std::array<double,3>& center)
|
||||
{
|
||||
if(chamber.radiusSubvoxels<=0||chamber.clearHeightSubvoxels<=0)return false;
|
||||
const double floor=static_cast<double>(chamber.floorCenterSubvoxels[2]);
|
||||
const double ceiling=floor+static_cast<double>(chamber.clearHeightSubvoxels);
|
||||
if(landingDiscRadius<=0||passage.controlPoints.size()<2)return false;
|
||||
const std::size_t endpointIndex=upper?0:
|
||||
std::min(passage.lowerMouthControlIndex,passage.controlPoints.size()-1);
|
||||
const auto& endpoint=passage.controlPoints[endpointIndex];
|
||||
const double floor=static_cast<double>(endpoint.positionSubvoxels[2]
|
||||
-endpoint.radiusSubvoxels);
|
||||
const double ceiling=floor+static_cast<double>(landingHeadroom);
|
||||
if(center[2]<floor||center[2]>ceiling)return false;
|
||||
const auto& mouth=upper?passage.upperMouthSubvoxels:passage.lowerMouthSubvoxels;
|
||||
return IsInsideResolvedLayoutPassageLandingDiscV1(
|
||||
{landingMouth[0],landingMouth[1]},landingDiscRadius,center[0],center[1]);
|
||||
{mouth[0],mouth[1]},landingDiscRadius,center[0],center[1]);
|
||||
};
|
||||
for(std::size_t segment=0;segment+1<passage.controlPoints.size();++segment)
|
||||
{
|
||||
@@ -4404,10 +4317,8 @@ static FPassageTubeSelfOverlapCensus CensusPassageTubeSelfOverlaps(
|
||||
if(routeDistance<=endRun||horizontalAlong.back()-routeDistance<=endRun)continue;
|
||||
const std::array<double,3> center={a[0]+t*(b[0]-a[0]),
|
||||
a[1]+t*(b[1]-a[1]),a[2]+t*(b[2]-a[2])};
|
||||
upperColumnViolation|=overlapsLandingColumn(passage.upperLandingChamber,
|
||||
passage.upperMouthSubvoxels,center);
|
||||
lowerColumnViolation|=overlapsLandingColumn(passage.lowerLandingChamber,
|
||||
passage.lowerMouthSubvoxels,center);
|
||||
upperColumnViolation|=overlapsLandingColumn(true,center);
|
||||
lowerColumnViolation|=overlapsLandingColumn(false,center);
|
||||
if(upperColumnViolation&&lowerColumnViolation)break;
|
||||
}
|
||||
if((upperColumnViolation||lowerColumnViolation)
|
||||
@@ -4867,72 +4778,6 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
||||
}
|
||||
}
|
||||
}
|
||||
// Reserve each landing envelope inside its host band before building h(s).
|
||||
// SurfaceWorld can raise its floor by at most the 4.5 m headroom allowance,
|
||||
// so include that bounded rise as well as the grade reach and floor shell.
|
||||
constexpr std::int64_t MaximumLandingGradeQ16=17560;
|
||||
const std::int64_t landingRadius=mouthRadius+3*qPerMetre;
|
||||
const std::int64_t landingClearHeight=static_cast<std::int64_t>(
|
||||
std::llround(4.5*qPerMetre));
|
||||
const std::int64_t landingShell=static_cast<std::int64_t>(
|
||||
std::llround(0.75*qPerMetre));
|
||||
const std::int64_t landingPlaneReach=(landingRadius
|
||||
*MaximumLandingGradeQ16+65535)/65536;
|
||||
const std::array<std::int64_t,3> upperMouthBeforeBandFit=upperMouth;
|
||||
const std::array<std::int64_t,3> lowerMouthBeforeBandFit=lowerMouth;
|
||||
const auto fitMouth=[&](std::array<std::int64_t,3>& mouth,
|
||||
const ResolvedLayoutStratumV2& host,std::int64_t playerHeightQ,
|
||||
const char* side)
|
||||
{
|
||||
if(!host.content.presentInCurrentField)return true;
|
||||
const std::int64_t minimumFloor=contentZQ(host.content.bottomMicrometres)
|
||||
+landingPlaneReach+landingShell;
|
||||
const std::int64_t nativeFloorRise=host.generatorKind=="SurfaceWorld"
|
||||
?landingClearHeight:0;
|
||||
const std::int64_t maximumFloor=contentZQ(host.content.topMicrometres)
|
||||
-landingPlaneReach-landingShell-landingClearHeight-nativeFloorRise;
|
||||
if(maximumFloor<minimumFloor)
|
||||
{error=passage.stableId+" "+side
|
||||
+" landing chamber envelope is taller than its host content band";return false;}
|
||||
const std::int64_t requestedFloor=mouth[2]-playerHeightQ;
|
||||
const std::int64_t fittedFloor=std::clamp(requestedFloor,
|
||||
minimumFloor,maximumFloor);
|
||||
if(fittedFloor!=requestedFloor)
|
||||
{
|
||||
mouth[2]=fittedFloor+playerHeightQ;
|
||||
std::printf("[PassageLandingChamberPlacement] id=%s side=%s floor_shift_m=%.3f grade_bound_deg=15.000 clear_height_m=4.500 shell_m=0.750 status=PASS\n",
|
||||
passage.stableId.c_str(),side,
|
||||
(fittedFloor-requestedFloor)/static_cast<double>(qPerMetre));
|
||||
}
|
||||
return true;
|
||||
};
|
||||
if(!fitMouth(upperMouth,upper,upperPlayerCenterAboveFloorQ,"upper")
|
||||
||!fitMouth(lowerMouth,lower,playerCenterAboveFloorQ,"lower"))return false;
|
||||
const auto bindFittedMouthAnchor=[&](
|
||||
const ResolvedLayoutStratumV2& host,
|
||||
const std::array<std::int64_t,3>& before,
|
||||
const std::array<std::int64_t,3>& after,const char* side)
|
||||
{
|
||||
if(before==after)return;
|
||||
auto mutableHost=std::find_if(layout.strates.begin(),layout.strates.end(),
|
||||
[&](const auto& stratum)
|
||||
{return stratum.strateIndex==host.strateIndex;});
|
||||
if(mutableHost!=layout.strates.end())
|
||||
for(auto& anchor:mutableHost->anchors)
|
||||
if((anchor.kind=="passage_arrival"
|
||||
||anchor.kind=="passage_departure")
|
||||
&&anchor.values.size()>=3
|
||||
&&anchor.values[0]==after[0]
|
||||
&&anchor.values[1]==after[1])
|
||||
{
|
||||
anchor.values[2]=after[2];
|
||||
std::printf("[PassageLandingChamberArrivalBinding] id=%s side=%s anchor=%s status=PASS\n",
|
||||
passage.stableId.c_str(),side,anchor.stableId.c_str());
|
||||
break;
|
||||
}
|
||||
};
|
||||
bindFittedMouthAnchor(upper,upperMouthBeforeBandFit,upperMouth,"upper");
|
||||
bindFittedMouthAnchor(lower,lowerMouthBeforeBandFit,lowerMouth,"lower");
|
||||
if(!radialWithin(upperMouth,placementRadius)||!radialWithin(lowerMouth,placementRadius))
|
||||
{error=passage.stableId+" route mouth is outside the resolved PassageConfig.DistanceMax radius";return false;}
|
||||
passage.upperMouthSubvoxels=upperMouth;passage.lowerMouthSubvoxels=lowerMouth;
|
||||
@@ -6007,18 +5852,18 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
||||
passage.controlPoints[lowerMouthIndex].positionSubvoxels[2]=lowerMouth[2]
|
||||
+passage.controlPoints[lowerMouthIndex].radiusSubvoxels
|
||||
-playerCenterAboveFloorQ;
|
||||
std::vector<VoxelForgeCore::PassageSpline::FPoint> chamberFloorControls;
|
||||
std::vector<double> chamberFloorProfile;
|
||||
chamberFloorControls.reserve(passage.controlPoints.size());
|
||||
chamberFloorProfile.reserve(passage.controlPoints.size());
|
||||
std::vector<VoxelForgeCore::PassageSpline::FPoint> endpointFloorControls;
|
||||
std::vector<double> endpointFloorProfile;
|
||||
endpointFloorControls.reserve(passage.controlPoints.size());
|
||||
endpointFloorProfile.reserve(passage.controlPoints.size());
|
||||
for(const auto& point:passage.controlPoints)
|
||||
{
|
||||
chamberFloorControls.push_back({
|
||||
endpointFloorControls.push_back({
|
||||
static_cast<double>(point.positionSubvoxels[0]),
|
||||
static_cast<double>(point.positionSubvoxels[1]),
|
||||
static_cast<double>(point.positionSubvoxels[2]),
|
||||
static_cast<double>(point.radiusSubvoxels)});
|
||||
chamberFloorProfile.push_back(static_cast<double>(
|
||||
endpointFloorProfile.push_back(static_cast<double>(
|
||||
point.positionSubvoxels[2]-point.radiusSubvoxels));
|
||||
}
|
||||
const auto routePoints=[&](const ResolvedLayoutStratumV2& host)
|
||||
@@ -6039,12 +5884,12 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
||||
route.erase(route.begin(),route.end()-2);
|
||||
double routeHeight=0.0,distanceSquared=0.0;
|
||||
if(VoxelForgeCore::PassageSpline::ClosestRouteHeightAtXY(route,
|
||||
chamberFloorControls.front()[0],chamberFloorControls.front()[1],
|
||||
endpointFloorControls.front()[0],endpointFloorControls.front()[1],
|
||||
routeHeight,distanceSquared)
|
||||
&&distanceSquared<=std::pow(chamberFloorControls.front()[3]
|
||||
&&distanceSquared<=std::pow(endpointFloorControls.front()[3]
|
||||
+2.0*qPerMetre,2.0))
|
||||
VoxelForgeCore::PassageSpline::BlendEndpointFloorToRoute(
|
||||
chamberFloorControls,chamberFloorProfile,0,route,qPerMetre,
|
||||
endpointFloorControls,endpointFloorProfile,0,route,qPerMetre,
|
||||
upper.generatorKind=="SurfaceWorld"?0.98:1.13);
|
||||
}
|
||||
if(lower.generatorKind=="TunnelNetwork"
|
||||
@@ -6054,31 +5899,30 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
||||
if(route.size()>2)route.resize(2);
|
||||
double routeHeight=0.0,distanceSquared=0.0;
|
||||
if(VoxelForgeCore::PassageSpline::ClosestRouteHeightAtXY(route,
|
||||
chamberFloorControls[lowerMouthIndex][0],
|
||||
chamberFloorControls[lowerMouthIndex][1],
|
||||
endpointFloorControls[lowerMouthIndex][0],
|
||||
endpointFloorControls[lowerMouthIndex][1],
|
||||
routeHeight,distanceSquared)
|
||||
&&distanceSquared<=std::pow(
|
||||
chamberFloorControls[lowerMouthIndex][3]
|
||||
endpointFloorControls[lowerMouthIndex][3]
|
||||
+2.0*qPerMetre,2.0))
|
||||
VoxelForgeCore::PassageSpline::BlendEndpointFloorToRoute(
|
||||
chamberFloorControls,chamberFloorProfile,
|
||||
endpointFloorControls,endpointFloorProfile,
|
||||
lowerMouthIndex,route,qPerMetre);
|
||||
}
|
||||
const double upperEndRunFloor=chamberFloorProfile.front();
|
||||
const double lowerEndRunFloor=chamberFloorProfile[lowerMouthIndex];
|
||||
for(std::size_t i=0;i<chamberFloorProfile.size();++i)
|
||||
const double upperEndRunFloor=endpointFloorProfile.front();
|
||||
const double lowerEndRunFloor=endpointFloorProfile[lowerMouthIndex];
|
||||
for(std::size_t i=0;i<endpointFloorProfile.size();++i)
|
||||
{
|
||||
if(cumulative[i]<=upperFlatEndDistance)
|
||||
chamberFloorProfile[i]=upperEndRunFloor;
|
||||
endpointFloorProfile[i]=upperEndRunFloor;
|
||||
else if(cumulative[i]>=lowerFlatStartDistance)
|
||||
chamberFloorProfile[i]=lowerEndRunFloor;
|
||||
endpointFloorProfile[i]=lowerEndRunFloor;
|
||||
}
|
||||
// Commit the endpoint route fit into h(s) before chamber records are
|
||||
// resolved. The passage field and both landing planes then consume the
|
||||
// same deterministic profile instead of blending the chamber in later.
|
||||
// Store route binding and level end runs in h(s), then use that profile
|
||||
// directly for the bore and its landing-pad stamp.
|
||||
for(std::size_t i=0;i<passage.controlPoints.size();++i)
|
||||
passage.controlPoints[i].positionSubvoxels[2]=static_cast<std::int64_t>(
|
||||
std::llround(chamberFloorProfile[i]))
|
||||
std::llround(endpointFloorProfile[i]))
|
||||
+passage.controlPoints[i].radiusSubvoxels;
|
||||
const auto endRunAudit=AuditResolvedLayoutPassageEndRunsV1(
|
||||
passage,layout.coreVoxelSizeMicrometres,endRunLength);
|
||||
@@ -6090,117 +5934,6 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
||||
endRunAudit.startMaximumGradeDegrees,
|
||||
endRunAudit.endMaximumGradeDegrees,
|
||||
endRunAudit.passed?"PASS":"FAIL");
|
||||
const auto resolveLandingChamber=[&](
|
||||
ResolvedLayoutPassageLandingChamberV1& chamber,
|
||||
const std::array<std::int64_t,3>& mouth,std::size_t endpointIndex,
|
||||
std::size_t neighborIndex,const ResolvedLayoutStratumV2& host,
|
||||
const char* side)
|
||||
{
|
||||
chamber.floorCenterSubvoxels=mouth;
|
||||
chamber.radiusSubvoxels=mouthRadius+3*qPerMetre;
|
||||
chamber.clearHeightSubvoxels=static_cast<std::int64_t>(
|
||||
std::llround(4.5*qPerMetre));
|
||||
const auto& endpoint=passage.controlPoints[endpointIndex];
|
||||
const auto& neighbor=passage.controlPoints[neighborIndex];
|
||||
const std::int64_t endpointFloor=static_cast<std::int64_t>(
|
||||
std::llround(chamberFloorProfile[endpointIndex]));
|
||||
const std::int64_t neighborFloor=static_cast<std::int64_t>(
|
||||
std::llround(chamberFloorProfile[neighborIndex]));
|
||||
chamber.floorCenterSubvoxels[2]=endpointFloor;
|
||||
const std::int64_t dx=endpoint.positionSubvoxels[0]
|
||||
-neighbor.positionSubvoxels[0];
|
||||
const std::int64_t dy=endpoint.positionSubvoxels[1]
|
||||
-neighbor.positionSubvoxels[1];
|
||||
const std::int64_t dz=endpointFloor-neighborFloor;
|
||||
const std::int64_t horizontalSquared=dx*dx+dy*dy;
|
||||
if(horizontalSquared<=0)
|
||||
{error=passage.stableId+" "+side+" landing chamber has no endpoint tangent";return false;}
|
||||
chamber.floorGradientQ16={
|
||||
PassageRoundRatio(dx<0?-dz:dz,std::abs(dx)*65536,
|
||||
horizontalSquared),
|
||||
PassageRoundRatio(dy<0?-dz:dz,std::abs(dy)*65536,
|
||||
horizontalSquared)};
|
||||
if(host.generatorKind=="SurfaceWorld"&&side[0]=='u')
|
||||
{
|
||||
// The surface route enters the outer half of this chamber. Fit
|
||||
// its landing plane to that receiving route, while keeping its
|
||||
// center height equal to the passage's planned h(s) endpoint.
|
||||
const auto surfaceRoute=routePoints(host);
|
||||
const double radiusSquared=static_cast<double>(
|
||||
chamber.radiusSubvoxels)*chamber.radiusSubvoxels;
|
||||
double farthestDistanceSquared=0.0;
|
||||
std::array<double,3> farthestPoint{};
|
||||
for(const auto& routePoint:surfaceRoute)
|
||||
{
|
||||
const double routeDx=routePoint[0]-mouth[0];
|
||||
const double routeDy=routePoint[1]-mouth[1];
|
||||
const double distanceSquared=routeDx*routeDx+routeDy*routeDy;
|
||||
if(distanceSquared<=radiusSquared
|
||||
&&distanceSquared>farthestDistanceSquared)
|
||||
{
|
||||
farthestDistanceSquared=distanceSquared;
|
||||
farthestPoint=routePoint;
|
||||
}
|
||||
}
|
||||
double mouthRouteHeight=0.0,mouthRouteDistanceSquared=0.0;
|
||||
if(farthestDistanceSquared>0.0
|
||||
&&VoxelForgeCore::PassageSpline::ClosestRouteHeightAtXY(
|
||||
surfaceRoute,static_cast<double>(mouth[0]),
|
||||
static_cast<double>(mouth[1]),mouthRouteHeight,
|
||||
mouthRouteDistanceSquared))
|
||||
{
|
||||
const std::int64_t surfaceDx=static_cast<std::int64_t>(
|
||||
std::llround(farthestPoint[0]-mouth[0]));
|
||||
const std::int64_t surfaceDy=static_cast<std::int64_t>(
|
||||
std::llround(farthestPoint[1]-mouth[1]));
|
||||
const std::int64_t surfaceDz=static_cast<std::int64_t>(
|
||||
std::llround(farthestPoint[2]-mouthRouteHeight));
|
||||
const std::int64_t surfaceHorizontalSquared=surfaceDx*surfaceDx
|
||||
+surfaceDy*surfaceDy;
|
||||
if(surfaceHorizontalSquared>0)
|
||||
chamber.floorGradientQ16={
|
||||
PassageRoundRatio(surfaceDx<0?-surfaceDz:surfaceDz,
|
||||
std::abs(surfaceDx)*65536,
|
||||
surfaceHorizontalSquared),
|
||||
PassageRoundRatio(surfaceDy<0?-surfaceDz:surfaceDz,
|
||||
std::abs(surfaceDy)*65536,
|
||||
surfaceHorizontalSquared)};
|
||||
}
|
||||
}
|
||||
const std::uint64_t gradientSquared=static_cast<std::uint64_t>(
|
||||
chamber.floorGradientQ16[0]*chamber.floorGradientQ16[0]
|
||||
+chamber.floorGradientQ16[1]*chamber.floorGradientQ16[1]);
|
||||
const std::int64_t gradientMagnitude=static_cast<std::int64_t>(
|
||||
PassageIsqrt(gradientSquared));
|
||||
constexpr std::int64_t MaximumLandingGradeQ16=17560;
|
||||
if(gradientMagnitude>MaximumLandingGradeQ16)
|
||||
{error=passage.stableId+" "+side+" landing chamber floor exceeds 15 degrees";return false;}
|
||||
const std::int64_t planeReachQ=PassageRoundRatio(
|
||||
chamber.radiusSubvoxels,gradientMagnitude,65536);
|
||||
const std::int64_t shellQ=static_cast<std::int64_t>(
|
||||
std::llround(0.75*qPerMetre));
|
||||
const auto qToUm=[&](std::int64_t q)
|
||||
{return q*static_cast<std::int64_t>(layout.coreVoxelSizeMicrometres)
|
||||
/WorldAssemblySubvoxelsPerVoxel;};
|
||||
if(!host.content.presentInCurrentField
|
||||
||qToUm(chamber.floorCenterSubvoxels[2]-planeReachQ-shellQ)
|
||||
<host.content.bottomMicrometres
|
||||
||qToUm(chamber.floorCenterSubvoxels[2]+planeReachQ
|
||||
+chamber.clearHeightSubvoxels+shellQ)
|
||||
>host.content.topMicrometres)
|
||||
{
|
||||
error=passage.stableId+" "+side
|
||||
+" landing chamber does not fit inside its host content band";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
if(!resolveLandingChamber(passage.upperLandingChamber,upperMouth,0,1,
|
||||
upper,"upper")
|
||||
||!resolveLandingChamber(passage.lowerLandingChamber,lowerMouth,
|
||||
lowerMouthIndex,lowerMouthIndex-1,lower,"lower"))
|
||||
return false;
|
||||
passage.floorProfileIncludesLandingChambers=true;
|
||||
const auto overlapCensus=AuditResolvedLayoutPassageSelfOverlapV1(
|
||||
passage,layout.coreVoxelSizeMicrometres);
|
||||
std::printf("[PassageTubeSelfOverlapCensus] id=%s segment_pairs=%llu measured_pairs=%llu local_exempt_pairs=%llu overlap_pairs=%llu first_overlap_segments=(%llu,%llu) end_turn_max_deg=%.3f end_turn_violations=%u landing_column_violations=%llu minimum_clearance_m=%.6f status=%s\n",
|
||||
@@ -6256,14 +5989,20 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
||||
endRunLength/static_cast<double>(qPerMetre));
|
||||
}
|
||||
}
|
||||
std::printf("[PassageLandingChamberPlan] id=%s upper_radius_m=%.3f lower_radius_m=%.3f clear_height_m=4.500 upper_floor_grade_q16=(%lld,%lld) lower_floor_grade_q16=(%lld,%lld) status=PASS\n",
|
||||
std::printf("[PassageLandingPadPlan] id=%s upper_radius_m=%.3f lower_radius_m=%.3f upper_floor_z_m=%.3f lower_floor_z_m=%.3f source=h(s) status=PASS\n",
|
||||
passage.stableId.c_str(),
|
||||
passage.upperLandingChamber.radiusSubvoxels/static_cast<double>(qPerMetre),
|
||||
passage.lowerLandingChamber.radiusSubvoxels/static_cast<double>(qPerMetre),
|
||||
static_cast<long long>(passage.upperLandingChamber.floorGradientQ16[0]),
|
||||
static_cast<long long>(passage.upperLandingChamber.floorGradientQ16[1]),
|
||||
static_cast<long long>(passage.lowerLandingChamber.floorGradientQ16[0]),
|
||||
static_cast<long long>(passage.lowerLandingChamber.floorGradientQ16[1]));
|
||||
ResolvedLayoutPassageLandingDiscRadiusSubvoxels(
|
||||
passage.mouthRadiusSubvoxels,layout.coreVoxelSizeMicrometres)
|
||||
/static_cast<double>(qPerMetre),
|
||||
ResolvedLayoutPassageLandingDiscRadiusSubvoxels(
|
||||
passage.mouthRadiusSubvoxels,layout.coreVoxelSizeMicrometres)
|
||||
/static_cast<double>(qPerMetre),
|
||||
(passage.controlPoints.front().positionSubvoxels[2]
|
||||
-passage.controlPoints.front().radiusSubvoxels)
|
||||
/static_cast<double>(qPerMetre),
|
||||
(passage.controlPoints[passage.lowerMouthControlIndex].positionSubvoxels[2]
|
||||
-passage.controlPoints[passage.lowerMouthControlIndex].radiusSubvoxels)
|
||||
/static_cast<double>(qPerMetre));
|
||||
layout.passages.push_back(std::move(passage));
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1875,6 +1875,20 @@ namespace VoxelForgeCore
|
||||
{ return impl_&&impl_->shapeReady.load(std::memory_order_acquire)&&impl_->shape?impl_->shape->DensityAtUnclamped(static_cast<float>(position.x),static_cast<float>(position.y),static_cast<float>(position.z)):0.0f; }
|
||||
float World::passageSourceDensityAt(Vec3d position) const
|
||||
{ return impl_&&impl_->shapeReady.load(std::memory_order_acquire)&&impl_->shape?impl_->shape->PassageSourceDensityAt(static_cast<float>(position.x),static_cast<float>(position.y),static_cast<float>(position.z)):0.0f; }
|
||||
float World::passageFloorShellDistanceAt(Vec3d position) const
|
||||
{ return impl_&&impl_->shapeReady.load(std::memory_order_acquire)&&impl_->shape?impl_->shape->PassageFloorShellDistanceAt(static_cast<float>(position.x),static_cast<float>(position.y),static_cast<float>(position.z)):std::numeric_limits<float>::infinity(); }
|
||||
bool World::resolvedPassageLandingPadInfluenceBounds(std::size_t passageIndex,
|
||||
bool upper,std::array<double,3>& minimumCoreVoxels,
|
||||
std::array<double,3>& maximumCoreVoxels) const
|
||||
{
|
||||
if(!impl_||!impl_->shapeReady.load(std::memory_order_acquire)||!impl_->shape
|
||||
||!impl_->shape->ResolvedLayout
|
||||
||passageIndex>=impl_->shape->ResolvedLayout->passages.size())return false;
|
||||
return FStandaloneShapeWorld::ResolvedLandingPadInfluenceBounds(
|
||||
impl_->shape->ResolvedLayout->passages[passageIndex],upper,
|
||||
impl_->shape->ResolvedLayout->coreVoxelSizeMicrometres,
|
||||
minimumCoreVoxels,maximumCoreVoxels);
|
||||
}
|
||||
DetailFieldSample World::detailSampleAt(Vec3d position) const
|
||||
{ return impl_&&impl_->shapeReady.load(std::memory_order_acquire)&&impl_->shape?impl_->shape->DetailSampleAt(static_cast<float>(position.x),static_cast<float>(position.y),static_cast<float>(position.z)):DetailFieldSample{}; }
|
||||
float World::densityAtLattice(Vec3i position) const
|
||||
|
||||
Reference in new issue
Block a user