passages: two-pass swept-tube tunnels (owner design, L99, v215) - pending owner visual review

Route plan unchanged. Per tile, route segments whose bounded influence (tube + 4 m) touches the
tile write min-combined tube distance (base sweep); relief/floor/mouth shaping only in the shell
(detail sweep); 0.5 m edge blend into the strate field. Near-ready @W12: SW 1.01->0.80,
FI 2.23->1.43, TN 2.18->1.37, VS 1.09->0.98 s; whole view FI 3.38->2.37 s. All passage walk
audits pass (21,425/21,425 canonical, 3/3 interfaces); 24-run Clang/GCC W1/W8/W12 determinism;
0 sign flips. Change: mean 0.02 cm / 0.05 deg; max 25.9 cm (passage walls/roofs ~3.5 m from
route, LOD2) and 49.8 deg (rock walls 13-25 m from routes, LOD2). Captures with diffs in
Linux99/captures. Fast tier PASS (v215 pins).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
This commit is contained in:
Fr0zkaandClaude Opus 5.5 committed 2026-10-28 14:38:45 +01:00
1 parent 36140b573f
commit fa6363b31a
67 files changed
+2022 -45

No files matched your search

+451 -31
View File
@@ -100,16 +100,9 @@ static double VF_L91PassageSourceFloorVoxels()
static double VF_L93PassageCarveReachMetres()
{
static const double Value=[]
{
const char* Raw=std::getenv("VOXELFORGE_L93_PASSAGE_CARVE_REACH_METRES");
if(!Raw||!*Raw)return 0.0;
char* End=nullptr;
const double Parsed=std::strtod(Raw,&End);
return End!=Raw&&*End=='\0'&&std::isfinite(Parsed)&&Parsed>0.0
?Parsed:0.0;
}();
return Value;
// The generated world uses a fixed bounded tube influence. An explicit diagnostic
// option can still select the legacy point-evaluation path for comparisons.
return 4.0;
}
// Standalone-only world compiler. It compiles the runtime room/tunnel/passages into
@@ -259,6 +252,7 @@ public:
{
std::size_t segmentIndex=0;
std::array<std::int64_t,3> minimumSubvoxels{},maximumSubvoxels{};
std::array<std::int64_t,3> sweepMinimumSubvoxels{},sweepMaximumSubvoxels{};
};
struct FSegmentRangeCache
{
@@ -280,6 +274,31 @@ public:
std::uint64_t generation=0;
double numerator=0.0;
};
struct FPassageSweepSample
{
double signedDistance=std::numeric_limits<double>::infinity();
double t=0.0;
std::uint32_t segmentIndex=std::numeric_limits<std::uint32_t>::max();
float wallRelief=0.0f,mouthSurfaceRelief=0.0f,mouthTreadFieldRelief=0.0f;
bool valid=false,hasWallRelief=false,hasMouthSurfaceRelief=false,
hasMouthTreadFieldRelief=false;
};
struct FPassageTileSweepData
{
struct FPassageSamples
{
std::size_t passageIndex=0;
std::vector<FPassageSweepSample> samples;
};
std::array<std::int64_t,3> gridMinimumSubvoxels{};
std::int64_t stepSubvoxels=0;
std::int32_t dimension=0;
double influenceSubvoxels=0.0;
std::vector<std::int32_t> passageToSweep;
std::vector<FPassageSamples> passages;
std::atomic<std::uint64_t> lookups{0},cacheHits{0},baseInfluenceHits{0},
outOfInfluenceHits{0},missingSamples{0};
};
FScopedPassageTileQueries(const FStandaloneShapeWorld& shape,
const std::array<std::int64_t,3>& minimumSubvoxels,
const std::array<std::int64_t,3>& maximumSubvoxels,
@@ -319,6 +338,7 @@ public:
? shape.ResolvedLayout->passages.size() : 0, 0);
passageColumns_=passageRoutes_;
passageMouthSupports_=passageRoutes_;
passageRouteSegments_.resize(passageRoutes_.size());
passageMouthSupportSegments_.resize(passageRoutes_.size());
passageSegmentRangeCaches_.resize(passageRoutes_.size());
if(!disablePassageComposition&&shape.ResolvedLayout
@@ -367,8 +387,20 @@ public:
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};
FCandidateSegment candidate;
candidate.segmentIndex=feature.segmentIndex;
candidate.minimumSubvoxels=featureBounds.minimumSubvoxels;
candidate.maximumSubvoxels=featureBounds.maximumSubvoxels;
if(!feature.bColumn&&!feature.bMouthSupport
&&feature.passageIndex<shape.PassageSegmentData.size()
&&feature.segmentIndex<shape.PassageSegmentData[
feature.passageIndex].size())
{
const auto& segment=shape.PassageSegmentData[feature.passageIndex][
feature.segmentIndex];
candidate.sweepMinimumSubvoxels=segment.sweepMinimumSubvoxels;
candidate.sweepMaximumSubvoxels=segment.sweepMaximumSubvoxels;
}
if(feature.bColumn)passageColumns_[feature.passageIndex]=1;
else if(feature.bMouthSupport)
{
@@ -378,10 +410,18 @@ public:
else
{
passageRoutes_[feature.passageIndex]=1;
passageRouteSegments_[feature.passageIndex].push_back(candidate);
++routeSegmentCandidateCount_;
routeFeatureOverlap_=true;
}
}
for(auto& values:passageRouteSegments_)
{
std::sort(values.begin(),values.end(),[](const auto& a,const auto& b)
{return a.segmentIndex<b.segmentIndex;});
values.erase(std::unique(values.begin(),values.end(),[](const auto& a,const auto& b)
{return a.segmentIndex==b.segmentIndex;}),values.end());
}
for(auto& values:passageMouthSupportSegments_)
{
std::sort(values.begin(),values.end(),[](const auto& a,const auto& b)
@@ -405,6 +445,7 @@ public:
stampIndices_(source.stampIndices_),cavernFeatureIndices_(source.cavernFeatureIndices_),
passageRoutes_(source.passageRoutes_),passageColumns_(source.passageColumns_),
passageMouthSupports_(source.passageMouthSupports_),
passageRouteSegments_(source.passageRouteSegments_),
passageMouthSupportSegments_(source.passageMouthSupportSegments_),
passageSegmentRangeCaches_(source.passageSegmentRangeCaches_.size()),
upperMouthSupportPassages_(source.upperMouthSupportPassages_),
@@ -412,10 +453,13 @@ public:
routeFeatureOverlap_(source.routeFeatureOverlap_),columnZOverlap_(source.columnZOverlap_),
mayContainResolvedBoundaryBand_(source.mayContainResolvedBoundaryBand_),
mayContainTransitionPassageFade_(source.mayContainTransitionPassageFade_),
previous_(ActivePassageTileQueries),profileLod_(source.profileLod_),
previous_(ActivePassageTileQueries),profileStats_(source.profileStats_),
profileLod_(source.profileLod_),
candidateProfiled_(source.candidateProfiled_),
passageSourceFloorVoxels_(source.passageSourceFloorVoxels_),
passageCarveReachMetres_(source.passageCarveReachMetres_),
passageSweepData_(source.passageSweepData_),
ownsPassageSweepDiagnostics_(false),
clampCounters_(source.clampCounters_),ownsClampCounters_(false),
projectionCacheSize_(source.projectionCacheSize_),
reuseCounters_(source.reuseCounters_),ownsReuseCounters_(false)
@@ -423,6 +467,19 @@ public:
~FScopedPassageTileQueries()
{
ActivePassageTileQueries=previous_;
if(ownsPassageSweepDiagnostics_&&profileStats_&&passageSweepData_)
{
profileStats_->passageSweepLookups=passageSweepData_->lookups.load(
std::memory_order_relaxed);
profileStats_->passageSweepCacheHits=passageSweepData_->cacheHits.load(
std::memory_order_relaxed);
profileStats_->passageSweepBaseInfluenceHits=
passageSweepData_->baseInfluenceHits.load(std::memory_order_relaxed);
profileStats_->passageSweepOutOfInfluenceHits=
passageSweepData_->outOfInfluenceHits.load(std::memory_order_relaxed);
profileStats_->passageSweepMissingSamples=passageSweepData_->missingSamples.load(
std::memory_order_relaxed);
}
if(ownsReuseCounters_&&reuseCounters_)
std::printf("[L98PassageReuseTile] lod=%d projection_columns=%llu projection_computes=%llu projection_hits=%llu floor_profile_reads=%llu carve_prepared=%llu support_prepared=%llu capsule_prepared=%llu mouth_prepared=%llu\n",
profileLod_,
@@ -500,6 +557,25 @@ public:
FScopedPassageTileQueries& operator=(const FScopedPassageTileQueries&)=delete;
bool matches(const FStandaloneShapeWorld& shape) const{return shape_==&shape;}
void prepareSweptTubeBase(const FIntVector& origin,std::int32_t step,
std::int32_t cells,std::uint32_t seed);
const FPassageSweepSample* sweptSample(std::size_t passageIndex,
double qx,double qy,double qz) const;
bool hasSweptTubeBase() const noexcept{return passageSweepData_!=nullptr;}
void copySweptTubeRuntimeCounts() const noexcept
{
if(!profileStats_||!passageSweepData_)return;
profileStats_->passageSweepLookups=passageSweepData_->lookups.load(
std::memory_order_relaxed);
profileStats_->passageSweepCacheHits=passageSweepData_->cacheHits.load(
std::memory_order_relaxed);
profileStats_->passageSweepBaseInfluenceHits=
passageSweepData_->baseInfluenceHits.load(std::memory_order_relaxed);
profileStats_->passageSweepOutOfInfluenceHits=
passageSweepData_->outOfInfluenceHits.load(std::memory_order_relaxed);
profileStats_->passageSweepMissingSamples=passageSweepData_->missingSamples.load(
std::memory_order_relaxed);
}
bool beginProjectionColumn() const
{
const bool previous=projectionColumnActive_;
@@ -670,6 +746,7 @@ 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>> passageRouteSegments_;
std::vector<std::vector<FCandidateSegment>> passageMouthSupportSegments_;
mutable std::vector<std::array<FSegmentRangeCache,4>> passageSegmentRangeCaches_;
std::vector<std::size_t> upperMouthSupportPassages_;
@@ -683,6 +760,8 @@ public:
bool candidateProfiled_=false;
double passageSourceFloorVoxels_=0.0;
double passageCarveReachMetres_=0.0;
std::shared_ptr<FPassageTileSweepData> passageSweepData_;
bool ownsPassageSweepDiagnostics_=true;
mutable std::shared_ptr<FPassageSourceClampCounters> clampCounters_;
bool ownsClampCounters_=true;
std::size_t projectionCacheSize_=0;
@@ -1761,6 +1840,8 @@ public:
const bool surfaceToTunnelSupport=passageConstants.surfaceToTunnelSupport;
const double firstFloorQ=passageConstants.firstFloorQ;
const double lastFloorQ=passageConstants.lastFloorQ;
if(!passageTileScopeMatches
||passageTileQueries->hasFeature(passageIndex,true))
for(const auto& crossing:PassageVoidCrossings)
{
if(!crossing.needsColumn||crossing.passageIndex!=passageIndex
@@ -1829,6 +1910,14 @@ public:
if(l90CandidateProfiled)recordColumnProof(columnDistance,composed);
composed=std::min(composed,static_cast<float>(columnDistance/Q));
}
const bool usePreparedPassageSweep=passageTileScopeMatches
&&passageCarveReachEnabled&&passageTileQueries->hasSweptTubeBase();
const auto* preparedSweepSample=usePreparedPassageSweep
?passageTileQueries->sweptSample(passageIndex,qx,qy,qz):nullptr;
// The tile sweep has already visited every segment whose bounded route or
// support envelope reaches this lattice point. No route query can affect it.
if(usePreparedPassageSweep&&(!preparedSweepSample
||preparedSweepSample->signedDistance>passageCarveReachQ))continue;
const bool tilePassageFree=passageTileScopeMatches
&&!ActivePassageTileQueries->hasFeature(passageIndex,false);
if(composed>=0.0f&&tilePassageFree)
@@ -2017,7 +2106,15 @@ public:
// the long descent and inspect both endpoint neighborhoods directly.
// The route-bound landing can rise and fall after the lower mouth. Scan
// that short tail directly; binary search is only valid on the descent.
if(passageTileScopeMatches)
if(usePreparedPassageSweep)
{
bestSignedDistance=preparedSweepSample->signedDistance;
bestT=preparedSweepSample->t;
bestIndex=static_cast<std::ptrdiff_t>(preparedSweepSample->segmentIndex);
bestScanPhase=0;
bestScanIndex=bestIndex;
}
else if(passageTileScopeMatches)
{
VisitNearestPassageSegmentRange(passageIndex,carveBegin,carveEnd,qx,qy,qz,
0,bestSignedDistance,considerCarveSegment);
@@ -2065,10 +2162,16 @@ public:
{
if(!hasCachedWallRelief)
{
cachedWallRelief=VF_FieldPassageWallRelief(static_cast<float>(X),
static_cast<float>(Y),static_cast<float>(Z),passageReliefSeed);
if(usePreparedPassageSweep)
cachedWallRelief=preparedSweepSample->hasWallRelief
?preparedSweepSample->wallRelief:0.0f;
else
{
cachedWallRelief=VF_FieldPassageWallRelief(static_cast<float>(X),
static_cast<float>(Y),static_cast<float>(Z),passageReliefSeed);
if(passageProfile)++passageProfile->passageReliefFieldCalls;
}
hasCachedWallRelief=true;
if(passageProfile)++passageProfile->passageReliefFieldCalls;
}
return cachedWallRelief;
};
@@ -2076,11 +2179,17 @@ public:
{
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);
if(usePreparedPassageSweep
&&preparedSweepSample->hasMouthSurfaceRelief)
cachedMouthSurfaceRelief=preparedSweepSample->mouthSurfaceRelief;
else
{
cachedMouthSurfaceRelief=VF_FieldSurfaceRelief(
static_cast<float>(X)*1.5f,static_cast<float>(Y)*1.5f,
static_cast<float>(Z)*1.5f,passageReliefSeed^0x4D4F5554u);
if(passageProfile)++passageProfile->passageReliefFieldCalls;
}
hasCachedMouthSurfaceRelief=true;
if(passageProfile)++passageProfile->passageReliefFieldCalls;
}
return cachedMouthSurfaceRelief;
};
@@ -2088,11 +2197,18 @@ public:
{
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);
if(usePreparedPassageSweep
&&preparedSweepSample->hasMouthTreadFieldRelief)
cachedMouthTreadFieldRelief=
preparedSweepSample->mouthTreadFieldRelief;
else
{
cachedMouthTreadFieldRelief=VF_FieldSurfaceRelief(
static_cast<float>(X)*0.75f,static_cast<float>(Y)*0.75f,
static_cast<float>(Z)*0.75f,passageReliefSeed^0x54485244u);
if(passageProfile)++passageProfile->passageReliefFieldCalls;
}
hasCachedMouthTreadFieldRelief=true;
if(passageProfile)++passageProfile->passageReliefFieldCalls;
}
return cachedMouthTreadFieldRelief;
};
@@ -4597,6 +4713,7 @@ private:
struct FPassageSegmentData
{
std::array<double,3> a{},b{},delta{},catmullBefore{},catmullAfter{};
std::array<std::int64_t,3> sweepMinimumSubvoxels{},sweepMaximumSubvoxels{};
double radiusA=0.0,radiusB=0.0,radiusDelta=0.0;
double catmullRadiusBefore=0.0,catmullRadiusAfter=0.0;
double lengthSq=0.0,horizontalLengthSq=0.0;
@@ -5944,6 +6061,14 @@ private:
-std::numeric_limits<double>::infinity(),
-std::numeric_limits<double>::infinity(),
-std::numeric_limits<double>::infinity()};
std::array<double,3> tubeMinimum={
std::numeric_limits<double>::infinity(),
std::numeric_limits<double>::infinity(),
std::numeric_limits<double>::infinity()};
std::array<double,3> tubeMaximum={
-std::numeric_limits<double>::infinity(),
-std::numeric_limits<double>::infinity(),
-std::numeric_limits<double>::infinity()};
const auto& p0=bMayUseSmoothCarve
?passage.controlPoints[segment-1]:a;
const auto& p3=bMayUseSmoothCarve
@@ -6019,17 +6144,37 @@ private:
{
featureMinimum[axis]=std::min(featureMinimum[axis],proof.minimum[axis]);
featureMaximum[axis]=std::max(featureMaximum[axis],proof.maximum[axis]);
tubeMinimum[axis]=std::min(tubeMinimum[axis],
centerMinimum[axis]-relievedRadius);
tubeMaximum[axis]=std::max(tubeMaximum[axis],
centerMaximum[axis]+relievedRadius);
}
PassageProofBounds.push_back(proof);
}
auto& preparedSegment=PassageSegmentData[passageIndex][segment];
for(std::size_t axis=0;axis<3;++axis)
{
preparedSegment.sweepMinimumSubvoxels[axis]=
static_cast<std::int64_t>(std::floor(
(tubeMinimum[axis]-4.0*voxelsPerMetre)*Q));
preparedSegment.sweepMaximumSubvoxels[axis]=
static_cast<std::int64_t>(std::ceil(
(tubeMaximum[axis]+4.0*voxelsPerMetre)*Q));
}
const std::array<std::int64_t,3> featureMin={
static_cast<std::int64_t>(std::floor(featureMinimum[0]*Q)),
static_cast<std::int64_t>(std::floor(featureMinimum[1]*Q)),
static_cast<std::int64_t>(std::floor(featureMinimum[2]*Q))};
static_cast<std::int64_t>(std::floor(
(featureMinimum[0]-4.0*voxelsPerMetre)*Q)),
static_cast<std::int64_t>(std::floor(
(featureMinimum[1]-4.0*voxelsPerMetre)*Q)),
static_cast<std::int64_t>(std::floor(
(featureMinimum[2]-4.0*voxelsPerMetre)*Q))};
const std::array<std::int64_t,3> featureMax={
static_cast<std::int64_t>(std::ceil(featureMaximum[0]*Q)),
static_cast<std::int64_t>(std::ceil(featureMaximum[1]*Q)),
static_cast<std::int64_t>(std::ceil(featureMaximum[2]*Q))};
static_cast<std::int64_t>(std::ceil(
(featureMaximum[0]+4.0*voxelsPerMetre)*Q)),
static_cast<std::int64_t>(std::ceil(
(featureMaximum[1]+4.0*voxelsPerMetre)*Q)),
static_cast<std::int64_t>(std::ceil(
(featureMaximum[2]+4.0*voxelsPerMetre)*Q))};
PassageFeatureBounds.push_back({static_cast<std::uint64_t>(PassageTileFeatures.size()),
featureMin,featureMax});
PassageTileFeatures.push_back({passageIndex,segment,false});
@@ -13891,3 +14036,278 @@ private:
FPassageCandidateBrickKeyHash> L90PassageCandidateBricks;
inline static thread_local const FPassageCandidateBrick* ActivePassageCandidateBrick=nullptr;
};
inline void FStandaloneShapeWorld::FScopedPassageTileQueries::prepareSweptTubeBase(
const FIntVector& origin,std::int32_t step,std::int32_t cells,std::uint32_t seed)
{
constexpr std::int64_t Q=VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
constexpr double InfluenceMetres=4.0;
if(!shape_||step<=0||cells<=0||!std::isfinite(passageCarveReachMetres_)
||std::abs(passageCarveReachMetres_-InfluenceMetres)>1.0e-9)return;
auto sweep=std::make_shared<FPassageTileSweepData>();
sweep->gridMinimumSubvoxels={
(static_cast<std::int64_t>(origin.X)-step)*Q,
(static_cast<std::int64_t>(origin.Y)-step)*Q,
(static_cast<std::int64_t>(origin.Z)-step)*Q};
sweep->stepSubvoxels=static_cast<std::int64_t>(step)*Q;
sweep->dimension=cells+3;
const std::size_t gridSampleCount=static_cast<std::size_t>(sweep->dimension)
*static_cast<std::size_t>(sweep->dimension)*static_cast<std::size_t>(sweep->dimension);
sweep->influenceSubvoxels=InfluenceMetres*static_cast<double>(Q)*1000000.0
/std::max<std::uint32_t>(1,shape_->ResolvedLayout
?shape_->ResolvedLayout->coreVoxelSizeMicrometres:250000);
sweep->passageToSweep.assign(passageRouteSegments_.size(),-1);
std::vector<std::uint8_t> anyPassageInfluence(gridSampleCount,0);
const double qPerMetre=static_cast<double>(Q)*1000000.0/
std::max<std::uint32_t>(1,shape_->ResolvedLayout
?shape_->ResolvedLayout->coreVoxelSizeMicrometres:250000);
const double wallReliefShell=0.41*qPerMetre;
const double floorDetailShell=1.0*qPerMetre;
const bool profile=profileStats_!=nullptr;
if(profile)
{
profileStats_->passageSweepRouteSegmentCandidates=routeSegmentCandidateCount_;
profileStats_->passageSweepGridSamples=gridSampleCount;
}
const auto gridRange=[&](std::size_t axis,std::int64_t minimum,std::int64_t maximum,
std::int32_t& first,std::int32_t& last)
{
const double relativeMinimum=static_cast<double>(minimum
-sweep->gridMinimumSubvoxels[axis])/sweep->stepSubvoxels;
const double relativeMaximum=static_cast<double>(maximum
-sweep->gridMinimumSubvoxels[axis])/sweep->stepSubvoxels;
first=std::max<std::int32_t>(0,static_cast<std::int32_t>(std::ceil(relativeMinimum)));
last=std::min<std::int32_t>(sweep->dimension-1,
static_cast<std::int32_t>(std::floor(relativeMaximum)));
};
for(std::size_t passageIndex=0;passageIndex<passageRouteSegments_.size();++passageIndex)
{
const auto& candidates=passageRouteSegments_[passageIndex];
if(candidates.empty())continue;
const std::int32_t sweepIndex=static_cast<std::int32_t>(sweep->passages.size());
sweep->passageToSweep[passageIndex]=sweepIndex;
auto& passageSamples=sweep->passages.emplace_back();
passageSamples.passageIndex=passageIndex;
passageSamples.samples.resize(gridSampleCount);
for(const auto& candidate:candidates)
{
if(candidate.segmentIndex>=shape_->PassageSegmentData[passageIndex].size())continue;
std::array<std::int32_t,3> first{},last{};
for(std::size_t axis=0;axis<3;++axis)
gridRange(axis,candidate.sweepMinimumSubvoxels[axis],
candidate.sweepMaximumSubvoxels[axis],first[axis],last[axis]);
if(first[0]>last[0]||first[1]>last[1]||first[2]>last[2])continue;
const auto& constants=shape_->PassageComposerConstants[passageIndex];
const auto& segment=shape_->PassageSegmentData[passageIndex][candidate.segmentIndex];
if(segment.lengthSq<=0.0)continue;
const std::ptrdiff_t segmentIndex=static_cast<std::ptrdiff_t>(candidate.segmentIndex);
const bool smoothCarveJoin=segmentIndex>2
&&segmentIndex+(constants.internalChainPassage?2:12)<constants.lowerMouthIndex
&&segment.lengthSubvoxels<=8.0*qPerMetre
&&!shape_->IsPassageShortSpanSegment(passageIndex,candidate.segmentIndex-1)
&&!shape_->IsPassageShortSpanSegment(passageIndex,candidate.segmentIndex)
&&!shape_->IsPassageShortSpanSegment(passageIndex,candidate.segmentIndex+1);
for(std::int32_t gz=first[2];gz<=last[2];++gz)
for(std::int32_t gy=first[1];gy<=last[1];++gy)
for(std::int32_t gx=first[0];gx<=last[0];++gx)
{
const double qx=static_cast<double>(sweep->gridMinimumSubvoxels[0]
+static_cast<std::int64_t>(gx)*sweep->stepSubvoxels);
const double qy=static_cast<double>(sweep->gridMinimumSubvoxels[1]
+static_cast<std::int64_t>(gy)*sweep->stepSubvoxels);
const double qz=static_cast<double>(sweep->gridMinimumSubvoxels[2]
+static_cast<std::int64_t>(gz)*sweep->stepSubvoxels);
const double projectionXY=(qx-segment.a[0])*segment.delta[0]
+(qy-segment.a[1])*segment.delta[1];
const double t=std::clamp((projectionXY
+(qz-segment.a[2])*segment.delta[2])/segment.lengthSq,0.0,1.0);
double cx=segment.a[0]+t*segment.delta[0];
double cy=segment.a[1]+t*segment.delta[1];
double cz=segment.a[2]+t*segment.delta[2];
if(smoothCarveJoin)
{
const double t2=t*t,t3=t2*t;
for(std::size_t axis=0;axis<3;++axis)
{
const double v0=segment.catmullBefore[axis];
const double v1=segment.a[axis];
const double v2=segment.b[axis];
const double v3=segment.catmullAfter[axis];
const double curve=0.5*((2.0*v1)+(-v0+v2)*t
+(2.0*v0-5.0*v1+4.0*v2-v3)*t2
+(-v0+3.0*v1-3.0*v2+v3)*t3);
const double line=axis==0?cx:axis==1?cy:cz;
const double value=line+(curve-line);
if(axis==0)cx=value;
else if(axis==1)cy=value;
else cz=value;
}
}
const double radius=segment.radiusA+t*segment.radiusDelta;
const double ex=qx-cx,ey=qy-cy,ez=qz-cz;
const double signedDistance=FStandaloneShapeWorld::DeterministicSqrt(
ex*ex+ey*ey+ez*ez)-radius;
if(profile)++profileStats_->passageSweepSegmentSamplePairs;
if(signedDistance>sweep->influenceSubvoxels)
{
if(profile)++profileStats_->passageSweepOutsideInfluencePairs;
}
const std::size_t sampleIndex=(static_cast<std::size_t>(gz)
*static_cast<std::size_t>(sweep->dimension)+static_cast<std::size_t>(gy))
*static_cast<std::size_t>(sweep->dimension)+static_cast<std::size_t>(gx);
auto& sample=passageSamples.samples[sampleIndex];
if(!sample.valid||signedDistance<sample.signedDistance
||(signedDistance==sample.signedDistance
&&candidate.segmentIndex<sample.segmentIndex))
{
sample.signedDistance=signedDistance;
sample.t=t;
sample.segmentIndex=static_cast<std::uint32_t>(candidate.segmentIndex);
sample.valid=true;
sample.hasWallRelief=false;
sample.hasMouthSurfaceRelief=false;
sample.hasMouthTreadFieldRelief=false;
if(profile)++profileStats_->passageSweepMinimumUpdates;
}
}
}
}
for(auto& passageSamples:sweep->passages)
{
const std::size_t passageIndex=passageSamples.passageIndex;
const auto& constants=shape_->PassageComposerConstants[passageIndex];
const auto& segments=shape_->PassageSegmentData[passageIndex];
for(std::size_t sampleIndex=0;sampleIndex<passageSamples.samples.size();++sampleIndex)
{
auto& sample=passageSamples.samples[sampleIndex];
if(!sample.valid)continue;
const std::int32_t gx=static_cast<std::int32_t>(sampleIndex
%static_cast<std::size_t>(sweep->dimension));
const std::int32_t gy=static_cast<std::int32_t>((sampleIndex
/static_cast<std::size_t>(sweep->dimension))
%static_cast<std::size_t>(sweep->dimension));
const std::int32_t gz=static_cast<std::int32_t>(sampleIndex
/(static_cast<std::size_t>(sweep->dimension)
*static_cast<std::size_t>(sweep->dimension)));
const double qx=static_cast<double>(sweep->gridMinimumSubvoxels[0]
+static_cast<std::int64_t>(gx)*sweep->stepSubvoxels);
const double qy=static_cast<double>(sweep->gridMinimumSubvoxels[1]
+static_cast<std::int64_t>(gy)*sweep->stepSubvoxels);
const double qz=static_cast<double>(sweep->gridMinimumSubvoxels[2]
+static_cast<std::int64_t>(gz)*sweep->stepSubvoxels);
const auto& segment=segments[sample.segmentIndex];
const double radius=segment.radiusA+sample.t*segment.radiusDelta;
const double centerZ=segment.a[2]+sample.t*segment.delta[2];
if(sample.signedDistance<=sweep->influenceSubvoxels)
{
anyPassageInfluence[sampleIndex]=1;
if(profile)++profileStats_->passageSweepBaseInfluenceSamples;
}
if(std::abs(sample.signedDistance)<=wallReliefShell)
{
const float x=static_cast<float>(qx/Q);
const float y=static_cast<float>(qy/Q);
const float z=static_cast<float>(qz/Q);
sample.wallRelief=VF_FieldPassageWallRelief(x,y,z,seed);
sample.hasWallRelief=true;
if(profile)
{
++profileStats_->passageSweepDetailShellSamples;
++profileStats_->passageReliefFieldCalls;
}
}
const std::size_t segmentIndex=sample.segmentIndex;
const double floorDistance=std::abs(qz-(centerZ-radius));
const double dx=qx-(segment.a[0]+sample.t*segment.delta[0]);
const double dy=qy-(segment.a[1]+sample.t*segment.delta[1]);
const double horizontalDistance=FStandaloneShapeWorld::DeterministicSqrt(
dx*dx+dy*dy);
const bool upperMouthSegment=segmentIndex<=2;
const auto lowerMouth=static_cast<std::size_t>(
std::max<std::ptrdiff_t>(0,constants.lowerMouthIndex));
const bool lowerMouthSegment=segmentIndex+2>=lowerMouth
&&segmentIndex<=lowerMouth+2;
const float x=static_cast<float>(qx/Q);
const float y=static_cast<float>(qy/Q);
const float z=static_cast<float>(qz/Q);
if(upperMouthSegment&&floorDistance<=4.0*qPerMetre
&&horizontalDistance<=3.0*qPerMetre)
{
sample.mouthSurfaceRelief=VF_FieldSurfaceRelief(x*1.5f,y*1.5f,z*1.5f,
seed^0x4D4F5554u);
sample.hasMouthSurfaceRelief=true;
if(profile)++profileStats_->passageReliefFieldCalls;
}
if((upperMouthSegment||lowerMouthSegment)
&&floorDistance<=floorDetailShell&&horizontalDistance<=2.5*qPerMetre)
{
sample.mouthTreadFieldRelief=VF_FieldSurfaceRelief(x*0.75f,y*0.75f,
z*0.75f,seed^0x54485244u);
sample.hasMouthTreadFieldRelief=true;
if(profile)++profileStats_->passageReliefFieldCalls;
}
}
}
if(profile)
{
profileStats_->passageSweepNoInfluenceGridSamples=
static_cast<std::uint64_t>(std::count(anyPassageInfluence.begin(),
anyPassageInfluence.end(),static_cast<std::uint8_t>(0)));
}
passageSweepData_=std::move(sweep);
}
inline const FStandaloneShapeWorld::FScopedPassageTileQueries::FPassageSweepSample*
FStandaloneShapeWorld::FScopedPassageTileQueries::sweptSample(
std::size_t passageIndex,double qx,double qy,double qz) const
{
const auto sweep=passageSweepData_;
if(!sweep)return nullptr;
sweep->lookups.fetch_add(1,std::memory_order_relaxed);
if(passageIndex>=sweep->passageToSweep.size())
{
sweep->missingSamples.fetch_add(1,std::memory_order_relaxed);
return nullptr;
}
const std::int32_t passageSweepIndex=sweep->passageToSweep[passageIndex];
if(passageSweepIndex<0||static_cast<std::size_t>(passageSweepIndex)>=sweep->passages.size())
{
sweep->missingSamples.fetch_add(1,std::memory_order_relaxed);
return nullptr;
}
const std::array<double,3> query={qx,qy,qz};
std::array<std::int64_t,3> grid{};
for(std::size_t axis=0;axis<3;++axis)
{
const double coordinate=(query[axis]-sweep->gridMinimumSubvoxels[axis])
/sweep->stepSubvoxels;
const auto rounded=static_cast<std::int64_t>(std::llround(coordinate));
if(rounded<0||rounded>=sweep->dimension
||std::abs(coordinate-static_cast<double>(rounded))>1.0e-5)
{
sweep->missingSamples.fetch_add(1,std::memory_order_relaxed);
return nullptr;
}
grid[axis]=rounded;
}
const std::size_t index=(static_cast<std::size_t>(grid[2])
*static_cast<std::size_t>(sweep->dimension)+static_cast<std::size_t>(grid[1]))
*static_cast<std::size_t>(sweep->dimension)+static_cast<std::size_t>(grid[0]);
const auto& samples=sweep->passages[static_cast<std::size_t>(passageSweepIndex)].samples;
if(index>=samples.size()||!samples[index].valid)
{
sweep->missingSamples.fetch_add(1,std::memory_order_relaxed);
return nullptr;
}
sweep->cacheHits.fetch_add(1,std::memory_order_relaxed);
if(samples[index].signedDistance<=sweep->influenceSubvoxels)
sweep->baseInfluenceHits.fetch_add(1,std::memory_order_relaxed);
else sweep->outOfInfluenceHits.fetch_add(1,std::memory_order_relaxed);
return &samples[index];
}