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:
Fr0zkaandClaude Opus 5 committed 2026-09-13 18:17:28 +02:00
1 parent 2de603cd77
commit cfad3d1c18
8 files changed
+728 -145

No files matched your search

+1 -1
View File
@@ -302,7 +302,7 @@ casse cette arithmetique *structurellement* — `BeginPlay` loggue une **Error**
| `ProcessPendingChunks` | 301 | Drains ProcessQueue under per-frame budget; reads the per-request cancellation token before removing its map entry, drops obsolete/aborted results, then applies each live result via `ApplyTileResult`. | | `ProcessPendingChunks` | 301 | Drains ProcessQueue under per-frame budget; reads the per-request cancellation token before removing its map entry, drops obsolete/aborted results, then applies each live result via `ApplyTileResult`. |
| `ApplyTileResult` | — | **Shared game-thread apply** for one `FChunkResult` (async drain + sync carve): discards stale generation epochs and keys no longer desired, marks loaded, ingests capture, EMPTY releases the tile's existing component (a re-gen can flip content→empty on band change — old geometry must not linger), else `ApplyMeshToTile`. Returns true iff a visible mesh uploaded (counts the budget). Doesn't touch `PendingTiles` (caller's). | | `ApplyTileResult` | — | **Shared game-thread apply** for one `FChunkResult` (async drain + sync carve): discards stale generation epochs and keys no longer desired, marks loaded, ingests capture, EMPTY releases the tile's existing component (a re-gen can flip content→empty on band change — old geometry must not linger), else `ApplyMeshToTile`. Returns true iff a visible mesh uploaded (counts the budget). Doesn't touch `PendingTiles` (caller's). |
| `FVoxelStackSampler` / `FScopedVoxelStackRegistration` (`VoxelStackSampler.h/.cpp`) | — | **Diagnostic stack-sampling profiler (2026-09-13), off by default.** A sampler thread captures registered generation workers' stacks (`CaptureThreadStackBackTrace`) every N µs and tags each with the tile's LOD. It symbolises after the run (inline frames too) into CSV plus a summary under `Saved/`. Registration is per tile TASK in `LoadTile` (and the export's tasks), never per sample; disabled costs one relaxed atomic load. Game `-voxel.SampleStacks=<us>`, commandlet `-samplestacks[=us]`. ~4-5% overhead at 1 ms, so trust large shares, not single-digit deltas. Report: `PERF-SAMPLED.md`. | | `FVoxelStackSampler` / `FScopedVoxelStackRegistration` (`VoxelStackSampler.h/.cpp`) | — | **Diagnostic stack-sampling profiler (2026-09-13), off by default.** A sampler thread captures registered generation workers' stacks (`CaptureThreadStackBackTrace`) every N µs and tags each with the tile's LOD. It symbolises after the run (inline frames too) into CSV plus a summary under `Saved/`. Registration is per tile TASK in `LoadTile` (and the export's tasks), never per sample; disabled costs one relaxed atomic load. Game `-voxel.SampleStacks=<us>`, commandlet `-samplestacks[=us]`. ~4-5% overhead at 1 ms, so trust large shares, not single-digit deltas. Report: `PERF-SAMPLED.md`. |
| Room-cache performance (2026-09-13): `VF_FindPlayerFitPointForRoomMemoized` / `GPlayerFitMemo` (`VoxelCaveMorphology.cpp`), `voxel.TileCacheWindow` + `VoxelGenLOD::TileOriginVoxels/TileCellsPerAxis` (`VoxelGenerator.h`), `FChunkSDFSpatialIndex` | — | **Player-fit memo:** exact 97-word key, process-wide, `voxel.PlayerFitMemo`. **Tile cache window:** the room cache covers the requesting tile's footprint plus a fixed collect margin. It is set via `TGuardValue` in `GenerateMesh`/classify, and is window-invariant by construction (the margin does not grow with the tile). **Spatial index:** ordered XY buckets over cache primitives, exact rejection. The `static_assert` in `VoxelStrateTypes.h` ties `VF_STRATE_PARAM_FIELDS` to `FStrateGenerationParams` (83 fields + 8 B padding). | | Room-cache performance (2026-09-13): `VF_FindPlayerFitPointForRoomMemoized` / `GPlayerFitMemo` (`VoxelCaveMorphology.cpp`), `voxel.TileCacheWindow` + `VoxelGenLOD::TileOriginVoxels/TileCellsPerAxis` (`VoxelGenerator.h`), `FChunkSDFSpatialIndex` | — | **Player-fit memo:** exact 97-word key, process-wide, `voxel.PlayerFitMemo`. **Tile cache window:** the room cache covers the requesting tile's footprint plus a fixed collect margin. It is set via `TGuardValue` in `GenerateMesh`/classify, and is window-invariant by construction (the margin does not grow with the tile). **LOD-gated:** fused path at LOD ≥ `voxel.TileCacheWindowMinLOD` (3), op-stack at LOD ≥ `voxel.TileCacheWindowOpStackMinLOD` (4); lower LODs keep the 4-chunk worker window, because the tile window slowed LOD0 evaluation ~6%. Strates whose authored terrain ops exceed the proven 256-voxel extent (or are malformed/unloaded) fall back to the legacy window (`VF_ComputeTerrainOpEnvelope`). `voxel.SpatialIndex` is -1 auto (off at fused LOD0, on elsewhere), 0 or 1. **Spatial index:** ordered XY buckets over cache primitives, exact rejection. The `static_assert` in `VoxelStrateTypes.h` ties `VF_STRATE_PARAM_FIELDS` to `FStrateGenerationParams` (83 fields + 8 B padding). |
| `GenerateTileResult` | — | **Shared worker-side gen** for one tile (async `LoadTile` task + sync `SyncRemeshTile`): [ClassifyTile (T1.d) + exact validation, only when `voxel.OuterClassifierMode=1`; **default 0 since 2026-09-13**] → `GenerateMesh`/`GenerateSheetMesh` → `BuildTileStreamSet`. Reads Generator/Mesher only → safe on a worker or the game thread; fills `FChunkResult`, no enqueue. | | `GenerateTileResult` | — | **Shared worker-side gen** for one tile (async `LoadTile` task + sync `SyncRemeshTile`): [ClassifyTile (T1.d) + exact validation, only when `voxel.OuterClassifierMode=1`; **default 0 since 2026-09-13**] → `GenerateMesh`/`GenerateSheetMesh` → `BuildTileStreamSet`. Reads Generator/Mesher only → safe on a worker or the game thread; fills `FChunkResult`, no enqueue. |
| `SyncRemeshTile` | — | **INSTANT DIG**: level-0 same-frame re-mesh on the game thread (`GenerateTileResult` + `ApplyTileResult` inline, Cells=CHUNK_SIZE/Step=1 + strate band, no capture). Used for the tile under the brush centre so a carve is visible THIS frame; one full-res gen on the game thread. | | `SyncRemeshTile` | — | **INSTANT DIG**: level-0 same-frame re-mesh on the game thread (`GenerateTileResult` + `ApplyTileResult` inline, Cells=CHUNK_SIZE/Step=1 + strate band, no capture). Used for the tile under the brush centre so a carve is visible THIS frame; one full-res gen on the game thread. |
| `UpdateChunksAroundPosition` | 362 | Builds desired set, cancels pending keys that left it, sorts from the **true pawn position**, submits the occupied/support LOD0 tile and the next tile along the pawn heading as an absolute `BackgroundHigh` prefix, then loads/unloads and handles LOD changes. **Delta cull**: `BuildDesiredTiles` returns the LEAVERS (stamped `DesiredStamped` map, one sweep) — only those + `TransitionHold` are considered per crossing, not every loaded tile. `BuildDesiredTiles` also applies `RenderDistanceChunks`: the outermost shell widens to cover the distance — as level-MaxClipLevel MC tiles, or (F18 `bFarSheetRing`) as a SHEET ring at level MaxClipLevel+span (covered-check vs the MaxClipLevel box; `VF_OuterShell` shared with `IsTileInClipRange` so the cull sees the same horizon), dz pre-clamped to the vertical band. **§9.3 anchors:** also prunes dead `StreamingAnchors` + detects their chunk crossings → rebuilds the desired set when an anchor moves/(un)registers (`bAnchorsMoved`/`bForceDesiredRebuild`), same cadence as player movement. §8.10. | | `UpdateChunksAroundPosition` | 362 | Builds desired set, cancels pending keys that left it, sorts from the **true pawn position**, submits the occupied/support LOD0 tile and the next tile along the pawn heading as an absolute `BackgroundHigh` prefix, then loads/unloads and handles LOD changes. **Delta cull**: `BuildDesiredTiles` returns the LEAVERS (stamped `DesiredStamped` map, one sweep) — only those + `TransitionHold` are considered per crossing, not every loaded tile. `BuildDesiredTiles` also applies `RenderDistanceChunks`: the outermost shell widens to cover the distance — as level-MaxClipLevel MC tiles, or (F18 `bFarSheetRing`) as a SHEET ring at level MaxClipLevel+span (covered-check vs the MaxClipLevel box; `VF_OuterShell` shared with `IsTileInClipRange` so the cull sees the same horizon), dz pre-clamped to the vertical band. **§9.3 anchors:** also prunes dead `StreamingAnchors` + detects their chunk crossings → rebuilds the desired set when an anchor moves/(un)registers (`bAnchorsMoved`/`bForceDesiredRebuild`), same cadence as player movement. §8.10. |
+376 -24
View File
@@ -1538,12 +1538,13 @@ namespace
template <typename FVisit> template <typename FVisit>
static int32 VF_ForEachSpatialCandidate( static int32 VF_ForEachSpatialCandidate(
const FChunkSDFSpatialIndex& Index, int32 ItemCount, const FChunkSDFSpatialIndex& Index, int32 ItemCount,
float WorldX, float WorldY, FVisit&& Visit) float WorldX, float WorldY, FVisit&& Visit,
bool bUseSpatialIndex = true)
{ {
int32 Begin = 0; int32 Begin = 0;
int32 End = 0; int32 End = 0;
int32 CandidateCount = 0; int32 CandidateCount = 0;
if (Index.GetRange(WorldX, WorldY, Begin, End)) if (bUseSpatialIndex && Index.GetRange(WorldX, WorldY, Begin, End))
{ {
for (int32 Cursor = Begin; Cursor < End; ++Cursor) for (int32 Cursor = Begin; Cursor < End; ++Cursor)
{ {
@@ -1786,6 +1787,10 @@ struct FBuildRoomMouthRise
struct FVFRoomGraphReach struct FVFRoomGraphReach
{ {
float DirectRoomReach = 0.0f; float DirectRoomReach = 0.0f;
// Complete XY reach of a room body plus any authored terrain operation that can be selected
// for that room. This is also used by the STORE cull; leaving it out would drop a room whose
// pit/chimney/detail override extends beyond the base SDF body.
float TerrainOpReach = 0.0f;
float FloorReliefEnvelope = 0.0f; float FloorReliefEnvelope = 0.0f;
float BlendEnvelope = 0.0f; float BlendEnvelope = 0.0f;
float MaxTunnelLength = 0.0f; float MaxTunnelLength = 0.0f;
@@ -1808,8 +1813,324 @@ static float VF_RoomShapeReachUpperBound(float RadiusXY, float RadiusZ)
return FMath::Max3(Ellipsoid, RoundedBox, Capsule); return FMath::Max3(Ellipsoid, RoundedBox, Capsule);
} }
// Terrain operations are authored outside FStrateGenerationParams and are selected per room at
// cache-build time. The tile-window proof must therefore include both the transported params and
// every operation that the pool can select. The limit is deliberately finite: an authored feature
// beyond it uses the legacy worker window, while the fixed-margin proof remains valid for all
// assets that stay inside this envelope.
static constexpr float VF_MaxProvenTerrainOpExtent = 256.0f;
static bool VF_ComputeTerrainOpEnvelope(
const FStrateGenerationParams& Params,
const TArray<FStrateTerrainOpEntry>* TerrainOps,
float BaseRoomReach,
float AllRoomRadiusEnvelope,
float BlendEnvelope,
float& OutReach)
{
float MaxReach = BaseRoomReach;
float MaxAuthoredExtent = 0.0f;
bool bHasActiveTerrainOperation = false;
auto RecordAuthoredExtent = [&](float Extent) -> bool
{
if (!FMath::IsFinite(Extent) || Extent < 0.0f)
{
return false;
}
bHasActiveTerrainOperation = true;
MaxAuthoredExtent = FMath::Max(MaxAuthoredExtent, Extent);
return MaxAuthoredExtent <= VF_MaxProvenTerrainOpExtent;
};
auto RecordReach = [&](float Reach) -> bool
{
if (!FMath::IsFinite(Reach) || Reach < 0.0f)
{
return false;
}
MaxReach = FMath::Max(MaxReach, Reach);
return FMath::IsFinite(MaxReach);
};
auto RecordDetail = [&](float EffectRange) -> bool
{
return RecordAuthoredExtent(EffectRange)
&& RecordReach(BaseRoomReach + EffectRange);
};
auto RecordStructural = [&](float EffectExtent, float FeatureReach) -> bool
{
return RecordAuthoredExtent(EffectExtent)
&& RecordReach(FeatureReach);
};
auto AccumulateParams = [&](const FStrateGenerationParams& OpParams) -> bool
{
const float OperationValues[] = {
OpParams.TerraceStepHeight,
OpParams.TerraceHardness,
OpParams.TerraceNoiseDisplacement,
OpParams.LayerLineSpacing,
OpParams.LayerLineDepth,
OpParams.OverhangStrength,
OpParams.OverhangDepth,
OpParams.OverhangFrequency,
OpParams.RibbingSpacing,
OpParams.RibbingDepth,
OpParams.CliffStrength,
OpParams.ScallopStrength,
OpParams.ScallopFrequency,
OpParams.ArchDensity,
OpParams.ArchMinRadius,
OpParams.ArchMaxRadius,
OpParams.ColumnDensity,
OpParams.ColumnMinRadius,
OpParams.ColumnMaxRadius,
OpParams.PitDensity,
OpParams.PitMinRadius,
OpParams.PitMaxRadius,
OpParams.PitDepth,
OpParams.ChimneyDensity,
OpParams.ChimneyMinRadius,
OpParams.ChimneyMaxRadius,
OpParams.ChimneyHeight,
OpParams.DomeDensity,
OpParams.DomeMinRadius,
OpParams.DomeMaxRadius,
OpParams.DomeHeightRatio,
OpParams.PinchDensity,
OpParams.PinchStrength,
OpParams.PinchLength};
for (const float Value : OperationValues)
{
if (!FMath::IsFinite(Value))
{
return false;
}
}
if (OpParams.TerraceStepHeight > 0.0f)
{
if (OpParams.TerraceHardness < 0.0f || OpParams.TerraceHardness > 1.0f
|| OpParams.TerraceNoiseDisplacement < 0.0f)
{
return false;
}
if (!RecordDetail(
OpParams.TerraceStepHeight * 3.0f
+ OpParams.TerraceNoiseDisplacement + 4.0f))
{
return false;
}
}
if (OpParams.LayerLineSpacing > 0.0f)
{
if (OpParams.LayerLineDepth < 0.0f
|| !RecordDetail(OpParams.LayerLineSpacing
+ OpParams.LayerLineDepth + 4.0f))
{
return false;
}
}
if (OpParams.RibbingSpacing > 0.0f)
{
if (OpParams.RibbingDepth < 0.0f
|| !RecordDetail(OpParams.RibbingSpacing
+ OpParams.RibbingDepth + 4.0f))
{
return false;
}
}
if (OpParams.CliffStrength > 0.0f)
{
if (!RecordDetail(OpParams.CliffStrength + 8.0f))
{
return false;
}
}
if (OpParams.ScallopStrength > 0.0f)
{
if (OpParams.ScallopFrequency <= 0.0f
|| !RecordDetail(
4.0f / OpParams.ScallopFrequency
+ OpParams.ScallopStrength + 4.0f))
{
return false;
}
}
if (OpParams.OverhangStrength > 0.0f)
{
if (OpParams.OverhangDepth < 0.0f
|| OpParams.OverhangFrequency <= 0.0f
|| !RecordDetail(OpParams.OverhangDepth * 2.0f + 4.0f))
{
return false;
}
}
if (OpParams.ArchDensity > 0.0f)
{
if (OpParams.ArchMinRadius < 0.0f || OpParams.ArchMaxRadius < 0.0f)
{
return false;
}
const float Radius = FMath::Max(OpParams.ArchMinRadius, OpParams.ArchMaxRadius);
if (!RecordStructural(
Radius + 4.0f,
AllRoomRadiusEnvelope * 1.2f + Radius + BlendEnvelope * 3.0f + 4.0f))
{
return false;
}
}
if (OpParams.ColumnDensity > 0.0f)
{
if (OpParams.ColumnMinRadius < 0.0f || OpParams.ColumnMaxRadius < 0.0f)
{
return false;
}
const float Radius = FMath::Max(OpParams.ColumnMinRadius, OpParams.ColumnMaxRadius);
if (!RecordStructural(
Radius + 6.0f,
AllRoomRadiusEnvelope * 0.75f + Radius + 6.0f))
{
return false;
}
}
if (OpParams.PitDensity > 0.0f)
{
if (OpParams.PitMinRadius < 0.0f || OpParams.PitMaxRadius < 0.0f
|| OpParams.PitDepth < 0.0f)
{
return false;
}
const float Radius = FMath::Max(OpParams.PitMinRadius, OpParams.PitMaxRadius);
if (!RecordStructural(
2.0f * Radius + OpParams.PitDepth + BlendEnvelope + 4.0f,
AllRoomRadiusEnvelope * 0.6f + 2.0f * Radius
+ OpParams.PitDepth + BlendEnvelope + 4.0f))
{
return false;
}
}
if (OpParams.ChimneyDensity > 0.0f)
{
if (OpParams.ChimneyMinRadius < 0.0f || OpParams.ChimneyMaxRadius < 0.0f
|| OpParams.ChimneyHeight < 0.0f)
{
return false;
}
const float Radius = FMath::Max(
OpParams.ChimneyMinRadius, OpParams.ChimneyMaxRadius);
if (!RecordStructural(
2.0f * Radius + OpParams.ChimneyHeight + BlendEnvelope + 4.0f,
AllRoomRadiusEnvelope * 0.6f + 2.0f * Radius
+ OpParams.ChimneyHeight + BlendEnvelope + 4.0f))
{
return false;
}
}
if (OpParams.DomeDensity > 0.0f)
{
if (OpParams.DomeMinRadius < 0.0f || OpParams.DomeMaxRadius < 0.0f
|| OpParams.DomeHeightRatio < 0.0f)
{
return false;
}
const float Radius = FMath::Max(OpParams.DomeMinRadius, OpParams.DomeMaxRadius);
if (!RecordStructural(
Radius * (1.0f + OpParams.DomeHeightRatio) + 4.0f,
AllRoomRadiusEnvelope * 0.4f + Radius
* (1.0f + OpParams.DomeHeightRatio) + 4.0f))
{
return false;
}
}
if (OpParams.PinchDensity > 0.0f)
{
if (OpParams.PinchStrength < 0.0f || OpParams.PinchLength < 0.0f)
{
return false;
}
const float Extent = FMath::Max(OpParams.PinchStrength, OpParams.PinchLength);
if (!RecordStructural(
Extent + 5.0f,
AllRoomRadiusEnvelope * 0.85f + Extent + 5.0f))
{
return false;
}
}
return true;
};
if (!AccumulateParams(Params))
{
return false;
}
if (TerrainOps != nullptr)
{
for (const FStrateTerrainOpEntry& Entry : *TerrainOps)
{
if (!FMath::IsFinite(Entry.Probability)
|| Entry.Probability < 0.0f || Entry.Probability > 1.0f
|| !FMath::IsFinite(Entry.Weight)
|| Entry.Weight < 0.0f || Entry.Weight > 3.0f)
{
return false;
}
if (Entry.Probability <= 0.0f || Entry.Weight <= 0.0f)
{
continue;
}
if (Entry.Operation.IsNull())
{
continue;
}
const UVoxelTerrainOpDefinition* Operation = Entry.Operation.Get();
if (Operation == nullptr)
{
// A referenced but unloaded asset is not a proof of absence. Keep the legacy
// window until the manager has a concrete definition to inspect.
return false;
}
switch (Operation->Type)
{
case EVoxelTerrainOpType::Terrace:
case EVoxelTerrainOpType::LayerLines:
case EVoxelTerrainOpType::Ribbing:
case EVoxelTerrainOpType::Cliff:
case EVoxelTerrainOpType::Scallop:
case EVoxelTerrainOpType::Overhang:
case EVoxelTerrainOpType::Arch:
case EVoxelTerrainOpType::Column:
case EVoxelTerrainOpType::Pit:
case EVoxelTerrainOpType::Chimney:
case EVoxelTerrainOpType::Dome:
case EVoxelTerrainOpType::Pinch:
break;
default:
return false;
}
FStrateGenerationParams OperationParams{};
Operation->ApplyTo(OperationParams, Entry.Weight);
if (!AccumulateParams(OperationParams))
{
return false;
}
}
}
// Keep the field at zero when the strate has no active terrain operation. The caller already
// has the base room envelope; using that same value as a second global room radius would make
// the legacy/no-op path store extra rooms and could change the canonical field. Active pools
// publish the wider envelope so the tile proof and STORE cull see pits/chimneys/detail reach.
OutReach = bHasActiveTerrainOperation ? MaxReach : 0.0f;
return FMath::IsFinite(OutReach)
&& FMath::IsFinite(MaxAuthoredExtent)
&& MaxAuthoredExtent <= VF_MaxProvenTerrainOpExtent;
}
static bool VF_ComputeRoomGraphReach( static bool VF_ComputeRoomGraphReach(
const FStrateGenerationParams& Params, const FStrateGenerationParams& Params,
const TArray<FStrateTerrainOpEntry>* TerrainOps,
FVFRoomGraphReach& OutReach) FVFRoomGraphReach& OutReach)
{ {
const float RelevantParams[] = { const float RelevantParams[] = {
@@ -1892,9 +2213,17 @@ static bool VF_ComputeRoomGraphReach(
FMath::Abs(Params.TunnelMaxRadius)) * 1.18f); FMath::Abs(Params.TunnelMaxRadius)) * 1.18f);
const float MaxTunnelLength = FMath::Max( const float MaxTunnelLength = FMath::Max(
Params.MaxTunnelLength, 0.0f); Params.MaxTunnelLength, 0.0f);
const float DirectRoomReach = VF_RoomShapeReachUpperBound( const float BaseDirectRoomReach = VF_RoomShapeReachUpperBound(
AllRoomRadiusEnvelope, AllRoomHeightEnvelope) AllRoomRadiusEnvelope, AllRoomHeightEnvelope)
+ FloorReliefEnvelope + BlendEnvelope * 3.0f; + FloorReliefEnvelope + BlendEnvelope * 3.0f;
float TerrainOpReach = BaseDirectRoomReach;
if (!VF_ComputeTerrainOpEnvelope(
Params, TerrainOps, BaseDirectRoomReach,
AllRoomRadiusEnvelope, BlendEnvelope, TerrainOpReach))
{
return false;
}
const float DirectRoomReach = FMath::Max(BaseDirectRoomReach, TerrainOpReach);
const float TunnelInfluence = FMath::Abs(Params.TunnelWarpStrength) const float TunnelInfluence = FMath::Abs(Params.TunnelWarpStrength)
+ TunnelRadiusEnvelope + BlendEnvelope * 3.0f; + TunnelRadiusEnvelope + BlendEnvelope * 3.0f;
const float MaxInfluence = FMath::Max(DirectRoomReach, TunnelInfluence); const float MaxInfluence = FMath::Max(DirectRoomReach, TunnelInfluence);
@@ -1958,6 +2287,7 @@ static bool VF_ComputeRoomGraphReach(
} }
OutReach.DirectRoomReach = DirectRoomReach; OutReach.DirectRoomReach = DirectRoomReach;
OutReach.TerrainOpReach = TerrainOpReach;
OutReach.FloorReliefEnvelope = FloorReliefEnvelope; OutReach.FloorReliefEnvelope = FloorReliefEnvelope;
OutReach.BlendEnvelope = BlendEnvelope; OutReach.BlendEnvelope = BlendEnvelope;
OutReach.MaxTunnelLength = MaxTunnelLength; OutReach.MaxTunnelLength = MaxTunnelLength;
@@ -2000,9 +2330,10 @@ static bool VF_MayHaveRoomGraphFeature(
{ {
return true; return true;
} }
const float RoomReach = VF_RoomShapeReachUpperBound( const float BaseRoomReach = VF_RoomShapeReachUpperBound(
Room.RadiusXY, Room.RadiusZ) Room.RadiusXY, Room.RadiusZ)
+ Reach.FloorReliefEnvelope + Reach.BlendEnvelope * 3.0f; + Reach.FloorReliefEnvelope + Reach.BlendEnvelope * 3.0f;
const float RoomReach = FMath::Max(BaseRoomReach, Reach.TerrainOpReach);
if (!FMath::IsFinite(RoomReach) || RoomReach < 0.0f) if (!FMath::IsFinite(RoomReach) || RoomReach < 0.0f)
{ {
return true; return true;
@@ -5474,7 +5805,8 @@ bool VoxelCaveMorphology::MayHaveFeatureInSearchBox(
float SearchMaxX, float SearchMaxY, float SearchMaxX, float SearchMaxY,
const FStrateGenerationParams& Params, const FStrateGenerationParams& Params,
uint32 Seed, int32 StrateIndex, uint32 Seed, int32 StrateIndex,
bool bUseZ, float SearchMinZ, float SearchMaxZ) bool bUseZ, float SearchMinZ, float SearchMaxZ,
const TArray<FStrateTerrainOpEntry>* TerrainOps)
{ {
if (!FMath::IsFinite(SearchMinX) || !FMath::IsFinite(SearchMinY) if (!FMath::IsFinite(SearchMinX) || !FMath::IsFinite(SearchMinY)
|| !FMath::IsFinite(SearchMaxX) || !FMath::IsFinite(SearchMaxY) || !FMath::IsFinite(SearchMaxX) || !FMath::IsFinite(SearchMaxY)
@@ -5496,7 +5828,7 @@ bool VoxelCaveMorphology::MayHaveFeatureInSearchBox(
} }
FVFRoomGraphReach Reach; FVFRoomGraphReach Reach;
if (!VF_ComputeRoomGraphReach(Params, Reach)) if (!VF_ComputeRoomGraphReach(Params, TerrainOps, Reach))
{ {
return true; return true;
} }
@@ -5639,10 +5971,11 @@ bool VoxelCaveMorphology::MayHaveFeatureInSearchBox(
} }
bool VoxelCaveMorphology::IsRoomGraphWindowInvariant( bool VoxelCaveMorphology::IsRoomGraphWindowInvariant(
const FStrateGenerationParams& Params) const FStrateGenerationParams& Params,
const TArray<FStrateTerrainOpEntry>* TerrainOps)
{ {
FVFRoomGraphReach Reach; FVFRoomGraphReach Reach;
return VF_ComputeRoomGraphReach(Params, Reach); return VF_ComputeRoomGraphReach(Params, TerrainOps, Reach);
} }
//============================================================================= //=============================================================================
@@ -5707,7 +6040,8 @@ void VoxelCaveMorphology::BuildChunkCache(
// behavior below. The tile-window optimization is only a field-preserving optimization for // behavior below. The tile-window optimization is only a field-preserving optimization for
// the finite parameter envelope accepted by VF_ComputeRoomGraphReach. // the finite parameter envelope accepted by VF_ComputeRoomGraphReach.
FVFRoomGraphReach FeatureReach; FVFRoomGraphReach FeatureReach;
const bool bHasFeatureReach = VF_ComputeRoomGraphReach(Params, FeatureReach); const bool bHasFeatureReach = VF_ComputeRoomGraphReach(
Params, TerrainOps, FeatureReach);
//========================================================================= //=========================================================================
// INFLUENCE RADII // INFLUENCE RADII
@@ -5801,7 +6135,9 @@ void VoxelCaveMorphology::BuildChunkCache(
auto RoomReachesSearchBox = [&](const FVector& C, float RadiusXY, float RadiusZ) -> bool auto RoomReachesSearchBox = [&](const FVector& C, float RadiusXY, float RadiusZ) -> bool
{ {
const float ShapeReach = VF_RoomShapeReachUpperBound(RadiusXY, RadiusZ); const float ShapeReach = VF_RoomShapeReachUpperBound(RadiusXY, RadiusZ);
const float Reach = ShapeReach + FloorReliefEnvelope + BlendEnvelope * 3.0f; const float Reach = FMath::Max(
ShapeReach + FloorReliefEnvelope + BlendEnvelope * 3.0f,
bHasFeatureReach ? FeatureReach.TerrainOpReach : 0.0f);
const float dx = FMath::Max3((float)(SearchMinX - C.X), 0.0f, (float)(C.X - SearchMaxX)); const float dx = FMath::Max3((float)(SearchMinX - C.X), 0.0f, (float)(C.X - SearchMaxX));
const float dy = FMath::Max3((float)(SearchMinY - C.Y), 0.0f, (float)(C.Y - SearchMaxY)); const float dy = FMath::Max3((float)(SearchMinY - C.Y), 0.0f, (float)(C.Y - SearchMaxY));
return (dx * dx + dy * dy) <= Reach * Reach; return (dx * dx + dy * dy) <= Reach * Reach;
@@ -6766,6 +7102,10 @@ void VoxelCaveMorphology::BuildChunkCache(
FMath::Abs(CR.ShapeA.Z)); FMath::Abs(CR.ShapeA.Z));
} }
MaxExtent = ShapeReach + FloorReliefEnvelope + BlendEnvelope * 3.0f; MaxExtent = ShapeReach + FloorReliefEnvelope + BlendEnvelope * 3.0f;
if (bHasFeatureReach)
{
MaxExtent = FMath::Max(MaxExtent, FeatureReach.TerrainOpReach);
}
CR.CullRadiusSq = MaxExtent * MaxExtent; CR.CullRadiusSq = MaxExtent * MaxExtent;
} }
@@ -7004,7 +7344,8 @@ float VoxelCaveMorphology::EvaluateSDFCached(
float WorldX, float WorldY, float WorldZ, float WorldX, float WorldY, float WorldZ,
const FChunkSDFCache& Cache, const FChunkSDFCache& Cache,
float SDFBlendRadius, float SDFBlendRadius,
int32* OutNearestRoomIdx) int32* OutNearestRoomIdx,
bool bUseSpatialIndex)
{ {
VoxelDensityProfile::FScopedTimer ProfileTimer( VoxelDensityProfile::FScopedTimer ProfileTimer(
VoxelDensityProfile::EBucket::RoomGraphSdf); VoxelDensityProfile::EBucket::RoomGraphSdf);
@@ -7117,7 +7458,8 @@ float VoxelCaveMorphology::EvaluateSDFCached(
}; };
const int32 RoomCandidateCount = VF_ForEachSpatialCandidate( const int32 RoomCandidateCount = VF_ForEachSpatialCandidate(
Cache.RoomSpatialIndex, Cache.Rooms.Num(), WorldX, WorldY, EvaluateRoom); Cache.RoomSpatialIndex, Cache.Rooms.Num(), WorldX, WorldY, EvaluateRoom,
bUseSpatialIndex);
if (VoxelDensityProfile::AreCountersEnabled()) if (VoxelDensityProfile::AreCountersEnabled())
{ {
VoxelDensityProfile::AddCounter( VoxelDensityProfile::AddCounter(
@@ -7148,7 +7490,7 @@ float VoxelCaveMorphology::EvaluateSDFCached(
}; };
VF_ForEachSpatialCandidate( VF_ForEachSpatialCandidate(
Cache.RoomFloorJoinSpatialIndex, Cache.RoomFloorJoins.Num(), Cache.RoomFloorJoinSpatialIndex, Cache.RoomFloorJoins.Num(),
WorldX, WorldY, EvaluateJoin); WorldX, WorldY, EvaluateJoin, bUseSpatialIndex);
//========================================================================= //=========================================================================
// Tunnel SDFs // Tunnel SDFs
@@ -7188,7 +7530,8 @@ float VoxelCaveMorphology::EvaluateSDFCached(
}; };
const int32 TunnelCandidateCount = VF_ForEachSpatialCandidate( const int32 TunnelCandidateCount = VF_ForEachSpatialCandidate(
Cache.TunnelSpatialIndex, Cache.Tunnels.Num(), WorldX, WorldY, EvaluateTunnel); Cache.TunnelSpatialIndex, Cache.Tunnels.Num(), WorldX, WorldY, EvaluateTunnel,
bUseSpatialIndex);
if (VoxelDensityProfile::AreCountersEnabled()) if (VoxelDensityProfile::AreCountersEnabled())
{ {
VoxelDensityProfile::AddCounter( VoxelDensityProfile::AddCounter(
@@ -7207,7 +7550,8 @@ float VoxelCaveMorphology::EvaluateSDFCached(
float VoxelCaveMorphology::EvaluateTunnelCoreSDF( float VoxelCaveMorphology::EvaluateTunnelCoreSDF(
float WorldX, float WorldY, float WorldZ, float WorldX, float WorldY, float WorldZ,
const FChunkSDFCache& Cache) const FChunkSDFCache& Cache,
bool bUseSpatialIndex)
{ {
const FVector Pos(WorldX, WorldY, WorldZ); const FVector Pos(WorldX, WorldY, WorldZ);
float MinSDF = FLT_MAX; float MinSDF = FLT_MAX;
@@ -7237,7 +7581,8 @@ float VoxelCaveMorphology::EvaluateTunnelCoreSDF(
}; };
VF_ForEachSpatialCandidate( VF_ForEachSpatialCandidate(
Cache.TunnelSpatialIndex, Cache.Tunnels.Num(), WorldX, WorldY, EvaluateTunnel); Cache.TunnelSpatialIndex, Cache.Tunnels.Num(), WorldX, WorldY, EvaluateTunnel,
bUseSpatialIndex);
return MinSDF; return MinSDF;
} }
@@ -7245,7 +7590,8 @@ float VoxelCaveMorphology::EvaluateTunnelCoreSDF(
FTunnelCoreWorldEvaluation VoxelCaveMorphology::EvaluateTunnelCoreWorld( FTunnelCoreWorldEvaluation VoxelCaveMorphology::EvaluateTunnelCoreWorld(
float WorldX, float WorldY, float WorldZ, float WorldX, float WorldY, float WorldZ,
const FChunkSDFCache& Cache, const FChunkSDFCache& Cache,
const FTunnelSupportFloorColumn* SupportColumn) const FTunnelSupportFloorColumn* SupportColumn,
bool bUseSpatialIndex)
{ {
VoxelDensityProfile::FScopedTimer ProfileTimer( VoxelDensityProfile::FScopedTimer ProfileTimer(
VoxelDensityProfile::EBucket::TunnelCoreWorld); VoxelDensityProfile::EBucket::TunnelCoreWorld);
@@ -7358,7 +7704,8 @@ FTunnelCoreWorldEvaluation VoxelCaveMorphology::EvaluateTunnelCoreWorld(
}; };
const int32 TunnelCandidateCount = VF_ForEachSpatialCandidate( const int32 TunnelCandidateCount = VF_ForEachSpatialCandidate(
Cache.TunnelSpatialIndex, Cache.Tunnels.Num(), WorldX, WorldY, EvaluateTunnel); Cache.TunnelSpatialIndex, Cache.Tunnels.Num(), WorldX, WorldY, EvaluateTunnel,
bUseSpatialIndex);
if (VoxelDensityProfile::AreCountersEnabled()) if (VoxelDensityProfile::AreCountersEnabled())
{ {
VoxelDensityProfile::AddCounter( VoxelDensityProfile::AddCounter(
@@ -7371,14 +7718,17 @@ FTunnelCoreWorldEvaluation VoxelCaveMorphology::EvaluateTunnelCoreWorld(
float VoxelCaveMorphology::EvaluateTunnelCoreWorldSDF( float VoxelCaveMorphology::EvaluateTunnelCoreWorldSDF(
float WorldX, float WorldY, float WorldZ, float WorldX, float WorldY, float WorldZ,
const FChunkSDFCache& Cache) const FChunkSDFCache& Cache,
bool bUseSpatialIndex)
{ {
return EvaluateTunnelCoreWorld(WorldX, WorldY, WorldZ, Cache).SDF; return EvaluateTunnelCoreWorld(
WorldX, WorldY, WorldZ, Cache, nullptr, bUseSpatialIndex).SDF;
} }
bool VoxelCaveMorphology::IsTunnelSupportFloorWorldPoint( bool VoxelCaveMorphology::IsTunnelSupportFloorWorldPoint(
float WorldX, float WorldY, float WorldZ, float WorldX, float WorldY, float WorldZ,
const FChunkSDFCache& Cache) const FChunkSDFCache& Cache,
bool bUseSpatialIndex)
{ {
const FVector Pos(WorldX, WorldY, WorldZ); const FVector Pos(WorldX, WorldY, WorldZ);
@@ -7462,7 +7812,7 @@ bool VoxelCaveMorphology::IsTunnelSupportFloorWorldPoint(
int32 Begin = 0; int32 Begin = 0;
int32 End = 0; int32 End = 0;
if (Cache.TunnelSpatialIndex.GetRange(WorldX, WorldY, Begin, End)) if (bUseSpatialIndex && Cache.TunnelSpatialIndex.GetRange(WorldX, WorldY, Begin, End))
{ {
TestRange(Begin, End); TestRange(Begin, End);
} }
@@ -7494,7 +7844,8 @@ bool VoxelCaveMorphology::IsTunnelSupportFloorWorldPoint(
void VoxelCaveMorphology::BuildTunnelSupportFloorColumn( void VoxelCaveMorphology::BuildTunnelSupportFloorColumn(
float WorldX, float WorldY, float WorldX, float WorldY,
const FChunkSDFCache& Cache, const FChunkSDFCache& Cache,
FTunnelSupportFloorColumn& OutColumn) FTunnelSupportFloorColumn& OutColumn,
bool bUseSpatialIndex)
{ {
VoxelDensityProfile::FScopedTimer ProfileTimer( VoxelDensityProfile::FScopedTimer ProfileTimer(
VoxelDensityProfile::EBucket::TunnelCoreSupport); VoxelDensityProfile::EBucket::TunnelCoreSupport);
@@ -7552,7 +7903,8 @@ void VoxelCaveMorphology::BuildTunnelSupportFloorColumn(
} }
}; };
const int32 CandidateCount = VF_ForEachSpatialCandidate( const int32 CandidateCount = VF_ForEachSpatialCandidate(
Cache.TunnelSpatialIndex, Cache.Tunnels.Num(), WorldX, WorldY, EvaluateTunnel); Cache.TunnelSpatialIndex, Cache.Tunnels.Num(), WorldX, WorldY, EvaluateTunnel,
bUseSpatialIndex);
if (VoxelDensityProfile::AreCountersEnabled()) if (VoxelDensityProfile::AreCountersEnabled())
{ {
VoxelDensityProfile::AddCounter( VoxelDensityProfile::AddCounter(
+120 -71
View File
@@ -3822,6 +3822,21 @@ namespace
* voxel PRÈS D'UNE SURFACE, pas partout) sans que onze opérateurs la refassent chacun. */ * voxel PRÈS D'UNE SURFACE, pas partout) sans que onze opérateurs la refassent chacun. */
FStrateGenerationParams LocalParams; FStrateGenerationParams LocalParams;
bool bLocalParamsValid = false; bool bLocalParamsValid = false;
// Window invariance is a cache-policy proof, not a sample evaluator. Keep its result
// beside the worker-local source state so LOD0 does not pay the full reach walk once
// per voxel.
bool bTileCachePolicyValid = false;
bool bTileCachePolicyResult = false;
bool bTileCachePolicyHasContext = false;
int32 TileCachePolicySampleStep = 0;
FIntVector TileCachePolicyOrigin = FIntVector::ZeroValue;
int32 TileCachePolicyStep = 0;
int32 TileCachePolicyCells = 0;
uint32 TileCachePolicyFingerprint = 0xFFFFFFFFu;
uint32 TileCachePolicyLayout = 0xFFFFFFFFu;
uint64 TileCachePolicyManagerLifetimeId = 0;
const TArray<FStrateTerrainOpEntry>* TileCachePolicyTerrainOps = nullptr;
}; };
FState& State() const FState& State() const
@@ -3861,7 +3876,8 @@ namespace
FTunnelSupportFloorColumn& OutColumn) const FTunnelSupportFloorColumn& OutColumn) const
{ {
VoxelCaveMorphology::BuildTunnelSupportFloorColumn( VoxelCaveMorphology::BuildTunnelSupportFloorColumn(
WorldX, WorldY, Cache, OutColumn); WorldX, WorldY, Cache, OutColumn,
VoxelGenLOD::ShouldUseSpatialIndex(false));
} }
const FTunnelSupportFloorColumn* GetSupportColumn(const FVector& Position) const const FTunnelSupportFloorColumn* GetSupportColumn(const FVector& Position) const
@@ -3917,7 +3933,8 @@ namespace
const FVector& Position = S.LastWorldPosition; const FVector& Position = S.LastWorldPosition;
const FTunnelSupportFloorColumn* SupportColumn = GetSupportColumn(Position); const FTunnelSupportFloorColumn* SupportColumn = GetSupportColumn(Position);
S.LastTunnelCoreWorldEvaluation = VoxelCaveMorphology::EvaluateTunnelCoreWorld( S.LastTunnelCoreWorldEvaluation = VoxelCaveMorphology::EvaluateTunnelCoreWorld(
Position.X, Position.Y, Position.Z, GetCache(), SupportColumn); Position.X, Position.Y, Position.Z, GetCache(), SupportColumn,
VoxelGenLOD::ShouldUseSpatialIndex(false));
S.LastTunnelCoreEvaluationPosition = Position; S.LastTunnelCoreEvaluationPosition = Position;
S.bLastTunnelCoreWorldEvaluationValid = true; S.bLastTunnelCoreWorldEvaluationValid = true;
} }
@@ -3991,7 +4008,9 @@ namespace
*/ */
float ProbeSdfUnwarped(float X, float Y, float Z) const float ProbeSdfUnwarped(float X, float Y, float Z) const
{ {
return VoxelCaveMorphology::EvaluateSDFCached(X, Y, Z, GetCache(), P.SDFBlendRadius); return VoxelCaveMorphology::EvaluateSDFCached(
X, Y, Z, GetCache(), P.SDFBlendRadius, nullptr,
VoxelGenLOD::ShouldUseSpatialIndex(false));
} }
/** Le Z « effectif » : `VerticalScale` étire le monde AVANT le bruit. Pure fonction de Z et /** Le Z « effectif » : `VerticalScale` étire le monde AVANT le bruit. Pure fonction de Z et
@@ -4126,10 +4145,47 @@ namespace
FIntVector RequestedTileOrigin = FIntVector::ZeroValue; FIntVector RequestedTileOrigin = FIntVector::ZeroValue;
int32 RequestedTileStep = SampleStep; int32 RequestedTileStep = SampleStep;
int32 RequestedTileCells = 0; int32 RequestedTileCells = 0;
const bool bUseTileCacheWindow = VoxelGenLOD::IsTileCacheWindowEnabled() const TArray<FStrateTerrainOpEntry>* TerrainOps = nullptr;
&& VoxelGenLOD::GetThreadTileCacheWindow( if (LiveManager)
RequestedTileOrigin, RequestedTileStep, RequestedTileCells) {
&& VoxelCaveMorphology::IsRoomGraphWindowInvariant(P); const int32 ChunkX = FMath::FloorToInt(WorldX / (float)CHUNK_SIZE);
const int32 ChunkY = FMath::FloorToInt(WorldY / (float)CHUNK_SIZE);
const int32 ChunkZ = FMath::FloorToInt(WorldZ / (float)CHUNK_SIZE);
UVoxelStrateDefinition* Def = LiveManager->GetStrateForChunk(
FIntVector(ChunkX, ChunkY, ChunkZ));
if (Def) { TerrainOps = &Def->TerrainOperations; }
}
const bool bTileWindowEligible = VoxelGenLOD::IsTileCacheWindowEnabled(false);
const bool bHasTileWindowContext = VoxelGenLOD::GetThreadTileCacheWindow(
RequestedTileOrigin, RequestedTileStep, RequestedTileCells);
const bool bTilePolicyKeyChanged =
!S.bTileCachePolicyValid
|| S.bTileCachePolicyHasContext
!= (bTileWindowEligible && bHasTileWindowContext)
|| S.TileCachePolicySampleStep != RequestedTileStep
|| S.TileCachePolicyOrigin != RequestedTileOrigin
|| S.TileCachePolicyStep != RequestedTileStep
|| S.TileCachePolicyCells != RequestedTileCells
|| S.TileCachePolicyFingerprint != ParamsFingerprint
|| S.TileCachePolicyLayout != LayoutVersion
|| S.TileCachePolicyManagerLifetimeId != ManagerLifetimeId
|| S.TileCachePolicyTerrainOps != TerrainOps;
if (bTilePolicyKeyChanged)
{
S.bTileCachePolicyValid = true;
S.bTileCachePolicyHasContext = bTileWindowEligible && bHasTileWindowContext;
S.TileCachePolicySampleStep = RequestedTileStep;
S.TileCachePolicyOrigin = RequestedTileOrigin;
S.TileCachePolicyStep = RequestedTileStep;
S.TileCachePolicyCells = RequestedTileCells;
S.TileCachePolicyFingerprint = ParamsFingerprint;
S.TileCachePolicyLayout = LayoutVersion;
S.TileCachePolicyManagerLifetimeId = ManagerLifetimeId;
S.TileCachePolicyTerrainOps = TerrainOps;
S.bTileCachePolicyResult = S.bTileCachePolicyHasContext
&& VoxelCaveMorphology::IsRoomGraphWindowInvariant(P, TerrainOps);
}
const bool bUseTileCacheWindow = S.bTileCachePolicyResult;
int32 RegionSize = 0; int32 RegionSize = 0;
int32 RegionMinX = 0; int32 RegionMinX = 0;
int32 RegionMinY = 0; int32 RegionMinY = 0;
@@ -4143,9 +4199,11 @@ namespace
} }
else else
{ {
const int32 RegionChunks = SampleStep <= 1 // Fine requests retain the historical four-chunk worker window. The op-stack
? 1 // tile-sized window is gated to LOD4+ (the fused path starts at LOD3), so
: (SampleStep >= 32 ? 4 : FMath::Max(4, (SampleStep + 3) / 4)); // LOD0-LOD2 and the op-stack's LOD3 floor do not pay a full tile rebuild on their
// critical path while still reusing neighbouring chunks on the same worker.
const int32 RegionChunks = 4;
RegionSize = CHUNK_SIZE * RegionChunks; RegionSize = CHUNK_SIZE * RegionChunks;
const int32 RegionCellX = FMath::FloorToInt(WorldX / (float)RegionSize); const int32 RegionCellX = FMath::FloorToInt(WorldX / (float)RegionSize);
const int32 RegionCellY = FMath::FloorToInt(WorldY / (float)RegionSize); const int32 RegionCellY = FMath::FloorToInt(WorldY / (float)RegionSize);
@@ -4157,17 +4215,6 @@ namespace
+ (bUseTileCacheWindow ? static_cast<float>(SampleStep) : 0.0f) + (bUseTileCacheWindow ? static_cast<float>(SampleStep) : 0.0f)
+ 2.0f; + 2.0f;
const TArray<FStrateTerrainOpEntry>* TerrainOps = nullptr;
if (LiveManager)
{
const int32 ChunkX = FMath::FloorToInt(WorldX / (float)CHUNK_SIZE);
const int32 ChunkY = FMath::FloorToInt(WorldY / (float)CHUNK_SIZE);
const int32 ChunkZ = FMath::FloorToInt(WorldZ / (float)CHUNK_SIZE);
UVoxelStrateDefinition* Def = LiveManager->GetStrateForChunk(
FIntVector(ChunkX, ChunkY, ChunkZ));
if (Def) { TerrainOps = &Def->TerrainOperations; }
}
const FChunkSDFCache* PreviousActiveCache = S.ActiveCache; const FChunkSDFCache* PreviousActiveCache = S.ActiveCache;
bool bRebuiltLocalCache = false; bool bRebuiltLocalCache = false;
if (SampleStep > 1) if (SampleStep > 1)
@@ -4247,9 +4294,10 @@ namespace
S.SupportColumns.Invalidate(); S.SupportColumns.Invalidate();
} }
const bool bUseSpatialIndex = VoxelGenLOD::ShouldUseSpatialIndex(false);
float CaveSDF = VoxelCaveMorphology::EvaluateSDFCached( float CaveSDF = VoxelCaveMorphology::EvaluateSDFCached(
WarpedX, WarpedY, WarpedZ, GetCache(), P.SDFBlendRadius, WarpedX, WarpedY, WarpedZ, GetCache(), P.SDFBlendRadius,
&S.NearestRoom); &S.NearestRoom, bUseSpatialIndex);
//--------------------------------------------------------------- //---------------------------------------------------------------
// PITS & CHEMINÉES — coordonnées RÉELLES, SmoothMin dans le même canal SDF // PITS & CHEMINÉES — coordonnées RÉELLES, SmoothMin dans le même canal SDF
@@ -4289,7 +4337,7 @@ namespace
}; };
VF_ForEachChunkSDFSpatialCandidate( VF_ForEachChunkSDFSpatialCandidate(
GetCache().PitSpatialIndex, GetCache().Pits.Num(), GetCache().PitSpatialIndex, GetCache().Pits.Num(),
WorldX, WorldY, EvaluatePit); WorldX, WorldY, EvaluatePit, bUseSpatialIndex);
auto EvaluateChimney = [&](int32 ChimneyIndex) auto EvaluateChimney = [&](int32 ChimneyIndex)
{ {
@@ -4320,7 +4368,7 @@ namespace
}; };
VF_ForEachChunkSDFSpatialCandidate( VF_ForEachChunkSDFSpatialCandidate(
GetCache().ChimneySpatialIndex, GetCache().Chimneys.Num(), GetCache().ChimneySpatialIndex, GetCache().Chimneys.Num(),
WorldX, WorldY, EvaluateChimney); WorldX, WorldY, EvaluateChimney, bUseSpatialIndex);
InOut.Sdf = CaveSDF; InOut.Sdf = CaveSDF;
@@ -4332,7 +4380,8 @@ namespace
const FTunnelSupportFloorColumn* SupportColumn = GetSupportColumn( const FTunnelSupportFloorColumn* SupportColumn = GetSupportColumn(
S.LastWorldPosition); S.LastWorldPosition);
S.LastTunnelCoreWorldEvaluation = VoxelCaveMorphology::EvaluateTunnelCoreWorld( S.LastTunnelCoreWorldEvaluation = VoxelCaveMorphology::EvaluateTunnelCoreWorld(
WorldX, WorldY, WorldZ, GetCache(), SupportColumn); WorldX, WorldY, WorldZ, GetCache(), SupportColumn,
bUseSpatialIndex);
S.LastTunnelCoreEvaluationPosition = S.LastWorldPosition; S.LastTunnelCoreEvaluationPosition = S.LastWorldPosition;
S.bLastTunnelCoreWorldEvaluationValid = true; S.bLastTunnelCoreWorldEvaluationValid = true;
if (S.LastTunnelCoreWorldEvaluation.bSupportFloor if (S.LastTunnelCoreWorldEvaluation.bSupportFloor
@@ -4782,13 +4831,49 @@ namespace
return; return;
} }
int32 StrateIdx = 0;
const TArray<FStrateTerrainOpEntry>* TerrainOps = nullptr;
if (LiveManager)
{
StrateIdx = LiveManager->GetStrateIndex(
((float)CZ0 + 0.5f) * CHUNK_SIZE * VOXEL_SIZE);
for (int32 CZ = CZ0 + 1; CZ <= CZ1; ++CZ)
{
if (LiveManager->GetStrateIndex(
((float)CZ + 0.5f) * CHUNK_SIZE * VOXEL_SIZE) != StrateIdx)
{
Unknown();
return;
}
}
// A room operation is part of the graph cache. Every Z slice covered by this
// proof must resolve to the same definition before one cache can represent it.
UVoxelStrateDefinition* Def0 = nullptr;
for (int32 CZ = CZ0; CZ <= CZ1; ++CZ)
{
UVoxelStrateDefinition* Def =
LiveManager->GetStrateForChunk(FIntVector(0, 0, CZ));
if (CZ == CZ0)
{
Def0 = Def;
}
else if (Def != Def0)
{
Unknown();
return;
}
}
if (Def0) { TerrainOps = &Def0->TerrainOperations; }
}
FIntVector RequestedTileOrigin = FIntVector::ZeroValue; FIntVector RequestedTileOrigin = FIntVector::ZeroValue;
int32 RequestedTileStep = FMath::Max(Ctx.Step, 1); int32 RequestedTileStep = FMath::Max(Ctx.Step, 1);
int32 RequestedTileCells = 0; int32 RequestedTileCells = 0;
const bool bUseTileCacheWindow = VoxelGenLOD::IsTileCacheWindowEnabled() const bool bUseTileCacheWindow = VoxelGenLOD::IsTileCacheWindowEnabled(false)
&& VoxelGenLOD::GetThreadTileCacheWindow( && VoxelGenLOD::GetThreadTileCacheWindow(
RequestedTileOrigin, RequestedTileStep, RequestedTileCells) RequestedTileOrigin, RequestedTileStep, RequestedTileCells)
&& VoxelCaveMorphology::IsRoomGraphWindowInvariant(P); && VoxelCaveMorphology::IsRoomGraphWindowInvariant(P, TerrainOps);
const float RequestedTileExtent = static_cast<float>( const float RequestedTileExtent = static_cast<float>(
static_cast<int64>(RequestedTileStep) static_cast<int64>(RequestedTileStep)
* static_cast<int64>(RequestedTileCells)); * static_cast<int64>(RequestedTileCells));
@@ -4849,46 +4934,6 @@ namespace
return; return;
} }
int32 StrateIdx = 0;
const TArray<FStrateTerrainOpEntry>* TerrainOps = nullptr;
if (LiveManager)
{
StrateIdx = LiveManager->GetStrateIndex(
((float)CZ0 + 0.5f) * CHUNK_SIZE * VOXEL_SIZE);
for (int32 CZ = CZ0 + 1; CZ <= CZ1; ++CZ)
{
if (LiveManager->GetStrateIndex(
((float)CZ + 0.5f) * CHUNK_SIZE * VOXEL_SIZE) != StrateIdx)
{
Unknown();
return;
}
}
// The room-op pool is part of the SDF: BuildChunkCache bakes pits, chimneys, and
// columns from it. Every chunk touched by this query must therefore resolve to the
// same definition before one cache can represent the box.
// GetStrateForChunk is a Z-layout lookup; the manager ignores
// X/Y. Check one representative per touched Z instead of
// repeating the same layout search for every coarse XY key.
UVoxelStrateDefinition* Def0 = nullptr;
for (int32 CZ = CZ0; CZ <= CZ1; ++CZ)
{
UVoxelStrateDefinition* Def =
LiveManager->GetStrateForChunk(FIntVector(0, 0, CZ));
if (CZ == CZ0)
{
Def0 = Def;
}
else if (Def != Def0)
{
Unknown();
return;
}
}
if (Def0) { TerrainOps = &Def0->TerrainOperations; }
}
const uint32 LV = Ctx.LayoutVersion; const uint32 LV = Ctx.LayoutVersion;
auto IsSharedCacheCurrent = [&]() -> bool auto IsSharedCacheCurrent = [&]() -> bool
{ {
@@ -5085,7 +5130,8 @@ namespace
CacheSearchMaxY, CacheSearchMaxY,
P, SeedU, StrateIdx, true, P, SeedU, StrateIdx, true,
CacheSearchMinZ, CacheSearchMinZ,
CacheSearchMaxZ); CacheSearchMaxZ,
TerrainOps);
const int64 MaxSharedChunkSpan = Ctx.Step >= 32 ? 32 : 16; const int64 MaxSharedChunkSpan = Ctx.Step >= 32 ? 32 : 16;
bool bUsedSharedCache = false; bool bUsedSharedCache = false;
const bool bTileWithinSharedBudget = bUseTileCacheWindow const bool bTileWithinSharedBudget = bUseTileCacheWindow
@@ -6747,13 +6793,15 @@ namespace
{ {
VoxelCaveMorphology::BuildTunnelSupportFloorColumn( VoxelCaveMorphology::BuildTunnelSupportFloorColumn(
static_cast<float>(World.X), static_cast<float>(World.Y), static_cast<float>(World.X), static_cast<float>(World.Y),
GetBoxCache(), EmptySupportColumn); GetBoxCache(), EmptySupportColumn,
VoxelGenLOD::ShouldUseSpatialIndex(false));
SupportColumn = &EmptySupportColumn; SupportColumn = &EmptySupportColumn;
} }
const FTunnelCoreWorldEvaluation Evaluation = const FTunnelCoreWorldEvaluation Evaluation =
VoxelCaveMorphology::EvaluateTunnelCoreWorld( VoxelCaveMorphology::EvaluateTunnelCoreWorld(
static_cast<float>(World.X), static_cast<float>(World.Y), static_cast<float>(World.X), static_cast<float>(World.Y),
static_cast<float>(World.Z), GetBoxCache(), SupportColumn); static_cast<float>(World.Z), GetBoxCache(), SupportColumn,
VoxelGenLOD::ShouldUseSpatialIndex(false));
++B.ExactTailEvaluated; ++B.ExactTailEvaluated;
if (!FMath::IsFinite(Evaluation.SDF)) if (!FMath::IsFinite(Evaluation.SDF))
{ {
@@ -8062,7 +8110,8 @@ namespace
}; };
VF_ForEachChunkSDFSpatialCandidate( VF_ForEachChunkSDFSpatialCandidate(
Rooms->GetCache().ColumnSpatialIndex, Rooms->GetCache().ColumnSpatialIndex,
Rooms->GetCache().Columns.Num(), WorldX, WorldY, EvaluateColumn); Rooms->GetCache().Columns.Num(), WorldX, WorldY, EvaluateColumn,
VoxelGenLOD::ShouldUseSpatialIndex(false));
} }
/** The cached column list is the complete source of this op's work. A valid empty list is /** The cached column list is the complete source of this op's work. A valid empty list is
+176 -28
View File
@@ -56,15 +56,34 @@ namespace
} }
} }
// A tile-sized cache window is the production default. Keep the switch in the generator // A tile-sized cache window is the production default on the coarse paths where it wins.
// module so native and op-stack sources make the same A/B decision, including when a // Keep the switches in the generator module so native and op-stack sources make the same A/B
// headless harness applies command-line CVars after module startup. // decision, including when a headless harness applies command-line CVars after module startup.
int32 GVoxelForgeTileCacheWindow = 1; int32 GVoxelForgeTileCacheWindow = 1;
FAutoConsoleVariableRef CVarVoxelForgeTileCacheWindow( FAutoConsoleVariableRef CVarVoxelForgeTileCacheWindow(
TEXT("voxel.TileCacheWindow"), TEXT("voxel.TileCacheWindow"),
GVoxelForgeTileCacheWindow, GVoxelForgeTileCacheWindow,
TEXT("Use the requesting tile's aligned footprint plus the deterministic warp/halo margin for room caches.")); TEXT("Use the requesting tile's aligned footprint plus the deterministic warp/halo margin for room caches."));
bool GVoxelForgeTileCacheWindowSwitchParsed = false; 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() void VF_ParseTileCacheWindowSwitch()
{ {
@@ -80,6 +99,42 @@ namespace
GVoxelForgeTileCacheWindow = CommandLineValue; GVoxelForgeTileCacheWindow = CommandLineValue;
} }
GVoxelForgeTileCacheWindow = GVoxelForgeTileCacheWindow != 0 ? 1 : 0; 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; SampleStep = Value;
} }
bool VoxelGenLOD::IsTileCacheWindowEnabled() bool VoxelGenLOD::IsTileCacheWindowEnabled(bool bFusedPath)
{ {
VF_ParseTileCacheWindowSwitch(); 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( bool VoxelGenLOD::GetThreadTileCacheWindow(
@@ -485,6 +548,25 @@ bool VoxelGenLOD::GetThreadTileCacheWindow(
&& Extent <= static_cast<int64>(CHUNK_SIZE) * 64; && 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 // 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 // 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, // sample a DIFFERENT (float hash-gradient) noise field than the old FMath::PerlinNoise3D,
@@ -1244,7 +1326,8 @@ namespace
static void BuildTunnelSupportColumnsForChunk( static void BuildTunnelSupportColumnsForChunk(
FChunkSDFCache& Cache, FChunkSDFCache& Cache,
const FIntVector& ChunkCoord); const FIntVector& ChunkCoord,
bool bUseSpatialIndex);
static const FTunnelSupportFloorColumn* FindTunnelSupportColumn( static const FTunnelSupportFloorColumn* FindTunnelSupportColumn(
const FChunkSDFCache& Cache, const FChunkSDFCache& Cache,
@@ -1353,10 +1436,16 @@ namespace
FIntVector RequestedTileOrigin = FIntVector::ZeroValue; FIntVector RequestedTileOrigin = FIntVector::ZeroValue;
int32 RequestedTileStep = 1; int32 RequestedTileStep = 1;
int32 RequestedTileCells = 0; 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( && VoxelGenLOD::GetThreadTileCacheWindow(
RequestedTileOrigin, RequestedTileStep, RequestedTileCells) RequestedTileOrigin, RequestedTileStep, RequestedTileCells)
&& VoxelCaveMorphology::IsRoomGraphWindowInvariant(Params); && VoxelCaveMorphology::IsRoomGraphWindowInvariant(Params, TerrainOps);
const bool bUseSpatialIndex = VoxelGenLOD::ShouldUseSpatialIndex(true);
const int32 StrateIndex = Manager.GetStrateIndex( const int32 StrateIndex = Manager.GetStrateIndex(
(static_cast<float>(ChunkCoord.Z) + 0.5f) (static_cast<float>(ChunkCoord.Z) + 0.5f)
* static_cast<float>(CHUNK_SIZE) * VOXEL_SIZE); * 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. // density cursor crosses a chunk; the room/tunnel graph itself is not rebuilt.
if (OutState.SupportChunk != ChunkCoord) if (OutState.SupportChunk != ChunkCoord)
{ {
BuildTunnelSupportColumnsForChunk(OutState.Cache, ChunkCoord); BuildTunnelSupportColumnsForChunk(
OutState.Cache, ChunkCoord, bUseSpatialIndex);
OutState.SupportChunk = ChunkCoord; OutState.SupportChunk = ChunkCoord;
} }
OutState.Chunk = ChunkCoord; OutState.Chunk = ChunkCoord;
@@ -1421,7 +1511,7 @@ namespace
StrateIndex, nullptr, StrateIndex, nullptr,
ERoomGraphBuildSite::GeneratorTunnelCore); ERoomGraphBuildSite::GeneratorTunnelCore);
BuildTunnelSupportColumnsForChunk( BuildTunnelSupportColumnsForChunk(
OutState.Cache, ChunkCoord); OutState.Cache, ChunkCoord, bUseSpatialIndex);
OutState.OwnerId = OwnerId; OutState.OwnerId = OwnerId;
OutState.ManagerLifetimeId = ManagerLifetimeId; OutState.ManagerLifetimeId = ManagerLifetimeId;
OutState.Chunk = ChunkCoord; OutState.Chunk = ChunkCoord;
@@ -1440,7 +1530,8 @@ namespace
static void BuildTunnelSupportColumnsForChunk( static void BuildTunnelSupportColumnsForChunk(
FChunkSDFCache& Cache, FChunkSDFCache& Cache,
const FIntVector& ChunkCoord) const FIntVector& ChunkCoord,
bool bUseSpatialIndex)
{ {
const int32 MinX = ChunkCoord.X * CHUNK_SIZE - 1; const int32 MinX = ChunkCoord.X * CHUNK_SIZE - 1;
const int32 MinY = ChunkCoord.Y * CHUNK_SIZE - 1; const int32 MinY = ChunkCoord.Y * CHUNK_SIZE - 1;
@@ -1463,7 +1554,8 @@ namespace
static_cast<float>(MinX + X), static_cast<float>(MinX + X),
static_cast<float>(MinY + Y), static_cast<float>(MinY + Y),
Cache, Cache,
Column); Column,
bUseSpatialIndex);
if (Column.Intervals.Num() == 0) if (Column.Intervals.Num() == 0)
{ {
continue; continue;
@@ -1805,7 +1897,8 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co
FIntVector RequestedTileOrigin = FIntVector::ZeroValue; FIntVector RequestedTileOrigin = FIntVector::ZeroValue;
int32 RequestedTileStep = 1; int32 RequestedTileStep = 1;
int32 RequestedTileCells = 0; int32 RequestedTileCells = 0;
const bool bTileCacheContext = VoxelGenLOD::IsTileCacheWindowEnabled() const bool bTileCacheContext = VoxelGenLOD::IsTileCacheWindowEnabled(
!bShareTunnelStackByZ)
&& VoxelGenLOD::GetThreadTileCacheWindow( && VoxelGenLOD::GetThreadTileCacheWindow(
RequestedTileOrigin, RequestedTileStep, RequestedTileCells); RequestedTileOrigin, RequestedTileStep, RequestedTileCells);
FVoxelOpStack* ActiveOpStack = &CP_OpStack; FVoxelOpStack* ActiveOpStack = &CP_OpStack;
@@ -2352,7 +2445,9 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co
VoxelDensityProfile::FScopedTimer ProfileTimer( VoxelDensityProfile::FScopedTimer ProfileTimer(
VoxelDensityProfile::EBucket::TunnelCorePosts); VoxelDensityProfile::EBucket::TunnelCorePosts);
PreDisturbanceTunnelCore = VoxelCaveMorphology::EvaluateTunnelCoreWorld( PreDisturbanceTunnelCore = VoxelCaveMorphology::EvaluateTunnelCoreWorld(
WorldX, WorldY, WorldZ, ActiveTunnelCoreCache->Cache, SupportColumn); WorldX, WorldY, WorldZ, ActiveTunnelCoreCache->Cache, SupportColumn,
VoxelGenLOD::ShouldUseSpatialIndex(
CP_UseOpStack ? bUsedFusedEvaluator : true));
bHavePreDisturbanceTunnelCore = true; bHavePreDisturbanceTunnelCore = true;
} }
const bool bProtectAuthoredTunnelFloor = bHavePreDisturbanceTunnelCore const bool bProtectAuthoredTunnelFloor = bHavePreDisturbanceTunnelCore
@@ -2499,7 +2594,9 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co
VoxelDensityProfile::EBucket::TunnelCorePosts); VoxelDensityProfile::EBucket::TunnelCorePosts);
TunnelCore = VoxelCaveMorphology::EvaluateTunnelCoreWorld( TunnelCore = VoxelCaveMorphology::EvaluateTunnelCoreWorld(
WorldX, WorldY, WorldZ, ActiveTunnelCoreCache->Cache, WorldX, WorldY, WorldZ, ActiveTunnelCoreCache->Cache,
SupportColumn); SupportColumn,
VoxelGenLOD::ShouldUseSpatialIndex(
CP_UseOpStack ? bUsedFusedEvaluator : true));
bHaveTunnelCore = true; bHaveTunnelCore = true;
} }
if (bHaveTunnelCore) if (bHaveTunnelCore)
@@ -2831,15 +2928,27 @@ float UVoxelGenerator::GetDensityWithParams(float WorldX, float WorldY, float Wo
thread_local int32 CachedTileCells = 0; thread_local int32 CachedTileCells = 0;
thread_local uint64 CachedManagerLifetimeId = 0; thread_local uint64 CachedManagerLifetimeId = 0;
thread_local const TArray<FStrateTerrainOpEntry>* CachedTerrainOps = nullptr; 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 bUseFusedCacheWindow = OutTunnelCore != nullptr;
const bool bUseSpatialIndex = VoxelGenLOD::ShouldUseSpatialIndex(bUseFusedCacheWindow);
FIntVector RequestedTileOrigin = FIntVector::ZeroValue; FIntVector RequestedTileOrigin = FIntVector::ZeroValue;
int32 RequestedTileStep = 1; int32 RequestedTileStep = 1;
int32 RequestedTileCells = 0; int32 RequestedTileCells = 0;
const bool bUseTileCacheWindow = VoxelGenLOD::IsTileCacheWindowEnabled()
&& VoxelGenLOD::GetThreadTileCacheWindow(
RequestedTileOrigin, RequestedTileStep, RequestedTileCells)
&& VoxelCaveMorphology::IsRoomGraphWindowInvariant(Params);
const uint64 CurrentManagerLifetimeId = StrateManager const uint64 CurrentManagerLifetimeId = StrateManager
? StrateManager->GetCacheLifetimeId() : 0; ? StrateManager->GetCacheLifetimeId() : 0;
const int32 QueryChunkX = FMath::FloorToInt(WorldX / (float)CHUNK_SIZE); const int32 QueryChunkX = FMath::FloorToInt(WorldX / (float)CHUNK_SIZE);
@@ -2865,6 +2974,39 @@ float UVoxelGenerator::GetDensityWithParams(float WorldX, float WorldY, float Wo
QueryChunkZ)); QueryChunkZ));
if (Def) TerrainOps = &Def->TerrainOperations; 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. // 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 // 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( CaveSDF = VoxelCaveMorphology::EvaluateSDFCached(
WarpedX, WarpedY, WarpedZ, WarpedX, WarpedY, WarpedZ,
SDFCache, Params.SDFBlendRadius, SDFCache, Params.SDFBlendRadius,
&NearestRoomIdx &NearestRoomIdx,
bUseSpatialIndex
); );
// ── PIT & CHIMNEY SDF INTEGRATION ── // ── PIT & CHIMNEY SDF INTEGRATION ──
@@ -3039,7 +3182,8 @@ float UVoxelGenerator::GetDensityWithParams(float WorldX, float WorldY, float Wo
CaveSDF = VoxelSDF::SmoothMin(CaveSDF, PitSDF, Pit.BlendK); CaveSDF = VoxelSDF::SmoothMin(CaveSDF, PitSDF, Pit.BlendK);
}; };
VF_ForEachChunkSDFSpatialCandidate( VF_ForEachChunkSDFSpatialCandidate(
SDFCache.PitSpatialIndex, SDFCache.Pits.Num(), WorldX, WorldY, EvaluatePit); SDFCache.PitSpatialIndex, SDFCache.Pits.Num(), WorldX, WorldY, EvaluatePit,
bUseSpatialIndex);
auto EvaluateChimney = [&](int32 ChimneyIndex) auto EvaluateChimney = [&](int32 ChimneyIndex)
{ {
@@ -3071,7 +3215,7 @@ float UVoxelGenerator::GetDensityWithParams(float WorldX, float WorldY, float Wo
}; };
VF_ForEachChunkSDFSpatialCandidate( VF_ForEachChunkSDFSpatialCandidate(
SDFCache.ChimneySpatialIndex, SDFCache.Chimneys.Num(), SDFCache.ChimneySpatialIndex, SDFCache.Chimneys.Num(),
WorldX, WorldY, EvaluateChimney); WorldX, WorldY, EvaluateChimney, bUseSpatialIndex);
// Convert SDF to density carving: // Convert SDF to density carving:
// CaveSDF < 0 means we're inside a room/tunnel/pit → carve to air // 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 // 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. // here would turn a cache-window change into hundreds of unnecessary column builds.
*OutTunnelCore = VoxelCaveMorphology::EvaluateTunnelCoreWorld( *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, // 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). // only when near a surface — the common case is cheap (DistFromSurface > TerraceRange).
float SDF_Zp1 = VoxelCaveMorphology::EvaluateSDFCached( 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( 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; float GZ = (SDF_Zp1 - SDF_Zm1) * 0.5f;
// Normalized vertical component: 1 = perfectly horizontal surface (floor/ceiling) // Normalized vertical component: 1 = perfectly horizontal surface (floor/ceiling)
// 0 = perfectly vertical surface (wall) // 0 = perfectly vertical surface (wall)
@@ -3620,7 +3766,8 @@ float UVoxelGenerator::GetDensityWithParams(float WorldX, float WorldY, float Wo
} }
}; };
VF_ForEachChunkSDFSpatialCandidate( 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) // 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 // 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. // same predicates once in its common MC-space post section below both generation paths.
const bool bTunnelSupportFloor = VoxelCaveMorphology::IsTunnelSupportFloorWorldPoint( 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 // 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. // operator-stack path; its support band follows the same relieved swept floor.
if (bTunnelSupportFloor) if (bTunnelSupportFloor)
@@ -3899,7 +4046,8 @@ float UVoxelGenerator::GetDensityWithParams(float WorldX, float WorldY, float Wo
Density = FMath::Max(Density, StructuralSolidDensity); Density = FMath::Max(Density, StructuralSolidDensity);
} }
const float TunnelCoreSDF = (Params.RoomDensity > 0.0f && Params.RoomSpacing > 0.0f) 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; : FLT_MAX;
if (!bTunnelSupportFloor if (!bTunnelSupportFloor
&& TunnelCoreSDF < -VoxelPassageGeometry::CaveTunnelAirCoreInsetVoxels) && TunnelCoreSDF < -VoxelPassageGeometry::CaveTunnelAirCoreInsetVoxels)
@@ -735,6 +735,8 @@ struct FStoredSample
const FResolvedProgramCounter* LeafEntry = nullptr; const FResolvedProgramCounter* LeafEntry = nullptr;
const FResolvedSymbol* LeafFunction = nullptr; const FResolvedSymbol* LeafFunction = nullptr;
const FResolvedSymbol* LeafSource = nullptr; const FResolvedSymbol* LeafSource = nullptr;
uint64 LeafProgramCounter = 0;
bool bHasLeafProgramCounter = false;
bool bSampleHasResolvedLeaf = false; bool bSampleHasResolvedLeaf = false;
bool bSampleHasWaitLike = false; bool bSampleHasWaitLike = false;
@@ -755,8 +757,8 @@ struct FStoredSample
} }
if (FrameIndex == 0) if (FrameIndex == 0)
{ {
LeafEntry = Entry; LeafProgramCounter = ProgramCounter;
LeafFunction = FrameFunction; bHasLeafProgramCounter = true;
bSampleHasResolvedLeaf = true; bSampleHasResolvedLeaf = true;
} }
} }
@@ -769,12 +771,8 @@ struct FStoredSample
{ {
if (FrameIndex == 0) if (FrameIndex == 0)
{ {
LeafEntry = Entry; LeafProgramCounter = ProgramCounter;
if (LeafFunction == nullptr) bHasLeafProgramCounter = true;
{
LeafFunction = FirstFunctionSymbol(*Entry);
}
LeafSource = FirstSourceSymbol(*Entry);
} }
for (const FResolvedSymbol& Symbol : Entry->Symbols) for (const FResolvedSymbol& Symbol : Entry->Symbols)
{ {
@@ -792,10 +790,18 @@ struct FStoredSample
} }
} }
if (LeafEntry != nullptr && LeafSource == nullptr) // SymbolCache is a TMap. FindOrAdd while resolving later frames may rehash it, so
// pointers captured during the loop are not stable. Re-find the leaf only after all
// PCs for this sample have been inserted.
if (bHasLeafProgramCounter)
{ {
LeafEntry = SymbolCache.Find(LeafProgramCounter);
if (LeafEntry != nullptr)
{
LeafFunction = FirstFunctionSymbol(*LeafEntry);
LeafSource = FirstSourceSymbol(*LeafEntry); LeafSource = FirstSourceSymbol(*LeafEntry);
} }
}
auto AddToAggregate = [&](FProfileAggregateSet& Aggregate) auto AddToAggregate = [&](FProfileAggregateSet& Aggregate)
{ {
+13 -1
View File
@@ -3849,9 +3849,21 @@ void AVoxelWorld::GenerateTileResult(const FVoxelTileKey& Tile, const FIntVector
return bMeasureTile && TileProfileEnd.Counters[Index] >= TileProfileStart.Counters[Index] return bMeasureTile && TileProfileEnd.Counters[Index] >= TileProfileStart.Counters[Index]
? TileProfileEnd.Counters[Index] - TileProfileStart.Counters[Index] : 0; ? TileProfileEnd.Counters[Index] - TileProfileStart.Counters[Index] : 0;
}; };
const auto CycleDelta = [&](VoxelDensityProfile::EBucket Bucket) -> uint64
{
const int32 Index = static_cast<int32>(Bucket);
return bMeasureTile && TileProfileEnd.Cycles[Index] >= TileProfileStart.Cycles[Index]
? TileProfileEnd.Cycles[Index] - TileProfileStart.Cycles[Index] : 0;
};
const double TileSeconds = FPlatformTime::Seconds() - TileStartSeconds;
const double CacheBuildSeconds = FPlatformTime::ToSeconds64(
CycleDelta(VoxelDensityProfile::EBucket::RoomGraphBuild));
const double EvaluationSeconds = FMath::Max(
0.0, TileSeconds - CacheBuildSeconds);
UE_LOG(LogTemp, Display, UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeTileProfile] tile=(%d,%d,%d) level=%d step=%d cells=%d sheet=%d aborted=%d empty=%d " TEXT("[VoxelForgeTileProfile] tile=(%d,%d,%d) level=%d step=%d cells=%d sheet=%d aborted=%d empty=%d "
"verdict=%d classify=%.6f mesh=%.6f streams=%.6f seconds=%.6f " "verdict=%d classify=%.6f mesh=%.6f streams=%.6f seconds=%.6f "
"cache_build=%.6f evaluation=%.6f "
"refine=%u stack=%u core_samples=%u final_samples=%u core_hits=%u final_hits=%u " "refine=%u stack=%u core_samples=%u final_samples=%u core_hits=%u final_hits=%u "
"core_leaves=%u final_leaves=%u tail_queries=%u tail_eval=%u " "core_leaves=%u final_leaves=%u tail_queries=%u tail_eval=%u "
"stack_work=%.6f core_work=%.6f final_work=%.6f tail_work=%.6f " "stack_work=%.6f core_work=%.6f final_work=%.6f tail_work=%.6f "
@@ -3868,7 +3880,7 @@ void AVoxelWorld::GenerateTileResult(const FVoxelTileKey& Tile, const FIntVector
Tile.Coord.X, Tile.Coord.Y, Tile.Coord.Z, Tile.Level, Step, Cells, Tile.Coord.X, Tile.Coord.Y, Tile.Coord.Z, Tile.Level, Step, Cells,
bSheetTile ? 1 : 0, Result.bAborted ? 1 : 0, bSheetTile ? 1 : 0, Result.bAborted ? 1 : 0,
Result.bEmpty ? 1 : 0, ClassifyVerdict, ClassifySeconds, MeshSeconds, StreamSeconds, Result.bEmpty ? 1 : 0, ClassifyVerdict, ClassifySeconds, MeshSeconds, StreamSeconds,
FPlatformTime::Seconds() - TileStartSeconds, TileSeconds, CacheBuildSeconds, EvaluationSeconds,
ClassifierStats.RefineNodes, ClassifierStats.StackBoxCalls, ClassifierStats.RefineNodes, ClassifierStats.StackBoxCalls,
ClassifierStats.ExactCoreSamples, ClassifierStats.ExactFinalSamples, ClassifierStats.ExactCoreSamples, ClassifierStats.ExactFinalSamples,
ClassifierStats.ExactCoreCacheHits, ClassifierStats.ExactFinalCacheHits, ClassifierStats.ExactCoreCacheHits, ClassifierStats.ExactFinalCacheHits,
+19 -10
View File
@@ -852,12 +852,13 @@ struct FChunkSDFSpatialIndex
template <typename FVisit> template <typename FVisit>
FORCEINLINE int32 VF_ForEachChunkSDFSpatialCandidate( FORCEINLINE int32 VF_ForEachChunkSDFSpatialCandidate(
const FChunkSDFSpatialIndex& Index, int32 ItemCount, const FChunkSDFSpatialIndex& Index, int32 ItemCount,
float WorldX, float WorldY, FVisit&& Visit) float WorldX, float WorldY, FVisit&& Visit,
bool bUseSpatialIndex = true)
{ {
int32 Begin = 0; int32 Begin = 0;
int32 End = 0; int32 End = 0;
int32 CandidateCount = 0; int32 CandidateCount = 0;
if (Index.GetRange(WorldX, WorldY, Begin, End)) if (bUseSpatialIndex && Index.GetRange(WorldX, WorldY, Begin, End))
{ {
for (int32 Cursor = Begin; Cursor < End; ++Cursor) for (int32 Cursor = Begin; Cursor < End; ++Cursor)
{ {
@@ -1008,14 +1009,16 @@ namespace VoxelCaveMorphology
const FStrateGenerationParams& Params, const FStrateGenerationParams& Params,
uint32 Seed, int32 StrateIndex, uint32 Seed, int32 StrateIndex,
bool bUseZ = false, bool bUseZ = false,
float SearchMinZ = 0.0f, float SearchMaxZ = 0.0f float SearchMinZ = 0.0f, float SearchMaxZ = 0.0f,
const TArray<FStrateTerrainOpEntry>* TerrainOps = nullptr
); );
// True only for the finite parameter envelope for which BuildChunkCache's fixed collect // True only for the finite parameter envelope for which BuildChunkCache's fixed collect
// margin proves window invariance. Callers may widen the XY store window to a whole tile only // margin proves window invariance. Callers may widen the XY store window to a whole tile only
// when this returns true; malformed or unbounded authored input must retain the legacy path. // when this returns true; malformed or unbounded authored input must retain the legacy path.
VOXELFORGE_API bool IsRoomGraphWindowInvariant( VOXELFORGE_API bool IsRoomGraphWindowInvariant(
const FStrateGenerationParams& Params const FStrateGenerationParams& Params,
const TArray<FStrateTerrainOpEntry>* TerrainOps = nullptr
); );
// PHASE 2: Evaluate the SDF at a single world position using cached data. // PHASE 2: Evaluate the SDF at a single world position using cached data.
@@ -1036,7 +1039,8 @@ namespace VoxelCaveMorphology
float WorldX, float WorldY, float WorldZ, float WorldX, float WorldY, float WorldZ,
const FChunkSDFCache& Cache, const FChunkSDFCache& Cache,
float SDFBlendRadius, float SDFBlendRadius,
int32* OutNearestRoomIdx = nullptr int32* OutNearestRoomIdx = nullptr,
bool bUseSpatialIndex = true
); );
// Evaluate the raw union of cached graph-tunnel capsules. This is intentionally separate from // Evaluate the raw union of cached graph-tunnel capsules. This is intentionally separate from
@@ -1044,7 +1048,8 @@ namespace VoxelCaveMorphology
// tunnel's walkable air core without reintroducing a connector or changing room ownership. // tunnel's walkable air core without reintroducing a connector or changing room ownership.
VOXELFORGE_API float EvaluateTunnelCoreSDF( VOXELFORGE_API float EvaluateTunnelCoreSDF(
float WorldX, float WorldY, float WorldZ, float WorldX, float WorldY, float WorldZ,
const FChunkSDFCache& Cache const FChunkSDFCache& Cache,
bool bUseSpatialIndex = true
); );
// Evaluate the final structural air contract against the world-space wandering chain. Room // Evaluate the final structural air contract against the world-space wandering chain. Room
@@ -1052,13 +1057,15 @@ namespace VoxelCaveMorphology
// walkable floor follows the authored chain exactly. // walkable floor follows the authored chain exactly.
VOXELFORGE_API float EvaluateTunnelCoreWorldSDF( VOXELFORGE_API float EvaluateTunnelCoreWorldSDF(
float WorldX, float WorldY, float WorldZ, float WorldX, float WorldY, float WorldZ,
const FChunkSDFCache& Cache const FChunkSDFCache& Cache,
bool bUseSpatialIndex = true
); );
VOXELFORGE_API FTunnelCoreWorldEvaluation EvaluateTunnelCoreWorld( VOXELFORGE_API FTunnelCoreWorldEvaluation EvaluateTunnelCoreWorld(
float WorldX, float WorldY, float WorldZ, float WorldX, float WorldY, float WorldZ,
const FChunkSDFCache& Cache, const FChunkSDFCache& Cache,
const FTunnelSupportFloorColumn* SupportColumn = nullptr const FTunnelSupportFloorColumn* SupportColumn = nullptr,
bool bUseSpatialIndex = true
); );
// 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
@@ -1066,7 +1073,8 @@ namespace VoxelCaveMorphology
// the rounded capsule's bottom from becoming an unsupported point. // the rounded capsule's bottom from becoming an unsupported point.
VOXELFORGE_API bool IsTunnelSupportFloorWorldPoint( VOXELFORGE_API bool IsTunnelSupportFloorWorldPoint(
float WorldX, float WorldY, float WorldZ, float WorldX, float WorldY, float WorldZ,
const FChunkSDFCache& Cache const FChunkSDFCache& Cache,
bool bUseSpatialIndex = true
); );
// Build/query the same support-floor predicate at column granularity. The column stores its // Build/query the same support-floor predicate at column granularity. The column stores its
@@ -1075,7 +1083,8 @@ namespace VoxelCaveMorphology
VOXELFORGE_API void BuildTunnelSupportFloorColumn( VOXELFORGE_API void BuildTunnelSupportFloorColumn(
float WorldX, float WorldY, float WorldX, float WorldY,
const FChunkSDFCache& Cache, const FChunkSDFCache& Cache,
FTunnelSupportFloorColumn& OutColumn FTunnelSupportFloorColumn& OutColumn,
bool bUseSpatialIndex = true
); );
VOXELFORGE_API bool IsTunnelSupportFloorColumnZ( VOXELFORGE_API bool IsTunnelSupportFloorColumnZ(
+8 -1
View File
@@ -63,7 +63,7 @@ namespace VoxelGenLOD
VOXELFORGE_API void SetThreadOctaveBias(int32 Value); VOXELFORGE_API void SetThreadOctaveBias(int32 Value);
VOXELFORGE_API int32 GetThreadSampleStep(); VOXELFORGE_API int32 GetThreadSampleStep();
VOXELFORGE_API void SetThreadSampleStep(int32 Value); VOXELFORGE_API void SetThreadSampleStep(int32 Value);
VOXELFORGE_API bool IsTileCacheWindowEnabled(); VOXELFORGE_API bool IsTileCacheWindowEnabled(bool bFusedPath = true);
VOXELFORGE_API bool GetThreadTileCacheWindow( VOXELFORGE_API bool GetThreadTileCacheWindow(
FIntVector& OutOriginVoxels, int32& OutStep, int32& OutCellsPerAxis); FIntVector& OutOriginVoxels, int32& OutStep, int32& OutCellsPerAxis);
@@ -426,6 +426,13 @@ private:
const FBiomeContext& Ctx, FChunkBiomeCache& Cache) const; const FBiomeContext& Ctx, FChunkBiomeCache& Cache) const;
}; };
namespace VoxelGenLOD
{
// -1 uses the measured path/LOD policy; 0/1 force the spatial broad phase off/on for an A/B
// run. The policy affects candidate enumeration only, never cache contents or ordering.
VOXELFORGE_API bool ShouldUseSpatialIndex(bool bFusedPath);
}
/** /**
* Optional diagnostics for one ClassifyTile call. This is populated only when the caller asks * Optional diagnostics for one ClassifyTile call. This is populated only when the caller asks
* for it (the streaming profiler does); it is never consulted by the proof and cannot affect a * for it (the streaming profiler does); it is never consulted by the proof and cannot affect a