perf(generation): field-exact LOD0 overhead removal; the unknown bucket is Core.dll

Field-preserving floor round (Codex; reviewed by me against the code):
- exact per-(sample, passage) caching of native/generic floor projections and walkable-air
  checks, behind voxel.FloorRound1PassageProjectionCache
- a zero-work disabled path for VoxelDensityProfile::FScopedTimer, unchanged when enabled
- pointer / range-for iteration in the proven-hot tunnel, passage, spatial and candidate loops
  (TArray RangeCheck was ~6.5% of LOD0 samples on the Development binaries the owner plays on)
- FVector2D temporaries replaced with scalar doubles, same operations in the same order
- FSurfaceColumn made trivial, so thread-local construction does no work. Verified safe:
  ComputeSurfaceColumn writes all five outputs unconditionally before any branch, and every read
  of Box.Cols is gated by Box.Computed

Verified: export OBJ byte-identical (b3e5f4c3..9b377); capability 20,830 / 10,909; all 8
interleaved game runs gave identical totals (841 tiles / 669,834 triangles static, 2,348 /
1,985,216 moving, the same as previous rounds). Per-tile identity for the always-on changes still
needs a check against a stored trace; that is queued first in the next round.

Game path (sampler off, two interleaved runs each):
  projection-cache switch alone: neutral (static worker +0.41%, moving -0.05%)
  vs the previous round (cfad3d1, same harness; cross-round numbers reproduced within ~1% here):
    LOD0 generation p50  static 76.6 -> 65.8 ms (-14%), moving 72.4 -> 59.5 ms (-18%)
    LOD0 request p50/p95 moving 79.4/158.5 -> 67.6/137.6 ms
    worker seconds       static 39.2 -> 33.4, moving 113.7 -> 97.9
  The always-on changes carry the gain; they have no switch, so this is a cross-round comparison.

The sampler's ~12.9% "unknown" LOD0 leaf is engine code: UnrealEditor-Core.dll + RVA 0xC080,
without a Core PDB. The old FSurfaceColumn label is gone. Next round names it by its callers.

PERF-SAMPLED.md: corrected a sentence that repeated my false "daytime use" explanation.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TaFdXhTdYMkW1xrV6ApT6Y
This commit is contained in:
Fr0zkaandClaude Opus 5 committed 2026-09-13 21:00:34 +02:00
1 parent b839edd3a3
commit ab4bbe3680
9 files changed
+629 -170

No files matched your search

