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:
1 parent
b839edd3a3
commit
ab4bbe3680
9 files changed
+629
-170
No files matched your search
@@ -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
@@ -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
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -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:
|
||||||
|
|||||||
@@ -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)))
|
||||||
|
|||||||
@@ -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;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -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),
|
||||||
|
|||||||
Reference in new issue
Block a user