core: one tunnel for two strates with landing chambers (L151, owner design; v238, grade open)
Each passage records upper and lower landing chambers (floor, gradient, radius, 4.5 m clear height) composed with the tunnel shell; cave route endpoints bind to the chambers; envelope audit permits only the named chamber joints. Seed 2: 0 solid in 319,579 passage+chamber capsule samples, 0 shell violations, 0 envelope conflicts, controls fire. Open: tunnel-to-chamber floor blend leaves 19.1/19.0 deg grades (limit 15); sweeps/timing not run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
No files matched your search
@@ -510,8 +510,8 @@ namespace VoxelForgeCore
|
|||||||
// preserves open cave lanes through passage shell overlaps; 236 binds passage mouths to
|
// preserves open cave lanes through passage shell overlaps; 236 binds passage mouths to
|
||||||
// the planned graph route, preserves its walk corridor through shell overlaps, and keeps
|
// the planned graph route, preserves its walk corridor through shell overlaps, and keeps
|
||||||
// boundary-blended host rock from widening the authored passage wall.
|
// boundary-blended host rock from widening the authored passage wall.
|
||||||
// v237 adds the shared Core cave-graph shaft primitive and basal terminal route.
|
// v238 plans one resolved passage spline with explicit landing chambers.
|
||||||
inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 237;
|
inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 238;
|
||||||
|
|
||||||
enum class FieldChannel : std::uint32_t
|
enum class FieldChannel : std::uint32_t
|
||||||
{
|
{
|
||||||
@@ -1209,6 +1209,16 @@ namespace VoxelForgeCore
|
|||||||
std::int64_t radiusSubvoxels = 0;
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
struct ResolvedLayoutPassageV1
|
struct ResolvedLayoutPassageV1
|
||||||
{
|
{
|
||||||
std::string stableId;
|
std::string stableId;
|
||||||
@@ -1223,6 +1233,8 @@ namespace VoxelForgeCore
|
|||||||
std::int64_t midRadiusSubvoxels = 0;
|
std::int64_t midRadiusSubvoxels = 0;
|
||||||
// Derived from the mouth binding; the main descent is monotone through this point.
|
// Derived from the mouth binding; the main descent is monotone through this point.
|
||||||
std::size_t lowerMouthControlIndex = 0;
|
std::size_t lowerMouthControlIndex = 0;
|
||||||
|
ResolvedLayoutPassageLandingChamberV1 upperLandingChamber;
|
||||||
|
ResolvedLayoutPassageLandingChamberV1 lowerLandingChamber;
|
||||||
std::vector<ResolvedLayoutPassagePointV1> controlPoints;
|
std::vector<ResolvedLayoutPassagePointV1> controlPoints;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -565,7 +565,21 @@ namespace
|
|||||||
<<"],\"lower_mouth_q\":["<<p.lowerMouthSubvoxels[0]<<','
|
<<"],\"lower_mouth_q\":["<<p.lowerMouthSubvoxels[0]<<','
|
||||||
<<p.lowerMouthSubvoxels[1]<<','<<p.lowerMouthSubvoxels[2]
|
<<p.lowerMouthSubvoxels[1]<<','<<p.lowerMouthSubvoxels[2]
|
||||||
<<"],\"mouth_radius_q\":"<<p.mouthRadiusSubvoxels
|
<<"],\"mouth_radius_q\":"<<p.mouthRadiusSubvoxels
|
||||||
<<",\"mid_radius_q\":"<<p.midRadiusSubvoxels<<",\"control_points\":[";
|
<<",\"mid_radius_q\":"<<p.midRadiusSubvoxels;
|
||||||
|
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\":[";
|
||||||
for(std::size_t j=0;j<p.controlPoints.size();++j)
|
for(std::size_t j=0;j<p.controlPoints.size();++j)
|
||||||
{
|
{
|
||||||
if(j)out<<',';const auto& point=p.controlPoints[j];
|
if(j)out<<',';const auto& point=p.controlPoints[j];
|
||||||
@@ -1095,6 +1109,62 @@ bool ResolvedLayoutSnapshotV3::validate(ResolvedLayoutValidationModeV3 mode,
|
|||||||
if(!insideContent(*upper,passage.upperMouthSubvoxels)
|
if(!insideContent(*upper,passage.upperMouthSubvoxels)
|
||||||
||!insideContent(*lower,passage.lowerMouthSubvoxels))
|
||!insideContent(*lower,passage.lowerMouthSubvoxels))
|
||||||
add("passage_mouth_content",passage.stableId+" mouth is outside its owned content band");
|
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;
|
if(passage.controlPoints.size()<2)continue;
|
||||||
const std::int64_t PlayerCenterAboveFloorQ=coreVoxelSizeMicrometres>0
|
const std::int64_t PlayerCenterAboveFloorQ=coreVoxelSizeMicrometres>0
|
||||||
?static_cast<std::int64_t>(std::llround(1.13*WorldAssemblySubvoxelsPerVoxel
|
?static_cast<std::int64_t>(std::llround(1.13*WorldAssemblySubvoxelsPerVoxel
|
||||||
@@ -1730,6 +1800,21 @@ std::optional<ResolvedLayoutSnapshotV3> ResolvedLayoutSnapshotV3::FromJson(
|
|||||||
const auto& lower=v.Get("lower_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<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();
|
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())
|
for(const auto& q:v.Get("control_points").AsArray())
|
||||||
{
|
{
|
||||||
ResolvedLayoutPassagePointV1 point;const auto& position=q.Get("position_q").AsArray();
|
ResolvedLayoutPassagePointV1 point;const auto& position=q.Get("position_q").AsArray();
|
||||||
@@ -2367,7 +2452,6 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
|
|||||||
constexpr std::int64_t MinimumBoreRadius=1750000;
|
constexpr std::int64_t MinimumBoreRadius=1750000;
|
||||||
constexpr std::int64_t MinimumBoreHeight=2200000;
|
constexpr std::int64_t MinimumBoreHeight=2200000;
|
||||||
constexpr std::int64_t PlannedBoreHeight=4500000;
|
constexpr std::int64_t PlannedBoreHeight=4500000;
|
||||||
constexpr std::int64_t MouthOverlap=2000000;
|
|
||||||
constexpr std::int64_t PortalLeadLength=6000000;
|
constexpr std::int64_t PortalLeadLength=6000000;
|
||||||
constexpr std::int64_t MaximumBoreRadius=10000000;
|
constexpr std::int64_t MaximumBoreRadius=10000000;
|
||||||
constexpr std::int64_t MaximumWalkGradeDenominator=5; // 1:5 = 11.31 degrees.
|
constexpr std::int64_t MaximumWalkGradeDenominator=5; // 1:5 = 11.31 degrees.
|
||||||
@@ -2394,13 +2478,13 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
|
|||||||
const auto qToMicrometres=[&](std::int64_t q)
|
const auto qToMicrometres=[&](std::int64_t q)
|
||||||
{return q*static_cast<std::int64_t>(layout.coreVoxelSizeMicrometres)
|
{return q*static_cast<std::int64_t>(layout.coreVoxelSizeMicrometres)
|
||||||
/WorldAssemblySubvoxelsPerVoxel;};
|
/WorldAssemblySubvoxelsPerVoxel;};
|
||||||
const std::int64_t mouthBoreRadius=std::clamp<std::int64_t>(
|
constexpr std::int64_t LandingConnectorBoreRadius=2000000;
|
||||||
std::max(MinimumBoreRadius,settings.minimumTunnelRadiusMicrometres),
|
const std::int64_t mouthBoreRadius=LandingConnectorBoreRadius;
|
||||||
MinimumBoreRadius,MaximumBoreRadius);
|
|
||||||
const auto chooseMouthRouteTangent=[&](const ResolvedLayoutPassageV1& passage,
|
const auto chooseMouthRouteTangent=[&](const ResolvedLayoutPassageV1& passage,
|
||||||
std::size_t mouthIndex,bool lowerSide,
|
std::size_t mouthIndex,bool lowerSide,
|
||||||
const std::array<std::int64_t,2>& authoredRouteTangentQ16,
|
const std::array<std::int64_t,2>& authoredRouteTangentQ16,
|
||||||
std::int64_t authoredFloorSlopeQ16,const FPoint& mouthFloorPoint)
|
std::int64_t authoredFloorSlopeQ16,const FPoint& mouthFloorPoint,
|
||||||
|
std::int64_t chamberRadiusMicrometres)
|
||||||
{
|
{
|
||||||
constexpr std::array<std::array<std::int64_t,2>,16> DirectionsQ16={{
|
constexpr std::array<std::array<std::int64_t,2>,16> DirectionsQ16={{
|
||||||
{{65536,0}},{{60547,25080}},{{46341,46341}},{{25080,60547}},
|
{{65536,0}},{{60547,25080}},{{46341,46341}},{{25080,60547}},
|
||||||
@@ -2418,7 +2502,9 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
|
|||||||
+outwardQ16[1]*authoredRouteTangentQ16[1])
|
+outwardQ16[1]*authoredRouteTangentQ16[1])
|
||||||
/(65536ll*65536ll));
|
/(65536ll*65536ll));
|
||||||
constexpr std::int64_t MouthClearanceProbeExtent=48000000;
|
constexpr std::int64_t MouthClearanceProbeExtent=48000000;
|
||||||
for(std::int64_t distance=2000000;
|
const std::int64_t firstClearanceProbe=std::max<std::int64_t>(
|
||||||
|
2000000,chamberRadiusMicrometres+1000000);
|
||||||
|
for(std::int64_t distance=firstClearanceProbe;
|
||||||
distance<=MouthClearanceProbeExtent;distance+=2000000)
|
distance<=MouthClearanceProbeExtent;distance+=2000000)
|
||||||
{
|
{
|
||||||
const std::int64_t x=mouthFloorPoint[0]
|
const std::int64_t x=mouthFloorPoint[0]
|
||||||
@@ -2515,9 +2601,10 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
|
|||||||
bool lowerSide,FPoint& point,std::array<std::int64_t,2>& routeTangentQ16,
|
bool lowerSide,FPoint& point,std::array<std::int64_t,2>& routeTangentQ16,
|
||||||
std::int64_t& floorSlopeQ16)
|
std::int64_t& floorSlopeQ16)
|
||||||
{
|
{
|
||||||
|
const auto& chamber=lowerSide?passage.lowerLandingChamber
|
||||||
|
:passage.upperLandingChamber;
|
||||||
for(std::size_t axis=0;axis<3;++axis)
|
for(std::size_t axis=0;axis<3;++axis)
|
||||||
point[axis]=qToMicrometres(lowerSide?passage.lowerMouthSubvoxels[axis]
|
point[axis]=qToMicrometres(chamber.floorCenterSubvoxels[axis]);
|
||||||
:passage.upperMouthSubvoxels[axis]);
|
|
||||||
if(passage.controlPoints.size()<2)return false;
|
if(passage.controlPoints.size()<2)return false;
|
||||||
const std::size_t mouthIndex=lowerSide
|
const std::size_t mouthIndex=lowerSide
|
||||||
?std::min(passage.lowerMouthControlIndex,passage.controlPoints.size()-1):0;
|
?std::min(passage.lowerMouthControlIndex,passage.controlPoints.size()-1):0;
|
||||||
@@ -2537,28 +2624,26 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
|
|||||||
if(tangentLength<=0)return false;
|
if(tangentLength<=0)return false;
|
||||||
routeTangentQ16={tx*65536/tangentLength,ty*65536/tangentLength};
|
routeTangentQ16={tx*65536/tangentLength,ty*65536/tangentLength};
|
||||||
const auto& mouthControl=passage.controlPoints[mouthIndex];
|
const auto& mouthControl=passage.controlPoints[mouthIndex];
|
||||||
const auto& tangentControl=passage.controlPoints[tangentIndex];
|
const std::int64_t mouthFloor=chamber.floorCenterSubvoxels[2];
|
||||||
const std::int64_t mouthFloor=mouthControl.positionSubvoxels[2]
|
|
||||||
-mouthControl.radiusSubvoxels;
|
|
||||||
const std::int64_t tangentFloor=tangentControl.positionSubvoxels[2]
|
|
||||||
-tangentControl.radiusSubvoxels;
|
|
||||||
// Resolved passage mouths already store the player center at floor + 1.13 m.
|
|
||||||
// Cave edge control points store the floor itself, so start from the passage
|
|
||||||
// control's floor rather than reusing the player-center mouth height.
|
|
||||||
point[2]=qToMicrometres(mouthFloor);
|
point[2]=qToMicrometres(mouthFloor);
|
||||||
const std::int64_t passageFloorDelta=direction*(mouthFloor-tangentFloor);
|
floorSlopeQ16=(chamber.floorGradientQ16[0]*routeTangentQ16[0]
|
||||||
floorSlopeQ16=passageFloorDelta*65536/tangentLength;
|
+chamber.floorGradientQ16[1]*routeTangentQ16[1])/65536;
|
||||||
const auto authoredRouteTangentQ16=routeTangentQ16;
|
const auto authoredRouteTangentQ16=routeTangentQ16;
|
||||||
routeTangentQ16=chooseMouthRouteTangent(passage,mouthIndex,lowerSide,
|
routeTangentQ16=chooseMouthRouteTangent(passage,mouthIndex,lowerSide,
|
||||||
authoredRouteTangentQ16,floorSlopeQ16,point);
|
authoredRouteTangentQ16,floorSlopeQ16,point,
|
||||||
const std::int64_t tangentAlignment=routeTangentQ16[0]
|
qToMicrometres(chamber.radiusSubvoxels));
|
||||||
*authoredRouteTangentQ16[0]+routeTangentQ16[1]
|
floorSlopeQ16=(chamber.floorGradientQ16[0]*routeTangentQ16[0]
|
||||||
*authoredRouteTangentQ16[1];
|
+chamber.floorGradientQ16[1]*routeTangentQ16[1])/65536;
|
||||||
floorSlopeQ16=floorSlopeQ16*tangentAlignment/(65536ll*65536ll);
|
|
||||||
const std::int64_t offsetDirection=lowerSide?1:-1;
|
const std::int64_t offsetDirection=lowerSide?1:-1;
|
||||||
point[0]+=offsetDirection*routeTangentQ16[0]*MouthOverlap/65536;
|
const std::int64_t portOffset=std::max<std::int64_t>(0,
|
||||||
point[1]+=offsetDirection*routeTangentQ16[1]*MouthOverlap/65536;
|
qToMicrometres(chamber.radiusSubvoxels)-LandingConnectorBoreRadius);
|
||||||
point[2]+=offsetDirection*floorSlopeQ16*MouthOverlap/65536;
|
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;
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
FPoint routeStart{},routeEnd{};
|
FPoint routeStart{},routeEnd{};
|
||||||
@@ -3391,13 +3476,14 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
|
|||||||
const auto arrivalPoint=routeStart;
|
const auto arrivalPoint=routeStart;
|
||||||
FPoint previousPoint=arrivalPoint;
|
FPoint previousPoint=arrivalPoint;
|
||||||
std::int64_t previousProfileDistance=0;
|
std::int64_t previousProfileDistance=0;
|
||||||
std::string previousAnchor=arrivalPassage->stableId+"/lower-mouth";
|
std::string previousAnchor=arrivalPassage->stableId+"/lower-landing-chamber";
|
||||||
for(std::size_t nodeIndex=0;nodeIndex<nodes.size();++nodeIndex)
|
for(std::size_t nodeIndex=0;nodeIndex<nodes.size();++nodeIndex)
|
||||||
{
|
{
|
||||||
const auto& node=nodes[nodeIndex];
|
const auto& node=nodes[nodeIndex];
|
||||||
const std::string edgeId=routePrefix+"route-edge-"+std::to_string(nodeIndex);
|
const std::string edgeId=routePrefix+"route-edge-"+std::to_string(nodeIndex);
|
||||||
auto edge=makeEdge(edgeId,previousAnchor,previousPoint,node,
|
auto edge=makeEdge(edgeId,previousAnchor,previousPoint,node,
|
||||||
selectedBoreRadius(edgeId),
|
nodeIndex==0?std::min(selectedBoreRadius(edgeId),LandingConnectorBoreRadius)
|
||||||
|
:selectedBoreRadius(edgeId),
|
||||||
previousProfileDistance,node.floorProfileDistanceMicrometres);
|
previousProfileDistance,node.floorProfileDistanceMicrometres);
|
||||||
edges.push_back(std::move(edge));
|
edges.push_back(std::move(edge));
|
||||||
previousPoint={node.point[0],node.point[1],node.point[2]};
|
previousPoint={node.point[0],node.point[1],node.point[2]};
|
||||||
@@ -3407,8 +3493,10 @@ bool PlanResolvedCavernNetworkV1(const ResolvedLayoutSnapshotV3& layout,
|
|||||||
if(!nodes.empty()&&departurePassage)
|
if(!nodes.empty()&&departurePassage)
|
||||||
{
|
{
|
||||||
const std::string departureEdgeId=routePrefix+"departure-edge";
|
const std::string departureEdgeId=routePrefix+"departure-edge";
|
||||||
auto edge=makeEdge(departureEdgeId,departurePassage->stableId+"/upper-mouth",
|
auto edge=makeEdge(departureEdgeId,
|
||||||
routeEnd,nodes.back(),selectedBoreRadius(departureEdgeId),
|
departurePassage->stableId+"/upper-landing-chamber",
|
||||||
|
routeEnd,nodes.back(),
|
||||||
|
std::min(selectedBoreRadius(departureEdgeId),LandingConnectorBoreRadius),
|
||||||
totalRouteLength,nodes.back().floorProfileDistanceMicrometres);
|
totalRouteLength,nodes.back().floorProfileDistanceMicrometres);
|
||||||
edges.push_back(std::move(edge));
|
edges.push_back(std::move(edge));
|
||||||
}
|
}
|
||||||
@@ -3873,7 +3961,6 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
|||||||
{error=passage.stableId+" route mouth is outside the resolved PassageConfig.DistanceMax radius";return false;}
|
{error=passage.stableId+" route mouth is outside the resolved PassageConfig.DistanceMax radius";return false;}
|
||||||
passage.upperMouthSubvoxels=upperMouth;passage.lowerMouthSubvoxels=lowerMouth;
|
passage.upperMouthSubvoxels=upperMouth;passage.lowerMouthSubvoxels=lowerMouth;
|
||||||
|
|
||||||
constexpr std::int64_t LandingExtensionQ=8;
|
|
||||||
std::array<std::int64_t,2> upperTangent{},lowerTangent{};
|
std::array<std::int64_t,2> upperTangent{},lowerTangent{};
|
||||||
const bool hasUpperTangent=hasUpperRoute
|
const bool hasUpperTangent=hasUpperRoute
|
||||||
&&routeTangent(upper,true,upperTangent);
|
&&routeTangent(upper,true,upperTangent);
|
||||||
@@ -4643,39 +4730,6 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
|||||||
}
|
}
|
||||||
pushXY({lowerMouth[0],lowerMouth[1]});totalHorizontal=measurePath();
|
pushXY({lowerMouth[0],lowerMouth[1]});totalHorizontal=measurePath();
|
||||||
std::size_t lowerMouthIndex=xy.size()-1;
|
std::size_t lowerMouthIndex=xy.size()-1;
|
||||||
std::vector<std::array<std::int64_t,3>> lowerLandingPoints;
|
|
||||||
if(hasLowerTangent)
|
|
||||||
{
|
|
||||||
std::array<std::int64_t,3> previousRoutePoint=lowerMouth;
|
|
||||||
std::int64_t remainingQ=LandingExtensionQ*qPerMetre;
|
|
||||||
bool skippedMouth=false;
|
|
||||||
for(const auto& anchor:lower.anchors)
|
|
||||||
if(anchor.kind=="route_point"&&anchor.values.size()>=3)
|
|
||||||
{
|
|
||||||
const std::array<std::int64_t,3> routePoint{{anchor.values[0],
|
|
||||||
anchor.values[1],anchor.values[2]}};
|
|
||||||
if(!skippedMouth){skippedMouth=true;continue;}
|
|
||||||
if(remainingQ<=0)break;
|
|
||||||
const std::int64_t dx=routePoint[0]-previousRoutePoint[0];
|
|
||||||
const std::int64_t dy=routePoint[1]-previousRoutePoint[1];
|
|
||||||
const std::uint64_t segmentLength=distanceQ(dx,dy);
|
|
||||||
if(segmentLength==0)continue;
|
|
||||||
const std::int64_t advance=std::min<std::int64_t>(remainingQ,
|
|
||||||
static_cast<std::int64_t>(segmentLength));
|
|
||||||
std::array<std::int64_t,3> landingPoint{};
|
|
||||||
for(std::size_t axis=0;axis<3;++axis)
|
|
||||||
landingPoint[axis]=previousRoutePoint[axis]+PassageRoundRatio(
|
|
||||||
routePoint[axis]-previousRoutePoint[axis],advance,
|
|
||||||
static_cast<std::int64_t>(segmentLength));
|
|
||||||
if(!radialWithin(landingPoint,placementRadius))break;
|
|
||||||
lowerLandingPoints.push_back(landingPoint);
|
|
||||||
pushXY({landingPoint[0],landingPoint[1]});
|
|
||||||
previousRoutePoint=landingPoint;remainingQ-=advance;
|
|
||||||
if(advance<static_cast<std::int64_t>(segmentLength))break;
|
|
||||||
}
|
|
||||||
if(lowerLandingPoints.empty())
|
|
||||||
{error=passage.stableId+" lower route has no landing length inside PassageConfig.DistanceMax";return false;}
|
|
||||||
}
|
|
||||||
if(VoxelForgeCore::WorldAssemblyEvaluatorVersion>=188
|
if(VoxelForgeCore::WorldAssemblyEvaluatorVersion>=188
|
||||||
&&style==0&&anchoredIslandPassage&&!anchoredSpiral&&xy.size()>4)
|
&&style==0&&anchoredIslandPassage&&!anchoredSpiral&&xy.size()>4)
|
||||||
{
|
{
|
||||||
@@ -4718,8 +4772,7 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
|||||||
std::max<std::int64_t>(8*qPerMetre,2*mouthRadius));
|
std::max<std::int64_t>(8*qPerMetre,2*mouthRadius));
|
||||||
for(std::size_t i=0;i<xy.size();++i)
|
for(std::size_t i=0;i<xy.size();++i)
|
||||||
{
|
{
|
||||||
std::int64_t radius=(i==0||i==lowerMouthIndex
|
std::int64_t radius=(i==0||i==lowerMouthIndex)?mouthRadius:midRadius;
|
||||||
||(i>lowerMouthIndex&&i+1>=xy.size()-1))?mouthRadius:midRadius;
|
|
||||||
if(surfaceGroundDeparture&&i<lowerMouthIndex)
|
if(surfaceGroundDeparture&&i<lowerMouthIndex)
|
||||||
{
|
{
|
||||||
const double t=std::clamp(static_cast<double>(cumulative[i])
|
const double t=std::clamp(static_cast<double>(cumulative[i])
|
||||||
@@ -4761,9 +4814,7 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
std::int64_t playerZ=0;
|
std::int64_t playerZ=0;
|
||||||
if(i>lowerMouthIndex)
|
if(hasAnchorHelix&&i<=helixEndIndex)
|
||||||
playerZ=lowerLandingPoints[i-lowerMouthIndex-1][2];
|
|
||||||
else if(hasAnchorHelix&&i<=helixEndIndex)
|
|
||||||
playerZ=upperMouth[2]+PassageRoundRatio(helixBedrockZ-upperMouth[2],
|
playerZ=upperMouth[2]+PassageRoundRatio(helixBedrockZ-upperMouth[2],
|
||||||
static_cast<std::int64_t>(cumulative[i]),
|
static_cast<std::int64_t>(cumulative[i]),
|
||||||
static_cast<std::int64_t>(helixHorizontal));
|
static_cast<std::int64_t>(helixHorizontal));
|
||||||
@@ -4815,6 +4866,201 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
|||||||
passage.controlPoints[lowerMouthIndex].positionSubvoxels[2]=lowerMouth[2]
|
passage.controlPoints[lowerMouthIndex].positionSubvoxels[2]=lowerMouth[2]
|
||||||
+passage.controlPoints[lowerMouthIndex].radiusSubvoxels
|
+passage.controlPoints[lowerMouthIndex].radiusSubvoxels
|
||||||
-playerCenterAboveFloorQ;
|
-playerCenterAboveFloorQ;
|
||||||
|
std::vector<VoxelForgeCore::PassageSpline::FPoint> chamberFloorControls;
|
||||||
|
std::vector<double> chamberFloorProfile;
|
||||||
|
chamberFloorControls.reserve(passage.controlPoints.size());
|
||||||
|
chamberFloorProfile.reserve(passage.controlPoints.size());
|
||||||
|
for(const auto& point:passage.controlPoints)
|
||||||
|
{
|
||||||
|
chamberFloorControls.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>(
|
||||||
|
point.positionSubvoxels[2]-point.radiusSubvoxels));
|
||||||
|
}
|
||||||
|
const auto routePoints=[&](const ResolvedLayoutStratumV2& host)
|
||||||
|
{
|
||||||
|
std::vector<std::array<double,3>> route;
|
||||||
|
for(const auto& anchor:host.anchors)
|
||||||
|
if(anchor.kind=="route_point"&&anchor.values.size()>=3)
|
||||||
|
route.push_back({static_cast<double>(anchor.values[0]),
|
||||||
|
static_cast<double>(anchor.values[1]),
|
||||||
|
static_cast<double>(anchor.values[2])});
|
||||||
|
return route;
|
||||||
|
};
|
||||||
|
if(upper.generatorKind=="TunnelNetwork"
|
||||||
|
||upper.generatorKind=="SurfaceWorld")
|
||||||
|
{
|
||||||
|
auto route=routePoints(upper);
|
||||||
|
if(upper.generatorKind=="TunnelNetwork"&&route.size()>2)
|
||||||
|
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],
|
||||||
|
routeHeight,distanceSquared)
|
||||||
|
&&distanceSquared<=std::pow(chamberFloorControls.front()[3]
|
||||||
|
+2.0*qPerMetre,2.0))
|
||||||
|
VoxelForgeCore::PassageSpline::BlendEndpointFloorToRoute(
|
||||||
|
chamberFloorControls,chamberFloorProfile,0,route,qPerMetre,
|
||||||
|
upper.generatorKind=="SurfaceWorld"?0.98:1.13);
|
||||||
|
}
|
||||||
|
if(lower.generatorKind=="TunnelNetwork"
|
||||||
|
||lower.generatorKind=="VerticalShafts")
|
||||||
|
{
|
||||||
|
auto route=routePoints(lower);
|
||||||
|
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],
|
||||||
|
routeHeight,distanceSquared)
|
||||||
|
&&distanceSquared<=std::pow(
|
||||||
|
chamberFloorControls[lowerMouthIndex][3]
|
||||||
|
+2.0*qPerMetre,2.0))
|
||||||
|
VoxelForgeCore::PassageSpline::BlendEndpointFloorToRoute(
|
||||||
|
chamberFloorControls,chamberFloorProfile,
|
||||||
|
lowerMouthIndex,route,qPerMetre);
|
||||||
|
}
|
||||||
|
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")
|
||||||
|
{
|
||||||
|
const auto surfaceRoute=routePoints(host);
|
||||||
|
std::vector<std::array<double,3>> nearbyRoute;
|
||||||
|
// Fit the landing floor only to route anchors inside its
|
||||||
|
// chamber footprint; samples beyond the chamber can pull
|
||||||
|
// the floor away from the route at the joint.
|
||||||
|
const double supportRadius=static_cast<double>(
|
||||||
|
chamber.radiusSubvoxels);
|
||||||
|
for(const auto& point:surfaceRoute)
|
||||||
|
{
|
||||||
|
const double routeDx=point[0]-endpoint.positionSubvoxels[0];
|
||||||
|
const double routeDy=point[1]-endpoint.positionSubvoxels[1];
|
||||||
|
if(routeDx*routeDx+routeDy*routeDy<=supportRadius*supportRadius)
|
||||||
|
nearbyRoute.push_back(point);
|
||||||
|
}
|
||||||
|
if(nearbyRoute.size()>=2)
|
||||||
|
{
|
||||||
|
std::array<double,3> mean{};
|
||||||
|
for(const auto& point:nearbyRoute)
|
||||||
|
for(std::size_t axis=0;axis<3;++axis)
|
||||||
|
mean[axis]+=point[axis]/nearbyRoute.size();
|
||||||
|
double xx=0.0,xy=0.0,yy=0.0,xz=0.0,yz=0.0;
|
||||||
|
for(const auto& point:nearbyRoute)
|
||||||
|
{
|
||||||
|
const double routeDx=point[0]-mean[0];
|
||||||
|
const double routeDy=point[1]-mean[1];
|
||||||
|
const double routeDz=point[2]-mean[2];
|
||||||
|
xx+=routeDx*routeDx;xy+=routeDx*routeDy;
|
||||||
|
yy+=routeDy*routeDy;xz+=routeDx*routeDz;
|
||||||
|
yz+=routeDy*routeDz;
|
||||||
|
}
|
||||||
|
const double determinant=xx*yy-xy*xy;
|
||||||
|
double gradientX=0.0,gradientY=0.0;
|
||||||
|
if(determinant>1.0e-9)
|
||||||
|
{
|
||||||
|
gradientX=(xz*yy-yz*xy)/determinant;
|
||||||
|
gradientY=(yz*xx-xz*xy)/determinant;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Route anchors can be collinear inside the chamber.
|
||||||
|
// Fit their dominant horizontal direction by least
|
||||||
|
// squares instead of extrapolating from one far point.
|
||||||
|
const double trace=xx+yy;
|
||||||
|
const double discriminant=std::sqrt(std::max(0.0,
|
||||||
|
(xx-yy)*(xx-yy)+4.0*xy*xy));
|
||||||
|
const double variance=0.5*(trace+discriminant);
|
||||||
|
if(variance>1.0e-9)
|
||||||
|
{
|
||||||
|
double axisX=1.0,axisY=0.0;
|
||||||
|
if(std::abs(xy)>1.0e-12)
|
||||||
|
{axisX=xy;axisY=variance-xx;}
|
||||||
|
else if(yy>xx)
|
||||||
|
{axisX=0.0;axisY=1.0;}
|
||||||
|
const double axisLength=std::hypot(axisX,axisY);
|
||||||
|
axisX/=axisLength;axisY/=axisLength;
|
||||||
|
const double routeSlope=(axisX*xz+axisY*yz)/variance;
|
||||||
|
gradientX=routeSlope*axisX;
|
||||||
|
gradientY=routeSlope*axisY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
chamber.floorGradientQ16={
|
||||||
|
static_cast<std::int32_t>(std::llround(gradientX*65536.0)),
|
||||||
|
static_cast<std::int32_t>(std::llround(gradientY*65536.0))};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
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",
|
||||||
|
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]));
|
||||||
layout.passages.push_back(std::move(passage));
|
layout.passages.push_back(std::move(passage));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4842,17 +5088,22 @@ ResolvedLayoutValidatorAuditV3 AuditResolvedLayoutValidatorV3()
|
|||||||
else result.failures.push_back({"negative_control_accepted",std::move(name)+" was accepted by strict validator"});
|
else result.failures.push_back({"negative_control_accepted",std::move(name)+" was accepted by strict validator"});
|
||||||
};
|
};
|
||||||
auto base=ResolvedLayoutSnapshotV3{};base.worldIdentity="synthetic";base.ownerVoxelSizeMicrometres=125000;
|
auto base=ResolvedLayoutSnapshotV3{};base.worldIdentity="synthetic";base.ownerVoxelSizeMicrometres=125000;
|
||||||
base.coreVoxelSizeMicrometres=250000;base.strates={make("a",0,10000000,0),make("b",1,-2000000,-12000000)};
|
base.coreVoxelSizeMicrometres=250000;base.strates={make("a",0,30000000,0),make("b",1,-2000000,-40000000)};
|
||||||
ResolvedLayoutPassageConfigV1 passageConfig;passageConfig.upperStrateIndex=0;passageConfig.connections=1;
|
ResolvedLayoutPassageConfigV1 passageConfig;passageConfig.upperStrateIndex=0;passageConfig.connections=1;
|
||||||
passageConfig.mouthRadiusSubvoxels=1280;passageConfig.midRadiusSubvoxels=1280;
|
passageConfig.mouthRadiusSubvoxels=1280;passageConfig.midRadiusSubvoxels=1280;
|
||||||
passageConfig.reachMinimumSubvoxels=2048;passageConfig.reachMaximumSubvoxels=2048;
|
passageConfig.reachMinimumSubvoxels=9216;passageConfig.reachMaximumSubvoxels=9216;
|
||||||
passageConfig.distanceMinimumSubvoxels=1024;passageConfig.distanceMaximumSubvoxels=10240;
|
passageConfig.distanceMinimumSubvoxels=1024;passageConfig.distanceMaximumSubvoxels=10240;
|
||||||
std::string planError;
|
std::string planError;
|
||||||
if(!PlanResolvedLayoutPassagesV1(base,19,{passageConfig},planError))
|
if(!PlanResolvedLayoutPassagesV1(base,19,{passageConfig},planError))
|
||||||
result.failures.push_back({"synthetic_passage_plan_failed",planError});
|
result.failures.push_back({"synthetic_passage_plan_failed",planError});
|
||||||
{std::vector<ResolvedLayoutIssueV2> issues;std::string error;
|
{std::vector<ResolvedLayoutIssueV2> issues;std::string error;
|
||||||
if(!base.validate(ResolvedLayoutValidationModeV3::strict,issues,error))
|
if(!base.validate(ResolvedLayoutValidationModeV3::strict,issues,error))
|
||||||
result.failures.push_back({"positive_control_rejected","valid ceiling/content/bedrock layout was rejected"});}
|
{
|
||||||
|
for(const auto& issue:issues)
|
||||||
|
std::printf("[ResolvedLayoutValidatorPositiveControlIssue] code=%s detail=%s\n",
|
||||||
|
issue.code.c_str(),issue.detail.c_str());
|
||||||
|
result.failures.push_back({"positive_control_rejected","valid ceiling/content/bedrock layout was rejected"});
|
||||||
|
}}
|
||||||
auto missingPassage=base;missingPassage.passages.clear();expectReject("missing interface passage",missingPassage);
|
auto missingPassage=base;missingPassage.passages.clear();expectReject("missing interface passage",missingPassage);
|
||||||
auto outsidePassage=base;
|
auto outsidePassage=base;
|
||||||
if(!outsidePassage.passages.empty())
|
if(!outsidePassage.passages.empty())
|
||||||
|
|||||||
@@ -1280,6 +1280,8 @@ public:
|
|||||||
double unionDistance=std::numeric_limits<double>::infinity();
|
double unionDistance=std::numeric_limits<double>::infinity();
|
||||||
double unionShellDistance=std::numeric_limits<double>::infinity();
|
double unionShellDistance=std::numeric_limits<double>::infinity();
|
||||||
double unionFloorShellDistance=std::numeric_limits<double>::infinity();
|
double unionFloorShellDistance=std::numeric_limits<double>::infinity();
|
||||||
|
double unionLandingChamberFloorShellDistance=
|
||||||
|
std::numeric_limits<double>::infinity();
|
||||||
double plannedTunnelPassageShellDistance=std::numeric_limits<double>::infinity();
|
double plannedTunnelPassageShellDistance=std::numeric_limits<double>::infinity();
|
||||||
const std::uint32_t reliefSeed=static_cast<std::uint32_t>(Owner?Owner->Seed:0);
|
const std::uint32_t reliefSeed=static_cast<std::uint32_t>(Owner?Owner->Seed:0);
|
||||||
const auto* queryStratum=FindBoundStratumForManagerZ(Z);
|
const auto* queryStratum=FindBoundStratumForManagerZ(Z);
|
||||||
@@ -1317,14 +1319,56 @@ public:
|
|||||||
const bool passageTargetsPlannedTunnelNetwork=plannedTunnelStratum;
|
const bool passageTargetsPlannedTunnelNetwork=plannedTunnelStratum;
|
||||||
const double mouthShellBlendMetres=passageTargetsPlannedTunnelNetwork
|
const double mouthShellBlendMetres=passageTargetsPlannedTunnelNetwork
|
||||||
?2.0:MouthShellBlendMetres;
|
?2.0:MouthShellBlendMetres;
|
||||||
// Legacy native cave fields own the floor and mouth carve on their
|
// Legacy native cave fields own the tunnel bore on their lower-strate
|
||||||
// lower-strate boundary. The planner's TunnelNetwork field instead
|
// boundary. The shared passage composer still owns its landing chamber.
|
||||||
// needs the shared passage composer to carry its incoming mouth open.
|
|
||||||
if(passageTargetsNativeCaveField&&!passageTargetsPlannedTunnelNetwork)
|
|
||||||
continue;
|
|
||||||
if(passageProfile)++passageProfile->passageIterations;
|
if(passageProfile)++passageProfile->passageIterations;
|
||||||
const auto& constants=PassageComposerConstants[passageIndex];
|
const auto& constants=PassageComposerConstants[passageIndex];
|
||||||
|
|
||||||
|
const VoxelForgeCore::ResolvedLayoutPassageLandingChamberV1* landingChamber=nullptr;
|
||||||
|
if(queryStratum&&queryStratum->Resolved)
|
||||||
|
{
|
||||||
|
if(queryStratum->StrateIndex==passage.upperStrateIndex)
|
||||||
|
landingChamber=&passage.upperLandingChamber;
|
||||||
|
else if(queryStratum->StrateIndex==passage.lowerStrateIndex)
|
||||||
|
landingChamber=&passage.lowerLandingChamber;
|
||||||
|
}
|
||||||
|
if(landingChamber&&landingChamber->radiusSubvoxels>0
|
||||||
|
&&landingChamber->clearHeightSubvoxels>0)
|
||||||
|
{
|
||||||
|
const double centerX=static_cast<double>(
|
||||||
|
landingChamber->floorCenterSubvoxels[0]);
|
||||||
|
const double centerY=static_cast<double>(
|
||||||
|
landingChamber->floorCenterSubvoxels[1]);
|
||||||
|
const double floorCenterZ=static_cast<double>(
|
||||||
|
landingChamber->floorCenterSubvoxels[2])
|
||||||
|
+(landingChamber->floorGradientQ16[0]*(qx-centerX)
|
||||||
|
+landingChamber->floorGradientQ16[1]*(qy-centerY))/65536.0;
|
||||||
|
const double radialDistance=DeterministicSqrt(
|
||||||
|
(qx-centerX)*(qx-centerX)+(qy-centerY)*(qy-centerY))
|
||||||
|
-landingChamber->radiusSubvoxels;
|
||||||
|
const double floorDistance=floorCenterZ-qz;
|
||||||
|
const double ceilingDistance=qz-(floorCenterZ
|
||||||
|
+landingChamber->clearHeightSubvoxels);
|
||||||
|
const double chamberDistance=std::max({radialDistance,
|
||||||
|
floorDistance,ceilingDistance});
|
||||||
|
const double chamberShellDistance=std::max(-chamberDistance,
|
||||||
|
chamberDistance-shellThickness);
|
||||||
|
const double chamberFloorShellDistance=std::max(
|
||||||
|
std::max(-floorDistance,floorDistance-shellThickness),
|
||||||
|
radialDistance);
|
||||||
|
unionDistance=std::min(unionDistance,chamberDistance);
|
||||||
|
unionShellDistance=std::min(unionShellDistance,chamberShellDistance);
|
||||||
|
unionFloorShellDistance=std::min(unionFloorShellDistance,
|
||||||
|
chamberFloorShellDistance);
|
||||||
|
unionLandingChamberFloorShellDistance=std::min(
|
||||||
|
unionLandingChamberFloorShellDistance,chamberFloorShellDistance);
|
||||||
|
if(passageTargetsPlannedTunnelNetwork)
|
||||||
|
plannedTunnelPassageShellDistance=std::min(
|
||||||
|
plannedTunnelPassageShellDistance,chamberShellDistance);
|
||||||
|
}
|
||||||
|
if(passageTargetsNativeCaveField&&!passageTargetsPlannedTunnelNetwork)
|
||||||
|
continue;
|
||||||
|
|
||||||
FNearestTubeSample nearest;
|
FNearestTubeSample nearest;
|
||||||
const bool usePreparedSweep=passageTileScopeMatches
|
const bool usePreparedSweep=passageTileScopeMatches
|
||||||
&&passageTileQueries->hasSweptTubeBase();
|
&&passageTileQueries->hasSweptTubeBase();
|
||||||
@@ -1615,6 +1659,11 @@ public:
|
|||||||
|
|
||||||
if(!std::isfinite(unionDistance))return density;
|
if(!std::isfinite(unionDistance))return density;
|
||||||
const float passageAirDistance=static_cast<float>(-unionDistance/Q);
|
const float passageAirDistance=static_cast<float>(-unionDistance/Q);
|
||||||
|
// Keep the landing floor shell where it is exposed to the chamber. At
|
||||||
|
// the tunnel joint, the passage void must be able to open through that
|
||||||
|
// floor shell so its own continuous floor and shell define the throat.
|
||||||
|
if(unionLandingChamberFloorShellDistance<0.0&&unionDistance>0.0)
|
||||||
|
return static_cast<float>(unionLandingChamberFloorShellDistance/Q);
|
||||||
const auto plannedPortalWalkCorridorContains=[&]()
|
const auto plannedPortalWalkCorridorContains=[&]()
|
||||||
{
|
{
|
||||||
if(!queryStratum||!queryStratum->Resolved
|
if(!queryStratum||!queryStratum->Resolved
|
||||||
@@ -1678,7 +1727,11 @@ public:
|
|||||||
const double routeFloor=std::lerp(a[2],b[2],t)-1.13;
|
const double routeFloor=std::lerp(a[2],b[2],t)-1.13;
|
||||||
return queryZ>=routeFloor-0.30&&queryZ<=routeFloor+2.25;
|
return queryZ>=routeFloor-0.30&&queryZ<=routeFloor+2.25;
|
||||||
};
|
};
|
||||||
constexpr double PortalWalkLeadMetres=8.0;
|
// The connected layer route begins inside the landing chamber. Let
|
||||||
|
// its air open the chamber's plain side shell through the chamber
|
||||||
|
// radius plus the shell thickness; the condition below remains
|
||||||
|
// limited to samples where the tunnel/chamber shell is present.
|
||||||
|
constexpr double PortalWalkLeadMetres=9.0;
|
||||||
const auto arrival=position(*first),arrivalInner=position(*second);
|
const auto arrival=position(*first),arrivalInner=position(*second);
|
||||||
const double arrivalInnerDistance=std::hypot(
|
const double arrivalInnerDistance=std::hypot(
|
||||||
arrivalInner[0]-arrival[0],arrivalInner[1]-arrival[1]);
|
arrivalInner[0]-arrival[0],arrivalInner[1]-arrival[1]);
|
||||||
@@ -1693,6 +1746,90 @@ public:
|
|||||||
departureTailDistance)
|
departureTailDistance)
|
||||||
||nearRouteSegment(*lastTwo,*last,PortalWalkLeadMetres,true,0.0);
|
||nearRouteSegment(*lastTwo,*last,PortalWalkLeadMetres,true,0.0);
|
||||||
};
|
};
|
||||||
|
const auto connectedSurfaceLandingShellContains=[&]()
|
||||||
|
{
|
||||||
|
if(!queryStratum||!queryStratum->Resolved
|
||||||
|
||queryStratum->Resolved->generatorKind!="SurfaceWorld")return false;
|
||||||
|
const double queryX=qx/qPerMetre,queryY=qy/qPerMetre,queryZ=qz/qPerMetre;
|
||||||
|
constexpr double ChamberShellThicknessMetres=0.75;
|
||||||
|
constexpr double RouteClearanceMetres=1.25;
|
||||||
|
constexpr double RouteHeightClearanceMetres=1.5;
|
||||||
|
constexpr double PortalWalkLeadMetres=9.0;
|
||||||
|
for(const auto& passage:ResolvedLayout->passages)
|
||||||
|
{
|
||||||
|
if(passage.upperStrateIndex!=queryStratum->StrateIndex)continue;
|
||||||
|
const auto& chamber=passage.upperLandingChamber;
|
||||||
|
if(chamber.radiusSubvoxels<=0||chamber.clearHeightSubvoxels<=0)continue;
|
||||||
|
const double cx=static_cast<double>(chamber.floorCenterSubvoxels[0])/qPerMetre;
|
||||||
|
const double cy=static_cast<double>(chamber.floorCenterSubvoxels[1])/qPerMetre;
|
||||||
|
const double floorZ=static_cast<double>(chamber.floorCenterSubvoxels[2])/qPerMetre
|
||||||
|
+chamber.floorGradientQ16[0]/65536.0*(queryX-cx)
|
||||||
|
+chamber.floorGradientQ16[1]/65536.0*(queryY-cy);
|
||||||
|
const double radialDistance=std::hypot(queryX-cx,queryY-cy)
|
||||||
|
-static_cast<double>(chamber.radiusSubvoxels)/qPerMetre;
|
||||||
|
const double floorDistance=floorZ-queryZ;
|
||||||
|
const double ceilingDistance=queryZ-(floorZ
|
||||||
|
+static_cast<double>(chamber.clearHeightSubvoxels)/qPerMetre);
|
||||||
|
const double chamberDistance=std::max({radialDistance,
|
||||||
|
floorDistance,ceilingDistance});
|
||||||
|
const double chamberShellDistance=std::max(-chamberDistance,
|
||||||
|
chamberDistance-ChamberShellThicknessMetres);
|
||||||
|
if(chamberDistance<=0.0||chamberShellDistance>=0.0)continue;
|
||||||
|
|
||||||
|
std::vector<const VoxelForgeCore::ResolvedLayoutAnchorV2*> route;
|
||||||
|
for(const auto& anchor:queryStratum->Resolved->anchors)
|
||||||
|
if(anchor.kind=="route_point"&&anchor.values.size()>=3)
|
||||||
|
route.push_back(&anchor);
|
||||||
|
if(route.size()<2)continue;
|
||||||
|
const double mouthZ=static_cast<double>(passage.upperMouthSubvoxels[2])
|
||||||
|
/qPerMetre;
|
||||||
|
std::size_t endpoint=route.size();
|
||||||
|
double endpointDistanceSquared=1.0;
|
||||||
|
for(std::size_t index=0;index<route.size();++index)
|
||||||
|
{
|
||||||
|
const double x=route[index]->values[0]/qPerMetre;
|
||||||
|
const double y=route[index]->values[1]/qPerMetre;
|
||||||
|
const double z=route[index]->values[2]/qPerMetre;
|
||||||
|
const double dx=x-cx,dy=y-cy;
|
||||||
|
const double distanceSquared=dx*dx+dy*dy;
|
||||||
|
if(distanceSquared<endpointDistanceSquared
|
||||||
|
&&std::abs(z-mouthZ)<=1.5)
|
||||||
|
{endpoint=index;endpointDistanceSquared=distanceSquared;}
|
||||||
|
}
|
||||||
|
if(endpoint==route.size())continue;
|
||||||
|
std::vector<double> routeDistance(route.size(),0.0);
|
||||||
|
for(std::size_t index=1;index<route.size();++index)
|
||||||
|
{
|
||||||
|
const double dx=(route[index]->values[0]-route[index-1]->values[0])
|
||||||
|
/qPerMetre;
|
||||||
|
const double dy=(route[index]->values[1]-route[index-1]->values[1])
|
||||||
|
/qPerMetre;
|
||||||
|
routeDistance[index]=routeDistance[index-1]+std::hypot(dx,dy);
|
||||||
|
}
|
||||||
|
for(std::size_t index=1;index<route.size();++index)
|
||||||
|
{
|
||||||
|
const auto& a=*route[index-1];
|
||||||
|
const auto& b=*route[index];
|
||||||
|
const double ax=a.values[0]/qPerMetre,ay=a.values[1]/qPerMetre;
|
||||||
|
const double az=a.values[2]/qPerMetre;
|
||||||
|
const double dx=(b.values[0]-a.values[0])/qPerMetre;
|
||||||
|
const double dy=(b.values[1]-a.values[1])/qPerMetre;
|
||||||
|
const double horizontalLengthSquared=dx*dx+dy*dy;
|
||||||
|
if(horizontalLengthSquared<=1.0e-12)continue;
|
||||||
|
const double t=std::clamp(((queryX-ax)*dx+(queryY-ay)*dy)
|
||||||
|
/horizontalLengthSquared,0.0,1.0);
|
||||||
|
const double alongFromEndpoint=std::abs(
|
||||||
|
std::lerp(routeDistance[index-1],routeDistance[index],t)
|
||||||
|
-routeDistance[endpoint]);
|
||||||
|
if(alongFromEndpoint>PortalWalkLeadMetres)continue;
|
||||||
|
const double routeX=ax+t*dx,routeY=ay+t*dy;
|
||||||
|
const double routeZ=az+t*(b.values[2]-a.values[2])/qPerMetre;
|
||||||
|
if(std::hypot(queryX-routeX,queryY-routeY)<=RouteClearanceMetres
|
||||||
|
&&std::abs(queryZ-routeZ)<=RouteHeightClearanceMetres)return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
if(plannedTunnelStratum
|
if(plannedTunnelStratum
|
||||||
&&nativeTunnelFieldDensity!=std::numeric_limits<float>::lowest()
|
&&nativeTunnelFieldDensity!=std::numeric_limits<float>::lowest()
|
||||||
&&nativeTunnelFieldDensity<0.0f)
|
&&nativeTunnelFieldDensity<0.0f)
|
||||||
@@ -1755,7 +1892,11 @@ public:
|
|||||||
if(nativeTunnelFieldDensity!=std::numeric_limits<float>::lowest())
|
if(nativeTunnelFieldDensity!=std::numeric_limits<float>::lowest())
|
||||||
return std::max(density,passageAirDistance);
|
return std::max(density,passageAirDistance);
|
||||||
if(unionDistance>0.0&&unionShellDistance<0.0&&density>=0.0f)
|
if(unionDistance>0.0&&unionShellDistance<0.0&&density>=0.0f)
|
||||||
|
{
|
||||||
|
if(connectedSurfaceLandingShellContains())
|
||||||
|
return std::max(density,passageAirDistance);
|
||||||
return static_cast<float>(unionShellDistance/Q);
|
return static_cast<float>(unionShellDistance/Q);
|
||||||
|
}
|
||||||
return std::max(density,passageAirDistance);
|
return std::max(density,passageAirDistance);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1892,6 +2033,26 @@ public:
|
|||||||
}
|
}
|
||||||
if(overlaps(low,high))return true;
|
if(overlaps(low,high))return true;
|
||||||
}
|
}
|
||||||
|
const auto chamberOverlaps=[&](
|
||||||
|
const VoxelForgeCore::ResolvedLayoutPassageLandingChamberV1& chamber)
|
||||||
|
{
|
||||||
|
const double cx=static_cast<double>(chamber.floorCenterSubvoxels[0])/Q;
|
||||||
|
const double cy=static_cast<double>(chamber.floorCenterSubvoxels[1])/Q;
|
||||||
|
const double cz=static_cast<double>(chamber.floorCenterSubvoxels[2])/Q;
|
||||||
|
const double radius=static_cast<double>(chamber.radiusSubvoxels)/Q;
|
||||||
|
const double floorExtent=radius*std::hypot(
|
||||||
|
static_cast<double>(chamber.floorGradientQ16[0])/65536.0,
|
||||||
|
static_cast<double>(chamber.floorGradientQ16[1])/65536.0);
|
||||||
|
const double extent=reachCoreVoxels+0.01;
|
||||||
|
const double low[3]={cx-radius-extent,cy-radius-extent,
|
||||||
|
cz-floorExtent-extent};
|
||||||
|
const double high[3]={cx+radius+extent,cy+radius+extent,
|
||||||
|
cz+floorExtent+static_cast<double>(
|
||||||
|
chamber.clearHeightSubvoxels)/Q+extent};
|
||||||
|
return overlaps(low,high);
|
||||||
|
};
|
||||||
|
if(chamberOverlaps(passage.upperLandingChamber)
|
||||||
|
||chamberOverlaps(passage.lowerLandingChamber))return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -3668,6 +3829,12 @@ private:
|
|||||||
double lowerMouthPlannedFloor=0.0;
|
double lowerMouthPlannedFloor=0.0;
|
||||||
double lowerMouthTangentX=0.0,lowerMouthTangentY=0.0;
|
double lowerMouthTangentX=0.0,lowerMouthTangentY=0.0;
|
||||||
std::vector<std::array<double,3>> lowerMouthReceivingRoute;
|
std::vector<std::array<double,3>> lowerMouthReceivingRoute;
|
||||||
|
std::array<double,3> upperLandingFloorCenterSubvoxels{};
|
||||||
|
std::array<double,2> upperLandingFloorGradient{};
|
||||||
|
double upperLandingRadiusSubvoxels=0.0;
|
||||||
|
std::array<double,3> lowerLandingFloorCenterSubvoxels{};
|
||||||
|
std::array<double,2> lowerLandingFloorGradient{};
|
||||||
|
double lowerLandingRadiusSubvoxels=0.0;
|
||||||
std::size_t routeEndControlIndex=0;
|
std::size_t routeEndControlIndex=0;
|
||||||
};
|
};
|
||||||
struct FPassageSegmentData
|
struct FPassageSegmentData
|
||||||
@@ -3692,6 +3859,49 @@ private:
|
|||||||
std::max(0.0,radiusSubvoxels-0.23*qPerMetre));
|
std::max(0.0,radiusSubvoxels-0.23*qPerMetre));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static double BlendPassageFloorToLandingChambers(
|
||||||
|
const FPassageComposerConstants& constants,
|
||||||
|
const FPassageSegmentData& segment,double floorT,
|
||||||
|
double queryXSubvoxels,double queryYSubvoxels,double plannedFloor,
|
||||||
|
double qPerMetre)
|
||||||
|
{
|
||||||
|
if(qPerMetre<=0.0||!(constants.totalPathLengthMetres>0.0))
|
||||||
|
return plannedFloor;
|
||||||
|
const double alongMetres=segment.authoredAlongMetres
|
||||||
|
+floorT*segment.authoredLengthMetres;
|
||||||
|
constexpr double LandingFloorBlendMetres=12.0;
|
||||||
|
const auto floorWeight=[&](double distanceMetres,double radiusSubvoxels)
|
||||||
|
{
|
||||||
|
if(!(radiusSubvoxels>0.0))return 0.0;
|
||||||
|
const double radiusMetres=radiusSubvoxels/qPerMetre;
|
||||||
|
if(distanceMetres<=radiusMetres)return 1.0;
|
||||||
|
if(distanceMetres>=radiusMetres+LandingFloorBlendMetres)return 0.0;
|
||||||
|
const double t=(distanceMetres-radiusMetres)/LandingFloorBlendMetres;
|
||||||
|
return 1.0-t*t*(3.0-2.0*t);
|
||||||
|
};
|
||||||
|
const double upperWeight=floorWeight(std::max(0.0,alongMetres),
|
||||||
|
constants.upperLandingRadiusSubvoxels);
|
||||||
|
const double lowerWeight=floorWeight(std::max(0.0,
|
||||||
|
constants.totalPathLengthMetres-alongMetres),
|
||||||
|
constants.lowerLandingRadiusSubvoxels);
|
||||||
|
if(upperWeight<=0.0&&lowerWeight<=0.0)return plannedFloor;
|
||||||
|
const auto floorAt=[&](const std::array<double,3>& center,
|
||||||
|
const std::array<double,2>& gradient)
|
||||||
|
{
|
||||||
|
return center[2]+gradient[0]*(queryXSubvoxels-center[0])
|
||||||
|
+gradient[1]*(queryYSubvoxels-center[1]);
|
||||||
|
};
|
||||||
|
const double upperFloor=floorAt(constants.upperLandingFloorCenterSubvoxels,
|
||||||
|
constants.upperLandingFloorGradient);
|
||||||
|
const double lowerFloor=floorAt(constants.lowerLandingFloorCenterSubvoxels,
|
||||||
|
constants.lowerLandingFloorGradient);
|
||||||
|
const double totalWeight=upperWeight+lowerWeight;
|
||||||
|
if(totalWeight<=1.0)
|
||||||
|
return plannedFloor*(1.0-totalWeight)
|
||||||
|
+upperFloor*upperWeight+lowerFloor*lowerWeight;
|
||||||
|
return (upperFloor*upperWeight+lowerFloor*lowerWeight)/totalWeight;
|
||||||
|
}
|
||||||
|
|
||||||
static double PassageFlatFloorCenterZSubvoxels(
|
static double PassageFlatFloorCenterZSubvoxels(
|
||||||
const FPassageComposerConstants& constants,
|
const FPassageComposerConstants& constants,
|
||||||
const std::vector<FPassageSegmentData>& segments,
|
const std::vector<FPassageSegmentData>& segments,
|
||||||
@@ -3742,7 +3952,9 @@ private:
|
|||||||
-constants.lowerMouthTangentX,-constants.lowerMouthTangentY,
|
-constants.lowerMouthTangentX,-constants.lowerMouthTangentY,
|
||||||
VoxelForgeCore::PassageSpline::UpperMouthFloorBlendRadiusMetres,1.13,
|
VoxelForgeCore::PassageSpline::UpperMouthFloorBlendRadiusMetres,1.13,
|
||||||
qPerMetre);
|
qPerMetre);
|
||||||
return lowerBlendFloor+floorDepthSubvoxels;
|
const double landingFloor=BlendPassageFloorToLandingChambers(constants,
|
||||||
|
segment,floorT,queryXSubvoxels,queryYSubvoxels,lowerBlendFloor,qPerMetre);
|
||||||
|
return landingFloor+floorDepthSubvoxels;
|
||||||
}
|
}
|
||||||
|
|
||||||
static float PassageFlatBottomDistance(float roundDistanceSubvoxels,
|
static float PassageFlatBottomDistance(float roundDistanceSubvoxels,
|
||||||
@@ -4900,6 +5112,23 @@ private:
|
|||||||
passage.lowerMouthControlIndex,1,passage.controlPoints.size()-1);
|
passage.lowerMouthControlIndex,1,passage.controlPoints.size()-1);
|
||||||
auto& constants=PassageComposerConstants[passageIndex];
|
auto& constants=PassageComposerConstants[passageIndex];
|
||||||
constants.routeEndControlIndex=routeEnd;
|
constants.routeEndControlIndex=routeEnd;
|
||||||
|
for(std::size_t axis=0;axis<3;++axis)
|
||||||
|
{
|
||||||
|
constants.upperLandingFloorCenterSubvoxels[axis]=static_cast<double>(
|
||||||
|
passage.upperLandingChamber.floorCenterSubvoxels[axis]);
|
||||||
|
constants.lowerLandingFloorCenterSubvoxels[axis]=static_cast<double>(
|
||||||
|
passage.lowerLandingChamber.floorCenterSubvoxels[axis]);
|
||||||
|
}
|
||||||
|
constants.upperLandingFloorGradient={
|
||||||
|
passage.upperLandingChamber.floorGradientQ16[0]/65536.0,
|
||||||
|
passage.upperLandingChamber.floorGradientQ16[1]/65536.0};
|
||||||
|
constants.lowerLandingFloorGradient={
|
||||||
|
passage.lowerLandingChamber.floorGradientQ16[0]/65536.0,
|
||||||
|
passage.lowerLandingChamber.floorGradientQ16[1]/65536.0};
|
||||||
|
constants.upperLandingRadiusSubvoxels=static_cast<double>(
|
||||||
|
passage.upperLandingChamber.radiusSubvoxels);
|
||||||
|
constants.lowerLandingRadiusSubvoxels=static_cast<double>(
|
||||||
|
passage.lowerLandingChamber.radiusSubvoxels);
|
||||||
const auto upperStratum=std::find_if(ResolvedLayout->strates.begin(),
|
const auto upperStratum=std::find_if(ResolvedLayout->strates.begin(),
|
||||||
ResolvedLayout->strates.end(),[&](const auto& stratum)
|
ResolvedLayout->strates.end(),[&](const auto& stratum)
|
||||||
{return stratum.strateIndex==passage.upperStrateIndex;});
|
{return stratum.strateIndex==passage.upperStrateIndex;});
|
||||||
@@ -4928,7 +5157,8 @@ private:
|
|||||||
double upperMouthFloorDepth=authoredUpperMouthFloorDepth;
|
double upperMouthFloorDepth=authoredUpperMouthFloorDepth;
|
||||||
if(upperStratum!=ResolvedLayout->strates.end()
|
if(upperStratum!=ResolvedLayout->strates.end()
|
||||||
&&(upperStratum->generatorKind=="FloatingIslands"
|
&&(upperStratum->generatorKind=="FloatingIslands"
|
||||||
||upperStratum->generatorKind=="TunnelNetwork"))
|
||upperStratum->generatorKind=="TunnelNetwork"
|
||||||
|
||upperStratum->generatorKind=="SurfaceWorld"))
|
||||||
{
|
{
|
||||||
std::vector<std::array<double,3>> route;
|
std::vector<std::array<double,3>> route;
|
||||||
for(const auto& anchor:upperStratum->anchors)
|
for(const auto& anchor:upperStratum->anchors)
|
||||||
@@ -4947,16 +5177,19 @@ private:
|
|||||||
upperMouthFloorDepth=std::max(upperMouthFloorDepth,
|
upperMouthFloorDepth=std::max(upperMouthFloorDepth,
|
||||||
std::max(0.0,(controls.front()[2]-routeHeight)/qPerMetre
|
std::max(0.0,(controls.front()[2]-routeHeight)/qPerMetre
|
||||||
+constants.upperMouthPlayerCenterHeightMetres));
|
+constants.upperMouthPlayerCenterHeightMetres));
|
||||||
if(upperStratum->generatorKind=="TunnelNetwork")
|
if(upperStratum->generatorKind=="TunnelNetwork"
|
||||||
|
||upperStratum->generatorKind=="SurfaceWorld")
|
||||||
VoxelForgeCore::PassageSpline::BlendEndpointFloorToRoute(
|
VoxelForgeCore::PassageSpline::BlendEndpointFloorToRoute(
|
||||||
controls,plannedFloorProfile,0,route,qPerMetre);
|
controls,plannedFloorProfile,0,route,qPerMetre,
|
||||||
|
constants.upperMouthPlayerCenterHeightMetres);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
controls.front()[3]=std::max(controls.front()[3],
|
controls.front()[3]=std::max(controls.front()[3],
|
||||||
upperMouthFloorDepth*qPerMetre);
|
upperMouthFloorDepth*qPerMetre);
|
||||||
if(upperStratum!=ResolvedLayout->strates.end()
|
if(upperStratum!=ResolvedLayout->strates.end()
|
||||||
&&(upperStratum->generatorKind=="FloatingIslands"
|
&&(upperStratum->generatorKind=="FloatingIslands"
|
||||||
||upperStratum->generatorKind=="TunnelNetwork"))
|
||upperStratum->generatorKind=="TunnelNetwork"
|
||||||
|
||upperStratum->generatorKind=="SurfaceWorld"))
|
||||||
for(const auto& anchor:upperStratum->anchors)
|
for(const auto& anchor:upperStratum->anchors)
|
||||||
if(anchor.kind=="route_point"&&anchor.values.size()>=3)
|
if(anchor.kind=="route_point"&&anchor.values.size()>=3)
|
||||||
constants.upperMouthReceivingRoute.push_back({
|
constants.upperMouthReceivingRoute.push_back({
|
||||||
@@ -5044,8 +5277,8 @@ private:
|
|||||||
constants.upperMouthTangentY=sampledSegments.front().b[1]
|
constants.upperMouthTangentY=sampledSegments.front().b[1]
|
||||||
-sampledSegments.front().a[1];
|
-sampledSegments.front().a[1];
|
||||||
}
|
}
|
||||||
const std::string lowerMouthAnchor=passage.stableId+"/lower-mouth";
|
const std::string lowerMouthAnchor=passage.stableId+"/lower-landing-chamber";
|
||||||
const std::string upperMouthAnchor=passage.stableId+"/upper-mouth";
|
const std::string upperMouthAnchor=passage.stableId+"/upper-landing-chamber";
|
||||||
for(const auto& edge:ResolvedLayout->cavernEdges)
|
for(const auto& edge:ResolvedLayout->cavernEdges)
|
||||||
{
|
{
|
||||||
if(edge.controlPointsMicrometres.size()<2)continue;
|
if(edge.controlPointsMicrometres.size()<2)continue;
|
||||||
@@ -5230,6 +5463,40 @@ private:
|
|||||||
std::ceil((localMaximum[axis]+localExtent)*Q));
|
std::ceil((localMaximum[axis]+localExtent)*Q));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const auto includeLandingChamber=[&](
|
||||||
|
const VoxelForgeCore::ResolvedLayoutPassageLandingChamberV1& chamber)
|
||||||
|
{
|
||||||
|
const double cx=static_cast<double>(chamber.floorCenterSubvoxels[0])/Q;
|
||||||
|
const double cy=static_cast<double>(chamber.floorCenterSubvoxels[1])/Q;
|
||||||
|
const double cz=static_cast<double>(chamber.floorCenterSubvoxels[2])/Q;
|
||||||
|
const double radius=static_cast<double>(chamber.radiusSubvoxels)/Q;
|
||||||
|
const double gx=static_cast<double>(chamber.floorGradientQ16[0])/65536.0;
|
||||||
|
const double gy=static_cast<double>(chamber.floorGradientQ16[1])/65536.0;
|
||||||
|
const double floorExtent=radius*std::hypot(gx,gy);
|
||||||
|
const double height=static_cast<double>(
|
||||||
|
chamber.clearHeightSubvoxels)/Q;
|
||||||
|
const double extent=requiredReachCoreVoxels+0.01;
|
||||||
|
const std::array<double,3> minimum{
|
||||||
|
cx-radius-extent,cy-radius-extent,cz-floorExtent-extent};
|
||||||
|
const std::array<double,3> maximum{
|
||||||
|
cx+radius+extent,cy+radius+extent,
|
||||||
|
cz+floorExtent+height+extent};
|
||||||
|
FPassageProofBound proof;
|
||||||
|
for(std::size_t axis=0;axis<3;++axis)
|
||||||
|
{
|
||||||
|
routeMinimum[axis]=std::min(routeMinimum[axis],minimum[axis]);
|
||||||
|
routeMaximum[axis]=std::max(routeMaximum[axis],maximum[axis]);
|
||||||
|
proof.minimum[axis]=minimum[axis];
|
||||||
|
proof.maximum[axis]=maximum[axis];
|
||||||
|
}
|
||||||
|
PassageProofBounds.push_back(proof);
|
||||||
|
};
|
||||||
|
if(segmentIndex==0)
|
||||||
|
includeLandingChamber(
|
||||||
|
ResolvedLayout->passages[passageIndex].upperLandingChamber);
|
||||||
|
if(segmentEndIndex==segmentCount)
|
||||||
|
includeLandingChamber(
|
||||||
|
ResolvedLayout->passages[passageIndex].lowerLandingChamber);
|
||||||
const double routeExtent=maximumRadiusSubvoxels/Q
|
const double routeExtent=maximumRadiusSubvoxels/Q
|
||||||
+requiredReachCoreVoxels+0.01;
|
+requiredReachCoreVoxels+0.01;
|
||||||
const double tileExtent=routeExtent;
|
const double tileExtent=routeExtent;
|
||||||
|
|||||||
@@ -0,0 +1,318 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Audit cave primitive bounds against passage envelopes, allowing chamber joints only."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import math
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
|
INFLUENCE_REACH_M = 1.053334
|
||||||
|
SHELL_THICKNESS_M = 0.75
|
||||||
|
|
||||||
|
|
||||||
|
def _point(control: dict[str, Any], scale: float) -> tuple[float, float, float, float]:
|
||||||
|
x, y, z = control["position_q"]
|
||||||
|
return (x * scale, y * scale, z * scale, control["radius_q"] * scale)
|
||||||
|
|
||||||
|
|
||||||
|
def _knot_span(a, b) -> float:
|
||||||
|
return max(1.0e-6, math.sqrt(math.sqrt((b[0] - a[0]) ** 2 + (b[1] - a[1]) ** 2)))
|
||||||
|
|
||||||
|
|
||||||
|
def _evaluate(controls, span: int, t: float):
|
||||||
|
p1, p2 = controls[span], controls[span + 1]
|
||||||
|
p0 = controls[span - 1] if span else controls[0]
|
||||||
|
p3 = controls[min(span + 2, len(controls) - 1)]
|
||||||
|
dt0, dt1, dt2 = _knot_span(p0, p1), _knot_span(p1, p2), _knot_span(p2, p3)
|
||||||
|
t2, t3 = t * t, t * t * t
|
||||||
|
result = []
|
||||||
|
for axis in (0, 1, 3):
|
||||||
|
m1 = dt1 * ((p1[axis] - p0[axis]) / dt0
|
||||||
|
- (p2[axis] - p0[axis]) / (dt0 + dt1)
|
||||||
|
+ (p2[axis] - p1[axis]) / dt1)
|
||||||
|
m2 = dt1 * ((p2[axis] - p1[axis]) / dt1
|
||||||
|
- (p3[axis] - p1[axis]) / (dt1 + dt2)
|
||||||
|
+ (p3[axis] - p2[axis]) / dt2)
|
||||||
|
value = ((2 * t3 - 3 * t2 + 1) * p1[axis]
|
||||||
|
+ (-2 * t3 + 3 * t2) * p2[axis]
|
||||||
|
+ (t3 - 2 * t2 + t) * m1 + (t3 - t2) * m2)
|
||||||
|
result.append(value)
|
||||||
|
radius = min(max(result[2], min(p1[3], p2[3])), max(p1[3], p2[3]))
|
||||||
|
return result[0], result[1], p1[2] + (p2[2] - p1[2]) * t, radius
|
||||||
|
|
||||||
|
|
||||||
|
def _segments(passage: dict[str, Any], scale: float):
|
||||||
|
controls = [_point(row, scale) for row in passage["control_points"]]
|
||||||
|
route_end = min(max(1, int(passage.get("lower_mouth_control_index", len(controls) - 1))),
|
||||||
|
len(controls) - 1)
|
||||||
|
along = 0.0
|
||||||
|
for span in range(route_end):
|
||||||
|
a, b = controls[span], controls[span + 1]
|
||||||
|
chord = math.hypot(b[0] - a[0], b[1] - a[1])
|
||||||
|
steps = max(1, math.ceil(chord / 2.0))
|
||||||
|
prior = _evaluate(controls, span, 0.0)
|
||||||
|
for step in range(1, steps + 1):
|
||||||
|
current = _evaluate(controls, span, step / steps)
|
||||||
|
length = math.dist(prior[:3], current[:3])
|
||||||
|
yield prior, current, along, max(prior[3], current[3]) + INFLUENCE_REACH_M
|
||||||
|
along += length
|
||||||
|
prior = current
|
||||||
|
|
||||||
|
|
||||||
|
def _distance_squared(a, b, bounds, t: float) -> float:
|
||||||
|
total = 0.0
|
||||||
|
for axis in range(3):
|
||||||
|
pos = a[axis] + (b[axis] - a[axis]) * t
|
||||||
|
lo, hi = bounds[axis]
|
||||||
|
gap = lo - pos if pos < lo else pos - hi if pos > hi else 0.0
|
||||||
|
total += gap * gap
|
||||||
|
return total
|
||||||
|
|
||||||
|
|
||||||
|
def _overlap_interval(a, b, bounds, radius: float):
|
||||||
|
cuts = {0.0, 1.0}
|
||||||
|
for axis in range(3):
|
||||||
|
delta = b[axis] - a[axis]
|
||||||
|
if abs(delta) < 1.0e-15:
|
||||||
|
continue
|
||||||
|
for edge in bounds[axis]:
|
||||||
|
t = (edge - a[axis]) / delta
|
||||||
|
if 0.0 < t < 1.0:
|
||||||
|
cuts.add(t)
|
||||||
|
cuts = sorted(cuts)
|
||||||
|
best_t, best_d2 = 0.0, math.inf
|
||||||
|
for lo, hi in zip(cuts, cuts[1:]):
|
||||||
|
mid = (lo + hi) * 0.5
|
||||||
|
quadratic = linear = 0.0
|
||||||
|
for axis in range(3):
|
||||||
|
delta = b[axis] - a[axis]
|
||||||
|
pos = a[axis] + delta * mid
|
||||||
|
low, high = bounds[axis]
|
||||||
|
edge = low if pos < low else high if pos > high else None
|
||||||
|
if edge is not None:
|
||||||
|
quadratic += delta * delta
|
||||||
|
linear += delta * (a[axis] - edge)
|
||||||
|
candidate = min(hi, max(lo, -linear / quadratic)) if quadratic else lo
|
||||||
|
d2 = _distance_squared(a, b, bounds, candidate)
|
||||||
|
if d2 < best_d2:
|
||||||
|
best_t, best_d2 = candidate, d2
|
||||||
|
for t in (0.0, 1.0):
|
||||||
|
d2 = _distance_squared(a, b, bounds, t)
|
||||||
|
if d2 < best_d2:
|
||||||
|
best_t, best_d2 = t, d2
|
||||||
|
if best_d2 > radius * radius:
|
||||||
|
return None
|
||||||
|
|
||||||
|
radius2 = radius * radius
|
||||||
|
if _distance_squared(a, b, bounds, 0.0) <= radius2:
|
||||||
|
left = 0.0
|
||||||
|
else:
|
||||||
|
low, high = 0.0, best_t
|
||||||
|
for _ in range(42):
|
||||||
|
mid = (low + high) * 0.5
|
||||||
|
if _distance_squared(a, b, bounds, mid) <= radius2:
|
||||||
|
high = mid
|
||||||
|
else:
|
||||||
|
low = mid
|
||||||
|
left = high
|
||||||
|
if _distance_squared(a, b, bounds, 1.0) <= radius2:
|
||||||
|
right = 1.0
|
||||||
|
else:
|
||||||
|
low, high = best_t, 1.0
|
||||||
|
for _ in range(42):
|
||||||
|
mid = (low + high) * 0.5
|
||||||
|
if _distance_squared(a, b, bounds, mid) <= radius2:
|
||||||
|
low = mid
|
||||||
|
else:
|
||||||
|
high = mid
|
||||||
|
right = low
|
||||||
|
return left, right, best_t
|
||||||
|
|
||||||
|
|
||||||
|
def _inside_chamber(point, chamber: dict[str, Any], scale: float) -> bool:
|
||||||
|
cx, cy, cz = (value * scale for value in chamber["floor_center_q"])
|
||||||
|
radius = chamber["radius_q"] * scale
|
||||||
|
clear = chamber["clear_height_q"] * scale
|
||||||
|
gx, gy = (value / 65536.0 for value in chamber["floor_gradient_q16"])
|
||||||
|
floor = cz + gx * (point[0] - cx) + gy * (point[1] - cy)
|
||||||
|
chamber_distance = max(math.hypot(point[0] - cx, point[1] - cy) - radius,
|
||||||
|
floor - point[2], point[2] - (floor + clear))
|
||||||
|
return chamber_distance <= SHELL_THICKNESS_M
|
||||||
|
|
||||||
|
|
||||||
|
def _primitive_bounds(row: dict[str, Any]):
|
||||||
|
return tuple((row["bounds_min_um"][i] / 1.0e6,
|
||||||
|
row["bounds_max_um"][i] / 1.0e6) for i in range(3))
|
||||||
|
|
||||||
|
|
||||||
|
def _bezier(points, t: float):
|
||||||
|
u = 1.0 - t
|
||||||
|
return tuple(u**3 * points[0][axis] + 3*u*u*t * points[1][axis]
|
||||||
|
+ 3*u*t*t * points[2][axis] + t**3 * points[3][axis]
|
||||||
|
for axis in range(3))
|
||||||
|
|
||||||
|
|
||||||
|
def _distance_to_ellipse(radial: float, vertical: float,
|
||||||
|
radius: float, height: float) -> float:
|
||||||
|
"""Euclidean distance to the edge bore's horizontal/vertical ellipse."""
|
||||||
|
radial = abs(radial)
|
||||||
|
vertical = abs(vertical)
|
||||||
|
if (radial / radius) ** 2 + (vertical / height) ** 2 <= 1.0:
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
lo, hi = 0.0, math.pi / 2.0
|
||||||
|
ratio = (math.sqrt(5.0) - 1.0) / 2.0
|
||||||
|
|
||||||
|
def squared_distance(angle: float) -> float:
|
||||||
|
return ((radial - radius * math.cos(angle)) ** 2
|
||||||
|
+ (vertical - height * math.sin(angle)) ** 2)
|
||||||
|
|
||||||
|
left = hi - ratio * (hi - lo)
|
||||||
|
right = lo + ratio * (hi - lo)
|
||||||
|
left_value, right_value = squared_distance(left), squared_distance(right)
|
||||||
|
for _ in range(48):
|
||||||
|
if left_value < right_value:
|
||||||
|
hi, right, right_value = right, left, left_value
|
||||||
|
left = hi - ratio * (hi - lo)
|
||||||
|
left_value = squared_distance(left)
|
||||||
|
else:
|
||||||
|
lo, left, left_value = left, right, right_value
|
||||||
|
right = lo + ratio * (hi - lo)
|
||||||
|
right_value = squared_distance(right)
|
||||||
|
return math.sqrt(min(left_value, right_value))
|
||||||
|
|
||||||
|
|
||||||
|
def _actual_edge_clearance(passage, edge, scale: float,
|
||||||
|
chamber: dict[str, Any]) -> float:
|
||||||
|
"""Return minimum true tube clearance outside the landing chamber.
|
||||||
|
|
||||||
|
The route edge is a swept ellipse, with its recorded polyline as the axis.
|
||||||
|
Comparing the tunnel tube to that ellipse avoids the false clashes caused by
|
||||||
|
intersecting two enclosing boxes.
|
||||||
|
"""
|
||||||
|
controls = [tuple(value / 1.0e6 for value in point)
|
||||||
|
for point in edge["control_points_um"]]
|
||||||
|
axis = [tuple(value / 1.0e6 for value in point)
|
||||||
|
for point in edge.get("polyline_um", [])]
|
||||||
|
if len(axis) < 2:
|
||||||
|
axis = [_bezier(controls, index / 64.0) for index in range(65)]
|
||||||
|
edge_radius = (edge["bore_radius_um"]
|
||||||
|
+ edge["irregularity_amplitude_um"]) / 1.0e6
|
||||||
|
edge_height = (edge["bore_height_um"]
|
||||||
|
+ edge["irregularity_amplitude_um"]) / 1.0e6
|
||||||
|
passage_segments = passage.get("_audit_segments") or tuple(_segments(passage, scale))
|
||||||
|
minimum_clearance = math.inf
|
||||||
|
for a, b, _, passage_radius in passage_segments:
|
||||||
|
length = math.dist(a[:3], b[:3])
|
||||||
|
steps = max(1, math.ceil(length / 0.05))
|
||||||
|
for step in range(steps + 1):
|
||||||
|
t = step / steps
|
||||||
|
point = tuple(a[i] + (b[i] - a[i]) * t for i in range(3))
|
||||||
|
if _inside_chamber(point, chamber, scale):
|
||||||
|
continue
|
||||||
|
tunnel_radius = (a[3] + (b[3] - a[3]) * t
|
||||||
|
+ INFLUENCE_REACH_M)
|
||||||
|
for start, end in zip(axis, axis[1:]):
|
||||||
|
delta = tuple(end[i] - start[i] for i in range(3))
|
||||||
|
length_squared = sum(value * value for value in delta)
|
||||||
|
projection = (sum((point[i] - start[i]) * delta[i]
|
||||||
|
for i in range(3)) / length_squared
|
||||||
|
if length_squared else 0.0)
|
||||||
|
projection = min(1.0, max(0.0, projection))
|
||||||
|
center = tuple(start[i] + projection * delta[i] for i in range(3))
|
||||||
|
radial = math.hypot(point[0] - center[0], point[1] - center[1])
|
||||||
|
vertical = abs(point[2] - center[2])
|
||||||
|
clearance = (_distance_to_ellipse(radial, vertical,
|
||||||
|
edge_radius, edge_height)
|
||||||
|
- tunnel_radius)
|
||||||
|
minimum_clearance = min(minimum_clearance, clearance)
|
||||||
|
return minimum_clearance
|
||||||
|
|
||||||
|
|
||||||
|
def _test_bounds(bounds, strate_index: int, passages, scale: float,
|
||||||
|
*, allow_chamber: bool = True):
|
||||||
|
for passage in passages:
|
||||||
|
if strate_index not in (passage["upper_strate_index"], passage["lower_strate_index"]):
|
||||||
|
continue
|
||||||
|
chamber_key = ("upper_landing_chamber" if strate_index == passage["upper_strate_index"]
|
||||||
|
else "lower_landing_chamber")
|
||||||
|
chamber = passage[chamber_key]
|
||||||
|
segments = passage.get("_audit_segments")
|
||||||
|
if segments is None:
|
||||||
|
segments = tuple(_segments(passage, scale))
|
||||||
|
for a, b, along, radius in segments:
|
||||||
|
expanded = [(a[i] - radius, b[i] + radius) if a[i] <= b[i]
|
||||||
|
else (b[i] - radius, a[i] + radius) for i in range(3)]
|
||||||
|
if any(expanded[i][1] < bounds[i][0] or expanded[i][0] > bounds[i][1]
|
||||||
|
for i in range(3)):
|
||||||
|
continue
|
||||||
|
overlap = _overlap_interval(a, b, bounds, radius)
|
||||||
|
if overlap is None:
|
||||||
|
continue
|
||||||
|
first, last, closest = overlap
|
||||||
|
if allow_chamber:
|
||||||
|
points = [tuple(a[i] + (b[i] - a[i]) * t for i in range(3))
|
||||||
|
for t in (first, closest, last)]
|
||||||
|
if all(_inside_chamber(point, chamber, scale) for point in points):
|
||||||
|
continue
|
||||||
|
point = tuple(a[i] + (b[i] - a[i]) * closest for i in range(3))
|
||||||
|
return passage["stable_id"], along + closest * math.dist(a[:3], b[:3]), point
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def audit_layout(layout: dict[str, Any]) -> dict[str, Any]:
|
||||||
|
scale = layout["core_voxel_size_um"] / (256.0 * 1.0e6)
|
||||||
|
passages = [dict(row, _audit_segments=tuple(_segments(row, scale)))
|
||||||
|
for row in layout.get("passages", [])]
|
||||||
|
primitives = [*layout.get("cavern_rooms", []), *layout.get("cavern_edges", [])]
|
||||||
|
checked = conflicts = chamber_joints = bounds_candidates = refined_false_positives = 0
|
||||||
|
first_conflict = None
|
||||||
|
for primitive in primitives:
|
||||||
|
bounds = _primitive_bounds(primitive)
|
||||||
|
checked += 1
|
||||||
|
primitive_conflicted = False
|
||||||
|
for passage in passages:
|
||||||
|
found = _test_bounds(bounds, primitive["strate_index"], [passage], scale)
|
||||||
|
if not found:
|
||||||
|
continue
|
||||||
|
bounds_candidates += 1
|
||||||
|
passage_id, station, point = found
|
||||||
|
if "control_points_um" in primitive:
|
||||||
|
side = ("upper_landing_chamber"
|
||||||
|
if primitive["strate_index"] == passage["upper_strate_index"]
|
||||||
|
else "lower_landing_chamber")
|
||||||
|
clearance = _actual_edge_clearance(passage, primitive, scale,
|
||||||
|
passage[side])
|
||||||
|
if clearance >= 0.0:
|
||||||
|
refined_false_positives += 1
|
||||||
|
continue
|
||||||
|
primitive_conflicted = True
|
||||||
|
conflicts += 1
|
||||||
|
if first_conflict is None:
|
||||||
|
first_conflict = {"primitive": primitive["stable_id"],
|
||||||
|
"passage": passage_id, "station_m": round(station, 3),
|
||||||
|
"center_m": [round(value, 3) for value in point]}
|
||||||
|
break
|
||||||
|
if not primitive_conflicted:
|
||||||
|
# Count the edge's named landing association as a confirmed joint.
|
||||||
|
anchor = primitive.get("start_anchor_stable_id", "")
|
||||||
|
if anchor.endswith(("/upper-landing-chamber", "/lower-landing-chamber")):
|
||||||
|
chamber_joints += 1
|
||||||
|
|
||||||
|
negative_controls = 0
|
||||||
|
long_passage = max(passages, key=lambda row: len(row["control_points"]), default=None)
|
||||||
|
if long_passage:
|
||||||
|
controls = [_point(row, scale) for row in long_passage["control_points"]]
|
||||||
|
index = min(max(1, len(controls) // 2), len(controls) - 2)
|
||||||
|
center = controls[index]
|
||||||
|
bounds = tuple((center[axis] - 0.25, center[axis] + 0.25) for axis in range(3))
|
||||||
|
# Select the side whose chamber is farther from this forced tunnel overlap.
|
||||||
|
strate = long_passage["lower_strate_index"]
|
||||||
|
if _test_bounds(bounds, strate, [long_passage], scale, allow_chamber=False):
|
||||||
|
negative_controls = 1
|
||||||
|
return {"primitives_checked": checked, "bounds_candidates": bounds_candidates,
|
||||||
|
"refined_edge_false_positives": refined_false_positives,
|
||||||
|
"outside_chamber_conflicts": conflicts,
|
||||||
|
"chamber_joints": chamber_joints, "negative_controls": negative_controls,
|
||||||
|
"negative_controls_expected": 1, "first_conflict": first_conflict,
|
||||||
|
"passed": conflicts == 0 and negative_controls == 1}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
# L151 — One tunnel for two strata
|
||||||
|
|
||||||
|
| Landing chamber | Before (L150) | After (L151) |
|
||||||
|
|---|---|---|
|
||||||
|
| passage/0-1/0 · upper |  |  |
|
||||||
|
| passage/0-1/0 · lower |  |  |
|
||||||
|
| passage/1-2/0 · upper |  |  |
|
||||||
|
| passage/1-2/0 · lower |  |  |
|
||||||
|
| passage/2-3/0 · upper |  |  |
|
||||||
|
| passage/2-3/0 · lower |  |  |
|
||||||
|
|
||||||
|
Seed 0 camera poses are matched at eye height. All 12 images passed the capture quality check. The L150 build stamp is `c6b8a100355e271ddc40eb5973569d2ff4e2ba8d9c628c72f8e0af2493f0609f`; L151 is `1ca3b84f6d35f7f355ec0f72dabd3b87a455ea02e5789fea611916962e5db8e5`. The [capture index](captures/INDEX.md) and [manifest](captures/manifest.json) record the poses and files.
|
||||||
|
|
||||||
|
## Result
|
||||||
|
|
||||||
|
**L151 is not promotion-ready.** The resolved layout now records one passage spline with an upper and lower landing chamber, and the evaluator version is 238. The best tested floor blend still leaves two walk segments above the 15° limit: 19.077° on passage 0-1 and 19.033° on passage 2-3. The three-variant stop rule was reached; I stopped without a fourth geometry attempt. No owner baselines were promoted.
|
||||||
|
|
||||||
|
## What changed
|
||||||
|
|
||||||
|
- Added serialized and validated landing chamber records with floor center, floor gradient, radius, and 4.5 m clear height. The shared passage field composes each chamber with the tunnel shell and floor.
|
||||||
|
- Cave route endpoints now bind to the passage's landing chamber anchors. The passage floor blends to chamber floors over the tested 12 m transition.
|
||||||
|
- Extended passage audits for chamber capsule clearance, floor and wall shell, and blocked-world negative controls. Added a geometric cave-primitive envelope audit that permits the named chamber joints and refines conservative edge-box candidates against the swept bore.
|
||||||
|
- Retained the L135 mouth profile, L147 interior-air gate, and L148 envelope allowance.
|
||||||
|
|
||||||
|
## Measurements and checks
|
||||||
|
|
||||||
|
- **Focused owner-world run:** one seed-2 W8 run, source stamp above. Three interfaces were audited. Passage and chamber interiors together had 0 solid samples across 319,579 capsule samples; all six chambers had 0 solid samples. Passage floor, wall, and interior shell violations were all zero. Naturalness, mouth profile, ripple, and all three layer route audits passed. Tunnel-network, vertical-shaft, and blocked-passage negative controls were detected (1/1, 1/1, and 3/3). The combined passage summary failed because only one of three interfaces met walkability.
|
||||||
|
- **Chamber envelope audit:** 549 primitives checked; 2 conservative bounds candidates were refined as false positives; 0 conflicts remained outside chambers; 3 named chamber joints and the negative control passed (1/1). Result: [JSON](../../../../Saved/Linux/l151/passage-envelope-audit.json).
|
||||||
|
- **Blend attempts:** 12 m retained two grades at 19.077° and 19.033°. Extending the blend to 24 m produced 436 solid interior capsule samples and one wall violation, as well as grades of 15.282° and 17.406° on passage 0-1. Restricting the external blend to 0 m kept interior and shell checks clear but left a 22.226° grade on passage 0-1. The 12 m variant is the final source state.
|
||||||
|
- **Build and snapshots:** Clang 19 build passed for stamp `1ca3b84f…`; the explicit seed-0 resolved-layout snapshot round trip passed. Python capture and envelope audit scripts ran successfully. `git diff --check` passed for the tracked diff; the report's whitespace and image references were checked separately.
|
||||||
|
- **Seams:** the saved seed-2 log has no `[PassageTileSeamAudit]` result, so seam status is unverified.
|
||||||
|
|
||||||
|
## Not run
|
||||||
|
|
||||||
|
The three-failure stop was reached before the fast tier, the 16-seed Tunnel Network and Vertical Shafts sweeps, Clang/GCC W1/W8/W12, and L80 timing for all four layers. No timing claim is made. No owner-world baseline files were updated because the composed walkability gate failed. The only modified `Source/` file is `Standalone/Source/StreamBenchmarkPipeline.h`; no Unreal `Source/` files changed.
|
||||||
|
|
||||||
|
## Remaining risk
|
||||||
|
|
||||||
|
The tunnel-to-chamber floor transition still creates local grades above 15° in two interfaces. The envelope audit and geometry evidence cover seed 2 only; multi-seed composed behavior, seams, compiler/worker determinism, the fast tier, and L80 performance remain unverified.
|
||||||
@@ -0,0 +1,206 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Capture matched eye-height views from both ends of every owner tunnel."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import math
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[4]
|
||||||
|
DATA = ROOT / "Standalone/data/owner_world.json"
|
||||||
|
OUT = ROOT / "Saved/Linux/l151/captures"
|
||||||
|
LAYOUT = OUT / "after-resolved-layout.json"
|
||||||
|
ARTIFACTS = ROOT / "Standalone/Research/WorldAssemblerM3/Linux151/captures"
|
||||||
|
LIMIT = ["taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--"]
|
||||||
|
VERSIONS = {
|
||||||
|
"before-l150": {
|
||||||
|
"headless": ROOT / "Standalone/bin/linux/VoxelForgeCoreHeadless-l150-head-clang",
|
||||||
|
"renderer": ROOT / "Standalone/bin/linux/VoxelForgeOffscreenCapture-l150-head-clang",
|
||||||
|
"stamp": "c6b8a100355e271ddc40eb5973569d2ff4e2ba8d9c628c72f8e0af2493f0609f",
|
||||||
|
},
|
||||||
|
"after-l151": {
|
||||||
|
"headless": ROOT / "Standalone/bin/linux/VoxelForgeCoreHeadless-l151-clang",
|
||||||
|
"renderer": ROOT / "Standalone/bin/linux/VoxelForgeOffscreenCapture-l151-clang",
|
||||||
|
"stamp": "1ca3b84f6d35f7f355ec0f72dabd3b87a455ea02e5789fea611916962e5db8e5",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
STAMP_RE = re.compile(r"^\[VoxelForgeSourceStamp\] sha256=([0-9a-f]{64})$", re.M)
|
||||||
|
|
||||||
|
|
||||||
|
def fmt(values) -> str:
|
||||||
|
return ",".join(f"{value:.6f}" for value in values)
|
||||||
|
|
||||||
|
|
||||||
|
def angles(camera, target) -> tuple[float, float]:
|
||||||
|
dx, dy, dz = (target[index] - camera[index] for index in range(3))
|
||||||
|
return math.degrees(math.atan2(dx, dy)), math.degrees(
|
||||||
|
math.atan2(dz, math.hypot(dx, dy)))
|
||||||
|
|
||||||
|
|
||||||
|
def point_q(layout: dict, values) -> tuple[float, float, float]:
|
||||||
|
scale = layout["core_voxel_size_um"] / (256.0 * 1_000_000.0)
|
||||||
|
return tuple(value * scale for value in values)
|
||||||
|
|
||||||
|
|
||||||
|
def chamber_views(layout: dict) -> list[dict]:
|
||||||
|
result = []
|
||||||
|
for passage in layout["passages"]:
|
||||||
|
controls = passage["control_points"]
|
||||||
|
for side, endpoint, neighbor in (
|
||||||
|
("upper", controls[0], controls[1]),
|
||||||
|
("lower", controls[-1], controls[-2])):
|
||||||
|
chamber = passage[f"{side}_landing_chamber"]
|
||||||
|
center = point_q(layout, chamber["floor_center_q"])
|
||||||
|
a = point_q(layout, neighbor["position_q"])
|
||||||
|
b = point_q(layout, endpoint["position_q"])
|
||||||
|
dx, dy = a[0] - b[0], a[1] - b[1]
|
||||||
|
length = math.hypot(dx, dy)
|
||||||
|
if length <= 1.0e-9:
|
||||||
|
raise ValueError(f"degenerate {side} tunnel tangent: {passage['stable_id']}")
|
||||||
|
dx, dy = dx / length, dy / length
|
||||||
|
eye_z = center[2] + 1.45
|
||||||
|
distance = 9.0
|
||||||
|
target = (center[0] + dx * distance, center[1] + dy * distance,
|
||||||
|
eye_z + (chamber["floor_gradient_q16"][0] / 65536.0 * dx
|
||||||
|
+ chamber["floor_gradient_q16"][1] / 65536.0 * dy)
|
||||||
|
* distance)
|
||||||
|
camera = (center[0], center[1], eye_z)
|
||||||
|
slug = passage["stable_id"].replace("passage/", "").replace("/", "-")
|
||||||
|
result.append({"name": f"{slug}-{side}-landing", "passage": passage["stable_id"],
|
||||||
|
"side": side, "camera": camera, "target": target,
|
||||||
|
"focus": (center[0], center[1], eye_z, 18.0)})
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def stamped_run(name: str, command: list[object], log: Path, expected_stamp: str,
|
||||||
|
env: dict[str, str]) -> str:
|
||||||
|
binary = Path(str(command[0]))
|
||||||
|
probe = subprocess.run([*LIMIT, "timeout", "15s", str(binary), "--print-source-stamp"],
|
||||||
|
cwd=ROOT, env=env, text=True, stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.STDOUT, check=False, timeout=20)
|
||||||
|
match = STAMP_RE.search(probe.stdout or "")
|
||||||
|
if probe.returncode or not match or match.group(1) != expected_stamp:
|
||||||
|
raise RuntimeError(f"{name}: source stamp mismatch for {binary}")
|
||||||
|
log.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
with log.open("w", encoding="utf-8") as stream:
|
||||||
|
result = subprocess.run([*LIMIT, "timeout", "1740s", *map(str, command)],
|
||||||
|
cwd=ROOT, env=env, stdout=stream, stderr=subprocess.STDOUT,
|
||||||
|
timeout=1745, check=False)
|
||||||
|
output = log.read_text(encoding="utf-8", errors="replace")
|
||||||
|
emitted = STAMP_RE.search(output)
|
||||||
|
if result.returncode or not emitted or emitted.group(1) != expected_stamp:
|
||||||
|
raise RuntimeError(f"{name}: export or render failed; see {log}")
|
||||||
|
return output
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_layout(env: dict[str, str]) -> dict:
|
||||||
|
config = VERSIONS["after-l151"]
|
||||||
|
command = [config["headless"], DATA, "--world-assembler-prototype",
|
||||||
|
"--stream-benchmark", "--stream-pipeline", "--workers=12",
|
||||||
|
"--clip-radius=6", "--max-clip-level=0", "--max-level=0",
|
||||||
|
"--force-generate", "--seed=0", "--recipe=surface-world", "--resolved-layout-only",
|
||||||
|
f"--resolved-layout-output={LAYOUT}"]
|
||||||
|
stamped_run("after-l151-resolve-layout", list(map(str, command)),
|
||||||
|
OUT / "after-l151-resolved-layout.log", config["stamp"], env)
|
||||||
|
layout = json.loads(LAYOUT.read_text(encoding="utf-8"))
|
||||||
|
if len(layout.get("passages", [])) != 3:
|
||||||
|
raise RuntimeError("owner layout did not resolve the expected three tunnels")
|
||||||
|
return layout
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
if not DATA.is_file():
|
||||||
|
raise SystemExit("missing owner fixture")
|
||||||
|
OUT.mkdir(parents=True, exist_ok=True)
|
||||||
|
ARTIFACTS.mkdir(parents=True, exist_ok=True)
|
||||||
|
env = os.environ.copy()
|
||||||
|
env["LP_NUM_THREADS"] = "12"
|
||||||
|
env["XDG_CACHE_HOME"] = str(OUT / "xdg-cache")
|
||||||
|
layout = resolve_layout(env)
|
||||||
|
views = chamber_views(layout)
|
||||||
|
quality_tool = ROOT / "Standalone/Tools/verify/capture_quality.py"
|
||||||
|
manifest = []
|
||||||
|
failures = []
|
||||||
|
for version, config in VERSIONS.items():
|
||||||
|
for view in views:
|
||||||
|
camera, target = view["camera"], view["target"]
|
||||||
|
yaw, pitch = angles(camera, target)
|
||||||
|
mesh_dir = OUT / "meshes" / version
|
||||||
|
mesh_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
mesh = mesh_dir / f"{view['name']}.vfmesh"
|
||||||
|
capture_layout = mesh_dir / f"{view['name']}-layout.json"
|
||||||
|
export = [config["headless"], DATA, "--world-assembler-prototype",
|
||||||
|
"--stream-benchmark", "--stream-pipeline", "--workers=12",
|
||||||
|
"--clip-radius=6", "--max-clip-level=0", "--max-level=0",
|
||||||
|
"--force-generate", "--seed=0", "--recipe=surface-world",
|
||||||
|
"--render-distance-chunks=0",
|
||||||
|
f"--camera={fmt((*camera, yaw, pitch))}",
|
||||||
|
f"--resolved-layout-output={capture_layout}", f"--mesh-export={mesh}"]
|
||||||
|
stamped_run(f"{version}-{view['name']}-export", list(map(str, export)),
|
||||||
|
OUT / f"{version}-{view['name']}-export.log", config["stamp"], env)
|
||||||
|
if not mesh.is_file() or mesh.stat().st_size == 0:
|
||||||
|
raise RuntimeError(f"{version}-{view['name']}: empty mesh export")
|
||||||
|
png = OUT / f"{version}-{view['name']}.png"
|
||||||
|
jpg = ARTIFACTS / f"{version}-{view['name']}.jpg"
|
||||||
|
render = [config["renderer"], mesh, png, "--size=1280x720",
|
||||||
|
"--fov-deg=78.0", f"--camera-m={fmt(camera)}",
|
||||||
|
f"--yaw-deg={yaw:.6f}", f"--pitch-deg={pitch:.6f}",
|
||||||
|
"--tile-skirts=off", "--shade=readable", "--headlamp=on",
|
||||||
|
"--height-tint=on", "--height-center-m=-950", "--height-range-m=60",
|
||||||
|
"--tile-edges=off", f"--focus-region={fmt(view['focus'])}"]
|
||||||
|
stamped_run(f"{version}-{view['name']}-render", list(map(str, render)),
|
||||||
|
OUT / f"{version}-{view['name']}-render.log", config["stamp"], env)
|
||||||
|
subprocess.run([*LIMIT, "timeout", "120s", "ffmpeg", "-nostdin",
|
||||||
|
"-hide_banner", "-loglevel", "error", "-y", "-i", png,
|
||||||
|
"-frames:v", "1", "-q:v", "4", jpg], cwd=ROOT, env=env,
|
||||||
|
check=True, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT,
|
||||||
|
timeout=125)
|
||||||
|
check = subprocess.run([*LIMIT, "timeout", "120s", sys.executable,
|
||||||
|
quality_tool, jpg], cwd=ROOT, env=env, text=True,
|
||||||
|
stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
|
||||||
|
check=False, timeout=125)
|
||||||
|
quality_ok = check.returncode == 0 and "status=PASS" in check.stdout
|
||||||
|
if not quality_ok:
|
||||||
|
failures.append(f"{version}-{view['name']}")
|
||||||
|
quality_line = (check.stdout.strip().splitlines()[-1]
|
||||||
|
if check.stdout.strip() else f"quality exit={check.returncode}")
|
||||||
|
manifest.append({"version": version, "view": view["name"],
|
||||||
|
"passage": view["passage"], "side": view["side"],
|
||||||
|
"file": str(jpg.relative_to(ROOT)), "camera_m": camera,
|
||||||
|
"target_m": target, "yaw_deg": yaw, "pitch_deg": pitch,
|
||||||
|
"source_stamp": config["stamp"],
|
||||||
|
"pose_layout_identity": layout.get("stable_identity"),
|
||||||
|
"quality_status": "PASS" if quality_ok else "FAIL",
|
||||||
|
"quality": quality_line})
|
||||||
|
print(f"[L151Capture] version={version} view={view['name']} "
|
||||||
|
f"quality={'PASS' if quality_ok else 'FAIL'} "
|
||||||
|
f"file={jpg.relative_to(ROOT)}", flush=True)
|
||||||
|
(ARTIFACTS / "manifest.json").write_text(
|
||||||
|
json.dumps(manifest, indent=2) + "\n", encoding="utf-8")
|
||||||
|
with (ARTIFACTS / "INDEX.md").open("w", encoding="utf-8") as index:
|
||||||
|
index.write("# L151 matched owner tunnel landing captures\n\n"
|
||||||
|
"Seed 0 camera poses resolved from the L151 owner layout and reused for both "
|
||||||
|
"versions. Each view is at eye height at one of the three tunnel landing "
|
||||||
|
"chambers. Before uses the L150 head build; after uses L151 source stamp "
|
||||||
|
f"`{VERSIONS['after-l151']['stamp']}`.\n\n")
|
||||||
|
for view in views:
|
||||||
|
index.write(f"## {view['passage']} / {view['side']} landing\n\n"
|
||||||
|
f"- Before: [image](before-l150-{view['name']}.jpg)\n"
|
||||||
|
f"- After: [image](after-l151-{view['name']}.jpg)\n\n")
|
||||||
|
index.write(f"Capture quality failures: {len(failures)}/{len(manifest)}.\n")
|
||||||
|
print(f"[L151CaptureSummary] chamber_views={len(views)} "
|
||||||
|
f"images={len(manifest)} quality_failures={len(failures)}", flush=True)
|
||||||
|
return 0 if not failures else 1
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
try:
|
||||||
|
raise SystemExit(main())
|
||||||
|
except Exception as error:
|
||||||
|
print(f"[L151CaptureFailure] {error}", file=sys.stderr, flush=True)
|
||||||
|
raise SystemExit(1)
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
# L151 matched owner tunnel landing captures
|
||||||
|
|
||||||
|
Seed 0 camera poses resolved from the L151 owner layout and reused for both versions. Each view is at eye height at one of the three tunnel landing chambers. Before uses the L150 head build; after uses L151 source stamp `1ca3b84f6d35f7f355ec0f72dabd3b87a455ea02e5789fea611916962e5db8e5`.
|
||||||
|
|
||||||
|
## passage/0-1/0 / upper landing
|
||||||
|
|
||||||
|
- Before: [image](before-l150-0-1-0-upper-landing.jpg)
|
||||||
|
- After: [image](after-l151-0-1-0-upper-landing.jpg)
|
||||||
|
|
||||||
|
## passage/0-1/0 / lower landing
|
||||||
|
|
||||||
|
- Before: [image](before-l150-0-1-0-lower-landing.jpg)
|
||||||
|
- After: [image](after-l151-0-1-0-lower-landing.jpg)
|
||||||
|
|
||||||
|
## passage/1-2/0 / upper landing
|
||||||
|
|
||||||
|
- Before: [image](before-l150-1-2-0-upper-landing.jpg)
|
||||||
|
- After: [image](after-l151-1-2-0-upper-landing.jpg)
|
||||||
|
|
||||||
|
## passage/1-2/0 / lower landing
|
||||||
|
|
||||||
|
- Before: [image](before-l150-1-2-0-lower-landing.jpg)
|
||||||
|
- After: [image](after-l151-1-2-0-lower-landing.jpg)
|
||||||
|
|
||||||
|
## passage/2-3/0 / upper landing
|
||||||
|
|
||||||
|
- Before: [image](before-l150-2-3-0-upper-landing.jpg)
|
||||||
|
- After: [image](after-l151-2-3-0-upper-landing.jpg)
|
||||||
|
|
||||||
|
## passage/2-3/0 / lower landing
|
||||||
|
|
||||||
|
- Before: [image](before-l150-2-3-0-lower-landing.jpg)
|
||||||
|
- After: [image](after-l151-2-3-0-lower-landing.jpg)
|
||||||
|
|
||||||
|
Capture quality failures: 0/12.
|
||||||
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 16 KiB |
@@ -0,0 +1,278 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"version": "before-l150",
|
||||||
|
"view": "0-1-0-upper-landing",
|
||||||
|
"passage": "passage/0-1/0",
|
||||||
|
"side": "upper",
|
||||||
|
"file": "Standalone/Research/WorldAssemblerM3/Linux151/captures/before-l150-0-1-0-upper-landing.jpg",
|
||||||
|
"camera_m": [
|
||||||
|
37.0,
|
||||||
|
-5.25,
|
||||||
|
-372.3810546875
|
||||||
|
],
|
||||||
|
"target_m": [
|
||||||
|
28.40474992442715,
|
||||||
|
-7.918646874047773,
|
||||||
|
-372.325197471535
|
||||||
|
],
|
||||||
|
"yaw_deg": -107.24848481126237,
|
||||||
|
"pitch_deg": 0.3555935154874107,
|
||||||
|
"source_stamp": "c6b8a100355e271ddc40eb5973569d2ff4e2ba8d9c628c72f8e0af2493f0609f",
|
||||||
|
"pose_layout_identity": "resolved-layout-v3-fnv64:ed02312296ac2003",
|
||||||
|
"quality_status": "PASS",
|
||||||
|
"quality": "[CaptureQuality] image=/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux151/captures/before-l150-0-1-0-upper-landing.jpg pixels=921600 mean_luma=113.553 stddev_luma=8.807 coverage_luma_gt12=1.00000 thresholds=(stddev>=2.0,coverage>=0.001) status=PASS"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version": "before-l150",
|
||||||
|
"view": "0-1-0-lower-landing",
|
||||||
|
"passage": "passage/0-1/0",
|
||||||
|
"side": "lower",
|
||||||
|
"file": "Standalone/Research/WorldAssemblerM3/Linux151/captures/before-l150-0-1-0-lower-landing.jpg",
|
||||||
|
"camera_m": [
|
||||||
|
25.8369140625,
|
||||||
|
3.19921875,
|
||||||
|
-736.6798828125
|
||||||
|
],
|
||||||
|
"target_m": [
|
||||||
|
17.97380590951016,
|
||||||
|
-1.1793118453464206,
|
||||||
|
-734.970169262755
|
||||||
|
],
|
||||||
|
"yaw_deg": -119.11101808688004,
|
||||||
|
"pitch_deg": 10.756207017349817,
|
||||||
|
"source_stamp": "c6b8a100355e271ddc40eb5973569d2ff4e2ba8d9c628c72f8e0af2493f0609f",
|
||||||
|
"pose_layout_identity": "resolved-layout-v3-fnv64:ed02312296ac2003",
|
||||||
|
"quality_status": "PASS",
|
||||||
|
"quality": "[CaptureQuality] image=/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux151/captures/before-l150-0-1-0-lower-landing.jpg pixels=921600 mean_luma=106.418 stddev_luma=2.881 coverage_luma_gt12=1.00000 thresholds=(stddev>=2.0,coverage>=0.001) status=PASS"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version": "before-l150",
|
||||||
|
"view": "1-2-0-upper-landing",
|
||||||
|
"passage": "passage/1-2/0",
|
||||||
|
"side": "upper",
|
||||||
|
"file": "Standalone/Research/WorldAssemblerM3/Linux151/captures/before-l150-1-2-0-upper-landing.jpg",
|
||||||
|
"camera_m": [
|
||||||
|
19.166015625,
|
||||||
|
-14.2001953125,
|
||||||
|
-731.8810546875
|
||||||
|
],
|
||||||
|
"target_m": [
|
||||||
|
10.35825176774404,
|
||||||
|
-16.050410390488395,
|
||||||
|
-733.216132282697
|
||||||
|
],
|
||||||
|
"yaw_deg": -101.86342177874246,
|
||||||
|
"pitch_deg": -8.437834550031965,
|
||||||
|
"source_stamp": "c6b8a100355e271ddc40eb5973569d2ff4e2ba8d9c628c72f8e0af2493f0609f",
|
||||||
|
"pose_layout_identity": "resolved-layout-v3-fnv64:ed02312296ac2003",
|
||||||
|
"quality_status": "PASS",
|
||||||
|
"quality": "[CaptureQuality] image=/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux151/captures/before-l150-1-2-0-upper-landing.jpg pixels=921600 mean_luma=108.420 stddev_luma=2.873 coverage_luma_gt12=1.00000 thresholds=(stddev>=2.0,coverage>=0.001) status=PASS"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version": "before-l150",
|
||||||
|
"view": "1-2-0-lower-landing",
|
||||||
|
"passage": "passage/1-2/0",
|
||||||
|
"side": "lower",
|
||||||
|
"file": "Standalone/Research/WorldAssemblerM3/Linux151/captures/before-l150-1-2-0-lower-landing.jpg",
|
||||||
|
"camera_m": [
|
||||||
|
19.166015625,
|
||||||
|
-14.2001953125,
|
||||||
|
-753.05390625
|
||||||
|
],
|
||||||
|
"target_m": [
|
||||||
|
10.48848361510052,
|
||||||
|
-16.587754362485967,
|
||||||
|
-751.7193175939389
|
||||||
|
],
|
||||||
|
"yaw_deg": -105.38384894032598,
|
||||||
|
"pitch_deg": 8.434788863627224,
|
||||||
|
"source_stamp": "c6b8a100355e271ddc40eb5973569d2ff4e2ba8d9c628c72f8e0af2493f0609f",
|
||||||
|
"pose_layout_identity": "resolved-layout-v3-fnv64:ed02312296ac2003",
|
||||||
|
"quality_status": "PASS",
|
||||||
|
"quality": "[CaptureQuality] image=/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux151/captures/before-l150-1-2-0-lower-landing.jpg pixels=921600 mean_luma=113.918 stddev_luma=10.489 coverage_luma_gt12=1.00000 thresholds=(stddev>=2.0,coverage>=0.001) status=PASS"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version": "before-l150",
|
||||||
|
"view": "2-3-0-upper-landing",
|
||||||
|
"passage": "passage/2-3/0",
|
||||||
|
"side": "upper",
|
||||||
|
"file": "Standalone/Research/WorldAssemblerM3/Linux151/captures/before-l150-2-3-0-upper-landing.jpg",
|
||||||
|
"camera_m": [
|
||||||
|
-20.0625,
|
||||||
|
-5.068359375,
|
||||||
|
-812.075390625
|
||||||
|
],
|
||||||
|
"target_m": [
|
||||||
|
-13.604954164712868,
|
||||||
|
-11.337338700629157,
|
||||||
|
-813.6662645746064
|
||||||
|
],
|
||||||
|
"yaw_deg": 134.15112141809087,
|
||||||
|
"pitch_deg": -10.024269857937275,
|
||||||
|
"source_stamp": "c6b8a100355e271ddc40eb5973569d2ff4e2ba8d9c628c72f8e0af2493f0609f",
|
||||||
|
"pose_layout_identity": "resolved-layout-v3-fnv64:ed02312296ac2003",
|
||||||
|
"quality_status": "PASS",
|
||||||
|
"quality": "[CaptureQuality] image=/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux151/captures/before-l150-2-3-0-upper-landing.jpg pixels=921600 mean_luma=108.739 stddev_luma=2.947 coverage_luma_gt12=1.00000 thresholds=(stddev>=2.0,coverage>=0.001) status=PASS"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version": "before-l150",
|
||||||
|
"view": "2-3-0-lower-landing",
|
||||||
|
"passage": "passage/2-3/0",
|
||||||
|
"side": "lower",
|
||||||
|
"file": "Standalone/Research/WorldAssemblerM3/Linux151/captures/before-l150-2-3-0-lower-landing.jpg",
|
||||||
|
"camera_m": [
|
||||||
|
-20.0625,
|
||||||
|
-5.068359375,
|
||||||
|
-900.0333984375
|
||||||
|
],
|
||||||
|
"target_m": [
|
||||||
|
-14.865342978484083,
|
||||||
|
-12.416118141706198,
|
||||||
|
-898.4427583280197
|
||||||
|
],
|
||||||
|
"yaw_deg": 144.72777711381306,
|
||||||
|
"pitch_deg": 10.022826283920647,
|
||||||
|
"source_stamp": "c6b8a100355e271ddc40eb5973569d2ff4e2ba8d9c628c72f8e0af2493f0609f",
|
||||||
|
"pose_layout_identity": "resolved-layout-v3-fnv64:ed02312296ac2003",
|
||||||
|
"quality_status": "PASS",
|
||||||
|
"quality": "[CaptureQuality] image=/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux151/captures/before-l150-2-3-0-lower-landing.jpg pixels=921600 mean_luma=112.176 stddev_luma=9.871 coverage_luma_gt12=1.00000 thresholds=(stddev>=2.0,coverage>=0.001) status=PASS"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version": "after-l151",
|
||||||
|
"view": "0-1-0-upper-landing",
|
||||||
|
"passage": "passage/0-1/0",
|
||||||
|
"side": "upper",
|
||||||
|
"file": "Standalone/Research/WorldAssemblerM3/Linux151/captures/after-l151-0-1-0-upper-landing.jpg",
|
||||||
|
"camera_m": [
|
||||||
|
37.0,
|
||||||
|
-5.25,
|
||||||
|
-372.3810546875
|
||||||
|
],
|
||||||
|
"target_m": [
|
||||||
|
28.40474992442715,
|
||||||
|
-7.918646874047773,
|
||||||
|
-372.325197471535
|
||||||
|
],
|
||||||
|
"yaw_deg": -107.24848481126237,
|
||||||
|
"pitch_deg": 0.3555935154874107,
|
||||||
|
"source_stamp": "1ca3b84f6d35f7f355ec0f72dabd3b87a455ea02e5789fea611916962e5db8e5",
|
||||||
|
"pose_layout_identity": "resolved-layout-v3-fnv64:ed02312296ac2003",
|
||||||
|
"quality_status": "PASS",
|
||||||
|
"quality": "[CaptureQuality] image=/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux151/captures/after-l151-0-1-0-upper-landing.jpg pixels=921600 mean_luma=118.392 stddev_luma=15.921 coverage_luma_gt12=1.00000 thresholds=(stddev>=2.0,coverage>=0.001) status=PASS"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version": "after-l151",
|
||||||
|
"view": "0-1-0-lower-landing",
|
||||||
|
"passage": "passage/0-1/0",
|
||||||
|
"side": "lower",
|
||||||
|
"file": "Standalone/Research/WorldAssemblerM3/Linux151/captures/after-l151-0-1-0-lower-landing.jpg",
|
||||||
|
"camera_m": [
|
||||||
|
25.8369140625,
|
||||||
|
3.19921875,
|
||||||
|
-736.6798828125
|
||||||
|
],
|
||||||
|
"target_m": [
|
||||||
|
17.97380590951016,
|
||||||
|
-1.1793118453464206,
|
||||||
|
-734.970169262755
|
||||||
|
],
|
||||||
|
"yaw_deg": -119.11101808688004,
|
||||||
|
"pitch_deg": 10.756207017349817,
|
||||||
|
"source_stamp": "1ca3b84f6d35f7f355ec0f72dabd3b87a455ea02e5789fea611916962e5db8e5",
|
||||||
|
"pose_layout_identity": "resolved-layout-v3-fnv64:ed02312296ac2003",
|
||||||
|
"quality_status": "PASS",
|
||||||
|
"quality": "[CaptureQuality] image=/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux151/captures/after-l151-0-1-0-lower-landing.jpg pixels=921600 mean_luma=111.426 stddev_luma=12.411 coverage_luma_gt12=1.00000 thresholds=(stddev>=2.0,coverage>=0.001) status=PASS"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version": "after-l151",
|
||||||
|
"view": "1-2-0-upper-landing",
|
||||||
|
"passage": "passage/1-2/0",
|
||||||
|
"side": "upper",
|
||||||
|
"file": "Standalone/Research/WorldAssemblerM3/Linux151/captures/after-l151-1-2-0-upper-landing.jpg",
|
||||||
|
"camera_m": [
|
||||||
|
19.166015625,
|
||||||
|
-14.2001953125,
|
||||||
|
-731.8810546875
|
||||||
|
],
|
||||||
|
"target_m": [
|
||||||
|
10.35825176774404,
|
||||||
|
-16.050410390488395,
|
||||||
|
-733.216132282697
|
||||||
|
],
|
||||||
|
"yaw_deg": -101.86342177874246,
|
||||||
|
"pitch_deg": -8.437834550031965,
|
||||||
|
"source_stamp": "1ca3b84f6d35f7f355ec0f72dabd3b87a455ea02e5789fea611916962e5db8e5",
|
||||||
|
"pose_layout_identity": "resolved-layout-v3-fnv64:ed02312296ac2003",
|
||||||
|
"quality_status": "PASS",
|
||||||
|
"quality": "[CaptureQuality] image=/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux151/captures/after-l151-1-2-0-upper-landing.jpg pixels=921600 mean_luma=117.134 stddev_luma=15.664 coverage_luma_gt12=1.00000 thresholds=(stddev>=2.0,coverage>=0.001) status=PASS"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version": "after-l151",
|
||||||
|
"view": "1-2-0-lower-landing",
|
||||||
|
"passage": "passage/1-2/0",
|
||||||
|
"side": "lower",
|
||||||
|
"file": "Standalone/Research/WorldAssemblerM3/Linux151/captures/after-l151-1-2-0-lower-landing.jpg",
|
||||||
|
"camera_m": [
|
||||||
|
19.166015625,
|
||||||
|
-14.2001953125,
|
||||||
|
-753.05390625
|
||||||
|
],
|
||||||
|
"target_m": [
|
||||||
|
10.48848361510052,
|
||||||
|
-16.587754362485967,
|
||||||
|
-751.7193175939389
|
||||||
|
],
|
||||||
|
"yaw_deg": -105.38384894032598,
|
||||||
|
"pitch_deg": 8.434788863627224,
|
||||||
|
"source_stamp": "1ca3b84f6d35f7f355ec0f72dabd3b87a455ea02e5789fea611916962e5db8e5",
|
||||||
|
"pose_layout_identity": "resolved-layout-v3-fnv64:ed02312296ac2003",
|
||||||
|
"quality_status": "PASS",
|
||||||
|
"quality": "[CaptureQuality] image=/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux151/captures/after-l151-1-2-0-lower-landing.jpg pixels=921600 mean_luma=119.710 stddev_luma=14.975 coverage_luma_gt12=1.00000 thresholds=(stddev>=2.0,coverage>=0.001) status=PASS"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version": "after-l151",
|
||||||
|
"view": "2-3-0-upper-landing",
|
||||||
|
"passage": "passage/2-3/0",
|
||||||
|
"side": "upper",
|
||||||
|
"file": "Standalone/Research/WorldAssemblerM3/Linux151/captures/after-l151-2-3-0-upper-landing.jpg",
|
||||||
|
"camera_m": [
|
||||||
|
-20.0625,
|
||||||
|
-5.068359375,
|
||||||
|
-812.075390625
|
||||||
|
],
|
||||||
|
"target_m": [
|
||||||
|
-13.604954164712868,
|
||||||
|
-11.337338700629157,
|
||||||
|
-813.6662645746064
|
||||||
|
],
|
||||||
|
"yaw_deg": 134.15112141809087,
|
||||||
|
"pitch_deg": -10.024269857937275,
|
||||||
|
"source_stamp": "1ca3b84f6d35f7f355ec0f72dabd3b87a455ea02e5789fea611916962e5db8e5",
|
||||||
|
"pose_layout_identity": "resolved-layout-v3-fnv64:ed02312296ac2003",
|
||||||
|
"quality_status": "PASS",
|
||||||
|
"quality": "[CaptureQuality] image=/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux151/captures/after-l151-2-3-0-upper-landing.jpg pixels=921600 mean_luma=118.198 stddev_luma=15.882 coverage_luma_gt12=1.00000 thresholds=(stddev>=2.0,coverage>=0.001) status=PASS"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version": "after-l151",
|
||||||
|
"view": "2-3-0-lower-landing",
|
||||||
|
"passage": "passage/2-3/0",
|
||||||
|
"side": "lower",
|
||||||
|
"file": "Standalone/Research/WorldAssemblerM3/Linux151/captures/after-l151-2-3-0-lower-landing.jpg",
|
||||||
|
"camera_m": [
|
||||||
|
-20.0625,
|
||||||
|
-5.068359375,
|
||||||
|
-900.0333984375
|
||||||
|
],
|
||||||
|
"target_m": [
|
||||||
|
-14.865342978484083,
|
||||||
|
-12.416118141706198,
|
||||||
|
-898.4427583280197
|
||||||
|
],
|
||||||
|
"yaw_deg": 144.72777711381306,
|
||||||
|
"pitch_deg": 10.022826283920647,
|
||||||
|
"source_stamp": "1ca3b84f6d35f7f355ec0f72dabd3b87a455ea02e5789fea611916962e5db8e5",
|
||||||
|
"pose_layout_identity": "resolved-layout-v3-fnv64:ed02312296ac2003",
|
||||||
|
"quality_status": "PASS",
|
||||||
|
"quality": "[CaptureQuality] image=/home/jahni/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Linux151/captures/after-l151-2-3-0-lower-landing.jpg pixels=921600 mean_luma=121.734 stddev_luma=16.193 coverage_luma_gt12=1.00000 thresholds=(stddev>=2.0,coverage>=0.001) status=PASS"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -3090,7 +3090,16 @@ namespace StandaloneStreamBenchmark
|
|||||||
const double horizontal=std::hypot(dx,dy)*coreMetres;
|
const double horizontal=std::hypot(dx,dy)*coreMetres;
|
||||||
const double vertical=std::abs(dz)*coreMetres;
|
const double vertical=std::abs(dz)*coreMetres;
|
||||||
if(horizontal<=1.0e-6||vertical>horizontal*0.2679491924311227)
|
if(horizontal<=1.0e-6||vertical>horizontal*0.2679491924311227)
|
||||||
|
{
|
||||||
|
if(audit.gradeFailures<8)
|
||||||
|
std::printf("[ResolvedWalkGradeFailure] segment=%zu a_xyz_m=(%.3f,%.3f,%.3f) b_xyz_m=(%.3f,%.3f,%.3f) horizontal_m=%.6f vertical_m=%.6f grade_deg=%.3f\n",
|
||||||
|
segment-1,a[0]*coreMetres,a[1]*coreMetres,
|
||||||
|
a[2]*coreMetres,b[0]*coreMetres,b[1]*coreMetres,
|
||||||
|
b[2]*coreMetres,horizontal,vertical,
|
||||||
|
std::atan2(vertical,horizontal)
|
||||||
|
*180.0/3.14159265358979323846);
|
||||||
++audit.gradeFailures;
|
++audit.gradeFailures;
|
||||||
|
}
|
||||||
const double length=std::sqrt(dx*dx+dy*dy+dz*dz)*coreMetres;
|
const double length=std::sqrt(dx*dx+dy*dy+dz*dz)*coreMetres;
|
||||||
const std::uint64_t intervals=std::max<std::uint64_t>(1,
|
const std::uint64_t intervals=std::max<std::uint64_t>(1,
|
||||||
static_cast<std::uint64_t>(std::ceil(length/0.5)));
|
static_cast<std::uint64_t>(std::ceil(length/0.5)));
|
||||||
@@ -3193,7 +3202,8 @@ namespace StandaloneStreamBenchmark
|
|||||||
-qToMetres(passage.upperMouthSubvoxels[2])+mouthCenterHeight);
|
-qToMetres(passage.upperMouthSubvoxels[2])+mouthCenterHeight);
|
||||||
if(upperStratum==ResolvedLayout->strates.end()
|
if(upperStratum==ResolvedLayout->strates.end()
|
||||||
||(upperStratum->generatorKind!="FloatingIslands"
|
||(upperStratum->generatorKind!="FloatingIslands"
|
||||||
&&upperStratum->generatorKind!="TunnelNetwork"))
|
&&upperStratum->generatorKind!="TunnelNetwork"
|
||||||
|
&&upperStratum->generatorKind!="SurfaceWorld"))
|
||||||
return authoredMouthDepth;
|
return authoredMouthDepth;
|
||||||
std::vector<std::array<double,3>> route;
|
std::vector<std::array<double,3>> route;
|
||||||
for(const auto& anchor:upperStratum->anchors)
|
for(const auto& anchor:upperStratum->anchors)
|
||||||
@@ -3246,7 +3256,8 @@ namespace StandaloneStreamBenchmark
|
|||||||
{return candidate.strateIndex==strateIndex;});
|
{return candidate.strateIndex==strateIndex;});
|
||||||
if(stratum!=ResolvedLayout->strates.end()
|
if(stratum!=ResolvedLayout->strates.end()
|
||||||
&&(stratum->generatorKind=="TunnelNetwork"
|
&&(stratum->generatorKind=="TunnelNetwork"
|
||||||
||stratum->generatorKind=="FloatingIslands"))
|
||stratum->generatorKind=="FloatingIslands"
|
||||||
|
||stratum->generatorKind=="SurfaceWorld"))
|
||||||
for(const auto& anchor:stratum->anchors)
|
for(const auto& anchor:stratum->anchors)
|
||||||
if(anchor.kind=="route_point"&&anchor.values.size()>=3)
|
if(anchor.kind=="route_point"&&anchor.values.size()>=3)
|
||||||
route.push_back({qToMetres(anchor.values[0]),
|
route.push_back({qToMetres(anchor.values[0]),
|
||||||
@@ -3256,17 +3267,20 @@ namespace StandaloneStreamBenchmark
|
|||||||
const auto upper=std::find_if(ResolvedLayout->strates.begin(),
|
const auto upper=std::find_if(ResolvedLayout->strates.begin(),
|
||||||
ResolvedLayout->strates.end(),[&](const auto& stratum)
|
ResolvedLayout->strates.end(),[&](const auto& stratum)
|
||||||
{return stratum.strateIndex==passage.upperStrateIndex;});
|
{return stratum.strateIndex==passage.upperStrateIndex;});
|
||||||
if(upper!=ResolvedLayout->strates.end()
|
if(upper!=ResolvedLayout->strates.end()&&!controls.empty()
|
||||||
&&upper->generatorKind=="TunnelNetwork"&&!controls.empty())
|
&&(upper->generatorKind=="TunnelNetwork"
|
||||||
|
||upper->generatorKind=="SurfaceWorld"))
|
||||||
{
|
{
|
||||||
auto route=routeForStrate(passage.upperStrateIndex);
|
auto route=routeForStrate(passage.upperStrateIndex);
|
||||||
if(route.size()>2)route.erase(route.begin(),route.end()-2);
|
if(upper->generatorKind=="TunnelNetwork"&&route.size()>2)
|
||||||
|
route.erase(route.begin(),route.end()-2);
|
||||||
double routeHeight=0.0,distanceSquared=0.0;
|
double routeHeight=0.0,distanceSquared=0.0;
|
||||||
if(VoxelForgeCore::PassageSpline::ClosestRouteHeightAtXY(route,
|
if(VoxelForgeCore::PassageSpline::ClosestRouteHeightAtXY(route,
|
||||||
controls.front()[0],controls.front()[1],routeHeight,distanceSquared)
|
controls.front()[0],controls.front()[1],routeHeight,distanceSquared)
|
||||||
&&distanceSquared<=std::pow(controls.front()[3]+2.0,2.0))
|
&&distanceSquared<=std::pow(controls.front()[3]+2.0,2.0))
|
||||||
VoxelForgeCore::PassageSpline::BlendEndpointFloorToRoute(
|
VoxelForgeCore::PassageSpline::BlendEndpointFloorToRoute(
|
||||||
controls,profile,0,route,1.0);
|
controls,profile,0,route,1.0,
|
||||||
|
upper->generatorKind=="SurfaceWorld"?0.98:1.13);
|
||||||
}
|
}
|
||||||
const auto lower=std::find_if(ResolvedLayout->strates.begin(),
|
const auto lower=std::find_if(ResolvedLayout->strates.begin(),
|
||||||
ResolvedLayout->strates.end(),[&](const auto& stratum)
|
ResolvedLayout->strates.end(),[&](const auto& stratum)
|
||||||
@@ -3325,6 +3339,44 @@ namespace StandaloneStreamBenchmark
|
|||||||
double x=0.0,y=0.0,z=0.0,floorZ=0.0,radius=0.0,distance=0.0;
|
double x=0.0,y=0.0,z=0.0,floorZ=0.0,radius=0.0,distance=0.0;
|
||||||
std::size_t segmentIndex=0;
|
std::size_t segmentIndex=0;
|
||||||
};
|
};
|
||||||
|
const auto blendLandingChamberFloor=[&](
|
||||||
|
const VoxelForgeCore::ResolvedLayoutPassageV1& passage,
|
||||||
|
double alongMetres,double totalLengthMetres,double x,double y,
|
||||||
|
double plannedFloor)
|
||||||
|
{
|
||||||
|
constexpr double LandingFloorBlendMetres=12.0;
|
||||||
|
const auto weight=[&](double distanceMetres,
|
||||||
|
const VoxelForgeCore::ResolvedLayoutPassageLandingChamberV1& chamber)
|
||||||
|
{
|
||||||
|
const double radius=qToMetres(chamber.radiusSubvoxels);
|
||||||
|
if(!(radius>0.0))return 0.0;
|
||||||
|
if(distanceMetres<=radius)return 1.0;
|
||||||
|
if(distanceMetres>=radius+LandingFloorBlendMetres)return 0.0;
|
||||||
|
const double t=(distanceMetres-radius)/LandingFloorBlendMetres;
|
||||||
|
return 1.0-t*t*(3.0-2.0*t);
|
||||||
|
};
|
||||||
|
const double upperWeight=weight(std::max(0.0,alongMetres),
|
||||||
|
passage.upperLandingChamber);
|
||||||
|
const double lowerWeight=weight(std::max(0.0,
|
||||||
|
totalLengthMetres-alongMetres),passage.lowerLandingChamber);
|
||||||
|
if(upperWeight<=0.0&&lowerWeight<=0.0)return plannedFloor;
|
||||||
|
const auto floorAt=[&](
|
||||||
|
const VoxelForgeCore::ResolvedLayoutPassageLandingChamberV1& chamber)
|
||||||
|
{
|
||||||
|
const double cx=qToMetres(chamber.floorCenterSubvoxels[0]);
|
||||||
|
const double cy=qToMetres(chamber.floorCenterSubvoxels[1]);
|
||||||
|
const double cz=qToMetres(chamber.floorCenterSubvoxels[2]);
|
||||||
|
return cz+chamber.floorGradientQ16[0]/65536.0*(x-cx)
|
||||||
|
+chamber.floorGradientQ16[1]/65536.0*(y-cy);
|
||||||
|
};
|
||||||
|
const double upperFloor=floorAt(passage.upperLandingChamber);
|
||||||
|
const double lowerFloor=floorAt(passage.lowerLandingChamber);
|
||||||
|
const double totalWeight=upperWeight+lowerWeight;
|
||||||
|
if(totalWeight<=1.0)
|
||||||
|
return plannedFloor*(1.0-totalWeight)
|
||||||
|
+upperFloor*upperWeight+lowerFloor*lowerWeight;
|
||||||
|
return (upperFloor*upperWeight+lowerFloor*lowerWeight)/totalWeight;
|
||||||
|
};
|
||||||
const auto passageSmoothRoute=[&](
|
const auto passageSmoothRoute=[&](
|
||||||
const VoxelForgeCore::ResolvedLayoutPassageV1& passage,double bodyDepth)
|
const VoxelForgeCore::ResolvedLayoutPassageV1& passage,double bodyDepth)
|
||||||
{
|
{
|
||||||
@@ -3354,6 +3406,16 @@ namespace StandaloneStreamBenchmark
|
|||||||
segment.alongMetres+segment.lengthMetres,
|
segment.alongMetres+segment.lengthMetres,
|
||||||
segment.controlSpan+1});
|
segment.controlSpan+1});
|
||||||
}
|
}
|
||||||
|
if(route.size()>=2)
|
||||||
|
{
|
||||||
|
const double totalLength=route.back().distance;
|
||||||
|
for(auto& point:route)
|
||||||
|
{
|
||||||
|
point.floorZ=blendLandingChamberFloor(passage,point.distance,
|
||||||
|
totalLength,point.x,point.y,point.floorZ);
|
||||||
|
point.z=point.floorZ+passageTubeDepthAt(point.radius,bodyDepth);
|
||||||
|
}
|
||||||
|
}
|
||||||
return route;
|
return route;
|
||||||
};
|
};
|
||||||
std::vector<double> allPassageBodyFloorDepths;
|
std::vector<double> allPassageBodyFloorDepths;
|
||||||
@@ -3389,7 +3451,8 @@ namespace StandaloneStreamBenchmark
|
|||||||
{return stratum.strateIndex==passage.upperStrateIndex;});
|
{return stratum.strateIndex==passage.upperStrateIndex;});
|
||||||
if(upperStratum!=ResolvedLayout->strates.end()
|
if(upperStratum!=ResolvedLayout->strates.end()
|
||||||
&&(upperStratum->generatorKind=="FloatingIslands"
|
&&(upperStratum->generatorKind=="FloatingIslands"
|
||||||
||upperStratum->generatorKind=="TunnelNetwork"))
|
||upperStratum->generatorKind=="TunnelNetwork"
|
||||||
|
||upperStratum->generatorKind=="SurfaceWorld"))
|
||||||
{
|
{
|
||||||
for(const auto& anchor:upperStratum->anchors)
|
for(const auto& anchor:upperStratum->anchors)
|
||||||
if(anchor.kind=="route_point"&&anchor.values.size()>=3)
|
if(anchor.kind=="route_point"&&anchor.values.size()>=3)
|
||||||
@@ -3466,16 +3529,49 @@ namespace StandaloneStreamBenchmark
|
|||||||
if(passageIndex>=allPassageFloorBlends.size())return plannedFloor;
|
if(passageIndex>=allPassageFloorBlends.size())return plannedFloor;
|
||||||
const auto& blends=allPassageFloorBlends[passageIndex];
|
const auto& blends=allPassageFloorBlends[passageIndex];
|
||||||
double floor=plannedFloor;
|
double floor=plannedFloor;
|
||||||
|
const auto upperStratum=std::find_if(ResolvedLayout->strates.begin(),
|
||||||
|
ResolvedLayout->strates.end(),[&](const auto& stratum)
|
||||||
|
{return stratum.strateIndex
|
||||||
|
==ResolvedLayout->passages[passageIndex].upperStrateIndex;});
|
||||||
|
const double upperPlayerCenterHeight=upperStratum
|
||||||
|
!=ResolvedLayout->strates.end()
|
||||||
|
&&upperStratum->generatorKind=="SurfaceWorld"?0.98:1.13;
|
||||||
floor=VoxelForgeCore::PassageSpline::BlendUpperMouthFloorAtXY(
|
floor=VoxelForgeCore::PassageSpline::BlendUpperMouthFloorAtXY(
|
||||||
blends.upper.receivingRoute,x,y,floor,blends.upper.mouthX,
|
blends.upper.receivingRoute,x,y,floor,blends.upper.mouthX,
|
||||||
blends.upper.mouthY,blends.upper.mouthFloor,
|
blends.upper.mouthY,blends.upper.mouthFloor,
|
||||||
blends.upper.tangentX,blends.upper.tangentY,
|
blends.upper.tangentX,blends.upper.tangentY,
|
||||||
VoxelForgeCore::PassageSpline::UpperMouthFloorBlendRadiusMetres,1.13);
|
VoxelForgeCore::PassageSpline::UpperMouthFloorBlendRadiusMetres,
|
||||||
return VoxelForgeCore::PassageSpline::BlendUpperMouthFloorAtXY(
|
upperPlayerCenterHeight);
|
||||||
|
floor=VoxelForgeCore::PassageSpline::BlendUpperMouthFloorAtXY(
|
||||||
blends.lower.receivingRoute,x,y,floor,blends.lower.mouthX,
|
blends.lower.receivingRoute,x,y,floor,blends.lower.mouthX,
|
||||||
blends.lower.mouthY,blends.lower.mouthFloor,
|
blends.lower.mouthY,blends.lower.mouthFloor,
|
||||||
-blends.lower.tangentX,-blends.lower.tangentY,
|
-blends.lower.tangentX,-blends.lower.tangentY,
|
||||||
VoxelForgeCore::PassageSpline::UpperMouthFloorBlendRadiusMetres,1.13);
|
VoxelForgeCore::PassageSpline::UpperMouthFloorBlendRadiusMetres,1.13);
|
||||||
|
if(passageIndex>=allPassageSmoothRoutes.size())return floor;
|
||||||
|
const auto& route=allPassageSmoothRoutes[passageIndex];
|
||||||
|
if(route.size()<2)return floor;
|
||||||
|
double closestDistanceSquared=std::numeric_limits<double>::infinity();
|
||||||
|
double closestAlong=0.0;
|
||||||
|
for(std::size_t index=1;index<route.size();++index)
|
||||||
|
{
|
||||||
|
const auto& a=route[index-1];
|
||||||
|
const auto& b=route[index];
|
||||||
|
const double dx=b.x-a.x,dy=b.y-a.y;
|
||||||
|
const double lengthSquared=dx*dx+dy*dy;
|
||||||
|
const double t=lengthSquared>1.0e-12
|
||||||
|
?std::clamp(((x-a.x)*dx+(y-a.y)*dy)/lengthSquared,0.0,1.0):0.0;
|
||||||
|
const double nearestX=a.x+t*dx,nearestY=a.y+t*dy;
|
||||||
|
const double ex=x-nearestX,ey=y-nearestY;
|
||||||
|
const double distanceSquared=ex*ex+ey*ey;
|
||||||
|
if(distanceSquared<closestDistanceSquared)
|
||||||
|
{
|
||||||
|
closestDistanceSquared=distanceSquared;
|
||||||
|
closestAlong=std::lerp(a.distance,b.distance,t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return blendLandingChamberFloor(
|
||||||
|
ResolvedLayout->passages[passageIndex],closestAlong,
|
||||||
|
route.back().distance,x,y,floor);
|
||||||
};
|
};
|
||||||
const auto interpolatePassageRoute=[](
|
const auto interpolatePassageRoute=[](
|
||||||
const std::vector<FPassageAuditRoutePoint>& route,double distance)
|
const std::vector<FPassageAuditRoutePoint>& route,double distance)
|
||||||
@@ -3627,6 +3723,26 @@ namespace StandaloneStreamBenchmark
|
|||||||
&&passageIndex!=selectedPassage)continue;
|
&&passageIndex!=selectedPassage)continue;
|
||||||
const auto& passage=ResolvedLayout->passages[passageIndex];
|
const auto& passage=ResolvedLayout->passages[passageIndex];
|
||||||
if(passage.controlPoints.size()<2)continue;
|
if(passage.controlPoints.size()<2)continue;
|
||||||
|
const auto inLandingChamber=[&](
|
||||||
|
const VoxelForgeCore::ResolvedLayoutPassageLandingChamberV1& chamber)
|
||||||
|
{
|
||||||
|
if(chamber.radiusSubvoxels<=0||chamber.clearHeightSubvoxels<=0)
|
||||||
|
return false;
|
||||||
|
const double centerX=qToMetres(chamber.floorCenterSubvoxels[0]);
|
||||||
|
const double centerY=qToMetres(chamber.floorCenterSubvoxels[1]);
|
||||||
|
const double floorCenterZ=qToMetres(
|
||||||
|
chamber.floorCenterSubvoxels[2])
|
||||||
|
+chamber.floorGradientQ16[0]/65536.0*(xMetres-centerX)
|
||||||
|
+chamber.floorGradientQ16[1]/65536.0*(yMetres-centerY);
|
||||||
|
const double radialDistance=std::hypot(xMetres-centerX,
|
||||||
|
yMetres-centerY)-qToMetres(chamber.radiusSubvoxels);
|
||||||
|
const double floorDistance=floorCenterZ-zMetres;
|
||||||
|
const double ceilingDistance=zMetres-(floorCenterZ
|
||||||
|
+qToMetres(chamber.clearHeightSubvoxels));
|
||||||
|
return std::max({radialDistance,floorDistance,ceilingDistance})<=0.0;
|
||||||
|
};
|
||||||
|
if(inLandingChamber(passage.upperLandingChamber)
|
||||||
|
||inLandingChamber(passage.lowerLandingChamber))return true;
|
||||||
const auto& route=allPassageSmoothRoutes[passageIndex];
|
const auto& route=allPassageSmoothRoutes[passageIndex];
|
||||||
if(route.size()<2)continue;
|
if(route.size()<2)continue;
|
||||||
double bestRoundDistance=std::numeric_limits<double>::infinity();
|
double bestRoundDistance=std::numeric_limits<double>::infinity();
|
||||||
@@ -4580,6 +4696,78 @@ namespace StandaloneStreamBenchmark
|
|||||||
[index](const auto& s){return s.strateIndex==index;});
|
[index](const auto& s){return s.strateIndex==index;});
|
||||||
return it==ResolvedLayout->strates.end()?nullptr:&*it;
|
return it==ResolvedLayout->strates.end()?nullptr:&*it;
|
||||||
};
|
};
|
||||||
|
const auto landingChamberConnectionContains=[&](
|
||||||
|
const VoxelForgeCore::ResolvedLayoutPassageV1& passage,bool upper,
|
||||||
|
double xMetres,double yMetres,double zMetres)
|
||||||
|
{
|
||||||
|
const auto& chamber=upper?passage.upperLandingChamber
|
||||||
|
:passage.lowerLandingChamber;
|
||||||
|
const auto* stratum=findStratum(upper?passage.upperStrateIndex
|
||||||
|
:passage.lowerStrateIndex);
|
||||||
|
if(!stratum||chamber.radiusSubvoxels<=0)return false;
|
||||||
|
std::vector<const VoxelForgeCore::ResolvedLayoutAnchorV2*> route;
|
||||||
|
for(const auto& anchor:stratum->anchors)
|
||||||
|
if(anchor.kind=="route_point"&&anchor.values.size()>=3)
|
||||||
|
route.push_back(&anchor);
|
||||||
|
if(route.size()<2)return false;
|
||||||
|
const double cx=qToMetres(chamber.floorCenterSubvoxels[0]);
|
||||||
|
const double cy=qToMetres(chamber.floorCenterSubvoxels[1]);
|
||||||
|
const double mouthZ=qToMetres(upper?passage.upperMouthSubvoxels[2]
|
||||||
|
:passage.lowerMouthSubvoxels[2]);
|
||||||
|
std::size_t endpoint=route.size();
|
||||||
|
double endpointDistanceSquared=1.0;
|
||||||
|
for(std::size_t index=0;index<route.size();++index)
|
||||||
|
{
|
||||||
|
const double dx=qToMetres(route[index]->values[0])-cx;
|
||||||
|
const double dy=qToMetres(route[index]->values[1])-cy;
|
||||||
|
const double z=qToMetres(route[index]->values[2]);
|
||||||
|
const double distanceSquared=dx*dx+dy*dy;
|
||||||
|
if(distanceSquared<endpointDistanceSquared
|
||||||
|
&&std::abs(z-mouthZ)<=1.5)
|
||||||
|
{endpoint=index;endpointDistanceSquared=distanceSquared;}
|
||||||
|
}
|
||||||
|
if(endpoint==route.size())return false;
|
||||||
|
std::vector<double> routeDistance(route.size(),0.0);
|
||||||
|
for(std::size_t index=1;index<route.size();++index)
|
||||||
|
{
|
||||||
|
const double dx=qToMetres(route[index]->values[0]
|
||||||
|
-route[index-1]->values[0]);
|
||||||
|
const double dy=qToMetres(route[index]->values[1]
|
||||||
|
-route[index-1]->values[1]);
|
||||||
|
routeDistance[index]=routeDistance[index-1]+std::hypot(dx,dy);
|
||||||
|
}
|
||||||
|
for(std::size_t index=1;index<route.size();++index)
|
||||||
|
{
|
||||||
|
const auto& a=*route[index-1];
|
||||||
|
const auto& b=*route[index];
|
||||||
|
const double ax=qToMetres(a.values[0]),ay=qToMetres(a.values[1]);
|
||||||
|
const double az=qToMetres(a.values[2]);
|
||||||
|
const double dx=qToMetres(b.values[0]-a.values[0]);
|
||||||
|
const double dy=qToMetres(b.values[1]-a.values[1]);
|
||||||
|
const double lengthSquared=dx*dx+dy*dy;
|
||||||
|
if(lengthSquared<=1.0e-12)continue;
|
||||||
|
const double t=std::clamp(((xMetres-ax)*dx+(yMetres-ay)*dy)
|
||||||
|
/lengthSquared,0.0,1.0);
|
||||||
|
const double fromEndpoint=std::abs(std::lerp(
|
||||||
|
routeDistance[index-1],routeDistance[index],t)
|
||||||
|
-routeDistance[endpoint]);
|
||||||
|
if(fromEndpoint>9.0)continue;
|
||||||
|
const double routeX=ax+t*dx,routeY=ay+t*dy;
|
||||||
|
const double routeZ=az+t*qToMetres(b.values[2]-a.values[2]);
|
||||||
|
const double lateralDistance=std::hypot(xMetres-routeX,
|
||||||
|
yMetres-routeY);
|
||||||
|
if(stratum->generatorKind=="TunnelNetwork"
|
||||||
|
||stratum->generatorKind=="VerticalShafts")
|
||||||
|
{
|
||||||
|
const double routeFloor=routeZ-1.13;
|
||||||
|
if(lateralDistance<=0.80&&zMetres>=routeFloor-0.30
|
||||||
|
&&zMetres<=routeFloor+2.25)return true;
|
||||||
|
}
|
||||||
|
else if(lateralDistance<=1.25
|
||||||
|
&&std::abs(zMetres-routeZ)<=1.5)return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
const auto controlSnapshot=CoreSnapshot.withoutResolvedLayoutPassages();
|
const auto controlSnapshot=CoreSnapshot.withoutResolvedLayoutPassages();
|
||||||
VoxelForgeCore::World blockedWorld(controlSnapshot);
|
VoxelForgeCore::World blockedWorld(controlSnapshot);
|
||||||
std::string blockedCompileError;
|
std::string blockedCompileError;
|
||||||
@@ -4596,6 +4784,9 @@ namespace StandaloneStreamBenchmark
|
|||||||
{
|
{
|
||||||
std::uint64_t floorSamples=0,floorViolations=0,openAirSamples=0;
|
std::uint64_t floorSamples=0,floorViolations=0,openAirSamples=0;
|
||||||
std::uint64_t wallSamples=0,wallViolations=0;
|
std::uint64_t wallSamples=0,wallViolations=0;
|
||||||
|
std::uint64_t chamberFloorSamples=0,chamberFloorViolations=0;
|
||||||
|
std::uint64_t chamberWallSamples=0,chamberWallViolations=0;
|
||||||
|
std::uint64_t chamberConnectionSkips=0;
|
||||||
std::uint64_t floorInteriorWins=0,wallInteriorWins=0;
|
std::uint64_t floorInteriorWins=0,wallInteriorWins=0;
|
||||||
std::uint64_t floorMouthBlendSkips=0,wallMouthBlendSkips=0;
|
std::uint64_t floorMouthBlendSkips=0,wallMouthBlendSkips=0;
|
||||||
std::uint64_t interiorSamples=0,interiorViolations=0;
|
std::uint64_t interiorSamples=0,interiorViolations=0;
|
||||||
@@ -4782,6 +4973,27 @@ namespace StandaloneStreamBenchmark
|
|||||||
shellDistance);
|
shellDistance);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
constexpr double LandingChamberShellThicknessMetres=0.75;
|
||||||
|
for(const auto& passage:ResolvedLayout->passages)
|
||||||
|
for(const auto* chamber:{&passage.upperLandingChamber,
|
||||||
|
&passage.lowerLandingChamber})
|
||||||
|
{
|
||||||
|
if(chamber->radiusSubvoxels<=0)continue;
|
||||||
|
const double centerX=qToMetres(chamber->floorCenterSubvoxels[0]);
|
||||||
|
const double centerY=qToMetres(chamber->floorCenterSubvoxels[1]);
|
||||||
|
const double floorCenterZ=qToMetres(
|
||||||
|
chamber->floorCenterSubvoxels[2])
|
||||||
|
+chamber->floorGradientQ16[0]/65536.0*(xMetres-centerX)
|
||||||
|
+chamber->floorGradientQ16[1]/65536.0*(yMetres-centerY);
|
||||||
|
const double radialDistance=std::hypot(xMetres-centerX,
|
||||||
|
yMetres-centerY)-qToMetres(chamber->radiusSubvoxels);
|
||||||
|
const double floorDistance=floorCenterZ-zMetres;
|
||||||
|
const double shellDistance=std::max({-floorDistance,
|
||||||
|
floorDistance-LandingChamberShellThicknessMetres,
|
||||||
|
radialDistance});
|
||||||
|
minimumShellDistance=std::min(minimumShellDistance,
|
||||||
|
shellDistance);
|
||||||
|
}
|
||||||
return minimumShellDistance;
|
return minimumShellDistance;
|
||||||
};
|
};
|
||||||
for(std::size_t segment=1;segment<route.size();++segment)
|
for(std::size_t segment=1;segment<route.size();++segment)
|
||||||
@@ -4934,6 +5146,89 @@ namespace StandaloneStreamBenchmark
|
|||||||
if(interior<=0.0f)++audit.interiorViolations;
|
if(interior<=0.0f)++audit.interiorViolations;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
constexpr std::array<double,3> chamberFloorRadii{{0.0,2.5,5.0}};
|
||||||
|
constexpr std::array<double,2> chamberWallHeights{{1.13,3.20}};
|
||||||
|
constexpr std::size_t ChamberAngularSamples=16;
|
||||||
|
for(const bool upper:{true,false})
|
||||||
|
{
|
||||||
|
const auto& chamber=upper?passage.upperLandingChamber
|
||||||
|
:passage.lowerLandingChamber;
|
||||||
|
if(chamber.radiusSubvoxels<=0||chamber.clearHeightSubvoxels<=0)continue;
|
||||||
|
const double centerX=qToMetres(chamber.floorCenterSubvoxels[0]);
|
||||||
|
const double centerY=qToMetres(chamber.floorCenterSubvoxels[1]);
|
||||||
|
const double baseFloorZ=qToMetres(chamber.floorCenterSubvoxels[2]);
|
||||||
|
const double radius=qToMetres(chamber.radiusSubvoxels);
|
||||||
|
const double gx=chamber.floorGradientQ16[0]/65536.0;
|
||||||
|
const double gy=chamber.floorGradientQ16[1]/65536.0;
|
||||||
|
for(const double radial:chamberFloorRadii)
|
||||||
|
{
|
||||||
|
if(radial>=radius-1.0)continue;
|
||||||
|
const std::size_t angles=radial==0.0?1:ChamberAngularSamples;
|
||||||
|
for(std::size_t angleIndex=0;angleIndex<angles;++angleIndex)
|
||||||
|
{
|
||||||
|
const double angle=2.0*3.14159265358979323846
|
||||||
|
*static_cast<double>(angleIndex)/ChamberAngularSamples;
|
||||||
|
const double sampleX=centerX+radial*std::cos(angle);
|
||||||
|
const double sampleY=centerY+radial*std::sin(angle);
|
||||||
|
const double floorZ=baseFloorZ+gx*(sampleX-centerX)
|
||||||
|
+gy*(sampleY-centerY);
|
||||||
|
for(const double depth:PassageShellAuditDepthsMetres)
|
||||||
|
{
|
||||||
|
const double sampleZ=floorZ-depth;
|
||||||
|
const VoxelForgeCore::Vec3d position{sampleX/coreMetres,
|
||||||
|
sampleY/coreMetres,sampleZ/coreMetres};
|
||||||
|
if(Native.densityAt(position)<0.0f)continue;
|
||||||
|
if(landingChamberConnectionContains(passage,upper,
|
||||||
|
sampleX,sampleY,sampleZ))
|
||||||
|
{++audit.chamberConnectionSkips;continue;}
|
||||||
|
++audit.chamberFloorSamples;
|
||||||
|
const float density=Field.densityAt(position);
|
||||||
|
if(density>=PassageShellDensityTolerance)
|
||||||
|
{
|
||||||
|
++audit.chamberFloorViolations;
|
||||||
|
std::printf("[PassageLandingChamberShellSample] id=%s side=%s kind=floor_missing xyz_m=(%.3f,%.3f,%.3f) native=%.6f density=%.6f\n",
|
||||||
|
passage.stableId.c_str(),upper?"upper":"lower",
|
||||||
|
sampleX,sampleY,sampleZ,Native.densityAt(position),density);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for(std::size_t angleIndex=0;angleIndex<ChamberAngularSamples;
|
||||||
|
++angleIndex)
|
||||||
|
{
|
||||||
|
const double angle=2.0*3.14159265358979323846
|
||||||
|
*static_cast<double>(angleIndex)/ChamberAngularSamples;
|
||||||
|
const double nx=std::cos(angle),ny=std::sin(angle);
|
||||||
|
for(const double depth:PassageShellAuditDepthsMetres)
|
||||||
|
for(const double height:chamberWallHeights)
|
||||||
|
{
|
||||||
|
const double sampleX=centerX+(radius+depth)*nx;
|
||||||
|
const double sampleY=centerY+(radius+depth)*ny;
|
||||||
|
const double floorZ=baseFloorZ+gx*(sampleX-centerX)
|
||||||
|
+gy*(sampleY-centerY);
|
||||||
|
const double sampleZ=floorZ+height;
|
||||||
|
const VoxelForgeCore::Vec3d position{sampleX/coreMetres,
|
||||||
|
sampleY/coreMetres,sampleZ/coreMetres};
|
||||||
|
if(Native.densityAt(position)<0.0f)continue;
|
||||||
|
if(passageInteriorWinsAt(sampleX,sampleY,sampleZ,
|
||||||
|
std::numeric_limits<std::size_t>::max(),
|
||||||
|
std::numeric_limits<std::size_t>::max(),passageIndex)
|
||||||
|
||landingChamberConnectionContains(passage,upper,
|
||||||
|
sampleX,sampleY,sampleZ))
|
||||||
|
{++audit.chamberConnectionSkips;continue;}
|
||||||
|
++audit.chamberWallSamples;
|
||||||
|
const float density=Field.densityAt(position);
|
||||||
|
if(density>=PassageShellDensityTolerance)
|
||||||
|
{
|
||||||
|
++audit.chamberWallViolations;
|
||||||
|
std::printf("[PassageLandingChamberShellSample] id=%s side=%s kind=wall_missing xyz_m=(%.3f,%.3f,%.3f) native=%.6f density=%.6f radial_m=%.3f height_m=%.3f\n",
|
||||||
|
passage.stableId.c_str(),upper?"upper":"lower",
|
||||||
|
sampleX,sampleY,sampleZ,Native.densityAt(position),density,
|
||||||
|
radius+depth,height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return audit;
|
return audit;
|
||||||
};
|
};
|
||||||
const auto mouthInContent=[&](std::int32_t index,
|
const auto mouthInContent=[&](std::int32_t index,
|
||||||
@@ -4947,14 +5242,74 @@ namespace StandaloneStreamBenchmark
|
|||||||
return zUm>=stratum->content.bottomMicrometres
|
return zUm>=stratum->content.bottomMicrometres
|
||||||
&&zUm<stratum->content.topMicrometres;
|
&&zUm<stratum->content.topMicrometres;
|
||||||
};
|
};
|
||||||
|
const auto auditLandingChamber=[&](const VoxelForgeCore::World& field,
|
||||||
|
const VoxelForgeCore::ResolvedLayoutPassageV1& passage,
|
||||||
|
std::size_t passageIndex,bool upper)
|
||||||
|
{
|
||||||
|
FWalkAudit total;total.passed=true;
|
||||||
|
const auto& chamber=upper?passage.upperLandingChamber
|
||||||
|
:passage.lowerLandingChamber;
|
||||||
|
if(chamber.radiusSubvoxels<=0||chamber.clearHeightSubvoxels<=0)
|
||||||
|
{total.passed=false;return total;}
|
||||||
|
constexpr std::array<std::array<double,2>,8> directions{{
|
||||||
|
{{1.0,0.0}},{{0.7071067811865475,0.7071067811865475}},
|
||||||
|
{{0.0,1.0}},{{-0.7071067811865475,0.7071067811865475}},
|
||||||
|
{{-1.0,0.0}},{{-0.7071067811865475,-0.7071067811865475}},
|
||||||
|
{{0.0,-1.0}},{{0.7071067811865475,-0.7071067811865475}}}};
|
||||||
|
const double centerX=qToMetres(chamber.floorCenterSubvoxels[0]);
|
||||||
|
const double centerY=qToMetres(chamber.floorCenterSubvoxels[1]);
|
||||||
|
const double centerZ=qToMetres(chamber.floorCenterSubvoxels[2]);
|
||||||
|
const double radius=std::min(5.75,
|
||||||
|
qToMetres(chamber.radiusSubvoxels)-1.5);
|
||||||
|
const double gx=chamber.floorGradientQ16[0]/65536.0;
|
||||||
|
const double gy=chamber.floorGradientQ16[1]/65536.0;
|
||||||
|
const std::function<bool(double,double,double)> interiorPredicate=
|
||||||
|
[&](double x,double y,double z)
|
||||||
|
{return passageInteriorWinsAt(x,y,z,
|
||||||
|
std::numeric_limits<std::size_t>::max(),
|
||||||
|
std::numeric_limits<std::size_t>::max(),passageIndex);};
|
||||||
|
for(const auto& direction:directions)
|
||||||
|
{
|
||||||
|
std::vector<std::array<double,3>> chamberPath;
|
||||||
|
const std::size_t intervals=std::max<std::size_t>(1,
|
||||||
|
static_cast<std::size_t>(std::ceil(radius/0.5)));
|
||||||
|
chamberPath.reserve(intervals+1);
|
||||||
|
for(std::size_t step=0;step<=intervals;++step)
|
||||||
|
{
|
||||||
|
const double distance=radius*step/intervals;
|
||||||
|
const double x=centerX+direction[0]*distance;
|
||||||
|
const double y=centerY+direction[1]*distance;
|
||||||
|
const double floorZ=centerZ+gx*(x-centerX)+gy*(y-centerY);
|
||||||
|
chamberPath.push_back({x/coreMetres,y/coreMetres,
|
||||||
|
(floorZ+1.13)/coreMetres});
|
||||||
|
}
|
||||||
|
const FWalkAudit part=auditPlayerPath(field,chamberPath,
|
||||||
|
0.125,0.125,1.125,3,0.45,{},interiorPredicate);
|
||||||
|
total.segments+=part.segments;total.centers+=part.centers;
|
||||||
|
total.capsuleSamples+=part.capsuleSamples;
|
||||||
|
total.blocked+=part.blocked;
|
||||||
|
total.passageInteriorCapsuleSamples+=part.passageInteriorCapsuleSamples;
|
||||||
|
total.passageInteriorSolidSamples+=part.passageInteriorSolidSamples;
|
||||||
|
total.supportSamples+=part.supportSamples;
|
||||||
|
total.unsupported+=part.unsupported;
|
||||||
|
total.gradeFailures+=part.gradeFailures;
|
||||||
|
total.supportOverlapSkips+=part.supportOverlapSkips;
|
||||||
|
total.passed=total.passed&&part.passed;
|
||||||
|
}
|
||||||
|
return total;
|
||||||
|
};
|
||||||
std::uint64_t passageCount=0,passagePasses=0,blockedControls=0,naturalPasses=0;
|
std::uint64_t passageCount=0,passagePasses=0,blockedControls=0,naturalPasses=0;
|
||||||
std::uint64_t passageInteriorCapsuleSamples=0,passageInteriorSolidSamples=0;
|
std::uint64_t passageInteriorCapsuleSamples=0,passageInteriorSolidSamples=0;
|
||||||
std::uint64_t passageInteriorControls=0;
|
std::uint64_t passageInteriorControls=0;
|
||||||
|
std::uint64_t landingChamberCount=0,landingChamberNegativeControls=0;
|
||||||
std::uint64_t mouthProfilePasses=0,ripplePasses=0,rippleControlsDetected=0;
|
std::uint64_t mouthProfilePasses=0,ripplePasses=0,rippleControlsDetected=0;
|
||||||
std::uint64_t floorRippleControlsDetected=0,floorStepRippleControlsDetected=0;
|
std::uint64_t floorRippleControlsDetected=0,floorStepRippleControlsDetected=0;
|
||||||
std::uint64_t shellAudits=0,shellFloorSamples=0,shellFloorViolations=0;
|
std::uint64_t shellAudits=0,shellFloorSamples=0,shellFloorViolations=0;
|
||||||
std::uint64_t shellFloorMouthBlendSkips=0;
|
std::uint64_t shellFloorMouthBlendSkips=0;
|
||||||
std::uint64_t shellOpenAirSamples=0,shellWallSamples=0,shellWallViolations=0;
|
std::uint64_t shellOpenAirSamples=0,shellWallSamples=0,shellWallViolations=0;
|
||||||
|
std::uint64_t shellChamberFloorSamples=0,shellChamberFloorViolations=0;
|
||||||
|
std::uint64_t shellChamberWallSamples=0,shellChamberWallViolations=0;
|
||||||
|
std::uint64_t shellChamberConnectionSkips=0;
|
||||||
std::uint64_t shellInteriorSamples=0,shellInteriorViolations=0;
|
std::uint64_t shellInteriorSamples=0,shellInteriorViolations=0;
|
||||||
for(std::size_t passageIndex=0;passageIndex<ResolvedLayout->passages.size();++passageIndex)
|
for(std::size_t passageIndex=0;passageIndex<ResolvedLayout->passages.size();++passageIndex)
|
||||||
{
|
{
|
||||||
@@ -4980,11 +5335,26 @@ namespace StandaloneStreamBenchmark
|
|||||||
std::numeric_limits<std::size_t>::max(),passageIndex);};
|
std::numeric_limits<std::size_t>::max(),passageIndex);};
|
||||||
const FWalkAudit passageAudit=auditPlayerPath(Shape,path,0.375,0.125,
|
const FWalkAudit passageAudit=auditPlayerPath(Shape,path,0.375,0.125,
|
||||||
1.125,3,0.45,passageSupportExemption,passageInteriorPredicate);
|
1.125,3,0.45,passageSupportExemption,passageInteriorPredicate);
|
||||||
|
const FWalkAudit upperChamberAudit=auditLandingChamber(Shape,
|
||||||
|
passage,passageIndex,true);
|
||||||
|
const FWalkAudit lowerChamberAudit=auditLandingChamber(Shape,
|
||||||
|
passage,passageIndex,false);
|
||||||
|
const bool landingChambersWalkable=upperChamberAudit.passed
|
||||||
|
&&lowerChamberAudit.passed;
|
||||||
const bool passageInteriorAirPassed=
|
const bool passageInteriorAirPassed=
|
||||||
passageAudit.passageInteriorCapsuleSamples>0
|
passageAudit.passageInteriorCapsuleSamples>0
|
||||||
&&passageAudit.passageInteriorSolidSamples==0;
|
&&passageAudit.passageInteriorSolidSamples==0
|
||||||
passageInteriorCapsuleSamples+=passageAudit.passageInteriorCapsuleSamples;
|
&&upperChamberAudit.passageInteriorCapsuleSamples>0
|
||||||
passageInteriorSolidSamples+=passageAudit.passageInteriorSolidSamples;
|
&&lowerChamberAudit.passageInteriorCapsuleSamples>0
|
||||||
|
&&upperChamberAudit.passageInteriorSolidSamples==0
|
||||||
|
&&lowerChamberAudit.passageInteriorSolidSamples==0;
|
||||||
|
passageInteriorCapsuleSamples+=passageAudit.passageInteriorCapsuleSamples
|
||||||
|
+upperChamberAudit.passageInteriorCapsuleSamples
|
||||||
|
+lowerChamberAudit.passageInteriorCapsuleSamples;
|
||||||
|
passageInteriorSolidSamples+=passageAudit.passageInteriorSolidSamples
|
||||||
|
+upperChamberAudit.passageInteriorSolidSamples
|
||||||
|
+lowerChamberAudit.passageInteriorSolidSamples;
|
||||||
|
landingChamberCount+=2;
|
||||||
const FPassageNaturalnessAudit naturalAudit=
|
const FPassageNaturalnessAudit naturalAudit=
|
||||||
auditPassageNaturalness(Shape,passageIndex);
|
auditPassageNaturalness(Shape,passageIndex);
|
||||||
const auto lowerProfileStratum=std::find_if(ResolvedLayout->strates.begin(),
|
const auto lowerProfileStratum=std::find_if(ResolvedLayout->strates.begin(),
|
||||||
@@ -5027,6 +5397,11 @@ namespace StandaloneStreamBenchmark
|
|||||||
shellOpenAirSamples+=shellAudit.openAirSamples;
|
shellOpenAirSamples+=shellAudit.openAirSamples;
|
||||||
shellWallSamples+=shellAudit.wallSamples;
|
shellWallSamples+=shellAudit.wallSamples;
|
||||||
shellWallViolations+=shellAudit.wallViolations;
|
shellWallViolations+=shellAudit.wallViolations;
|
||||||
|
shellChamberFloorSamples+=shellAudit.chamberFloorSamples;
|
||||||
|
shellChamberFloorViolations+=shellAudit.chamberFloorViolations;
|
||||||
|
shellChamberWallSamples+=shellAudit.chamberWallSamples;
|
||||||
|
shellChamberWallViolations+=shellAudit.chamberWallViolations;
|
||||||
|
shellChamberConnectionSkips+=shellAudit.chamberConnectionSkips;
|
||||||
shellInteriorSamples+=shellAudit.interiorSamples;
|
shellInteriorSamples+=shellAudit.interiorSamples;
|
||||||
shellInteriorViolations+=shellAudit.interiorViolations;
|
shellInteriorViolations+=shellAudit.interiorViolations;
|
||||||
const auto radialSquared=[](const std::array<std::int64_t,3>& p)
|
const auto radialSquared=[](const std::array<std::int64_t,3>& p)
|
||||||
@@ -5043,7 +5418,8 @@ namespace StandaloneStreamBenchmark
|
|||||||
rippleControlsDetected+=rippleAudit.controlDetected;
|
rippleControlsDetected+=rippleAudit.controlDetected;
|
||||||
floorRippleControlsDetected+=rippleAudit.floorControlDetected;
|
floorRippleControlsDetected+=rippleAudit.floorControlDetected;
|
||||||
floorStepRippleControlsDetected+=rippleAudit.floorStepControlDetected;
|
floorStepRippleControlsDetected+=rippleAudit.floorStepControlDetected;
|
||||||
passagePasses+=passageAudit.passed&&passageInteriorAirPassed
|
passagePasses+=passageAudit.passed&&landingChambersWalkable
|
||||||
|
&&passageInteriorAirPassed
|
||||||
&&mouthsInRadius&&mouthsInContent
|
&&mouthsInRadius&&mouthsInContent
|
||||||
&&naturalAudit.passed&&mouthProfileAudit.passed&&rippleAudit.passed;
|
&&naturalAudit.passed&&mouthProfileAudit.passed&&rippleAudit.passed;
|
||||||
std::printf("[ResolvedPassageAudit] id=%s interface=%d-%d control_points=%zu center_samples=%llu capsule_samples=%llu blocked=%llu passage_interior_capsule_samples=%llu passage_interior_solid_samples=%llu interior_air=%s support_samples=%llu support_overlap_skips=%llu unsupported=%llu grade_failures=%llu mouth_content=%d/2 mouth_radius=%d/2 walkability=%s\n",
|
std::printf("[ResolvedPassageAudit] id=%s interface=%d-%d control_points=%zu center_samples=%llu capsule_samples=%llu blocked=%llu passage_interior_capsule_samples=%llu passage_interior_solid_samples=%llu interior_air=%s support_samples=%llu support_overlap_skips=%llu unsupported=%llu grade_failures=%llu mouth_content=%d/2 mouth_radius=%d/2 walkability=%s\n",
|
||||||
@@ -5058,7 +5434,26 @@ namespace StandaloneStreamBenchmark
|
|||||||
static_cast<unsigned long long>(passageAudit.supportOverlapSkips),
|
static_cast<unsigned long long>(passageAudit.supportOverlapSkips),
|
||||||
static_cast<unsigned long long>(passageAudit.unsupported),
|
static_cast<unsigned long long>(passageAudit.unsupported),
|
||||||
static_cast<unsigned long long>(passageAudit.gradeFailures),mouthsInContent?2:0,
|
static_cast<unsigned long long>(passageAudit.gradeFailures),mouthsInContent?2:0,
|
||||||
mouthsInRadius?2:0,passageAudit.passed?"PASS":"FAIL");
|
mouthsInRadius?2:0,
|
||||||
|
passageAudit.passed&&landingChambersWalkable?"PASS":"FAIL");
|
||||||
|
std::printf("[PassageLandingChamberAudit] id=%s side=upper capsule_samples=%llu solid_samples=%llu support_samples=%llu unsupported=%llu grade_failures=%llu status=%s\n",
|
||||||
|
passage.stableId.c_str(),
|
||||||
|
static_cast<unsigned long long>(upperChamberAudit.passageInteriorCapsuleSamples),
|
||||||
|
static_cast<unsigned long long>(upperChamberAudit.passageInteriorSolidSamples),
|
||||||
|
static_cast<unsigned long long>(upperChamberAudit.supportSamples),
|
||||||
|
static_cast<unsigned long long>(upperChamberAudit.unsupported),
|
||||||
|
static_cast<unsigned long long>(upperChamberAudit.gradeFailures),
|
||||||
|
upperChamberAudit.passed&&upperChamberAudit.passageInteriorSolidSamples==0
|
||||||
|
&&upperChamberAudit.passageInteriorCapsuleSamples>0?"PASS":"FAIL");
|
||||||
|
std::printf("[PassageLandingChamberAudit] id=%s side=lower capsule_samples=%llu solid_samples=%llu support_samples=%llu unsupported=%llu grade_failures=%llu status=%s\n",
|
||||||
|
passage.stableId.c_str(),
|
||||||
|
static_cast<unsigned long long>(lowerChamberAudit.passageInteriorCapsuleSamples),
|
||||||
|
static_cast<unsigned long long>(lowerChamberAudit.passageInteriorSolidSamples),
|
||||||
|
static_cast<unsigned long long>(lowerChamberAudit.supportSamples),
|
||||||
|
static_cast<unsigned long long>(lowerChamberAudit.unsupported),
|
||||||
|
static_cast<unsigned long long>(lowerChamberAudit.gradeFailures),
|
||||||
|
lowerChamberAudit.passed&&lowerChamberAudit.passageInteriorSolidSamples==0
|
||||||
|
&&lowerChamberAudit.passageInteriorCapsuleSamples>0?"PASS":"FAIL");
|
||||||
std::printf("[PassageNaturalnessAudit] id=%s flat_run_max_m=%.3f limit_m=%.3f wall_relief_p2p_m=%.3f minimum_m=%.3f samples=%llu missing=%llu status=%s\n",
|
std::printf("[PassageNaturalnessAudit] id=%s flat_run_max_m=%.3f limit_m=%.3f wall_relief_p2p_m=%.3f minimum_m=%.3f samples=%llu missing=%llu status=%s\n",
|
||||||
passage.stableId.c_str(),naturalAudit.maximumFlatRunMetres,MaximumFlatRunMetres,
|
passage.stableId.c_str(),naturalAudit.maximumFlatRunMetres,MaximumFlatRunMetres,
|
||||||
naturalAudit.wallReliefPeakToPeakMetres,MinimumWallReliefMetres,
|
naturalAudit.wallReliefPeakToPeakMetres,MinimumWallReliefMetres,
|
||||||
@@ -5114,12 +5509,17 @@ namespace StandaloneStreamBenchmark
|
|||||||
passage.stableId.c_str(),value[0],value[1],value[2],value[2]-value[1],
|
passage.stableId.c_str(),value[0],value[1],value[2],value[2]-value[1],
|
||||||
value[3],value[4],value[5]);
|
value[3],value[4],value[5]);
|
||||||
}
|
}
|
||||||
std::printf("[PassageShellAudit] id=%s floor_samples=%llu floor_violations=%llu floor_overlap_skips=%llu floor_mouth_blend_skips=%llu open_air_shell_samples=%llu wall_samples=%llu wall_violations=%llu wall_overlap_skips=%llu wall_mouth_blend_skips=%llu interior_samples=%llu interior_violations=%llu spacing_m=%.3f status=%s\n",
|
std::printf("[PassageShellAudit] id=%s floor_samples=%llu floor_violations=%llu floor_overlap_skips=%llu floor_mouth_blend_skips=%llu chamber_floor_samples=%llu chamber_floor_violations=%llu chamber_wall_samples=%llu chamber_wall_violations=%llu chamber_connection_skips=%llu open_air_shell_samples=%llu wall_samples=%llu wall_violations=%llu wall_overlap_skips=%llu wall_mouth_blend_skips=%llu interior_samples=%llu interior_violations=%llu spacing_m=%.3f status=%s\n",
|
||||||
passage.stableId.c_str(),
|
passage.stableId.c_str(),
|
||||||
static_cast<unsigned long long>(shellAudit.floorSamples),
|
static_cast<unsigned long long>(shellAudit.floorSamples),
|
||||||
static_cast<unsigned long long>(shellAudit.floorViolations),
|
static_cast<unsigned long long>(shellAudit.floorViolations),
|
||||||
static_cast<unsigned long long>(shellAudit.floorInteriorWins),
|
static_cast<unsigned long long>(shellAudit.floorInteriorWins),
|
||||||
static_cast<unsigned long long>(shellAudit.floorMouthBlendSkips),
|
static_cast<unsigned long long>(shellAudit.floorMouthBlendSkips),
|
||||||
|
static_cast<unsigned long long>(shellAudit.chamberFloorSamples),
|
||||||
|
static_cast<unsigned long long>(shellAudit.chamberFloorViolations),
|
||||||
|
static_cast<unsigned long long>(shellAudit.chamberWallSamples),
|
||||||
|
static_cast<unsigned long long>(shellAudit.chamberWallViolations),
|
||||||
|
static_cast<unsigned long long>(shellAudit.chamberConnectionSkips),
|
||||||
static_cast<unsigned long long>(shellAudit.openAirSamples),
|
static_cast<unsigned long long>(shellAudit.openAirSamples),
|
||||||
static_cast<unsigned long long>(shellAudit.wallSamples),
|
static_cast<unsigned long long>(shellAudit.wallSamples),
|
||||||
static_cast<unsigned long long>(shellAudit.wallViolations),
|
static_cast<unsigned long long>(shellAudit.wallViolations),
|
||||||
@@ -5129,6 +5529,8 @@ namespace StandaloneStreamBenchmark
|
|||||||
static_cast<unsigned long long>(shellAudit.interiorViolations),
|
static_cast<unsigned long long>(shellAudit.interiorViolations),
|
||||||
PassageShellAuditSpacingMetres,
|
PassageShellAuditSpacingMetres,
|
||||||
shellAudit.floorViolations==0&&shellAudit.wallViolations==0
|
shellAudit.floorViolations==0&&shellAudit.wallViolations==0
|
||||||
|
&&shellAudit.chamberFloorViolations==0
|
||||||
|
&&shellAudit.chamberWallViolations==0
|
||||||
&&shellAudit.interiorViolations==0
|
&&shellAudit.interiorViolations==0
|
||||||
?"PASS":"FAIL");
|
?"PASS":"FAIL");
|
||||||
for(std::size_t i=0;i<shellAudit.firstViolationCount;++i)
|
for(std::size_t i=0;i<shellAudit.firstViolationCount;++i)
|
||||||
@@ -5425,7 +5827,19 @@ namespace StandaloneStreamBenchmark
|
|||||||
std::numeric_limits<std::size_t>::max(),passageIndex);};
|
std::numeric_limits<std::size_t>::max(),passageIndex);};
|
||||||
const FWalkAudit blockedAudit=auditPlayerPath(blockedWorld,path,
|
const FWalkAudit blockedAudit=auditPlayerPath(blockedWorld,path,
|
||||||
0.125,0.125,0.25,3,0.45,{},passageInteriorPredicate);
|
0.125,0.125,0.25,3,0.45,{},passageInteriorPredicate);
|
||||||
|
const FWalkAudit upperChamberBlocked=auditLandingChamber(blockedWorld,
|
||||||
|
passage,passageIndex,true);
|
||||||
|
const FWalkAudit lowerChamberBlocked=auditLandingChamber(blockedWorld,
|
||||||
|
passage,passageIndex,false);
|
||||||
blockedControls+=blockedAudit.blocked>0&&!blockedAudit.passed;
|
blockedControls+=blockedAudit.blocked>0&&!blockedAudit.passed;
|
||||||
|
const bool upperChamberControlDetected=
|
||||||
|
upperChamberBlocked.passageInteriorCapsuleSamples>0
|
||||||
|
&&upperChamberBlocked.passageInteriorSolidSamples>0;
|
||||||
|
const bool lowerChamberControlDetected=
|
||||||
|
lowerChamberBlocked.passageInteriorCapsuleSamples>0
|
||||||
|
&&lowerChamberBlocked.passageInteriorSolidSamples>0;
|
||||||
|
landingChamberNegativeControls+=upperChamberControlDetected
|
||||||
|
+lowerChamberControlDetected;
|
||||||
const bool passageInteriorControlDetected=
|
const bool passageInteriorControlDetected=
|
||||||
blockedAudit.passageInteriorCapsuleSamples>0
|
blockedAudit.passageInteriorCapsuleSamples>0
|
||||||
&&blockedAudit.passageInteriorSolidSamples>0;
|
&&blockedAudit.passageInteriorSolidSamples>0;
|
||||||
@@ -5433,6 +5847,16 @@ namespace StandaloneStreamBenchmark
|
|||||||
std::printf("[ResolvedPassageBlockedControl] id=%s blocked_capsule_samples=%llu detected=%s\n",
|
std::printf("[ResolvedPassageBlockedControl] id=%s blocked_capsule_samples=%llu detected=%s\n",
|
||||||
passage.stableId.c_str(),static_cast<unsigned long long>(blockedAudit.blocked),
|
passage.stableId.c_str(),static_cast<unsigned long long>(blockedAudit.blocked),
|
||||||
blockedAudit.blocked>0&&!blockedAudit.passed?"true":"false");
|
blockedAudit.blocked>0&&!blockedAudit.passed?"true":"false");
|
||||||
|
std::printf("[PassageLandingChamberNegativeControl] id=%s side=upper capsule_samples=%llu interior_solid_samples=%llu detected=%s\n",
|
||||||
|
passage.stableId.c_str(),
|
||||||
|
static_cast<unsigned long long>(upperChamberBlocked.passageInteriorCapsuleSamples),
|
||||||
|
static_cast<unsigned long long>(upperChamberBlocked.passageInteriorSolidSamples),
|
||||||
|
upperChamberControlDetected?"true":"false");
|
||||||
|
std::printf("[PassageLandingChamberNegativeControl] id=%s side=lower capsule_samples=%llu interior_solid_samples=%llu detected=%s\n",
|
||||||
|
passage.stableId.c_str(),
|
||||||
|
static_cast<unsigned long long>(lowerChamberBlocked.passageInteriorCapsuleSamples),
|
||||||
|
static_cast<unsigned long long>(lowerChamberBlocked.passageInteriorSolidSamples),
|
||||||
|
lowerChamberControlDetected?"true":"false");
|
||||||
std::printf("[ResolvedPassageInteriorNegativeControl] id=%s passage_interior_capsule_samples=%llu interior_solid_samples=%llu detected=%s status=%s\n",
|
std::printf("[ResolvedPassageInteriorNegativeControl] id=%s passage_interior_capsule_samples=%llu interior_solid_samples=%llu detected=%s status=%s\n",
|
||||||
passage.stableId.c_str(),
|
passage.stableId.c_str(),
|
||||||
static_cast<unsigned long long>(blockedAudit.passageInteriorCapsuleSamples),
|
static_cast<unsigned long long>(blockedAudit.passageInteriorCapsuleSamples),
|
||||||
@@ -5443,21 +5867,35 @@ namespace StandaloneStreamBenchmark
|
|||||||
const bool passageInteriorAuditPassed=passageCount>0
|
const bool passageInteriorAuditPassed=passageCount>0
|
||||||
&&passageInteriorCapsuleSamples>0&&passageInteriorSolidSamples==0
|
&&passageInteriorCapsuleSamples>0&&passageInteriorSolidSamples==0
|
||||||
&&passageInteriorControls==passageCount;
|
&&passageInteriorControls==passageCount;
|
||||||
std::printf("[PassageInteriorCapsuleAuditSummary] interfaces=%llu capsule_samples=%llu solid_samples=%llu negative_controls=%llu/%llu status=%s\n",
|
std::printf("[PassageInteriorCapsuleAuditSummary] interfaces=%llu capsule_samples=%llu solid_samples=%llu negative_controls=%llu/%llu landing_chambers=%llu chamber_negative_controls=%llu/%llu status=%s\n",
|
||||||
static_cast<unsigned long long>(passageCount),
|
static_cast<unsigned long long>(passageCount),
|
||||||
static_cast<unsigned long long>(passageInteriorCapsuleSamples),
|
static_cast<unsigned long long>(passageInteriorCapsuleSamples),
|
||||||
static_cast<unsigned long long>(passageInteriorSolidSamples),
|
static_cast<unsigned long long>(passageInteriorSolidSamples),
|
||||||
static_cast<unsigned long long>(passageInteriorControls),
|
static_cast<unsigned long long>(passageInteriorControls),
|
||||||
static_cast<unsigned long long>(passageCount),
|
static_cast<unsigned long long>(passageCount),
|
||||||
|
static_cast<unsigned long long>(landingChamberCount),
|
||||||
|
static_cast<unsigned long long>(landingChamberNegativeControls),
|
||||||
|
static_cast<unsigned long long>(landingChamberCount),
|
||||||
passageInteriorAuditPassed?"PASS":"FAIL");
|
passageInteriorAuditPassed?"PASS":"FAIL");
|
||||||
|
std::printf("[PassageLandingChamberNegativeControlSummary] detected=%llu/%llu naturally_open_baselines=%llu status=OBSERVED\n",
|
||||||
|
static_cast<unsigned long long>(landingChamberNegativeControls),
|
||||||
|
static_cast<unsigned long long>(landingChamberCount),
|
||||||
|
static_cast<unsigned long long>(landingChamberCount
|
||||||
|
-landingChamberNegativeControls));
|
||||||
const bool shellAuditPassed=shellAudits==passageCount
|
const bool shellAuditPassed=shellAudits==passageCount
|
||||||
&&shellFloorViolations==0&&shellWallViolations==0
|
&&shellFloorViolations==0&&shellWallViolations==0
|
||||||
|
&&shellChamberFloorViolations==0&&shellChamberWallViolations==0
|
||||||
&&shellInteriorViolations==0&&shellOpenAirSamples>0;
|
&&shellInteriorViolations==0&&shellOpenAirSamples>0;
|
||||||
std::printf("[PassageShellAuditSummary] interfaces=%llu floor_samples=%llu floor_violations=%llu floor_mouth_blend_skips=%llu open_air_shell_samples=%llu wall_samples=%llu wall_violations=%llu interior_samples=%llu interior_violations=%llu status=%s\n",
|
std::printf("[PassageShellAuditSummary] interfaces=%llu floor_samples=%llu floor_violations=%llu floor_mouth_blend_skips=%llu chamber_floor_samples=%llu chamber_floor_violations=%llu chamber_wall_samples=%llu chamber_wall_violations=%llu chamber_connection_skips=%llu open_air_shell_samples=%llu wall_samples=%llu wall_violations=%llu interior_samples=%llu interior_violations=%llu status=%s\n",
|
||||||
static_cast<unsigned long long>(shellAudits),
|
static_cast<unsigned long long>(shellAudits),
|
||||||
static_cast<unsigned long long>(shellFloorSamples),
|
static_cast<unsigned long long>(shellFloorSamples),
|
||||||
static_cast<unsigned long long>(shellFloorViolations),
|
static_cast<unsigned long long>(shellFloorViolations),
|
||||||
static_cast<unsigned long long>(shellFloorMouthBlendSkips),
|
static_cast<unsigned long long>(shellFloorMouthBlendSkips),
|
||||||
|
static_cast<unsigned long long>(shellChamberFloorSamples),
|
||||||
|
static_cast<unsigned long long>(shellChamberFloorViolations),
|
||||||
|
static_cast<unsigned long long>(shellChamberWallSamples),
|
||||||
|
static_cast<unsigned long long>(shellChamberWallViolations),
|
||||||
|
static_cast<unsigned long long>(shellChamberConnectionSkips),
|
||||||
static_cast<unsigned long long>(shellOpenAirSamples),
|
static_cast<unsigned long long>(shellOpenAirSamples),
|
||||||
static_cast<unsigned long long>(shellWallSamples),
|
static_cast<unsigned long long>(shellWallSamples),
|
||||||
static_cast<unsigned long long>(shellWallViolations),
|
static_cast<unsigned long long>(shellWallViolations),
|
||||||
|
|||||||