streaming: deeper ready queue + startup apply cap; tile-local mouth-support culling (L72)

- Unreal: ready queue depth 8 -> 16 (cap 4 worker-sets / 64), startup mesh-apply cap
  12 -> 24 within the unchanged 14 ms budget. Owner SW arrival, same 2,850 keys:
  initial set ready 4.78 -> 3.74 s, queue p95 123 -> 35 ms, request-to-apply p95
  139 -> 45 ms; walkable gate 1.07 -> 1.11 s; per-frame apply p95 0.73 ms.
- Core composer: mouth-support segments filtered by tile bounds, mouth relief result reused
  (route checks -80/-87% in passage tiles). Hashes identical W1/W8/W12, Clang and GCC.
- Opt-in per-tile composer counters. Verify control meshes stored as .xz (78.5 -> 18.4 MB).
- Fast tier 269 PASS.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
This commit is contained in:
Fr0zkaandClaude Opus 5.5 committed 2026-10-27 08:25:14 +01:00
1 parent 971206c2ea
commit d970d75be8
26 files changed
+383 -82

No files matched your search

+201 -60
View File
@@ -172,11 +172,19 @@ public:
class FScopedPassageTileQueries
{
public:
struct FCandidateSegment
{
std::size_t segmentIndex=0;
std::array<std::int64_t,3> minimumSubvoxels{},maximumSubvoxels{};
};
FScopedPassageTileQueries(const FStandaloneShapeWorld& shape,
const std::array<std::int64_t,3>& minimumSubvoxels,
const std::array<std::int64_t,3>& maximumSubvoxels)
const std::array<std::int64_t,3>& maximumSubvoxels,
VoxelForgeCore::TileGenerationStats* profileStats=nullptr)
:shape_(&shape),minimumSubvoxels_(minimumSubvoxels),
maximumSubvoxels_(maximumSubvoxels),previous_(ActivePassageTileQueries)
maximumSubvoxels_(maximumSubvoxels),previous_(ActivePassageTileQueries),
profileStats_(profileStats)
{
mayContainResolvedBoundaryBand_=shape.ResolvedBoundaryBandsIntersectSubvoxelRange(
minimumSubvoxels_[2],maximumSubvoxels_[2]);
@@ -190,6 +198,7 @@ public:
passageRoutes_.assign(shape.ResolvedLayout?shape.ResolvedLayout->passages.size():0,0);
passageColumns_=passageRoutes_;
passageMouthSupports_=passageRoutes_;
passageMouthSupportSegments_.resize(passageRoutes_.size());
for(const auto& crossing:shape.PassageVoidCrossings)
if(crossing.needsColumn
&&crossing.bottomZSubvoxels<=maximumSubvoxels_[2]
@@ -200,14 +209,27 @@ public:
if(featureIndex>=shape.PassageTileFeatures.size())continue;
const auto& feature=shape.PassageTileFeatures[featureIndex];
if(feature.passageIndex>=passageRoutes_.size())continue;
const auto& featureBounds=shape.PassageFeatureBounds[featureIndex];
const FCandidateSegment candidate{feature.segmentIndex,
featureBounds.minimumSubvoxels,featureBounds.maximumSubvoxels};
if(feature.bColumn)passageColumns_[feature.passageIndex]=1;
else if(feature.bMouthSupport)passageMouthSupports_[feature.passageIndex]=1;
else if(feature.bMouthSupport)
{
passageMouthSupports_[feature.passageIndex]=1;
passageMouthSupportSegments_[feature.passageIndex].push_back(candidate);
}
else
{
passageRoutes_[feature.passageIndex]=1;
++routeSegmentCandidateCount_;
routeFeatureOverlap_=true;
}
}
for(auto& values:passageMouthSupportSegments_)
{
std::sort(values.begin(),values.end(),[](const auto& a,const auto& b)
{return a.segmentIndex<b.segmentIndex;});
}
ActivePassageTileQueries=this;
}
~FScopedPassageTileQueries(){ActivePassageTileQueries=previous_;}
@@ -220,6 +242,12 @@ public:
const auto& candidates=columnFeature?passageColumns_:passageRoutes_;
return passageIndex<candidates.size()&&candidates[passageIndex]!=0;
}
const std::vector<FCandidateSegment>& mouthSupportSegments(std::size_t passageIndex) const
{
static const std::vector<FCandidateSegment> Empty;
return passageIndex<passageMouthSupportSegments_.size()
?passageMouthSupportSegments_[passageIndex]:Empty;
}
bool hasMouthSupport(std::size_t passageIndex) const
{return passageIndex<passageMouthSupports_.size()&&passageMouthSupports_[passageIndex]!=0;}
bool hasAnyMouthSupport() const
@@ -235,6 +263,9 @@ public:
&&shape_->ResolvedBoundaryBandMayAffectAtZMicrometres(zMicrometres);
}
std::size_t featureCount() const{return featureIndices_.size();}
std::size_t routeSegmentCandidateCount() const{return routeSegmentCandidateCount_;}
VoxelForgeCore::TileGenerationStats* profileStats() const noexcept
{return profileStats_;}
bool hasStampCandidates() const{return !stampIndices_.empty();}
const std::vector<std::uint32_t>& stampCandidates() const{return stampIndices_;}
bool hasCavernCandidates() const{return !cavernFeatureIndices_.empty();}
@@ -248,10 +279,13 @@ public:
std::vector<std::uint32_t> stampIndices_;
std::vector<std::uint32_t> cavernFeatureIndices_;
std::vector<std::uint8_t> passageRoutes_,passageColumns_,passageMouthSupports_;
std::vector<std::vector<FCandidateSegment>> passageMouthSupportSegments_;
std::size_t routeSegmentCandidateCount_=0;
bool routeFeatureOverlap_=false,columnZOverlap_=false;
bool mayContainResolvedBoundaryBand_=true;
bool mayContainTransitionPassageFade_=true;
const FScopedPassageTileQueries* previous_;
VoxelForgeCore::TileGenerationStats* profileStats_=nullptr;
};
bool IsResolvedPassageColumnSample(std::size_t PassageIndex,std::size_t SegmentIndex,
@@ -953,9 +987,13 @@ public:
const auto* passageTileQueries=ActivePassageTileQueries;
const bool passageTileScopeMatches=passageTileQueries
&&passageTileQueries->matches(*this);
auto* passageProfile=passageTileScopeMatches
?passageTileQueries->profileStats():nullptr;
if(passageProfile)++passageProfile->passageComposerQueries;
bool queryNearUpperMouthSupport=false;
for(const auto& passage:ResolvedLayout->passages)
{
if(passageProfile)++passageProfile->passageUpperMouthFilterChecks;
const double dx=qx-static_cast<double>(passage.upperMouthSubvoxels[0]);
const double dy=qy-static_cast<double>(passage.upperMouthSubvoxels[1]);
const double dz=qz-static_cast<double>(passage.upperMouthSubvoxels[2]);
@@ -972,26 +1010,20 @@ public:
const double playerCenterAboveFloorQ=1.13*qPerMetre;
float composed=density;
float deferredSupportDensity=std::numeric_limits<float>::infinity();
const auto composeRouteMouthSupport=[&](std::int32_t strateIndex,
const std::array<std::int64_t,3>& mouth)
const auto composeRouteMouthSupport=[&](std::size_t passageIndex)
{
// Surface-route treads may reinforce native solid terrain only.
if(!(density<0.0f))return;
if(passageIndex>=ResolvedLayout->passages.size()
||passageIndex>=PassageMouthSupportRoutes.size())return;
const auto& passage=ResolvedLayout->passages[passageIndex];
const auto& mouth=passage.upperMouthSubvoxels;
const double mouthDx=qx-static_cast<double>(mouth[0]);
const double mouthDy=qy-static_cast<double>(mouth[1]);
const double routeSupportReach=13.0*qPerMetre;
if(mouthDx*mouthDx+mouthDy*mouthDy>routeSupportReach*routeSupportReach)return;
const auto stratum=std::find_if(ResolvedLayout->strates.begin(),
ResolvedLayout->strates.end(),[strateIndex](const auto& value)
{return value.strateIndex==strateIndex;});
if(stratum==ResolvedLayout->strates.end())return;
std::vector<const VoxelForgeCore::ResolvedLayoutAnchorV2*> route;
route.reserve(stratum->anchors.size());
for(const auto& anchor:stratum->anchors)
if(anchor.kind=="route_point"&&anchor.values.size()>=3)
route.push_back(&anchor);
if(route.size()<2)return;
const std::size_t lastSegment=route.size()-1;
const auto& route=PassageMouthSupportRoutes[passageIndex];
if(route.empty())return;
constexpr double MouthSupportBlendMetres=0.45;
// Route anchors encode the player center at half the authored capsule
// height above the tread. Keep the support deck below that same foot
@@ -999,18 +1031,21 @@ public:
constexpr double RouteCenterAboveFootMetres=0.88;
const double mouthBlendWidth=MouthSupportBlendMetres*qPerMetre;
double mouthSupportDistance=std::numeric_limits<double>::infinity();
for(std::size_t index=0;index<lastSegment;++index)
const auto evaluateSegment=[&](std::size_t index)
{
const auto& a=*route[index];const auto& b=*route[index+1];
const double dx=static_cast<double>(b.values[0]-a.values[0]);
const double dy=static_cast<double>(b.values[1]-a.values[1]);
if(index>=route.size())return;
if(passageProfile)++passageProfile->passageMouthRouteSegmentChecks;
const auto& segment=route[index];
const double dx=static_cast<double>(segment.b[0]-segment.a[0]);
const double dy=static_cast<double>(segment.b[1]-segment.a[1]);
const double lengthSq=dx*dx+dy*dy;
const double t=lengthSq>0.0?std::clamp(((qx-a.values[0])*dx
+(qy-a.values[1])*dy)/lengthSq,0.0,1.0):0.0;
const double centerX=a.values[0]+t*dx,centerY=a.values[1]+t*dy;
const double centerZ=a.values[2]+t*(static_cast<double>(b.values[2])-a.values[2]);
const double routeRadius=a.values.size()>=4
?a.values[3]+t*(static_cast<double>(b.values[3])-a.values[3]):0.0;
const double t=lengthSq>0.0?std::clamp(((qx-segment.a[0])*dx
+(qy-segment.a[1])*dy)/lengthSq,0.0,1.0):0.0;
const double centerX=segment.a[0]+t*dx,centerY=segment.a[1]+t*dy;
const double centerZ=segment.a[2]
+t*(static_cast<double>(segment.b[2])-segment.a[2]);
const double routeRadius=segment.hasRadius
?segment.a[3]+t*(static_cast<double>(segment.b[3])-segment.a[3]):0.0;
const double halfWidth=std::clamp(routeRadius,
0.42*qPerMetre,0.56*qPerMetre);
const double horizontalX=qx-centerX,horizontalY=qy-centerY;
@@ -1022,12 +1057,29 @@ public:
const double topDistance=qz-floorQ;
const double bottomDistance=floorQ-qPerMetre-qz;
const double verticalDistance=std::max(topDistance,bottomDistance);
if(sideDistance>mouthBlendWidth||verticalDistance>mouthBlendWidth)continue;
if(sideDistance>mouthBlendWidth||verticalDistance>mouthBlendWidth)return;
const double outside=DeterministicSqrt(
std::max(sideDistance,0.0)*std::max(sideDistance,0.0)
+std::max(verticalDistance,0.0)*std::max(verticalDistance,0.0));
const double inside=std::min(std::max(sideDistance,verticalDistance),0.0);
mouthSupportDistance=std::min(mouthSupportDistance,outside+inside);
};
if(passageTileScopeMatches)
{
for(const auto& candidate:passageTileQueries->mouthSupportSegments(passageIndex))
{
const bool containsPoint=qx>=candidate.minimumSubvoxels[0]
&&qx<=candidate.maximumSubvoxels[0]
&&qy>=candidate.minimumSubvoxels[1]
&&qy<=candidate.maximumSubvoxels[1]
&&qz>=candidate.minimumSubvoxels[2]
&&qz<=candidate.maximumSubvoxels[2];
if(containsPoint)evaluateSegment(candidate.segmentIndex);
}
}
else
{
for(std::size_t index=0;index<route.size();++index)evaluateSegment(index);
}
if(std::isfinite(mouthSupportDistance))
composed=SmoothMinDistance(composed,
@@ -1037,6 +1089,7 @@ public:
// The connected passage tread continues the surface route into the tunnel.
for(std::size_t passageIndex=0;passageIndex<ResolvedLayout->passages.size();++passageIndex)
{
if(passageProfile)++passageProfile->passageIterations;
const auto& passage=ResolvedLayout->passages[passageIndex];
if(passage.controlPoints.size()<2)continue;
const auto supportStratum=[&](std::int32_t strateIndex)
@@ -1071,6 +1124,7 @@ public:
const auto laterPassageMayCarve=[&](std::ptrdiff_t supportSegment,
double sampleX,double sampleY,double sampleZ)
{
if(passageProfile)++passageProfile->passageLaterBoreChecks;
return PassageSupportMayBeCarvedByLaterBore(passageIndex,
supportSegment,sampleX,sampleY,sampleZ,0.35*qPerMetre);
};
@@ -1086,6 +1140,7 @@ public:
{
if(!crossing.needsColumn||crossing.passageIndex!=passageIndex
||qz>crossing.topZSubvoxels||qz<crossing.bottomZSubvoxels)continue;
if(passageProfile)++passageProfile->passageColumnCandidates;
#if defined(VF_L30_ENABLE_COST_COUNTERS)
++ThreadClassificationStats().ColumnZOverlapCalls;
#endif
@@ -1120,6 +1175,7 @@ public:
#else
if(horizontalSquared>exteriorLimit*exteriorLimit)continue;
#endif
if(passageProfile)++passageProfile->passageColumnDistanceCalls;
columnDistance=PassageColumnSignedDistance(crossing,X,Y,Z);
}
else
@@ -1127,6 +1183,7 @@ public:
#if defined(VF_L30_ENABLE_COST_COUNTERS)
++ThreadClassificationStats().ColumnSignedDistanceCalls;
#endif
if(passageProfile)++passageProfile->passageColumnDistanceCalls;
columnDistance=PassageColumnSignedDistance(crossing,X,Y,Z);
}
composed=std::min(composed,static_cast<float>(columnDistance/Q));
@@ -1166,12 +1223,16 @@ public:
const std::ptrdiff_t mouthSearchBegin=std::max<std::ptrdiff_t>(0,lowerMouthIndex-8);
const std::ptrdiff_t mouthSearchEnd=std::min<std::ptrdiff_t>(lastIndex,
lowerMouthIndex+8);
if(density<0.0f&&tilePassageFree
&&!PassageMayAffectDensity(passageIndex,carveBegin,carveEnd,
if(density<0.0f&&tilePassageFree)
{
if(passageProfile)++passageProfile->passageNegativeBoundChecks;
if(!PassageMayAffectDensity(passageIndex,carveBegin,carveEnd,
lowerMouthIndex,mouthSearchBegin,mouthSearchEnd,lastIndex,qx,qy,qz,
-static_cast<double>(composed)*Q+0.24*qPerMetre))
{
continue;
{
if(passageProfile)++passageProfile->passageNegativeBoundRejects;
continue;
}
}
// For a positive source sample, the tube can only change it within relief reach;
// the supported column tread is within 2.25 m of its centerline. A short-span
@@ -1181,10 +1242,18 @@ public:
// n'a pas cette borne, donc sa requête complète est conservée.
const bool canCullPositiveRoute=passageTileScopeMatches&&density>=0.0f
&&!PassageHasShortSpanSegments(passageIndex);
if(canCullPositiveRoute&&!PassageMayAffectDensity(passageIndex,carveBegin,carveEnd,
lowerMouthIndex,mouthSearchBegin,mouthSearchEnd,lastIndex,qx,qy,qz,
std::max(2.25*qPerMetre,-static_cast<double>(composed)*Q
+0.24*qPerMetre)))continue;
if(canCullPositiveRoute)
{
if(passageProfile)++passageProfile->passagePositiveBoundChecks;
if(!PassageMayAffectDensity(passageIndex,carveBegin,carveEnd,
lowerMouthIndex,mouthSearchBegin,mouthSearchEnd,lastIndex,qx,qy,qz,
std::max(2.25*qPerMetre,-static_cast<double>(composed)*Q
+0.24*qPerMetre)))
{
if(passageProfile)++passageProfile->passagePositiveBoundRejects;
continue;
}
}
double bestSignedDistance=std::numeric_limits<double>::infinity();
double bestPlayerCapsuleSignedDistance=std::numeric_limits<double>::infinity();
double bestT=0.0;
@@ -1208,10 +1277,12 @@ public:
lowerMouthDx*lowerMouthDx+lowerMouthDy*lowerMouthDy
+lowerMouthDz*lowerMouthDz)/qPerMetre;
const double lowerMouthT=std::clamp((20.0-lowerMouthDistance)/12.0,0.0,1.0);
if(passageProfile)++passageProfile->passageSmoothstepEvaluations;
const double lowerMouthBlend=lowerMouthT*lowerMouthT
*(3.0-2.0*lowerMouthT);
const auto considerCarveSegment=[&](std::size_t segmentIndex,int scanPhase)
{
if(passageProfile)++passageProfile->passageCarveSegmentVisits;
const std::ptrdiff_t index=static_cast<std::ptrdiff_t>(segmentIndex);
const auto& a=passage.controlPoints[static_cast<std::size_t>(index)];
const auto& b=passage.controlPoints[static_cast<std::size_t>(index+1)];
@@ -1249,6 +1320,7 @@ public:
&&!IsPassageShortSpanSegment(passageIndex,segmentIndex+1);
if(smoothCarveJoin)
{
if(passageProfile)++passageProfile->passageSmoothCarveJoins;
const auto& p0=passage.controlPoints[static_cast<std::size_t>(index-1)];
const auto& p3=passage.controlPoints[static_cast<std::size_t>(index+2)];
constexpr double SmoothJoinWeight=1.0;
@@ -1326,12 +1398,11 @@ public:
const float mouthSurfaceRelief=VF_FieldSurfaceRelief(static_cast<float>(X)*1.5f,
static_cast<float>(Y)*1.5f,static_cast<float>(Z)*1.5f,
static_cast<std::uint32_t>(Owner?Owner->Seed:0)^0x4D4F5554u);
const float mouthTerrainRelief=VF_FieldSurfaceRelief(static_cast<float>(X)*1.5f,
static_cast<float>(Y)*1.5f,static_cast<float>(Z)*1.5f,
static_cast<std::uint32_t>(Owner?Owner->Seed:0)^0x4D4F5554u);
const float mouthTerrainRelief=mouthSurfaceRelief;
const float mouthTreadFieldRelief=VF_FieldSurfaceRelief(static_cast<float>(X)*0.75f,
static_cast<float>(Y)*0.75f,static_cast<float>(Z)*0.75f,
static_cast<std::uint32_t>(Owner?Owner->Seed:0)^0x54485244u);
if(passageProfile)passageProfile->passageReliefFieldCalls+=3;
const auto routeSegmentLengthMetres=[&](std::ptrdiff_t routeIndex)
{
const auto& before=passage.controlPoints[static_cast<std::size_t>(routeIndex)];
@@ -1381,6 +1452,7 @@ public:
for(std::ptrdiff_t routeIndex=0;
routeIndex<std::min<std::ptrdiff_t>(3,lowerMouthIndex);++routeIndex)
{
if(passageProfile)++passageProfile->passageMouthRouteSegmentChecks;
const auto& before=passage.controlPoints[static_cast<std::size_t>(routeIndex)];
const auto& after=passage.controlPoints[static_cast<std::size_t>(routeIndex+1)];
const double ax=static_cast<double>(before.positionSubvoxels[0]);
@@ -1402,6 +1474,7 @@ public:
static_cast<float>((az+t*dz)/qPerMetre*1.5),
static_cast<std::uint32_t>(Owner?Owner->Seed:0)^0x4D4F5554u)),
-1.0,1.0);
if(passageProfile)++passageProfile->passageReliefFieldCalls;
const double lateralOffset=pathRelief*0.18*qPerMetre;
projectedX-=dy/horizontalLength*lateralOffset;
projectedY+=dx/horizontalLength*lateralOffset;
@@ -1437,10 +1510,12 @@ public:
if(std::isfinite(distanceFromOpenMouth))
{
const double fade=std::clamp(distanceFromOpenMouth/11.0,0.0,1.0);
if(passageProfile)++passageProfile->passageSmoothstepEvaluations;
const double smooth=fade*fade*(3.0-2.0*fade);
mouthFloorBlend=1.0-smooth;
}
const double floorReliefT=std::clamp((verticalWallAlignment-0.55)/0.45,0.0,1.0);
if(passageProfile)++passageProfile->passageSmoothstepEvaluations;
const double floorReliefWeight=floorReliefT*floorReliefT
*(3.0-2.0*floorReliefT);
const double floorReliefOffset=std::clamp(
@@ -1466,6 +1541,7 @@ public:
constexpr double mouthCarveFadeMetres=8.0;
mouthCarveFade=std::clamp(distanceFromOpenMouth/mouthCarveFadeMetres,
0.0,1.0);
if(passageProfile)++passageProfile->passageSmoothstepEvaluations;
mouthCarveFade=mouthCarveFade*mouthCarveFade*(3.0-2.0*mouthCarveFade);
}
const double floorAlignment=std::max(verticalWallAlignment,
@@ -1477,6 +1553,7 @@ public:
const double belowFloor=(mouthEntryFloorQ-qz)/qPerMetre;
const double supportRamp=std::clamp(belowFloor/0.15,0.0,1.0);
const double supportFade=std::clamp((1.0-belowFloor)/0.35,0.0,1.0);
if(passageProfile)passageProfile->passageSmoothstepEvaluations+=2;
const double rampSmooth=supportRamp*supportRamp*(3.0-2.0*supportRamp);
const double fadeSmooth=supportFade*supportFade*(3.0-2.0*supportFade);
floorBelowWeight=rampSmooth*fadeSmooth;
@@ -1485,6 +1562,7 @@ public:
// former bore-wide alignment feather left a straight platform-width
// bridge across the FI mouth.
double mouthWalkLaneT=std::clamp((1.05-mouthEntryPlanDistance)/0.60,0.0,1.0);
if(passageProfile)++passageProfile->passageSmoothstepEvaluations;
mouthWalkLaneT=mouthWalkLaneT*mouthWalkLaneT*(3.0-2.0*mouthWalkLaneT);
const double mouthWalkLaneWeight=upperMouthFloorFade?mouthWalkLaneT:0.0;
const double floorCarveWeight=1.0-(1.0-mouthCarveFade)
@@ -1496,6 +1574,7 @@ public:
const float composedAfterCarve=composed;
const auto considerSupportSegment=[&](std::ptrdiff_t index)
{
if(passageProfile)++passageProfile->passageSupportSegmentVisits;
const auto& segmentA=passage.controlPoints[static_cast<std::size_t>(index)];
const auto& segmentB=passage.controlPoints[static_cast<std::size_t>(index+1)];
const double dx=static_cast<double>(segmentB.positionSubvoxels[0])-segmentA.positionSubvoxels[0];
@@ -1538,6 +1617,7 @@ public:
static_cast<double>(segmentB.radiusSubvoxels)-segmentA.radiusSubvoxels);
if(smoothSupportJoin)
{
if(passageProfile)++passageProfile->passageSmoothSupportJoins;
const auto& p0=passage.controlPoints[static_cast<std::size_t>(index-1)];
const auto& p3=passage.controlPoints[static_cast<std::size_t>(index+2)];
const double t2=supportT*supportT,t3=t2*supportT;
@@ -1568,6 +1648,7 @@ public:
static_cast<float>(centerZ/qPerMetre*1.5),
static_cast<std::uint32_t>(Owner?Owner->Seed:0)^0x4D4F5554u)),
-1.0,1.0);
if(passageProfile)++passageProfile->passageReliefFieldCalls;
treadRelief=pathRelief;
const double lateralOffset=pathRelief*0.18*qPerMetre;
centerX-=dy/horizontalLength*lateralOffset;
@@ -1660,8 +1741,10 @@ public:
suffixMetres+(1.0-supportT)*supportSegmentLength);
}
double mouthReliefT=std::clamp(distanceFromMouthMetres/8.0,0.0,1.0);
if(passageProfile)++passageProfile->passageSmoothstepEvaluations;
mouthReliefT=mouthReliefT*mouthReliefT*(3.0-2.0*mouthReliefT);
double mouthSupportT=std::clamp((distanceFromMouthMetres-0.25)/4.0,0.0,1.0);
if(passageProfile)++passageProfile->passageSmoothstepEvaluations;
mouthSupportT=mouthSupportT*mouthSupportT*(3.0-2.0*mouthSupportT);
const double mouthSupportWidth=std::lerp(0.82,1.00,mouthSupportT);
const double supportHalfWidthMetres=mouthTread?mouthSupportWidth
@@ -1687,6 +1770,7 @@ public:
?std::clamp((horizontalDistance
-(supportHalfWidth-supportEdgeBlendMetres*qPerMetre))
/(supportEdgeBlendMetres*qPerMetre),0.0,1.0):0.0;
if(passageProfile)++passageProfile->passageSmoothstepEvaluations;
const double supportEdgeSmooth=supportEdgeT*supportEdgeT
*(3.0-2.0*supportEdgeT);
const double mouthSupportThicknessVoxels=std::lerp(
@@ -1700,6 +1784,7 @@ public:
{
const double mouthBlendT=std::clamp(
distanceFromMouthMetres/0.75,0.0,1.0);
if(passageProfile)++passageProfile->passageSmoothstepEvaluations;
mouthSupportBlend=0.75+0.25*mouthBlendT*mouthBlendT
*(3.0-2.0*mouthBlendT);
}
@@ -1802,12 +1887,17 @@ public:
const double capsuleQueryZ=bestSupportUsesSignedDistance?bestSupportFloorQ:qz;
const bool supportCapsuleProfileAvailable=passageIndex<PassageNearestNodes.size()
&&!PassageNearestNodes[passageIndex].empty();
const bool capsuleAllowsSupport=(passageTileScopeMatches
bool capsuleAllowsSupport;
if(passageTileScopeMatches
||(bestSupportUsesSignedDistance&&supportCapsuleProfileAvailable))
?!PassagePlayerCapsuleBlocksSupport(passageIndex,carveBegin,carveEnd,
lowerMouthIndex,mouthSearchBegin,mouthSearchEnd,lastIndex,qx,qy,
capsuleQueryZ,qPerMetre,capsuleClearanceMetres)
:bestPlayerCapsuleSignedDistance>=capsuleClearanceMetres*qPerMetre;
{
if(passageProfile)++passageProfile->passageSupportCapsuleChecks;
capsuleAllowsSupport=!PassagePlayerCapsuleBlocksSupport(passageIndex,
carveBegin,carveEnd,lowerMouthIndex,mouthSearchBegin,mouthSearchEnd,
lastIndex,qx,qy,capsuleQueryZ,qPerMetre,capsuleClearanceMetres);
}
else capsuleAllowsSupport=
bestPlayerCapsuleSignedDistance>=capsuleClearanceMetres*qPerMetre;
bool supportApplied=false;
bool supportNeedsDeferral=false;
// Outside bridge and short-span segments, add the tread only where the
@@ -1826,9 +1916,12 @@ public:
// and noise. The capsule test keeps neighbouring coils open.
const bool nativeRestoreCapsuleClear=density<0.0f
&&bestSupportNativeBlend>0.0&&capsuleAllowsSupport
&&!PassagePlayerCapsuleBlocksSupport(passageIndex,carveBegin,
carveEnd,lowerMouthIndex,mouthSearchBegin,mouthSearchEnd,
lastIndex,qx,qy,qz,qPerMetre,capsuleClearanceMetres);
&&([&](){
if(passageProfile)++passageProfile->passageSupportCapsuleChecks;
return !PassagePlayerCapsuleBlocksSupport(passageIndex,carveBegin,
carveEnd,lowerMouthIndex,mouthSearchBegin,mouthSearchEnd,
lastIndex,qx,qy,qz,qPerMetre,capsuleClearanceMetres);
})();
if(nativeRestoreCapsuleClear)
{
const double nativeSurfaceDetail=std::clamp(
@@ -1851,6 +1944,7 @@ public:
}
if(supportApplied)
{
if(passageProfile)++passageProfile->passageOtherCapsuleChecks;
if(PassageOtherCapsuleBlocksSupport(passageIndex,
bestSupportSegmentIndex,qx,qy,qz,qPerMetre,capsuleClearanceMetres))
{
@@ -1875,8 +1969,10 @@ public:
const bool tileHasSupport=passageTileScopeMatches;
if(!tileHasSupport
||ActivePassageTileQueries->hasMouthSupport(passageIndex))
composeRouteMouthSupport(passage.upperStrateIndex,
passage.upperMouthSubvoxels);
{
if(passageProfile)++passageProfile->passageMouthSupportCalls;
composeRouteMouthSupport(passageIndex);
}
}
composed=std::min(composed,deferredSupportDensity);
return composed;
@@ -3332,6 +3428,11 @@ private:
std::size_t passageIndex=0,segmentIndex=0;
bool bColumn=false,bMouthSupport=false;
};
struct FPassageMouthSupportSegment
{
std::array<std::int64_t,4> a{},b{};
bool hasRadius=false;
};
struct FPassageNearestNode
{
std::array<double,3> minimum{},maximum{};
@@ -4309,6 +4410,8 @@ private:
PassageNearestNodes.clear();
PassageProofBounds.clear();PassageProofNodes.clear();
PassageNearestNodes.resize(ResolvedLayout?ResolvedLayout->passages.size():0);
PassageMouthSupportRoutes.clear();
PassageMouthSupportRoutes.resize(ResolvedLayout?ResolvedLayout->passages.size():0);
PassageLaterBoreSources.clear();
PassageColumnSegmentFlags.clear();PassageShortSpanSegmentFlags.clear();
PassageHasShortSpanFlags.clear();
@@ -4316,6 +4419,7 @@ private:
constexpr double Q=static_cast<double>(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel);
const double voxelsPerMetre=Owner&&Owner->CoreVoxelSizeMicrons>0
?1000000.0/Owner->CoreVoxelSizeMicrons:4.0;
const double qPerMetre=Q*voxelsPerMetre;
const double bridgeHalfWidthVoxels=3.5*voxelsPerMetre;
const double bridgeFootDepthVoxels=(5.0-0.13)*voxelsPerMetre;
PassageColumnSegmentFlags.resize(ResolvedLayout->passages.size());
@@ -4367,9 +4471,12 @@ private:
{
std::array<const VoxelForgeCore::ResolvedLayoutAnchorV2*,5> route{};
std::size_t routeCount=0;
std::vector<const VoxelForgeCore::ResolvedLayoutAnchorV2*> allRoutePoints;
allRoutePoints.reserve(upperStratum->anchors.size());
for(const auto& anchor:upperStratum->anchors)
if(anchor.kind=="route_point"&&anchor.values.size()>=3)
{
allRoutePoints.push_back(&anchor);
if(routeCount<route.size())route[routeCount++]=&anchor;
else
{
@@ -4379,6 +4486,25 @@ private:
}
if(routeCount>=2)
{
auto& supportRoutes=PassageMouthSupportRoutes[passageIndex];
supportRoutes.reserve(allRoutePoints.size()-1);
for(std::size_t index=0;index+1<allRoutePoints.size();++index)
{
FPassageMouthSupportSegment segment;
for(std::size_t axis=0;axis<3;++axis)
{
segment.a[axis]=allRoutePoints[index]->values[axis];
segment.b[axis]=allRoutePoints[index+1]->values[axis];
}
segment.hasRadius=allRoutePoints[index]->values.size()>=4
&&allRoutePoints[index+1]->values.size()>=4;
if(segment.hasRadius)
{
segment.a[3]=allRoutePoints[index]->values[3];
segment.b[3]=allRoutePoints[index+1]->values[3];
}
supportRoutes.push_back(segment);
}
const double mouthReach=13.0*voxelsPerMetre*Q;
double minimumZ=std::numeric_limits<double>::infinity();
double maximumZ=-std::numeric_limits<double>::infinity();
@@ -4395,17 +4521,32 @@ private:
supportProof.maximum={(mouthX+mouthReach)/Q,(mouthY+mouthReach)/Q,
(maximumZ+3.0*voxelsPerMetre*Q)/Q};
PassageProofBounds.push_back(supportProof);
const std::array<std::int64_t,3> featureMin={
static_cast<std::int64_t>(std::floor(supportProof.minimum[0]*Q)),
static_cast<std::int64_t>(std::floor(supportProof.minimum[1]*Q)),
static_cast<std::int64_t>(std::floor(supportProof.minimum[2]*Q))};
const std::array<std::int64_t,3> featureMax={
static_cast<std::int64_t>(std::ceil(supportProof.maximum[0]*Q)),
static_cast<std::int64_t>(std::ceil(supportProof.maximum[1]*Q)),
static_cast<std::int64_t>(std::ceil(supportProof.maximum[2]*Q))};
PassageFeatureBounds.push_back({static_cast<std::uint64_t>(PassageTileFeatures.size()),
featureMin,featureMax});
PassageTileFeatures.push_back({passageIndex,0,false,true});
for(std::size_t index=0;index<supportRoutes.size();++index)
{
const auto& segment=supportRoutes[index];
constexpr double HorizontalReachMetres=1.01;
constexpr double MouthSupportTopOffsetMetres=0.68;
constexpr double MouthSupportBottomOffsetMetres=2.58;
const std::array<std::int64_t,3> featureMinimum={
std::min(segment.a[0],segment.b[0])-static_cast<std::int64_t>(
std::ceil(HorizontalReachMetres*qPerMetre)),
std::min(segment.a[1],segment.b[1])-static_cast<std::int64_t>(
std::ceil(HorizontalReachMetres*qPerMetre)),
static_cast<std::int64_t>(std::floor(
std::min(segment.a[2],segment.b[2])
-MouthSupportBottomOffsetMetres*qPerMetre))};
const std::array<std::int64_t,3> featureMaximum={
std::max(segment.a[0],segment.b[0])+static_cast<std::int64_t>(
std::ceil(HorizontalReachMetres*qPerMetre)),
std::max(segment.a[1],segment.b[1])+static_cast<std::int64_t>(
std::ceil(HorizontalReachMetres*qPerMetre)),
static_cast<std::int64_t>(std::ceil(
std::max(segment.a[2],segment.b[2])
-MouthSupportTopOffsetMetres*qPerMetre))};
PassageFeatureBounds.push_back({static_cast<std::uint64_t>(
PassageTileFeatures.size()),featureMinimum,featureMaximum});
PassageTileFeatures.push_back({passageIndex,index,false,true});
}
}
}
}
@@ -4414,7 +4555,6 @@ private:
// hot query can test a short list of physical bounds instead of walking a
// full spatial tree for every density sample.
PassageLaterBoreSources.resize(ResolvedLayout->passages.size());
const double qPerMetre=Q*voxelsPerMetre;
for(std::size_t sourcePassageIndex=0;
sourcePassageIndex<ResolvedLayout->passages.size();++sourcePassageIndex)
{
@@ -11170,6 +11310,7 @@ private:
std::vector<FPassageFeatureNode> PassageFeatureNodes;
bool PassageFeatureBoundsFailOpen=false;
std::vector<std::vector<FPassageNearestNode>> PassageNearestNodes;
std::vector<std::vector<FPassageMouthSupportSegment>> PassageMouthSupportRoutes;
std::vector<std::vector<FPassageLaterBoreSource>> PassageLaterBoreSources;
std::vector<FPassageProofBound> PassageProofBounds;
std::vector<FPassageProofNode> PassageProofNodes;
+16 -1
View File
@@ -2620,7 +2620,22 @@ namespace VoxelForgeCore
// GenerateMesh n'échantillonne que origin..origin+extent et la marge de normale d'un pas ;
// queryMinimum/queryMaximum ci-dessus couvrent exactement ce domaine inclusif.
const FStandaloneShapeWorld::FScopedPassageTileQueries passageFeatureScope(
tileShape,queryMinimum,queryMaximum);
tileShape,queryMinimum,queryMaximum,
options.profilePassageComposer?&out.diagnostics:nullptr);
if(options.profilePassageComposer)
{
out.diagnostics.passageComposerProfiled=true;
out.diagnostics.passageTileFeatureCount=passageFeatureScope.featureCount();
if(impl_->resolvedLayout)
{
out.diagnostics.passageTileRouteSegmentCandidates=
passageFeatureScope.routeSegmentCandidateCount();
for(std::size_t passageIndex=0;
passageIndex<impl_->resolvedLayout->passages.size();++passageIndex)
out.diagnostics.passageTileMouthSegmentCandidates+=
passageFeatureScope.mouthSupportSegments(passageIndex).size();
}
}
const bool bDiagnosticColumnSampling=options.diagnosticGlobalOctaveDrop!=0
||options.diagnosticColumnInterpolationStride>1;
const std::uint32_t PreviousOctaveDrop=bDiagnosticColumnSampling