diff --git a/Source/VoxelForge/Private/VoxelMarchingCubesMesher.cpp b/Source/VoxelForge/Private/VoxelMarchingCubesMesher.cpp index 9a4e5f7..cf331f6 100644 --- a/Source/VoxelForge/Private/VoxelMarchingCubesMesher.cpp +++ b/Source/VoxelForge/Private/VoxelMarchingCubesMesher.cpp @@ -4,6 +4,8 @@ #include "VoxelMarchingCubesMesher.h" #include "MarchingCubesTables.h" #include "VoxelDensityProfile.h" +#include "VoxelStrateManager.h" +#include "VoxelTilePostReach.h" #include "ProfilingDebugging/CpuProfilerTrace.h" #include "HAL/IConsoleManager.h" #include "Misc/CommandLine.h" @@ -17,6 +19,45 @@ namespace TEXT("Use the operator-major density block path for non-shared mesher grids.")); bool GVoxelForgeBlockSwitchParsed = false; + int32 GVoxelForgeTilePostReachDebug = 0; + FAutoConsoleVariableRef CVarVoxelForgeTilePostReachDebug( + TEXT("voxel.TilePostReachDebug"), + GVoxelForgeTilePostReachDebug, + TEXT("Run skipped landing/structural posts in a bypass and compare their exact result; " + "a nonzero difference fails the debug run.")); + + float GVoxelForgeTilePostReachScale = 1.0f; + FAutoConsoleVariableRef CVarVoxelForgeTilePostReachScale( + TEXT("voxel.TilePostReachScale"), + GVoxelForgeTilePostReachScale, + TEXT("Scale the per-tile post reach for deliberate soundness tests. Default 1.0 is exact; " + "values below 1 intentionally shrink the guard.")); + + bool GVoxelForgeTilePostReachSwitchesParsed = false; + + void VF_ParseTilePostReachSwitches() + { + if (GVoxelForgeTilePostReachSwitchesParsed) + { + return; + } + GVoxelForgeTilePostReachSwitchesParsed = true; + int32 DebugValue = GVoxelForgeTilePostReachDebug; + float ScaleValue = GVoxelForgeTilePostReachScale; + FParse::Value( + FCommandLine::Get(), TEXT("voxel.TilePostReachDebug="), DebugValue); + FParse::Value( + FCommandLine::Get(), TEXT("voxel.TilePostReachScale="), ScaleValue); + GVoxelForgeTilePostReachDebug = DebugValue != 0 ? 1 : 0; + if (!VoxelMath::IsFinite(ScaleValue) || ScaleValue <= 0.0f) + { + ScaleValue = 1.0f; + } + GVoxelForgeTilePostReachScale = FMath::Min(ScaleValue, 1.0f); + VoxelGenLOD::GTilePostReachDebugEnabled.store( + GVoxelForgeTilePostReachDebug != 0, std::memory_order_relaxed); + } + void VF_ParseOperatorBlockSwitch() { if (GVoxelForgeBlockSwitchParsed) { return; } @@ -42,6 +83,7 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels, { TRACE_CPUPROFILER_EVENT_SCOPE(VoxelForge_MesherGenerateMesh); VF_ParseOperatorBlockSwitch(); + VF_ParseTilePostReachSwitches(); FVoxelMeshData MeshData; if (OutCaptureGrid) { OutCaptureGrid->Reset(); } if (OutDensitySampleCount) { *OutDensitySampleCount = 0; } @@ -233,6 +275,49 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels, VoxelGenLOD::TileOriginVoxels, OriginVoxels); TGuardValue TileCellsGuard( VoxelGenLOD::TileCellsPerAxis, CellsPerAxis); + + // Every density sample in this tile sees the same post-reach decision. The one-point halo + // matches the actual grid fill below (gx/gy/gz = -1..GridDim), so a false bit proves that no + // landing or passage structural writer can reach any sample used by meshing or gradients. + TGuardValue TilePostReachDebugGuard( + VoxelGenLOD::bTilePostReachDebug, + GVoxelForgeTilePostReachDebug != 0); + TGuardValue TilePostReachBypassGuard( + VoxelGenLOD::bTilePostReachBypass, false); + uint8 TilePostReachFlags = VoxelGenLOD::AllTilePostReach; + if (Generator->StrateManager != nullptr) + { + const int64 Extent = static_cast(CellsPerAxis + 1) * Step; + const FBox LatticeWithHalo( + FVector( + static_cast(static_cast(OriginVoxels.X) - Step), + static_cast(static_cast(OriginVoxels.Y) - Step), + static_cast(static_cast(OriginVoxels.Z) - Step)), + FVector( + static_cast(static_cast(OriginVoxels.X) + Extent), + static_cast(static_cast(OriginVoxels.Y) + Extent), + static_cast(static_cast(OriginVoxels.Z) + Extent))); + const float ReachScale = GVoxelForgeTilePostReachScale; + TilePostReachFlags = 0; + if (Generator->StrateManager->AnyOriginLandingNearLattice( + LatticeWithHalo, OriginVoxels, Step, ReachScale)) + { + TilePostReachFlags |= VoxelGenLOD::OriginLandingReachable; + } + if (Generator->StrateManager->AnyPassageLandingNearLattice( + LatticeWithHalo, OriginVoxels, Step, ReachScale)) + { + TilePostReachFlags |= VoxelGenLOD::PassageLandingReachable; + } + if (Generator->StrateManager->AnyPassageStructuralPostNearLattice( + LatticeWithHalo, OriginVoxels, Step, ReachScale)) + { + TilePostReachFlags |= VoxelGenLOD::PassageStructuralReachable; + } + } + TGuardValue TilePostReachFlagsGuard( + VoxelGenLOD::TilePostReachFlags, TilePostReachFlags); + VoxelGenLOD::RecordTileReachMask(TilePostReachFlags); // COUPE DE CONTENU PAR STRATE — restreint le maillage (et l'échantillonnage) aux cellules // dont l'intervalle Z chevauche la bande [BandZMinVox, BandZMaxVox] (voxels inclusifs). // Les tuiles à capture ne sont jamais bandées (niveau 0 — garde-fou ci-dessous). diff --git a/Source/VoxelForge/Private/VoxelStrateManager.cpp b/Source/VoxelForge/Private/VoxelStrateManager.cpp index 387acc7..aa46b1e 100644 --- a/Source/VoxelForge/Private/VoxelStrateManager.cpp +++ b/Source/VoxelForge/Private/VoxelStrateManager.cpp @@ -2511,6 +2511,12 @@ float UVoxelStrateManager::EvaluateModifierSDF(float WorldX, float WorldY, float return FLT_MAX; } + // The landing terms are part of the passage modifier, so their reach proof is deliberately + // wider than the direct post's four-voxel blend: SmoothMin can lower a finite term by K/6 at + // each later combine. Outside that padded room domain, omitting the two landing evaluations + // is an identity for VF_ApplyPassageCarving. + const bool bEvaluateLandingSDF = VoxelGenLOD::IsPassageLandingReachable() + || VoxelGenLOD::IsTilePostReachBypassActive(); const FIntVector ChunkCoord( FMath::FloorToInt(WorldX / (float)CHUNK_SIZE), FMath::FloorToInt(WorldY / (float)CHUNK_SIZE), @@ -2602,10 +2608,13 @@ float UVoxelStrateManager::EvaluateModifierSDF(float WorldX, float WorldY, float // A landing is a real room with a hard flat-floor half-space, not a sphere around the // tube endpoint. It is the same fixed SDF geometry selected during GeneratePassages; it // never performs a source query here and does not synthesize a radial root connector. - const float UpperLandingSDF = VF_EvaluatePassageLandingSDF(Pos, P.UpperLanding); - const float LowerLandingSDF = VF_EvaluatePassageLandingSDF(Pos, P.LowerLanding); - MinSDF = VoxelSDF::SmoothMin(MinSDF, UpperLandingSDF, BlendK); - MinSDF = VoxelSDF::SmoothMin(MinSDF, LowerLandingSDF, BlendK); + if (bEvaluateLandingSDF) + { + const float UpperLandingSDF = VF_EvaluatePassageLandingSDF(Pos, P.UpperLanding); + const float LowerLandingSDF = VF_EvaluatePassageLandingSDF(Pos, P.LowerLanding); + MinSDF = VoxelSDF::SmoothMin(MinSDF, UpperLandingSDF, BlendK); + MinSDF = VoxelSDF::SmoothMin(MinSDF, LowerLandingSDF, BlendK); + } } return MinSDF; @@ -2617,13 +2626,41 @@ void UVoxelStrateManager::ApplyPassageModifier( { VoxelDensityProfile::FScopedTimer ProfileTimer( VoxelDensityProfile::EBucket::PassageModifier); + const bool bBypass = VoxelGenLOD::IsTilePostReachBypassActive(); + const bool bCompareLandingModifier = VoxelGenLOD::bTilePostReachDebug + && !VoxelGenLOD::IsPassageLandingReachable() && !bBypass; + float CanonicalModifierDensity = Density; + if (bCompareLandingModifier) + { + const bool PreviousBypass = VoxelGenLOD::bTilePostReachBypass; + VoxelGenLOD::bTilePostReachBypass = true; + const float CanonicalSDF = EvaluateModifierSDF(WorldX, WorldY, WorldZ); + VF_ApplyPassageCarving( + CanonicalModifierDensity, CanonicalSDF, BaseDensity, SealThickness); + VoxelGenLOD::bTilePostReachBypass = PreviousBypass; + } if (!VoxelDensityAblation::IsPassageCarvingOff()) { const float ModSDF = EvaluateModifierSDF(WorldX, WorldY, WorldZ); VF_ApplyPassageCarving(Density, ModSDF, BaseDensity, SealThickness); } + if (bCompareLandingModifier) + { + VoxelGenLOD::RecordSkippedPostComparison( + VoxelGenLOD::ETilePostComparisonKind::PassageLandingSDF, + !VoxelGenLOD::SameFloatBits(Density, CanonicalModifierDensity)); + } ApplyPassageLandingAir(Density, WorldX, WorldY, WorldZ, BaseDensity, SealThickness); + const bool bLandingReachable = VoxelGenLOD::IsPassageLandingReachable() + || VoxelGenLOD::IsTilePostReachBypassActive(); + const bool bStructuralReachable = VoxelGenLOD::IsPassageStructuralReachable() + || VoxelGenLOD::IsTilePostReachBypassActive(); + if (!bLandingReachable && !bStructuralReachable) + { + return; + } + const FIntVector ChunkCoord( FMath::FloorToInt(WorldX / (float)CHUNK_SIZE), FMath::FloorToInt(WorldY / (float)CHUNK_SIZE), @@ -2634,10 +2671,12 @@ void UVoxelStrateManager::ApplyPassageModifier( for (const int32 PassageIndex : Nearby) { const FVoxelPassage& Passage = PassageData[PassageIndex]; - const bool bLandingFloor = !VoxelDensityAblation::IsLandingPostsOff() + const bool bLandingFloor = bLandingReachable + && !VoxelDensityAblation::IsLandingPostsOff() && (VF_IsPassageLandingFloor(Position, Passage.UpperLanding) || VF_IsPassageLandingFloor(Position, Passage.LowerLanding)); - const bool bLegacySupportFloor = !VoxelDensityAblation::IsPassageStructuralPostsOff() + const bool bLegacySupportFloor = bStructuralReachable + && !VoxelDensityAblation::IsPassageStructuralPostsOff() && bPassageSupportFloorWritesEnabled && !Passage.bNativeFloorEnabled && VF_IsWalkableTunnelFloor(Passage, Position, this, PassageIndex); @@ -2656,7 +2695,10 @@ void UVoxelStrateManager::ApplyPassageModifier( // the current walkable tunnel. Reassert tunnel air after all floor posts so overlap cannot // turn a valid route into a solid plug. At the tunnel's own floor the air predicate is false, // so the support plane remains solid. - ApplyPassageTunnelAir(Density, WorldX, WorldY, WorldZ, BaseDensity, SealThickness); + if (bStructuralReachable) + { + ApplyPassageTunnelAir(Density, WorldX, WorldY, WorldZ, BaseDensity, SealThickness); + } } void UVoxelStrateManager::ApplyPassageCarvingOnly( @@ -2669,8 +2711,27 @@ void UVoxelStrateManager::ApplyPassageCarvingOnly( } VoxelDensityProfile::FScopedTimer ProfileTimer( VoxelDensityProfile::EBucket::PassageModifier); + const bool bBypass = VoxelGenLOD::IsTilePostReachBypassActive(); + const bool bCompareLandingModifier = VoxelGenLOD::bTilePostReachDebug + && !VoxelGenLOD::IsPassageLandingReachable() && !bBypass; + float CanonicalModifierDensity = Density; + if (bCompareLandingModifier) + { + const bool PreviousBypass = VoxelGenLOD::bTilePostReachBypass; + VoxelGenLOD::bTilePostReachBypass = true; + const float CanonicalSDF = EvaluateModifierSDF(WorldX, WorldY, WorldZ); + VF_ApplyPassageCarving( + CanonicalModifierDensity, CanonicalSDF, BaseDensity, SealThickness); + VoxelGenLOD::bTilePostReachBypass = PreviousBypass; + } const float ModSDF = EvaluateModifierSDF(WorldX, WorldY, WorldZ); VF_ApplyPassageCarving(Density, ModSDF, BaseDensity, SealThickness); + if (bCompareLandingModifier) + { + VoxelGenLOD::RecordSkippedPostComparison( + VoxelGenLOD::ETilePostComparisonKind::PassageLandingSDF, + !VoxelGenLOD::SameFloatBits(Density, CanonicalModifierDensity)); + } } void UVoxelStrateManager::ApplyPassageLandingAir( @@ -2681,6 +2742,16 @@ void UVoxelStrateManager::ApplyPassageLandingAir( { return; } + if (!VoxelGenLOD::IsPassageLandingReachable() + && !VoxelGenLOD::IsTilePostReachBypassActive()) + { + VoxelGenLOD::CompareSkippedPost(Density, [&]() + { + ApplyPassageLandingAir( + Density, WorldX, WorldY, WorldZ, BaseDensity, SealThickness); + }, VoxelGenLOD::ETilePostComparisonKind::PassageLandingAir); + return; + } VoxelDensityProfile::FScopedTimer ProfileTimer( VoxelDensityProfile::EBucket::PassageLandingAir); const FIntVector ChunkCoord( @@ -2749,6 +2820,16 @@ void UVoxelStrateManager::ApplyPassageTunnelAir( { return; } + if (!VoxelGenLOD::IsPassageStructuralReachable() + && !VoxelGenLOD::IsTilePostReachBypassActive()) + { + VoxelGenLOD::CompareSkippedPost(Density, [&]() + { + ApplyPassageTunnelAir( + Density, WorldX, WorldY, WorldZ, BaseDensity, SealThickness); + }, VoxelGenLOD::ETilePostComparisonKind::PassageTunnelAir); + return; + } VoxelDensityProfile::FScopedTimer ProfileTimer( VoxelDensityProfile::EBucket::PassageTunnelAir); const FVector Position(WorldX, WorldY, WorldZ); @@ -2784,6 +2865,30 @@ void UVoxelStrateManager::ApplyPassageLandingFloorMC( { return; } + const bool bBypass = VoxelGenLOD::IsTilePostReachBypassActive(); + const bool bLandingReachable = VoxelGenLOD::IsPassageLandingReachable() || bBypass; + const bool bStructuralReachable = VoxelGenLOD::IsPassageStructuralReachable() || bBypass; + const bool bCompareLanding = VoxelGenLOD::bTilePostReachDebug + && !bLandingReachable && !bBypass; + float CanonicalDensity = Density; + if (bCompareLanding) + { + const bool PreviousBypass = VoxelGenLOD::bTilePostReachBypass; + VoxelGenLOD::bTilePostReachBypass = true; + ApplyPassageLandingFloorMC( + CanonicalDensity, WorldX, WorldY, WorldZ, BaseDensity); + VoxelGenLOD::bTilePostReachBypass = PreviousBypass; + } + if (!bLandingReachable && !bStructuralReachable) + { + if (bCompareLanding) + { + VoxelGenLOD::RecordSkippedPostComparison( + VoxelGenLOD::ETilePostComparisonKind::PassageLandingFloor, + !VoxelGenLOD::SameFloatBits(Density, CanonicalDensity)); + } + return; + } VoxelDensityProfile::FScopedTimer ProfileTimer( VoxelDensityProfile::EBucket::PassageLandingFloor); const FIntVector ChunkCoord( @@ -2796,10 +2901,12 @@ void UVoxelStrateManager::ApplyPassageLandingFloorMC( for (const int32 PassageIndex : Nearby) { const FVoxelPassage& Passage = PassageData[PassageIndex]; - const bool bLandingFloor = !VoxelDensityAblation::IsLandingPostsOff() + const bool bLandingFloor = bLandingReachable + && !VoxelDensityAblation::IsLandingPostsOff() && (VF_IsPassageLandingFloor(Position, Passage.UpperLanding) || VF_IsPassageLandingFloor(Position, Passage.LowerLanding)); - const bool bLegacySupportFloor = !VoxelDensityAblation::IsPassageStructuralPostsOff() + const bool bLegacySupportFloor = bStructuralReachable + && !VoxelDensityAblation::IsPassageStructuralPostsOff() && bPassageSupportFloorWritesEnabled && !Passage.bNativeFloorEnabled && VF_IsWalkableTunnelFloor(Passage, Position, this, PassageIndex); @@ -2812,6 +2919,12 @@ void UVoxelStrateManager::ApplyPassageLandingFloorMC( break; } } + if (bCompareLanding) + { + VoxelGenLOD::RecordSkippedPostComparison( + VoxelGenLOD::ETilePostComparisonKind::PassageLandingFloor, + !VoxelGenLOD::SameFloatBits(Density, CanonicalDensity)); + } } static bool VF_IsPassageRoomFloor( @@ -2844,6 +2957,32 @@ void UVoxelStrateManager::ApplyPassageStructuralPostsMC( return; } + const bool bBypass = VoxelGenLOD::IsTilePostReachBypassActive(); + const bool bLandingReachable = VoxelGenLOD::IsPassageLandingReachable() || bBypass; + const bool bStructuralReachable = VoxelGenLOD::IsPassageStructuralReachable() || bBypass; + const bool bCompareLanding = VoxelGenLOD::bTilePostReachDebug + && !bLandingReachable && !bBypass; + float CanonicalDensity = Density; + if (bCompareLanding) + { + const bool PreviousBypass = VoxelGenLOD::bTilePostReachBypass; + VoxelGenLOD::bTilePostReachBypass = true; + ApplyPassageStructuralPostsMC( + CanonicalDensity, WorldX, WorldY, WorldZ, + BaseDensity, SealThickness, bProtectAuthoredTunnelFloor); + VoxelGenLOD::bTilePostReachBypass = PreviousBypass; + } + if (!bLandingReachable && !bStructuralReachable) + { + if (bCompareLanding) + { + VoxelGenLOD::RecordSkippedPostComparison( + VoxelGenLOD::ETilePostComparisonKind::PassageStructuralPosts, + !VoxelGenLOD::SameFloatBits(Density, CanonicalDensity)); + } + return; + } + VoxelDensityProfile::FScopedTimer ProfileTimer( VoxelDensityProfile::EBucket::PassageStructuralPosts); @@ -2873,36 +3012,37 @@ void UVoxelStrateManager::ApplyPassageStructuralPostsMC( const FVoxelPassage& Passage = PassageData[PassageIndex]; const bool bTunnelSupportFloor = - !bDisableStructuralPosts + bStructuralReachable && !bDisableStructuralPosts && bPassageSupportFloorWritesEnabled && !Passage.bNativeFloorEnabled && VF_IsWalkableTunnelFloor(Passage, Position, this, PassageIndex); - if (!bSuppressFloor && !bDisableLandingPosts + if (!bSuppressFloor && bLandingReachable && !bDisableLandingPosts && (VF_IsPassageLandingFloor(Position, Passage.UpperLanding) || VF_IsPassageLandingFloor(Position, Passage.LowerLanding))) { bAnyLandingFloor = true; } - if (!bSuppressFloor && !bDisableStructuralPosts && bTunnelSupportFloor) + if (!bSuppressFloor && bStructuralReachable + && !bDisableStructuralPosts && bTunnelSupportFloor) { bLegacyTunnelSupportFloor = true; } - if (!bSuppressFloor && !bDisableLandingPosts + if (!bSuppressFloor && bLandingReachable && !bDisableLandingPosts && (VF_IsPassageRoomFloor(Position, Passage.UpperLanding) || VF_IsPassageRoomFloor(Position, Passage.LowerLanding))) { bAnyRoomFloor = true; } - if (!bDisableStructuralPosts && !bWalkableAir + if (bStructuralReachable && !bDisableStructuralPosts && !bWalkableAir && FVector::DistSquared(Position, Passage.BoundCenter) <= Passage.BoundRadiusSq && VF_IsWalkableTunnelAir(Passage, Position, this, PassageIndex)) { bWalkableAir = true; } - if (!bDisableLandingPosts + if (bLandingReachable && !bDisableLandingPosts && FVector::DistSquared(Position, Passage.BoundCenter) <= Passage.BoundRadiusSq) { const FVoxelPassageLanding* Landings[] = { @@ -2950,6 +3090,12 @@ void UVoxelStrateManager::ApplyPassageStructuralPostsMC( VoxelDensityProfile::AddCounter( VoxelDensityProfile::ECounter::PassageSupportFloorBackstopFires); } + if (bCompareLanding) + { + VoxelGenLOD::RecordSkippedPostComparison( + VoxelGenLOD::ETilePostComparisonKind::PassageStructuralPosts, + !VoxelGenLOD::SameFloatBits(Density, CanonicalDensity)); + } } void UVoxelStrateManager::ApplyPassageNativeFloorMC( @@ -2960,6 +3106,15 @@ void UVoxelStrateManager::ApplyPassageNativeFloorMC( { return; } + if (!VoxelGenLOD::IsPassageStructuralReachable() + && !VoxelGenLOD::IsTilePostReachBypassActive()) + { + VoxelGenLOD::CompareSkippedPost(Density, [&]() + { + ApplyPassageNativeFloorMC(Density, WorldX, WorldY, WorldZ, BaseDensity); + }, VoxelGenLOD::ETilePostComparisonKind::PassageNativeFloor); + return; + } const FIntVector ChunkCoord( FMath::FloorToInt(WorldX / (float)CHUNK_SIZE), FMath::FloorToInt(WorldY / (float)CHUNK_SIZE), @@ -3018,6 +3173,16 @@ void UVoxelStrateManager::ApplyPassageLandingRoomFloorMC( { return; } + if (!VoxelGenLOD::IsPassageLandingReachable() + && !VoxelGenLOD::IsTilePostReachBypassActive()) + { + VoxelGenLOD::CompareSkippedPost(Density, [&]() + { + ApplyPassageLandingRoomFloorMC( + Density, WorldX, WorldY, WorldZ, BaseDensity); + }, VoxelGenLOD::ETilePostComparisonKind::PassageLandingRoomFloor); + return; + } VoxelDensityProfile::FScopedTimer ProfileTimer( VoxelDensityProfile::EBucket::PassageLandingRoomFloor); const FIntVector ChunkCoord( @@ -3912,6 +4077,22 @@ namespace FMath::Min((float)Box.Max.Z, MaxZ), (float)Origin.Z, Step); } + + static bool VF_LatticeBoxTouchesSphere( + const FBox& Box, const FVector& Center, float Radius) + { + if (!Box.IsValid || !VoxelMath::IsFinite(Center.X) + || !VoxelMath::IsFinite(Center.Y) || !VoxelMath::IsFinite(Center.Z) + || !VoxelMath::IsFinite(Radius) || Radius < 0.0f) + { + return true; + } + const FVector Closest( + FMath::Clamp(Center.X, (float)Box.Min.X, (float)Box.Max.X), + FMath::Clamp(Center.Y, (float)Box.Min.Y, (float)Box.Max.Y), + FMath::Clamp(Center.Z, (float)Box.Min.Z, (float)Box.Max.Z)); + return FVector::DistSquared(Closest, Center) <= Radius * Radius; + } } bool UVoxelStrateManager::AnyPassageNearLattice( @@ -4768,6 +4949,189 @@ bool UVoxelStrateManager::AnyPassageLandingFloorNearLattice( return false; } +bool UVoxelStrateManager::AnyPassageLandingNearLattice( + const FBox& VoxelBox, const FIntVector& LatticeOrigin, int32 Step, + float ReachScale) const +{ + if (Step <= 0 || !VoxelBox.IsValid + || !VoxelMath::IsFinite(ReachScale) || ReachScale <= 0.0f) + { + return true; + } + + // VF_ApplyPassageCarving has a hard no-op band at ModSDF >= 4. A landing term outside the + // rounded-room AABB by 4 + K (K=3) is either saturated out of SmoothMin when the tube is + // inside the carve band, or leaves the folded result >= 4 when the tube is outside it. That + // proves the landing term cannot change the field; the direct landing/floor posts use their + // smaller, operation-specific pads below. + for (const FVoxelPassage& Passage : Passages) + { + if (!VoxelMath::IsFinite(Passage.BoundCenter.X) + || !VoxelMath::IsFinite(Passage.BoundCenter.Y) + || !VoxelMath::IsFinite(Passage.BoundCenter.Z) + || !VoxelMath::IsFinite(Passage.BoundRadius) + || !VoxelMath::IsFinite(Passage.BoundRadiusSq) + || Passage.BoundRadius < 0.0f || Passage.BoundRadiusSq < 0.0f) + { + return true; + } + + if (Passage.ControlPoints.Num() >= 2) + { + if (Passage.ControlPoints.Num() > 4096) + { + return true; + } + const bool bTaper = Passage.ControlRadii.Num() == Passage.ControlPoints.Num(); + if (!bTaper && !VoxelMath::IsFinite(Passage.Radius)) + { + return true; + } + for (int32 PointIndex = 0; + PointIndex < Passage.ControlPoints.Num(); ++PointIndex) + { + const FVector& Point = Passage.ControlPoints[PointIndex]; + if (!VoxelMath::IsFinite(Point.X) || !VoxelMath::IsFinite(Point.Y) + || !VoxelMath::IsFinite(Point.Z) + || (bTaper && !VoxelMath::IsFinite(Passage.ControlRadii[PointIndex]))) + { + return true; + } + } + } + else + { + if (!VoxelMath::IsFinite(Passage.UpperPoint.X) + || !VoxelMath::IsFinite(Passage.UpperPoint.Y) + || !VoxelMath::IsFinite(Passage.UpperPoint.Z) + || !VoxelMath::IsFinite(Passage.LowerPoint.X) + || !VoxelMath::IsFinite(Passage.LowerPoint.Y) + || !VoxelMath::IsFinite(Passage.LowerPoint.Z) + || !VoxelMath::IsFinite(Passage.Radius)) + { + return true; + } + } + + const FVoxelPassageLanding* Landings[] = { + &Passage.UpperLanding, &Passage.LowerLanding }; + for (const FVoxelPassageLanding* Landing : Landings) + { + if (!VoxelMath::IsFinite(Landing->StandingPoint.X) + || !VoxelMath::IsFinite(Landing->StandingPoint.Y) + || !VoxelMath::IsFinite(Landing->FloorZ) + || !VoxelMath::IsFinite(Landing->CeilingZ) + || !VoxelMath::IsFinite(Landing->HalfWidth) + || !VoxelMath::IsFinite(Landing->FloorThickness)) + { + return true; + } + + if (Landing->HalfWidth > 0.0f + && Landing->FloorThickness > 0.0f + && VF_LatticeBoxTouchesAABB( + VoxelBox, LatticeOrigin, Step, + Landing->StandingPoint.X - FMath::Max(Landing->HalfWidth - 1.0f, 0.0f) + - ReachScale, + Landing->StandingPoint.X + FMath::Max(Landing->HalfWidth - 1.0f, 0.0f) + + ReachScale, + Landing->StandingPoint.Y - FMath::Max(Landing->HalfWidth - 1.0f, 0.0f) + - ReachScale, + Landing->StandingPoint.Y + FMath::Max(Landing->HalfWidth - 1.0f, 0.0f) + + ReachScale, + Landing->FloorZ - Landing->FloorThickness - ReachScale, + Landing->FloorZ + ReachScale)) + { + return true; + } + + if (Landing->HalfWidth <= 0.0f || Landing->CeilingZ <= Landing->FloorZ) + { + // VF_EvaluatePassageLandingSDF returns FLT_MAX for this descriptor. The floor + // test above has already handled the only remaining possible writer. + continue; + } + + } + } + + constexpr float SmoothMinK = 3.0f; + const float RoomPad = (VoxelPassageGeometry::LandingCarveBlendVoxels + SmoothMinK) + * ReachScale; + if (!VoxelMath::IsFinite(RoomPad) || RoomPad < 0.0f) + { + return true; + } + + for (const FVoxelPassage& Passage : Passages) + { + const FVoxelPassageLanding* Landings[] = { + &Passage.UpperLanding, &Passage.LowerLanding }; + for (const FVoxelPassageLanding* Landing : Landings) + { + if (Landing->HalfWidth <= 0.0f || Landing->CeilingZ <= Landing->FloorZ) + { + continue; + } + constexpr float RoomRounding = 1.5f; + const float Height = Landing->CeilingZ - Landing->FloorZ; + const float HalfX = FMath::Max(Landing->HalfWidth - RoomRounding, 0.25f); + const float HalfZ = FMath::Max(Height * 0.5f - RoomRounding, 0.25f); + const float CenterZ = (Landing->FloorZ + Landing->CeilingZ) * 0.5f; + if (VF_LatticeBoxTouchesAABB( + VoxelBox, LatticeOrigin, Step, + Landing->StandingPoint.X - HalfX - RoomRounding - RoomPad, + Landing->StandingPoint.X + HalfX + RoomRounding + RoomPad, + Landing->StandingPoint.Y - HalfX - RoomRounding - RoomPad, + Landing->StandingPoint.Y + HalfX + RoomRounding + RoomPad, + FMath::Min(Landing->FloorZ, + CenterZ - HalfZ - RoomRounding) - RoomPad, + FMath::Max(Landing->CeilingZ, + CenterZ + HalfZ + RoomRounding) + RoomPad)) + { + return true; + } + } + } + return false; +} + +bool UVoxelStrateManager::AnyPassageStructuralPostNearLattice( + const FBox& VoxelBox, const FIntVector& LatticeOrigin, int32 Step, + float ReachScale) const +{ + (void)LatticeOrigin; + (void)Step; + if (Step <= 0 || !VoxelBox.IsValid + || !VoxelMath::IsFinite(ReachScale) || ReachScale <= 0.0f) + { + return true; + } + + // The construction-time BoundRadius encloses every tube, landing room, floor band, and its + // four-voxel carve margin. All structural writers below are subsets of that sphere, so a + // sphere/AABB miss proves identity without doing a per-sample chunk-shortlist lookup. + for (const FVoxelPassage& Passage : Passages) + { + if (!VoxelMath::IsFinite(Passage.BoundCenter.X) + || !VoxelMath::IsFinite(Passage.BoundCenter.Y) + || !VoxelMath::IsFinite(Passage.BoundCenter.Z) + || !VoxelMath::IsFinite(Passage.BoundRadius) + || !VoxelMath::IsFinite(Passage.BoundRadiusSq) + || Passage.BoundRadius < 0.0f || Passage.BoundRadiusSq < 0.0f) + { + return true; + } + const float Reach = (Passage.BoundRadius + 1.0f) * ReachScale; + if (!VoxelMath::IsFinite(Reach) + || VF_LatticeBoxTouchesSphere(VoxelBox, Passage.BoundCenter, Reach)) + { + return true; + } + } + return false; +} + bool UVoxelStrateManager::AnyLandingFloorAtLattice( const FBox& VoxelBox, const FIntVector& LatticeOrigin, int32 Step) const { @@ -5013,9 +5377,14 @@ bool UVoxelStrateManager::AnyOriginLandingFloorNearBox( } bool UVoxelStrateManager::AnyOriginLandingNearLattice( - const FBox& VoxelBox, const FIntVector& LatticeOrigin, int32 Step) const + const FBox& VoxelBox, const FIntVector& LatticeOrigin, int32 Step, + float ReachScale) const { if (OriginSpineRadius <= 0.0f) return false; + if (!VoxelMath::IsFinite(ReachScale) || ReachScale <= 0.0f) + { + return true; + } for (const FStrateSlot& Slot : StrateLayout) { if (Slot.Definition == nullptr) continue; @@ -5023,7 +5392,8 @@ bool UVoxelStrateManager::AnyOriginLandingNearLattice( const float BottomZ = static_cast(Slot.BottomChunkZ) * CHUNK_SIZE; const float Seal = VF_BoundarySealThicknessForDefinition(*Slot.Definition); if (VoxelPassageGeometry::OriginLandingRoomTouchesLattice( - VoxelBox, LatticeOrigin, Step, TopZ, BottomZ, Seal, OriginSpineRadius)) + VoxelBox, LatticeOrigin, Step, TopZ, BottomZ, Seal, OriginSpineRadius, + ReachScale)) { return true; } diff --git a/Source/VoxelForge/Private/VoxelWorld.cpp b/Source/VoxelForge/Private/VoxelWorld.cpp index 17096ff..a0418a5 100644 --- a/Source/VoxelForge/Private/VoxelWorld.cpp +++ b/Source/VoxelForge/Private/VoxelWorld.cpp @@ -19,6 +19,7 @@ #include "VoxelStats.h" #include "VoxelCaveMorphology.h" #include "VoxelPassageGeometry.h" +#include "VoxelTilePostReach.h" // IWYU (FPSemantics = Precise ⇒ plus de PCH partagé) : GetPlayerPosition déréférence le pawn, donc // APawn doit être COMPLET — `Casts.h` n'en donne qu'une déclaration avant. APlayerController était // complet par transitivité seulement : on l'inclut explicitement, c'est exactement la fragilité @@ -1132,6 +1133,98 @@ void AVoxelWorld::EndPlay(const EEndPlayReason::Type EndPlayReason) } VoxelCaveMorphology::LogPlayerFitMemoStats(); LogStreamingLatencySummary(); + const uint64 TilePostReachDifferences = + VoxelGenLOD::GSkippedPostDifferences.load(std::memory_order_relaxed); + UE_LOG(LogTemp, Display, + TEXT("[VoxelForgeTilePostReach] tiles=%llu origin_reachable=%llu origin_skipped=%llu " + "passage_landing_reachable=%llu passage_landing_skipped=%llu " + "passage_structural_reachable=%llu passage_structural_skipped=%llu " + "debug=%d comparisons=%llu differences=%llu"), + static_cast( + VoxelGenLOD::GTilePostReachTileCount.load(std::memory_order_relaxed)), + static_cast( + VoxelGenLOD::GOriginLandingReachableTiles.load(std::memory_order_relaxed)), + static_cast( + VoxelGenLOD::GOriginLandingSkippedTiles.load(std::memory_order_relaxed)), + static_cast( + VoxelGenLOD::GPassageLandingReachableTiles.load(std::memory_order_relaxed)), + static_cast( + VoxelGenLOD::GPassageLandingSkippedTiles.load(std::memory_order_relaxed)), + static_cast( + VoxelGenLOD::GPassageStructuralReachableTiles.load(std::memory_order_relaxed)), + static_cast( + VoxelGenLOD::GPassageStructuralSkippedTiles.load(std::memory_order_relaxed)), + VoxelGenLOD::GTilePostReachDebugEnabled.load(std::memory_order_relaxed) ? 1 : 0, + static_cast( + VoxelGenLOD::GSkippedPostComparisons.load(std::memory_order_relaxed)), + static_cast(TilePostReachDifferences)); + UE_LOG(LogTemp, Display, + TEXT("[VoxelForgeTilePostReachKinds] comparisons=" + "origin_air=%llu/origin_floor=%llu/passage_sdf=%llu/landing_air=%llu/" + "tunnel_air=%llu/landing_floor=%llu/structural=%llu/native_floor=%llu/room_floor=%llu " + "differences=%llu/%llu/%llu/%llu/%llu/%llu/%llu/%llu/%llu"), + static_cast(VoxelGenLOD::GSkippedPostComparisonsByKind[ + static_cast(VoxelGenLOD::ETilePostComparisonKind::OriginAir)].load( + std::memory_order_relaxed)), + static_cast(VoxelGenLOD::GSkippedPostComparisonsByKind[ + static_cast(VoxelGenLOD::ETilePostComparisonKind::OriginFloor)].load( + std::memory_order_relaxed)), + static_cast(VoxelGenLOD::GSkippedPostComparisonsByKind[ + static_cast(VoxelGenLOD::ETilePostComparisonKind::PassageLandingSDF)].load( + std::memory_order_relaxed)), + static_cast(VoxelGenLOD::GSkippedPostComparisonsByKind[ + static_cast(VoxelGenLOD::ETilePostComparisonKind::PassageLandingAir)].load( + std::memory_order_relaxed)), + static_cast(VoxelGenLOD::GSkippedPostComparisonsByKind[ + static_cast(VoxelGenLOD::ETilePostComparisonKind::PassageTunnelAir)].load( + std::memory_order_relaxed)), + static_cast(VoxelGenLOD::GSkippedPostComparisonsByKind[ + static_cast(VoxelGenLOD::ETilePostComparisonKind::PassageLandingFloor)].load( + std::memory_order_relaxed)), + static_cast(VoxelGenLOD::GSkippedPostComparisonsByKind[ + static_cast(VoxelGenLOD::ETilePostComparisonKind::PassageStructuralPosts)].load( + std::memory_order_relaxed)), + static_cast(VoxelGenLOD::GSkippedPostComparisonsByKind[ + static_cast(VoxelGenLOD::ETilePostComparisonKind::PassageNativeFloor)].load( + std::memory_order_relaxed)), + static_cast(VoxelGenLOD::GSkippedPostComparisonsByKind[ + static_cast(VoxelGenLOD::ETilePostComparisonKind::PassageLandingRoomFloor)].load( + std::memory_order_relaxed)), + static_cast(VoxelGenLOD::GSkippedPostDifferencesByKind[ + static_cast(VoxelGenLOD::ETilePostComparisonKind::OriginAir)].load( + std::memory_order_relaxed)), + static_cast(VoxelGenLOD::GSkippedPostDifferencesByKind[ + static_cast(VoxelGenLOD::ETilePostComparisonKind::OriginFloor)].load( + std::memory_order_relaxed)), + static_cast(VoxelGenLOD::GSkippedPostDifferencesByKind[ + static_cast(VoxelGenLOD::ETilePostComparisonKind::PassageLandingSDF)].load( + std::memory_order_relaxed)), + static_cast(VoxelGenLOD::GSkippedPostDifferencesByKind[ + static_cast(VoxelGenLOD::ETilePostComparisonKind::PassageLandingAir)].load( + std::memory_order_relaxed)), + static_cast(VoxelGenLOD::GSkippedPostDifferencesByKind[ + static_cast(VoxelGenLOD::ETilePostComparisonKind::PassageTunnelAir)].load( + std::memory_order_relaxed)), + static_cast(VoxelGenLOD::GSkippedPostDifferencesByKind[ + static_cast(VoxelGenLOD::ETilePostComparisonKind::PassageLandingFloor)].load( + std::memory_order_relaxed)), + static_cast(VoxelGenLOD::GSkippedPostDifferencesByKind[ + static_cast(VoxelGenLOD::ETilePostComparisonKind::PassageStructuralPosts)].load( + std::memory_order_relaxed)), + static_cast(VoxelGenLOD::GSkippedPostDifferencesByKind[ + static_cast(VoxelGenLOD::ETilePostComparisonKind::PassageNativeFloor)].load( + std::memory_order_relaxed)), + static_cast(VoxelGenLOD::GSkippedPostDifferencesByKind[ + static_cast(VoxelGenLOD::ETilePostComparisonKind::PassageLandingRoomFloor)].load( + std::memory_order_relaxed))); + if (VoxelGenLOD::GTilePostReachDebugEnabled.load(std::memory_order_relaxed) + && TilePostReachDifferences != 0) + { + UE_LOG(LogTemp, Error, + TEXT("[VoxelForgeTilePostReach] soundness failure: skipped post changed the field.")); + FPlatformMisc::RequestExitWithStatus( + false, 1, TEXT("VoxelForge tile post reach soundness failure")); + } PendingTiles.Empty(); PendingTileCancellation.Empty(); PendingUnload.Empty(); diff --git a/Source/VoxelForge/Public/VoxelDensityPrimitives.h b/Source/VoxelForge/Public/VoxelDensityPrimitives.h index 4b7e1d0..893a8ae 100644 --- a/Source/VoxelForge/Public/VoxelDensityPrimitives.h +++ b/Source/VoxelForge/Public/VoxelDensityPrimitives.h @@ -28,6 +28,7 @@ #include "VoxelTypes.h" // SmoothStep01 #include "VoxelPassageGeometry.h" // shared body-sized landing/tunnel geometry #include "VoxelDensityAblation.h" // development-only world-changing measurements +#include "VoxelTilePostReach.h" // worker-local landing/structural post reach bits //============================================================================= // SEAL DE FRONTIÈRE / BOUNDARY SEAL @@ -193,6 +194,17 @@ FORCEINLINE void VF_ApplyOriginLandingAirMC(float& Density, float WorldX, float WorldY, float WorldZ, float StrateTopZ, float StrateBottomZ, float SealThickness, float BaseDensity, float Radius) { + if (!VoxelGenLOD::IsOriginLandingReachable() + && !VoxelGenLOD::IsTilePostReachBypassActive()) + { + VoxelGenLOD::CompareSkippedPost(Density, [&]() + { + VF_ApplyOriginLandingAirMC( + Density, WorldX, WorldY, WorldZ, + StrateTopZ, StrateBottomZ, SealThickness, BaseDensity, Radius); + }, VoxelGenLOD::ETilePostComparisonKind::OriginAir); + return; + } float InternalDensity = -Density; VF_ApplyOriginLandingAir(InternalDensity, WorldX, WorldY, WorldZ, StrateTopZ, StrateBottomZ, SealThickness, BaseDensity, Radius); @@ -204,6 +216,17 @@ FORCEINLINE void VF_ApplyOriginLandingFloorMC(float& Density, float WorldX, float WorldY, float WorldZ, float StrateTopZ, float StrateBottomZ, float SealThickness, float BaseDensity, float Radius) { + if (!VoxelGenLOD::IsOriginLandingReachable() + && !VoxelGenLOD::IsTilePostReachBypassActive()) + { + VoxelGenLOD::CompareSkippedPost(Density, [&]() + { + VF_ApplyOriginLandingFloorMC( + Density, WorldX, WorldY, WorldZ, + StrateTopZ, StrateBottomZ, SealThickness, BaseDensity, Radius); + }, VoxelGenLOD::ETilePostComparisonKind::OriginFloor); + return; + } float InternalDensity = -Density; VF_ApplyOriginLandingFloor(InternalDensity, WorldX, WorldY, WorldZ, StrateTopZ, StrateBottomZ, SealThickness, BaseDensity, Radius); diff --git a/Source/VoxelForge/Public/VoxelPassageGeometry.h b/Source/VoxelForge/Public/VoxelPassageGeometry.h index 2ca9f5d..e6692c6 100644 --- a/Source/VoxelForge/Public/VoxelPassageGeometry.h +++ b/Source/VoxelForge/Public/VoxelPassageGeometry.h @@ -471,13 +471,20 @@ namespace VoxelPassageGeometry FORCEINLINE bool OriginLandingRoomTouchesLattice( const FBox& VoxelBox, const FIntVector& LatticeOrigin, int32 Step, - float StrateTopZ, float StrateBottomZ, float SealThickness, float OriginRadius) + float StrateTopZ, float StrateBottomZ, float SealThickness, float OriginRadius, + float ReachScale = 1.0f) { if (!VoxelBox.IsValid) return false; + if (!VoxelMath::IsFinite(ReachScale) || ReachScale <= 0.0f) + { + // This is an invalid proof request, not an empty room. The manager treats a + // malformed scale as conservative/unknown before calling this helper. + return true; + } const FOriginLandingGeometry Geometry = BuildOriginLandingGeometry( StrateTopZ, StrateBottomZ, SealThickness, OriginRadius); if (!Geometry.bValid) return false; - const float Pad = LandingCarveBlendVoxels; + const float Pad = LandingCarveBlendVoxels * ReachScale; return LatticeAxisHasSampleInInterval( FMath::Max((float)VoxelBox.Min.X, -Geometry.HalfWidth - Pad), FMath::Min((float)VoxelBox.Max.X, Geometry.HalfWidth + Pad), diff --git a/Source/VoxelForge/Public/VoxelStrateManager.h b/Source/VoxelForge/Public/VoxelStrateManager.h index 2a16e64..a31c11b 100644 --- a/Source/VoxelForge/Public/VoxelStrateManager.h +++ b/Source/VoxelForge/Public/VoxelStrateManager.h @@ -472,6 +472,14 @@ public: float BaseDensity, float SealThickness) const; bool AnyPassageLandingFloorNearLattice(const FBox& VoxelBox, const FIntVector& LatticeOrigin, int32 Step) const; + /** Conservative landing-room plus floor reach for the exact MC lattice and its halo. */ + bool AnyPassageLandingNearLattice(const FBox& VoxelBox, + const FIntVector& LatticeOrigin, int32 Step, + float ReachScale = 1.0f) const; + /** Conservative sphere guard for all passage-owned structural posts and native D-floors. */ + bool AnyPassageStructuralPostNearLattice(const FBox& VoxelBox, + const FIntVector& LatticeOrigin, int32 Step, + float ReachScale = 1.0f) const; /** Exact presence test for a structural floor write on the MC lattice. */ bool AnyLandingFloorAtLattice(const FBox& VoxelBox, const FIntVector& LatticeOrigin, int32 Step) const; @@ -481,7 +489,8 @@ public: bool AnyOriginLandingFloorNearBox(const FVector& MinVoxel, const FVector& MaxVoxel) const; bool AnyOriginLandingNearLattice(const FBox& VoxelBox, - const FIntVector& LatticeOrigin, int32 Step) const; + const FIntVector& LatticeOrigin, int32 Step, + float ReachScale = 1.0f) const; /** Exact conservative candidate for the post-disturbance origin-room air reassertion. */ bool AnyOriginLandingAirNearLattice(const FBox& VoxelBox, const FIntVector& LatticeOrigin, int32 Step, diff --git a/Source/VoxelForge/Public/VoxelTilePostReach.h b/Source/VoxelForge/Public/VoxelTilePostReach.h new file mode 100644 index 0000000..dba39f5 --- /dev/null +++ b/Source/VoxelForge/Public/VoxelTilePostReach.h @@ -0,0 +1,183 @@ +// Per-tile reach state for density posts. +// +// The marching-cubes worker installs this state once for the tile's lattice plus its one-point +// halo. Density helpers only read the bit that owns them. The inline TLS is intentional: the +// runtime hot path must not cross a DLL boundary for a single boolean load. + +#pragma once + +#include "CoreMinimal.h" + +#include + +namespace VoxelGenLOD +{ + enum ETilePostReach : uint8 + { + OriginLandingReachable = 1u << 0, + PassageLandingReachable = 1u << 1, + PassageStructuralReachable= 1u << 2, + AllTilePostReach = OriginLandingReachable + | PassageLandingReachable + | PassageStructuralReachable, + }; + + enum class ETilePostComparisonKind : uint8 + { + Other = 0, + OriginAir, + OriginFloor, + PassageLandingSDF, + PassageLandingAir, + PassageTunnelAir, + PassageLandingFloor, + PassageStructuralPosts, + PassageNativeFloor, + PassageLandingRoomFloor, + Count, + }; + + // A tile that is not being meshed, or a point query outside the mesher, must retain the + // canonical behaviour. GenerateMesh scopes this to its computed mask. + inline thread_local uint8 TilePostReachFlags = AllTilePostReach; + inline thread_local bool bTilePostReachDebug = false; + inline thread_local bool bTilePostReachBypass = false; + + // These are diagnostics only. They never participate in a field decision. + inline std::atomic GTilePostReachDebugEnabled { false }; + inline std::atomic GTilePostReachTileCount { 0 }; + inline std::atomic GOriginLandingReachableTiles { 0 }; + inline std::atomic GPassageLandingReachableTiles { 0 }; + inline std::atomic GPassageStructuralReachableTiles { 0 }; + inline std::atomic GOriginLandingSkippedTiles { 0 }; + inline std::atomic GPassageLandingSkippedTiles { 0 }; + inline std::atomic GPassageStructuralSkippedTiles { 0 }; + inline std::atomic GSkippedPostComparisons { 0 }; + inline std::atomic GSkippedPostDifferences { 0 }; + inline std::atomic GSkippedPostComparisonsByKind[ + static_cast(ETilePostComparisonKind::Count)]{}; + inline std::atomic GSkippedPostDifferencesByKind[ + static_cast(ETilePostComparisonKind::Count)]{}; + + FORCEINLINE bool IsTilePostReachable(uint8 Bit) + { + return (TilePostReachFlags & Bit) != 0; + } + + FORCEINLINE bool IsOriginLandingReachable() + { + return IsTilePostReachable(OriginLandingReachable); + } + + FORCEINLINE bool IsPassageLandingReachable() + { + return IsTilePostReachable(PassageLandingReachable); + } + + FORCEINLINE bool IsPassageStructuralReachable() + { + return IsTilePostReachable(PassageStructuralReachable); + } + + FORCEINLINE bool IsTilePostReachBypassActive() + { + return bTilePostReachBypass; + } + + FORCEINLINE void RecordTileReachMask(uint8 Flags) + { + GTilePostReachTileCount.fetch_add(1, std::memory_order_relaxed); + if ((Flags & OriginLandingReachable) != 0) + { + GOriginLandingReachableTiles.fetch_add(1, std::memory_order_relaxed); + } + else + { + GOriginLandingSkippedTiles.fetch_add(1, std::memory_order_relaxed); + } + if ((Flags & PassageLandingReachable) != 0) + { + GPassageLandingReachableTiles.fetch_add(1, std::memory_order_relaxed); + } + else + { + GPassageLandingSkippedTiles.fetch_add(1, std::memory_order_relaxed); + } + if ((Flags & PassageStructuralReachable) != 0) + { + GPassageStructuralReachableTiles.fetch_add(1, std::memory_order_relaxed); + } + else + { + GPassageStructuralSkippedTiles.fetch_add(1, std::memory_order_relaxed); + } + } + + FORCEINLINE bool SameFloatBits(float A, float B) + { + uint32 ABits = 0; + uint32 BBits = 0; + FMemory::Memcpy(&ABits, &A, sizeof(float)); + FMemory::Memcpy(&BBits, &B, sizeof(float)); + return ABits == BBits; + } + + FORCEINLINE void RecordSkippedPostComparison( + ETilePostComparisonKind Kind, bool bDifferent) + { + GSkippedPostComparisons.fetch_add(1, std::memory_order_relaxed); + const uint8 KindIndex = static_cast(Kind); + if (KindIndex < static_cast(ETilePostComparisonKind::Count)) + { + GSkippedPostComparisonsByKind[KindIndex].fetch_add( + 1, std::memory_order_relaxed); + } + if (bDifferent) + { + GSkippedPostDifferences.fetch_add(1, std::memory_order_relaxed); + if (KindIndex < static_cast(ETilePostComparisonKind::Count)) + { + GSkippedPostDifferencesByKind[KindIndex].fetch_add( + 1, std::memory_order_relaxed); + } + } + } + + FORCEINLINE void RecordSkippedPostComparison(bool bDifferent) + { + RecordSkippedPostComparison(ETilePostComparisonKind::Other, bDifferent); + } + + /** Run one skipped float post through its canonical body, then restore the fast-path value. */ + template + FORCEINLINE void CompareSkippedPost( + float& Density, CallableType Callable, + ETilePostComparisonKind Kind = ETilePostComparisonKind::Other) + { + if (!bTilePostReachDebug || bTilePostReachBypass) + { + return; + } + + const float Before = Density; + { + const bool PreviousBypass = bTilePostReachBypass; + bTilePostReachBypass = true; + Callable(); + bTilePostReachBypass = PreviousBypass; + } + RecordSkippedPostComparison(Kind, !SameFloatBits(Before, Density)); + Density = Before; + } + + /** Compare a scalar proof input such as EvaluateModifierSDF without changing the field. */ + FORCEINLINE void CompareSkippedPostValue( + float Candidate, float Canonical, + ETilePostComparisonKind Kind = ETilePostComparisonKind::Other) + { + if (bTilePostReachDebug && !bTilePostReachBypass) + { + RecordSkippedPostComparison(Kind, !SameFloatBits(Candidate, Canonical)); + } + } +}