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
+1060
-28
No files matched your search
@@ -7914,10 +7914,20 @@ FTunnelCoreWorldEvaluation VoxelCaveMorphology::EvaluateTunnelCoreWorld(
|
|||||||
const FTunnelSupportFloorColumn* SupportColumn,
|
const FTunnelSupportFloorColumn* SupportColumn,
|
||||||
bool bUseSpatialIndex)
|
bool bUseSpatialIndex)
|
||||||
{
|
{
|
||||||
|
VoxelGenLOD::FScopedReachCost ReachCost(
|
||||||
|
VoxelGenLOD::ETileReachCostKind::TunnelCoreWorld);
|
||||||
if (VoxelDensityAblation::IsTunnelCoreOff())
|
if (VoxelDensityAblation::IsTunnelCoreOff())
|
||||||
{
|
{
|
||||||
return FTunnelCoreWorldEvaluation();
|
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::FScopedTimer ProfileTimer(
|
||||||
VoxelDensityProfile::EBucket::TunnelCoreWorld);
|
VoxelDensityProfile::EBucket::TunnelCoreWorld);
|
||||||
const FVector Pos(WorldX, WorldY, WorldZ);
|
const FVector Pos(WorldX, WorldY, WorldZ);
|
||||||
@@ -8042,6 +8052,318 @@ FTunnelCoreWorldEvaluation VoxelCaveMorphology::EvaluateTunnelCoreWorld(
|
|||||||
return Result;
|
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 VoxelCaveMorphology::EvaluateTunnelCoreWorldSDF(
|
||||||
float WorldX, float WorldY, float WorldZ,
|
float WorldX, float WorldY, float WorldZ,
|
||||||
const FChunkSDFCache& Cache,
|
const FChunkSDFCache& Cache,
|
||||||
|
|||||||
@@ -2370,6 +2370,49 @@ namespace
|
|||||||
return static_cast<int32>(Hash & (TunnelDensityCacheSlotCount - 1));
|
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(
|
void PrepareTunnelCoreCache(
|
||||||
const UVoxelStrateManager& Manager,
|
const UVoxelStrateManager& Manager,
|
||||||
const FIntVector& ChunkCoord,
|
const FIntVector& ChunkCoord,
|
||||||
@@ -2398,6 +2441,14 @@ namespace
|
|||||||
* static_cast<float>(CHUNK_SIZE) * VOXEL_SIZE);
|
* static_cast<float>(CHUNK_SIZE) * VOXEL_SIZE);
|
||||||
const uint32 WorldSeed = static_cast<uint32>(Manager.GetWorldSeed());
|
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
|
const bool bSameGraphWindow = OutState.bValid
|
||||||
&& OutState.OwnerId == OwnerId
|
&& OutState.OwnerId == OwnerId
|
||||||
&& OutState.ManagerLifetimeId == ManagerLifetimeId
|
&& OutState.ManagerLifetimeId == ManagerLifetimeId
|
||||||
@@ -2457,6 +2508,7 @@ namespace
|
|||||||
Params, WorldSeed,
|
Params, WorldSeed,
|
||||||
StrateIndex, nullptr,
|
StrateIndex, nullptr,
|
||||||
ERoomGraphBuildSite::GeneratorTunnelCore);
|
ERoomGraphBuildSite::GeneratorTunnelCore);
|
||||||
|
UpdateTunnelCoreTileReach(OutState.Cache, bUseTileCacheWindow);
|
||||||
BuildTunnelSupportColumnsForChunk(
|
BuildTunnelSupportColumnsForChunk(
|
||||||
OutState.Cache, ChunkCoord, bUseSpatialIndex);
|
OutState.Cache, ChunkCoord, bUseSpatialIndex);
|
||||||
OutState.OwnerId = OwnerId;
|
OutState.OwnerId = OwnerId;
|
||||||
@@ -2723,6 +2775,24 @@ void UVoxelGenerator::EndDensityBlock() const
|
|||||||
|
|
||||||
float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) 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);
|
TRACE_CPUPROFILER_EVENT_SCOPE(VoxelForge_GetDensityAt);
|
||||||
VF_ParseFastIsFiniteSwitch();
|
VF_ParseFastIsFiniteSwitch();
|
||||||
VF_ParseFusedEvaluatorSwitch();
|
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
|
// 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,
|
// solid floor writer but before the global XY seal. This cache is built once per chunk,
|
||||||
// never once per voxel.
|
// 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()
|
if (!VoxelDensityAblation::IsTunnelCoreOff()
|
||||||
&& (ActiveTunnelCoreCache->bValid || CP_UseOpStack))
|
&& (ActiveTunnelCoreCache->bValid || CP_UseOpStack))
|
||||||
{
|
{
|
||||||
FTunnelCoreWorldEvaluation TunnelCore;
|
FTunnelCoreWorldEvaluation TunnelCore;
|
||||||
bool bHaveTunnelCore = bHavePreDisturbanceTunnelCore;
|
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)
|
if (bHaveTunnelCore)
|
||||||
{
|
{
|
||||||
TunnelCore = PreDisturbanceTunnelCore;
|
TunnelCore = PreDisturbanceTunnelCore;
|
||||||
}
|
}
|
||||||
if (!bHaveTunnelCore && ActiveTunnelCoreCache->bValid)
|
if (!bHaveTunnelCore && ActiveTunnelCoreCache->bValid
|
||||||
|
&& (bTunnelCoreReachable || bTunnelCoreBypass))
|
||||||
{
|
{
|
||||||
const FTunnelSupportFloorColumn* SupportColumn = nullptr;
|
const FTunnelSupportFloorColumn* SupportColumn = nullptr;
|
||||||
FTunnelSupportFloorColumn EmptySupportColumn;
|
FTunnelSupportFloorColumn EmptySupportColumn;
|
||||||
@@ -3631,30 +3737,10 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co
|
|||||||
FMath::Max(LandingBaseDensity * 2.0f, 1.0f));
|
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);
|
|
||||||
}
|
}
|
||||||
|
if (bHaveTunnelCore || (!bTunnelCoreReachable && !bTunnelCoreBypass))
|
||||||
{
|
{
|
||||||
VoxelDensityProfile::FScopedTimer ProfileTimer(
|
ApplyGraphLandingFloors();
|
||||||
VoxelDensityProfile::EBucket::PassageLandingFloor);
|
|
||||||
StrateManager->ApplyPassageLandingFloorMC(
|
|
||||||
Result, WorldX, WorldY, WorldZ, LandingBaseDensity);
|
|
||||||
}
|
|
||||||
{
|
|
||||||
VoxelDensityProfile::FScopedTimer ProfileTimer(
|
|
||||||
VoxelDensityProfile::EBucket::PassageLandingRoomFloor);
|
|
||||||
StrateManager->ApplyPassageLandingRoomFloorMC(
|
|
||||||
Result, WorldX, WorldY, WorldZ, LandingBaseDensity);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Inter-strate passages own the same final D-floor contract as graph tunnels. This is
|
// 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::FScopedTimer DensityTailTimer(
|
||||||
VoxelDensityProfile::EBucket::DensityTail);
|
VoxelDensityProfile::EBucket::DensityTail);
|
||||||
VoxelPassageGeometry::ResetVerticalShaftConnectorAirMarker();
|
VoxelPassageGeometry::ResetVerticalShaftConnectorAirMarker();
|
||||||
|
if (bCompareFinalField)
|
||||||
|
{
|
||||||
|
const bool bFinalFieldDifferent =
|
||||||
|
!VoxelGenLOD::SameFloatBits(Result, CanonicalFinalField);
|
||||||
|
VoxelGenLOD::RecordSkippedPostComparison(
|
||||||
|
VoxelGenLOD::ETilePostComparisonKind::FinalField,
|
||||||
|
bFinalFieldDifferent);
|
||||||
|
}
|
||||||
return Result;
|
return Result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4089,6 +4183,7 @@ float UVoxelGenerator::GetDensityWithParams(float WorldX, float WorldY, float Wo
|
|||||||
TerrainOps,
|
TerrainOps,
|
||||||
ERoomGraphBuildSite::GeneratorTile
|
ERoomGraphBuildSite::GeneratorTile
|
||||||
);
|
);
|
||||||
|
UpdateTunnelCoreTileReach(SDFCache, bUseTileCacheWindow);
|
||||||
|
|
||||||
CachedSMinX = SMinX; CachedSMaxX = SMaxX;
|
CachedSMinX = SMinX; CachedSMaxX = SMaxX;
|
||||||
CachedSMinY = SMinY; CachedSMaxY = SMaxY;
|
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; "
|
TEXT("Scale the per-tile post reach for deliberate soundness tests. Default 1.0 is exact; "
|
||||||
"values below 1 intentionally shrink the guard."));
|
"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;
|
bool GVoxelForgeTilePostReachSwitchesParsed = false;
|
||||||
|
|
||||||
void VF_ParseTilePostReachSwitches()
|
void VF_ParseTilePostReachSwitches()
|
||||||
@@ -44,18 +50,24 @@ namespace
|
|||||||
GVoxelForgeTilePostReachSwitchesParsed = true;
|
GVoxelForgeTilePostReachSwitchesParsed = true;
|
||||||
int32 DebugValue = GVoxelForgeTilePostReachDebug;
|
int32 DebugValue = GVoxelForgeTilePostReachDebug;
|
||||||
float ScaleValue = GVoxelForgeTilePostReachScale;
|
float ScaleValue = GVoxelForgeTilePostReachScale;
|
||||||
|
int32 CostDiagnosticsValue = GVoxelForgeTileReachCostDiagnostics;
|
||||||
FParse::Value(
|
FParse::Value(
|
||||||
FCommandLine::Get(), TEXT("voxel.TilePostReachDebug="), DebugValue);
|
FCommandLine::Get(), TEXT("voxel.TilePostReachDebug="), DebugValue);
|
||||||
FParse::Value(
|
FParse::Value(
|
||||||
FCommandLine::Get(), TEXT("voxel.TilePostReachScale="), ScaleValue);
|
FCommandLine::Get(), TEXT("voxel.TilePostReachScale="), ScaleValue);
|
||||||
|
FParse::Value(
|
||||||
|
FCommandLine::Get(), TEXT("voxel.TileReachCostDiagnostics="), CostDiagnosticsValue);
|
||||||
GVoxelForgeTilePostReachDebug = DebugValue != 0 ? 1 : 0;
|
GVoxelForgeTilePostReachDebug = DebugValue != 0 ? 1 : 0;
|
||||||
if (!VoxelMath::IsFinite(ScaleValue) || ScaleValue <= 0.0f)
|
if (!VoxelMath::IsFinite(ScaleValue) || ScaleValue <= 0.0f)
|
||||||
{
|
{
|
||||||
ScaleValue = 1.0f;
|
ScaleValue = 1.0f;
|
||||||
}
|
}
|
||||||
GVoxelForgeTilePostReachScale = FMath::Min(ScaleValue, 1.0f);
|
GVoxelForgeTilePostReachScale = FMath::Min(ScaleValue, 1.0f);
|
||||||
|
GVoxelForgeTileReachCostDiagnostics = CostDiagnosticsValue != 0 ? 1 : 0;
|
||||||
VoxelGenLOD::GTilePostReachDebugEnabled.store(
|
VoxelGenLOD::GTilePostReachDebugEnabled.store(
|
||||||
GVoxelForgeTilePostReachDebug != 0, std::memory_order_relaxed);
|
GVoxelForgeTilePostReachDebug != 0, std::memory_order_relaxed);
|
||||||
|
VoxelGenLOD::GTileReachCostDiagnosticsEnabled.store(
|
||||||
|
GVoxelForgeTileReachCostDiagnostics != 0, std::memory_order_relaxed);
|
||||||
}
|
}
|
||||||
|
|
||||||
void VF_ParseOperatorBlockSwitch()
|
void VF_ParseOperatorBlockSwitch()
|
||||||
@@ -284,6 +296,23 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
|||||||
GVoxelForgeTilePostReachDebug != 0);
|
GVoxelForgeTilePostReachDebug != 0);
|
||||||
TGuardValue<bool> TilePostReachBypassGuard(
|
TGuardValue<bool> TilePostReachBypassGuard(
|
||||||
VoxelGenLOD::bTilePostReachBypass, false);
|
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;
|
uint8 TilePostReachFlags = VoxelGenLOD::AllTilePostReach;
|
||||||
if (Generator->StrateManager != nullptr)
|
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.X) + Extent),
|
||||||
static_cast<float>(static_cast<int64>(OriginVoxels.Y) + Extent),
|
static_cast<float>(static_cast<int64>(OriginVoxels.Y) + Extent),
|
||||||
static_cast<float>(static_cast<int64>(OriginVoxels.Z) + Extent)));
|
static_cast<float>(static_cast<int64>(OriginVoxels.Z) + Extent)));
|
||||||
const float ReachScale = GVoxelForgeTilePostReachScale;
|
// Core stays conservative until the tile-window tunnel cache is built. Passage carving
|
||||||
TilePostReachFlags = 0;
|
// is manager-owned and can be decided immediately from the same lattice+halo box.
|
||||||
|
TilePostReachFlags = VoxelGenLOD::TunnelCoreReachable;
|
||||||
if (Generator->StrateManager->AnyOriginLandingNearLattice(
|
if (Generator->StrateManager->AnyOriginLandingNearLattice(
|
||||||
LatticeWithHalo, OriginVoxels, Step, ReachScale))
|
LatticeWithHalo, OriginVoxels, Step, ReachScale))
|
||||||
{
|
{
|
||||||
@@ -309,6 +339,11 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
|||||||
{
|
{
|
||||||
TilePostReachFlags |= VoxelGenLOD::PassageLandingReachable;
|
TilePostReachFlags |= VoxelGenLOD::PassageLandingReachable;
|
||||||
}
|
}
|
||||||
|
if (Generator->StrateManager->AnyPassageCarvingNearLattice(
|
||||||
|
LatticeWithHalo, OriginVoxels, Step, ReachScale))
|
||||||
|
{
|
||||||
|
TilePostReachFlags |= VoxelGenLOD::PassageCarvingReachable;
|
||||||
|
}
|
||||||
if (Generator->StrateManager->AnyPassageStructuralPostNearLattice(
|
if (Generator->StrateManager->AnyPassageStructuralPostNearLattice(
|
||||||
LatticeWithHalo, OriginVoxels, Step, ReachScale))
|
LatticeWithHalo, OriginVoxels, Step, ReachScale))
|
||||||
{
|
{
|
||||||
@@ -317,6 +352,30 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
|||||||
}
|
}
|
||||||
TGuardValue<uint8> TilePostReachFlagsGuard(
|
TGuardValue<uint8> TilePostReachFlagsGuard(
|
||||||
VoxelGenLOD::TilePostReachFlags, TilePostReachFlags);
|
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);
|
VoxelGenLOD::RecordTileReachMask(TilePostReachFlags);
|
||||||
// COUPE DE CONTENU PAR STRATE — restreint le maillage (et l'échantillonnage) aux cellules
|
// COUPE DE CONTENU PAR STRATE — restreint le maillage (et l'échantillonnage) aux cellules
|
||||||
// dont l'intervalle Z chevauche la bande [BandZMinVox, BandZMaxVox] (voxels inclusifs).
|
// dont l'intervalle Z chevauche la bande [BandZMinVox, BandZMaxVox] (voxels inclusifs).
|
||||||
@@ -660,6 +719,15 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// World voxel = tile origin + grid offset scaled by the cell size (Step).
|
// 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(
|
DensityGrid[LocalIndex] = Generator->GetDensityAt(
|
||||||
OriginVoxels.X + gx * Step,
|
OriginVoxels.X + gx * Step,
|
||||||
OriginVoxels.Y + gy * Step,
|
OriginVoxels.Y + gy * Step,
|
||||||
|
|||||||
@@ -2506,10 +2506,21 @@ void UVoxelStrateManager::GeneratePassages()
|
|||||||
|
|
||||||
float UVoxelStrateManager::EvaluateModifierSDF(float WorldX, float WorldY, float WorldZ) const
|
float UVoxelStrateManager::EvaluateModifierSDF(float WorldX, float WorldY, float WorldZ) const
|
||||||
{
|
{
|
||||||
|
VoxelGenLOD::FScopedReachCost ReachCost(
|
||||||
|
VoxelGenLOD::ETileReachCostKind::PassageCarving);
|
||||||
if (VoxelDensityAblation::IsPassageCarvingOff())
|
if (VoxelDensityAblation::IsPassageCarvingOff())
|
||||||
{
|
{
|
||||||
return FLT_MAX;
|
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
|
// 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
|
// 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;
|
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(
|
bool UVoxelStrateManager::AnyPassageStructuralPostNearLattice(
|
||||||
const FBox& VoxelBox, const FIntVector& LatticeOrigin, int32 Step,
|
const FBox& VoxelBox, const FIntVector& LatticeOrigin, int32 Step,
|
||||||
float ReachScale) const
|
float ReachScale) const
|
||||||
|
|||||||
@@ -1162,7 +1162,7 @@ void AVoxelWorld::EndPlay(const EEndPlayReason::Type EndPlayReason)
|
|||||||
TEXT("[VoxelForgeTilePostReachKinds] comparisons="
|
TEXT("[VoxelForgeTilePostReachKinds] comparisons="
|
||||||
"origin_air=%llu/origin_floor=%llu/passage_sdf=%llu/landing_air=%llu/"
|
"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 "
|
"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<unsigned long long>(VoxelGenLOD::GSkippedPostComparisonsByKind[
|
||||||
static_cast<uint8>(VoxelGenLOD::ETilePostComparisonKind::OriginAir)].load(
|
static_cast<uint8>(VoxelGenLOD::ETilePostComparisonKind::OriginAir)].load(
|
||||||
std::memory_order_relaxed)),
|
std::memory_order_relaxed)),
|
||||||
@@ -1190,6 +1190,9 @@ void AVoxelWorld::EndPlay(const EEndPlayReason::Type EndPlayReason)
|
|||||||
static_cast<unsigned long long>(VoxelGenLOD::GSkippedPostComparisonsByKind[
|
static_cast<unsigned long long>(VoxelGenLOD::GSkippedPostComparisonsByKind[
|
||||||
static_cast<uint8>(VoxelGenLOD::ETilePostComparisonKind::PassageLandingRoomFloor)].load(
|
static_cast<uint8>(VoxelGenLOD::ETilePostComparisonKind::PassageLandingRoomFloor)].load(
|
||||||
std::memory_order_relaxed)),
|
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<unsigned long long>(VoxelGenLOD::GSkippedPostDifferencesByKind[
|
||||||
static_cast<uint8>(VoxelGenLOD::ETilePostComparisonKind::OriginAir)].load(
|
static_cast<uint8>(VoxelGenLOD::ETilePostComparisonKind::OriginAir)].load(
|
||||||
std::memory_order_relaxed)),
|
std::memory_order_relaxed)),
|
||||||
@@ -1216,6 +1219,106 @@ void AVoxelWorld::EndPlay(const EEndPlayReason::Type EndPlayReason)
|
|||||||
std::memory_order_relaxed)),
|
std::memory_order_relaxed)),
|
||||||
static_cast<unsigned long long>(VoxelGenLOD::GSkippedPostDifferencesByKind[
|
static_cast<unsigned long long>(VoxelGenLOD::GSkippedPostDifferencesByKind[
|
||||||
static_cast<uint8>(VoxelGenLOD::ETilePostComparisonKind::PassageLandingRoomFloor)].load(
|
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)));
|
std::memory_order_relaxed)));
|
||||||
if (VoxelGenLOD::GTilePostReachDebugEnabled.load(std::memory_order_relaxed)
|
if (VoxelGenLOD::GTilePostReachDebugEnabled.load(std::memory_order_relaxed)
|
||||||
&& TilePostReachDifferences != 0)
|
&& TilePostReachDifferences != 0)
|
||||||
|
|||||||
@@ -1081,6 +1081,14 @@ namespace VoxelCaveMorphology
|
|||||||
bool bUseSpatialIndex = true
|
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
|
// 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
|
// 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.
|
// the rounded capsule's bottom from becoming an unsupported point.
|
||||||
|
|||||||
@@ -476,6 +476,10 @@ public:
|
|||||||
bool AnyPassageLandingNearLattice(const FBox& VoxelBox,
|
bool AnyPassageLandingNearLattice(const FBox& VoxelBox,
|
||||||
const FIntVector& LatticeOrigin, int32 Step,
|
const FIntVector& LatticeOrigin, int32 Step,
|
||||||
float ReachScale = 1.0f) const;
|
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. */
|
/** Conservative sphere guard for all passage-owned structural posts and native D-floors. */
|
||||||
bool AnyPassageStructuralPostNearLattice(const FBox& VoxelBox,
|
bool AnyPassageStructuralPostNearLattice(const FBox& VoxelBox,
|
||||||
const FIntVector& LatticeOrigin, int32 Step,
|
const FIntVector& LatticeOrigin, int32 Step,
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "CoreMinimal.h"
|
#include "CoreMinimal.h"
|
||||||
|
#include "HAL/PlatformTime.h"
|
||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
|
|
||||||
@@ -17,9 +18,14 @@ namespace VoxelGenLOD
|
|||||||
OriginLandingReachable = 1u << 0,
|
OriginLandingReachable = 1u << 0,
|
||||||
PassageLandingReachable = 1u << 1,
|
PassageLandingReachable = 1u << 1,
|
||||||
PassageStructuralReachable= 1u << 2,
|
PassageStructuralReachable= 1u << 2,
|
||||||
|
TunnelCoreReachable = 1u << 3,
|
||||||
|
PassageCarvingReachable = 1u << 4,
|
||||||
AllTilePostReach = OriginLandingReachable
|
AllTilePostReach = OriginLandingReachable
|
||||||
| PassageLandingReachable
|
| PassageLandingReachable
|
||||||
| PassageStructuralReachable,
|
| PassageStructuralReachable
|
||||||
|
| TunnelCoreReachable
|
||||||
|
| PassageCarvingReachable,
|
||||||
|
AllTileReach = AllTilePostReach,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class ETilePostComparisonKind : uint8
|
enum class ETilePostComparisonKind : uint8
|
||||||
@@ -34,14 +40,65 @@ namespace VoxelGenLOD
|
|||||||
PassageStructuralPosts,
|
PassageStructuralPosts,
|
||||||
PassageNativeFloor,
|
PassageNativeFloor,
|
||||||
PassageLandingRoomFloor,
|
PassageLandingRoomFloor,
|
||||||
|
TunnelCore,
|
||||||
|
PassageCarving,
|
||||||
|
FinalField,
|
||||||
Count,
|
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
|
// 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.
|
// 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 bTilePostReachDebug = false;
|
||||||
inline thread_local bool bTilePostReachBypass = 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.
|
// These are diagnostics only. They never participate in a field decision.
|
||||||
inline std::atomic<bool> GTilePostReachDebugEnabled { false };
|
inline std::atomic<bool> GTilePostReachDebugEnabled { false };
|
||||||
@@ -52,12 +109,25 @@ namespace VoxelGenLOD
|
|||||||
inline std::atomic<uint64> GOriginLandingSkippedTiles { 0 };
|
inline std::atomic<uint64> GOriginLandingSkippedTiles { 0 };
|
||||||
inline std::atomic<uint64> GPassageLandingSkippedTiles { 0 };
|
inline std::atomic<uint64> GPassageLandingSkippedTiles { 0 };
|
||||||
inline std::atomic<uint64> GPassageStructuralSkippedTiles { 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> GSkippedPostComparisons { 0 };
|
||||||
inline std::atomic<uint64> GSkippedPostDifferences { 0 };
|
inline std::atomic<uint64> GSkippedPostDifferences { 0 };
|
||||||
inline std::atomic<uint64> GSkippedPostComparisonsByKind[
|
inline std::atomic<uint64> GSkippedPostComparisonsByKind[
|
||||||
static_cast<uint8>(ETilePostComparisonKind::Count)]{};
|
static_cast<uint8>(ETilePostComparisonKind::Count)]{};
|
||||||
inline std::atomic<uint64> GSkippedPostDifferencesByKind[
|
inline std::atomic<uint64> GSkippedPostDifferencesByKind[
|
||||||
static_cast<uint8>(ETilePostComparisonKind::Count)]{};
|
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)
|
FORCEINLINE bool IsTilePostReachable(uint8 Bit)
|
||||||
{
|
{
|
||||||
@@ -79,6 +149,25 @@ namespace VoxelGenLOD
|
|||||||
return IsTilePostReachable(PassageStructuralReachable);
|
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()
|
FORCEINLINE bool IsTilePostReachBypassActive()
|
||||||
{
|
{
|
||||||
return bTilePostReachBypass;
|
return bTilePostReachBypass;
|
||||||
@@ -111,7 +200,121 @@ namespace VoxelGenLOD
|
|||||||
{
|
{
|
||||||
GPassageStructuralSkippedTiles.fetch_add(1, std::memory_order_relaxed);
|
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)
|
FORCEINLINE bool SameFloatBits(float A, float B)
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in new issue
Block a user