#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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // 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: 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); } }; using FGeneratorFieldAdapterV1=std::variant; struct FBoundStratumField { enum class EKind : uint8 { Legacy, SurfaceWorld, FloatingIslands }; 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; }; const std::vector& GetPassageVoidCrossings() const {return PassageVoidCrossings;} class FScopedPassageTileQueries { public: FScopedPassageTileQueries(const FStandaloneShapeWorld& shape, const std::array& minimumSubvoxels, const std::array& maximumSubvoxels) :shape_(&shape),minimumSubvoxels_(minimumSubvoxels), maximumSubvoxels_(maximumSubvoxels),previous_(ActivePassageTileQueries) { mayContainResolvedBoundaryBand_=shape.ResolvedBoundaryBandsIntersectSubvoxelRange( minimumSubvoxels_[2],maximumSubvoxels_[2]); mayContainTransitionPassageFade_=shape.ResolvedTransitionPassageFadeIntersectsTileXY( minimumSubvoxels_,maximumSubvoxels_); featureIndices_=shape.BuildPassageFeatureTileList( minimumSubvoxels_,maximumSubvoxels_); stampIndices_=shape.BuildStampTileList(minimumSubvoxels_,maximumSubvoxels_); cavernFeatureIndices_=shape.BuildCavernFeatureTileList( minimumSubvoxels_,maximumSubvoxels_); passageRoutes_.assign(shape.ResolvedLayout?shape.ResolvedLayout->passages.size():0,0); passageColumns_=passageRoutes_; passageMouthSupports_=passageRoutes_; for(const auto& crossing:shape.PassageVoidCrossings) if(crossing.needsColumn &&crossing.bottomZSubvoxels<=maximumSubvoxels_[2] &&crossing.topZSubvoxels>=minimumSubvoxels_[2]) columnZOverlap_=true; 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; if(feature.bColumn)passageColumns_[feature.passageIndex]=1; else if(feature.bMouthSupport)passageMouthSupports_[feature.passageIndex]=1; else { passageRoutes_[feature.passageIndex]=1; routeFeatureOverlap_=true; } } ActivePassageTileQueries=this; } ~FScopedPassageTileQueries(){ActivePassageTileQueries=previous_;} FScopedPassageTileQueries(const FScopedPassageTileQueries&)=delete; FScopedPassageTileQueries& operator=(const FScopedPassageTileQueries&)=delete; bool matches(const FStandaloneShapeWorld& shape) const{return shape_==&shape;} bool hasFeature(std::size_t passageIndex,bool columnFeature) const { const auto& candidates=columnFeature?passageColumns_:passageRoutes_; return passageIndexResolvedBoundaryBandMayAffectAtZMicrometres(zMicrometres); } std::size_t featureCount() const{return featureIndices_.size();} 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_; std::vector stampIndices_; std::vector cavernFeatureIndices_; std::vector passageRoutes_,passageColumns_,passageMouthSupports_; bool routeFeatureOverlap_=false,columnZOverlap_=false; bool mayContainResolvedBoundaryBand_=true; bool mayContainTransitionPassageFade_=true; const FScopedPassageTileQueries* previous_; }; bool IsResolvedPassageColumnSample(std::size_t PassageIndex,std::size_t SegmentIndex, const VoxelForgeCore::Vec3d& Position) const { (void)SegmentIndex; for(const auto& range:PassageVoidCrossings) { if(!range.needsColumn||range.passageIndex!=PassageIndex)continue; if(PassageColumnSignedDistance(range,static_cast(Position.x), static_cast(Position.y),static_cast(Position.z))<=0.0)return true; } return false; } bool IsResolvedPassageShortSpanSample(std::size_t PassageIndex,std::size_t SegmentIndex, const VoxelForgeCore::Vec3d& Position) const { return IsPassageShortSpanSegment(PassageIndex,SegmentIndex) &&PassageBridgeSignedDistance(PassageIndex,SegmentIndex, static_cast(Position.x),static_cast(Position.y), static_cast(Position.z))<=0.0; } 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)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); } } } 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)); } 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(!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) const { if(!ResolvedLayout||ResolvedLayout->passages.empty()||!std::isfinite(X) ||!std::isfinite(Y)||!std::isfinite(Z))return density; constexpr double Q=static_cast(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel); 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 auto* passageTileQueries=ActivePassageTileQueries; const bool passageTileScopeMatches=passageTileQueries &&passageTileQueries->matches(*this); bool queryNearUpperMouthSupport=false; for(const auto& passage:ResolvedLayout->passages) { const double dx=qx-static_cast(passage.upperMouthSubvoxels[0]); const double dy=qy-static_cast(passage.upperMouthSubvoxels[1]); const double dz=qz-static_cast(passage.upperMouthSubvoxels[2]); const double reach=15.0*qPerMetre; if(dx*dx+dy*dy<=reach*reach&&std::abs(dz)<=8.0*qPerMetre) {queryNearUpperMouthSupport=true;break;} } if(passageTileScopeMatches&&density>=0.0f &&!passageTileQueries->hasRouteFeatureOverlap() &&!passageTileQueries->hasColumnZOverlap() &&!passageTileQueries->hasAnyMouthSupport() &&!queryNearUpperMouthSupport)return density; const double floorThicknessVoxels=qPerMetre/Q; const double playerCenterAboveFloorQ=1.13*qPerMetre; float composed=density; const auto composeRouteMouthSupport=[&](std::int32_t strateIndex, const std::array& mouth,bool departure) { // Surface-route treads may reinforce native solid terrain only. if(!(density<0.0f))return; const double mouthDx=qx-static_cast(mouth[0]); const double mouthDy=qy-static_cast(mouth[1]); const double routeSupportReach=13.0*qPerMetre; if(mouthDx*mouthDx+mouthDy*mouthDy>routeSupportReach*routeSupportReach)return; const auto stratum=std::find_if(ResolvedLayout->strates.begin(), ResolvedLayout->strates.end(),[strateIndex](const auto& value) {return value.strateIndex==strateIndex;}); if(stratum==ResolvedLayout->strates.end())return; std::array route{}; std::size_t routeCount=0; for(const auto& anchor:stratum->anchors) if(anchor.kind=="route_point"&&anchor.values.size()>=3) { if(departure) { if(routeCount::infinity(); for(std::size_t index=0;index(b.values[0]-a.values[0]); const double dy=static_cast(b.values[1]-a.values[1]); const double lengthSq=dx*dx+dy*dy; const double t=lengthSq>0.0?std::clamp(((qx-a.values[0])*dx +(qy-a.values[1])*dy)/lengthSq,0.0,1.0):0.0; const double centerX=a.values[0]+t*dx,centerY=a.values[1]+t*dy; const double centerZ=a.values[2]+t*(static_cast(b.values[2])-a.values[2]); const double routeRadius=a.values.size()>=4 ?a.values[3]+t*(static_cast(b.values[3])-a.values[3]):0.0; const double halfWidth=std::clamp(routeRadius, 0.42*qPerMetre,0.56*qPerMetre); const double horizontalX=qx-centerX,horizontalY=qy-centerY; const double horizontalDistance=DeterministicSqrt( horizontalX*horizontalX+horizontalY*horizontalY); const double floorQ=centerZ-(VoxelForgeCore::WorldAssemblyEvaluatorVersion>=184 ?playerCenterAboveFloorQ+0.25*qPerMetre:1.20*qPerMetre); const double sideDistance=horizontalDistance-halfWidth; const double topDistance=qz-floorQ; const double bottomDistance=floorQ-qPerMetre-qz; const double verticalDistance=std::max(topDistance,bottomDistance); if(sideDistance>mouthBlendWidth||verticalDistance>mouthBlendWidth)continue; const double outside=DeterministicSqrt( std::max(sideDistance,0.0)*std::max(sideDistance,0.0) +std::max(verticalDistance,0.0)*std::max(verticalDistance,0.0)); const double inside=std::min(std::max(sideDistance,verticalDistance),0.0); mouthSupportDistance=std::min(mouthSupportDistance,outside+inside); } if(std::isfinite(mouthSupportDistance)) composed=SmoothMinDistance(composed, static_cast(mouthSupportDistance/Q), static_cast(mouthBlendWidth/Q)); }; // The connected passage tread continues the surface route into the tunnel. for(std::size_t passageIndex=0;passageIndexpassages.size();++passageIndex) { const auto& passage=ResolvedLayout->passages[passageIndex]; if(passage.controlPoints.size()<2)continue; const auto supportStratum=[&](std::int32_t strateIndex) ->const VoxelForgeCore::ResolvedLayoutStratumV2* { const auto it=std::find_if(ResolvedLayout->strates.begin(), ResolvedLayout->strates.end(),[strateIndex](const auto& stratum) {return stratum.strateIndex==strateIndex;}); return it==ResolvedLayout->strates.end()?nullptr:&*it; }; const auto* supportUpper=supportStratum(passage.upperStrateIndex); const auto* supportLower=supportStratum(passage.lowerStrateIndex); const bool floatingIslandPassage=supportUpper &&supportUpper->passageCapabilities.hasFloatingIslandLanding; const bool surfaceToTunnelSupport=supportUpper&&supportLower &&supportUpper->lowerInterfaceCapabilities.surfaceToTunnelLandingSupport; const double firstFloorQ=passage.controlPoints.front().positionSubvoxels[2] -passage.controlPoints.front().radiusSubvoxels+playerCenterAboveFloorQ; const double lastFloorQ=passage.controlPoints.back().positionSubvoxels[2] -passage.controlPoints.back().radiusSubvoxels+playerCenterAboveFloorQ; for(const auto& crossing:PassageVoidCrossings) { if(!crossing.needsColumn||crossing.passageIndex!=passageIndex ||qz>crossing.topZSubvoxels||qzhasFeature(passageIndex,true); if(tileProvesColumnExterior) { #if defined(VF_L30_ENABLE_COST_COUNTERS) ++ThreadClassificationStats().ColumnGuardCalls; #endif if(composed<0.0f) { #if defined(VF_L30_ENABLE_COST_COUNTERS) ++ThreadClassificationStats().ColumnGuardNonpositiveSkips; #endif continue; } const double dx=qx-crossing.centerXSubvoxels; const double dy=qy-crossing.centerYSubvoxels; const double horizontalSquared=dx*dx+dy*dy; const double maximumRadius=static_cast(crossing.maximumRadiusSubvoxels); const double exteriorLimit=maximumRadius +static_cast(composed)*Q+1.0e-4; #if defined(VF_L30_ENABLE_COST_COUNTERS) if(horizontalSquared>exteriorLimit*exteriorLimit) { ++ThreadClassificationStats().ColumnGuardRadialRejects; continue; } ++ThreadClassificationStats().ColumnSignedDistanceCalls; #else if(horizontalSquared>exteriorLimit*exteriorLimit)continue; #endif columnDistance=PassageColumnSignedDistance(crossing,X,Y,Z); } else { #if defined(VF_L30_ENABLE_COST_COUNTERS) ++ThreadClassificationStats().ColumnSignedDistanceCalls; #endif columnDistance=PassageColumnSignedDistance(crossing,X,Y,Z); } composed=std::min(composed,static_cast(columnDistance/Q)); } const bool tilePassageFree=passageTileScopeMatches &&!ActivePassageTileQueries->hasFeature(passageIndex,false); if(composed>=0.0f&&tilePassageFree) { continue; } if(qz>firstFloorQ+2.0*passage.mouthRadiusSubvoxels ||qz(passage.controlPoints.size())-1; const std::ptrdiff_t lowerMouthIndex=std::clamp( static_cast(passage.lowerMouthControlIndex),1,lastIndex); const auto mainEnd=passage.controlPoints.begin()+lowerMouthIndex+1; // The tube radius is measured from its centerline, while floorProfile is // one player-center offset away. Search the full vertical radius of the SDF; // a narrow point window misses earlier turns of a low-pitch helix. const double carveSearchMargin=std::max(passage.mouthRadiusSubvoxels, passage.midRadiusSubvoxels)+0.25*qPerMetre; const auto carveHigh=std::lower_bound(passage.controlPoints.begin(),mainEnd, qz+carveSearchMargin, [&](const auto& point,double value){return floorProfile(point)>value;}); const auto carveLow=std::lower_bound(passage.controlPoints.begin(),mainEnd, qz-carveSearchMargin, [&](const auto& point,double value){return floorProfile(point)>value;}); const std::ptrdiff_t carveBegin=std::max(0, (carveHigh-passage.controlPoints.begin())-1); const std::ptrdiff_t carveEnd=std::min(lowerMouthIndex-1, (carveLow-passage.controlPoints.begin())+1); const std::ptrdiff_t mouthSearchBegin=std::max(0,lowerMouthIndex-8); const std::ptrdiff_t mouthSearchEnd=std::min(lastIndex, lowerMouthIndex+8); if(density<0.0f&&tilePassageFree &&!PassageMayAffectDensity(passageIndex,carveBegin,carveEnd, lowerMouthIndex,mouthSearchBegin,mouthSearchEnd,lastIndex,qx,qy,qz, -static_cast(composed)*Q+0.24*qPerMetre)) { continue; } // For a positive source sample, the tube can only change it within relief reach; // the supported column tread is within 2.25 m of its centerline. A short-span // bridge's positive exterior SDF is not bounded this way, so keep its full query. // Pour un échantillon source positif, le tube ne change le champ qu'à portée du relief ; // le plancher du pilier reste à 2,25 m de son axe. Le SDF extérieur d'une courte passerelle // n'a pas cette borne, donc sa requête complète est conservée. const bool canCullPositiveRoute=passageTileScopeMatches&&density>=0.0f &&!PassageHasShortSpanSegments(passageIndex); if(canCullPositiveRoute&&!PassageMayAffectDensity(passageIndex,carveBegin,carveEnd, lowerMouthIndex,mouthSearchBegin,mouthSearchEnd,lastIndex,qx,qy,qz, std::max(2.25*qPerMetre,-static_cast(composed)*Q +0.24*qPerMetre)))continue; double bestSignedDistance=std::numeric_limits::infinity(); double bestPlayerCapsuleSignedDistance=std::numeric_limits::infinity(); double bestT=0.0; std::ptrdiff_t bestIndex=0; int bestScanPhase=std::numeric_limits::max(); std::ptrdiff_t bestScanIndex=std::numeric_limits::max(); std::ptrdiff_t bestSupportSegmentIndex=0; bool hasSupportFloor=false; bool bestSupportIsMouthTread=false; double bestSupportMouthBlend=1.0; double bestSupportDistanceSquared=std::numeric_limits::infinity(); double bestSupportDensity=std::numeric_limits::infinity(); double bestSupportNativeBlend=0.0; double bestSupportFloorQ=0.0; bool bestSupportUsesSignedDistance=false; std::ptrdiff_t selectedSupportSegmentIndex=-1; const double lowerMouthDx=qx-passage.lowerMouthSubvoxels[0]; const double lowerMouthDy=qy-passage.lowerMouthSubvoxels[1]; const double lowerMouthDz=qz-passage.lowerMouthSubvoxels[2]; const double lowerMouthDistance=DeterministicSqrt( lowerMouthDx*lowerMouthDx+lowerMouthDy*lowerMouthDy +lowerMouthDz*lowerMouthDz)/qPerMetre; const double lowerMouthT=std::clamp((20.0-lowerMouthDistance)/12.0,0.0,1.0); const double lowerMouthBlend=lowerMouthT*lowerMouthT *(3.0-2.0*lowerMouthT); const auto considerCarveSegment=[&](std::size_t segmentIndex,int scanPhase) { const std::ptrdiff_t index=static_cast(segmentIndex); const auto& a=passage.controlPoints[static_cast(index)]; const auto& b=passage.controlPoints[static_cast(index+1)]; const double ax=static_cast(a.positionSubvoxels[0]); const double ay=static_cast(a.positionSubvoxels[1]); const double az=static_cast(a.positionSubvoxels[2]); const double dx=static_cast(b.positionSubvoxels[0])-ax; const double dy=static_cast(b.positionSubvoxels[1])-ay; const double dz=static_cast(b.positionSubvoxels[2])-az; const double lengthSq=dx*dx+dy*dy+dz*dz; if(lengthSq<=0.0)return; const double t=std::clamp(((qx-ax)*dx+(qy-ay)*dy+(qz-az)*dz)/lengthSq,0.0,1.0); if(!passageTileScopeMatches) { const double horizontalLengthSq=dx*dx+dy*dy; const double playerT=horizontalLengthSq>0.0 ?std::clamp(((qx-ax)*dx+(qy-ay)*dy)/horizontalLengthSq,0.0,1.0):t; const double playerDx=qx-(ax+playerT*dx); const double playerDy=qy-(ay+playerT*dy); const double playerRadius=static_cast(a.radiusSubvoxels) +playerT*(b.radiusSubvoxels-a.radiusSubvoxels); const double playerCenterZ=az+playerT*dz-playerRadius+playerCenterAboveFloorQ; const double playerVertical=std::max(0.0, std::abs(qz-playerCenterZ)-0.54*qPerMetre); bestPlayerCapsuleSignedDistance=std::min(bestPlayerCapsuleSignedDistance, DeterministicSqrt(playerDx*playerDx+playerDy*playerDy +playerVertical*playerVertical)-0.34*qPerMetre); } double cx=ax+t*dx,cy=ay+t*dy,cz=az+t*dz; const bool smoothCarveJoin=index>2&&index+12(index-1)]; const auto& p3=passage.controlPoints[static_cast(index+2)]; constexpr double SmoothJoinWeight=1.0; const double t2=t*t,t3=t2*t; for(std::size_t axis=0;axis<3;++axis) { const double v0=static_cast(p0.positionSubvoxels[axis]); const double v1=static_cast(a.positionSubvoxels[axis]); const double v2=static_cast(b.positionSubvoxels[axis]); const double v3=static_cast(p3.positionSubvoxels[axis]); const double curve=0.5*((2.0*v1)+(-v0+v2)*t +(2.0*v0-5.0*v1+4.0*v2-v3)*t2 +(-v0+3.0*v1-3.0*v2+v3)*t3); const double line=axis==0?cx:axis==1?cy:cz; const double blended=line+(curve-line)*SmoothJoinWeight; if(axis==0)cx=blended; else if(axis==1)cy=blended; else cz=blended; } } const double ex=qx-cx,ey=qy-cy,ez=qz-cz; const double radius=static_cast(a.radiusSubvoxels) +t*(b.radiusSubvoxels-a.radiusSubvoxels); const double signedDistance=DeterministicSqrt(ex*ex+ey*ey+ez*ez)-radius; if(signedDistance(8,lowerMouthIndex)-1,qx,qy,qz, 1,bestSignedDistance,considerCarveSegment); VisitNearestPassageSegmentRange(passageIndex,mouthSearchBegin,mouthSearchEnd-1, qx,qy,qz,2,bestSignedDistance,considerCarveSegment); VisitNearestPassageSegmentRange(passageIndex,lowerMouthIndex,lastIndex-1, qx,qy,qz,3,bestSignedDistance,considerCarveSegment); } else { for(std::ptrdiff_t index=carveBegin;index<=carveEnd;++index) considerCarveSegment(static_cast(index),0); for(std::ptrdiff_t index=0;index(8,lowerMouthIndex);++index) considerCarveSegment(static_cast(index),1); for(std::ptrdiff_t index=mouthSearchBegin;index(index),2); for(std::ptrdiff_t index=lowerMouthIndex;index(index),3); } if(!std::isfinite(bestSignedDistance))continue; const auto& nearestA=passage.controlPoints[static_cast(bestIndex)]; const auto& nearestB=passage.controlPoints[static_cast(bestIndex+1)]; const double nearestRadius=nearestA.radiusSubvoxels+bestT*( static_cast(nearestB.radiusSubvoxels)-nearestA.radiusSubvoxels); const double distanceToCenter=std::max(1.0e-9,bestSignedDistance+nearestRadius); const double verticalWallAlignment=distanceToCenter>1.0e-9 ?std::clamp(-(qz-(nearestA.positionSubvoxels[2]+bestT*( static_cast(nearestB.positionSubvoxels[2])-nearestA.positionSubvoxels[2]))) /distanceToCenter,0.0,1.0):0.0; const float relief=VF_FieldPassageWallRelief(static_cast(X),static_cast(Y), static_cast(Z),static_cast(Owner?Owner->Seed:0)); const float mouthSurfaceRelief=VF_FieldSurfaceRelief(static_cast(X)*1.5f, static_cast(Y)*1.5f,static_cast(Z)*1.5f, static_cast(Owner?Owner->Seed:0)^0x4D4F5554u); const float mouthTerrainRelief=VF_FieldSurfaceRelief(static_cast(X)*1.5f, static_cast(Y)*1.5f,static_cast(Z)*1.5f, static_cast(Owner?Owner->Seed:0)^0x4D4F5554u); const float mouthTreadFieldRelief=VF_FieldSurfaceRelief(static_cast(X)*0.75f, static_cast(Y)*0.75f,static_cast(Z)*0.75f, static_cast(Owner?Owner->Seed:0)^0x54485244u); const auto routeSegmentLengthMetres=[&](std::ptrdiff_t routeIndex) { const auto& before=passage.controlPoints[static_cast(routeIndex)]; const auto& after=passage.controlPoints[static_cast(routeIndex+1)]; const double dx=static_cast(after.positionSubvoxels[0]) -before.positionSubvoxels[0]; const double dy=static_cast(after.positionSubvoxels[1]) -before.positionSubvoxels[1]; const double dz=static_cast(after.positionSubvoxels[2]) -before.positionSubvoxels[2]; return DeterministicSqrt(dx*dx+dy*dy+dz*dz)/qPerMetre; }; double distanceFromOpenMouth=std::numeric_limits::infinity(); if(bestIndex<=2) { distanceFromOpenMouth=bestT*routeSegmentLengthMetres(bestIndex); for(std::ptrdiff_t routeIndex=0;routeIndex=lowerMouthIndex-2&&bestIndex<=lowerMouthIndex+2) { if(bestIndex::infinity(); double mouthEntryFloorQ=0.0; bool upperMouthFloorFade=false; { // The large mouth bore overlaps several inner coils. Its global // nearest SDF can belong to a later turn even at the lip, so use the // projected entrance path to recover the local floor orientation. double prefixMetres=0.0; double nearestEntryPlanDistance=std::numeric_limits::infinity(); double projectedEntryDistance=std::numeric_limits::infinity(); for(std::ptrdiff_t routeIndex=0; routeIndex(3,lowerMouthIndex);++routeIndex) { const auto& before=passage.controlPoints[static_cast(routeIndex)]; const auto& after=passage.controlPoints[static_cast(routeIndex+1)]; const double ax=static_cast(before.positionSubvoxels[0]); const double ay=static_cast(before.positionSubvoxels[1]); const double az=static_cast(before.positionSubvoxels[2]); const double dx=static_cast(after.positionSubvoxels[0])-ax; const double dy=static_cast(after.positionSubvoxels[1])-ay; const double dz=static_cast(after.positionSubvoxels[2])-az; const double horizontalLengthSq=dx*dx+dy*dy; const double t=horizontalLengthSq>0.0?std::clamp( ((qx-ax)*dx+(qy-ay)*dy)/horizontalLengthSq,0.0,1.0):0.0; double projectedX=ax+t*dx,projectedY=ay+t*dy; const double horizontalLength=DeterministicSqrt(horizontalLengthSq); if(horizontalLength>1.0e-6) { const double pathRelief=std::clamp(static_cast( VF_FieldSurfaceRelief(static_cast(projectedX/qPerMetre*1.5), static_cast(projectedY/qPerMetre*1.5), static_cast((az+t*dz)/qPerMetre*1.5), static_cast(Owner?Owner->Seed:0)^0x4D4F5554u)), -1.0,1.0); const double lateralOffset=pathRelief*0.18*qPerMetre; projectedX-=dy/horizontalLength*lateralOffset; projectedY+=dx/horizontalLength*lateralOffset; } const double planDx=qx-projectedX,planDy=qy-projectedY; const double planDistance=DeterministicSqrt(planDx*planDx+planDy*planDy); if(planDistance(after.radiusSubvoxels)-before.radiusSubvoxels); mouthEntryFloorQ=centerZ-radius+0.20*qPerMetre; const double centerDistance=DeterministicSqrt(planDx*planDx +planDy*planDy+(qz-centerZ)*(qz-centerZ)); mouthEntryFloorAlignment=centerDistance>1.0e-9 ?std::clamp(-(qz-centerZ)/centerDistance,0.0,1.0):0.0; projectedEntryDistance=prefixMetres +t*routeSegmentLengthMetres(routeIndex); } prefixMetres+=routeSegmentLengthMetres(routeIndex); } if(nearestEntryPlanDistance<=2.0*qPerMetre &&projectedEntryDistance<=8.0 &&std::abs(qz-mouthEntryFloorQ)<=4.0*qPerMetre) { distanceFromOpenMouth=projectedEntryDistance; mouthEntryPlanDistance=nearestEntryPlanDistance/qPerMetre; upperMouthFloorFade=true; } } double mouthFloorBlend=0.0; if(std::isfinite(distanceFromOpenMouth)) { const double fade=std::clamp(distanceFromOpenMouth/11.0,0.0,1.0); const double smooth=fade*fade*(3.0-2.0*fade); mouthFloorBlend=1.0-smooth; } const double floorReliefT=std::clamp((verticalWallAlignment-0.55)/0.45,0.0,1.0); const double floorReliefWeight=floorReliefT*floorReliefT *(3.0-2.0*floorReliefT); const double floorReliefOffset=std::clamp( static_cast(mouthTerrainRelief),-1.0,1.0) *0.20*qPerMetre*mouthFloorBlend*floorReliefWeight *(upperMouthFloorFade?1.0:0.0); const double wallReliefWeight=0.05+0.95*(1.0-verticalWallAlignment); const double roughSignedDistance=bestSignedDistance +static_cast(std::clamp(relief,-1.0f,1.0f))*0.21*qPerMetre*wallReliefWeight +floorReliefOffset; if(IsPassageShortSpanSegment(passageIndex,static_cast(bestIndex))) { const double bridgeDistance=PassageBridgeSignedDistance(passageIndex, static_cast(bestIndex),X,Y,Z); composed=std::min(composed,static_cast(bridgeDistance/Q)); } // Leave native crater ground at the lip. The bore opens through the walls // immediately, while its floor cut eases in along the passage centerline. double mouthCarveFade=1.0; if(upperMouthFloorFade) { constexpr double mouthCarveFadeMetres=8.0; mouthCarveFade=std::clamp(distanceFromOpenMouth/mouthCarveFadeMetres, 0.0,1.0); mouthCarveFade=mouthCarveFade*mouthCarveFade*(3.0-2.0*mouthCarveFade); } const double floorAlignment=std::max(verticalWallAlignment, mouthEntryFloorAlignment)*std::max(verticalWallAlignment, mouthEntryFloorAlignment); double floorBelowWeight=0.0; if(upperMouthFloorFade) { const double belowFloor=(mouthEntryFloorQ-qz)/qPerMetre; const double supportRamp=std::clamp(belowFloor/0.15,0.0,1.0); const double supportFade=std::clamp((1.0-belowFloor)/0.35,0.0,1.0); const double rampSmooth=supportRamp*supportRamp*(3.0-2.0*supportRamp); const double fadeSmooth=supportFade*supportFade*(3.0-2.0*supportFade); floorBelowWeight=rampSmooth*fadeSmooth; } // Preserve native terrain only along a capsule-scale worn lane. The // former bore-wide alignment feather left a straight platform-width // bridge across the FI mouth. double mouthWalkLaneT=std::clamp((1.05-mouthEntryPlanDistance)/0.60,0.0,1.0); mouthWalkLaneT=mouthWalkLaneT*mouthWalkLaneT*(3.0-2.0*mouthWalkLaneT); const double mouthWalkLaneWeight=upperMouthFloorFade?mouthWalkLaneT:0.0; const double floorCarveWeight=1.0-(1.0-mouthCarveFade) *floorAlignment*floorAlignment*floorBelowWeight*mouthWalkLaneWeight; const float fullyCarvedDensity=std::max(composed, static_cast(-roughSignedDistance/Q)); composed=std::lerp(composed,fullyCarvedDensity, static_cast(floorCarveWeight)); const auto considerSupportSegment=[&](std::ptrdiff_t index) { const auto& segmentA=passage.controlPoints[static_cast(index)]; const auto& segmentB=passage.controlPoints[static_cast(index+1)]; const double dx=static_cast(segmentB.positionSubvoxels[0])-segmentA.positionSubvoxels[0]; const double dy=static_cast(segmentB.positionSubvoxels[1])-segmentA.positionSubvoxels[1]; const double dz=static_cast(segmentB.positionSubvoxels[2])-segmentA.positionSubvoxels[2]; const double radiusA=segmentA.radiusSubvoxels; const double radiusB=segmentB.radiusSubvoxels; const bool entryMouthShoulder=index<=2; // Keep the tread within the probe band and below the capsule's // lowest sampled point. const bool bridgeDeck=IsPassageShortSpanSegment(passageIndex, static_cast(index)) ||IsPassageColumnSegment(passageIndex,static_cast(index)); // Keep a small capsule margin at regular treads and restore most of // the nominal height smoothly as a tread approaches its landing. double floorOffset=bridgeDeck ?PassageBridgeDeckOffsetMetres(passageIndex,static_cast(index)) :std::lerp(0.15,0.20,lowerMouthBlend); const double horizontalLengthSq=dx*dx+dy*dy; const bool overlapSupportJoin=index+12(index)) &&(index==0||(!IsPassageShortSpanSegment(passageIndex, static_cast(index-1)))); const bool smoothSupportJoin=index>0&&overlapSupportJoin &&!IsPassageShortSpanSegment(passageIndex,static_cast(index+1)); const double projection=horizontalLengthSq>0.0 ?((qx-segmentA.positionSubvoxels[0])*dx +(qy-segmentA.positionSubvoxels[1])*dy)/horizontalLengthSq:bestT; // Extend tread segments by a fixed distance through route knots. A // fractional extension made the support width change with segment length. const double overlapFraction=overlapSupportJoin&&horizontalLengthSq>0.0 ?std::min(0.5,2.0*qPerMetre/DeterministicSqrt(horizontalLengthSq)):0.0; const double supportT=horizontalLengthSq>0.0 ?std::clamp(projection,-overlapFraction,1.0+overlapFraction):bestT; double centerX=segmentA.positionSubvoxels[0]+supportT*dx; double centerY=segmentA.positionSubvoxels[1]+supportT*dy; double centerZ=segmentA.positionSubvoxels[2]+supportT*( static_cast(segmentB.positionSubvoxels[2])-segmentA.positionSubvoxels[2]); double radius=segmentA.radiusSubvoxels+supportT*( static_cast(segmentB.radiusSubvoxels)-segmentA.radiusSubvoxels); if(smoothSupportJoin) { const auto& p0=passage.controlPoints[static_cast(index-1)]; const auto& p3=passage.controlPoints[static_cast(index+2)]; const double t2=supportT*supportT,t3=t2*supportT; const auto interpolate=[&](double v0,double v1,double v2,double v3) { return 0.5*((2.0*v1)+(-v0+v2)*supportT +(2.0*v0-5.0*v1+4.0*v2-v3)*t2 +(-v0+3.0*v1-3.0*v2+v3)*t3); }; constexpr double SmoothSupportHeightWeight=0.25; centerZ+=(interpolate(p0.positionSubvoxels[2],segmentA.positionSubvoxels[2], segmentB.positionSubvoxels[2],p3.positionSubvoxels[2])-centerZ) *SmoothSupportHeightWeight; radius+=(interpolate(p0.radiusSubvoxels,segmentA.radiusSubvoxels, segmentB.radiusSubvoxels,p3.radiusSubvoxels)-radius) *SmoothSupportHeightWeight; } double treadRelief=0.0; if(entryMouthShoulder ||(index>=lowerMouthIndex-2&&index<=lowerMouthIndex+2)) { const double horizontalLength=DeterministicSqrt(horizontalLengthSq); if(horizontalLength>1.0e-6) { const double pathRelief=std::clamp(static_cast( VF_FieldSurfaceRelief(static_cast(centerX/qPerMetre*1.5), static_cast(centerY/qPerMetre*1.5), static_cast(centerZ/qPerMetre*1.5), static_cast(Owner?Owner->Seed:0)^0x4D4F5554u)), -1.0,1.0); treadRelief=pathRelief; const double lateralOffset=pathRelief*0.18*qPerMetre; centerX-=dy/horizontalLength*lateralOffset; centerY+=dx/horizontalLength*lateralOffset; } } const double horizontalX=qx-centerX,horizontalY=qy-centerY; const double horizontalDistance=DeterministicSqrt(horizontalX*horizontalX +horizontalY*horizontalY); const double supportPlayerCenterZ=centerZ-radius+playerCenterAboveFloorQ; const double supportQueryDepth=supportPlayerCenterZ-qz; // Support belongs just below the avatar's foot plane. Queries within // the capsule body, or a neighbouring coil's overlapping bore, must // not turn that tread into a ceiling or mid-air shelf. // Sparse authored routes use the full signed distance around the top; // dense routes retain the legacy one-sided tread density. const double supportSegmentLength=DeterministicSqrt( horizontalLengthSq+dz*dz)/qPerMetre; const double sparseRouteThreshold=VoxelForgeCore::WorldAssemblyEvaluatorVersion>=188 &&floatingIslandPassage&&passage.style==0?1.0:1.5; const bool sparseRouteSupport=supportSegmentLength>sparseRouteThreshold; const double minimumSupportQueryDepth=index==selectedSupportSegmentIndex &&sparseRouteSupport?0.65:0.93; if(supportQueryDepth1.30*qPerMetre)return; const bool fallbackSupport=qz>=supportPlayerCenterZ-1.50*qPerMetre; const double supportThicknessVoxels=fallbackSupport ?qPerMetre/Q:0.5*qPerMetre/Q; // Keep the supported ramp to a comfortable walking width. Extending a // full bore-radius shelf across overlapping helix coils can block the // capsule on the neighbouring turn. const bool lowerMouthBodyOverlap=index>=lowerMouthIndex-2 &&supportQueryDepth<0.98*qPerMetre; // Keep a narrow walk core, but place its top on the native surface // so the support remains a worn ramp instead of a route-height slab. const bool mouthTread=entryMouthShoulder &&(!floatingIslandPassage||passage.style==0); double distanceFromMouthMetres=std::numeric_limits::infinity(); if(entryMouthShoulder) { double prefixMetres=0.0; for(std::ptrdiff_t routeIndex=0;routeIndex(routeIndex)]; const auto& after=passage.controlPoints[ static_cast(routeIndex+1)]; const double dx=static_cast(after.positionSubvoxels[0]) -before.positionSubvoxels[0]; const double dy=static_cast(after.positionSubvoxels[1]) -before.positionSubvoxels[1]; const double dz=static_cast(after.positionSubvoxels[2]) -before.positionSubvoxels[2]; prefixMetres+=DeterministicSqrt(dx*dx+dy*dy+dz*dz)/qPerMetre; } distanceFromMouthMetres=std::max(0.0, prefixMetres+supportT*supportSegmentLength); } else if(lowerMouthBodyOverlap) { double suffixMetres=0.0; for(std::ptrdiff_t routeIndex=index+1; routeIndex(routeIndex)]; const auto& after=passage.controlPoints[ static_cast(routeIndex+1)]; const double dx=static_cast(after.positionSubvoxels[0]) -before.positionSubvoxels[0]; const double dy=static_cast(after.positionSubvoxels[1]) -before.positionSubvoxels[1]; const double dz=static_cast(after.positionSubvoxels[2]) -before.positionSubvoxels[2]; suffixMetres+=DeterministicSqrt(dx*dx+dy*dy+dz*dz)/qPerMetre; } distanceFromMouthMetres=std::max(0.0, suffixMetres+(1.0-supportT)*supportSegmentLength); } double mouthReliefT=std::clamp(distanceFromMouthMetres/8.0,0.0,1.0); mouthReliefT=mouthReliefT*mouthReliefT*(3.0-2.0*mouthReliefT); double mouthSupportT=std::clamp((distanceFromMouthMetres-0.25)/4.0,0.0,1.0); mouthSupportT=mouthSupportT*mouthSupportT*(3.0-2.0*mouthSupportT); const double mouthSupportWidth=std::lerp(0.82,1.00,mouthSupportT); const double supportHalfWidthMetres=mouthTread?mouthSupportWidth :lowerMouthBodyOverlap?0.90:1.25; double supportHalfWidth=std::min(radius, supportHalfWidthMetres*qPerMetre); const double shoulderWarp=mouthTread ?std::clamp(static_cast(mouthSurfaceRelief),-1.0,1.0) *0.12*qPerMetre:0.0; supportHalfWidth=mouthTread ?std::clamp(supportHalfWidth+shoulderWarp, std::lerp(0.52,0.62,mouthSupportT)*qPerMetre, std::lerp(0.92,1.10,mouthSupportT)*qPerMetre) :std::max(0.0,supportHalfWidth+shoulderWarp); // Keep the walk core within the player footprint, then ease the outer // edge into native ground over a long enough shoulder to avoid a berm. const double supportEdgeCoreMetres=mouthTread?0.08 :entryMouthShoulder?0.60:0.90; const double supportEdgeBlendMetres=std::min( mouthTread?1.40:entryMouthShoulder?0.85:0.35, std::max(0.0,supportHalfWidth/qPerMetre-supportEdgeCoreMetres)); const double supportEdgeT=supportEdgeBlendMetres>1.0e-6 ?std::clamp((horizontalDistance -(supportHalfWidth-supportEdgeBlendMetres*qPerMetre)) /(supportEdgeBlendMetres*qPerMetre),0.0,1.0):0.0; const double supportEdgeSmooth=supportEdgeT*supportEdgeT *(3.0-2.0*supportEdgeT); const double mouthSupportThicknessVoxels=std::lerp( 0.35*supportThicknessVoxels,supportThicknessVoxels,mouthSupportT); const double localSupportThicknessVoxels=entryMouthShoulder ?mouthSupportThicknessVoxels*(1.0-0.72*supportEdgeSmooth) :supportThicknessVoxels; double mouthSupportBlend=1.0; if(mouthTread&&std::isfinite(distanceFromMouthMetres)) { const double mouthBlendT=std::clamp( distanceFromMouthMetres/0.75,0.0,1.0); mouthSupportBlend=0.75+0.25*mouthBlendT*mouthBlendT *(3.0-2.0*mouthBlendT); } const double supportEdgeDepth=mouthTread?0.25:0.35; const double mouthFloorOffset=mouthTread ?std::lerp(0.15,0.19,lowerMouthBlend):floorOffset; const double baseFloorQ=centerZ-radius+mouthFloorOffset*qPerMetre -supportEdgeSmooth*supportEdgeDepth*qPerMetre; const double nativeSurfaceQ=static_cast(qz) -static_cast(density)*Q; // Keep support near the route floor; visible mouth preservation below // blends the source field directly instead of extending this SDF top. const double nativeFieldOffset=std::clamp(nativeSurfaceQ-baseFloorQ, -0.80*qPerMetre,0.80*qPerMetre); const double nativeSurfaceBlend=mouthTread?1.0:1.0-mouthReliefT; const double nativeTreadOffset=std::clamp( nativeFieldOffset*nativeSurfaceBlend+floorReliefOffset -std::abs(treadRelief)*0.08*qPerMetre *(1.0-0.25*mouthReliefT), -0.80*qPerMetre,0.80*qPerMetre); const double floorQ=baseFloorQ +(mouthTread?std::clamp(nativeTreadOffset +std::clamp(static_cast(mouthTreadFieldRelief),-1.0,1.0) *0.40*qPerMetre,-0.80*qPerMetre,0.0):0.0); const double floorVoxels=floorQ/Q; const bool signedDistanceSupport=sparseRouteSupport||mouthTread; const double supportPositiveRangeVoxels=signedDistanceSupport ?0.35*qPerMetre/Q:1.0/Q; if(ZfloorVoxels+supportPositiveRangeVoxels)return; const double supportQueryHalfWidth=supportHalfWidth +(mouthTread?0.45*qPerMetre:0.0); if(horizontalDistance>supportQueryHalfWidth)return; const double supportDistanceSquared=horizontalDistance*horizontalDistance +(Z-floorVoxels)*(Z-floorVoxels); if(supportDistanceSquared>=bestSupportDistanceSquared)return; bestSupportDistanceSquared=supportDistanceSquared; hasSupportFloor=true; bestSupportSegmentIndex=index; bestSupportIsMouthTread=mouthTread; if(signedDistanceSupport) { // Sparse support profiles need both sides of a signed distance // so the sampled zero crossing stays on the tread top. const double topDistance=qz-floorQ; const double bottomDistance=floorQ-localSupportThicknessVoxels*Q-qz; const double verticalDistance=std::max(topDistance,bottomDistance); const double sideDistance=horizontalDistance-supportHalfWidth; bestSupportDensity=SmoothMaxDistance( static_cast(sideDistance/Q), static_cast(verticalDistance/Q), entryMouthShoulder?0.80f:0.20f); } else { bestSupportDensity=std::clamp(Z-floorVoxels, -localSupportThicknessVoxels,0.0); } bestSupportFloorQ=floorQ; bestSupportUsesSignedDistance=signedDistanceSupport; bestSupportMouthBlend=mouthSupportBlend; bestSupportNativeBlend=mouthTread ?0.85*mouthSupportBlend*(1.0-supportEdgeSmooth):0.0; }; std::ptrdiff_t supportCenterIndex=bestIndex; if(bestIndexvalue;}); const std::ptrdiff_t profileIndex=std::clamp( supportProfile-passage.controlPoints.begin(),0,lowerMouthIndex); // At floor height the nearest 3D centerline can belong to the next // coil below. The descending floor profile identifies the coil that // owns this tread, even when it is more than eight control points away. supportCenterIndex=profileIndex; } const std::ptrdiff_t supportBegin=std::max(0,supportCenterIndex-1); const std::ptrdiff_t supportEnd=std::min(lastIndex-1, supportCenterIndex+1); selectedSupportSegmentIndex=supportCenterIndex>0 ?supportCenterIndex-1:supportCenterIndex; for(std::ptrdiff_t index=supportBegin;index<=supportEnd;++index) considerSupportSegment(index); // Keep route treads inside the native rock envelope. Carving may reopen the // floor above this point, but a support cannot create a new surface in air. const bool passageColumnFloorSupport=hasSupportFloor &&IsPassageColumnSegment(passageIndex, static_cast(bestSupportSegmentIndex)); const bool shortSpanFloorSupport=hasSupportFloor &&IsPassageShortSpanSegment(passageIndex, static_cast(bestSupportSegmentIndex)); const double capsuleClearanceMetres=bestSupportUsesSignedDistance?0.045:0.05; const double capsuleQueryZ=bestSupportUsesSignedDistance?bestSupportFloorQ:qz; const bool supportCapsuleProfileAvailable=passageIndex=capsuleClearanceMetres*qPerMetre; // Outside bridge and short-span segments, add the tread only where the // native field already provides a solid envelope; the capsule check keeps // that support below the player's walk space. if(hasSupportFloor&&(density<0.0f||passageColumnFloorSupport ||shortSpanFloorSupport) &&(capsuleAllowsSupport||bestSupportDensity>=0.0)) { if(bestSupportIsMouthTread) { // Preserve the owner's native ground through the shallow mouth // carve. Blending back the native field keeps its crater curvature // and noise. The capsule test keeps neighbouring coils open. const bool nativeRestoreCapsuleClear=density<0.0f &&bestSupportNativeBlend>0.0&&capsuleAllowsSupport &&!PassagePlayerCapsuleBlocksSupport(passageIndex,carveBegin, carveEnd,lowerMouthIndex,mouthSearchBegin,mouthSearchEnd, lastIndex,qx,qy,qz,qPerMetre,capsuleClearanceMetres); if(nativeRestoreCapsuleClear) { const double nativeSurfaceDetail=std::clamp( static_cast(mouthTerrainRelief),-1.0,1.0) *0.18*qPerMetre/Q; const float nativeGroundWithRelief=static_cast(density -nativeSurfaceDetail); composed=std::lerp(composed,nativeGroundWithRelief, static_cast(bestSupportNativeBlend)); } } else composed=std::min(composed,static_cast(bestSupportDensity)); } } // Restore the surface route where the passage cut crossed otherwise solid // native ground. The support stays native-solid-only, so it cannot float. for(std::size_t passageIndex=0;passageIndexpassages.size();++passageIndex) { const auto& passage=ResolvedLayout->passages[passageIndex]; if(passage.controlPoints.size()<2)continue; const bool tileHasSupport=passageTileScopeMatches; if(!tileHasSupport ||ActivePassageTileQueries->hasMouthSupport(passageIndex)) composeRouteMouthSupport(passage.upperStrateIndex, passage.upperMouthSubvoxels,true); } return composed; } float ApplyResolvedStrateComposition(float density,float X,float Y,float Z) const { const float graphDensity=ApplyResolvedCavernGraph(density,X,Y,Z); const float composed=ApplyResolvedStratePassageCarves( ApplyResolvedStrateBoundaryBand(graphDensity,X,Y,Z),X,Y,Z); const float stamped=StampRuntime.empty()?composed:ApplyResolvedSdfStamps(composed,X,Y,Z); // Keep the explicit graph floor cap after smooth unions, relief, passages, // and decorative stamps so nothing can reopen the tunnel floor below profile. return ApplyResolvedCavernFloorCaps(stamped,X,Y,Z); } bool ResolvedPassageIntersectsBox(const int32 minimum[3],const int32 maximum[3]) const { if(!ResolvedLayout||ResolvedLayout->passages.empty())return false; if(PassageProofNodes.empty()) { // Native-only readiness tiles have not built the passage BVH. Keep their // exact conservative fallback instead of indexing an empty tree. constexpr double Q=static_cast(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel); const double voxelsPerMetre=Owner&&Owner->CoreVoxelSizeMicrons>0 ?1000000.0/Owner->CoreVoxelSizeMicrons:4.0; const double bridgeHalfWidthVoxels=3.5*voxelsPerMetre; const double bridgeFootDepthVoxels=(5.0-0.13)*voxelsPerMetre; 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 i=1;i(std::max(a.radiusSubvoxels,b.radiusSubvoxels))/Q; const double floorThicknessVoxels=voxelsPerMetre; const double horizontalExtent=std::max(radius,bridgeHalfWidthVoxels); const double footDepth=std::max(floorThicknessVoxels,bridgeFootDepthVoxels); const double low[3]={ std::min(a.positionSubvoxels[0],b.positionSubvoxels[0])/Q-horizontalExtent, std::min(a.positionSubvoxels[1],b.positionSubvoxels[1])/Q-horizontalExtent, std::min(a.positionSubvoxels[2],b.positionSubvoxels[2])/Q-radius-footDepth}; const double high[3]={ std::max(a.positionSubvoxels[0],b.positionSubvoxels[0])/Q+horizontalExtent, std::max(a.positionSubvoxels[1],b.positionSubvoxels[1])/Q+horizontalExtent, std::max(a.positionSubvoxels[2],b.positionSubvoxels[2])/Q+radius}; if(overlaps(low,high))return true; } const auto upperStratum=std::find_if(ResolvedLayout->strates.begin(), ResolvedLayout->strates.end(),[&passage](const auto& value) {return value.strateIndex==passage.upperStrateIndex;}); if(upperStratum!=ResolvedLayout->strates.end()) { std::array route{}; std::size_t routeCount=0; for(const auto& anchor:upperStratum->anchors) if(anchor.kind=="route_point"&&anchor.values.size()>=3) { if(routeCount=2) { const double mouthReach=13.0*voxelsPerMetre*Q; double minimumZ=std::numeric_limits::infinity(); double maximumZ=-std::numeric_limits::infinity(); for(std::size_t index=0;index(route[index]->values[2])); maximumZ=std::max(maximumZ,static_cast(route[index]->values[2])); } const double mouthX=passage.upperMouthSubvoxels[0]; const double mouthY=passage.upperMouthSubvoxels[1]; const double low[3]={(mouthX-mouthReach)/Q,(mouthY-mouthReach)/Q, (minimumZ-3.0*voxelsPerMetre*Q)/Q}; const double high[3]={(mouthX+mouthReach)/Q,(mouthY+mouthReach)/Q, (maximumZ+3.0*voxelsPerMetre*Q)/Q}; if(overlaps(low,high))return true; } } } for(const auto& crossing:PassageVoidCrossings) { if(!crossing.needsColumn)continue; const double radius=static_cast(crossing.maximumRadiusSubvoxels)/Q; const double low[3]={crossing.centerXSubvoxels/Q-radius, crossing.centerYSubvoxels/Q-radius,crossing.bottomZSubvoxels/Q}; const double high[3]={crossing.centerXSubvoxels/Q+radius, crossing.centerYSubvoxels/Q+radius,crossing.topZSubvoxels/Q}; 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; }; struct FClassificationThreadStats { uint64 SolidBoxes=0,AirBoxes=0,MixedBoxes=0,UnknownBoxes=0; uint64 NativeFloatFieldCalls=0,NativeLatticeFieldCalls=0,NativeUnclampedFieldCalls=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 FClassificationThreadStats GetThreadClassificationStats() { return ThreadClassificationStats(); } 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 DetailApplySeconds = 0.0; double SignEdgeDistanceSeconds = 0.0; double PassageValidationSeconds = 0.0; double SharedFieldSeconds = 0.0; double FarPrimitiveSeconds = 0.0; double FarBinSeconds = 0.0; 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; 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) { 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; BindResolvedStratumFields(); bAnalyticOnly=false; bProfileDetailBand=bInProfileDetailBand; 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(); 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)); BuildPackedRockIntervals(Stats.RockIntervalLines); // Native density queries for non-heightfield strates use the exact interval // evaluator. Build those native intervals before scanning passage support; // the scan itself still completes before any resolved column is composed. BuildPassageVoidCrossingPlan(); BuildCandidateBands(Stats.CandidateAxisLines,Stats.CandidateMergeLines, Stats.CandidateMergeSeconds); if(!BuildGpuVisualDetailNodeSets(InGpuVisualDetailTiles,InCpuVisualDetailTiles,Error)) return false; 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(); if(bValidatePassageRoutes&&!ValidatePassageRoutes()) { 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("[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(); RebuildSharedFieldPrimitives(); Stats.FarPrimitiveSeconds=std::chrono::duration(std::chrono::steady_clock::now()-Stage).count(); Stage=std::chrono::steady_clock::now(); BuildSpatialBins(SpatialBinSize,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::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(bNativeOnly) { 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)) { ++ThreadClassificationStats().NativeFloatFieldCalls; return ApplyEditBrushes(ApplyResolvedStrateComposition(IslandDensity,X,Y,Z),X,Y,Z); } float NativeDensity=0.0f; if(NativeSurfaceDensityAt(X,Y,Z,NativeDensity)) { ++ThreadClassificationStats().NativeFloatFieldCalls; return ApplyEditBrushes(ApplyResolvedStrateComposition(NativeDensity,X,Y,Z),X,Y,Z); } if(UsesLegacySurfaceField(X,Y,Z)) 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) return PackedDensityAt(IX,IY,IZ,IsRock(IX,IY,IZ),X,Y,Z); return AnalyticDensityAt(X,Y,Z); } 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"; if(NativeSurfaceDensityAt(X,Y,Z,Density))return "SurfaceWorld-native"; if(UsesLegacySurfaceField(X,Y,Z))return "SurfaceWorld-legacy"; const FBoundStratumField* Bound=FindBoundStratumForZ(Z); 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 { if(SharedDensitySource)return SharedDensitySource->DensityAtLattice(X,Y,Z); if(bNativeOnly) { 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)) { ++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)) { ++ThreadClassificationStats().NativeLatticeFieldCalls; return ApplyEditBrushes(ApplyResolvedStrateComposition(NativeDensity,X,Y,Z),X,Y,Z); } if(UsesLegacySurfaceField(static_cast(X),static_cast(Y), static_cast(Z))) return LegacySurfaceDensityAt(static_cast(X),static_cast(Y), static_cast(Z)); const bool bPacked=!bAnalyticOnly&&Contains(X,Y,Z); if(bPacked) return PackedDensityAt(X,Y,Z,IsRock(X,Y,Z),static_cast(X), static_cast(Y),static_cast(Z)); return AnalyticDensityAt(static_cast(X),static_cast(Y), static_cast(Z)); } float DensityAtUnclamped(float X,float Y,float Z) const { if(SharedDensitySource)return SharedDensitySource->DensityAtUnclamped(X,Y,Z); if(bNativeOnly) { 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)) { ++ThreadClassificationStats().NativeUnclampedFieldCalls; return ApplyEditBrushes(ApplyResolvedStrateComposition(IslandDensity,X,Y,Z),X,Y,Z); } float NativeDensity=0.0f; if(NativeSurfaceDensityAt(X,Y,Z,NativeDensity)) { ++ThreadClassificationStats().NativeUnclampedFieldCalls; return ApplyEditBrushes(ApplyResolvedStrateComposition(NativeDensity,X,Y,Z),X,Y,Z); } if(UsesLegacySurfaceField(X,Y,Z)) return LegacySurfaceDensityAt(X,Y,Z); return AnalyticDensityAt(X,Y,Z,false); } VoxelForgeCore::DetailFieldSample DetailSampleAt(float X,float Y,float Z) const { if(SharedDensitySource)return SharedDensitySource->DetailSampleAt(X,Y,Z); 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 { if(SharedDensitySource) { SharedDensitySource->DensityAtLatticeX8(XStart,XStep,Y,Z,Count,OutDensity); return; } Count=std::clamp(Count,0,8); if(Count==0)return; if(bNativeOnly) { for(int32 Lane=0;Lane(Z))) { 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); float IslandDensity=0.0f; bHasNativeIslandSample|=NativeFloatingIslandDensityAt( SampleX,SampleY,SampleZ,IslandDensity); } if(bHasLegacySurfaceSample||bHasNativeIslandSample) { 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(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; bool bColumn=false,bMouthSupport=false; }; struct FPassageNearestNode { std::array minimum{},maximum{}; double maximumRadius=0.0; 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; std::uint64_t irregularitySeed=0; std::uint8_t quarterTurns=0; }; struct FCavernRuntimeFeature { const VoxelForgeCore::ResolvedCavernRoomV1* room=nullptr; const VoxelForgeCore::ResolvedCavernEdgeV1* edge=nullptr; std::array boundsMinimumMicrometres{},boundsMaximumMicrometres{}; 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; }; 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.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()); } 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(); if(!ResolvedLayout||!Owner||Owner->CoreVoxelSizeMicrons==0 ||ResolvedLayout->cavernGraphVocabularyVersion !=VoxelForgeCore::ResolvedLayoutCavernGraphVocabularyVersionV1)return; const float micronsPerVoxel=static_cast(Owner->CoreVoxelSizeMicrons); for(const auto& room:ResolvedLayout->cavernRooms) { FCavernRuntimeFeature runtime;runtime.room=&room; runtime.boundsMinimumMicrometres=room.boundsMinimumMicrometres; runtime.boundsMaximumMicrometres=room.boundsMaximumMicrometres; for(std::size_t axis=0;axis<3;++axis) { 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.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; CavernRuntime.push_back(runtime); } } std::vector BuildCavernFeatureTileList( const std::array& minimumSubvoxels, const std::array& maximumSubvoxels) const { std::vector result; if(CavernRuntime.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(minimumSubvoxels[axis]) *Owner->CoreVoxelSizeMicrons/Q; maximumMicrometres[axis]=static_cast(maximumSubvoxels[axis]) *Owner->CoreVoxelSizeMicrons/Q; } for(std::size_t index=0;index=minimumMicrometres[axis] &&feature.boundsMinimumMicrometres[axis] <=maximumMicrometres[axis]; if(overlap)result.push_back(static_cast(index)); } 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]; for(std::size_t axis=0;axis<3;++axis) { node.minimum[axis]=std::min({node.minimum[axis], static_cast(a.positionSubvoxels[axis]), static_cast(b.positionSubvoxels[axis])}); node.maximum[axis]=std::max({node.maximum[axis], static_cast(a.positionSubvoxels[axis]), static_cast(b.positionSubvoxels[axis])}); } node.maximumRadius=std::max({node.maximumRadius, static_cast(a.radiusSubvoxels),static_cast(b.radiusSubvoxels)}); } node.segments=segments; std::sort(node.segments.begin(),node.segments.end()); 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; } 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 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); PassageColumnSegmentFlags.clear();PassageShortSpanSegmentFlags.clear(); PassageHasShortSpanFlags.clear(); if(!ResolvedLayout||ResolvedLayout->passages.empty())return; constexpr double Q=static_cast(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel); const double voxelsPerMetre=Owner&&Owner->CoreVoxelSizeMicrons>0 ?1000000.0/Owner->CoreVoxelSizeMicrons:4.0; const double bridgeHalfWidthVoxels=3.5*voxelsPerMetre; const double bridgeFootDepthVoxels=(5.0-0.13)*voxelsPerMetre; PassageColumnSegmentFlags.resize(ResolvedLayout->passages.size()); PassageShortSpanSegmentFlags.resize(ResolvedLayout->passages.size()); 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; PassageColumnSegmentFlags[passageIndex].assign(segmentCount,0); PassageShortSpanSegmentFlags[passageIndex].assign(segmentCount,0); if(segmentCount>0) { std::vector segmentIds(segmentCount); for(std::size_t i=0;i(std::max(a.radiusSubvoxels,b.radiusSubvoxels))/Q; const double horizontalExtent=std::max(radius,bridgeHalfWidthVoxels); const double footDepth=std::max(voxelsPerMetre,bridgeFootDepthVoxels); FPassageProofBound proof; proof.minimum={std::min(a.positionSubvoxels[0],b.positionSubvoxels[0])/Q-horizontalExtent, std::min(a.positionSubvoxels[1],b.positionSubvoxels[1])/Q-horizontalExtent, std::min(a.positionSubvoxels[2],b.positionSubvoxels[2])/Q-radius-footDepth}; proof.maximum={std::max(a.positionSubvoxels[0],b.positionSubvoxels[0])/Q+horizontalExtent, std::max(a.positionSubvoxels[1],b.positionSubvoxels[1])/Q+horizontalExtent, std::max(a.positionSubvoxels[2],b.positionSubvoxels[2])/Q+radius}; PassageProofBounds.push_back(proof); const std::array featureMin={ static_cast(std::floor(proof.minimum[0]*Q)), static_cast(std::floor(proof.minimum[1]*Q)), static_cast(std::floor(proof.minimum[2]*Q))}; const std::array featureMax={ static_cast(std::ceil(proof.maximum[0]*Q)), static_cast(std::ceil(proof.maximum[1]*Q)), static_cast(std::ceil(proof.maximum[2]*Q))}; PassageFeatureBounds.push_back({static_cast(PassageTileFeatures.size()), featureMin,featureMax}); PassageTileFeatures.push_back({passageIndex,segment,false}); } const auto upperStratum=std::find_if(ResolvedLayout->strates.begin(), ResolvedLayout->strates.end(),[&passage](const auto& value) {return value.strateIndex==passage.upperStrateIndex;}); if(upperStratum!=ResolvedLayout->strates.end()) { std::array route{}; std::size_t routeCount=0; for(const auto& anchor:upperStratum->anchors) if(anchor.kind=="route_point"&&anchor.values.size()>=3) { if(routeCount=2) { const double mouthReach=13.0*voxelsPerMetre*Q; double minimumZ=std::numeric_limits::infinity(); double maximumZ=-std::numeric_limits::infinity(); for(std::size_t index=0;index(route[index]->values[2])); maximumZ=std::max(maximumZ,static_cast(route[index]->values[2])); } const double mouthX=passage.upperMouthSubvoxels[0]; const double mouthY=passage.upperMouthSubvoxels[1]; FPassageProofBound supportProof; supportProof.minimum={(mouthX-mouthReach)/Q,(mouthY-mouthReach)/Q, (minimumZ-3.0*voxelsPerMetre*Q)/Q}; supportProof.maximum={(mouthX+mouthReach)/Q,(mouthY+mouthReach)/Q, (maximumZ+3.0*voxelsPerMetre*Q)/Q}; PassageProofBounds.push_back(supportProof); const std::array featureMin={ static_cast(std::floor(supportProof.minimum[0]*Q)), static_cast(std::floor(supportProof.minimum[1]*Q)), static_cast(std::floor(supportProof.minimum[2]*Q))}; const std::array featureMax={ static_cast(std::ceil(supportProof.maximum[0]*Q)), static_cast(std::ceil(supportProof.maximum[1]*Q)), static_cast(std::ceil(supportProof.maximum[2]*Q))}; PassageFeatureBounds.push_back({static_cast(PassageTileFeatures.size()), featureMin,featureMax}); PassageTileFeatures.push_back({passageIndex,0,false,true}); } } } for(const auto& crossing:PassageVoidCrossings) { if(crossing.passageIndexpassages.size()) { const std::int32_t strate=ResolvedLayout->passages[ crossing.passageIndex].upperStrateIndex; const auto upper=std::find_if(ResolvedLayout->strates.begin(), ResolvedLayout->strates.end(),[strate](const auto& value) {return value.strateIndex==strate;}); surfaceWorldUpper=upper!=ResolvedLayout->strates.end() &&upper->passageCapabilities.hasSurfaceLanding; } const std::size_t shortSpanPadding=!crossing.needsColumn &&surfaceWorldUpper&&!flags.empty()?2:0; const std::size_t first=std::min(crossing.firstSegment>shortSpanPadding ?crossing.firstSegment-shortSpanPadding:0,flags.size()); const std::size_t last=std::min(crossing.lastSegment+shortSpanPadding, flags.empty()?0:flags.size()-1); if(!flags.empty()&&first<=last) std::fill(flags.begin()+first,flags.begin()+last+1,1); } if(!crossing.needsColumn)continue; FPassageProofBound proof; const double radius=static_cast(crossing.maximumRadiusSubvoxels)/Q; proof.minimum={crossing.centerXSubvoxels/Q-radius,crossing.centerYSubvoxels/Q-radius, crossing.bottomZSubvoxels/Q}; proof.maximum={crossing.centerXSubvoxels/Q+radius,crossing.centerYSubvoxels/Q+radius, crossing.topZSubvoxels/Q}; PassageProofBounds.push_back(proof); const std::array featureMin={ static_cast(std::floor(proof.minimum[0]*Q)), static_cast(std::floor(proof.minimum[1]*Q)), static_cast(std::floor(proof.minimum[2]*Q))}; const std::array featureMax={ static_cast(std::ceil(proof.maximum[0]*Q)), static_cast(std::ceil(proof.maximum[1]*Q)), static_cast(std::ceil(proof.maximum[2]*Q))}; PassageFeatureBounds.push_back({static_cast(PassageTileFeatures.size()), featureMin,featureMax}); PassageTileFeatures.push_back({crossing.passageIndex,0,true}); } PassageHasShortSpanFlags.assign(PassageShortSpanSegmentFlags.size(),0); for(std::size_t passageIndex=0;passageIndex( std::any_of(PassageShortSpanSegmentFlags[passageIndex].begin(), PassageShortSpanSegmentFlags[passageIndex].end(), [](std::uint8_t flag){return flag!=0;})); 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()); } double PassageNearestNodeLowerBound(const FPassageNearestNode& node, double qx,double qy,double qz) const { const double dx=qxnode.maximum[0]?qx-node.maximum[0]:0.0; const double dy=qynode.maximum[1]?qy-node.maximum[1]:0.0; const double dz=qznode.maximum[2]?qz-node.maximum[2]:0.0; return DeterministicSqrt(dx*dx+dy*dy+dz*dz)-node.maximumRadius-1.0e-6; } template void VisitNearestPassageSpatialNode(std::size_t passageIndex,int nodeIndex, std::size_t first,std::size_t end,double qx,double qy,double qz,int priority, double& bestDistance,FVisit& visit) const { if(passageIndex>=PassageNearestNodes.size()||nodeIndex<0)return; const auto& nodes=PassageNearestNodes[passageIndex]; const auto& node=nodes[static_cast(nodeIndex)]; const auto candidate=std::lower_bound(node.segments.begin(),node.segments.end(),first); if(candidate==node.segments.end()||*candidate>=end)return; const double lowerBound=PassageNearestNodeLowerBound(node,qx,qy,qz); if(lowerBound>bestDistance)return; if(node.left<0) { for(auto it=candidate;it!=node.segments.end()&&*it(node.left)],qx,qy,qz); const double rightBound=PassageNearestNodeLowerBound( nodes[static_cast(node.right)],qx,qy,qz); const int nearChild=leftBound<=rightBound?node.left:node.right; const int farChild=leftBound<=rightBound?node.right:node.left; VisitNearestPassageSpatialNode(passageIndex,nearChild,first,end,qx,qy,qz, priority,bestDistance,visit); VisitNearestPassageSpatialNode(passageIndex,farChild,first,end,qx,qy,qz, priority,bestDistance,visit); } 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,FVisit&& visit) const { if(first>last||passageIndex>=PassageNearestNodes.size() ||PassageNearestNodes[passageIndex].empty())return; auto callback=std::forward(visit); VisitNearestPassageSpatialNode(passageIndex,0,static_cast(first), static_cast(last+1),qx,qy,qz,priority,bestDistance,callback); } bool PassagePotentialWithinNode(std::size_t passageIndex,int nodeIndex, std::size_t first,std::size_t end,double qx,double qy,double qz, double signedDistanceLimit) const { if(passageIndex>=PassageNearestNodes.size()||nodeIndex<0)return false; const auto& nodes=PassageNearestNodes[passageIndex]; const auto& node=nodes[static_cast(nodeIndex)]; const auto candidate=std::lower_bound(node.segments.begin(),node.segments.end(),first); if(candidate==node.segments.end()||*candidate>=end)return false; if(PassageNearestNodeLowerBound(node,qx,qy,qz)>signedDistanceLimit)return false; if(node.left<0)return true; const double leftBound=PassageNearestNodeLowerBound( nodes[static_cast(node.left)],qx,qy,qz); const double rightBound=PassageNearestNodeLowerBound( nodes[static_cast(node.right)],qx,qy,qz); const int nearChild=leftBound<=rightBound?node.left:node.right; const int farChild=leftBound<=rightBound?node.right:node.left; return PassagePotentialWithinNode(passageIndex,nearChild,first,end,qx,qy,qz, signedDistanceLimit) ||PassagePotentialWithinNode(passageIndex,farChild,first,end,qx,qy,qz, signedDistanceLimit); } bool PassageMayAffectDensity(std::size_t passageIndex,std::ptrdiff_t carveBegin, std::ptrdiff_t carveEnd,std::ptrdiff_t lowerMouthIndex, std::ptrdiff_t mouthSearchBegin,std::ptrdiff_t mouthSearchEnd,std::ptrdiff_t lastIndex, double qx,double qy,double qz,double signedDistanceLimit) const { const auto possible=[&](std::ptrdiff_t first,std::ptrdiff_t last) { return first<=last&&passageIndex(first), static_cast(last+1),qx,qy,qz,signedDistanceLimit); }; return possible(carveBegin,carveEnd) ||possible(0,std::min(8,lowerMouthIndex)-1) ||possible(mouthSearchBegin,mouthSearchEnd-1) ||possible(lowerMouthIndex,lastIndex-1); } bool PassagePlayerCapsuleBlocksSupportNode(std::size_t passageIndex,int nodeIndex, std::size_t first,std::size_t end,double qx,double qy,double qz,double qPerMetre, double minimumClearanceMetres) const { if(passageIndex>=PassageNearestNodes.size()||nodeIndex<0)return false; const auto& nodes=PassageNearestNodes[passageIndex]; const auto& node=nodes[static_cast(nodeIndex)]; auto candidate=std::lower_bound(node.segments.begin(),node.segments.end(),first); if(candidate==node.segments.end()||*candidate>=end)return false; const double xyReach=0.39*qPerMetre; if(qx+xyReachnode.maximum[0] ||qy+xyReachnode.maximum[1])return false; const double minimumCenterZ=node.minimum[2]-node.maximumRadius+0.20*qPerMetre; const double maximumCenterZ=node.maximum[2]+2.06*qPerMetre; if(qzmaximumCenterZ)return false; const auto& passage=ResolvedLayout->passages[passageIndex]; if(node.left<0) { for(auto it=candidate;it!=node.segments.end()&&*it(b.positionSubvoxels[0])-ax; const double dy=static_cast(b.positionSubvoxels[1])-ay; const double dz=static_cast(b.positionSubvoxels[2])-az; const double lengthSq=dx*dx+dy*dy+dz*dz; if(lengthSq<=0.0)continue; const double horizontalLengthSq=dx*dx+dy*dy; const double t=horizontalLengthSq>0.0 ?std::clamp(((qx-ax)*dx+(qy-ay)*dy)/horizontalLengthSq,0.0,1.0):0.0; const double centerX=ax+t*dx,centerY=ay+t*dy; const double centerZ=az+t*dz-(static_cast(a.radiusSubvoxels) +t*(static_cast(b.radiusSubvoxels)-a.radiusSubvoxels))+1.13*qPerMetre; const double horizontalX=qx-centerX,horizontalY=qy-centerY; const double vertical=std::max(0.0,std::abs(qz-centerZ)-0.54*qPerMetre); const double distance=DeterministicSqrt(horizontalX*horizontalX+horizontalY*horizontalY +vertical*vertical)-0.34*qPerMetre; if(distance(first),static_cast(last+1), qx,qy,qz,qPerMetre,minimumClearanceMetres); }; return blocked(carveBegin,carveEnd) ||blocked(0,std::min(8,lowerMouthIndex)-1) ||blocked(mouthSearchBegin,mouthSearchEnd-1) ||blocked(lowerMouthIndex,lastIndex-1); } 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); } bool IsPassageColumnSegment(std::size_t PassageIndex,std::size_t SegmentIndex) const { return PassageIndex=ResolvedLayout->passages.size()) return std::numeric_limits::infinity(); const auto& passage=ResolvedLayout->passages[PassageIndex]; if(SegmentIndex+1>=passage.controlPoints.size()) return std::numeric_limits::infinity(); constexpr double Q=static_cast(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel); const double qPerMetre=Q*1000000.0/std::max(1, ResolvedLayout->coreVoxelSizeMicrometres); const auto& a=passage.controlPoints[SegmentIndex]; const auto& b=passage.controlPoints[SegmentIndex+1]; const auto upperStratum=std::find_if(ResolvedLayout->strates.begin(), ResolvedLayout->strates.end(),[&passage](const auto& value) {return value.strateIndex==passage.upperStrateIndex;}); const bool surfaceWorldUpper=upperStratum!=ResolvedLayout->strates.end() &&upperStratum->passageCapabilities.hasSurfaceLanding; const double ax=static_cast(a.positionSubvoxels[0]); const double ay=static_cast(a.positionSubvoxels[1]); const double dx=static_cast(b.positionSubvoxels[0])-ax; const double dy=static_cast(b.positionSubvoxels[1])-ay; const double horizontalLengthSq=dx*dx+dy*dy; const double qx=static_cast(X)*Q,qy=static_cast(Y)*Q; const double qz=static_cast(Z)*Q; const double t=horizontalLengthSq>0.0 ?std::clamp(((qx-ax)*dx+(qy-ay)*dy)/horizontalLengthSq,0.0,1.0):0.0; const double centerX=ax+t*dx,centerY=ay+t*dy; const double centerZ=a.positionSubvoxels[2]+t*( static_cast(b.positionSubvoxels[2])-a.positionSubvoxels[2]); const double radius=a.radiusSubvoxels+t*( static_cast(b.radiusSubvoxels)-a.radiusSubvoxels); // Embed the deck 13 cm above the bore bottom: within the 25 cm support // probe, while leaving 16 cm between it and the capsule's lowest sample. const double floorQ=centerZ-radius +PassageBridgeDeckOffsetMetres(PassageIndex,SegmentIndex)*qPerMetre; const double relief=std::clamp(static_cast(VF_FieldSurfaceRelief(X,Y,Z, static_cast(Owner?Owner->Seed:0))),-1.0,1.0); const double halfWidth=(surfaceWorldUpper?3.65+0.10*relief:3.30+0.20*relief) *qPerMetre; const double thickness=(4.0+1.0*relief)*qPerMetre; const double horizontalDistance=DeterministicSqrt((qx-centerX)*(qx-centerX) +(qy-centerY)*(qy-centerY)); const double sideDistance=horizontalDistance-halfWidth; const double topDistance=qz-floorQ; const double bottomDistance=floorQ-thickness-qz; const double verticalDistance=std::max(topDistance,bottomDistance); const double outside=DeterministicSqrt( std::max(sideDistance,0.0)*std::max(sideDistance,0.0) +std::max(verticalDistance,0.0)*std::max(verticalDistance,0.0)); const double inside=std::min(std::max(sideDistance,verticalDistance),0.0); return outside+inside; } double PassageColumnSignedDistance(const FStandalonePassageVoidCrossing& crossing, float X,float Y,float Z) const { if(crossing.maximumRadiusSubvoxels<=0||crossing.topZSubvoxels <=crossing.bottomZSubvoxels)return std::numeric_limits::infinity(); constexpr double Q=static_cast(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel); const double qPerMetre=Q*1000000.0/std::max(1, ResolvedLayout?ResolvedLayout->coreVoxelSizeMicrometres:250000u); const double qx=static_cast(X)*Q,qy=static_cast(Y)*Q, qz=static_cast(Z)*Q; const double dx=qx-crossing.centerXSubvoxels; const double dy=qy-crossing.centerYSubvoxels; const double maximumRadius=static_cast(crossing.maximumRadiusSubvoxels); const double horizontalSquared=dx*dx+dy*dy; if(horizontalSquared>maximumRadius*maximumRadius) return DeterministicSqrt(horizontalSquared)-maximumRadius; if(qz>crossing.topZSubvoxels) return qz-crossing.topZSubvoxels; if(qzpassages.size() &&qzpassages[crossing.passageIndex]; const auto lower=std::find_if(ResolvedLayout->strates.begin(), ResolvedLayout->strates.end(),[&](const auto& value) {return value.strateIndex==passage.lowerStrateIndex;}); if(lower!=ResolvedLayout->strates.end()) { const double capsuleRadius=0.37*qPerMetre; const double capsuleHalfHeight=0.86*qPerMetre; const double capsuleRadiusSquared=capsuleRadius*capsuleRadius; const VoxelForgeCore::ResolvedLayoutAnchorV2* previous=nullptr; for(const auto& anchor:lower->anchors) { if(anchor.kind!="route_point"||anchor.values.size()<3)continue; if(previous) { const double ax=previous->values[0],ay=previous->values[1]; const double dx=static_cast(anchor.values[0])-ax; const double dy=static_cast(anchor.values[1])-ay; const double lengthSquared=dx*dx+dy*dy; const double t=lengthSquared>0.0?std::clamp( ((qx-ax)*dx+(qy-ay)*dy)/lengthSquared,0.0,1.0):0.0; const double routeX=ax+t*dx,routeY=ay+t*dy; const double routeZ=previous->values[2]+t*( static_cast(anchor.values[2])-previous->values[2]); const double routeDx=qx-routeX,routeDy=qy-routeY; if(routeDx*routeDx+routeDy*routeDy<=capsuleRadiusSquared &&std::abs(qz-routeZ)<=capsuleHalfHeight) return maximumRadius+qPerMetre; } previous=&anchor; } } } const double height=static_cast(crossing.topZSubvoxels -crossing.bottomZSubvoxels); const double t=std::clamp((crossing.topZSubvoxels-qz)/height,0.0,1.0); const auto smoothStep=[](double value) { value=std::clamp(value,0.0,1.0); return value*value*(3.0-2.0*value); }; const float z=static_cast(Z); const float scaledZ=z*0.12f; const float driftX=VF_FieldSurfaceRelief(0.0f,0.0f,scaledZ, static_cast(Owner?Owner->Seed:0)^0x434f4c58u); const float driftY=VF_FieldSurfaceRelief(0.0f,0.0f,scaledZ, static_cast(Owner?Owner->Seed:0)^0x434f4c59u); const double axisX=crossing.centerXSubvoxels +std::clamp(static_cast(driftX),-1.0,1.0)*0.35*qPerMetre; const double axisY=crossing.centerYSubvoxels +std::clamp(static_cast(driftY),-1.0,1.0)*0.35*qPerMetre; const double centerDx=qx-axisX,centerDy=qy-axisY; const double radialDistance=DeterministicSqrt(centerDx*centerDx+centerDy*centerDy); const float broadRelief=VF_FieldSurfaceRelief(X*0.12f,Y*0.12f,Z*0.12f, static_cast(Owner?Owner->Seed:0)^0x434f4c52u); const float surfaceRelief=VF_FieldSurfaceRelief(X,Y,Z, static_cast(Owner?Owner->Seed:0)^0x434f4c53u); const double baseFlare=6.0*qPerMetre*smoothStep((t-0.82)/0.18); const double ceilingFlare=2.0*qPerMetre*(1.0-smoothStep(t/0.075)); const double irregularity=1.25*qPerMetre +std::clamp(static_cast(broadRelief),-1.0,1.0)*1.10*qPerMetre +std::clamp(static_cast(surfaceRelief),-1.0,1.0)*0.35*qPerMetre; const double radius=static_cast(crossing.minimumRadiusSubvoxels) +std::max(0.15*qPerMetre,irregularity)+baseFlare+ceilingFlare; const double radialDistanceField=radialDistance-radius; const double topDistance=qz-crossing.topZSubvoxels; const double bottomDistance=crossing.bottomZSubvoxels-qz; const double verticalDistance=std::max(topDistance,bottomDistance); const double outside=DeterministicSqrt( std::max(radialDistanceField,0.0)*std::max(radialDistanceField,0.0) +std::max(verticalDistance,0.0)*std::max(verticalDistance,0.0)); const double inside=std::min(std::max(radialDistanceField,verticalDistance),0.0); return outside+inside; } 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); } void BuildPassageVoidCrossingPlan() { BuildResolvedBoundaryZIntervals(); PassageVoidCrossings.clear(); if(!Owner||!ResolvedLayout||ResolvedLayout->passages.empty())return; if(!Owner->PassageVoidCrossingCache) {BuildPassageSpatialIndices();return;} auto& cache=*Owner->PassageVoidCrossingCache; std::lock_guard lock(cache.mutex); const std::string cacheKey=ResolvedLayout->stableIdentity+"/passage-void-columns-v1"; const auto cached=cache.byLayout.find(cacheKey); if(cached!=cache.byLayout.end()) {PassageVoidCrossings=cached->second;BuildPassageSpatialIndices();return;} constexpr double Q=static_cast(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel); const double qPerMetre=Q*1000000.0/std::max(1, ResolvedLayout->coreVoxelSizeMicrometres); constexpr std::array,5> supportOffsets{{ {{0.0,0.0}},{{0.45,0.0}},{{-0.45,0.0}},{{0.0,0.45}},{{0.0,-0.45}}}}; constexpr std::array supportDepthsMetres{{0.125,0.25}}; constexpr double shortSpanMaximumMetres=12.5; // 2.5 x the 5 m legacy support thickness. struct FPathSample { std::size_t segment=0; std::array point{}; bool nativeSupported=false; }; const auto nativeSupportedAt=[&](const std::array& point) { const double floorQ=point[2]-point[3]+0.13*qPerMetre; std::uint32_t supported=0; for(const auto& offset:supportOffsets) { bool hasSolid=false; for(const double depth:supportDepthsMetres) { const double sampleZ=floorQ-depth*qPerMetre; const float density=NativePassageDensityAt( static_cast((point[0]+offset[0]*qPerMetre)/Q), static_cast((point[1]+offset[1]*qPerMetre)/Q), static_cast(sampleZ/Q)); if(density<0.0f){hasSolid=true;break;} } supported+=hasSolid; } return supported>=3; }; for(std::size_t passageIndex=0;passageIndexpassages.size(); ++passageIndex) { const auto& passage=ResolvedLayout->passages[passageIndex]; const auto upperStratum=std::find_if(ResolvedLayout->strates.begin(), ResolvedLayout->strates.end(),[&passage](const auto& value) {return value.strateIndex==passage.upperStrateIndex;}); const bool surfaceWorldUpper=upperStratum!=ResolvedLayout->strates.end() &&upperStratum->passageCapabilities.hasSurfaceLanding; if(passage.controlPoints.size()<2)continue; std::vector samples; samples.reserve(passage.controlPoints.size()*2); const auto pointAt=[&](std::size_t segment,double t) { const auto& a=passage.controlPoints[segment]; const auto& b=passage.controlPoints[segment+1]; FPathSample sample;sample.segment=segment; for(std::size_t axis=0;axis<3;++axis) sample.point[axis]=a.positionSubvoxels[axis]+t*( static_cast(b.positionSubvoxels[axis]) -a.positionSubvoxels[axis]); sample.point[3]=a.radiusSubvoxels+t*( static_cast(b.radiusSubvoxels)-a.radiusSubvoxels); sample.nativeSupported=nativeSupportedAt(sample.point); return sample; }; samples.push_back(pointAt(0,0.0)); for(std::size_t segment=0;segment+1(b.positionSubvoxels[0])-a.positionSubvoxels[0]; const double dy=static_cast(b.positionSubvoxels[1])-a.positionSubvoxels[1]; const double dz=static_cast(b.positionSubvoxels[2])-a.positionSubvoxels[2]; const double lengthMetres=DeterministicSqrt(dx*dx+dy*dy+dz*dz)/qPerMetre; const std::size_t intervals=std::max(1, static_cast(std::ceil(lengthMetres/0.5))); for(std::size_t step=1;step<=intervals;++step) samples.push_back(pointAt(segment,static_cast(step)/intervals)); } std::size_t cursor=0; while(cursor0?gapBegin-1:gapBegin; const std::size_t endSample=cursor0&&samples[startSample].nativeSupported; crossing.connectedAtEnd=cursorshortSpanMaximumMetres; if(crossing.needsColumn&&passage.controlPoints.size()>1) crossing.lastSegment=std::min(crossing.lastSegment+1, passage.controlPoints.size()-2); if(crossing.needsColumn) { std::int64_t sumX=0,sumY=0; std::uint64_t centerSamples=0; for(std::size_t index=gapBegin;index<=gapEnd;++index) { sumX+=static_cast(std::llround(samples[index].point[0])); sumY+=static_cast(std::llround(samples[index].point[1])); ++centerSamples; } crossing.centerXSubvoxels=centerSamples>0 ?static_cast(std::llround(static_cast(sumX)/centerSamples)):0; crossing.centerYSubvoxels=centerSamples>0 ?static_cast(std::llround(static_cast(sumY)/centerSamples)):0; for(const auto& stratum:ResolvedLayout->strates) if(stratum.strateIndex==passage.upperStrateIndex &&stratum.passageCapabilities.usePassageAnchorForColumns) for(const auto& anchor:stratum.anchors) if(anchor.kind=="passage_anchor"&&anchor.values.size()>=2) { crossing.centerXSubvoxels=anchor.values[0]; crossing.centerYSubvoxels=anchor.values[1]; break; } double maximumOffsetSubvoxels=0.0; double maximumBoreSubvoxels=0.0; double minimumCrossingZ=std::min(crossing.startPoint[2],crossing.endPoint[2]); double maximumCrossingZ=std::max(crossing.startPoint[2],crossing.endPoint[2]); for(std::size_t sampleIndex=0;sampleIndexcrossing.lastSegment)continue; } else if(sampleIndexendSample)continue; const auto& point=sample.point; const double dx=point[0]-crossing.centerXSubvoxels; const double dy=point[1]-crossing.centerYSubvoxels; maximumOffsetSubvoxels=std::max(maximumOffsetSubvoxels, DeterministicSqrt(dx*dx+dy*dy)); maximumBoreSubvoxels=std::max(maximumBoreSubvoxels,point[3]); if(surfaceWorldUpper) { minimumCrossingZ=std::min(minimumCrossingZ,point[2]); maximumCrossingZ=std::max(maximumCrossingZ,point[2]); } } const double maximumOffsetMetres=maximumOffsetSubvoxels/qPerMetre; const double maximumBoreMetres=maximumBoreSubvoxels/qPerMetre; const double minimumRadiusMetres=maximumOffsetMetres+maximumBoreMetres+2.0; const double nominalRadiusMetres=minimumRadiusMetres+1.25; crossing.maximumRouteOffsetMetres=maximumOffsetMetres; crossing.maximumBoreRadiusMetres=maximumBoreMetres; crossing.minimumRadiusSubvoxels=static_cast(std::ceil( minimumRadiusMetres*qPerMetre)); crossing.nominalRadiusSubvoxels=static_cast(std::llround( nominalRadiusMetres*qPerMetre)); crossing.maximumRadiusSubvoxels=crossing.minimumRadiusSubvoxels +static_cast(std::ceil(11.25*qPerMetre)); crossing.topZSubvoxels=static_cast(std::llround( maximumCrossingZ+5.0*qPerMetre)); crossing.bottomZSubvoxels=static_cast(std::llround( minimumCrossingZ-6.0*qPerMetre)); } PassageVoidCrossings.push_back(crossing); if(crossing.needsColumn) { const double coreRadius=crossing.nominalRadiusSubvoxels/qPerMetre; const double minimumWall=coreRadius-crossing.maximumRouteOffsetMetres -crossing.maximumBoreRadiusMetres; std::printf("[PassageVoidColumnPlan] id=%s segments=%zu..%zu unsupported_samples=%llu span_m=%.3f threshold_m=%.3f connected=%d/%d axis_m=(%.3f,%.3f) z_m=%.3f..%.3f route_radius_m=%.3f bore_radius_m=%.3f min_radius_m=%.3f nominal_radius_m=%.3f max_radius_m=%.3f nominal_wall_m=%.3f flare_m=6.000 status=%s\n", passage.stableId.c_str(),crossing.firstSegment,crossing.lastSegment, static_cast(crossing.unsupportedSamples),gapLength, shortSpanMaximumMetres,static_cast(crossing.connectedAtStart), static_cast(crossing.connectedAtEnd), crossing.centerXSubvoxels/qPerMetre,crossing.centerYSubvoxels/qPerMetre, crossing.topZSubvoxels/qPerMetre,crossing.bottomZSubvoxels/qPerMetre, crossing.maximumRouteOffsetMetres,crossing.maximumBoreRadiusMetres, crossing.minimumRadiusSubvoxels/qPerMetre,coreRadius, crossing.maximumRadiusSubvoxels/qPerMetre,minimumWall, crossing.connectedAtStart&&crossing.connectedAtEnd?"PLANNED":"UNATTACHED"); } else std::printf("[PassageShortGroundedSpanPlan] id=%s segments=%zu..%zu span_m=%.3f threshold_m=%.3f connected=%d/%d width_m=7.300 thickness_m=3.000..5.000 status=%s\n", passage.stableId.c_str(),crossing.firstSegment,crossing.lastSegment, gapLength,shortSpanMaximumMetres,static_cast(crossing.connectedAtStart), static_cast(crossing.connectedAtEnd), crossing.connectedAtStart&&crossing.connectedAtEnd?"PLANNED":"UNATTACHED"); } if(std::none_of(PassageVoidCrossings.begin(),PassageVoidCrossings.end(), [passageIndex](const auto& range){return range.passageIndex==passageIndex;})) std::printf("[PassageVoidColumnPlan] id=%s crossings=0 status=NOT_REQUIRED\n", passage.stableId.c_str()); } cache.byLayout.emplace(cacheKey,PassageVoidCrossings); BuildPassageSpatialIndices(); } 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; } 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=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; return bApplyEdits?ApplyEditBrushes(composed,X,Y,Z):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=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) { const float Detail=DetailNoise(X,Y,Z,SurfaceSDF,CaveInfluence, FloorInfluence,PassageInfluence)*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); return bApplyEdits?ApplyEditBrushes(Density,X,Y,Z):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; } 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||Candidate->Kind!=FBoundStratumField::EKind::SurfaceWorld ||!Candidate->SurfacePlan||ChunkZSurfacePlan->bottomChunkZ ||ChunkZ>Candidate->SurfacePlan->topChunkZ)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; } FBoxClassification ClassifyBoxDetailed(const FIntVector& Origin,int32 Step,int32 Cells, bool bUseNativeLatticeProof=false) const { if(SharedDensitySource) return SharedDensitySource->ClassifyBoxDetailed(Origin,Step,Cells,bUseNativeLatticeProof); using Clock=std::chrono::steady_clock; const auto Start=Clock::now(); const int32 Pad=std::max(Step,1); int32 BMin[3]={Origin.X-Pad,Origin.Y-Pad,Origin.Z-Pad}; int32 BMax[3]={Origin.X+(Cells+1)*Step,Origin.Y+(Cells+1)*Step,Origin.Z+(Cells+1)*Step}; const VoxelForgeCore::WorldAssemblyStratumPlanV1* SurfacePlan=nullptr; const VoxelForgeCore::WorldAssemblyFloatingIslandPlanV1* IslandPlan=nullptr; const FSurfaceWorldFieldAdapter* SurfaceField=nullptr; const FFloatingIslandFieldAdapter* IslandField=nullptr; bool bSurfaceOverlap=false,bIslandOverlap=false,bOtherStrateOverlap=false; bool bIslandOtherStrateOverlap=false,bBadPlan=false; 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->Kind==FBoundStratumField::EKind::SurfaceWorld) { 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); } else {bOtherStrateOverlap=true;bIslandOtherStrateOverlap=true;} } } const auto Finish=[&](EVoxelTileClass Verdict,bool bUnknown=false) { const bool bNative=(bSurfaceOverlap&&!bOtherStrateOverlap) ||(bIslandOverlap&&!bIslandOtherStrateOverlap); FBoxClassification Result{Verdict,bNative,bUnknown}; auto& Counts=ThreadClassificationStats(); Counts.Milliseconds+=std::chrono::duration(Clock::now()-Start).count(); if(!Result.bNativeSurface||bUnknown)++Counts.UnknownBoxes; else if(Verdict==EVoxelTileClass::AllSolid)++Counts.SolidBoxes; else if(Verdict==EVoxelTileClass::AllAir)++Counts.AirBoxes; else ++Counts.MixedBoxes; return Result; }; 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}; if(!BuildStampTileList(minimum,maximum).empty()) 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}; if(!BuildCavernFeatureTileList(minimum,maximum).empty()) return Finish(EVoxelTileClass::Mixed,true); } if(ResolvedPassageIntersectsBox(BMin,BMax)) 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); 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; if(!bInsideProofBox||!bSurfaceOverlap||bIslandOverlap||bOtherStrateOverlap ||bBadPlan||!EditBrushes.empty()) { RecordNativeOnlyViolation(); return Finish(EVoxelTileClass::Mixed,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 ||(!bSurfaceOverlap&&bOtherStrateOverlap)))) 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&&(!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); // 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(Step,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); } 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||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; 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) { constexpr std::int64_t FilterStep=8*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel; std::int64_t weightedSum=0; 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(); 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); } } 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 { 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) { 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 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::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; } 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) const { 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 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(distanceSquared0.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; } float ApplyResolvedCavernFloorCaps(float density,float X,float Y,float Z) const { 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(distanceSquared0.0f&&feature.floorSupportDepthVoxels>0.0f &&Z>=floorZ-feature.floorSupportDepthVoxels &&radialcavernFeatureCandidates())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 BuildOwnerPrimitives() { 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); for(const FStrateSlot& Slot:Layout) { if(!Slot.Definition)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)); } 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(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); } } for(uint32 Id=0;Id(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)continue; 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); const std::array Axis0={1,0,0},Axis1={0,1,0},Axis2={0,0,1}; AddEllipsoid(P.Center,{P.Rx,P.Ry,P.Rz},Axis0,Axis1,Axis2,true); } } for(const FParent& P:Parents) { 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; AddTaperedCapsule(A,B,64.0f,64.0f,true); Primitives.back().BlendRadius=0.0; FarConnectorSegments.push_back({A,B,64.0f}); } } 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() { 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; } 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) { 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; 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]) 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=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; 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 { if(SharedDensitySource) { SharedDensitySource->DensityAtLatticeZRun(X,Y,ZStart,ZStep,Count,OutDensity); return; } if(Count<=0||OutDensity==nullptr)return; if(bNativeOnly) { for(int32 Lane=0;Lane(ZStart+Lane*ZStep)); if(bHasResolvedBoundaryBand) { for(int32 Lane=0;Lane(X), static_cast(Y),static_cast(ZStart+Lane*ZStep)); if(bHasLegacySurfaceSample) { for(int32 Lane=0;Lane(X),static_cast(Y),static_cast(Owner->Seed)); for(int32 Offset=0;Offsetsecond; int32 BatchCount=1; while(BatchCount=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)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); 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) { 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; 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)); } Cave=VF_FieldSmoothMin(Cave,Distance,static_cast(P.BlendRadius)); } 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]) Overlay=std::max(Overlay,-VF_FieldPrimitiveDistance(Shared,X,Y,Z)); } } if(OutCriticalAir)*OutCriticalAir=bCriticalAir; return VF_FieldSmoothMin(Base,std::max(Cave,Overlay),StrataCaveBlendRadius); } 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; const float 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) 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; const float 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) 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() { 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; 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::vector PassageVoidCrossings; std::array AxisBins; std::unordered_map SpatialBins; 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 CavernRuntime; std::vector PassageFeatureBounds; std::vector PassageTileFeatures; std::vector PassageFeatureOrder; std::vector PassageFeatureNodes; bool PassageFeatureBoundsFailOpen=false; std::vector> PassageNearestNodes; std::vector PassageProofBounds; std::vector PassageProofNodes; std::vector> PassageColumnSegmentFlags,PassageShortSpanSegmentFlags; std::vector PassageHasShortSpanFlags; };