perf(generation): gate the tile cache window by LOD; LOD0 back to switch-off speed
2de603c's tile-sized room cache cut worker CPU, but LOD0 generation was ~9% slower. The per-tile
split shows the loss was evaluation (more candidates per sample in a bigger window), not cache
building. Policy, measured per path and LOD:
- fused path: tile window at LOD >= voxel.TileCacheWindowMinLOD (default 3)
- op-stack path: tile window at LOD >= voxel.TileCacheWindowOpStackMinLOD (default 4)
- lower LODs keep the 4-chunk worker window
- voxel.SpatialIndex: -1 auto (off at fused LOD0, on elsewhere), 0 or 1
Proof caveat closed: VF_ComputeTerrainOpEnvelope adds authored terrain-op extents (pits, chimneys,
detail overrides, ...) to the invariance envelope. Ops beyond the proven 256-voxel extent, or
malformed/unloaded ones, force the legacy window, so window invariance holds for every asset.
Sampler fix: the leaf symbol kept pointers into a TMap that FindOrAdd can rehash while later
frames resolve; the leaf is now re-found by program counter after insertion.
Game path, two runs each, sampler off (Codex; checked against its report):
LOD0 request p50/p95 generation p50/p95 worker s
static optimized 106.3 / 175.9 ms 76.6 / 124.3 ms 39.2
static switch-off 106.3 / 173.0 75.9 / 124.1 58.5 (-33%)
moving optimized 79.4 / 158.5 72.4 / 123.7 113.7
moving switch-off 82.8 / 160.4 73.2 / 125.4 141.9 (-20%)
LOD0 is flat within noise static and slightly better moving; worker CPU keeps most of the gain.
Field exact: 841/841 per-tile triangle identity, export OBJ SHA-256 unchanged (b3e5f4c3..9b377),
capability 20,830 / 10,909.
Caution: compare these numbers only within this round. The machine is in daytime use, so
absolute figures drift between rounds.
Current LOD0 map (sampler, moving, inclusive): tunnel SDF 12.5% + tunnel core 12.3% (the same
tunnel in two coordinate spaces); passage carving 11.1%, structural posts 11.4%, native floor
4.1%, with floor projection at 8.9% inside them; cave warp ~8% exclusive. Development-build-only
overheads: TArray RangeCheck ~6.5%, per-sample FScopedTimer ~2.6%.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TaFdXhTdYMkW1xrV6ApT6Y
This commit is contained in:
1 parent
2de603cd77
commit
cfad3d1c18
8 files changed
+729
-146
No files matched your search
@@ -56,15 +56,34 @@ namespace
|
||||
}
|
||||
}
|
||||
|
||||
// A tile-sized cache window is the production default. Keep the switch in the generator
|
||||
// module so native and op-stack sources make the same A/B decision, including when a
|
||||
// headless harness applies command-line CVars after module startup.
|
||||
// A tile-sized cache window is the production default on the coarse paths where it wins.
|
||||
// Keep the switches in the generator module so native and op-stack sources make the same A/B
|
||||
// decision, including when a headless harness applies command-line CVars after module startup.
|
||||
int32 GVoxelForgeTileCacheWindow = 1;
|
||||
FAutoConsoleVariableRef CVarVoxelForgeTileCacheWindow(
|
||||
TEXT("voxel.TileCacheWindow"),
|
||||
GVoxelForgeTileCacheWindow,
|
||||
TEXT("Use the requesting tile's aligned footprint plus the deterministic warp/halo margin for room caches."));
|
||||
bool GVoxelForgeTileCacheWindowSwitchParsed = false;
|
||||
// Refreshed profiles put the fused crossover at LOD3 and the op-stack crossover at LOD4.
|
||||
// Separate defaults keep a path that loses at LOD3 from paying for a larger candidate set.
|
||||
int32 GVoxelForgeTileCacheWindowMinLOD = 3;
|
||||
FAutoConsoleVariableRef CVarVoxelForgeTileCacheWindowMinLOD(
|
||||
TEXT("voxel.TileCacheWindowMinLOD"),
|
||||
GVoxelForgeTileCacheWindowMinLOD,
|
||||
TEXT("Minimum SampleStep LOD for the fused tile-sized room cache window."));
|
||||
int32 GVoxelForgeTileCacheWindowOpStackMinLOD = 4;
|
||||
FAutoConsoleVariableRef CVarVoxelForgeTileCacheWindowOpStackMinLOD(
|
||||
TEXT("voxel.TileCacheWindowOpStackMinLOD"),
|
||||
GVoxelForgeTileCacheWindowOpStackMinLOD,
|
||||
TEXT("Minimum SampleStep LOD for the op-stack tile-sized room cache window."));
|
||||
|
||||
int32 GVoxelForgeSpatialIndex = -1;
|
||||
FAutoConsoleVariableRef CVarVoxelForgeSpatialIndex(
|
||||
TEXT("voxel.SpatialIndex"),
|
||||
GVoxelForgeSpatialIndex,
|
||||
TEXT("Room-cache spatial broad phase: -1 measured auto policy, 0 off, 1 on."));
|
||||
bool GVoxelForgeSpatialIndexSwitchParsed = false;
|
||||
|
||||
void VF_ParseTileCacheWindowSwitch()
|
||||
{
|
||||
@@ -80,6 +99,42 @@ namespace
|
||||
GVoxelForgeTileCacheWindow = CommandLineValue;
|
||||
}
|
||||
GVoxelForgeTileCacheWindow = GVoxelForgeTileCacheWindow != 0 ? 1 : 0;
|
||||
|
||||
int32 MinLODCommandLineValue = GVoxelForgeTileCacheWindowMinLOD;
|
||||
if (FParse::Value(
|
||||
FCommandLine::Get(), TEXT("voxel.TileCacheWindowMinLOD="),
|
||||
MinLODCommandLineValue))
|
||||
{
|
||||
GVoxelForgeTileCacheWindowMinLOD = MinLODCommandLineValue;
|
||||
}
|
||||
GVoxelForgeTileCacheWindowMinLOD = FMath::Clamp(
|
||||
GVoxelForgeTileCacheWindowMinLOD, 0, 5);
|
||||
|
||||
int32 OpStackMinLODCommandLineValue = GVoxelForgeTileCacheWindowOpStackMinLOD;
|
||||
if (FParse::Value(
|
||||
FCommandLine::Get(), TEXT("voxel.TileCacheWindowOpStackMinLOD="),
|
||||
OpStackMinLODCommandLineValue))
|
||||
{
|
||||
GVoxelForgeTileCacheWindowOpStackMinLOD = OpStackMinLODCommandLineValue;
|
||||
}
|
||||
GVoxelForgeTileCacheWindowOpStackMinLOD = FMath::Clamp(
|
||||
GVoxelForgeTileCacheWindowOpStackMinLOD, 0, 5);
|
||||
}
|
||||
|
||||
void VF_ParseSpatialIndexSwitch()
|
||||
{
|
||||
if (GVoxelForgeSpatialIndexSwitchParsed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
GVoxelForgeSpatialIndexSwitchParsed = true;
|
||||
int32 CommandLineValue = GVoxelForgeSpatialIndex;
|
||||
if (FParse::Value(
|
||||
FCommandLine::Get(), TEXT("voxel.SpatialIndex="), CommandLineValue))
|
||||
{
|
||||
GVoxelForgeSpatialIndex = CommandLineValue;
|
||||
}
|
||||
GVoxelForgeSpatialIndex = FMath::Clamp(GVoxelForgeSpatialIndex, -1, 1);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -460,10 +515,18 @@ void VoxelGenLOD::SetThreadSampleStep(int32 Value)
|
||||
SampleStep = Value;
|
||||
}
|
||||
|
||||
bool VoxelGenLOD::IsTileCacheWindowEnabled()
|
||||
bool VoxelGenLOD::IsTileCacheWindowEnabled(bool bFusedPath)
|
||||
{
|
||||
VF_ParseTileCacheWindowSwitch();
|
||||
return GVoxelForgeTileCacheWindow != 0;
|
||||
if (GVoxelForgeTileCacheWindow == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
const int32 RequiredMinLOD = bFusedPath
|
||||
? GVoxelForgeTileCacheWindowMinLOD
|
||||
: GVoxelForgeTileCacheWindowOpStackMinLOD;
|
||||
const int32 RequiredStep = 1 << FMath::Clamp(RequiredMinLOD, 0, 5);
|
||||
return FMath::Max(SampleStep, 1) >= RequiredStep;
|
||||
}
|
||||
|
||||
bool VoxelGenLOD::GetThreadTileCacheWindow(
|
||||
@@ -485,6 +548,25 @@ bool VoxelGenLOD::GetThreadTileCacheWindow(
|
||||
&& Extent <= static_cast<int64>(CHUNK_SIZE) * 64;
|
||||
}
|
||||
|
||||
bool VoxelGenLOD::ShouldUseSpatialIndex(bool bFusedPath)
|
||||
{
|
||||
VF_ParseSpatialIndexSwitch();
|
||||
if (GVoxelForgeSpatialIndex >= 0)
|
||||
{
|
||||
return GVoxelForgeSpatialIndex != 0;
|
||||
}
|
||||
|
||||
// The full game sweep makes the fused LOD0 floor faster without the broad phase. Keep it
|
||||
// enabled for LOD1+ where the candidate reduction pays for GetRange, and keep the measured
|
||||
// op-stack policy enabled at every tested LOD. The explicit override remains useful for
|
||||
// field/determinism A/B runs.
|
||||
if (bFusedPath && FMath::Max(SampleStep, 1) <= 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// NOTE (T2.a): the fBm/Ridged bodies moved to VoxelNoise.h, where octaves are evaluated
|
||||
// 4-wide via SSE (Perlin3D_x4). These thin wrappers keep every call site unchanged. They
|
||||
// sample a DIFFERENT (float hash-gradient) noise field than the old FMath::PerlinNoise3D,
|
||||
@@ -1244,7 +1326,8 @@ namespace
|
||||
|
||||
static void BuildTunnelSupportColumnsForChunk(
|
||||
FChunkSDFCache& Cache,
|
||||
const FIntVector& ChunkCoord);
|
||||
const FIntVector& ChunkCoord,
|
||||
bool bUseSpatialIndex);
|
||||
|
||||
static const FTunnelSupportFloorColumn* FindTunnelSupportColumn(
|
||||
const FChunkSDFCache& Cache,
|
||||
@@ -1353,10 +1436,16 @@ namespace
|
||||
FIntVector RequestedTileOrigin = FIntVector::ZeroValue;
|
||||
int32 RequestedTileStep = 1;
|
||||
int32 RequestedTileCells = 0;
|
||||
const bool bUseTileCacheWindow = VoxelGenLOD::IsTileCacheWindowEnabled()
|
||||
const TArray<FStrateTerrainOpEntry>* TerrainOps = nullptr;
|
||||
if (UVoxelStrateDefinition* Def = Manager.GetStrateForChunk(ChunkCoord))
|
||||
{
|
||||
TerrainOps = &Def->TerrainOperations;
|
||||
}
|
||||
const bool bUseTileCacheWindow = VoxelGenLOD::IsTileCacheWindowEnabled(true)
|
||||
&& VoxelGenLOD::GetThreadTileCacheWindow(
|
||||
RequestedTileOrigin, RequestedTileStep, RequestedTileCells)
|
||||
&& VoxelCaveMorphology::IsRoomGraphWindowInvariant(Params);
|
||||
&& VoxelCaveMorphology::IsRoomGraphWindowInvariant(Params, TerrainOps);
|
||||
const bool bUseSpatialIndex = VoxelGenLOD::ShouldUseSpatialIndex(true);
|
||||
const int32 StrateIndex = Manager.GetStrateIndex(
|
||||
(static_cast<float>(ChunkCoord.Z) + 0.5f)
|
||||
* static_cast<float>(CHUNK_SIZE) * VOXEL_SIZE);
|
||||
@@ -1382,7 +1471,8 @@ namespace
|
||||
// density cursor crosses a chunk; the room/tunnel graph itself is not rebuilt.
|
||||
if (OutState.SupportChunk != ChunkCoord)
|
||||
{
|
||||
BuildTunnelSupportColumnsForChunk(OutState.Cache, ChunkCoord);
|
||||
BuildTunnelSupportColumnsForChunk(
|
||||
OutState.Cache, ChunkCoord, bUseSpatialIndex);
|
||||
OutState.SupportChunk = ChunkCoord;
|
||||
}
|
||||
OutState.Chunk = ChunkCoord;
|
||||
@@ -1421,7 +1511,7 @@ namespace
|
||||
StrateIndex, nullptr,
|
||||
ERoomGraphBuildSite::GeneratorTunnelCore);
|
||||
BuildTunnelSupportColumnsForChunk(
|
||||
OutState.Cache, ChunkCoord);
|
||||
OutState.Cache, ChunkCoord, bUseSpatialIndex);
|
||||
OutState.OwnerId = OwnerId;
|
||||
OutState.ManagerLifetimeId = ManagerLifetimeId;
|
||||
OutState.Chunk = ChunkCoord;
|
||||
@@ -1440,7 +1530,8 @@ namespace
|
||||
|
||||
static void BuildTunnelSupportColumnsForChunk(
|
||||
FChunkSDFCache& Cache,
|
||||
const FIntVector& ChunkCoord)
|
||||
const FIntVector& ChunkCoord,
|
||||
bool bUseSpatialIndex)
|
||||
{
|
||||
const int32 MinX = ChunkCoord.X * CHUNK_SIZE - 1;
|
||||
const int32 MinY = ChunkCoord.Y * CHUNK_SIZE - 1;
|
||||
@@ -1463,7 +1554,8 @@ namespace
|
||||
static_cast<float>(MinX + X),
|
||||
static_cast<float>(MinY + Y),
|
||||
Cache,
|
||||
Column);
|
||||
Column,
|
||||
bUseSpatialIndex);
|
||||
if (Column.Intervals.Num() == 0)
|
||||
{
|
||||
continue;
|
||||
@@ -1805,7 +1897,8 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co
|
||||
FIntVector RequestedTileOrigin = FIntVector::ZeroValue;
|
||||
int32 RequestedTileStep = 1;
|
||||
int32 RequestedTileCells = 0;
|
||||
const bool bTileCacheContext = VoxelGenLOD::IsTileCacheWindowEnabled()
|
||||
const bool bTileCacheContext = VoxelGenLOD::IsTileCacheWindowEnabled(
|
||||
!bShareTunnelStackByZ)
|
||||
&& VoxelGenLOD::GetThreadTileCacheWindow(
|
||||
RequestedTileOrigin, RequestedTileStep, RequestedTileCells);
|
||||
FVoxelOpStack* ActiveOpStack = &CP_OpStack;
|
||||
@@ -2352,7 +2445,9 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co
|
||||
VoxelDensityProfile::FScopedTimer ProfileTimer(
|
||||
VoxelDensityProfile::EBucket::TunnelCorePosts);
|
||||
PreDisturbanceTunnelCore = VoxelCaveMorphology::EvaluateTunnelCoreWorld(
|
||||
WorldX, WorldY, WorldZ, ActiveTunnelCoreCache->Cache, SupportColumn);
|
||||
WorldX, WorldY, WorldZ, ActiveTunnelCoreCache->Cache, SupportColumn,
|
||||
VoxelGenLOD::ShouldUseSpatialIndex(
|
||||
CP_UseOpStack ? bUsedFusedEvaluator : true));
|
||||
bHavePreDisturbanceTunnelCore = true;
|
||||
}
|
||||
const bool bProtectAuthoredTunnelFloor = bHavePreDisturbanceTunnelCore
|
||||
@@ -2499,7 +2594,9 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co
|
||||
VoxelDensityProfile::EBucket::TunnelCorePosts);
|
||||
TunnelCore = VoxelCaveMorphology::EvaluateTunnelCoreWorld(
|
||||
WorldX, WorldY, WorldZ, ActiveTunnelCoreCache->Cache,
|
||||
SupportColumn);
|
||||
SupportColumn,
|
||||
VoxelGenLOD::ShouldUseSpatialIndex(
|
||||
CP_UseOpStack ? bUsedFusedEvaluator : true));
|
||||
bHaveTunnelCore = true;
|
||||
}
|
||||
if (bHaveTunnelCore)
|
||||
@@ -2831,15 +2928,27 @@ float UVoxelGenerator::GetDensityWithParams(float WorldX, float WorldY, float Wo
|
||||
thread_local int32 CachedTileCells = 0;
|
||||
thread_local uint64 CachedManagerLifetimeId = 0;
|
||||
thread_local const TArray<FStrateTerrainOpEntry>* CachedTerrainOps = nullptr;
|
||||
// The envelope proof is constant for a cache-policy key. Do not run the complete room/edge
|
||||
// reach calculation for every density sample: that would turn a build-time guard into a
|
||||
// per-sample cost on the LOD0 floor.
|
||||
thread_local bool CachedTilePolicyValid = false;
|
||||
thread_local bool CachedTilePolicyResult = false;
|
||||
thread_local bool CachedTilePolicyHasContext = false;
|
||||
thread_local bool CachedTilePolicyFusedPath = false;
|
||||
thread_local int32 CachedTilePolicySampleStep = 0;
|
||||
thread_local FIntVector CachedTilePolicyOrigin = FIntVector::ZeroValue;
|
||||
thread_local int32 CachedTilePolicyStep = 0;
|
||||
thread_local int32 CachedTilePolicyCells = 0;
|
||||
thread_local uint32 CachedTilePolicyFingerprint = 0xFFFFFFFFu;
|
||||
thread_local uint32 CachedTilePolicyLayout = 0xFFFFFFFFu;
|
||||
thread_local uint64 CachedTilePolicyManagerLifetimeId = 0;
|
||||
thread_local const TArray<FStrateTerrainOpEntry>* CachedTilePolicyTerrainOps = nullptr;
|
||||
|
||||
const bool bUseFusedCacheWindow = OutTunnelCore != nullptr;
|
||||
const bool bUseSpatialIndex = VoxelGenLOD::ShouldUseSpatialIndex(bUseFusedCacheWindow);
|
||||
FIntVector RequestedTileOrigin = FIntVector::ZeroValue;
|
||||
int32 RequestedTileStep = 1;
|
||||
int32 RequestedTileCells = 0;
|
||||
const bool bUseTileCacheWindow = VoxelGenLOD::IsTileCacheWindowEnabled()
|
||||
&& VoxelGenLOD::GetThreadTileCacheWindow(
|
||||
RequestedTileOrigin, RequestedTileStep, RequestedTileCells)
|
||||
&& VoxelCaveMorphology::IsRoomGraphWindowInvariant(Params);
|
||||
const uint64 CurrentManagerLifetimeId = StrateManager
|
||||
? StrateManager->GetCacheLifetimeId() : 0;
|
||||
const int32 QueryChunkX = FMath::FloorToInt(WorldX / (float)CHUNK_SIZE);
|
||||
@@ -2865,6 +2974,39 @@ float UVoxelGenerator::GetDensityWithParams(float WorldX, float WorldY, float Wo
|
||||
QueryChunkZ));
|
||||
if (Def) TerrainOps = &Def->TerrainOperations;
|
||||
}
|
||||
const bool bTileWindowEligible = VoxelGenLOD::IsTileCacheWindowEnabled(
|
||||
bUseFusedCacheWindow);
|
||||
const bool bHasTileWindowContext = VoxelGenLOD::GetThreadTileCacheWindow(
|
||||
RequestedTileOrigin, RequestedTileStep, RequestedTileCells);
|
||||
const bool bTilePolicyKeyChanged =
|
||||
!CachedTilePolicyValid
|
||||
|| CachedTilePolicyHasContext != (bTileWindowEligible && bHasTileWindowContext)
|
||||
|| CachedTilePolicyFusedPath != bUseFusedCacheWindow
|
||||
|| CachedTilePolicySampleStep != RequestedTileStep
|
||||
|| CachedTilePolicyOrigin != RequestedTileOrigin
|
||||
|| CachedTilePolicyStep != RequestedTileStep
|
||||
|| CachedTilePolicyCells != RequestedTileCells
|
||||
|| CachedTilePolicyFingerprint != ParamsFingerprint
|
||||
|| CachedTilePolicyLayout != LayoutVersion
|
||||
|| CachedTilePolicyManagerLifetimeId != CurrentManagerLifetimeId
|
||||
|| CachedTilePolicyTerrainOps != TerrainOps;
|
||||
if (bTilePolicyKeyChanged)
|
||||
{
|
||||
CachedTilePolicyValid = true;
|
||||
CachedTilePolicyHasContext = bTileWindowEligible && bHasTileWindowContext;
|
||||
CachedTilePolicyFusedPath = bUseFusedCacheWindow;
|
||||
CachedTilePolicySampleStep = RequestedTileStep;
|
||||
CachedTilePolicyOrigin = RequestedTileOrigin;
|
||||
CachedTilePolicyStep = RequestedTileStep;
|
||||
CachedTilePolicyCells = RequestedTileCells;
|
||||
CachedTilePolicyFingerprint = ParamsFingerprint;
|
||||
CachedTilePolicyLayout = LayoutVersion;
|
||||
CachedTilePolicyManagerLifetimeId = CurrentManagerLifetimeId;
|
||||
CachedTilePolicyTerrainOps = TerrainOps;
|
||||
CachedTilePolicyResult = CachedTilePolicyHasContext
|
||||
&& VoxelCaveMorphology::IsRoomGraphWindowInvariant(Params, TerrainOps);
|
||||
}
|
||||
const bool bUseTileCacheWindow = CachedTilePolicyResult;
|
||||
|
||||
// Index of the room with the smallest (most-inside) SDF for this voxel.
|
||||
// Written by EvaluateSDFCached, read by the terrain ops block to pick the
|
||||
@@ -2989,7 +3131,8 @@ float UVoxelGenerator::GetDensityWithParams(float WorldX, float WorldY, float Wo
|
||||
CaveSDF = VoxelCaveMorphology::EvaluateSDFCached(
|
||||
WarpedX, WarpedY, WarpedZ,
|
||||
SDFCache, Params.SDFBlendRadius,
|
||||
&NearestRoomIdx
|
||||
&NearestRoomIdx,
|
||||
bUseSpatialIndex
|
||||
);
|
||||
|
||||
// ── PIT & CHIMNEY SDF INTEGRATION ──
|
||||
@@ -3039,7 +3182,8 @@ float UVoxelGenerator::GetDensityWithParams(float WorldX, float WorldY, float Wo
|
||||
CaveSDF = VoxelSDF::SmoothMin(CaveSDF, PitSDF, Pit.BlendK);
|
||||
};
|
||||
VF_ForEachChunkSDFSpatialCandidate(
|
||||
SDFCache.PitSpatialIndex, SDFCache.Pits.Num(), WorldX, WorldY, EvaluatePit);
|
||||
SDFCache.PitSpatialIndex, SDFCache.Pits.Num(), WorldX, WorldY, EvaluatePit,
|
||||
bUseSpatialIndex);
|
||||
|
||||
auto EvaluateChimney = [&](int32 ChimneyIndex)
|
||||
{
|
||||
@@ -3071,7 +3215,7 @@ float UVoxelGenerator::GetDensityWithParams(float WorldX, float WorldY, float Wo
|
||||
};
|
||||
VF_ForEachChunkSDFSpatialCandidate(
|
||||
SDFCache.ChimneySpatialIndex, SDFCache.Chimneys.Num(),
|
||||
WorldX, WorldY, EvaluateChimney);
|
||||
WorldX, WorldY, EvaluateChimney, bUseSpatialIndex);
|
||||
|
||||
// Convert SDF to density carving:
|
||||
// CaveSDF < 0 means we're inside a room/tunnel/pit → carve to air
|
||||
@@ -3102,7 +3246,7 @@ float UVoxelGenerator::GetDensityWithParams(float WorldX, float WorldY, float Wo
|
||||
// The interpreted source has a lazy worker LRU for block evaluation; building that table
|
||||
// here would turn a cache-window change into hundreds of unnecessary column builds.
|
||||
*OutTunnelCore = VoxelCaveMorphology::EvaluateTunnelCoreWorld(
|
||||
WorldX, WorldY, WorldZ, SDFCache, nullptr);
|
||||
WorldX, WorldY, WorldZ, SDFCache, nullptr, bUseSpatialIndex);
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
@@ -3328,9 +3472,11 @@ float UVoxelGenerator::GetDensityWithParams(float WorldX, float WorldY, float Wo
|
||||
// We use the cached SDF so this costs two extra SDF evaluations per voxel,
|
||||
// only when near a surface — the common case is cheap (DistFromSurface > TerraceRange).
|
||||
float SDF_Zp1 = VoxelCaveMorphology::EvaluateSDFCached(
|
||||
WorldX, WorldY, WorldZ + 1.0f, SDFCache, Params.SDFBlendRadius);
|
||||
WorldX, WorldY, WorldZ + 1.0f, SDFCache, Params.SDFBlendRadius,
|
||||
nullptr, bUseSpatialIndex);
|
||||
float SDF_Zm1 = VoxelCaveMorphology::EvaluateSDFCached(
|
||||
WorldX, WorldY, WorldZ - 1.0f, SDFCache, Params.SDFBlendRadius);
|
||||
WorldX, WorldY, WorldZ - 1.0f, SDFCache, Params.SDFBlendRadius,
|
||||
nullptr, bUseSpatialIndex);
|
||||
float GZ = (SDF_Zp1 - SDF_Zm1) * 0.5f;
|
||||
// Normalized vertical component: 1 = perfectly horizontal surface (floor/ceiling)
|
||||
// 0 = perfectly vertical surface (wall)
|
||||
@@ -3620,7 +3766,8 @@ float UVoxelGenerator::GetDensityWithParams(float WorldX, float WorldY, float Wo
|
||||
}
|
||||
};
|
||||
VF_ForEachChunkSDFSpatialCandidate(
|
||||
SDFCache.ColumnSpatialIndex, SDFCache.Columns.Num(), WorldX, WorldY, EvaluateColumn);
|
||||
SDFCache.ColumnSpatialIndex, SDFCache.Columns.Num(), WorldX, WorldY, EvaluateColumn,
|
||||
bUseSpatialIndex);
|
||||
|
||||
//=========================================================================
|
||||
// STEP 4g: DOMES (room-relative hemispherical ceilings)
|
||||
@@ -3881,7 +4028,7 @@ float UVoxelGenerator::GetDensityWithParams(float WorldX, float WorldY, float Wo
|
||||
// GetDensityWithParams retain this historical tail; GetDensityAt skips it and applies the
|
||||
// same predicates once in its common MC-space post section below both generation paths.
|
||||
const bool bTunnelSupportFloor = VoxelCaveMorphology::IsTunnelSupportFloorWorldPoint(
|
||||
WorldX, WorldY, WorldZ, SDFCache);
|
||||
WorldX, WorldY, WorldZ, SDFCache, bUseSpatialIndex);
|
||||
// Keep the direct legacy entry point capability-safe for callers outside the canonical
|
||||
// operator-stack path; its support band follows the same relieved swept floor.
|
||||
if (bTunnelSupportFloor)
|
||||
@@ -3899,7 +4046,8 @@ float UVoxelGenerator::GetDensityWithParams(float WorldX, float WorldY, float Wo
|
||||
Density = FMath::Max(Density, StructuralSolidDensity);
|
||||
}
|
||||
const float TunnelCoreSDF = (Params.RoomDensity > 0.0f && Params.RoomSpacing > 0.0f)
|
||||
? VoxelCaveMorphology::EvaluateTunnelCoreWorldSDF(WorldX, WorldY, WorldZ, SDFCache)
|
||||
? VoxelCaveMorphology::EvaluateTunnelCoreWorldSDF(
|
||||
WorldX, WorldY, WorldZ, SDFCache, bUseSpatialIndex)
|
||||
: FLT_MAX;
|
||||
if (!bTunnelSupportFloor
|
||||
&& TunnelCoreSDF < -VoxelPassageGeometry::CaveTunnelAirCoreInsetVoxels)
|
||||
|
||||
Reference in new issue
Block a user