+18
View File
@@ -492,6 +492,13 @@ shape draws for inter-strate passages are keyed by `(seed, upper-strate index, c
per-value salt)` through `VoxelHash`, so one passage's connection count cannot shift any later per-value salt)` through `VoxelHash`, so one passage's connection count cannot shift any later
passage. Global passage settings were removed from `VoxelSettings`. passage. Global passage settings were removed from `VoxelSettings`.
The repeated floor queries use the same `thread_local` passage state: `FPassageEvaluationCache` owns
one indexed `FloorProjections` entry per passage, keyed by exact double `(X,Y,Z)` equality for the
current sample. Native projection, generic fallback projection, and walkable-air classification
reuse the cached float outputs and validity bits; manager lifetime, layout version, and chunk changes
invalidate the cache. `voxel.FloorRound1PassageProjectionCache` is an A/B switch only. It changes no
projection arithmetic and no density value; a cache hit is an exact reuse of the first result.
After each passage's XY is selected, `GeneratePassages` asks **both** participating strates for a After each passage's XY is selected, `GeneratePassages` asks **both** participating strates for a
player-fit source point through the pure free function `VF_SuggestLandingPoint` player-fit source point through the pure free function `VF_SuggestLandingPoint`
(`VoxelCaveMorphology`). (`VoxelCaveMorphology`).
@@ -674,6 +681,17 @@ driven by `EditorBrush*` props.
**Used ONLY for integer-XY queries**; fractional queries compute directly → bit-identical. Don't **Used ONLY for integer-XY queries**; fractional queries compute directly → bit-identical. Don't
re-introduce a ChunkZ key, don't feed it fractional coords. (`GetSurfaceHeightAt`'s own `OC_*` oracle re-introduce a ChunkZ key, don't feed it fractional coords. (`GetSurfaceHeightAt`'s own `OC_*` oracle
cache is separate and still per-chunk — lower volume, not worth the LRU.) cache is separate and still per-chunk — lower volume, not worth the LRU.)
- **Surface-column construction (field-preserving follow-up)**: `FSurfaceColumn` is intentionally a
trivial payload. `ComputeSurfaceColumn` writes all five outputs before publishing `Computed`, so
its default member initialization was never observable; removing that generated TLS leaf avoids
constructor work without changing a height or density. Integer queries still read the cached
payload, and fractional queries still use the same direct locals.
- **Sampler unknown-source audit**: the earlier plugin-side `FSurfaceColumn` constructor PCs no longer
occur after the trivial-payload change. The largest remaining LOD0 unknown cluster in the final
sampler raw CSV is `UnrealEditor-Core.dll + RVA 0xC080` (runtime PCs around
`0x7FF88382C080`), inside the private `.pdata` function range `0xB4D0–0xC32B`; the packaged Core
binary has no PDB, so a source function name cannot be recovered here. Smaller unresolved groups
map to generated engine reflection/CoreUObject code, not to `Plugins\VoxelForge`.
- **Collision only at LOD0 (T1.c)** (`ApplyMeshToChunk`): `UpdateSectionConfig(..., LOD==0)`. - **Collision only at LOD0 (T1.c)** (`ApplyMeshToChunk`): `UpdateSectionConfig(..., LOD==0)`.
LOD1/2 chunks are unreachable (the §8.10 reconciliation hot-swaps to LOD0 before the player LOD1/2 chunks are unreachable (the §8.10 reconciliation hot-swaps to LOD0 before the player
arrives), so cooking their Chaos collision is waste. Don't force collision on for all LODs. arrives), so cooking their Chaos collision is waste. Don't force collision on for all LODs.
+3 -3
View File
@@ -194,7 +194,7 @@ bit. They are port-correctness oracles, not fidelity checks: the acceptance bar
| `VoxelDensityOps::MakeGridColumnMod` | 3 | Infinite-height cylinders on a world grid, 3×3 cell memo. Adds solid only ⇒ `FillOnly` when a column reaches the box, `Identity` otherwise — and that `Identity` is what lets the source's `AllAir` verdict survive. | | `VoxelDensityOps::MakeGridColumnMod` | 3 | Infinite-height cylinders on a world grid, 3×3 cell memo. Adds solid only ⇒ `FillOnly` when a column reaches the box, `Identity` otherwise — and that `Identity` is what lets the source's `AllAir` verdict survive. |
| `FShaftFieldSource` (internal) | 1 | VerticalShafts' memoised inner 3×3 shaft/capsule source. Each rebuild uses a direct-indexed wider halo (default 9×9 tree-emission / 15×15 roll) so every emitted shaft has its complete 5×5 parent window plus capsule reach; the nearest strictly more-central shaft in ±2 cells is the deterministic parent, with a deterministic lower-origin ±3 fallback and a direct `(0,0)` spine fallback. Thus every shaft has a provable path to the spine. Existing `VoxelHash::Pair`/`Spacing*1.6` links remain additive texture/loops. Tree and spine capsules use a radius strictly above `sup|FBM|=1.5`, and tree Z is chosen in a ledge-free band; the structural post still owns the column and the ledge modifier keeps its mathematical axis boundary open. The source publishes a conservative interval covering both shafts and connector capsules; it does not answer for the ledge or converter. | | `FShaftFieldSource` (internal) | 1 | VerticalShafts' memoised inner 3×3 shaft/capsule source. Each rebuild uses a direct-indexed wider halo (default 9×9 tree-emission / 15×15 roll) so every emitted shaft has its complete 5×5 parent window plus capsule reach; the nearest strictly more-central shaft in ±2 cells is the deterministic parent, with a deterministic lower-origin ±3 fallback and a direct `(0,0)` spine fallback. Thus every shaft has a provable path to the spine. Existing `VoxelHash::Pair`/`Spacing*1.6` links remain additive texture/loops. Tree and spine capsules use a radius strictly above `sup|FBM|=1.5`, and tree Z is chosen in a ledge-free band; the structural post still owns the column and the ledge modifier keeps its mathematical axis boundary open. The source publishes a conservative interval covering both shafts and connector capsules; it does not answer for the ledge or converter. |
| `VoxelDensityOps::BuildSlabStack` | — | 6 ops, **no branch on archetype**: FlatPlain and CrystalChamber differ only in defaults, exactly as `GetSlabDensity` already had it. 8 archetypes → 7. | | `VoxelDensityOps::BuildSlabStack` | — | 6 ops, **no branch on archetype**: FlatPlain and CrystalChamber differ only in defaults, exactly as `GetSlabDensity` already had it. 8 archetypes → 7. |
| `FSurfaceColumnSource` (internal) | 1 | The bridge between the two spaces: consumes the ground + sky-cap **height** stacks and produces density. `IsXYPure()` **false** — the heights are XY-pure, a distance to them never is. Owns a **six-box spatial LRU** of direct-indexed per-column cells, keyed by `PrepareChunk` on `(StrateBottomWorldZ, LayoutVersion, Seed, ParamsFingerprint)` so it is **shared down the whole vertical strate stack**, exactly like `GSurfColCache`. Six 81×81 boxes preserve hot columns across interleaved regions at roughly 0.79 MiB TLS before padding (more memory, fewer whole-cache recenter/recompute misses). Fractional XY remains direct-compute. | | `FSurfaceColumnSource` (internal) | 1 | The bridge between the two spaces: consumes the ground + sky-cap **height** stacks and produces density. `IsXYPure()` **false** — the heights are XY-pure, a distance to them never is. Owns a **six-box spatial LRU** of direct-indexed per-column cells, keyed by `PrepareChunk` on `(StrateBottomWorldZ, LayoutVersion, Seed, ParamsFingerprint)` so it is **shared down the whole vertical strate stack**, exactly like `GSurfColCache`. Six 81×81 boxes preserve hot columns across interleaved regions at roughly 0.79 MiB TLS before padding (more memory, fewer whole-cache recenter/recompute misses). Fractional XY remains direct-compute. `FSurfaceColumn` is deliberately trivial: all five outputs are written before the cache's `Computed` publish, so removing its default initialization removes a TLS constructor leaf without changing a value. |
| `VoxelDensityOps::BuildSurfaceStack` | — | SurfaceWorld, complete: column + overhang + 4 structural, plus biome blending when `PerBiomeParams` is non-empty. Takes ownership of an `IVoxelBiomeField`. | | `VoxelDensityOps::BuildSurfaceStack` | — | SurfaceWorld, complete: column + overhang + 4 structural, plus biome blending when `PerBiomeParams` is non-empty. Takes ownership of an `IVoxelBiomeField`. |
| `VoxelDensityOps::BuildVerticalShaftStack` | — | 9 ops, and **three are Maze's reused unchanged** (`ConstantRock`, `SdfRoughness`, `SdfCarve`) with different tuning (freq 0.1 vs 0.12, window `rough+4` vs `R+rough+2`). Passes the runtime spine radius into `FShaftFieldSource`, which mirrors the generator's connector-only origin endpoint and roughness-safe spine capsule. The measured proof of `OPSTACK-PLAN §2.5`'s reuse claim. | | `VoxelDensityOps::BuildVerticalShaftStack` | — | 9 ops, and **three are Maze's reused unchanged** (`ConstantRock`, `SdfRoughness`, `SdfCarve`) with different tuning (freq 0.1 vs 0.12, window `rough+4` vs `R+rough+2`). Passes the runtime spine radius into `FShaftFieldSource`, which mirrors the generator's connector-only origin endpoint and roughness-safe spine capsule. The measured proof of `OPSTACK-PLAN §2.5`'s reuse claim. |
| `FRoomGraphSource` (internal) | 1 | TunnelNetwork's SDF spine. **CALLS `BuildChunkCache`/`EvaluateSDFCached` — does not transcribe them**: that is where §8.4's two-region discipline lives and a copy would fork it. Owns the cave warp (scope = this op alone; pits/chimneys read *unwarped* coords, which is why no FRAME op was needed). Its cache key adds a **params CRC + LayoutVersion**; the original lacked them until AUDIT §C2 was fixed (2026-07-28) and now carries them too. The source now publishes a conservative SDF interval through the box fold; it does not answer for the carve or detail stack. It builds the cache for the queried box into a *second* per-worker cache (never `FState::Cache`), and the interval uses the same cull/threshold disjunction as the per-voxel source: a primitive is harmless when it fails its cull or its own SDF stays ≥ `T+K`; `T = max(3·SDFBlendRadius, WormNetworkRange)`, with `−K` slack for the final `SmoothMin`. Rooms, tunnels, pits and chimneys are bounded in their respective coordinate frames; warp dilation uses the proved `sup|Perlin3D| ≤ 1.5` envelope. | | `FRoomGraphSource` (internal) | 1 | TunnelNetwork's SDF spine. **CALLS `BuildChunkCache`/`EvaluateSDFCached` — does not transcribe them**: that is where §8.4's two-region discipline lives and a copy would fork it. Owns the cave warp (scope = this op alone; pits/chimneys read *unwarped* coords, which is why no FRAME op was needed). Its cache key adds a **params CRC + LayoutVersion**; the original lacked them until AUDIT §C2 was fixed (2026-07-28) and now carries them too. The source now publishes a conservative SDF interval through the box fold; it does not answer for the carve or detail stack. It builds the cache for the queried box into a *second* per-worker cache (never `FState::Cache`), and the interval uses the same cull/threshold disjunction as the per-voxel source: a primitive is harmless when it fails its cull or its own SDF stays ≥ `T+K`; `T = max(3·SDFBlendRadius, WormNetworkRange)`, with `−K` slack for the final `SmoothMin`. Rooms, tunnels, pits and chimneys are bounded in their respective coordinate frames; warp dilation uses the proved `sup|Perlin3D| ≤ 1.5` envelope. |
@@ -344,7 +344,7 @@ This is **where terrain shape lives.**
| **`GetDensityWithParams`** | 277 | TunnelNetwork pipeline (~1000 lines). See §4. ⚠️ Takes **required** `ParamsFingerprint` + `LayoutVersion` since the AUDIT §C2 fix (2026-07-28) — they go into the SDF cache key so a chunk can no longer be evaluated against a neighbour's rooms. Callers compute the CRC **once per chunk** (`CP_TunnelFP`), never per voxel. | | **`GetDensityWithParams`** | 277 | TunnelNetwork pipeline (~1000 lines). See §4. ⚠️ Takes **required** `ParamsFingerprint` + `LayoutVersion` since the AUDIT §C2 fix (2026-07-28) — they go into the SDF cache key so a chunk can no longer be evaluated against a neighbour's rooms. Callers compute the CRC **once per chunk** (`CP_TunnelFP`), never per voxel. |
| **`GetSlabDensity`** | 1306 | FlatPlain/CrystalChamber pipeline. See §4.2. | | **`GetSlabDensity`** | 1306 | FlatPlain/CrystalChamber pipeline. See §4.2. |
| `SampleSurfaceStructuralZ` | — | **F20:** the RAW SurfaceWorld heightfield (continents+mountains+detail), BEFORE any terrain op; returns terrain Z + relief M. Cliff re-samples it at an XY offset for a cheap analytic slope. | | `SampleSurfaceStructuralZ` | — | **F20:** the RAW SurfaceWorld heightfield (continents+mountains+detail), BEFORE any terrain op; returns terrain Z + relief M. Cliff re-samples it at an XY offset for a cheap analytic slope. |
| `ComputeSurfaceTerrainZ` / `GetSurfaceDensity` | — | SurfaceWorld heightfield → terrain Z, then density; biome **output-blend** lerps dominant/neighbour heights (`ParamsD`/`ParamsN`/weight). **F20 surface ops** (`FSurfaceGenerationParams`, biome-selected + slope/relief-conditioned, all default off): Cliff (slope-gated STEEPENING — push height from local mean where steep ⇒ sheer walls; 4 structural resamples only when on), Terrace (relief-gated + `TerraceHardness`), LayerLines (sedimentary shelves) — pure per-column height REMAPS applied here so the single height oracle stays consistent (MC/sheets/ClassifyTile/deco/BP bridge). **Phase 2 OVERHANG** (volumetric — real jutting shelves): in `SurfaceDensityFromColumn`, for AIR voxels in a window `(TerrainZ, TerrainZ+OverhangHeight]` above a steep slope, the heightfield is re-sampled UPHILL (toward the cliff) by a reach that GROWS with height (tiny low ⇒ air over the void, full high ⇒ borrows the far cliff rock) and unioned in ⇒ a shelf attached to the cliff, tapering out over the void with air beneath (the sketch). Per-column `OverhangAmp`(=strength·slope-gate) + unit uphill `(DirX,DirY)` resolved once in `ComputeSurfaceColumn` (gradient sampled at the REACH scale so a spot over the void can see the cliff), cached on `FSurfaceColumn`. Genuine 3D (per-voxel structural re-eval, gated to steep overhang columns). Off ⇒ byte-identical. §8.14. | | `ComputeSurfaceTerrainZ` / `GetSurfaceDensity` | — | SurfaceWorld heightfield → terrain Z, then density; biome **output-blend** lerps dominant/neighbour heights (`ParamsD`/`ParamsN`/weight). **F20 surface ops** (`FSurfaceGenerationParams`, biome-selected + slope/relief-conditioned, all default off): Cliff (slope-gated STEEPENING — push height from local mean where steep ⇒ sheer walls; 4 structural resamples only when on), Terrace (relief-gated + `TerraceHardness`), LayerLines (sedimentary shelves) — pure per-column height REMAPS applied here so the single height oracle stays consistent (MC/sheets/ClassifyTile/deco/BP bridge). **Phase 2 OVERHANG** (volumetric — real jutting shelves): in `SurfaceDensityFromColumn`, for AIR voxels in a window `(TerrainZ, TerrainZ+OverhangHeight]` above a steep slope, the heightfield is re-sampled UPHILL (toward the cliff) by a reach that GROWS with height (tiny low ⇒ air over the void, full high ⇒ borrows the far cliff rock) and unioned in ⇒ a shelf attached to the cliff, tapering out over the void with air beneath (the sketch). Per-column `OverhangAmp`(=strength·slope-gate) + unit uphill `(DirX,DirY)` resolved once in `ComputeSurfaceColumn` (gradient sampled at the REACH scale so a spot over the void can see the cliff), cached on `FSurfaceColumn`. Genuine 3D (per-voxel structural re-eval, gated to steep overhang columns). Off ⇒ byte-identical. The field-preserving LOD0 pass removes only needless temporaries/default construction; every output remains bit-identical. §8.14. |
| `VF_BuildOpStackForChunk` (file-static) | — | **The archetype → stack mapping, written down once.** `GetDensityAt` and `ClassifyTile` both call it; params are passed in, never fetched here. A second copy would be the worst bug available in this file — a tile skipped on the verdict of a stack that is not the one producing its density is a hole. Returns false (⇒ caller falls back to the `switch`) for an unported archetype, missing params, or a **degenerate strate**, since five archetype functions early-out to air there and the stack deliberately has no such early-out. `Refs.Surface == nullptr` makes it refuse SurfaceWorld, which is how `ClassifyTile` keeps its own exact-lattice proof. | | `VF_BuildOpStackForChunk` (file-static) | — | **The archetype → stack mapping, written down once.** `GetDensityAt` and `ClassifyTile` both call it; params are passed in, never fetched here. A second copy would be the worst bug available in this file — a tile skipped on the verdict of a stack that is not the one producing its density is a hole. Returns false (⇒ caller falls back to the `switch`) for an unported archetype, missing params, or a **degenerate strate**, since five archetype functions early-out to air there and the stack deliberately has no such early-out. `Refs.Surface == nullptr` makes it refuse SurfaceWorld, which is how `ClassifyTile` keeps its own exact-lattice proof. |
| `ClassifyTile` | — | **T1.d trivial-tile reject. ⛔ Not called by streaming by default since 2026-09-13** (`voxel.OuterClassifierMode=0`; net-negative in game, see ARCHITECTURE §8.10). Cooked/editor recipes now build the exact recipe stack and call its proved `ClassifyBox`; a tile crossing a slot/gap, any diff, or any disturbance returns `Mixed`. This replaces the old global `HasComposerRecipeOverride()` force-to-Mixed. Native cave/surface/gap proofs and all §8.10 caches remain unchanged. | | `ClassifyTile` | — | **T1.d trivial-tile reject. ⛔ Not called by streaming by default since 2026-09-13** (`voxel.OuterClassifierMode=0`; net-negative in game, see ARCHITECTURE §8.10). Cooked/editor recipes now build the exact recipe stack and call its proved `ClassifyBox`; a tile crossing a slot/gap, any diff, or any disturbance returns `Mixed`. This replaces the old global `HasComposerRecipeOverride()` force-to-Mixed. Native cave/surface/gap proofs and all §8.10 caches remain unchanged. |
| `SampleRelief` / `SampleMoisture` | — | Climate fields (pure XY, [0,1]). Relief = shared source of truth for the relief map M. §8.14. | | `SampleRelief` / `SampleMoisture` | — | Climate fields (pure XY, [0,1]). Relief = shared source of truth for the relief map M. §8.14. |
@@ -498,7 +498,7 @@ Maps depth→strate at runtime; owns passages.
|--------|-----------|------| |--------|-----------|------|
| `Initialize` | 10 | Keeps the existing sequencing through `GeneratePassages`, but selects one slot source: a validated cooked season (exact ordered bounds/vector/recipe) or the unchanged authored fixed+sorted/shuffled pool. Assigned-invalid seasons fail closed. Authored source definitions are duplicated as content bags; generated slots use deterministic C++ defaults and zero auto-passages because schema 2 does not store passage configuration. | | `Initialize` | 10 | Keeps the existing sequencing through `GeneratePassages`, but selects one slot source: a validated cooked season (exact ordered bounds/vector/recipe) or the unchanged authored fixed+sorted/shuffled pool. Assigned-invalid seasons fail closed. Authored source definitions are duplicated as content bags; generated slots use deterministic C++ defaults and zero auto-passages because schema 2 does not store passage configuration. |
| `GeneratePassages` | 247 | Deterministic passages between consecutive strates (per-strate `PassageConfig::Style` control points; auto passages retain the existing `EVoxelPassageType` default); placement and shape values are independently salted hashes of seed + boundary slot + connection index. Each mouth independently queries its own strate's pure player-fit `VF_SuggestLandingPoint` with its contract seed (cave room salt, otherwise world seed), may move within its archetype-specific budget, then receives a body-sized local room/floor. The origin landing is reserved for the future shaft; no landing-to-root road is generated. Unsupported/no-fit answers preserve the old random reach; a deterministic level dog-leg is inserted when the final ramp needs it to stay ≤44°. All descriptors and bounds are generated without reading another passage. | | `GeneratePassages` | 247 | Deterministic passages between consecutive strates (per-strate `PassageConfig::Style` control points; auto passages retain the existing `EVoxelPassageType` default); placement and shape values are independently salted hashes of seed + boundary slot + connection index. Each mouth independently queries its own strate's pure player-fit `VF_SuggestLandingPoint` with its contract seed (cave room salt, otherwise world seed), may move within its archetype-specific budget, then receives a body-sized local room/floor. The origin landing is reserved for the future shaft; no landing-to-root road is generated. Unsupported/no-fit answers preserve the old random reach; a deterministic level dog-leg is inserted when the final ramp needs it to stay ≤44°. All descriptors and bounds are generated without reading another passage. |
| `EvaluateModifierSDF` | 357 | SDF of passage tubes plus local landing rooms at a point. A per-chunk `thread_local` shortlist (`PassagesVersion`-stamped) → far chunks return `FLT_MAX` without walking `Passages`; the same cache serves floor membership. §8.10. | | `EvaluateModifierSDF` | 357 | SDF of passage tubes plus local landing rooms at a point. A per-chunk `thread_local` shortlist (`PassagesVersion`-stamped) → far chunks return `FLT_MAX` without walking `Passages`; the same cache serves floor membership. `FPassageEvaluationCache::FloorProjections` reuses native/generic floor projections and walkable-air results once per `(sample, passage)` using exact double XYZ equality and unchanged float outputs; `voxel.FloorRound1PassageProjectionCache=0` disables only this A/B cache. §8.10. |
| `ApplyPassageModifier` | 1319 | Shared legacy/op-stack passage post: carves tube and local landing air through the idempotent landing primitive, then restores the local landing support slab in internal positive-solid density. | | `ApplyPassageModifier` | 1319 | Shared legacy/op-stack passage post: carves tube and local landing air through the idempotent landing primitive, then restores the local landing support slab in internal positive-solid density. |
| `VF_ApplyPassageLandingCarving` | — | Idempotent density-independent landing-air threshold shared by the structural pass and the post-disturbance MC backstop; keeps legacy and operator-stack results bit-identical. | | `VF_ApplyPassageLandingCarving` | — | Idempotent density-independent landing-air threshold shared by the structural pass and the post-disturbance MC backstop; keeps legacy and operator-stack results bit-identical. |
| `ApplyPassageLandingFloorMC` | 1347 | Reasserts the same support slab after MC-space disturbances and before the final XY edge seal. | | `ApplyPassageLandingFloorMC` | 1347 | Reasserts the same support slab after MC-space disturbances and before the final XY edge seal. |
+162
View File
@@ -766,3 +766,165 @@ No rank is assigned to the 74-field parameter copy: the sampler did not measure
## Handoff ## Handoff
This round changed only diagnostic instrumentation and report artifacts under E:\Projet Unreal\VoxelM\Plugins\VoxelForge. Engine source was not modified. No commit, push, or stash was performed. The key caveat is explicit: the instrument has good closure and a successful planted-cost proof, but its 1 ms game overhead is above the requested timing/worker-CPU noise on static and above timing noise on moving. Treat the large, repeated component differences as useful; do not treat small single-digit improvements as established until that overhead is brought inside the game baseline. This round changed only diagnostic instrumentation and report artifacts under E:\Projet Unreal\VoxelM\Plugins\VoxelForge. Engine source was not modified. No commit, push, or stash was performed. The key caveat is explicit: the instrument has good closure and a successful planted-cost proof, but its 1 ms game overhead is above the requested timing/worker-CPU noise on static and above timing noise on moving. Treat the large, repeated component differences as useful; do not treat small single-digit improvements as established until that overhead is brought inside the game baseline.
## Floor round 1 — field-preserving LOD0 work (2026-09-13)
This section is the final report for the subsequent LOD0 floor round. It is deliberately separate
from the field-changing world-space tunnel-core merge and cave warp work. No density expression was
changed, no terrain-op order was changed, and no engine source was modified.
### Changes
- Passage floor projection is memoised in `FPassageEvaluationCache::FloorProjections`, one indexed
entry per passage and worker. Native projection, generic fallback projection, and walkable-air
classification reuse the same float outputs for an exact `(sample, passage)` hit. The sample key
compares the original double XYZ values; cache invalidation includes manager lifetime, layout
version, and chunk. `voxel.FloorRound1PassageProjectionCache` disables only this cache for A/B.
- Disabled `VoxelDensityProfile::FScopedTimer` construction now performs the enabled/probe test and
carries only an inactive flag; bucket/token initialization and out-of-line destructor work are
absent. Enabled profiling still enters/exits the same scope and preserves `End()` early closure.
- Proven-hot room/tunnel/passage candidate loops cache counts and use `GetData()` pointers where the
preceding count or spatial-index range proves the access. Other array checks remain intact.
- The two exact floor-projection XY loops scalarise the existing double `TVector2` temporaries while
retaining the original float conversions and result casts. `FVector::DistSquared` float paths were
left alone.
- `FSurfaceColumn` is now a trivial payload. `ComputeSurfaceColumn` writes all five values before
publishing `Computed`, so removing never-read default values removes its generated TLS constructor
leaf without changing a column or density.
### Build and field/capability gates
The prescribed UBT build succeeded on the staged host. The loaded runtime was verified on every field,
capability, and game process as:
`E:\Projet Unreal\VoxelM\Plugins\VoxelForge\Saved\BuildHost\HostProject\Plugins\VoxelForge\Binaries\Win64\UnrealEditor-VoxelForge.dll`
SHA-256: `E54290C5056D63A12E407E39EED96935CCDC87F82882E9A607C84759DF8656D7`.
Field runs:
- `E:\Projet Unreal\VoxelM\Plugins\VoxelForge\Saved\BuildHost\HostProject\Plugins\VoxelForge\Saved\FloorR1BookField2Off_20260913Out`
- `E:\Projet Unreal\VoxelM\Plugins\VoxelForge\Saved\BuildHost\HostProject\Plugins\VoxelForge\Saved\FloorR1BookField2On_20260913Out`
Both reports are `status=ok`, deterministic repeat `passed`, geometry CRC `07C14005`, 82,273
vertices, and 153,346 triangles. Both `geometry.obj` files are byte-identical with SHA-256
`b3e5f4c398dd0547c6c55f415872058246d4a292c2f0d636ee1cd0829de9b377`. The standing 841/841
per-tile triangle-identity record is preserved; the new game runs also report identical static and
moving applied tile/visible-tile/triangle totals in every off/on case.
Capability run:
`E:\Projet Unreal\VoxelM\Plugins\VoxelForge\Saved\BuildHost\HostProject\Plugins\VoxelForge\Saved\FloorR1BookCapability_20260913Out\explore.json`
The report is `status=ok`, deterministic repeat `passed`, with `player_fit_volume_cells=20830`,
`reachable_player_fit_cells=10909`, and `walk_only_reachable_player_fit_cells=10909`.
### Clean game A/B
Sampler off, diagnostics off, LOD0, `voxel.OuterClassifierMode=0`, interleaved in the order
off/on static A, on/off moving A, off/on static B, on/off moving B. Static runs had 343 LOD0 ready
samples; moving runs had 1,274.
| run | cache | request p50/p95 (s) | generation p50/p95 (s) | worker s | applied tiles / visible / triangles |
|---|---:|---:|---:|---:|---:|
| OffStaticA | 0 | 0.091413 / 0.145960 | 0.066545 / 0.108736 | 33.354203 | 841 / 447 / 669834 |
| OnStaticA | 1 | 0.091688 / 0.151787 | 0.066298 / 0.107866 | 33.549964 | 841 / 447 / 669834 |
| OnMovingA | 1 | 0.067281 / 0.138041 | 0.059686 / 0.108387 | 97.919700 | 2348 / 1055 / 1985216 |
| OffMovingA | 0 | 0.067659 / 0.135287 | 0.059316 / 0.107414 | 98.007684 | 2348 / 1055 / 1985216 |
| OffStaticB | 0 | 0.088911 / 0.146950 | 0.064988 / 0.104965 | 33.246204 | 841 / 447 / 669834 |
| OnStaticB | 1 | 0.090368 / 0.151585 | 0.065554 / 0.107661 | 33.323367 | 841 / 447 / 669834 |
| OnMovingB | 1 | 0.067540 / 0.139517 | 0.059545 / 0.108946 | 97.915109 | 2348 / 1055 / 1985216 |
| OffMovingB | 0 | 0.067745 / 0.137666 | 0.059707 / 0.108898 | 97.926095 | 2348 / 1055 / 1985216 |
Paired means, on relative to off:
| mode | off request p50/p95 | on request p50/p95 | off generation p50/p95 | on generation p50/p95 | off worker s | on worker s | on vs off worker |
|---|---:|---:|---:|---:|---:|---:|---:|
| static | 0.090162 / 0.146455 | 0.091028 / 0.151686 | 0.065767 / 0.106851 | 0.065926 / 0.107764 | 33.300204 | 33.436666 | +0.41% |
| moving | 0.067702 / 0.136477 | 0.067411 / 0.138779 | 0.059512 / 0.108156 | 0.059616 / 0.108667 | 97.966890 | 97.917405 | −0.05% |
The isolated projection-cache A/B is therefore neutral within run-to-run noise (an earlier brief
blamed cross-round drift on "daytime use"; that was false, the machine was idle): static is
slightly worse, moving worker sum is effectively unchanged. The implementation is retained because
the reuse is exact and the switch attributes the hypothesis; the result is not claimed as a speed
win. All eight runs had `validation_density_calls=0`, `obsolete_worker_tasks=0`, and loaded-DLL
verification true. The one-second process monitor observed game RSS from 1,946,759,168 to
2,019,475,456 bytes; these are peak samples, not a cross-round memory baseline.
### Final sampler-on moving run
Raw samples:
`E:\Projet Unreal\VoxelM\Plugins\VoxelForge\Saved\BuildHost\HostProject\Plugins\VoxelForge\Saved\VoxelStackSamples_game_3352.csv`
Summary:
`E:\Projet Unreal\VoxelM\Plugins\VoxelForge\Saved\BuildHost\HostProject\Plugins\VoxelForge\Saved\VoxelStackSummary_game_3352.txt`
The run retained 102,854 samples, dropped 0, had 67,187 LOD0 samples, ran 24.978248 s, and had
unknown-frame fraction 0.472158. It reported 2,348 applied tiles, 1,055 visible tiles, 1,985,216
triangles, 2,360 generation tasks, 103.016440 worker seconds, and zero obsolete worker tasks.
LOD0 exclusive top 20:
| # | samples | % | leaf |
|---:|---:|---:|---|
| 1 | 11531 | 17.1625 | `GetDensityWithParams` — `VoxelGenerator.cpp:2874` |
| 2 | 8658 | 12.8864 | unknown |
| 3 | 3068 | 4.5664 | `GetDensityAt` — `VoxelGenerator.cpp:1998` |
| 4 | 2702 | 4.0216 | `Sqrt` |
| 5 | 2312 | 3.4411 | `VoxelSDF::SmoothMin` |
| 6 | 2209 | 3.2878 | `EvaluateTunnelCoreWorld` lambda — `VoxelCaveMorphology.cpp:7648` |
| 7 | 1876 | 2.7922 | `EvaluateSDFCached` lambda — `VoxelCaveMorphology.cpp:7526` |
| 8 | 1788 | 2.6612 | `VF_ProjectNativePassageFloorUncached` — `VoxelStrateManager.cpp:597` |
| 9 | 1504 | 2.2385 | `VF_DistanceSquaredToAabb` — `VoxelCaveMorphology.cpp:68` |
| 10 | 1419 | 2.1120 | `VF_ProjectTunnelSegmentXY` — `VoxelCaveMorphology.cpp:970` |
| 11 | 1268 | 1.8873 | `ProjectWalkableTunnelFloor` — `VoxelPassageGeometry.h:314` |
| 12 | 1241 | 1.8471 | `VF_EvaluateSweptTunnelChain` — `VoxelCaveMorphology.cpp:1006` |
| 13 | 1009 | 1.5018 | swept-chain lambda — `VoxelCaveMorphology.cpp:1076` |
| 14 | 880 | 1.3098 | `VF_EvaluatePassageLandingSDF` — `VoxelCaveMorphology.cpp:5690` |
| 15 | 844 | 1.2562 | `GenerateMesh` — `VoxelMarchingCubesMesher.cpp:578` |
| 16 | 758 | 1.1282 | `FScopedTimer::End` — `VoxelDensityProfile.h:432` |
| 17 | 732 | 1.0895 | `EvaluateModifierSDF` — `VoxelStrateManager.cpp:2470` |
| 18 | 729 | 1.0850 | `ApplyPassageStructuralPostsMC` — `VoxelStrateManager.cpp:2808` |
| 19 | 700 | 1.0419 | `VF_GetPassageFloorProjectionCacheEntry` — `VoxelStrateManager.cpp:349` |
| 20 | 609 | 0.9064 | `EvaluateSDFCached` lambda — `VoxelCaveMorphology.cpp:7397` |
LOD0 inclusive top 20:
| # | samples | % | inclusive function |
|---:|---:|---:|---|
| 1 | 67187 | 100.0000 | `Invoke` |
| 2 | 67187 | 100.0000 | `LowLevelTasks::FTask::Init` lambda |
| 3 | 67187 | 100.0000 | `LowLevelTasks::TTaskDelegate::Call` |
| 4 | 67187 | 100.0000 | `UE::Tasks::Private::FTaskBase::Init` lambda |
| 5 | 67187 | 100.0000 | `LowLevelTasks::TTaskDelegate::CallAndMove` |
| 6 | 67187 | 100.0000 | `UE::Tasks::Private::FTaskBase::TryExecuteTask` |
| 7 | 67187 | 100.0000 | `TExecutableTaskBase::ExecuteTask` |
| 8 | 67187 | 100.0000 | `AVoxelWorld::LoadTile` lambda — `VoxelWorld.cpp:3782` |
| 9 | 67186 | 99.9985 | `AVoxelWorld::GenerateTileResult` — `VoxelWorld.cpp:4126` |
| 10 | 67100 | 99.8705 | `UVoxelMarchingCubesMesher::GenerateMesh` — `VoxelMarchingCubesMesher.cpp:578` |
| 11 | 65522 | 97.5218 | `UVoxelGenerator::GetDensityAt` — `VoxelGenerator.cpp:2297` |
| 12 | 40538 | 60.3361 | `UVoxelGenerator::GetDensityWithParams` — `VoxelGenerator.cpp:3117` |
| 13 | 18270 | 27.1928 | `VF_ForEachSpatialCandidate` — `VoxelCaveMorphology.cpp:1582` |
| 14 | 11093 | 16.5106 | `VoxelCaveMorphology::EvaluateSDFCached` — `VoxelCaveMorphology.cpp:7377` |
| 15 | 8829 | 13.1409 | `EvaluateSDFCached` lambda — `VoxelCaveMorphology.cpp:7526` |
| 16 | 8777 | 13.0635 | `VF_EvaluateSweptTunnel` — `VoxelCaveMorphology.cpp:1167` |
| 17 | 8581 | 12.7718 | `VoxelCaveMorphology::EvaluateTunnelCoreWorld` — `VoxelCaveMorphology.cpp:7732` |
| 18 | 8366 | 12.4518 | `VF_EvaluateSweptTunnelChain` — `VoxelCaveMorphology.cpp:1029` |
| 19 | 7811 | 11.6258 | `ApplyPassageCarvingOnly` — `VoxelStrateManager.cpp:2596` |
| 20 | 7682 | 11.4338 | `VF_ApplyPassageCarving` — `VoxelDensityPrimitives.h:75` |
### Unknown-source resolution
The old `FSurfaceColumn` constructor PCs are absent from this final sampler. The LOD0 unknown leaf
is 8,658 samples (12.8864%), but the raw PC cluster that accounts for the largest part is not plugin
code: `0x00007FF88382C080`, `...C08A`, `...C095`, `...C0A5`, and `...C0A9` map to the loaded
`UnrealEditor-Core.dll` at runtime RVA `0xC080`, inside its private `.pdata` function range
`0xB4D0–0xC32B`. The packaged Core binary has no PDB, so the source function name cannot be resolved.
Smaller unresolved groups map to `UnrealEditor-Engine.dll + RVA 0x500947` (generated reflection
`StaticStruct` code) and `UnrealEditor-CoreUObject.dll + RVA 0x76168`; none maps to
`Plugins\VoxelForge`.
This closes the actionable part of candidate 5: the previous plugin constructor was removed, while
the remaining unknown is an engine-symbolisation limitation. No commit, push, or stash was performed.
@@ -712,12 +712,16 @@ namespace
float SDFBlendRadius) float SDFBlendRadius)
{ {
FVFRoomFloorOwnership Result; FVFRoomFloorOwnership Result;
if (ControlPoints.Num() < 2 const int32 ControlPointCount = ControlPoints.Num();
|| ControlRadii.Num() != ControlPoints.Num()) if (ControlPointCount < 2
|| ControlRadii.Num() != ControlPointCount)
{ {
return Result; return Result;
} }
const FVector* ControlPointData = ControlPoints.GetData();
const float* ControlRadiusData = ControlRadii.GetData();
const float Fade = FMath::Max( const float Fade = FMath::Max(
FMath::Max(SDFBlendRadius, 0.0f) * 0.35f, FMath::Max(SDFBlendRadius, 0.0f) * 0.35f,
1.0f); 1.0f);
@@ -725,8 +729,8 @@ namespace
for (int32 Endpoint = 0; Endpoint < 2; ++Endpoint) for (int32 Endpoint = 0; Endpoint < 2; ++Endpoint)
{ {
const int32 ControlIndex = Endpoint == 0 const int32 ControlIndex = Endpoint == 0
? 0 : ControlPoints.Num() - 1; ? 0 : ControlPointCount - 1;
const FVector& Mouth = ControlPoints[ControlIndex]; const FVector& Mouth = ControlPointData[ControlIndex];
const float DX = static_cast<float>(Position.X - Mouth.X); const float DX = static_cast<float>(Position.X - Mouth.X);
const float DY = static_cast<float>(Position.Y - Mouth.Y); const float DY = static_cast<float>(Position.Y - Mouth.Y);
const float Distance = FMath::Sqrt(DX * DX + DY * DY); const float Distance = FMath::Sqrt(DX * DX + DY * DY);
@@ -752,7 +756,7 @@ namespace
continue; continue;
} }
const float DefaultFloorZ = static_cast<float>(Mouth.Z) const float DefaultFloorZ = static_cast<float>(Mouth.Z)
- FMath::Abs(ControlRadii[ControlIndex]); - FMath::Abs(ControlRadiusData[ControlIndex]);
const float StoredFloorZ = Endpoint == 0 ? MouthFloorZA : MouthFloorZB; const float StoredFloorZ = Endpoint == 0 ? MouthFloorZA : MouthFloorZB;
const float FloorZ = StoredFloorZ > -FLT_MAX const float FloorZ = StoredFloorZ > -FLT_MAX
&& FMath::IsFinite(StoredFloorZ) && FMath::IsFinite(StoredFloorZ)
@@ -815,26 +819,23 @@ namespace
const FVector& B, float RadiusB, const FVector& B, float RadiusB,
float T, int32 SegmentIndex, int32 NumSegments, float T, int32 SegmentIndex, int32 NumSegments,
const FCachedTunnel& Tunnel, const FCachedTunnel& Tunnel,
const TArray<FTunnelFloorSegmentProfile>* FloorProfiles, const FTunnelFloorSegmentProfile* FloorProfile,
const FVFRoomFloorOwnership* RoomFloorOwnership) const FVFRoomFloorOwnership* RoomFloorOwnership)
{ {
FVFTunnelFloorTerms Terms; FVFTunnelFloorTerms Terms;
if (FloorProfiles != nullptr if (FloorProfile != nullptr)
&& FloorProfiles->Num() == NumSegments
&& FloorProfiles->IsValidIndex(SegmentIndex))
{ {
const FTunnelFloorSegmentProfile& Profile = (*FloorProfiles)[SegmentIndex];
const float ClampedT = FMath::Clamp(T, 0.0f, 1.0f); const float ClampedT = FMath::Clamp(T, 0.0f, 1.0f);
const float ProfileT = Profile.NumSteps > 0 const float ProfileT = FloorProfile->NumSteps > 0
? (ClampedT >= 1.0f ? (ClampedT >= 1.0f
? 1.0f ? 1.0f
: FMath::FloorToFloat(ClampedT : FMath::FloorToFloat(ClampedT
* static_cast<float>(Profile.NumSteps)) * static_cast<float>(FloorProfile->NumSteps))
/ static_cast<float>(Profile.NumSteps)) / static_cast<float>(FloorProfile->NumSteps))
: ClampedT; : ClampedT;
Terms.BaseFloorZ = FMath::Lerp( Terms.BaseFloorZ = FMath::Lerp(
Profile.StartFloorZ, Profile.EndFloorZ, ProfileT); FloorProfile->StartFloorZ, FloorProfile->EndFloorZ, ProfileT);
Terms.ReliefScale = Profile.ReliefScale; Terms.ReliefScale = FloorProfile->ReliefScale;
} }
else else
{ {
@@ -878,8 +879,10 @@ namespace
RoomFloorOwnership->Weight, 0.0f, 1.0f); RoomFloorOwnership->Weight, 0.0f, 1.0f);
} }
const float HorizontalRun = FVector2D( const double DeltaX = static_cast<double>(static_cast<float>(B.X - A.X));
static_cast<float>(B.X - A.X), static_cast<float>(B.Y - A.Y)).Size(); const double DeltaY = static_cast<double>(static_cast<float>(B.Y - A.Y));
const float HorizontalRun = static_cast<float>(FMath::Sqrt(
DeltaX * DeltaX + DeltaY * DeltaY));
Terms.ReliefEnvelope = 1.0f; Terms.ReliefEnvelope = 1.0f;
if (SegmentIndex == 0) if (SegmentIndex == 0)
{ {
@@ -926,12 +929,12 @@ namespace
const void* CacheIdentity, int32 TunnelIndex, const void* CacheIdentity, int32 TunnelIndex,
bool bEvaluateRelief, bool bEvaluateRelief,
const FVFFloorReliefColumnKey& Column, const FVFFloorReliefColumnKey& Column,
const TArray<FTunnelFloorSegmentProfile>* FloorProfiles, const FTunnelFloorSegmentProfile* FloorProfile,
const FVFRoomFloorOwnership* RoomFloorOwnership) const FVFRoomFloorOwnership* RoomFloorOwnership)
{ {
const FVFTunnelFloorTerms Terms = VF_TunnelFloorTerms( const FVFTunnelFloorTerms Terms = VF_TunnelFloorTerms(
A, RadiusA, B, RadiusB, A, RadiusA, B, RadiusB,
T, SegmentIndex, NumSegments, Tunnel, FloorProfiles, T, SegmentIndex, NumSegments, Tunnel, FloorProfile,
RoomFloorOwnership); RoomFloorOwnership);
return VF_TunnelFloorFromTerms( return VF_TunnelFloorFromTerms(
Position, Tunnel, Terms, Position, Tunnel, Terms,
@@ -942,10 +945,12 @@ namespace
const FVector& Position, const FVector& A, const FVector& B, const FVector& Position, const FVector& A, const FVector& B,
float& OutT, float& OutDistanceSquared) float& OutT, float& OutDistanceSquared)
{ {
const FVector2D AXY(static_cast<float>(A.X), static_cast<float>(A.Y)); const double AXY_X = static_cast<double>(static_cast<float>(A.X));
const FVector2D Delta( const double AXY_Y = static_cast<double>(static_cast<float>(A.Y));
static_cast<float>(B.X - A.X), static_cast<float>(B.Y - A.Y)); const double DeltaX = static_cast<double>(static_cast<float>(B.X - A.X));
const float LengthSquared = Delta.SizeSquared(); const double DeltaY = static_cast<double>(static_cast<float>(B.Y - A.Y));
const float LengthSquared = static_cast<float>(
DeltaX * DeltaX + DeltaY * DeltaY);
if (LengthSquared <= KINDA_SMALL_NUMBER) if (LengthSquared <= KINDA_SMALL_NUMBER)
{ {
OutT = 0.0f; OutT = 0.0f;
@@ -953,13 +958,17 @@ namespace
return false; return false;
} }
const FVector2D QueryXY( const double QueryX = static_cast<double>(static_cast<float>(Position.X));
static_cast<float>(Position.X), static_cast<float>(Position.Y)); const double QueryY = static_cast<double>(static_cast<float>(Position.Y));
OutT = FMath::Clamp( const double Dot = (QueryX - AXY_X) * DeltaX
FVector2D::DotProduct(QueryXY - AXY, Delta) / LengthSquared, + (QueryY - AXY_Y) * DeltaY;
0.0f, 1.0f); OutT = FMath::Clamp(Dot / LengthSquared, 0.0f, 1.0f);
const FVector2D ClosestXY = AXY + Delta * OutT; const double ClosestX = AXY_X + DeltaX * OutT;
OutDistanceSquared = (QueryXY - ClosestXY).SizeSquared(); const double ClosestY = AXY_Y + DeltaY * OutT;
const double DistanceX = QueryX - ClosestX;
const double DistanceY = QueryY - ClosestY;
OutDistanceSquared = static_cast<float>(
DistanceX * DistanceX + DistanceY * DistanceY);
return true; return true;
} }
@@ -976,22 +985,30 @@ namespace
const FVFRoomFloorOwnership* RoomFloorOwnership) const FVFRoomFloorOwnership* RoomFloorOwnership)
{ {
FVFTunnelShapeEvaluation Result; FVFTunnelShapeEvaluation Result;
if (ControlPoints.Num() < 2 const int32 ControlPointCount = ControlPoints.Num();
|| ControlRadii.Num() != ControlPoints.Num()) if (ControlPointCount < 2
|| ControlRadii.Num() != ControlPointCount)
{ {
return Result; return Result;
} }
const int32 NumSegments = ControlPoints.Num() - 1; const int32 NumSegments = ControlPointCount - 1;
const FVector* ControlPointData = ControlPoints.GetData();
const float* ControlRadiusData = ControlRadii.GetData();
const FTunnelFloorSegmentProfile* FloorProfileData = nullptr;
if (FloorProfiles != nullptr && FloorProfiles->Num() == NumSegments)
{
FloorProfileData = FloorProfiles->GetData();
}
const float FloorBlend = FMath::Max(SDFBlendRadius, 0.0f) * 0.35f; const float FloorBlend = FMath::Max(SDFBlendRadius, 0.0f) * 0.35f;
for (int32 SegmentIndex = 0; for (int32 SegmentIndex = 0;
SegmentIndex < NumSegments; SegmentIndex < NumSegments;
++SegmentIndex) ++SegmentIndex)
{ {
const FVector& A = ControlPoints[SegmentIndex]; const FVector& A = ControlPointData[SegmentIndex];
const FVector& B = ControlPoints[SegmentIndex + 1]; const FVector& B = ControlPointData[SegmentIndex + 1];
const float RadiusA = ControlRadii[SegmentIndex]; const float RadiusA = ControlRadiusData[SegmentIndex];
const float RadiusB = ControlRadii[SegmentIndex + 1]; const float RadiusB = ControlRadiusData[SegmentIndex + 1];
float T = 0.0f; float T = 0.0f;
float HorizontalDistanceSquared = FLT_MAX; float HorizontalDistanceSquared = FLT_MAX;
@@ -1007,17 +1024,12 @@ namespace
continue; continue;
} }
const FTunnelFloorSegmentProfile* Profile = FloorProfileData != nullptr
? FloorProfileData + SegmentIndex : nullptr;
const FVFTunnelFloorTerms FloorTerms = VF_TunnelFloorTerms( const FVFTunnelFloorTerms FloorTerms = VF_TunnelFloorTerms(
A, RadiusA, B, RadiusB, A, RadiusA, B, RadiusB,
T, SegmentIndex, NumSegments, Tunnel, FloorProfiles, T, SegmentIndex, NumSegments, Tunnel, Profile,
RoomFloorOwnership); RoomFloorOwnership);
const FTunnelFloorSegmentProfile* Profile = nullptr;
if (FloorProfiles != nullptr
&& FloorProfiles->Num() == NumSegments
&& FloorProfiles->IsValidIndex(SegmentIndex))
{
Profile = &(*FloorProfiles)[SegmentIndex];
}
const float NaturalFloorZ = Profile != nullptr const float NaturalFloorZ = Profile != nullptr
? FMath::Lerp( ? FMath::Lerp(
Profile->NaturalStartFloorZ, Profile->NaturalStartFloorZ,
@@ -1220,7 +1232,14 @@ namespace
return false; return false;
} }
const int32 NumSegments = ControlPoints.Num() - 1; const int32 ControlPointCount = ControlPoints.Num();
const int32 NumSegments = ControlPointCount - 1;
const FVector* ControlPointData = ControlPoints.GetData();
const float* ControlRadiusData = ControlRadii.GetData();
const TArray<FTunnelFloorSegmentProfile>* FloorProfiles = bWorldChain
? &Tunnel.WorldFloorProfiles : &Tunnel.FloorProfiles;
const FTunnelFloorSegmentProfile* FloorProfileData =
FloorProfiles->Num() == NumSegments ? FloorProfiles->GetData() : nullptr;
float BestDistanceSquared = FLT_MAX; float BestDistanceSquared = FLT_MAX;
bool bFound = false; bool bFound = false;
for (int32 SegmentIndex = 0; for (int32 SegmentIndex = 0;
@@ -1231,22 +1250,22 @@ namespace
float DistanceSquared = FLT_MAX; float DistanceSquared = FLT_MAX;
if (!VF_ProjectTunnelSegmentXY( if (!VF_ProjectTunnelSegmentXY(
Position, Position,
ControlPoints[SegmentIndex], ControlPoints[SegmentIndex + 1], ControlPointData[SegmentIndex], ControlPointData[SegmentIndex + 1],
T, DistanceSquared) T, DistanceSquared)
|| DistanceSquared >= BestDistanceSquared) || DistanceSquared >= BestDistanceSquared)
{ {
continue; continue;
} }
const float RadiusA = ControlRadii[SegmentIndex]; const float RadiusA = ControlRadiusData[SegmentIndex];
const float RadiusB = ControlRadii[SegmentIndex + 1]; const float RadiusB = ControlRadiusData[SegmentIndex + 1];
OutFloorZ = VF_TunnelFloorAtSegment( OutFloorZ = VF_TunnelFloorAtSegment(
ControlPoints[SegmentIndex], RadiusA, ControlPointData[SegmentIndex], RadiusA,
ControlPoints[SegmentIndex + 1], RadiusB, ControlPointData[SegmentIndex + 1], RadiusB,
T, SegmentIndex, NumSegments, Position, Tunnel, T, SegmentIndex, NumSegments, Position, Tunnel,
CacheIdentity, TunnelIndex, /*bEvaluateRelief=*/true, CacheIdentity, TunnelIndex, /*bEvaluateRelief=*/true,
ReliefColumn, ReliefColumn,
bWorldChain ? &Tunnel.WorldFloorProfiles : &Tunnel.FloorProfiles, FloorProfileData != nullptr ? FloorProfileData + SegmentIndex : nullptr,
RoomFloorOwnership); RoomFloorOwnership);
OutSupportRadius = FMath::Max( OutSupportRadius = FMath::Max(
FMath::Min(FMath::Abs(RadiusA), FMath::Abs(RadiusB)) - 0.5f, FMath::Min(FMath::Abs(RadiusA), FMath::Abs(RadiusB)) - 0.5f,
@@ -1331,8 +1350,9 @@ bool FChunkSDFSpatialIndex::GetRange(
return false; return false;
} }
OutBegin = CellOffsets[static_cast<int32>(Slot)]; const int32* CellOffsetData = CellOffsets.GetData();
OutEnd = CellOffsets[static_cast<int32>(Slot + 1)]; OutBegin = CellOffsetData[static_cast<int32>(Slot)];
OutEnd = CellOffsetData[static_cast<int32>(Slot + 1)];
return OutBegin >= 0 && OutEnd >= OutBegin && OutEnd <= ItemIndices.Num(); return OutBegin >= 0 && OutEnd >= OutBegin && OutEnd <= ItemIndices.Num();
} }
@@ -1546,9 +1566,10 @@ namespace
int32 CandidateCount = 0; int32 CandidateCount = 0;
if (bUseSpatialIndex && Index.GetRange(WorldX, WorldY, Begin, End)) if (bUseSpatialIndex && Index.GetRange(WorldX, WorldY, Begin, End))
{ {
const int32* ItemIndexData = Index.ItemIndices.GetData();
for (int32 Cursor = Begin; Cursor < End; ++Cursor) for (int32 Cursor = Begin; Cursor < End; ++Cursor)
{ {
const int32 ItemIndex = Index.ItemIndices[Cursor]; const int32 ItemIndex = ItemIndexData[Cursor];
if (ItemIndex >= 0 && ItemIndex < ItemCount) if (ItemIndex >= 0 && ItemIndex < ItemCount)
{ {
++CandidateCount; ++CandidateCount;
@@ -7354,6 +7375,9 @@ float VoxelCaveMorphology::EvaluateSDFCached(
const FVector Pos(WorldX, WorldY, WorldZ); const FVector Pos(WorldX, WorldY, WorldZ);
const FVFFloorReliefColumnKey ReliefColumn = const FVFFloorReliefColumnKey ReliefColumn =
VF_MakeFloorReliefColumnKey(Pos.X, Pos.Y); VF_MakeFloorReliefColumnKey(Pos.X, Pos.Y);
const FCachedRoom* RoomData = Cache.Rooms.GetData();
const FCachedRoomFloorJoin* RoomFloorJoinData = Cache.RoomFloorJoins.GetData();
const FCachedTunnel* TunnelData = Cache.Tunnels.GetData();
// Track which room contributes the smallest (most-inside) raw SDF. // Track which room contributes the smallest (most-inside) raw SDF.
// This is used by the terrain ops system to find the "owning" room for // This is used by the terrain ops system to find the "owning" room for
@@ -7367,7 +7391,7 @@ float VoxelCaveMorphology::EvaluateSDFCached(
//========================================================================= //=========================================================================
auto EvaluateRoom = [&](int32 RoomIdx) auto EvaluateRoom = [&](int32 RoomIdx)
{ {
const FCachedRoom& Room = Cache.Rooms[RoomIdx]; const FCachedRoom& Room = RoomData[RoomIdx];
// --- DISTANCE CULL --- // --- DISTANCE CULL ---
const float DistSq = VF_FloatDistSquared(WorldX, WorldY, WorldZ, Room.Center); const float DistSq = VF_FloatDistSquared(WorldX, WorldY, WorldZ, Room.Center);
@@ -7476,7 +7500,7 @@ float VoxelCaveMorphology::EvaluateSDFCached(
// not participate in nearest-room terrain-op ownership. // not participate in nearest-room terrain-op ownership.
auto EvaluateJoin = [&](int32 JoinIdx) auto EvaluateJoin = [&](int32 JoinIdx)
{ {
const FCachedRoomFloorJoin& Join = Cache.RoomFloorJoins[JoinIdx]; const FCachedRoomFloorJoin& Join = RoomFloorJoinData[JoinIdx];
const float DistSq = VF_FloatDistSquared(WorldX, WorldY, WorldZ, Join.BoundCenter); const float DistSq = VF_FloatDistSquared(WorldX, WorldY, WorldZ, Join.BoundCenter);
if (DistSq > Join.BoundRadiusSq) return; if (DistSq > Join.BoundRadiusSq) return;
@@ -7497,7 +7521,7 @@ float VoxelCaveMorphology::EvaluateSDFCached(
//========================================================================= //=========================================================================
auto EvaluateTunnel = [&](int32 TunnelIdx) auto EvaluateTunnel = [&](int32 TunnelIdx)
{ {
const FCachedTunnel& Tunnel = Cache.Tunnels[TunnelIdx]; const FCachedTunnel& Tunnel = TunnelData[TunnelIdx];
if (Tunnel.SDFInfluenceRadius > 0.0f if (Tunnel.SDFInfluenceRadius > 0.0f
&& VF_DistanceSquaredToAabb( && VF_DistanceSquaredToAabb(
WorldX, WorldY, WorldZ, WorldX, WorldY, WorldZ,
@@ -7555,10 +7579,11 @@ float VoxelCaveMorphology::EvaluateTunnelCoreSDF(
{ {
const FVector Pos(WorldX, WorldY, WorldZ); const FVector Pos(WorldX, WorldY, WorldZ);
float MinSDF = FLT_MAX; float MinSDF = FLT_MAX;
const FCachedTunnel* TunnelData = Cache.Tunnels.GetData();
auto EvaluateTunnel = [&](int32 TunnelIdx) auto EvaluateTunnel = [&](int32 TunnelIdx)
{ {
const FCachedTunnel& Tunnel = Cache.Tunnels[TunnelIdx]; const FCachedTunnel& Tunnel = TunnelData[TunnelIdx];
if (Tunnel.SDFInfluenceRadius > 0.0f if (Tunnel.SDFInfluenceRadius > 0.0f
&& VF_DistanceSquaredToAabb( && VF_DistanceSquaredToAabb(
WorldX, WorldY, WorldZ, WorldX, WorldY, WorldZ,
@@ -7597,6 +7622,7 @@ FTunnelCoreWorldEvaluation VoxelCaveMorphology::EvaluateTunnelCoreWorld(
VoxelDensityProfile::EBucket::TunnelCoreWorld); VoxelDensityProfile::EBucket::TunnelCoreWorld);
const FVector Pos(WorldX, WorldY, WorldZ); const FVector Pos(WorldX, WorldY, WorldZ);
FTunnelCoreWorldEvaluation Result; FTunnelCoreWorldEvaluation Result;
const FCachedTunnel* TunnelData = Cache.Tunnels.GetData();
if (SupportColumn != nullptr && VoxelDensityProfile::AreCountersEnabled()) if (SupportColumn != nullptr && VoxelDensityProfile::AreCountersEnabled())
{ {
@@ -7605,7 +7631,7 @@ FTunnelCoreWorldEvaluation VoxelCaveMorphology::EvaluateTunnelCoreWorld(
} }
auto EvaluateTunnel = [&](int32 TunnelIdx) auto EvaluateTunnel = [&](int32 TunnelIdx)
{ {
const FCachedTunnel& Tunnel = Cache.Tunnels[TunnelIdx]; const FCachedTunnel& Tunnel = TunnelData[TunnelIdx];
const bool bHasWorldChain = Tunnel.WorldControlPoints.Num() >= 2 const bool bHasWorldChain = Tunnel.WorldControlPoints.Num() >= 2
&& Tunnel.WorldControlRadii.Num() == Tunnel.WorldControlPoints.Num(); && Tunnel.WorldControlRadii.Num() == Tunnel.WorldControlPoints.Num();
const FVector& BoundCenter = bHasWorldChain const FVector& BoundCenter = bHasWorldChain
@@ -7731,10 +7757,11 @@ bool VoxelCaveMorphology::IsTunnelSupportFloorWorldPoint(
bool bUseSpatialIndex) bool bUseSpatialIndex)
{ {
const FVector Pos(WorldX, WorldY, WorldZ); const FVector Pos(WorldX, WorldY, WorldZ);
const FCachedTunnel* TunnelData = Cache.Tunnels.GetData();
auto TestTunnel = [&](int32 TunnelIdx) -> bool auto TestTunnel = [&](int32 TunnelIdx) -> bool
{ {
const FCachedTunnel& Tunnel = Cache.Tunnels[TunnelIdx]; const FCachedTunnel& Tunnel = TunnelData[TunnelIdx];
const bool bHasWorldChain = Tunnel.WorldControlPoints.Num() >= 2 const bool bHasWorldChain = Tunnel.WorldControlPoints.Num() >= 2
&& Tunnel.WorldControlRadii.Num() == Tunnel.WorldControlPoints.Num(); && Tunnel.WorldControlRadii.Num() == Tunnel.WorldControlPoints.Num();
const FVector& CenterlineMin = bHasWorldChain const FVector& CenterlineMin = bHasWorldChain
@@ -7789,9 +7816,10 @@ bool VoxelCaveMorphology::IsTunnelSupportFloorWorldPoint(
int32 CandidateCount = 0; int32 CandidateCount = 0;
auto TestRange = [&](int32 Begin, int32 End) auto TestRange = [&](int32 Begin, int32 End)
{ {
const int32* ItemIndexData = Cache.TunnelSpatialIndex.ItemIndices.GetData();
for (int32 Cursor = Begin; Cursor < End; ++Cursor) for (int32 Cursor = Begin; Cursor < End; ++Cursor)
{ {
const int32 TunnelIdx = Cache.TunnelSpatialIndex.ItemIndices[Cursor]; const int32 TunnelIdx = ItemIndexData[Cursor];
if (!Cache.Tunnels.IsValidIndex(TunnelIdx)) if (!Cache.Tunnels.IsValidIndex(TunnelIdx))
{ {
continue; continue;
@@ -7858,10 +7886,11 @@ void VoxelCaveMorphology::BuildTunnelSupportFloorColumn(
} }
const FVector ColumnPosition(WorldX, WorldY, 0.0f); const FVector ColumnPosition(WorldX, WorldY, 0.0f);
const FCachedTunnel* TunnelData = Cache.Tunnels.GetData();
auto EvaluateTunnel = [&](int32 TunnelIdx) auto EvaluateTunnel = [&](int32 TunnelIdx)
{ {
const FCachedTunnel& Tunnel = Cache.Tunnels[TunnelIdx]; const FCachedTunnel& Tunnel = TunnelData[TunnelIdx];
const FVFRoomFloorOwnership RoomMouthOwnership = const FVFRoomFloorOwnership RoomMouthOwnership =
VF_FindWorldTunnelMouthOwnership(ColumnPosition, Tunnel, Cache.SDFBlendRadius); VF_FindWorldTunnelMouthOwnership(ColumnPosition, Tunnel, Cache.SDFBlendRadius);
float FloorZ = 0.0f; float FloorZ = 0.0f;
@@ -858,7 +858,7 @@ namespace VoxelDensityProfile
FScopeToken BeginScope(EBucket Bucket) FScopeToken BeginScope(EBucket Bucket)
{ {
FScopeToken Token; FScopeToken Token{};
if (!IsEnabledFast()) if (!IsEnabledFast())
{ {
return Token; return Token;
@@ -972,15 +972,4 @@ namespace VoxelDensityProfile
GSampledScopeActive = Token.bPreviousSampledScopeActive; GSampledScopeActive = Token.bPreviousSampledScopeActive;
} }
FScopedTimer::~FScopedTimer()
{
End();
}
void FScopedTimer::End()
{
if (!Token.bEntered) { return; }
EndScope(Bucket, Token);
Token.bEntered = false;
}
} }
+15 -9
View File
@@ -206,8 +206,12 @@ private:
// uphill). See SurfaceDensityFromColumn for the warped-terrain union that makes the shelf. // uphill). See SurfaceDensityFromColumn for the warped-terrain union that makes the shelf.
struct FSurfaceColumn struct FSurfaceColumn
{ {
float TerrainZ = 0.0f; float CeilSurf = 0.0f; // Toutes les sorties sont écrites par ComputeSurfaceColumn avant Computed=true ; les valeurs
float OverhangAmp = 0.0f; float DirX = 0.0f; float DirY = 0.0f; // par défaut ne sont donc jamais lues et leur constructeur implicite coûte dans le TLS.
// Every output is written by ComputeSurfaceColumn before Computed=true; the defaults are never
// read, and removing them keeps FSurfaceColumn trivial so TLS construction has no leaf work.
float TerrainZ, CeilSurf;
float OverhangAmp, DirX, DirY;
}; };
struct FSurfaceColumnBox struct FSurfaceColumnBox
@@ -2355,7 +2359,6 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co
Result = GetMazeDensity(WorldX, WorldY, WorldZ, CP_Maze); break; Result = GetMazeDensity(WorldX, WorldY, WorldZ, CP_Maze); break;
case ECaveGeneratorType::SurfaceWorld: case ECaveGeneratorType::SurfaceWorld:
{ {
FSurfaceColumn Col;
// Integer XY (the density grid) → reuse the column down its whole Z extent (T1.a). // Integer XY (the density grid) → reuse the column down its whole Z extent (T1.a).
// Fractional XY (gradient-normal samples) → compute directly (no cache key). // Fractional XY (gradient-normal samples) → compute directly (no cache key).
if (WorldX == FMath::FloorToFloat(WorldX) && WorldY == FMath::FloorToFloat(WorldY)) if (WorldX == FMath::FloorToFloat(WorldX) && WorldY == FMath::FloorToFloat(WorldY))
@@ -2373,17 +2376,20 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co
Box.Cols[CI].OverhangAmp, Box.Cols[CI].DirX, Box.Cols[CI].DirY); Box.Cols[CI].OverhangAmp, Box.Cols[CI].DirX, Box.Cols[CI].DirY);
Box.Computed[CI] = true; Box.Computed[CI] = true;
} }
Col = Box.Cols[CI]; const FSurfaceColumn& Col = Box.Cols[CI];
Result = SurfaceDensityFromColumn(WorldX, WorldY, WorldZ,
Col.TerrainZ, Col.CeilSurf, Col.OverhangAmp,
Col.DirX, Col.DirY, CP_Surface);
} }
else else
{ {
float TerrainZ, CeilSurf, OverhangAmp, DirX, DirY;
ComputeSurfaceColumn(WorldX, WorldY, ChunkCoord.Z, CP_Surface, CP_BiomeCtx, ComputeSurfaceColumn(WorldX, WorldY, ChunkCoord.Z, CP_Surface, CP_BiomeCtx,
CP_SurfaceBiomeParams, CP_BiomeCache, Col.TerrainZ, Col.CeilSurf, CP_SurfaceBiomeParams, CP_BiomeCache, TerrainZ, CeilSurf,
Col.OverhangAmp, Col.DirX, Col.DirY); OverhangAmp, DirX, DirY);
Result = SurfaceDensityFromColumn(WorldX, WorldY, WorldZ,
TerrainZ, CeilSurf, OverhangAmp, DirX, DirY, CP_Surface);
} }
Result = SurfaceDensityFromColumn(WorldX, WorldY, WorldZ,
Col.TerrainZ, Col.CeilSurf, Col.OverhangAmp, Col.DirX, Col.DirY, CP_Surface);
break; break;
} }
case ECaveGeneratorType::VerticalShafts: case ECaveGeneratorType::VerticalShafts:
+288 -60
View File
@@ -12,6 +12,7 @@
#include "VoxelStartupTrace.h" #include "VoxelStartupTrace.h"
#include "VoxelTerrainOpDefinition.h" // For UVoxelTerrainOpDefinition::ApplyTo #include "VoxelTerrainOpDefinition.h" // For UVoxelTerrainOpDefinition::ApplyTo
#include "VoxelBiomeDefinition.h" // For UVoxelBiomeDefinition (biome context flatten) #include "VoxelBiomeDefinition.h" // For UVoxelBiomeDefinition (biome context flatten)
#include "HAL/IConsoleManager.h"
#include "Misc/CommandLine.h" #include "Misc/CommandLine.h"
#include "Misc/Crc.h" #include "Misc/Crc.h"
#include "Misc/Parse.h" #include "Misc/Parse.h"
@@ -23,6 +24,15 @@ namespace
{ {
std::atomic<uint64> GNextStrateManagerLifetimeId { 0 }; std::atomic<uint64> GNextStrateManagerLifetimeId { 0 };
// Exact A/B switch for the repeated passage-floor projection item. The other field-
// preserving rewrites in this round are deliberately unconditional; this switch isolates
// the projection reuse without changing any density arithmetic.
int32 GVoxelForgeFloorRound1PassageProjectionCache = 1;
FAutoConsoleVariableRef CVarVoxelForgeFloorRound1PassageProjectionCache(
TEXT("voxel.FloorRound1PassageProjectionCache"),
GVoxelForgeFloorRound1PassageProjectionCache,
TEXT("Reuse exact passage floor projections within one density sample; 0 disables the A/B cache."));
struct FRuntimeRoughnessOverrides struct FRuntimeRoughnessOverrides
{ {
bool bSurfaceRoughness = false; bool bSurfaceRoughness = false;
@@ -185,6 +195,43 @@ static float PassageFBM(float X, float Seed)
namespace namespace
{ {
// One projection result per passage is enough for the current voxel sample. The surrounding
// passage cache already moves with (manager, layout, chunk); a generation invalidates old flags
// before any caller can observe them. Keeping the entries indexed by passage avoids the TMap
// rehash path that this hot loop used to carry, while a collision-free hit remains exact.
struct FPassageFloorProjectionCacheEntry
{
// The sample key lives once in FPassageEvaluationCache. A monotonically increasing
// generation makes an entry's old flags inert without clearing its float payload.
// The payload is only read after the corresponding valid flag is set.
uint64 SampleGeneration = 0;
uint8 ComputedFlags = 0;
bool bNativeValid = false;
float NativeFloorZ = 0.0f;
float NativeSupportRadius = 0.0f;
bool bGenericValid = false;
float GenericFloorZ = 0.0f;
float GenericSupportRadius = 0.0f;
bool bWalkableAir = false;
enum : uint8
{
NativeComputed = 1u << 0,
GenericComputed = 1u << 1,
WalkableAirComputed = 1u << 2
};
FORCEINLINE bool Has(uint8 Flag) const
{
return (ComputedFlags & Flag) != 0;
}
FORCEINLINE void SetComputed(uint8 Flag)
{
ComputedFlags |= Flag;
}
};
/** /**
* One shared passage cache for the tube SDF and landing-floor fill. * One shared passage cache for the tube SDF and landing-floor fill.
* *
@@ -199,6 +246,12 @@ namespace
FIntVector Chunk = FIntVector(INT32_MAX, INT32_MAX, INT32_MAX); FIntVector Chunk = FIntVector(INT32_MAX, INT32_MAX, INT32_MAX);
uint32 Version = 0xFFFFFFFFu; uint32 Version = 0xFFFFFFFFu;
TArray<int32> Nearby; TArray<int32> Nearby;
TArray<FPassageFloorProjectionCacheEntry> FloorProjections;
bool bFloorProjectionSampleValid = false;
double FloorProjectionX = 0.0;
double FloorProjectionY = 0.0;
double FloorProjectionZ = 0.0;
uint64 FloorProjectionGeneration = 0;
}; };
FPassageEvaluationCache& VF_GetPassageEvaluationCache() FPassageEvaluationCache& VF_GetPassageEvaluationCache()
@@ -225,6 +278,8 @@ namespace
Cache.Chunk = ChunkCoord; Cache.Chunk = ChunkCoord;
Cache.Version = Version; Cache.Version = Version;
Cache.Nearby.Reset(); Cache.Nearby.Reset();
Cache.FloorProjections.Reset();
Cache.bFloorProjectionSampleValid = false;
if (!Manager) return Cache.Nearby; if (!Manager) return Cache.Nearby;
const FVector ChunkCenter( const FVector ChunkCenter(
@@ -233,6 +288,7 @@ namespace
(ChunkCoord.Z + 0.5f) * (float)CHUNK_SIZE); (ChunkCoord.Z + 0.5f) * (float)CHUNK_SIZE);
const float ChunkRadius = (float)CHUNK_SIZE * 0.8660254f + 3.0f; const float ChunkRadius = (float)CHUNK_SIZE * 0.8660254f + 3.0f;
const TArray<FVoxelPassage>& Passages = Manager->GetPassages(); const TArray<FVoxelPassage>& Passages = Manager->GetPassages();
Cache.FloorProjections.SetNum(Passages.Num());
for (int32 PassageIndex = 0; PassageIndex < Passages.Num(); ++PassageIndex) for (int32 PassageIndex = 0; PassageIndex < Passages.Num(); ++PassageIndex)
{ {
const FVoxelPassage& Passage = Passages[PassageIndex]; const FVoxelPassage& Passage = Passages[PassageIndex];
@@ -245,6 +301,59 @@ namespace
return Cache.Nearby; return Cache.Nearby;
} }
FPassageFloorProjectionCacheEntry* VF_GetPassageFloorProjectionCacheEntry(
const UVoxelStrateManager* Manager,
const FVector& Position,
int32 PassageIndex)
{
if (GVoxelForgeFloorRound1PassageProjectionCache == 0
|| Manager == nullptr || PassageIndex < 0)
{
return nullptr;
}
FPassageEvaluationCache& Cache = VF_GetPassageEvaluationCache();
if (Cache.Owner != Manager
|| Cache.OwnerLifetimeId != Manager->GetCacheLifetimeId()
|| Cache.Version != Manager->GetLayoutVersion()
|| PassageIndex >= Cache.FloorProjections.Num())
{
return nullptr;
}
if (!Cache.bFloorProjectionSampleValid
|| Cache.FloorProjectionX != Position.X
|| Cache.FloorProjectionY != Position.Y
|| Cache.FloorProjectionZ != Position.Z)
{
Cache.bFloorProjectionSampleValid = true;
Cache.FloorProjectionX = Position.X;
Cache.FloorProjectionY = Position.Y;
Cache.FloorProjectionZ = Position.Z;
++Cache.FloorProjectionGeneration;
if (Cache.FloorProjectionGeneration == 0)
{
// Keep generation zero reserved for never-initialized entries. This is
// unreachable in a practical worker lifetime, but preserves the invariant if a
// process survives long enough to wrap the counter.
for (FPassageFloorProjectionCacheEntry& Candidate : Cache.FloorProjections)
{
Candidate.SampleGeneration = 0;
}
++Cache.FloorProjectionGeneration;
}
}
FPassageFloorProjectionCacheEntry* Entry =
Cache.FloorProjections.GetData() + PassageIndex;
if (Entry->SampleGeneration != Cache.FloorProjectionGeneration)
{
Entry->SampleGeneration = Cache.FloorProjectionGeneration;
Entry->ComputedFlags = 0;
}
return Entry;
}
// Passage relief is a function of the immutable passage descriptor and the actual XY column. // Passage relief is a function of the immutable passage descriptor and the actual XY column.
// Keep the cache fixed-size and thread-local, like the morphology floor cache: a long export // Keep the cache fixed-size and thread-local, like the morphology floor cache: a long export
// cannot retain one entry per tile, and collisions only lose a hit. Fractional normal // cannot retain one entry per tile, and collisions only lose a hit. Fractional normal
@@ -425,49 +534,58 @@ namespace
0.0f, VF_PassageMaxReliefScale); 0.0f, VF_PassageMaxReliefScale);
} }
static bool VF_ProjectNativePassageFloor( static bool VF_ProjectNativePassageFloorUncached(
const FVoxelPassage& Passage, const FVector& Position, const FVoxelPassage& Passage, const FVector& Position,
float& OutFloorZ, float& OutSupportRadius, float& OutFloorZ, float& OutSupportRadius,
const UVoxelStrateManager* Manager = nullptr, const UVoxelStrateManager* Manager = nullptr,
int32 PassageIndex = INDEX_NONE) int32 PassageIndex = INDEX_NONE)
{ {
const int32 ControlPointCount = Passage.ControlPoints.Num();
if (!Passage.bNativeFloorEnabled if (!Passage.bNativeFloorEnabled
|| Passage.ControlPoints.Num() < 2 || ControlPointCount < 2
|| Passage.ControlRadii.Num() != Passage.ControlPoints.Num() || Passage.ControlRadii.Num() != ControlPointCount
|| Passage.NativeFloorProfileZ.Num() != Passage.ControlPoints.Num()) || Passage.NativeFloorProfileZ.Num() != ControlPointCount)
{ {
return false; return false;
} }
const FVector* ControlPointData = Passage.ControlPoints.GetData();
const float* ControlRadiusData = Passage.ControlRadii.GetData();
const float* NativeFloorProfileData = Passage.NativeFloorProfileZ.GetData();
const double QueryX = static_cast<double>(static_cast<float>(Position.X));
const double QueryY = static_cast<double>(static_cast<float>(Position.Y));
float BestDistanceSquared = FLT_MAX; float BestDistanceSquared = FLT_MAX;
int32 BestSegment = INDEX_NONE; int32 BestSegment = INDEX_NONE;
float BestT = 0.0f; float BestT = 0.0f;
float BestSupportRadius = 0.0f; float BestSupportRadius = 0.0f;
for (int32 SegmentIndex = 0; for (int32 SegmentIndex = 0;
SegmentIndex + 1 < Passage.ControlPoints.Num(); SegmentIndex + 1 < ControlPointCount;
++SegmentIndex) ++SegmentIndex)
{ {
const FVector& A = Passage.ControlPoints[SegmentIndex]; const FVector& A = ControlPointData[SegmentIndex];
const FVector& B = Passage.ControlPoints[SegmentIndex + 1]; const FVector& B = ControlPointData[SegmentIndex + 1];
const FVector2D Delta( const double AXY_X = static_cast<double>(static_cast<float>(A.X));
static_cast<float>(B.X - A.X), const double AXY_Y = static_cast<double>(static_cast<float>(A.Y));
static_cast<float>(B.Y - A.Y)); const double DeltaX = static_cast<double>(static_cast<float>(B.X - A.X));
const float LengthSquared = Delta.SizeSquared(); const double DeltaY = static_cast<double>(static_cast<float>(B.Y - A.Y));
const float LengthSquared = static_cast<float>(
DeltaX * DeltaX + DeltaY * DeltaY);
if (!(LengthSquared > KINDA_SMALL_NUMBER)) if (!(LengthSquared > KINDA_SMALL_NUMBER))
{ {
continue; continue;
} }
const FVector2D QueryXY( const double Dot = (QueryX - AXY_X) * DeltaX
static_cast<float>(Position.X), + (QueryY - AXY_Y) * DeltaY;
static_cast<float>(Position.Y));
const FVector2D AXY(
static_cast<float>(A.X), static_cast<float>(A.Y));
const float T = FMath::Clamp( const float T = FMath::Clamp(
FVector2D::DotProduct(QueryXY - AXY, Delta) / LengthSquared, Dot / LengthSquared,
0.0f, 1.0f); 0.0f, 1.0f);
const FVector2D ClosestXY = AXY + Delta * T; const double ClosestX = AXY_X + DeltaX * T;
const float DistanceSquared = (QueryXY - ClosestXY).SizeSquared(); const double ClosestY = AXY_Y + DeltaY * T;
const double DistanceX = QueryX - ClosestX;
const double DistanceY = QueryY - ClosestY;
const float DistanceSquared = static_cast<float>(
DistanceX * DistanceX + DistanceY * DistanceY);
if (DistanceSquared >= BestDistanceSquared) if (DistanceSquared >= BestDistanceSquared)
{ {
continue; continue;
@@ -478,8 +596,8 @@ namespace
BestT = T; BestT = T;
BestSupportRadius = FMath::Max( BestSupportRadius = FMath::Max(
FMath::Min( FMath::Min(
FMath::Abs(Passage.ControlRadii[SegmentIndex]), FMath::Abs(ControlRadiusData[SegmentIndex]),
FMath::Abs(Passage.ControlRadii[SegmentIndex + 1])) - 0.5f, FMath::Abs(ControlRadiusData[SegmentIndex + 1])) - 0.5f,
VoxelPassageGeometry::PlayerRadiusVoxels); VoxelPassageGeometry::PlayerRadiusVoxels);
} }
@@ -491,21 +609,25 @@ namespace
const float ClampedT = FMath::Clamp(BestT, 0.0f, 1.0f); const float ClampedT = FMath::Clamp(BestT, 0.0f, 1.0f);
OutFloorZ = FMath::Lerp( OutFloorZ = FMath::Lerp(
Passage.NativeFloorProfileZ[BestSegment], NativeFloorProfileData[BestSegment],
Passage.NativeFloorProfileZ[BestSegment + 1], NativeFloorProfileData[BestSegment + 1],
ClampedT); ClampedT);
float ReliefScale = 0.0f; float ReliefScale = 0.0f;
if (Passage.NativeFloorReliefScales.IsValidIndex(BestSegment)) const int32 ReliefScaleCount = Passage.NativeFloorReliefScales.Num();
if (BestSegment >= 0 && BestSegment < ReliefScaleCount)
{ {
ReliefScale = Passage.NativeFloorReliefScales[BestSegment]; ReliefScale = Passage.NativeFloorReliefScales.GetData()[BestSegment];
} }
float Envelope = 1.0f; float Envelope = 1.0f;
const float HorizontalRun = FVector2D( const FVector& BestA = ControlPointData[BestSegment];
static_cast<float>(Passage.ControlPoints[BestSegment + 1].X const FVector& BestB = ControlPointData[BestSegment + 1];
- Passage.ControlPoints[BestSegment].X), const double BestDeltaX = static_cast<double>(static_cast<float>(
static_cast<float>(Passage.ControlPoints[BestSegment + 1].Y BestB.X - BestA.X));
- Passage.ControlPoints[BestSegment].Y)).Size(); const double BestDeltaY = static_cast<double>(static_cast<float>(
BestB.Y - BestA.Y));
const float HorizontalRun = static_cast<float>(FMath::Sqrt(
BestDeltaX * BestDeltaX + BestDeltaY * BestDeltaY));
if (BestSegment == 0) if (BestSegment == 0)
{ {
Envelope *= SmoothStep01(FMath::Clamp( Envelope *= SmoothStep01(FMath::Clamp(
@@ -513,7 +635,7 @@ namespace
/ VoxelPassageGeometry::WalkableTunnelLandingApronVoxels, / VoxelPassageGeometry::WalkableTunnelLandingApronVoxels,
0.0f, 1.0f)); 0.0f, 1.0f));
} }
if (BestSegment + 1 == Passage.ControlPoints.Num() - 1) if (BestSegment + 1 == ControlPointCount - 1)
{ {
Envelope *= SmoothStep01(FMath::Clamp( Envelope *= SmoothStep01(FMath::Clamp(
((1.0f - ClampedT) * HorizontalRun) ((1.0f - ClampedT) * HorizontalRun)
@@ -531,25 +653,82 @@ namespace
&& OutSupportRadius > 0.0f; && OutSupportRadius > 0.0f;
} }
static bool VF_ProjectNativePassageFloor(
const FVoxelPassage& Passage, const FVector& Position,
float& OutFloorZ, float& OutSupportRadius,
const UVoxelStrateManager* Manager = nullptr,
int32 PassageIndex = INDEX_NONE)
{
FPassageFloorProjectionCacheEntry* Entry =
VF_GetPassageFloorProjectionCacheEntry(Manager, Position, PassageIndex);
if (Entry == nullptr)
{
return VF_ProjectNativePassageFloorUncached(
Passage, Position, OutFloorZ, OutSupportRadius,
Manager, PassageIndex);
}
if (!Entry->Has(FPassageFloorProjectionCacheEntry::NativeComputed))
{
Entry->bNativeValid = VF_ProjectNativePassageFloorUncached(
Passage, Position, Entry->NativeFloorZ,
Entry->NativeSupportRadius, Manager, PassageIndex);
Entry->SetComputed(FPassageFloorProjectionCacheEntry::NativeComputed);
}
if (Entry->bNativeValid)
{
OutFloorZ = Entry->NativeFloorZ;
OutSupportRadius = Entry->NativeSupportRadius;
}
return Entry->bNativeValid;
}
static bool VF_ProjectPassageFloor( static bool VF_ProjectPassageFloor(
const FVoxelPassage& Passage, const FVector& Position, const FVoxelPassage& Passage, const FVector& Position,
float& OutFloorZ, float& OutSupportRadius, float& OutFloorZ, float& OutSupportRadius,
const UVoxelStrateManager* Manager = nullptr, const UVoxelStrateManager* Manager = nullptr,
int32 PassageIndex = INDEX_NONE) int32 PassageIndex = INDEX_NONE)
{ {
if (VF_ProjectNativePassageFloor( FPassageFloorProjectionCacheEntry* Entry =
Passage, Position, OutFloorZ, OutSupportRadius, VF_GetPassageFloorProjectionCacheEntry(Manager, Position, PassageIndex);
Manager, PassageIndex)) if (Entry != nullptr
&& Entry->Has(FPassageFloorProjectionCacheEntry::GenericComputed))
{ {
return true; if (Entry->bGenericValid)
{
OutFloorZ = Entry->GenericFloorZ;
OutSupportRadius = Entry->GenericSupportRadius;
}
return Entry->bGenericValid;
} }
if (!Passage.bWalkableTunnelContract)
float FloorZ = 0.0f;
float SupportRadius = 0.0f;
bool bValid = VF_ProjectNativePassageFloor(
Passage, Position, FloorZ, SupportRadius,
Manager, PassageIndex);
if (!bValid && Passage.bWalkableTunnelContract)
{ {
return false; bValid = VoxelPassageGeometry::ProjectWalkableTunnelFloor(
Passage.ControlPoints, Passage.ControlRadii, Position,
FloorZ, SupportRadius);
} }
return VoxelPassageGeometry::ProjectWalkableTunnelFloor(
Passage.ControlPoints, Passage.ControlRadii, Position, if (Entry != nullptr)
OutFloorZ, OutSupportRadius); {
Entry->bGenericValid = bValid;
if (bValid)
{
Entry->GenericFloorZ = FloorZ;
Entry->GenericSupportRadius = SupportRadius;
}
Entry->SetComputed(FPassageFloorProjectionCacheEntry::GenericComputed);
}
if (bValid)
{
OutFloorZ = FloorZ;
OutSupportRadius = SupportRadius;
}
return bValid;
} }
} }
@@ -700,7 +879,9 @@ static bool VF_IsWalkableTunnelAir(
const FVoxelPassage& Passage, const FVoxelPassage& Passage,
const FVector& Position, const FVector& Position,
const UVoxelStrateManager* Manager = nullptr, const UVoxelStrateManager* Manager = nullptr,
int32 PassageIndex = INDEX_NONE) int32 PassageIndex = INDEX_NONE,
float* OutFloorZ = nullptr,
float* OutSupportRadius = nullptr)
{ {
if (!Passage.bWalkableTunnelContract if (!Passage.bWalkableTunnelContract
|| Passage.ControlPoints.Num() < 2 || Passage.ControlPoints.Num() < 2
@@ -709,6 +890,25 @@ static bool VF_IsWalkableTunnelAir(
return false; return false;
} }
FPassageFloorProjectionCacheEntry* Entry =
VF_GetPassageFloorProjectionCacheEntry(Manager, Position, PassageIndex);
if (Entry != nullptr
&& Entry->Has(FPassageFloorProjectionCacheEntry::WalkableAirComputed))
{
if (Entry->bWalkableAir)
{
if (OutFloorZ != nullptr)
{
*OutFloorZ = Entry->GenericFloorZ;
}
if (OutSupportRadius != nullptr)
{
*OutSupportRadius = Entry->GenericSupportRadius;
}
}
return Entry->bWalkableAir;
}
float FloorZ = 0.0f; float FloorZ = 0.0f;
float SupportRadius = 0.0f; float SupportRadius = 0.0f;
if (!VF_ProjectPassageFloor( if (!VF_ProjectPassageFloor(
@@ -722,9 +922,26 @@ static bool VF_IsWalkableTunnelAir(
// stable air volume even when a source archetype or a disturbance would otherwise refill the // stable air volume even when a source archetype or a disturbance would otherwise refill the
// shallow part of the rounded SDF. The floor writer below owns the closed lower face. // shallow part of the rounded SDF. The floor writer below owns the closed lower face.
const float AirHeight = 2.0f * SupportRadius; const float AirHeight = 2.0f * SupportRadius;
return Position.Z > FloorZ const bool bWalkableAir = Position.Z > FloorZ
+ VoxelPassageGeometry::WalkableTunnelFloorAirClearanceVoxels + VoxelPassageGeometry::WalkableTunnelFloorAirClearanceVoxels
&& Position.Z < FloorZ + AirHeight - KINDA_SMALL_NUMBER; && Position.Z < FloorZ + AirHeight - KINDA_SMALL_NUMBER;
if (Entry != nullptr)
{
Entry->bWalkableAir = bWalkableAir;
Entry->SetComputed(FPassageFloorProjectionCacheEntry::WalkableAirComputed);
}
if (bWalkableAir)
{
if (OutFloorZ != nullptr)
{
*OutFloorZ = FloorZ;
}
if (OutSupportRadius != nullptr)
{
*OutSupportRadius = SupportRadius;
}
}
return bWalkableAir;
} }
static bool VF_FindWalkableTunnelAir( static bool VF_FindWalkableTunnelAir(
@@ -748,21 +965,21 @@ static bool VF_FindWalkableTunnelAir(
FMath::FloorToInt(Position.Z / (float)CHUNK_SIZE)); FMath::FloorToInt(Position.Z / (float)CHUNK_SIZE));
const TArray<int32>& Nearby = VF_GetNearbyPassages(Manager, ChunkCoord); const TArray<int32>& Nearby = VF_GetNearbyPassages(Manager, ChunkCoord);
const TArray<FVoxelPassage>& Passages = Manager->GetPassages(); const TArray<FVoxelPassage>& Passages = Manager->GetPassages();
const FVoxelPassage* PassageData = Passages.GetData();
for (const int32 PassageIndex : Nearby) for (const int32 PassageIndex : Nearby)
{ {
if (!Passages.IsValidIndex(PassageIndex)) if (!Passages.IsValidIndex(PassageIndex))
{ {
continue; continue;
} }
const FVoxelPassage& Passage = Passages[PassageIndex]; const FVoxelPassage& Passage = PassageData[PassageIndex];
if (FVector::DistSquared(Position, Passage.BoundCenter) > Passage.BoundRadiusSq if (FVector::DistSquared(Position, Passage.BoundCenter) > Passage.BoundRadiusSq
|| !VF_IsWalkableTunnelAir(Passage, Position, Manager, PassageIndex)) || !VF_IsWalkableTunnelAir(
Passage, Position, Manager, PassageIndex,
&OutFloorZ, &OutSupportRadius))
{ {
continue; continue;
} }
VF_ProjectPassageFloor(
Passage, Position, OutFloorZ, OutSupportRadius,
Manager, PassageIndex);
OutPassageIndex = PassageIndex; OutPassageIndex = PassageIndex;
return true; return true;
} }
@@ -783,13 +1000,14 @@ static bool VF_IsAnyPassageFloorAt(
FMath::FloorToInt(Position.Z / (float)CHUNK_SIZE)); FMath::FloorToInt(Position.Z / (float)CHUNK_SIZE));
const TArray<int32>& Nearby = VF_GetNearbyPassages(Manager, ChunkCoord); const TArray<int32>& Nearby = VF_GetNearbyPassages(Manager, ChunkCoord);
const TArray<FVoxelPassage>& Passages = Manager->GetPassages(); const TArray<FVoxelPassage>& Passages = Manager->GetPassages();
const FVoxelPassage* PassageData = Passages.GetData();
for (const int32 PassageIndex : Nearby) for (const int32 PassageIndex : Nearby)
{ {
if (!Passages.IsValidIndex(PassageIndex)) if (!Passages.IsValidIndex(PassageIndex))
{ {
continue; continue;
} }
const FVoxelPassage& Passage = Passages[PassageIndex]; const FVoxelPassage& Passage = PassageData[PassageIndex];
if (!VoxelPassageGeometry::VerticalShaftConnectorAirMarker() if (!VoxelPassageGeometry::VerticalShaftConnectorAirMarker()
&& (VF_IsPassageLandingFloor(Position, Passage.UpperLanding) && (VF_IsPassageLandingFloor(Position, Passage.UpperLanding)
|| VF_IsPassageLandingFloor(Position, Passage.LowerLanding) || VF_IsPassageLandingFloor(Position, Passage.LowerLanding)
@@ -2242,6 +2460,7 @@ float UVoxelStrateManager::EvaluateModifierSDF(float WorldX, float WorldY, float
float MinSDF = FLT_MAX; float MinSDF = FLT_MAX;
const float BlendK = 3.0f; // Smooth blend for passage junctions const float BlendK = 3.0f; // Smooth blend for passage junctions
const FVoxelPassage* PassageData = Passages.GetData();
//========================================================================= //=========================================================================
// PASSAGES — tapered capsule chains between strates (per-strate PassageConfig). // PASSAGES — tapered capsule chains between strates (per-strate PassageConfig).
@@ -2251,7 +2470,7 @@ float UVoxelStrateManager::EvaluateModifierSDF(float WorldX, float WorldY, float
const FVector Pos(WorldX, WorldY, WorldZ); const FVector Pos(WorldX, WorldY, WorldZ);
for (int32 PIdx : Nearby) for (int32 PIdx : Nearby)
{ {
const FVoxelPassage& P = Passages[PIdx]; const FVoxelPassage& P = PassageData[PIdx];
// BOUNDING-SPHERE REJECT: skip passages this voxel can't possibly be inside. // BOUNDING-SPHERE REJECT: skip passages this voxel can't possibly be inside.
// EvaluateModifierSDF runs PER VOXEL and used to evaluate every passage's full // EvaluateModifierSDF runs PER VOXEL and used to evaluate every passage's full
// capsule chain unconditionally — the dominant lag source once passages became // capsule chain unconditionally — the dominant lag source once passages became
@@ -2263,19 +2482,22 @@ float UVoxelStrateManager::EvaluateModifierSDF(float WorldX, float WorldY, float
VoxelDensityProfile::ECounter::PassageEvaluated); VoxelDensityProfile::ECounter::PassageEvaluated);
} }
if (P.ControlPoints.Num() >= 2) const int32 ControlPointCount = P.ControlPoints.Num();
if (ControlPointCount >= 2)
{ {
// Tapered capsule chain along the control points. ControlRadii (if present) // Tapered capsule chain along the control points. ControlRadii (if present)
// gives the per-point width so the tunnel can flare at the mouths and pinch // gives the per-point width so the tunnel can flare at the mouths and pinch
// in the middle; otherwise the uniform Radius is used. // in the middle; otherwise the uniform Radius is used.
const bool bTaper = (P.ControlRadii.Num() == P.ControlPoints.Num()); const bool bTaper = (P.ControlRadii.Num() == P.ControlPoints.Num());
const FVector* ControlPointData = P.ControlPoints.GetData();
const float* ControlRadiusData = P.ControlRadii.GetData();
float PassageSDF = FLT_MAX; float PassageSDF = FLT_MAX;
for (int32 j = 0; j < P.ControlPoints.Num() - 1; j++) for (int32 j = 0; j < ControlPointCount - 1; j++)
{ {
const float rA = bTaper ? P.ControlRadii[j] : P.Radius; const float rA = bTaper ? ControlRadiusData[j] : P.Radius;
const float rB = bTaper ? P.ControlRadii[j + 1] : P.Radius; const float rB = bTaper ? ControlRadiusData[j + 1] : P.Radius;
const float SegSDF = VoxelSDF::TaperedCapsule( const float SegSDF = VoxelSDF::TaperedCapsule(
Pos, P.ControlPoints[j], P.ControlPoints[j + 1], rA, rB); Pos, ControlPointData[j], ControlPointData[j + 1], rA, rB);
PassageSDF = VoxelSDF::SmoothMin(PassageSDF, SegSDF, BlendK); PassageSDF = VoxelSDF::SmoothMin(PassageSDF, SegSDF, BlendK);
} }
@@ -2334,10 +2556,11 @@ void UVoxelStrateManager::ApplyPassageModifier(
FMath::FloorToInt(WorldY / (float)CHUNK_SIZE), FMath::FloorToInt(WorldY / (float)CHUNK_SIZE),
FMath::FloorToInt(WorldZ / (float)CHUNK_SIZE)); FMath::FloorToInt(WorldZ / (float)CHUNK_SIZE));
const TArray<int32>& Nearby = VF_GetNearbyPassages(this, ChunkCoord); const TArray<int32>& Nearby = VF_GetNearbyPassages(this, ChunkCoord);
const FVoxelPassage* PassageData = Passages.GetData();
const FVector Position(WorldX, WorldY, WorldZ); const FVector Position(WorldX, WorldY, WorldZ);
for (const int32 PassageIndex : Nearby) for (const int32 PassageIndex : Nearby)
{ {
const FVoxelPassage& Passage = Passages[PassageIndex]; const FVoxelPassage& Passage = PassageData[PassageIndex];
if (!VoxelPassageGeometry::VerticalShaftConnectorAirMarker() if (!VoxelPassageGeometry::VerticalShaftConnectorAirMarker()
&& (VF_IsPassageLandingFloor(Position, Passage.UpperLanding) && (VF_IsPassageLandingFloor(Position, Passage.UpperLanding)
|| VF_IsPassageLandingFloor(Position, Passage.LowerLanding) || VF_IsPassageLandingFloor(Position, Passage.LowerLanding)
@@ -2386,6 +2609,7 @@ void UVoxelStrateManager::ApplyPassageLandingAir(
const TArray<int32>& Nearby = VF_GetNearbyPassages(this, ChunkCoord); const TArray<int32>& Nearby = VF_GetNearbyPassages(this, ChunkCoord);
if (Nearby.Num() == 0) return; if (Nearby.Num() == 0) return;
const FVoxelPassage* PassageData = Passages.GetData();
const FVector Position(WorldX, WorldY, WorldZ); const FVector Position(WorldX, WorldY, WorldZ);
// A smooth room blend may overlap the end of a ramp. The analytic support plane owns that // A smooth room blend may overlap the end of a ramp. The analytic support plane owns that
// overlap; otherwise landing air can erase the tunnel's final-density floor after the tunnel // overlap; otherwise landing air can erase the tunnel's final-density floor after the tunnel
@@ -2397,7 +2621,7 @@ void UVoxelStrateManager::ApplyPassageLandingAir(
float MinLandingSDF = FLT_MAX; float MinLandingSDF = FLT_MAX;
for (const int32 PassageIndex : Nearby) for (const int32 PassageIndex : Nearby)
{ {
const FVoxelPassage& Passage = Passages[PassageIndex]; const FVoxelPassage& Passage = PassageData[PassageIndex];
if (FVector::DistSquared(Position, Passage.BoundCenter) > Passage.BoundRadiusSq) if (FVector::DistSquared(Position, Passage.BoundCenter) > Passage.BoundRadiusSq)
{ {
continue; continue;
@@ -2477,10 +2701,11 @@ void UVoxelStrateManager::ApplyPassageLandingFloorMC(
FMath::FloorToInt(WorldY / (float)CHUNK_SIZE), FMath::FloorToInt(WorldY / (float)CHUNK_SIZE),
FMath::FloorToInt(WorldZ / (float)CHUNK_SIZE)); FMath::FloorToInt(WorldZ / (float)CHUNK_SIZE));
const TArray<int32>& Nearby = VF_GetNearbyPassages(this, ChunkCoord); const TArray<int32>& Nearby = VF_GetNearbyPassages(this, ChunkCoord);
const FVoxelPassage* PassageData = Passages.GetData();
const FVector Position(WorldX, WorldY, WorldZ); const FVector Position(WorldX, WorldY, WorldZ);
for (const int32 PassageIndex : Nearby) for (const int32 PassageIndex : Nearby)
{ {
const FVoxelPassage& Passage = Passages[PassageIndex]; const FVoxelPassage& Passage = PassageData[PassageIndex];
if (!VoxelPassageGeometry::VerticalShaftConnectorAirMarker() if (!VoxelPassageGeometry::VerticalShaftConnectorAirMarker()
&& (VF_IsPassageLandingFloor(Position, Passage.UpperLanding) && (VF_IsPassageLandingFloor(Position, Passage.UpperLanding)
|| VF_IsPassageLandingFloor(Position, Passage.LowerLanding) || VF_IsPassageLandingFloor(Position, Passage.LowerLanding)
@@ -2529,6 +2754,7 @@ void UVoxelStrateManager::ApplyPassageStructuralPostsMC(
const TArray<int32>& Nearby = VF_GetNearbyPassages(this, ChunkCoord); const TArray<int32>& Nearby = VF_GetNearbyPassages(this, ChunkCoord);
if (Nearby.Num() == 0) return; if (Nearby.Num() == 0) return;
const FVoxelPassage* PassageData = Passages.GetData();
const FVector Position(WorldX, WorldY, WorldZ); const FVector Position(WorldX, WorldY, WorldZ);
const bool bSuppressFloor = const bool bSuppressFloor =
VoxelPassageGeometry::VerticalShaftConnectorAirMarker(); VoxelPassageGeometry::VerticalShaftConnectorAirMarker();
@@ -2544,7 +2770,7 @@ void UVoxelStrateManager::ApplyPassageStructuralPostsMC(
for (const int32 PassageIndex : Nearby) for (const int32 PassageIndex : Nearby)
{ {
if (!Passages.IsValidIndex(PassageIndex)) continue; if (!Passages.IsValidIndex(PassageIndex)) continue;
const FVoxelPassage& Passage = Passages[PassageIndex]; const FVoxelPassage& Passage = PassageData[PassageIndex];
const bool bTunnelSupportFloor = const bool bTunnelSupportFloor =
bPassageSupportFloorWritesEnabled bPassageSupportFloorWritesEnabled
@@ -2640,6 +2866,7 @@ void UVoxelStrateManager::ApplyPassageNativeFloorMC(
return; return;
} }
const FVoxelPassage* PassageData = Passages.GetData();
const FVector Position(WorldX, WorldY, WorldZ); const FVector Position(WorldX, WorldY, WorldZ);
for (const int32 PassageIndex : Nearby) for (const int32 PassageIndex : Nearby)
{ {
@@ -2647,7 +2874,7 @@ void UVoxelStrateManager::ApplyPassageNativeFloorMC(
{ {
continue; continue;
} }
const FVoxelPassage& Passage = Passages[PassageIndex]; const FVoxelPassage& Passage = PassageData[PassageIndex];
if (!Passage.bNativeFloorEnabled) if (!Passage.bNativeFloorEnabled)
{ {
continue; continue;
@@ -2689,10 +2916,11 @@ void UVoxelStrateManager::ApplyPassageLandingRoomFloorMC(
FMath::FloorToInt(WorldY / (float)CHUNK_SIZE), FMath::FloorToInt(WorldY / (float)CHUNK_SIZE),
FMath::FloorToInt(WorldZ / (float)CHUNK_SIZE)); FMath::FloorToInt(WorldZ / (float)CHUNK_SIZE));
const TArray<int32>& Nearby = VF_GetNearbyPassages(this, ChunkCoord); const TArray<int32>& Nearby = VF_GetNearbyPassages(this, ChunkCoord);
const FVoxelPassage* PassageData = Passages.GetData();
const FVector Position(WorldX, WorldY, WorldZ); const FVector Position(WorldX, WorldY, WorldZ);
for (const int32 PassageIndex : Nearby) for (const int32 PassageIndex : Nearby)
{ {
const FVoxelPassage& Passage = Passages[PassageIndex]; const FVoxelPassage& Passage = PassageData[PassageIndex];
if (!VoxelPassageGeometry::VerticalShaftConnectorAirMarker() if (!VoxelPassageGeometry::VerticalShaftConnectorAirMarker()
&& (VF_IsPassageRoomFloor(Position, Passage.UpperLanding) && (VF_IsPassageRoomFloor(Position, Passage.UpperLanding)
|| VF_IsPassageRoomFloor(Position, Passage.LowerLanding))) || VF_IsPassageRoomFloor(Position, Passage.LowerLanding)))
+25 -11
View File
@@ -383,12 +383,12 @@ namespace VoxelDensityProfile
struct FScopeToken struct FScopeToken
{ {
bool bEntered = false; bool bEntered;
bool bSampled = false; bool bSampled;
bool bBlockTiming = false; bool bBlockTiming;
bool bPreviousSampled = false; bool bPreviousSampled;
bool bPreviousSampledScopeActive = false; bool bPreviousSampledScopeActive;
uint64 StartCycles = 0; uint64 StartCycles;
}; };
VOXELFORGE_API FScopeToken BeginScope(EBucket Bucket); VOXELFORGE_API FScopeToken BeginScope(EBucket Bucket);
@@ -398,16 +398,16 @@ namespace VoxelDensityProfile
{ {
public: public:
explicit FScopedTimer(EBucket InBucket) explicit FScopedTimer(EBucket InBucket)
: Bucket(InBucket)
{ {
if (IsEnabledFast() && ShouldProbeScopeFast(InBucket)) if (IsEnabledFast() && ShouldProbeScopeFast(InBucket))
{ {
Bucket = InBucket;
Token = BeginScope(InBucket); Token = BeginScope(InBucket);
bActive = Token.bEntered;
} }
} }
explicit FScopedTimer(const TCHAR* InName) explicit FScopedTimer(const TCHAR* InName)
: Bucket(EBucket::OtherOp)
{ {
if (IsEnabledFast()) if (IsEnabledFast())
{ {
@@ -415,21 +415,35 @@ namespace VoxelDensityProfile
if (ShouldProbeScopeFast(Bucket)) if (ShouldProbeScopeFast(Bucket))
{ {
Token = BeginScope(Bucket); Token = BeginScope(Bucket);
bActive = Token.bEntered;
} }
} }
} }
~FScopedTimer(); FORCEINLINE ~FScopedTimer()
{
End();
}
// End a range before its lexical scope ends. This lets GetDensityAt split its enclosing // End a range before its lexical scope ends. This lets GetDensityAt split its enclosing
// total into disjoint prologue/core/post phases without moving the large cache block. // total into disjoint prologue/core/post phases without moving the large cache block.
void End(); FORCEINLINE void End()
{
if (!bActive)
{
return;
}
EndScope(Bucket, Token);
Token.bEntered = false;
bActive = false;
}
FScopedTimer(const FScopedTimer&) = delete; FScopedTimer(const FScopedTimer&) = delete;
FScopedTimer& operator=(const FScopedTimer&) = delete; FScopedTimer& operator=(const FScopedTimer&) = delete;
private: private:
EBucket Bucket = EBucket::OtherOp; EBucket Bucket;
FScopeToken Token; FScopeToken Token;
bool bActive = false;
}; };
} }
+25 -12
View File
@@ -302,35 +302,48 @@ namespace VoxelPassageGeometry
return false; return false;
} }
const FVector2D QueryXY(Position.X, Position.Y); const double QueryX = Position.X;
const double QueryY = Position.Y;
const int32 ControlPointCount = ControlPoints.Num();
const FVector* ControlPointData = ControlPoints.GetData();
const float* ControlRadiusData = ControlRadii.GetData();
float BestDistanceSquared = FLT_MAX; float BestDistanceSquared = FLT_MAX;
bool bFoundSegment = false; bool bFoundSegment = false;
for (int32 SegmentIndex = 0; for (int32 SegmentIndex = 0;
SegmentIndex + 1 < ControlPoints.Num(); SegmentIndex + 1 < ControlPointCount;
++SegmentIndex) ++SegmentIndex)
{ {
const FVector& A = ControlPoints[SegmentIndex]; const FVector& A = ControlPointData[SegmentIndex];
const FVector& B = ControlPoints[SegmentIndex + 1]; const FVector& B = ControlPointData[SegmentIndex + 1];
const FVector2D AXY(A.X, A.Y); const double AXY_X = A.X;
const FVector2D Delta = FVector2D(B.X, B.Y) - AXY; const double AXY_Y = A.Y;
const float LengthSquared = Delta.SizeSquared(); const double DeltaX = B.X - AXY_X;
const double DeltaY = B.Y - AXY_Y;
const float LengthSquared = static_cast<float>(
DeltaX * DeltaX + DeltaY * DeltaY);
if (LengthSquared <= KINDA_SMALL_NUMBER) if (LengthSquared <= KINDA_SMALL_NUMBER)
{ {
continue; continue;
} }
const double Dot = (QueryX - AXY_X) * DeltaX
+ (QueryY - AXY_Y) * DeltaY;
const float T = FMath::Clamp( const float T = FMath::Clamp(
FVector2D::DotProduct(QueryXY - AXY, Delta) / LengthSquared, Dot / LengthSquared,
0.0f, 1.0f); 0.0f, 1.0f);
const FVector2D ClosestXY = AXY + Delta * T; const double ClosestX = AXY_X + DeltaX * T;
const float DistanceSquared = (QueryXY - ClosestXY).SizeSquared(); const double ClosestY = AXY_Y + DeltaY * T;
const double DistanceX = QueryX - ClosestX;
const double DistanceY = QueryY - ClosestY;
const float DistanceSquared = static_cast<float>(
DistanceX * DistanceX + DistanceY * DistanceY);
if (DistanceSquared >= BestDistanceSquared) if (DistanceSquared >= BestDistanceSquared)
{ {
continue; continue;
} }
const float StartRadius = FMath::Abs(ControlRadii[SegmentIndex]); const float StartRadius = FMath::Abs(ControlRadiusData[SegmentIndex]);
const float EndRadius = FMath::Abs(ControlRadii[SegmentIndex + 1]); const float EndRadius = FMath::Abs(ControlRadiusData[SegmentIndex + 1]);
OutFloorZ = FMath::Lerp( OutFloorZ = FMath::Lerp(
TunnelFloorZ(A, StartRadius), TunnelFloorZ(A, StartRadius),
TunnelFloorZ(B, EndRadius), TunnelFloorZ(B, EndRadius),