passages: exact relief skip + per-tile BVH node masks (L82); FI/TN LOD0-2 -12%
- Wall relief noise is skipped when its hard-bounded contribution cannot change the final max; mouth surface/tread noise only on paths that use it. - Exact active BVH-node masks cached per tile/passage/segment range; no per-sample lower_bound. - Full-view audit imports the exported Core plan (not the stale 26-point root route): 21,425/21,425 walkability samples pass. Cold (L80 protocol, medians) LOD0-2: SW 1.16->1.11, FI 2.86->2.51, TN 2.88->2.54, VS 1.23->1.22 s. Passage share FI 64.6->58.7%, TN 65.4->58.4%. Hashes identical (v214). TN "slowdown" from L81 not reproduced (5 pairs, 3.08 -> 2.75 s). Fast tier PASS; Linux UE host builds. 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
9cb5e53b17
commit
f572108dc4
10 files changed
+665
-41
No files matched your search
@@ -179,6 +179,11 @@ public:
|
||||
std::size_t segmentIndex=0;
|
||||
std::array<std::int64_t,3> minimumSubvoxels{},maximumSubvoxels{};
|
||||
};
|
||||
struct FSegmentRangeCache
|
||||
{
|
||||
std::ptrdiff_t first=-1,end=-1;
|
||||
std::vector<std::uint8_t> activeNodes;
|
||||
};
|
||||
|
||||
FScopedPassageTileQueries(const FStandaloneShapeWorld& shape,
|
||||
const std::array<std::int64_t,3>& minimumSubvoxels,
|
||||
@@ -201,6 +206,7 @@ public:
|
||||
passageColumns_=passageRoutes_;
|
||||
passageMouthSupports_=passageRoutes_;
|
||||
passageMouthSupportSegments_.resize(passageRoutes_.size());
|
||||
passageSegmentRangeCaches_.resize(passageRoutes_.size());
|
||||
if(shape.ResolvedLayout&&!shape.ResolvedLayout->passages.empty())
|
||||
{
|
||||
constexpr double Q=static_cast<double>(
|
||||
@@ -281,6 +287,22 @@ public:
|
||||
return passageIndex<passageMouthSupportSegments_.size()
|
||||
?passageMouthSupportSegments_[passageIndex]:Empty;
|
||||
}
|
||||
const std::vector<std::uint8_t>* segmentRangeNodeMask(std::size_t passageIndex,
|
||||
std::ptrdiff_t first,std::ptrdiff_t end,int priority) const
|
||||
{
|
||||
if(passageIndex>=passageSegmentRangeCaches_.size()
|
||||
||priority<0||priority>=4)return nullptr;
|
||||
FSegmentRangeCache& cache=passageSegmentRangeCaches_[passageIndex][
|
||||
static_cast<std::size_t>(priority)];
|
||||
if(cache.first!=first||cache.end!=end)
|
||||
{
|
||||
cache.first=first;
|
||||
cache.end=end;
|
||||
cache.activeNodes=shape_->BuildPassageSegmentRangeNodeMask(
|
||||
passageIndex,first,end);
|
||||
}
|
||||
return &cache.activeNodes;
|
||||
}
|
||||
bool hasMouthSupport(std::size_t passageIndex) const
|
||||
{return passageIndex<passageMouthSupports_.size()&&passageMouthSupports_[passageIndex]!=0;}
|
||||
bool hasAnyMouthSupport() const
|
||||
@@ -317,6 +339,7 @@ public:
|
||||
std::vector<std::uint32_t> cavernFeatureIndices_;
|
||||
std::vector<std::uint8_t> passageRoutes_,passageColumns_,passageMouthSupports_;
|
||||
std::vector<std::vector<FCandidateSegment>> passageMouthSupportSegments_;
|
||||
mutable std::vector<std::array<FSegmentRangeCache,4>> passageSegmentRangeCaches_;
|
||||
std::vector<std::size_t> upperMouthSupportPassages_;
|
||||
std::size_t routeSegmentCandidateCount_=0;
|
||||
bool routeFeatureOverlap_=false,columnZOverlap_=false;
|
||||
@@ -1513,17 +1536,46 @@ public:
|
||||
?std::clamp(-(qz-(nearestA.positionSubvoxels[2]+bestT*(
|
||||
static_cast<double>(nearestB.positionSubvoxels[2])-nearestA.positionSubvoxels[2])))
|
||||
/distanceToCenter,0.0,1.0):0.0;
|
||||
const float relief=VF_FieldPassageWallRelief(static_cast<float>(X),
|
||||
static_cast<float>(Y),static_cast<float>(Z),
|
||||
static_cast<std::uint32_t>(Owner?Owner->Seed:0));
|
||||
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=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 std::uint32_t passageReliefSeed=static_cast<std::uint32_t>(Owner?Owner->Seed:0);
|
||||
float cachedWallRelief=0.0f,cachedMouthSurfaceRelief=0.0f,
|
||||
cachedMouthTreadFieldRelief=0.0f;
|
||||
bool hasCachedWallRelief=false,hasCachedMouthSurfaceRelief=false,
|
||||
hasCachedMouthTreadFieldRelief=false;
|
||||
const auto getWallRelief=[&]()
|
||||
{
|
||||
if(!hasCachedWallRelief)
|
||||
{
|
||||
cachedWallRelief=VF_FieldPassageWallRelief(static_cast<float>(X),
|
||||
static_cast<float>(Y),static_cast<float>(Z),passageReliefSeed);
|
||||
hasCachedWallRelief=true;
|
||||
if(passageProfile)++passageProfile->passageReliefFieldCalls;
|
||||
}
|
||||
return cachedWallRelief;
|
||||
};
|
||||
const auto getMouthSurfaceRelief=[&]()
|
||||
{
|
||||
if(!hasCachedMouthSurfaceRelief)
|
||||
{
|
||||
cachedMouthSurfaceRelief=VF_FieldSurfaceRelief(
|
||||
static_cast<float>(X)*1.5f,static_cast<float>(Y)*1.5f,
|
||||
static_cast<float>(Z)*1.5f,passageReliefSeed^0x4D4F5554u);
|
||||
hasCachedMouthSurfaceRelief=true;
|
||||
if(passageProfile)++passageProfile->passageReliefFieldCalls;
|
||||
}
|
||||
return cachedMouthSurfaceRelief;
|
||||
};
|
||||
const auto getMouthTreadFieldRelief=[&]()
|
||||
{
|
||||
if(!hasCachedMouthTreadFieldRelief)
|
||||
{
|
||||
cachedMouthTreadFieldRelief=VF_FieldSurfaceRelief(
|
||||
static_cast<float>(X)*0.75f,static_cast<float>(Y)*0.75f,
|
||||
static_cast<float>(Z)*0.75f,passageReliefSeed^0x54485244u);
|
||||
hasCachedMouthTreadFieldRelief=true;
|
||||
if(passageProfile)++passageProfile->passageReliefFieldCalls;
|
||||
}
|
||||
return cachedMouthTreadFieldRelief;
|
||||
};
|
||||
const auto routeSegmentLengthMetres=[&](std::ptrdiff_t routeIndex)
|
||||
{
|
||||
const auto& before=passage.controlPoints[static_cast<std::size_t>(routeIndex)];
|
||||
@@ -1639,21 +1691,33 @@ public:
|
||||
if(passageProfile)++passageProfile->passageSmoothstepEvaluations;
|
||||
const double floorReliefWeight=floorReliefT*floorReliefT
|
||||
*(3.0-2.0*floorReliefT);
|
||||
const double wallOpening=1.0-verticalWallAlignment;
|
||||
const double wallReliefWeight=0.05+0.95*wallOpening;
|
||||
const bool shortSpanCarve=IsPassageShortSpanSegment(passageIndex,
|
||||
static_cast<std::size_t>(bestIndex));
|
||||
float carveInputAfterBridge=composed;
|
||||
if(shortSpanCarve)
|
||||
carveInputAfterBridge=std::min(composed,static_cast<float>(
|
||||
PassageBridgeSignedDistance(passageIndex,
|
||||
static_cast<std::size_t>(bestIndex),X,Y,Z)/Q));
|
||||
const double maximumWallReliefOffset=0.21*qPerMetre*wallReliefWeight;
|
||||
const double maximumMouthFloorReliefOffset=upperMouthFloorFade
|
||||
?0.20*qPerMetre*mouthFloorBlend*floorReliefWeight:0.0;
|
||||
const double maximumCarvedDensity=-(bestSignedDistance
|
||||
-maximumWallReliefOffset-maximumMouthFloorReliefOffset)/Q;
|
||||
const bool wallReliefCanChangeCarve=!std::isfinite(maximumCarvedDensity)
|
||||
||maximumCarvedDensity>static_cast<double>(carveInputAfterBridge);
|
||||
const float relief=wallReliefCanChangeCarve?getWallRelief():0.0f;
|
||||
const float mouthTerrainRelief=upperMouthFloorFade
|
||||
?getMouthSurfaceRelief():0.0f;
|
||||
const double floorReliefOffset=std::clamp(
|
||||
static_cast<double>(mouthTerrainRelief),-1.0,1.0)
|
||||
*0.20*qPerMetre*mouthFloorBlend*floorReliefWeight
|
||||
*(upperMouthFloorFade?1.0:0.0);
|
||||
const double wallOpening=1.0-verticalWallAlignment;
|
||||
const double wallReliefWeight=0.05+0.95*wallOpening;
|
||||
const double roughSignedDistance=bestSignedDistance
|
||||
+static_cast<double>(std::clamp(relief,-1.0f,1.0f))*0.21*qPerMetre*wallReliefWeight
|
||||
+floorReliefOffset;
|
||||
if(IsPassageShortSpanSegment(passageIndex,static_cast<std::size_t>(bestIndex)))
|
||||
{
|
||||
const double bridgeDistance=PassageBridgeSignedDistance(passageIndex,
|
||||
static_cast<std::size_t>(bestIndex),X,Y,Z);
|
||||
composed=std::min(composed,static_cast<float>(bridgeDistance/Q));
|
||||
}
|
||||
if(shortSpanCarve)composed=carveInputAfterBridge;
|
||||
// Leave native crater ground at the lip. The bore opens through the walls
|
||||
// immediately, while its floor cut eases in along the passage centerline.
|
||||
double mouthCarveFade=1.0;
|
||||
@@ -1894,7 +1958,7 @@ public:
|
||||
double supportHalfWidth=std::min(radius,
|
||||
supportHalfWidthMetres*qPerMetre);
|
||||
const double shoulderWarp=mouthTread
|
||||
?std::clamp(static_cast<double>(mouthSurfaceRelief),-1.0,1.0)
|
||||
?std::clamp(static_cast<double>(getMouthSurfaceRelief()),-1.0,1.0)
|
||||
*0.12*qPerMetre:0.0;
|
||||
supportHalfWidth=mouthTread
|
||||
?std::clamp(supportHalfWidth+shoulderWarp,
|
||||
@@ -1950,7 +2014,7 @@ public:
|
||||
-0.80*qPerMetre,0.80*qPerMetre);
|
||||
const double floorQ=baseFloorQ
|
||||
+(mouthTread?std::clamp(nativeTreadOffset
|
||||
+std::clamp(static_cast<double>(mouthTreadFieldRelief),-1.0,1.0)
|
||||
+std::clamp(static_cast<double>(getMouthTreadFieldRelief()),-1.0,1.0)
|
||||
*0.40*qPerMetre,-0.80*qPerMetre,0.0):0.0);
|
||||
const double floorVoxels=floorQ/Q;
|
||||
const bool signedDistanceSupport=sparseRouteSupport||mouthTread;
|
||||
@@ -2073,7 +2137,7 @@ public:
|
||||
if(nativeRestoreCapsuleClear)
|
||||
{
|
||||
const double nativeSurfaceDetail=std::clamp(
|
||||
static_cast<double>(mouthTerrainRelief),-1.0,1.0)
|
||||
static_cast<double>(getMouthSurfaceRelief()),-1.0,1.0)
|
||||
*0.18*qPerMetre/Q;
|
||||
const float nativeGroundWithRelief=static_cast<float>(
|
||||
(supportNeedsDeferral?std::max(-1.0f,density):density)
|
||||
@@ -4836,6 +4900,42 @@ private:
|
||||
return nodeIndex;
|
||||
}
|
||||
|
||||
std::vector<std::uint8_t> BuildPassageSegmentRangeNodeMask(
|
||||
std::size_t passageIndex,std::ptrdiff_t first,std::ptrdiff_t end) const
|
||||
{
|
||||
std::vector<std::uint8_t> active;
|
||||
if(first>=end||passageIndex>=PassageNearestNodes.size()
|
||||
||PassageNearestNodes[passageIndex].empty())return active;
|
||||
const auto& nodes=PassageNearestNodes[passageIndex];
|
||||
active.assign(nodes.size(),0);
|
||||
const auto visit=[&](auto&& self,int nodeIndex)->bool
|
||||
{
|
||||
if(nodeIndex<0)return false;
|
||||
const std::size_t index=static_cast<std::size_t>(nodeIndex);
|
||||
const auto& node=nodes[index];
|
||||
if(static_cast<std::ptrdiff_t>(node.maximumSegment)<first
|
||||
||static_cast<std::ptrdiff_t>(node.minimumSegment)>=end)return false;
|
||||
bool hasCandidate=false;
|
||||
if(node.left<0)
|
||||
{
|
||||
for(const std::size_t segment:node.segments)
|
||||
hasCandidate=hasCandidate
|
||||
||(static_cast<std::ptrdiff_t>(segment)>=first
|
||||
&&static_cast<std::ptrdiff_t>(segment)<end);
|
||||
}
|
||||
else
|
||||
{
|
||||
const bool leftHasCandidate=self(self,node.left);
|
||||
const bool rightHasCandidate=self(self,node.right);
|
||||
hasCandidate=leftHasCandidate||rightHasCandidate;
|
||||
}
|
||||
active[index]=static_cast<std::uint8_t>(hasCandidate);
|
||||
return hasCandidate;
|
||||
};
|
||||
visit(visit,0);
|
||||
return active;
|
||||
}
|
||||
|
||||
int BuildPassageProofNode(std::size_t first,std::size_t end)
|
||||
{
|
||||
FPassageProofNode node;node.first=first;node.end=end;
|
||||
@@ -5410,35 +5510,53 @@ private:
|
||||
template<typename FVisit>
|
||||
void VisitNearestPassageSpatialNode(std::size_t passageIndex,int nodeIndex,
|
||||
std::size_t first,std::size_t end,double qx,double qy,double qz,int priority,
|
||||
double& bestDistance,FVisit& visit) const
|
||||
const std::vector<std::uint8_t>* activeNodes,double& bestDistance,
|
||||
FVisit& visit) const
|
||||
{
|
||||
if(passageIndex>=PassageNearestNodes.size()||nodeIndex<0)return;
|
||||
const auto& nodes=PassageNearestNodes[passageIndex];
|
||||
const auto& node=nodes[static_cast<std::size_t>(nodeIndex)];
|
||||
if(node.maximumSegment<first||node.minimumSegment>=end)return;
|
||||
const bool allSegmentsInRange=first<=node.minimumSegment
|
||||
&&node.maximumSegment<end;
|
||||
const auto candidate=allSegmentsInRange?node.segments.begin()
|
||||
:std::lower_bound(node.segments.begin(),node.segments.end(),first);
|
||||
if(candidate==node.segments.end()||*candidate>=end)return;
|
||||
const std::size_t nodeOffset=static_cast<std::size_t>(nodeIndex);
|
||||
if(activeNodes&&(nodeOffset>=activeNodes->size()||!(*activeNodes)[nodeOffset]))return;
|
||||
const auto& node=nodes[nodeOffset];
|
||||
if(!activeNodes&&(node.maximumSegment<first||node.minimumSegment>=end))return;
|
||||
auto candidate=node.segments.begin();
|
||||
if(!activeNodes)
|
||||
{
|
||||
const bool allSegmentsInRange=first<=node.minimumSegment
|
||||
&&node.maximumSegment<end;
|
||||
if(!allSegmentsInRange)
|
||||
candidate=std::lower_bound(node.segments.begin(),node.segments.end(),first);
|
||||
if(candidate==node.segments.end()||*candidate>=end)return;
|
||||
}
|
||||
const double lowerBound=PassageNearestNodeLowerBound(node,qx,qy,qz);
|
||||
if(lowerBound>bestDistance)return;
|
||||
if(node.left<0)
|
||||
{
|
||||
for(auto it=candidate;it!=node.segments.end()&&*it<end;++it)
|
||||
for(auto it=candidate;it!=node.segments.end();++it)
|
||||
{
|
||||
if(*it<first||*it>=end)continue;
|
||||
visit(*it,priority);
|
||||
}
|
||||
return;
|
||||
}
|
||||
const double leftBound=PassageNearestNodeLowerBound(
|
||||
nodes[static_cast<std::size_t>(node.left)],qx,qy,qz);
|
||||
const double rightBound=PassageNearestNodeLowerBound(
|
||||
nodes[static_cast<std::size_t>(node.right)],qx,qy,qz);
|
||||
const bool leftActive=!activeNodes
|
||||
||(static_cast<std::size_t>(node.left)<activeNodes->size()
|
||||
&&(*activeNodes)[static_cast<std::size_t>(node.left)]);
|
||||
const bool rightActive=!activeNodes
|
||||
||(static_cast<std::size_t>(node.right)<activeNodes->size()
|
||||
&&(*activeNodes)[static_cast<std::size_t>(node.right)]);
|
||||
const double leftBound=leftActive?PassageNearestNodeLowerBound(
|
||||
nodes[static_cast<std::size_t>(node.left)],qx,qy,qz)
|
||||
:std::numeric_limits<double>::infinity();
|
||||
const double rightBound=rightActive?PassageNearestNodeLowerBound(
|
||||
nodes[static_cast<std::size_t>(node.right)],qx,qy,qz)
|
||||
:std::numeric_limits<double>::infinity();
|
||||
const int nearChild=leftBound<=rightBound?node.left:node.right;
|
||||
const int farChild=leftBound<=rightBound?node.right:node.left;
|
||||
VisitNearestPassageSpatialNode(passageIndex,nearChild,first,end,qx,qy,qz,
|
||||
priority,bestDistance,visit);
|
||||
priority,activeNodes,bestDistance,visit);
|
||||
VisitNearestPassageSpatialNode(passageIndex,farChild,first,end,qx,qy,qz,
|
||||
priority,bestDistance,visit);
|
||||
priority,activeNodes,bestDistance,visit);
|
||||
}
|
||||
|
||||
template<typename FVisit>
|
||||
@@ -5449,8 +5567,12 @@ private:
|
||||
if(first>last||passageIndex>=PassageNearestNodes.size()
|
||||
||PassageNearestNodes[passageIndex].empty())return;
|
||||
auto callback=std::forward<FVisit>(visit);
|
||||
const auto* tileQueries=ActivePassageTileQueries;
|
||||
const auto* activeNodes=tileQueries&&tileQueries->matches(*this)
|
||||
?tileQueries->segmentRangeNodeMask(passageIndex,first,last+1,priority):nullptr;
|
||||
VisitNearestPassageSpatialNode(passageIndex,0,static_cast<std::size_t>(first),
|
||||
static_cast<std::size_t>(last+1),qx,qy,qz,priority,bestDistance,callback);
|
||||
static_cast<std::size_t>(last+1),qx,qy,qz,priority,activeNodes,
|
||||
bestDistance,callback);
|
||||
}
|
||||
|
||||
bool PassagePotentialWithinNode(std::size_t passageIndex,int nodeIndex,
|
||||
@@ -12312,6 +12434,11 @@ private:
|
||||
static_cast<unsigned long long>(Stats.WalkSamples));
|
||||
return Stats.WalkFailures==0;
|
||||
}
|
||||
// An imported Unreal-resolved layout is the canonical route source. An empty
|
||||
// passage list is meaningful for blocked controls, so never fall back to the
|
||||
// regenerated legacy owner route in that case. Recipe overrides without an
|
||||
// exported Core plan retain their generated owner route audit.
|
||||
if(Owner&&Owner->bHasUnrealResolvedCorePlan&&ResolvedLayout)return true;
|
||||
constexpr double SampleStep=0.5;
|
||||
uint32 PassageIndex=0;
|
||||
for(const FVoxelPassage& P:Owner->Manager->GetPassages())
|
||||
|
||||
@@ -11,5 +11,6 @@ namespace VoxelForgeCore
|
||||
// the same command-line overrides as the viewer's reference loader.
|
||||
ResolvedWorldSnapshot MakeStandaloneSnapshotFromParsedJson(const VFJson::Value& jsonRoot,
|
||||
std::int32_t seed,std::int32_t clipRadius,std::int32_t maxLodOverride,
|
||||
bool roundTunnels,bool enhancedTunnelWalls,std::string& error);
|
||||
bool roundTunnels,bool enhancedTunnelWalls,std::string& error,
|
||||
bool hasUnrealResolvedCorePlan=false);
|
||||
}
|
||||
@@ -926,10 +926,12 @@ namespace VoxelForgeCore
|
||||
|
||||
ResolvedWorldSnapshot MakeStandaloneSnapshotFromParsedJson(const VFJson::Value& jsonRoot,
|
||||
std::int32_t seed,std::int32_t clipRadius,std::int32_t maxLodOverride,
|
||||
bool roundTunnels,bool enhancedTunnelWalls,std::string& error)
|
||||
bool roundTunnels,bool enhancedTunnelWalls,std::string& error,
|
||||
bool hasUnrealResolvedCorePlan)
|
||||
{
|
||||
auto owner=std::make_shared<FStandaloneOwnerWorld>();
|
||||
if(!StandaloneOwnerLoader::Resolve(jsonRoot,*owner,error))return {};
|
||||
owner->bHasUnrealResolvedCorePlan=hasUnrealResolvedCorePlan;
|
||||
std::shared_ptr<const WorldAssemblyManifest> worldAssembly;
|
||||
if(jsonRoot.Get("world_assembly").IsObject())
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user