diff --git a/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h b/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h index 4799b9e..13440df 100644 --- a/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h +++ b/Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h @@ -512,7 +512,8 @@ namespace VoxelForgeCore // boundary-blended host rock from widening the authored passage wall. // v238 plans one resolved passage spline with explicit landing chambers. // v239 plans their floor planes as the passage's continuous floor profile. - inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 239; + // v240 replaces chamber boxes and shells with bounded clamp-and-cone landing pads. + inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 240; enum class FieldChannel : std::uint32_t { diff --git a/Standalone/Core/src/ShapeWorldInternal.h b/Standalone/Core/src/ShapeWorldInternal.h index 6b1a6af..3ce93e4 100644 --- a/Standalone/Core/src/ShapeWorldInternal.h +++ b/Standalone/Core/src/ShapeWorldInternal.h @@ -56,6 +56,11 @@ static bool VF_IsL130PassageReachProofAuditEnabled() static constexpr double VF_PassageShellThicknessMetres=0.75; static constexpr double VF_PassageWallReliefMetres=0.21; static constexpr double VF_PassageCornerBlendMetres=0.50; +static constexpr double VF_LandingPadConeSlope=0.2125565616700221; // tan(12 degrees) +static constexpr double VF_LandingPadFillDepthMetres=1.25; +static constexpr double VF_LandingPadHeadroomMetres=3.0; +static constexpr double VF_LandingPadBlendRadiusMetres=8.0; +static constexpr double VF_LandingPadSmoothMinMaximumMetres=0.50; static constexpr double VF_PassageReachMarginMetres=0.01; // Radial support in metres: shell + maximum relief + the smooth-max support // (corner-blend width / 6) + a 1 cm guard for float and bound rounding. @@ -1234,8 +1239,11 @@ public: float ApplyResolvedStratePassageCarves(float density,float X,float Y,float Z, float nativeTunnelFieldDensity=std::numeric_limits::lowest(), - float nativeFloorSupport=std::numeric_limits::lowest()) const + float nativeFloorSupport=std::numeric_limits::lowest(), + float* outTubeOnlyDistance=nullptr) const { + if(outTubeOnlyDistance) + *outTubeOnlyDistance=std::numeric_limits::infinity(); VoxelForgeCore::Linux77Profiler::FScopedPhase l77PassagePhase( VoxelForgeCore::Linux77Profiler::EPhase::Passage); if(!ResolvedLayout||ResolvedLayout->passages.empty()||!std::isfinite(X) @@ -1280,8 +1288,6 @@ public: double unionDistance=std::numeric_limits::infinity(); double unionShellDistance=std::numeric_limits::infinity(); double unionFloorShellDistance=std::numeric_limits::infinity(); - double unionLandingChamberFloorShellDistance= - std::numeric_limits::infinity(); double plannedTunnelPassageShellDistance=std::numeric_limits::infinity(); const std::uint32_t reliefSeed=static_cast(Owner?Owner->Seed:0); const auto* queryStratum=FindBoundStratumForManagerZ(Z); @@ -1324,48 +1330,6 @@ public: if(passageProfile)++passageProfile->passageIterations; 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( - landingChamber->floorCenterSubvoxels[0]); - const double centerY=static_cast( - landingChamber->floorCenterSubvoxels[1]); - const double floorCenterZ=static_cast( - 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; @@ -1658,12 +1622,9 @@ public: } if(!std::isfinite(unionDistance))return density; + if(outTubeOnlyDistance) + *outTubeOnlyDistance=static_cast(unionDistance/Q); const float passageAirDistance=static_cast(-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(unionLandingChamberFloorShellDistance/Q); const auto plannedPortalWalkCorridorContains=[&]() { if(!queryStratum||!queryStratum->Resolved @@ -1746,90 +1707,6 @@ public: departureTailDistance) ||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(chamber.floorCenterSubvoxels[0])/qPerMetre; - const double cy=static_cast(chamber.floorCenterSubvoxels[1])/qPerMetre; - const double floorZ=static_cast(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(chamber.radiusSubvoxels)/qPerMetre; - const double floorDistance=floorZ-queryZ; - const double ceilingDistance=queryZ-(floorZ - +static_cast(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 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(passage.upperMouthSubvoxels[2]) - /qPerMetre; - std::size_t endpoint=route.size(); - double endpointDistanceSquared=1.0; - for(std::size_t index=0;indexvalues[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 routeDistance(route.size(),0.0); - for(std::size_t index=1;indexvalues[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;indexPortalWalkLeadMetres)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 &&nativeTunnelFieldDensity!=std::numeric_limits::lowest() &&nativeTunnelFieldDensity<0.0f) @@ -1893,13 +1770,95 @@ public: return std::max(density,passageAirDistance); if(unionDistance>0.0&&unionShellDistance<0.0&&density>=0.0f) { - if(connectedSurfaceLandingShellContains()) - return std::max(density,passageAirDistance); return static_cast(unionShellDistance/Q); } return std::max(density,passageAirDistance); } + float ApplyResolvedLandingPadStamps(float density,float X,float Y,float Z, + float tubeOnlyDistance) const + { + if(!ResolvedLayout||ResolvedLayout->passages.empty()||!std::isfinite(X) + ||!std::isfinite(Y)||!std::isfinite(Z))return density; + const auto* bound=FindBoundStratumForManagerZ(Z); + if(!bound||!bound->Resolved)return density; + const double coreMetres=std::max(1, + ResolvedLayout->coreVoxelSizeMicrometres)/1000000.0; + constexpr double Q=static_cast( + VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel); + const double qPerMetre=Q/coreMetres; + const double xMetres=static_cast(X)*coreMetres; + const double yMetres=static_cast(Y)*coreMetres; + const double zMetres=static_cast(Z)*coreMetres; + const double tubeDistanceMetres=static_cast(tubeOnlyDistance)*coreMetres; + const auto* tileQueries=FScopedPassageTileQueries::CurrentFor(*this); + const auto* candidatePassages=tileQueries + ?&tileQueries->routePassageIndices():nullptr; + const std::size_t candidateCount=candidatePassages + ?candidatePassages->size():ResolvedLayout->passages.size(); + float result=density; + constexpr double CosTwelveDegrees=0.9781476007338057; + constexpr double OuterReachMetres=VF_PassageInfluenceReachMetres; + for(std::size_t offset=0;offset=ResolvedLayout->passages.size())continue; + const auto& passage=ResolvedLayout->passages[passageIndex]; + const auto applyPad=[&]( + const VoxelForgeCore::ResolvedLayoutPassageLandingChamberV1& pad) + { + if(pad.radiusSubvoxels<=0)return; + const double centerX=static_cast(pad.floorCenterSubvoxels[0]) + /qPerMetre; + const double centerY=static_cast(pad.floorCenterSubvoxels[1]) + /qPerMetre; + const double floorP=static_cast(pad.floorCenterSubvoxels[2]) + /qPerMetre; + const double radius=std::max(3.0, + static_cast(std::max(0, + passage.mouthRadiusSubvoxels))/qPerMetre+1.0); + const double dx=xMetres-centerX,dy=yMetres-centerY; + const double radial=DeterministicSqrt(dx*dx+dy*dy); + const double outerRadius=radius+VF_LandingPadBlendRadiusMetres; + if(radial>outerRadius+OuterReachMetres + ||zMetresfloorP+VF_LandingPadHeadroomMetres + +VF_LandingPadConeSlope*VF_LandingPadBlendRadiusMetres + +OuterReachMetres)return; + const double coneRun=std::max(0.0,radial-radius); + const double lower=floorP-VF_LandingPadConeSlope*coneRun; + const double upper=floorP+VF_LandingPadConeSlope*coneRun; + const double fillDistanceMetres=std::max({ + CosTwelveDegrees*(zMetres-lower), + CosTwelveDegrees*(lower-VF_LandingPadFillDepthMetres-zMetres), + radial-outerRadius}); + const double airDistanceMetres=std::min({ + CosTwelveDegrees*(zMetres-upper), + CosTwelveDegrees*(upper+VF_LandingPadHeadroomMetres-zMetres), + outerRadius-radial}); + const double fillVoxels=fillDistanceMetres/coreMetres; + const double airVoxels=airDistanceMetres/coreMetres; + const double maskedFill=std::max(fillVoxels,-tubeDistanceMetres/coreMetres); + const double smoothWidthMetres=VF_LandingPadSmoothMinMaximumMetres + *std::clamp(radial-radius,0.0,1.0); + const float next=static_cast(result); + const float fill=static_cast(maskedFill); + const float width=static_cast(smoothWidthMetres/coreMetres); + const float clamped=width>0.0f + ?SmoothMinDistance(next,fill,width):std::min(next,fill); + result=std::max(clamped,static_cast(airVoxels)); + }; + if(passage.upperStrateIndex==bound->StrateIndex) + applyPad(passage.upperLandingChamber); + if(passage.lowerStrateIndex==bound->StrateIndex) + applyPad(passage.lowerLandingChamber); + } + return result; + } + float ApplyResolvedStrateComposition(float density,float X,float Y,float Z) const { float graphDensity=0.0f; @@ -1930,6 +1889,7 @@ public: } float composed=boundary; float stamped=composed; + float tubeOnlyDistance=std::numeric_limits::infinity(); if(!StampRuntime.empty()) { VoxelForgeCore::Linux77Profiler::FScopedPhase l77StampPhase( @@ -1963,12 +1923,13 @@ public: const float nativeTunnelFieldDensity=nativeTunnelField ?graphDensity:std::numeric_limits::lowest(); stamped=ApplyResolvedStratePassageCarves(stamped,X,Y,Z, - nativeTunnelFieldDensity,nativeGraphFloorSupport); + nativeTunnelFieldDensity,nativeGraphFloorSupport, + &tubeOnlyDistance); } if(bTraceTnComposition) std::printf("[L75TNCompositionTrace] input=%.9g graph=%.9g boundary=%.9g passage=%.9g stamp=%.9g native=1\n", density,graphDensity,boundary,composed,stamped); - return stamped; + return ApplyResolvedLandingPadStamps(stamped,X,Y,Z,tubeOnlyDistance); } // The legacy landmark demo retains its compatibility floor cap. Native caves // already combine each floor with its wall before the single void carve above. @@ -1985,12 +1946,14 @@ public: VoxelForgeCore::Linux77Profiler::EPhase::Passage); FScopedLegacyFieldTermTimer TermTimer( ELegacyFieldTerm::ComposerPassage); - final=ApplyResolvedStratePassageCarves(final,X,Y,Z); + final=ApplyResolvedStratePassageCarves(final,X,Y,Z, + std::numeric_limits::lowest(), + std::numeric_limits::lowest(),&tubeOnlyDistance); } if(bTraceTnComposition) std::printf("[L75TNCompositionTrace] input=%.9g graph=%.9g boundary=%.9g passage=%.9g stamp=%.9g final=%.9g native=0\n", density,graphDensity,boundary,composed,stamped,final); - return final; + return ApplyResolvedLandingPadStamps(final,X,Y,Z,tubeOnlyDistance); } bool ResolvedPassageIntersectsBox(const int32 minimum[3],const int32 maximum[3]) const @@ -2033,26 +1996,35 @@ public: } if(overlaps(low,high))return true; } - const auto chamberOverlaps=[&]( - const VoxelForgeCore::ResolvedLayoutPassageLandingChamberV1& chamber) - { - const double cx=static_cast(chamber.floorCenterSubvoxels[0])/Q; - const double cy=static_cast(chamber.floorCenterSubvoxels[1])/Q; - const double cz=static_cast(chamber.floorCenterSubvoxels[2])/Q; - const double radius=static_cast(chamber.radiusSubvoxels)/Q; - const double floorExtent=radius*std::hypot( - static_cast(chamber.floorGradientQ16[0])/65536.0, - static_cast(chamber.floorGradientQ16[1])/65536.0); + const auto padOverlaps=[&]( + const VoxelForgeCore::ResolvedLayoutPassageLandingChamberV1& pad, + std::int64_t mouthRadiusSubvoxels) + { + if(pad.radiusSubvoxels<=0)return false; + const double cx=static_cast(pad.floorCenterSubvoxels[0])/Q; + const double cy=static_cast(pad.floorCenterSubvoxels[1])/Q; + const double pz=static_cast(pad.floorCenterSubvoxels[2])/Q; + const double qPerMetre=Q*1000000.0/ + std::max(1,ResolvedLayout->coreVoxelSizeMicrometres); + const double coreMetres=Q/qPerMetre; + const double radius=std::max(3.0, + static_cast(std::max(0,mouthRadiusSubvoxels)) + /qPerMetre+1.0); + const double outerRadius=radius+VF_LandingPadBlendRadiusMetres; 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( - chamber.clearHeightSubvoxels)/Q+extent}; + const double coneHeight=VF_LandingPadConeSlope + *VF_LandingPadBlendRadiusMetres; + const double low[3]={cx-outerRadius/coreMetres-extent, + cy-outerRadius/coreMetres-extent, + pz-(VF_LandingPadFillDepthMetres+coneHeight)/coreMetres-extent}; + const double high[3]={cx+outerRadius/coreMetres+extent, + cy+outerRadius/coreMetres+extent, + pz+(VF_LandingPadHeadroomMetres+coneHeight)/coreMetres+extent}; return overlaps(low,high); }; - if(chamberOverlaps(passage.upperLandingChamber) - ||chamberOverlaps(passage.lowerLandingChamber))return true; + if(padOverlaps(passage.upperLandingChamber,passage.mouthRadiusSubvoxels) + ||padOverlaps(passage.lowerLandingChamber,passage.mouthRadiusSubvoxels)) + return true; } return false; } @@ -5415,6 +5387,9 @@ private: if(!ResolvedLayout||ResolvedLayout->passages.empty())return; constexpr double Q=static_cast(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel); constexpr std::size_t CandidateFineSamplesPerSpan=5; + const double coreMetres=std::max(1, + ResolvedLayout->coreVoxelSizeMicrometres)/1000000.0; + const double qPerMetre=Q/coreMetres; const double requiredReachCoreVoxels=VF_PassageInfluenceReachCoreVoxels( ResolvedLayout->coreVoxelSizeMicrometres); for(std::size_t passageIndex=0;passageIndexpassages.size(); @@ -5471,40 +5446,47 @@ private: std::ceil((localMaximum[axis]+localExtent)*Q)); } } - const auto includeLandingChamber=[&]( - const VoxelForgeCore::ResolvedLayoutPassageLandingChamberV1& chamber) - { - const double cx=static_cast(chamber.floorCenterSubvoxels[0])/Q; - const double cy=static_cast(chamber.floorCenterSubvoxels[1])/Q; - const double cz=static_cast(chamber.floorCenterSubvoxels[2])/Q; - const double radius=static_cast(chamber.radiusSubvoxels)/Q; - const double gx=static_cast(chamber.floorGradientQ16[0])/65536.0; - const double gy=static_cast(chamber.floorGradientQ16[1])/65536.0; - const double floorExtent=radius*std::hypot(gx,gy); - const double height=static_cast( - chamber.clearHeightSubvoxels)/Q; - const double extent=requiredReachCoreVoxels+0.01; - const std::array minimum{ - cx-radius-extent,cy-radius-extent,cz-floorExtent-extent}; - const std::array maximum{ - cx+radius+extent,cy+radius+extent, - cz+floorExtent+height+extent}; + const auto includeLandingPad=[&]( + const VoxelForgeCore::ResolvedLayoutPassageLandingChamberV1& pad, + std::int64_t mouthRadiusSubvoxels) + { + if(pad.radiusSubvoxels<=0)return; + const double cx=static_cast(pad.floorCenterSubvoxels[0])/Q; + const double cy=static_cast(pad.floorCenterSubvoxels[1])/Q; + const double pz=static_cast(pad.floorCenterSubvoxels[2])/Q; + const double radius=std::max(3.0, + static_cast(std::max(0,mouthRadiusSubvoxels)) + /qPerMetre+1.0); + const double outerRadius=radius+VF_LandingPadBlendRadiusMetres; + const double coneHeight=VF_LandingPadConeSlope + *VF_LandingPadBlendRadiusMetres; + const std::array baseMinimum{ + cx-outerRadius/coreMetres, + cy-outerRadius/coreMetres, + pz-(VF_LandingPadFillDepthMetres+coneHeight)/coreMetres}; + const std::array baseMaximum{ + cx+outerRadius/coreMetres, + cy+outerRadius/coreMetres, + pz+(VF_LandingPadHeadroomMetres+coneHeight)/coreMetres}; FPassageProofBound proof; + const double extent=requiredReachCoreVoxels+0.01; 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]; + routeMinimum[axis]=std::min(routeMinimum[axis],baseMinimum[axis]); + routeMaximum[axis]=std::max(routeMaximum[axis],baseMaximum[axis]); + proof.minimum[axis]=baseMinimum[axis]-extent; + proof.maximum[axis]=baseMaximum[axis]+extent; } PassageProofBounds.push_back(proof); }; if(segmentIndex==0) - includeLandingChamber( - ResolvedLayout->passages[passageIndex].upperLandingChamber); + includeLandingPad(ResolvedLayout->passages[passageIndex] + .upperLandingChamber,ResolvedLayout->passages[passageIndex] + .mouthRadiusSubvoxels); if(segmentEndIndex==segmentCount) - includeLandingChamber( - ResolvedLayout->passages[passageIndex].lowerLandingChamber); + includeLandingPad(ResolvedLayout->passages[passageIndex] + .lowerLandingChamber,ResolvedLayout->passages[passageIndex] + .mouthRadiusSubvoxels); const double routeExtent=maximumRadiusSubvoxels/Q +requiredReachCoreVoxels+0.01; const double tileExtent=routeExtent; @@ -5629,6 +5611,108 @@ private: } } } + for(std::size_t passageIndex=0; + passageIndexpassages.size();++passageIndex) + { + const auto& passage=ResolvedLayout->passages[passageIndex]; + const auto sampleLandingPad=[&]( + const VoxelForgeCore::ResolvedLayoutPassageLandingChamberV1& pad) + { + if(pad.radiusSubvoxels<=0)return; + const double centerX=static_cast( + pad.floorCenterSubvoxels[0])/qPerMetre; + const double centerY=static_cast( + pad.floorCenterSubvoxels[1])/qPerMetre; + const double floorP=static_cast( + pad.floorCenterSubvoxels[2])/qPerMetre; + const double radius=std::max(3.0, + static_cast(std::max(0, + passage.mouthRadiusSubvoxels))/qPerMetre+1.0); + const double probeRadius=radius+VF_LandingPadBlendRadiusMetres-0.25; + const std::array probeHeights{ + floorP-0.5*VF_LandingPadFillDepthMetres, + floorP+0.5*VF_LandingPadHeadroomMetres}; + for(const double probeZ:probeHeights) + { + const double sampleQ[3]={centerX*qPerMetre, + centerY*qPerMetre,probeZ*qPerMetre}; + const double sampleVoxel[3]={sampleQ[0]/Q, + sampleQ[1]/Q,sampleQ[2]/Q}; + const float contribution=ApplyResolvedLandingPadStamps( + 0.0f,static_cast(sampleVoxel[0]), + static_cast(sampleVoxel[1]), + static_cast(sampleVoxel[2]), + std::numeric_limits::infinity()); + ++sampled; + if(std::abs(contribution)<=1.0e-6f)continue; + ++signRelevant; + if(PassageProofNodes.empty() + ||!PassageProofBoundsOverlapNode(0,sampleVoxel,sampleVoxel)) + ++proofMisses; + std::array sampleMinimum{},sampleMaximum{}; + for(std::size_t axis=0;axis<3;++axis) + { + sampleMinimum[axis]=static_cast( + std::floor(sampleQ[axis])); + sampleMaximum[axis]=static_cast( + std::ceil(sampleQ[axis])); + } + const auto candidates=BuildPassageFeatureTileList( + sampleMinimum,sampleMaximum); + const bool featureContainsPassage=std::any_of( + candidates.begin(),candidates.end(), + [&](std::uint32_t featureIndex) + { + return featureIndex(edgeSampleVoxel[0]), + static_cast(edgeSampleVoxel[1]), + static_cast(edgeSampleVoxel[2]), + std::numeric_limits::infinity()); + ++sampled; + if(std::abs(edgeContribution)<=1.0e-6f)continue; + ++signRelevant; + if(PassageProofNodes.empty() + ||!PassageProofBoundsOverlapNode(0,edgeSampleVoxel,edgeSampleVoxel)) + ++proofMisses; + for(std::size_t axis=0;axis<3;++axis) + { + sampleMinimum[axis]=static_cast( + std::floor(edgeSampleQ[axis])); + sampleMaximum[axis]=static_cast( + std::ceil(edgeSampleQ[axis])); + } + const auto edgeCandidates=BuildPassageFeatureTileList( + sampleMinimum,sampleMaximum); + const bool edgeFeatureContainsPassage=std::any_of( + edgeCandidates.begin(),edgeCandidates.end(), + [&](std::uint32_t featureIndex) + { + return featureIndex0&&signRelevant>0 &&proofMisses==0&&featureMisses==0; std::printf("[L130PassageReachProofAudit] reach_m=%.3f sampled=%llu sign_relevant=%llu proof_misses=%llu feature_misses=%llu status=%s\n", diff --git a/Standalone/Source/StreamBenchmarkPipeline.h b/Standalone/Source/StreamBenchmarkPipeline.h index 2fcf4b5..1b502ed 100644 --- a/Standalone/Source/StreamBenchmarkPipeline.h +++ b/Standalone/Source/StreamBenchmarkPipeline.h @@ -4703,78 +4703,6 @@ namespace StandaloneStreamBenchmark [index](const auto& s){return s.strateIndex==index;}); 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 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;indexvalues[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 routeDistance(route.size(),0.0); - for(std::size_t index=1;indexvalues[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;index9.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(); VoxelForgeCore::World blockedWorld(controlSnapshot); std::string blockedCompileError; @@ -5153,89 +5081,6 @@ namespace StandaloneStreamBenchmark if(interior<=0.0f)++audit.interiorViolations; } } - constexpr std::array chamberFloorRadii{{0.0,2.5,5.0}}; - constexpr std::array 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(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(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::max(), - std::numeric_limits::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; }; const auto mouthInContent=[&](std::int32_t index, @@ -5305,10 +5150,257 @@ namespace StandaloneStreamBenchmark } return total; }; + struct FLandingPadAudit + { + std::uint64_t flatSamples=0,flatFailures=0; + std::uint64_t supportSamples=0,supportFailures=0; + std::uint64_t headroomSamples=0,headroomFailures=0; + std::uint64_t blendRootSamples=0,blendMissing=0,blendGradeFailures=0; + std::uint64_t negativeControlsDetected=0; + double maximumFlatnessMetres=0.0,maximumGradeDegrees=0.0; + bool passed=false; + }; + const auto auditLandingPad=[&](const VoxelForgeCore::World& field, + const VoxelForgeCore::ResolvedLayoutPassageV1& passage,bool upper) + { + constexpr double ConeSlope=0.2125565616700221; + constexpr double CosTwelveDegrees=0.9781476007338057; + constexpr double FillDepthMetres=1.25,HeadroomMetres=3.0; + constexpr double BlendRadiusMetres=8.0; + constexpr double FlatnessLimitMetres=0.020; + constexpr double GradeLimitDegrees=15.0; + constexpr std::size_t RadialDirections=36; + constexpr double FloorSearchStepMetres=0.125; + FLandingPadAudit audit; + const auto& pad=upper?passage.upperLandingChamber + :passage.lowerLandingChamber; + if(pad.radiusSubvoxels<=0)return audit; + const double centerX=qToMetres(pad.floorCenterSubvoxels[0]); + const double centerY=qToMetres(pad.floorCenterSubvoxels[1]); + const double floorP=qToMetres(pad.floorCenterSubvoxels[2]); + const double radius=std::max(3.0, + qToMetres(std::max(0,passage.mouthRadiusSubvoxels))+1.0); + const auto densityMetres=[&](const VoxelForgeCore::World& source, + double x,double y,double z) + { + const float value=source.densityAt({x/coreMetres,y/coreMetres, + z/coreMetres}); + return std::isfinite(value)?static_cast(value)*coreMetres + :std::numeric_limits::quiet_NaN(); + }; + struct FFloorRoot + {double z=0.0;std::uint32_t crossings=0;bool found=false;}; + const auto findFloorRoot=[&](auto&& sampleDensity,double x,double y, + double low,double high) + { + FFloorRoot result; + double previousZ=low; + double previous=sampleDensity(x,y,previousZ); + if(!std::isfinite(previous))return result; + for(double z=low+FloorSearchStepMetres; + z=0.0) + { + const double denominator=current-previous; + const double t=std::abs(denominator)>1.0e-12 + ?std::clamp(-previous/denominator,0.0,1.0):0.0; + const double root=std::lerp(previousZ,currentZ,t); + ++result.crossings; + if(!result.found)result.z=root; + result.found=true; + } + previousZ=currentZ; + previous=current; + if(currentZ>=high)break; + } + return result; + }; + const auto fieldDensity=[&](double x,double y,double z) + {return densityMetres(field,x,y,z);}; + const auto sourceFieldDensity=[&](double x,double y,double z) + { + const float value=Shape.passageSourceDensityAt({x/coreMetres, + y/coreMetres,z/coreMetres}); + return std::isfinite(value)?static_cast(value)*coreMetres + :std::numeric_limits::quiet_NaN(); + }; + struct FFlatProbe {double x=0.0,y=0.0,z=0.0;}; + std::vector flatProbes; + const auto probeFlatness=[&](double radial,double angle) + { + const double x=centerX+radial*std::cos(angle); + const double y=centerY+radial*std::sin(angle); + const auto root=findFloorRoot(fieldDensity,x,y, + floorP-0.75,floorP+0.75); + ++audit.flatSamples; + if(!root.found||root.crossings!=1) + {++audit.flatFailures;return;} + const double error=std::abs(root.z-floorP); + audit.maximumFlatnessMetres=std::max( + audit.maximumFlatnessMetres,error); + if(error>FlatnessLimitMetres)++audit.flatFailures; + flatProbes.push_back({x,y,root.z}); + }; + probeFlatness(0.0,0.0); + for(std::size_t direction=0;direction(direction)/RadialDirections; + probeFlatness(0.90*radius,angle); + } + constexpr std::array SupportRadii{{0.0,0.5,0.9}}; + for(const double radialFraction:SupportRadii) + for(std::size_t direction=0;direction(direction)/RadialDirections; + const double x=centerX+radialFraction*radius*std::cos(angle); + const double y=centerY+radialFraction*radius*std::sin(angle); + ++audit.supportSamples; + if(!(fieldDensity(x,y,floorP-0.10)<0.0)) + ++audit.supportFailures; + ++audit.headroomSamples; + if(!(fieldDensity(x,y,floorP+2.0)>0.0)) + ++audit.headroomFailures; + } + constexpr std::size_t BlendStations=16; + const double blendEnd=BlendRadiusMetres-0.25; + for(std::size_t direction=0;direction(direction)/RadialDirections; + bool previousFound=false; + double previousRadius=0.0,previousFloor=0.0; + for(std::size_t station=0;station<=BlendStations;++station) + { + const double radial=radius+blendEnd + *static_cast(station)/BlendStations; + const double x=centerX+radial*std::cos(angle); + const double y=centerY+radial*std::sin(angle); + const double coneRun=std::max(0.0,radial-radius); + const double coneHeight=ConeSlope*coneRun; + const auto root=findFloorRoot(fieldDensity,x,y, + floorP-coneHeight-0.75,floorP+coneHeight+0.75); + ++audit.blendRootSamples; + if(!root.found||root.crossings!=1) + { + ++audit.blendMissing; + previousFound=false; + continue; + } + if(previousFound) + { + const double grade=std::atan2( + std::abs(root.z-previousFloor),radial-previousRadius) + *180.0/3.14159265358979323846; + audit.maximumGradeDegrees=std::max( + audit.maximumGradeDegrees,grade); + if(grade>GradeLimitDegrees)++audit.blendGradeFailures; + } + previousFound=true; + previousRadius=radial; + previousFloor=root.z; + } + } + const auto sourceField=[&](double x,double y,double z) + {return sourceFieldDensity(x,y,z);}; + bool padOffRejected=false; + for(std::size_t direction=0;direction(direction)/RadialDirections; + const double x=centerX+0.90*radius*std::cos(angle); + const double y=centerY+0.90*radius*std::sin(angle); + const auto root=findFloorRoot(sourceField,x,y, + floorP-0.75,floorP+0.75); + if(!root.found||root.crossings!=1 + ||std::abs(root.z-floorP)>FlatnessLimitMetres) + {padOffRejected=true;break;} + } + const bool tiltRejected=std::any_of(flatProbes.begin(),flatProbes.end(), + [&](const FFlatProbe& probe) + { + return std::abs((probe.z-floorP) + +0.02*(probe.x-centerX))>FlatnessLimitMetres; + }); + const auto controlStamp=[&](double blendRadius,double radial,double z, + double native,double tubeDistance) + { + const double coneRun=std::max(0.0,radial-radius); + const double lower=floorP-ConeSlope*coneRun; + const double upper=floorP+ConeSlope*coneRun; + const double outer=radius+blendRadius; + const double fill=std::max({CosTwelveDegrees*(z-lower), + CosTwelveDegrees*(lower-FillDepthMetres-z),radial-outer}); + const double air=std::min({CosTwelveDegrees*(z-upper), + CosTwelveDegrees*(upper+HeadroomMetres-z),outer-radial}); + const double masked=std::max(fill,-tubeDistance); + const double smoothWidth=0.5*std::clamp(radial-radius,0.0,1.0); + double smoothMinimum=std::min(native,masked); + if(smoothWidth>0.0) + { + const double h=std::max(smoothWidth + -std::abs(native-masked),0.0)/smoothWidth; + smoothMinimum-=h*h*h*(smoothWidth/6.0); + } + return std::max(smoothMinimum,air); + }; + const double outerProbe=radius+BlendRadiusMetres-0.25; + const double outerProbeZ=floorP-ConeSlope*(outerProbe-radius)-0.05; + const double fullBlendProbe=controlStamp(BlendRadiusMetres, + outerProbe,outerProbeZ,0.0,std::numeric_limits::infinity()); + const double halfBlendProbe=controlStamp(BlendRadiusMetres*0.5, + outerProbe,outerProbeZ,0.0,std::numeric_limits::infinity()); + const bool halfBlendRejected=fullBlendProbe< -0.01 + &&std::abs(halfBlendProbe)<1.0e-6; + const double boreMasked=controlStamp(BlendRadiusMetres, + radius*0.5,floorP-0.25,0.25,-0.50); + const double boreUnmasked=controlStamp(BlendRadiusMetres, + radius*0.5,floorP-0.25,0.25, + std::numeric_limits::infinity()); + const bool boreMaskRejected=boreMasked>0.0&&boreUnmasked<0.0; + audit.negativeControlsDetected=static_cast(padOffRejected) + +static_cast(tiltRejected) + +static_cast(halfBlendRejected) + +static_cast(boreMaskRejected); + audit.passed=audit.flatSamples>0&&audit.flatFailures==0 + &&audit.supportSamples>0&&audit.supportFailures==0 + &&audit.headroomSamples>0&&audit.headroomFailures==0 + &&audit.blendRootSamples>0&&audit.blendMissing==0 + &&audit.blendGradeFailures==0 + &&audit.negativeControlsDetected==4; + std::printf("[LandingPadAudit] id=%s side=%s radius_m=%.3f blend_m=%.3f flatness_samples=%llu flatness_max_m=%.5f flatness_failures=%llu support=%llu/%llu headroom=%llu/%llu blend_transects=%zu blend_roots=%llu blend_missing=%llu blend_grade_max_deg=%.3f blend_grade_failures=%llu negative_controls=%llu/4 pad_off=%s tilt_2pct=%s blend_B_half=%s bore_mask_removed=%s status=%s\n", + passage.stableId.c_str(),upper?"upper":"lower",radius, + BlendRadiusMetres,static_cast(audit.flatSamples), + audit.maximumFlatnessMetres, + static_cast(audit.flatFailures), + static_cast(audit.supportSamples + -audit.supportFailures), + static_cast(audit.supportSamples), + static_cast(audit.headroomSamples + -audit.headroomFailures), + static_cast(audit.headroomSamples), + RadialDirections, + static_cast(audit.blendRootSamples), + static_cast(audit.blendMissing), + audit.maximumGradeDegrees, + static_cast(audit.blendGradeFailures), + static_cast(audit.negativeControlsDetected), + padOffRejected?"detected":"MISSED",tiltRejected?"detected":"MISSED", + halfBlendRejected?"detected":"MISSED", + boreMaskRejected?"detected":"MISSED",audit.passed?"PASS":"FAIL"); + return audit; + }; std::uint64_t passageCount=0,passagePasses=0,blockedControls=0,naturalPasses=0; std::uint64_t passageInteriorCapsuleSamples=0,passageInteriorSolidSamples=0; std::uint64_t passageInteriorControls=0; std::uint64_t landingChamberCount=0,landingChamberNegativeControls=0; + std::uint64_t landingPadCount=0,landingPadPasses=0,landingPadControls=0; std::uint64_t mouthProfilePasses=0,ripplePasses=0,rippleControlsDetected=0; std::uint64_t floorRippleControlsDetected=0,floorStepRippleControlsDetected=0; std::uint64_t shellAudits=0,shellFloorSamples=0,shellFloorViolations=0; @@ -5346,6 +5438,13 @@ namespace StandaloneStreamBenchmark passage,passageIndex,true); const FWalkAudit lowerChamberAudit=auditLandingChamber(Shape, passage,passageIndex,false); + const FLandingPadAudit upperPadAudit=auditLandingPad(Shape,passage,true); + const FLandingPadAudit lowerPadAudit=auditLandingPad(Shape,passage,false); + landingPadCount+=2; + landingPadPasses+=static_cast(upperPadAudit.passed) + +static_cast(lowerPadAudit.passed); + landingPadControls+=upperPadAudit.negativeControlsDetected + +lowerPadAudit.negativeControlsDetected; const bool landingChambersWalkable=upperChamberAudit.passed &&lowerChamberAudit.passed; const bool passageInteriorAirPassed= @@ -5426,6 +5525,7 @@ namespace StandaloneStreamBenchmark floorRippleControlsDetected+=rippleAudit.floorControlDetected; floorStepRippleControlsDetected+=rippleAudit.floorStepControlDetected; passagePasses+=passageAudit.passed&&landingChambersWalkable + &&upperPadAudit.passed&&lowerPadAudit.passed &&passageInteriorAirPassed &&mouthsInRadius&&mouthsInContent &&naturalAudit.passed&&mouthProfileAudit.passed&&rippleAudit.passed; @@ -5889,6 +5989,13 @@ namespace StandaloneStreamBenchmark static_cast(landingChamberCount), static_cast(landingChamberCount -landingChamberNegativeControls)); + std::printf("[LandingPadAuditSummary] pads=%llu passed=%llu negative_controls=%llu/%llu status=%s\n", + static_cast(landingPadCount), + static_cast(landingPadPasses), + static_cast(landingPadControls), + static_cast(landingPadCount*4), + landingPadCount>0&&landingPadPasses==landingPadCount + &&landingPadControls==landingPadCount*4?"PASS":"FAIL"); const bool shellAuditPassed=shellAudits==passageCount &&shellFloorViolations==0&&shellWallViolations==0 &&shellChamberFloorViolations==0&&shellChamberWallViolations==0 @@ -5935,6 +6042,8 @@ namespace StandaloneStreamBenchmark ripplePasses==passageCount?"PASS":"FAIL"); const bool auditPassed=passageCount>0&&passagePasses==passageCount &&passageInteriorAuditPassed + &&landingPadCount>0&&landingPadPasses==landingPadCount + &&landingPadControls==landingPadCount*4 &&naturalPasses==passageCount&&l23SmoothWallDetected &&mouthProfilePasses==passageCount&&l23TerraceControlDetected &&ripplePasses==passageCount