perf(generation): exact reach gating for the tunnel core (per tile) and passage carving (per 8^3 block)
Applies ce4d878's decide-once-then-prove pattern to the two largest exact targets from the re-rank
(tunnel core 20.8%, passage carving 9.1%). Codex; reviewed by me.
Measured first, where the time is:
core world: far 223,860 vs near 35,515,979 cycles -> tile gate only; the core is
near-dominated (~99%)
core tail: far 363,440 vs near 20,110,219
passage, tile: far 7,315,426 vs near 30,168,796 -> refined to an 8^3 block gate
passage, 8^3: far 11,233,384 vs near 26,250,838
The core already uses the tunnel spatial index. An exact skip cannot remove its cost: that cost is
in tiles near tunnels. The remaining lever there is the field-changing SDF/core merge (next).
- Core reach per tile, from lattice plus halo: swept capsule reach, floor band, floor-profile
deviation, relief bound, room-mouth ownership. The GetDensityAt tail is identity unless
bSupportFloor, bRoomFloor or CoreSDF < -inset.
- Passage carving per 8^3 block: capsule-chain and landing-room domains, the carve threshold and
the SmoothMin K/6 margin. Malformed descriptors stay candidates (conservative).
- voxel.TileReachCostDiagnostics (default 0) for the far/near cost split.
Soundness: the final-field debug comparison ran 130,285,889 comparisons with 0 differences; shrunk
reach (0.5) produced 140,035 differences (60,098 in the final field) and failed as required.
Field exact: canonical 07C14005 / b3e5f4c3..9b377, 64/64 per-tile ledger identical, capability
20,830 / 10,909, strate-crossing and sealed-proof tests pass.
Final DEFAULT build vs the committed-tip DLL (same command line, two interleaved runs each):
static worker 28.84 -> 28.10 s (-2.6%) LOD0 p50 53.3 -> 51.5 ms p95 94.4 -> 93.1 ms
moving worker 61.39 -> 60.61 s (-1.3%) LOD0 p50 50.9 -> 49.7 ms p95 92.21 -> 92.27 ms (flat,
+0.06%, within noise)
Maintenance note: these reach proofs must track tunnel/passage geometry. The next round adds an
automation test for the final-field comparison so a geometry change cannot silently break them.
Ledger: 15 builds (one C2065 fixed, one access-denied path retry); rejected TileOnly/BlockLookup
variants were slower; one hung diagnostic (a non-owner PID) was stopped; wrapper quoting failures
launched no editor. No crash folders.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TaFdXhTdYMkW1xrV6ApT6Y
This commit is contained in:
1 parent
0208e93606
commit
a668833ef5
8 files changed
+1062
-30
No files matched your search
@@ -7914,10 +7914,20 @@ FTunnelCoreWorldEvaluation VoxelCaveMorphology::EvaluateTunnelCoreWorld(
|
||||
const FTunnelSupportFloorColumn* SupportColumn,
|
||||
bool bUseSpatialIndex)
|
||||
{
|
||||
VoxelGenLOD::FScopedReachCost ReachCost(
|
||||
VoxelGenLOD::ETileReachCostKind::TunnelCoreWorld);
|
||||
if (VoxelDensityAblation::IsTunnelCoreOff())
|
||||
{
|
||||
return FTunnelCoreWorldEvaluation();
|
||||
}
|
||||
// GenerateMesh installs the exact tile/block reach mask after building the complete tile
|
||||
// cache. Point queries retain the canonical all-reachable TLS default; debug proof calls set
|
||||
// the bypass bit so the same sample can be evaluated as the reference field.
|
||||
if (!VoxelGenLOD::IsTilePostReachBypassActive()
|
||||
&& !VoxelGenLOD::IsTunnelCoreReachable())
|
||||
{
|
||||
return FTunnelCoreWorldEvaluation();
|
||||
}
|
||||
VoxelDensityProfile::FScopedTimer ProfileTimer(
|
||||
VoxelDensityProfile::EBucket::TunnelCoreWorld);
|
||||
const FVector Pos(WorldX, WorldY, WorldZ);
|
||||
@@ -8042,6 +8052,318 @@ FTunnelCoreWorldEvaluation VoxelCaveMorphology::EvaluateTunnelCoreWorld(
|
||||
return Result;
|
||||
}
|
||||
|
||||
bool VoxelCaveMorphology::AnyTunnelCoreWorldNearLattice(
|
||||
const FChunkSDFCache& Cache,
|
||||
const FBox& VoxelBox,
|
||||
float ReachScale)
|
||||
{
|
||||
if (VoxelDensityAblation::IsTunnelCoreOff())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!VoxelBox.IsValid
|
||||
|| !VoxelMath::IsFinite(ReachScale) || ReachScale <= 0.0f)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
const auto TouchesSphere = [&VoxelBox](const FVector& Center, float Radius) -> bool
|
||||
{
|
||||
if (!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)VoxelBox.Min.X, (float)VoxelBox.Max.X),
|
||||
FMath::Clamp(Center.Y, (float)VoxelBox.Min.Y, (float)VoxelBox.Max.Y),
|
||||
FMath::Clamp(Center.Z, (float)VoxelBox.Min.Z, (float)VoxelBox.Max.Z));
|
||||
return FVector::DistSquared(Closest, Center) <= Radius * Radius;
|
||||
};
|
||||
|
||||
const auto TouchesExpandedAabb = [&VoxelBox](
|
||||
const FVector& Min, const FVector& Max, float Pad) -> bool
|
||||
{
|
||||
if (!VoxelMath::IsFinite(Min.X) || !VoxelMath::IsFinite(Min.Y)
|
||||
|| !VoxelMath::IsFinite(Min.Z) || !VoxelMath::IsFinite(Max.X)
|
||||
|| !VoxelMath::IsFinite(Max.Y) || !VoxelMath::IsFinite(Max.Z)
|
||||
|| !VoxelMath::IsFinite(Pad) || Pad < 0.0f)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
const float DX = FMath::Max3(
|
||||
static_cast<float>(VoxelBox.Min.X) - static_cast<float>(Max.X),
|
||||
static_cast<float>(Min.X) - static_cast<float>(VoxelBox.Max.X), 0.0f);
|
||||
const float DY = FMath::Max3(
|
||||
static_cast<float>(VoxelBox.Min.Y) - static_cast<float>(Max.Y),
|
||||
static_cast<float>(Min.Y) - static_cast<float>(VoxelBox.Max.Y), 0.0f);
|
||||
const float DZ = FMath::Max3(
|
||||
static_cast<float>(VoxelBox.Min.Z) - static_cast<float>(Max.Z),
|
||||
static_cast<float>(Min.Z) - static_cast<float>(VoxelBox.Max.Z), 0.0f);
|
||||
return DX <= Pad && DY <= Pad && DZ <= Pad;
|
||||
};
|
||||
|
||||
const float Fade = FMath::Max(Cache.SDFBlendRadius, 0.0f) * 0.35f;
|
||||
if (!VoxelMath::IsFinite(Fade))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
for (const FCachedTunnel& Tunnel : Cache.Tunnels)
|
||||
{
|
||||
const bool bHasWorldChain = Tunnel.WorldControlPoints.Num() >= 2
|
||||
&& Tunnel.WorldControlRadii.Num() == Tunnel.WorldControlPoints.Num();
|
||||
const FVector& BoundCenter = bHasWorldChain
|
||||
? Tunnel.WorldBoundCenter : Tunnel.BoundCenter;
|
||||
const float BoundRadiusSq = bHasWorldChain
|
||||
? Tunnel.WorldBoundRadiusSq : Tunnel.BoundRadiusSq;
|
||||
const float InfluenceRadius = bHasWorldChain
|
||||
? Tunnel.WorldInfluenceRadius : Tunnel.SDFInfluenceRadius;
|
||||
const FVector& CenterlineMin = bHasWorldChain
|
||||
? Tunnel.WorldCenterlineMin : Tunnel.SDFCenterlineMin;
|
||||
const FVector& CenterlineMax = bHasWorldChain
|
||||
? Tunnel.WorldCenterlineMax : Tunnel.SDFCenterlineMax;
|
||||
|
||||
if (!VoxelMath::IsFinite(BoundCenter.X)
|
||||
|| !VoxelMath::IsFinite(BoundCenter.Y)
|
||||
|| !VoxelMath::IsFinite(BoundCenter.Z)
|
||||
|| !VoxelMath::IsFinite(BoundRadiusSq) || BoundRadiusSq < 0.0f
|
||||
|| !VoxelMath::IsFinite(InfluenceRadius) || InfluenceRadius < 0.0f
|
||||
|| !VoxelMath::IsFinite(CenterlineMin.X)
|
||||
|| !VoxelMath::IsFinite(CenterlineMin.Y)
|
||||
|| !VoxelMath::IsFinite(CenterlineMin.Z)
|
||||
|| !VoxelMath::IsFinite(CenterlineMax.X)
|
||||
|| !VoxelMath::IsFinite(CenterlineMax.Y)
|
||||
|| !VoxelMath::IsFinite(CenterlineMax.Z))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (bHasWorldChain)
|
||||
{
|
||||
for (int32 Index = 0; Index < Tunnel.WorldControlPoints.Num(); ++Index)
|
||||
{
|
||||
const FVector& Point = Tunnel.WorldControlPoints[Index];
|
||||
if (!VoxelMath::IsFinite(Point.X) || !VoxelMath::IsFinite(Point.Y)
|
||||
|| !VoxelMath::IsFinite(Point.Z)
|
||||
|| !VoxelMath::IsFinite(Tunnel.WorldControlRadii[Index]))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// VF_TunnelFloorRelief uses the same bounded noise as the floor profile. Use the
|
||||
// profile-independent maximum scale (1.0) here: the normal authored scale is <= .20,
|
||||
// but this remains sound for an old or malformed profile that bypassed that clamp.
|
||||
float ReliefBound = 0.0f;
|
||||
if (Tunnel.FloorReliefStrength > 0.0f)
|
||||
{
|
||||
if (!VoxelMath::IsFinite(Tunnel.FloorReliefStrength)
|
||||
|| !VoxelMath::IsFinite(Tunnel.FloorReliefFrequency))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
ReliefBound = FMath::Abs(Tunnel.FloorReliefStrength)
|
||||
* VOXEL_NOISE_SCALE * 1.01f;
|
||||
if (!VoxelMath::IsFinite(ReliefBound))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
float MouthReach = 0.0f;
|
||||
float MouthFloorDelta = 0.0f;
|
||||
if (bHasWorldChain && Tunnel.bHasFloorRoomOwnership)
|
||||
{
|
||||
MouthReach = FMath::Max(
|
||||
FMath::Max(Tunnel.WorldMouthBlendRadiusA, Tunnel.WorldMouthBlendRadiusB),
|
||||
0.0f) + Fade + 1.0f;
|
||||
if (!VoxelMath::IsFinite(MouthReach))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
const float NaturalA = static_cast<float>(Tunnel.WorldControlPoints[0].Z)
|
||||
- FMath::Abs(Tunnel.WorldControlRadii[0]);
|
||||
const int32 Last = Tunnel.WorldControlPoints.Num() - 1;
|
||||
const float NaturalB = static_cast<float>(Tunnel.WorldControlPoints[Last].Z)
|
||||
- FMath::Abs(Tunnel.WorldControlRadii[Last]);
|
||||
if (!VoxelMath::IsFinite(NaturalA) || !VoxelMath::IsFinite(NaturalB))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (Tunnel.WorldMouthFloorZA > -FLT_MAX
|
||||
&& !VoxelMath::IsFinite(Tunnel.WorldMouthFloorZA))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (Tunnel.WorldMouthFloorZB > -FLT_MAX
|
||||
&& !VoxelMath::IsFinite(Tunnel.WorldMouthFloorZB))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (Tunnel.WorldMouthFloorZA > -FLT_MAX)
|
||||
{
|
||||
MouthFloorDelta = FMath::Max(
|
||||
MouthFloorDelta, FMath::Abs(Tunnel.WorldMouthFloorZA - NaturalA));
|
||||
}
|
||||
if (Tunnel.WorldMouthFloorZB > -FLT_MAX)
|
||||
{
|
||||
MouthFloorDelta = FMath::Max(
|
||||
MouthFloorDelta, FMath::Abs(Tunnel.WorldMouthFloorZB - NaturalB));
|
||||
}
|
||||
}
|
||||
|
||||
// Tighten the construction sphere to the actual swept chain before falling back to the
|
||||
// legacy bound. The evaluator tests a capsule segment, not the sphere around the whole
|
||||
// wandering chain; using segment AABBs removes the long-chain false near result while
|
||||
// retaining a conservative pad for radius, floor band, relief, and SmoothMax blend.
|
||||
const TArray<FVector>& ChainPoints = bHasWorldChain
|
||||
? Tunnel.WorldControlPoints : Tunnel.ControlPoints;
|
||||
const TArray<float>& ChainRadii = bHasWorldChain
|
||||
? Tunnel.WorldControlRadii : Tunnel.ControlRadii;
|
||||
bool bHasTightChain = ChainPoints.Num() >= 2
|
||||
&& ChainRadii.Num() == ChainPoints.Num();
|
||||
bool bTightTouch = false;
|
||||
if (bHasTightChain)
|
||||
{
|
||||
float MaxRadius = 0.0f;
|
||||
for (int32 Index = 0; Index < ChainPoints.Num(); ++Index)
|
||||
{
|
||||
const FVector& Point = ChainPoints[Index];
|
||||
if (!VoxelMath::IsFinite(Point.X) || !VoxelMath::IsFinite(Point.Y)
|
||||
|| !VoxelMath::IsFinite(Point.Z)
|
||||
|| !VoxelMath::IsFinite(ChainRadii[Index]))
|
||||
{
|
||||
bHasTightChain = false;
|
||||
break;
|
||||
}
|
||||
MaxRadius = FMath::Max(MaxRadius, FMath::Abs(ChainRadii[Index]));
|
||||
}
|
||||
if (bHasTightChain)
|
||||
{
|
||||
float ProfileFloorDelta = 0.0f;
|
||||
const TArray<FTunnelFloorSegmentProfile>& Profiles = bHasWorldChain
|
||||
? Tunnel.WorldFloorProfiles : Tunnel.FloorProfiles;
|
||||
if (Tunnel.bTunnelFloorEnabled
|
||||
&& Profiles.Num() == ChainPoints.Num() - 1)
|
||||
{
|
||||
for (int32 SegmentIndex = 0;
|
||||
SegmentIndex < Profiles.Num(); ++SegmentIndex)
|
||||
{
|
||||
const FTunnelFloorSegmentProfile& Profile = Profiles[SegmentIndex];
|
||||
if (!VoxelMath::IsFinite(Profile.StartFloorZ)
|
||||
|| !VoxelMath::IsFinite(Profile.EndFloorZ))
|
||||
{
|
||||
bHasTightChain = false;
|
||||
break;
|
||||
}
|
||||
const float NaturalA = static_cast<float>(
|
||||
ChainPoints[SegmentIndex].Z)
|
||||
- FMath::Abs(ChainRadii[SegmentIndex]);
|
||||
const float NaturalB = static_cast<float>(
|
||||
ChainPoints[SegmentIndex + 1].Z)
|
||||
- FMath::Abs(ChainRadii[SegmentIndex + 1]);
|
||||
ProfileFloorDelta = FMath::Max(
|
||||
ProfileFloorDelta,
|
||||
FMath::Max(
|
||||
FMath::Abs(Profile.StartFloorZ - NaturalA),
|
||||
FMath::Abs(Profile.EndFloorZ - NaturalB)));
|
||||
}
|
||||
}
|
||||
if (bHasTightChain)
|
||||
{
|
||||
const float FloorPad = Tunnel.bTunnelFloorEnabled
|
||||
? FMath::Max(
|
||||
VoxelPassageGeometry::LandingFloorThicknessVoxels,
|
||||
FMath::Max(Cache.SDFBlendRadius, 0.0f) * 0.35f)
|
||||
: 0.0f;
|
||||
const float SegmentPad = MaxRadius + FloorPad
|
||||
+ ProfileFloorDelta + ReliefBound;
|
||||
if (!VoxelMath::IsFinite(SegmentPad))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
for (int32 SegmentIndex = 0;
|
||||
SegmentIndex + 1 < ChainPoints.Num(); ++SegmentIndex)
|
||||
{
|
||||
const FVector Min(
|
||||
FMath::Min(ChainPoints[SegmentIndex].X,
|
||||
ChainPoints[SegmentIndex + 1].X),
|
||||
FMath::Min(ChainPoints[SegmentIndex].Y,
|
||||
ChainPoints[SegmentIndex + 1].Y),
|
||||
FMath::Min(ChainPoints[SegmentIndex].Z,
|
||||
ChainPoints[SegmentIndex + 1].Z));
|
||||
const FVector Max(
|
||||
FMath::Max(ChainPoints[SegmentIndex].X,
|
||||
ChainPoints[SegmentIndex + 1].X),
|
||||
FMath::Max(ChainPoints[SegmentIndex].Y,
|
||||
ChainPoints[SegmentIndex + 1].Y),
|
||||
FMath::Max(ChainPoints[SegmentIndex].Z,
|
||||
ChainPoints[SegmentIndex + 1].Z));
|
||||
if (TouchesExpandedAabb(Min, Max, SegmentPad * ReachScale))
|
||||
{
|
||||
bTightTouch = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!bTightTouch && Tunnel.bTunnelFloorEnabled
|
||||
&& bHasWorldChain && Tunnel.bHasFloorRoomOwnership)
|
||||
{
|
||||
const float MouthHorizontal = MouthReach;
|
||||
const float MouthVertical = FMath::Max(
|
||||
MaxRadius + VoxelPassageGeometry::LandingFloorThicknessVoxels
|
||||
+ ReliefBound,
|
||||
MouthFloorDelta + VoxelPassageGeometry::LandingFloorThicknessVoxels
|
||||
+ ReliefBound);
|
||||
const float ScaledHorizontal = MouthHorizontal * ReachScale;
|
||||
const float ScaledVertical = MouthVertical * ReachScale;
|
||||
for (int32 Endpoint : {0, ChainPoints.Num() - 1})
|
||||
{
|
||||
const FVector& Point = ChainPoints[Endpoint];
|
||||
if (TouchesExpandedAabb(
|
||||
FVector(
|
||||
static_cast<float>(Point.X) - ScaledHorizontal,
|
||||
static_cast<float>(Point.Y) - ScaledHorizontal,
|
||||
static_cast<float>(Point.Z) - ScaledVertical),
|
||||
FVector(
|
||||
static_cast<float>(Point.X) + ScaledHorizontal,
|
||||
static_cast<float>(Point.Y) + ScaledHorizontal,
|
||||
static_cast<float>(Point.Z) + ScaledVertical),
|
||||
0.0f))
|
||||
{
|
||||
bTightTouch = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (bHasTightChain && !bTightTouch)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// The broad sphere already encloses the swept radius and finite floor band. Add the
|
||||
// vertical relief and room-mouth ownership envelopes before asking whether the lattice
|
||||
// box can touch it. A miss is therefore an identity proof for the core evaluator and
|
||||
// its support/room-floor tail; its only field writes are bSupportFloor, bRoomFloor, or
|
||||
// CoreSDF < -inset, all of which are inside this envelope.
|
||||
const float Reach = (
|
||||
FMath::Sqrt(FMath::Max(BoundRadiusSq, 0.0f))
|
||||
+ ReliefBound + MouthReach + MouthFloorDelta) * ReachScale;
|
||||
if (!VoxelMath::IsFinite(Reach)
|
||||
|| TouchesSphere(BoundCenter, Reach))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
float VoxelCaveMorphology::EvaluateTunnelCoreWorldSDF(
|
||||
float WorldX, float WorldY, float WorldZ,
|
||||
const FChunkSDFCache& Cache,
|
||||
|
||||
@@ -2370,6 +2370,49 @@ namespace
|
||||
return static_cast<int32>(Hash & (TunnelDensityCacheSlotCount - 1));
|
||||
}
|
||||
|
||||
static void UpdateTunnelCoreTileReach(
|
||||
const FChunkSDFCache& Cache,
|
||||
bool bUseTileCacheWindow)
|
||||
{
|
||||
if (!bUseTileCacheWindow
|
||||
|| !VoxelGenLOD::bTileReachDiagnosticTile
|
||||
|| VoxelGenLOD::TileCellsPerAxis <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const int32 Step = FMath::Max(VoxelGenLOD::GetThreadSampleStep(), 1);
|
||||
const int64 Extent = static_cast<int64>(VoxelGenLOD::TileCellsPerAxis + 1)
|
||||
* static_cast<int64>(Step);
|
||||
if (Extent <= 0 || Extent > MAX_int32)
|
||||
{
|
||||
VoxelGenLOD::RecordTunnelCoreReachDecision(true);
|
||||
return;
|
||||
}
|
||||
const FIntVector& Origin = VoxelGenLOD::TileOriginVoxels;
|
||||
const FBox LatticeWithHalo(
|
||||
FVector(
|
||||
static_cast<float>(static_cast<int64>(Origin.X) - Step),
|
||||
static_cast<float>(static_cast<int64>(Origin.Y) - Step),
|
||||
static_cast<float>(static_cast<int64>(Origin.Z) - Step)),
|
||||
FVector(
|
||||
static_cast<float>(static_cast<int64>(Origin.X) + Extent),
|
||||
static_cast<float>(static_cast<int64>(Origin.Y) + Extent),
|
||||
static_cast<float>(static_cast<int64>(Origin.Z) + Extent)));
|
||||
const bool bNear = VoxelCaveMorphology::AnyTunnelCoreWorldNearLattice(
|
||||
Cache, LatticeWithHalo, VoxelGenLOD::TileReachScale);
|
||||
if (bNear)
|
||||
{
|
||||
VoxelGenLOD::TilePostReachFlags |= VoxelGenLOD::TunnelCoreReachable;
|
||||
}
|
||||
else
|
||||
{
|
||||
VoxelGenLOD::TilePostReachFlags &= ~VoxelGenLOD::TunnelCoreReachable;
|
||||
}
|
||||
VoxelGenLOD::bTileCoreReachProofEnabled = true;
|
||||
VoxelGenLOD::RecordTunnelCoreReachDecision(bNear);
|
||||
}
|
||||
|
||||
void PrepareTunnelCoreCache(
|
||||
const UVoxelStrateManager& Manager,
|
||||
const FIntVector& ChunkCoord,
|
||||
@@ -2398,6 +2441,14 @@ namespace
|
||||
* static_cast<float>(CHUNK_SIZE) * VOXEL_SIZE);
|
||||
const uint32 WorldSeed = static_cast<uint32>(Manager.GetWorldSeed());
|
||||
|
||||
// A tile-window cache contains the complete graph reach for this tile. Chunk-local
|
||||
// caches deliberately leave the core bit conservative; updating it there would make the
|
||||
// first chunk's false result hide a tunnel that enters through a later cache window.
|
||||
if (bUseTileCacheWindow && OutState.bValid)
|
||||
{
|
||||
UpdateTunnelCoreTileReach(OutState.Cache, bUseTileCacheWindow);
|
||||
}
|
||||
|
||||
const bool bSameGraphWindow = OutState.bValid
|
||||
&& OutState.OwnerId == OwnerId
|
||||
&& OutState.ManagerLifetimeId == ManagerLifetimeId
|
||||
@@ -2457,6 +2508,7 @@ namespace
|
||||
Params, WorldSeed,
|
||||
StrateIndex, nullptr,
|
||||
ERoomGraphBuildSite::GeneratorTunnelCore);
|
||||
UpdateTunnelCoreTileReach(OutState.Cache, bUseTileCacheWindow);
|
||||
BuildTunnelSupportColumnsForChunk(
|
||||
OutState.Cache, ChunkCoord, bUseSpatialIndex);
|
||||
OutState.OwnerId = OwnerId;
|
||||
@@ -2723,6 +2775,24 @@ void UVoxelGenerator::EndDensityBlock() const
|
||||
|
||||
float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) const
|
||||
{
|
||||
// Soundness mode: for a sample in a block whose core or passage-carving reach bit is clear,
|
||||
// evaluate the complete canonical field once with the bypass bit set. The normal call then
|
||||
// takes the fast path below and compares the final MC field after the boundary seal and diff
|
||||
// layer—not an intermediate SDF. This is deliberately debug-only; production pays no
|
||||
// recursive call and only the evaluator/tail flag reads.
|
||||
const bool bCompareFinalField = VoxelGenLOD::bTilePostReachDebug
|
||||
&& !VoxelGenLOD::IsTilePostReachBypassActive()
|
||||
&& VoxelGenLOD::bTileReachDiagnosticTile
|
||||
&& (!VoxelGenLOD::IsTunnelCoreReachable()
|
||||
|| !VoxelGenLOD::IsPassageCarvingReachable());
|
||||
float CanonicalFinalField = 0.0f;
|
||||
if (bCompareFinalField)
|
||||
{
|
||||
const bool PreviousBypass = VoxelGenLOD::bTilePostReachBypass;
|
||||
VoxelGenLOD::bTilePostReachBypass = true;
|
||||
CanonicalFinalField = GetDensityAt(WorldX, WorldY, WorldZ);
|
||||
VoxelGenLOD::bTilePostReachBypass = PreviousBypass;
|
||||
}
|
||||
TRACE_CPUPROFILER_EVENT_SCOPE(VoxelForge_GetDensityAt);
|
||||
VF_ParseFastIsFiniteSwitch();
|
||||
VF_ParseFusedEvaluatorSwitch();
|
||||
@@ -3542,16 +3612,52 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co
|
||||
// bridge over a graph tunnel. Reassert the native cached tunnel core here, after every
|
||||
// solid floor writer but before the global XY seal. This cache is built once per chunk,
|
||||
// never once per voxel.
|
||||
VoxelGenLOD::FScopedReachCost TunnelCoreTailReachCost(
|
||||
VoxelGenLOD::ETileReachCostKind::TunnelCoreTail);
|
||||
const bool bTunnelCoreReachable = VoxelGenLOD::IsTunnelCoreReachable();
|
||||
const bool bTunnelCoreBypass = VoxelGenLOD::IsTilePostReachBypassActive();
|
||||
auto ApplyGraphLandingFloors = [&]()
|
||||
{
|
||||
// These are passage/origin landing posts, not tunnel-core writes. Keep them alive when
|
||||
// the core bit is clear because their own per-tile reach bits may still be set.
|
||||
{
|
||||
VoxelDensityProfile::FScopedTimer ProfileTimer(
|
||||
VoxelDensityProfile::EBucket::PassageLandingFloor);
|
||||
VF_ApplyOriginLandingFloorMC(
|
||||
Result, WorldX, WorldY, WorldZ,
|
||||
CP_Dist.StrateTopWorldZ, CP_Dist.StrateBottomWorldZ,
|
||||
CP_Dist.BoundarySealThickness, LandingBaseDensity, OriginSpineRadius);
|
||||
}
|
||||
{
|
||||
VoxelDensityProfile::FScopedTimer ProfileTimer(
|
||||
VoxelDensityProfile::EBucket::PassageLandingFloor);
|
||||
StrateManager->ApplyPassageLandingFloorMC(
|
||||
Result, WorldX, WorldY, WorldZ, LandingBaseDensity);
|
||||
}
|
||||
{
|
||||
VoxelDensityProfile::FScopedTimer ProfileTimer(
|
||||
VoxelDensityProfile::EBucket::PassageLandingRoomFloor);
|
||||
StrateManager->ApplyPassageLandingRoomFloorMC(
|
||||
Result, WorldX, WorldY, WorldZ, LandingBaseDensity);
|
||||
}
|
||||
};
|
||||
if (!VoxelDensityAblation::IsTunnelCoreOff()
|
||||
&& (ActiveTunnelCoreCache->bValid || CP_UseOpStack))
|
||||
{
|
||||
FTunnelCoreWorldEvaluation TunnelCore;
|
||||
bool bHaveTunnelCore = bHavePreDisturbanceTunnelCore;
|
||||
if (!bTunnelCoreReachable && !bTunnelCoreBypass)
|
||||
{
|
||||
// The fused/source hand-off is also empty on the skipped path. Do not let the
|
||||
// default object turn this into a second tail evaluation.
|
||||
bHaveTunnelCore = false;
|
||||
}
|
||||
if (bHaveTunnelCore)
|
||||
{
|
||||
TunnelCore = PreDisturbanceTunnelCore;
|
||||
}
|
||||
if (!bHaveTunnelCore && ActiveTunnelCoreCache->bValid)
|
||||
if (!bHaveTunnelCore && ActiveTunnelCoreCache->bValid
|
||||
&& (bTunnelCoreReachable || bTunnelCoreBypass))
|
||||
{
|
||||
const FTunnelSupportFloorColumn* SupportColumn = nullptr;
|
||||
FTunnelSupportFloorColumn EmptySupportColumn;
|
||||
@@ -3631,30 +3737,10 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co
|
||||
FMath::Max(LandingBaseDensity * 2.0f, 1.0f));
|
||||
}
|
||||
|
||||
// The graph tunnel can overlap an inter-strate landing at a room mouth. Its air
|
||||
// backstop is allowed to reopen the tunnel, but the landing's proved support floor
|
||||
// must own the final floor band; otherwise the graph post can erase the only support
|
||||
// surface at the mouth and leave the player-fit graph with a disconnected pocket.
|
||||
{
|
||||
VoxelDensityProfile::FScopedTimer ProfileTimer(
|
||||
VoxelDensityProfile::EBucket::PassageLandingFloor);
|
||||
VF_ApplyOriginLandingFloorMC(
|
||||
Result, WorldX, WorldY, WorldZ,
|
||||
CP_Dist.StrateTopWorldZ, CP_Dist.StrateBottomWorldZ,
|
||||
CP_Dist.BoundarySealThickness, LandingBaseDensity, OriginSpineRadius);
|
||||
}
|
||||
{
|
||||
VoxelDensityProfile::FScopedTimer ProfileTimer(
|
||||
VoxelDensityProfile::EBucket::PassageLandingFloor);
|
||||
StrateManager->ApplyPassageLandingFloorMC(
|
||||
Result, WorldX, WorldY, WorldZ, LandingBaseDensity);
|
||||
}
|
||||
{
|
||||
VoxelDensityProfile::FScopedTimer ProfileTimer(
|
||||
VoxelDensityProfile::EBucket::PassageLandingRoomFloor);
|
||||
StrateManager->ApplyPassageLandingRoomFloorMC(
|
||||
Result, WorldX, WorldY, WorldZ, LandingBaseDensity);
|
||||
}
|
||||
}
|
||||
if (bHaveTunnelCore || (!bTunnelCoreReachable && !bTunnelCoreBypass))
|
||||
{
|
||||
ApplyGraphLandingFloors();
|
||||
}
|
||||
}
|
||||
// Inter-strate passages own the same final D-floor contract as graph tunnels. This is
|
||||
@@ -3750,6 +3836,14 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co
|
||||
VoxelDensityProfile::FScopedTimer DensityTailTimer(
|
||||
VoxelDensityProfile::EBucket::DensityTail);
|
||||
VoxelPassageGeometry::ResetVerticalShaftConnectorAirMarker();
|
||||
if (bCompareFinalField)
|
||||
{
|
||||
const bool bFinalFieldDifferent =
|
||||
!VoxelGenLOD::SameFloatBits(Result, CanonicalFinalField);
|
||||
VoxelGenLOD::RecordSkippedPostComparison(
|
||||
VoxelGenLOD::ETilePostComparisonKind::FinalField,
|
||||
bFinalFieldDifferent);
|
||||
}
|
||||
return Result;
|
||||
}
|
||||
|
||||
@@ -4089,6 +4183,7 @@ float UVoxelGenerator::GetDensityWithParams(float WorldX, float WorldY, float Wo
|
||||
TerrainOps,
|
||||
ERoomGraphBuildSite::GeneratorTile
|
||||
);
|
||||
UpdateTunnelCoreTileReach(SDFCache, bUseTileCacheWindow);
|
||||
|
||||
CachedSMinX = SMinX; CachedSMaxX = SMaxX;
|
||||
CachedSMinY = SMinY; CachedSMaxY = SMaxY;
|
||||
|
||||
@@ -33,6 +33,12 @@ namespace
|
||||
TEXT("Scale the per-tile post reach for deliberate soundness tests. Default 1.0 is exact; "
|
||||
"values below 1 intentionally shrink the guard."));
|
||||
|
||||
int32 GVoxelForgeTileReachCostDiagnostics = 0;
|
||||
FAutoConsoleVariableRef CVarVoxelForgeTileReachCostDiagnostics(
|
||||
TEXT("voxel.TileReachCostDiagnostics"),
|
||||
GVoxelForgeTileReachCostDiagnostics,
|
||||
TEXT("Measure core/passage density cost split by tile reach; no field gate."));
|
||||
|
||||
bool GVoxelForgeTilePostReachSwitchesParsed = false;
|
||||
|
||||
void VF_ParseTilePostReachSwitches()
|
||||
@@ -44,18 +50,24 @@ namespace
|
||||
GVoxelForgeTilePostReachSwitchesParsed = true;
|
||||
int32 DebugValue = GVoxelForgeTilePostReachDebug;
|
||||
float ScaleValue = GVoxelForgeTilePostReachScale;
|
||||
int32 CostDiagnosticsValue = GVoxelForgeTileReachCostDiagnostics;
|
||||
FParse::Value(
|
||||
FCommandLine::Get(), TEXT("voxel.TilePostReachDebug="), DebugValue);
|
||||
FParse::Value(
|
||||
FCommandLine::Get(), TEXT("voxel.TilePostReachScale="), ScaleValue);
|
||||
FParse::Value(
|
||||
FCommandLine::Get(), TEXT("voxel.TileReachCostDiagnostics="), CostDiagnosticsValue);
|
||||
GVoxelForgeTilePostReachDebug = DebugValue != 0 ? 1 : 0;
|
||||
if (!VoxelMath::IsFinite(ScaleValue) || ScaleValue <= 0.0f)
|
||||
{
|
||||
ScaleValue = 1.0f;
|
||||
}
|
||||
GVoxelForgeTilePostReachScale = FMath::Min(ScaleValue, 1.0f);
|
||||
GVoxelForgeTileReachCostDiagnostics = CostDiagnosticsValue != 0 ? 1 : 0;
|
||||
VoxelGenLOD::GTilePostReachDebugEnabled.store(
|
||||
GVoxelForgeTilePostReachDebug != 0, std::memory_order_relaxed);
|
||||
VoxelGenLOD::GTileReachCostDiagnosticsEnabled.store(
|
||||
GVoxelForgeTileReachCostDiagnostics != 0, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void VF_ParseOperatorBlockSwitch()
|
||||
@@ -284,6 +296,23 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
||||
GVoxelForgeTilePostReachDebug != 0);
|
||||
TGuardValue<bool> TilePostReachBypassGuard(
|
||||
VoxelGenLOD::bTilePostReachBypass, false);
|
||||
TGuardValue<bool> TileReachDiagnosticGuard(
|
||||
VoxelGenLOD::bTileReachDiagnosticTile, true);
|
||||
TGuardValue<bool> TileCoreReachProofGuard(
|
||||
VoxelGenLOD::bTileCoreReachProofEnabled, false);
|
||||
TGuardValue<bool> TileCoreReachDecisionGuard(
|
||||
VoxelGenLOD::bTileCoreReachDecisionRecorded, false);
|
||||
TGuardValue<float> TileReachScaleGuard(
|
||||
VoxelGenLOD::TileReachScale, GVoxelForgeTilePostReachScale);
|
||||
const float ReachScale = GVoxelForgeTilePostReachScale;
|
||||
const int32 ReachBlocksPerAxis = FMath::DivideAndRoundUp(
|
||||
CellsPerAxis, VoxelGenLOD::TileReachBlockCells);
|
||||
TGuardValue<int32> TileReachBlocksGuard(
|
||||
VoxelGenLOD::TileReachBlocksPerAxis, ReachBlocksPerAxis);
|
||||
TGuardValue<int32> TileReachBlockIndexGuard(
|
||||
VoxelGenLOD::TileReachBlockIndex, INDEX_NONE);
|
||||
TGuardValue<bool> TileBlockReachValidGuard(
|
||||
VoxelGenLOD::bTileBlockReachValid, false);
|
||||
uint8 TilePostReachFlags = VoxelGenLOD::AllTilePostReach;
|
||||
if (Generator->StrateManager != nullptr)
|
||||
{
|
||||
@@ -297,8 +326,9 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
||||
static_cast<float>(static_cast<int64>(OriginVoxels.X) + Extent),
|
||||
static_cast<float>(static_cast<int64>(OriginVoxels.Y) + Extent),
|
||||
static_cast<float>(static_cast<int64>(OriginVoxels.Z) + Extent)));
|
||||
const float ReachScale = GVoxelForgeTilePostReachScale;
|
||||
TilePostReachFlags = 0;
|
||||
// Core stays conservative until the tile-window tunnel cache is built. Passage carving
|
||||
// is manager-owned and can be decided immediately from the same lattice+halo box.
|
||||
TilePostReachFlags = VoxelGenLOD::TunnelCoreReachable;
|
||||
if (Generator->StrateManager->AnyOriginLandingNearLattice(
|
||||
LatticeWithHalo, OriginVoxels, Step, ReachScale))
|
||||
{
|
||||
@@ -309,6 +339,11 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
||||
{
|
||||
TilePostReachFlags |= VoxelGenLOD::PassageLandingReachable;
|
||||
}
|
||||
if (Generator->StrateManager->AnyPassageCarvingNearLattice(
|
||||
LatticeWithHalo, OriginVoxels, Step, ReachScale))
|
||||
{
|
||||
TilePostReachFlags |= VoxelGenLOD::PassageCarvingReachable;
|
||||
}
|
||||
if (Generator->StrateManager->AnyPassageStructuralPostNearLattice(
|
||||
LatticeWithHalo, OriginVoxels, Step, ReachScale))
|
||||
{
|
||||
@@ -317,6 +352,30 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
||||
}
|
||||
TGuardValue<uint8> TilePostReachFlagsGuard(
|
||||
VoxelGenLOD::TilePostReachFlags, TilePostReachFlags);
|
||||
for (int32 BlockZ = 0; BlockZ < ReachBlocksPerAxis; ++BlockZ)
|
||||
{
|
||||
for (int32 BlockY = 0; BlockY < ReachBlocksPerAxis; ++BlockY)
|
||||
{
|
||||
for (int32 BlockX = 0; BlockX < ReachBlocksPerAxis; ++BlockX)
|
||||
{
|
||||
const int32 BlockIndex =
|
||||
((BlockZ * ReachBlocksPerAxis) + BlockY) * ReachBlocksPerAxis + BlockX;
|
||||
uint8 BlockFlags = TilePostReachFlags;
|
||||
if (Generator->StrateManager != nullptr
|
||||
&& (TilePostReachFlags & VoxelGenLOD::PassageCarvingReachable) != 0
|
||||
&& !Generator->StrateManager->AnyPassageCarvingNearLattice(
|
||||
VoxelGenLOD::MakeTileReachBlockBox(
|
||||
OriginVoxels, Step, CellsPerAxis,
|
||||
BlockX, BlockY, BlockZ),
|
||||
OriginVoxels, Step, ReachScale))
|
||||
{
|
||||
BlockFlags &= ~VoxelGenLOD::PassageCarvingReachable;
|
||||
}
|
||||
VoxelGenLOD::TileBlockReachFlags[BlockIndex] = BlockFlags;
|
||||
}
|
||||
}
|
||||
}
|
||||
VoxelGenLOD::bTileBlockReachValid = true;
|
||||
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).
|
||||
@@ -660,6 +719,15 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
||||
else
|
||||
{
|
||||
// World voxel = tile origin + grid offset scaled by the cell size (Step).
|
||||
const int32 ReachBlockX = FMath::Clamp(gx, 0, CellsPerAxis - 1)
|
||||
/ VoxelGenLOD::TileReachBlockCells;
|
||||
const int32 ReachBlockY = FMath::Clamp(gy, 0, CellsPerAxis - 1)
|
||||
/ VoxelGenLOD::TileReachBlockCells;
|
||||
const int32 ReachBlockZ = FMath::Clamp(gz, 0, CellsPerAxis - 1)
|
||||
/ VoxelGenLOD::TileReachBlockCells;
|
||||
VoxelGenLOD::TileReachBlockIndex =
|
||||
((ReachBlockZ * ReachBlocksPerAxis) + ReachBlockY)
|
||||
* ReachBlocksPerAxis + ReachBlockX;
|
||||
DensityGrid[LocalIndex] = Generator->GetDensityAt(
|
||||
OriginVoxels.X + gx * Step,
|
||||
OriginVoxels.Y + gy * Step,
|
||||
|
||||
@@ -2506,10 +2506,21 @@ void UVoxelStrateManager::GeneratePassages()
|
||||
|
||||
float UVoxelStrateManager::EvaluateModifierSDF(float WorldX, float WorldY, float WorldZ) const
|
||||
{
|
||||
VoxelGenLOD::FScopedReachCost ReachCost(
|
||||
VoxelGenLOD::ETileReachCostKind::PassageCarving);
|
||||
if (VoxelDensityAblation::IsPassageCarvingOff())
|
||||
{
|
||||
return FLT_MAX;
|
||||
}
|
||||
// GenerateMesh installs this once per tile/block. Outside the mesher the TLS defaults to
|
||||
// the canonical all-reachable state, and debug canonical calls set the bypass bit explicitly.
|
||||
// A miss is an identity for VF_ApplyPassageCarving, so do not even enter the spatial shortlist
|
||||
// or capsule chain on a proven-far sample.
|
||||
if (!VoxelGenLOD::IsTilePostReachBypassActive()
|
||||
&& !VoxelGenLOD::IsPassageCarvingReachable())
|
||||
{
|
||||
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
|
||||
@@ -5096,6 +5107,224 @@ bool UVoxelStrateManager::AnyPassageLandingNearLattice(
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UVoxelStrateManager::AnyPassageCarvingNearLattice(
|
||||
const FBox& VoxelBox, const FIntVector& LatticeOrigin, int32 Step,
|
||||
float ReachScale) const
|
||||
{
|
||||
if (VoxelDensityAblation::IsPassageCarvingOff())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (Step <= 0 || !VoxelBox.IsValid
|
||||
|| !VoxelMath::IsFinite(ReachScale) || ReachScale <= 0.0f)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// BoundRadius is built around every capsule control point and both landing rooms. The
|
||||
// construction already adds the ModSDF<4 carve band. SmoothMin can lower a chain of
|
||||
// otherwise-far finite terms by at most K/6 per combine, so include that finite morphology
|
||||
// loss as well. A miss of this sphere proves every capsule/landing term folds to the hard
|
||||
// no-op band before VF_ApplyPassageCarving; malformed descriptors remain candidates.
|
||||
int32 SmoothMinCombines = 0;
|
||||
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 Index = 0; Index < Passage.ControlPoints.Num(); ++Index)
|
||||
{
|
||||
const FVector& Point = Passage.ControlPoints[Index];
|
||||
if (!VoxelMath::IsFinite(Point.X) || !VoxelMath::IsFinite(Point.Y)
|
||||
|| !VoxelMath::IsFinite(Point.Z)
|
||||
|| (bTaper && !VoxelMath::IsFinite(Passage.ControlRadii[Index])))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
SmoothMinCombines += FMath::Max(Passage.ControlPoints.Num() - 2, 0);
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
if (Passage.bNativeFloorEnabled)
|
||||
{
|
||||
++SmoothMinCombines; // SmoothMax is the same K/6 polynomial bound.
|
||||
}
|
||||
SmoothMinCombines += 3; // passage union plus its two landing terms
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
constexpr float CarveThreshold = 4.0f;
|
||||
constexpr float BlendK = 3.0f;
|
||||
const float MorphologyPad = CarveThreshold
|
||||
+ BlendK * static_cast<float>(FMath::Max(SmoothMinCombines, 0)) / 6.0f;
|
||||
if (!VoxelMath::IsFinite(MorphologyPad) || MorphologyPad < 0.0f)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
for (const FVoxelPassage& Passage : Passages)
|
||||
{
|
||||
bool bHasTightGeometry = false;
|
||||
bool bTightTouch = false;
|
||||
const float PassagePad = MorphologyPad * ReachScale;
|
||||
if (!VoxelMath::IsFinite(PassagePad) || PassagePad < 0.0f)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (Passage.ControlPoints.Num() >= 2)
|
||||
{
|
||||
bHasTightGeometry = Passage.ControlPoints.Num() <= 4096;
|
||||
const bool bTaper = Passage.ControlRadii.Num() == Passage.ControlPoints.Num();
|
||||
const float MaxRadius = bTaper
|
||||
? [&Passage]()
|
||||
{
|
||||
float Result = 0.0f;
|
||||
for (const float Radius : Passage.ControlRadii)
|
||||
{
|
||||
Result = FMath::Max(Result, FMath::Abs(Radius));
|
||||
}
|
||||
return Result;
|
||||
}()
|
||||
: FMath::Abs(Passage.Radius);
|
||||
if (!VoxelMath::IsFinite(MaxRadius))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
const float SegmentPad = (MaxRadius
|
||||
+ (Passage.bNativeFloorEnabled
|
||||
? VoxelPassageGeometry::LandingFloorThicknessVoxels : 0.0f)
|
||||
+ PassagePad);
|
||||
if (!VoxelMath::IsFinite(SegmentPad))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
for (int32 SegmentIndex = 0;
|
||||
bHasTightGeometry && SegmentIndex + 1 < Passage.ControlPoints.Num();
|
||||
++SegmentIndex)
|
||||
{
|
||||
const FVector& A = Passage.ControlPoints[SegmentIndex];
|
||||
const FVector& B = Passage.ControlPoints[SegmentIndex + 1];
|
||||
if (VF_LatticeBoxTouchesAABB(
|
||||
VoxelBox, LatticeOrigin, Step,
|
||||
FMath::Min(A.X, B.X) - SegmentPad,
|
||||
FMath::Max(A.X, B.X) + SegmentPad,
|
||||
FMath::Min(A.Y, B.Y) - SegmentPad,
|
||||
FMath::Max(A.Y, B.Y) + SegmentPad,
|
||||
FMath::Min(A.Z, B.Z) - SegmentPad,
|
||||
FMath::Max(A.Z, B.Z) + SegmentPad))
|
||||
{
|
||||
bTightTouch = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
bHasTightGeometry = true;
|
||||
const float SegmentPad = FMath::Abs(Passage.Radius)
|
||||
+ (Passage.bNativeFloorEnabled
|
||||
? VoxelPassageGeometry::LandingFloorThicknessVoxels : 0.0f)
|
||||
+ PassagePad;
|
||||
if (!VoxelMath::IsFinite(SegmentPad))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (VF_LatticeBoxTouchesAABB(
|
||||
VoxelBox, LatticeOrigin, Step,
|
||||
FMath::Min(Passage.UpperPoint.X, Passage.LowerPoint.X) - SegmentPad,
|
||||
FMath::Max(Passage.UpperPoint.X, Passage.LowerPoint.X) + SegmentPad,
|
||||
FMath::Min(Passage.UpperPoint.Y, Passage.LowerPoint.Y) - SegmentPad,
|
||||
FMath::Max(Passage.UpperPoint.Y, Passage.LowerPoint.Y) + SegmentPad,
|
||||
FMath::Min(Passage.UpperPoint.Z, Passage.LowerPoint.Z) - SegmentPad,
|
||||
FMath::Max(Passage.UpperPoint.Z, Passage.LowerPoint.Z) + SegmentPad))
|
||||
{
|
||||
bTightTouch = true;
|
||||
}
|
||||
}
|
||||
|
||||
const float RoomPad = PassagePad;
|
||||
for (const FVoxelPassageLanding* Landing : {
|
||||
&Passage.UpperLanding, &Passage.LowerLanding })
|
||||
{
|
||||
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))
|
||||
{
|
||||
bTightTouch = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (bHasTightGeometry && !bTightTouch)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const float Reach = (Passage.BoundRadius + MorphologyPad) * ReachScale;
|
||||
if (!VoxelMath::IsFinite(Reach)
|
||||
|| VF_LatticeBoxTouchesSphere(VoxelBox, Passage.BoundCenter, Reach))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UVoxelStrateManager::AnyPassageStructuralPostNearLattice(
|
||||
const FBox& VoxelBox, const FIntVector& LatticeOrigin, int32 Step,
|
||||
float ReachScale) const
|
||||
|
||||
@@ -1162,7 +1162,7 @@ void AVoxelWorld::EndPlay(const EEndPlayReason::Type EndPlayReason)
|
||||
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"),
|
||||
"final_field=%llu differences=%llu/%llu/%llu/%llu/%llu/%llu/%llu/%llu/%llu/%llu"),
|
||||
static_cast<unsigned long long>(VoxelGenLOD::GSkippedPostComparisonsByKind[
|
||||
static_cast<uint8>(VoxelGenLOD::ETilePostComparisonKind::OriginAir)].load(
|
||||
std::memory_order_relaxed)),
|
||||
@@ -1190,6 +1190,9 @@ void AVoxelWorld::EndPlay(const EEndPlayReason::Type EndPlayReason)
|
||||
static_cast<unsigned long long>(VoxelGenLOD::GSkippedPostComparisonsByKind[
|
||||
static_cast<uint8>(VoxelGenLOD::ETilePostComparisonKind::PassageLandingRoomFloor)].load(
|
||||
std::memory_order_relaxed)),
|
||||
static_cast<unsigned long long>(VoxelGenLOD::GSkippedPostComparisonsByKind[
|
||||
static_cast<uint8>(VoxelGenLOD::ETilePostComparisonKind::FinalField)].load(
|
||||
std::memory_order_relaxed)),
|
||||
static_cast<unsigned long long>(VoxelGenLOD::GSkippedPostDifferencesByKind[
|
||||
static_cast<uint8>(VoxelGenLOD::ETilePostComparisonKind::OriginAir)].load(
|
||||
std::memory_order_relaxed)),
|
||||
@@ -1216,6 +1219,106 @@ void AVoxelWorld::EndPlay(const EEndPlayReason::Type EndPlayReason)
|
||||
std::memory_order_relaxed)),
|
||||
static_cast<unsigned long long>(VoxelGenLOD::GSkippedPostDifferencesByKind[
|
||||
static_cast<uint8>(VoxelGenLOD::ETilePostComparisonKind::PassageLandingRoomFloor)].load(
|
||||
std::memory_order_relaxed)),
|
||||
static_cast<unsigned long long>(VoxelGenLOD::GSkippedPostDifferencesByKind[
|
||||
static_cast<uint8>(VoxelGenLOD::ETilePostComparisonKind::FinalField)].load(
|
||||
std::memory_order_relaxed)));
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeTileReachDecisions] core_reachable=%llu core_skipped=%llu "
|
||||
"passage_carving_reachable=%llu passage_carving_skipped=%llu"),
|
||||
static_cast<unsigned long long>(VoxelGenLOD::GTunnelCoreReachableTiles.load(
|
||||
std::memory_order_relaxed)),
|
||||
static_cast<unsigned long long>(VoxelGenLOD::GTunnelCoreSkippedTiles.load(
|
||||
std::memory_order_relaxed)),
|
||||
static_cast<unsigned long long>(VoxelGenLOD::GPassageCarvingReachableTiles.load(
|
||||
std::memory_order_relaxed)),
|
||||
static_cast<unsigned long long>(VoxelGenLOD::GPassageCarvingSkippedTiles.load(
|
||||
std::memory_order_relaxed)));
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeTileReachCost] core_world_far_calls=%llu far_cycles=%llu "
|
||||
"near_calls=%llu near_cycles=%llu"),
|
||||
static_cast<unsigned long long>(VoxelGenLOD::GTileReachCostCalls[
|
||||
static_cast<uint8>(VoxelGenLOD::ETileReachCostKind::TunnelCoreWorld)][0].load(
|
||||
std::memory_order_relaxed)),
|
||||
static_cast<unsigned long long>(VoxelGenLOD::GTileReachCostCycles[
|
||||
static_cast<uint8>(VoxelGenLOD::ETileReachCostKind::TunnelCoreWorld)][0].load(
|
||||
std::memory_order_relaxed)),
|
||||
static_cast<unsigned long long>(VoxelGenLOD::GTileReachCostCalls[
|
||||
static_cast<uint8>(VoxelGenLOD::ETileReachCostKind::TunnelCoreWorld)][1].load(
|
||||
std::memory_order_relaxed)),
|
||||
static_cast<unsigned long long>(VoxelGenLOD::GTileReachCostCycles[
|
||||
static_cast<uint8>(VoxelGenLOD::ETileReachCostKind::TunnelCoreWorld)][1].load(
|
||||
std::memory_order_relaxed)));
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeTileReachCost] core_tail_far_calls=%llu far_cycles=%llu "
|
||||
"near_calls=%llu near_cycles=%llu"),
|
||||
static_cast<unsigned long long>(VoxelGenLOD::GTileReachCostCalls[
|
||||
static_cast<uint8>(VoxelGenLOD::ETileReachCostKind::TunnelCoreTail)][0].load(
|
||||
std::memory_order_relaxed)),
|
||||
static_cast<unsigned long long>(VoxelGenLOD::GTileReachCostCycles[
|
||||
static_cast<uint8>(VoxelGenLOD::ETileReachCostKind::TunnelCoreTail)][0].load(
|
||||
std::memory_order_relaxed)),
|
||||
static_cast<unsigned long long>(VoxelGenLOD::GTileReachCostCalls[
|
||||
static_cast<uint8>(VoxelGenLOD::ETileReachCostKind::TunnelCoreTail)][1].load(
|
||||
std::memory_order_relaxed)),
|
||||
static_cast<unsigned long long>(VoxelGenLOD::GTileReachCostCycles[
|
||||
static_cast<uint8>(VoxelGenLOD::ETileReachCostKind::TunnelCoreTail)][1].load(
|
||||
std::memory_order_relaxed)));
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeTileReachCost] passage_carving_far_calls=%llu far_cycles=%llu "
|
||||
"near_calls=%llu near_cycles=%llu"),
|
||||
static_cast<unsigned long long>(VoxelGenLOD::GTileReachCostCalls[
|
||||
static_cast<uint8>(VoxelGenLOD::ETileReachCostKind::PassageCarving)][0].load(
|
||||
std::memory_order_relaxed)),
|
||||
static_cast<unsigned long long>(VoxelGenLOD::GTileReachCostCycles[
|
||||
static_cast<uint8>(VoxelGenLOD::ETileReachCostKind::PassageCarving)][0].load(
|
||||
std::memory_order_relaxed)),
|
||||
static_cast<unsigned long long>(VoxelGenLOD::GTileReachCostCalls[
|
||||
static_cast<uint8>(VoxelGenLOD::ETileReachCostKind::PassageCarving)][1].load(
|
||||
std::memory_order_relaxed)),
|
||||
static_cast<unsigned long long>(VoxelGenLOD::GTileReachCostCycles[
|
||||
static_cast<uint8>(VoxelGenLOD::ETileReachCostKind::PassageCarving)][1].load(
|
||||
std::memory_order_relaxed)));
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeTileBlockReachCost] core_world_far_calls=%llu far_cycles=%llu "
|
||||
"near_calls=%llu near_cycles=%llu; core_tail_far_calls=%llu far_cycles=%llu "
|
||||
"near_calls=%llu near_cycles=%llu; passage_far_calls=%llu far_cycles=%llu "
|
||||
"near_calls=%llu near_cycles=%llu"),
|
||||
static_cast<unsigned long long>(VoxelGenLOD::GTileReachBlockCostCalls[
|
||||
static_cast<uint8>(VoxelGenLOD::ETileReachCostKind::TunnelCoreWorld)][0].load(
|
||||
std::memory_order_relaxed)),
|
||||
static_cast<unsigned long long>(VoxelGenLOD::GTileReachBlockCostCycles[
|
||||
static_cast<uint8>(VoxelGenLOD::ETileReachCostKind::TunnelCoreWorld)][0].load(
|
||||
std::memory_order_relaxed)),
|
||||
static_cast<unsigned long long>(VoxelGenLOD::GTileReachBlockCostCalls[
|
||||
static_cast<uint8>(VoxelGenLOD::ETileReachCostKind::TunnelCoreWorld)][1].load(
|
||||
std::memory_order_relaxed)),
|
||||
static_cast<unsigned long long>(VoxelGenLOD::GTileReachBlockCostCycles[
|
||||
static_cast<uint8>(VoxelGenLOD::ETileReachCostKind::TunnelCoreWorld)][1].load(
|
||||
std::memory_order_relaxed)),
|
||||
static_cast<unsigned long long>(VoxelGenLOD::GTileReachBlockCostCalls[
|
||||
static_cast<uint8>(VoxelGenLOD::ETileReachCostKind::TunnelCoreTail)][0].load(
|
||||
std::memory_order_relaxed)),
|
||||
static_cast<unsigned long long>(VoxelGenLOD::GTileReachBlockCostCycles[
|
||||
static_cast<uint8>(VoxelGenLOD::ETileReachCostKind::TunnelCoreTail)][0].load(
|
||||
std::memory_order_relaxed)),
|
||||
static_cast<unsigned long long>(VoxelGenLOD::GTileReachBlockCostCalls[
|
||||
static_cast<uint8>(VoxelGenLOD::ETileReachCostKind::TunnelCoreTail)][1].load(
|
||||
std::memory_order_relaxed)),
|
||||
static_cast<unsigned long long>(VoxelGenLOD::GTileReachBlockCostCycles[
|
||||
static_cast<uint8>(VoxelGenLOD::ETileReachCostKind::TunnelCoreTail)][1].load(
|
||||
std::memory_order_relaxed)),
|
||||
static_cast<unsigned long long>(VoxelGenLOD::GTileReachBlockCostCalls[
|
||||
static_cast<uint8>(VoxelGenLOD::ETileReachCostKind::PassageCarving)][0].load(
|
||||
std::memory_order_relaxed)),
|
||||
static_cast<unsigned long long>(VoxelGenLOD::GTileReachBlockCostCycles[
|
||||
static_cast<uint8>(VoxelGenLOD::ETileReachCostKind::PassageCarving)][0].load(
|
||||
std::memory_order_relaxed)),
|
||||
static_cast<unsigned long long>(VoxelGenLOD::GTileReachBlockCostCalls[
|
||||
static_cast<uint8>(VoxelGenLOD::ETileReachCostKind::PassageCarving)][1].load(
|
||||
std::memory_order_relaxed)),
|
||||
static_cast<unsigned long long>(VoxelGenLOD::GTileReachBlockCostCycles[
|
||||
static_cast<uint8>(VoxelGenLOD::ETileReachCostKind::PassageCarving)][1].load(
|
||||
std::memory_order_relaxed)));
|
||||
if (VoxelGenLOD::GTilePostReachDebugEnabled.load(std::memory_order_relaxed)
|
||||
&& TilePostReachDifferences != 0)
|
||||
|
||||
@@ -1081,6 +1081,14 @@ namespace VoxelCaveMorphology
|
||||
bool bUseSpatialIndex = true
|
||||
);
|
||||
|
||||
// Per-tile broad reach for the world-space core/floor post. The bound includes the swept
|
||||
// chain, floor band, relief envelope, and the finite room-mouth ownership apron.
|
||||
VOXELFORGE_API bool AnyTunnelCoreWorldNearLattice(
|
||||
const FChunkSDFCache& Cache,
|
||||
const FBox& VoxelBox,
|
||||
float ReachScale = 1.0f
|
||||
);
|
||||
|
||||
// True for the finite support slab beneath a world-space graph tunnel. The native density
|
||||
// path and the operator stack use this same predicate before reopening the air core, keeping
|
||||
// the rounded capsule's bottom from becoming an unsupported point.
|
||||
|
||||
@@ -476,6 +476,10 @@ public:
|
||||
bool AnyPassageLandingNearLattice(const FBox& VoxelBox,
|
||||
const FIntVector& LatticeOrigin, int32 Step,
|
||||
float ReachScale = 1.0f) const;
|
||||
/** Per-tile reach for the modifier itself: capsule chain, landing terms, and carve blend. */
|
||||
bool AnyPassageCarvingNearLattice(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,
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "HAL/PlatformTime.h"
|
||||
|
||||
#include <atomic>
|
||||
|
||||
@@ -17,9 +18,14 @@ namespace VoxelGenLOD
|
||||
OriginLandingReachable = 1u << 0,
|
||||
PassageLandingReachable = 1u << 1,
|
||||
PassageStructuralReachable= 1u << 2,
|
||||
TunnelCoreReachable = 1u << 3,
|
||||
PassageCarvingReachable = 1u << 4,
|
||||
AllTilePostReach = OriginLandingReachable
|
||||
| PassageLandingReachable
|
||||
| PassageStructuralReachable,
|
||||
| PassageStructuralReachable
|
||||
| TunnelCoreReachable
|
||||
| PassageCarvingReachable,
|
||||
AllTileReach = AllTilePostReach,
|
||||
};
|
||||
|
||||
enum class ETilePostComparisonKind : uint8
|
||||
@@ -34,14 +40,65 @@ namespace VoxelGenLOD
|
||||
PassageStructuralPosts,
|
||||
PassageNativeFloor,
|
||||
PassageLandingRoomFloor,
|
||||
TunnelCore,
|
||||
PassageCarving,
|
||||
FinalField,
|
||||
Count,
|
||||
};
|
||||
|
||||
enum class ETileReachCostKind : uint8
|
||||
{
|
||||
TunnelCoreWorld = 0,
|
||||
TunnelCoreTail,
|
||||
PassageCarving,
|
||||
Count,
|
||||
};
|
||||
|
||||
constexpr int32 TileReachBlockCells = 8;
|
||||
constexpr int32 TileReachMaxBlocksPerAxis = 4;
|
||||
constexpr int32 TileReachMaxBlockCount =
|
||||
TileReachMaxBlocksPerAxis * TileReachMaxBlocksPerAxis * TileReachMaxBlocksPerAxis;
|
||||
|
||||
FORCEINLINE FBox MakeTileReachBlockBox(
|
||||
const FIntVector& TileOrigin, int32 Step, int32 CellsPerAxis,
|
||||
int32 BlockX, int32 BlockY, int32 BlockZ)
|
||||
{
|
||||
const int32 SafeStep = FMath::Max(Step, 1);
|
||||
const int32 MinX = BlockX * TileReachBlockCells;
|
||||
const int32 MinY = BlockY * TileReachBlockCells;
|
||||
const int32 MinZ = BlockZ * TileReachBlockCells;
|
||||
const int32 MaxX = FMath::Min(
|
||||
CellsPerAxis, MinX + TileReachBlockCells);
|
||||
const int32 MaxY = FMath::Min(
|
||||
CellsPerAxis, MinY + TileReachBlockCells);
|
||||
const int32 MaxZ = FMath::Min(
|
||||
CellsPerAxis, MinZ + TileReachBlockCells);
|
||||
return FBox(
|
||||
FVector(
|
||||
static_cast<float>(static_cast<int64>(TileOrigin.X) + MinX * SafeStep - SafeStep),
|
||||
static_cast<float>(static_cast<int64>(TileOrigin.Y) + MinY * SafeStep - SafeStep),
|
||||
static_cast<float>(static_cast<int64>(TileOrigin.Z) + MinZ * SafeStep - SafeStep)),
|
||||
FVector(
|
||||
static_cast<float>(static_cast<int64>(TileOrigin.X) + MaxX * SafeStep),
|
||||
static_cast<float>(static_cast<int64>(TileOrigin.Y) + MaxY * SafeStep),
|
||||
static_cast<float>(static_cast<int64>(TileOrigin.Z) + MaxZ * SafeStep)));
|
||||
}
|
||||
|
||||
// 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 uint8 TilePostReachFlags = AllTileReach;
|
||||
inline thread_local bool bTilePostReachDebug = false;
|
||||
inline thread_local bool bTilePostReachBypass = false;
|
||||
inline thread_local bool bTileReachDiagnosticTile = false;
|
||||
inline thread_local bool bTileCoreReachDecisionRecorded = false;
|
||||
inline thread_local float TileReachScale = 1.0f;
|
||||
inline thread_local int32 TileReachBlocksPerAxis = 0;
|
||||
inline thread_local int32 TileReachBlockIndex = INDEX_NONE;
|
||||
inline thread_local bool bTileBlockReachValid = false;
|
||||
inline thread_local uint8 TileBlockReachFlags[TileReachMaxBlockCount]{};
|
||||
// A false result is installed only after a cache was built with the whole mesher tile as its
|
||||
// graph window. Chunk-local caches leave this true (the conservative no-skip fallback).
|
||||
inline thread_local bool bTileCoreReachProofEnabled = false;
|
||||
|
||||
// These are diagnostics only. They never participate in a field decision.
|
||||
inline std::atomic<bool> GTilePostReachDebugEnabled { false };
|
||||
@@ -52,12 +109,25 @@ namespace VoxelGenLOD
|
||||
inline std::atomic<uint64> GOriginLandingSkippedTiles { 0 };
|
||||
inline std::atomic<uint64> GPassageLandingSkippedTiles { 0 };
|
||||
inline std::atomic<uint64> GPassageStructuralSkippedTiles { 0 };
|
||||
inline std::atomic<uint64> GTunnelCoreReachableTiles { 0 };
|
||||
inline std::atomic<uint64> GTunnelCoreSkippedTiles { 0 };
|
||||
inline std::atomic<uint64> GPassageCarvingReachableTiles { 0 };
|
||||
inline std::atomic<uint64> GPassageCarvingSkippedTiles { 0 };
|
||||
inline std::atomic<uint64> GSkippedPostComparisons { 0 };
|
||||
inline std::atomic<uint64> GSkippedPostDifferences { 0 };
|
||||
inline std::atomic<uint64> GSkippedPostComparisonsByKind[
|
||||
static_cast<uint8>(ETilePostComparisonKind::Count)]{};
|
||||
inline std::atomic<uint64> GSkippedPostDifferencesByKind[
|
||||
static_cast<uint8>(ETilePostComparisonKind::Count)]{};
|
||||
inline std::atomic<bool> GTileReachCostDiagnosticsEnabled { false };
|
||||
inline std::atomic<uint64> GTileReachCostCalls[
|
||||
static_cast<uint8>(ETileReachCostKind::Count)][2]{};
|
||||
inline std::atomic<uint64> GTileReachCostCycles[
|
||||
static_cast<uint8>(ETileReachCostKind::Count)][2]{};
|
||||
inline std::atomic<uint64> GTileReachBlockCostCalls[
|
||||
static_cast<uint8>(ETileReachCostKind::Count)][2]{};
|
||||
inline std::atomic<uint64> GTileReachBlockCostCycles[
|
||||
static_cast<uint8>(ETileReachCostKind::Count)][2]{};
|
||||
|
||||
FORCEINLINE bool IsTilePostReachable(uint8 Bit)
|
||||
{
|
||||
@@ -79,6 +149,25 @@ namespace VoxelGenLOD
|
||||
return IsTilePostReachable(PassageStructuralReachable);
|
||||
}
|
||||
|
||||
FORCEINLINE bool IsTunnelCoreReachable()
|
||||
{
|
||||
// Core's measured far share is negligible; keep its exact tile decision and avoid a
|
||||
// second, weaker block geometry approximation. Passage carving is the only block-gated
|
||||
// work because its tight chain produced a material far share in the pre-gate measure.
|
||||
return IsTilePostReachable(TunnelCoreReachable);
|
||||
}
|
||||
|
||||
FORCEINLINE bool IsPassageCarvingReachable()
|
||||
{
|
||||
if (bTileBlockReachValid
|
||||
&& TileReachBlockIndex >= 0
|
||||
&& TileReachBlockIndex < TileReachMaxBlockCount)
|
||||
{
|
||||
return (TileBlockReachFlags[TileReachBlockIndex] & PassageCarvingReachable) != 0;
|
||||
}
|
||||
return IsTilePostReachable(PassageCarvingReachable);
|
||||
}
|
||||
|
||||
FORCEINLINE bool IsTilePostReachBypassActive()
|
||||
{
|
||||
return bTilePostReachBypass;
|
||||
@@ -111,8 +200,122 @@ namespace VoxelGenLOD
|
||||
{
|
||||
GPassageStructuralSkippedTiles.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
if ((Flags & PassageCarvingReachable) != 0)
|
||||
{
|
||||
GPassageCarvingReachableTiles.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
else
|
||||
{
|
||||
GPassageCarvingSkippedTiles.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
FORCEINLINE void RecordTunnelCoreReachDecision(bool bReachable)
|
||||
{
|
||||
if (bTileCoreReachDecisionRecorded)
|
||||
{
|
||||
return;
|
||||
}
|
||||
bTileCoreReachDecisionRecorded = true;
|
||||
if (bReachable)
|
||||
{
|
||||
GTunnelCoreReachableTiles.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
else
|
||||
{
|
||||
GTunnelCoreSkippedTiles.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
FORCEINLINE uint8 TileReachCostIndex(ETileReachCostKind Kind)
|
||||
{
|
||||
switch (Kind)
|
||||
{
|
||||
case ETileReachCostKind::TunnelCoreWorld:
|
||||
case ETileReachCostKind::TunnelCoreTail:
|
||||
return IsTilePostReachable(TunnelCoreReachable) ? 1u : 0u;
|
||||
case ETileReachCostKind::PassageCarving:
|
||||
return IsTilePostReachable(PassageCarvingReachable) ? 1u : 0u;
|
||||
default:
|
||||
return 1u;
|
||||
}
|
||||
}
|
||||
|
||||
FORCEINLINE uint8 BlockReachCostIndex(ETileReachCostKind Kind)
|
||||
{
|
||||
switch (Kind)
|
||||
{
|
||||
case ETileReachCostKind::TunnelCoreWorld:
|
||||
case ETileReachCostKind::TunnelCoreTail:
|
||||
return IsTunnelCoreReachable() ? 1u : 0u;
|
||||
case ETileReachCostKind::PassageCarving:
|
||||
return IsPassageCarvingReachable() ? 1u : 0u;
|
||||
default:
|
||||
return 1u;
|
||||
}
|
||||
}
|
||||
|
||||
FORCEINLINE void RecordTileReachBlockCost(
|
||||
ETileReachCostKind Kind, uint8 ReachIndex, uint64 Cycles)
|
||||
{
|
||||
const uint8 KindIndex = static_cast<uint8>(Kind);
|
||||
if (KindIndex < static_cast<uint8>(ETileReachCostKind::Count))
|
||||
{
|
||||
GTileReachBlockCostCalls[KindIndex][ReachIndex].fetch_add(
|
||||
1, std::memory_order_relaxed);
|
||||
GTileReachBlockCostCycles[KindIndex][ReachIndex].fetch_add(
|
||||
Cycles, std::memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
/** Inclusive timing split used for the pre-gate near/far measurement only. */
|
||||
struct FScopedReachCost
|
||||
{
|
||||
ETileReachCostKind Kind;
|
||||
uint8 TileReachIndex = 1;
|
||||
uint8 BlockReachIndex = 1;
|
||||
uint64 StartCycles = 0;
|
||||
bool bActive = false;
|
||||
|
||||
explicit FScopedReachCost(ETileReachCostKind InKind)
|
||||
: Kind(InKind)
|
||||
{
|
||||
if (!bTileReachDiagnosticTile
|
||||
|| !GTileReachCostDiagnosticsEnabled.load(std::memory_order_relaxed))
|
||||
{
|
||||
return;
|
||||
}
|
||||
TileReachIndex = TileReachCostIndex(Kind);
|
||||
BlockReachIndex = BlockReachCostIndex(Kind);
|
||||
StartCycles = FPlatformTime::Cycles64();
|
||||
bActive = true;
|
||||
}
|
||||
|
||||
~FScopedReachCost()
|
||||
{
|
||||
if (!bActive)
|
||||
{
|
||||
return;
|
||||
}
|
||||
const uint8 KindIndex = static_cast<uint8>(Kind);
|
||||
if (KindIndex < static_cast<uint8>(ETileReachCostKind::Count))
|
||||
{
|
||||
const uint64 Cycles = FPlatformTime::Cycles64() - StartCycles;
|
||||
GTileReachCostCalls[KindIndex][TileReachIndex].fetch_add(
|
||||
1, std::memory_order_relaxed);
|
||||
GTileReachCostCycles[KindIndex][TileReachIndex].fetch_add(
|
||||
Cycles,
|
||||
std::memory_order_relaxed);
|
||||
if (bTileBlockReachValid
|
||||
&& TileReachBlockIndex >= 0
|
||||
&& TileReachBlockIndex < TileReachMaxBlockCount)
|
||||
{
|
||||
RecordTileReachBlockCost(Kind, BlockReachIndex, Cycles);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
FORCEINLINE bool SameFloatBits(float A, float B)
|
||||
{
|
||||
uint32 ABits = 0;
|
||||
|
||||
Reference in new issue
Block a user