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:
1 parent
971206c2ea
commit
d970d75be8
26 files changed
+383
-82
No files matched your search
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user