#pragma once #include "OwnerWorldLoader.h" #include "StandaloneResearchTunnels.h" #include "VoxelCaveMorphology.h" #include "VoxelNoise.h" #include "VoxelStrateManager.h" #include "VoxelDensityPrimitives.h" #include "VoxelForgeCore/VoxelForgeCore.h" #include "VoxelForgeFieldShared.h" #include "VoxelForgeCore/FloatingIslandInstrumentation.h" #include "VoxelForgeCore/Linux77Profiler.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // L88-only composition split. These switches let the timing harness remove passage work and // resolved boundary-band work independently. They are diagnostic only, never world settings. static bool VF_IsL88DiagnosticSwitchEnabled(const char* Name) { const char* Value=std::getenv(Name); return Value&&Value[0]=='1'&&Value[1]=='\0'; } static bool VF_IsL88PassageCompositionDisabled() { static const bool bDisabled=VF_IsL88DiagnosticSwitchEnabled( "VOXELFORGE_L88_DISABLE_PASSAGES") ||VF_IsL88DiagnosticSwitchEnabled("VOXELFORGE_L86_DISABLE_PASSAGE_COMPOSITION"); return bDisabled; } static bool VF_IsL88BoundaryBandsDisabled() { static const bool bDisabled=VF_IsL88DiagnosticSwitchEnabled( "VOXELFORGE_L88_DISABLE_BOUNDARY_BANDS") ||VF_IsL88DiagnosticSwitchEnabled("VOXELFORGE_L86_DISABLE_PASSAGE_COMPOSITION"); return bDisabled; } static bool VF_IsL88AllowPassageAblationWalkabilityFailure() { static const bool bEnabled=VF_IsL88DiagnosticSwitchEnabled( "VOXELFORGE_L88_ALLOW_PASSAGE_ABLATION_WALKABILITY_FAILURE"); return bEnabled; } static bool VF_IsL98PassagePrepareTraceEnabled() { static const bool bEnabled=VF_IsL88DiagnosticSwitchEnabled( "VOXELFORGE_L98_PASSAGE_PREP_TRACE"); return bEnabled; } static double VF_L93PassageCarveReachMetres() { // 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; } enum class EPassageDensityQueryCaller : std::size_t { Unknown, DensityGrid, GradientNormalFallback, MidpointCorrection, SkirtSurfaceVertex, SkirtExtrudedVertex, SkirtClippedVertex, SkirtRefinementFace, SkirtCoarseCentroid, FarShellSurfaceQuery, FarShellProjectionNormal, FarShellStratumRefinement, Count }; inline constexpr std::size_t PassageDensityQueryCallerCount= static_cast(EPassageDensityQueryCaller::Count); // 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. class FStandaloneShapeWorld { public: static EPassageDensityQueryCaller& CurrentPassageDensityQueryCaller() noexcept { static thread_local EPassageDensityQueryCaller Caller= EPassageDensityQueryCaller::Unknown; return Caller; } struct FScopedPassageDensityQueryCaller { explicit FScopedPassageDensityQueryCaller(EPassageDensityQueryCaller Caller) :Previous(CurrentPassageDensityQueryCaller()) {CurrentPassageDensityQueryCaller()=Caller;} ~FScopedPassageDensityQueryCaller() {CurrentPassageDensityQueryCaller()=Previous;} EPassageDensityQueryCaller Previous; }; const VoxelForgeCore::ResolvedLayoutSnapshotV3* ResolvedLayout=nullptr; struct FResolvedStratumBounds { double BottomZ=0.0; double TopZ=0.0; double SealThickness=0.0; }; // Immutable adapters share densityAt(x,y,z,out) without a virtual call in the sample // loop. The variant is selected once per resolved strate; callers retain concrete access // to fixed-point and interval operations where the legacy arithmetic requires them. struct FSurfaceWorldFieldAdapter { VoxelForgeCore::Heightfield2D Evaluator; FSurfaceWorldFieldAdapter(const VoxelForgeCore::WorldAssemblyRecipeV1& Recipe, const VoxelForgeCore::WorldAssemblyStratumPlanV1& Plan) noexcept :Evaluator(Recipe,Plan){} bool densityAt(float X,float Y,float Z,float& OutDensity) const noexcept { if(!std::isfinite(X)||!std::isfinite(Y)||!std::isfinite(Z))return false; constexpr double Scale=static_cast( VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel); const double MaxVoxel=static_cast(std::numeric_limits::max())/Scale; if(std::abs(static_cast(X))>MaxVoxel ||std::abs(static_cast(Y))>MaxVoxel ||std::abs(static_cast(Z))>MaxVoxel)return false; const auto ToFixed=[](float Value) {return static_cast(std::llround(static_cast(Value) *VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel));}; if(!Evaluator.isValid())return false; OutDensity=static_cast(Evaluator.point(ToFixed(X),ToFixed(Y),ToFixed(Z)) .densitySubvoxels/Scale); return true; } }; struct FFloatingIslandFieldAdapter { VoxelForgeCore::FloatingIslandFieldV1 Evaluator; int32 BottomChunkZ=0; std::vector LegacyBoundaryParams; FFloatingIslandFieldAdapter(const VoxelForgeCore::FloatingIslandRecipeV1& Recipe, const VoxelForgeCore::FloatingIslandPlanV1& Plan,int32 InBottomChunkZ, int32 InTopChunkZ,const UVoxelStrateManager* Manager) :Evaluator(Recipe,Plan),BottomChunkZ(InBottomChunkZ) { if(Manager&&Recipe.geometryVersion<4&&InTopChunkZ>=InBottomChunkZ) { LegacyBoundaryParams.reserve(static_cast( static_cast(InTopChunkZ)-InBottomChunkZ+1)); for(int32 ChunkZ=InBottomChunkZ;ChunkZ<=InTopChunkZ;++ChunkZ) LegacyBoundaryParams.push_back(Manager->GetFloatingIslandParamsForChunk( FIntVector(0,0,ChunkZ))); } } std::int64_t densitySubvoxelsAt(std::int64_t X,std::int64_t Y, std::int64_t Z) const noexcept {return Evaluator.densitySubvoxels(X,Y,Z);} const FFloatingIslandParams* legacyBoundaryParamsAt(int32 ChunkZ) const noexcept { const int64 Index=static_cast(ChunkZ)-BottomChunkZ; return Index>=0&&static_cast(Index)(Index)]:nullptr; } bool densityAt(float X,float Y,float Z,float& OutDensity) const noexcept { if(!Evaluator.isValid()||!std::isfinite(X)||!std::isfinite(Y) ||!std::isfinite(Z))return false; constexpr double Scale=static_cast( VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel); constexpr double Maximum=1000000000000.0/Scale; if(std::abs(static_cast(X))>Maximum ||std::abs(static_cast(Y))>Maximum ||std::abs(static_cast(Z))>Maximum)return false; const auto ToFixed=[](float Value) {return static_cast(std::llround(static_cast(Value) *VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel));}; OutDensity=static_cast(static_cast(densitySubvoxelsAt( ToFixed(X),ToFixed(Y),ToFixed(Z)))/Scale); return std::isfinite(OutDensity); } }; struct FLegacyGeneratorFieldAdapter { const UVoxelGenerator* Generator=nullptr; bool densityAt(float X,float Y,float Z,float& OutDensity) const { if(!Generator)return false; OutDensity=Generator->GetDensityAt(X,Y,Z); return std::isfinite(OutDensity); } }; struct FCavesFieldAdapter { std::uint32_t CoreVoxelSizeMicrometres=0; bool densityAt(float X,float Y,float Z,float& OutDensity) const noexcept { if(CoreVoxelSizeMicrometres==0||!std::isfinite(X) ||!std::isfinite(Y)||!std::isfinite(Z))return false; const double metresPerVoxel=CoreVoxelSizeMicrometres/1000000.0; // A negative metre distance denotes host rock. Cave features are composed // into this solid adapter once, in ApplyResolvedCavernGraph. OutDensity=static_cast(-32.0/metresPerVoxel); return std::isfinite(OutDensity); } }; using FGeneratorFieldAdapterV1=std::variant; struct FBoundStratumField { enum class EKind : uint8 { Legacy, SurfaceWorld, FloatingIslands, Caves }; const VoxelForgeCore::ResolvedLayoutStratumV2* Resolved=nullptr; const VoxelForgeCore::WorldAssemblyStratumPlanV1* SurfacePlan=nullptr; const VoxelForgeCore::WorldAssemblyFloatingIslandPlanV1* IslandPlan=nullptr; FGeneratorFieldAdapterV1 Evaluator; int32 StrateIndex=-1; int32 BottomChunkZ=0; int32 TopChunkZ=-1; EKind Kind=EKind::Legacy; }; class FScopedPassageTileQueries { public: struct FCandidateSegment { std::size_t segmentIndex=0; std::array minimumSubvoxels{},maximumSubvoxels{}; std::array sweepMinimumSubvoxels{},sweepMaximumSubvoxels{}; }; struct FPassageSweepSample { double signedDistance=std::numeric_limits::infinity(); double shellSignedDistance=std::numeric_limits::infinity(); double roundSignedDistance=std::numeric_limits::infinity(); double centerZSubvoxels=0.0,floorCenterZSubvoxels=0.0; double radiusSubvoxels=0.0,floorDepthSubvoxels=0.0; double t=0.0; std::uint32_t segmentIndex=std::numeric_limits::max(); float wallRelief=0.0f; bool valid=false,hasWallRelief=false; }; struct FPassageTileSweepData { struct FPassageSamples { std::size_t passageIndex=0; std::vector samples; }; std::array gridMinimumSubvoxels{}; std::int64_t stepSubvoxels=0; std::int32_t dimension=0; double influenceSubvoxels=0.0; std::vector passageToSweep; std::vector passages; std::atomic lookups{0},cacheHits{0},baseInfluenceHits{0}, outOfInfluenceHits{0},missingSamples{0}; std::array,PassageDensityQueryCallerCount> missingLookupsByCaller{},exactRouteFallbacksByCaller{}, featureBoundSkipsByCaller{},tileFreeSkipsByCaller{}, verticalBoundSkipsByCaller{},negativeReachSkipsByCaller{}, positiveReachSkipsByCaller{}; }; FScopedPassageTileQueries(const FStandaloneShapeWorld& shape, const std::array& minimumSubvoxels, const std::array& maximumSubvoxels, VoxelForgeCore::TileGenerationStats* profileStats=nullptr,int32 profileLod=0, double passageCarveReachMetres=-1.0) :shape_(&shape),minimumSubvoxels_(minimumSubvoxels), maximumSubvoxels_(maximumSubvoxels),previous_(ActivePassageTileQueries), profileStats_(profileStats),profileLod_(profileLod), passageCarveReachMetres_(std::isfinite(passageCarveReachMetres) &&passageCarveReachMetres>=0.0?passageCarveReachMetres :VF_L93PassageCarveReachMetres()) { mayContainResolvedBoundaryBand_=shape.ResolvedBoundaryBandsIntersectSubvoxelRange( minimumSubvoxels_[2],maximumSubvoxels_[2]); const bool disablePassageComposition=VF_IsL88PassageCompositionDisabled(); if(VF_IsL88BoundaryBandsDisabled())mayContainResolvedBoundaryBand_=false; mayContainTransitionPassageFade_=!disablePassageComposition &&shape.ResolvedTransitionPassageFadeIntersectsTileXY( minimumSubvoxels_,maximumSubvoxels_); if(!disablePassageComposition) featureIndices_=shape.BuildPassageFeatureTileList( minimumSubvoxels_,maximumSubvoxels_); stampIndices_=shape.BuildStampTileList(minimumSubvoxels_,maximumSubvoxels_); cavernFeatureIndices_=shape.BuildCavernFeatureTileList( minimumSubvoxels_,maximumSubvoxels_); passageRoutes_.assign(shape.ResolvedLayout&&!disablePassageComposition ?shape.ResolvedLayout->passages.size():0,0); passageRouteSegments_.resize(passageRoutes_.size()); for(const std::uint32_t featureIndex:featureIndices_) { if(featureIndex>=shape.PassageTileFeatures.size())continue; const auto& feature=shape.PassageTileFeatures[featureIndex]; if(feature.passageIndex>=passageRoutes_.size())continue; const auto& bounds=shape.PassageFeatureBounds[featureIndex]; FCandidateSegment candidate; candidate.segmentIndex=feature.segmentIndex; candidate.minimumSubvoxels=bounds.minimumSubvoxels; candidate.maximumSubvoxels=bounds.maximumSubvoxels; candidate.sweepMinimumSubvoxels=bounds.minimumSubvoxels; candidate.sweepMaximumSubvoxels=bounds.maximumSubvoxels; passageRoutes_[feature.passageIndex]=1; passageRouteSegments_[feature.passageIndex].push_back(candidate); ++routeSegmentCandidateCount_; } for(auto& candidates:passageRouteSegments_) { std::sort(candidates.begin(),candidates.end(),[](const auto& a,const auto& b) {return a.segmentIndexpassageSweepLookups=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); for(std::size_t caller=0;callerpassageSweepMissingLookupsByCaller[caller]= passageSweepData_->missingLookupsByCaller[caller].load( std::memory_order_relaxed); profileStats_->passageSweepExactRouteFallbacksByCaller[caller]= passageSweepData_->exactRouteFallbacksByCaller[caller].load( std::memory_order_relaxed); profileStats_->passageSweepFeatureBoundSkipsByCaller[caller]= passageSweepData_->featureBoundSkipsByCaller[caller].load( std::memory_order_relaxed); profileStats_->passageSweepTileFreeSkipsByCaller[caller]= passageSweepData_->tileFreeSkipsByCaller[caller].load( std::memory_order_relaxed); profileStats_->passageSweepVerticalBoundSkipsByCaller[caller]= passageSweepData_->verticalBoundSkipsByCaller[caller].load( std::memory_order_relaxed); profileStats_->passageSweepNegativeReachSkipsByCaller[caller]= passageSweepData_->negativeReachSkipsByCaller[caller].load( std::memory_order_relaxed); profileStats_->passageSweepPositiveReachSkipsByCaller[caller]= passageSweepData_->positiveReachSkipsByCaller[caller].load( std::memory_order_relaxed); } } void recordExactRouteFallback() const noexcept { if(!profileStats_||!passageSweepData_)return; const auto caller=static_cast( FStandaloneShapeWorld::CurrentPassageDensityQueryCaller()); if(callerexactRouteFallbacksByCaller[caller].fetch_add( 1,std::memory_order_relaxed); } void recordFeatureBoundSkip() const noexcept { if(!profileStats_||!passageSweepData_)return; const auto caller=static_cast( FStandaloneShapeWorld::CurrentPassageDensityQueryCaller()); if(callerfeatureBoundSkipsByCaller[caller].fetch_add( 1,std::memory_order_relaxed); } enum class ESweepMissSkipReason : std::uint8_t {TileFree,VerticalBound,NegativeReach,PositiveReach}; void recordSweepMissSkip(ESweepMissSkipReason reason) const noexcept { if(!profileStats_||!passageSweepData_)return; const auto caller=static_cast( FStandaloneShapeWorld::CurrentPassageDensityQueryCaller()); if(caller>=PassageDensityQueryCallerCount)return; std::atomic* counts=nullptr; switch(reason) { case ESweepMissSkipReason::TileFree: counts=&passageSweepData_->tileFreeSkipsByCaller[caller];break; case ESweepMissSkipReason::VerticalBound: counts=&passageSweepData_->verticalBoundSkipsByCaller[caller];break; case ESweepMissSkipReason::NegativeReach: counts=&passageSweepData_->negativeReachSkipsByCaller[caller];break; case ESweepMissSkipReason::PositiveReach: counts=&passageSweepData_->positiveReachSkipsByCaller[caller];break; } if(counts)counts->fetch_add(1,std::memory_order_relaxed); } static const FScopedPassageTileQueries* CurrentFor( const FStandaloneShapeWorld& shape) { const auto* current=ActivePassageTileQueries; return current&¤t->shape_==&shape?current:nullptr; } bool hasRouteFeature(std::size_t passageIndex) const {return passageIndex=passageRouteSegments_.size())return false; for(const auto& candidate:passageRouteSegments_[passageIndex]) { if(qxcandidate.maximumSubvoxels[0] ||qycandidate.maximumSubvoxels[1] ||qzcandidate.maximumSubvoxels[2])continue; return true; } return false; } bool mayContainResolvedBoundaryBand() const{return mayContainResolvedBoundaryBand_;} bool mayContainTransitionPassageFade() const{return mayContainTransitionPassageFade_;} bool mayContainResolvedBoundaryBandAtZ(double zMicrometres) const { return mayContainResolvedBoundaryBand_ &&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_;} double passageCarveReachMetres() const noexcept{return passageCarveReachMetres_;} bool hasStampCandidates() const{return !stampIndices_.empty();} const std::vector& stampCandidates() const{return stampIndices_;} bool hasCavernCandidates() const{return !cavernFeatureIndices_.empty();} const std::vector& cavernFeatureCandidates() const {return cavernFeatureIndices_;} private: const FStandaloneShapeWorld* shape_; std::array minimumSubvoxels_,maximumSubvoxels_; std::vector featureIndices_,stampIndices_,cavernFeatureIndices_; std::vector passageRoutes_; std::vector> passageRouteSegments_; std::size_t routeSegmentCandidateCount_=0; bool mayContainResolvedBoundaryBand_=true,mayContainTransitionPassageFade_=true; const FScopedPassageTileQueries* previous_; VoxelForgeCore::TileGenerationStats* profileStats_=nullptr; int32 profileLod_=0; double passageCarveReachMetres_=0.0; std::shared_ptr passageSweepData_; bool ownsPassageSweepDiagnostics_=true; }; double GetCoreVoxelSizeCm() const { return Owner == nullptr || Owner->CoreVoxelSizeMicrons == 0 ? static_cast(VOXEL_SIZE) : static_cast(Owner->CoreVoxelSizeMicrons) / 10000.0; } double GetResolvedLayoutChunkWidthVoxels() const { return ResolvedLayout&&ResolvedLayout->strateLayoutChunkWidthSubvoxels>0 ?static_cast(ResolvedLayout->strateLayoutChunkWidthSubvoxels) /VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel :Owner?static_cast(Owner->StrateLayoutChunkWidthSubvoxels) /VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel:0.0; } const VoxelForgeCore::ResolvedLayoutStratumV2* FindResolvedLayoutStratum(int32 StrateIndex) const { if(!ResolvedLayout)return nullptr; if(StrateIndex>=0&&static_cast(StrateIndex)strates.size()) { const auto& direct=ResolvedLayout->strates[static_cast(StrateIndex)]; if(direct.strateIndex==StrateIndex)return &direct; } const auto it=std::find_if(ResolvedLayout->strates.begin(),ResolvedLayout->strates.end(), [StrateIndex](const auto& item){return item.strateIndex==StrateIndex;}); return it==ResolvedLayout->strates.end()?nullptr:&*it; } void BindResolvedStratumFields() { BoundStratumFields.clear(); BoundManagerLayoutChunkWidthVoxels=Owner&&Owner->Manager ?Owner->Manager->GetStrateLayoutChunkWidthVoxels():0.0f; if(!ResolvedLayout||ResolvedLayout->strateLayoutChunkSizeMicrometres<=0) {BuildPassageComposerConstants();return;} const int64 ChunkWidthMicrometres=ResolvedLayout->strateLayoutChunkSizeMicrometres; const auto FloorDiv64=[](int64 Value,int64 Divisor) { const int64 Quotient=Value/Divisor; const int64 Remainder=Value%Divisor; return Remainder<0?Quotient-1:Quotient; }; BoundStratumFields.reserve(ResolvedLayout->strates.size()); for(const auto& Stratum:ResolvedLayout->strates) { if(Stratum.strateIndex<0 ||Stratum.interiorTopMicrometres<=Stratum.interiorBottomMicrometres) continue; const int64 Bottom=FloorDiv64(Stratum.interiorBottomMicrometres, ChunkWidthMicrometres); const int64 Top=FloorDiv64(Stratum.interiorTopMicrometres-1, ChunkWidthMicrometres); if(Bottom::min() ||Bottom>std::numeric_limits::max() ||Top::min() ||Top>std::numeric_limits::max()||Top(Bottom); Bound.TopChunkZ=static_cast(Top); if(Stratum.generatorKind=="SurfaceWorld") { Bound.Kind=FBoundStratumField::EKind::SurfaceWorld; if(WorldAssembly) { const auto Plan=std::find_if(WorldAssembly->plan.strates.begin(), WorldAssembly->plan.strates.end(),[&](const auto& Candidate) {return Candidate.strateIndex==Bound.StrateIndex &&Candidate.bottomChunkZ==Bound.BottomChunkZ &&Candidate.topChunkZ==Bound.TopChunkZ;}); if(Plan!=WorldAssembly->plan.strates.end()) { Bound.SurfacePlan=&*Plan; FSurfaceWorldFieldAdapter Adapter(WorldAssembly->recipe,*Plan); if(Adapter.Evaluator.isValid()) Bound.Evaluator.emplace( std::move(Adapter)); } } } else if(Stratum.generatorKind=="FloatingIslands") { Bound.Kind=FBoundStratumField::EKind::FloatingIslands; if(WorldAssembly) { const auto Plan=std::find_if(WorldAssembly->floatingIslands.begin(), WorldAssembly->floatingIslands.end(),[&](const auto& Candidate) {return Candidate.strateIndex==Bound.StrateIndex &&Candidate.bottomChunkZ==Bound.BottomChunkZ &&Candidate.topChunkZ==Bound.TopChunkZ;}); if(Plan!=WorldAssembly->floatingIslands.end()) { Bound.IslandPlan=&*Plan; Bound.Evaluator.emplace( Plan->manifest.recipe,Plan->manifest.plan, Bound.BottomChunkZ,Bound.TopChunkZ, Owner?Owner->Manager.get():nullptr); } } } else if(VoxelForgeCore::HasNativeGraphField(Stratum) &&(VoxelForgeCore::IsNativeCaveFieldStratum(Stratum) ?Stratum.passageCapabilities.hasNativeCaveField :Stratum.passageCapabilities.hasNativeLatticeField)) { Bound.Kind=FBoundStratumField::EKind::Caves; Bound.Evaluator.emplace( FCavesFieldAdapter{ResolvedLayout->coreVoxelSizeMicrometres}); } if(std::holds_alternative(Bound.Evaluator)) Bound.Evaluator.emplace( FLegacyGeneratorFieldAdapter{Owner?Owner->Generator.get():nullptr}); BoundStratumFields.push_back(std::move(Bound)); } std::sort(BoundStratumFields.begin(),BoundStratumFields.end(), [](const auto& A,const auto& B){return A.BottomChunkZ(GetResolvedLayoutChunkWidthVoxels()); if(!(LayoutWidth>0.0f))return nullptr; return FindBoundStratumForChunk(FMath::FloorToInt(Z/LayoutWidth)); } bool IsFloatingIslandFieldAtZ(float Z) const { const FBoundStratumField* Bound=FindBoundStratumForZ(Z); return Bound&&Bound->Kind==FBoundStratumField::EKind::FloatingIslands; } bool IsLegacyFloatingIslandFieldAtZ(float Z) const { const FBoundStratumField* Bound=FindBoundStratumForZ(Z); return Bound&&Bound->Resolved &&Bound->Resolved->generatorKind=="FloatingIslands" &&Bound->IslandPlan==nullptr; } const FBoundStratumField* FindBoundStratumForManagerZ(float Z) const { if(!std::isfinite(Z)||!Owner||!(BoundManagerLayoutChunkWidthVoxels>0.0f)) return nullptr; const float WorldZCentimetres=static_cast( static_cast(Z)*GetCoreVoxelSizeCm()); const int32 ChunkZ=FMath::FloorToInt((WorldZCentimetres/VOXEL_SIZE) /BoundManagerLayoutChunkWidthVoxels); return FindBoundStratumForChunk(ChunkZ); } const VoxelForgeCore::ResolvedLayoutStratumV2* FindResolvedLayoutStratumByOrder(int32 Order) const { if(!ResolvedLayout||Order<0)return nullptr; if(static_cast(Order)strates.size()) { const auto& direct=ResolvedLayout->strates[static_cast(Order)]; if(direct.order==Order)return &direct; } const auto it=std::find_if(ResolvedLayout->strates.begin(),ResolvedLayout->strates.end(), [Order](const auto& item){return item.order==Order;}); return it==ResolvedLayout->strates.end()?nullptr:&*it; } bool IsResolvedLayoutTransitionActive(const VoxelForgeCore::ResolvedLayoutStratumV2& Upper) const { return Upper.transitionMode!="Hard"&&!Upper.transitionFallsBackToHard &&Upper.transitionWidthMicrometres>0 &&Upper.transitionHonouredInCore; } bool ResolvedBoundaryBandsIntersectSubvoxelRange(std::int64_t minimumZSubvoxels, std::int64_t maximumZSubvoxels) const { if(!ResolvedLayout||!Owner||Owner->CoreVoxelSizeMicrons==0 ||minimumZSubvoxels>maximumZSubvoxels)return false; constexpr double Subvoxels=static_cast( VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel); const double queryMinimumMicrometres=static_cast(minimumZSubvoxels) *Owner->CoreVoxelSizeMicrons/Subvoxels; const double queryMaximumMicrometres=static_cast(maximumZSubvoxels) *Owner->CoreVoxelSizeMicrons/Subvoxels; const double sampleMarginMicrometres=static_cast(Owner->CoreVoxelSizeMicrons); for(const auto& interval:ResolvedBoundaryZIntervalsMicrometres) { if(queryMaximumMicrometres+sampleMarginMicrometres>=interval[0]-1.0 &&queryMinimumMicrometres-sampleMarginMicrometresat(1); } void BuildResolvedBoundaryZIntervals() { ResolvedBoundaryZIntervalsMicrometres.clear(); if(!ResolvedLayout)return; for(const auto& stratum:ResolvedLayout->strates) { if(stratum.ceiling.presentInCurrentField &&stratum.ceiling.bottomMicrometres(stratum.ceiling.bottomMicrometres), static_cast(stratum.ceiling.topMicrometres)}); if(stratum.bedrock.presentInCurrentField &&stratum.bedrock.bottomMicrometres(stratum.bedrock.bottomMicrometres), static_cast(stratum.bedrock.topMicrometres)}); } for(std::int32_t order=0;order+1(ResolvedLayout->strates.size()); ++order) { const auto* upper=FindResolvedLayoutStratumByOrder(order); const auto* lower=FindResolvedLayoutStratumByOrder(order+1); if(!upper||!lower||!IsResolvedLayoutTransitionActive(*upper))continue; const auto displacement=ResolvedTransitionMaximumFaceDisplacementMicrometres( *upper,*lower); ResolvedBoundaryZIntervalsMicrometres.push_back({ static_cast(std::min(upper->bedrock.bottomMicrometres, lower->ceiling.bottomMicrometres)-displacement), static_cast(std::max(upper->bedrock.topMicrometres, lower->ceiling.topMicrometres)+displacement)}); } std::sort(ResolvedBoundaryZIntervalsMicrometres.begin(), ResolvedBoundaryZIntervalsMicrometres.end(), [](const auto& a,const auto& b){return a[0]0&&interval[0]<=ResolvedBoundaryZIntervalsMicrometres[write-1][1]) ResolvedBoundaryZIntervalsMicrometres[write-1][1]=std::max( ResolvedBoundaryZIntervalsMicrometres[write-1][1],interval[1]); else ResolvedBoundaryZIntervalsMicrometres[write++]=interval; } ResolvedBoundaryZIntervalsMicrometres.resize(write); } std::int64_t ResolvedTransitionMaximumFaceDisplacementMicrometres( const VoxelForgeCore::ResolvedLayoutStratumV2& Upper, const VoxelForgeCore::ResolvedLayoutStratumV2& Lower) const { const std::int64_t width=std::max(0,Upper.transitionWidthMicrometres); if(Upper.transitionMode=="Gradient") return width/8; if(Upper.transitionMode!="Interleaved")return 0; const std::int64_t upperThickness=std::max(0, Upper.bedrock.topMicrometres-Upper.bedrock.bottomMicrometres); const std::int64_t lowerThickness=std::max(0, Lower.ceiling.topMicrometres-Lower.ceiling.bottomMicrometres); const std::int64_t maximumCrossing=std::min(width,upperThickness+lowerThickness); return (maximumCrossing+1)/2; } bool ResolvedTransitionPassageFadeIntersectsTileXY( const std::array& minimumSubvoxels, const std::array& maximumSubvoxels) const { if(!Owner||!ResolvedLayout||Owner->CoreVoxelSizeMicrons==0)return true; for(std::size_t axis=0;axis<2;++axis) if(minimumSubvoxels[axis]>maximumSubvoxels[axis])return true; constexpr std::int64_t Subvoxels=VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel; constexpr std::int64_t SupportMarginMicrometres=1000000; constexpr std::int64_t FadeWidthMicrometres=2000000; const double voxelMicrometres=static_cast(Owner->CoreVoxelSizeMicrons); const double minimumX=static_cast(minimumSubvoxels[0])*voxelMicrometres /Subvoxels-voxelMicrometres; const double maximumX=static_cast(maximumSubvoxels[0])*voxelMicrometres /Subvoxels+voxelMicrometres; const double minimumY=static_cast(minimumSubvoxels[1])*voxelMicrometres /Subvoxels-voxelMicrometres; const double maximumY=static_cast(maximumSubvoxels[1])*voxelMicrometres /Subvoxels+voxelMicrometres; for(std::int32_t order=0;order+1(ResolvedLayout->strates.size()); ++order) { const auto* upper=FindResolvedLayoutStratumByOrder(order); const auto* lower=FindResolvedLayoutStratumByOrder(order+1); if(!upper||!lower||!IsResolvedLayoutTransitionActive(*upper))continue; const std::int64_t seam=upper->bedrock.bottomMicrometres; for(const auto& passage:ResolvedLayout->passages) { if(passage.upperStrateIndex!=upper->strateIndex ||passage.lowerStrateIndex!=lower->strateIndex)continue; const std::int64_t mouthRadius=std::max(0, passage.mouthRadiusSubvoxels*Owner->CoreVoxelSizeMicrons/Subvoxels); const std::int64_t protectedRadius=mouthRadius+SupportMarginMicrometres; const double fadeEnd=static_cast(protectedRadius+FadeWidthMicrometres); for(const auto* mouth:{&passage.upperMouthSubvoxels,&passage.lowerMouthSubvoxels}) { const std::int64_t mouthZ=mouth->at(2)*Owner->CoreVoxelSizeMicrons/Subvoxels; if(std::abs(mouthZ-seam) >upper->transitionWidthMicrometres/2+mouthRadius)continue; const double mouthX=static_cast(mouth->at(0) *Owner->CoreVoxelSizeMicrons/Subvoxels); const double mouthY=static_cast(mouth->at(1) *Owner->CoreVoxelSizeMicrons/Subvoxels); const double dx=mouthXmaximumX?mouthX-maximumX:0.0; const double dy=mouthYmaximumY?mouthY-maximumY:0.0; if(dx*dx+dy*dyCoreVoxelSizeMicrons==0)return Q16; const std::int64_t seam=Upper.bedrock.bottomMicrometres; const std::int64_t coreMicrometres=Owner->CoreVoxelSizeMicrons; const std::int64_t subvoxelsPerVoxel=VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel; std::int64_t weight=Q16; for(const auto& passage:ResolvedLayout->passages) { if(passage.upperStrateIndex!=Upper.strateIndex ||passage.lowerStrateIndex!=Lower.strateIndex)continue; const std::int64_t mouthRadiusMicrometres=std::max(0, passage.mouthRadiusSubvoxels*coreMicrometres/subvoxelsPerVoxel); const std::int64_t protectedRadius=mouthRadiusMicrometres +SupportMarginMicrometres; for(const auto* mouth:{&passage.upperMouthSubvoxels,&passage.lowerMouthSubvoxels}) { #if defined(VF_L30_ENABLE_COST_COUNTERS) ++ThreadClassificationStats().TransitionPassageMouthTests; #endif const std::int64_t mouthZMicrometres=mouth->at(2)*coreMicrometres /subvoxelsPerVoxel; if(std::abs(mouthZMicrometres-seam) >Upper.transitionWidthMicrometres/2+mouthRadiusMicrometres)continue; const double dx=static_cast(XMicrometres-mouth->at(0)*coreMicrometres /subvoxelsPerVoxel); const double dy=static_cast(YMicrometres-mouth->at(1)*coreMicrometres /subvoxelsPerVoxel); const std::int64_t fadeEnd=protectedRadius+FadeWidthMicrometres; const double distanceSquared=dx*dx+dy*dy; if(distanceSquared>=static_cast(fadeEnd)*fadeEnd)continue; if(distanceSquared<=static_cast(protectedRadius)*protectedRadius)return 0; const double distance=std::sqrt(distanceSquared); const double t=static_cast(static_cast(distance) -protectedRadius)/FadeWidthMicrometres; const double smooth=t*t*(3.0-2.0*t); weight=std::min(weight,static_cast(std::llround(smooth*Q16))); } } return weight; } VoxelForgeCore::ResolvedLayoutTransitionBandsV1 ResolvedTransitionBandsAtXY( const VoxelForgeCore::ResolvedLayoutStratumV2& Upper, const VoxelForgeCore::ResolvedLayoutStratumV2& Lower, std::int64_t XMicrometres,std::int64_t YMicrometres,bool SkipPassageFade=false) const { auto bands=VoxelForgeCore::EvaluateResolvedLayoutTransitionBandsV1(Upper,Lower, Owner?Owner->Seed:0,ResolvedLayout?ResolvedLayout->strateLayoutChunkSizeMicrometres:0, XMicrometres,YMicrometres); constexpr std::int64_t TransitionQ16=1ll<<16; const std::int64_t weight=SkipPassageFade?TransitionQ16: ResolvedTransitionPassageWeightQ16(Upper,Lower,XMicrometres,YMicrometres); if(weight>=TransitionQ16)return bands; const std::int64_t seam=Upper.bedrock.bottomMicrometres; const std::int64_t upperThickness=Upper.bedrock.topMicrometres-seam; const std::int64_t lowerThickness=Lower.ceiling.topMicrometres -Lower.ceiling.bottomMicrometres; const auto scaled=[&](std::int64_t value){return (value*weight)/TransitionQ16;}; bands.upperBedrockBottomMicrometres=seam+scaled( bands.upperBedrockBottomMicrometres-seam); bands.upperBedrockTopMicrometres=bands.upperBedrockBottomMicrometres+upperThickness; bands.lowerCeilingTopMicrometres=seam+scaled( bands.lowerCeilingTopMicrometres-seam); bands.lowerCeilingBottomMicrometres=bands.lowerCeilingTopMicrometres-lowerThickness; bands.crossingDepthMicrometres=std::max(0, bands.lowerCeilingTopMicrometres-bands.upperBedrockBottomMicrometres); return bands; } bool ResolvedBedrockTopAt(const VoxelForgeCore::ResolvedLayoutStratumV2& Upper, double X,double Y,double& TopZ) const { const auto* lower=FindResolvedLayoutStratumByOrder(Upper.order+1); if(!lower||!Owner||Owner->CoreVoxelSizeMicrons==0 ||!IsResolvedLayoutTransitionActive(Upper))return false; const auto toMicrometres=[&](double value) {return static_cast(std::llround(value*Owner->CoreVoxelSizeMicrons));}; const auto* tileQueries=ActivePassageTileQueries; const bool skipPassageFade=tileQueries&&tileQueries->matches(*this) &&!tileQueries->mayContainTransitionPassageFade(); const auto bands=ResolvedTransitionBandsAtXY(Upper,*lower,toMicrometres(X), toMicrometres(Y),skipPassageFade); TopZ=static_cast(bands.upperBedrockTopMicrometres) /Owner->CoreVoxelSizeMicrons; return std::isfinite(TopZ); } bool ResolvedCeilingBottomAt(const VoxelForgeCore::ResolvedLayoutStratumV2& Lower, double X,double Y,double& BottomZ) const { const auto* upper=FindResolvedLayoutStratumByOrder(Lower.order-1); if(!upper||!Owner||Owner->CoreVoxelSizeMicrons==0 ||!IsResolvedLayoutTransitionActive(*upper))return false; const auto toMicrometres=[&](double value) {return static_cast(std::llround(value*Owner->CoreVoxelSizeMicrons));}; const auto* tileQueries=ActivePassageTileQueries; const bool skipPassageFade=tileQueries&&tileQueries->matches(*this) &&!tileQueries->mayContainTransitionPassageFade(); const auto bands=ResolvedTransitionBandsAtXY(*upper,Lower,toMicrometres(X), toMicrometres(Y),skipPassageFade); BottomZ=static_cast(bands.lowerCeilingBottomMicrometres) /Owner->CoreVoxelSizeMicrons; return std::isfinite(BottomZ); } bool IsInsideResolvedStrateBoundaryBand(float Z) const { #if defined(VF_L30_ENABLE_COST_COUNTERS) ++ThreadClassificationStats().BoundaryBandCalls; #endif const auto* tileQueries=ActivePassageTileQueries; if(tileQueries&&tileQueries->matches(*this)) { if(!tileQueries->mayContainResolvedBoundaryBand()) { #if defined(VF_L30_ENABLE_COST_COUNTERS) ++ThreadClassificationStats().BoundaryTileCullRejects; #endif return false; } if(Owner&&!tileQueries->mayContainResolvedBoundaryBandAtZ( static_cast(Z)*Owner->CoreVoxelSizeMicrons)) { #if defined(VF_L30_ENABLE_COST_COUNTERS) ++ThreadClassificationStats().BoundaryPointCullRejects; #endif return false; } // This predicate only selects the scalar path for boundary-capable Z // samples. The merged intervals are conservative, so a positive is safe. return true; } if(!ResolvedLayout||!Owner||!std::isfinite(Z))return false; const FBoundStratumField* bound=FindBoundStratumForManagerZ(Z); const auto* stratum=bound?FindResolvedLayoutStratum(bound->StrateIndex):nullptr; if(!stratum)return false; const double zMicrometres=static_cast(Z)*Owner->CoreVoxelSizeMicrons; const auto solidAt=[&](const VoxelForgeCore::ResolvedLayoutBandV2& band) {return band.presentInCurrentField&&zMicrometres>=band.bottomMicrometres &&zMicrometresbedrock.bottomMicrometres, Lower->ceiling.bottomMicrometres)-maxDisplacement; const double maximum=std::max(Upper->bedrock.topMicrometres, Lower->ceiling.topMicrometres)+maxDisplacement; const bool touches=zMicrometres>=minimum&&zMicrometresorder+1); const auto* upper=stratum->order>0?FindResolvedLayoutStratumByOrder(stratum->order-1):nullptr; if(mayTouchTransition(stratum,lower)||mayTouchTransition(upper,stratum))return true; return solidAt(stratum->ceiling)||solidAt(stratum->bedrock); } float ApplyResolvedStrateBoundaryBand(float density,float X,float Y,float Z) const { #if defined(VF_L30_ENABLE_COST_COUNTERS) ++ThreadClassificationStats().BoundaryBandCalls; #endif (void)X;(void)Y; const auto* tileQueries=ActivePassageTileQueries; if(tileQueries&&tileQueries->matches(*this)) if(auto* profile=tileQueries->profileStats()) ++profile->resolvedBoundaryBandQueries; if(tileQueries&&tileQueries->matches(*this)) { if(!tileQueries->mayContainResolvedBoundaryBand()) { #if defined(VF_L30_ENABLE_COST_COUNTERS) ++ThreadClassificationStats().BoundaryTileCullRejects; #endif return density; } if(Owner&&!tileQueries->mayContainResolvedBoundaryBandAtZ( static_cast(Z)*Owner->CoreVoxelSizeMicrons)) { #if defined(VF_L30_ENABLE_COST_COUNTERS) ++ThreadClassificationStats().BoundaryPointCullRejects; #endif return density; } } if(!ResolvedLayout||!Owner||Owner->CoreVoxelSizeMicrons==0 ||!std::isfinite(Z))return density; const double zMicrometres=static_cast(Z)*Owner->CoreVoxelSizeMicrons; if(ResolvedLayout->strates.size()==2) { const auto* transitionUpper=FindResolvedLayoutStratumByOrder(0); const auto* transitionLower=FindResolvedLayoutStratumByOrder(1); if(transitionUpper&&transitionLower &&IsResolvedLayoutTransitionActive(*transitionUpper)) { #if defined(VF_L30_ENABLE_COST_COUNTERS) ++ThreadClassificationStats().TransitionWindowChecks; #endif const auto maximumDisplacement=ResolvedTransitionMaximumFaceDisplacementMicrometres( *transitionUpper,*transitionLower); const double minimum=std::min(transitionUpper->bedrock.bottomMicrometres, transitionLower->ceiling.bottomMicrometres)-maximumDisplacement; const double maximum=std::max(transitionUpper->bedrock.topMicrometres, transitionLower->ceiling.topMicrometres)+maximumDisplacement; if(zMicrometres>=minimum&&zMicrometresmatches(*this) &&!tileQueries->mayContainTransitionPassageFade(); #if defined(VF_L30_ENABLE_COST_COUNTERS) if(skipPassageFade) ++ThreadClassificationStats().TransitionPassageTileSkips; #endif const auto xMicrometres=static_cast(std::llround( static_cast(X)*Owner->CoreVoxelSizeMicrons)); const auto yMicrometres=static_cast(std::llround( static_cast(Y)*Owner->CoreVoxelSizeMicrons)); const auto bands=ResolvedTransitionBandsAtXY(*transitionUpper, *transitionLower,xMicrometres,yMicrometres,skipPassageFade); const std::int64_t solidBottom=std::min( bands.upperBedrockBottomMicrometres,bands.lowerCeilingBottomMicrometres); const std::int64_t solidTop=std::max( bands.upperBedrockTopMicrometres,bands.lowerCeilingTopMicrometres); const double signedDistanceMicrometres=zMicrometres(solidBottom-zMicrometres) :zMicrometres>solidTop ?static_cast(zMicrometres-solidTop) :-static_cast(std::min(zMicrometres-solidBottom, solidTop-zMicrometres)); const std::int64_t seam=transitionUpper->bedrock.bottomMicrometres; const bool floatingIslandAirGap= transitionUpper->lowerInterfaceCapabilities.preserveUpperBedrockAirGap &&bands.upperBedrockTopMicrometres=bands.upperBedrockTopMicrometres &&zMicrometres0.0&&density<0.0f &&!floatingIslandAirGap)) return static_cast(signedDistanceMicrometres /Owner->CoreVoxelSizeMicrons); } } } const FBoundStratumField* bound=FindBoundStratumForManagerZ(Z); const auto* stratum=bound?FindResolvedLayoutStratum(bound->StrateIndex):nullptr; if(!stratum)return density; const auto solidAt=[&](const VoxelForgeCore::ResolvedLayoutBandV2& band) {return band.presentInCurrentField&&zMicrometres>=band.bottomMicrometres &&zMicrometresbedrock.bottomMicrometres, lower->ceiling.bottomMicrometres)-maxDisplacement; const double maximum=std::max(upper->bedrock.topMicrometres, lower->ceiling.topMicrometres)+maxDisplacement; if(zMicrometres=maximum)return false; #if defined(VF_L30_ENABLE_COST_COUNTERS) ++ThreadClassificationStats().TransitionWindowHits; ++ThreadClassificationStats().TransitionEvaluatorCalls; #endif if(upper==stratum)bBedrockHandledByTransition=true; if(lower==stratum)bCeilingHandledByTransition=true; const auto* tileQueries=ActivePassageTileQueries; const std::int64_t xMicrometres=static_cast(std::llround( static_cast(X)*Owner->CoreVoxelSizeMicrons)); const std::int64_t yMicrometres=static_cast(std::llround( static_cast(Y)*Owner->CoreVoxelSizeMicrons)); const bool skipPassageFade=tileQueries&&tileQueries->matches(*this) &&!tileQueries->mayContainTransitionPassageFade(); #if defined(VF_L30_ENABLE_COST_COUNTERS) if(skipPassageFade)++ThreadClassificationStats().TransitionPassageTileSkips; #endif const auto resolvedBands=ResolvedTransitionBandsAtXY(*upper,*lower, xMicrometres,yMicrometres,skipPassageFade); // Treat the two authored bands as one continuous solid interval. Its // exterior faces remain the lower ceiling bottom and upper bedrock top; // measuring signed distance from those faces also clears native generator // density on both air sides of the displaced interface. const std::int64_t solidBottom=std::min( resolvedBands.upperBedrockBottomMicrometres, resolvedBands.lowerCeilingBottomMicrometres); const std::int64_t solidTop=std::max( resolvedBands.upperBedrockTopMicrometres, resolvedBands.lowerCeilingTopMicrometres); const double signedDistanceMicrometres=zMicrometres(solidBottom-zMicrometres) :zMicrometres>solidTop ?static_cast(zMicrometres-solidTop) :-static_cast(std::min(zMicrometres-solidBottom, solidTop-zMicrometres)); const std::int64_t seam=upper->bedrock.bottomMicrometres; const bool floatingIslandAirGap= upper->lowerInterfaceCapabilities.preserveUpperBedrockAirGap &&resolvedBands.upperBedrockTopMicrometres=resolvedBands.upperBedrockTopMicrometres &&zMicrometres0.0&&density<0.0f&&!floatingIslandAirGap) return false; density=static_cast(signedDistanceMicrometres /std::max(1,Owner->CoreVoxelSizeMicrons)); return true; }; const auto* lower=FindResolvedLayoutStratumByOrder(stratum->order+1); const auto* upper=stratum->order>0?FindResolvedLayoutStratumByOrder(stratum->order-1):nullptr; if(applyInterface(stratum,lower)||applyInterface(upper,stratum))return density; const auto* band=(!bCeilingHandledByTransition&&solidAt(stratum->ceiling)) ?&stratum->ceiling:(!bBedrockHandledByTransition&&solidAt(stratum->bedrock)) ?&stratum->bedrock:nullptr; if(!band)return density; return -std::max(0.05f,static_cast(band->solidDensityQ16)/65536.0f); } float ApplyResolvedStratePassageCarves(float density,float X,float Y,float Z, float nativeTunnelFieldDensity=std::numeric_limits::lowest(), float nativeFloorSupport=std::numeric_limits::lowest()) const { VoxelForgeCore::Linux77Profiler::FScopedPhase l77PassagePhase( VoxelForgeCore::Linux77Profiler::EPhase::Passage); if(!ResolvedLayout||ResolvedLayout->passages.empty()||!std::isfinite(X) ||!std::isfinite(Y)||!std::isfinite(Z))return density; constexpr double Q=static_cast(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel); constexpr double FloorShellThicknessMetres=0.75; constexpr double WallReliefMetres=0.21; constexpr double MouthShellBlendMetres=5.0; const double qPerMetre=Q*1000000.0/ std::max(1,ResolvedLayout->coreVoxelSizeMicrometres); const double qx=static_cast(X)*Q,qy=static_cast(Y)*Q, qz=static_cast(Z)*Q; const double shellThickness=FloorShellThicknessMetres*qPerMetre; const auto* passageTileQueries=ActivePassageTileQueries; const bool passageTileScopeMatches=passageTileQueries &&passageTileQueries->matches(*this); auto* passageProfile=passageTileScopeMatches ?passageTileQueries->profileStats():nullptr; if(passageProfile)++passageProfile->passageComposerQueries; struct FNearestTubeSample { double signedDistance=std::numeric_limits::infinity(); double shellSignedDistance=std::numeric_limits::infinity(); double roundSignedDistance=std::numeric_limits::infinity(); double centerZ=0.0,floorCenterZ=0.0,radius=0.0,floorDepth=0.0,t=0.0; std::size_t segmentIndex=0; bool valid=false; const FScopedPassageTileQueries::FPassageSweepSample* swept=nullptr; }; double unionDistance=std::numeric_limits::infinity(); double unionShellDistance=std::numeric_limits::infinity(); const std::uint32_t reliefSeed=static_cast(Owner?Owner->Seed:0); for(std::size_t passageIndex=0;passageIndexpassages.size(); ++passageIndex) { const auto& passage=ResolvedLayout->passages[passageIndex]; if(passage.controlPoints.size()<2)continue; if(passageProfile)++passageProfile->passageIterations; if(passageTileScopeMatches&&!passageTileQueries->hasRouteFeature(passageIndex)) continue; const auto& constants=PassageComposerConstants[passageIndex]; FNearestTubeSample nearest; const bool usePreparedSweep=passageTileScopeMatches &&passageTileQueries->hasSweptTubeBase(); const auto* swept=usePreparedSweep ?passageTileQueries->sweptSample(passageIndex,qx,qy,qz):nullptr; if(swept) { nearest.signedDistance=swept->signedDistance; nearest.shellSignedDistance=swept->shellSignedDistance; nearest.roundSignedDistance=swept->roundSignedDistance; nearest.segmentIndex=swept->segmentIndex; nearest.t=swept->t; nearest.centerZ=swept->centerZSubvoxels; nearest.floorCenterZ=swept->floorCenterZSubvoxels; nearest.radius=swept->radiusSubvoxels; nearest.floorDepth=swept->floorDepthSubvoxels; nearest.valid=true; nearest.swept=swept; } else { if(usePreparedSweep) { if(!passageTileQueries->routeFeatureMayAffect(passageIndex,qx,qy,qz)) { passageTileQueries->recordFeatureBoundSkip(); continue; } passageTileQueries->recordExactRouteFallback(); } const std::ptrdiff_t lastIndex=static_cast( passage.controlPoints.size())-1; double bestDistance=std::numeric_limits::infinity(); const auto visitSegment=[&](std::size_t segmentIndex,int) { if(passageProfile)++passageProfile->passageCarveSegmentVisits; const auto& segment=PassageSegmentData[passageIndex][segmentIndex]; if(segment.lengthSq<=0.0)return; const double projection=(qx-segment.a[0])*segment.delta[0] +(qy-segment.a[1])*segment.delta[1] +(qz-segment.a[2])*segment.delta[2]; const double t=std::clamp(projection/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]; const double radius=segment.radiusA+t*segment.radiusDelta; const double ex=qx-cx,ey=qy-cy,ez=qz-cz; const double roundDistance=DeterministicSqrt( ex*ex+ey*ey+ez*ez)-radius; const double floorT=segment.horizontalLengthSq>1.0e-12 ?std::clamp(((qx-segment.a[0])*segment.delta[0] +(qy-segment.a[1])*segment.delta[1]) /segment.horizontalLengthSq,0.0,1.0):t; const double floorRadius=segment.radiusA+floorT*segment.radiusDelta; const double floorDepth=PassageFlatFloorDepthSubvoxels( constants,segmentIndex,floorT,floorRadius,qPerMetre); const double floorCenterZ=PassageFlatFloorCenterZSubvoxels( constants,segmentIndex,floorT,segment,floorDepth,qPerMetre); const double flatDistance=PassageFlatBottomDistance( static_cast(roundDistance),floorCenterZ,qz,floorDepth,qPerMetre); if(flatDistance1.0e-12 ?std::clamp(((qx-segment.a[0])*segment.delta[0] +(qy-segment.a[1])*segment.delta[1]) /segment.horizontalLengthSq,0.0,1.0):t; const double floorRadius=segment.radiusA+floorT*segment.radiusDelta; const double floorDepth=PassageFlatFloorDepthSubvoxels( constants,segmentIndex,floorT,floorRadius,qPerMetre); const double floorCenterZ=PassageFlatFloorCenterZSubvoxels( constants,segmentIndex,floorT,segment,floorDepth,qPerMetre); const double flatDistance=PassageFlatBottomDistance( static_cast(roundDistance),floorCenterZ,qz,floorDepth,qPerMetre); const double alongMetres=segment.authoredAlongMetres +t*segment.authoredLengthMetres; const double mouthDistanceMetres=std::max(0.0,std::min(alongMetres, constants.totalPathLengthMetres-alongMetres)); const double shellFade=std::clamp(mouthDistanceMetres/ MouthShellBlendMetres,0.0,1.0); const double smoothShellFade=shellFade*shellFade *(3.0-2.0*shellFade); const double floorDistanceMetres=(floorCenterZ-floorDepth-qz) /qPerMetre; const double floorBlend=std::clamp(floorDistanceMetres/0.25,0.0,1.0); const double smoothFloorBlend=floorBlend*floorBlend *(3.0-2.0*floorBlend); const double mouthWallThickness=segmentIndex =constants.routeEndControlIndex) { const double pastMouthMetres=std::max(0.0,alongMetres -constants.totalPathLengthMetres); const double extensionFade=std::clamp(1.0 -pastMouthMetres/MouthShellBlendMetres,0.0,1.0); floorExtensionFade=extensionFade*extensionFade *(3.0-2.0*extensionFade); } const double floorShellThickness=smoothFloorBlend*shellThickness *floorExtensionFade*smoothShellFade; const double localThickness=std::max(mouthWallThickness, floorShellThickness); const double localShellDistance=std::max(-static_cast(flatDistance), static_cast(flatDistance)-localThickness); nearest.shellSignedDistance=std::min(nearest.shellSignedDistance, localShellDistance); }; VisitNearestPassageSegmentRange(passageIndex,0,lastIndex-1, qx,qy,qz,0,shellSearchBound,visitShellSegment); } if(!nearest.valid)continue; double distanceToCenter=std::max(1.0e-9, nearest.roundSignedDistance+nearest.radius); const double verticalWallAlignment=distanceToCenter>1.0e-9 ?std::clamp(-(qz-nearest.centerZ)/distanceToCenter,0.0,1.0):0.0; const double wallReliefWeight=0.05+0.95*(1.0-verticalWallAlignment); float relief=0.0f; if(nearest.swept) { if(nearest.swept->hasWallRelief)relief=nearest.swept->wallRelief; } else { relief=VF_FieldPassageWallRelief(X,Y,Z,reliefSeed); if(passageProfile)++passageProfile->passageReliefFieldCalls; } const double roughRoundDistance=nearest.roundSignedDistance +static_cast(std::clamp(relief,-1.0f,1.0f)) *WallReliefMetres*qPerMetre*wallReliefWeight; const double shapedDistance=PassageFlatBottomDistance( static_cast(roughRoundDistance),nearest.floorCenterZ,qz, nearest.floorDepth,qPerMetre); const auto& segment=PassageSegmentData[passageIndex][nearest.segmentIndex]; const double alongMetres=segment.authoredAlongMetres +nearest.t*segment.authoredLengthMetres; const double mouthDistanceMetres=std::max(0.0,std::min(alongMetres, constants.totalPathLengthMetres-alongMetres)); const double shellFade=std::clamp(mouthDistanceMetres/ MouthShellBlendMetres,0.0,1.0); const double smoothShellFade=shellFade*shellFade*(3.0-2.0*shellFade); // Ease both wall and floor shell into the host ground over five metres. // The floor shell remains below the floor plane throughout that blend. const double floorDistanceMetres=(nearest.floorCenterZ -nearest.floorDepth-qz)/qPerMetre; const double floorBlend=std::clamp(floorDistanceMetres/0.25,0.0,1.0); const double smoothFloorBlend=floorBlend*floorBlend *(3.0-2.0*floorBlend); const double mouthWallThickness=nearest.segmentIndex =constants.routeEndControlIndex) { const double pastMouthMetres=std::max(0.0,alongMetres -constants.totalPathLengthMetres); const double extensionFade=std::clamp(1.0 -pastMouthMetres/MouthShellBlendMetres,0.0,1.0); floorExtensionFade=extensionFade*extensionFade *(3.0-2.0*extensionFade); } const double floorShellThickness=smoothFloorBlend*shellThickness *floorExtensionFade*smoothShellFade; const double localShellThickness=std::max(mouthWallThickness, floorShellThickness); unionDistance=std::min(unionDistance,static_cast(shapedDistance)); const double localShellDistance=std::max(-static_cast(shapedDistance), static_cast(shapedDistance)-localShellThickness); unionShellDistance=std::min(unionShellDistance, std::min(localShellDistance,nearest.shellSignedDistance)); } if(!std::isfinite(unionDistance))return density; // The union's interior is unconditional air. Its one exterior shell is // solid only in air, so it leaves host rock unchanged. Taking the union // before writing either polarity makes every tunnel interior beat every // overlapping tunnel shell. // Positive native graph distance is authored tunnel interior. Blend it // into the same air union before any passage shell can change its sign. // This also preserves the cave interior across a shared-layer boundary // blend, which may be solid on the passage's side of that interface. if(nativeTunnelFieldDensity>=0.0f) { const float passageAir=unionDistance<=0.0 ?static_cast(-unionDistance/Q) :std::numeric_limits::lowest(); return std::max(nativeTunnelFieldDensity,passageAir); } if(nativeFloorSupport!=std::numeric_limits::lowest() &&nativeFloorSupport<0.0f) return std::min(nativeFloorSupport,unionDistance<=0.0 ?static_cast(-unionDistance/Q):density); if(unionDistance<=0.0) return static_cast(-unionDistance/Q); if(unionShellDistance<0.0&&density>=0.0f) return static_cast(unionShellDistance/Q); return density; } float ApplyResolvedStrateComposition(float density,float X,float Y,float Z) const { float graphDensity=0.0f; float nativeLatticeFloorSupport=std::numeric_limits::lowest(); { VoxelForgeCore::Linux77Profiler::FScopedPhase l77GraphPhase( VoxelForgeCore::Linux77Profiler::EPhase::CompositionGraph); FScopedLegacyFieldTermTimer TermTimer(ELegacyFieldTerm::ComposerCavern); graphDensity=ApplyResolvedCavernGraph(density,X,Y,Z, &nativeLatticeFloorSupport); } const auto* bound=FindBoundStratumForManagerZ(Z); const bool nativeLattice=bound&&bound->Resolved &&VoxelForgeCore::IsNativeLatticeFieldStratum(*bound->Resolved) &&bound->Resolved->passageCapabilities.hasNativeLatticeField; // The native lattice field is a complete host-rock SDF: it owns both the // voids and the surrounding solid. Replacing its air-side distances with // the generic stratum transition distance creates a discontinuity at a // mouth that crosses a wide transition band. Keep that opt-in field whole; // its host-rock value already supplies the ceiling and bedrock. const bool disablePassageComposition=!nativeLattice &&VF_IsL88PassageCompositionDisabled(); const bool disableBoundaryBands=!nativeLattice &&VF_IsL88BoundaryBandsDisabled(); float boundary=graphDensity; if(!nativeLattice&&!disableBoundaryBands) { VoxelForgeCore::Linux77Profiler::FScopedPhase l77BandsPhase( VoxelForgeCore::Linux77Profiler::EPhase::Bands); FScopedLegacyFieldTermTimer TermTimer(ELegacyFieldTerm::ComposerBoundary); boundary=ApplyResolvedStrateBoundaryBand(graphDensity,X,Y,Z); } float composed=boundary; float stamped=composed; if(!StampRuntime.empty()) { VoxelForgeCore::Linux77Profiler::FScopedPhase l77StampPhase( VoxelForgeCore::Linux77Profiler::EPhase::Stamps); FScopedLegacyFieldTermTimer TermTimer(ELegacyFieldTerm::ComposerStamps); stamped=ApplyResolvedSdfStamps(composed,X,Y,Z); } static const bool bL75TraceTnComposition=[] { const char* Value=std::getenv("VOXELFORGE_L75_TN_BLOCK_REGRESSION"); return Value&&Value[0]=='1'&&Value[1]=='\0'; }(); const bool bTraceTnComposition=bL75TraceTnComposition &&X==-80.0f&&Y==-20.0f&&Z==-3218.0f; if(bound&&bound->Kind==FBoundStratumField::EKind::Caves &&bound->Resolved&&VoxelForgeCore::HasNativeGraphField(*bound->Resolved) &&(VoxelForgeCore::IsNativeCaveFieldStratum(*bound->Resolved) ?bound->Resolved->passageCapabilities.hasNativeCaveField :bound->Resolved->passageCapabilities.hasNativeLatticeField)) { if(!disablePassageComposition) { VoxelForgeCore::Linux77Profiler::FScopedPhase l77PassagePhase( VoxelForgeCore::Linux77Profiler::EPhase::Passage); FScopedLegacyFieldTermTimer TermTimer( ELegacyFieldTerm::ComposerPassage); const bool nativeTunnelField= (VoxelForgeCore::IsNativeCaveFieldStratum(*bound->Resolved) &&bound->Resolved->passageCapabilities.hasNativeCaveField) ||(VoxelForgeCore::IsNativeLatticeFieldStratum(*bound->Resolved) &&bound->Resolved->passageCapabilities.hasNativeLatticeField); const float nativeTunnelFieldDensity=nativeTunnelField ?graphDensity:std::numeric_limits::lowest(); stamped=ApplyResolvedStratePassageCarves(stamped,X,Y,Z, nativeTunnelFieldDensity,nativeLatticeFloorSupport); } if(bTraceTnComposition) std::printf("[L75TNCompositionTrace] input=%.9g graph=%.9g boundary=%.9g passage=%.9g stamp=%.9g native=1\n", density,graphDensity,boundary,composed,stamped); return stamped; } // The legacy landmark demo retains its compatibility floor cap. Native caves // already combine each floor with its wall before the single void carve above. float final=0.0f; { VoxelForgeCore::Linux77Profiler::FScopedPhase l77CapsPhase( VoxelForgeCore::Linux77Profiler::EPhase::Caps); FScopedLegacyFieldTermTimer TermTimer( ELegacyFieldTerm::ComposerPassage); final=ApplyResolvedCavernFloorCaps(stamped,X,Y,Z); } if(!disablePassageComposition) { VoxelForgeCore::Linux77Profiler::FScopedPhase l77PassagePhase( VoxelForgeCore::Linux77Profiler::EPhase::Passage); FScopedLegacyFieldTermTimer TermTimer( ELegacyFieldTerm::ComposerPassage); final=ApplyResolvedStratePassageCarves(final,X,Y,Z); } if(bTraceTnComposition) std::printf("[L75TNCompositionTrace] input=%.9g graph=%.9g boundary=%.9g passage=%.9g stamp=%.9g final=%.9g native=0\n", density,graphDensity,boundary,composed,stamped,final); return final; } bool ResolvedPassageIntersectsBox(const int32 minimum[3],const int32 maximum[3]) const { if(!ResolvedLayout||ResolvedLayout->passages.empty())return false; if(PassageProofNodes.empty()) { constexpr double Q=static_cast( VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel); const double qPerMetre=Q*1000000.0/ std::max(1,ResolvedLayout->coreVoxelSizeMicrometres); const auto overlaps=[&](const double low[3],const double high[3]) { for(int axis=0;axis<3;++axis) if(high[axis]maximum[axis])return false; return true; }; for(const auto& passage:ResolvedLayout->passages) { for(std::size_t segment=0;segment+1(a.radiusSubvoxels), static_cast(b.radiusSubvoxels))/Q+1.0; double low[3]{},high[3]{}; for(int axis=0;axis<3;++axis) { const double values[4]={ static_cast(p0.positionSubvoxels[axis])/Q, static_cast(a.positionSubvoxels[axis])/Q, static_cast(b.positionSubvoxels[axis])/Q, static_cast(p3.positionSubvoxels[axis])/Q}; low[axis]=*std::min_element(std::begin(values),std::end(values))-radius; high[axis]=*std::max_element(std::begin(values),std::end(values))+radius; } if(overlaps(low,high))return true; } } return false; } const double low[3]={static_cast(minimum[0]),static_cast(minimum[1]), static_cast(minimum[2])}; const double high[3]={static_cast(maximum[0]),static_cast(maximum[1]), static_cast(maximum[2])}; return PassageProofBoundsOverlapNode(0,low,high); } static bool MakeResolvedStratumBounds(double BottomZ,double TopZ,double SealThickness, FResolvedStratumBounds& Out) { if(!std::isfinite(BottomZ)||!std::isfinite(TopZ)||!std::isfinite(SealThickness) ||TopZ<=BottomZ||SealThickness<0.0)return false; Out={BottomZ,TopZ,SealThickness}; return true; } bool GetResolvedStratumBounds(const FStrateSlot& Slot,FResolvedStratumBounds& Out) const { if(!Owner)return false; if(const auto* resolved=FindResolvedLayoutStratum(Slot.StrateIndex)) { const double coreMicrons=std::max(1,Owner->CoreVoxelSizeMicrons); const double bottom=static_cast(resolved->interiorBottomMicrometres)/coreMicrons; const double top=static_cast(resolved->interiorTopMicrometres)/coreMicrons; const double ceiling=static_cast(resolved->ceiling.topMicrometres -resolved->ceiling.bottomMicrometres)/coreMicrons; const double bedrock=static_cast(resolved->bedrock.topMicrometres -resolved->bedrock.bottomMicrometres)/coreMicrons; return MakeResolvedStratumBounds(bottom,top,std::max(ceiling,bedrock),Out); } const int32 MidChunkZ=(Slot.TopChunkZ+Slot.BottomChunkZ)/2; const FBoundStratumField* Bound=FindBoundStratumForChunk(MidChunkZ); if(bUseNativeManifestFields&&WorldAssembly&&Bound &&Bound->Kind==FBoundStratumField::EKind::FloatingIslands) { const auto* Plan=Bound->IslandPlan; if(Plan&&Plan->manifest.recipe.geometryVersion>=3) { constexpr double Scale=static_cast( VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel); return MakeResolvedStratumBounds( static_cast(Plan->manifest.recipe.layoutMinimumZSubvoxels)/Scale, static_cast(Plan->manifest.recipe.layoutMaximumZSubvoxels)/Scale, static_cast(Plan->manifest.recipe.boundarySealSubvoxels)/Scale,Out); } } const FStrateGenerationParams Params=Owner->Manager->GetGenerationParams( FIntVector(0,0,MidChunkZ)); const double chunkWidth=GetResolvedLayoutChunkWidthVoxels(); return MakeResolvedStratumBounds(static_cast(Slot.BottomChunkZ)*chunkWidth, static_cast(Slot.TopChunkZ+1)*chunkWidth, static_cast(Params.BoundarySealThickness),Out); } enum class EDetailVariant : uint8 { Coherent, Smooth, PerVoxel }; struct FBoxClassification { EVoxelTileClass Verdict=EVoxelTileClass::Mixed; bool bNativeSurface=false; bool bUnknown=false; bool bSkipSafe=false; bool bLegacyFieldCandidate=false; bool bLegacyBlockProofRelevant=false; std::uint8_t LegacyProofVeto=0; }; enum class ELegacyProofVeto : std::uint8_t { None=0,DetailEnvelope=1,ComposerCandidate=2,PassageBounds=3, PrimitiveOverlap=4,Transition=5,StratumCrossing=6,SmoothMinSlack=7,Other=8 }; struct FClassificationThreadStats { uint64 SolidBoxes=0,AirBoxes=0,MixedBoxes=0,UnknownBoxes=0; uint64 NativeFloatFieldCalls=0,NativeLatticeFieldCalls=0,NativeUnclampedFieldCalls=0; bool CaptureDensityQueryProfile=false; uint64 DensityPointCalls=0,DensityFloatPointCalls=0,DensityLatticePointCalls=0, DensityUnclampedPointCalls=0,DensityX8PointCalls=0; std::array DensityOffGridFloatByCaller{}, DensityOffGridUnclampedByCaller{},DensityOffGridDetailByCaller{}; uint64 DensityFieldEvalNanoseconds=0,DensityFieldEvalSamplePoints=0; std::array DensityFieldEvalNanosecondsByKind{},DensityFieldEvalSamplePointsByKind{}; static constexpr std::size_t LegacyFieldTermCount=9; uint64 LegacyFieldEvalCalls=0; std::array LegacyFieldTermCalls{}; std::array LegacyFieldTermNanoseconds{}; std::array LegacyFieldTermTimedCalls{}; VoxelForgeCore::Linux77Profiler::FDispatchCounters Linux77DispatchCounters{}; uint64 LegacyPrimitiveCandidateVisits=0,LegacyPrimitiveDistanceEvaluations=0; uint64 LegacySmoothMinCalls=0,LegacySmoothMinNanoseconds=0, LegacySmoothMinTimedCalls=0; double Milliseconds=0.0; #if defined(VF_L30_ENABLE_COST_COUNTERS) uint64 BoundaryBandCalls=0,TransitionWindowChecks=0,TransitionWindowHits=0; uint64 TransitionEvaluatorCalls=0,TransitionPassageFadeCalls=0,TransitionPassageMouthTests=0; uint64 BoundaryTileCullRejects=0,BoundaryPointCullRejects=0; uint64 ColumnZOverlapCalls=0,ColumnGuardCalls=0,ColumnGuardNonpositiveSkips=0; uint64 ColumnGuardRadialRejects=0,ColumnSignedDistanceCalls=0; uint64 TransitionPassageTileSkips=0; #endif }; static void ResetThreadClassificationStats() { ThreadClassificationStats()={}; } static void SetThreadDensityQueryProfileEnabled(bool Enabled) {ThreadClassificationStats().CaptureDensityQueryProfile=Enabled;} static FClassificationThreadStats GetThreadClassificationStats() { return ThreadClassificationStats(); } static void RecordOffGridDensityQuery(float X,float Y,float Z, std::array& Counts) { if((std::abs(static_cast(X)-std::round(static_cast(X)))<=1.0e-5 &&std::abs(static_cast(Y)-std::round(static_cast(Y)))<=1.0e-5 &&std::abs(static_cast(Z)-std::round(static_cast(Z)))<=1.0e-5)) return; const auto Caller=static_cast(CurrentPassageDensityQueryCaller()); if(Caller(CurrentContext.stage); const auto routeIndex=static_cast(route); if(stage>=StageCount||routeIndex>=RouteCount)return; auto& counter=ThreadClassificationStats().Linux77DispatchCounters[stage][routeIndex]; ++counter.calls;counter.points+=points; #else (void)route;(void)points; #endif } enum class EDensityQueryKind : uint8 { Float=0,Lattice=1,Unclamped=2,X8=3 }; static uint32& DensityQuerySuppressionDepth() {static thread_local uint32 Depth=0;return Depth;} static uint64& DensityQuerySampleSequence() {static thread_local uint64 Sequence=0;return Sequence;} static bool& DensityQueryTermTimingActive() {static thread_local bool Active=false;return Active;} static bool& LegacyFieldTermScopeActive() {static thread_local bool Active=false;return Active;} enum class ELegacyFieldTerm : std::size_t { PrimitiveScan, Detail, SurfaceReliefNoise, OwnerNoiseAndWarp, ComposerCavern, ComposerBoundary, ComposerPassage, ComposerStamps, EditBrushes, Count }; struct FScopedLegacyFieldTermTimer { #if defined(VF_L76_PROFILE_LEGACY_TERMS) explicit FScopedLegacyFieldTermTimer(ELegacyFieldTerm InTerm, uint64 InMultiplicity=1) :Term(static_cast(InTerm)),Multiplicity(InMultiplicity) { if(!LegacyFieldTermScopeActive())return; FClassificationThreadStats& Stats=ThreadClassificationStats(); Stats.LegacyFieldTermCalls[Term]+=Multiplicity; if(!DensityQueryTermTimingActive())return; Start=std::chrono::steady_clock::now(); bTimed=true; } ~FScopedLegacyFieldTermTimer() { if(!bTimed)return; const uint64 Nanoseconds=static_cast(std::max(0, std::chrono::duration_cast( std::chrono::steady_clock::now()-Start).count())); FClassificationThreadStats& Stats=ThreadClassificationStats(); Stats.LegacyFieldTermNanoseconds[Term]+=Nanoseconds; Stats.LegacyFieldTermTimedCalls[Term]+=Multiplicity; } std::size_t Term=0; uint64 Multiplicity=1; std::chrono::steady_clock::time_point Start{}; bool bTimed=false; #else explicit FScopedLegacyFieldTermTimer(ELegacyFieldTerm,uint64=1) noexcept {} #endif }; struct FScopedLegacyFieldTermScope { #if defined(VF_L76_PROFILE_LEGACY_TERMS) explicit FScopedLegacyFieldTermScope(bool bEligible,uint64 InMultiplicity=1) :Previous(LegacyFieldTermScopeActive()),Multiplicity(InMultiplicity) { const bool bEnabled=ThreadClassificationStats().CaptureDensityQueryProfile &&bEligible; LegacyFieldTermScopeActive()=bEnabled; if(bEnabled)ThreadClassificationStats().LegacyFieldEvalCalls+=Multiplicity; } ~FScopedLegacyFieldTermScope() {LegacyFieldTermScopeActive()=Previous;} bool Previous=false; uint64 Multiplicity=1; #else explicit FScopedLegacyFieldTermScope(bool,uint64=1) noexcept {} #endif }; struct FScopedLegacySmoothMinTimer { #if defined(VF_L76_PROFILE_LEGACY_TERMS) FScopedLegacySmoothMinTimer() { if(!LegacyFieldTermScopeActive())return; ++ThreadClassificationStats().LegacySmoothMinCalls; if(!DensityQueryTermTimingActive())return; Start=std::chrono::steady_clock::now(); bTimed=true; } ~FScopedLegacySmoothMinTimer() { if(!bTimed)return; auto& Stats=ThreadClassificationStats(); Stats.LegacySmoothMinNanoseconds+=static_cast(std::max(0, std::chrono::duration_cast( std::chrono::steady_clock::now()-Start).count())); ++Stats.LegacySmoothMinTimedCalls; } std::chrono::steady_clock::time_point Start{}; bool bTimed=false; #else FScopedLegacySmoothMinTimer() noexcept {} #endif }; struct FScopedDensityQueryProfile { FScopedDensityQueryProfile(EDensityQueryKind Kind,uint64 PointCount=1) :stats(&ThreadClassificationStats()),kindIndex(static_cast(Kind)), pointCount(PointCount) { if(!stats->CaptureDensityQueryProfile||DensityQuerySuppressionDepth()!=0) {stats=nullptr;return;} #if defined(VF_L76_PROFILE_LEGACY_TERMS) PreviousTermTiming=DensityQueryTermTimingActive(); DensityQueryTermTimingActive()=false; #endif stats->DensityPointCalls+=pointCount; switch(Kind) { case EDensityQueryKind::Float:stats->DensityFloatPointCalls+=pointCount;break; case EDensityQueryKind::Lattice:stats->DensityLatticePointCalls+=pointCount;break; case EDensityQueryKind::Unclamped:stats->DensityUnclampedPointCalls+=pointCount;break; case EDensityQueryKind::X8:stats->DensityX8PointCalls+=pointCount;break; } const uint64 Sequence=DensityQuerySampleSequence(); DensityQuerySampleSequence()+=pointCount; if(((Sequence+pointCount)>>8)==(Sequence>>8))return; sampled=true;start=std::chrono::steady_clock::now(); #if defined(VF_L76_PROFILE_LEGACY_TERMS) DensityQueryTermTimingActive()=true; #endif } ~FScopedDensityQueryProfile() { if(stats==nullptr)return; #if defined(VF_L76_PROFILE_LEGACY_TERMS) DensityQueryTermTimingActive()=PreviousTermTiming; #endif if(sampled) { const uint64 nanoseconds=static_cast(std::max(0, std::chrono::duration_cast( std::chrono::steady_clock::now()-start).count())); stats->DensityFieldEvalNanoseconds+=nanoseconds; stats->DensityFieldEvalSamplePoints+=pointCount; stats->DensityFieldEvalNanosecondsByKind[kindIndex]+=nanoseconds; stats->DensityFieldEvalSamplePointsByKind[kindIndex]+=pointCount; } } bool IsActive()const noexcept{return stats!=nullptr;} FClassificationThreadStats* stats=nullptr; std::size_t kindIndex=0; uint64 pointCount=0; std::chrono::steady_clock::time_point start{}; bool sampled=false; #if defined(VF_L76_PROFILE_LEGACY_TERMS) bool PreviousTermTiming=false; #endif }; struct FScopedDensityQueryBatch { explicit FScopedDensityQueryBatch(bool Enabled):enabled(Enabled) {if(enabled)++DensityQuerySuppressionDepth();} ~FScopedDensityQueryBatch(){if(enabled)--DensityQuerySuppressionDepth();} bool enabled=false; }; struct FFarConnectorSegment { FVector Start=FVector::ZeroVector; FVector End=FVector::ZeroVector; float Radius=0.0f; }; struct FEditBrush { enum class EShape : uint8 { Sphere, Capsule, RoundedBox }; enum class EOperation : uint8 { Carve, AddRock, DensityOffset }; EShape Shape=EShape::Sphere; EOperation Operation=EOperation::Carve; std::array A{}; std::array B{}; std::array HalfExtent{}; double Radius=0.0; double Rounding=0.0; float Strength=0.0f; float Falloff=0.0f; }; struct FParallelTiming { double WallSeconds = 0.0; double WorkerSeconds = 0.0; }; struct FDetailWorkerProfile { uint64 Lines=0,Candidates=0,DetailEvaluations=0,Flips=0; uint64 LocalBandSkips=0; uint64 GpuVisualCandidates=0,CpuVisualCandidates=0,GpuOnlyCandidates=0; uint64 GpuVisualDetailEvaluations=0,GpuOnlyDetailEvaluations=0; uint64 DistanceSamples=0,DistanceSampleNanoseconds=0; uint64 DetailSamples=0,DetailSampleNanoseconds=0; uint64 ReliefSampleNanoseconds=0,LayerLookupSampleNanoseconds=0; uint64 OwnerRoughnessSampleNanoseconds=0,OwnerRoughnessFieldCalls=0; uint64 OwnerRoughnessActiveCalls=0,OwnerNoiseCacheHits=0,OwnerNoiseCacheMisses=0; }; struct FBuildStats { double ShapeSeconds = 0.0; double IntervalSeconds = 0.0; double SurfaceSeconds = 0.0; double FarShapeSeconds = 0.0; uint64 PrimitiveCount = 0; uint64 RoomCount = 0; uint64 RoomFloorJoinCount = 0; uint64 TunnelCount = 0; uint64 PassageCount = 0; uint64 SignNodes = 0; uint64 SurfaceCandidates = 0; uint64 SurfaceProxyNodes = 0; uint64 DetailFlips = 0; uint64 WalkSamples = 0; uint64 WalkFailures = 0; double MaxCapsuleJoinRadiusDelta = 0.0; double MaxCapsuleJoinRadiusSlopeDelta = 0.0; double MaxCapsuleJoinFloorDelta = 0.0; double MaxCapsuleJoinFloorSlopeDelta = 0.0; double MaxRawUnmarkedFloorSlopeDelta = 0.0; double MaxUnmarkedFloorSlopeDelta = 0.0; double MaxUnmarkedFloorDirectionSlopeDelta = 0.0; double MaxMarkedFloorSlopeDelta = 0.0; double MaxFloorCurveDeviation = 0.0; double MaxFloorProfileEndpointValueError = 0.0; double MaxFloorProfileEndpointSlopeError = 0.0; bool FloorProfileEndpointStepControlDetected = false; bool FloorProfileEndpointSlopeControlDetected = false; double LocalPrimitiveSeconds = 0.0; double LocalLineBinSeconds = 0.0; double CandidateMergeSeconds = 0.0; double PackedSignBuildSeconds = 0.0; double PassageSweepPrepSeconds = 0.0; double CandidateSweepPrepSeconds = 0.0; double VisualDetailPrepSeconds = 0.0; double DetailApplySeconds = 0.0; double SignEdgeDistanceSeconds = 0.0; double PassageValidationSeconds = 0.0; double SharedFieldSeconds = 0.0; double OwnerShapeWorkerSeconds = 0.0; double FarPrimitiveSeconds = 0.0; double FarParentShapeSeconds = 0.0; double FarSharedPrimitiveSeconds = 0.0; double FarBinSeconds = 0.0; FParallelTiming FarParentGeometryLines; FParallelTiming RockIntervalLines; FParallelTiming CandidateAxisLines; FParallelTiming CandidateMergeLines; FParallelTiming DetailBandLines; uint64 CurvedFloorSegments = 0; uint64 MarkedFloorSlopeJoins = 0; double LegacySegmentRadiusStep = 0.0; double SmoothedProfileRadiusStep = 0.0; double MaxTunnelBendDegrees = 0.0; FVector MaxTunnelBendVoxels = FVector::ZeroVector; FVector MaxTunnelBendDirection = FVector::ZeroVector; uint64 CapsuleJoinCount = 0; }; struct FPrimitive { int32 CenterSub[3]{}; int32 CapsuleStartSub[3]{}; int32 CapsuleEndSub[3]{}; double CapsuleStartWorld[3]{}; double CapsuleAxisDelta[3]{}; double CapsuleAxisLengthSquared = 0.0; double CapsuleAxisLength = 0.0; int64 Q[3][3]{}; int32 Min[3]{}; int32 Max[3]{}; float MinRadius = 1.0f; float CapsuleStartRadius = 1.0f; float CapsuleEndRadius = 1.0f; float CapsuleProfileStartRadius = 1.0f; float CapsuleProfileEndRadius = 1.0f; double CapsuleProfileStartT = 0.0; double CapsuleProfileEndT = 1.0; double CapsuleRadiusSlopeStart = 0.0; double CapsuleRadiusSlopeEnd = 0.0; double BlendRadius = 0.0; double FloorBlendRadius = 0.0; bool bCarve = true; bool bCritical = false; bool bPassageSurface = false; bool bCapsule = false; bool bSmoothCapsuleRadius = false; bool bRoundedBox = false; bool bHasFloorClip = false; bool bCurvedFloorClip = false; double RoundedHalfExtent[3]{}; double Rounding = 0.0; double FloorCenterZ = 0.0; double FloorSlopeX = 0.0; double FloorSlopeY = 0.0; double FloorCurveOriginX = 0.0; double FloorCurveOriginY = 0.0; double FloorCurveDeltaX = 0.0; double FloorCurveDeltaY = 0.0; double FloorCurveLength = 0.0; double FloorCurveStartZ = 0.0; double FloorCurveEndZ = 0.0; double FloorCurveTangentStart = 0.0; double FloorCurveTangentEnd = 0.0; double FloorCurveStartBlend = 0.0; double FloorCurveEndBlend = 0.0; double FloorCurveMaxDeviation = 0.0; double FloorCurveEndpointValueError = 0.0; double FloorCurveEndpointSlopeError = 0.0; }; struct FInterval { int32 Lo = 0, Hi = -1; }; struct FBinKey { int32 X=0,Y=0,Z=0; bool operator==(const FBinKey&)const=default; }; struct FBinHash { size_t operator()(const FBinKey& K) const noexcept { uint64 H=static_cast(K.X)*0x9E3779B185EBCA87ull; H^=static_cast(static_cast(K.Y))*0xC2B2AE3D27D4EB4Full; H^=static_cast(static_cast(K.Z))*0x165667B19E3779F9ull; H^=H>>29;H*=0xBF58476D1CE4E5B9ull;H^=H>>31; return static_cast(H); } }; struct FBinItems { static constexpr size_t InlineCapacity=4; struct FHeapStorage {uint32* Data=nullptr;uint32 Capacity=0;uint32 Reserved=0;}; union FStorage { std::array Inline; FHeapStorage Heap; FStorage():Inline{}{} ~FStorage(){} } Storage; uint32 Count=0; uint32 bHeap=0; FBinItems()=default; FBinItems(const FBinItems&)=delete; FBinItems& operator=(const FBinItems&)=delete; FBinItems(FBinItems&& Other) noexcept {MoveFrom(Other);} FBinItems& operator=(FBinItems&& Other) noexcept { if(this!=&Other){Release();MoveFrom(Other);} return *this; } ~FBinItems(){Release();} void push_back(uint32 Id) { if(!bHeap&&Count Previous=Storage.Inline; Storage.Heap={new uint32[8],8,0}; std::copy(Previous.begin(),Previous.end(),Storage.Heap.Data); bHeap=1; } else if(Count==Storage.Heap.Capacity) { const uint32 NewCapacity=Storage.Heap.Capacity*2; uint32* NewData=new uint32[NewCapacity]; std::copy_n(Storage.Heap.Data,Count,NewData); delete[] Storage.Heap.Data; Storage.Heap={NewData,NewCapacity,0}; } Storage.Heap.Data[Count++]=Id; } size_t size() const {return Count;} size_t capacity() const {return bHeap?Storage.Heap.Capacity:InlineCapacity;} const uint32* begin() const {return bHeap?Storage.Heap.Data:Storage.Inline.data();} const uint32* end() const {return begin()+Count;} private: void Release() { if(bHeap)delete[] Storage.Heap.Data; bHeap=0;Count=0; } void MoveFrom(FBinItems& Other) { Count=Other.Count;bHeap=Other.bHeap; if(bHeap) { Storage.Heap=Other.Storage.Heap; Other.bHeap=0;Other.Count=0; } else { Storage.Inline=Other.Storage.Inline; Other.Count=0; } } }; static constexpr int32 QScale = 1 << 28; static constexpr int32 SubPerVoxel = 16; static constexpr int32 DetailBand = 6; static constexpr float DetailSupremum = 4.0f; static constexpr double JoinBlendRadius = 1.5; static constexpr double RoomUnionBlendRadius = 12.0; static constexpr double FloorClipBlendRadius = 16.0; static constexpr float StrataCaveBlendRadius = 12.0f; static constexpr int32 LineBinSize = 32; static constexpr int32 SpatialBinSize = 256; static constexpr int32 FarSpatialBinSize = 512; bool Build(FStandaloneOwnerWorld& World, const FIntVector& MinVoxels, const FIntVector& MaxVoxels, const FIntVector& ClipMinVoxels, const FIntVector& ClipMaxVoxels, int32 Workers, EDetailVariant Variant, bool bIncludeFarWorld, std::string& Error, const VoxelForgeCore::JobScheduler* InScheduler=nullptr, bool bInRoundTunnels=false,bool bInProfileDetailBand=false, bool bValidatePassageRoutes=true, const std::vector& InGpuVisualDetailTiles={}, const std::vector& InCpuVisualDetailTiles={}, const VoxelForgeCore::WorldAssemblyManifest* InWorldAssembly=nullptr, const VoxelForgeCore::ResolvedLayoutSnapshotV3* InResolvedLayout=nullptr, bool bInParallelSignEdgeRepair=false) { using Clock=std::chrono::steady_clock; const auto TotalStart=Clock::now(); Owner=&World;WorldAssembly=InWorldAssembly;ResolvedLayout=InResolvedLayout; WorkerCount=std::clamp(Workers,1,16);DetailVariant=Variant; Scheduler=InScheduler;RoundTunnelsEnabled=bInRoundTunnels; bUseNativeManifestFields=true; UnbinnedSpatialPrimitiveIds.clear(); BindResolvedStratumFields(); bAnalyticOnly=false; bProfileDetailBand=bInProfileDetailBand; bParallelSignEdgeRepair=bInParallelSignEdgeRepair; for(int32 A=0;A<3;++A) { Min[A]=(&MinVoxels.X)[A]-2; Max[A]=(&MaxVoxels.X)[A]+2; ClipMin[A]=(&ClipMinVoxels.X)[A]; ClipMax[A]=(&ClipMaxVoxels.X)[A]; Size[A]=Max[A]-Min[A]+1; if(Size[A]<=0||Size[A]>4096||ClipMax[A](Size[0])*Size[1]*Size[2]; if(NodeCount>160000000ull) {Error="shape interval region exceeds the 160-million-node safety bound";return false;} Stats={};Stats.SignNodes=NodeCount; AuditFloorProfileBlendNegativeControls(); auto Stage=Clock::now(); BuildLayerBounds(); auto Substage=Clock::now(); const auto& OwnerLayout=Owner->Manager->GetLayout(); if(WorkerCount>1&&Scheduler&&OwnerLayout.Num()>1) BuildOwnerPrimitivesParallel(); else BuildOwnerPrimitives(); Stats.LocalPrimitiveSeconds=std::chrono::duration(Clock::now()-Substage).count(); Substage=Clock::now(); if(RoundTunnelsEnabled) BuildRoundTunnelPrimitives(); BuildSpatialBins(LineBinSize); Stats.LocalLineBinSeconds=std::chrono::duration(Clock::now()-Substage).count(); const auto SharedStage=Clock::now(); RebuildSharedFieldPrimitives(); Stats.SharedFieldSeconds=std::chrono::duration(Clock::now()-SharedStage).count(); Stats.ShapeSeconds=std::chrono::duration(Clock::now()-Stage).count(); Stats.PrimitiveCount=Primitives.size(); if(Primitives.empty()) {Error="owner shape extraction produced no rooms, tunnels, or passages";return false;} Stage=Clock::now(); const size_t NZ=static_cast(Size[2]); WordsPerZ=(NZ+63u)/64u; RockWords.assign(static_cast(Size[0])*Size[1]*WordsPerZ,0); SurfaceDistanceQ=std::make_unique_for_overwrite(static_cast(NodeCount)); Substage=Clock::now(); BuildPackedRockIntervals(Stats.RockIntervalLines); Stats.PackedSignBuildSeconds=std::chrono::duration(Clock::now()-Substage).count(); // Native density queries for non-heightfield strates use the exact interval // evaluator. Build those native intervals before preparing the tunnel sweeps. Substage=Clock::now(); BuildResolvedBoundaryZIntervals(); BuildPassageSpatialIndices(); Stats.PassageSweepPrepSeconds=std::chrono::duration(Clock::now()-Substage).count(); Substage=Clock::now(); BuildCandidateBands(Stats.CandidateAxisLines,Stats.CandidateMergeLines, Stats.CandidateMergeSeconds); Stats.CandidateSweepPrepSeconds=std::chrono::duration(Clock::now()-Substage).count(); Substage=Clock::now(); if(!BuildGpuVisualDetailNodeSets(InGpuVisualDetailTiles,InCpuVisualDetailTiles,Error)) return false; Stats.VisualDetailPrepSeconds=std::chrono::duration(Clock::now()-Substage).count(); Stats.IntervalSeconds=std::chrono::duration(Clock::now()-Stage).count(); Stage=Clock::now(); Substage=Clock::now(); ApplyDetailBand(Stats.DetailBandLines); Stats.DetailApplySeconds=std::chrono::duration(Clock::now()-Substage).count(); Substage=Clock::now(); Stats.SurfaceProxyNodes+=CompleteSignEdgeDistances(); Stats.SignEdgeDistanceSeconds=std::chrono::duration(Clock::now()-Substage).count(); Stats.SurfaceSeconds=std::chrono::duration(Clock::now()-Stage).count(); // Route validation is world-wide. Detail regions reuse the same authored route data and // do not need to repeat the 5,642-point world audit for every entering tile group. Substage=Clock::now(); const bool bPassageRoutesValid=!bValidatePassageRoutes||ValidatePassageRoutes(); if(!bPassageRoutesValid) { if(VF_IsL88PassageCompositionDisabled() &&!VF_IsL88BoundaryBandsDisabled() &&VF_IsL88AllowPassageAblationWalkabilityFailure()) { std::printf("[L88PassageAblation] route walkability failed as expected; " "continuing only to measure passage-off/bands-on readiness\n"); } else { Error="compiled shape world contains a solid passage centreline sample"; return false; } } Stats.PassageValidationSeconds=std::chrono::duration(Clock::now()-Substage).count(); Built=true; if(bIncludeFarWorld)BuildFarWorld(); const uint64 WordBytes=static_cast(RockWords.size()+CandidateWords.size())*sizeof(uint64); uint64 CandidateAxisBytes=0; for(const auto& AxisWords:CandidateAxisWords) CandidateAxisBytes+=static_cast(AxisWords.capacity())*sizeof(uint64); uint64 BinReferences=0,BinCapacity=0,InlineBins=0,OverflowBins=0; for(const auto& Bin:SpatialBins) { BinReferences+=Bin.second.size();BinCapacity+=Bin.second.capacity(); if(Bin.second.size()<=FBinItems::InlineCapacity)++InlineBins; else ++OverflowBins; } std::printf("[ShapeProfile] local_primitives=%.3fms line_bins=%.3fms rock_lines_wall=%.3fms rock_lines_worker=%.3fms candidate_axes_wall=%.3fms candidate_axes_worker=%.3fms candidate_merge_wall=%.3fms candidate_merge_worker=%.3fms candidate_merge_serial=%.3fms detail_lines_wall=%.3fms detail_lines_worker=%.3fms\n", Stats.LocalPrimitiveSeconds*1000.0,Stats.LocalLineBinSeconds*1000.0, Stats.RockIntervalLines.WallSeconds*1000.0,Stats.RockIntervalLines.WorkerSeconds*1000.0, Stats.CandidateAxisLines.WallSeconds*1000.0,Stats.CandidateAxisLines.WorkerSeconds*1000.0, Stats.CandidateMergeLines.WallSeconds*1000.0,Stats.CandidateMergeLines.WorkerSeconds*1000.0, Stats.CandidateMergeSeconds*1000.0,Stats.DetailBandLines.WallSeconds*1000.0, Stats.DetailBandLines.WorkerSeconds*1000.0); std::printf("[ShapeSubstage] detail_apply=%.3fms sign_edge_distances=%.3fms passage_validation=%.3fms shared_field_primitives=%.3fms far_parents=%.3fms\n", Stats.DetailApplySeconds*1000.0,Stats.SignEdgeDistanceSeconds*1000.0, Stats.PassageValidationSeconds*1000.0,Stats.SharedFieldSeconds*1000.0, Stats.FarShapeSeconds*1000.0); std::printf("[ShapeBreakdown] owner_shapes=%.3fms owner_shape_worker=%.3fms owner_bins=%.3fms packed_signs=%.3fms passage_prep=%.3fms candidate_sweeps=%.3fms visual_detail_prep=%.3fms detail_band=%.3fms sign_edge_repair=%.3fms route_validation=%.3fms far_parent_shapes=%.3fms far_shared_primitives=%.3fms far_bins=%.3fms\n", Stats.LocalPrimitiveSeconds*1000.0,Stats.OwnerShapeWorkerSeconds*1000.0, Stats.LocalLineBinSeconds*1000.0, Stats.PackedSignBuildSeconds*1000.0,Stats.PassageSweepPrepSeconds*1000.0, Stats.CandidateSweepPrepSeconds*1000.0,Stats.VisualDetailPrepSeconds*1000.0, Stats.DetailApplySeconds*1000.0,Stats.SignEdgeDistanceSeconds*1000.0, Stats.PassageValidationSeconds*1000.0,Stats.FarParentShapeSeconds*1000.0, Stats.FarSharedPrimitiveSeconds*1000.0,Stats.FarBinSeconds*1000.0); std::printf("[ShapeAllocation] words=%.2fMiB candidate_axis_words=%.2fMiB proxy=%.2fMiB primitives=%.2fMiB spatial_bins=%llu spatial_bin_refs=%llu spatial_bin_capacity=%llu inline_bins=%llu overflow_bins=%llu far_primitive_build=%.3fms far_bin_rebuild=%.3fms\n", static_cast(WordBytes)/1048576.0,static_cast(CandidateAxisBytes)/1048576.0, static_cast(Stats.SignNodes*sizeof(int16))/1048576.0, static_cast(Primitives.capacity()*sizeof(FPrimitive))/1048576.0, static_cast(SpatialBins.size()), static_cast(BinReferences), static_cast(BinCapacity), static_cast(InlineBins), static_cast(OverflowBins),Stats.FarPrimitiveSeconds*1000.0, Stats.FarBinSeconds*1000.0); std::printf("[ShapeCompile] primitives=%llu rooms=%llu tunnels=%llu passages=%llu nodes=%llu packed=%.2f MiB surface_proxy_nodes=%llu proxy_address_capacity=%.2f MiB workers=%d\n", static_cast(Stats.PrimitiveCount), static_cast(Stats.RoomCount), static_cast(Stats.TunnelCount), static_cast(Stats.PassageCount), static_cast(Stats.SignNodes), static_cast(WordBytes)/1048576.0, static_cast(Stats.SurfaceProxyNodes), static_cast(Stats.SignNodes*sizeof(int16))/1048576.0,WorkerCount); std::printf("[ShapeStage] local-shapes=%.3f ms intervals+signs=%.3f ms surface-band=%.3f ms far-parents=%.3f ms total=%.3f ms candidates=%llu flips=%llu walk=%llu/%llu\n", Stats.ShapeSeconds*1000.0,Stats.IntervalSeconds*1000.0,Stats.SurfaceSeconds*1000.0,Stats.FarShapeSeconds*1000.0, std::chrono::duration(Clock::now()-TotalStart).count()*1000.0, static_cast(Stats.SurfaceCandidates), static_cast(Stats.DetailFlips), static_cast(Stats.WalkSamples-Stats.WalkFailures), static_cast(Stats.WalkSamples)); std::printf("[RoomFloorJoinAudit] automatic_room_overlaps=%llu thin_positive_support_overlays=0 status=PASS\n", static_cast(Stats.RoomFloorJoinCount)); const double SmoothUnionGrowthBound=JoinBlendRadius/6.0; const bool bFloorSlopePass=Stats.MaxUnmarkedFloorSlopeDelta<=0.75; const bool bFloorBlendPass=Stats.MaxFloorProfileEndpointValueError<=0.01 &&Stats.MaxFloorProfileEndpointSlopeError<=0.001 &&Stats.FloorProfileEndpointStepControlDetected &&Stats.FloorProfileEndpointSlopeControlDetected; std::printf("[FloorSlopeJoinAudit] raw_unmarked_max_delta=%.5f vox generated_tangent_delta=%.5f vox bend_direction_slope_change=%.5f vox marked_max_delta=%.5f vox marked_joins=%llu threshold=0.750 status=%s\n", Stats.MaxRawUnmarkedFloorSlopeDelta,Stats.MaxUnmarkedFloorSlopeDelta, Stats.MaxUnmarkedFloorDirectionSlopeDelta,Stats.MaxMarkedFloorSlopeDelta, static_cast(Stats.MarkedFloorSlopeJoins),bFloorSlopePass?"PASS":"FAIL"); std::printf("[FloorProfileBlend] c1_segments=%llu max_chord_deviation=%.3f vox informational endpoint_value_residual=%.8f vox endpoint_slope_residual=%.8f vox_per_m thresholds=(0.010,0.001) negative_controls=%s/%s status=%s\n", static_cast(Stats.CurvedFloorSegments),Stats.MaxFloorCurveDeviation, Stats.MaxFloorProfileEndpointValueError,Stats.MaxFloorProfileEndpointSlopeError, Stats.FloorProfileEndpointStepControlDetected?"step_detected":"step_missed", Stats.FloorProfileEndpointSlopeControlDetected?"slope_detected":"slope_missed", bFloorBlendPass?"PASS":"FAIL"); std::printf("[TunnelRadiusAudit] shared_joins=%llu legacy_fixed_segment_max_step=%.4f vox smoothed_profile_max_step=%.4f vox tapered_endpoint_max_delta=%.6f vox radius_slope_join_delta=%.8f floor_endpoint_max_delta=%.6f vox floor_slope_join_delta=%.5f unmarked_floor_slope_delta=%.5f marked_floor_slope_delta=%.5f marked_slope_joins=%llu smooth_union_growth_bound=%.4f vox thresholds=(0.005,0.000001,0.005,0.25) status=%s\n", static_cast(Stats.CapsuleJoinCount),Stats.LegacySegmentRadiusStep, Stats.SmoothedProfileRadiusStep,Stats.MaxCapsuleJoinRadiusDelta, Stats.MaxCapsuleJoinRadiusSlopeDelta, Stats.MaxCapsuleJoinFloorDelta,Stats.MaxCapsuleJoinFloorSlopeDelta, Stats.MaxUnmarkedFloorSlopeDelta,Stats.MaxMarkedFloorSlopeDelta, static_cast(Stats.MarkedFloorSlopeJoins),SmoothUnionGrowthBound, Stats.MaxCapsuleJoinRadiusDelta<=0.005&&Stats.MaxCapsuleJoinRadiusSlopeDelta<=1.0e-6 &&Stats.MaxCapsuleJoinFloorDelta<=0.005 &&SmoothUnionGrowthBound<=0.25?"PASS":"FAIL"); const double BendHorizontal=std::sqrt(Stats.MaxTunnelBendDirection.X*Stats.MaxTunnelBendDirection.X +Stats.MaxTunnelBendDirection.Y*Stats.MaxTunnelBendDirection.Y); const double BendYaw=std::atan2(Stats.MaxTunnelBendDirection.X,Stats.MaxTunnelBendDirection.Y)*57.29577951308232; const double BendPitch=std::atan2(Stats.MaxTunnelBendDirection.Z,BendHorizontal)*57.29577951308232; std::printf("[TunnelBendAudit] sharpest_bend=%.2f deg centerline=(%.2f,%.2f,%.2f) vox tangent=(%.4f,%.4f,%.4f) camera_yaw=%.2f camera_pitch=%.2f\n", Stats.MaxTunnelBendDegrees,Stats.MaxTunnelBendVoxels.X, Stats.MaxTunnelBendVoxels.Y,Stats.MaxTunnelBendVoxels.Z, Stats.MaxTunnelBendDirection.X,Stats.MaxTunnelBendDirection.Y, Stats.MaxTunnelBendDirection.Z,BendYaw,BendPitch); std::fflush(stdout); return true; } bool BuildAnalyticRegion(FStandaloneOwnerWorld& World,const FIntVector& MinVoxels, const FIntVector& MaxVoxels,const FIntVector& ClipMinVoxels, const FIntVector& ClipMaxVoxels,EDetailVariant Variant, bool bInRoundTunnels, const VoxelForgeCore::WorldAssemblyManifest* InWorldAssembly, const VoxelForgeCore::ResolvedLayoutSnapshotV3* InResolvedLayout, std::string& Error) { using Clock=std::chrono::steady_clock; Owner=&World;WorldAssembly=InWorldAssembly;ResolvedLayout=InResolvedLayout; WorkerCount=1;DetailVariant=Variant;Scheduler=nullptr; // Analytic pages retain their bounded owner bins for route bookkeeping. Once published, // the Core world binds each page to its base shape as the single density evaluator. bUseNativeManifestFields=true; BindResolvedStratumFields(); RoundTunnelsEnabled=bInRoundTunnels;bAnalyticOnly=true;bProfileDetailBand=false; for(int32 A=0;A<3;++A) { Min[A]=(&MinVoxels.X)[A]-2;Max[A]=(&MaxVoxels.X)[A]+2; ClipMin[A]=(&ClipMinVoxels.X)[A];ClipMax[A]=(&ClipMaxVoxels.X)[A]; Size[A]=Max[A]-Min[A]+1; if(Size[A]<=0||Size[A]>4096||ClipMax[A](Clock::now()-Stage).count(); Stats.PrimitiveCount=Primitives.size(); if(Primitives.empty()) {Error="owner shape extraction produced no rooms, tunnels, or passages";return false;} RebuildSharedFieldPrimitives(); Built=true; return true; } void InitializeNativeSurfaceOnly(FStandaloneOwnerWorld& World, const VoxelForgeCore::WorldAssemblyManifest* InWorldAssembly, const VoxelForgeCore::ResolvedLayoutSnapshotV3* InResolvedLayout, const FIntVector& InMinimum,const FIntVector& InMaximum) { Owner=&World;WorldAssembly=InWorldAssembly;ResolvedLayout=InResolvedLayout; NativeOnlyMinimum=InMinimum;NativeOnlyMaximum=InMaximum; bUseNativeManifestFields=true;bNativeOnly=true;bAnalyticOnly=true;Built=true; BindResolvedStratumFields(); NativeOnlyViolations.store(0,std::memory_order_relaxed); } bool BuildShapeDependencyProof(FStandaloneOwnerWorld& World, const FIntVector& MinVoxels,const FIntVector& MaxVoxels,bool bInRoundTunnels, const VoxelForgeCore::ResolvedLayoutSnapshotV3* InResolvedLayout=nullptr) { Owner=&World;ResolvedLayout=InResolvedLayout;WorkerCount=1;RoundTunnelsEnabled=bInRoundTunnels; bAnalyticOnly=true;bNativeOnly=false;Stats={}; for(int32 Axis=0;Axis<3;++Axis) { Min[Axis]=(&MinVoxels.X)[Axis]-2;Max[Axis]=(&MaxVoxels.X)[Axis]+2; ClipMin[Axis]=(&MinVoxels.X)[Axis];ClipMax[Axis]=(&MaxVoxels.X)[Axis]; Size[Axis]=Max[Axis]-Min[Axis]+1; if(Size[Axis]<=0||Size[Axis]>4096||ClipMax[Axis]=BMin[Axis] &&Primitive.Min[Axis]-DetailBand<=BMax[Axis]; if(bOverlaps)return true; } for(const FInterval& Range:LayerRanges) if(Range.Lo<=BMax[2]&&Range.Hi>=BMin[2] &&(Range.Lo>=BMin[2]||Range.Hi<=BMax[2]))return true; return false; } bool IsNativeOnly() const { return bNativeOnly; } uint32 GetNativeOnlyViolationCount() const { return NativeOnlyViolations.load(std::memory_order_relaxed); } void RecordNativeOnlyViolation() const { NativeOnlyViolations.fetch_add(1,std::memory_order_relaxed); } const FBuildStats& GetStats() const { return Stats; } bool IsBuilt() const { return Built; } bool HasFarWorld() const { return FarBuilt; } const std::vector& GetFarConnectorSegments() const { return FarConnectorSegments; } void FindFarConnectorCandidates(const FIntVector& QueryMin,const FIntVector& QueryMax, std::vector& OutIndices) const { constexpr int32 BinSize=2048; OutIndices.clear(); const int32 MinX=FloorDiv(QueryMin.X,BinSize),MaxX=FloorDiv(QueryMax.X,BinSize); const int32 MinY=FloorDiv(QueryMin.Y,BinSize),MaxY=FloorDiv(QueryMax.Y,BinSize); const int32 MinZ=FloorDiv(QueryMin.Z,BinSize),MaxZ=FloorDiv(QueryMax.Z,BinSize); for(int32 Z=MinZ;Z<=MaxZ;++Z)for(int32 Y=MinY;Y<=MaxY;++Y) for(int32 X=MinX;X<=MaxX;++X) { const auto It=FarConnectorSpatialBins.find({X,Y,Z}); if(It!=FarConnectorSpatialBins.end()) OutIndices.insert(OutIndices.end(),It->second.begin(),It->second.end()); } std::sort(OutIndices.begin(),OutIndices.end()); OutIndices.erase(std::unique(OutIndices.begin(),OutIndices.end()),OutIndices.end()); } static uint32 FarFieldBinHash(int32 X,int32 Y,int32 Z) { uint32 Hash=2166136261u; Hash=(Hash^static_cast(X))*16777619u; Hash=(Hash^static_cast(Y))*16777619u; Hash=(Hash^static_cast(Z))*16777619u; Hash^=Hash>>16;Hash*=0x7FEB352Du;Hash^=Hash>>15; Hash*=0x846CA68Bu;Hash^=Hash>>16; return Hash; } static VoxelForgeCore::FarFieldPrimitiveGpu ToFarFieldGpuPrimitive(const VF_FieldPrimitive& S) { VoxelForgeCore::FarFieldPrimitiveGpu G{}; G.flags=S.flags; for(int32 A=0;A<3;++A) { G.minVoxel[A]=S.minVoxel[A];G.maxVoxel[A]=S.maxVoxel[A]; G.center[A]=static_cast(S.center[A]); G.capsuleStart[A]=static_cast(S.capsuleStart[A]); G.capsuleDelta[A]=static_cast(S.capsuleDelta[A]); G.roundedHalfExtent[A]=static_cast(S.roundedHalfExtent[A]); } for(int32 I=0;I<9;++I)G.q[I]=static_cast(S.q[I]); G.minRadius=static_cast(S.minRadius); G.capsuleAxisLengthSquared=static_cast(S.capsuleAxisLengthSquared); G.capsuleAxisLength=static_cast(S.capsuleAxisLength); G.capsuleStartRadius=static_cast(S.capsuleStartRadius); G.capsuleEndRadius=static_cast(S.capsuleEndRadius); G.profileStartT=static_cast(S.profileStartT);G.profileEndT=static_cast(S.profileEndT); G.profileStartRadius=static_cast(S.profileStartRadius);G.profileEndRadius=static_cast(S.profileEndRadius); G.radiusSlopeStart=static_cast(S.radiusSlopeStart);G.radiusSlopeEnd=static_cast(S.radiusSlopeEnd); G.rounding=static_cast(S.rounding); G.blendRadius=static_cast(S.blendRadius);G.floorBlendRadius=static_cast(S.floorBlendRadius); G.floorCenterZ=static_cast(S.floorCenterZ);G.floorSlopeX=static_cast(S.floorSlopeX); G.floorSlopeY=static_cast(S.floorSlopeY); G.floorCurveOriginX=static_cast(S.floorCurveOriginX);G.floorCurveOriginY=static_cast(S.floorCurveOriginY); G.floorCurveDeltaX=static_cast(S.floorCurveDeltaX);G.floorCurveDeltaY=static_cast(S.floorCurveDeltaY); G.floorCurveLength=static_cast(S.floorCurveLength); G.floorCurveStartZ=static_cast(S.floorCurveStartZ);G.floorCurveEndZ=static_cast(S.floorCurveEndZ); G.floorCurveTangentStart=static_cast(S.floorCurveTangentStart); G.floorCurveTangentEnd=static_cast(S.floorCurveTangentEnd); G.floorCurveStartBlend=static_cast(S.floorCurveStartBlend); G.floorCurveEndBlend=static_cast(S.floorCurveEndBlend); return G; } std::shared_ptr GetFarFieldWorldInput( std::string& Error) const { std::lock_guard Lock(FarFieldGpuInputMutex); if(CachedFarFieldGpuInput)return CachedFarFieldGpuInput; if(!FarBuilt||SpatialBins.empty()||SharedFieldPrimitives.empty()) {Error="far world has no shared primitives or spatial bins";return {};} if(SpatialBins.size()>std::numeric_limits::max()/2u) {Error="far world spatial-bin table exceeds the GPU 32-bit slot limit";return {};} const size_t TableCapacity=std::bit_ceil(std::max(2,SpatialBins.size()*2)); if(TableCapacity>std::numeric_limits::max()) {Error="far world spatial-bin table exceeds the D3D11 element limit";return {};} auto Data=std::make_shared(); Data->spatialBinSize=static_cast(ActiveSpatialBinSize); Data->spatialBinMask=static_cast(TableCapacity-1); Data->fieldSeed=static_cast(Owner?Owner->Seed:0); for(int32 Axis=0;Axis<3;++Axis) {Data->detailMinVoxel[Axis]=Min[Axis];Data->detailMaxVoxel[Axis]=Max[Axis];} Data->shapes.reserve(SharedFieldPrimitives.size()); for(const VF_FieldPrimitive& Shape:SharedFieldPrimitives) Data->shapes.push_back(ToFarFieldGpuPrimitive(Shape)); Data->detailLayers.reserve(LayerFields.size()); for(const FLayerField& Field:LayerFields) { const FStrateGenerationParams& Params=Field.Params; Data->detailLayers.push_back({Field.Lo,Field.Hi,Params.SurfaceRoughness, Params.RoughnessFrequency,Params.VerticalScale,Params.DomainWarpStrength, Params.DomainWarpFrequency,static_cast(Params.RoughnessNoiseType)}); } Data->spatialBins.resize(TableCapacity); size_t ReferenceCount=0; for(const auto& Bin:SpatialBins)ReferenceCount+=Bin.second.size(); Data->spatialBinShapeIds.reserve(ReferenceCount); for(const auto& [Key,Items]:SpatialBins) { if(Items.size()>std::numeric_limits::max() ||Data->spatialBinShapeIds.size()+Items.size()>std::numeric_limits::max()) {Error="far world spatial-bin shape IDs exceed the GPU 32-bit offset limit";return {};} uint32 Slot=FarFieldBinHash(Key.X,Key.Y,Key.Z)&Data->spatialBinMask; while(Data->spatialBins[Slot].count!=0)Slot=(Slot+1)&Data->spatialBinMask; VoxelForgeCore::FarFieldSpatialBin& GpuBin=Data->spatialBins[Slot]; GpuBin.x=Key.X;GpuBin.y=Key.Y;GpuBin.z=Key.Z; GpuBin.offset=static_cast(Data->spatialBinShapeIds.size()); GpuBin.count=static_cast(Items.size()); for(const uint32 Id:Items)Data->spatialBinShapeIds.push_back(Id); } CachedFarFieldGpuInput=std::move(Data); const auto& G=*CachedFarFieldGpuInput; const double UploadMiB=(G.shapes.size()*sizeof(VoxelForgeCore::FarFieldPrimitiveGpu) +G.spatialBins.size()*sizeof(VoxelForgeCore::FarFieldSpatialBin) +G.spatialBinShapeIds.size()*sizeof(uint32))/1048576.0; std::printf("[FarGpuWorldData] shapes=%zu spatial_bins=%zu hash_slots=%zu bin_shape_ids=%zu bin_size=%d detail_min=(%d,%d,%d) detail_max=(%d,%d,%d) bytes=%.3fMiB upload_scope=once_per_world\n", G.shapes.size(),SpatialBins.size(),G.spatialBins.size(),G.spatialBinShapeIds.size(), ActiveSpatialBinSize,G.detailMinVoxel[0],G.detailMinVoxel[1],G.detailMinVoxel[2], G.detailMaxVoxel[0],G.detailMaxVoxel[1],G.detailMaxVoxel[2],UploadMiB); return CachedFarFieldGpuInput; } bool ExportFarFieldTileInput(const FIntVector& Origin,int32 Cells,int32 Step, bool bNearDetail,VoxelForgeCore::FarFieldTileInput& Out,std::string& Error) const { if(!FarBuilt||Cells<=0||Cells>32||Step<=0) {Error="far field export requires a built far world and a bounded coarse tile";return false;} const int32 Dim=Cells+3; const size_t SampleCount=static_cast(Dim)*Dim*Dim; Out={};Error.clear();Out.origin={Origin.X,Origin.Y,Origin.Z};Out.cells=Cells;Out.step=Step;Out.sampleDim=Dim; Out.nearDetailEnabled=bNearDetail; Out.worldInput=GetFarFieldWorldInput(Error); if(!Out.worldInput)return false; if(LayerRanges.empty()) {Out.strataLo=-100000.0f;Out.strataHi=100000.0f;} else {Out.strataLo=static_cast(LayerRanges[0].Lo);Out.strataHi=static_cast(LayerRanges[0].Hi);} Out.strataBlend=StrataCaveBlendRadius; if(Owner&&Owner->Manager) { const double TileZ0=Origin.Z,TileZ1=Origin.Z+static_cast(Cells)*Step; for(const FStrateSlot& Slot:Owner->Manager->GetLayout()) { FResolvedStratumBounds Bounds; if(!GetResolvedStratumBounds(Slot,Bounds))continue; const float HalfWidth=static_cast(std::max(8.0, Bounds.SealThickness+8.0)); const float Boundaries[2]={static_cast(Bounds.BottomZ), static_cast(Bounds.TopZ)}; for(const float Z:Boundaries) if(Z+HalfWidth>=TileZ0&&Z-HalfWidth<=TileZ1) Out.stratumBoundaries.push_back({Z,HalfWidth}); } std::sort(Out.stratumBoundaries.begin(),Out.stratumBoundaries.end(), [](const auto& A,const auto& B){return A.z::max()); const int32 LatticeMin[3]={Origin.X-Step,Origin.Y-Step,Origin.Z-Step}; const int32 LatticeMax[3]={Origin.X+(Cells+1)*Step,Origin.Y+(Cells+1)*Step, Origin.Z+(Cells+1)*Step}; std::vector CriticalCandidates,OverlayCandidates; for(uint32 Id=0;Id=LatticeMin[Axis]-4 &&Shape.Min[Axis]<=LatticeMax[Axis]+4; OverlayOverlap&=Shape.Max[Axis]>=LatticeMin[Axis] &&Shape.Min[Axis]<=LatticeMax[Axis]; } if(CriticalOverlap)CriticalCandidates.push_back(Id); if(OverlayOverlap)OverlayCandidates.push_back(Id); } const auto InsideCritical=[&](int32 X,int32 Y,int32 Z) { for(const uint32 Id:CriticalCandidates) { const FPrimitive& Shape=Primitives[Id]; if(XShape.Max[0]||YShape.Max[1] ||ZShape.Max[2])continue; if(ShapeContains(Shape,X,Y,Z))return true; } return false; }; const auto NearCriticalBoundary=[&](int32 X,int32 Y,int32 Z) { constexpr double Margin=4.0; for(const uint32 Id:CriticalCandidates) { const FPrimitive& Shape=Primitives[Id]; if(XShape.Max[0]+Margin ||YShape.Max[1]+Margin ||ZShape.Max[2]+Margin)continue; float Distance=ShapeDistance(Shape,X,Y,Z); if(Shape.bHasFloorClip) { const float FloorDistance=static_cast(FloorAt(Shape,X,Y)-Z); Distance=SmoothMaxDistance(Distance,FloorDistance, static_cast(Shape.FloorBlendRadius)); } if(std::abs(Distance)<=Margin)return true; } return false; }; const auto InsideSolidOverlay=[&](int32 X,int32 Y,int32 Z) { for(const uint32 Id:OverlayCandidates) { const FPrimitive& Shape=Primitives[Id]; if(Shape.bCarve||XShape.Max[0] ||YShape.Max[1]||ZShape.Max[2])continue; if(ShapeDistance(Shape,X,Y,Z)<=0.0f)return true; } return false; }; const auto& Connectors=GetFarConnectorSegments(); // CPU repair panels are only used by the survival path at LOD6 and above. // Keep this sparse repair out of LOD2..5, where the base GPU surface has // sufficient resolution and the CPU path emits no panel geometry. if(Cells*Step>=(CHUNK_SIZE<<6)) { std::vector ConnectorCandidates; FindFarConnectorCandidates( FIntVector(Origin.X-64,Origin.Y-64,Origin.Z-64), FIntVector(Origin.X+Cells*Step+64,Origin.Y+Cells*Step+64, Origin.Z+Cells*Step+64),ConnectorCandidates); const double TileMin[3]={static_cast(Origin.X),static_cast(Origin.Y), static_cast(Origin.Z)}; const double TileMax[3]={TileMin[0]+Cells*Step,TileMin[1]+Cells*Step, TileMin[2]+Cells*Step}; for(const uint32 ConnectorIndex:ConnectorCandidates) { if(ConnectorIndex>=Connectors.size())continue; const FFarConnectorSegment& Connector=Connectors[ConnectorIndex]; const double Start[3]={Connector.Start.X,Connector.Start.Y,Connector.Start.Z}; const double Delta[3]={Connector.End.X-Connector.Start.X, Connector.End.Y-Connector.Start.Y,Connector.End.Z-Connector.Start.Z}; double TMin=0.0,TMax=1.0;bool Intersects=true; for(int32 Axis=0;Axis<3;++Axis) { if(std::abs(Delta[Axis])<1.0e-9) {if(Start[Axis]TileMax[Axis])Intersects=false;continue;} double A=(TileMin[Axis]-Start[Axis])/Delta[Axis]; double B=(TileMax[Axis]-Start[Axis])/Delta[Axis]; if(A>B)std::swap(A,B); TMin=std::max(TMin,A);TMax=std::min(TMax,B); if(TMin>TMax){Intersects=false;break;} } const double Length=std::sqrt(Delta[0]*Delta[0]+Delta[1]*Delta[1]+Delta[2]*Delta[2]); if(!Intersects||Length<=1.0e-6||TMax-TMin<=1.0e-7)continue; const uint32 AxialSampleCount=std::max(1,static_cast(std::ceil(Length/128.0))); const double FirstValue=std::ceil(TMin*AxialSampleCount-0.5-1.0e-8); const double LastValue=std::floor(TMax*AxialSampleCount-0.5+1.0e-8); const uint32 FirstSample=static_cast(std::clamp(FirstValue,0.0, static_cast(AxialSampleCount))); const uint32 SampleCount=LastValue>=FirstValue?static_cast(std::max(0.0, std::min(LastValue,static_cast(AxialSampleCount-1))-FirstValue+1.0)):0; const uint32 RingCount=SampleCount+2; const uint64 VertexCount=static_cast(RingCount-1)*24u; if(Out.connectorVertexCount+VertexCount>std::numeric_limits::max()) {Error="far connector panel output exceeds the 32-bit tile limit";return false;} VoxelForgeCore::FarFieldConnectorPanel Panel{}; for(int32 Axis=0;Axis<3;++Axis) {Panel.start[Axis]=static_cast(Start[Axis]);Panel.end[Axis]=static_cast(Start[Axis]+Delta[Axis]);} Panel.radius=Connector.Radius;Panel.tMin=static_cast(TMin);Panel.tMax=static_cast(TMax); Panel.ringCount=RingCount;Panel.ringOffset=Out.connectorRingCount; Panel.vertexOffset=Out.connectorVertexCount; Panel.axialSampleStart=FirstSample;Panel.axialSampleCount=AxialSampleCount; Out.connectorRingCount+=Panel.ringCount;Out.connectorVertexCount+=static_cast(VertexCount); Out.connectorPanels.push_back(Panel); } } size_t SampleIndex=0; for(int32 Z=-1;Z<=Cells+1;++Z) for(int32 Y=-1;Y<=Cells+1;++Y) for(int32 X=-1;X<=Cells+1;++X,++SampleIndex) { const int32 WX=Origin.X+X*Step,WY=Origin.Y+Y*Step,WZ=Origin.Z+Z*Step; if(bNearDetail&&!bAnalyticOnly&&Contains(WX,WY,WZ)) { const size_t Column=static_cast(WY-Min[1])*Size[0] +static_cast(WX-Min[0]); const size_t Zi=static_cast(WZ-Min[2]); const size_t Word=Column*WordsPerZ+(Zi>>6); const uint64 Mask=uint64{1}<<(Zi&63u); if((CandidateWords[Word]&Mask)==0) Out.nearPackedProxy[SampleIndex]=0u; else { ++Out.nearPackedCandidateSampleCount; const size_t Node=Zi*static_cast(Size[1])*Size[0]+Column; const int32 Q=std::abs(static_cast(SurfaceDistanceQ[Node])); const uint32 Magnitude=static_cast(std::clamp(Q,1,32767)); if(Magnitude==1u) { // At the minimum quantization step a one-ULP GPU sign flip // can change an entire marching-cubes cell. Keep this sparse // ambiguous boundary decision aligned with the packed cache. const bool bRock=(RockWords[Word]&Mask)!=0; Out.nearPackedProxy[SampleIndex]=0x40000000u |(bRock?0x80000000u:0u)|Magnitude; } else Out.nearPackedProxy[SampleIndex]=Magnitude; } } if(!bNearDetail&&WX>=Min[0]&&WX<=Max[0]&&WY>=Min[1]&&WY<=Max[1] &&WZ>=Min[2]&&WZ<=Max[2]) {Error="tile lattice intersects the near analytic/detail region; this export is far-field only";return false;} if(InsideCritical(WX,WY,WZ)) { Out.protectedAir[SampleIndex>>5]|=1u<<(SampleIndex&31u); ++Out.protectedSampleCount; } else if(NearCriticalBoundary(WX,WY,WZ)) { // Preserve exact signs within roughly one far-field lattice step of // route air. This catches thin walls/floors at the route boundary // without evaluating the ordinary far field on the CPU. const float CriticalDensity=AnalyticDensityAt(static_cast(WX), static_cast(WY),static_cast(WZ)); Out.protectedThin[SampleIndex>>5]|=1u<<(SampleIndex&31u); if(CriticalDensity>=0.0f)Out.protectedAir[SampleIndex>>5]|=1u<<(SampleIndex&31u); else { Out.protectedSolid[SampleIndex>>5]|=1u<<(SampleIndex&31u); ++Out.protectedSolidSampleCount; } ++Out.protectedThinSampleCount; } else { bool bSealSample=false; for(const VoxelForgeCore::FarFieldStratumBoundary& Boundary:Out.stratumBoundaries) { if(std::abs(static_cast(WZ)-Boundary.z)>0.5)continue; // Only the exact lattice samples that lie on a seal height need a // CPU sign override. The surrounding far field remains GPU-decided. const float SealDensity=AnalyticDensityAt(static_cast(WX), static_cast(WY),static_cast(WZ)); if(SealDensity>=0.0f) Out.protectedAir[SampleIndex>>5]|=1u<<(SampleIndex&31u); else Out.protectedSolid[SampleIndex>>5]|=1u<<(SampleIndex&31u); ++Out.protectedSealSampleCount; if(SealDensity<0.0f)++Out.protectedSolidSampleCount; bSealSample=true; break; } if(!bSealSample&&InsideSolidOverlay(WX,WY,WZ)) { Out.protectedSolid[SampleIndex>>5]|=1u<<(SampleIndex&31u); ++Out.protectedSolidSampleCount; ++Out.protectedSolidOverlaySampleCount; } } } return true; } bool IsFarFieldTileOutsideNearRegion(const FIntVector& Origin,int32 Cells,int32 Step) const { if(!FarBuilt||Cells<=0||Cells>32||Step<=0)return false; const int32 Origins[3]={Origin.X,Origin.Y,Origin.Z}; for(int32 Axis=0;Axis<3;++Axis) { const double First=std::ceil((static_cast(Min[Axis])-Origins[Axis])/Step); const double Last=std::floor((static_cast(Max[Axis])-Origins[Axis])/Step); if(First>Cells+1||Last< -1)return true; } return false; } bool DebugCriticalAirAt(double X,double Y,double Z) const { return IsInsideCriticalAir(X,Y,Z); } bool DebugSolidOverlayAt(double X,double Y,double Z) const { return IsInsideSolidOverlay(X,Y,Z); } void SetEditTransaction(std::vector Brushes) { EditBrushes=std::move(Brushes); EditBoundsMin={MAX_int32,MAX_int32,MAX_int32}; EditBoundsMax={MIN_int32,MIN_int32,MIN_int32}; for(const FEditBrush& Brush:EditBrushes) { double Extent[3]{}; if(Brush.Shape==FEditBrush::EShape::Sphere) for(int32 Axis=0;Axis<3;++Axis)Extent[Axis]=Brush.Radius; else if(Brush.Shape==FEditBrush::EShape::Capsule) for(int32 Axis=0;Axis<3;++Axis)Extent[Axis]=Brush.Radius +(Brush.Operation==FEditBrush::EOperation::DensityOffset ?std::max(Brush.Falloff,0.01f):0.0f); else for(int32 Axis=0;Axis<3;++Axis)Extent[Axis]=Brush.HalfExtent[Axis]+Brush.Rounding +(Brush.Operation==FEditBrush::EOperation::DensityOffset ?std::max(Brush.Falloff,0.01f):0.0f); for(int32 Axis=0;Axis<3;++Axis) { const double Lo=Brush.Shape==FEditBrush::EShape::Capsule ?std::min(Brush.A[Axis],Brush.B[Axis])-Extent[Axis] :Brush.A[Axis]-Extent[Axis]; const double Hi=Brush.Shape==FEditBrush::EShape::Capsule ?std::max(Brush.A[Axis],Brush.B[Axis])+Extent[Axis] :Brush.A[Axis]+Extent[Axis]; (&EditBoundsMin.X)[Axis]=std::min((&EditBoundsMin.X)[Axis],static_cast(std::floor(Lo))); (&EditBoundsMax.X)[Axis]=std::max((&EditBoundsMax.X)[Axis],static_cast(std::ceil(Hi))); } } } void SetSharedDensitySource(const FStandaloneShapeWorld* Source) { SharedDensitySource=Source==this?nullptr:Source; } bool HasEditTransaction() const { return !EditBrushes.empty(); } FIntVector GetEditBoundsMin() const { return EditBoundsMin; } FIntVector GetEditBoundsMax() const { return EditBoundsMax; } void BuildFarWorld() { if(FarBuilt)return; auto Stage=std::chrono::steady_clock::now(); BuildFarParentShapes(); Stats.FarParentShapeSeconds=std::chrono::duration( std::chrono::steady_clock::now()-Stage).count(); Stage=std::chrono::steady_clock::now(); RebuildSharedFieldPrimitives(); Stats.FarSharedPrimitiveSeconds=std::chrono::duration( std::chrono::steady_clock::now()-Stage).count(); Stats.FarPrimitiveSeconds=Stats.FarParentShapeSeconds+Stats.FarSharedPrimitiveSeconds; Stage=std::chrono::steady_clock::now(); BuildSpatialBins(FarSpatialBinSize,false); Stats.FarBinSeconds=std::chrono::duration( std::chrono::steady_clock::now()-Stage).count(); Stats.FarShapeSeconds=Stats.FarPrimitiveSeconds+Stats.FarBinSeconds; Stats.PrimitiveCount=Primitives.size();FarBuilt=true; std::printf("[FarShapeStage] parent_shapes=%.3f ms spatial_bins=%.3f ms total=%.3f ms primitives=%llu\n", Stats.FarPrimitiveSeconds*1000.0,Stats.FarBinSeconds*1000.0,Stats.FarShapeSeconds*1000.0, static_cast(Stats.PrimitiveCount)); std::printf("[FarShapeBreakdown] parent_generation=%.3fms parent_geometry_wall=%.3fms parent_geometry_worker=%.3fms shared_fields=%.3fms bin_rebuild=%.3fms\n", Stats.FarParentShapeSeconds*1000.0,Stats.FarParentGeometryLines.WallSeconds*1000.0, Stats.FarParentGeometryLines.WorkerSeconds*1000.0, Stats.FarSharedPrimitiveSeconds*1000.0,Stats.FarBinSeconds*1000.0); std::fflush(stdout); } // Lattice signs come from packed intervals; a bounded analytic distance proxy supplies // edge interpolation and normals to the existing mesher. float DensityAt(float X,float Y,float Z) const { if(SharedDensitySource)return SharedDensitySource->DensityAt(X,Y,Z); if(const auto* QueryProfile=FScopedPassageTileQueries::CurrentFor(*this); QueryProfile&&QueryProfile->capturesCallerDiagnostics()) RecordOffGridDensityQuery(X,Y,Z, ThreadClassificationStats().DensityOffGridFloatByCaller); FScopedDensityQueryProfile queryProfile(EDensityQueryKind::Float); using namespace VoxelForgeCore::Linux77Profiler; FScopedRoute l77DispatchRoute(ERoute::Dispatch); FScopedPhase l77DispatchPhase(EPhase::Dispatch); if(bNativeOnly) { RecordLinux77Dispatch(ERoute::Native,1); FScopedRoute l77Route(ERoute::Native); FScopedPhase l77Base(EPhase::BaseField); if(!NativeOnlyContains(X,Y,Z)) {RecordNativeOnlyViolation();return 0.0f;} float NativeDensity=0.0f; if(!NativeSurfaceDensityAt(X,Y,Z,NativeDensity)) {RecordNativeOnlyViolation();return 0.0f;} return ApplyEditBrushes(ApplyResolvedStrateComposition(NativeDensity,X,Y,Z),X,Y,Z); } float IslandDensity=0.0f; if(NativeFloatingIslandDensityAt(X,Y,Z,IslandDensity)) { RecordLinux77Dispatch(ERoute::Native,1); FScopedRoute l77Route(ERoute::Native); FScopedPhase l77Base(EPhase::BaseField); ++ThreadClassificationStats().NativeFloatFieldCalls; return ApplyEditBrushes(ApplyResolvedStrateComposition(IslandDensity,X,Y,Z),X,Y,Z); } float NativeDensity=0.0f; if(NativeSurfaceDensityAt(X,Y,Z,NativeDensity)) { RecordLinux77Dispatch(ERoute::Native,1); FScopedRoute l77Route(ERoute::Native); FScopedPhase l77Base(EPhase::BaseField); ++ThreadClassificationStats().NativeFloatFieldCalls; return ApplyEditBrushes(ApplyResolvedStrateComposition(NativeDensity,X,Y,Z),X,Y,Z); } if(UsesLegacySurfaceField(X,Y,Z)) { RecordLinux77Dispatch(ERoute::LegacySurface,1); FScopedRoute l77Route(ERoute::LegacySurface); FScopedPhase l77Base(EPhase::BaseField); return LegacySurfaceDensityAt(X,Y,Z); } int32 IX=0,IY=0,IZ=0; const bool bInteger=NearInteger(X,IX)&&NearInteger(Y,IY)&&NearInteger(Z,IZ); const bool bPacked=!bAnalyticOnly&&bInteger&&Contains(IX,IY,IZ); if(bPacked) { RecordLinux77Dispatch(ERoute::PackedScalar,1); FScopedRoute l77Route(ERoute::PackedScalar); FScopedPhase l77Base(EPhase::BaseField); return PackedDensityAt(IX,IY,IZ,IsRock(IX,IY,IZ),X,Y,Z); } RecordLinux77Dispatch(ERoute::AnalyticScalar,1); FScopedRoute l77Route(ERoute::AnalyticScalar); FScopedPhase l77Base(EPhase::BaseField); return AnalyticDensityAt(X,Y,Z); } float PassageSourceDensityAt(float X,float Y,float Z) const {return NativePassageDensityAt(X,Y,Z);} std::vector OreVeinTileCandidates( const std::array& minimum, const std::array& maximum) const {return BuildOreVeinTileList(minimum,maximum);} VoxelForgeCore::MaterialSampleV1 EvaluateMaterialAt(float X,float Y,float Z, std::span candidates, std::uint64_t* candidateChecks=nullptr) const { if(SharedDensitySource) return SharedDensitySource->MaterialAt(X,Y,Z,candidates,candidateChecks); return MaterialAt(X,Y,Z,candidates,candidateChecks); } VoxelForgeCore::MaterialSampleV1 MaterialAt(float X,float Y,float Z) const { if(SharedDensitySource)return SharedDensitySource->MaterialAt(X,Y,Z); const auto candidates=BuildOreVeinPointList(X,Y,Z); return EvaluateMaterialAt(X,Y,Z,candidates); } std::string DensityDispatchPathAt(float X,float Y,float Z) const { if(SharedDensitySource)return SharedDensitySource->DensityDispatchPathAt(X,Y,Z); float Density=0.0f; if(NativeFloatingIslandDensityAt(X,Y,Z,Density))return "FloatingIslands-native"; const auto* Bound=FindBoundStratumForZ(Z); if(Bound&&Bound->Kind==FBoundStratumField::EKind::Caves &&NativeSurfaceDensityAt(X,Y,Z,Density)) return Bound->Resolved&&VoxelForgeCore::IsNativeLatticeFieldStratum(*Bound->Resolved) ?"Lattice-native":"Caves-native"; if(NativeSurfaceDensityAt(X,Y,Z,Density))return "SurfaceWorld-native"; if(UsesLegacySurfaceField(X,Y,Z))return "SurfaceWorld-legacy"; if(Bound&&Bound->Resolved) { const std::string& Kind=Bound->Resolved->generatorKind; return "compiled-analytic/"+Kind; } return Owner?"compiled-analytic/TunnelNetwork":"compiled-analytic/fallback"; } float DensityAtLattice(int32 X,int32 Y,int32 Z) const { using namespace VoxelForgeCore::Linux77Profiler; if(SharedDensitySource) { if(ThreadContextActive)RecordLinux77Dispatch(ERoute::SharedSource,1); FScopedRoute l77Route(ERoute::SharedSource); FScopedPhase l77Phase(EPhase::BaseField); return SharedDensitySource->DensityAtLattice(X,Y,Z); } FScopedDensityQueryProfile queryProfile(EDensityQueryKind::Lattice); FScopedRoute l77DispatchRoute(ERoute::Dispatch); FScopedPhase l77DispatchPhase(EPhase::Dispatch); if(bNativeOnly) { RecordLinux77Dispatch(ERoute::Native,1); FScopedRoute l77Route(ERoute::Native); FScopedPhase l77Base(EPhase::BaseField); if(!NativeOnlyContains(static_cast(X),static_cast(Y), static_cast(Z))) {RecordNativeOnlyViolation();return 0.0f;} float NativeDensity=0.0f; if(!NativeSurfaceDensityAt(static_cast(X),static_cast(Y), static_cast(Z),NativeDensity)) {RecordNativeOnlyViolation();return 0.0f;} return ApplyEditBrushes(ApplyResolvedStrateComposition(NativeDensity,X,Y,Z),X,Y,Z); } float IslandDensity=0.0f; if(NativeFloatingIslandDensityAt(static_cast(X),static_cast(Y), static_cast(Z),IslandDensity)) { RecordLinux77Dispatch(ERoute::Native,1); FScopedRoute l77Route(ERoute::Native); FScopedPhase l77Base(EPhase::BaseField); ++ThreadClassificationStats().NativeLatticeFieldCalls; return ApplyEditBrushes(ApplyResolvedStrateComposition(IslandDensity,X,Y,Z),X,Y,Z); } float NativeDensity=0.0f; if(NativeSurfaceDensityAt(static_cast(X),static_cast(Y), static_cast(Z),NativeDensity)) { RecordLinux77Dispatch(ERoute::Native,1); FScopedRoute l77Route(ERoute::Native); FScopedPhase l77Base(EPhase::BaseField); ++ThreadClassificationStats().NativeLatticeFieldCalls; return ApplyEditBrushes(ApplyResolvedStrateComposition(NativeDensity,X,Y,Z),X,Y,Z); } if(UsesLegacySurfaceField(static_cast(X),static_cast(Y), static_cast(Z))) { RecordLinux77Dispatch(ERoute::LegacySurface,1); FScopedRoute l77Route(ERoute::LegacySurface); FScopedPhase l77Base(EPhase::BaseField); return LegacySurfaceDensityAt(static_cast(X),static_cast(Y), static_cast(Z)); } const bool bPacked=!bAnalyticOnly&&Contains(X,Y,Z); static const bool bL75TraceTnDensity=[] { const char* Value=std::getenv("VOXELFORGE_L75_TN_BLOCK_REGRESSION"); return Value&&Value[0]=='1'&&Value[1]=='\0'; }(); if(bL75TraceTnDensity&&X==-80&&Y==-20&&Z==-3218) { if(!bPacked) std::printf("[L75TNDensityTrace] packed=0\n"); else { const size_t Column=static_cast(Y-Min[1])*Size[0] +static_cast(X-Min[0]); const size_t Zi=static_cast(Z-Min[2]); const size_t Word=Column*WordsPerZ+(Zi>>6); const uint64 Mask=uint64{1}<<(Zi&63u); const bool bRock=(RockWords[Word]&Mask)!=0; const bool bCandidate=(CandidateWords[Word]&Mask)!=0; const float Analytic=AnalyticDensityAt(static_cast(X), static_cast(Y),static_cast(Z),false); std::printf("[L75TNDensityTrace] packed=%d rock=%d candidate=%d proxy=%d analytic=%.9g\n", bPacked?1:0,bRock?1:0,bCandidate?1:0, bCandidate?static_cast(SurfaceDistanceQ[Zi*static_cast(Size[1])*Size[0] +Column]):0,Analytic); for(const uint32 Id:GetLineCandidates(2,X,Y)) { if(Id>=Primitives.size())continue; const FPrimitive& Primitive=Primitives[Id]; if(!Primitive.bCarve||XPrimitive.Max[0] ||YPrimitive.Max[1] ||ZPrimitive.Max[2])continue; std::printf("[L75TNDensityTrace] primitive=%u carve=1 critical=%d bounds=(%d,%d,%d)..(%d,%d,%d) contains=%d sdf=%.9g\n", Id,Primitive.bCritical?1:0,Primitive.Min[0],Primitive.Min[1],Primitive.Min[2], Primitive.Max[0],Primitive.Max[1],Primitive.Max[2], ShapeContains(Primitive,X,Y,Z)?1:0,ShapeDistance(Primitive,X,Y,Z)); } } } if(bPacked) { RecordLinux77Dispatch(ERoute::PackedScalar,1); FScopedRoute l77Route(ERoute::PackedScalar); FScopedPhase l77Base(EPhase::BaseField); return PackedDensityAt(X,Y,Z,IsRock(X,Y,Z),static_cast(X), static_cast(Y),static_cast(Z)); } RecordLinux77Dispatch(ERoute::AnalyticScalar,1); FScopedRoute l77Route(ERoute::AnalyticScalar); FScopedPhase l77Base(EPhase::BaseField); return AnalyticDensityAt(static_cast(X),static_cast(Y), static_cast(Z)); } float DensityAtUnclamped(float X,float Y,float Z) const { using namespace VoxelForgeCore::Linux77Profiler; if(SharedDensitySource) { if(ThreadContextActive)RecordLinux77Dispatch(ERoute::SharedSource,1); FScopedRoute l77Route(ERoute::SharedSource); FScopedPhase l77Phase(EPhase::BaseField); return SharedDensitySource->DensityAtUnclamped(X,Y,Z); } if(const auto* QueryProfile=FScopedPassageTileQueries::CurrentFor(*this); QueryProfile&&QueryProfile->capturesCallerDiagnostics()) RecordOffGridDensityQuery(X,Y,Z, ThreadClassificationStats().DensityOffGridUnclampedByCaller); FScopedDensityQueryProfile queryProfile(EDensityQueryKind::Unclamped); FScopedRoute l77DispatchRoute(ERoute::Dispatch); FScopedPhase l77DispatchPhase(EPhase::Dispatch); if(bNativeOnly) { RecordLinux77Dispatch(ERoute::Native,1); FScopedRoute l77Route(ERoute::Native); FScopedPhase l77Base(EPhase::BaseField); if(!NativeOnlyContains(X,Y,Z)) {RecordNativeOnlyViolation();return 0.0f;} float NativeDensity=0.0f; if(!NativeSurfaceDensityAt(X,Y,Z,NativeDensity)) {RecordNativeOnlyViolation();return 0.0f;} return ApplyEditBrushes(ApplyResolvedStrateComposition(NativeDensity,X,Y,Z),X,Y,Z); } float IslandDensity=0.0f; if(NativeFloatingIslandDensityAt(X,Y,Z,IslandDensity)) { RecordLinux77Dispatch(ERoute::Native,1); FScopedRoute l77Route(ERoute::Native); FScopedPhase l77Base(EPhase::BaseField); ++ThreadClassificationStats().NativeUnclampedFieldCalls; return ApplyEditBrushes(ApplyResolvedStrateComposition(IslandDensity,X,Y,Z),X,Y,Z); } float NativeDensity=0.0f; if(NativeSurfaceDensityAt(X,Y,Z,NativeDensity)) { RecordLinux77Dispatch(ERoute::Native,1); FScopedRoute l77Route(ERoute::Native); FScopedPhase l77Base(EPhase::BaseField); ++ThreadClassificationStats().NativeUnclampedFieldCalls; return ApplyEditBrushes(ApplyResolvedStrateComposition(NativeDensity,X,Y,Z),X,Y,Z); } if(UsesLegacySurfaceField(X,Y,Z)) { RecordLinux77Dispatch(ERoute::LegacySurface,1); FScopedRoute l77Route(ERoute::LegacySurface); FScopedPhase l77Base(EPhase::BaseField); return LegacySurfaceDensityAt(X,Y,Z); } RecordLinux77Dispatch(ERoute::AnalyticScalar,1); FScopedRoute l77Route(ERoute::AnalyticScalar); FScopedPhase l77Base(EPhase::BaseField); return AnalyticDensityAt(X,Y,Z,false); } VoxelForgeCore::DetailFieldSample DetailSampleAt(float X,float Y,float Z) const { if(SharedDensitySource)return SharedDensitySource->DetailSampleAt(X,Y,Z); if(const auto* QueryProfile=FScopedPassageTileQueries::CurrentFor(*this); QueryProfile&&QueryProfile->capturesCallerDiagnostics()) RecordOffGridDensityQuery(X,Y,Z, ThreadClassificationStats().DensityOffGridDetailByCaller); VoxelForgeCore::DetailFieldSample Sample; if(bNativeOnly) {RecordNativeOnlyViolation();return Sample;} AnalyticDensityAt(X,Y,Z,false,&Sample.displacement, &Sample.caveInfluence,&Sample.floorInfluence); return Sample; } bool IsProxyBandFallback(float Density) const { return std::abs(std::abs(Density)-(DetailSupremum+0.125f))<1.0e-4f; } bool IsAnalyticOnly() const { return bAnalyticOnly; } void DensityAtLatticeX8(int32 XStart,int32 XStep,int32 Y,int32 Z, int32 Count,float OutDensity[8]) const { using namespace VoxelForgeCore::Linux77Profiler; if(SharedDensitySource) { if(ThreadContextActive&&Count>0)RecordLinux77Dispatch(ERoute::SharedSource, static_cast(Count)); FScopedRoute l77Route(ERoute::SharedSource); FScopedPhase l77Phase(EPhase::BaseField); SharedDensitySource->DensityAtLatticeX8(XStart,XStep,Y,Z,Count,OutDensity); return; } Count=std::clamp(Count,0,8); if(Count==0)return; FScopedRoute l77DispatchRoute(ERoute::Dispatch); FScopedPhase l77DispatchPhase(EPhase::Dispatch); FScopedDensityQueryProfile queryProfile(EDensityQueryKind::X8, static_cast(Count)); FScopedDensityQueryBatch queryBatch(queryProfile.IsActive()); if(bNativeOnly) { for(int32 Lane=0;Lane(Z))) { for(int32 Lane=0;Lane NativeIslandDensity{}; std::array HasNativeIslandSample{}; for(int32 Lane=0;Lane(XStart+Lane*XStep); const float SampleY=static_cast(Y); const float SampleZ=static_cast(Z); bHasLegacySurfaceSample|=UsesLegacySurfaceField(SampleX,SampleY,SampleZ); const auto* managerBound=FindBoundStratumForManagerZ(SampleZ); const auto* resolvedBound=FindBoundStratumForZ(SampleZ); bHasNativeGraphSample|=(managerBound &&managerBound->Kind==FBoundStratumField::EKind::Caves) ||(resolvedBound&&resolvedBound->Kind==FBoundStratumField::EKind::Caves); HasNativeIslandSample[static_cast(Lane)]=NativeFloatingIslandDensityAt( SampleX,SampleY,SampleZ,NativeIslandDensity[static_cast(Lane)]); bHasNativeIslandSample|=HasNativeIslandSample[static_cast(Lane)]; } if(bHasLegacySurfaceSample||bHasNativeIslandSample||bHasNativeGraphSample) { for(int32 Lane=0;Lane(Lane); if(HasNativeIslandSample[Index]) { ++ThreadClassificationStats().NativeFloatFieldCalls; const float SampleX=static_cast(XStart+Lane*XStep); const float SampleY=static_cast(Y); const float SampleZ=static_cast(Z); OutDensity[Lane]=ApplyEditBrushes(ApplyResolvedStrateComposition( NativeIslandDensity[Index],SampleX,SampleY,SampleZ), SampleX,SampleY,SampleZ); } else OutDensity[Lane]=DensityAtLattice(XStart+Lane*XStep,Y,Z); } return; } if(bAnalyticOnly) { for(int32 Lane=0;Lane=Min[1]&&Y<=Max[1] &&Z>=Min[2]&&Z<=Max[2] &&XStart>=Min[0] &&XStart+static_cast(Count-1)*XStep<=Max[0]; if(!bFast) { for(int32 Lane=0;Lane(Count)); FScopedRoute l77PackedRoute(ERoute::PackedX8); FScopedPhase l77PackedBase(EPhase::BaseField); const size_t ColumnStart=static_cast(Y-Min[1])*Size[0] +static_cast(XStart-Min[0]); const size_t Zi=static_cast(Z-Min[2]); const size_t WordOffset=Zi>>6; const uint64 Mask=uint64{1}<<(Zi&63u); const size_t NodeBase=Zi*static_cast(Size[1])*Size[0]+ColumnStart; constexpr float ProxyBandEdge=DetailSupremum+0.125f; for(int32 Lane=0;Lane(Lane*XStep); const size_t Word=Column*WordsPerZ+WordOffset; const bool bRock=(RockWords[Word]&Mask)!=0; float Density; if(CandidateWords[Word]&Mask) { const int16 Proxy=SurfaceDistanceQ[NodeBase +static_cast(Lane*XStep)]; Density=static_cast(Proxy)*(1.0f/256.0f); if((Density<0.0f)!=bRock) Density=(bRock?-1.0f:1.0f)*std::max(std::abs(Density),1.0f/256.0f); } else Density=bRock?-ProxyBandEdge:ProxyBandEdge; OutDensity[Lane]=Density; } } private: struct FPassageTileFeature { std::size_t passageIndex=0,segmentIndex=0; }; struct FPassageComposerConstants { double floorDepthMetres=0.0,totalPathLengthMetres=0.0; double authoredTotalPathLengthMetres=0.0; double upperMouthFloorDepthMetres=0.0,lowerMouthFloorDepthMetres=0.0; std::size_t routeEndControlIndex=0; }; struct FPassageSegmentData { std::array a{},b{},delta{}; std::array sweepMinimumSubvoxels{},sweepMaximumSubvoxels{}; double authoredCenterZA=0.0,authoredCenterZDelta=0.0; double authoredAlongMetres=0.0,authoredLengthMetres=0.0; double radiusA=0.0,radiusB=0.0,radiusDelta=0.0; double lengthSq=0.0,horizontalLengthSq=0.0; double lengthSubvoxels=0.0,horizontalLength=0.0; }; static double PassageFlatFloorDepthSubvoxels( const FPassageComposerConstants& constants,std::size_t segmentIndex,double t, double radiusSubvoxels,double qPerMetre) { (void)segmentIndex;(void)t; if(!std::isfinite(qPerMetre)||qPerMetre<=0.0) return std::max(0.0,radiusSubvoxels-0.23); return std::min(constants.floorDepthMetres*qPerMetre, std::max(0.0,radiusSubvoxels-0.23*qPerMetre)); } static double PassageRouteFloorDepthMetres( const FPassageComposerConstants& constants,double alongMetres,double radiusMetres) { const double localRadiusDepth=std::max(0.0,radiusMetres-0.23); const double mouthDistance=std::max(0.0,std::min(alongMetres, constants.authoredTotalPathLengthMetres-alongMetres)); const double linear=std::clamp(mouthDistance/5.0,0.0,1.0); const double ramp=linear*linear*(3.0-2.0*linear); const double target=alongMetres<=constants.authoredTotalPathLengthMetres*0.5 ?constants.upperMouthFloorDepthMetres :constants.lowerMouthFloorDepthMetres; // The mouth coordinate is the player center 1.13 m above the floor. // Let its ramp reach the full radius there, then ease to radius-0.23 m // through the body over five metres. const double mouthDepth=std::clamp(target,0.0,std::max(0.0,radiusMetres)); return std::lerp(mouthDepth,localRadiusDepth,ramp); } static double PassageFlatFloorCenterZSubvoxels( const FPassageComposerConstants& constants,std::size_t segmentIndex,double floorT, const FPassageSegmentData& segment,double floorDepthSubvoxels,double qPerMetre) { if(segmentIndex>=constants.routeEndControlIndex||qPerMetre<=0.0) return segment.a[2]+floorT*segment.delta[2]; const double radiusMetres=(segment.radiusA+floorT*segment.radiusDelta)/qPerMetre; const double alongMetres=segment.authoredAlongMetres +floorT*segment.authoredLengthMetres; const double routeDepth=PassageRouteFloorDepthMetres( constants,alongMetres,radiusMetres); return segment.authoredCenterZA+floorT*segment.authoredCenterZDelta -routeDepth*qPerMetre+floorDepthSubvoxels; } static float PassageFlatBottomDistance(float roundDistanceSubvoxels, double centerZSubvoxels,double queryZSubvoxels,double floorDepthSubvoxels, double qPerMetre) { constexpr double CornerBlendMetres=0.50; const float floorDistance=static_cast(centerZSubvoxels -floorDepthSubvoxels-queryZSubvoxels); return SmoothMaxDistance(roundDistanceSubvoxels,floorDistance, static_cast(CornerBlendMetres*qPerMetre)); } struct FPassageNearestNode { std::array minimum{},maximum{}; double maximumRadius=0.0; std::size_t minimumSegment=0,maximumSegment=0; bool segmentRangeContiguous=false; std::vector segments; int left=-1,right=-1; }; struct FPassageProofBound { std::array minimum{},maximum{}; }; struct FPassageProofNode { std::array minimum{},maximum{}; std::size_t first=0,end=0; int left=-1,right=-1; }; struct FPassageFeatureNode { std::array minimum{},maximum{}; std::size_t first=0,end=0; int left=-1,right=-1; }; struct FStampSpatialBound { std::array minimumSubvoxels{},maximumSubvoxels{}; }; struct FStampSpatialNode { std::array minimumSubvoxels{},maximumSubvoxels{}; std::size_t first=0,end=0; int left=-1,right=-1; }; struct FStampRuntime { const VoxelForgeCore::ResolvedLayoutStampV1* record=nullptr; std::array positionVoxels{},halfExtentsVoxels{}; float smoothWidthVoxels=0.0f; float irregularityAmplitudeVoxels=0.0f; float irregularityWavelengthVoxels=0.0f; float roundingRadiusVoxels=0.0f; std::uint64_t irregularitySeed=0; std::uint8_t quarterTurns=0; }; struct FOreVeinSpatialBound { std::array minimumSubvoxels{},maximumSubvoxels{}; }; struct FOreVeinSpatialNode { std::array minimumSubvoxels{},maximumSubvoxels{}; std::size_t first=0,end=0; int left=-1,right=-1; }; struct FOreVeinRuntime { const VoxelForgeCore::ResolvedLayoutOreVeinV1* record=nullptr; std::array,2> endpointsVoxels{}; double radiusVoxels=0.0,irregularityAmplitudeVoxels=0.0; double irregularityWavelengthVoxels=0.0,blendWidthVoxels=0.0; std::uint64_t irregularitySeed=0; std::uint16_t resourceMaterialId=0; }; struct FCavernRuntimeFeature { const VoxelForgeCore::ResolvedCavernRoomV1* room=nullptr; const VoxelForgeCore::ResolvedCavernEdgeV1* edge=nullptr; bool nativeGraph=false; std::array boundsMinimumMicrometres{},boundsMaximumMicrometres{}; std::array centerMetres{},halfExtentsMetres{}; struct FSegment { std::array start{},end{},minimum{},maximum{}; }; struct FSegmentNode { std::array minimum{},maximum{}; std::size_t first=0,end=0; int left=-1,right=-1; }; std::vector segments; std::vector segmentOrder; std::vector segmentNodes; double minimumFloorMetres=std::numeric_limits::infinity(); double maximumFloorMetres=-std::numeric_limits::infinity(); double maximumSegmentGrade=0.0; std::array centerVoxels{},halfExtentsVoxels{}; std::array,4> controlPointsVoxels{}; float floorVoxels=0.0f; float boreRadiusVoxels=0.0f,boreHeightVoxels=0.0f; float floorSupportDepthVoxels=0.0f; float smoothWidthVoxels=0.0f; float irregularityAmplitudeVoxels=0.0f; float irregularityWavelengthVoxels=0.0f; std::uint64_t irregularitySeed=0; }; struct FCavernSpatialNode { std::array minimumMicrometres{},maximumMicrometres{}; std::size_t first=0,end=0; int left=-1,right=-1; }; struct FMetricInterval { double minimum=0.0,maximum=0.0; bool known=true; }; int BuildStampSpatialNode(std::size_t first,std::size_t end) { FStampSpatialNode node;node.first=first;node.end=end; node.minimumSubvoxels.fill(std::numeric_limits::max()); node.maximumSubvoxels.fill(std::numeric_limits::min()); for(std::size_t offset=first;offset(StampSpatialNodes.size()); StampSpatialNodes.push_back(node); if(end-first>8) { std::size_t splitAxis=0; for(std::size_t axis=1;axis<3;++axis) if(static_cast(node.maximumSubvoxels[axis]) -node.minimumSubvoxels[axis] >static_cast(node.maximumSubvoxels[splitAxis]) -node.minimumSubvoxels[splitAxis])splitAxis=axis; const std::size_t middle=first+(end-first)/2; std::nth_element(StampSpatialOrder.begin()+first, StampSpatialOrder.begin()+middle,StampSpatialOrder.begin()+end, [this,splitAxis](std::uint32_t a,std::uint32_t b) { const auto& left=StampSpatialBounds[a];const auto& right=StampSpatialBounds[b]; const long double leftCenter=static_cast( left.minimumSubvoxels[splitAxis])+left.maximumSubvoxels[splitAxis]; const long double rightCenter=static_cast( right.minimumSubvoxels[splitAxis])+right.maximumSubvoxels[splitAxis]; return leftCenter==rightCenter?a& minimum, const std::array& maximum, std::vector& result) const { if(nodeIndex<0)return; const auto& node=StampSpatialNodes[static_cast(nodeIndex)]; for(std::size_t axis=0;axis<3;++axis) if(node.maximumSubvoxels[axis]maximum[axis])return; if(node.left<0) { for(std::size_t offset=node.first;offset=minimum[axis]; if(overlap)result.push_back(index); } return; } QueryStampSpatialNode(node.left,minimum,maximum,result); QueryStampSpatialNode(node.right,minimum,maximum,result); } void BuildStampSpatialIndex() { StampSpatialBounds.clear();StampSpatialOrder.clear();StampSpatialNodes.clear(); StampRuntime.clear(); if(!ResolvedLayout||ResolvedLayout->stamps.empty()||!Owner ||Owner->CoreVoxelSizeMicrons==0)return; constexpr long double Q=VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel; const long double scale=Q/Owner->CoreVoxelSizeMicrons; StampSpatialBounds.reserve(ResolvedLayout->stamps.size()); StampRuntime.reserve(ResolvedLayout->stamps.size()); for(const auto& stamp:ResolvedLayout->stamps) { FStampSpatialBound bound; for(std::size_t axis=0;axis<3;++axis) { bound.minimumSubvoxels[axis]=static_cast(std::floor( static_cast(stamp.boundsMinimumMicrometres[axis])*scale)); bound.maximumSubvoxels[axis]=static_cast(std::ceil( static_cast(stamp.boundsMaximumMicrometres[axis])*scale)); } StampSpatialBounds.push_back(bound); FStampRuntime runtime;runtime.record=&stamp; for(std::size_t axis=0;axis<3;++axis) runtime.positionVoxels[axis]=static_cast( static_cast(stamp.positionMicrometres[axis]) /Owner->CoreVoxelSizeMicrons); const std::size_t xAxis=stamp.yawQuarterTurns%2?1:0; const std::size_t yAxis=stamp.yawQuarterTurns%2?0:1; runtime.halfExtentsVoxels={ static_cast(static_cast(stamp.halfExtentsMicrometres[0]) /Owner->CoreVoxelSizeMicrons), static_cast(static_cast(stamp.halfExtentsMicrometres[1]) /Owner->CoreVoxelSizeMicrons), static_cast(static_cast(stamp.halfExtentsMicrometres[2]) /Owner->CoreVoxelSizeMicrons)}; (void)xAxis;(void)yAxis; runtime.smoothWidthVoxels=static_cast( static_cast(stamp.smoothWidthMicrometres)/Owner->CoreVoxelSizeMicrons); runtime.irregularityAmplitudeVoxels=static_cast( static_cast(stamp.irregularityAmplitudeMicrometres) /Owner->CoreVoxelSizeMicrons); runtime.irregularityWavelengthVoxels=static_cast( static_cast(stamp.irregularityWavelengthMicrometres) /Owner->CoreVoxelSizeMicrons); runtime.roundingRadiusVoxels=static_cast( static_cast(stamp.roundingRadiusMicrometres) /Owner->CoreVoxelSizeMicrons); runtime.irregularitySeed=stamp.irregularitySeed; runtime.quarterTurns=stamp.yawQuarterTurns; StampRuntime.push_back(runtime); } StampSpatialOrder.resize(StampSpatialBounds.size()); for(std::size_t i=0;i(i); if(!StampSpatialOrder.empty())BuildStampSpatialNode(0,StampSpatialOrder.size()); } int BuildOreVeinSpatialNode(std::size_t first,std::size_t end) { FOreVeinSpatialNode node;node.first=first;node.end=end; node.minimumSubvoxels.fill(std::numeric_limits::max()); node.maximumSubvoxels.fill(std::numeric_limits::min()); for(std::size_t offset=first;offset(OreVeinSpatialNodes.size()); OreVeinSpatialNodes.push_back(node); if(end-first>8) { std::size_t splitAxis=0; for(std::size_t axis=1;axis<3;++axis) if(static_cast(node.maximumSubvoxels[axis]) -node.minimumSubvoxels[axis] >static_cast(node.maximumSubvoxels[splitAxis]) -node.minimumSubvoxels[splitAxis])splitAxis=axis; const std::size_t middle=first+(end-first)/2; std::nth_element(OreVeinSpatialOrder.begin()+first, OreVeinSpatialOrder.begin()+middle,OreVeinSpatialOrder.begin()+end, [this,splitAxis](std::uint32_t a,std::uint32_t b) { const auto& left=OreVeinSpatialBounds[a]; const auto& right=OreVeinSpatialBounds[b]; const long double leftCenter=static_cast( left.minimumSubvoxels[splitAxis])+left.maximumSubvoxels[splitAxis]; const long double rightCenter=static_cast( right.minimumSubvoxels[splitAxis])+right.maximumSubvoxels[splitAxis]; return leftCenter==rightCenter?a& minimum, const std::array& maximum, std::vector& result) const { if(nodeIndex<0)return; const auto& node=OreVeinSpatialNodes[static_cast(nodeIndex)]; for(std::size_t axis=0;axis<3;++axis) if(node.maximumSubvoxels[axis]maximum[axis])return; if(node.left<0) { for(std::size_t offset=node.first;offset=minimum[axis]; if(overlap)result.push_back(index); } return; } QueryOreVeinSpatialNode(node.left,minimum,maximum,result); QueryOreVeinSpatialNode(node.right,minimum,maximum,result); } void BuildOreVeinSpatialIndex() { OreVeinSpatialBounds.clear();OreVeinSpatialOrder.clear();OreVeinSpatialNodes.clear(); OreVeinRuntime.clear(); if(!ResolvedLayout||ResolvedLayout->oreVeins.empty()||!Owner ||Owner->CoreVoxelSizeMicrons==0)return; constexpr long double Q=VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel; const long double scale=Q/Owner->CoreVoxelSizeMicrons; OreVeinSpatialBounds.reserve(ResolvedLayout->oreVeins.size()); OreVeinRuntime.reserve(ResolvedLayout->oreVeins.size()); for(const auto& vein:ResolvedLayout->oreVeins) { FOreVeinSpatialBound bound; for(std::size_t axis=0;axis<3;++axis) { bound.minimumSubvoxels[axis]=static_cast(std::floor( static_cast(vein.boundsMinimumMicrometres[axis])*scale)); bound.maximumSubvoxels[axis]=static_cast(std::ceil( static_cast(vein.boundsMaximumMicrometres[axis])*scale)); } OreVeinSpatialBounds.push_back(bound); FOreVeinRuntime runtime;runtime.record=&vein; for(std::size_t endpoint=0;endpoint<2;++endpoint) for(std::size_t axis=0;axis<3;++axis) runtime.endpointsVoxels[endpoint][axis]= static_cast(vein.endpointsMicrometres[endpoint][axis]) /Owner->CoreVoxelSizeMicrons; runtime.radiusVoxels=static_cast(vein.radiusMicrometres) /Owner->CoreVoxelSizeMicrons; runtime.irregularityAmplitudeVoxels= static_cast(vein.irregularityAmplitudeMicrometres) /Owner->CoreVoxelSizeMicrons; runtime.irregularityWavelengthVoxels= static_cast(vein.irregularityWavelengthMicrometres) /Owner->CoreVoxelSizeMicrons; runtime.blendWidthVoxels=static_cast(vein.blendWidthMicrometres) /Owner->CoreVoxelSizeMicrons; runtime.irregularitySeed=vein.irregularitySeed; runtime.resourceMaterialId=vein.resourceMaterialId; OreVeinRuntime.push_back(runtime); } OreVeinSpatialOrder.resize(OreVeinSpatialBounds.size()); for(std::size_t i=0;i(i); if(!OreVeinSpatialOrder.empty())BuildOreVeinSpatialNode(0,OreVeinSpatialOrder.size()); } std::vector BuildOreVeinTileList( const std::array& minimum, const std::array& maximum) const { std::vector result; if(OreVeinSpatialBounds.empty())return result; for(std::size_t axis=0;axis<3;++axis) if(minimum[axis]>maximum[axis]) { result.resize(OreVeinSpatialBounds.size()); for(std::size_t i=0;i(i); return result; } QueryOreVeinSpatialNode(0,minimum,maximum,result); std::sort(result.begin(),result.end()); return result; } std::vector BuildOreVeinPointList(float X,float Y,float Z) const { constexpr std::int64_t Q=VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel; const auto toQ=[](float value) { const long double scaled=static_cast(value)*Q; if(!std::isfinite(value)||scaled::min() ||scaled>std::numeric_limits::max())return std::int64_t{0}; return static_cast(std::llround(scaled)); }; const std::array point={toQ(X),toQ(Y),toQ(Z)}; return BuildOreVeinTileList(point,point); } VoxelForgeCore::MaterialSampleV1 MaterialAt(float X,float Y,float Z, std::span candidates, std::uint64_t* candidateChecks=nullptr) const { using namespace VoxelForgeCore; MaterialSampleV1 result; if(!ResolvedLayout||OreVeinRuntime.empty()||candidates.empty() ||!std::isfinite(X)||!std::isfinite(Y)||!std::isfinite(Z))return result; const FBoundStratumField* bound=FindBoundStratumForZ(Z); if(!bound)return result; const std::array point={X,Y,Z}; const auto noiseAt=[](std::uint64_t seed,std::int64_t x,std::int64_t y,std::int64_t z) { std::uint64_t hash=14695981039346656037ull; for(const std::uint64_t word:{seed,static_cast(x), static_cast(y),static_cast(z)}) for(int byte=0;byte<8;++byte) {hash^=static_cast(word>>(byte*8));hash*=1099511628211ull;} hash^=hash>>30;hash*=0xbf58476d1ce4e5b9ull; hash^=hash>>27;hash*=0x94d049bb133111ebull;hash^=hash>>31; return static_cast(static_cast(hash>>32))/2147483647.5-1.0; }; const auto fade=[](double value){return value*value*(3.0-2.0*value);}; for(const std::uint32_t candidate:candidates) { if(candidateChecks)++*candidateChecks; if(candidate>=OreVeinRuntime.size())continue; const auto& vein=OreVeinRuntime[candidate]; if(!vein.record||vein.record->strateIndex!=bound->StrateIndex)continue; const double wavelength=vein.irregularityWavelengthVoxels; double irregularity=0.0; if(vein.irregularityAmplitudeVoxels>0.0&&wavelength>0.0) { std::array cell{};std::array t{}; for(std::size_t axis=0;axis<3;++axis) { const double coordinate=point[axis]/wavelength; cell[axis]=static_cast(std::floor(coordinate)); t[axis]=fade(coordinate-cell[axis]); } double layer[2][2]{}; for(int z=0;z<2;++z)for(int y=0;y<2;++y) { const double a=noiseAt(vein.irregularitySeed,cell[0],cell[1]+y,cell[2]+z); const double b=noiseAt(vein.irregularitySeed,cell[0]+1,cell[1]+y,cell[2]+z); layer[z][y]=std::lerp(a,b,t[0]); } irregularity=std::lerp(std::lerp(layer[0][0],layer[0][1],t[1]), std::lerp(layer[1][0],layer[1][1],t[1]),t[2]); } const auto& a=vein.endpointsVoxels[0];const auto& b=vein.endpointsVoxels[1]; double lengthSquared=0.0,dot=0.0; for(std::size_t axis=0;axis<3;++axis) {const double delta=b[axis]-a[axis];lengthSquared+=delta*delta;dot+=(point[axis]-a[axis])*delta;} const double t=lengthSquared>0.0?std::clamp(dot/lengthSquared,0.0,1.0):0.0; double distanceSquared=0.0; for(std::size_t axis=0;axis<3;++axis) {const double delta=point[axis]-(a[axis]+t*(b[axis]-a[axis]));distanceSquared+=delta*delta;} const double distance=std::sqrt(distanceSquared); const double radius=std::max(0.0,vein.radiusVoxels +irregularity*vein.irregularityAmplitudeVoxels); if(distance>=radius+vein.blendWidthVoxels)continue; double weight=1.0; if(distance>radius) { const double u=std::clamp((radius+vein.blendWidthVoxels-distance) /vein.blendWidthVoxels,0.0,1.0); weight=u*u*(3.0-2.0*u); } const auto weightQ16=static_cast(std::clamp( std::llround(weight*65535.0),0ll,65535ll)); if(weightQ16==0)continue; std::size_t slot=2; for(std::size_t index=0;index<2;++index) if(result.resourceMaterialIds[index]==vein.resourceMaterialId) {slot=index;break;} if(slot==2) for(std::size_t index=0;index<2;++index) if(result.resourceMaterialIds[index]==0){slot=index;break;} if(slot<2) { result.resourceMaterialIds[slot]=vein.resourceMaterialId; const std::uint32_t sum=static_cast( result.resourceWeightsQ16[slot])+weightQ16; result.resourceWeightsQ16[slot]=static_cast( std::min(65535,sum)); } } const std::uint32_t total=static_cast(result.resourceWeightsQ16[0]) +result.resourceWeightsQ16[1]; if(total>65535) { result.resourceWeightsQ16[0]=static_cast( static_cast(result.resourceWeightsQ16[0])*65535/total); result.resourceWeightsQ16[1]=static_cast( 65535-result.resourceWeightsQ16[0]); } return result; } std::vector BuildStampTileList( const std::array& minimum, const std::array& maximum) const { std::vector result; if(StampSpatialBounds.empty())return result; for(std::size_t axis=0;axis<3;++axis) if(minimum[axis]>maximum[axis]) { result.resize(StampSpatialBounds.size()); for(std::size_t i=0;i(i); return result; } QueryStampSpatialNode(0,minimum,maximum,result); // Stamp union/carve composition follows canonical stable-ID order. The BVH query // order is spatial and can differ per tile, which would let a later carve erase a // fill on meshed tiles even though unscoped density queries use the canonical order. std::sort(result.begin(),result.end()); return result; } void BuildCavernSpatialIndex() { CavernRuntime.clear();CavernSpatialNodes.clear();CavernSpatialOrder.clear(); if(!ResolvedLayout||!Owner||Owner->CoreVoxelSizeMicrons==0 ||ResolvedLayout->cavernGraphVocabularyVersion !=VoxelForgeCore::ResolvedLayoutCavernGraphVocabularyVersionV1)return; const auto isNativeGraphStrate=[this](std::int32_t strateIndex) { const auto found=std::find_if(ResolvedLayout->strates.begin(), ResolvedLayout->strates.end(),[strateIndex](const auto& stratum) {return stratum.strateIndex==strateIndex;}); return found!=ResolvedLayout->strates.end() &&VoxelForgeCore::HasNativeGraphField(*found) &&(VoxelForgeCore::IsNativeCaveFieldStratum(*found) ?found->passageCapabilities.hasNativeCaveField :found->passageCapabilities.hasNativeLatticeField); }; const float micronsPerVoxel=static_cast(Owner->CoreVoxelSizeMicrons); for(const auto& room:ResolvedLayout->cavernRooms) { FCavernRuntimeFeature runtime;runtime.room=&room; runtime.nativeGraph=isNativeGraphStrate(room.strateIndex); runtime.boundsMinimumMicrometres=room.boundsMinimumMicrometres; runtime.boundsMaximumMicrometres=room.boundsMaximumMicrometres; for(std::size_t axis=0;axis<3;++axis) { runtime.centerMetres[axis]=room.centerMicrometres[axis]/1000000.0; runtime.halfExtentsMetres[axis]=room.halfExtentsMicrometres[axis]/1000000.0; runtime.centerVoxels[axis]=static_cast( static_cast(room.centerMicrometres[axis])/micronsPerVoxel); runtime.halfExtentsVoxels[axis]=static_cast( static_cast(room.halfExtentsMicrometres[axis])/micronsPerVoxel); } runtime.floorVoxels=static_cast( static_cast(room.floorMicrometres)/micronsPerVoxel); runtime.smoothWidthVoxels=static_cast( static_cast(room.smoothWidthMicrometres)/micronsPerVoxel); runtime.irregularityAmplitudeVoxels=static_cast( static_cast(room.irregularityAmplitudeMicrometres)/micronsPerVoxel); runtime.irregularityWavelengthVoxels=static_cast( static_cast(room.irregularityWavelengthMicrometres)/micronsPerVoxel); runtime.irregularitySeed=room.irregularitySeed; CavernRuntime.push_back(runtime); } for(const auto& edge:ResolvedLayout->cavernEdges) { FCavernRuntimeFeature runtime;runtime.edge=&edge; runtime.nativeGraph=isNativeGraphStrate(edge.strateIndex); runtime.boundsMinimumMicrometres=edge.boundsMinimumMicrometres; runtime.boundsMaximumMicrometres=edge.boundsMaximumMicrometres; for(std::size_t point=0;point<4;++point) for(std::size_t axis=0;axis<3;++axis) runtime.controlPointsVoxels[point][axis]=static_cast( static_cast(edge.controlPointsMicrometres[point][axis]) /micronsPerVoxel); runtime.boreRadiusVoxels=static_cast( static_cast(edge.boreRadiusMicrometres)/micronsPerVoxel); runtime.boreHeightVoxels=static_cast( static_cast(edge.boreHeightMicrometres)/micronsPerVoxel); runtime.floorSupportDepthVoxels=static_cast( static_cast(edge.floorSupportDepthMicrometres)/micronsPerVoxel); runtime.smoothWidthVoxels=static_cast( static_cast(edge.smoothWidthMicrometres)/micronsPerVoxel); runtime.irregularityAmplitudeVoxels=static_cast( static_cast(edge.irregularityAmplitudeMicrometres)/micronsPerVoxel); runtime.irregularityWavelengthVoxels=static_cast( static_cast(edge.irregularityWavelengthMicrometres)/micronsPerVoxel); runtime.irregularitySeed=edge.irregularitySeed; runtime.segments.reserve(edge.polylineMicrometres.size()>1 ?edge.polylineMicrometres.size()-1:0); for(std::size_t point=1;point1.0e-9) runtime.maximumSegmentGrade=std::max(runtime.maximumSegmentGrade, std::abs((segment.end[2]-segment.start[2])/horizontal)); runtime.segments.push_back(segment); } runtime.segmentOrder.resize(runtime.segments.size()); for(std::size_t index=0;index(index); if(!runtime.segmentOrder.empty()) { const auto buildSegmentNode=[&](const auto& self,std::size_t first, std::size_t end)->int { FCavernRuntimeFeature::FSegmentNode node; node.first=first;node.end=end; node.minimum.fill(std::numeric_limits::infinity()); node.maximum.fill(-std::numeric_limits::infinity()); for(std::size_t offset=first;offset(runtime.segmentNodes.size()); runtime.segmentNodes.push_back(node); if(end-first>4) { std::size_t splitAxis=0; for(std::size_t axis=1;axis<3;++axis) if(node.maximum[axis]-node.minimum[axis] >node.maximum[splitAxis]-node.minimum[splitAxis]) splitAxis=axis; const std::size_t middle=first+(end-first)/2; std::sort(runtime.segmentOrder.begin()+first, runtime.segmentOrder.begin()+end, [&](std::uint32_t a,std::uint32_t b) { const auto& left=runtime.segments[a]; const auto& right=runtime.segments[b]; const double ca=left.minimum[splitAxis]+left.maximum[splitAxis]; const double cb=right.minimum[splitAxis]+right.maximum[splitAxis]; return ca==cb?astrateIndex :a.edge?a.edge->strateIndex:-1; const std::int32_t rightStrate=b.room?b.room->strateIndex :b.edge?b.edge->strateIndex:-1; if(leftStrate!=rightStrate)return leftStratestableId:a.edge->stableId; const std::string& right=b.room?b.room->stableId:b.edge->stableId; return left(i); if(!CavernSpatialOrder.empty())BuildCavernSpatialNode(0,CavernSpatialOrder.size()); } int BuildCavernSpatialNode(std::size_t first,std::size_t end) { FCavernSpatialNode node;node.first=first;node.end=end; node.minimumMicrometres.fill(std::numeric_limits::max()); node.maximumMicrometres.fill(std::numeric_limits::min()); for(std::size_t offset=first;offset(CavernSpatialNodes.size()); CavernSpatialNodes.push_back(node); if(end-first>8) { std::size_t axis=0; for(std::size_t candidate=1;candidate<3;++candidate) if(static_cast(node.maximumMicrometres[candidate]) -node.minimumMicrometres[candidate] >static_cast(node.maximumMicrometres[axis]) -node.minimumMicrometres[axis])axis=candidate; const std::size_t middle=first+(end-first)/2; std::nth_element(CavernSpatialOrder.begin()+first, CavernSpatialOrder.begin()+middle,CavernSpatialOrder.begin()+end, [this,axis](std::uint32_t leftIndex,std::uint32_t rightIndex) { const auto& left=CavernRuntime[leftIndex]; const auto& right=CavernRuntime[rightIndex]; const long double leftCenter=static_cast( left.boundsMinimumMicrometres[axis]) +left.boundsMaximumMicrometres[axis]; const long double rightCenter=static_cast( right.boundsMinimumMicrometres[axis]) +right.boundsMaximumMicrometres[axis]; const std::string& leftId=left.room?left.room->stableId:left.edge->stableId; const std::string& rightId=right.room?right.room->stableId:right.edge->stableId; return leftCenter==rightCenter?leftId& minimum, const std::array& maximum, std::vector& result) const { if(nodeIndex<0)return; const auto& node=CavernSpatialNodes[static_cast(nodeIndex)]; for(std::size_t axis=0;axis<3;++axis) if(node.maximumMicrometres[axis]maximum[axis])return; if(node.left<0) { for(std::size_t offset=node.first;offset=minimum[axis]; if(overlaps)result.push_back(index); } return; } QueryCavernSpatialNode(node.left,minimum,maximum,result); QueryCavernSpatialNode(node.right,minimum,maximum,result); } std::vector BuildCavernFeatureTileList( const std::array& minimumSubvoxels, const std::array& maximumSubvoxels) const { std::vector result; if(CavernRuntime.empty()||CavernSpatialNodes.empty())return result; if(!Owner||Owner->CoreVoxelSizeMicrons==0) { result.resize(CavernRuntime.size()); for(std::size_t i=0;i(i); return result; } constexpr long double Q=VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel; std::array minimumMicrometres{},maximumMicrometres{}; for(std::size_t axis=0;axis<3;++axis) { if(minimumSubvoxels[axis]>maximumSubvoxels[axis]) { result.resize(CavernRuntime.size()); for(std::size_t i=0;i(i); return result; } minimumMicrometres[axis]=static_cast(std::floor( static_cast(minimumSubvoxels[axis]) *Owner->CoreVoxelSizeMicrons/Q)); maximumMicrometres[axis]=static_cast(std::ceil( static_cast(maximumSubvoxels[axis]) *Owner->CoreVoxelSizeMicrons/Q)); } QueryCavernSpatialNode(0,minimumMicrometres,maximumMicrometres,result); // Runtime order is legacy insertion order for legacy cavern graphs and // canonical stable-ID order within native caves strates. std::sort(result.begin(),result.end()); return result; } int BuildPassageNearestNode(std::size_t passageIndex,std::vector segments) { const auto& points=ResolvedLayout->passages[passageIndex].controlPoints; FPassageNearestNode node; node.minimum.fill(std::numeric_limits::infinity()); node.maximum.fill(-std::numeric_limits::infinity()); for(const std::size_t segment:segments) { const auto& a=points[segment];const auto& b=points[segment+1]; const auto& prepared=PassageSegmentData[passageIndex][segment]; for(std::size_t axis=0;axis<3;++axis) { node.minimum[axis]=std::min({node.minimum[axis], prepared.a[axis],prepared.b[axis]}); node.maximum[axis]=std::max({node.maximum[axis], prepared.a[axis],prepared.b[axis]}); } node.maximumRadius=std::max({node.maximumRadius, static_cast(a.radiusSubvoxels),static_cast(b.radiusSubvoxels)}); } node.minimumSegment=*std::min_element(segments.begin(),segments.end()); node.maximumSegment=*std::max_element(segments.begin(),segments.end()); node.segments=segments; std::sort(node.segments.begin(),node.segments.end()); node.segmentRangeContiguous=node.segments.size() ==node.maximumSegment-node.minimumSegment+1; const int nodeIndex=static_cast(PassageNearestNodes[passageIndex].size()); PassageNearestNodes[passageIndex].push_back(std::move(node)); if(segments.size()>4) { std::size_t splitAxis=0; for(std::size_t axis=1;axis<3;++axis) if(PassageNearestNodes[passageIndex][nodeIndex].maximum[axis] -PassageNearestNodes[passageIndex][nodeIndex].minimum[axis] >PassageNearestNodes[passageIndex][nodeIndex].maximum[splitAxis] -PassageNearestNodes[passageIndex][nodeIndex].minimum[splitAxis])splitAxis=axis; const std::size_t middle=segments.size()/2; const auto center=[&](std::size_t segment) {return static_cast(points[segment].positionSubvoxels[splitAxis]) +static_cast(points[segment+1].positionSubvoxels[splitAxis]);}; std::nth_element(segments.begin(),segments.begin()+middle,segments.end(), [&](std::size_t a,std::size_t b) {const double ca=center(a),cb=center(b);return ca==cb?a leftSegments(segments.begin(),segments.begin()+middle); std::vector rightSegments(segments.begin()+middle,segments.end()); const int left=BuildPassageNearestNode(passageIndex,std::move(leftSegments)); const int right=BuildPassageNearestNode(passageIndex,std::move(rightSegments)); PassageNearestNodes[passageIndex][nodeIndex].left=left; PassageNearestNodes[passageIndex][nodeIndex].right=right; } return nodeIndex; } template void VisitNearestPassageSegmentRange(std::size_t passageIndex, std::ptrdiff_t first,std::ptrdiff_t last,double qx,double qy,double qz, int priority,double& bestDistance,FVisitor&& visitor) const { (void)priority; if(first>last||passageIndex>=PassageNearestNodes.size() ||PassageNearestNodes[passageIndex].empty())return; const auto& nodes=PassageNearestNodes[passageIndex]; const double qPerMetre=static_cast( VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel)*1000000.0 /std::max(1,ResolvedLayout ?ResolvedLayout->coreVoxelSizeMicrometres:250000); constexpr double SmoothDistanceSlackMetres=0.25; const auto lowerBound=[&](const FPassageNearestNode& node) { const double dx=std::max({node.minimum[0]-qx,0.0,qx-node.maximum[0]}); const double dy=std::max({node.minimum[1]-qy,0.0,qy-node.maximum[1]}); const double dz=std::max({node.minimum[2]-qz,0.0,qz-node.maximum[2]}); return DeterministicSqrt(dx*dx+dy*dy+dz*dz)-node.maximumRadius -SmoothDistanceSlackMetres*qPerMetre; }; const auto visit=[&](auto&& self,int nodeIndex)->void { if(nodeIndex<0)return; const auto& node=nodes[static_cast(nodeIndex)]; if(static_cast(node.maximumSegment)(node.minimumSegment)>last ||lowerBound(node)>bestDistance)return; if(node.left<0) { for(const std::size_t segment:node.segments) if(static_cast(segment)>=first &&static_cast(segment)<=last) visitor(segment,0); return; } const double leftBound=lowerBound(nodes[static_cast(node.left)]); const double rightBound=lowerBound(nodes[static_cast(node.right)]); if(leftBound<=rightBound) { self(self,node.left); self(self,node.right); } else { self(self,node.right); self(self,node.left); } }; visit(visit,0); } std::vector BuildPassageSegmentRangeNodeMask( std::size_t passageIndex,std::ptrdiff_t first,std::ptrdiff_t end) const { std::vector 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(nodeIndex); const auto& node=nodes[index]; if(static_cast(node.maximumSegment)(node.minimumSegment)>=end)return false; bool hasCandidate=false; if(node.left<0) { if(node.segmentRangeContiguous) hasCandidate=static_cast(node.maximumSegment)>=first &&static_cast(node.minimumSegment)(segment)>=first &&static_cast(segment)(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; node.minimum.fill(std::numeric_limits::infinity()); node.maximum.fill(-std::numeric_limits::infinity()); for(std::size_t index=first;index(PassageProofNodes.size()); PassageProofNodes.push_back(node); if(end-first>8) { std::size_t splitAxis=0; for(std::size_t axis=1;axis<3;++axis) if(node.maximum[axis]-node.minimum[axis] >node.maximum[splitAxis]-node.minimum[splitAxis])splitAxis=axis; const std::size_t middle=first+(end-first)/2; std::nth_element(PassageProofBounds.begin()+first,PassageProofBounds.begin()+middle, PassageProofBounds.begin()+end,[splitAxis](const auto& a,const auto& b) {return a.minimum[splitAxis]+a.maximum[splitAxis] ::max()); node.maximum.fill(std::numeric_limits::min()); for(std::size_t offset=first;offset(PassageFeatureNodes.size()); PassageFeatureNodes.push_back(node); if(end-first>8) { std::size_t splitAxis=0; for(std::size_t axis=1;axis<3;++axis) if(static_cast(node.maximum[axis])-node.minimum[axis] >static_cast(node.maximum[splitAxis])-node.minimum[splitAxis]) splitAxis=axis; const std::size_t middle=first+(end-first)/2; std::nth_element(PassageFeatureOrder.begin()+first,PassageFeatureOrder.begin()+middle, PassageFeatureOrder.begin()+end,[this,splitAxis](std::uint32_t a,std::uint32_t b) { const auto& left=PassageFeatureBounds[a]; const auto& right=PassageFeatureBounds[b]; const double leftCenter=static_cast(left.minimumSubvoxels[splitAxis])*0.5 +static_cast(left.maximumSubvoxels[splitAxis])*0.5; const double rightCenter=static_cast(right.minimumSubvoxels[splitAxis])*0.5 +static_cast(right.maximumSubvoxels[splitAxis])*0.5; return leftCenter==rightCenter?a& minimum, const std::array& maximum, std::vector& result) const { if(nodeIndex<0)return; const auto& node=PassageFeatureNodes[static_cast(nodeIndex)]; for(std::size_t axis=0;axis<3;++axis) if(node.maximum[axis]maximum[axis])return; if(node.left<0) { for(std::size_t offset=node.first;offset=minimum[axis]; if(overlaps)result.push_back(featureIndex); } return; } QueryPassageFeatureNode(node.left,minimum,maximum,result); QueryPassageFeatureNode(node.right,minimum,maximum,result); } std::vector BuildPassageFeatureTileList( const std::array& minimum, const std::array& maximum) const { std::vector result; if(PassageFeatureBounds.empty())return result; bool failOpen=PassageFeatureBoundsFailOpen; for(std::size_t axis=0;axis<3;++axis)failOpen=failOpen||minimum[axis]>maximum[axis]; if(failOpen) { result.resize(PassageFeatureBounds.size()); for(std::size_t i=0;i(i); return result; } if(!PassageFeatureNodes.empty())QueryPassageFeatureNode(0,minimum,maximum,result); return result; } void BuildPassageComposerConstants() { const bool tracePreparation=VF_IsL98PassagePrepareTraceEnabled(); const auto preparationStart=tracePreparation ?std::chrono::steady_clock::now():std::chrono::steady_clock::time_point{}; PassageComposerConstants.clear(); PassageSegmentData.clear(); PassageSegmentOffsets.clear(); if(!ResolvedLayout) { if(tracePreparation) std::printf("[L98PassagePrepare] passages=0 segments=0 bytes=0 prepare_ms=%.3f\n", std::chrono::duration( std::chrono::steady_clock::now()-preparationStart).count()); return; } PassageComposerConstants.resize(ResolvedLayout->passages.size()); PassageSegmentData.resize(ResolvedLayout->passages.size()); PassageSegmentOffsets.resize(ResolvedLayout->passages.size()+1,0); constexpr double Q=static_cast(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel); const double qPerMetre=Q*1000000.0/ std::max(1,ResolvedLayout->coreVoxelSizeMicrometres); std::size_t preparedSegments=0; for(std::size_t passageIndex=0;passageIndexpassages.size(); ++passageIndex) { PassageSegmentOffsets[passageIndex]=preparedSegments; const auto& passage=ResolvedLayout->passages[passageIndex]; if(passage.controlPoints.size()<2) { PassageSegmentOffsets[passageIndex+1]=preparedSegments; continue; } auto& segmentData=PassageSegmentData[passageIndex]; segmentData.resize(passage.controlPoints.size()-1); const std::size_t routeEnd=std::clamp( passage.lowerMouthControlIndex,1,passage.controlPoints.size()-1); std::vector radiiMetres; radiiMetres.reserve(routeEnd+1); for(std::size_t pointIndex=0;pointIndex<=routeEnd;++pointIndex) { radiiMetres.push_back(static_cast( passage.controlPoints[pointIndex].radiusSubvoxels)/qPerMetre); } auto& constants=PassageComposerConstants[passageIndex]; constants.routeEndControlIndex=routeEnd; std::vector authoredPathDistanceMetres( passage.controlPoints.size(),0.0); for(std::size_t pointIndex=1;pointIndex(b[0]-a[0])/qPerMetre; const double dy=static_cast(b[1]-a[1])/qPerMetre; const double dz=static_cast(b[2]-a[2])/qPerMetre; authoredPathDistanceMetres[pointIndex]= authoredPathDistanceMetres[pointIndex-1] +DeterministicSqrt(dx*dx+dy*dy+dz*dz); } const double authoredTotalPathLengthMetres= authoredPathDistanceMetres[routeEnd]; constants.authoredTotalPathLengthMetres=authoredTotalPathLengthMetres; double minimumBodyRadius=std::numeric_limits::infinity(); double minimumRadius=std::numeric_limits::infinity(); for(std::size_t pointIndex=0;pointIndex<=routeEnd;++pointIndex) { const double radius=radiiMetres[pointIndex]; minimumRadius=std::min(minimumRadius,radius); const double along=authoredPathDistanceMetres[pointIndex]; if(along>=10.0&&authoredTotalPathLengthMetres-along>=10.0) minimumBodyRadius=std::min(minimumBodyRadius,radius); } if(!std::isfinite(minimumBodyRadius))minimumBodyRadius=minimumRadius; // One fixed flat-floor depth through the body. The authored grade // constraint follows centreline-radius+0.23 m; shift the swept centreline // where a taper changes radius so this floor keeps that same route. constants.floorDepthMetres=std::max(0.5,minimumBodyRadius-0.23); constants.upperMouthFloorDepthMetres=std::max(0.0, static_cast(passage.controlPoints.front().positionSubvoxels[2] -passage.upperMouthSubvoxels[2])/qPerMetre+1.13); constants.lowerMouthFloorDepthMetres=std::max(0.0, static_cast(passage.controlPoints[routeEnd].positionSubvoxels[2] -passage.lowerMouthSubvoxels[2])/qPerMetre+1.13); const auto nativeCaveMouth=std::find_if(ResolvedLayout->cavernEdges.begin(), ResolvedLayout->cavernEdges.end(),[&](const auto& edge) { return edge.strateIndex==passage.lowerStrateIndex &&edge.startAnchorStableId==passage.stableId+"/lower-mouth" &&!edge.polylineMicrometres.empty(); }); if(nativeCaveMouth!=ResolvedLayout->cavernEdges.end()) { const double routeFloorMetres=static_cast( passage.controlPoints[routeEnd].positionSubvoxels[2])/qPerMetre; const double nativeFloorMetres=static_cast( nativeCaveMouth->polylineMicrometres.front()[2])/1000000.0; constants.lowerMouthFloorDepthMetres=std::max(0.0, routeFloorMetres-nativeFloorMetres); } const auto routeFloorDepth=[&](std::size_t pointIndex,double radius) { const double localRadiusDepth=std::max(0.0,radius-0.23); const double along=authoredPathDistanceMetres[pointIndex]; const double mouthDistance=std::max(0.0,std::min(along, authoredTotalPathLengthMetres-along)); const double linear=std::clamp(mouthDistance/5.0,0.0,1.0); const double ramp=linear*linear*(3.0-2.0*linear); const double target=along<=authoredTotalPathLengthMetres*0.5 ?constants.upperMouthFloorDepthMetres :constants.lowerMouthFloorDepthMetres; const double mouthDepth=std::clamp(target,0.0,std::max(0.0,radius)); return std::lerp(mouthDepth,localRadiusDepth,ramp); }; std::vector effectiveCenterZSubvoxels( passage.controlPoints.size(),0.0); for(std::size_t pointIndex=0;pointIndex(passage.controlPoints[pointIndex] .radiusSubvoxels)/qPerMetre; const double localTubeDepth=std::min(constants.floorDepthMetres, std::max(0.0,radius-0.23)); const double routeDepth=pointIndex<=routeEnd ?routeFloorDepth(pointIndex,radius):std::max(0.0,radius-0.23); effectiveCenterZSubvoxels[pointIndex]= static_cast(passage.controlPoints[pointIndex].positionSubvoxels[2]) +(-routeDepth+localTubeDepth)*qPerMetre; } for(std::size_t segmentIndex=0;segmentIndex(a.positionSubvoxels[2]); data.authoredCenterZDelta=static_cast( b.positionSubvoxels[2]-a.positionSubvoxels[2]); data.authoredAlongMetres=authoredPathDistanceMetres[segmentIndex]; data.authoredLengthMetres=authoredPathDistanceMetres[segmentIndex+1] -authoredPathDistanceMetres[segmentIndex]; for(std::size_t axis=0;axis<3;++axis) { data.a[axis]=static_cast(a.positionSubvoxels[axis]); data.b[axis]=static_cast(b.positionSubvoxels[axis]); if(axis==2) { data.a[axis]=effectiveCenterZSubvoxels[segmentIndex]; data.b[axis]=effectiveCenterZSubvoxels[segmentIndex+1]; } data.delta[axis]=data.b[axis]-data.a[axis]; } data.radiusA=static_cast(a.radiusSubvoxels); data.radiusB=static_cast(b.radiusSubvoxels); data.radiusDelta=static_cast(b.radiusSubvoxels)-a.radiusSubvoxels; const double dx=data.delta[0],dy=data.delta[1],dz=data.delta[2]; data.lengthSq=dx*dx+dy*dy+dz*dz; data.horizontalLengthSq=dx*dx+dy*dy; data.lengthSubvoxels=DeterministicSqrt(data.lengthSq); data.horizontalLength=DeterministicSqrt(data.horizontalLengthSq); ++preparedSegments; } constants.totalPathLengthMetres=authoredTotalPathLengthMetres; PassageSegmentOffsets[passageIndex+1]=preparedSegments; } if(tracePreparation) std::printf("[L109PassagePrepare] passages=%zu segments=%zu bytes=%zu prepare_ms=%.3f\n", PassageSegmentData.size(),preparedSegments, preparedSegments*sizeof(FPassageSegmentData), std::chrono::duration( std::chrono::steady_clock::now()-preparationStart).count()); } void BuildPassageSpatialIndices() { BuildStampSpatialIndex(); BuildCavernSpatialIndex(); PassageTileFeatures.clear(); PassageFeatureBounds.clear(); PassageFeatureOrder.clear(); PassageFeatureNodes.clear(); PassageFeatureBoundsFailOpen=false; PassageNearestNodes.clear(); PassageProofBounds.clear(); PassageProofNodes.clear(); PassageNearestNodes.resize(ResolvedLayout?ResolvedLayout->passages.size():0); if(!ResolvedLayout||ResolvedLayout->passages.empty())return; constexpr double Q=static_cast(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel); constexpr double PassageShellMetres=0.75; constexpr double ReliefMetres=0.21; constexpr double SweepReachMetres=4.0; for(std::size_t passageIndex=0;passageIndexpassages.size(); ++passageIndex) { const auto& passage=ResolvedLayout->passages[passageIndex]; const std::size_t segmentCount=passage.controlPoints.size()>1 ?passage.controlPoints.size()-1:0; if(segmentCount==0)continue; std::vector segmentIds(segmentCount); for(std::size_t i=0;i(a.radiusSubvoxels), static_cast(b.radiusSubvoxels))/Q; std::array routeMinimum{},routeMaximum{}; for(std::size_t axis=0;axis<3;++axis) { routeMinimum[axis]=std::min(prepared.a[axis],prepared.b[axis])/Q; routeMaximum[axis]=std::max(prepared.a[axis],prepared.b[axis])/Q; } const double routeExtent=maximumRadius+PassageShellMetres +ReliefMetres+0.01; const double tileExtent=routeExtent+SweepReachMetres; FPassageProofBound proof; for(std::size_t axis=0;axis<3;++axis) { const double extent=routeExtent; proof.minimum[axis]=routeMinimum[axis]-extent; proof.maximum[axis]=routeMaximum[axis]+extent; } PassageProofBounds.push_back(proof); std::array featureMinimum{},featureMaximum{}; std::array sweepMinimum{},sweepMaximum{}; for(std::size_t axis=0;axis<3;++axis) { featureMinimum[axis]=static_cast(std::floor( (routeMinimum[axis]-tileExtent)*Q)); featureMaximum[axis]=static_cast(std::ceil( (routeMaximum[axis]+tileExtent)*Q)); sweepMinimum[axis]=featureMinimum[axis]; sweepMaximum[axis]=featureMaximum[axis]; prepared.sweepMinimumSubvoxels[axis]=sweepMinimum[axis]; prepared.sweepMaximumSubvoxels[axis]=sweepMaximum[axis]; } PassageFeatureBounds.push_back({ static_cast(PassageTileFeatures.size()), featureMinimum,featureMaximum}); PassageTileFeatures.push_back({passageIndex,segmentIndex}); } } for(std::size_t i=0;i(i)); for(const auto& feature:PassageFeatureBounds) for(std::size_t axis=0;axis<3;++axis) PassageFeatureBoundsFailOpen=PassageFeatureBoundsFailOpen ||feature.minimumSubvoxels[axis]>feature.maximumSubvoxels[axis]; if(!PassageFeatureOrder.empty()) BuildPassageFeatureNode(0,PassageFeatureOrder.size()); if(!PassageProofBounds.empty()) BuildPassageProofNode(0,PassageProofBounds.size()); } bool PassageProofBoundsOverlapNode(int nodeIndex,const double minimum[3], const double maximum[3]) const { if(nodeIndex<0)return false; const auto& node=PassageProofNodes[static_cast(nodeIndex)]; for(std::size_t axis=0;axis<3;++axis) if(node.maximum[axis]maximum[axis])return false; if(node.left<0) { for(std::size_t index=node.first;index=minimum[axis] &&PassageProofBounds[index].minimum[axis]<=maximum[axis]; if(overlap)return true; } return false; } return PassageProofBoundsOverlapNode(node.left,minimum,maximum) ||PassageProofBoundsOverlapNode(node.right,minimum,maximum); } float NativePassageDensityAt(float X,float Y,float Z) const { float density=0.0f; if(!NativeFloatingIslandDensityAt(X,Y,Z,density) &&!NativeSurfaceDensityAt(X,Y,Z,density)) { if(UsesLegacySurfaceField(X,Y,Z)&&Owner&&Owner->Generator) { if(!LegacyGeneratorDensityAt(X,Y,Z,density)) density=Owner->Generator->GetDensityAt(X,Y,Z); } else density=AnalyticDensityAt(X,Y,Z,true,nullptr,nullptr,nullptr, false,false); } return ApplyResolvedStrateBoundaryBand(density,X,Y,Z); } static uint64 PackLatticeKey(int32 X,int32 Y,int32 Z) { constexpr uint64 Mask=0x1FFFFFull; return (static_cast(static_cast(X)&Mask)<<42) |(static_cast(static_cast(Y)&Mask)<<21) |(static_cast(static_cast(Z)&Mask)); } float PackedDensityAt(int32 X,int32 Y,int32 Z,bool bRock,float FX,float FY,float FZ) const { const size_t Column=static_cast(Y-Min[1])*Size[0]+static_cast(X-Min[0]); const size_t Zi=static_cast(Z-Min[2]); const size_t Word=Column*WordsPerZ+(Zi>>6); const uint64 Mask=uint64{1}<<(Zi&63u); if(CandidateWords[Word]&Mask) { const size_t Node=Zi*static_cast(Size[1])*Size[0]+Column; const int16 Proxy=SurfaceDistanceQ[Node]; float Density=static_cast(Proxy)*(1.0f/256.0f); if((Density<0.0f)!=bRock) Density=(bRock?-1.0f:1.0f)*std::max(std::abs(Density),1.0f/256.0f); return ApplyEditBrushes(ApplyResolvedStrateComposition(Density,FX,FY,FZ),FX,FY,FZ); } // Match the fixed fallback to the edge of the stored proxy band. The // displacement is tapered to zero there, so this avoids a magnitude // jump along the candidate bit-mask boundary. constexpr float ProxyBandEdge=DetailSupremum+0.125f; return ApplyEditBrushes(ApplyResolvedStrateComposition( bRock?-ProxyBandEdge:ProxyBandEdge,FX,FY,FZ),FX,FY,FZ); } float AnalyticDensityAt(float X,float Y,float Z,bool bClampNearZero=true, float* OutDetailDisplacement=nullptr, float* OutCaveInfluence=nullptr, float* OutFloorInfluence=nullptr, bool bApplyEdits=true,bool bApplyComposition=true) const { if(SharedDensitySource)return SharedDensitySource->AnalyticDensityAt(X,Y,Z, bClampNearZero,OutDetailDisplacement,OutCaveInfluence,OutFloorInfluence, bApplyEdits,bApplyComposition); if(OutDetailDisplacement)*OutDetailDisplacement=0.0f; if(OutCaveInfluence)*OutCaveInfluence=0.0f; if(OutFloorInfluence)*OutFloorInfluence=0.0f; float IslandDensity=0.0f; if(NativeFloatingIslandDensityAt(X,Y,Z,IslandDensity)) { const float composed=bApplyComposition ?ApplyResolvedStrateComposition(IslandDensity,X,Y,Z):IslandDensity; return bApplyEdits?ApplyEditBrushes(composed,X,Y,Z):composed; } if(UsesLegacySurfaceField(X,Y,Z)) { float NativeDensity=0.0f; if(NativeSurfaceDensityAt(X,Y,Z,NativeDensity)) { if(bApplyComposition) NativeDensity=ApplyResolvedStrateComposition(NativeDensity,X,Y,Z); return bApplyEdits?ApplyEditBrushes(NativeDensity,X,Y,Z):NativeDensity; } float Density=0.0f; if(!LegacyGeneratorDensityAt(X,Y,Z,Density)) Density=Owner->Generator->GetDensityAt(X,Y,Z); const float composed=bApplyComposition ?ApplyResolvedStrateComposition(Density,X,Y,Z):Density; return bApplyEdits?ApplyEditBrushes(composed,X,Y,Z):composed; } #if defined(VF_L76_PROFILE_LEGACY_TERMS) const FBoundStratumField* LegacyTermBound=FindBoundStratumForZ(Z); const bool bLegacyFiOrTnField=LegacyTermBound&&LegacyTermBound->Resolved &&(LegacyTermBound->Resolved->generatorKind=="TunnelNetwork" ||(LegacyTermBound->Resolved->generatorKind=="FloatingIslands" &&LegacyTermBound->IslandPlan==nullptr)); FScopedLegacyFieldTermScope LegacyFieldTermScope(bLegacyFiOrTnField); #endif const bool bInsideFine=X>=Min[0]&&X<=Max[0]&&Y>=Min[1]&&Y<=Max[1] &&Z>=Min[2]&&Z<=Max[2]; if(!bInsideFine) { bool bCriticalAir=false; float RockDistance=0.0f; { FScopedLegacyFieldTermTimer TermTimer(ELegacyFieldTerm::PrimitiveScan); RockDistance=CoarseRockDistance(X,Y,Z,&bCriticalAir); } if(bCriticalAir)RockDistance=std::min(RockDistance,-1.0f/16.0f); const bool bRock=RockDistance>=0.0f; float Density=-RockDistance; if((Density<0.0f)!=bRock||(bClampNearZero&&std::abs(Density)<0.05f)) Density=(bRock?-1.0f:1.0f)*std::max(std::abs(Density), bClampNearZero?0.05f:1.0e-7f); const float composed=bApplyComposition ?ApplyResolvedStrateComposition(Density,X,Y,Z):Density; if(bApplyEdits) { FScopedLegacyFieldTermTimer TermTimer(ELegacyFieldTerm::EditBrushes); return ApplyEditBrushes(composed,X,Y,Z); } return composed; } ESurfaceFamily SurfaceFamily=ESurfaceFamily::Strata;float SurfaceSDF=100000.0f; bool bFloorSurface=false,bCriticalAir=false;float CaveInfluence=0.0f,FloorInfluence=0.0f; float PassageInfluence=0.0f; float RockDistance=0.0f; { FScopedLegacyFieldTermTimer TermTimer(ELegacyFieldTerm::PrimitiveScan); RockDistance=EffectiveRockDistance(X,Y,Z,&SurfaceFamily,&SurfaceSDF, &bFloorSurface,&CaveInfluence,&FloorInfluence,&bCriticalAir,nullptr,false, &PassageInfluence); } if(OutCaveInfluence)*OutCaveInfluence=CaveInfluence; if(OutFloorInfluence)*OutFloorInfluence=FloorInfluence; if(bInsideFine&&DetailVariant!=EDetailVariant::Smooth &&std::abs(RockDistance)<=DetailSupremum+0.125f) { const float Fade=DetailBandFadeAt(X,Y,Z,RockDistance,SurfaceSDF, CaveInfluence,FloorInfluence); if(Fade>0.0f) { float DetailNoiseValue=0.0f; { FScopedLegacyFieldTermTimer TermTimer(ELegacyFieldTerm::Detail); DetailNoiseValue=DetailNoise(X,Y,Z,SurfaceSDF,CaveInfluence, FloorInfluence,PassageInfluence); } const float Detail=DetailNoiseValue*Fade; RockDistance+=Detail; if(OutDetailDisplacement)*OutDetailDisplacement=Detail; } } if(bCriticalAir)RockDistance=std::min(RockDistance,-1.0f/16.0f); const bool bRock=RockDistance>=0.0f; float Density=-RockDistance; if((Density<0.0f)!=bRock||(bClampNearZero&&std::abs(Density)<0.05f)) Density=(bRock?-1.0f:1.0f)*std::max(std::abs(Density), bClampNearZero?0.05f:1.0e-7f); if(bApplyComposition)Density=ApplyResolvedStrateComposition(Density,X,Y,Z); if(bApplyEdits) { FScopedLegacyFieldTermTimer TermTimer(ELegacyFieldTerm::EditBrushes); return ApplyEditBrushes(Density,X,Y,Z); } return Density; } public: bool GetNativeSurfaceHeightsAt(float X,float Y,int32 ChunkZ, float& OutGroundZ,float& OutCeilingZ) const { if(SharedDensitySource) return SharedDensitySource->GetNativeSurfaceHeightsAt(X,Y,ChunkZ,OutGroundZ,OutCeilingZ); if(!WorldAssembly||!ResolvedLayout||!Owner||!Owner->Generator ||!std::isfinite(X)||!std::isfinite(Y))return false; const double ChunkWidth=GetResolvedLayoutChunkWidthVoxels(); const std::int64_t chunkMinimum=static_cast(std::llround(ChunkZ*ChunkWidth)); const std::int64_t chunkMaximum=static_cast(std::llround((ChunkZ+1)*ChunkWidth)); const FBoundStratumField* Bound=FindBoundStratumForChunk(ChunkZ); const auto* Field=Bound?std::get_if(&Bound->Evaluator):nullptr; if(bNativeOnly&&(!NativeOnlyContainsColumn(X,Y) ||chunkMaximumNativeOnlyMaximum.Z ||!Bound||Bound->Kind!=FBoundStratumField::EKind::SurfaceWorld)) {RecordNativeOnlyViolation();return false;} if(!Bound||Bound->Kind!=FBoundStratumField::EKind::SurfaceWorld ||!Bound->SurfacePlan||!Field||!Field->Evaluator.isValid())return false; constexpr double Scale=static_cast(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel); const double MaxVoxel=static_cast(std::numeric_limits::max())/Scale; if(std::abs(static_cast(X))>MaxVoxel||std::abs(static_cast(Y))>MaxVoxel) return false; const auto ToFixed=[](float Value) {return static_cast(std::llround(static_cast(Value) *VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel));}; const auto Column=Field->Evaluator.point(ToFixed(X),ToFixed(Y), static_cast(ChunkZ)*ResolvedLayout->strateLayoutChunkWidthSubvoxels); OutGroundZ=static_cast(Column.groundHeightSubvoxels/Scale); OutCeilingZ=static_cast(Column.capHeightSubvoxels/Scale); return std::isfinite(OutGroundZ)&&std::isfinite(OutCeilingZ); } EVoxelTileClass ClassifyBox(const FIntVector& Origin,int32 Step,int32 Cells) const { return ClassifyBoxDetailed(Origin,Step,Cells).Verdict; } bool IsNativeSurfaceBox(const FIntVector& Origin,int32 Step,int32 Cells) const { if(SharedDensitySource)return SharedDensitySource->IsNativeSurfaceBox(Origin,Step,Cells); if(!WorldAssembly||!ResolvedLayout||!Owner||Step<=0||Cells<=0)return false; const int32 Pad=std::max(Step,1); const int32 BMin=Origin.Z-Pad; const int32 BMax=Origin.Z+(Cells+1)*Step; if(!StampSpatialBounds.empty()) { constexpr std::int64_t Q=VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel; const std::array minimum={ static_cast(Origin.X-Pad)*Q, static_cast(Origin.Y-Pad)*Q, static_cast(BMin)*Q}; const std::array maximum={ static_cast(Origin.X+(Cells+1)*Step)*Q, static_cast(Origin.Y+(Cells+1)*Step)*Q, static_cast(BMax)*Q}; if(!BuildStampTileList(minimum,maximum).empty())return false; } std::vector caveCandidates; if(!CavernRuntime.empty()) { constexpr std::int64_t Q=VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel; const std::array minimum={ static_cast(Origin.X-Pad)*Q, static_cast(Origin.Y-Pad)*Q, static_cast(BMin)*Q}; const std::array maximum={ static_cast(Origin.X+(Cells+1)*Step)*Q, static_cast(Origin.Y+(Cells+1)*Step)*Q, static_cast(BMax)*Q}; if(!BuildCavernFeatureTileList(minimum,maximum).empty())return false; } const float LayoutWidth=static_cast(GetResolvedLayoutChunkWidthVoxels()); const int32 FirstChunk=FMath::FloorToInt(static_cast(BMin)/LayoutWidth); const int32 LastChunk=FMath::FloorToInt(static_cast(BMax)/LayoutWidth); const FBoundStratumField* BoundPlan=nullptr; for(int32 ChunkZ=FirstChunk;ChunkZ<=LastChunk;++ChunkZ) { const auto* Candidate=FindBoundStratumForChunk(ChunkZ); if(!Candidate)return false; if(Candidate->Kind==FBoundStratumField::EKind::SurfaceWorld) { if(!Candidate->SurfacePlan||ChunkZSurfacePlan->bottomChunkZ ||ChunkZ>Candidate->SurfacePlan->topChunkZ)return false; } else if(Candidate->Kind==FBoundStratumField::EKind::Caves) { if(!Candidate->Resolved||!VoxelForgeCore::HasNativeGraphField(*Candidate->Resolved) ||(VoxelForgeCore::IsNativeCaveFieldStratum(*Candidate->Resolved) ?!Candidate->Resolved->passageCapabilities.hasNativeCaveField :!Candidate->Resolved->passageCapabilities.hasNativeLatticeField) ||!std::get_if(&Candidate->Evaluator))return false; } else return false; if(BoundPlan&&BoundPlan!=Candidate)return false; BoundPlan=Candidate; } if(bNativeOnly) { const int32 BMinX=Origin.X-Pad,BMinY=Origin.Y-Pad; const int32 BMaxX=Origin.X+(Cells+1)*Step; const int32 BMaxY=Origin.Y+(Cells+1)*Step; if(BMinXNativeOnlyMaximum.X ||BMaxY>NativeOnlyMaximum.Y||BMax>NativeOnlyMaximum.Z) {RecordNativeOnlyViolation();return false;} } if(bNativeOnly&&!BoundPlan)RecordNativeOnlyViolation(); return BoundPlan!=nullptr; } FMetricInterval ClassifyNativeCaveFieldBox(const int32 BMin[3],const int32 BMax[3], std::int32_t strateIndex,const std::vector& candidates) const { const double metresPerVoxel=Owner&&Owner->CoreVoxelSizeMicrons>0 ?Owner->CoreVoxelSizeMicrons/1000000.0:0.0; if(!(metresPerVoxel>0.0))return {0.0,0.0,false}; std::array lo{},hi{}; for(std::size_t axis=0;axis<3;++axis) { lo[axis]=static_cast(BMin[axis])*metresPerVoxel; hi[axis]=static_cast(BMax[axis])*metresPerVoxel; } FMetricInterval caveVoid{std::numeric_limits::infinity(), std::numeric_limits::infinity(),true}; bool haveFeature=false; double latticeFloorSupportLowerBound=std::numeric_limits::infinity(); const auto* resolvedStratum=FindResolvedLayoutStratum(strateIndex); const bool nativeLattice=resolvedStratum &&VoxelForgeCore::IsNativeLatticeFieldStratum(*resolvedStratum) &&resolvedStratum->passageCapabilities.hasNativeLatticeField; for(const std::uint32_t index:candidates) { if(index>=CavernRuntime.size())continue; const auto& feature=CavernRuntime[index]; const std::int32_t featureStrate=feature.room?feature.room->strateIndex :feature.edge?feature.edge->strateIndex:-1; if(featureStrate!=strateIndex)continue; FMetricInterval featureVoid; if(feature.room) { double normalizedMinimumSquared=0.0,normalizedMaximumSquared=0.0; double meanRadius=0.0; for(std::size_t axis=0;axis<3;++axis) { const double radius=feature.halfExtentsMetres[axis]; if(!(radius>0.0))return {0.0,0.0,false}; const double center=feature.centerMetres[axis]; const double deltaMinimum=centerhi[axis]?center-hi[axis]:0.0; const double deltaMaximum=std::max(std::abs(lo[axis]-center), std::abs(hi[axis]-center)); normalizedMinimumSquared+=(deltaMinimum/radius)*(deltaMinimum/radius); normalizedMaximumSquared+=(deltaMaximum/radius)*(deltaMaximum/radius); meanRadius+=radius/3.0; } const FMetricInterval ellipsoid{ (std::sqrt(normalizedMinimumSquared)-1.0)*meanRadius, (std::sqrt(normalizedMaximumSquared)-1.0)*meanRadius,true}; const double floorZ=feature.room->floorMicrometres/1000000.0; const FMetricInterval floor={floorZ-hi[2],floorZ-lo[2],true}; const double width=feature.room->smoothWidthMicrometres/1000000.0; featureVoid={SmoothMaxMetres(ellipsoid.minimum,floor.minimum,width), SmoothMaxMetres(ellipsoid.maximum,floor.maximum,width),true}; } else if(feature.edge&&!feature.segments.empty()) { const double radius=feature.edge->boreRadiusMicrometres/1000000.0; const double height=feature.edge->boreHeightMicrometres/1000000.0; if(!(radius>0.0&&height>0.0))return {0.0,0.0,false}; double tubeMinimum=std::numeric_limits::infinity(); double tubeMaximum=std::numeric_limits::infinity(); const std::array inverseScale={1.0/radius,1.0/radius, 1.0/height}; const std::array boxMinimum={lo[0]*inverseScale[0], lo[1]*inverseScale[1],lo[2]*inverseScale[2]}; const std::array boxMaximum={hi[0]*inverseScale[0], hi[1]*inverseScale[1],hi[2]*inverseScale[2]}; const double scale=std::min(radius,height); const auto nodeLowerBound=[&](const FCavernRuntimeFeature::FSegmentNode& node) { double gapSquared=0.0; for(std::size_t axis=0;axis<3;++axis) { const double low=node.minimum[axis]*inverseScale[axis]; const double high=node.maximum[axis]*inverseScale[axis]; const double gap=highboxMaximum[axis]?low-boxMaximum[axis]:0.0; gapSquared+=gap*gap; } return (std::sqrt(gapSquared)-1.0)*scale; }; const auto evaluateSegment=[&](std::uint32_t segmentIndex) { const auto& segment=feature.segments[segmentIndex]; std::array segmentStart{},segmentEnd{}; for(std::size_t axis=0;axis<3;++axis) { segmentStart[axis]=segment.start[axis]*inverseScale[axis]; segmentEnd[axis]=segment.end[axis]*inverseScale[axis]; } double gapSquared=0.0; for(std::size_t axis=0;axis<3;++axis) { const double segmentMinimum=std::min(segmentStart[axis],segmentEnd[axis]); const double segmentMaximum=std::max(segmentStart[axis],segmentEnd[axis]); const double gap=segmentMaximumboxMaximum[axis] ?segmentMinimum-boxMaximum[axis]:0.0; gapSquared+=gap*gap; } const double distanceLower=std::sqrt(gapSquared); const std::array boxCenter={ (boxMinimum[0]+boxMaximum[0])*0.5, (boxMinimum[1]+boxMaximum[1])*0.5, (boxMinimum[2]+boxMaximum[2])*0.5}; double lengthSquared=0.0,dot=0.0; for(std::size_t axis=0;axis<3;++axis) { const double delta=segmentEnd[axis]-segmentStart[axis]; lengthSquared+=delta*delta; dot+=(boxCenter[axis]-segmentStart[axis])*delta; } const double t=lengthSquared>0.0 ?std::clamp(dot/lengthSquared,0.0,1.0):0.0; double farthestSquared=0.0; for(std::size_t axis=0;axis<3;++axis) { const double fixedPoint=segmentStart[axis] +t*(segmentEnd[axis]-segmentStart[axis]); const double farthest=std::max( std::abs(boxMinimum[axis]-fixedPoint), std::abs(boxMaximum[axis]-fixedPoint)); farthestSquared+=farthest*farthest; } const double distanceUpper=std::sqrt(farthestSquared); tubeMinimum=std::min(tubeMinimum,(distanceLower-1.0)*scale); tubeMaximum=std::min(tubeMaximum,(distanceUpper-1.0)*scale); }; if(feature.segmentNodes.empty())return {0.0,0.0,false}; const auto visitNode=[&](const auto& self,int nodeIndex)->void { if(nodeIndex<0||static_cast(nodeIndex)>=feature.segmentNodes.size())return; const auto& node=feature.segmentNodes[static_cast(nodeIndex)]; if(nodeLowerBound(node)>tubeMaximum)return; if(node.left<0) { for(std::size_t offset=node.first;offset(node.left)]); const double rightBound=nodeLowerBound(feature.segmentNodes[ static_cast(node.right)]); if(leftBound<=rightBound) {self(self,node.left);self(self,node.right);} else {self(self,node.right);self(self,node.left);} }; visitNode(visitNode,0); const double floorMinimum=feature.minimumFloorMetres; const double floorMaximum=feature.maximumFloorMetres; const double maximumGrade=feature.maximumSegmentGrade; if(!std::isfinite(tubeMinimum)||!std::isfinite(tubeMaximum) ||!std::isfinite(floorMinimum)||!std::isfinite(floorMaximum)) return {0.0,0.0,false}; const double scaleMinimum=1.0/std::sqrt(1.0+maximumGrade*maximumGrade); const double numeratorMinimum=floorMinimum-hi[2]; const double numeratorMaximum=floorMaximum-lo[2]; const std::array products={numeratorMinimum*scaleMinimum, numeratorMinimum,numeratorMaximum*scaleMinimum,numeratorMaximum}; const FMetricInterval floor={ *std::min_element(products.begin(),products.end()), *std::max_element(products.begin(),products.end()),true}; const FMetricInterval tube={tubeMinimum,tubeMaximum,true}; const double width=feature.edge->smoothWidthMicrometres/1000000.0; featureVoid={SmoothMaxMetres(tube.minimum,floor.minimum,width), SmoothMaxMetres(tube.maximum,floor.maximum,width),true}; } else return {0.0,0.0,false}; if(nativeLattice) { const double supportDepth=feature.edge ?feature.edge->floorSupportDepthMicrometres/1000000.0:0.0; const double floorMinimum=feature.edge ?feature.minimumFloorMetres:std::numeric_limits::infinity(); const double floorMaximum=feature.edge ?feature.maximumFloorMetres:-std::numeric_limits::infinity(); const bool overlapsXY=hi[0]>=feature.boundsMinimumMicrometres[0]/1.0e6 &&lo[0]<=feature.boundsMaximumMicrometres[0]/1.0e6 &&hi[1]>=feature.boundsMinimumMicrometres[1]/1.0e6 &&lo[1]<=feature.boundsMaximumMicrometres[1]/1.0e6; if(supportDepth>0.0&&overlapsXY &&hi[2]>=floorMinimum-supportDepth&&lo[2]<=floorMaximum) latticeFloorSupportLowerBound=std::min(latticeFloorSupportLowerBound, std::max(-supportDepth,lo[2]-floorMaximum)); } if(!std::isfinite(featureVoid.minimum)||!std::isfinite(featureVoid.maximum)) return {0.0,0.0,false}; if(!haveFeature) { caveVoid=featureVoid;haveFeature=true; } else { const double width=feature.room ?(feature.room->unionWidthMicrometres>0 ?feature.room->unionWidthMicrometres :feature.room->smoothWidthMicrometres)/1000000.0 :(feature.edge->unionWidthMicrometres>0 ?feature.edge->unionWidthMicrometres :feature.edge->smoothWidthMicrometres)/1000000.0; caveVoid.minimum=SmoothMinMetres(caveVoid.minimum,featureVoid.minimum,width); caveVoid.maximum=SmoothMinMetres(caveVoid.maximum,featureVoid.maximum,width); } } constexpr double HostRockMetres=-32.0; FMetricInterval result{HostRockMetres,HostRockMetres,true}; if(haveFeature) { result.minimum=std::max(HostRockMetres,-caveVoid.maximum); result.maximum=std::max(HostRockMetres,-caveVoid.minimum); } // The final lattice field owns an extra support cap after feature union. // Lower only the interval minimum when a box could touch that cap; the // untouched maximum remains a sound bound when only part of the box does. if(std::isfinite(latticeFloorSupportLowerBound)) result.minimum=std::min(result.minimum,latticeFloorSupportLowerBound); // Cover floating-point rounding in interval endpoints and the field's fixed-point // coordinate conversion while keeping the interval tight compared with one micron. constexpr double OutwardPaddingMetres=1.0e-6; result.minimum=std::nextafter(result.minimum-OutwardPaddingMetres, -std::numeric_limits::infinity()); result.maximum=std::nextafter(result.maximum+OutwardPaddingMetres, std::numeric_limits::infinity()); result.known=std::isfinite(result.minimum)&&std::isfinite(result.maximum) &&result.minimum<=result.maximum; return result; } FBoxClassification ClassifyBoxDetailed(const FIntVector& Origin,int32 Step,int32 Cells, bool bUseNativeLatticeProof=false) const { if(SharedDensitySource) return SharedDensitySource->ClassifyBoxDetailed(Origin,Step,Cells,bUseNativeLatticeProof); const int32 Pad=std::max(Step,1); const int32 BMin[3]={Origin.X-Pad,Origin.Y-Pad,Origin.Z-Pad}; const int32 BMax[3]={Origin.X+(Cells+1)*Step,Origin.Y+(Cells+1)*Step, Origin.Z+(Cells+1)*Step}; return ClassifyBoundsDetailed(BMin,BMax,std::max(Step,1),bUseNativeLatticeProof); } FBoxClassification ClassifyBoundsDetailed(const int32 InputMin[3], const int32 InputMax[3],int32 SampleStep,bool bUseNativeLatticeProof=false) const { if(SharedDensitySource) return SharedDensitySource->ClassifyBoundsDetailed( InputMin,InputMax,SampleStep,bUseNativeLatticeProof); using Clock=std::chrono::steady_clock; const auto Start=Clock::now(); std::uint8_t LegacyProofVeto=static_cast(ELegacyProofVeto::None); static const bool bL74TightNegativeControl=[] { const char* Value=std::getenv("VOXELFORGE_L74_TIGHT_PROOF_CONTROL"); const bool Enabled=Value&&Value[0]=='1'&&Value[1]=='\0'; if(Enabled) std::printf("[L74NegativeControl] enabled=1 bound=base-sign-only-detail-and-primitive-envelope-omitted\n"); return Enabled; }(); const int32 BMin[3]={InputMin[0],InputMin[1],InputMin[2]}; const int32 BMax[3]={InputMax[0],InputMax[1],InputMax[2]}; static const bool bL75TraceTnBlock=[] { const char* Value=std::getenv("VOXELFORGE_L75_TN_BLOCK_REGRESSION"); return Value&&Value[0]=='1'&&Value[1]=='\0'; }(); constexpr int32 ProbeX=-80,ProbeY=-20,ProbeZ=-3218; const bool bTraceTnBlock=bL75TraceTnBlock &&ProbeX>=BMin[0]&&ProbeX<=BMax[0] &&ProbeY>=BMin[1]&&ProbeY<=BMax[1] &&ProbeZ>=BMin[2]&&ProbeZ<=BMax[2]; if(bTraceTnBlock) std::printf("[L75TNProofTrace] phase=enter step=%d bounds=(%d,%d,%d)..(%d,%d,%d)\n", SampleStep,BMin[0],BMin[1],BMin[2],BMax[0],BMax[1],BMax[2]); const VoxelForgeCore::WorldAssemblyStratumPlanV1* SurfacePlan=nullptr; const VoxelForgeCore::WorldAssemblyFloatingIslandPlanV1* IslandPlan=nullptr; const FSurfaceWorldFieldAdapter* SurfaceField=nullptr; const FFloatingIslandFieldAdapter* IslandField=nullptr; const FBoundStratumField* CaveBound=nullptr; bool bSurfaceOverlap=false,bIslandOverlap=false,bCavesOverlap=false; bool bOtherStrateOverlap=false,bLegacyOverlap=false,bCaveOtherStrateOverlap=false; bool bIslandOtherStrateOverlap=false,bBadPlan=false; bool bMissingStratumBound=false; bool bStratumSlicingExpectedToPay=false; bool bLegacyFieldCandidate=WorldAssembly&&Owner&&Owner->Manager &&ResolvedLayout&&Owner->Generator; bool bLegacyBlockProofRelevant=false; std::vector caveCandidates; if(Owner&&ResolvedLayout) { const float LayoutWidth=static_cast(GetResolvedLayoutChunkWidthVoxels()); const int32 FirstChunk=FMath::FloorToInt(static_cast(BMin[2])/LayoutWidth); const int32 LastChunk=FMath::FloorToInt(static_cast(BMax[2])/LayoutWidth); for(int32 ChunkZ=FirstChunk;ChunkZ<=LastChunk;++ChunkZ) { const FBoundStratumField* Bound=FindBoundStratumForChunk(ChunkZ); if(Bound&&Bound->Resolved &&(Bound->Resolved->generatorKind=="FloatingIslands" ||Bound->Resolved->generatorKind=="VerticalShafts")) bStratumSlicingExpectedToPay=true; if(Bound&&Bound->Kind==FBoundStratumField::EKind::SurfaceWorld) { bLegacyFieldCandidate=false; bSurfaceOverlap=true;bIslandOtherStrateOverlap=true; const auto* Candidate=Bound->SurfacePlan; if(!Candidate||ChunkZbottomChunkZ ||ChunkZ>Candidate->topChunkZ ||(SurfacePlan&&SurfacePlan!=Candidate))bBadPlan=true; SurfacePlan=Candidate; SurfaceField=std::get_if( &Bound->Evaluator); } else if(Bound&&Bound->Kind==FBoundStratumField::EKind::FloatingIslands) { bIslandOverlap=true;bOtherStrateOverlap=true; const auto* Candidate=Bound->IslandPlan; if(!Candidate||ChunkZbottomChunkZ ||ChunkZ>Candidate->topChunkZ ||(IslandPlan&&IslandPlan!=Candidate))bBadPlan=true; IslandPlan=Candidate; IslandField=std::get_if( &Bound->Evaluator); if(Bound->IslandPlan)bLegacyFieldCandidate=false; else if(Bound->Resolved &&Bound->Resolved->generatorKind=="TunnelNetwork") bLegacyBlockProofRelevant=true; } else if(Bound&&Bound->Kind==FBoundStratumField::EKind::Caves) { bLegacyFieldCandidate=false; bCavesOverlap=true;bOtherStrateOverlap=true; bIslandOtherStrateOverlap=true; if(CaveBound&&CaveBound!=Bound)bBadPlan=true; CaveBound=Bound; if(!Bound->Resolved||!VoxelForgeCore::HasNativeGraphField(*Bound->Resolved) ||(VoxelForgeCore::IsNativeCaveFieldStratum(*Bound->Resolved) ?!Bound->Resolved->passageCapabilities.hasNativeCaveField :!Bound->Resolved->passageCapabilities.hasNativeLatticeField) ||!std::get_if(&Bound->Evaluator))bBadPlan=true; } else { if(!Bound){bLegacyFieldCandidate=false;bMissingStratumBound=true;} else if(Bound->Resolved &&Bound->Resolved->generatorKind=="TunnelNetwork") bLegacyBlockProofRelevant=true; bOtherStrateOverlap=true;bIslandOtherStrateOverlap=true;bLegacyOverlap=true; } } bCaveOtherStrateOverlap=bCavesOverlap &&(bSurfaceOverlap||bIslandOverlap||bLegacyOverlap); } if(bTraceTnBlock) std::printf("[L75TNProofTrace] phase=field_bounds legacy=%d island=%d surface=%d caves=%d missing=%d bad_plan=%d\n", bLegacyFieldCandidate?1:0,bIslandOverlap?1:0,bSurfaceOverlap?1:0, bCavesOverlap?1:0,bMissingStratumBound?1:0,bBadPlan?1:0); const auto Finish=[&](EVoxelTileClass Verdict,bool bUnknown=false, bool bLegacyAnalyticProof=false) { const bool bNative=(bSurfaceOverlap&&!bOtherStrateOverlap) ||(bIslandOverlap&&!bIslandOtherStrateOverlap) ||(bCavesOverlap&&!bCaveOtherStrateOverlap); const bool bUniform=Verdict==EVoxelTileClass::AllAir ||Verdict==EVoxelTileClass::AllSolid; FBoxClassification Result{Verdict,bNative,bUnknown, bLegacyAnalyticProof||(bNative&&!bUnknown&&bUniform)}; Result.bLegacyFieldCandidate=bLegacyFieldCandidate; Result.bLegacyBlockProofRelevant=bLegacyBlockProofRelevant; Result.LegacyProofVeto=Result.bSkipSafe ?static_cast(ELegacyProofVeto::None):LegacyProofVeto; auto& Counts=ThreadClassificationStats(); Counts.Milliseconds+=std::chrono::duration(Clock::now()-Start).count(); if(bUnknown)++Counts.UnknownBoxes; else if(Verdict==EVoxelTileClass::AllSolid)++Counts.SolidBoxes; else if(Verdict==EVoxelTileClass::AllAir)++Counts.AirBoxes; else ++Counts.MixedBoxes; return Result; }; if(SampleStep<=2&&bStratumSlicingExpectedToPay&&Owner&&ResolvedLayout &&SampleStep>0&&BMax[2]>=BMin[2] &&(static_cast(BMax[2])-BMin[2])%SampleStep==0) { const double LayoutWidth=GetResolvedLayoutChunkWidthVoxels(); if(LayoutWidth>0.0&&std::isfinite(LayoutWidth)) { std::vector> StratumSlices; int32 SliceStart=BMin[2]; auto BoundAt=[&](int32 Z) { const int32 ChunkZ=FMath::FloorToInt(static_cast(Z)/LayoutWidth); return FindBoundStratumForChunk(ChunkZ); }; const FBoundStratumField* PreviousBound=BoundAt(SliceStart); for(std::int64_t Z=static_cast(BMin[2])+SampleStep; Z<=BMax[2];Z+=SampleStep) { const auto* Bound=BoundAt(static_cast(Z)); if(Bound==PreviousBound)continue; StratumSlices.push_back({SliceStart,static_cast(Z-SampleStep)}); SliceStart=static_cast(Z); PreviousBound=Bound; } if(!StratumSlices.empty()) { StratumSlices.push_back({SliceStart,BMax[2]}); EVoxelTileClass CommonVerdict=EVoxelTileClass::Mixed; bool bHaveVerdict=false,bAllNative=true,bAllLegacy=true,bAnyLegacy=false; for(const auto& Slice:StratumSlices) { const int32 SliceMinimum[3]={BMin[0],BMin[1],Slice.first}; const int32 SliceMaximum[3]={BMax[0],BMax[1],Slice.second}; const auto Part=ClassifyBoundsDetailed(SliceMinimum,SliceMaximum, SampleStep,bUseNativeLatticeProof); if(!Part.bSkipSafe||Part.bUnknown ||(Part.Verdict!=EVoxelTileClass::AllSolid &&Part.Verdict!=EVoxelTileClass::AllAir)) { LegacyProofVeto=Part.LegacyProofVeto ?Part.LegacyProofVeto :static_cast(ELegacyProofVeto::StratumCrossing); return Finish(EVoxelTileClass::Mixed,Part.bUnknown); } if(!bHaveVerdict) { CommonVerdict=Part.Verdict; bHaveVerdict=true; } else if(Part.Verdict!=CommonVerdict) { LegacyProofVeto=static_cast( ELegacyProofVeto::StratumCrossing); return Finish(EVoxelTileClass::Mixed); } bAllNative=bAllNative&&Part.bNativeSurface; bAllLegacy=bAllLegacy&&Part.bLegacyFieldCandidate; bAnyLegacy=bAnyLegacy||Part.bLegacyBlockProofRelevant; } auto Result=Finish(CommonVerdict,false,true); Result.bNativeSurface=bAllNative; Result.bLegacyFieldCandidate=bAllLegacy; Result.bLegacyBlockProofRelevant=bAnyLegacy; return Result; } } } std::vector stampCandidates; if(!StampSpatialBounds.empty()) { constexpr std::int64_t Q=VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel; const std::array minimum={ static_cast(BMin[0])*Q, static_cast(BMin[1])*Q, static_cast(BMin[2])*Q}; const std::array maximum={ static_cast(BMax[0])*Q, static_cast(BMax[1])*Q, static_cast(BMax[2])*Q}; stampCandidates=BuildStampTileList(minimum,maximum); for(const std::uint32_t index:stampCandidates) { if(index>=StampRuntime.size())return Finish(EVoxelTileClass::Mixed,true); const auto& stamp=StampRuntime[index]; if(!stamp.record||(stamp.record->kind !=VoxelForgeCore::ResolvedSdfStampKindV1::fillMonolith &&stamp.record->kind !=VoxelForgeCore::ResolvedSdfStampKindV1::fillPlatform) ||!SurfacePlan||stamp.record->strateIndex!=SurfacePlan->strateIndex) { LegacyProofVeto=static_cast(ELegacyProofVeto::ComposerCandidate); return Finish(EVoxelTileClass::Mixed,true); } } } if(!CavernRuntime.empty()) { constexpr std::int64_t Q=VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel; const std::array minimum={ static_cast(BMin[0])*Q, static_cast(BMin[1])*Q, static_cast(BMin[2])*Q}; const std::array maximum={ static_cast(BMax[0])*Q, static_cast(BMax[1])*Q, static_cast(BMax[2])*Q}; caveCandidates=BuildCavernFeatureTileList(minimum,maximum); if(!caveCandidates.empty()&&!bCavesOverlap&&!bL74TightNegativeControl) { LegacyProofVeto=static_cast(ELegacyProofVeto::ComposerCandidate); return Finish(EVoxelTileClass::Mixed,true); } } const bool bPassageOverlap=ResolvedPassageIntersectsBox(BMin,BMax); if(bTraceTnBlock) std::printf("[L75TNProofTrace] phase=passage intersects=%d\n",bPassageOverlap?1:0); if(bPassageOverlap) { LegacyProofVeto=static_cast(ELegacyProofVeto::PassageBounds); return Finish(EVoxelTileClass::Mixed,true); } if(!EditBrushes.empty()&&EditBoundsMin.X<=BMax[0]&&EditBoundsMax.X>=BMin[0] &&EditBoundsMin.Y<=BMax[1]&&EditBoundsMax.Y>=BMin[1] &&EditBoundsMin.Z<=BMax[2]&&EditBoundsMax.Z>=BMin[2]) return Finish(EVoxelTileClass::Mixed,true); if(ResolvedLayout&&Owner) { const double ZMinMicrometres=static_cast(BMin[2])*Owner->CoreVoxelSizeMicrons; const double ZMaxMicrometres=static_cast(BMax[2])*Owner->CoreVoxelSizeMicrons; for(const auto& Stratum:ResolvedLayout->strates) for(const auto* Band:{&Stratum.ceiling,&Stratum.bedrock}) if(Band->presentInCurrentField&&ZMaxMicrometres>=Band->bottomMicrometres &&ZMinMicrometrestopMicrometres) { if(ZMinMicrometres>=Band->bottomMicrometres &&ZMaxMicrometrestopMicrometres) return Finish(EVoxelTileClass::AllSolid); LegacyProofVeto=static_cast(ELegacyProofVeto::StratumCrossing); return Finish(EVoxelTileClass::Mixed); } } if(bNativeOnly) { const bool bInsideProofBox=BMin[0]>=NativeOnlyMinimum.X &&BMin[1]>=NativeOnlyMinimum.Y&&BMin[2]>=NativeOnlyMinimum.Z &&BMax[0]<=NativeOnlyMaximum.X&&BMax[1]<=NativeOnlyMaximum.Y &&BMax[2]<=NativeOnlyMaximum.Z; const bool bSupportedNative=(bSurfaceOverlap&&!bOtherStrateOverlap) ||(bIslandOverlap&&!bIslandOtherStrateOverlap) ||(bCavesOverlap&&!bCaveOtherStrateOverlap); if(!bInsideProofBox||!bSupportedNative ||bBadPlan||!EditBrushes.empty()) { RecordNativeOnlyViolation(); return Finish(EVoxelTileClass::Mixed,true); } } // Boundary transitions can replace otherwise-air density with a solid seal and // can append a separate face mesh. Their authored bands were checked above; this // expanded window also covers the maximum XY displacement at every active seam. if(ResolvedLayout&&Owner&&Owner->CoreVoxelSizeMicrons>0) { const double ZMinimumMicrometres=static_cast(BMin[2]) *Owner->CoreVoxelSizeMicrons; const double ZMaximumMicrometres=static_cast(BMax[2]) *Owner->CoreVoxelSizeMicrons; for(const auto& Upper:ResolvedLayout->strates) { const auto* Lower=FindResolvedLayoutStratumByOrder(Upper.order+1); if(!Lower||!IsResolvedLayoutTransitionActive(Upper))continue; const double MaximumDisplacement= ResolvedTransitionMaximumFaceDisplacementMicrometres(Upper,*Lower); const double WindowMinimum=std::min(Upper.bedrock.bottomMicrometres, Lower->ceiling.bottomMicrometres)-MaximumDisplacement; const double WindowMaximum=std::max(Upper.bedrock.topMicrometres, Lower->ceiling.topMicrometres)+MaximumDisplacement; if(ZMaximumMicrometres>=WindowMinimum &&ZMinimumMicrometres<=WindowMaximum) { LegacyProofVeto=static_cast(ELegacyProofVeto::Transition); return Finish(EVoxelTileClass::Mixed,true); } } } const auto TryLegacyAnalyticProof=[&]() -> std::optional { // The shared base shape may use packed occupancy, while its selected legacy // field remains the compiled strata distance plus bounded primitive/detail // terms. SurfaceWorld op stacks, native adapters, and any active composer // feature fail closed and keep using their existing path. if(!WorldAssembly||!Owner||!Owner->Manager ||!ResolvedLayout||!Owner->Generator) { LegacyProofVeto=static_cast(ELegacyProofVeto::Other); return std::nullopt; } if(!stampCandidates.empty()||!caveCandidates.empty()) { LegacyProofVeto=static_cast(ELegacyProofVeto::ComposerCandidate); if(!bL74TightNegativeControl)return std::nullopt; } if(bSurfaceOverlap||bCavesOverlap) { const bool bCrossedFieldKinds=(bSurfaceOverlap &&(bIslandOverlap||bCavesOverlap||bLegacyOverlap)) ||(bIslandOverlap&&(bSurfaceOverlap||bCavesOverlap||bLegacyOverlap)) ||(bCavesOverlap&&(bSurfaceOverlap||bIslandOverlap||bLegacyOverlap)); LegacyProofVeto=static_cast(bCrossedFieldKinds||bBadPlan ?ELegacyProofVeto::StratumCrossing:ELegacyProofVeto::Other); return std::nullopt; } if(!bLegacyFieldCandidate) { LegacyProofVeto=static_cast(bBadPlan||bMissingStratumBound ?ELegacyProofVeto::StratumCrossing:ELegacyProofVeto::Other); return std::nullopt; } if(!EditBrushes.empty()||LayerRanges.empty()||ActiveSpatialBinSize<=0) { LegacyProofVeto=static_cast(ELegacyProofVeto::Other); return std::nullopt; } const double LayoutWidth=GetResolvedLayoutChunkWidthVoxels(); if(!(LayoutWidth>0.0)||!std::isfinite(LayoutWidth)) { LegacyProofVeto=static_cast(ELegacyProofVeto::Other); return std::nullopt; } // The earlier chunk scan establishes that this padded box selects only the // compiled legacy field. Native fields and missing chunk bounds stay fail-closed. for(int32 BZ=FloorDiv(BMin[2],ActiveSpatialBinSize); BZ<=FloorDiv(BMax[2],ActiveSpatialBinSize);++BZ) for(int32 BY=FloorDiv(BMin[1],ActiveSpatialBinSize); BY<=FloorDiv(BMax[1],ActiveSpatialBinSize);++BY) for(int32 BX=FloorDiv(BMin[0],ActiveSpatialBinSize); BX<=FloorDiv(BMax[0],ActiveSpatialBinSize);++BX) { const auto It=SpatialBins.find({BX,BY,BZ}); if(It==SpatialBins.end())continue; for(const uint32 Id:It->second) { const FPrimitive& Primitive=Primitives[Id]; bool Overlap=true; for(int32 Axis=0;Axis<3;++Axis) Overlap=Overlap &&Primitive.Max[Axis]+DetailBand>=BMin[Axis] &&Primitive.Min[Axis]-DetailBand<=BMax[Axis]; if(bTraceTnBlock&&Overlap) std::printf("[L75TNProofTrace] phase=primitive_overlap id=%u bounds=(%d,%d,%d)..(%d,%d,%d)\n", Id,Primitive.Min[0],Primitive.Min[1],Primitive.Min[2], Primitive.Max[0],Primitive.Max[1],Primitive.Max[2]); if(Overlap&&!bL74TightNegativeControl) { LegacyProofVeto=static_cast(ELegacyProofVeto::PrimitiveOverlap); return std::nullopt; } } } for(const uint32 Id:UnbinnedSpatialPrimitiveIds) { if(Id>=Primitives.size())continue; const FPrimitive& Primitive=Primitives[Id]; bool Overlap=true; for(int32 Axis=0;Axis<3;++Axis) Overlap=Overlap &&Primitive.Max[Axis]+DetailBand>=BMin[Axis] &&Primitive.Min[Axis]-DetailBand<=BMax[Axis]; if(bTraceTnBlock&&Overlap) std::printf("[L75TNProofTrace] phase=unbinned_primitive_overlap id=%u bounds=(%d,%d,%d)..(%d,%d,%d)\n", Id,Primitive.Min[0],Primitive.Min[1],Primitive.Min[2], Primitive.Max[0],Primitive.Max[1],Primitive.Max[2]); if(Overlap&&!bL74TightNegativeControl) { LegacyProofVeto=static_cast(ELegacyProofVeto::PrimitiveOverlap); return std::nullopt; } } const FInterval& Layer=LayerRanges.front(); if(Layer.Hi<=Layer.Lo) { LegacyProofVeto=static_cast(ELegacyProofVeto::Other); return std::nullopt; } const auto BaseAt=[&](double Z) {return std::min(Z-static_cast(Layer.Lo), static_cast(Layer.Hi)-Z);}; const double Z0=static_cast(BMin[2]); const double Z1=static_cast(BMax[2]); const double Base0=BaseAt(Z0),Base1=BaseAt(Z1); const double BaseMinimum=std::min(Base0,Base1); double BaseMaximum=std::max(Base0,Base1); const double Peak=(static_cast(Layer.Lo)+Layer.Hi)*0.5; if(Peak>=Z0&&Peak<=Z1)BaseMaximum=std::max(BaseMaximum,BaseAt(Peak)); // With no active primitive, smooth-min sees only its +100000 sentinel; // outside the width-12 transition it is exactly the base field. Charge its // 12/6 maximum only if the base interval can approach that sentinel. constexpr double DetailSupport=DetailSupremum+0.125; constexpr double SmoothMinSentinel=100000.0; const double SmoothMinSlack=BaseMaximum>SmoothMinSentinel -StrataCaveBlendRadius?StrataCaveBlendRadius/6.0:0.0; constexpr double SafetyEpsilon=1.0e-3; const double SolidMargin=bL74TightNegativeControl ?SafetyEpsilon:DetailSupport+SmoothMinSlack+SafetyEpsilon; const double AirMargin=bL74TightNegativeControl ?SafetyEpsilon:DetailSupport+SafetyEpsilon; if(bTraceTnBlock) std::printf("[L75TNProofTrace] phase=legacy_interval base_min=%.9f base_max=%.9f detail=%.9f smooth_slack=%.9f solid_margin=%.9f air_margin=%.9f\n", BaseMinimum,BaseMaximum,DetailSupport,SmoothMinSlack,SolidMargin,AirMargin); if(BaseMinimum>SolidMargin) return EVoxelTileClass::AllSolid; if(BaseMaximum<-AirMargin) return EVoxelTileClass::AllAir; if(!bL74TightNegativeControl&&SmoothMinSlack>0.0 &&BaseMinimum>DetailSupport+SafetyEpsilon) LegacyProofVeto=static_cast(ELegacyProofVeto::SmoothMinSlack); else LegacyProofVeto=static_cast(ELegacyProofVeto::DetailEnvelope); return std::nullopt; }; if(const auto LegacyVerdict=TryLegacyAnalyticProof()) return Finish(*LegacyVerdict,false,true); // A box crossing recipes cannot use one field interval; unknown composition stays Mixed. // Une boite qui traverse plusieurs recettes reste Mixed, car l'intervalle d'un seul champ ne suffit pas. if(bBadPlan||(bSurfaceOverlap&&(bIslandOverlap||bOtherStrateOverlap)) ||(bIslandOverlap&&bIslandOtherStrateOverlap) ||(bCavesOverlap&&(bCaveOtherStrateOverlap||bSurfaceOverlap||bIslandOverlap))) { LegacyProofVeto=static_cast(ELegacyProofVeto::StratumCrossing); return Finish(EVoxelTileClass::Mixed,true); } // The native interval path below is the exact field selected first by DensityAtLattice // for SurfaceWorld and FloatingIslands. Legacy analytic primitives/layer bounds cannot // modify that field; only the explicit edit transaction above can. Keep their guards on // the legacy classifier path, where those overlays do participate in density. if(!bNativeOnly&&!bCavesOverlap&&(!bUseNativeLatticeProof ||(!bSurfaceOverlap&&!bIslandOverlap))) { for(int32 BZ=FloorDiv(BMin[2],ActiveSpatialBinSize);BZ<=FloorDiv(BMax[2],ActiveSpatialBinSize);++BZ) for(int32 BY=FloorDiv(BMin[1],ActiveSpatialBinSize);BY<=FloorDiv(BMax[1],ActiveSpatialBinSize);++BY) for(int32 BX=FloorDiv(BMin[0],ActiveSpatialBinSize);BX<=FloorDiv(BMax[0],ActiveSpatialBinSize);++BX) { const auto It=SpatialBins.find({BX,BY,BZ});if(It==SpatialBins.end())continue; for(uint32 Id:It->second) { const FPrimitive& P=Primitives[Id];bool Overlap=true; for(int32 A=0;A<3;++A) Overlap &= P.Max[A]+DetailBand>=BMin[A]&&P.Min[A]-DetailBand<=BMax[A]; if(Overlap)return Finish(EVoxelTileClass::Mixed); } } for(const FInterval& R:LayerRanges) if(R.Lo<=BMax[2]&&R.Hi>=BMin[2] &&(R.Lo>=BMin[2]||R.Hi<=BMax[2])) return Finish(EVoxelTileClass::Mixed); } if(bSurfaceOverlap) { if(!WorldAssembly||!SurfacePlan||!SurfaceField) return Finish(EVoxelTileClass::Mixed,true); if(!stampCandidates.empty()) { constexpr std::int64_t Scale=VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel; const auto interval=SurfaceField->Evaluator.interval( static_cast(BMin[0])*Scale, static_cast(BMin[1])*Scale, static_cast(BMin[2])*Scale, static_cast(BMax[0])*Scale, static_cast(BMax[1])*Scale, static_cast(BMax[2])*Scale); if(!interval.known||interval.minimumDensitySubvoxels >interval.maximumDensitySubvoxels) return Finish(EVoxelTileClass::Mixed,true); FMetricInterval composed{ static_cast(interval.minimumDensitySubvoxels)/Scale, static_cast(interval.maximumDensitySubvoxels)/Scale,true}; for(const std::uint32_t index:stampCandidates) { const auto& stamp=StampRuntime[index]; const auto feature=StampDistanceInterval(stamp,BMin,BMax); if(!feature.known)return Finish(EVoxelTileClass::Mixed,true); composed.minimum=SmoothMinDistance(static_cast(composed.minimum), static_cast(feature.minimum),stamp.smoothWidthVoxels); composed.maximum=SmoothMinDistance(static_cast(composed.maximum), static_cast(feature.maximum),stamp.smoothWidthVoxels); } constexpr double outwardPadding=2.0e-5; composed.minimum=std::nextafter(composed.minimum-outwardPadding, -std::numeric_limits::infinity()); composed.maximum=std::nextafter(composed.maximum+outwardPadding, std::numeric_limits::infinity()); if(!std::isfinite(composed.minimum)||!std::isfinite(composed.maximum) ||composed.minimum>composed.maximum) return Finish(EVoxelTileClass::Mixed,true); if(composed.minimum>=0.0)return Finish(EVoxelTileClass::AllAir); if(composed.maximum<0.0)return Finish(EVoxelTileClass::AllSolid); return Finish(EVoxelTileClass::Mixed); } // classifyLatticeBox takes the exact extrema of ground/cap columns at every // MC lattice XY in this box. Since density is min(z-ground, cap-z), those extrema // bound every lattice sample in Z without a noise amplitude guess. const auto Verdict=SurfaceField->Evaluator.classifyLatticeBox( static_cast(BMin[0])*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel, static_cast(BMin[1])*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel, static_cast(BMin[2])*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel, static_cast(BMax[0])*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel, static_cast(BMax[1])*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel, static_cast(BMax[2])*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel, std::max(SampleStep,1)); switch(Verdict) { case VoxelForgeCore::HeightfieldBoxClass::allAir: return Finish(EVoxelTileClass::AllAir); case VoxelForgeCore::HeightfieldBoxClass::allSolid: return Finish(EVoxelTileClass::AllSolid); case VoxelForgeCore::HeightfieldBoxClass::mixed: return Finish(EVoxelTileClass::Mixed); default:return Finish(EVoxelTileClass::Mixed,true); } } if(bIslandOverlap) { if(!IslandPlan||!IslandField)return Finish(EVoxelTileClass::Mixed,true); constexpr std::int64_t Scale=VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel; const std::int64_t Bottom=IslandPlan->manifest.recipe.layoutMinimumZSubvoxels; const std::int64_t Top=IslandPlan->manifest.recipe.layoutMaximumZSubvoxels; const std::int64_t Seal=IslandPlan->manifest.recipe.boundarySealSubvoxels; const std::int64_t BoxBottom=static_cast(BMin[2])*Scale; const std::int64_t BoxTop=static_cast(BMax[2])*Scale; const bool bSharedTopSealEnabled=IslandPlan->manifest.recipe.geometryVersion<3 ||!VoxelDensityAblation::IsBoundarySealOff(); const bool bTopSealOverlap=bSharedTopSealEnabled &&BoxBottom<=Top&&BoxTop>Top-Seal; const bool bBottomSealOverlap=IslandPlan->manifest.recipe.geometryVersion<3 &&BoxBottom=Bottom; if(bBottomSealOverlap) return Finish(BoxBottom>=Bottom&&BoxTop<=Bottom+Seal ?EVoxelTileClass::AllSolid:EVoxelTileClass::Mixed); if(bTopSealOverlap) return Finish((IslandPlan->manifest.recipe.geometryVersion<3 ?BoxBottom>=Top-Seal:BoxBottom>Top-Seal)&&BoxTop<=Top ?EVoxelTileClass::AllSolid:EVoxelTileClass::Mixed); if(!IslandField->Evaluator.isValid())return Finish(EVoxelTileClass::Mixed,true); const auto Interval=IslandField->Evaluator.interval(static_cast(BMin[0])*Scale, static_cast(BMin[1])*Scale,static_cast(BMin[2])*Scale, static_cast(BMax[0])*Scale,static_cast(BMax[1])*Scale, static_cast(BMax[2])*Scale); if(!Interval.known)return Finish(EVoxelTileClass::Mixed,true); if(Interval.minimumDensitySubvoxels>=0)return Finish(EVoxelTileClass::AllAir); if(Interval.maximumDensitySubvoxels<0)return Finish(EVoxelTileClass::AllSolid); return Finish(EVoxelTileClass::Mixed); } if(bCavesOverlap) { if(!CaveBound||!std::get_if(&CaveBound->Evaluator)) return Finish(EVoxelTileClass::Mixed,true); const auto interval=ClassifyNativeCaveFieldBox(BMin,BMax, CaveBound->StrateIndex,caveCandidates); if(!interval.known)return Finish(EVoxelTileClass::Mixed,true); if(interval.minimum>0.0)return Finish(EVoxelTileClass::AllAir); if(interval.maximum<0.0)return Finish(EVoxelTileClass::AllSolid); return Finish(EVoxelTileClass::Mixed); } const bool bRock=EvaluateFast((BMin[0]+BMax[0])*0.5, (BMin[1]+BMax[1])*0.5,(BMin[2]+BMax[2])*0.5); return Finish(bRock?EVoxelTileClass::AllSolid:EVoxelTileClass::AllAir); } bool IsRock(int32 X,int32 Y,int32 Z) const { if(SharedDensitySource)return SharedDensitySource->IsRock(X,Y,Z); if(!Contains(X,Y,Z))return EvaluateFast(X,Y,Z); const size_t Line=static_cast(Y-Min[1])*Size[0] +static_cast(X-Min[0]); const size_t Zi=static_cast(Z-Min[2]); return ((RockWords[Line*WordsPerZ+(Zi>>6)]>>(Zi&63u))&1u)!=0; } bool IsDetailAuditPassing() const { const double Correlation=DetailAutocorrelation(DetailVariant); const bool bFlips=Stats.DetailFlips>=1000; const bool bCoherent=Correlation>0.45; const bool bFamilies=AuditSurfaceBandFamilies(); std::printf("[DetailAudit] mode=%s supremum=%.2f vox band=%d changed_signs=%llu adjacent_correlation=%.4f surface_detail_check=%s spatial_scale_check=%s\n", DetailVariant==EDetailVariant::Coherent?"coherent":DetailVariant==EDetailVariant::Smooth?"smooth":"per-voxel", DetailSupremum,DetailBand,static_cast(Stats.DetailFlips),Correlation, bFlips&&bFamilies?"PASS":"FAIL",bCoherent?"PASS":"FAIL"); return bFlips&&bCoherent&&bFamilies; } bool AuditSurfaceBandFamilies(bool bPassageOnly=false) const { struct FPatchKey { int32 X=0,Y=0,Z=0;uint8 Family=0; bool operator==(const FPatchKey&)const=default; }; struct FPatchHash { size_t operator()(const FPatchKey& K)const noexcept { uint64 H=static_cast(K.X)*0x9E3779B185EBCA87ull; H^=static_cast(static_cast(K.Y))*0xC2B2AE3D27D4EB4Full; H^=static_cast(static_cast(K.Z))*0x165667B19E3779F9ull; H^=static_cast(K.Family)*0x94D049BB133111EBull; H^=H>>29;H*=0xBF58476D1CE4E5B9ull;H^=H>>31; return static_cast(H); } }; struct FPatchStats {uint64 Count=0;double Sum=0.0,SumSq=0.0;}; struct FFamilyStats {uint64 Count=0,NearZero=0;double Sum=0.0,SumSq=0.0;}; std::unordered_map Patches; std::array Families{}; FFamilyStats PassageWalls,OrdinaryCaveWalls; for(size_t Line=0;Line(Size[0])*Size[1];++Line) { const int32 X=Min[0]+static_cast(Line%Size[0]); const int32 Y=Min[1]+static_cast(Line/Size[0]); const size_t WordBase=Line*WordsPerZ; for(size_t W=0;W=static_cast(Size[2]))continue; const int32 Z=Min[2]+static_cast(Zi); ESurfaceFamily Family=ESurfaceFamily::Strata;float SurfaceSDF=100000.0f; bool bFloorSurface=false;float CaveInfluence=0.0f,FloorInfluence=0.0f; float PassageInfluence=0.0f; const float Raw=EffectiveRockDistance(X,Y,Z,&Family,&SurfaceSDF, &bFloorSurface,&CaveInfluence,&FloorInfluence,nullptr,nullptr,false, &PassageInfluence); if(std::abs(Raw)>=0.5f)continue; const float Fade=DetailBandFadeAt(X,Y,Z,Raw,SurfaceSDF, CaveInfluence,FloorInfluence); const float Detail=Fade>0.0f ?DetailNoise(X,Y,Z,SurfaceSDF,CaveInfluence,FloorInfluence, PassageInfluence)*Fade:0.0f; const size_t FamilyIndex=static_cast(Family); FFamilyStats& F=Families[FamilyIndex];++F.Count;F.Sum+=Detail;F.SumSq+=Detail*Detail; if(std::abs(Detail)<0.05f)++F.NearZero; if(PassageInfluence>=0.5f) {++PassageWalls.Count;PassageWalls.Sum+=Detail;PassageWalls.SumSq+=Detail*Detail;} else if(Family==ESurfaceFamily::Cave&&PassageInfluence<0.1f) {++OrdinaryCaveWalls.Count;OrdinaryCaveWalls.Sum+=Detail;OrdinaryCaveWalls.SumSq+=Detail*Detail;} const FPatchKey Key{FloorDiv(X,16),FloorDiv(Y,16),FloorDiv(Z,16), static_cast(FamilyIndex)}; FPatchStats& P=Patches[Key];++P.Count;P.Sum+=Detail;P.SumSq+=Detail*Detail; } } } std::array FlatCounts{};uint64 LargestFlatPatch=0; for(const auto& Entry:Patches) { const FPatchStats& P=Entry.second;if(P.Count<16)continue; const double Mean=P.Sum/P.Count; const double StdDev=std::sqrt(std::max(0.0,P.SumSq/P.Count-Mean*Mean)); if(StdDev<0.10) { FlatCounts[Entry.first.Family]+=P.Count; LargestFlatPatch=std::max(LargestFlatPatch,P.Count); } } bool bPass=true;uint64 Tested=0,FlatTotal=0,Total=0; for(size_t I=0;I(F.NearZero)/F.Count; const bool FamilyPass=StdDev>=0.15&&ZeroShare<0.60&&FlatCounts[I](F.Count),StdDev,ZeroShare*100.0, static_cast(FlatCounts[I]),FamilyPass?"PASS":"FAIL"); } const double FlatShare=Total?100.0*static_cast(FlatTotal)/Total:100.0; std::printf("[FlatPatchAudit] patch_width=16 vox min_patch_samples=16 flat_stddev_lt=0.10 flat_share=%.1f%% largest_flat_patch=%llu status=%s\n", FlatShare,static_cast(LargestFlatPatch), Tested>0&&FlatShare<20.0?"PASS":"FAIL"); const double PassageRms=PassageWalls.Count ?std::sqrt(PassageWalls.SumSq/PassageWalls.Count):0.0; const double OrdinaryCaveRms=OrdinaryCaveWalls.Count ?std::sqrt(OrdinaryCaveWalls.SumSq/OrdinaryCaveWalls.Count):0.0; const double PassageRmsRatio=OrdinaryCaveRms>1.0e-9 ?PassageRms/OrdinaryCaveRms:0.0; const bool bHasPassageSamples=PassageWalls.Count>0; const bool bRatioPass=PassageWalls.Count>=32&&OrdinaryCaveWalls.Count>=32 &&PassageRmsRatio>=0.75&&PassageRmsRatio<=1.25; const bool bPassageDetail=bPassageOnly?bRatioPass:(!bHasPassageSamples||bRatioPass); const char* PassageStatus=!bHasPassageSamples ?(bPassageOnly?"FAIL":"SKIP"):(bPassageDetail?"PASS":"FAIL"); std::printf("[PassageWallDetailAudit] passage_samples=%llu passage_rms=%.4f cave_samples=%llu cave_rms=%.4f ratio=%.3f range=(0.750,1.250) status=%s\n", static_cast(PassageWalls.Count),PassageRms, static_cast(OrdinaryCaveWalls.Count),OrdinaryCaveRms, PassageRmsRatio,PassageStatus); return bPassageOnly?bPassageDetail:(Tested>0&&FlatShare<20.0&&bPass&&bPassageDetail); } bool AuditGridStairIntegrity() const { uint64 CheckedEdges=0,MissingAny=0,MissingBoth=0; std::array,8> Examples{}; size_t ExampleCount=0; const auto Record=[&](uint64 EdgeMask,uint64 CandidateA,uint64 CandidateB, int32 X,int32 Y,int32 Z,int32 Axis) { CheckedEdges+=std::popcount(EdgeMask); const uint64 Missing=EdgeMask&~(CandidateA&CandidateB); uint64 AnyMissing=Missing; MissingAny+=std::popcount(Missing); MissingBoth+=std::popcount(EdgeMask&~(CandidateA|CandidateB)); while(AnyMissing&&ExampleCount(Bit),Axis}; } }; const size_t Columns=static_cast(Size[0])*Size[1]; for(size_t Line=0;Line(Line%Size[0]); const int32 Y=Min[1]+static_cast(Line/Size[0]); const size_t Base=Line*WordsPerZ; for(size_t W=0;W>63:0; const uint64 PreviousCandidates=W?CandidateWords[Word-1]>>63:0; uint64 Valid=(Size[2]-W*64)>=64?~uint64{0} :((uint64{1}<<(Size[2]-W*64))-1); if(W==0)Valid&=~uint64{1}; const uint64 ShiftedRock=(Rock<<1)|PreviousRock; const uint64 ShiftedCandidates=(Candidates<<1)|PreviousCandidates; Record((Rock^ShiftedRock)&Valid,Candidates,ShiftedCandidates, X,Y,Min[2]+static_cast(W*64),2); if(X+1(W*64),0); } if(Y+1(Size[0])*WordsPerZ; Record(Rock^RockWords[Other],Candidates,CandidateWords[Other], X,Y,Min[2]+static_cast(W*64),1); } } } std::printf("[GridStairDistanceAudit] region=(%d,%d,%d)..(%d,%d,%d) sign_edges=%llu candidate_fallback_edges=%llu both_fallback_edges=%llu status=%s\n", Min[0],Min[1],Min[2],Max[0],Max[1],Max[2], static_cast(CheckedEdges), static_cast(MissingAny), static_cast(MissingBoth),MissingAny==0?"PASS":"FAIL"); const auto CandidateAt=[&](int32 X,int32 Y,int32 Z) { if(!Contains(X,Y,Z))return false; const size_t Column=static_cast(Y-Min[1])*Size[0]+static_cast(X-Min[0]); const size_t Zi=static_cast(Z-Min[2]); return (CandidateWords[Column*WordsPerZ+(Zi>>6)]&(uint64{1}<<(Zi&63u)))!=0; }; for(size_t I=0;I(AX),static_cast(AY),static_cast(AZ)); const float BExact=AnalyticDensityAt(static_cast(BX),static_cast(BY),static_cast(BZ)); std::printf("[GridStairDistanceCandidate] a=(%d,%d,%d) b=(%d,%d,%d) axis=%d candidate=(%d,%d) solid=(%d,%d) density=(%.4f,%.4f) exact=(%.4f,%.4f) critical_air=(%d,%d)\n", AX,AY,AZ,BX,BY,BZ,Examples[I][3],CandidateAt(AX,AY,AZ),CandidateAt(BX,BY,BZ), IsRock(AX,AY,AZ),IsRock(BX,BY,BZ), AD,BD,AExact,BExact, IsInsideCriticalAir(AX,AY,AZ),IsInsideCriticalAir(BX,BY,BZ)); } return MissingAny==0; } bool AuditViewSurface(double CameraX,double CameraY,double CameraZ, float Yaw,float Pitch) const { struct FFamilyStats { uint64 Hits=0,NearZero=0,AxisLocked=0; double Sum=0.0,SumSq=0.0; }; std::array FamilyStats{}; constexpr std::array YawOffsets={-36.0f,-30.0f,-24.0f,-18.0f,-12.0f,-6.0f, 0.0f,6.0f,12.0f,18.0f,24.0f,30.0f,36.0f}; constexpr std::array PitchOffsets={-80.0f,-70.0f,-60.0f,-50.0f,-40.0f, -30.0f,-20.0f,-10.0f,0.0f,10.0f,20.0f}; const double OX=CameraX*4.0,OY=CameraY*4.0,OZ=CameraZ*4.0; auto Distance=[&](double X,double Y,double Z,ESurfaceFamily* OutFamily=nullptr, float* OutRaw=nullptr,float* OutDetail=nullptr, bool* OutFloorSurface=nullptr,float* OutPassageInfluence=nullptr, float* OutCaveInfluence=nullptr,float* OutFloorInfluence=nullptr, float* OutSurfaceSDF=nullptr) { ESurfaceFamily Family=ESurfaceFamily::Strata;float SurfaceSDF=100000.0f; bool bFloorSurface=false;float CaveInfluence=0.0f,FloorInfluence=0.0f; float PassageInfluence=0.0f; float D=EffectiveRockDistance(X,Y,Z,&Family,&SurfaceSDF, &bFloorSurface,&CaveInfluence,&FloorInfluence,nullptr,nullptr,false, &PassageInfluence); float Detail=0.0f; const bool bInside=X>=Min[0]&&X<=Max[0]&&Y>=Min[1]&&Y<=Max[1] &&Z>=Min[2]&&Z<=Max[2]; const bool bCriticalAir=IsInsideCriticalAir(X,Y,Z); if(bInside&&DetailVariant!=EDetailVariant::Smooth&&std::abs(D)<=DetailSupremum+0.125f) { const float Fade=DetailBandFadeAt(X,Y,Z,D,SurfaceSDF,CaveInfluence,FloorInfluence); if(Fade>0.0f) {Detail=DetailNoise(X,Y,Z,SurfaceSDF,CaveInfluence,FloorInfluence, PassageInfluence)*Fade;D+=Detail;} } if(bCriticalAir)D=std::min(D,-1.0f/16.0f); if(OutFamily)*OutFamily=Family; if(OutRaw)*OutRaw=D-Detail; if(OutDetail)*OutDetail=Detail; if(OutFloorSurface)*OutFloorSurface=bFloorSurface; if(OutPassageInfluence)*OutPassageInfluence=PassageInfluence; if(OutCaveInfluence)*OutCaveInfluence=CaveInfluence; if(OutFloorInfluence)*OutFloorInfluence=FloorInfluence; if(OutSurfaceSDF)*OutSurfaceSDF=SurfaceSDF; return D; }; uint64 TotalHits=0,TotalAxisLocked=0,TotalNearZero=0,FloorTaggedHits=0,FloorAxisLocked=0; uint64 FamilyTransitionPairs=0,StableFamilyPairs=0; uint64 ThinDetachedSheets=0; double ThinnestDetachedSheet=std::numeric_limits::max(); double FamilyTransitionGradientSq=0.0,StableFamilyGradientSq=0.0; for(float YawOffset:YawOffsets)for(float PitchOffset:PitchOffsets) { const double YawRad=Yaw+YawOffset*0.017453292519943295; const double PitchRad=Pitch+PitchOffset*0.017453292519943295; const double CP=std::cos(PitchRad),DX=std::sin(YawRad)*CP, DY=std::cos(YawRad)*CP,DZ=std::sin(PitchRad); double PreviousT=0.0; float PreviousD=Distance(OX,OY,OZ+0.001); bool bFound=false; double PrimaryHitT=-1.0; for(double T=0.5;T<=256.0;T+=0.5) { const double X=OX+DX*T,Y=OY+DY*T,Z=OZ+DZ*T; const float D=Distance(X,Y,Z); if(PreviousD<0.0f&&D>=0.0f) { double Lo=PreviousT,Hi=T; for(int32 It=0;It<10;++It) { const double Mid=(Lo+Hi)*0.5; if(Distance(OX+DX*Mid,OY+DY*Mid,OZ+DZ*Mid)<0.0f)Lo=Mid;else Hi=Mid; } const double HitT=(Lo+Hi)*0.5; PrimaryHitT=HitT; const double XH=OX+DX*HitT,YH=OY+DY*HitT,ZH=OZ+DZ*HitT; ESurfaceFamily Family=ESurfaceFamily::Strata;float Raw=0.0f,Detail=0.0f; bool bFloorSurface=false;float PassageInfluence=0.0f; float CaveInfluence=0.0f,FloorInfluence=0.0f,SurfaceSDF=100000.0f; Distance(XH,YH,ZH,&Family,&Raw,&Detail,&bFloorSurface,&PassageInfluence, &CaveInfluence,&FloorInfluence,&SurfaceSDF); const double GX=Distance(XH+0.5,YH,ZH)-Distance(XH-0.5,YH,ZH); const double GY=Distance(XH,YH+0.5,ZH)-Distance(XH,YH-0.5,ZH); const double GZ=Distance(XH,YH,ZH+0.5)-Distance(XH,YH,ZH-0.5); const double GL=std::sqrt(GX*GX+GY*GY+GZ*GZ); const double AxisDot=GL>1.0e-8?std::max({std::abs(GX),std::abs(GY),std::abs(GZ)})/GL:1.0; for(int32 Axis=0;Axis<3;++Axis) { double AP[3]={XH,YH,ZH},AM[3]={XH,YH,ZH}; AP[Axis]+=0.5;AM[Axis]-=0.5; ESurfaceFamily FamilyP=ESurfaceFamily::Strata,FamilyM=ESurfaceFamily::Strata; float DetailP=0.0f,DetailM=0.0f;bool bFloorP=false,bFloorM=false; Distance(AP[0],AP[1],AP[2],&FamilyP,nullptr,&DetailP,&bFloorP); Distance(AM[0],AM[1],AM[2],&FamilyM,nullptr,&DetailM,&bFloorM); const double Gradient=static_cast(DetailP-DetailM); if(FamilyP!=FamilyM||bFloorP!=bFloorM) {FamilyTransitionGradientSq+=Gradient*Gradient;++FamilyTransitionPairs;} else {StableFamilyGradientSq+=Gradient*Gradient;++StableFamilyPairs;} } constexpr double ScanRadius=16.0; constexpr int32 ScanSteps=128; double ScanT=std::max(0.0,HitT-ScanRadius); ESurfaceFamily ScanFamily=ESurfaceFamily::Strata; bool bScanFloor=false;float ScanRaw=0.0f,ScanDetail=0.0f; Distance(OX+DX*ScanT,OY+DY*ScanT,OZ+DZ*ScanT, &ScanFamily,&ScanRaw,&ScanDetail,&bScanFloor); for(int32 Step=1;Step<=ScanSteps;++Step) { const double T=std::min(HitT+ScanRadius,ScanT+0.25); ESurfaceFamily NextFamily=ESurfaceFamily::Strata; bool bNextFloor=false;float NextRaw=0.0f,NextDetail=0.0f; Distance(OX+DX*T,OY+DY*T,OZ+DZ*T, &NextFamily,&NextRaw,&NextDetail,&bNextFloor); if(std::abs(ScanRaw)<=5.0f&&std::abs(NextRaw)<=5.0f) { const double Gradient=static_cast(NextDetail-ScanDetail); if(ScanFamily!=NextFamily||bScanFloor!=bNextFloor) {FamilyTransitionGradientSq+=Gradient*Gradient;++FamilyTransitionPairs;} else {StableFamilyGradientSq+=Gradient*Gradient;++StableFamilyPairs;} } ScanT=T;ScanFamily=NextFamily;bScanFloor=bNextFloor; ScanRaw=NextRaw;ScanDetail=NextDetail; } FFamilyStats& S=FamilyStats[static_cast(Family)]; ++S.Hits;++TotalHits;S.Sum+=Detail;S.SumSq+=Detail*Detail; if(std::abs(Detail)<0.05f){++S.NearZero;++TotalNearZero;} if(AxisDot>0.985){++S.AxisLocked;++TotalAxisLocked;} if(bFloorSurface){++FloorTaggedHits;if(AxisDot>0.985)++FloorAxisLocked;} if(YawOffset==0.0f&&PitchOffset==0.0f) { const char* Name=Family==ESurfaceFamily::Strata?"strata": Family==ESurfaceFamily::Cave?"cave":"solid_overlay"; const FLayerField* Field=FindLayerField(ZH); FOwnerNoiseLineContext OwnerNoise8,OwnerNoise2; const float Owner8=Field?OwnerCaveRoughness(XH,YH,ZH,SurfaceSDF, *Field,CaveInfluence,OwnerNoise8,nullptr,8):0.0f; const float Owner2=Field?OwnerCaveRoughness(XH,YH,ZH,SurfaceSDF, *Field,CaveInfluence,OwnerNoise2,nullptr,2):0.0f; const float Relief=SurfaceRelief(static_cast(XH), static_cast(YH),static_cast(ZH), static_cast(Owner->Seed)); std::printf("[SurfaceRay] t=%.2f vox hit=(%.2f,%.2f,%.2f) family=%s floor_surface=%s cave_influence=%.3f floor_influence=%.3f passage_influence=%.3f surface_sdf=%.4f raw_sdf=%.4f detail=%.4f relief=%.4f owner8=%.4f owner2=%.4f axis_dot=%.4f\n", HitT,XH,YH,ZH,Name,bFloorSurface?"yes":"no",CaveInfluence, FloorInfluence,PassageInfluence,SurfaceSDF,Raw,Detail,Relief, Owner8,Owner2,AxisDot); } bFound=true;break; } PreviousT=T;PreviousD=D; } if(PrimaryHitT>=0.0) { constexpr double SheetStep=0.25; constexpr double MinimumAirGap=3.0; constexpr double MaximumSheetThickness=1.0; double T=PrimaryHitT+SheetStep; float Prev=Distance(OX+DX*T,OY+DY*T,OZ+DZ*T); bool bInSolid=Prev>=0.0f; double SolidStart=T,LastSolidExit=PrimaryHitT; bool bSeparatedFromPrevious=false; for(T+=SheetStep;T<=256.0;T+=SheetStep) { const float D=Distance(OX+DX*T,OY+DY*T,OZ+DZ*T); if(!bInSolid&&Prev<0.0f&&D>=0.0f) { SolidStart=T; bSeparatedFromPrevious=(T-LastSolidExit)>=MinimumAirGap; bInSolid=true; } else if(bInSolid&&Prev>=0.0f&&D<0.0f) { const double Thickness=T-SolidStart; if(bSeparatedFromPrevious&&Thickness(TotalNearZero)/TotalHits:1.0; const double AxisShare=TotalHits?static_cast(TotalAxisLocked)/TotalHits:1.0; const double FloorAxisShare=FloorTaggedHits?static_cast(FloorAxisLocked)/FloorTaggedHits:1.0; const double TransitionRMS=FamilyTransitionPairs ?std::sqrt(FamilyTransitionGradientSq/FamilyTransitionPairs):0.0; const double StableRMS=StableFamilyPairs ?std::sqrt(StableFamilyGradientSq/StableFamilyPairs):0.0; const double TransitionRatio=StableRMS>1.0e-6?TransitionRMS/StableRMS:1000000.0; double Sum=0.0,SumSq=0.0; for(const FFamilyStats& S:FamilyStats){Sum+=S.Sum;SumSq+=S.SumSq;} const double Mean=TotalHits?Sum/TotalHits:0.0; const double Variance=TotalHits?std::max(0.0,SumSq/TotalHits-Mean*Mean):0.0; const double SurfaceDetailRMS=std::sqrt(Variance); const double RelativeJoinStep=SurfaceDetailRMS>1.0e-6?TransitionRMS/SurfaceDetailRMS:1000000.0; const bool bDetailPass=TotalHits>=24&&std::sqrt(Variance)>=0.15&&NearZeroShare<0.50; constexpr double GridStairThreshold=0.40; const bool bJoinCoverage=FloorTaggedHits>=8 &&(FamilyTransitionPairs==0||(FamilyTransitionPairs>=8&&RelativeJoinStep=24&&AxisShare<0.60 &&bJoinCoverage; const bool bThinSheetPass=ThinDetachedSheets==0; std::printf("[FlatSurfaceAudit] rays=%llu detail_stddev=%.4f vox near_zero_share=%.1f%% threshold=50.0%% status=%s\n", static_cast(TotalHits),std::sqrt(Variance),NearZeroShare*100.0, bDetailPass?"PASS":"FAIL"); std::printf("[GridStairAudit] rays=%llu axis_locked_normals=%.1f%% threshold=60.0%% floor_tagged_hits=%llu floor_axis_locked=%.1f%% family_transition_pairs=%llu transition_rms=%.4f vox stable_rms=%.4f vox surface_detail_rms=%.4f vox relative_join_step=%.3f threshold=%.3f legacy_join_ratio=%.3f status=%s\n", static_cast(TotalHits),AxisShare*100.0, static_cast(FloorTaggedHits),FloorAxisShare*100.0, static_cast(FamilyTransitionPairs),TransitionRMS,StableRMS, SurfaceDetailRMS,RelativeJoinStep,GridStairThreshold,TransitionRatio,bGridPass?"PASS":"FAIL"); std::printf("[ThinSheetAudit] rays=%llu isolated_sheets=%llu thickness_lt=1.000 vox air_gap_ge=3.000 vox thinnest=%.3f vox status=%s\n", static_cast(TotalHits),static_cast(ThinDetachedSheets), ThinDetachedSheets?ThinnestDetachedSheet:0.0,bThinSheetPass?"PASS":"FAIL"); for(size_t I=0;I(S.Hits),M,V, static_cast(S.NearZero),static_cast(S.AxisLocked)); } const bool bFloorProfilePass=Stats.MaxUnmarkedFloorSlopeDelta<=0.75 &&Stats.MaxFloorProfileEndpointValueError<=0.01 &&Stats.MaxFloorProfileEndpointSlopeError<=0.001 &&Stats.FloorProfileEndpointStepControlDetected &&Stats.FloorProfileEndpointSlopeControlDetected; return bDetailPass&&bGridPass&&bThinSheetPass&&bFloorProfilePass; } private: bool NativeOnlyContainsColumn(float X,float Y) const { return std::isfinite(X)&&std::isfinite(Y) &&X>=NativeOnlyMinimum.X&&X<=NativeOnlyMaximum.X &&Y>=NativeOnlyMinimum.Y&&Y<=NativeOnlyMaximum.Y; } bool NativeOnlyContains(float X,float Y,float Z) const { return std::isfinite(X)&&std::isfinite(Y)&&std::isfinite(Z) &&X>=NativeOnlyMinimum.X&&X<=NativeOnlyMaximum.X &&Y>=NativeOnlyMinimum.Y&&Y<=NativeOnlyMaximum.Y &&Z>=NativeOnlyMinimum.Z&&Z<=NativeOnlyMaximum.Z; } static FClassificationThreadStats& ThreadClassificationStats() { static thread_local FClassificationThreadStats Stats; return Stats; } const VoxelForgeCore::WorldAssemblyStratumPlanV1* FindWorldAssemblyStratum( int32 StrateIndex,int32 ChunkZ) const { if(!WorldAssembly||StrateIndex<0)return nullptr; const auto It=std::find_if(WorldAssembly->plan.strates.begin(),WorldAssembly->plan.strates.end(), [StrateIndex,ChunkZ](const auto& Entry) {return Entry.strateIndex==StrateIndex&&ChunkZ<=Entry.topChunkZ&&ChunkZ>=Entry.bottomChunkZ;}); return It==WorldAssembly->plan.strates.end()?nullptr:&*It; } const VoxelForgeCore::WorldAssemblyFloatingIslandPlanV1* FindWorldAssemblyFloatingIslandStratum( int32 StrateIndex,int32 ChunkZ) const { if(!WorldAssembly||StrateIndex<0)return nullptr; const auto It=std::find_if(WorldAssembly->floatingIslands.begin(), WorldAssembly->floatingIslands.end(),[StrateIndex,ChunkZ](const auto& Entry) {return Entry.strateIndex==StrateIndex&&ChunkZ<=Entry.topChunkZ&&ChunkZ>=Entry.bottomChunkZ;}); return It==WorldAssembly->floatingIslands.end()?nullptr:&*It; } bool NativeSurfaceDensityAt(float X,float Y,float Z,float& OutDensity) const { if(!bUseNativeManifestFields||!WorldAssembly||!ResolvedLayout||!Owner ||!Owner->Manager||!Owner->Generator||!std::isfinite(X) ||!std::isfinite(Y)||!std::isfinite(Z))return false; const float LayoutWidth=static_cast(GetResolvedLayoutChunkWidthVoxels()); if(!(LayoutWidth>0.0f))return false; const int32 ChunkZ=FMath::FloorToInt(Z/LayoutWidth); const FBoundStratumField* Bound=FindBoundStratumForChunk(ChunkZ); if(!Bound)return false; if(Bound->Kind==FBoundStratumField::EKind::Caves) { const auto* Field=std::get_if(&Bound->Evaluator); return Field&&Field->densityAt(X,Y,Z,OutDensity); } if(Bound->Kind!=FBoundStratumField::EKind::SurfaceWorld ||!Bound->SurfacePlan||ChunkZSurfacePlan->bottomChunkZ ||ChunkZ>Bound->SurfacePlan->topChunkZ)return false; const auto* Field=std::get_if(&Bound->Evaluator); return Field&&Field->densityAt(X,Y,Z,OutDensity); } bool NativeFloatingIslandDensityAt(float X,float Y,float Z,float& OutDensity) const { if(!bUseNativeManifestFields||!WorldAssembly||!Owner||!Owner->Manager||!Owner->Generator ||!ResolvedLayout||!std::isfinite(X)||!std::isfinite(Y)||!std::isfinite(Z)) return false; // Convert Core query Z back to the owner's legacy chunk coordinate. // Convertir Z du Core vers la coordonnee de chunk legacy du proprietaire. const double LayoutChunkWidth=GetResolvedLayoutChunkWidthVoxels(); if(!(LayoutChunkWidth>0.0))return false; const int32 ChunkZ=FMath::FloorToInt(Z/static_cast(LayoutChunkWidth)); const FBoundStratumField* Bound=FindBoundStratumForChunk(ChunkZ); if(!Bound||Bound->Kind!=FBoundStratumField::EKind::FloatingIslands ||!Bound->IslandPlan||ChunkZIslandPlan->bottomChunkZ ||ChunkZ>Bound->IslandPlan->topChunkZ)return false; const int32 StrateIndex=Bound->StrateIndex; const auto* Plan=Bound->IslandPlan; const auto* FieldAdapter=std::get_if( &Bound->Evaluator); if(!FieldAdapter||!FieldAdapter->Evaluator.isValid())return false; VoxelForgeCore::floating_island_detail::FScopedIslandAttributionSample attributionSample; constexpr double Scale=static_cast(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel); constexpr double Maximum=1000000000000.0/Scale; if(std::abs(static_cast(X))>Maximum||std::abs(static_cast(Y))>Maximum ||std::abs(static_cast(Z))>Maximum)return false; const auto ToFixed=[](float Value) {return static_cast(std::llround(static_cast(Value) *VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel));}; const std::int64_t qx=ToFixed(X),qy=ToFixed(Y),qz=ToFixed(Z); std::int64_t Density=FieldAdapter->densitySubvoxelsAt(qx,qy,qz); const std::int64_t NativeDensity=Density; // The anchor-root field keeps broad lobes, but its shortest authored noise // wavelength is below one metre. Filter that detail only around a FloatingIslands // passage mouth so Marching Cubes does not expose voxel-sized chips on the crater // wall. The broad root profile is unchanged outside this local mouth envelope. double mouthFilterWeight=0.0; const double qToMetres=static_cast(ResolvedLayout->coreVoxelSizeMicrometres) /(static_cast(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel)*1000000.0); const double densityMetres=std::abs(static_cast(Density))*qToMetres; if(densityMetres<4.0) { bool hasResolvedPassageMouth=false; for(const auto& passage:ResolvedLayout->passages) { if(passage.upperStrateIndex!=StrateIndex)continue; hasResolvedPassageMouth=true; const double dx=(static_cast(qx)-passage.upperMouthSubvoxels[0])*qToMetres; const double dy=(static_cast(qy)-passage.upperMouthSubvoxels[1])*qToMetres; const double dz=(static_cast(qz)-passage.upperMouthSubvoxels[2])*qToMetres; const double distance=std::sqrt(dx*dx+dy*dy+dz*dz); constexpr double FilterCoreRadiusMetres=8.5; constexpr double FilterOuterRadiusMetres=12.5; const double fade=std::clamp((FilterOuterRadiusMetres-distance) /(FilterOuterRadiusMetres-FilterCoreRadiusMetres),0.0,1.0); const double weight=fade*fade*(3.0-2.0*fade); mouthFilterWeight=std::max(mouthFilterWeight,weight); } if(!hasResolvedPassageMouth) { // The passage-free native control retains the stratum route anchors. // Use its terminal route point as the same local mouth filter origin so // both native and composed fields receive the same root smoothing. 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()) { const VoxelForgeCore::ResolvedLayoutAnchorV2* terminal=nullptr; for(const auto& anchor:stratum->anchors) if(anchor.kind=="route_point"&&anchor.values.size()>=3) terminal=&anchor; if(terminal) { const double dx=static_cast(qx-terminal->values[0])*qToMetres; const double dy=static_cast(qy-terminal->values[1])*qToMetres; const double dz=static_cast(qz-terminal->values[2])*qToMetres; const double distance=std::sqrt(dx*dx+dy*dy+dz*dz); constexpr double FilterCoreRadiusMetres=8.5; constexpr double FilterOuterRadiusMetres=12.5; const double fade=std::clamp((FilterOuterRadiusMetres-distance) /(FilterOuterRadiusMetres-FilterCoreRadiusMetres),0.0,1.0); mouthFilterWeight=fade*fade*(3.0-2.0*fade); } } } } double routeNativeBlend=0.0; const auto mouthPassage=std::find_if(ResolvedLayout->passages.begin(), ResolvedLayout->passages.end(),[StrateIndex](const auto& passage) {return passage.upperStrateIndex==StrateIndex;}); const double queryX=static_cast(qx)*qToMetres; const double queryY=static_cast(qy)*qToMetres; const double queryZ=static_cast(qz)*qToMetres; const bool outsideCraterMouth=mouthPassage==ResolvedLayout->passages.end() ||std::hypot(queryX-mouthPassage->upperMouthSubvoxels[0]*qToMetres, queryY-mouthPassage->upperMouthSubvoxels[1]*qToMetres)>5.5; if(mouthFilterWeight>0.0&&outsideCraterMouth) { // Retain the native route only on the outer approach. The crater-mouth // interior stays with the filtered ground field and cannot become a bridge. 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()) { const VoxelForgeCore::ResolvedLayoutAnchorV2* previous=nullptr; for(const auto& anchor:stratum->anchors) { if(anchor.kind!="route_point"||anchor.values.size()<3)continue; if(previous) { const double ax=static_cast(previous->values[0])*qToMetres; const double ay=static_cast(previous->values[1])*qToMetres; const double az=static_cast(previous->values[2])*qToMetres; const double dx=(static_cast(anchor.values[0]) -previous->values[0])*qToMetres; const double dy=(static_cast(anchor.values[1]) -previous->values[1])*qToMetres; const double lengthSquared=dx*dx+dy*dy; const double t=lengthSquared>0.0?std::clamp( ((queryX-ax)*dx+(queryY-ay)*dy)/lengthSquared,0.0,1.0):0.0; const double centerX=ax+t*dx,centerY=ay+t*dy; const double centerZ=az+t*(static_cast(anchor.values[2]) -previous->values[2])*qToMetres; const double supportRadius=previous->values.size()>=4 ?static_cast(previous->values[3])*qToMetres:0.75; const double floorZ=centerZ-1.20; constexpr double WalkCoreRadiusMetres=0.45; constexpr double WearShoulderMetres=0.80; const double protectedRadius=std::min(WalkCoreRadiusMetres, supportRadius+0.10); const double routeDistance=std::hypot(queryX-centerX,queryY-centerY); const double radialT=std::clamp( (protectedRadius+WearShoulderMetres-routeDistance) /WearShoulderMetres,0.0,1.0); const double verticalT=std::clamp( (3.25-std::abs(queryZ-floorZ))/2.0,0.0,1.0); const double radialSmooth=radialT*radialT*(3.0-2.0*radialT); const double verticalSmooth=verticalT*verticalT*(3.0-2.0*verticalT); routeNativeBlend=std::max(routeNativeBlend, radialSmooth*verticalSmooth); } previous=&anchor; } } } if(mouthFilterWeight>0.0) { VoxelForgeCore::floating_island_detail::FScopedIslandAttributionTimer mouthFilterTimer( VoxelForgeCore::floating_island_detail::EIslandAttributionBucket::MouthFilter); constexpr std::int64_t FilterStep=8*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel; std::int64_t weightedSum=0; VoxelForgeCore::floating_island_detail::FScopedIslandFieldCaller passageFilterCaller( VoxelForgeCore::floating_island_detail::EIslandFieldCaller::PassageFilter); for(int dz=-1;dz<=1;++dz) for(int dy=-1;dy<=1;++dy) for(int dx=-1;dx<=1;++dx) { const std::int64_t weight=(dx==0?2:1)*(dy==0?2:1)*(dz==0?2:1); const std::int64_t sample=(dx==0&&dy==0&&dz==0)?Density: FieldAdapter->densitySubvoxelsAt(qx+dx*FilterStep,qy+dy*FilterStep, qz+dz*FilterStep); weightedSum+=sample*weight; } // A separable [1,2,1]^3 kernel over a 2 m footprint filters sub-voxel // root detail while retaining the signed field on both sides of the surface. const double filtered=static_cast(weightedSum)/64.0; std::int64_t filteredDensity=static_cast(std::llround( std::lerp(static_cast(Density),filtered,mouthFilterWeight))); // Filtering detail at an open mouth may soften the surface, but it cannot // turn native air into a new solid shelf over the crater. if(NativeDensity>=0&&filteredDensity<0)filteredDensity=0; Density=filteredDensity; if(routeNativeBlend>0.0&&NativeDensity<0) { const double routePreserved=std::lerp(static_cast(Density), static_cast(std::min(Density,NativeDensity)), routeNativeBlend*0.60); Density=static_cast(std::llround(routePreserved)); } // Marching Cubes can retain an isolated solid with no two-metre axis // thickness as a handful of disconnected triangles. Remove only shallow // negative samples surrounded by air at a one-metre probe distance; a // walk floor or wall has solid material on at least one side of this test. constexpr double FragmentProbeMetres=1.0; const std::int64_t fragmentStep=static_cast(std::llround( FragmentProbeMetres/qToMetres)); if(mouthFilterWeight>=0.25&&Density<0 &&static_cast(-Density)*qToMetres::max(); VoxelForgeCore::floating_island_detail::FScopedIslandFieldCaller passageFilterCaller( VoxelForgeCore::floating_island_detail::EIslandFieldCaller::PassageFilter); for(std::size_t axis=0;axis<3&&surroundedByAir;++axis) for(const int sign:{-1,1}) { std::array sample{qx,qy,qz}; sample[axis]+=sign*fragmentStep; const std::int64_t neighbor=FieldAdapter->densitySubvoxelsAt( sample[0],sample[1],sample[2]); if(neighbor<=0) { surroundedByAir=false; break; } minimumAirDensity=std::min(minimumAirDensity,neighbor); } if(surroundedByAir)Density=std::max(Density,minimumAirDensity); } } VoxelForgeCore::floating_island_detail::FScopedIslandAttributionTimer boundaryTimer( VoxelForgeCore::floating_island_detail::EIslandAttributionBucket::Boundary); const std::int64_t Bottom=Plan->manifest.recipe.layoutMinimumZSubvoxels; const std::int64_t Top=Plan->manifest.recipe.layoutMaximumZSubvoxels; const std::int64_t Seal=Plan->manifest.recipe.boundarySealSubvoxels; const std::int64_t Zq=ToFixed(Z); if(Plan->manifest.recipe.geometryVersion<3) { if(Zq>=Bottom&&Zq(1,Zq-Bottom)); if(Zq<=Top&&Zq>Top-Seal) Density=std::min(Density,-std::max(1,Top-Zq)); OutDensity=static_cast(static_cast(Density)/Scale); } else if(Plan->manifest.recipe.geometryVersion<4) { OutDensity=static_cast(static_cast(Density)/Scale); FResolvedStratumBounds Bounds; if(!MakeResolvedStratumBounds(static_cast(Bottom)/Scale, static_cast(Top)/Scale,static_cast(Seal)/Scale,Bounds)) return false; const FFloatingIslandParams* Params=FieldAdapter->legacyBoundaryParamsAt(ChunkZ); if(!Params)return false; float InternalDensity=-OutDensity; VF_ApplyUpperBoundarySeal(InternalDensity,Z,static_cast(Bounds.TopZ), static_cast(Bounds.SealThickness),Params->BaseDensity); OutDensity=-InternalDensity; } else OutDensity=static_cast(static_cast(Density)/Scale); return std::isfinite(OutDensity); } bool UsesLegacySurfaceField(float X,float Y,float Z) const { const FBoundStratumField* Bound=FindBoundStratumForZ(Z); return Bound&&Bound->Kind==FBoundStratumField::EKind::SurfaceWorld; } bool LegacyGeneratorDensityAt(float X,float Y,float Z,float& OutDensity) const { const FBoundStratumField* Bound=FindBoundStratumForZ(Z); const auto* Legacy=Bound?std::get_if( &Bound->Evaluator):nullptr; return Legacy&&Legacy->densityAt(X,Y,Z,OutDensity); } float LegacySurfaceDensityAt(float X,float Y,float Z) const { // SurfaceWorld consumes biome overrides, terrain ops, water level and its sky cap in // the legacy stack. Reuse that immutable evaluator as an exact temporary bridge. // SurfaceWorld lit biomes, ops de terrain, niveau d'eau et plafond; reutiliser ce champ. float density=0.0f; if(!LegacyGeneratorDensityAt(X,Y,Z,density)) density=Owner->Generator->GetDensityAt(X,Y,Z); density=ApplyResolvedStrateComposition(density,X,Y,Z); return ApplyEditBrushes(density,X,Y,Z); } float ApplyEditBrushes(float Density,double X,double Y,double Z) const { VoxelForgeCore::Linux77Profiler::FScopedPhase l77EditPhase( VoxelForgeCore::Linux77Profiler::EPhase::Edit); if(EditBrushes.empty()||XEditBoundsMax.X ||YEditBoundsMax.Y||ZEditBoundsMax.Z) return Density; const double P[3]={X,Y,Z}; for(const FEditBrush& Brush:EditBrushes) { double Distance=0.0; if(Brush.Shape==FEditBrush::EShape::Sphere) { const double DX=X-Brush.A[0],DY=Y-Brush.A[1],DZ=Z-Brush.A[2]; Distance=DeterministicSqrt(DX*DX+DY*DY+DZ*DZ)-Brush.Radius; } else if(Brush.Shape==FEditBrush::EShape::Capsule) { double AB[3]{},AP[3]{},LengthSq=0.0,Dot=0.0; for(int32 Axis=0;Axis<3;++Axis) {AB[Axis]=Brush.B[Axis]-Brush.A[Axis];AP[Axis]=P[Axis]-Brush.A[Axis];LengthSq+=AB[Axis]*AB[Axis];Dot+=AB[Axis]*AP[Axis];} const double T=LengthSq>1.0e-12?std::clamp(Dot/LengthSq,0.0,1.0):0.0; double D2=0.0; for(int32 Axis=0;Axis<3;++Axis) {const double D=AP[Axis]-T*AB[Axis];D2+=D*D;} Distance=DeterministicSqrt(D2)-Brush.Radius; } else { double OutsideSq=0.0,MaxQ=-std::numeric_limits::infinity(); for(int32 Axis=0;Axis<3;++Axis) { const double Q=std::abs(P[Axis]-Brush.A[Axis])-Brush.HalfExtent[Axis]; const double Outside=std::max(Q,0.0); OutsideSq+=Outside*Outside;MaxQ=std::max(MaxQ,Q); } Distance=DeterministicSqrt(OutsideSq)+std::min(MaxQ,0.0)-Brush.Rounding; } if(Brush.Operation==FEditBrush::EOperation::DensityOffset) { double Influence=0.0; if(Brush.Shape==FEditBrush::EShape::Sphere) { const double Radius=std::max(Brush.Radius,1.0e-12); const double T=std::clamp((Distance+Brush.Radius)/Radius,0.0,1.0); if(Distance+Brush.Radius>=Brush.Radius)continue; Influence=1.0-T*T*(3.0-2.0*T); } else { const double Band=std::max(static_cast(Brush.Falloff),0.01); if(Distance>=Band)continue; const double T=std::clamp(std::max(Distance,0.0)/Band,0.0,1.0); Influence=1.0-T*T*(3.0-2.0*T); } Density+=Brush.Strength*static_cast(Influence); continue; } if(Brush.Operation==FEditBrush::EOperation::Carve) Density=std::max(Density,static_cast(-Distance)); else Density=std::min(Density,static_cast(Distance)); } return Density; } struct FAxisBins { int32 MinA=0,MinB=0,CountA=0,CountB=0; std::vector> Items; }; struct FLayerField { int32 Lo=0,Hi=-1; int32 StrateIndex=INDEX_NONE; FStrateGenerationParams Params; }; enum class ESurfaceFamily : uint8 { Strata, Cave, SolidOverlay }; static bool NearInteger(float V,int32& Out) { const float R=std::round(V); if(std::abs(V-R)>0.0001f||R(MIN_int32)||R>static_cast(MAX_int32))return false; Out=static_cast(R);return true; } static double DeterministicSqrt(double Value) { if(!(Value>0.0))return 0.0; return _mm_cvtsd_f64(_mm_sqrt_sd(_mm_setzero_pd(),_mm_set_sd(Value))); } bool Contains(int32 X,int32 Y,int32 Z) const {return X>=Min[0]&&X<=Max[0]&&Y>=Min[1]&&Y<=Max[1]&&Z>=Min[2]&&Z<=Max[2];} static int32 FloorDiv(int32 A,int32 B) {int32 Q=A/B,R=A%B;return Q-((R!=0&&R<0)?1:0);} static int64 RoundDiv(int64 N,int64 D) {return N>=0?(N+D/2)/D:-((-N+D/2)/D);} static float SmoothMinDistance(float A,float B,float Width) { FScopedLegacySmoothMinTimer SmoothMinTimer; if(Width<=0.0f) { return std::min(A,B); } const float H=std::max(Width-std::abs(A-B),0.0f)/Width; return std::min(A,B)-(H*H*H)*(Width/6.0f); } static float SmoothMaxDistance(float A,float B,float Width) {return -SmoothMinDistance(-A,-B,Width);} static std::uint64_t StampMix64(std::uint64_t value) { value^=value>>30;value*=0xbf58476d1ce4e5b9ull; value^=value>>27;value*=0x94d049bb133111ebull; return value^(value>>31); } static float StampLatticeValue(std::uint64_t seed,std::int64_t x, std::int64_t y,std::int64_t z) { std::uint64_t hash=StampMix64(seed^StampMix64(static_cast(x))); hash=StampMix64(hash^StampMix64(static_cast(y))); hash=StampMix64(hash^StampMix64(static_cast(z))); const double unit=static_cast(hash>>11) /static_cast((std::uint64_t{1}<<53)-1); return static_cast(unit*2.0-1.0); } static float StampValueNoise(float x,float y,float z,float wavelength, std::uint64_t seed) { if(!(wavelength>0.0f))return 0.0f; const double sx=static_cast(x)/wavelength; const double sy=static_cast(y)/wavelength; const double sz=static_cast(z)/wavelength; const std::int64_t ix=static_cast(std::floor(sx)); const std::int64_t iy=static_cast(std::floor(sy)); const std::int64_t iz=static_cast(std::floor(sz)); const auto smooth=[](float t){return t*t*(3.0f-2.0f*t);}; const float tx=smooth(static_cast(sx-ix)); const float ty=smooth(static_cast(sy-iy)); const float tz=smooth(static_cast(sz-iz)); const auto lerp=[](float a,float b,float t){return a+(b-a)*t;}; const float z0=lerp(lerp(StampLatticeValue(seed,ix,iy,iz), StampLatticeValue(seed,ix+1,iy,iz),tx), lerp(StampLatticeValue(seed,ix,iy+1,iz), StampLatticeValue(seed,ix+1,iy+1,iz),tx),ty); const float z1=lerp(lerp(StampLatticeValue(seed,ix,iy,iz+1), StampLatticeValue(seed,ix+1,iy,iz+1),tx), lerp(StampLatticeValue(seed,ix,iy+1,iz+1), StampLatticeValue(seed,ix+1,iy+1,iz+1),tx),ty); return lerp(z0,z1,tz); } static float StampEllipsoidSdf(float x,float y,float z, const std::array& radius) { const float minimum=std::min({radius[0],radius[1],radius[2]}); if(!(minimum>0.0f))return std::numeric_limits::infinity(); const float k0=std::sqrt((x/radius[0])*(x/radius[0]) +(y/radius[1])*(y/radius[1])+(z/radius[2])*(z/radius[2])); const float k1=std::sqrt((x/(radius[0]*radius[0]))*(x/(radius[0]*radius[0])) +(y/(radius[1]*radius[1]))*(y/(radius[1]*radius[1])) +(z/(radius[2]*radius[2]))*(z/(radius[2]*radius[2]))); return k1>1.0e-8f?k0*(k0-1.0f)/k1:-minimum; } static float StampRoundedBoxSdf(float x,float y,float z, const std::array& halfSize,float rounding) { const float qx=std::abs(x)-halfSize[0]+rounding; const float qy=std::abs(y)-halfSize[1]+rounding; const float qz=std::abs(z)-halfSize[2]+rounding; const float outside=std::sqrt(std::max(qx,0.0f)*std::max(qx,0.0f) +std::max(qy,0.0f)*std::max(qy,0.0f) +std::max(qz,0.0f)*std::max(qz,0.0f)); return outside+std::min(std::max({qx,qy,qz}),0.0f)-rounding; } static float StampPillarSdf(float x,float y,float z, const std::array& halfSize) { const float radial=(std::sqrt((x/halfSize[0])*(x/halfSize[0]) +(y/halfSize[1])*(y/halfSize[1]))-1.0f) *std::min(halfSize[0],halfSize[1]); const float vertical=std::abs(z)-halfSize[2]; const float outside=std::sqrt(std::max(radial,0.0f)*std::max(radial,0.0f) +std::max(vertical,0.0f)*std::max(vertical,0.0f)); return outside+std::min(std::max(radial,vertical),0.0f); } static float StampRoundedCappedCylinderSdf(float x,float y,float z, const std::array& halfSize,float rounding) { const float radius=halfSize[0]; if(!(radius>0.0f&&halfSize[0]==halfSize[1]&&halfSize[2]>0.0f) ||rounding<0.0f||rounding>std::min(radius,halfSize[2])) return std::numeric_limits::infinity(); const float radial=std::sqrt(x*x+y*y)-(radius-rounding); const float vertical=std::abs(z)-(halfSize[2]-rounding); const float outside=std::sqrt(std::max(radial,0.0f)*std::max(radial,0.0f) +std::max(vertical,0.0f)*std::max(vertical,0.0f)); return outside+std::min(std::max(radial,vertical),0.0f)-rounding; } static float StampArchSdf(float x,float y,float z, const std::array& halfSize) { const float round=std::min(0.25f,std::min(halfSize[0],halfSize[1])*0.08f); const float lower=StampRoundedBoxSdf(x,y,z+halfSize[2]*0.5f, {halfSize[0],halfSize[1],halfSize[2]*0.5f},round); const float top=std::max(StampEllipsoidSdf(x,y,z,halfSize),-z); return SmoothMinDistance(lower,top,round*2.0f); } float StampSdf(const FStampRuntime& stamp,float x,float y,float z) const { float localX=x-stamp.positionVoxels[0]; float localY=y-stamp.positionVoxels[1]; const float localZ=z-stamp.positionVoxels[2]; switch(stamp.quarterTurns&3u) { case 1:{const float oldX=localX;localX=localY;localY=-oldX;break;} case 2:localX=-localX;localY=-localY;break; case 3:{const float oldX=localX;localX=-localY;localY=oldX;break;} default:break; } float distance=0.0f; switch(stamp.record->kind) { case VoxelForgeCore::ResolvedSdfStampKindV1::carveEllipsoid: distance=StampEllipsoidSdf(localX,localY,localZ,stamp.halfExtentsVoxels);break; case VoxelForgeCore::ResolvedSdfStampKindV1::carveArch: distance=StampArchSdf(localX,localY,localZ,stamp.halfExtentsVoxels);break; case VoxelForgeCore::ResolvedSdfStampKindV1::fillPillar: distance=StampPillarSdf(localX,localY,localZ,stamp.halfExtentsVoxels);break; case VoxelForgeCore::ResolvedSdfStampKindV1::fillMonolith: distance=StampRoundedCappedCylinderSdf(localX,localY,localZ, stamp.halfExtentsVoxels,stamp.roundingRadiusVoxels);break; case VoxelForgeCore::ResolvedSdfStampKindV1::fillPlatform: distance=StampRoundedBoxSdf(localX,localY,localZ, stamp.halfExtentsVoxels,stamp.roundingRadiusVoxels);break; case VoxelForgeCore::ResolvedSdfStampKindV1::fillDais: if(ResolvedLayout&&ResolvedLayout->stampVocabularyVersion >=VoxelForgeCore::ResolvedLayoutStampVocabularyVersionV2) distance=StampEllipsoidSdf(localX,localY,localZ,stamp.halfExtentsVoxels); else distance=StampRoundedBoxSdf(localX,localY,localZ,stamp.halfExtentsVoxels, std::min(0.35f,std::min({stamp.halfExtentsVoxels[0], stamp.halfExtentsVoxels[1],stamp.halfExtentsVoxels[2]})*0.15f)); break; default:return std::numeric_limits::infinity(); } if(stamp.irregularityAmplitudeVoxels>0.0f) distance+=StampValueNoise(localX,localY,localZ, stamp.irregularityWavelengthVoxels,stamp.irregularitySeed) *stamp.irregularityAmplitudeVoxels; return distance; } static double StampAbsMinimum(double minimum,double maximum) {return minimum<=0.0&&maximum>=0.0?0.0:std::min(std::abs(minimum),std::abs(maximum));} static double StampRoundedBoxSdfDouble(const std::array& absolute, const std::array& halfSize,double rounding) { const double qx=absolute[0]-halfSize[0]+rounding; const double qy=absolute[1]-halfSize[1]+rounding; const double qz=absolute[2]-halfSize[2]+rounding; const double outside=std::sqrt(std::max(qx,0.0)*std::max(qx,0.0) +std::max(qy,0.0)*std::max(qy,0.0) +std::max(qz,0.0)*std::max(qz,0.0)); return outside+std::min(std::max({qx,qy,qz}),0.0)-rounding; } FMetricInterval StampDistanceInterval(const FStampRuntime& stamp, const int32 bMin[3],const int32 bMax[3]) const { if(!stamp.record||(stamp.record->kind !=VoxelForgeCore::ResolvedSdfStampKindV1::fillMonolith &&stamp.record->kind!=VoxelForgeCore::ResolvedSdfStampKindV1::fillPlatform)) return {0.0,0.0,false}; std::array localMinimum{},localMaximum{}; const std::array center={stamp.positionVoxels[0], stamp.positionVoxels[1],stamp.positionVoxels[2]}; const auto axisRange=[&](std::size_t axis) { return std::array{static_cast(bMin[axis]), static_cast(bMax[axis])}; }; const auto xRange=axisRange(0),yRange=axisRange(1),zRange=axisRange(2); switch(stamp.quarterTurns&3u) { case 1: localMinimum={yRange[0]-center[1],center[0]-xRange[1],zRange[0]-center[2]}; localMaximum={yRange[1]-center[1],center[0]-xRange[0],zRange[1]-center[2]}; break; case 2: localMinimum={center[0]-xRange[1],center[1]-yRange[1],zRange[0]-center[2]}; localMaximum={center[0]-xRange[0],center[1]-yRange[0],zRange[1]-center[2]}; break; case 3: localMinimum={center[1]-yRange[1],xRange[0]-center[0],zRange[0]-center[2]}; localMaximum={center[1]-yRange[0],xRange[1]-center[0],zRange[1]-center[2]}; break; default: localMinimum={xRange[0]-center[0],yRange[0]-center[1],zRange[0]-center[2]}; localMaximum={xRange[1]-center[0],yRange[1]-center[1],zRange[1]-center[2]}; break; } std::array absoluteMinimum{},absoluteMaximum{}; for(std::size_t axis=0;axis<3;++axis) { absoluteMinimum[axis]=StampAbsMinimum(localMinimum[axis],localMaximum[axis]); absoluteMaximum[axis]=std::max(std::abs(localMinimum[axis]), std::abs(localMaximum[axis])); } double minimum=0.0,maximum=0.0; if(stamp.record->kind==VoxelForgeCore::ResolvedSdfStampKindV1::fillMonolith) { const double radialMinimum=std::hypot(absoluteMinimum[0],absoluteMinimum[1]); const double radialMaximum=std::hypot(absoluteMaximum[0],absoluteMaximum[1]); const double round=stamp.roundingRadiusVoxels; const double side=stamp.halfExtentsVoxels[0]-round; const double cap=stamp.halfExtentsVoxels[2]-round; const auto eval=[&](double radial,double vertical) { const double qr=radial-side,qz=vertical-cap; return std::hypot(std::max(qr,0.0),std::max(qz,0.0)) +std::min(std::max(qr,qz),0.0)-round; }; minimum=eval(radialMinimum,absoluteMinimum[2]); maximum=eval(radialMaximum,absoluteMaximum[2]); } else if(stamp.record->kind==VoxelForgeCore::ResolvedSdfStampKindV1::fillPlatform) { const std::array half={stamp.halfExtentsVoxels[0], stamp.halfExtentsVoxels[1],stamp.halfExtentsVoxels[2]}; minimum=StampRoundedBoxSdfDouble(absoluteMinimum,half,stamp.roundingRadiusVoxels); maximum=StampRoundedBoxSdfDouble(absoluteMaximum,half,stamp.roundingRadiusVoxels); } else return {0.0,0.0,false}; constexpr double outwardPadding=2.0e-5; return {std::nextafter(minimum-outwardPadding, -std::numeric_limits::infinity()), std::nextafter(maximum+outwardPadding, std::numeric_limits::infinity()),true}; } float ApplyResolvedSdfStamps(float density,float X,float Y,float Z) const { if(StampRuntime.empty()||!Owner||Owner->CoreVoxelSizeMicrons==0)return density; const auto* tileQueries=ActivePassageTileQueries; const bool scoped=tileQueries&&tileQueries->matches(*this); if(scoped&&!tileQueries->hasStampCandidates())return density; const auto* bound=FindBoundStratumForManagerZ(Z); if(!bound)return density; const double micronsPerVoxel=Owner->CoreVoxelSizeMicrons; const std::array worldMicrometres={X*micronsPerVoxel, Y*micronsPerVoxel,Z*micronsPerVoxel}; float composed=density; const auto apply=[&](std::uint32_t index) { if(index>=StampRuntime.size())return; const auto& runtime=StampRuntime[index];const auto& record=*runtime.record; if(record.strateIndex!=bound->StrateIndex)return; for(std::size_t axis=0;axis<3;++axis) if(worldMicrometres[axis]record.boundsMaximumMicrometres[axis])return; const float distance=StampSdf(runtime,X,Y,Z); if(record.kind==VoxelForgeCore::ResolvedSdfStampKindV1::carveEllipsoid ||record.kind==VoxelForgeCore::ResolvedSdfStampKindV1::carveArch) composed=SmoothMaxDistance(composed,-distance,runtime.smoothWidthVoxels); else composed=SmoothMinDistance(composed,distance,runtime.smoothWidthVoxels); }; if(scoped) for(const auto index:tileQueries->stampCandidates())apply(index); else for(std::size_t index=0;index(index)); return composed; } float ApplyResolvedCavernGraph(float density,float X,float Y,float Z, float* nativeFloorSupport=nullptr) const { if(nativeFloorSupport) *nativeFloorSupport=std::numeric_limits::lowest(); if(CavernRuntime.empty()||!Owner||Owner->CoreVoxelSizeMicrons==0)return density; const auto* tileQueries=ActivePassageTileQueries; const bool scoped=tileQueries&&tileQueries->matches(*this); if(scoped&&!tileQueries->hasCavernCandidates())return density; const auto* bound=FindBoundStratumForManagerZ(Z); if(!bound)return density; if(bound->Kind==FBoundStratumField::EKind::Caves &&bound->Resolved&&VoxelForgeCore::HasNativeGraphField(*bound->Resolved) &&(VoxelForgeCore::IsNativeCaveFieldStratum(*bound->Resolved) ?bound->Resolved->passageCapabilities.hasNativeCaveField :bound->Resolved->passageCapabilities.hasNativeLatticeField)) return ApplyResolvedNativeCavesField(density,X,Y,Z,*bound,tileQueries,scoped, nativeFloorSupport); const double micronsPerVoxel=Owner->CoreVoxelSizeMicrons; const std::array worldMicrometres={X*micronsPerVoxel, Y*micronsPerVoxel,Z*micronsPerVoxel}; float composed=density; const auto apply=[&](std::uint32_t index) { if(index>=CavernRuntime.size())return; const auto& feature=CavernRuntime[index]; const std::int32_t strateIndex=feature.room?feature.room->strateIndex :feature.edge?feature.edge->strateIndex:-1; if(strateIndex!=bound->StrateIndex)return; for(std::size_t axis=0;axis<3;++axis) if(worldMicrometres[axis]feature.boundsMaximumMicrometres[axis])return; if(feature.room) { const float localX=X-feature.centerVoxels[0]; const float localY=Y-feature.centerVoxels[1]; const float localZ=Z-feature.centerVoxels[2]; if(Z0.0f) { const float fade=SmoothUnit((Z-feature.floorVoxels)/0.8f); distance+=StampValueNoise(localX,localY,localZ, feature.irregularityWavelengthVoxels,feature.irregularitySeed) *feature.irregularityAmplitudeVoxels*fade; } composed=SmoothMaxDistance(composed,-distance,feature.smoothWidthVoxels); const float horizontal=std::sqrt( (localX*localX)/(feature.halfExtentsVoxels[0] *feature.halfExtentsVoxels[0]) +(localY*localY)/(feature.halfExtentsVoxels[1] *feature.halfExtentsVoxels[1])); if(horizontal<=1.0f) composed=std::min(composed,Z-feature.floorVoxels); return; } if(!feature.edge)return; const auto bezier=[&](float t,std::size_t axis) { const float one=1.0f-t; return one*one*one*feature.controlPointsVoxels[0][axis] +3.0f*one*one*t*feature.controlPointsVoxels[1][axis] +3.0f*one*t*t*feature.controlPointsVoxels[2][axis] +t*t*t*feature.controlPointsVoxels[3][axis]; }; const auto derivative=[&](float t,std::size_t axis) { const float one=1.0f-t; return 3.0f*one*one*(feature.controlPointsVoxels[1][axis] -feature.controlPointsVoxels[0][axis]) +6.0f*one*t*(feature.controlPointsVoxels[2][axis] -feature.controlPointsVoxels[1][axis]) +3.0f*t*t*(feature.controlPointsVoxels[3][axis] -feature.controlPointsVoxels[2][axis]); }; const auto secondDerivative=[&](float t,std::size_t axis) { return 6.0f*(1.0f-t)*(feature.controlPointsVoxels[2][axis] -2.0f*feature.controlPointsVoxels[1][axis] +feature.controlPointsVoxels[0][axis]) +6.0f*t*(feature.controlPointsVoxels[3][axis] -2.0f*feature.controlPointsVoxels[2][axis] +feature.controlPointsVoxels[1][axis]); }; float t=0.0f,bestDistanceSquared=std::numeric_limits::infinity(); for(int sample=0;sample<=16;++sample) { const float candidate=static_cast(sample)/16.0f; const float dx=X-bezier(candidate,0),dy=Y-bezier(candidate,1); const float distanceSquared=dx*dx+dy*dy; if(distanceSquared1.0e-5f ?derivative(t,2)/floorHorizontalSpeed:0.0f; const float floorDistanceScale=1.0f/std::sqrt(1.0f+floorGrade*floorGrade); const float radial=std::hypot(X-centerX,Y-centerY); const float radius=feature.boreRadiusVoxels; const float vertical=(Z-floorZ)/feature.boreHeightVoxels; const float normalized=std::sqrt((radial/radius)*(radial/radius) +vertical*vertical); float distance=(normalized-1.0f)*std::min(radius,feature.boreHeightVoxels); if(feature.irregularityAmplitudeVoxels>0.0f) { const float fade=SmoothUnit((Z-floorZ)/0.8f); distance+=StampValueNoise(X-feature.controlPointsVoxels[0][0], Y-feature.controlPointsVoxels[0][1],Z-floorZ, feature.irregularityWavelengthVoxels,feature.irregularitySeed) *feature.irregularityAmplitudeVoxels*fade; } if(Z0.0f&&feature.floorSupportDepthVoxels>0.0f &&Z>=floorZ-feature.floorSupportDepthVoxels &&radialcavernFeatureCandidates())apply(index); else for(std::size_t index=0;index(index)); return composed; } static double SmoothMinMetres(double a,double b,double width) { if(!(width>0.0))return std::min(a,b); const double h=std::max(width-std::abs(a-b),0.0)/width; return std::min(a,b)-(h*h*h)*(width/6.0); } static double SmoothMaxMetres(double a,double b,double width) {return -SmoothMinMetres(-a,-b,width);} float ApplyResolvedNativeCavesField(float density,float X,float Y,float Z, const FBoundStratumField& bound,const FScopedPassageTileQueries* tileQueries, bool scoped,float* nativeFloorSupport=nullptr) const { if(nativeFloorSupport) *nativeFloorSupport=std::numeric_limits::lowest(); (void)density; const double metresPerVoxel=Owner->CoreVoxelSizeMicrons/1000000.0; const std::array point={X*metresPerVoxel,Y*metresPerVoxel, Z*metresPerVoxel}; std::vector localCandidates; const std::vector* candidates=nullptr; if(scoped)candidates=&tileQueries->cavernFeatureCandidates(); else { constexpr double Q=VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel; const std::array coordinate={ static_cast(std::llround(X*Q)), static_cast(std::llround(Y*Q)), static_cast(std::llround(Z*Q))}; localCandidates=BuildCavernFeatureTileList(coordinate,coordinate); candidates=&localCandidates; } double caveVoid=std::numeric_limits::infinity(); double latticeFloorSupportMinimum=std::numeric_limits::infinity(); const bool nativeLattice=bound.Resolved &&VoxelForgeCore::IsNativeLatticeFieldStratum(*bound.Resolved) &&bound.Resolved->passageCapabilities.hasNativeLatticeField; for(const std::uint32_t index:*candidates) { if(index>=CavernRuntime.size())continue; const auto& feature=CavernRuntime[index]; const std::int32_t strateIndex=feature.room?feature.room->strateIndex :feature.edge?feature.edge->strateIndex:-1; if(strateIndex!=bound.StrateIndex)continue; double featureVoid=std::numeric_limits::infinity(); if(feature.room) { double normalizedSquared=0.0,meanRadius=0.0; for(std::size_t axis=0;axis<3;++axis) { const double radius=feature.halfExtentsMetres[axis]; if(!(radius>0.0)){normalizedSquared=std::numeric_limits::infinity();break;} const double delta=(point[axis]-feature.centerMetres[axis])/radius; normalizedSquared+=delta*delta;meanRadius+=radius/3.0; } const double ellipsoid=(std::sqrt(normalizedSquared)-1.0)*meanRadius; const double floor=feature.room->floorMicrometres/1000000.0-point[2]; const double width=feature.room->smoothWidthMicrometres/1000000.0; featureVoid=SmoothMaxMetres(ellipsoid,floor,width); } else if(feature.edge&&!feature.segments.empty()) { double tube=std::numeric_limits::infinity(); double nearestFloor=0.0,nearestGrade=0.0,nearestRadial=0.0; const double radius=feature.edge->boreRadiusMicrometres/1000000.0; const double height=feature.edge->boreHeightMicrometres/1000000.0; constexpr double SegmentDistanceTieToleranceMetres=1.0e-12; if(!(radius>0.0&&height>0.0))continue; const double minScale=std::min(radius,height); std::uint32_t nearestSegment=std::numeric_limits::max(); const auto evaluateSegment=[&](std::uint32_t segmentIndex) { if(segmentIndex>=feature.segments.size())return; const auto& segment=feature.segments[segmentIndex]; double lengthSquared=0.0,dot=0.0; for(std::size_t axis=0;axis<3;++axis) { const double delta=segment.end[axis]-segment.start[axis]; lengthSquared+=delta*delta; dot+=(point[axis]-segment.start[axis])*delta; } const double t=lengthSquared>0.0 ?std::clamp(dot/lengthSquared,0.0,1.0):0.0; double radialSquared=0.0,vertical=0.0; for(std::size_t axis=0;axis<3;++axis) { const double delta=point[axis]-(segment.start[axis] +t*(segment.end[axis]-segment.start[axis])); if(axis<2)radialSquared+=delta*delta; else vertical=delta; } const double normalized=std::sqrt(radialSquared/(radius*radius) +vertical*vertical/(height*height)); const double distance=(normalized-1.0)*std::min(radius,height); const bool clearlyCloser=distance1.0e-9 ?(segment.end[2]-segment.start[2])/horizontal:0.0; } }; const auto nodeLowerBound=[&](const FCavernRuntimeFeature::FSegmentNode& node) { double gapSquared=0.0; for(std::size_t axis=0;axis<3;++axis) { const double scale=axis==2?height:radius; const double q=point[axis]/scale; const double low=node.minimum[axis]/scale; const double high=node.maximum[axis]/scale; const double gap=qhigh?q-high:0.0; gapSquared+=gap*gap; } return (std::sqrt(gapSquared)-1.0)*minScale; }; if(!feature.segmentNodes.empty()) { const auto visitNode=[&](const auto& self,int nodeIndex)->void { if(nodeIndex<0||static_cast(nodeIndex)>=feature.segmentNodes.size())return; const auto& node=feature.segmentNodes[static_cast(nodeIndex)]; if(nodeLowerBound(node)>tube+SegmentDistanceTieToleranceMetres)return; if(node.left<0) { for(std::size_t offset=node.first;offset(node.left)]); const double rightBound=nodeLowerBound(feature.segmentNodes[ static_cast(node.right)]); if(leftBound<=rightBound) {self(self,node.left);self(self,node.right);} else {self(self,node.right);self(self,node.left);} }; visitNode(visitNode,0); } else for(std::uint32_t segment=0;segmentfloorSupportDepthMicrometres/1000000.0; if(nativeLattice&&supportDepth>0.0&&nearestRadialsmoothWidthMicrometres/1000000.0; featureVoid=SmoothMaxMetres(tube,floor,width); } if(!std::isfinite(featureVoid))continue; if(!std::isfinite(caveVoid))caveVoid=featureVoid; else caveVoid=SmoothMinMetres(caveVoid,featureVoid, feature.room ?(feature.room->unionWidthMicrometres>0 ?feature.room->unionWidthMicrometres :feature.room->smoothWidthMicrometres)/1000000.0 :(feature.edge->unionWidthMicrometres>0 ?feature.edge->unionWidthMicrometres :feature.edge->smoothWidthMicrometres)/1000000.0); } // A versioned native caves recipe owns a solid host-rock field. The // resolved interval oracle uses the same finite -32 m solid value; using // the underlying legacy generator density here would leak its unrelated // terrain detail into the cave floor and invalidate those proofs. constexpr double HostRockMetres=-32.0; const double hostDistance=HostRockMetres; double composed=std::isfinite(caveVoid) ?SmoothMaxMetres(hostDistance,-caveVoid,0.0):hostDistance; // Keep the floor support owned by the lattice route after smooth-unioning // neighboring room and corridor voids. This cap is limited to each record's // explicit support depth so separated helix turns remain independent. if(std::isfinite(latticeFloorSupportMinimum)) composed=std::min(composed,latticeFloorSupportMinimum); if(nativeFloorSupport&&std::isfinite(latticeFloorSupportMinimum)) *nativeFloorSupport=static_cast(latticeFloorSupportMinimum/metresPerVoxel); return static_cast(composed/metresPerVoxel); } float ApplyResolvedCavernFloorCaps(float density,float X,float Y,float Z) const { VoxelForgeCore::Linux77Profiler::FScopedPhase l77CapsPhase( VoxelForgeCore::Linux77Profiler::EPhase::Caps); if(CavernRuntime.empty()||!Owner||Owner->CoreVoxelSizeMicrons==0)return density; const auto* tileQueries=ActivePassageTileQueries; const bool scoped=tileQueries&&tileQueries->matches(*this); if(scoped&&!tileQueries->hasCavernCandidates())return density; const auto* bound=FindBoundStratumForManagerZ(Z); if(!bound)return density; const double micronsPerVoxel=Owner->CoreVoxelSizeMicrons; const std::array worldMicrometres={X*micronsPerVoxel, Y*micronsPerVoxel,Z*micronsPerVoxel}; float capped=density; const auto apply=[&](std::uint32_t index) { if(index>=CavernRuntime.size())return; const auto& feature=CavernRuntime[index]; const std::int32_t strateIndex=feature.room?feature.room->strateIndex :feature.edge?feature.edge->strateIndex:-1; if(strateIndex!=bound->StrateIndex)return; for(std::size_t axis=0;axis<3;++axis) if(worldMicrometres[axis]feature.boundsMaximumMicrometres[axis])return; if(feature.room) { if(Z::infinity(); for(int sample=0;sample<=16;++sample) { const float candidate=static_cast(sample)/16.0f; const float dx=X-bezier(candidate,0),dy=Y-bezier(candidate,1); const float distanceSquared=dx*dx+dy*dy; if(distanceSquared1.0e-5f ?derivative(t,2)/floorHorizontalSpeed:0.0f; const float floorDistanceScale=1.0f/std::sqrt(1.0f+floorGrade*floorGrade); const float radial=std::hypot(X-bezier(t,0),Y-bezier(t,1)); const float floorDistance=(Z-floorZ)*floorDistanceScale; const float floorSdfBand=std::max(1.0f,feature.floorSupportDepthVoxels); if(Z0.0f&&feature.floorSupportDepthVoxels>0.0f &&Z>=floorZ-feature.floorSupportDepthVoxels &&radial0.0f) { const float fade=SmoothUnit((Z-floorZ)/0.8f); tunnelDistance+=StampValueNoise( X-feature.controlPointsVoxels[0][0], Y-feature.controlPointsVoxels[0][1],Z-floorZ, feature.irregularityWavelengthVoxels,feature.irregularitySeed) *feature.irregularityAmplitudeVoxels*fade; } // Reapply the tunnel SDF in the floor-adjacent strip. Keep a narrow // correction along the ramp, with a wider fade only at the stamped mouth. const float heightAboveFloor=Z-floorZ; const float mouthFade=1.0f-SmoothUnit(t/0.10f); const float recarveWeight=heightAboveFloor<=0.25f?1.0f:mouthFade; if(recarveWeight>0.0f) { const float recarved=SmoothMaxDistance(capped,-tunnelDistance, feature.smoothWidthVoxels); capped=std::lerp(capped,recarved,recarveWeight); } } if(radial<=feature.boreRadiusVoxels) capped=std::min(capped,floorDistance); }; if(scoped) for(const auto index:tileQueries->cavernFeatureCandidates())apply(index); else for(std::size_t index=0;index(index)); return capped; } static float SmoothUnit(float V) { V=std::clamp(V,0.0f,1.0f); return V*V*(3.0f-2.0f*V); } static float DetailBandFade(float SignedDistance) { // Keep the larger candidate mask conservative around smooth family unions; // visible relief still fades to zero beyond its 4.125-voxel support radius. constexpr float Edge=DetailSupremum+0.125f; constexpr float Width=1.5f; return SmoothUnit((Edge-std::abs(SignedDistance))/Width); } static float DetailContrast(float CaveInfluence,float FloorInfluence) { const float Cave=std::clamp(CaveInfluence,0.0f,1.0f); const float Floor=std::min(Cave,std::clamp(FloorInfluence,0.0f,1.0f)); const float StrataWeight=1.0f-Cave; const float CaveWeight=Cave-Floor; const float FloorWeight=Floor; // The three influences are a partition of unity. Cave floors blend at the // cave family's amplitude instead of multiplying both family contributions. return 1.35f*StrataWeight+1.00f*CaveWeight+0.55f*FloorWeight; } static float BlendDetailFamilies(float Relief,float PassageBandRelief,float OwnerRelief, float CaveInfluence,float FloorInfluence,float PassageInfluence) { const float Cave=std::clamp(CaveInfluence,0.0f,1.0f); const float Floor=std::min(Cave,std::clamp(FloorInfluence,0.0f,1.0f)); const float Passage=std::clamp(PassageInfluence,0.0f,1.0f); const float NonPassageWeight=1.0f-Passage; const float StrataWeight=NonPassageWeight*(1.0f-Cave); const float CaveWeight=NonPassageWeight*(Cave-Floor); const float FloorWeight=NonPassageWeight*Floor; const float StrataRelief=1.35f*(Relief+0.5f*OwnerRelief); const float CaveRelief=1.00f*Relief; const float FloorRelief=0.55f*Relief; // The shared passage sweep supplies its own irregular relief. Keep only a // small trace of the terrain value-noise bands so their cell edges cannot // dominate the passage wall and read as repeated rings. const float PassageRelief=0.10f*PassageBandRelief; return StrataWeight*StrataRelief+CaveWeight*CaveRelief +FloorWeight*FloorRelief+Passage*PassageRelief; } static constexpr float DetailBandInnerRadius=1.5f; static float DetailBandFade(float SignedDistance,float Edge) { const float Distance=std::abs(SignedDistance); if(Distance<=DetailBandInnerRadius)return 1.0f; const float Width=Edge-DetailBandInnerRadius; return Width>0.0f?SmoothUnit((Edge-Distance)/Width):0.0f; } static float LocalDetailBandEdge(float MacroValue,float SurfaceSDF, float CaveInfluence,float FloorInfluence,const FLayerField* Field) { float OwnerBound=0.0f; if(Field) { const FStrateGenerationParams& Params=Field->Params; const float Depth=Params.SurfaceRoughness*2.0f; if(Depth>0.0f&&std::abs(SurfaceSDF)0.0f) { const float Fade=1.0f-std::abs(SurfaceSDF)/Depth; // Perlin is bounded by 2; the squared ridged fold by 2.25. This // uses the largest type bound and remains valid under domain warp. OwnerBound=3.15f*VOXEL_NOISE_SCALE*Params.SurfaceRoughness*Fade*Fade; } } const float ReliefBound=1.20f*std::abs(MacroValue) +(0.80f+0.25f*MacroValue)*(0.40f+0.60f); const float Contrast=DetailContrast(CaveInfluence,FloorInfluence); const float LocalSupremum=std::min(3.9f,(ReliefBound+0.5f*OwnerBound)*Contrast); return std::clamp(LocalSupremum+0.125f,DetailBandInnerRadius,DetailSupremum+0.125f); } float DetailBandFadeAt(double X,double Y,double Z,float SignedDistance,float SurfaceSDF, float CaveInfluence,float FloorInfluence) const { if(DetailVariant!=EDetailVariant::Coherent ||std::abs(SignedDistance)<=DetailBandInnerRadius) return DetailBandFade(SignedDistance); const float MacroValue=FStandaloneResearchTunnels::ValueNoise( static_cast(X),static_cast(Y),static_cast(Z),32, static_cast(Owner->Seed)^0xB5297A4Du); const FLayerField* Field=FindLayerField(Z); return DetailBandFade(SignedDistance,LocalDetailBandEdge(MacroValue,SurfaceSDF, CaveInfluence,FloorInfluence,Field)); } void BuildLayerBounds() { LayerRanges.clear();LayerFields.clear(); const auto& Layout=Owner->Manager->GetLayout(); int32 Top=MIN_int32,Bottom=MAX_int32; for(const FStrateSlot& Slot:Layout) { FResolvedStratumBounds Bounds; if(!GetResolvedStratumBounds(Slot,Bounds))continue; const int32 ResolvedBottom=FMath::FloorToInt(static_cast(Bounds.BottomZ)); const int32 ResolvedTop=FMath::CeilToInt(static_cast(Bounds.TopZ)); Top=std::max(Top,ResolvedTop);Bottom=std::min(Bottom,ResolvedBottom); if(!Slot.Definition)continue; const int32 MidChunkZ=(Slot.TopChunkZ+Slot.BottomChunkZ)/2; FLayerField Field; Field.Lo=ResolvedBottom; Field.Hi=ResolvedTop; Field.StrateIndex=Slot.StrateIndex; Field.Params=Owner->Manager->GetGenerationParams(FIntVector(0,0,MidChunkZ)); LayerFields.push_back(std::move(Field)); } if(Top>=Bottom) { LayerRanges.push_back({Bottom,Top}); } } static std::array V3(const FVector& V){return {V.X,V.Y,V.Z};} static double Dot(const std::array& A,const std::array& B) {return A[0]*B[0]+A[1]*B[1]+A[2]*B[2];} static std::array Sub(const FVector& A,const FVector& B) {return {A.X-B.X,A.Y-B.Y,A.Z-B.Z};} static std::array Normalize(std::array V) { const double L=std::sqrt(std::max(1.0e-24,Dot(V,V))); for(double& C:V)C/=L;return V; } static std::array Cross(const std::array& A,const std::array& B) {return {A[1]*B[2]-A[2]*B[1],A[2]*B[0]-A[0]*B[2],A[0]*B[1]-A[1]*B[0]};} static VF_FieldPrimitive MakeSharedFieldPrimitive(const FPrimitive& P) { VF_FieldPrimitive Out{}; if(P.bCapsule)Out.flags|=VF_FIELD_PRIMITIVE_CAPSULE; if(P.bSmoothCapsuleRadius)Out.flags|=VF_FIELD_PRIMITIVE_SMOOTH_CAPSULE; if(P.bRoundedBox)Out.flags|=VF_FIELD_PRIMITIVE_ROUNDED_BOX; if(P.bHasFloorClip)Out.flags|=VF_FIELD_PRIMITIVE_HAS_FLOOR; if(P.bCurvedFloorClip)Out.flags|=VF_FIELD_PRIMITIVE_CURVED_FLOOR; if(P.bCarve)Out.flags|=VF_FIELD_PRIMITIVE_CARVE; if(P.bCritical)Out.flags|=VF_FIELD_PRIMITIVE_CRITICAL; if(P.bPassageSurface)Out.flags|=VF_FIELD_PRIMITIVE_PASSAGE; for(int32 A=0;A<3;++A) { Out.minVoxel[A]=P.Min[A];Out.maxVoxel[A]=P.Max[A]; Out.center[A]=P.CenterSub[A]/16.0; Out.capsuleStart[A]=P.CapsuleStartWorld[A]; Out.capsuleDelta[A]=P.CapsuleAxisDelta[A]; Out.roundedHalfExtent[A]=P.RoundedHalfExtent[A]; } for(int32 I=0;I<3;++I)for(int32 J=0;J<3;++J) Out.q[I*3+J]=static_cast(P.Q[I][J])/QScale; Out.minRadius=P.MinRadius; Out.capsuleAxisLengthSquared=P.CapsuleAxisLengthSquared; Out.capsuleAxisLength=P.CapsuleAxisLength; Out.capsuleStartRadius=P.CapsuleStartRadius; Out.capsuleEndRadius=P.CapsuleEndRadius; Out.profileStartT=P.CapsuleProfileStartT; Out.profileEndT=P.CapsuleProfileEndT; Out.profileStartRadius=P.CapsuleProfileStartRadius; Out.profileEndRadius=P.CapsuleProfileEndRadius; Out.radiusSlopeStart=P.CapsuleRadiusSlopeStart; Out.radiusSlopeEnd=P.CapsuleRadiusSlopeEnd; Out.rounding=P.Rounding; Out.blendRadius=P.BlendRadius; Out.floorBlendRadius=P.FloorBlendRadius; Out.floorCenterZ=P.FloorCenterZ; Out.floorSlopeX=P.FloorSlopeX; Out.floorSlopeY=P.FloorSlopeY; Out.floorCurveOriginX=P.FloorCurveOriginX; Out.floorCurveOriginY=P.FloorCurveOriginY; Out.floorCurveDeltaX=P.FloorCurveDeltaX; Out.floorCurveDeltaY=P.FloorCurveDeltaY; Out.floorCurveLength=P.FloorCurveLength; Out.floorCurveStartZ=P.FloorCurveStartZ; Out.floorCurveEndZ=P.FloorCurveEndZ; Out.floorCurveTangentStart=P.FloorCurveTangentStart; Out.floorCurveTangentEnd=P.FloorCurveTangentEnd; Out.floorCurveStartBlend=P.FloorCurveStartBlend; Out.floorCurveEndBlend=P.FloorCurveEndBlend; return Out; } void RebuildSharedFieldPrimitives() { InvalidateFarFieldGpuInput(); SharedFieldPrimitives.clear();SharedFieldPrimitives.reserve(Primitives.size()); for(const FPrimitive& P:Primitives) SharedFieldPrimitives.push_back(MakeSharedFieldPrimitive(P)); } void AddEllipsoid(const FVector& Center,const std::array& Radii, const std::array& Axis0={1,0,0}, const std::array& Axis1={0,1,0}, const std::array& Axis2={0,0,1},bool bCarve=true) { FPrimitive P;const auto C=V3(Center); for(int32 A=0;A<3;++A)P.CenterSub[A]=static_cast(std::llround(C[A]*SubPerVoxel)); const std::array,3> Axes={Axis0,Axis1,Axis2}; int32 RSub[3]; for(int32 K=0;K<3;++K)RSub[K]=std::max(16,static_cast(std::llround(std::max(1.0,Radii[K])*SubPerVoxel))); for(int32 I=0;I<3;++I)for(int32 J=0;J<3;++J) { int64 Sum=0; for(int32 K=0;K<3;++K) { const int64 RI=static_cast(std::llround(Axes[K][I]*32768.0)); const int64 RJ=static_cast(std::llround(Axes[K][J]*32768.0)); const int64 Den=static_cast(32768)*32768*RSub[K]*RSub[K]; Sum+=RoundDiv(RI*RJ*QScale,Den); } P.Q[I][J]=Sum; } for(int32 A=0;A<3;++A) { double E=0.0;for(int32 K=0;K<3;++K)E+=std::abs(Axes[K][A])*Radii[K]; P.Min[A]=static_cast(std::floor(C[A]-E))-1; P.Max[A]=static_cast(std::ceil(C[A]+E))+1; } P.MinRadius=static_cast(std::min({Radii[0],Radii[1],Radii[2]})); P.BlendRadius=ActiveBlendRadius; P.bCarve=bCarve;Primitives.push_back(P); } void AddRoundedBox(const FVector& Center,const std::array& HalfExtent, double Rounding,bool bCarve=true) { const std::array Outer={HalfExtent[0]+Rounding,HalfExtent[1]+Rounding,HalfExtent[2]+Rounding}; AddEllipsoid(Center,Outer,{1,0,0},{0,1,0},{0,0,1},bCarve); FPrimitive& P=Primitives.back();P.bRoundedBox=true;P.Rounding=Rounding; for(int32 A=0;A<3;++A)P.RoundedHalfExtent[A]=HalfExtent[A]; } static void ApplyFloorClip(FPrimitive& P,double FloorCenterZ,double SlopeX=0.0,double SlopeY=0.0, double FloorBlendRadius=0.0) { P.bHasFloorClip=true;P.FloorCenterZ=FloorCenterZ-0.5; P.FloorSlopeX=SlopeX;P.FloorSlopeY=SlopeY;P.FloorBlendRadius=FloorBlendRadius; } void AddCapsule(const FVector& A,const FVector& B,float Radius,bool bCarve=true,bool bCritical=false, bool bFloorClip=false,double FloorA=0.0,double FloorB=0.0) { AddLegacyEllipsoidCapsule(A,B,Radius,bCarve,bCritical,bFloorClip,FloorA,FloorB); } static FVector ApproximateUnwarp(const FVector& Target,const FStrateGenerationParams& Params, uint32 Seed) { FVector WorldPoint=Target; const float ZScale=Params.VerticalScale>0.0f?Params.VerticalScale:1.0f; if(ZScale!=1.0f)WorldPoint.Z*=ZScale; if(Params.CaveWarpStrength<=0.0f)return WorldPoint; for(int32 Iteration=0;Iteration<8;++Iteration) { const FVector Mapped=VoxelCaveMorphology::ApplyCaveWarp(WorldPoint,Params,Seed); const FVector Error=Target-Mapped; if(!Error.ContainsNaN()&&VoxelMath::IsFinite(Error.X) &&VoxelMath::IsFinite(Error.Y)&&VoxelMath::IsFinite(Error.Z)) { WorldPoint.X+=Error.X;WorldPoint.Y+=Error.Y;WorldPoint.Z+=Error.Z*ZScale; } } return WorldPoint; } void AddLegacyEllipsoidCapsule(const FVector& A,const FVector& B,float Radius,bool bCarve, bool bCritical,bool bFloorClip,double FloorA,double FloorB) { const auto D=Sub(B,A);const double Length=std::sqrt(Dot(D,D)); const double PreviousBlendRadius=ActiveBlendRadius;ActiveBlendRadius=0.0; if(!(Length>1.0e-4)) { const size_t Before=Primitives.size(); AddEllipsoid(A,{Radius,Radius,Radius},{1,0,0},{0,1,0},{0,0,1},bCarve); if(bFloorClip)ApplyFloorClip(Primitives.back(),std::min(FloorA,FloorB),0.0,0.0, 0.0); if(bCritical)for(size_t I=Before;I Ref=std::abs(U[2])<0.90?std::array{0,0,1}:std::array{0,1,0}; const auto V=Normalize(Cross(U,Ref));const auto W=Normalize(Cross(U,V)); const double SegmentStep=std::max(8.0,static_cast(Radius)*2.0); const int32 Segments=std::max(1,static_cast(std::ceil(Length/SegmentStep))); for(int32 I=0;I(I)/Segments,T1=static_cast(I+1)/Segments; const FVector P0(A.X+D[0]*T0,A.Y+D[1]*T0,A.Z+D[2]*T0); const FVector P1(A.X+D[0]*T1,A.Y+D[1]*T1,A.Z+D[2]*T1); const FVector C((P0.X+P1.X)*0.5,(P0.Y+P1.Y)*0.5,(P0.Z+P1.Z)*0.5); const double Half=Length*(T1-T0)*0.5+Radius; const size_t Before=Primitives.size();AddEllipsoid(C,{Half,Radius,Radius},U,V,W,bCarve); if(bFloorClip) { const double Mid=(T0+T1)*0.5; const double Floor=(1.0-Mid)*FloorA+Mid*FloorB; const double DX=P1.X-P0.X,DY=P1.Y-P0.Y,HorizontalSq=DX*DX+DY*DY; const double FloorStep=(FloorB-FloorA)*(T1-T0); if(HorizontalSq>1.0e-6) ApplyFloorClip(Primitives.back(),Floor,FloorStep*DX/HorizontalSq, FloorStep*DY/HorizontalSq,0.0); else ApplyFloorClip(Primitives.back(),std::min(FloorA,FloorB),0.0,0.0, 0.0); } if(bCritical)for(size_t J=Before;J1.0e-4)) { AddEllipsoid(A,{RadiusA,RadiusA,RadiusA},{1,0,0},{0,1,0},{0,0,1},bCarve); Primitives.back().bCritical=bCritical; if(bFloorClip)ApplyFloorClip(Primitives.back(),std::min(FloorA,FloorB),0.0,0.0, ActiveFloorBlendRadius); return; } ExtendStart=std::clamp(ExtendStart,0.0,Length*0.4); ExtendEnd=std::clamp(ExtendEnd,0.0,Length*0.4); const double RadiusSlope=(static_cast(RadiusB)-RadiusA)/Length; const float ExtendedRadiusA=static_cast(std::max(1.0,RadiusA-RadiusSlope*ExtendStart)); const float ExtendedRadiusB=static_cast(std::max(1.0,RadiusB+RadiusSlope*ExtendEnd)); const double Start[3]={A.X-D[0]*(ExtendStart/Length),A.Y-D[1]*(ExtendStart/Length),A.Z-D[2]*(ExtendStart/Length)}; const double End[3]={B.X+D[0]*(ExtendEnd/Length),B.Y+D[1]*(ExtendEnd/Length),B.Z+D[2]*(ExtendEnd/Length)}; FPrimitive P; P.bCapsule=true;P.bCarve=bCarve;P.bCritical=bCritical; P.CapsuleStartRadius=ExtendedRadiusA;P.CapsuleEndRadius=ExtendedRadiusB; P.MinRadius=std::min(ExtendedRadiusA,ExtendedRadiusB);P.BlendRadius=ActiveBlendRadius; const double R=std::max(ExtendedRadiusA,ExtendedRadiusB); double AxisLengthSquared=0.0; for(int32 Axis=0;Axis<3;++Axis) { P.CapsuleStartSub[Axis]=static_cast(std::llround(Start[Axis]*SubPerVoxel)); P.CapsuleEndSub[Axis]=static_cast(std::llround(End[Axis]*SubPerVoxel)); P.CapsuleStartWorld[Axis]=P.CapsuleStartSub[Axis]/static_cast(SubPerVoxel); const double CapsuleEndWorld=P.CapsuleEndSub[Axis]/static_cast(SubPerVoxel); P.CapsuleAxisDelta[Axis]=CapsuleEndWorld-P.CapsuleStartWorld[Axis]; AxisLengthSquared+=P.CapsuleAxisDelta[Axis]*P.CapsuleAxisDelta[Axis]; P.CenterSub[Axis]=static_cast(std::llround((Start[Axis]+End[Axis])*0.5*SubPerVoxel)); P.Min[Axis]=static_cast(std::floor(std::min(Start[Axis],End[Axis])-R))-1; P.Max[Axis]=static_cast(std::ceil(std::max(Start[Axis],End[Axis])+R))+1; } P.CapsuleAxisLengthSquared=AxisLengthSquared; P.CapsuleAxisLength=std::sqrt(std::max(0.0,AxisLengthSquared)); if(bFloorClip) { const double FloorSlope=(FloorB-FloorA)/Length; const double ExtendedFloorA=FloorA-FloorSlope*ExtendStart; const double ExtendedFloorB=FloorB+FloorSlope*ExtendEnd; const double ExtendedD[3]={End[0]-Start[0],End[1]-Start[1],End[2]-Start[2]}; const double HorizontalSq=ExtendedD[0]*ExtendedD[0]+ExtendedD[1]*ExtendedD[1]; if(HorizontalSq>1.0e-6) ApplyFloorClip(P,(ExtendedFloorA+ExtendedFloorB)*0.5, (ExtendedFloorB-ExtendedFloorA)*ExtendedD[0]/HorizontalSq, (ExtendedFloorB-ExtendedFloorA)*ExtendedD[1]/HorizontalSq,ActiveFloorBlendRadius); else ApplyFloorClip(P,std::min(ExtendedFloorA,ExtendedFloorB),0.0,0.0,ActiveFloorBlendRadius); } Primitives.push_back(P); } static double ContinuousJoinExtension(float RadiusA,float RadiusB,double SegmentLength) { return std::min(std::max(0.0,SegmentLength*0.35), JoinBlendRadius+0.5*static_cast(std::max(RadiusA,RadiusB))); } static float CapsuleRadiusAt(const FPrimitive& P,const FVector& Point) { if(!P.bCapsule)return P.MinRadius; const double Start[3]={P.CapsuleStartSub[0]/static_cast(SubPerVoxel), P.CapsuleStartSub[1]/static_cast(SubPerVoxel), P.CapsuleStartSub[2]/static_cast(SubPerVoxel)}; const double End[3]={P.CapsuleEndSub[0]/static_cast(SubPerVoxel), P.CapsuleEndSub[1]/static_cast(SubPerVoxel), P.CapsuleEndSub[2]/static_cast(SubPerVoxel)}; const double V[3]={Point.X-Start[0],Point.Y-Start[1],Point.Z-Start[2]}; const double D[3]={End[0]-Start[0],End[1]-Start[1],End[2]-Start[2]}; const double D2=D[0]*D[0]+D[1]*D[1]+D[2]*D[2]; const double T=D2>1.0e-12?(V[0]*D[0]+V[1]*D[1]+V[2]*D[2])/D2:0.0; return CapsuleRadiusAtT(P,T,std::sqrt(std::max(0.0,D2))); } static void FitCapsuleRadiiAtEndpoints(FPrimitive& P,const FVector& PointA,float RadiusA, const FVector& PointB,float RadiusB) { const double Start[3]={P.CapsuleStartSub[0]/static_cast(SubPerVoxel), P.CapsuleStartSub[1]/static_cast(SubPerVoxel), P.CapsuleStartSub[2]/static_cast(SubPerVoxel)}; const double End[3]={P.CapsuleEndSub[0]/static_cast(SubPerVoxel), P.CapsuleEndSub[1]/static_cast(SubPerVoxel), P.CapsuleEndSub[2]/static_cast(SubPerVoxel)}; const double D[3]={End[0]-Start[0],End[1]-Start[1],End[2]-Start[2]}; const double D2=D[0]*D[0]+D[1]*D[1]+D[2]*D[2]; if(D2<=1.0e-12)return; const auto Parameter=[&](const FVector& Point) { const double V[3]={Point.X-Start[0],Point.Y-Start[1],Point.Z-Start[2]}; return (V[0]*D[0]+V[1]*D[1]+V[2]*D[2])/D2; }; const double TA=Parameter(PointA),TB=Parameter(PointB); if(std::abs(TB-TA)<=1.0e-9)return; const double Slope=(RadiusB-RadiusA)/(TB-TA); P.CapsuleStartRadius=static_cast(RadiusA-Slope*TA); P.CapsuleEndRadius=static_cast(P.CapsuleStartRadius+Slope); P.MinRadius=std::min(P.CapsuleStartRadius,P.CapsuleEndRadius); } static void SetCapsuleRadiusProfile(FPrimitive& P,const FVector& PointA,float RadiusA, double SlopeA,const FVector& PointB,float RadiusB,double SlopeB) { const double Start[3]={P.CapsuleStartSub[0]/static_cast(SubPerVoxel), P.CapsuleStartSub[1]/static_cast(SubPerVoxel), P.CapsuleStartSub[2]/static_cast(SubPerVoxel)}; const double End[3]={P.CapsuleEndSub[0]/static_cast(SubPerVoxel), P.CapsuleEndSub[1]/static_cast(SubPerVoxel), P.CapsuleEndSub[2]/static_cast(SubPerVoxel)}; const double D[3]={End[0]-Start[0],End[1]-Start[1],End[2]-Start[2]}; const double D2=D[0]*D[0]+D[1]*D[1]+D[2]*D[2]; if(D2<=1.0e-12)return; const auto Parameter=[&](const FVector& Point) { const double V[3]={Point.X-Start[0],Point.Y-Start[1],Point.Z-Start[2]}; return std::clamp((V[0]*D[0]+V[1]*D[1]+V[2]*D[2])/D2,0.0,1.0); }; P.CapsuleProfileStartT=Parameter(PointA); P.CapsuleProfileEndT=Parameter(PointB); P.CapsuleProfileStartRadius=RadiusA; P.CapsuleProfileEndRadius=RadiusB; P.CapsuleRadiusSlopeStart=SlopeA; P.CapsuleRadiusSlopeEnd=SlopeB; P.bSmoothCapsuleRadius=P.CapsuleProfileEndT-P.CapsuleProfileStartT>1.0e-6; } static float SmoothedControlRadius(const TArray& Radii,int32 Index,float Fallback, float Minimum) { const int32 Count=Radii.Num(); const float Current=std::max(Minimum,Radii.IsValidIndex(Index)?Radii[Index]:Fallback); if(Index<=0||Index+1>=Count)return Current; const auto At=[&](int32 I) {return std::max(Minimum,Radii[std::clamp(I,0,Count-1)]);}; return (At(Index-4)+8.0f*At(Index-3)+28.0f*At(Index-2)+56.0f*At(Index-1) +70.0f*Current+56.0f*At(Index+1)+28.0f*At(Index+2)+8.0f*At(Index+3) +At(Index+4))*(1.0f/256.0f); } static double ControlRadiusSlopeAt(const TArray& Points,const TArray& Radii, int32 Index,float Fallback,float Minimum) { if(Points.Num()<2||Index<0||Index>=Points.Num())return 0.0; const auto RadiusAt=[&](int32 I) {return static_cast(SmoothedControlRadius(Radii,I,Fallback,Minimum));}; const auto LengthAt=[&](int32 I) { const FVector& A=Points[I];const FVector& B=Points[I+1]; const double DX=B.X-A.X,DY=B.Y-A.Y,DZ=B.Z-A.Z; return std::sqrt(DX*DX+DY*DY+DZ*DZ); }; if(Index==0) { const double Length=LengthAt(0); return Length>1.0e-9?(RadiusAt(1)-RadiusAt(0))/Length:0.0; } if(Index+1==Points.Num()) { const double Length=LengthAt(Index-1); return Length>1.0e-9?(RadiusAt(Index)-RadiusAt(Index-1))/Length:0.0; } const double PrevLength=LengthAt(Index-1),NextLength=LengthAt(Index); if(PrevLength<=1.0e-9||NextLength<=1.0e-9)return 0.0; const double PrevSlope=(RadiusAt(Index)-RadiusAt(Index-1))/PrevLength; const double NextSlope=(RadiusAt(Index+1)-RadiusAt(Index))/NextLength; if(PrevSlope*NextSlope<=0.0)return 0.0; const double W1=2.0*NextLength+PrevLength,W2=NextLength+2.0*PrevLength; const double Harmonic=(W1+W2)/(W1/PrevSlope+W2/NextSlope); const double Limit=3.0*std::min(std::abs(PrevSlope),std::abs(NextSlope)); return std::copysign(std::min(std::abs(Harmonic),Limit),Harmonic); } static double EvaluateFloorProfile(double A,double B,double TangentA,double TangentB, double Length,double StartBlend,double EndBlend,double S) { const double Secant=(B-A)/Length; if(S<0.0)return A+TangentA*S; if(S>Length)return B+TangentB*(S-Length); double Result=std::lerp(A,B,S/Length); const auto Correction=[](double DeltaSlope,double Span,double T) { if(!(Span>0.0)||T<0.0||T>=1.0)return 0.0; return DeltaSlope*Span*(T-2.0*T*T+T*T*T); }; if(SLength)return TangentB; const double Secant=(B-A)/Length; double Result=Secant; const auto CorrectionSlope=[](double DeltaSlope,double T) {return DeltaSlope*(1.0-4.0*T+3.0*T*T);}; if(SValueThreshold; Stats.FloorProfileEndpointSlopeControlDetected= std::abs((startSlope+SlopeStep)-TangentA)>SlopeThreshold; std::printf("[FloorProfileBlendNegativeControls] endpoint_step=%.3f_vox tangent_step=%.3f_vox_per_m detected=%s/%s status=%s\n", Step,SlopeStep, Stats.FloorProfileEndpointStepControlDetected?"true":"false", Stats.FloorProfileEndpointSlopeControlDetected?"true":"false", Stats.FloorProfileEndpointStepControlDetected &&Stats.FloorProfileEndpointSlopeControlDetected?"PASS":"FAIL"); } static double FloorSurfaceRelief(double X,double Y,uint32 Seed) { // A shared XY field keeps independently authored floor clips from ending on // separate flat planes. Its peak displacement stays under 1.2 voxels. const float Broad=FStandaloneResearchTunnels::ValueNoise( static_cast(X),static_cast(Y),0.0f,32,Seed^0x7F4A7C15u); const float Mid=FStandaloneResearchTunnels::ValueNoise( static_cast(X),static_cast(Y),0.0f,16,Seed^0x94D049BBu); return 0.80*Broad+0.35*Mid; } static std::vector BuildFloorTangents(const TArray& Points, const std::vector& Floor,const std::vector& MarkedSegments) { const size_t Count=Floor.size();std::vector Tangent(Count,0.0); if(Count<2||Count!=static_cast(Points.Num())||MarkedSegments.size()+1!=Count)return Tangent; std::vector Length(Count-1,0.0),Slope(Count-1,0.0); for(size_t I=0;I+1(I)];const FVector& B=Points[static_cast(I+1)]; Length[I]=std::hypot(B.X-A.X,B.Y-A.Y); if(Length[I]>1.0e-6)Slope[I]=(Floor[I+1]-Floor[I])/Length[I]; } for(size_t I=0;I0&&Length[I-1]>1.0e-6; const bool bHasNext=I+11.0e-6; if(I==0){Tangent[I]=MarkedSegments[0]?0.0:Slope[0];continue;} if(I+1==Count){Tangent[I]=MarkedSegments[I-1]?0.0:Slope[I-1];continue;} const bool bPrevMarked=MarkedSegments[I-1]!=0,bNextMarked=MarkedSegments[I]!=0; if(bPrevMarked||bNextMarked) { if(!bPrevMarked&&bHasPrev)Tangent[I]=Slope[I-1]; else if(!bNextMarked&&bHasNext)Tangent[I]=Slope[I]; continue; } if(!bHasPrev||!bHasNext||Slope[I-1]*Slope[I]<=0.0)continue; const double W0=2.0*Length[I]+Length[I-1],W1=Length[I]+2.0*Length[I-1]; const double Denominator=W0/Slope[I-1]+W1/Slope[I]; if(std::abs(Denominator)>1.0e-12) Tangent[I]=(W0+W1)/Denominator; } return Tangent; } static void SetCurvedFloorClip(FPrimitive& P,const FVector& A,const FVector& B, double FloorA,double FloorB,double TangentA,double TangentB) { if(!P.bHasFloorClip)return; const double DX=B.X-A.X,DY=B.Y-A.Y,Length=std::hypot(DX,DY); if(Length<=1.0e-6)return; P.bCurvedFloorClip=true;P.FloorCurveOriginX=A.X;P.FloorCurveOriginY=A.Y; P.FloorCurveDeltaX=DX;P.FloorCurveDeltaY=DY;P.FloorCurveLength=Length; P.FloorCurveStartZ=FloorA;P.FloorCurveEndZ=FloorB; P.FloorCurveTangentStart=TangentA;P.FloorCurveTangentEnd=TangentB; P.FloorCurveStartBlend=std::min(8.0,Length*0.35); P.FloorCurveEndBlend=std::min(8.0,Length*0.35); for(int32 I=1;I<64;++I) { const double T=I/64.0; const double Curve=EvaluateFloorProfile(FloorA,FloorB,TangentA,TangentB, Length,P.FloorCurveStartBlend,P.FloorCurveEndBlend,Length*T); const double Linear=std::lerp(FloorA,FloorB,T); P.FloorCurveMaxDeviation=std::max(P.FloorCurveMaxDeviation,std::abs(Curve-Linear)); } P.FloorCurveEndpointValueError=std::max( std::abs(EvaluateFloorProfile(FloorA,FloorB,TangentA,TangentB,Length, P.FloorCurveStartBlend,P.FloorCurveEndBlend,0.0)-FloorA), std::abs(EvaluateFloorProfile(FloorA,FloorB,TangentA,TangentB,Length, P.FloorCurveStartBlend,P.FloorCurveEndBlend,Length)-FloorB)); P.FloorCurveEndpointSlopeError=std::max( std::abs(EvaluateFloorProfileSlope(FloorA,FloorB,TangentA,TangentB,Length, P.FloorCurveStartBlend,P.FloorCurveEndBlend,0.0)-TangentA), std::abs(EvaluateFloorProfileSlope(FloorA,FloorB,TangentA,TangentB,Length, P.FloorCurveStartBlend,P.FloorCurveEndBlend,Length)-TangentB)); } double FloorAt(const FPrimitive& P,double X,double Y) const { double Base=0.0; if(P.bCurvedFloorClip) { const double T=((X-P.FloorCurveOriginX)*P.FloorCurveDeltaX +(Y-P.FloorCurveOriginY)*P.FloorCurveDeltaY) /(P.FloorCurveLength*P.FloorCurveLength); Base=EvaluateFloorProfile(P.FloorCurveStartZ,P.FloorCurveEndZ, P.FloorCurveTangentStart,P.FloorCurveTangentEnd,P.FloorCurveLength, P.FloorCurveStartBlend,P.FloorCurveEndBlend,T*P.FloorCurveLength); } else Base=P.FloorCenterZ+P.FloorSlopeX*(X-P.CenterSub[0]/16.0) +P.FloorSlopeY*(Y-P.CenterSub[1]/16.0); return Base; } double FloorSlopeAtEndpoint(const FPrimitive& P,bool bEnd) const { const double DX=P.bCurvedFloorClip?P.FloorCurveDeltaX :P.CapsuleEndSub[0]-P.CapsuleStartSub[0]; const double DY=P.bCurvedFloorClip?P.FloorCurveDeltaY :P.CapsuleEndSub[1]-P.CapsuleStartSub[1]; const double Length=P.bCurvedFloorClip?P.FloorCurveLength :std::hypot(DX,DY)/SubPerVoxel; if(Length<=1.0e-9)return 0.0; const double OriginX=P.bCurvedFloorClip?P.FloorCurveOriginX :P.CapsuleStartSub[0]/static_cast(SubPerVoxel); const double OriginY=P.bCurvedFloorClip?P.FloorCurveOriginY :P.CapsuleStartSub[1]/static_cast(SubPerVoxel); const double X=OriginX+(bEnd?DX/(P.bCurvedFloorClip?1.0:SubPerVoxel):0.0); const double Y=OriginY+(bEnd?DY/(P.bCurvedFloorClip?1.0:SubPerVoxel):0.0); constexpr double Epsilon=0.125; const double UX=DX/(Length*(P.bCurvedFloorClip?1.0:SubPerVoxel)); const double UY=DY/(Length*(P.bCurvedFloorClip?1.0:SubPerVoxel)); return (FloorAt(P,X+UX*Epsilon,Y+UY*Epsilon) -FloorAt(P,X-UX*Epsilon,Y-UY*Epsilon))/(2.0*Epsilon); } void AccumulateOwnerPrimitiveStats(const FBuildStats& Other) { Stats.RoomCount+=Other.RoomCount; Stats.RoomFloorJoinCount+=Other.RoomFloorJoinCount; Stats.TunnelCount+=Other.TunnelCount; if(Other.MaxTunnelBendDegrees>Stats.MaxTunnelBendDegrees) { Stats.MaxTunnelBendDegrees=Other.MaxTunnelBendDegrees; Stats.MaxTunnelBendVoxels=Other.MaxTunnelBendVoxels; Stats.MaxTunnelBendDirection=Other.MaxTunnelBendDirection; } Stats.LegacySegmentRadiusStep=std::max(Stats.LegacySegmentRadiusStep, Other.LegacySegmentRadiusStep); Stats.MaxCapsuleJoinFloorDelta=std::max(Stats.MaxCapsuleJoinFloorDelta, Other.MaxCapsuleJoinFloorDelta); Stats.MaxMarkedFloorSlopeDelta=std::max(Stats.MaxMarkedFloorSlopeDelta, Other.MaxMarkedFloorSlopeDelta); Stats.MaxRawUnmarkedFloorSlopeDelta=std::max(Stats.MaxRawUnmarkedFloorSlopeDelta, Other.MaxRawUnmarkedFloorSlopeDelta); Stats.SmoothedProfileRadiusStep=std::max(Stats.SmoothedProfileRadiusStep, Other.SmoothedProfileRadiusStep); Stats.MaxFloorCurveDeviation=std::max(Stats.MaxFloorCurveDeviation, Other.MaxFloorCurveDeviation); Stats.MaxFloorProfileEndpointValueError=std::max( Stats.MaxFloorProfileEndpointValueError,Other.MaxFloorProfileEndpointValueError); Stats.MaxFloorProfileEndpointSlopeError=std::max( Stats.MaxFloorProfileEndpointSlopeError,Other.MaxFloorProfileEndpointSlopeError); Stats.MaxCapsuleJoinRadiusDelta=std::max(Stats.MaxCapsuleJoinRadiusDelta, Other.MaxCapsuleJoinRadiusDelta); Stats.MaxCapsuleJoinRadiusSlopeDelta=std::max(Stats.MaxCapsuleJoinRadiusSlopeDelta, Other.MaxCapsuleJoinRadiusSlopeDelta); Stats.MaxCapsuleJoinFloorSlopeDelta=std::max(Stats.MaxCapsuleJoinFloorSlopeDelta, Other.MaxCapsuleJoinFloorSlopeDelta); Stats.MaxUnmarkedFloorSlopeDelta=std::max(Stats.MaxUnmarkedFloorSlopeDelta, Other.MaxUnmarkedFloorSlopeDelta); Stats.MaxUnmarkedFloorDirectionSlopeDelta=std::max( Stats.MaxUnmarkedFloorDirectionSlopeDelta,Other.MaxUnmarkedFloorDirectionSlopeDelta); Stats.MarkedFloorSlopeJoins+=Other.MarkedFloorSlopeJoins; Stats.CurvedFloorSegments+=Other.CurvedFloorSegments; Stats.CapsuleJoinCount+=Other.CapsuleJoinCount; } void BuildOwnerPrimitivesParallel() { const auto& Layout=Owner->Manager->GetLayout(); struct FStratumResult { std::vector Primitives; FBuildStats Stats; }; struct FDispatch { FStandaloneShapeWorld* Parent=nullptr; const TArray* Layout=nullptr; std::vector* Results=nullptr; }; const size_t StratumCount=static_cast(Layout.Num()); std::vector Results(StratumCount); FDispatch Dispatch{this,&Layout,&Results}; const auto BuildStratum=[](void* Opaque,size_t Index,size_t) { auto& Context=*static_cast(Opaque); const FStrateSlot& Slot=(*Context.Layout)[Index]; if(!Slot.Definition)return; FStandaloneShapeWorld Partial; Partial.Owner=Context.Parent->Owner; Partial.WorldAssembly=Context.Parent->WorldAssembly; Partial.ResolvedLayout=Context.Parent->ResolvedLayout; Partial.LayerRanges=Context.Parent->LayerRanges; Partial.LayerFields=Context.Parent->LayerFields; for(int32 Axis=0;Axis<3;++Axis) { Partial.Min[Axis]=Context.Parent->Min[Axis]; Partial.Max[Axis]=Context.Parent->Max[Axis]; } Partial.BuildOwnerPrimitives(Slot.StrateIndex,true,false); FStratumResult& Result=(*Context.Results)[Index]; Result.Primitives=std::move(Partial.Primitives); Result.Stats=Partial.Stats; }; std::array WorkerSeconds{}; const size_t WorkerLimit=std::min(static_cast(WorkerCount),StratumCount); Stats.OwnerShapeWorkerSeconds=Scheduler->parallel_for(StratumCount,1,WorkerLimit, &Dispatch,BuildStratum,WorkerSeconds.data(),WorkerSeconds.size()); size_t PrimitiveCount=0; for(const FStratumResult& Result:Results)PrimitiveCount+=Result.Primitives.size(); Primitives.reserve(Primitives.size()+PrimitiveCount); for(FStratumResult& Result:Results) { AccumulateOwnerPrimitiveStats(Result.Stats); Primitives.insert(Primitives.end(), std::make_move_iterator(Result.Primitives.begin()), std::make_move_iterator(Result.Primitives.end())); } BuildOwnerPrimitives(-1,false,true); } void BuildOwnerPrimitives(int32 OnlyStrateIndex=-1,bool bBuildStrata=true, bool bBuildPassages=true) { const auto& Layout=Owner->Manager->GetLayout(); const float MinX=static_cast(Min[0]-256),MinY=static_cast(Min[1]-256); const float MaxX=static_cast(Max[0]+256),MaxY=static_cast(Max[1]+256); if(bBuildStrata)for(const FStrateSlot& Slot:Layout) { if(!Slot.Definition||(OnlyStrateIndex>=0&&Slot.StrateIndex!=OnlyStrateIndex))continue; const FIntVector MidChunk(0,0,(Slot.TopChunkZ+Slot.BottomChunkZ)/2); const FStrateGenerationParams Params=Owner->Manager->GetGenerationParams(MidChunk); ActiveBlendRadius=0.0; ActiveFloorBlendRadius=0.0; FChunkSDFCache Cache; VoxelCaveMorphology::BuildChunkCache(Cache,MinX,MinY,MaxX,MaxY,Params, static_cast(Owner->Seed), Slot.StrateIndex,&Slot.Definition->TerrainOperations,ERoomGraphBuildSite::Unknown); for(const FCachedRoom& R:Cache.Rooms) { ++Stats.RoomCount; const size_t RoomPrimitiveStart=Primitives.size(); const FVector RoomCenter=ApproximateUnwarp(R.Center,Params, static_cast(Owner->Seed)); if(R.ShapeType==2) AddCapsule(ApproximateUnwarp(R.ShapeA,Params,static_cast(Owner->Seed)), ApproximateUnwarp(R.ShapeB,Params,static_cast(Owner->Seed)), std::max(1.0f,R.ShapeR),true); else if(R.ShapeType==1) { AddRoundedBox(RoomCenter,{std::abs(R.ShapeA.X),std::abs(R.ShapeA.Y),std::abs(R.ShapeA.Z)}, std::max(0.0f,R.ShapeR),true); } else AddEllipsoid(RoomCenter,{std::abs(R.ShapeA.X),std::abs(R.ShapeA.Y),std::abs(R.ShapeA.Z)}, {1,0,0},{0,1,0},{0,0,1},true); if(R.FloorCutZ>-FLT_MAX*0.5f) { const double Relief=std::max(0.0f,R.FloorReliefStrength); const double FloorZ=ApproximateUnwarp( FVector(R.Center.X,R.Center.Y,R.FloorCutZ-Relief),Params, static_cast(Owner->Seed)).Z; for(size_t I=RoomPrimitiveStart;I(Cache.RoomFloorJoins.Num()); const double RoomBlendRadius=ActiveBlendRadius; ActiveBlendRadius=JoinBlendRadius; ActiveFloorBlendRadius=FloorClipBlendRadius; for(const FCachedTunnel& T:Cache.Tunnels) { ++Stats.TunnelCount; const bool bHasWorldChain=T.WorldControlPoints.Num()>1 &&T.WorldControlRadii.Num()==T.WorldControlPoints.Num(); const TArray& Points=bHasWorldChain?T.WorldControlPoints:T.ControlPoints; const TArray& Radii=bHasWorldChain?T.WorldControlRadii:T.ControlRadii; const TArray& FloorProfiles=bHasWorldChain &&T.WorldFloorProfiles.Num()==Points.Num()-1?T.WorldFloorProfiles:T.FloorProfiles; std::vector RawFloor(static_cast(Points.Num())); std::vector MarkedFloorSegments(Points.Num()>0?static_cast(Points.Num()-1):0,0); for(int32 I=0;I(I)]=static_cast(Points[I].Z -SmoothedControlRadius(Radii,I,T.RadiusA,2.0f)); if(FloorProfiles.Num()==Points.Num()-1) { for(int32 I=0;I+1(I)]=0.5f*(FloorProfiles[I-1].EndFloorZ+Profile.StartFloorZ); RawFloor[static_cast(I+1)]=Profile.EndFloorZ; MarkedFloorSegments[static_cast(I)]=Profile.bLedgeTransition?1:0; } } for(int32 I=0;I(I-1)]; const bool bNextUnmarked=I+1>=Points.Num() ||!MarkedFloorSegments[static_cast(I)]; if(bPreviousUnmarked&&bNextUnmarked) RawFloor[static_cast(I)]+=static_cast(FloorSurfaceRelief( Points[I].X,Points[I].Y,static_cast(Owner->Seed))); } const std::vector FloorTangents=BuildFloorTangents( Points,RawFloor,MarkedFloorSegments); for(int32 Join=1;Join+11.0e-6&&BL>1.0e-6) { const double Dot=(A.X*B.X+A.Y*B.Y+A.Z*B.Z)/(AL*BL); const double Angle=std::acos(std::clamp(Dot,-1.0,1.0))*57.29577951308232; if(Angle>Stats.MaxTunnelBendDegrees) { Stats.MaxTunnelBendDegrees=Angle;Stats.MaxTunnelBendVoxels=Points[Join]; const double DX=A.X/AL+B.X/BL,DY=A.Y/AL+B.Y/BL,DZ=A.Z/AL+B.Z/BL; const double DL=std::sqrt(DX*DX+DY*DY+DZ*DZ); if(DL>1.0e-6)Stats.MaxTunnelBendDirection=FVector(DX/DL,DY/DL,DZ/DL); } } const float RPrev=std::max(2.0f,Radii.IsValidIndex(Join-1)?Radii[Join-1]:T.RadiusA); const float RHere=std::max(2.0f,Radii.IsValidIndex(Join)?Radii[Join]:T.RadiusA); const float RNext=std::max(2.0f,Radii.IsValidIndex(Join+1)?Radii[Join+1]:T.RadiusB); Stats.LegacySegmentRadiusStep=std::max(Stats.LegacySegmentRadiusStep, static_cast(std::abs(std::max(RPrev,RHere)-std::max(RHere,RNext)))); if(FloorProfiles.IsValidIndex(Join-1)&&FloorProfiles.IsValidIndex(Join)) { Stats.MaxCapsuleJoinFloorDelta=std::max(Stats.MaxCapsuleJoinFloorDelta, static_cast(std::abs(FloorProfiles[Join-1].EndFloorZ -FloorProfiles[Join].StartFloorZ))); const FVector PrevD=Points[Join]-Points[Join-1]; const FVector NextD=Points[Join+1]-Points[Join]; const double PrevXY=std::sqrt(PrevD.X*PrevD.X+PrevD.Y*PrevD.Y); const double NextXY=std::sqrt(NextD.X*NextD.X+NextD.Y*NextD.Y); const double RawPrevSlope=PrevXY>1.0e-6 ?(FloorProfiles[Join-1].EndFloorZ-FloorProfiles[Join-1].StartFloorZ)/PrevXY:0.0; const double RawNextSlope=NextXY>1.0e-6 ?(FloorProfiles[Join].EndFloorZ-FloorProfiles[Join].StartFloorZ)/NextXY:0.0; const double RawSlopeDelta=std::abs(RawPrevSlope-RawNextSlope); if(FloorProfiles[Join-1].bLedgeTransition||FloorProfiles[Join].bLedgeTransition) { ++Stats.MarkedFloorSlopeJoins; Stats.MaxMarkedFloorSlopeDelta=std::max(Stats.MaxMarkedFloorSlopeDelta,RawSlopeDelta); } else { Stats.MaxRawUnmarkedFloorSlopeDelta=std::max(Stats.MaxRawUnmarkedFloorSlopeDelta,RawSlopeDelta); } } ++Stats.CapsuleJoinCount; } if(Points.Num()>1) { const size_t SegmentPrimitiveStart=Primitives.size(); for(int32 I=0;I+1(std::abs(R1-R0))); const FVector Segment=Points[I+1]-Points[I]; const double SegmentLength=std::sqrt(Segment.X*Segment.X+Segment.Y*Segment.Y+Segment.Z*Segment.Z); const double Extension=ContinuousJoinExtension(R0,R1,SegmentLength); const float ZA=RawFloor[static_cast(I)]; const float ZB=RawFloor[static_cast(I+1)]; AddTaperedCapsule(Points[I],Points[I+1],R0,R1,true,false,true,ZA,ZB, I>0?Extension:0.0,I+2(I)]) { SetCurvedFloorClip(Primitives.back(),Points[I],Points[I+1],ZA,ZB, FloorTangents[static_cast(I)],FloorTangents[static_cast(I+1)]); ++Stats.CurvedFloorSegments; Stats.MaxFloorCurveDeviation=std::max(Stats.MaxFloorCurveDeviation, Primitives.back().FloorCurveMaxDeviation); Stats.MaxFloorProfileEndpointValueError=std::max( Stats.MaxFloorProfileEndpointValueError, Primitives.back().FloorCurveEndpointValueError); Stats.MaxFloorProfileEndpointSlopeError=std::max( Stats.MaxFloorProfileEndpointSlopeError, Primitives.back().FloorCurveEndpointSlopeError); } FitCapsuleRadiiAtEndpoints(Primitives.back(),Points[I],R0,Points[I+1],R1); SetCapsuleRadiusProfile(Primitives.back(),Points[I],R0,RadiusSlope0, Points[I+1],R1,RadiusSlope1); } for(int32 Join=1;Join+1(Join-1)]; const FPrimitive& Next=Primitives[SegmentPrimitiveStart+static_cast(Join)]; const float PreviousEnd=CapsuleRadiusAt(Previous,Points[Join]); const float NextStart=CapsuleRadiusAt(Next,Points[Join]); Stats.MaxCapsuleJoinRadiusDelta=std::max(Stats.MaxCapsuleJoinRadiusDelta, static_cast(std::abs(PreviousEnd-NextStart))); Stats.MaxCapsuleJoinRadiusSlopeDelta=std::max( Stats.MaxCapsuleJoinRadiusSlopeDelta, std::abs(Previous.CapsuleRadiusSlopeEnd-Next.CapsuleRadiusSlopeStart)); if(!MarkedFloorSegments[static_cast(Join-1)] &&!MarkedFloorSegments[static_cast(Join)]) { const double TangentDelta=Previous.bCurvedFloorClip&&Next.bCurvedFloorClip ?std::abs(Previous.FloorCurveTangentEnd-Next.FloorCurveTangentStart) :std::abs(FloorSlopeAtEndpoint(Previous,true)-FloorSlopeAtEndpoint(Next,false)); const double DirectionalSlopeChange=std::abs(FloorSlopeAtEndpoint(Previous,true) -FloorSlopeAtEndpoint(Next,false)); Stats.MaxCapsuleJoinFloorSlopeDelta=std::max( Stats.MaxCapsuleJoinFloorSlopeDelta,TangentDelta); Stats.MaxUnmarkedFloorSlopeDelta=std::max( Stats.MaxUnmarkedFloorSlopeDelta,TangentDelta); Stats.MaxUnmarkedFloorDirectionSlopeDelta=std::max( Stats.MaxUnmarkedFloorDirectionSlopeDelta,DirectionalSlopeChange); } } } else AddCapsule(T.EndpointA,T.EndpointB,std::max(T.RadiusA,T.RadiusB),true); } ActiveBlendRadius=RoomBlendRadius; ActiveFloorBlendRadius=0.0; for(const FCachedPit& P:Cache.Pits) AddCapsule(FVector(P.CenterX,P.CenterY,P.TopZ-P.Depth), FVector(P.CenterX,P.CenterY,P.TopZ),std::max(2.0f,P.Radius),true); for(const FCachedChimney& C:Cache.Chimneys) AddCapsule(FVector(C.CenterX,C.CenterY,C.BottomZ), FVector(C.CenterX,C.CenterY,C.BottomZ+C.Height),std::max(2.0f,C.Radius),true); for(const FCachedColumn& C:Cache.Columns) { FVector Center(C.CenterX,C.CenterY,(LayerRanges[0].Lo+LayerRanges[0].Hi)*0.5); const double HalfZ=std::max(8.0,(LayerRanges[0].Hi-LayerRanges[0].Lo)*0.48); AddEllipsoid(Center,{std::max(2.0f,C.Radius),std::max(2.0f,C.Radius),HalfZ}, {1,0,0},{0,1,0},{0,0,1},false); } // Reproduce the owner's finite, top-anchored origin room in every strate. // Reproduire dans chaque strate la salle d'origine finie et ancrée en haut. AddOriginLandingRoom(FieldForStrate(Slot.StrateIndex)); } if(!bBuildPassages)return; const double PassageJoinBlend=ActiveBlendRadius;ActiveBlendRadius=JoinBlendRadius; ActiveFloorBlendRadius=FloorClipBlendRadius; for(const FVoxelPassage& P:Owner->Manager->GetPassages()) { ++Stats.PassageCount; const TArray& Points=P.ControlPoints; const TArray& Radii=P.ControlRadii; const size_t PassagePrimitiveStart=Primitives.size(); std::vector RawFloor(static_cast(Points.Num())); std::vector MarkedFloorSegments(Points.Num()>0?static_cast(Points.Num()-1):0,0); for(int32 I=0;I(I)]=static_cast(Points[I].Z -SmoothedControlRadius(Radii,I,P.Radius,6.0f)); if(P.NativeFloorProfileZ.Num()==Points.Num()) for(int32 I=0;I(I)]=P.NativeFloorProfileZ[I]; for(int32 I=0;I(I)]+=static_cast(FloorSurfaceRelief( Points[I].X,Points[I].Y,static_cast(Owner->Seed))); const std::vector FloorTangents=BuildFloorTangents( Points,RawFloor,MarkedFloorSegments); for(int32 Join=1;Join+1(std::abs(std::max(RPrev,RHere)-std::max(RHere,RNext)))); if(P.NativeFloorProfileZ.IsValidIndex(Join-1)&&P.NativeFloorProfileZ.IsValidIndex(Join)) { const FVector PrevD=Points[Join]-Points[Join-1]; const FVector NextD=Points[Join+1]-Points[Join]; const double PrevXY=std::sqrt(PrevD.X*PrevD.X+PrevD.Y*PrevD.Y); const double NextXY=std::sqrt(NextD.X*NextD.X+NextD.Y*NextD.Y); const double PrevSlope=PrevXY>1.0e-6 ?(P.NativeFloorProfileZ[Join]-P.NativeFloorProfileZ[Join-1])/PrevXY:0.0; const double NextSlope=NextXY>1.0e-6 ?(P.NativeFloorProfileZ[Join+1]-P.NativeFloorProfileZ[Join])/NextXY:0.0; const double RawSlopeDelta=std::abs(PrevSlope-NextSlope); Stats.MaxRawUnmarkedFloorSlopeDelta=std::max(Stats.MaxRawUnmarkedFloorSlopeDelta,RawSlopeDelta); } ++Stats.CapsuleJoinCount; } if(Points.Num()>1) { for(int32 I=0;I+1(std::abs(RadiusB-RadiusA))); const FVector Segment=Points[I+1]-Points[I]; const double SegmentLength=std::sqrt(Segment.X*Segment.X+Segment.Y*Segment.Y+Segment.Z*Segment.Z); const float ZA=RawFloor[static_cast(I)]; const float ZB=RawFloor[static_cast(I+1)]; const double Extension=ContinuousJoinExtension(RadiusA,RadiusB,SegmentLength); const double CoreExtension=ContinuousJoinExtension(std::min(2.5f,RadiusA*0.45f), std::min(2.5f,RadiusB*0.45f),SegmentLength); const double ExtendStart=I>0?Extension:0.0,ExtendEnd=I+20?CoreExtension:0.0,CoreExtendEnd=I+2(I)],FloorTangents[static_cast(I+1)]); ++Stats.CurvedFloorSegments; Stats.MaxFloorCurveDeviation=std::max(Stats.MaxFloorCurveDeviation, Primitives.back().FloorCurveMaxDeviation); Stats.MaxFloorProfileEndpointValueError=std::max( Stats.MaxFloorProfileEndpointValueError, Primitives.back().FloorCurveEndpointValueError); Stats.MaxFloorProfileEndpointSlopeError=std::max( Stats.MaxFloorProfileEndpointSlopeError, Primitives.back().FloorCurveEndpointSlopeError); FitCapsuleRadiiAtEndpoints(Primitives.back(),Points[I],RadiusA,Points[I+1],RadiusB); SetCapsuleRadiusProfile(Primitives.back(),Points[I],RadiusA,RadiusSlopeA, Points[I+1],RadiusB,RadiusSlopeB); const float CoreRadiusA=std::min(2.5f,RadiusA*0.45f); const float CoreRadiusB=std::min(2.5f,RadiusB*0.45f); const double CoreSlopeA=CoreRadiusA<2.5f-1.0e-4?RadiusSlopeA*0.45:0.0; const double CoreSlopeB=CoreRadiusB<2.5f-1.0e-4?RadiusSlopeB*0.45:0.0; // Keep a narrow, un-clipped air core through every authored control segment. // The larger outer tube owns the D-section floor; its interpolated floor curve // can bow above the path on sharp drops, so clipping this safety bore would // close the passage precisely where Cascading/Worm need the continuity repair. // Garder un petit noyau d'air sans coupe de plancher dans chaque segment. // Le tube large porte le plancher en D; sa courbe peut depasser l'axe dans les // chutes brusques et fermer la route si ce noyau herite de la meme coupe. AddTaperedCapsule(Points[I],Points[I+1],CoreRadiusA, CoreRadiusB,true,true,false,ZA,ZB, CoreExtendStart,CoreExtendEnd); FitCapsuleRadiiAtEndpoints(Primitives.back(),Points[I],CoreRadiusA, Points[I+1],CoreRadiusB); SetCapsuleRadiusProfile(Primitives.back(),Points[I],CoreRadiusA,CoreSlopeA, Points[I+1],CoreRadiusB,CoreSlopeB); } for(int32 Join=1;Join+1(Join-1)*2; const size_t NextBase=PassagePrimitiveStart+static_cast(Join)*2; for(size_t Tube=0;Tube<2;++Tube) { const FPrimitive& Previous=Primitives[PreviousBase+Tube]; const FPrimitive& Next=Primitives[NextBase+Tube]; const float PreviousEnd=CapsuleRadiusAt(Previous,Points[Join]); const float NextStart=CapsuleRadiusAt(Next,Points[Join]); Stats.MaxCapsuleJoinRadiusDelta=std::max(Stats.MaxCapsuleJoinRadiusDelta, static_cast(std::abs(PreviousEnd-NextStart))); Stats.MaxCapsuleJoinRadiusSlopeDelta=std::max( Stats.MaxCapsuleJoinRadiusSlopeDelta, std::abs(Previous.CapsuleRadiusSlopeEnd-Next.CapsuleRadiusSlopeStart)); if(Tube==0) { const double TangentDelta=Previous.bCurvedFloorClip&&Next.bCurvedFloorClip ?std::abs(Previous.FloorCurveTangentEnd-Next.FloorCurveTangentStart) :std::abs(FloorSlopeAtEndpoint(Previous,true)-FloorSlopeAtEndpoint(Next,false)); const double DirectionalSlopeChange=std::abs(FloorSlopeAtEndpoint(Previous,true) -FloorSlopeAtEndpoint(Next,false)); Stats.MaxCapsuleJoinFloorSlopeDelta=std::max( Stats.MaxCapsuleJoinFloorSlopeDelta,TangentDelta); Stats.MaxUnmarkedFloorSlopeDelta=std::max( Stats.MaxUnmarkedFloorSlopeDelta,TangentDelta); Stats.MaxUnmarkedFloorDirectionSlopeDelta=std::max( Stats.MaxUnmarkedFloorDirectionSlopeDelta,DirectionalSlopeChange); } } } } else { AddCapsule(P.UpperPoint,P.LowerPoint,std::max(6.0f,P.Radius),true,true); Primitives.back().bPassageSurface=true; } AddLanding(P.UpperLanding,true); AddLanding(P.LowerLanding,true); } ActiveBlendRadius=PassageJoinBlend;ActiveFloorBlendRadius=0.0; } const FLayerField* FieldForStrate(int32 StrateIndex) const { for(const FLayerField& Field:LayerFields) if(Field.StrateIndex==StrateIndex)return &Field; return nullptr; } void AddOriginLandingRoom(const FLayerField* Field) { if(Field==nullptr||Owner==nullptr||Owner->Settings==nullptr)return; const double Radius=Owner->Settings->OriginSpineRadius; if(!(Radius>0.0)||!std::isfinite(Radius))return; // These constants mirror VoxelPassageGeometry in legacy voxel units. // Ces constantes reprennent VoxelPassageGeometry en voxels historiques. constexpr double MinimumHalfWidth=12.0; constexpr double MinimumRoomHeight=24.0; constexpr double PlayerHeight=14.08; constexpr double Headroom=8.0; constexpr double FloorThickness=6.0; constexpr double SealSafetyMargin=8.0; constexpr double RoomRounding=3.0; constexpr double ShaftFloorClearance=8.0; const double SafeRadius=std::max(std::abs(Radius),2.0); const double HalfWidth=std::max({MinimumHalfWidth,SafeRadius+4.0, SafeRadius+ShaftFloorClearance}); const double DesiredHeight=std::max({MinimumRoomHeight,PlayerHeight+Headroom, 2.0*SafeRadius+4.0}); const double Seal=std::max(0.0,static_cast(Field->Params.BoundarySealThickness)); const double InnerBottom=static_cast(Field->Lo)+Seal; const double InnerTop=static_cast(Field->Hi)-Seal; const double MinimumFloor=InnerBottom+FloorThickness+SealSafetyMargin; const double MaximumFloor=InnerTop-DesiredHeight-SealSafetyMargin; if(!(InnerTop>InnerBottom)||MaximumFloor0.0f)||!(L.CeilingZ>L.FloorZ))return; const double HalfZ=std::max(2.0,(L.CeilingZ-L.FloorZ)*0.5); const FVector Center(L.StandingPoint.X,L.StandingPoint.Y,(L.CeilingZ+L.FloorZ)*0.5); const double Width=std::max(6.0f,L.HalfWidth); AddRoundedBox(Center,{Width,Width,HalfZ},2.0,true); if(bCritical) { const size_t Before=Primitives.size(); AddEllipsoid(Center,{2.5,2.5,HalfZ},{1,0,0},{0,1,0},{0,0,1},true); for(size_t I=Before;I(Owner->Seed); auto Unit=[](uint32 H){return static_cast(FStandaloneResearchTunnels::Unit(H));}; for(int32 CY=CY0;CY<=CY1;++CY)for(int32 CX=CX0;CX<=CX1;++CX) for(int32 Axis=0;Axis<2;++Axis) { const uint32 H=FStandaloneResearchTunnels::EdgeHash(CX,CY,Axis,Seed); if(Unit(H^0x68E31DA4u)>0.56)continue; const int32 NX=CX+(Axis==0?1:0),NY=CY+(Axis==1?1:0); auto Cell=[&](int32 X,int32 Y,double& OX,double& OY) { const uint32 CH=FStandaloneResearchTunnels::EdgeHash(X,Y,3,Seed); OX=(X+0.5)*Pitch+(Unit(CH^0xA511E9B3u)-0.5)*Pitch*0.18; OY=(Y+0.5)*Pitch+(Unit(CH^0x63D83595u)-0.5)*Pitch*0.18; }; double AX,AY,BX,BY;Cell(CX,CY,AX,AY);Cell(NX,NY,BX,BY); const double DX=BX-AX,DY=BY-AY,L2=DX*DX+DY*DY,L=std::sqrt(std::max(1.0e-8,L2)); const double NXrm=-DY/L,NYrm=DX/L; const double BA=(Unit(H^0xB5297A4Du)*2.0-1.0)*Pitch*0.16; const double BB=(Unit(H^0x1B56C4E9u)*2.0-1.0)*Pitch*0.16; const std::array P0={AX,AY,FStandaloneResearchTunnels::WorldCenterZVoxels}; const std::array C1={AX+DX/3.0+NXrm*BA,AY+DY/3.0+NYrm*BA,-80.0}; const std::array C2={AX+DX*2.0/3.0+NXrm*BB,AY+DY*2.0/3.0+NYrm*BB,-80.0}; const std::array P3={BX,BY,FStandaloneResearchTunnels::WorldCenterZVoxels}; const uint32 PhaseHash=H^0xA511E9B3u; const double Phase=Unit(PhaseHash)*6.2831853071795864769; auto Bezier=[&](double T) { const double O=1.0-T; std::array P{}; for(int32 A=0;A<3;++A) P[A]=O*O*O*P0[A]+3*O*O*T*C1[A]+3*O*T*T*C2[A]+T*T*T*P3[A]; const double R=RadiusBase*(0.82+0.36*Unit(H^0x63D83595u)) *(1.0+0.22*VoxelMath::DetSin(static_cast(6.2831853071795864769*T+Phase))); const double VW=4.0*VoxelMath::DetSin(static_cast(3.141592653589793*T)) *VoxelMath::DetSin(static_cast(6.2831853071795864769*T+Phase)); P[2]+=VW; return std::pair{P,R}; }; constexpr int32 Segments=12; auto Last=Bezier(0.0); for(int32 I=1;I<=Segments;++I) { const auto Next=Bezier(static_cast(I)/Segments); const FVector A(Last.first[0],Last.first[1],Last.first[2]); const FVector B(Next.first[0],Next.first[1],Next.first[2]); const FVector Segment=B-A; const double SegmentLength=std::sqrt(Segment.X*Segment.X+Segment.Y*Segment.Y+Segment.Z*Segment.Z); const double Extension=ContinuousJoinExtension(static_cast(Last.second), static_cast(Next.second),SegmentLength); AddTaperedCapsule(A,B,static_cast(Last.second),static_cast(Next.second), true,false,false,0.0,0.0,I>1?Extension:0.0,I=FarSpatialBinSize?8u:24u); SpatialBins.reserve(std::max(Primitives.size()*BinsPerPrimitive,1u)); if(!bBuildAxisBins) { CriticalAirBins.reserve(Primitives.size()/16+1); } if(bBuildAxisBins)for(int32 Axis=0;Axis<3;++Axis) { FAxisBins& B=AxisBins[Axis]; const int32 A=(Axis+1)%3,C=(Axis+2)%3; B.MinA=FloorDiv(Min[A],LineBinSize);B.MinB=FloorDiv(Min[C],LineBinSize); const int32 MaxA=FloorDiv(Max[A],LineBinSize),MaxB=FloorDiv(Max[C],LineBinSize); B.CountA=MaxA-B.MinA+1;B.CountB=MaxB-B.MinB+1; B.Items.resize(static_cast(B.CountA)*B.CountB); for(uint32 Id=0;IdA1||B0>B1)continue; for(int32 IB=B0;IB<=B1;++IB)for(int32 IA=A0;IA<=A1;++IA) B.Items[static_cast(IB-B.MinB)*B.CountA+(IA-B.MinA)].push_back(Id); } } const auto EnumeratePrimitiveBins=[&](uint32 Id,auto&& AddSpatial, auto&& AddCritical,auto&& AddUnbinned) { const FPrimitive& P=Primitives[Id]; const bool bCriticalAir=P.bCarve&&P.bCritical; const int32 X0=FloorDiv(P.Min[0]-DetailBand,BinResolution),X1=FloorDiv(P.Max[0]+DetailBand,BinResolution); const int32 Y0=FloorDiv(P.Min[1]-DetailBand,BinResolution),Y1=FloorDiv(P.Max[1]+DetailBand,BinResolution); const int32 Z0=FloorDiv(P.Min[2]-DetailBand,BinResolution),Z1=FloorDiv(P.Max[2]+DetailBand,BinResolution); const int64 BoundingBinCount=static_cast(X1-X0+1)*(Y1-Y0+1)*(Z1-Z0+1); if(BoundingBinCount>100000) { // Long diagonal passages have a huge AABB even though their capsule is narrow. // Rasterize the centerline with a conservative bin halo instead of dropping the // primitive from analytic queries outside the packed near region. // Les longues galeries diagonales ont une grande AABB mais un tube fin. // Echantillonner l'axe avec une marge prudente garde les requetes analytiques. if(!P.bCapsule) { // Large non-capsule bounds are deliberately omitted from point-query bins, // but interval proofs still need a complete candidate set. Keep these IDs // separate so block and tile proofs cannot mistake an omitted carve for air-free rock. AddUnbinned(Id); return; } const double BinWidth=static_cast(BinResolution); const double AxisStep=std::max(1.0,BinWidth*0.5); const int32 AxisSamples=std::max(1,static_cast(std::ceil( P.CapsuleAxisLength/AxisStep))); const double CapsuleRadius=std::max(P.CapsuleStartRadius,P.CapsuleEndRadius); const int32 Halo=std::clamp(static_cast(std::ceil( (CapsuleRadius+DetailBand)/BinWidth))+1,1,8); std::vector PrimitiveBins; const int64 HaloWidth=2*Halo+1; const uint64 Reserve=std::min( static_cast(AxisSamples+1)*HaloWidth*HaloWidth*HaloWidth, 1000000ull); PrimitiveBins.reserve(static_cast(Reserve)); for(int32 Sample=0;Sample<=AxisSamples;++Sample) { const double T=static_cast(Sample)/AxisSamples; const int32 CX=static_cast(std::floor(P.CapsuleStartWorld[0] +P.CapsuleAxisDelta[0]*T)); const int32 CY=static_cast(std::floor(P.CapsuleStartWorld[1] +P.CapsuleAxisDelta[1]*T)); const int32 CZ=static_cast(std::floor(P.CapsuleStartWorld[2] +P.CapsuleAxisDelta[2]*T)); const int32 BX=FloorDiv(CX,BinResolution),BY=FloorDiv(CY,BinResolution); const int32 BZ=FloorDiv(CZ,BinResolution); for(int32 DZ=-Halo;DZ<=Halo;++DZ) for(int32 DY=-Halo;DY<=Halo;++DY) for(int32 DX=-Halo;DX<=Halo;++DX) PrimitiveBins.push_back({BX+DX,BY+DY,BZ+DZ}); } std::sort(PrimitiveBins.begin(),PrimitiveBins.end(),[](const FBinKey& A,const FBinKey& B) { if(A.X!=B.X)return A.X Parents; std::unordered_map ByCell; const auto& Layout=Owner->Manager->GetLayout(); const uint32 Seed=static_cast(Owner->Seed); const auto Key=[](int32 GX,int32 GY,int32 Strate) {return (static_cast(static_cast(GX+524288)&0xFFFFFu)<<44) |(static_cast(static_cast(GY+524288)&0xFFFFFu)<<24) |(static_cast(static_cast(Strate)&0xFFFFu)<<8);}; auto Hash=[](uint32 X) {X^=X>>16;X*=0x7FEB352Du;X^=X>>15;X*=0x846CA68Bu;X^=X>>16;return X;}; auto Unit=[&](uint32 H){return static_cast(Hash(H)>>8)*(1.0/16777216.0);}; for(const FStrateSlot& Slot:Layout) { const double LayoutChunkWidth=GetResolvedLayoutChunkWidthVoxels(); const double ZBottom=static_cast(Slot.BottomChunkZ)*LayoutChunkWidth; const double ZTop=static_cast(Slot.TopChunkZ+1)*LayoutChunkWidth; for(int32 GY=GY0;GY<=GY1;++GY)for(int32 GX=GX0;GX<=GX1;++GX) { uint32 H=Hash(Seed^static_cast(GX)*0x9E3779B9u ^static_cast(GY)*0x85EBCA6Bu ^static_cast(Slot.StrateIndex)*0xC2B2AE35u); const double X=GX*static_cast(ParentPitch)+ParentPitch*0.5 +(Unit(H^0xA511E9B3u)-0.5)*192.0; const double Y=GY*static_cast(ParentPitch)+ParentPitch*0.5 +(Unit(H^0x63D83595u)-0.5)*192.0; const double Rx=300.0+Unit(H^0xB5297A4Du)*100.0; const double Ry=280.0+Unit(H^0x1B56C4E9u)*120.0; const double Rz=100.0+Unit(H^0x68E31DA4u)*60.0; if(X+Rx>=Min[0]&&X-Rx<=Max[0]&&Y+Ry>=Min[1]&&Y-Ry<=Max[1])continue; const double Z=ZBottom+(ZTop-ZBottom)*(0.35+0.30*Unit(H^0x27D4EB2Fu)); FParent P{GX,GY,Slot.StrateIndex,FVector(X,Y,Z),Rx,Ry,Rz}; const size_t Index=Parents.size();Parents.push_back(P); ByCell.emplace(Key(GX,GY,Slot.StrateIndex),Index); } } struct FParentGeometry { FPrimitive Ellipsoid; std::vector Connectors; std::vector Segments; }; std::vector ParentGeometry(Parents.size()); ParallelLines(Parents.size(),[&](size_t ParentIndex,int32) { const FParent& P=Parents[ParentIndex]; FParentGeometry& Geometry=ParentGeometry[ParentIndex]; FStandaloneShapeWorld Builder; Builder.ActiveBlendRadius=ActiveBlendRadius; Builder.AddEllipsoid(P.Center,{P.Rx,P.Ry,P.Rz},{1,0,0},{0,1,0},{0,0,1},true); Geometry.Ellipsoid=std::move(Builder.Primitives.back()); Builder.Primitives.clear(); Geometry.Connectors.reserve(2); Geometry.Segments.reserve(2); for(const auto [DX,DY]:std::array,2>{{{1,0},{0,1}}}) { const auto It=ByCell.find(Key(P.GX+DX,P.GY+DY,P.Strate)); if(It==ByCell.end())continue; const FVector A(P.Center.X,P.Center.Y,P.Center.Z); const FVector B=Parents[It->second].Center; const int32 MinX=static_cast(std::floor(std::min(A.X,B.X)-64.0)); const int32 MaxX=static_cast(std::ceil(std::max(A.X,B.X)+64.0)); const int32 MinY=static_cast(std::floor(std::min(A.Y,B.Y)-64.0)); const int32 MaxY=static_cast(std::ceil(std::max(A.Y,B.Y)+64.0)); if(MaxX>=Min[0]&&MinX<=Max[0]&&MaxY>=Min[1]&&MinY<=Max[1])continue; Builder.AddTaperedCapsule(A,B,64.0f,64.0f,true); Builder.Primitives.back().BlendRadius=0.0; Geometry.Connectors.push_back(std::move(Builder.Primitives.back())); Builder.Primitives.clear(); Geometry.Segments.push_back({A,B,64.0f}); } },&Stats.FarParentGeometryLines); size_t ConnectorCount=0; for(const FParentGeometry& Geometry:ParentGeometry) ConnectorCount+=Geometry.Connectors.size(); Primitives.reserve(Primitives.size()+Parents.size()+ConnectorCount); FarConnectorSegments.reserve(FarConnectorSegments.size()+ConnectorCount); for(FParentGeometry& Geometry:ParentGeometry) Primitives.push_back(std::move(Geometry.Ellipsoid)); for(FParentGeometry& Geometry:ParentGeometry) for(FPrimitive& Connector:Geometry.Connectors) Primitives.push_back(std::move(Connector)); for(FParentGeometry& Geometry:ParentGeometry) FarConnectorSegments.insert(FarConnectorSegments.end(), Geometry.Segments.begin(),Geometry.Segments.end()); BuildFarConnectorSpatialBins(); if(!Parents.empty()) std::printf("[FarParents] pitch=%d rooms=%zu links=%llu clip=(%d,%d)..(%d,%d)\n", ParentPitch,Parents.size(),static_cast(Primitives.size()), ClipMin[0],ClipMin[1],ClipMax[0],ClipMax[1]); } void BuildFarConnectorSpatialBins() { constexpr int32 BinSize=2048; FarConnectorSpatialBins.clear(); FarConnectorSpatialBins.reserve(FarConnectorSegments.size()); for(uint32 Index=0;Index(std::floor( (std::min(Connector.Start.X,Connector.End.X)-Connector.Radius)/BinSize)); const int32 MaxX=static_cast(std::floor( (std::max(Connector.Start.X,Connector.End.X)+Connector.Radius)/BinSize)); const int32 MinY=static_cast(std::floor( (std::min(Connector.Start.Y,Connector.End.Y)-Connector.Radius)/BinSize)); const int32 MaxY=static_cast(std::floor( (std::max(Connector.Start.Y,Connector.End.Y)+Connector.Radius)/BinSize)); const int32 MinZ=static_cast(std::floor( (std::min(Connector.Start.Z,Connector.End.Z)-Connector.Radius)/BinSize)); const int32 MaxZ=static_cast(std::floor( (std::max(Connector.Start.Z,Connector.End.Z)+Connector.Radius)/BinSize)); for(int32 Z=MinZ;Z<=MaxZ;++Z)for(int32 Y=MinY;Y<=MaxY;++Y) for(int32 X=MinX;X<=MaxX;++X) FarConnectorSpatialBins[{X,Y,Z}].push_back(Index); } } const std::vector& GetLineCandidates(int32 Axis,int32 A,int32 B) const { static const std::vector Empty; const FAxisBins& G=AxisBins[Axis]; const int32 IA=FloorDiv(A,LineBinSize)-G.MinA,IB=FloorDiv(B,LineBinSize)-G.MinB; if(IA<0||IB<0||IA>=G.CountA||IB>=G.CountB)return Empty; return G.Items[static_cast(IB)*G.CountA+IA]; } FBinKey PointBin(double X,double Y,double Z) const {return {FloorDiv(static_cast(std::floor(X)),ActiveSpatialBinSize),FloorDiv(static_cast(std::floor(Y)),ActiveSpatialBinSize),FloorDiv(static_cast(std::floor(Z)),ActiveSpatialBinSize)};} bool ShapeContains(const FPrimitive& P,int32 X,int32 Y,int32 Z) const { bool bInside=false; if(P.bCapsule) { const double Position[3]={static_cast(X),static_cast(Y),static_cast(Z)}; double V[3],D[3];double D2=0.0,VD=0.0,V2=0.0; for(int32 A=0;A<3;++A) { const double Start=P.CapsuleStartSub[A]/static_cast(SubPerVoxel); const double End=P.CapsuleEndSub[A]/static_cast(SubPerVoxel); V[A]=Position[A]-Start;D[A]=End-Start; D2+=D[A]*D[A];VD+=V[A]*D[A];V2+=V[A]*V[A]; } const double R0=P.CapsuleStartRadius,R1=P.CapsuleEndRadius,DR=R1-R0; const double QA=D2-DR*DR,QB=-2.0*(VD+R0*DR),QC=V2-R0*R0; double Minimum=std::min(QC,QA+QB+QC); if(QA>1.0e-12) { const double T=std::clamp(-QB/(2.0*QA),0.0,1.0); Minimum=std::min(Minimum,(QA*T+QB)*T+QC); } bInside=Minimum<=0.0; } else if(P.bRoundedBox) { const double D[3]={std::abs(X-P.CenterSub[0]/16.0)-P.RoundedHalfExtent[0], std::abs(Y-P.CenterSub[1]/16.0)-P.RoundedHalfExtent[1], std::abs(Z-P.CenterSub[2]/16.0)-P.RoundedHalfExtent[2]}; const double Ox=std::max(D[0],0.0),Oy=std::max(D[1],0.0),Oz=std::max(D[2],0.0); const double Outside=std::sqrt(Ox*Ox+Oy*Oy+Oz*Oz); const double Inside=std::min(std::max({D[0],D[1],D[2]}),0.0); bInside=Outside+Inside-P.Rounding<=0.0; } else { const int32 World[3]={X,Y,Z};int64 D[3]; for(int32 A=0;A<3;++A)D[A]=static_cast(World[A])*SubPerVoxel-P.CenterSub[A]; int64 Q=0; for(int32 I=0;I<3;++I)for(int32 J=0;J<3;++J)Q+=P.Q[I][J]*D[I]*D[J]; bInside=Q<=static_cast(QScale); } if(!bInside)return false; if(P.bHasFloorClip) { const double Floor=FloorAt(P,X,Y); if(P.FloorBlendRadius<=0.0)return Z>=Floor; return SmoothMaxDistance(ShapeDistance(P,X,Y,Z),static_cast(Floor-Z), static_cast(P.FloorBlendRadius))<=0.0f; } return true; } static float CapsuleRadiusAtT(const FPrimitive& P,double T,double AxisLength) { T=std::clamp(T,0.0,1.0); if(!P.bSmoothCapsuleRadius) return static_cast(P.CapsuleStartRadius +(P.CapsuleEndRadius-P.CapsuleStartRadius)*T); const double T0=P.CapsuleProfileStartT,T1=P.CapsuleProfileEndT; if(T(P.CapsuleProfileStartRadius +P.CapsuleRadiusSlopeStart*AxisLength*(T-T0)); if(T>T1)return static_cast(P.CapsuleProfileEndRadius +P.CapsuleRadiusSlopeEnd*AxisLength*(T-T1)); const double SpanT=std::max(1.0e-9,T1-T0); const double U=std::clamp((T-T0)/SpanT,0.0,1.0),U2=U*U,U3=U2*U; const double SpanDistance=AxisLength*SpanT; const double H00=2.0*U3-3.0*U2+1.0; const double H10=U3-2.0*U2+U; const double H01=-2.0*U3+3.0*U2; const double H11=U3-U2; return static_cast(H00*P.CapsuleProfileStartRadius +H10*SpanDistance*P.CapsuleRadiusSlopeStart +H01*P.CapsuleProfileEndRadius +H11*SpanDistance*P.CapsuleRadiusSlopeEnd); } float ShapeDistance(const FPrimitive& P,double X,double Y,double Z) const { if(P.bCapsule) { const double Position[3]={X,Y,Z}; double V[3],D[3],D2=0.0,VD=0.0; for(int32 A=0;A<3;++A) { V[A]=Position[A]-P.CapsuleStartWorld[A]; D[A]=P.CapsuleAxisDelta[A]; VD+=V[A]*D[A]; } D2=P.CapsuleAxisLengthSquared; const double T=D2>1.0e-12?std::clamp(VD/D2,0.0,1.0):0.0; double DistanceSquared=0.0; for(int32 A=0;A<3;++A) {const double E=V[A]-T*D[A];DistanceSquared+=E*E;} const double Radius=CapsuleRadiusAtT(P,T,P.CapsuleAxisLength); return static_cast(std::sqrt(DistanceSquared)-Radius); } if(P.bRoundedBox) { const double D[3]={std::abs(X-P.CenterSub[0]/16.0)-P.RoundedHalfExtent[0], std::abs(Y-P.CenterSub[1]/16.0)-P.RoundedHalfExtent[1], std::abs(Z-P.CenterSub[2]/16.0)-P.RoundedHalfExtent[2]}; const double Ox=std::max(D[0],0.0),Oy=std::max(D[1],0.0),Oz=std::max(D[2],0.0); const double Outside=std::sqrt(Ox*Ox+Oy*Oy+Oz*Oz); const double Inside=std::min(std::max({D[0],D[1],D[2]}),0.0); return static_cast(Outside+Inside-P.Rounding); } const double D[3]={X-P.CenterSub[0]/16.0,Y-P.CenterSub[1]/16.0,Z-P.CenterSub[2]/16.0}; double Q=0.0; for(int32 I=0;I<3;++I)for(int32 J=0;J<3;++J)Q+=static_cast(P.Q[I][J])/QScale*D[I]*D[J]*256.0; return static_cast((std::sqrt(std::max(0.0,Q))-1.0)*P.MinRadius); } bool SolveLine(const FPrimitive& P,int32 Axis,const int32 Fixed[3],FInterval& Out) const { const int32 A=(Axis+1)%3,B=(Axis+2)%3; const int32 Lo=P.Min[Axis],Hi=P.Max[Axis]; if(Fixed[A]P.Max[A]||Fixed[B]P.Max[B])return false; int32 L=Lo,H=Hi; if(!P.bRoundedBox&&!P.bCapsule) { double CoefA=static_cast(P.Q[Axis][Axis]); double CoefB=0.0,CoefC=-static_cast(QScale); int64 D[3];D[Axis]=0; for(int32 J=0;J<3;++J)if(J!=Axis)D[J]=static_cast(Fixed[J])*SubPerVoxel-P.CenterSub[J]; for(int32 J=0;J<3;++J)if(J!=Axis) { CoefB+=2.0*P.Q[Axis][J]*D[J]; for(int32 K=0;K<3;++K)if(K!=Axis)CoefC+=static_cast(P.Q[J][K])*D[J]*D[K]; } if(!(CoefA>0.0))return false; const double Disc=CoefB*CoefB-4.0*CoefA*CoefC; if(Disc<0.0)return false; const double S=std::sqrt(Disc); const double U0=(-CoefB-S)/(2.0*CoefA),U1=(-CoefB+S)/(2.0*CoefA); const double T0=(U0+P.CenterSub[Axis])/SubPerVoxel; const double T1=(U1+P.CenterSub[Axis])/SubPerVoxel; L=std::max(Lo,static_cast(std::floor(T0))-1); H=std::min(Hi,static_cast(std::ceil(T1))+1); } if(L>H)return false; if(P.bCarve&&P.bHasFloorClip&&(!P.bCurvedFloorClip||Axis==2)) { double X0=Fixed[0],Y0=Fixed[1],Z0=Fixed[2]; if(Axis==0)X0=0.0;else if(Axis==1)Y0=0.0;else Z0=0.0; const double Floor0=FloorAt(P,X0,Y0); const double SmoothAllowance=P.FloorBlendRadius>0.0?P.FloorBlendRadius/6.0:0.0; const double Margin0=Z0-(Floor0-SmoothAllowance); const double MarginSlope=Axis==0?-P.FloorSlopeX:Axis==1?-P.FloorSlopeY:1.0; if(std::abs(MarginSlope)<1.0e-12) {if(Margin0<0.0)return false;} else { const double Boundary=-Margin0/MarginSlope; if(MarginSlope>0.0)L=std::max(L,static_cast(std::ceil(Boundary))); else H=std::min(H,static_cast(std::floor(Boundary))); } } if(L>H)return false; int32 Mid=L+(H-L)/2; if(P.bCapsule) { double V2=0.0,VD=0.0,D2=0.0; for(int32 J=0;J<3;++J)if(J!=Axis) { const double Start=P.CapsuleStartSub[J]/static_cast(SubPerVoxel); const double End=P.CapsuleEndSub[J]/static_cast(SubPerVoxel); const double V=Fixed[J]-Start,D=End-Start; V2+=V*V;VD+=V*D;D2+=D*D; } const double R0=P.CapsuleStartRadius,DR=P.CapsuleEndRadius-R0; const double QA=D2-DR*DR,QB=-2.0*(VD+R0*DR),QC=V2-R0*R0; const auto Value=[&](double T){return (QA*T+QB)*T+QC;}; double TBest=Value(1.0)1.0e-12) { const double T=std::clamp(-QB/(2.0*QA),0.0,1.0); if(Value(T)0.0)return false; const double Start=P.CapsuleStartSub[Axis]/static_cast(SubPerVoxel); const double End=P.CapsuleEndSub[Axis]/static_cast(SubPerVoxel); Mid=std::clamp(static_cast(std::llround(Start+(End-Start)*TBest)),L,H); } if(!ShapeContains(P,Axis==0?Mid:Fixed[0],Axis==1?Mid:Fixed[1],Axis==2?Mid:Fixed[2])) { bool Found=false; const int32 SearchLimit=P.bCapsule?std::min(H-L,8):2; for(int32 Delta=1;Delta<=SearchLimit&&!Found;++Delta) { if(Mid-Delta>=L&&ShapeContains(P,Axis==0?Mid-Delta:Fixed[0],Axis==1?Mid-Delta:Fixed[1],Axis==2?Mid-Delta:Fixed[2])){Mid-=Delta;Found=true;} else if(Mid+Delta<=H&&ShapeContains(P,Axis==0?Mid+Delta:Fixed[0],Axis==1?Mid+Delta:Fixed[1],Axis==2?Mid+Delta:Fixed[2])){Mid+=Delta;Found=true;} } if(!Found&&P.bCapsule) { for(int32 Sample=L;Sample<=H;Sample+=2) if(ShapeContains(P,Axis==0?Sample:Fixed[0],Axis==1?Sample:Fixed[1],Axis==2?Sample:Fixed[2])) {Mid=Sample;Found=true;break;} if(!Found&&((H-L)&1) &&ShapeContains(P,Axis==0?H:Fixed[0],Axis==1?H:Fixed[1],Axis==2?H:Fixed[2])) {Mid=H;Found=true;} } if(!Found)return false; } int32 Left=L,Right=Mid; while(Left& Ranges) { if(Ranges.size()<2)return; std::sort(Ranges.begin(),Ranges.end(),[](const FInterval& A,const FInterval& B){return A.Lo==B.Lo?A.Hi& Words,size_t Base,int32 Lo,int32 Hi,bool bSet) { if(Lo>Hi)return; const size_t First=static_cast(Lo)>>6,Last=static_cast(Hi)>>6; for(size_t W=First;W<=Last;++W) { const uint32 Bit0=W==First?(static_cast(Lo)&63u):0u; const uint32 Bit1=W==Last?(static_cast(Hi)&63u):63u; const uint64 Mask=(~uint64{0}<>(63u-Bit1)); if(bSet)Words[Base+W]|=Mask;else Words[Base+W]&=~Mask; } } void BuildPackedRockIntervals(FParallelTiming& Timing) { const int32 NX=Size[0],NY=Size[1],NZ=Size[2]; CandidateAxisWords[2].assign(static_cast(NX)*NY*WordsPerZ,0); struct FIntervalScratch { std::vector Carve,Solid,ProtectedAir; }; std::array WorkerScratch; for(int32 Worker=0;Worker(Worker)].Carve.reserve(24); WorkerScratch[static_cast(Worker)].Solid.reserve(8); WorkerScratch[static_cast(Worker)].ProtectedAir.reserve(4); } ParallelLines(static_cast(NX)*NY,[&](size_t Line,int32 Worker) { const int32 X=Min[0]+static_cast(Line%NX),Y=Min[1]+static_cast(Line/NX); FIntervalScratch& Scratch=WorkerScratch[static_cast(Worker)]; std::vector& Carve=Scratch.Carve; std::vector& Solid=Scratch.Solid; std::vector& ProtectedAir=Scratch.ProtectedAir; Carve.clear();Solid.clear();ProtectedAir.clear(); const int32 Fixed[3]={X,Y,0}; for(uint32 Id:GetLineCandidates(2,X,Y)) { const FPrimitive& P=Primitives[Id];FInterval R; if(SolveLine(P,2,Fixed,R)) { if(!P.bCritical) { const size_t CandidateBase=Line*WordsPerZ; const int32 CandidateLo=std::max(Min[2],R.Lo-DetailBand); const int32 CandidateHi=std::min(Max[2],R.Lo+DetailBand); const int32 CandidateLo2=std::max(Min[2],R.Hi-DetailBand); const int32 CandidateHi2=std::min(Max[2],R.Hi+DetailBand); FillBits(CandidateAxisWords[2],CandidateBase, CandidateLo-Min[2],CandidateHi-Min[2],true); FillBits(CandidateAxisWords[2],CandidateBase, CandidateLo2-Min[2],CandidateHi2-Min[2],true); } R.Lo=std::max(R.Lo,Min[2])-Min[2];R.Hi=std::min(R.Hi,Max[2])-Min[2]; if(R.Lo<=R.Hi) { if(!P.bCarve)Solid.push_back(R); else if(P.bCritical)ProtectedAir.push_back(R); else Carve.push_back(R); } } } MergeIntervals(Carve);MergeIntervals(Solid);MergeIntervals(ProtectedAir); const size_t Base=Line*WordsPerZ; const int32 ZLo=LayerRanges[0].Lo, ZHi=LayerRanges[0].Hi; const int32 BaseLo=std::max(ZLo,Min[2])-Min[2],BaseHi=std::min(ZHi,Max[2])-Min[2]; if(BaseLo<=BaseHi)FillBits(RockWords,Base,BaseLo,BaseHi,true); for(const FInterval& R:Carve)FillBits(RockWords,Base,R.Lo,R.Hi,false); for(const FInterval& R:Solid)FillBits(RockWords,Base,R.Lo,R.Hi,true); // Critical passage air is the final authored override, including at landing floors. for(const FInterval& R:ProtectedAir)FillBits(RockWords,Base,R.Lo,R.Hi,false); },&Timing,nullptr,16); } void BuildCandidateBands(FParallelTiming& AxisTiming,FParallelTiming& MergeTiming, double& MergeSeconds) { const int32 N[3]={Size[0],Size[1],Size[2]}; for(int32 Axis=0;Axis<3;++Axis) { if(Axis==2)continue; const int32 A=(Axis+1)%3,B=(Axis+2)%3; const size_t Lines=static_cast(Size[A])*Size[B]; const size_t Words=(static_cast(N[Axis])+63u)/64u; CandidateAxisWords[Axis].assign(Lines*Words,0); ParallelLines(Lines,[&,Axis,A,B,Words](size_t Line) { const int32 CA=Min[A]+static_cast(Line%Size[A]); const int32 CB=Min[B]+static_cast(Line/Size[A]); int32 Fixed[3]{};Fixed[A]=CA;Fixed[B]=CB; for(uint32 Id:GetLineCandidates(Axis,CA,CB)) { const FPrimitive& P=Primitives[Id];if(P.bCritical)continue; FInterval R;if(!SolveLine(P,Axis,Fixed,R))continue; const int32 Lo=std::max(Min[Axis],R.Lo-DetailBand); const int32 Hi=std::min(Max[Axis],R.Lo+DetailBand); const int32 Lo2=std::max(Min[Axis],R.Hi-DetailBand); const int32 Hi2=std::min(Max[Axis],R.Hi+DetailBand); const size_t Base=Line*Words; FillBits(CandidateAxisWords[Axis],Base,Lo-Min[Axis],Hi-Min[Axis],true); FillBits(CandidateAxisWords[Axis],Base,Lo2-Min[Axis],Hi2-Min[Axis],true); } },&AxisTiming,nullptr,16); } const auto MergeStart=std::chrono::steady_clock::now(); CandidateWords=std::move(CandidateAxisWords[2]); for(int32 Axis=0;Axis<2;++Axis) { const int32 A=(Axis+1)%3,B=(Axis+2)%3; const size_t Words=(static_cast(Size[Axis])+63u)/64u; ParallelLines(static_cast(Size[A])*Size[B],[&,Axis,A,B,Words](size_t Line) { const int32 CA=Min[A]+static_cast(Line%Size[A]); const int32 CB=Min[B]+static_cast(Line/Size[A]); for(size_t W=0;W(W*64+Bit); if(T>Max[Axis])continue; int32 Coord[3]{};Coord[Axis]=T;Coord[A]=CA;Coord[B]=CB; const size_t Column=static_cast(Coord[1]-Min[1])*Size[0] +static_cast(Coord[0]-Min[0]); const size_t Zi=static_cast(Coord[2]-Min[2]); std::atomic_ref(CandidateWords[Column*WordsPerZ+(Zi>>6)]) .fetch_or(uint64{1}<<(Zi&63u),std::memory_order_relaxed); } } },&MergeTiming,nullptr,16); } const int32 LayerLo=LayerRanges.empty()?1:LayerRanges[0].Lo; const int32 LayerHi=LayerRanges.empty()?0:LayerRanges[0].Hi; if(LayerLo<=LayerHi) { for(size_t Line=0;Line(Size[0])*Size[1];++Line) { const size_t Base=Line*WordsPerZ; const int32 Lo0=std::max(Min[2],LayerLo-DetailBand)-Min[2]; const int32 Hi0=std::min(Max[2],LayerLo+DetailBand)-Min[2]; const int32 Lo1=std::max(Min[2],LayerHi-DetailBand)-Min[2]; const int32 Hi1=std::min(Max[2],LayerHi+DetailBand)-Min[2]; FillBits(CandidateWords,Base,Lo0,Hi0,true); FillBits(CandidateWords,Base,Lo1,Hi1,true); } } const double MergeElapsed=std::chrono::duration( std::chrono::steady_clock::now()-MergeStart).count(); MergeSeconds=std::max(0.0,MergeElapsed-MergeTiming.WallSeconds); } bool BuildGpuVisualDetailNodeSets( const std::vector& GpuTiles, const std::vector& CpuTiles, std::string& Error) { if(GpuTiles.empty()&&CpuTiles.empty()) { GpuVisualDetailWords.clear();CpuVisualDetailWords.clear(); if(bProfileDetailBand) { GpuVisualDetailWords.assign(CandidateWords.size(),0u); CpuVisualDetailWords.assign(CandidateWords.size(),0u); } GpuVisualDetailTileCount=CpuVisualDetailTileCount=0; GpuVisualDetailGridSamples=CpuVisualDetailGridSamples=0; GpuVisualDetailNodeCount=CpuVisualDetailNodeCount=0; return true; } const auto BuildSet=[&](const std::vector& Tiles, std::vector& Nodes,uint64& GridSamples)->bool { Nodes.clear();GridSamples=0; uint64 ReserveSamples=0; for(const auto& Tile:Tiles) { if(Tile.cells<=0||Tile.cells>256||Tile.step<=0) {Error="GPU visual detail-band tile region has invalid cells or step";return false;} const uint64 Dim=static_cast(Tile.cells)+3u; const uint64 TileSamples=Dim*Dim*Dim; if(TileSamples>std::numeric_limits::max()-ReserveSamples) {Error="GPU visual detail-band tile sample count overflowed 64 bits";return false;} ReserveSamples+=TileSamples; } GridSamples=ReserveSamples; const auto IsCandidate=[&](int32 X,int32 Y,int32 Z) { if(XMax[0]||YMax[1]||ZMax[2]) return false; const size_t Column=static_cast(Y-Min[1])*Size[0] +static_cast(X-Min[0]); const size_t Zi=static_cast(Z-Min[2]); return (CandidateWords[Column*WordsPerZ+(Zi>>6)] &(uint64{1}<<(Zi&63u)))!=0; }; Nodes.assign(CandidateWords.size(),0u); for(const auto& Tile:Tiles) for(int32 Z=-1;Z<=Tile.cells+1;++Z) for(int32 Y=-1;Y<=Tile.cells+1;++Y) for(int32 X=-1;X<=Tile.cells+1;++X) { const int32 WX=Tile.originVoxel.x+X*Tile.step; const int32 WY=Tile.originVoxel.y+Y*Tile.step; const int32 WZ=Tile.originVoxel.z+Z*Tile.step; if(!IsCandidate(WX,WY,WZ))continue; const size_t Column=static_cast(WY-Min[1])*Size[0] +static_cast(WX-Min[0]); const size_t Zi=static_cast(WZ-Min[2]); Nodes[Column*WordsPerZ+(Zi>>6)]|=uint64{1}<<(Zi&63u); } return true; }; if(!BuildSet(GpuTiles,GpuVisualDetailWords,GpuVisualDetailGridSamples) ||!BuildSet(CpuTiles,CpuVisualDetailWords,CpuVisualDetailGridSamples))return false; GpuVisualDetailNodeCount=0;CpuVisualDetailNodeCount=0; for(const uint64 Bits:GpuVisualDetailWords)GpuVisualDetailNodeCount+=std::popcount(Bits); for(const uint64 Bits:CpuVisualDetailWords)CpuVisualDetailNodeCount+=std::popcount(Bits); GpuVisualDetailTileCount=GpuTiles.size();CpuVisualDetailTileCount=CpuTiles.size(); if(!GpuTiles.empty()||!CpuTiles.empty()) { std::printf("[GpuVisualDetailBandPlan] gpu_tiles=%llu gpu_sample_nodes=%llu gpu_unique_candidate_nodes=%llu cpu_near_tiles=%llu cpu_sample_nodes=%llu cpu_unique_candidate_nodes=%llu\n", static_cast(GpuVisualDetailTileCount), static_cast(GpuVisualDetailGridSamples), static_cast(GpuVisualDetailNodeCount), static_cast(CpuVisualDetailTileCount), static_cast(CpuVisualDetailGridSamples), static_cast(CpuVisualDetailNodeCount)); } return true; } bool IsGpuVisualDetailNode(int32 X,int32 Y,int32 Z) const { if(!Contains(X,Y,Z)||GpuVisualDetailWords.empty())return false; const size_t Column=static_cast(Y-Min[1])*Size[0] +static_cast(X-Min[0]); const size_t Zi=static_cast(Z-Min[2]); return (GpuVisualDetailWords[Column*WordsPerZ+(Zi>>6)] &(uint64{1}<<(Zi&63u)))!=0; } bool IsCpuVisualDetailNode(int32 X,int32 Y,int32 Z) const { if(!Contains(X,Y,Z)||CpuVisualDetailWords.empty())return false; const size_t Column=static_cast(Y-Min[1])*Size[0] +static_cast(X-Min[0]); const size_t Zi=static_cast(Z-Min[2]); return (CpuVisualDetailWords[Column*WordsPerZ+(Zi>>6)] &(uint64{1}<<(Zi&63u)))!=0; } void ApplyDetailBand(FParallelTiming& Timing) { using Clock=std::chrono::steady_clock; const bool bProfile=bProfileDetailBand; std::array WorkerProfile{}; std::array WorkerActiveSeconds{}; std::atomic CandidateCount{0},ProxyCount{0},FlipCount{0}; ParallelLines(static_cast(Size[0])*Size[1],[&](size_t Line,int32 Worker) { const int32 X=Min[0]+static_cast(Line%Size[0]); const int32 Y=Min[1]+static_cast(Line/Size[0]); FDetailNoiseLineContext NoiseLine(static_cast(X),static_cast(Y), static_cast(Owner->Seed)); const FLayerField* LineField=nullptr; bool bLineFieldKnown=false; int32 LineFieldLo=0,LineFieldHi=-1; const auto FindLineLayerField=[&](double Z)->const FLayerField* { if(bLineFieldKnown&&LineField&&Z>=LineFieldLo&&Z<=LineFieldHi) return LineField; LineField=FindLayerField(Z); bLineFieldKnown=LineField!=nullptr; if(LineField){LineFieldLo=LineField->Lo;LineFieldHi=LineField->Hi;} return LineField; }; const int32 QueryBinX=FloorDiv(X,ActiveSpatialBinSize); const int32 QueryBinY=FloorDiv(Y,ActiveSpatialBinSize); int32 CachedQueryBinZ=std::numeric_limits::min(); const FBinItems* CachedQueryBinItems=nullptr; FDetailWorkerProfile& Profile=WorkerProfile[static_cast(Worker)]; if(bProfile)++Profile.Lines; uint64 LocalCandidates=0,LocalProxies=0,LocalFlips=0; const size_t WordBase=Line*WordsPerZ; for(size_t W=0;W::min(); const FBinItems* BatchBinItems=nullptr; const auto ProcessBatch=[&]() { if(BatchCount==0)return; FEffectiveRockSample Samples[8]; int32 ProfileDistanceSamples=0; if(bProfile) { for(int32 Lane=0;Lane0 ?Clock::now():Clock::time_point{}; EffectiveRockDistanceZ8(X,Y,BatchZ,BatchCount,BatchBinItems,Samples); if(ProfileDistanceSamples>0) { Profile.DistanceSampleNanoseconds+=static_cast( std::chrono::duration_cast( Clock::now()-DistanceStart).count()); Profile.DistanceSamples+=static_cast(BatchCount); } for(int32 Lane=0;Lane(Z-Min[2]); ++LocalCandidates; const FEffectiveRockSample& Sample=Samples[Lane]; const float SDF=Sample.Distance; if(std::abs(SDF)>DetailSupremum+0.125f)continue; const size_t VisualWordIndex=WordBase+(Zi>>6); const uint64 VisualMask=uint64{1}<<(Zi&63u); const bool bGpuVisualNode=bProfile &&(GpuVisualDetailWords[VisualWordIndex]&VisualMask)!=0; const bool bCpuVisualNode=bProfile &&(CpuVisualDetailWords[VisualWordIndex]&VisualMask)!=0; if(bProfile) { if(bGpuVisualNode)++Profile.GpuVisualCandidates; if(bCpuVisualNode)++Profile.CpuVisualCandidates; if(bGpuVisualNode&&!bCpuVisualNode)++Profile.GpuOnlyCandidates; } const float SurfaceSDF=Sample.SurfaceSDF; const float CaveInfluence=Sample.CaveInfluence; const float FloorInfluence=Sample.FloorInfluence; const float PassageInfluence=Sample.PassageInfluence; const bool bCriticalAir=Sample.bCriticalAir; float DetailFade=DetailBandFade(SDF); float MacroValue=0.0f; bool bMacroKnown=false; const FLayerField* DetailField=nullptr; bool bDetailFieldKnown=false; if(DetailVariant==EDetailVariant::Coherent &&std::abs(SDF)>DetailBandInnerRadius) { MacroValue=NoiseLine.Macro.Sample(static_cast(Z)); bMacroKnown=true; DetailField=FindLineLayerField(Z); bDetailFieldKnown=true; DetailFade=DetailBandFade(SDF,LocalDetailBandEdge(MacroValue, SurfaceSDF,CaveInfluence,FloorInfluence,DetailField)); } if(DetailFade<=0.0f&&bProfile)++Profile.LocalBandSkips; const bool bTimeDetail=DetailFade>0.0f&&bProfile &&((Profile.DetailEvaluations&63u)==0); const auto DetailStart=bTimeDetail?Clock::now():Clock::time_point{}; float Displaced=SDF; if(DetailFade>0.0f) { if(bProfile)++Profile.DetailEvaluations; if(bProfile&&bGpuVisualNode)++Profile.GpuVisualDetailEvaluations; if(bProfile&&bGpuVisualNode&&!bCpuVisualNode) ++Profile.GpuOnlyDetailEvaluations; Displaced+=DetailNoiseOnLine(X,Y,Z,Sample.SurfaceSDF, CaveInfluence,FloorInfluence,PassageInfluence,NoiseLine,bTimeDetail?&Profile:nullptr, DetailField,bDetailFieldKnown,bMacroKnown,MacroValue)*DetailFade; } if(bTimeDetail) { Profile.DetailSampleNanoseconds+=static_cast( std::chrono::duration_cast( Clock::now()-DetailStart).count()); ++Profile.DetailSamples; } if(bCriticalAir)Displaced=std::min(Displaced,-1.0f/16.0f); const bool bNewRock=Displaced>=0.0f; const bool bOldRock=((RockWords[WordBase+(Zi>>6)]>>(Zi&63u))&1u)!=0; if(bNewRock!=bOldRock) { RockWords[WordBase+(Zi>>6)]^=uint64{1}<<(Zi&63u); ++LocalFlips; if(bProfile)++Profile.Flips; } const size_t Node=(Zi*static_cast(Size[1]) +static_cast(Y-Min[1]))*Size[0] +static_cast(X-Min[0]); StoreSurfaceDistance(Node,-Displaced); ValidBits|=uint64{1}<<(Zi&63u); ++LocalProxies; } BatchCount=0; }; while(Bits) { const uint32 Bit=std::countr_zero(Bits);Bits&=Bits-1; const size_t Zi=W*64+Bit;if(Zi>=static_cast(Size[2]))continue; const int32 Z=Min[2]+static_cast(Zi); const int32 QueryBinZ=FloorDiv(Z,ActiveSpatialBinSize); if(BatchCount>0&&QueryBinZ!=BatchQueryBinZ)ProcessBatch(); if(QueryBinZ!=CachedQueryBinZ) { const auto Bin=SpatialBins.find({QueryBinX,QueryBinY,QueryBinZ}); CachedQueryBinItems=Bin==SpatialBins.end()?nullptr:&Bin->second; CachedQueryBinZ=QueryBinZ; } if(BatchCount==0)BatchQueryBinZ=QueryBinZ,BatchBinItems=CachedQueryBinItems; BatchZ[BatchCount++]=Z; if(BatchCount==8)ProcessBatch(); } ProcessBatch(); if(OriginalBits!=ValidBits)CandidateWords[WordBase+W]=ValidBits; } CandidateCount.fetch_add(LocalCandidates,std::memory_order_relaxed); ProxyCount.fetch_add(LocalProxies,std::memory_order_relaxed); FlipCount.fetch_add(LocalFlips,std::memory_order_relaxed); // Each X/Y column is independent. Claim eight adjacent columns at a time to // reduce scheduler atomics while preserving per-column scalar evaluation order. },&Timing,&WorkerActiveSeconds,8); Stats.SurfaceCandidates=CandidateCount.load(std::memory_order_relaxed); Stats.SurfaceProxyNodes=ProxyCount.load(std::memory_order_relaxed); Stats.DetailFlips=FlipCount.load(std::memory_order_relaxed); if(bProfile) { uint64 DistanceCalls=0,DetailCalls=0,DistanceSamples=0,DistanceSampleNs=0; uint64 DetailSamples=0,DetailSampleNs=0,ReliefSampleNs=0,LocalBandSkips=0; uint64 LayerLookupSampleNs=0,OwnerSampleNs=0,OwnerFieldCalls=0,OwnerActiveCalls=0; uint64 OwnerCacheHits=0,OwnerCacheMisses=0; uint64 GpuVisualCandidates=0,CpuVisualCandidates=0,GpuOnlyCandidates=0; uint64 GpuVisualDetailCalls=0,GpuOnlyDetailCalls=0; for(int32 Worker=0;Worker(Worker)]; DistanceCalls+=P.Candidates;DetailCalls+=P.DetailEvaluations; DistanceSamples+=P.DistanceSamples;DistanceSampleNs+=P.DistanceSampleNanoseconds; DetailSamples+=P.DetailSamples;DetailSampleNs+=P.DetailSampleNanoseconds; LocalBandSkips+=P.LocalBandSkips; ReliefSampleNs+=P.ReliefSampleNanoseconds; LayerLookupSampleNs+=P.LayerLookupSampleNanoseconds; OwnerSampleNs+=P.OwnerRoughnessSampleNanoseconds; OwnerFieldCalls+=P.OwnerRoughnessFieldCalls; OwnerActiveCalls+=P.OwnerRoughnessActiveCalls; OwnerCacheHits+=P.OwnerNoiseCacheHits; OwnerCacheMisses+=P.OwnerNoiseCacheMisses; GpuVisualCandidates+=P.GpuVisualCandidates; CpuVisualCandidates+=P.CpuVisualCandidates; GpuOnlyCandidates+=P.GpuOnlyCandidates; GpuVisualDetailCalls+=P.GpuVisualDetailEvaluations; GpuOnlyDetailCalls+=P.GpuOnlyDetailEvaluations; } const double DistanceAverageNs=DistanceSamples?static_cast(DistanceSampleNs)/DistanceSamples:0.0; const double DetailAverageNs=DetailSamples?static_cast(DetailSampleNs)/DetailSamples:0.0; const double CandidateWorkerNs=Stats.SurfaceCandidates ?Timing.WorkerSeconds*1.0e9/static_cast(Stats.SurfaceCandidates):0.0; std::printf("[DetailBandProfile] candidates=%llu distance_evaluations=%llu detail_noise_evaluations=%llu proxies=%llu flips=%llu candidate_worker_ns=%.1f distance_sample_ns=%.1f distance_estimated_worker_ms=%.1f detail_sample_ns=%.1f detail_estimated_worker_ms=%.1f worker_sum_ms=%.3f wall_ms=%.3f sampling=(distance_1_of_256,detail_1_of_64)\n", static_cast(Stats.SurfaceCandidates), static_cast(DistanceCalls),static_cast(DetailCalls), static_cast(Stats.SurfaceProxyNodes),static_cast(Stats.DetailFlips), CandidateWorkerNs,DistanceAverageNs,DistanceAverageNs*static_cast(DistanceCalls)/1.0e6, DetailAverageNs,DetailAverageNs*static_cast(DetailCalls)/1.0e6, Timing.WorkerSeconds*1000.0,Timing.WallSeconds*1000.0); std::printf("[DetailBandSubprofile] relief_sample_ns=%.1f relief_estimated_worker_ms=%.1f layer_lookup_sample_ns=%.1f layer_lookup_estimated_worker_ms=%.1f owner_sample_ns=%.1f owner_estimated_worker_ms=%.1f owner_sampled_field_calls=%llu owner_sampled_noise_calls=%llu owner_sampled_core_cache_hits=%llu owner_sampled_core_cache_misses=%llu local_band_skipped=%llu\n", DetailSamples?static_cast(ReliefSampleNs)/DetailSamples:0.0, DetailSamples?static_cast(ReliefSampleNs)/DetailSamples*DetailCalls/1.0e6:0.0, DetailSamples?static_cast(LayerLookupSampleNs)/DetailSamples:0.0, DetailSamples?static_cast(LayerLookupSampleNs)/DetailSamples*DetailCalls/1.0e6:0.0, DetailSamples?static_cast(OwnerSampleNs)/DetailSamples:0.0, DetailSamples?static_cast(OwnerSampleNs)/DetailSamples*DetailCalls/1.0e6:0.0, static_cast(OwnerFieldCalls), static_cast(OwnerActiveCalls), static_cast(OwnerCacheHits), static_cast(OwnerCacheMisses), static_cast(LocalBandSkips)); std::printf("[GpuDetailConsumerProfile] gpu_near_proxy_nodes=%llu cpu_near_mesh_nodes=%llu gpu_only_nodes=%llu gpu_near_detail_noise_evaluations=%llu gpu_only_detail_noise_evaluations=%llu\n", static_cast(GpuVisualCandidates), static_cast(CpuVisualCandidates), static_cast(GpuOnlyCandidates), static_cast(GpuVisualDetailCalls), static_cast(GpuOnlyDetailCalls)); for(int32 Worker=0;Worker(Worker)]; std::printf("[DetailBandWorker] id=%d active_ms=%.3f lines=%llu candidates=%llu detail_evaluations=%llu flips=%llu\n", Worker,WorkerActiveSeconds[static_cast(Worker)]*1000.0, static_cast(P.Lines),static_cast(P.Candidates), static_cast(P.DetailEvaluations),static_cast(P.Flips)); } } } uint64 CompleteSignEdgeDistances() { if(bParallelSignEdgeRepair&&Scheduler) return CompleteSignEdgeDistancesParallel(); uint64 AddedTotal=0,SignCorrections=0; const size_t Columns=static_cast(Size[0])*Size[1]; for(int32 Pass=0;Pass<4;++Pass) { uint64 AddedThisPass=0; bool bSignChanged=false; const auto EnsureDistance=[&](int32 X,int32 Y,int32 Z) { const size_t Column=static_cast(Y-Min[1])*Size[0] +static_cast(X-Min[0]); const size_t Zi=static_cast(Z-Min[2]); const size_t Word=Column*WordsPerZ+(Zi>>6); const uint64 Mask=uint64{1}<<(Zi&63u); if(CandidateWords[Word]&Mask)return; const float Density=AnalyticDensityAt(static_cast(X), static_cast(Y),static_cast(Z)); const size_t Node=(Zi*static_cast(Size[1]) +static_cast(Y-Min[1]))*Size[0] +static_cast(X-Min[0]); StoreSurfaceDistance(Node,Density); CandidateWords[Word]|=Mask; ++AddedThisPass; const bool bExactRock=Density<0.0f; const bool bPackedRock=(RockWords[Word]&Mask)!=0; if(bExactRock!=bPackedRock) { if(bExactRock)RockWords[Word]|=Mask; else RockWords[Word]&=~Mask; ++SignCorrections; bSignChanged=true; } }; for(size_t Line=0;Line(Line%Size[0]); const int32 Y=Min[1]+static_cast(Line/Size[0]); const size_t Base=Line*WordsPerZ; for(size_t W=0;W>63:0; const uint64 PreviousCandidates=W?CandidateWords[Word-1]>>63:0; uint64 Valid=(Size[2]-W*64)>=64?~uint64{0} :((uint64{1}<<(Size[2]-W*64))-1); if(W==0)Valid&=~uint64{1}; const uint64 ShiftedRock=(Rock<<1)|PreviousRock; const uint64 ShiftedCandidates=(Candidates<<1)|PreviousCandidates; uint64 Missing=((Rock^ShiftedRock)&Valid) &~(Candidates&ShiftedCandidates); while(Missing) { const uint32 Bit=std::countr_zero(Missing);Missing&=Missing-1; const int32 Z=Min[2]+static_cast(W*64+Bit); EnsureDistance(X,Y,Z); EnsureDistance(X,Y,Z-1); } if(X(W*64+Bit); EnsureDistance(X,Y,Z); EnsureDistance(X+1,Y,Z); } } if(Y(Size[0])*WordsPerZ; Missing=(Rock^RockWords[Other])&Valid &~(Candidates&CandidateWords[Other]); while(Missing) { const uint32 Bit=std::countr_zero(Missing);Missing&=Missing-1; const int32 Z=Min[2]+static_cast(W*64+Bit); EnsureDistance(X,Y,Z); EnsureDistance(X,Y+1,Z); } } } } AddedTotal+=AddedThisPass; if(!bSignChanged)break; } std::printf("[GridStairDistanceRepair] exact_endpoint_proxies=%llu occupancy_corrections=%llu\n", static_cast(AddedTotal), static_cast(SignCorrections)); return AddedTotal; } uint64 CompleteSignEdgeDistancesParallel() { uint64 AddedTotal=0,SignCorrections=0; const size_t Columns=static_cast(Size[0])*Size[1]; for(int32 Pass=0;Pass<4;++Pass) { std::vector MissingNodes; const auto AddMissing=[&](int32 X,int32 Y,int32 Z) { if(XMax[0]||YMax[1]||ZMax[2])return; const size_t Column=static_cast(Y-Min[1])*Size[0] +static_cast(X-Min[0]); const size_t Zi=static_cast(Z-Min[2]); const size_t Word=Column*WordsPerZ+(Zi>>6); const uint64 Mask=uint64{1}<<(Zi&63u); if((CandidateWords[Word]&Mask)==0) MissingNodes.push_back((Zi*static_cast(Size[1]) +static_cast(Y-Min[1]))*Size[0] +static_cast(X-Min[0])); }; // Discover against a stable pass snapshot. Density work is then independent per // lattice node; packed candidates and occupancy corrections publish in node order. for(size_t Line=0;Line(Line%Size[0]); const int32 Y=Min[1]+static_cast(Line/Size[0]); const size_t Base=Line*WordsPerZ; for(size_t W=0;W>63:0; const uint64 PreviousCandidates=W?CandidateWords[Word-1]>>63:0; uint64 Valid=(Size[2]-W*64)>=64?~uint64{0} :((uint64{1}<<(Size[2]-W*64))-1); if(W==0)Valid&=~uint64{1}; const uint64 ShiftedRock=(Rock<<1)|PreviousRock; const uint64 ShiftedCandidates=(Candidates<<1)|PreviousCandidates; uint64 Missing=((Rock^ShiftedRock)&Valid) &~(Candidates&ShiftedCandidates); while(Missing) { const uint32 Bit=std::countr_zero(Missing);Missing&=Missing-1; const int32 Z=Min[2]+static_cast(W*64+Bit); AddMissing(X,Y,Z);AddMissing(X,Y,Z-1); } if(X(W*64+Bit)); AddMissing(X+1,Y,Min[2]+static_cast(W*64+Bit)); } } if(Y(Size[0])*WordsPerZ; Missing=(Rock^RockWords[Other])&Valid &~(Candidates&CandidateWords[Other]); while(Missing) { const uint32 Bit=std::countr_zero(Missing);Missing&=Missing-1; AddMissing(X,Y,Min[2]+static_cast(W*64+Bit)); AddMissing(X,Y+1,Min[2]+static_cast(W*64+Bit)); } } } } std::sort(MissingNodes.begin(),MissingNodes.end()); MissingNodes.erase(std::unique(MissingNodes.begin(),MissingNodes.end()), MissingNodes.end()); std::vector Densities(MissingNodes.size()); struct FParallelContext { FStandaloneShapeWorld* Shape; const std::vector* Nodes; std::vector* Values; int32 Minimum[3]; int32 Extent[3]; } Context{this,&MissingNodes,&Densities,{Min[0],Min[1],Min[2]}, {Size[0],Size[1],Size[2]}}; const auto EvaluateNode=[](void* Raw,size_t Index,size_t) { auto& C=*static_cast(Raw); const size_t Node=(*C.Nodes)[Index]; const int32 X=static_cast(Node%static_cast(C.Extent[0])); const size_t YZ=Node/static_cast(C.Extent[0]); const int32 Y=static_cast(YZ%static_cast(C.Extent[1])); const int32 Z=static_cast(YZ/static_cast(C.Extent[1])); (*C.Values)[Index]=C.Shape->AnalyticDensityAt( static_cast(C.Minimum[0]+X),static_cast(C.Minimum[1]+Y), static_cast(C.Minimum[2]+Z)); }; Scheduler->parallel_for(MissingNodes.size(),64, static_cast(std::max(1,WorkerCount)),&Context,EvaluateNode,nullptr,0); uint64 AddedThisPass=0; bool bSignChanged=false; for(size_t Index=0;Index(Node%static_cast(Size[0])); const size_t YZ=Node/static_cast(Size[0]); const int32 Y=Min[1]+static_cast(YZ%static_cast(Size[1])); const int32 Z=Min[2]+static_cast(YZ/static_cast(Size[1])); const size_t Column=static_cast(Y-Min[1])*Size[0] +static_cast(X-Min[0]); const size_t Zi=static_cast(Z-Min[2]); const size_t Word=Column*WordsPerZ+(Zi>>6); const uint64 Mask=uint64{1}<<(Zi&63u); if(CandidateWords[Word]&Mask)continue; const float Density=Densities[Index]; StoreSurfaceDistance(Node,Density); CandidateWords[Word]|=Mask; ++AddedThisPass; const bool bExactRock=Density<0.0f; const bool bPackedRock=(RockWords[Word]&Mask)!=0; if(bExactRock!=bPackedRock) { if(bExactRock)RockWords[Word]|=Mask; else RockWords[Word]&=~Mask; ++SignCorrections; bSignChanged=true; } } AddedTotal+=AddedThisPass; if(!bSignChanged)break; } std::printf("[GridStairDistanceRepair] exact_endpoint_proxies=%llu occupancy_corrections=%llu mode=parallel_deterministic\n", static_cast(AddedTotal), static_cast(SignCorrections)); return AddedTotal; } void StoreSurfaceDistance(size_t Node,float Density) { int32 Q=static_cast(std::lround(Density*256.0f)); Q=std::clamp(Q,-32767,32767); if(Q==0)Q=Density<0.0f?-1:1; SurfaceDistanceQ[Node]=static_cast(Q); } bool IsInsideSolidOverlay(int32 X,int32 Y,int32 Z) const { const FBinKey K=PointBin(X,Y,Z);const auto It=SpatialBins.find(K); if(It==SpatialBins.end())return false; for(uint32 Id:It->second) { const FPrimitive& P=Primitives[Id];if(P.bCarve)continue; if(XP.Max[0]||YP.Max[1]||ZP.Max[2])continue; if(ShapeContains(P,X,Y,Z))return true; } return false; } bool IsInsideCriticalAir(int32 X,int32 Y,int32 Z) const { const auto It=CriticalAirBins.find(PointBin(X,Y,Z)); if(It==CriticalAirBins.end())return false; for(uint32 Id:It->second) { const FPrimitive& P=Primitives[Id];if(!P.bCarve||!P.bCritical)continue; if(XP.Max[0]||YP.Max[1]||ZP.Max[2])continue; if(ShapeContains(P,X,Y,Z))return true; } return false; } bool IsNearCriticalAirBoundary(int32 X,int32 Y,int32 Z,double Margin) const { const auto It=CriticalAirBins.find(PointBin(X,Y,Z)); if(It==CriticalAirBins.end())return false; for(uint32 Id:It->second) { const FPrimitive& P=Primitives[Id];if(!P.bCarve||!P.bCritical)continue; if(XP.Max[0]+Margin ||YP.Max[1]+Margin ||ZP.Max[2]+Margin)continue; float Distance=ShapeDistance(P,X,Y,Z); if(P.bHasFloorClip) { const float FloorDistance=static_cast(FloorAt(P,X,Y)-Z); Distance=SmoothMaxDistance(Distance,FloorDistance,static_cast(P.FloorBlendRadius)); } if(std::abs(Distance)<=Margin)return true; } return false; } bool IsInsideSolidOverlay(double X,double Y,double Z) const { const auto It=SpatialBins.find(PointBin(X,Y,Z)); if(It==SpatialBins.end())return false; for(uint32 Id:It->second) { const FPrimitive& P=Primitives[Id];if(P.bCarve)continue; if(XP.Max[0]||YP.Max[1]||ZP.Max[2])continue; if(ShapeDistance(P,X,Y,Z)<=0.0f)return true; } return false; } float SolidOverlayDistance(double X,double Y,double Z) const { const auto It=SpatialBins.find(PointBin(X,Y,Z)); if(It==SpatialBins.end())return -100000.0f; float Best=-100000.0f; for(uint32 Id:It->second) { const FPrimitive& P=Primitives[Id];if(P.bCarve)continue; if(XP.Max[0]||YP.Max[1]||ZP.Max[2])continue; Best=std::max(Best,-ShapeDistance(P,X,Y,Z)); } return Best; } bool IsInsideCriticalAir(double X,double Y,double Z) const { const auto It=CriticalAirBins.find(PointBin(X,Y,Z)); if(It==CriticalAirBins.end())return false; for(uint32 Id:It->second) { const FPrimitive& P=Primitives[Id];if(!P.bCarve||!P.bCritical)continue; if(XP.Max[0]||YP.Max[1]||ZP.Max[2])continue; if(ShapeContains(P,X,Y,Z))return true; } return false; } float CaveDistance(double X,double Y,double Z,bool* OutFloorSurface=nullptr, float* OutFloorInfluence=nullptr) const { const auto It=SpatialBins.find(PointBin(X,Y,Z)); if(It==SpatialBins.end()) { if(OutFloorSurface)*OutFloorSurface=false; if(OutFloorInfluence)*OutFloorInfluence=0.0f; return 100000.0f; } float Best=100000.0f; float BestFloorInfluence=0.0f; for(uint32 Id:It->second) { const FPrimitive& P=Primitives[Id];if(!P.bCarve)continue; if(XP.Max[0]+DetailBand||YP.Max[1]+DetailBand ||ZP.Max[2]+DetailBand)continue; float Distance=ShapeDistance(P,X,Y,Z); float PrimitiveFloorInfluence=0.0f; if(P.bHasFloorClip) { const double Floor=FloorAt(P,X,Y); const float FloorT=(static_cast(std::abs(Z-Floor))-static_cast(P.FloorBlendRadius)) /static_cast(std::max(DetailBand*2,1)); PrimitiveFloorInfluence=1.0f-SmoothUnit(FloorT); Distance=SmoothMaxDistance(Distance,static_cast(Floor-Z), static_cast(P.FloorBlendRadius)); } const float MetadataWidth=static_cast(std::max(P.BlendRadius,6.0)); const float PrimitiveWeight=SmoothUnit(0.5f+(Best-Distance)/(2.0f*MetadataWidth)); const float Combined=SmoothMinDistance(Best,Distance,static_cast(P.BlendRadius)); BestFloorInfluence=std::lerp(BestFloorInfluence,PrimitiveFloorInfluence,PrimitiveWeight); Best=Combined; } if(OutFloorInfluence)*OutFloorInfluence=BestFloorInfluence; if(OutFloorSurface)*OutFloorSurface=BestFloorInfluence>=0.5f; return Best; } float StrataDistance(double Z) const { if(LayerRanges.empty())return -100000.0f; return static_cast(std::min(Z-LayerRanges[0].Lo,LayerRanges[0].Hi-Z)); } const FLayerField* FindLayerField(double Z) const { for(const FLayerField& Field:LayerFields) if(Z>=Field.Lo&&Z<=Field.Hi)return &Field; return nullptr; } float EffectiveRockDistance(double X,double Y,double Z, ESurfaceFamily* OutFamily=nullptr, float* OutSurfaceSDF=nullptr, bool* OutFloorSurface=nullptr, float* OutCaveInfluence=nullptr, float* OutFloorInfluence=nullptr, bool* OutCriticalAir=nullptr, const FBinItems* KnownBinItems=nullptr, bool bKnownBin=false, float* OutPassageInfluence=nullptr) const { const float Base=StrataDistance(Z); float Cave=100000.0f; float Overlay=-100000.0f; float CaveFloorInfluence=0.0f; float CavePassageInfluence=0.0f; bool bCriticalAir=false; const FBinItems* BinItems=KnownBinItems; if(!bKnownBin) { const auto Bin=SpatialBins.find(PointBin(X,Y,Z)); if(Bin!=SpatialBins.end())BinItems=&Bin->second; } if(BinItems) { for(uint32 Id:*BinItems) { #if defined(VF_L76_PROFILE_LEGACY_TERMS) if(LegacyFieldTermScopeActive()) ++ThreadClassificationStats().LegacyPrimitiveCandidateVisits; #endif const FPrimitive& P=Primitives[Id]; if(P.bCarve) { if(P.bCritical&&X>=P.Min[0]&&X<=P.Max[0]&&Y>=P.Min[1]&&Y<=P.Max[1] &&Z>=P.Min[2]&&Z<=P.Max[2]&&ShapeContains(P, static_cast(X),static_cast(Y),static_cast(Z))) bCriticalAir=true; if(XP.Max[0]+DetailBand ||YP.Max[1]+DetailBand ||ZP.Max[2]+DetailBand)continue; #if defined(VF_L76_PROFILE_LEGACY_TERMS) if(LegacyFieldTermScopeActive()) ++ThreadClassificationStats().LegacyPrimitiveDistanceEvaluations; #endif float Distance=ShapeDistance(P,X,Y,Z); float PrimitiveFloorInfluence=0.0f; if(P.bHasFloorClip) { const double Floor=FloorAt(P,X,Y); const float FloorT=(static_cast(std::abs(Z-Floor)) -static_cast(P.FloorBlendRadius)) /static_cast(std::max(DetailBand*2,1)); PrimitiveFloorInfluence=1.0f-SmoothUnit(FloorT); Distance=SmoothMaxDistance(Distance,static_cast(Floor-Z), static_cast(P.FloorBlendRadius)); } const float MetadataWidth=static_cast(std::max(P.BlendRadius,6.0)); const float PrimitiveWeight=SmoothUnit(0.5f+(Cave-Distance)/(2.0f*MetadataWidth)); const float Combined=SmoothMinDistance(Cave,Distance,static_cast(P.BlendRadius)); CaveFloorInfluence=std::lerp(CaveFloorInfluence,PrimitiveFloorInfluence,PrimitiveWeight); if(OutPassageInfluence&&(CavePassageInfluence>0.0f||P.bPassageSurface)) CavePassageInfluence=std::lerp(CavePassageInfluence, P.bPassageSurface?1.0f:0.0f,PrimitiveWeight); Cave=Combined; } else if(X>=P.Min[0]&&X<=P.Max[0]&&Y>=P.Min[1]&&Y<=P.Max[1] &&Z>=P.Min[2]&&Z<=P.Max[2]) { #if defined(VF_L76_PROFILE_LEGACY_TERMS) if(LegacyFieldTermScopeActive()) ++ThreadClassificationStats().LegacyPrimitiveDistanceEvaluations; #endif Overlay=std::max(Overlay,-ShapeDistance(P,X,Y,Z)); } } } if(OutCriticalAir)*OutCriticalAir=bCriticalAir; const float CarveOrOverlay=std::max(Cave,Overlay); const ESurfaceFamily Family=Base<=CarveOrOverlay?ESurfaceFamily::Strata: (Cave>=Overlay?ESurfaceFamily::Cave:ESurfaceFamily::SolidOverlay); constexpr float FamilyBlendWidth=6.0f; const float CaveVsOverlay=SmoothUnit(0.5f+(Cave-Overlay)/(2.0f*FamilyBlendWidth)); const float CarveInfluence=SmoothUnit(0.5f+(Base-CarveOrOverlay)/(2.0f*FamilyBlendWidth)); const float CaveInfluence=CarveInfluence*CaveVsOverlay; const float FloorInfluence=CaveInfluence*CaveFloorInfluence; const float PassageInfluence=CaveInfluence*CavePassageInfluence; const float RockDistance=SmoothMinDistance(Base,CarveOrOverlay,StrataCaveBlendRadius); if(OutFamily) *OutFamily=Family; if(OutSurfaceSDF) *OutSurfaceSDF=Family==ESurfaceFamily::Strata?Base: (Family==ESurfaceFamily::Cave?Cave:Overlay); if(OutCaveInfluence)*OutCaveInfluence=CaveInfluence; if(OutFloorInfluence)*OutFloorInfluence=FloorInfluence; if(OutPassageInfluence)*OutPassageInfluence=PassageInfluence; if(OutFloorSurface)*OutFloorSurface=FloorInfluence>=0.5f; return RockDistance; } struct FEffectiveRockSample { float Distance=0.0f; float SurfaceSDF=0.0f; float CaveInfluence=0.0f; float FloorInfluence=0.0f; float PassageInfluence=0.0f; ESurfaceFamily Family=ESurfaceFamily::Strata; bool bFloorSurface=false; bool bCriticalAir=false; }; void ShapeDistanceZ8(const FPrimitive& P,int32 X,int32 Y,const int32 Z[8],int32 Count, float OutDistance[8]) const { const __m256d Zero=_mm256_setzero_pd(); const __m256d One=_mm256_set1_pd(1.0); const __m256d SignMask=_mm256_set1_pd(-0.0); for(int32 Base=0;Base(Z[Base+std::min(Lane,Lanes-1)]); const __m256d VZ=_mm256_setr_pd(ZLane[0],ZLane[1],ZLane[2],ZLane[3]); __m256d Distance; if(P.bCapsule) { const double VX=static_cast(X)-P.CapsuleStartWorld[0]; const double VY=static_cast(Y)-P.CapsuleStartWorld[1]; const __m256d VXv=_mm256_set1_pd(VX); const __m256d VYv=_mm256_set1_pd(VY); const __m256d VZv=_mm256_sub_pd(VZ,_mm256_set1_pd(P.CapsuleStartWorld[2])); __m256d VD=_mm256_mul_pd(VXv,_mm256_set1_pd(P.CapsuleAxisDelta[0])); VD=_mm256_add_pd(VD,_mm256_mul_pd(VYv,_mm256_set1_pd(P.CapsuleAxisDelta[1]))); VD=_mm256_add_pd(VD,_mm256_mul_pd(VZv,_mm256_set1_pd(P.CapsuleAxisDelta[2]))); const __m256d T=P.CapsuleAxisLengthSquared>1.0e-12 ?_mm256_max_pd(Zero,_mm256_min_pd(One, _mm256_div_pd(VD,_mm256_set1_pd(P.CapsuleAxisLengthSquared)))) :Zero; const __m256d EX=_mm256_sub_pd(VXv, _mm256_mul_pd(T,_mm256_set1_pd(P.CapsuleAxisDelta[0]))); const __m256d EY=_mm256_sub_pd(VYv, _mm256_mul_pd(T,_mm256_set1_pd(P.CapsuleAxisDelta[1]))); const __m256d EZ=_mm256_sub_pd(VZv, _mm256_mul_pd(T,_mm256_set1_pd(P.CapsuleAxisDelta[2]))); __m256d DistanceSquared=_mm256_mul_pd(EX,EX); DistanceSquared=_mm256_add_pd(DistanceSquared,_mm256_mul_pd(EY,EY)); DistanceSquared=_mm256_add_pd(DistanceSquared,_mm256_mul_pd(EZ,EZ)); const __m256d Root=_mm256_sqrt_pd(DistanceSquared); double TValues[4],RootValues[4]; _mm256_storeu_pd(TValues,T); _mm256_storeu_pd(RootValues,Root); for(int32 Lane=0;Lane(RootValues[Lane] -CapsuleRadiusAtT(P,TValues[Lane],P.CapsuleAxisLength)); continue; } if(P.bRoundedBox) { const double DX=std::abs(static_cast(X)-P.CenterSub[0]/16.0) -P.RoundedHalfExtent[0]; const double DY=std::abs(static_cast(Y)-P.CenterSub[1]/16.0) -P.RoundedHalfExtent[1]; const double DZCenter=P.CenterSub[2]/16.0; const __m256d DZv=_mm256_sub_pd(_mm256_andnot_pd(SignMask, _mm256_sub_pd(VZ,_mm256_set1_pd(DZCenter))), _mm256_set1_pd(P.RoundedHalfExtent[2])); const __m256d DXv=_mm256_set1_pd(DX),DYv=_mm256_set1_pd(DY); const __m256d Ox=_mm256_max_pd(DXv,Zero),Oy=_mm256_max_pd(DYv,Zero); const __m256d Oz=_mm256_max_pd(DZv,Zero); __m256d OutsideSquared=_mm256_mul_pd(Ox,Ox); OutsideSquared=_mm256_add_pd(OutsideSquared,_mm256_mul_pd(Oy,Oy)); OutsideSquared=_mm256_add_pd(OutsideSquared,_mm256_mul_pd(Oz,Oz)); const __m256d MaxXY=_mm256_max_pd(DXv,DYv); const __m256d MaxQ=_mm256_max_pd(MaxXY,DZv); const __m256d Inside=_mm256_min_pd(MaxQ,Zero); Distance=_mm256_sub_pd(_mm256_add_pd(_mm256_sqrt_pd(OutsideSquared),Inside), _mm256_set1_pd(P.Rounding)); } else { const __m256d D[3]={ _mm256_set1_pd(static_cast(X)-P.CenterSub[0]/16.0), _mm256_set1_pd(static_cast(Y)-P.CenterSub[1]/16.0), _mm256_sub_pd(VZ,_mm256_set1_pd(P.CenterSub[2]/16.0))}; __m256d Q=_mm256_setzero_pd(); for(int32 I=0;I<3;++I)for(int32 J=0;J<3;++J) { const double Coefficient=static_cast(P.Q[I][J])/QScale; __m256d Term=_mm256_mul_pd(_mm256_set1_pd(Coefficient),D[I]); Term=_mm256_mul_pd(Term,D[J]); Term=_mm256_mul_pd(Term,_mm256_set1_pd(256.0)); Q=_mm256_add_pd(Q,Term); } Distance=_mm256_mul_pd(_mm256_sub_pd( _mm256_sqrt_pd(_mm256_max_pd(Q,Zero)),_mm256_set1_pd(1.0)), _mm256_set1_pd(P.MinRadius)); } double Values[4]; _mm256_storeu_pd(Values,Distance); for(int32 Lane=0;Lane(Values[Lane]); } } void EffectiveRockDistanceZ8(int32 X,int32 Y,const int32 Z[8],int32 Count, const FBinItems* BinItems,FEffectiveRockSample Out[8]) const { float Base[8],Cave[8],Overlay[8],CaveFloorInfluence[8],CavePassageInfluence[8]; bool CriticalAir[8]{}; for(int32 Lane=0;Lane(Count); #endif const FPrimitive& P=Primitives[Id]; bool Eligible[8]{}; bool AnyEligible=false; if(P.bCarve) { for(int32 Lane=0;Lane=P.Min[0]&&X<=P.Max[0] &&Y>=P.Min[1]&&Y<=P.Max[1] &&ZValue>=P.Min[2]&&ZValue<=P.Max[2] &&ShapeContains(P,X,Y,ZValue)) CriticalAir[Lane]=true; if(XP.Max[0]+DetailBand ||YP.Max[1]+DetailBand ||ZValueP.Max[2]+DetailBand) continue; Eligible[Lane]=true; AnyEligible=true; } } else { for(int32 Lane=0;Lane=P.Min[0]&&X<=P.Max[0] &&Y>=P.Min[1]&&Y<=P.Max[1] &&ZValue>=P.Min[2]&&ZValue<=P.Max[2]; AnyEligible=AnyEligible||Eligible[Lane]; } } if(!AnyEligible)continue; #if defined(VF_L76_PROFILE_LEGACY_TERMS) if(LegacyFieldTermScopeActive()) ThreadClassificationStats().LegacyPrimitiveDistanceEvaluations+= static_cast(Count); #endif float Distances[8]; ShapeDistanceZ8(P,X,Y,Z,Count,Distances); if(P.bCarve) { const double Floor=P.bHasFloorClip?FloorAt(P,X,Y):0.0; const float MetadataWidth=static_cast(std::max(P.BlendRadius,6.0)); for(int32 Lane=0;Lane(std::abs(Z[Lane]-Floor)) -static_cast(P.FloorBlendRadius)) /static_cast(std::max(DetailBand*2,1)); PrimitiveFloorInfluence=1.0f-SmoothUnit(FloorT); Distance=SmoothMaxDistance(Distance,static_cast(Floor-Z[Lane]), static_cast(P.FloorBlendRadius)); } const float PrimitiveWeight=SmoothUnit(0.5f +(Cave[Lane]-Distance)/(2.0f*MetadataWidth)); const float Combined=SmoothMinDistance(Cave[Lane],Distance, static_cast(P.BlendRadius)); CaveFloorInfluence[Lane]=std::lerp(CaveFloorInfluence[Lane], PrimitiveFloorInfluence,PrimitiveWeight); if(CavePassageInfluence[Lane]>0.0f||P.bPassageSurface) CavePassageInfluence[Lane]=std::lerp(CavePassageInfluence[Lane], P.bPassageSurface?1.0f:0.0f,PrimitiveWeight); Cave[Lane]=Combined; } } else { for(int32 Lane=0;Lane=Overlay[Lane]?ESurfaceFamily::Cave:ESurfaceFamily::SolidOverlay); constexpr float FamilyBlendWidth=6.0f; const float CaveVsOverlay=SmoothUnit(0.5f +(Cave[Lane]-Overlay[Lane])/(2.0f*FamilyBlendWidth)); const float CarveInfluence=SmoothUnit(0.5f +(Base[Lane]-CarveOrOverlay)/(2.0f*FamilyBlendWidth)); const float CaveInfluence=CarveInfluence*CaveVsOverlay; const float FloorInfluence=CaveInfluence*CaveFloorInfluence[Lane]; const float PassageInfluence=CaveInfluence*CavePassageInfluence[Lane]; Out[Lane].Distance=SmoothMinDistance(Base[Lane],CarveOrOverlay,StrataCaveBlendRadius); Out[Lane].Family=Family; Out[Lane].SurfaceSDF=Family==ESurfaceFamily::Strata?Base[Lane]: (Family==ESurfaceFamily::Cave?Cave[Lane]:Overlay[Lane]); Out[Lane].CaveInfluence=CaveInfluence; Out[Lane].FloorInfluence=FloorInfluence; Out[Lane].PassageInfluence=PassageInfluence; Out[Lane].bFloorSurface=FloorInfluence>=0.5f; Out[Lane].bCriticalAir=CriticalAir[Lane]; } } public: void DensityAtLatticeZRun(int32 X,int32 Y,int32 ZStart,int32 ZStep, int32 Count,float* OutDensity) const { using namespace VoxelForgeCore::Linux77Profiler; if(SharedDensitySource) { if(ThreadContextActive&&Count>0)RecordLinux77Dispatch(ERoute::SharedSource, static_cast(Count)); FScopedRoute l77Route(ERoute::SharedSource); FScopedPhase l77Phase(EPhase::BaseField); SharedDensitySource->DensityAtLatticeZRun(X,Y,ZStart,ZStep,Count,OutDensity); return; } if(Count<=0||OutDensity==nullptr)return; FScopedRoute l77DispatchRoute(ERoute::Dispatch); FScopedPhase l77DispatchPhase(EPhase::Dispatch); FScopedDensityQueryProfile queryProfile(EDensityQueryKind::Lattice, static_cast(Count)); FScopedDensityQueryBatch queryBatch(queryProfile.IsActive()); if(bNativeOnly) { for(int32 Lane=0;Lane(ZStart+Lane*ZStep)); if(bHasResolvedBoundaryBand) { for(int32 Lane=0;Lane(ZStart+Lane*ZStep); bHasLegacySurfaceSample|=UsesLegacySurfaceField(static_cast(X), static_cast(Y),SampleZ); const auto* managerBound=FindBoundStratumForManagerZ(SampleZ); const auto* resolvedBound=FindBoundStratumForZ(SampleZ); bHasNativeGraphSample|=(managerBound &&managerBound->Kind==FBoundStratumField::EKind::Caves) ||(resolvedBound&&resolvedBound->Kind==FBoundStratumField::EKind::Caves); } if(bHasLegacySurfaceSample||bHasNativeGraphSample) { for(int32 Lane=0;Lane(Count)); FScopedRoute l77BatchRoute(ERoute::AnalyticBatch); FScopedPhase l77BatchBase(EPhase::BaseField); FDetailNoiseLineContext DetailNoiseLine( static_cast(X),static_cast(Y),static_cast(Owner->Seed)); for(int32 Offset=0;Offsetsecond; int32 BatchCount=1; while(BatchCount(Z[Lane])); bLegacyTermBatch=bLegacyTermBatch&&Bound&&Bound->Resolved &&(Bound->Resolved->generatorKind=="TunnelNetwork" ||(Bound->Resolved->generatorKind=="FloatingIslands" &&Bound->IslandPlan==nullptr)); } #else const bool bLegacyTermBatch=false; #endif FScopedLegacyFieldTermScope LegacyTermScope(bLegacyTermBatch, static_cast(BatchCount)); { FScopedLegacyFieldTermTimer TermTimer( ELegacyFieldTerm::PrimitiveScan,static_cast(BatchCount)); EffectiveRockDistanceZ8(X,Y,Z,BatchCount,BinItems,Samples); } for(int32 Lane=0;Lane=Min[0]&&X<=Max[0] &&Y>=Min[1]&&Y<=Max[1]&&SampleZ>=Min[2]&&SampleZ<=Max[2]; float RockDistance=Samples[Lane].Distance; if(bInsideFine&&DetailVariant!=EDetailVariant::Smooth &&std::abs(RockDistance)<=DetailSupremum+0.125f) { const float Fade=DetailBandFadeAt(static_cast(X), static_cast(Y),static_cast(SampleZ),RockDistance, Samples[Lane].SurfaceSDF,Samples[Lane].CaveInfluence, Samples[Lane].FloorInfluence); if(Fade>0.0f) { FScopedLegacyFieldTermTimer TermTimer(ELegacyFieldTerm::Detail); RockDistance+=DetailNoiseOnLine(static_cast(X), static_cast(Y),static_cast(SampleZ), Samples[Lane].SurfaceSDF,Samples[Lane].CaveInfluence, Samples[Lane].FloorInfluence,Samples[Lane].PassageInfluence, DetailNoiseLine)*Fade; } } if(Samples[Lane].bCriticalAir) RockDistance=std::min(RockDistance,-1.0f/16.0f); const bool bRock=RockDistance>=0.0f; float Density=-RockDistance; if((Density<0.0f)!=bRock||std::abs(Density)<0.05f) Density=(bRock?-1.0f:1.0f)*std::max(std::abs(Density),0.05f); { FScopedLegacyFieldTermTimer TermTimer(ELegacyFieldTerm::EditBrushes); OutDensity[Offset+Lane]=ApplyEditBrushes(Density,X,Y,SampleZ); } } Offset+=BatchCount; } } void DensityAtLatticeZ8(int32 X,int32 Y,int32 ZStart,int32 ZStep, int32 Count,float OutDensity[8]) const { DensityAtLatticeZRun(X,Y,ZStart,ZStep,std::clamp(Count,0,8),OutDensity); } private: float CoarseRockDistance(double X,double Y,double Z,bool* OutCriticalAir) const { const float Base=StrataDistance(Z); float Cave=100000.0f,Overlay=-100000.0f; bool bCriticalAir=false; const FBinKey QueryKey=PointBin(X,Y,Z); const auto QueryBin=SpatialBins.find(QueryKey); const FBinItems* QueryItems=QueryBin==SpatialBins.end()?nullptr:&QueryBin->second; if(QueryItems) { for(uint32 Id:*QueryItems) { #if defined(VF_L76_PROFILE_LEGACY_TERMS) if(LegacyFieldTermScopeActive()) ++ThreadClassificationStats().LegacyPrimitiveCandidateVisits; #endif const FPrimitive& P=Primitives[Id]; const VF_FieldPrimitive& Shared=SharedFieldPrimitives[Id]; if(P.bCarve) { if(P.bCritical&&X>=P.Min[0]&&X<=P.Max[0]&&Y>=P.Min[1]&&Y<=P.Max[1] &&Z>=P.Min[2]&&Z<=P.Max[2]&&ShapeContains(P, static_cast(X),static_cast(Y),static_cast(Z))) bCriticalAir=true; if(XP.Max[0]+DetailBand ||YP.Max[1]+DetailBand ||ZP.Max[2]+DetailBand)continue; #if defined(VF_L76_PROFILE_LEGACY_TERMS) if(LegacyFieldTermScopeActive()) ++ThreadClassificationStats().LegacyPrimitiveDistanceEvaluations; #endif float Distance=VF_FieldPrimitiveDistance(Shared,X,Y,Z); if(P.bHasFloorClip) { const double Floor=VF_FieldPrimitiveFloorAt(Shared,X,Y); Distance=VF_FieldSmoothMax(Distance,static_cast(Floor-Z), static_cast(P.FloorBlendRadius)); } float Combined=0.0f; { FScopedLegacySmoothMinTimer SmoothMinTimer; Combined=VF_FieldSmoothMin(Cave,Distance, static_cast(P.BlendRadius)); } Cave=Combined; } else if(X>=P.Min[0]&&X<=P.Max[0]&&Y>=P.Min[1]&&Y<=P.Max[1] &&Z>=P.Min[2]&&Z<=P.Max[2]) { #if defined(VF_L76_PROFILE_LEGACY_TERMS) if(LegacyFieldTermScopeActive()) ++ThreadClassificationStats().LegacyPrimitiveDistanceEvaluations; #endif Overlay=std::max(Overlay,-VF_FieldPrimitiveDistance(Shared,X,Y,Z)); } } } if(OutCriticalAir)*OutCriticalAir=bCriticalAir; float RockDistance=0.0f; { FScopedLegacySmoothMinTimer SmoothMinTimer; RockDistance=VF_FieldSmoothMin(Base,std::max(Cave,Overlay), StrataCaveBlendRadius); } return RockDistance; } struct FOwnerNoiseLineContext { struct FAnchorCache { const FLayerField* Field=nullptr; int32 GridZ0=MIN_int32; int32 SampleZ0=MIN_int32,SampleZ1=MIN_int32; float LowerCore=0.0f,UpperCore=0.0f; } Step8,Step2; }; float OwnerCaveRoughnessCore(double X,double Y,double Z, const FStrateGenerationParams& Params) const { // Keep CPU prediction and the near-field HLSL on one scalar source. The helper // preserves the legacy seed offsets, octave order, and roughness families. return VF_FieldOwnerRoughnessCore(static_cast(X),static_cast(Y), static_cast(Z),static_cast(Owner->Seed),Params.SurfaceRoughness, Params.RoughnessFrequency,Params.VerticalScale,Params.DomainWarpStrength, Params.DomainWarpFrequency,static_cast(Params.RoughnessNoiseType)); } float OwnerCaveRoughness(double X,double Y,double Z,float SurfaceSDF, const FLayerField& Field,float CaveInfluence, FOwnerNoiseLineContext& LineContext, FDetailWorkerProfile* Profile=nullptr, int32 AnchorStep=8) const { const FStrateGenerationParams& Params=Field.Params; const float Depth=Params.SurfaceRoughness*2.0f; if(!(Depth>0.0f)||std::abs(SurfaceSDF)>=Depth||!(Params.RoughnessFrequency>0.0f))return 0.0f; if(Profile)++Profile->OwnerRoughnessActiveCalls; const int32 ZStep=std::max(1,AnchorStep); const int32 GridZ0=static_cast(std::floor(Z/static_cast(ZStep)))*ZStep; const int32 SampleZ0=std::max(GridZ0,Field.Lo); const int32 SampleZ1=std::min(GridZ0+ZStep,Field.Hi); float Total=0.0f; if(ZStep==1) Total=OwnerCaveRoughnessCore(X,Y,Z,Params); else if(SampleZ1>SampleZ0) { FOwnerNoiseLineContext::FAnchorCache& Cache=ZStep==2 ?LineContext.Step2:LineContext.Step8; const bool bCacheHit=Cache.Field==&Field&&Cache.GridZ0==GridZ0 &&Cache.SampleZ0==SampleZ0&&Cache.SampleZ1==SampleZ1; if(Profile) { if(bCacheHit)++Profile->OwnerNoiseCacheHits; else ++Profile->OwnerNoiseCacheMisses; } if(!bCacheHit) { const bool bReusesPreviousUpper=Cache.Field==&Field &&Cache.SampleZ1==SampleZ0; Cache.Field=&Field; Cache.GridZ0=GridZ0; Cache.SampleZ0=SampleZ0; Cache.SampleZ1=SampleZ1; Cache.LowerCore=bReusesPreviousUpper?Cache.UpperCore :OwnerCaveRoughnessCore(X,Y,SampleZ0,Params); Cache.UpperCore=OwnerCaveRoughnessCore(X,Y,SampleZ1,Params); } const float Alpha=static_cast((Z-SampleZ0)/(SampleZ1-SampleZ0)); Total=FMath::Lerp(Cache.LowerCore,Cache.UpperCore,Alpha); } else Total=OwnerCaveRoughnessCore(X,Y,Z,Params); if(SurfaceSDF<0.0f) Total=std::lerp(Total,std::min(Total,0.0f),std::clamp(CaveInfluence,0.0f,1.0f)); const float Fade=1.0f-std::abs(SurfaceSDF)/Depth; return Total*Fade*Fade; } float OwnerCaveRoughnessForDetail(double X,double Y,double Z,float SurfaceSDF, const FLayerField& Field,float CaveInfluence, float PassageInfluence, FOwnerNoiseLineContext& LineContext, FDetailWorkerProfile* Profile=nullptr) const { constexpr float DirectStart=0.40f,DirectEnd=0.60f; constexpr int32 PassageAnchorStep=2; const float PassageWeight=std::clamp(PassageInfluence,0.0f,1.0f); if(PassageWeight>=DirectEnd) return OwnerCaveRoughness(X,Y,Z,SurfaceSDF,Field,CaveInfluence, LineContext,Profile,PassageAnchorStep); const float Anchored=OwnerCaveRoughness(X,Y,Z,SurfaceSDF,Field, CaveInfluence,LineContext,Profile); if(PassageWeight<=DirectStart)return Anchored; const float Direct=OwnerCaveRoughness(X,Y,Z,SurfaceSDF,Field, CaveInfluence,LineContext,Profile,PassageAnchorStep); const float DirectWeight=SmoothUnit((PassageWeight-DirectStart) /(DirectEnd-DirectStart)); const float AnchoredWeight=1.0f-DirectWeight; const float Normalization=1.0f/std::sqrt( AnchoredWeight*AnchoredWeight+DirectWeight*DirectWeight); return (AnchoredWeight*Anchored+DirectWeight*Direct)*Normalization; } static float SurfaceRelief(float X,float Y,float Z,uint32 Seed, float* OutPassageBandRelief=nullptr) { // Keep all active bands at four voxels or wider: the full-detail lattice can // resolve them without turning the surface into per-voxel steps. The broad // owner comparison showed that the shortest octave needed more usable relief. const float Macro=FStandaloneResearchTunnels::ValueNoise(X,Y,Z,32,Seed^0xB5297A4Du); const float Mid=FStandaloneResearchTunnels::ValueNoise(X,Y,Z,16,Seed^0x63D83595u); const float Fine=FStandaloneResearchTunnels::ValueNoise(X,Y,Z,4,Seed^0xA511E9B3u); if(OutPassageBandRelief) *OutPassageBandRelief=1.20f*Macro+(0.80f+0.25f*Macro)*(0.40f*Mid); return 1.20f*Macro+(0.80f+0.25f*Macro)*(0.40f*Mid+0.60f*Fine); } struct FValueNoiseLineContext { int32 X0=0,Y0=0,Z0=std::numeric_limits::min(); int32 Wavelength=1; uint32 Seed=0; float Inv=1.0f,TX=0.0f,TY=0.0f; std::array Corners{}; FValueNoiseLineContext(float X,float Y,int32 InWavelength,uint32 InSeed) :Wavelength(InWavelength),Seed(InSeed),Inv(1.0f/static_cast(InWavelength)) { const float FX=X*Inv,FY=Y*Inv; X0=FMath::FloorToInt(FX);Y0=FMath::FloorToInt(FY); TX=FStandaloneResearchTunnels::Fade(FX-static_cast(X0)); TY=FStandaloneResearchTunnels::Fade(FY-static_cast(Y0)); } void SetPlane(int32 Z,bool bUpper) { const size_t I=bUpper?4u:0u; Corners[I+0]=FStandaloneResearchTunnels::NoiseValue(X0,Y0,Z,Seed); Corners[I+1]=FStandaloneResearchTunnels::NoiseValue(X0+1,Y0,Z,Seed); Corners[I+2]=FStandaloneResearchTunnels::NoiseValue(X0,Y0+1,Z,Seed); Corners[I+3]=FStandaloneResearchTunnels::NoiseValue(X0+1,Y0+1,Z,Seed); } float Sample(float Z) { const float FZ=Z*Inv; const int32 NewZ0=FMath::FloorToInt(FZ); const float TZ=FStandaloneResearchTunnels::Fade(FZ-static_cast(NewZ0)); if(NewZ0!=Z0) { if(NewZ0==Z0+1) { Corners[0]=Corners[4];Corners[2]=Corners[6]; Corners[1]=Corners[5];Corners[3]=Corners[7]; SetPlane(NewZ0+1,true); } else { SetPlane(NewZ0,false);SetPlane(NewZ0+1,true); } Z0=NewZ0; } const float Z00=FMath::Lerp(Corners[0],Corners[1],TX); const float Z10=FMath::Lerp(Corners[2],Corners[3],TX); const float Z01=FMath::Lerp(Corners[4],Corners[5],TX); const float Z11=FMath::Lerp(Corners[6],Corners[7],TX); return FMath::Lerp(FMath::Lerp(Z00,Z10,TY),FMath::Lerp(Z01,Z11,TY),TZ); } }; struct FDetailNoiseLineContext { FValueNoiseLineContext Macro,Mid,Fine; FOwnerNoiseLineContext OwnerNoise; FDetailNoiseLineContext(float X,float Y,uint32 Seed) :Macro(X,Y,32,Seed^0xB5297A4Du),Mid(X,Y,16,Seed^0x63D83595u), Fine(X,Y,4,Seed^0xA511E9B3u) {} float SurfaceReliefAt(float Z,bool bMacroKnown=false,float KnownMacro=0.0f, float* OutPassageBandRelief=nullptr) { const float MacroValue=bMacroKnown?KnownMacro:Macro.Sample(Z); const float MidValue=Mid.Sample(Z); const float FineValue=Fine.Sample(Z); if(OutPassageBandRelief) *OutPassageBandRelief=1.20f*MacroValue +(0.80f+0.25f*MacroValue)*(0.40f*MidValue); return 1.20f*MacroValue+(0.80f+0.25f*MacroValue) *(0.40f*MidValue+0.60f*FineValue); } }; float DetailNoise(double X,double Y,double Z,float SurfaceSDF, float CaveInfluence,float FloorInfluence,float PassageInfluence) const { if(DetailVariant==EDetailVariant::Smooth)return 0.0f; if(DetailVariant==EDetailVariant::PerVoxel) { const int32 IX=static_cast(std::floor(X)),IY=static_cast(std::floor(Y)),IZ=static_cast(std::floor(Z)); return FStandaloneResearchTunnels::NoiseValue(IX,IY,IZ,static_cast(Owner->Seed)) *DetailSupremum; } float PassageBandRelief=0.0f; float Relief=0.0f; { FScopedLegacyFieldTermTimer TermTimer(ELegacyFieldTerm::SurfaceReliefNoise); Relief=SurfaceRelief(static_cast(X),static_cast(Y), static_cast(Z),static_cast(Owner->Seed),&PassageBandRelief); } const float PassageWeight=std::clamp(PassageInfluence,0.0f,1.0f); const FLayerField* Field=FindLayerField(Z); FOwnerNoiseLineContext OwnerNoise; float OwnerRelief=0.0f; if(Field) { FScopedLegacyFieldTermTimer TermTimer(ELegacyFieldTerm::OwnerNoiseAndWarp); OwnerRelief=OwnerCaveRoughnessForDetail(X,Y,Z,SurfaceSDF,*Field, CaveInfluence,PassageWeight,OwnerNoise); } const float Detail=std::clamp(BlendDetailFamilies(Relief,PassageBandRelief, OwnerRelief,CaveInfluence,FloorInfluence,PassageWeight),-3.9f,3.9f); return Detail; } float DetailNoiseOnLine(double X,double Y,double Z,float SurfaceSDF, float CaveInfluence,float FloorInfluence,float PassageInfluence, FDetailNoiseLineContext& NoiseLine, FDetailWorkerProfile* Profile=nullptr, const FLayerField* KnownField=nullptr, bool bFieldKnown=false,bool bMacroKnown=false, float KnownMacro=0.0f) const { if(DetailVariant!=EDetailVariant::Coherent) return DetailNoise(X,Y,Z,SurfaceSDF,CaveInfluence,FloorInfluence,PassageInfluence); using Clock=std::chrono::steady_clock; const auto ReliefStart=Profile?Clock::now():Clock::time_point{}; float PassageBandRelief=0.0f; float Relief=0.0f; { FScopedLegacyFieldTermTimer TermTimer(ELegacyFieldTerm::SurfaceReliefNoise); Relief=NoiseLine.SurfaceReliefAt(static_cast(Z), bMacroKnown,KnownMacro,&PassageBandRelief); } if(Profile) Profile->ReliefSampleNanoseconds+=static_cast( std::chrono::duration_cast(Clock::now()-ReliefStart).count()); const auto LookupStart=Profile?Clock::now():Clock::time_point{}; const FLayerField* Field=bFieldKnown?KnownField:FindLayerField(Z); if(Profile) Profile->LayerLookupSampleNanoseconds+=static_cast( std::chrono::duration_cast(Clock::now()-LookupStart).count()); const auto OwnerStart=Profile?Clock::now():Clock::time_point{}; if(Profile&&Field)++Profile->OwnerRoughnessFieldCalls; const float PassageWeight=std::clamp(PassageInfluence,0.0f,1.0f); float OwnerRelief=0.0f; if(Field) { FScopedLegacyFieldTermTimer TermTimer(ELegacyFieldTerm::OwnerNoiseAndWarp); OwnerRelief=OwnerCaveRoughnessForDetail(X,Y,Z,SurfaceSDF,*Field, CaveInfluence,PassageWeight,NoiseLine.OwnerNoise,Profile); } if(Profile) Profile->OwnerRoughnessSampleNanoseconds+=static_cast( std::chrono::duration_cast(Clock::now()-OwnerStart).count()); const float Detail=std::clamp(BlendDetailFamilies(Relief,PassageBandRelief, OwnerRelief,CaveInfluence,FloorInfluence,PassageWeight),-3.9f,3.9f); return Detail; } bool EvaluateFast(double X,double Y,double Z) const { ESurfaceFamily SurfaceFamily=ESurfaceFamily::Strata;float SurfaceSDF=100000.0f; bool bFloorSurface=false,bCriticalAir=false;float CaveInfluence=0.0f,FloorInfluence=0.0f; float PassageInfluence=0.0f; float RockDistance=EffectiveRockDistance(X,Y,Z,&SurfaceFamily,&SurfaceSDF, &bFloorSurface,&CaveInfluence,&FloorInfluence,&bCriticalAir,nullptr,false, &PassageInfluence); const bool bInsideFine=X>=Min[0]&&X<=Max[0]&&Y>=Min[1]&&Y<=Max[1]&&Z>=Min[2]&&Z<=Max[2]; if(bInsideFine&&DetailVariant!=EDetailVariant::Smooth &&std::abs(RockDistance)<=DetailSupremum+0.125f) { const float Fade=DetailBandFadeAt(X,Y,Z,RockDistance,SurfaceSDF, CaveInfluence,FloorInfluence); if(Fade>0.0f)RockDistance+=DetailNoise(X,Y,Z,SurfaceSDF,CaveInfluence, FloorInfluence,PassageInfluence)*Fade; } if(bCriticalAir)RockDistance=std::min(RockDistance,-1.0f/16.0f); return RockDistance>=0.0f; } bool ValidatePassageRoutes() { if(Owner&&Owner->bHasUnrealResolvedCorePlan &&ResolvedLayout&&!ResolvedLayout->passages.empty()) { constexpr double SampleStep=0.5; uint32 PassageIndex=0; for(const auto& passage:ResolvedLayout->passages) { const auto& points=passage.controlPoints; if(points.size()<2){++PassageIndex;continue;} uint64 routeSamples=0,routeFailures=0; int32 firstFailure[3]={0,0,0}; for(std::size_t segment=0;segment+1(a[0])/VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel; const double ay=static_cast(a[1])/VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel; const double az=static_cast(a[2])/VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel; const double bx=static_cast(b[0])/VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel; const double by=static_cast(b[1])/VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel; const double bz=static_cast(b[2])/VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel; const double length=std::sqrt((bx-ax)*(bx-ax)+(by-ay)*(by-ay)+(bz-az)*(bz-az)); const int32 count=std::max(1,static_cast(std::ceil(length/SampleStep))); uint64 segmentFailures=0; for(int32 sample=0;sample<=count;++sample) { const double t=static_cast(sample)/count; const int32 vx=static_cast(std::lround(ax+(bx-ax)*t)); const int32 vy=static_cast(std::lround(ay+(by-ay)*t)); const int32 vz=static_cast(std::lround(az+(bz-az)*t)); ++Stats.WalkSamples; ++routeSamples; const float density=DensityAt(static_cast(ax+(bx-ax)*t), static_cast(ay+(by-ay)*t), static_cast(az+(bz-az)*t)); if(!std::isfinite(density)||density<0.0f) { ++Stats.WalkFailures; ++routeFailures; ++segmentFailures; if(routeFailures==1) {firstFailure[0]=vx;firstFailure[1]=vy;firstFailure[2]=vz;} } } if(segmentFailures) std::fprintf(stderr,"[WalkabilitySegment] source=resolved_core_layout passage=%s segment=%zu failures=%llu/%llu length=%.2f\n", passage.stableId.c_str(),segment, static_cast(segmentFailures), static_cast(std::max(1,static_cast(std::ceil(length/SampleStep))+1)), length); } if(routeFailures) std::fprintf(stderr,"[WalkabilityRoute] source=resolved_core_layout passage=%u id=%s points=%zu failures=%llu/%llu first_solid=(%d,%d,%d)\n", PassageIndex,passage.stableId.c_str(),points.size(), static_cast(routeFailures), static_cast(routeSamples),firstFailure[0], firstFailure[1],firstFailure[2]); ++PassageIndex; } if(Stats.WalkFailures) std::fprintf(stderr,"[Walkability] FAIL source=resolved_core_layout %llu/%llu compiled Core passage centerline samples are solid\n", static_cast(Stats.WalkFailures), static_cast(Stats.WalkSamples)); else std::printf("[Walkability] PASS source=resolved_core_layout %llu/%llu compiled Core passage centerline samples remain open\n", static_cast(Stats.WalkSamples), static_cast(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()) { const auto& Points=P.ControlPoints; if(Points.Num()<2){++PassageIndex;continue;} uint64 RouteSamples=0,RouteFailures=0,RouteCritical=0; int32 FirstFailure[3]={0,0,0};bool bFirstFailureCritical=false; for(int32 I=0;I+1(std::ceil(L/SampleStep))); uint64 SegmentSamples=0,SegmentFailures=0,SegmentCritical=0; for(int32 J=0;J<=Count;++J) { const double T=static_cast(J)/Count; const double X=A.X+(B.X-A.X)*T,Y=A.Y+(B.Y-A.Y)*T,Z=A.Z+(B.Z-A.Z)*T; ++Stats.WalkSamples; ++RouteSamples;++SegmentSamples; const int32 VX=static_cast(std::lround(X)); const int32 VY=static_cast(std::lround(Y)); const int32 VZ=static_cast(std::lround(Z)); const bool bCritical=IsInsideCriticalAir(VX,VY,VZ); if(bCritical){++RouteCritical;++SegmentCritical;} if(IsRock(VX,VY,VZ)) { ++Stats.WalkFailures;++RouteFailures;++SegmentFailures; if(RouteFailures==1) {FirstFailure[0]=VX;FirstFailure[1]=VY;FirstFailure[2]=VZ;bFirstFailureCritical=bCritical;} } } if(SegmentFailures) std::fprintf(stderr,"[WalkabilitySegment] passage=%u segment=%d failures=%llu/%llu critical=%llu length=%.2f start=(%.2f,%.2f,%.2f) end=(%.2f,%.2f,%.2f)\n", PassageIndex,I,static_cast(SegmentFailures), static_cast(SegmentSamples), static_cast(SegmentCritical),L, A.X,A.Y,A.Z,B.X,B.Y,B.Z); } if(RouteFailures) std::fprintf(stderr,"[WalkabilityRoute] passage=%u type=%u points=%d failures=%llu/%llu critical_centerline=%llu first_solid=(%d,%d,%d) critical=%u\n", PassageIndex,static_cast(P.PassageType),Points.Num(), static_cast(RouteFailures), static_cast(RouteSamples), static_cast(RouteCritical),FirstFailure[0], FirstFailure[1],FirstFailure[2],bFirstFailureCritical?1u:0u); ++PassageIndex; } if(Stats.WalkFailures) std::fprintf(stderr,"[Walkability] FAIL %llu/%llu passage centerline samples are solid\n", static_cast(Stats.WalkFailures),static_cast(Stats.WalkSamples)); else std::printf("[Walkability] PASS %llu/%llu passage centerline samples remain open; native landing floors retained\n", static_cast(Stats.WalkSamples),static_cast(Stats.WalkSamples)); return Stats.WalkFailures==0; } static double DetailAutocorrelation(EDetailVariant Variant) { constexpr int32 N=24;double Sum=0.0,SumSq=0.0,PairA=0.0,PairB=0.0,PairAA=0.0,PairBB=0.0,PairAB=0.0;uint64 Count=0; auto Value=[&](int32 X,int32 Y,int32 Z)->double { if(Variant==EDetailVariant::PerVoxel)return FStandaloneResearchTunnels::NoiseValue(X,Y,Z,11); if(Variant==EDetailVariant::Smooth)return 0.0; return SurfaceRelief(static_cast(X),static_cast(Y),static_cast(Z),11); }; for(int32 Z=0;Z void ParallelLines(size_t Count,Fn&& Work,FParallelTiming* Timing=nullptr, std::array* WorkerActiveSeconds=nullptr,size_t WorkBatch=1) const { const auto WallStart=std::chrono::steady_clock::now(); if(WorkerCount<=1||Count<512||!Scheduler) { const auto WorkerStart=std::chrono::steady_clock::now(); for(size_t I=0;I)Work(I,0); else Work(I); } if(WorkerActiveSeconds) (*WorkerActiveSeconds)[0]=std::chrono::duration( std::chrono::steady_clock::now()-WorkerStart).count(); if(Timing) { const double WorkerSeconds=std::chrono::duration(std::chrono::steady_clock::now()-WorkerStart).count(); Timing->WallSeconds+=std::chrono::duration(std::chrono::steady_clock::now()-WallStart).count(); Timing->WorkerSeconds+=WorkerSeconds; } return; } WorkBatch=std::max(WorkBatch,1); using WorkType=std::remove_reference_t; struct FDispatchContext { WorkType* Work; } Dispatch{&Work}; const auto Invoke=[](void* Opaque,size_t Index,size_t Worker) { auto& Context=*static_cast(Opaque); if constexpr(std::is_invocable_v) (*Context.Work)(Index,static_cast(Worker)); else (*Context.Work)(Index); }; std::array ActiveSeconds{}; const double WorkerSeconds=Scheduler->parallel_for(Count,WorkBatch, static_cast(WorkerCount),&Dispatch,Invoke,ActiveSeconds.data(),ActiveSeconds.size()); if(WorkerActiveSeconds)*WorkerActiveSeconds=ActiveSeconds; if(Timing) { Timing->WallSeconds+=std::chrono::duration(std::chrono::steady_clock::now()-WallStart).count(); Timing->WorkerSeconds+=WorkerSeconds; } } void InvalidateFarFieldGpuInput() { std::lock_guard Lock(FarFieldGpuInputMutex); CachedFarFieldGpuInput.reset(); } FStandaloneOwnerWorld* Owner=nullptr; const VoxelForgeCore::WorldAssemblyManifest* WorldAssembly=nullptr; const FStandaloneShapeWorld* SharedDensitySource=nullptr; int32 WorkerCount=6; const VoxelForgeCore::JobScheduler* Scheduler=nullptr; bool RoundTunnelsEnabled=false; bool bProfileDetailBand=false; bool bParallelSignEdgeRepair=false; int32 Min[3]{},Max[3]{},ClipMin[3]{},ClipMax[3]{},Size[3]{}; FIntVector EditBoundsMin=FIntVector(MAX_int32,MAX_int32,MAX_int32); FIntVector EditBoundsMax=FIntVector(MIN_int32,MIN_int32,MIN_int32); int32 ActiveSpatialBinSize=LineBinSize; double ActiveBlendRadius=0.0; double ActiveFloorBlendRadius=0.0; size_t WordsPerZ=0; EDetailVariant DetailVariant=EDetailVariant::Coherent; bool Built=false; bool bNativeOnly=false; bool bUseNativeManifestFields=false; float BoundManagerLayoutChunkWidthVoxels=0.0f; FIntVector NativeOnlyMinimum=FIntVector::ZeroValue; FIntVector NativeOnlyMaximum=FIntVector::ZeroValue; mutable std::atomic NativeOnlyViolations{0}; bool FarBuilt=false; std::vector> ResolvedBoundaryZIntervalsMicrometres; std::vector BoundStratumFields; std::vector LayerRanges; std::vector LayerFields; std::vector Primitives; std::vector SharedFieldPrimitives; std::vector FarConnectorSegments; std::unordered_map,FBinHash> FarConnectorSpatialBins; std::vector EditBrushes; std::array AxisBins; std::unordered_map SpatialBins; std::vector UnbinnedSpatialPrimitiveIds; std::unordered_map CriticalAirBins; mutable std::mutex FarFieldGpuInputMutex; mutable std::shared_ptr CachedFarFieldGpuInput; std::vector RockWords; std::unique_ptr SurfaceDistanceQ; std::array,3> CandidateAxisWords; std::vector CandidateWords; std::vector GpuVisualDetailWords,CpuVisualDetailWords; uint64 GpuVisualDetailTileCount=0,CpuVisualDetailTileCount=0; uint64 GpuVisualDetailGridSamples=0,CpuVisualDetailGridSamples=0; uint64 GpuVisualDetailNodeCount=0,CpuVisualDetailNodeCount=0; FBuildStats Stats; bool bAnalyticOnly=false; inline static thread_local const FScopedPassageTileQueries* ActivePassageTileQueries=nullptr; std::vector StampSpatialBounds; std::vector StampSpatialNodes; std::vector StampSpatialOrder; std::vector StampRuntime; std::vector OreVeinSpatialBounds; std::vector OreVeinSpatialNodes; std::vector OreVeinSpatialOrder; std::vector OreVeinRuntime; std::vector CavernRuntime; std::vector CavernSpatialNodes; std::vector CavernSpatialOrder; std::vector PassageFeatureBounds; std::vector PassageTileFeatures; std::vector PassageFeatureOrder; std::vector PassageFeatureNodes; bool PassageFeatureBoundsFailOpen=false; std::vector> PassageNearestNodes; std::vector PassageComposerConstants; std::vector> PassageSegmentData; std::vector PassageSegmentOffsets; std::vector PassageProofBounds; std::vector PassageProofNodes; }; 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(); sweep->gridMinimumSubvoxels={ (static_cast(origin.X)-step)*Q, (static_cast(origin.Y)-step)*Q, (static_cast(origin.Z)-step)*Q}; sweep->stepSubvoxels=static_cast(step)*Q; sweep->dimension=cells+3; const std::size_t gridSampleCount=static_cast(sweep->dimension) *static_cast(sweep->dimension)*static_cast(sweep->dimension); sweep->influenceSubvoxels=InfluenceMetres*static_cast(Q)*1000000.0 /std::max(1,shape_->ResolvedLayout ?shape_->ResolvedLayout->coreVoxelSizeMicrometres:250000); sweep->passageToSweep.assign(passageRouteSegments_.size(),-1); std::vector anyPassageInfluence(gridSampleCount,0); const double qPerMetre=static_cast(Q)*1000000.0/ std::max(1,shape_->ResolvedLayout ?shape_->ResolvedLayout->coreVoxelSizeMicrometres:250000); const double shellThickness=0.75*qPerMetre; constexpr double MouthShellBlendMetres=5.0; 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(minimum -sweep->gridMinimumSubvoxels[axis])/sweep->stepSubvoxels; const double relativeMaximum=static_cast(maximum -sweep->gridMinimumSubvoxels[axis])/sweep->stepSubvoxels; first=std::max(0,static_cast(std::ceil(relativeMinimum))); last=std::min(sweep->dimension-1, static_cast(std::floor(relativeMaximum))); }; for(std::size_t passageIndex=0;passageIndex(sweep->passages.size()); sweep->passageToSweep[passageIndex]=sweepIndex; auto& passageSamples=sweep->passages.emplace_back(); passageSamples.passageIndex=passageIndex; passageSamples.samples.resize(gridSampleCount); const auto& constants=shape_->PassageComposerConstants[passageIndex]; for(const auto& candidate:candidates) { if(candidate.segmentIndex>=shape_->PassageSegmentData[passageIndex].size())continue; std::array 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& segment=shape_->PassageSegmentData[passageIndex][candidate.segmentIndex]; if(segment.lengthSq<=0.0)continue; 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(sweep->gridMinimumSubvoxels[0] +static_cast(gx)*sweep->stepSubvoxels); const double qy=static_cast(sweep->gridMinimumSubvoxels[1] +static_cast(gy)*sweep->stepSubvoxels); const double qz=static_cast(sweep->gridMinimumSubvoxels[2] +static_cast(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]; 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; const double floorT=segment.horizontalLengthSq>1.0e-12 ?std::clamp(projectionXY/segment.horizontalLengthSq,0.0,1.0):t; const double floorRadius=segment.radiusA+floorT*segment.radiusDelta; const double floorDepth=FStandaloneShapeWorld::PassageFlatFloorDepthSubvoxels( constants,candidate.segmentIndex,floorT,floorRadius,qPerMetre); const double floorCenterZ= FStandaloneShapeWorld::PassageFlatFloorCenterZSubvoxels( constants,candidate.segmentIndex,floorT,segment,floorDepth,qPerMetre); const double flatDistance=FStandaloneShapeWorld::PassageFlatBottomDistance( static_cast(signedDistance),floorCenterZ,qz,floorDepth,qPerMetre); if(profile)++profileStats_->passageSweepSegmentSamplePairs; if(signedDistance>sweep->influenceSubvoxels) { if(profile)++profileStats_->passageSweepOutsideInfluencePairs; } const std::size_t sampleIndex=(static_cast(gz) *static_cast(sweep->dimension)+static_cast(gy)) *static_cast(sweep->dimension)+static_cast(gx); auto& sample=passageSamples.samples[sampleIndex]; const double alongMetres=segment.authoredAlongMetres +t*segment.authoredLengthMetres; const double mouthDistanceMetres=std::max(0.0,std::min(alongMetres, constants.totalPathLengthMetres-alongMetres)); const double shellFade=std::clamp(mouthDistanceMetres/ MouthShellBlendMetres,0.0,1.0); const double smoothShellFade=shellFade*shellFade *(3.0-2.0*shellFade); const double floorDistanceMetres=(floorCenterZ-floorDepth-qz)/qPerMetre; const double floorBlend=std::clamp(floorDistanceMetres/0.25,0.0,1.0); const double smoothFloorBlend=floorBlend*floorBlend *(3.0-2.0*floorBlend); const double mouthWallThickness=candidate.segmentIndex =constants.routeEndControlIndex) { const double pastMouthMetres=std::max(0.0,alongMetres -constants.totalPathLengthMetres); const double extensionFade=std::clamp(1.0 -pastMouthMetres/MouthShellBlendMetres,0.0,1.0); floorExtensionFade=extensionFade*extensionFade *(3.0-2.0*extensionFade); } const double floorShellThickness=smoothFloorBlend*shellThickness *floorExtensionFade*smoothShellFade; const double localThickness=std::max(mouthWallThickness, floorShellThickness); const double localShellDistance=std::max(-static_cast(flatDistance), static_cast(flatDistance)-localThickness); sample.shellSignedDistance=std::min(sample.shellSignedDistance, localShellDistance); if(!sample.valid||flatDistance(candidate.segmentIndex); sample.valid=true; sample.hasWallRelief=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(sampleIndex %static_cast(sweep->dimension)); const std::int32_t gy=static_cast((sampleIndex /static_cast(sweep->dimension)) %static_cast(sweep->dimension)); const std::int32_t gz=static_cast(sampleIndex /(static_cast(sweep->dimension) *static_cast(sweep->dimension))); const double qx=static_cast(sweep->gridMinimumSubvoxels[0] +static_cast(gx)*sweep->stepSubvoxels); const double qy=static_cast(sweep->gridMinimumSubvoxels[1] +static_cast(gy)*sweep->stepSubvoxels); const double qz=static_cast(sweep->gridMinimumSubvoxels[2] +static_cast(gz)*sweep->stepSubvoxels); const double radius=sample.radiusSubvoxels; const double centerZ=sample.centerZSubvoxels; if(sample.signedDistance<=sweep->influenceSubvoxels) { anyPassageInfluence[sampleIndex]=1; if(profile)++profileStats_->passageSweepBaseInfluenceSamples; } if(std::abs(sample.roundSignedDistance)<=wallReliefShell) { const float x=static_cast(qx/Q); const float y=static_cast(qy/Q); const float z=static_cast(qz/Q); sample.wallRelief=VF_FieldPassageWallRelief(x,y,z,seed); sample.hasWallRelief=true; if(profile) { ++profileStats_->passageSweepDetailShellSamples; ++profileStats_->passageReliefFieldCalls; } } } } if(profile) { profileStats_->passageSweepNoInfluenceGridSamples= static_cast(std::count(anyPassageInfluence.begin(), anyPassageInfluence.end(),static_cast(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); const auto recordMissing=[&]() { sweep->missingSamples.fetch_add(1,std::memory_order_relaxed); if(!profileStats_)return; const auto caller=static_cast( FStandaloneShapeWorld::CurrentPassageDensityQueryCaller()); if(callermissingLookupsByCaller[caller].fetch_add(1,std::memory_order_relaxed); }; if(passageIndex>=sweep->passageToSweep.size()) { recordMissing(); return nullptr; } const std::int32_t passageSweepIndex=sweep->passageToSweep[passageIndex]; if(passageSweepIndex<0||static_cast(passageSweepIndex)>=sweep->passages.size()) { recordMissing(); return nullptr; } const std::array query={qx,qy,qz}; std::array 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::llround(coordinate)); if(rounded<0||rounded>=sweep->dimension ||std::abs(coordinate-static_cast(rounded))>1.0e-5) { recordMissing(); return nullptr; } grid[axis]=rounded; } const std::size_t index=(static_cast(grid[2]) *static_cast(sweep->dimension)+static_cast(grid[1])) *static_cast(sweep->dimension)+static_cast(grid[0]); const auto& samples=sweep->passages[static_cast(passageSweepIndex)].samples; if(index>=samples.size()||!samples[index].valid) { recordMissing(); 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]; }