diag: passage candidate compiler census (L90, opt-in); falsifier failed at 10 m reach
54,324 sparse 4 m bricks, uncapped carve/tread/clearance/mouth lists, built in 26-28 ms, ~12 MiB. Weighted over 30.7M near-LOD sample hits: active carve p90 13, mouth p90 13; reach fallback keeps 18.3% of passage time (FI 12%, TN 19%) - all because the density-dependent reach (-d*Q + 0.24 m) exceeds 10 m for deeply negative source samples. Not wired into production. R41 notes (Unreal LOD0-2 gap: ~0.7 s pre-request in BeginPlay, LOD2 tail, apply boost reset; L80's 4 missing tiles = re-requested empty collision-support tiles) attached. 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
04cdd197fb
commit
a7a03d42b4
5 files changed
+948
-5
No files matched your search
@@ -20,6 +20,7 @@
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
@@ -62,6 +63,13 @@ static bool VF_IsL88AllowPassageAblationWalkabilityFailure()
|
||||
return bEnabled;
|
||||
}
|
||||
|
||||
static bool VF_IsL90PassageCandidateCensusEnabled()
|
||||
{
|
||||
static const bool bEnabled=VF_IsL88DiagnosticSwitchEnabled(
|
||||
"VOXELFORGE_L90_PASSAGE_CANDIDATE_CENSUS");
|
||||
return bEnabled;
|
||||
}
|
||||
|
||||
// Standalone-only world compiler. It compiles the runtime room/tunnel/passages into
|
||||
// axis-line intervals, composes rock/cave signs into packed Z-column words, then displaces
|
||||
// only the conservative surface band. The copied Unreal generator remains the A/B reference.
|
||||
@@ -218,10 +226,11 @@ public:
|
||||
FScopedPassageTileQueries(const FStandaloneShapeWorld& shape,
|
||||
const std::array<std::int64_t,3>& minimumSubvoxels,
|
||||
const std::array<std::int64_t,3>& maximumSubvoxels,
|
||||
VoxelForgeCore::TileGenerationStats* profileStats=nullptr)
|
||||
VoxelForgeCore::TileGenerationStats* profileStats=nullptr,int32 profileLod=0)
|
||||
:shape_(&shape),minimumSubvoxels_(minimumSubvoxels),
|
||||
maximumSubvoxels_(maximumSubvoxels),previous_(ActivePassageTileQueries),
|
||||
profileStats_(profileStats)
|
||||
profileStats_(profileStats),profileLod_(profileLod),
|
||||
candidateProfiled_(profileStats!=nullptr&&shape.L90PassageCandidateProgramEnabled)
|
||||
{
|
||||
mayContainResolvedBoundaryBand_=shape.ResolvedBoundaryBandsIntersectSubvoxelRange(
|
||||
minimumSubvoxels_[2],maximumSubvoxels_[2]);
|
||||
@@ -328,9 +337,62 @@ public:
|
||||
routeFeatureOverlap_(source.routeFeatureOverlap_),columnZOverlap_(source.columnZOverlap_),
|
||||
mayContainResolvedBoundaryBand_(source.mayContainResolvedBoundaryBand_),
|
||||
mayContainTransitionPassageFade_(source.mayContainTransitionPassageFade_),
|
||||
previous_(ActivePassageTileQueries)
|
||||
previous_(ActivePassageTileQueries),profileLod_(source.profileLod_),
|
||||
candidateProfiled_(source.candidateProfiled_)
|
||||
{ ActivePassageTileQueries=this; }
|
||||
~FScopedPassageTileQueries(){ActivePassageTileQueries=previous_;}
|
||||
~FScopedPassageTileQueries()
|
||||
{
|
||||
ActivePassageTileQueries=previous_;
|
||||
if(!candidateProfiled_||l90Queries_==0)return;
|
||||
std::array<std::array<char,360>,4> HistText{};
|
||||
constexpr std::array<const char*,4> Names={"carve","tread","clearance","mouth_column"};
|
||||
for(std::size_t semantic=0;semantic<Names.size();++semantic)
|
||||
{
|
||||
std::size_t offset=0;
|
||||
for(std::size_t bin=0;bin<l90CandidateHistogram_[semantic].size();++bin)
|
||||
{
|
||||
const int written=std::snprintf(HistText[semantic].data()+offset,
|
||||
HistText[semantic].size()-offset,"%s%llu",bin?",":"",
|
||||
static_cast<unsigned long long>(l90CandidateHistogram_[semantic][bin]));
|
||||
if(written<0)break;
|
||||
offset=std::min(HistText[semantic].size(),offset+static_cast<std::size_t>(written));
|
||||
if(offset>=HistText[semantic].size())break;
|
||||
}
|
||||
}
|
||||
std::printf("[L90PassageCandidateTile] lod=%d min=(%lld,%lld,%lld) max=(%lld,%lld,%lld) queries=%llu no_brick=%llu fallback_iterations=%llu fallback_reach=%llu fallback_shortspan=%llu fallback_invalid=%llu fallback_proof=%llu fallback_ns=%llu query_ns=%llu fallback_carve=%llu fallback_tread=%llu fallback_clearance=%llu fallback_mouth=%llu proof_checks=(%llu,%llu,%llu,%llu,%llu,%llu) proof_misses=(%llu,%llu,%llu,%llu,%llu,%llu) carve_hist=%s tread_hist=%s clearance_hist=%s mouth_column_hist=%s\n",
|
||||
profileLod_,static_cast<long long>(minimumSubvoxels_[0]),
|
||||
static_cast<long long>(minimumSubvoxels_[1]),
|
||||
static_cast<long long>(minimumSubvoxels_[2]),
|
||||
static_cast<long long>(maximumSubvoxels_[0]),
|
||||
static_cast<long long>(maximumSubvoxels_[1]),
|
||||
static_cast<long long>(maximumSubvoxels_[2]),
|
||||
static_cast<unsigned long long>(l90Queries_),
|
||||
static_cast<unsigned long long>(l90NoBrickQueries_),
|
||||
static_cast<unsigned long long>(l90FallbackIterations_),
|
||||
static_cast<unsigned long long>(l90FallbackReachOverflow_),
|
||||
static_cast<unsigned long long>(l90FallbackShortSpan_),
|
||||
static_cast<unsigned long long>(l90FallbackInvalid_),
|
||||
static_cast<unsigned long long>(l90FallbackProof_),
|
||||
static_cast<unsigned long long>(l90FallbackNanoseconds_),
|
||||
static_cast<unsigned long long>(l90QueryNanoseconds_),
|
||||
static_cast<unsigned long long>(l90FallbackCarveVisits_),
|
||||
static_cast<unsigned long long>(l90FallbackTreadVisits_),
|
||||
static_cast<unsigned long long>(l90FallbackClearanceChecks_),
|
||||
static_cast<unsigned long long>(l90FallbackMouthChecks_),
|
||||
static_cast<unsigned long long>(l90ProofChecks_[0]),
|
||||
static_cast<unsigned long long>(l90ProofChecks_[1]),
|
||||
static_cast<unsigned long long>(l90ProofChecks_[2]),
|
||||
static_cast<unsigned long long>(l90ProofChecks_[3]),
|
||||
static_cast<unsigned long long>(l90ProofChecks_[4]),
|
||||
static_cast<unsigned long long>(l90ProofChecks_[5]),
|
||||
static_cast<unsigned long long>(l90ProofMisses_[0]),
|
||||
static_cast<unsigned long long>(l90ProofMisses_[1]),
|
||||
static_cast<unsigned long long>(l90ProofMisses_[2]),
|
||||
static_cast<unsigned long long>(l90ProofMisses_[3]),
|
||||
static_cast<unsigned long long>(l90ProofMisses_[4]),
|
||||
static_cast<unsigned long long>(l90ProofMisses_[5]),
|
||||
HistText[0].data(),HistText[1].data(),HistText[2].data(),HistText[3].data());
|
||||
}
|
||||
FScopedPassageTileQueries& operator=(const FScopedPassageTileQueries&)=delete;
|
||||
|
||||
bool matches(const FStandaloneShapeWorld& shape) const{return shape_==&shape;}
|
||||
@@ -388,6 +450,40 @@ public:
|
||||
std::size_t routeSegmentCandidateCount() const{return routeSegmentCandidateCount_;}
|
||||
VoxelForgeCore::TileGenerationStats* profileStats() const noexcept
|
||||
{return profileStats_;}
|
||||
bool candidateCensusProfiled() const noexcept{return candidateProfiled_;}
|
||||
void recordL90CandidateHit(const std::array<std::size_t,4>& counts,bool haveBrick) const
|
||||
{
|
||||
if(!candidateProfiled_)return;
|
||||
++l90Queries_;
|
||||
if(!haveBrick)++l90NoBrickQueries_;
|
||||
for(std::size_t semantic=0;semantic<counts.size();++semantic)
|
||||
++l90CandidateHistogram_[semantic][std::min<std::size_t>(counts[semantic],16)];
|
||||
}
|
||||
void recordL90Fallback(std::uint64_t elapsedNanoseconds,
|
||||
std::uint64_t carveVisits,std::uint64_t treadVisits,
|
||||
std::uint64_t clearanceChecks,std::uint64_t mouthChecks,
|
||||
std::uint8_t reason) const
|
||||
{
|
||||
if(!candidateProfiled_)return;
|
||||
++l90FallbackIterations_;
|
||||
l90FallbackNanoseconds_+=elapsedNanoseconds;
|
||||
l90FallbackCarveVisits_+=carveVisits;
|
||||
l90FallbackTreadVisits_+=treadVisits;
|
||||
l90FallbackClearanceChecks_+=clearanceChecks;
|
||||
l90FallbackMouthChecks_+=mouthChecks;
|
||||
if(reason==1)++l90FallbackReachOverflow_;
|
||||
else if(reason==2)++l90FallbackShortSpan_;
|
||||
else if(reason==3)++l90FallbackProof_;
|
||||
else ++l90FallbackInvalid_;
|
||||
}
|
||||
void recordL90QueryNanoseconds(std::uint64_t elapsedNanoseconds) const
|
||||
{if(candidateProfiled_)l90QueryNanoseconds_+=elapsedNanoseconds;}
|
||||
void recordL90ProofCheck(std::size_t semantic,bool contained) const
|
||||
{
|
||||
if(!candidateProfiled_||semantic>=l90ProofChecks_.size())return;
|
||||
++l90ProofChecks_[semantic];
|
||||
if(!contained)++l90ProofMisses_[semantic];
|
||||
}
|
||||
bool hasStampCandidates() const{return !stampIndices_.empty();}
|
||||
const std::vector<std::uint32_t>& stampCandidates() const{return stampIndices_;}
|
||||
bool hasCavernCandidates() const{return !cavernFeatureIndices_.empty();}
|
||||
@@ -410,6 +506,16 @@ public:
|
||||
bool mayContainTransitionPassageFade_=true;
|
||||
const FScopedPassageTileQueries* previous_;
|
||||
VoxelForgeCore::TileGenerationStats* profileStats_=nullptr;
|
||||
int32 profileLod_=0;
|
||||
bool candidateProfiled_=false;
|
||||
mutable std::uint64_t l90Queries_=0,l90NoBrickQueries_=0;
|
||||
mutable std::uint64_t l90FallbackIterations_=0,l90FallbackNanoseconds_=0,
|
||||
l90QueryNanoseconds_=0,l90FallbackCarveVisits_=0,l90FallbackTreadVisits_=0,
|
||||
l90FallbackClearanceChecks_=0,l90FallbackMouthChecks_=0,
|
||||
l90FallbackReachOverflow_=0,l90FallbackShortSpan_=0,l90FallbackInvalid_=0,
|
||||
l90FallbackProof_=0;
|
||||
mutable std::array<std::uint64_t,6> l90ProofChecks_{},l90ProofMisses_{};
|
||||
mutable std::array<std::array<std::uint64_t,17>,4> l90CandidateHistogram_{};
|
||||
};
|
||||
|
||||
bool IsResolvedPassageColumnSample(std::size_t PassageIndex,std::size_t SegmentIndex,
|
||||
@@ -1129,6 +1235,40 @@ public:
|
||||
auto* passageProfile=passageTileScopeMatches
|
||||
?passageTileQueries->profileStats():nullptr;
|
||||
if(passageProfile)++passageProfile->passageComposerQueries;
|
||||
const bool l90CandidateProfiled=passageTileScopeMatches
|
||||
&&passageTileQueries->candidateCensusProfiled();
|
||||
const auto* l90CandidateBrick=l90CandidateProfiled
|
||||
?FindPassageCandidateBrick(qx,qy,qz):nullptr;
|
||||
const std::array<std::size_t,4> l90CandidateCounts=l90CandidateBrick
|
||||
?std::array<std::size_t,4>{
|
||||
l90CandidateBrick->semantics[0].size(),l90CandidateBrick->semantics[1].size(),
|
||||
l90CandidateBrick->semantics[2].size(),l90CandidateBrick->semantics[3].size()}
|
||||
:std::array<std::size_t,4>{};
|
||||
if(l90CandidateProfiled)
|
||||
passageTileQueries->recordL90CandidateHit(l90CandidateCounts,
|
||||
l90CandidateBrick!=nullptr);
|
||||
struct FL90QueryTimer
|
||||
{
|
||||
const FScopedPassageTileQueries* scope=nullptr;
|
||||
std::chrono::steady_clock::time_point start{};
|
||||
~FL90QueryTimer()
|
||||
{
|
||||
if(scope)scope->recordL90QueryNanoseconds(static_cast<std::uint64_t>(
|
||||
std::chrono::duration_cast<std::chrono::nanoseconds>(
|
||||
std::chrono::steady_clock::now()-start).count()));
|
||||
}
|
||||
} l90QueryTimer{l90CandidateProfiled?passageTileQueries:nullptr,
|
||||
l90CandidateProfiled?std::chrono::steady_clock::now()
|
||||
:std::chrono::steady_clock::time_point{}};
|
||||
struct FL90CandidateBrickScope
|
||||
{
|
||||
const FPassageCandidateBrick* previous=nullptr;
|
||||
explicit FL90CandidateBrickScope(const FPassageCandidateBrick* current)
|
||||
:previous(FStandaloneShapeWorld::ActivePassageCandidateBrick)
|
||||
{FStandaloneShapeWorld::ActivePassageCandidateBrick=current;}
|
||||
~FL90CandidateBrickScope()
|
||||
{FStandaloneShapeWorld::ActivePassageCandidateBrick=previous;}
|
||||
} l90CandidateBrickScope(l90CandidateBrick);
|
||||
if(passageTileScopeMatches&&density>=0.0f
|
||||
&&!passageTileQueries->hasRouteFeatureOverlap()
|
||||
&&!passageTileQueries->hasColumnZOverlap()
|
||||
@@ -1296,6 +1436,12 @@ public:
|
||||
const double bottomDistance=floorQ-qPerMetre-qz;
|
||||
const double verticalDistance=std::max(topDistance,bottomDistance);
|
||||
if(sideDistance>mouthBlendWidth||verticalDistance>mouthBlendWidth)return;
|
||||
if(l90CandidateProfiled)
|
||||
{
|
||||
const bool contained=PassageCandidateBrickContains(l90CandidateBrick,3,
|
||||
passageIndex,static_cast<std::int32_t>(index),2);
|
||||
passageTileQueries->recordL90ProofCheck(3,contained);
|
||||
}
|
||||
const double outside=DeterministicSqrt(
|
||||
std::max(sideDistance,0.0)*std::max(sideDistance,0.0)
|
||||
+std::max(verticalDistance,0.0)*std::max(verticalDistance,0.0));
|
||||
@@ -1326,10 +1472,38 @@ public:
|
||||
};
|
||||
// The connected passage tread continues the surface route into the tunnel.
|
||||
const std::size_t passageIterationCount=ResolvedLayout->passages.size();
|
||||
struct FL90IterationTimer
|
||||
{
|
||||
const FScopedPassageTileQueries* scope=nullptr;
|
||||
std::chrono::steady_clock::time_point start{};
|
||||
bool fallback=false;
|
||||
std::uint8_t fallbackReason=0;
|
||||
std::uint64_t carveVisits=0,treadVisits=0,clearanceChecks=0,mouthChecks=0;
|
||||
void requireFallback(std::uint8_t reason)
|
||||
{fallback=true;fallbackReason=reason;}
|
||||
void recordCarve(){++carveVisits;}
|
||||
void recordTread(){++treadVisits;}
|
||||
void recordClearance(){++clearanceChecks;}
|
||||
void recordMouth(){++mouthChecks;}
|
||||
~FL90IterationTimer()
|
||||
{
|
||||
if(scope&&fallback)
|
||||
scope->recordL90Fallback(static_cast<std::uint64_t>(
|
||||
std::chrono::duration_cast<std::chrono::nanoseconds>(
|
||||
std::chrono::steady_clock::now()-start).count()),
|
||||
carveVisits,treadVisits,clearanceChecks,mouthChecks,fallbackReason);
|
||||
}
|
||||
};
|
||||
for(std::size_t passageIteration=0;passageIteration<passageIterationCount;
|
||||
++passageIteration)
|
||||
{
|
||||
const std::size_t passageIndex=passageIteration;
|
||||
FL90IterationTimer l90IterationTimer;
|
||||
if(l90CandidateProfiled)
|
||||
{
|
||||
l90IterationTimer.scope=passageTileQueries;
|
||||
l90IterationTimer.start=std::chrono::steady_clock::now();
|
||||
}
|
||||
if(passageProfile)++passageProfile->passageIterations;
|
||||
const auto& passage=ResolvedLayout->passages[passageIndex];
|
||||
if(passage.controlPoints.size()<2)continue;
|
||||
@@ -1341,6 +1515,7 @@ public:
|
||||
const auto laterPassageMayCarve=[&](std::ptrdiff_t supportSegment,
|
||||
double sampleX,double sampleY,double sampleZ)
|
||||
{
|
||||
l90IterationTimer.recordClearance();
|
||||
if(passageProfile)++passageProfile->passageLaterBoreChecks;
|
||||
return PassageSupportMayBeCarvedByLaterBore(passageIndex,
|
||||
supportSegment,sampleX,sampleY,sampleZ,0.35*qPerMetre);
|
||||
@@ -1353,6 +1528,21 @@ public:
|
||||
{
|
||||
if(!crossing.needsColumn||crossing.passageIndex!=passageIndex
|
||||
||qz>crossing.topZSubvoxels||qz<crossing.bottomZSubvoxels)continue;
|
||||
l90IterationTimer.recordMouth();
|
||||
const auto recordColumnProof=[&](double columnDistance,double inputComposed)
|
||||
{
|
||||
if(!l90CandidateProfiled)return;
|
||||
if(columnDistance<=L90PassageCandidateReachMetres*qPerMetre)
|
||||
{
|
||||
const bool contained=PassageCandidateBrickContains(
|
||||
l90CandidateBrick,3,passageIndex,
|
||||
static_cast<std::int32_t>(crossing.firstSegment),3);
|
||||
passageTileQueries->recordL90ProofCheck(4,contained);
|
||||
if(!contained)l90IterationTimer.requireFallback(3);
|
||||
}
|
||||
else if(columnDistance<static_cast<double>(inputComposed)*Q)
|
||||
l90IterationTimer.requireFallback(1);
|
||||
};
|
||||
if(passageProfile)++passageProfile->passageColumnCandidates;
|
||||
#if defined(VF_L30_ENABLE_COST_COUNTERS)
|
||||
++ThreadClassificationStats().ColumnZOverlapCalls;
|
||||
@@ -1399,6 +1589,7 @@ public:
|
||||
if(passageProfile)++passageProfile->passageColumnDistanceCalls;
|
||||
columnDistance=PassageColumnSignedDistance(crossing,X,Y,Z);
|
||||
}
|
||||
if(l90CandidateProfiled)recordColumnProof(columnDistance,composed);
|
||||
composed=std::min(composed,static_cast<float>(columnDistance/Q));
|
||||
}
|
||||
const bool tilePassageFree=passageTileScopeMatches
|
||||
@@ -1433,6 +1624,24 @@ public:
|
||||
(carveLow-passage.controlPoints.begin())+1);
|
||||
const std::ptrdiff_t mouthSearchBegin=passageConstants.mouthSearchBegin;
|
||||
const std::ptrdiff_t mouthSearchEnd=passageConstants.mouthSearchEnd;
|
||||
if(l90CandidateProfiled)
|
||||
{
|
||||
const double signedDistanceLimit=density<0.0f
|
||||
?-static_cast<double>(composed)*Q+0.24*qPerMetre
|
||||
:std::max(2.25*qPerMetre,-static_cast<double>(composed)*Q
|
||||
+0.24*qPerMetre);
|
||||
const bool sourceProofAvailable=L90PassageCandidateProgramValid
|
||||
&&std::isfinite(signedDistanceLimit)
|
||||
&&signedDistanceLimit<=L90PassageCandidateReachMetres*qPerMetre
|
||||
&&!(density>=0.0f&&PassageHasShortSpanSegments(passageIndex));
|
||||
if(!sourceProofAvailable)
|
||||
{
|
||||
const std::uint8_t reason=!L90PassageCandidateProgramValid
|
||||
||!std::isfinite(signedDistanceLimit)?0
|
||||
:signedDistanceLimit>L90PassageCandidateReachMetres*qPerMetre?1:2;
|
||||
l90IterationTimer.requireFallback(reason);
|
||||
}
|
||||
}
|
||||
if(density<0.0f&&tilePassageFree)
|
||||
{
|
||||
if(passageProfile)++passageProfile->passageNegativeBoundChecks;
|
||||
@@ -1492,6 +1701,7 @@ public:
|
||||
*(3.0-2.0*lowerMouthT);
|
||||
const auto considerCarveSegment=[&](std::size_t segmentIndex,int scanPhase)
|
||||
{
|
||||
l90IterationTimer.recordCarve();
|
||||
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)];
|
||||
@@ -1593,6 +1803,16 @@ public:
|
||||
considerCarveSegment(static_cast<std::size_t>(index),3);
|
||||
}
|
||||
if(!std::isfinite(bestSignedDistance))continue;
|
||||
const double l90SignedDistanceLimit=density<0.0f
|
||||
?-static_cast<double>(composed)*Q+0.24*qPerMetre
|
||||
:std::max(2.25*qPerMetre,-static_cast<double>(composed)*Q
|
||||
+0.24*qPerMetre);
|
||||
if(l90CandidateProfiled&&bestSignedDistance<=l90SignedDistanceLimit
|
||||
&&l90SignedDistanceLimit<=L90PassageCandidateReachMetres*qPerMetre)
|
||||
passageTileQueries->recordL90ProofCheck(0,
|
||||
L90PassageCandidateProgramValid
|
||||
&&PassageCandidateBrickContains(l90CandidateBrick,0,passageIndex,
|
||||
static_cast<std::int32_t>(bestIndex)));
|
||||
const auto& nearestA=passage.controlPoints[static_cast<std::size_t>(bestIndex)];
|
||||
const auto& nearestB=passage.controlPoints[static_cast<std::size_t>(bestIndex+1)];
|
||||
const double nearestRadius=nearestA.radiusSubvoxels+bestT*(
|
||||
@@ -1763,9 +1983,17 @@ public:
|
||||
static_cast<std::size_t>(bestIndex));
|
||||
float carveInputAfterBridge=composed;
|
||||
if(shortSpanCarve)
|
||||
{
|
||||
if(l90CandidateProfiled)
|
||||
{
|
||||
const bool contained=PassageCandidateBrickContains(l90CandidateBrick,3,
|
||||
passageIndex,static_cast<std::int32_t>(bestIndex),4);
|
||||
passageTileQueries->recordL90ProofCheck(5,contained);
|
||||
}
|
||||
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;
|
||||
@@ -1846,6 +2074,7 @@ public:
|
||||
const float composedAfterCarve=composed;
|
||||
const auto considerSupportSegment=[&](std::ptrdiff_t index)
|
||||
{
|
||||
l90IterationTimer.recordTread();
|
||||
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)];
|
||||
@@ -2151,6 +2380,10 @@ public:
|
||||
for(std::ptrdiff_t index=supportBegin;index<=supportEnd;++index)
|
||||
considerSupportSegment(index);
|
||||
}
|
||||
if(l90CandidateProfiled&&hasSupportFloor)
|
||||
passageTileQueries->recordL90ProofCheck(1,
|
||||
PassageCandidateBrickContains(l90CandidateBrick,1,passageIndex,
|
||||
static_cast<std::int32_t>(bestSupportSegmentIndex)));
|
||||
VoxelForgeCore::Linux77Profiler::FScopedPhase l77SupportApplyPhase(
|
||||
VoxelForgeCore::Linux77Profiler::EPhase::Support);
|
||||
// Keep route treads inside the native rock envelope. Carving may reopen the
|
||||
@@ -2169,6 +2402,7 @@ public:
|
||||
if(passageTileScopeMatches
|
||||
||(bestSupportUsesSignedDistance&&supportCapsuleProfileAvailable))
|
||||
{
|
||||
l90IterationTimer.recordClearance();
|
||||
if(passageProfile)++passageProfile->passageSupportCapsuleChecks;
|
||||
capsuleAllowsSupport=!PassagePlayerCapsuleBlocksSupport(passageIndex,
|
||||
carveBegin,carveEnd,lowerMouthIndex,mouthSearchBegin,mouthSearchEnd,
|
||||
@@ -2195,6 +2429,7 @@ public:
|
||||
const bool nativeRestoreCapsuleClear=density<0.0f
|
||||
&&bestSupportNativeBlend>0.0&&capsuleAllowsSupport
|
||||
&&([&](){
|
||||
l90IterationTimer.recordClearance();
|
||||
if(passageProfile)++passageProfile->passageSupportCapsuleChecks;
|
||||
return !PassagePlayerCapsuleBlocksSupport(passageIndex,carveBegin,
|
||||
carveEnd,lowerMouthIndex,mouthSearchBegin,mouthSearchEnd,
|
||||
@@ -2222,6 +2457,7 @@ public:
|
||||
}
|
||||
if(supportApplied)
|
||||
{
|
||||
l90IterationTimer.recordClearance();
|
||||
if(passageProfile)++passageProfile->passageOtherCapsuleChecks;
|
||||
if(PassageOtherCapsuleBlocksSupport(passageIndex,
|
||||
bestSupportSegmentIndex,qx,qy,qz,qPerMetre,capsuleClearanceMetres))
|
||||
@@ -4155,6 +4391,35 @@ private:
|
||||
std::vector<FPassageLaterBoreCandidate> candidates;
|
||||
std::vector<std::size_t> capsuleCandidatePassages;
|
||||
};
|
||||
struct FPassageCandidateBrickKey
|
||||
{
|
||||
std::int64_t x=0,y=0,z=0;
|
||||
bool operator==(const FPassageCandidateBrickKey&) const=default;
|
||||
};
|
||||
struct FPassageCandidateBrickKeyHash
|
||||
{
|
||||
std::size_t operator()(const FPassageCandidateBrickKey& key) const noexcept
|
||||
{
|
||||
const std::size_t hx=std::hash<std::int64_t>{}(key.x);
|
||||
const std::size_t hy=std::hash<std::int64_t>{}(key.y);
|
||||
const std::size_t hz=std::hash<std::int64_t>{}(key.z);
|
||||
return hx^(hy+static_cast<std::size_t>(0x9e3779b97f4a7c15ull)+(hx<<6)+(hx>>2))
|
||||
^(hz+static_cast<std::size_t>(0x9e3779b97f4a7c15ull)+(hy<<6)+(hy>>2));
|
||||
}
|
||||
};
|
||||
struct FPassageCandidateRef
|
||||
{
|
||||
std::uint32_t passageIndex=0;
|
||||
std::int32_t segmentIndex=-1;
|
||||
std::uint8_t featureKind=0;
|
||||
bool operator==(const FPassageCandidateRef&) const=default;
|
||||
};
|
||||
struct FPassageCandidateBrick
|
||||
{
|
||||
// carve, tread, clearance, mouth/column. Lists are complete for the
|
||||
// declared geometry and reach envelopes; vectors are never capped.
|
||||
std::array<std::vector<FPassageCandidateRef>,4> semantics;
|
||||
};
|
||||
struct FPassageProofNode
|
||||
{
|
||||
std::array<double,3> minimum{},maximum{};
|
||||
@@ -5208,6 +5473,9 @@ private:
|
||||
|
||||
void BuildPassageSpatialIndices()
|
||||
{
|
||||
L90PassageCandidateProgramEnabled=VF_IsL90PassageCandidateCensusEnabled();
|
||||
L90PassageCandidateBricks.clear();
|
||||
L90PassageCandidateProgramValid=false;
|
||||
BuildStampSpatialIndex();
|
||||
BuildCavernSpatialIndex();
|
||||
PassageTileFeatures.clear();PassageFeatureBounds.clear();
|
||||
@@ -5584,6 +5852,408 @@ private:
|
||||
||feature.minimumSubvoxels[axis]>feature.maximumSubvoxels[axis];
|
||||
if(!PassageFeatureOrder.empty())BuildPassageFeatureNode(0,PassageFeatureOrder.size());
|
||||
if(!PassageProofBounds.empty())BuildPassageProofNode(0,PassageProofBounds.size());
|
||||
if(L90PassageCandidateProgramEnabled)BuildPassageCandidateProgram();
|
||||
}
|
||||
|
||||
static FPassageCandidateBrickKey PassageCandidateBrickKeyFor(double qx,double qy,
|
||||
double qz,double brickEdgeSubvoxels)
|
||||
{
|
||||
return {static_cast<std::int64_t>(std::floor(qx/brickEdgeSubvoxels)),
|
||||
static_cast<std::int64_t>(std::floor(qy/brickEdgeSubvoxels)),
|
||||
static_cast<std::int64_t>(std::floor(qz/brickEdgeSubvoxels))};
|
||||
}
|
||||
|
||||
const FPassageCandidateBrick* FindPassageCandidateBrick(double qx,double qy,
|
||||
double qz) const
|
||||
{
|
||||
if(!L90PassageCandidateProgramValid)return nullptr;
|
||||
const auto key=PassageCandidateBrickKeyFor(qx,qy,qz,
|
||||
L90PassageCandidateBrickEdgeSubvoxels);
|
||||
const auto found=L90PassageCandidateBricks.find(key);
|
||||
return found==L90PassageCandidateBricks.end()?nullptr:&found->second;
|
||||
}
|
||||
|
||||
static bool PassageCandidateRefLess(const FPassageCandidateRef& a,
|
||||
const FPassageCandidateRef& b)
|
||||
{
|
||||
if(a.passageIndex!=b.passageIndex)return a.passageIndex<b.passageIndex;
|
||||
if(a.featureKind!=b.featureKind)return a.featureKind<b.featureKind;
|
||||
return a.segmentIndex<b.segmentIndex;
|
||||
}
|
||||
|
||||
bool PassageCandidateBrickContains(const FPassageCandidateBrick* brick,
|
||||
std::size_t semantic,std::size_t passageIndex,std::int32_t segmentIndex,
|
||||
std::uint8_t featureKind=0) const
|
||||
{
|
||||
if(!brick||semantic>=brick->semantics.size()
|
||||
||passageIndex>std::numeric_limits<std::uint32_t>::max())return false;
|
||||
const FPassageCandidateRef query{static_cast<std::uint32_t>(passageIndex),
|
||||
segmentIndex,featureKind};
|
||||
const auto& values=brick->semantics[semantic];
|
||||
return std::binary_search(values.begin(),values.end(),query,PassageCandidateRefLess);
|
||||
}
|
||||
|
||||
void BuildPassageCandidateProgram()
|
||||
{
|
||||
using FClock=std::chrono::steady_clock;
|
||||
const auto buildStart=FClock::now();
|
||||
L90PassageCandidateBricks.clear();
|
||||
L90PassageCandidateProgramValid=false;
|
||||
L90PassageCandidateBuildFailures=0;
|
||||
L90PassageCandidateReachMetres=10.0;
|
||||
constexpr double Q=static_cast<double>(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel);
|
||||
const double qPerMetre=Q*1000000.0/std::max<std::uint32_t>(1,
|
||||
ResolvedLayout->coreVoxelSizeMicrometres);
|
||||
const double voxelsPerMetre=qPerMetre/Q;
|
||||
L90PassageCandidateBrickEdgeSubvoxels=4.0*qPerMetre;
|
||||
const double reachSubvoxels=L90PassageCandidateReachMetres*qPerMetre;
|
||||
const double cubicPadding=1.0;
|
||||
std::array<std::uint64_t,4> emittedReferences{};
|
||||
const auto emit=[&](std::size_t semantic,const std::array<double,3>& minimum,
|
||||
const std::array<double,3>& maximum,const FPassageCandidateRef& reference)
|
||||
{
|
||||
if(semantic>=4||!std::isfinite(L90PassageCandidateBrickEdgeSubvoxels)
|
||||
||L90PassageCandidateBrickEdgeSubvoxels<=0.0)
|
||||
{++L90PassageCandidateBuildFailures;return;}
|
||||
FPassageCandidateBrickKey first{},last{};
|
||||
for(std::size_t axis=0;axis<3;++axis)
|
||||
{
|
||||
if(!std::isfinite(minimum[axis])||!std::isfinite(maximum[axis])
|
||||
||minimum[axis]>maximum[axis])
|
||||
{++L90PassageCandidateBuildFailures;return;}
|
||||
const long double cellMinimum=std::floor(static_cast<long double>(minimum[axis])
|
||||
/L90PassageCandidateBrickEdgeSubvoxels);
|
||||
const long double cellMaximum=std::floor(static_cast<long double>(maximum[axis])
|
||||
/L90PassageCandidateBrickEdgeSubvoxels);
|
||||
if(cellMinimum<static_cast<long double>(std::numeric_limits<std::int64_t>::min())
|
||||
||cellMinimum>static_cast<long double>(std::numeric_limits<std::int64_t>::max())
|
||||
||cellMaximum<static_cast<long double>(std::numeric_limits<std::int64_t>::min())
|
||||
||cellMaximum>static_cast<long double>(std::numeric_limits<std::int64_t>::max()))
|
||||
{++L90PassageCandidateBuildFailures;return;}
|
||||
const auto lo=static_cast<std::int64_t>(cellMinimum);
|
||||
const auto hi=static_cast<std::int64_t>(cellMaximum);
|
||||
if(axis==0){first.x=lo;last.x=hi;}
|
||||
else if(axis==1){first.y=lo;last.y=hi;}
|
||||
else {first.z=lo;last.z=hi;}
|
||||
}
|
||||
for(std::int64_t x=first.x;;++x)
|
||||
{
|
||||
for(std::int64_t y=first.y;;++y)
|
||||
{
|
||||
for(std::int64_t z=first.z;;++z)
|
||||
{
|
||||
auto& brick=L90PassageCandidateBricks[{x,y,z}];
|
||||
brick.semantics[semantic].push_back(reference);
|
||||
++emittedReferences[semantic];
|
||||
if(z==last.z)break;
|
||||
}
|
||||
if(y==last.y)break;
|
||||
}
|
||||
if(x==last.x)break;
|
||||
}
|
||||
};
|
||||
const auto pointBox=[&](const std::array<double,3>& centerMinimum,
|
||||
const std::array<double,3>& centerMaximum,double extentX,double extentY,
|
||||
double extentBelow,double extentAbove)
|
||||
{
|
||||
return std::pair<std::array<double,3>,std::array<double,3>>{
|
||||
{centerMinimum[0]-extentX,centerMinimum[1]-extentY,
|
||||
centerMinimum[2]-extentBelow},
|
||||
{centerMaximum[0]+extentX,centerMaximum[1]+extentY,
|
||||
centerMaximum[2]+extentAbove}};
|
||||
};
|
||||
|
||||
for(std::size_t passageIndex=0;passageIndex<ResolvedLayout->passages.size();
|
||||
++passageIndex)
|
||||
{
|
||||
const auto& passage=ResolvedLayout->passages[passageIndex];
|
||||
if(passage.controlPoints.size()<2)continue;
|
||||
const std::ptrdiff_t lastIndex=static_cast<std::ptrdiff_t>(
|
||||
passage.controlPoints.size())-1;
|
||||
const auto& constants=PassageComposerConstants[passageIndex];
|
||||
const std::ptrdiff_t lowerMouthIndex=constants.lowerMouthIndex;
|
||||
const auto pointArray=[&](std::ptrdiff_t index)
|
||||
{
|
||||
std::array<double,3> result{};
|
||||
const auto& point=passage.controlPoints[static_cast<std::size_t>(index)];
|
||||
for(std::size_t axis=0;axis<3;++axis)
|
||||
result[axis]=static_cast<double>(point.positionSubvoxels[axis]);
|
||||
return result;
|
||||
};
|
||||
for(std::size_t segment=0;segment+1<passage.controlPoints.size();++segment)
|
||||
{
|
||||
const auto& a=passage.controlPoints[segment];
|
||||
const auto& b=passage.controlPoints[segment+1];
|
||||
const double dx=static_cast<double>(b.positionSubvoxels[0])-a.positionSubvoxels[0];
|
||||
const double dy=static_cast<double>(b.positionSubvoxels[1])-a.positionSubvoxels[1];
|
||||
const double dz=static_cast<double>(b.positionSubvoxels[2])-a.positionSubvoxels[2];
|
||||
const double length=DeterministicSqrt(dx*dx+dy*dy+dz*dz);
|
||||
const bool smoothCarve=static_cast<std::ptrdiff_t>(segment)>2
|
||||
&&static_cast<std::ptrdiff_t>(segment)
|
||||
+(constants.internalChainPassage?2:12)<lowerMouthIndex
|
||||
&&length<=8.0*qPerMetre
|
||||
&&!IsPassageShortSpanSegment(passageIndex,segment-1)
|
||||
&&!IsPassageShortSpanSegment(passageIndex,segment)
|
||||
&&!IsPassageShortSpanSegment(passageIndex,segment+1);
|
||||
const auto p0=pointArray(smoothCarve
|
||||
?static_cast<std::ptrdiff_t>(segment)-1
|
||||
:static_cast<std::ptrdiff_t>(segment));
|
||||
const auto p1=pointArray(static_cast<std::ptrdiff_t>(segment));
|
||||
const auto p2=pointArray(static_cast<std::ptrdiff_t>(segment)+1);
|
||||
const auto p3=pointArray(smoothCarve
|
||||
?static_cast<std::ptrdiff_t>(segment)+2
|
||||
:static_cast<std::ptrdiff_t>(segment)+1);
|
||||
const auto supportP0=pointArray(segment>0
|
||||
?static_cast<std::ptrdiff_t>(segment)-1
|
||||
:static_cast<std::ptrdiff_t>(segment));
|
||||
const auto supportP3=pointArray(segment+2<passage.controlPoints.size()
|
||||
?static_cast<std::ptrdiff_t>(segment)+2
|
||||
:static_cast<std::ptrdiff_t>(segment)+1);
|
||||
const auto catmullRange=[&](std::size_t axis,double t0,double t1)
|
||||
{
|
||||
const double c0=p1[axis];
|
||||
const double c1=0.5*(-supportP0[axis]+p2[axis]);
|
||||
const double c2=0.5*(2.0*supportP0[axis]-5.0*p1[axis]
|
||||
+4.0*p2[axis]-supportP3[axis]);
|
||||
const double c3=0.5*(-supportP0[axis]+3.0*p1[axis]
|
||||
-3.0*p2[axis]+supportP3[axis]);
|
||||
const auto evaluate=[&](double t)
|
||||
{return ((c3*t+c2)*t+c1)*t+c0;};
|
||||
double low=std::min(evaluate(t0),evaluate(t1));
|
||||
double high=std::max(evaluate(t0),evaluate(t1));
|
||||
const double qa=3.0*c3,qb=2.0*c2,qc=c1;
|
||||
const auto includeRoot=[&](double t)
|
||||
{
|
||||
if(t<=t0||t>=t1||!std::isfinite(t))return;
|
||||
const double value=evaluate(t);
|
||||
low=std::min(low,value);high=std::max(high,value);
|
||||
};
|
||||
if(std::abs(qa)<=1.0e-12)
|
||||
{if(std::abs(qb)>1.0e-12)includeRoot(-qc/qb);}
|
||||
else
|
||||
{
|
||||
const double discriminant=qb*qb-4.0*qa*qc;
|
||||
if(discriminant>=0.0)
|
||||
{
|
||||
const double root=std::sqrt(discriminant);
|
||||
includeRoot((-qb-root)/(2.0*qa));
|
||||
includeRoot((-qb+root)/(2.0*qa));
|
||||
}
|
||||
}
|
||||
return std::pair<double,double>{low-cubicPadding,high+cubicPadding};
|
||||
};
|
||||
std::array<double,3> curveMinimum{},curveMaximum{};
|
||||
for(std::size_t axis=0;axis<3;++axis)
|
||||
{
|
||||
const double c0=p1[axis];
|
||||
const double c1=0.5*(-p0[axis]+p2[axis]);
|
||||
const double c2=0.5*(2.0*p0[axis]-5.0*p1[axis]
|
||||
+4.0*p2[axis]-p3[axis]);
|
||||
const double c3=0.5*(-p0[axis]+3.0*p1[axis]
|
||||
-3.0*p2[axis]+p3[axis]);
|
||||
const auto evaluate=[&](double t)
|
||||
{return smoothCarve?((c3*t+c2)*t+c1)*t+c0:p1[axis]+t*(p2[axis]-p1[axis]);};
|
||||
double low=std::min(evaluate(0.0),evaluate(1.0));
|
||||
double high=std::max(evaluate(0.0),evaluate(1.0));
|
||||
const double qa=3.0*c3,qb=2.0*c2,qc=c1;
|
||||
const auto includeRoot=[&](double t)
|
||||
{
|
||||
if(t<=0.0||t>=1.0||!std::isfinite(t))return;
|
||||
const double value=evaluate(t);
|
||||
low=std::min(low,value);high=std::max(high,value);
|
||||
};
|
||||
if(smoothCarve)
|
||||
{
|
||||
if(std::abs(qa)<=1.0e-12)
|
||||
{if(std::abs(qb)>1.0e-12)includeRoot(-qc/qb);}
|
||||
else
|
||||
{
|
||||
const double discriminant=qb*qb-4.0*qa*qc;
|
||||
if(discriminant>=0.0)
|
||||
{
|
||||
const double root=std::sqrt(discriminant);
|
||||
includeRoot((-qb-root)/(2.0*qa));
|
||||
includeRoot((-qb+root)/(2.0*qa));
|
||||
}
|
||||
}
|
||||
}
|
||||
curveMinimum[axis]=low-cubicPadding;
|
||||
curveMaximum[axis]=high+cubicPadding;
|
||||
}
|
||||
const double radius=static_cast<double>(std::max(a.radiusSubvoxels,
|
||||
b.radiusSubvoxels));
|
||||
const double carveExtent=radius+reachSubvoxels
|
||||
+(0.21+0.20)*qPerMetre+cubicPadding;
|
||||
const auto carveBox=pointBox(curveMinimum,curveMaximum,
|
||||
carveExtent,carveExtent,carveExtent,carveExtent);
|
||||
const FPassageCandidateRef routeReference{
|
||||
static_cast<std::uint32_t>(passageIndex),
|
||||
static_cast<std::int32_t>(segment),0};
|
||||
emit(0,carveBox.first,carveBox.second,routeReference);
|
||||
|
||||
// Treads extend by up to two metres through route knots, then project
|
||||
// across a 1.55 m half-width with relief. Include Catmull support-height
|
||||
// overshoot, extrapolated radius, and the full source-dependent floor band.
|
||||
const auto supportZCurve=catmullRange(2,-0.5,1.5);
|
||||
const double supportLinearMinimum=std::min(p1[2],p2[2])-0.5*std::abs(dz);
|
||||
const double supportLinearMaximum=std::max(p1[2],p2[2])+0.5*std::abs(dz);
|
||||
const double supportRadiusMaximum=radius
|
||||
+0.5*std::abs(static_cast<double>(b.radiusSubvoxels)
|
||||
-static_cast<double>(a.radiusSubvoxels))+cubicPadding;
|
||||
std::array<double,3> treadMinimum={
|
||||
std::min(p1[0],p2[0])-4.25*qPerMetre,
|
||||
std::min(p1[1],p2[1])-4.25*qPerMetre,
|
||||
std::min(supportLinearMinimum,supportZCurve.first)
|
||||
-supportRadiusMaximum-2.5*qPerMetre};
|
||||
std::array<double,3> treadMaximum={
|
||||
std::max(p1[0],p2[0])+4.25*qPerMetre,
|
||||
std::max(p1[1],p2[1])+4.25*qPerMetre,
|
||||
std::max(supportLinearMaximum,supportZCurve.second)+2.5*qPerMetre};
|
||||
emit(1,treadMinimum,treadMaximum,routeReference);
|
||||
|
||||
// Player capsules use horizontal projection, a 0.34 m radius, and a
|
||||
// 0.54 m axial half-length above the route floor.
|
||||
std::array<double,3> clearanceMinimum={
|
||||
std::min(p1[0],p2[0])-0.40*qPerMetre,
|
||||
std::min(p1[1],p2[1])-0.40*qPerMetre,
|
||||
std::min(p1[2],p2[2])-radius+0.20*qPerMetre};
|
||||
std::array<double,3> clearanceMaximum={
|
||||
std::max(p1[0],p2[0])+0.40*qPerMetre,
|
||||
std::max(p1[1],p2[1])+0.40*qPerMetre,
|
||||
std::max(p1[2],p2[2])+2.06*qPerMetre};
|
||||
emit(2,clearanceMinimum,clearanceMaximum,routeReference);
|
||||
|
||||
const std::ptrdiff_t index=static_cast<std::ptrdiff_t>(segment);
|
||||
const bool mouthCarve=index<std::min<std::ptrdiff_t>(8,lowerMouthIndex)
|
||||
||(index>=constants.mouthSearchBegin&&index<constants.mouthSearchEnd)
|
||||
||index>=lowerMouthIndex;
|
||||
if(mouthCarve)
|
||||
emit(3,carveBox.first,carveBox.second,{routeReference.passageIndex,
|
||||
routeReference.segmentIndex,1});
|
||||
}
|
||||
|
||||
// The upper-mouth support exception has a larger spatial envelope than the
|
||||
// tunnel body and remains an explicit compiled operation.
|
||||
const auto& upperMouth=passage.upperMouthSubvoxels;
|
||||
const double upperReach=15.0*qPerMetre,upperZReach=8.0*qPerMetre;
|
||||
emit(3,{upperMouth[0]-upperReach,upperMouth[1]-upperReach,
|
||||
upperMouth[2]-upperZReach},
|
||||
{upperMouth[0]+upperReach,upperMouth[1]+upperReach,
|
||||
upperMouth[2]+upperZReach},
|
||||
{static_cast<std::uint32_t>(passageIndex),-1,0});
|
||||
|
||||
if(passageIndex<PassageMouthSupportRoutes.size())
|
||||
{
|
||||
const auto& route=PassageMouthSupportRoutes[passageIndex];
|
||||
for(std::size_t segment=0;segment<route.size();++segment)
|
||||
{
|
||||
const auto& source=route[segment];
|
||||
emit(3,
|
||||
{std::min(source.a[0],source.b[0])-1.01*qPerMetre,
|
||||
std::min(source.a[1],source.b[1])-1.01*qPerMetre,
|
||||
std::min(source.a[2],source.b[2])-2.58*qPerMetre},
|
||||
{std::max(source.a[0],source.b[0])+1.01*qPerMetre,
|
||||
std::max(source.a[1],source.b[1])+1.01*qPerMetre,
|
||||
std::max(source.a[2],source.b[2])-0.68*qPerMetre},
|
||||
{static_cast<std::uint32_t>(passageIndex),
|
||||
static_cast<std::int32_t>(segment),2});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for(std::size_t crossingIndex=0;crossingIndex<PassageVoidCrossings.size();
|
||||
++crossingIndex)
|
||||
{
|
||||
const auto& crossing=PassageVoidCrossings[crossingIndex];
|
||||
if(crossing.passageIndex>=ResolvedLayout->passages.size())continue;
|
||||
if(crossing.needsColumn)
|
||||
{
|
||||
const double radius=static_cast<double>(crossing.maximumRadiusSubvoxels)
|
||||
+reachSubvoxels;
|
||||
emit(3,{crossing.centerXSubvoxels-radius,
|
||||
crossing.centerYSubvoxels-radius,
|
||||
static_cast<double>(crossing.bottomZSubvoxels)-reachSubvoxels},
|
||||
{crossing.centerXSubvoxels+radius,
|
||||
crossing.centerYSubvoxels+radius,
|
||||
static_cast<double>(crossing.topZSubvoxels)+reachSubvoxels},
|
||||
{static_cast<std::uint32_t>(crossing.passageIndex),
|
||||
static_cast<std::int32_t>(crossing.firstSegment),3});
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto& passage=ResolvedLayout->passages[crossing.passageIndex];
|
||||
bool surfaceWorldUpper=false;
|
||||
const std::int32_t upperStrate=passage.upperStrateIndex;
|
||||
const auto upper=std::find_if(ResolvedLayout->strates.begin(),
|
||||
ResolvedLayout->strates.end(),[upperStrate](const auto& value)
|
||||
{return value.strateIndex==upperStrate;});
|
||||
surfaceWorldUpper=upper!=ResolvedLayout->strates.end()
|
||||
&&upper->passageCapabilities.hasSurfaceLanding;
|
||||
const std::size_t shortSpanPadding=surfaceWorldUpper?2:0;
|
||||
const std::size_t first=std::min(crossing.firstSegment>shortSpanPadding
|
||||
?crossing.firstSegment-shortSpanPadding:0,
|
||||
passage.controlPoints.size()-1);
|
||||
const std::size_t last=std::min(crossing.lastSegment+shortSpanPadding,
|
||||
passage.controlPoints.size()-2);
|
||||
for(std::size_t segment=first;segment<=last;++segment)
|
||||
{
|
||||
const auto& a=passage.controlPoints[segment];
|
||||
const auto& b=passage.controlPoints[segment+1];
|
||||
const double radius=static_cast<double>(std::max(a.radiusSubvoxels,
|
||||
b.radiusSubvoxels))+3.8*qPerMetre;
|
||||
emit(3,{std::min(a.positionSubvoxels[0],b.positionSubvoxels[0])-radius,
|
||||
std::min(a.positionSubvoxels[1],b.positionSubvoxels[1])-radius,
|
||||
std::min(a.positionSubvoxels[2],b.positionSubvoxels[2])-5.0*qPerMetre},
|
||||
{std::max(a.positionSubvoxels[0],b.positionSubvoxels[0])+radius,
|
||||
std::max(a.positionSubvoxels[1],b.positionSubvoxels[1])+radius,
|
||||
std::max(a.positionSubvoxels[2],b.positionSubvoxels[2])+1.0*qPerMetre},
|
||||
{static_cast<std::uint32_t>(crossing.passageIndex),
|
||||
static_cast<std::int32_t>(segment),4});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for(auto& [key,brick]:L90PassageCandidateBricks)
|
||||
{
|
||||
(void)key;
|
||||
for(auto& list:brick.semantics)
|
||||
{
|
||||
std::sort(list.begin(),list.end(),PassageCandidateRefLess);
|
||||
list.erase(std::unique(list.begin(),list.end()),list.end());
|
||||
}
|
||||
}
|
||||
std::array<std::uint64_t,4> uniqueReferences{};
|
||||
std::size_t listCapacityBytes=0;
|
||||
for(const auto& [key,brick]:L90PassageCandidateBricks)
|
||||
{
|
||||
(void)key;
|
||||
for(std::size_t semantic=0;semantic<brick.semantics.size();++semantic)
|
||||
{
|
||||
uniqueReferences[semantic]+=brick.semantics[semantic].size();
|
||||
listCapacityBytes+=brick.semantics[semantic].capacity()
|
||||
*sizeof(FPassageCandidateRef);
|
||||
}
|
||||
}
|
||||
const std::size_t estimatedBytes=L90PassageCandidateBricks.bucket_count()
|
||||
*sizeof(void*)+L90PassageCandidateBricks.size()
|
||||
*(sizeof(FPassageCandidateBrickKey)+sizeof(FPassageCandidateBrick)
|
||||
+2*sizeof(void*))+listCapacityBytes;
|
||||
L90PassageCandidateProgramValid=L90PassageCandidateBuildFailures==0;
|
||||
const double buildMilliseconds=std::chrono::duration<double,std::milli>(
|
||||
FClock::now()-buildStart).count();
|
||||
std::printf("[L90PassageCandidateBuild] brick_m=4 reach_m=%.1f bricks=%zu emitted=(%llu,%llu,%llu,%llu) unique=(%llu,%llu,%llu,%llu) bytes_estimate=%zu build_ms=%.3f failures=%llu valid=%d\n",
|
||||
L90PassageCandidateReachMetres,L90PassageCandidateBricks.size(),
|
||||
static_cast<unsigned long long>(emittedReferences[0]),
|
||||
static_cast<unsigned long long>(emittedReferences[1]),
|
||||
static_cast<unsigned long long>(emittedReferences[2]),
|
||||
static_cast<unsigned long long>(emittedReferences[3]),
|
||||
static_cast<unsigned long long>(uniqueReferences[0]),
|
||||
static_cast<unsigned long long>(uniqueReferences[1]),
|
||||
static_cast<unsigned long long>(uniqueReferences[2]),
|
||||
static_cast<unsigned long long>(uniqueReferences[3]),
|
||||
estimatedBytes,buildMilliseconds,
|
||||
static_cast<unsigned long long>(L90PassageCandidateBuildFailures),
|
||||
static_cast<int>(L90PassageCandidateProgramValid));
|
||||
}
|
||||
|
||||
double PassageNearestNodeLowerBound(const FPassageNearestNode& node,
|
||||
@@ -5854,6 +6524,12 @@ private:
|
||||
+vertical*vertical)-0.34*qPerMetre;
|
||||
if(distance<minimumClearanceMetres*qPerMetre)
|
||||
{
|
||||
const auto* l90TileQueries=ActivePassageTileQueries;
|
||||
if(l90TileQueries&&l90TileQueries->matches(*this)
|
||||
&&l90TileQueries->candidateCensusProfiled())
|
||||
l90TileQueries->recordL90ProofCheck(2,
|
||||
PassageCandidateBrickContains(ActivePassageCandidateBrick,2,
|
||||
passageIndex,static_cast<std::int32_t>(index)));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -12751,4 +13427,12 @@ private:
|
||||
std::vector<FPassageProofNode> PassageProofNodes;
|
||||
std::vector<std::vector<std::uint8_t>> PassageColumnSegmentFlags,PassageShortSpanSegmentFlags;
|
||||
std::vector<std::uint8_t> PassageHasShortSpanFlags;
|
||||
bool L90PassageCandidateProgramEnabled=false;
|
||||
bool L90PassageCandidateProgramValid=false;
|
||||
double L90PassageCandidateBrickEdgeSubvoxels=0.0;
|
||||
double L90PassageCandidateReachMetres=0.0;
|
||||
std::uint64_t L90PassageCandidateBuildFailures=0;
|
||||
std::unordered_map<FPassageCandidateBrickKey,FPassageCandidateBrick,
|
||||
FPassageCandidateBrickKeyHash> L90PassageCandidateBricks;
|
||||
inline static thread_local const FPassageCandidateBrick* ActivePassageCandidateBrick=nullptr;
|
||||
};
|
||||
Reference in new issue
Block a user