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
+1
-1
@@ -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. |
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,9 +790,17 @@ 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)
|
||||||
{
|
{
|
||||||
LeafSource = FirstSourceSymbol(*LeafEntry);
|
LeafEntry = SymbolCache.Find(LeafProgramCounter);
|
||||||
|
if (LeafEntry != nullptr)
|
||||||
|
{
|
||||||
|
LeafFunction = FirstFunctionSymbol(*LeafEntry);
|
||||||
|
LeafSource = FirstSourceSymbol(*LeafEntry);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto AddToAggregate = [&](FProfileAggregateSet& Aggregate)
|
auto AddToAggregate = [&](FProfileAggregateSet& Aggregate)
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in new issue
Block a user