measure(perf): per-component ablation of the TunnelNetwork path; exact worms restored
Round (Codex; reviewed by me against PERF-SAMPLED.md). Revert: voxel.WormNoiseMode / voxel.WormLatticeStep, their storage and their switches are REMOVED. VoxelWormField has one path again, exact Perlin with the N1 short circuit. Kept: bEnableWorms (asset), WormsForceOff (measurement only), the exact block skip (default 0). Ablation instrument: 14 development-only, world-changing force-off bits (voxel.TunnelAblate*), applied on the fused, op-stack/classifier, cached SDF/core and modifier/floor paths. They resolve once per process, are hashed into the generation fingerprint, and are compiled to zero in UE_BUILD_SHIPPING (VoxelDensityAblation.cpp). They are a measurement instrument, never a gameplay or multiplayer switch. Defaults exact: canonical export OBJ b3e5f4c3..9b377, worms-region 311CA42C / 75,482 triangles, capability 20,830 / 10,909, and 841/841 keyed tiles identical to NextHeadStaticTrace_20260913. Static worker seconds saved by removing ONE component (two off runs vs the interleaved all-on 33.06 s; baseline noise 0.27%): tunnel SDF 11.4%, cave warp 10.3%, passage carving 10.1%, landing posts 9.9%, passage structural posts 5.6%, worms 2.4%, native floor 2.2%, origin spine 1.6%, room SDF 1.6%, everything else <= 1.1% Moving (2+2 runs each) confirms the top five at 6.0-11.6%. Tunnel SDF off cuts LOD0 generation p95 104 -> 83 ms: it owns the slow tail. The positive savings sum to 58% of the static total; the other 42% is shared. The rows are not additive. Unexplained: tunnel-core OFF made the static total 2.9x SLOWER (95.5 s) while LOD0 generation got faster (p50 64.7 -> 60.6 ms) and triangles fell. The report blames the changed field, which does not fit; more likely a slow fallback path in coarse tiles. That row is not a cost measurement. Ledger: every Unreal process exited 0 and verified the loaded DLL; two PowerShell parser errors happened before any process started. No crashes or retries. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TaFdXhTdYMkW1xrV6ApT6Y
This commit is contained in:
1 parent
c132243b30
commit
6ccf3718ff
12 files changed
+670
-532
No files matched your search
+237
-10
@@ -1226,21 +1226,21 @@ Both runs also reported `canonical_repeat_equal=true`. The disabled asset path t
|
|||||||
the hand-zeroed control byte-for-byte at the exported geometry hash, while exceeding the required
|
the hand-zeroed control byte-for-byte at the exported geometry hash, while exceeding the required
|
||||||
20,830 / 10,909 capability gate.
|
20,830 / 10,909 capability gate.
|
||||||
|
|
||||||
### Part 2 — measured cheap field
|
### Historical Part 2 — measured cheap field (reverted 2026-09-14)
|
||||||
|
|
||||||
The selected field is `voxel.WormNoiseMode=1`: a deterministic scalar value-noise evaluation
|
The historical build selected `voxel.WormNoiseMode=1`: a deterministic scalar value-noise
|
||||||
using the same worm coordinate mapping, threshold, N1 short-circuit, N2 offset, and strength
|
evaluation using the same worm coordinate mapping, threshold, N1 short-circuit, N2 offset, and
|
||||||
contract. The code default is mode 1. Mode 0 remains the legacy gradient-Perlin exact control.
|
strength contract. That selection was reverted after the ablation review. The current source is
|
||||||
`voxel.WormNoiseMode` and `voxel.WormLatticeStep` are documented development-only,
|
exact-only Perlin mode 0; `voxel.WormNoiseMode` and `voxel.WormLatticeStep`, their storage, and
|
||||||
world-changing experiments; they are not the multiplayer off switch and must be identical on
|
their switches no longer exist. The run names and measurements below are retained as historical
|
||||||
all peers and on every regeneration of a world.
|
evidence only.
|
||||||
|
|
||||||
The coarser lattice was measured rather than assumed. The first lattice implementation was much
|
The coarser lattice was measured rather than assumed. The first lattice implementation was much
|
||||||
slower because of cache churn; after the fixed tile-window cache, it returned to roughly the
|
slower because of cache churn; after the fixed tile-window cache, it returned to roughly the
|
||||||
exact cost but did not improve it. Representative static worker seconds were exact 33.10,
|
exact cost but did not improve it. Representative static worker seconds were exact 33.10,
|
||||||
lattice-2 initial 160.86, lattice-2 cache-fixed 33.78, lattice-4 cache-fixed 33.25, and exact
|
lattice-2 initial 160.86, lattice-2 cache-fixed 33.78, lattice-4 cache-fixed 33.25, and exact
|
||||||
block-skip 33.10. The lattice path is retained as a bounded experiment, but is not the selected
|
block-skip 33.10. The lattice path was a bounded historical experiment and is not retained. The
|
||||||
production mode. The exact block skip is also retained as a fail-closed exact-mode diagnostic;
|
exact block skip is retained as a fail-closed exact-mode diagnostic;
|
||||||
it is not used as a correctness or performance claim for value noise because its Perlin proof
|
it is not used as a correctness or performance claim for value noise because its Perlin proof
|
||||||
does not cover the changed field.
|
does not cover the changed field.
|
||||||
|
|
||||||
@@ -1255,7 +1255,7 @@ The capability and geometry candidates were:
|
|||||||
| `WormCheapValueLattice2Explore_20260914` | value, lattice 2 | `C47567A2` | 75,132 | 20,857 / 10,917 | pass; no speed gain |
|
| `WormCheapValueLattice2Explore_20260914` | value, lattice 2 | `C47567A2` | 75,132 | 20,857 / 10,917 | pass; no speed gain |
|
||||||
| `WormCheapFastHashExplore_20260914` | value, lattice 0 | `6204C7E6` | 75,956 | 20,851 / 10,917 | selected |
|
| `WormCheapFastHashExplore_20260914` | value, lattice 0 | `6204C7E6` | 75,956 | 20,851 / 10,917 | selected |
|
||||||
|
|
||||||
The selected mode changes the field, but the measured full-game gain is small rather than a
|
The historical selected mode changed the field, but the measured full-game gain was small rather than a
|
||||||
claim of a dramatic speedup. In two interleaved static pairs, exact averaged 0.087128 / 0.148415
|
claim of a dramatic speedup. In two interleaved static pairs, exact averaged 0.087128 / 0.148415
|
||||||
s request-to-ready p50/p95, 0.063042 / 0.105080 s generation p50/p95, and 33.213871 worker s;
|
s request-to-ready p50/p95, 0.063042 / 0.105080 s generation p50/p95, and 33.213871 worker s;
|
||||||
cheap averaged 0.087761 / 0.145897 s, 0.062863 / 0.106258 s, and 33.083774 worker s. In two
|
cheap averaged 0.087761 / 0.145897 s, 0.062863 / 0.106258 s, and 33.083774 worker s. In two
|
||||||
@@ -1438,3 +1438,230 @@ capability or comparison gate.
|
|||||||
| `WormFinalForceOffBuildSamplerOfflineRetry_20260914` | PASS; retried with writable `Saved\ZenData`/`Saved\DDC`, 733 modules, 10 loaded, zero identity mismatches, all 7,795 unique PCs mapped. |
|
| `WormFinalForceOffBuildSamplerOfflineRetry_20260914` | PASS; retried with writable `Saved\ZenData`/`Saved\DDC`, 733 modules, 10 loaded, zero identity mismatches, all 7,795 unique PCs mapped. |
|
||||||
| `PerfSampledBuild_FinalFingerprint.log` | PASS; final cache-key hardening rebuild, Result: Succeeded. |
|
| `PerfSampledBuild_FinalFingerprint.log` | PASS; final cache-key hardening rebuild, Result: Succeeded. |
|
||||||
| `WormFinalFingerprintSmoke_20260914` | PASS; post-hardening clean game smoke, exact staged runtime verified, writable DDC, zero crash folders/reporters. |
|
| `WormFinalFingerprintSmoke_20260914` | PASS; post-hardening clean game smoke, exact staged runtime verified, writable DDC, zero crash folders/reporters. |
|
||||||
|
## Cheap-worm revert and TunnelNetwork ablation round — 2026-09-14
|
||||||
|
|
||||||
|
This section supersedes the historical cheap-worm selection above. The earlier value-noise
|
||||||
|
experiment changed the field without producing a gain: static movement was within noise and the
|
||||||
|
moving worker result was about 2.1% slower. The production/default path therefore keeps exact
|
||||||
|
Perlin.
|
||||||
|
|
||||||
|
### Revert and exactness
|
||||||
|
|
||||||
|
- voxel.WormNoiseMode and voxel.WormLatticeStep, their storage, and their switches are removed.
|
||||||
|
- VoxelWormField now has one evaluation path: exact Perlin, with the existing N1 short circuit.
|
||||||
|
- The exact block skip remains available as voxel.WormBlockSkip, default 0. The asset-owned
|
||||||
|
bEnableWorms and the measurement-only voxel.WormsForceOff remain.
|
||||||
|
- The generation fingerprint still hashes bEnableWorms and WormsForceOff, and now also hashes the
|
||||||
|
resolved TunnelNetwork ablation mask.
|
||||||
|
- The new ablation controls are development-only, resolve once per process, are world-changing,
|
||||||
|
and are never gameplay or multiplayer switches. A nonzero bit means force that stage off; zero
|
||||||
|
means all stages on. Shipping builds resolve the mask to zero.
|
||||||
|
|
||||||
|
The prescribed UBT command from this file was used after synchronizing the staged host copy under
|
||||||
|
the plugin Saved directory. The final build log is
|
||||||
|
E:\Projet Unreal\VoxelM\Plugins\VoxelForge\Saved\PerfSampledBuild_Ablation.log and reports
|
||||||
|
Result: Succeeded. The loaded runtime was
|
||||||
|
E:\Projet Unreal\VoxelM\Plugins\VoxelForge\Saved\BuildHost\HostProject\Plugins\VoxelForge\Binaries\Win64\UnrealEditor-VoxelForge.dll
|
||||||
|
with SHA-256 A70528AC82641D7D610D8BA9497D0322B1EBDE22978B1E826A97FC4C0D038149. The editor module
|
||||||
|
SHA-256 was 8EA2A71BB29F828C5DE7559EC200AC4E86CF61F7DB5C8470406208073E22456D.
|
||||||
|
|
||||||
|
The two default commandlets both used
|
||||||
|
E:\Projet Unreal\VoxelM\Plugins\VoxelForge\Saved\DDC and
|
||||||
|
E:\Projet Unreal\VoxelM\Plugins\VoxelForge\Saved\ZenData:
|
||||||
|
|
||||||
|
- AblationDefaultExact_20260914Out: mask 0; status ok; canonical 128^3 export SHA-256
|
||||||
|
B3E5F4C398DD0547C6C55F415872058246D4A292C2F0D636EE1CD0829DE9B377; geometry CRC 07C14005;
|
||||||
|
82,273 vertices and 153,346 triangles.
|
||||||
|
- AblationDefaultCapability_20260914Out: mask 0; geometry CRC 311CA42C; 75,482 triangles;
|
||||||
|
player_fit_volume_cells 20,830; reachable_player_fit_cells 10,909; walk-only reachable
|
||||||
|
10,909; Connected; deterministic JSON.
|
||||||
|
|
||||||
|
The current game startup trace
|
||||||
|
E:\Projet Unreal\VoxelM\Plugins\VoxelForge\Saved\AblationDefaultStaticTrace_20260914.startup.jsonl
|
||||||
|
was compared by tile key to
|
||||||
|
E:\Projet Unreal\VoxelM\Plugins\VoxelForge\Saved\NextHeadStaticTrace_20260913.startup.jsonl:
|
||||||
|
841 versus 841 tile records, zero missing, zero extra, zero verdict/empty/triangle mismatches,
|
||||||
|
and identical all-tile and LOD0 triangle totals of 669,834 and 325,204. The current trace
|
||||||
|
finished with desired_set_satisfied_and_queue_drained and the loaded runtime hash above.
|
||||||
|
|
||||||
|
### Ablation controls
|
||||||
|
|
||||||
|
| bit | CVar | stage forced off |
|
||||||
|
|---:|---|---|
|
||||||
|
| 0x0001 | voxel.TunnelAblateCaveWarp | the three cave-warp Perlin calls; CaveWarpStrength resolves to zero |
|
||||||
|
| 0x0002 | voxel.TunnelAblateDetailOps | detail noise, roughness, and terrain operations |
|
||||||
|
| 0x0004 | voxel.TunnelAblateRoomSDF | room SDF and room-SDF joins in EvaluateSDFCached |
|
||||||
|
| 0x0008 | voxel.TunnelAblateTunnelSDF | tunnel SDF in EvaluateSDFCached |
|
||||||
|
| 0x0010 | voxel.TunnelAblateTunnelCore | EvaluateTunnelCoreWorld, support/core SDF work, and its GetDensityAt tail |
|
||||||
|
| 0x0020 | voxel.TunnelAblatePassageCarving | EvaluateModifierSDF and passage carving |
|
||||||
|
| 0x0040 | voxel.TunnelAblatePassageStructuralPosts | passage structural posts and legacy structural floor support |
|
||||||
|
| 0x0080 | voxel.TunnelAblateNativeFloor | native floor composition |
|
||||||
|
| 0x0100 | voxel.TunnelAblateDisturbances | ApplyDisturbances |
|
||||||
|
| 0x0200 | voxel.TunnelAblateOriginSpine | origin spine |
|
||||||
|
| 0x0400 | voxel.TunnelAblateBoundarySeal | boundary seal |
|
||||||
|
| 0x0800 | voxel.TunnelAblateLandingPosts | landing posts and landing-floor posts |
|
||||||
|
| 0x1000 | voxel.TunnelAblateXYEdgeSeal | XY edge seal |
|
||||||
|
| 0x2000 | voxel.TunnelAblatePitChimneySDF | pit and chimney SDF work |
|
||||||
|
|
||||||
|
Each override is applied in the fused evaluator, operator-stack source/classifier paths, cached
|
||||||
|
SDF/core paths, and modifier/floor paths where that stage exists. The resolved mask is emitted in
|
||||||
|
the explore JSON and is part of the generation fingerprint. This is an ablation instrument, not a
|
||||||
|
candidate field.
|
||||||
|
|
||||||
|
### Measurement method and noise
|
||||||
|
|
||||||
|
All game runs were sampler-off, NullRHI, operator block on, fused evaluator on, outer classifier
|
||||||
|
off, and used a 15-second clean session. Static runs produced 343 LOD0 samples and 841 applied
|
||||||
|
tiles. Each static row has two off runs; the two all-on static baselines are shared and interleaved
|
||||||
|
across the two passes. The all-on static mean was 33.064458 worker seconds, generation p50
|
||||||
|
0.064657 s, generation p95 0.105786 s. Baseline worker range was 0.27%; baseline generation-p95
|
||||||
|
range was 1.62%.
|
||||||
|
|
||||||
|
The table reports the mean of the two off runs, the saving against that all-on mean, and the
|
||||||
|
off-run worker range as a compact noise indicator. Positive saving means the off run used fewer
|
||||||
|
worker seconds. All rows completed 841 tiles with zero obsolete aborts.
|
||||||
|
|
||||||
|
| component removed | runs | off worker s | saving s (%) | off generation p50 s | off generation p95 s | off worker range |
|
||||||
|
|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
| tunnel SDF | 2 | 29.304 | 3.760 (11.37%) | 0.060813 | 0.084811 | 0.53% |
|
||||||
|
| cave warp | 2 | 29.654 | 3.411 (10.31%) | 0.056881 | 0.098260 | 2.03% |
|
||||||
|
| passage carving | 2 | 29.730 | 3.334 (10.08%) | 0.056336 | 0.096892 | 0.88% |
|
||||||
|
| landing posts | 2 | 29.800 | 3.264 (9.87%) | 0.056776 | 0.097884 | 1.33% |
|
||||||
|
| passage structural posts | 2 | 31.205 | 1.859 (5.62%) | 0.060143 | 0.099723 | 0.50% |
|
||||||
|
| worms | 2 | 32.265 | 0.799 (2.42%) | 0.064232 | 0.099511 | 0.83% |
|
||||||
|
| native floor | 2 | 32.347 | 0.718 (2.17%) | 0.063590 | 0.103897 | 0.45% |
|
||||||
|
| origin spine | 2 | 32.529 | 0.535 (1.62%) | 0.063174 | 0.102446 | 0.61% |
|
||||||
|
| room SDF | 2 | 32.542 | 0.522 (1.58%) | 0.064194 | 0.100999 | 0.90% |
|
||||||
|
| pit/chimney SDF | 2 | 32.689 | 0.375 (1.13%) | 0.064599 | 0.103472 | 0.26% |
|
||||||
|
| boundary seal | 2 | 32.814 | 0.251 (0.76%) | 0.064863 | 0.104105 | 1.21% |
|
||||||
|
| detail ops | 2 | 32.838 | 0.227 (0.69%) | 0.065330 | 0.100864 | 0.69% |
|
||||||
|
| XY edge seal | 2 | 32.901 | 0.164 (0.49%) | 0.065021 | 0.104411 | 1.30% |
|
||||||
|
| disturbances | 2 | 33.208 | -0.144 (-0.43%) | 0.065100 | 0.106342 | 0.53% |
|
||||||
|
| tunnel core | 2 | 95.468 | -62.404 (-188.73%) | 0.060597 | 0.098965 | 0.07% |
|
||||||
|
|
||||||
|
The field was intentionally allowed to change. For example, tunnel-SDF off produced 643,388
|
||||||
|
triangles, cave-warp off 655,726, passage-carving off 654,932, and tunnel-core off 655,654,
|
||||||
|
against the all-on 669,834 static triangles. Some low-cost rows retained the same mesh totals in
|
||||||
|
this window; they are still world-changing overrides and are not correctness candidates.
|
||||||
|
|
||||||
|
The sum of positive standalone static savings is 19.218608 worker seconds, 58.125% of the
|
||||||
|
33.064458-second baseline, leaving 13.845850 seconds (41.875%) as shared/unattributed cost in
|
||||||
|
this ablation accounting. The signed sum over every row is -43.329105 seconds because tunnel-core
|
||||||
|
removal is a large regression caused by the changed field; the rows are not additive and this is
|
||||||
|
why the signed sum is not a speedup prediction.
|
||||||
|
|
||||||
|
Moving confirmation covered the five largest positive static savers. Each row has two fresh
|
||||||
|
all-on baselines interleaved with two off runs. Movement began at 3 seconds and ran at 800 cm/s;
|
||||||
|
each run produced 882 LOD0 samples and 1,680 applied tiles, with zero obsolete aborts.
|
||||||
|
|
||||||
|
| component removed | runs | all-on worker s | off worker s | saving s (%) | all-on gen p50/p95 s | off gen p50/p95 s | baseline/off worker range |
|
||||||
|
|---|---:|---:|---:|---:|---:|---:|---:|
|
||||||
|
| tunnel SDF | 2+2 | 72.218 | 63.847 | 8.371 (11.59%) | 0.063238 / 0.104331 | 0.060117 / 0.082540 | 0.71% / 0.87% |
|
||||||
|
| cave warp | 2+2 | 72.810 | 65.010 | 7.800 (10.71%) | 0.064610 / 0.105986 | 0.055398 / 0.094423 | 0.46% / 1.14% |
|
||||||
|
| landing posts | 2+2 | 72.556 | 64.763 | 7.794 (10.74%) | 0.063820 / 0.105220 | 0.055539 / 0.096228 | 1.13% / 2.36% |
|
||||||
|
| passage carving | 2+2 | 72.043 | 64.552 | 7.492 (10.40%) | 0.063507 / 0.104761 | 0.055276 / 0.096373 | 1.07% / 1.72% |
|
||||||
|
| passage structural posts | 2+2 | 72.417 | 68.093 | 4.324 (5.97%) | 0.063779 / 0.104611 | 0.059068 / 0.101321 | 0.14% / 0.83% |
|
||||||
|
|
||||||
|
The moving standalone saving sum is 35.780708 worker seconds. Because these are five separate
|
||||||
|
ablations with separate baseline pairs, it is not a combined-speedup forecast; against the mean
|
||||||
|
72.409-second baseline per row it is 49.4%, leaving 36.628 seconds of shared/unattributed work.
|
||||||
|
|
||||||
|
### Ranking and plausible cheaper forms
|
||||||
|
|
||||||
|
1. Tunnel SDF — the largest measured real cost in both paths. A plausible exact optimization is
|
||||||
|
sharing one cached swept-tunnel result between EvaluateSDFCached, tunnel-core metadata, and
|
||||||
|
the final density tail, while preserving the existing evaluation order and float values. That
|
||||||
|
would be field-preserving. A lower-resolution or analytic approximation would be field-changing
|
||||||
|
and would need a fresh capability/export gate.
|
||||||
|
2. Cave warp — the three Perlin calls are expensive in the ablation. Exact reuse of the same
|
||||||
|
coordinates/results across the evaluator, cached graph, and op-stack paths is plausible and
|
||||||
|
field-preserving. Reducing frequency or replacing the noise is field-changing.
|
||||||
|
3. Passage carving — exact memoization/reuse of modifier SDF and passage candidate results across
|
||||||
|
EvaluateModifierSDF, ApplyPassageCarvingOnly, and the structural/floor queries is plausible and
|
||||||
|
field-preserving. Simplifying the carving geometry is field-changing.
|
||||||
|
4. Landing posts — exact per-tile reuse of landing/post/floor metadata is plausible and
|
||||||
|
field-preserving. Removing or thinning landing posts is field-changing.
|
||||||
|
5. Passage structural posts — exact reuse of structural-post SDF/metadata is plausible and
|
||||||
|
field-preserving. Removing or merging posts is field-changing.
|
||||||
|
|
||||||
|
Worms is sixth at 2.42% static and is now kept exact because the cheap alternatives had no gain.
|
||||||
|
Native floor and origin spine follow at 2.17% and 1.62%. Room SDF, detail ops, seals,
|
||||||
|
disturbances, and pit/chimney work are not attractive next-round targets on these measurements.
|
||||||
|
Tunnel-core removal is explicitly not a candidate: it increased worker time by about 2.9x.
|
||||||
|
These ablations explain why the old 19.4% line-level worm figure and the proposed tunnel-core
|
||||||
|
line targets were unsafe attribution guides.
|
||||||
|
|
||||||
|
### Complete launch ledger for this round
|
||||||
|
|
||||||
|
All game and commandlet logs below are under
|
||||||
|
E:\Projet Unreal\VoxelM\Plugins\VoxelForge\Saved. Every commandlet used the writable DDC path
|
||||||
|
E:\Projet Unreal\VoxelM\Plugins\VoxelForge\Saved\DDC. No offline symbolizer was needed or
|
||||||
|
launched in this round. Every Unreal process that ran exited 0, verified the loaded runtime
|
||||||
|
SHA-256 A70528AC82641D7D610D8BA9497D0322B1EBDE22978B1E826A97FC4C0D038149, and had no crash or
|
||||||
|
retry; the two parser-only attempts below launched no Unreal process. Peak observed private
|
||||||
|
memory was approximately 1.54–1.60 GiB per process, with no runaway growth.
|
||||||
|
|
||||||
|
| launch | outcome |
|
||||||
|
|---|---|
|
||||||
|
| initial prescribed staged build | UP-TO-DATE report; staged host was stale, so no measurement was taken |
|
||||||
|
| PerfSampledBuild_Ablation | PASS; prescribed UBT build, Result: Succeeded |
|
||||||
|
| AblationDefaultExact_20260914 | PASS; canonical export exact |
|
||||||
|
| AblationDefaultCapability_20260914 | PASS; 311CA42C, 75,482 triangles, 20,830 / 10,909 |
|
||||||
|
| static ablation batch launch 1 | FAILED before process start; PowerShell parser error from a missing DDC-argument quote |
|
||||||
|
| static ablation batch launch 2 | FAILED before process start; same quoting error; no Unreal process |
|
||||||
|
| AblationStaticBaseA_20260914 | PASS |
|
||||||
|
| AblationStaticBaseB_20260914 | PASS |
|
||||||
|
| AblationStaticCaveWarpA_20260914 | PASS |
|
||||||
|
| AblationStaticCaveWarpB_20260914 | PASS |
|
||||||
|
| AblationStaticDetailOpsA_20260914 | PASS |
|
||||||
|
| AblationStaticDetailOpsB_20260914 | PASS |
|
||||||
|
| AblationStaticRoomSDFA_20260914 | PASS |
|
||||||
|
| AblationStaticRoomSDFB_20260914 | PASS |
|
||||||
|
| AblationStaticTunnelSDFA_20260914 | PASS |
|
||||||
|
| AblationStaticTunnelSDFB_20260914 | PASS |
|
||||||
|
| AblationStaticTunnelCoreA_20260914 | PASS; 95.436091 worker seconds |
|
||||||
|
| AblationStaticTunnelCoreB_20260914 | PASS; 95.500636 worker seconds |
|
||||||
|
| AblationStaticPassageCarvingA_20260914 | PASS |
|
||||||
|
| AblationStaticPassageCarvingB_20260914 | PASS |
|
||||||
|
| AblationStaticPassageStructuralPostsA_20260914 | PASS |
|
||||||
|
| AblationStaticPassageStructuralPostsB_20260914 | PASS |
|
||||||
|
| AblationStaticNativeFloorA_20260914 | PASS |
|
||||||
|
| AblationStaticNativeFloorB_20260914 | PASS |
|
||||||
|
| AblationStaticDisturbancesA_20260914 | PASS |
|
||||||
|
| AblationStaticDisturbancesB_20260914 | PASS |
|
||||||
|
| AblationStaticOriginSpineA_20260914 | PASS |
|
||||||
|
| AblationStaticOriginSpineB_20260914 | PASS |
|
||||||
|
| AblationStaticBoundarySealA_20260914 | PASS |
|
||||||
|
| AblationStaticBoundarySealB_20260914 | PASS |
|
||||||
|
| AblationStaticLandingPostsA_20260914 | PASS |
|
||||||
|
| AblationStaticLandingPostsB_20260914 | PASS |
|
||||||
|
| AblationStaticXYEdgeSealA_20260914 | PASS |
|
||||||
|
| AblationStaticXYEdgeSealB_20260914 | PASS |
|
||||||
|
| AblationStaticPitChimneySDFA_20260914 | PASS |
|
||||||
|
| AblationStaticPitChimneySDFB_20260914 | PASS |
|
||||||
|
| AblationStaticWormsOffA_20260914 | PASS |
|
||||||
|
| AblationStaticWormsOffB_20260914 | PASS |
|
||||||
|
| AblationMovingTunnelSDFBaseA_20260914 | PASS |
|
||||||
|
| AblationMovingTunnelSDFA_20260914 | PASS |
|
||||||
|
| AblationMovingTunnelSDFBaseB_20260914 | PASS |
|
||||||
|
| AblationMovingTunnelSDFB_20260914 | PASS |
|
||||||
|
| AblationMovingCaveWarpBaseA_20260914 | PASS |
|
||||||
|
| AblationMovingCaveWarpA_20260914 | PASS |
|
||||||
|
| AblationMovingCaveWarpBaseB_20260914 | PASS |
|
||||||
|
| AblationMovingCaveWarpB_20260914 | PASS |
|
||||||
|
| AblationMovingPassageCarvingBaseA_20260914 | PASS |
|
||||||
|
| AblationMovingPassageCarvingA_20260914 | PASS |
|
||||||
|
| AblationMovingPassageCarvingBaseB_20260914 | PASS |
|
||||||
|
| AblationMovingPassageCarvingB_20260914 | PASS |
|
||||||
|
| AblationMovingLandingPostsBaseA_20260914 | PASS |
|
||||||
|
| AblationMovingLandingPostsA_20260914 | PASS |
|
||||||
|
| AblationMovingLandingPostsBaseB_20260914 | PASS |
|
||||||
|
| AblationMovingLandingPostsB_20260914 | PASS |
|
||||||
|
| AblationMovingPassageStructuralPostsBaseA_20260914 | PASS |
|
||||||
|
| AblationMovingPassageStructuralPostsA_20260914 | PASS |
|
||||||
|
| AblationMovingPassageStructuralPostsBaseB_20260914 | PASS |
|
||||||
|
| AblationMovingPassageStructuralPostsB_20260914 | PASS |
|
||||||
|
| AblationDefaultStaticTrace_20260914 | PASS; current 841-tile trace, exact keyed comparison |
|
||||||
|
|
||||||
|
No .uasset was edited. Engine source and binaries were not modified. No commit, push, or stash was
|
||||||
|
performed.
|
||||||
@@ -30,6 +30,7 @@
|
|||||||
#include "VoxelCaveMorphology.h"
|
#include "VoxelCaveMorphology.h"
|
||||||
#include "VoxelDensityProfile.h"
|
#include "VoxelDensityProfile.h"
|
||||||
#include "VoxelDensityPrimitives.h"
|
#include "VoxelDensityPrimitives.h"
|
||||||
|
#include "VoxelDensityAblation.h"
|
||||||
#include "VoxelStrateMeasure.h"
|
#include "VoxelStrateMeasure.h"
|
||||||
#include "VoxelTypes.h" // Pour VOXEL_NOISE_SCALE, SmoothStep01
|
#include "VoxelTypes.h" // Pour VOXEL_NOISE_SCALE, SmoothStep01
|
||||||
#include "VoxelStrateTypes.h"
|
#include "VoxelStrateTypes.h"
|
||||||
@@ -3166,6 +3167,10 @@ namespace
|
|||||||
const FStrateGenerationParams& Params,
|
const FStrateGenerationParams& Params,
|
||||||
uint32 Seed)
|
uint32 Seed)
|
||||||
{
|
{
|
||||||
|
if (VoxelDensityAblation::IsCaveWarpOff())
|
||||||
|
{
|
||||||
|
return WorldPoint;
|
||||||
|
}
|
||||||
float EffectiveZ = WorldPoint.Z;
|
float EffectiveZ = WorldPoint.Z;
|
||||||
if (Params.VerticalScale > 0.0f && Params.VerticalScale != 1.0f)
|
if (Params.VerticalScale > 0.0f && Params.VerticalScale != 1.0f)
|
||||||
{
|
{
|
||||||
@@ -7286,6 +7291,8 @@ void VoxelCaveMorphology::BuildChunkCache(
|
|||||||
CR.RoomOp->ApplyTo(OpParams, CR.RoomOpWeight);
|
CR.RoomOp->ApplyTo(OpParams, CR.RoomOpWeight);
|
||||||
|
|
||||||
// PITS — downward shafts anchored in the room's lower half.
|
// PITS — downward shafts anchored in the room's lower half.
|
||||||
|
if (!VoxelDensityAblation::IsPitChimneySDFOff())
|
||||||
|
{
|
||||||
BakeRoomFeature(CR, /*Max*/2, OpParams.PitDensity,
|
BakeRoomFeature(CR, /*Max*/2, OpParams.PitDensity,
|
||||||
0xDE1A7Eu, 6271u, 0xABCDu, 0x5EEDu,
|
0xDE1A7Eu, 6271u, 0xABCDu, 0x5EEDu,
|
||||||
/*XYScale*/0.6f, OpParams.PitMinRadius, OpParams.PitMaxRadius,
|
/*XYScale*/0.6f, OpParams.PitMinRadius, OpParams.PitMaxRadius,
|
||||||
@@ -7330,6 +7337,7 @@ void VoxelCaveMorphology::BuildChunkCache(
|
|||||||
Chim.BoundXYRadiusSq = MaxXYR * MaxXYR;
|
Chim.BoundXYRadiusSq = MaxXYR * MaxXYR;
|
||||||
OutCache.Chimneys.Add(Chim);
|
OutCache.Chimneys.Add(Chim);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// COLUMNS — full-height solid cylinders (no Z anchor, no flare).
|
// COLUMNS — full-height solid cylinders (no Z anchor, no flare).
|
||||||
BakeRoomFeature(CR, /*Max*/4, OpParams.ColumnDensity,
|
BakeRoomFeature(CR, /*Max*/4, OpParams.ColumnDensity,
|
||||||
@@ -7481,9 +7489,13 @@ float VoxelCaveMorphology::EvaluateSDFCached(
|
|||||||
MinSDF = VoxelSDF::SmoothMin(MinSDF, RoomSDF, BlendK);
|
MinSDF = VoxelSDF::SmoothMin(MinSDF, RoomSDF, BlendK);
|
||||||
};
|
};
|
||||||
|
|
||||||
const int32 RoomCandidateCount = VF_ForEachSpatialCandidate(
|
int32 RoomCandidateCount = 0;
|
||||||
|
if (!VoxelDensityAblation::IsRoomSDFOff())
|
||||||
|
{
|
||||||
|
RoomCandidateCount = VF_ForEachSpatialCandidate(
|
||||||
Cache.RoomSpatialIndex, Cache.Rooms.Num(), WorldX, WorldY, EvaluateRoom,
|
Cache.RoomSpatialIndex, Cache.Rooms.Num(), WorldX, WorldY, EvaluateRoom,
|
||||||
bUseSpatialIndex);
|
bUseSpatialIndex);
|
||||||
|
}
|
||||||
if (VoxelDensityProfile::AreCountersEnabled())
|
if (VoxelDensityProfile::AreCountersEnabled())
|
||||||
{
|
{
|
||||||
VoxelDensityProfile::AddCounter(
|
VoxelDensityProfile::AddCounter(
|
||||||
@@ -7512,9 +7524,12 @@ float VoxelCaveMorphology::EvaluateSDFCached(
|
|||||||
const float JoinSDF = FMath::Max(HorizontalSDF, VerticalSDF);
|
const float JoinSDF = FMath::Max(HorizontalSDF, VerticalSDF);
|
||||||
MinSDF = VoxelSDF::SmoothMin(MinSDF, JoinSDF, BlendK);
|
MinSDF = VoxelSDF::SmoothMin(MinSDF, JoinSDF, BlendK);
|
||||||
};
|
};
|
||||||
|
if (!VoxelDensityAblation::IsRoomSDFOff())
|
||||||
|
{
|
||||||
VF_ForEachSpatialCandidate(
|
VF_ForEachSpatialCandidate(
|
||||||
Cache.RoomFloorJoinSpatialIndex, Cache.RoomFloorJoins.Num(),
|
Cache.RoomFloorJoinSpatialIndex, Cache.RoomFloorJoins.Num(),
|
||||||
WorldX, WorldY, EvaluateJoin, bUseSpatialIndex);
|
WorldX, WorldY, EvaluateJoin, bUseSpatialIndex);
|
||||||
|
}
|
||||||
|
|
||||||
//=========================================================================
|
//=========================================================================
|
||||||
// Tunnel SDFs
|
// Tunnel SDFs
|
||||||
@@ -7553,9 +7568,13 @@ float VoxelCaveMorphology::EvaluateSDFCached(
|
|||||||
MinSDF = VoxelSDF::SmoothMin(MinSDF, TunnelSDF, BlendK);
|
MinSDF = VoxelSDF::SmoothMin(MinSDF, TunnelSDF, BlendK);
|
||||||
};
|
};
|
||||||
|
|
||||||
const int32 TunnelCandidateCount = VF_ForEachSpatialCandidate(
|
int32 TunnelCandidateCount = 0;
|
||||||
|
if (!VoxelDensityAblation::IsTunnelSDFOff())
|
||||||
|
{
|
||||||
|
TunnelCandidateCount = VF_ForEachSpatialCandidate(
|
||||||
Cache.TunnelSpatialIndex, Cache.Tunnels.Num(), WorldX, WorldY, EvaluateTunnel,
|
Cache.TunnelSpatialIndex, Cache.Tunnels.Num(), WorldX, WorldY, EvaluateTunnel,
|
||||||
bUseSpatialIndex);
|
bUseSpatialIndex);
|
||||||
|
}
|
||||||
if (VoxelDensityProfile::AreCountersEnabled())
|
if (VoxelDensityProfile::AreCountersEnabled())
|
||||||
{
|
{
|
||||||
VoxelDensityProfile::AddCounter(
|
VoxelDensityProfile::AddCounter(
|
||||||
@@ -7577,6 +7596,10 @@ float VoxelCaveMorphology::EvaluateTunnelCoreSDF(
|
|||||||
const FChunkSDFCache& Cache,
|
const FChunkSDFCache& Cache,
|
||||||
bool bUseSpatialIndex)
|
bool bUseSpatialIndex)
|
||||||
{
|
{
|
||||||
|
if (VoxelDensityAblation::IsTunnelCoreOff())
|
||||||
|
{
|
||||||
|
return FLT_MAX;
|
||||||
|
}
|
||||||
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();
|
const FCachedTunnel* TunnelData = Cache.Tunnels.GetData();
|
||||||
@@ -7618,6 +7641,10 @@ FTunnelCoreWorldEvaluation VoxelCaveMorphology::EvaluateTunnelCoreWorld(
|
|||||||
const FTunnelSupportFloorColumn* SupportColumn,
|
const FTunnelSupportFloorColumn* SupportColumn,
|
||||||
bool bUseSpatialIndex)
|
bool bUseSpatialIndex)
|
||||||
{
|
{
|
||||||
|
if (VoxelDensityAblation::IsTunnelCoreOff())
|
||||||
|
{
|
||||||
|
return FTunnelCoreWorldEvaluation();
|
||||||
|
}
|
||||||
VoxelDensityProfile::FScopedTimer ProfileTimer(
|
VoxelDensityProfile::FScopedTimer ProfileTimer(
|
||||||
VoxelDensityProfile::EBucket::TunnelCoreWorld);
|
VoxelDensityProfile::EBucket::TunnelCoreWorld);
|
||||||
const FVector Pos(WorldX, WorldY, WorldZ);
|
const FVector Pos(WorldX, WorldY, WorldZ);
|
||||||
@@ -7756,6 +7783,10 @@ bool VoxelCaveMorphology::IsTunnelSupportFloorWorldPoint(
|
|||||||
const FChunkSDFCache& Cache,
|
const FChunkSDFCache& Cache,
|
||||||
bool bUseSpatialIndex)
|
bool bUseSpatialIndex)
|
||||||
{
|
{
|
||||||
|
if (VoxelDensityAblation::IsTunnelCoreOff())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
const FVector Pos(WorldX, WorldY, WorldZ);
|
const FVector Pos(WorldX, WorldY, WorldZ);
|
||||||
const FCachedTunnel* TunnelData = Cache.Tunnels.GetData();
|
const FCachedTunnel* TunnelData = Cache.Tunnels.GetData();
|
||||||
|
|
||||||
@@ -7875,6 +7906,11 @@ void VoxelCaveMorphology::BuildTunnelSupportFloorColumn(
|
|||||||
FTunnelSupportFloorColumn& OutColumn,
|
FTunnelSupportFloorColumn& OutColumn,
|
||||||
bool bUseSpatialIndex)
|
bool bUseSpatialIndex)
|
||||||
{
|
{
|
||||||
|
if (VoxelDensityAblation::IsTunnelCoreOff())
|
||||||
|
{
|
||||||
|
OutColumn.Reset();
|
||||||
|
return;
|
||||||
|
}
|
||||||
VoxelDensityProfile::FScopedTimer ProfileTimer(
|
VoxelDensityProfile::FScopedTimer ProfileTimer(
|
||||||
VoxelDensityProfile::EBucket::TunnelCoreSupport);
|
VoxelDensityProfile::EBucket::TunnelCoreSupport);
|
||||||
OutColumn.Reset();
|
OutColumn.Reset();
|
||||||
|
|||||||
@@ -0,0 +1,131 @@
|
|||||||
|
// VoxelDensityAblation.cpp
|
||||||
|
// Development-only TunnelNetwork ablation controls.
|
||||||
|
|
||||||
|
#include "VoxelDensityAblation.h"
|
||||||
|
|
||||||
|
#include "HAL/IConsoleManager.h"
|
||||||
|
#include "Misc/CommandLine.h"
|
||||||
|
#include "Misc/Parse.h"
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
constexpr uint32 UnresolvedMask = 0xFFFFFFFFu;
|
||||||
|
std::atomic<uint32> GResolvedMask(UnresolvedMask);
|
||||||
|
|
||||||
|
#if !UE_BUILD_SHIPPING
|
||||||
|
int32 GCaveWarp = 0;
|
||||||
|
int32 GDetailOps = 0;
|
||||||
|
int32 GRoomSDF = 0;
|
||||||
|
int32 GTunnelSDF = 0;
|
||||||
|
int32 GTunnelCore = 0;
|
||||||
|
int32 GPassageCarving = 0;
|
||||||
|
int32 GPassageStructuralPosts = 0;
|
||||||
|
int32 GNativeFloor = 0;
|
||||||
|
int32 GDisturbances = 0;
|
||||||
|
int32 GOriginSpine = 0;
|
||||||
|
int32 GBoundarySeal = 0;
|
||||||
|
int32 GLandingPosts = 0;
|
||||||
|
int32 GXYEdgeSeal = 0;
|
||||||
|
int32 GPitChimneySDF = 0;
|
||||||
|
|
||||||
|
#define VF_ABLATION_CVAR(Name, Storage) \
|
||||||
|
FAutoConsoleVariableRef CVar##Storage( \
|
||||||
|
Name, Storage, \
|
||||||
|
TEXT("WORLD-CHANGING DEVELOPMENT-ONLY measurement override. Forces this " \
|
||||||
|
"TunnelNetwork density stage off; never use as a gameplay switch. " \
|
||||||
|
"All peers and regenerated worlds must use the same value. Resolved once " \
|
||||||
|
"per process. 0=on, 1=off."), ECVF_Default)
|
||||||
|
|
||||||
|
VF_ABLATION_CVAR(TEXT("voxel.TunnelAblateCaveWarp"), GCaveWarp);
|
||||||
|
VF_ABLATION_CVAR(TEXT("voxel.TunnelAblateDetailOps"), GDetailOps);
|
||||||
|
VF_ABLATION_CVAR(TEXT("voxel.TunnelAblateRoomSDF"), GRoomSDF);
|
||||||
|
VF_ABLATION_CVAR(TEXT("voxel.TunnelAblateTunnelSDF"), GTunnelSDF);
|
||||||
|
VF_ABLATION_CVAR(TEXT("voxel.TunnelAblateTunnelCore"), GTunnelCore);
|
||||||
|
VF_ABLATION_CVAR(TEXT("voxel.TunnelAblatePassageCarving"), GPassageCarving);
|
||||||
|
VF_ABLATION_CVAR(TEXT("voxel.TunnelAblatePassageStructuralPosts"), GPassageStructuralPosts);
|
||||||
|
VF_ABLATION_CVAR(TEXT("voxel.TunnelAblateNativeFloor"), GNativeFloor);
|
||||||
|
VF_ABLATION_CVAR(TEXT("voxel.TunnelAblateDisturbances"), GDisturbances);
|
||||||
|
VF_ABLATION_CVAR(TEXT("voxel.TunnelAblateOriginSpine"), GOriginSpine);
|
||||||
|
VF_ABLATION_CVAR(TEXT("voxel.TunnelAblateBoundarySeal"), GBoundarySeal);
|
||||||
|
VF_ABLATION_CVAR(TEXT("voxel.TunnelAblateLandingPosts"), GLandingPosts);
|
||||||
|
VF_ABLATION_CVAR(TEXT("voxel.TunnelAblateXYEdgeSeal"), GXYEdgeSeal);
|
||||||
|
VF_ABLATION_CVAR(TEXT("voxel.TunnelAblatePitChimneySDF"), GPitChimneySDF);
|
||||||
|
|
||||||
|
#undef VF_ABLATION_CVAR
|
||||||
|
|
||||||
|
uint32 ResolveValue(const TCHAR* Name, int32& Storage)
|
||||||
|
{
|
||||||
|
int32 Value = Storage;
|
||||||
|
FParse::Value(FCommandLine::Get(), Name, Value);
|
||||||
|
Storage = Value != 0 ? 1 : 0;
|
||||||
|
return Storage != 0 ? 1u : 0u;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32 ResolveMask()
|
||||||
|
{
|
||||||
|
uint32 Mask = 0;
|
||||||
|
Mask |= ResolveValue(TEXT("voxel.TunnelAblateCaveWarp="), GCaveWarp)
|
||||||
|
* static_cast<uint32>(VoxelDensityAblation::EStage::CaveWarp);
|
||||||
|
Mask |= ResolveValue(TEXT("voxel.TunnelAblateDetailOps="), GDetailOps)
|
||||||
|
* static_cast<uint32>(VoxelDensityAblation::EStage::DetailOps);
|
||||||
|
Mask |= ResolveValue(TEXT("voxel.TunnelAblateRoomSDF="), GRoomSDF)
|
||||||
|
* static_cast<uint32>(VoxelDensityAblation::EStage::RoomSDF);
|
||||||
|
Mask |= ResolveValue(TEXT("voxel.TunnelAblateTunnelSDF="), GTunnelSDF)
|
||||||
|
* static_cast<uint32>(VoxelDensityAblation::EStage::TunnelSDF);
|
||||||
|
Mask |= ResolveValue(TEXT("voxel.TunnelAblateTunnelCore="), GTunnelCore)
|
||||||
|
* static_cast<uint32>(VoxelDensityAblation::EStage::TunnelCore);
|
||||||
|
Mask |= ResolveValue(TEXT("voxel.TunnelAblatePassageCarving="), GPassageCarving)
|
||||||
|
* static_cast<uint32>(VoxelDensityAblation::EStage::PassageCarving);
|
||||||
|
Mask |= ResolveValue(
|
||||||
|
TEXT("voxel.TunnelAblatePassageStructuralPosts="), GPassageStructuralPosts)
|
||||||
|
* static_cast<uint32>(VoxelDensityAblation::EStage::PassageStructuralPosts);
|
||||||
|
Mask |= ResolveValue(TEXT("voxel.TunnelAblateNativeFloor="), GNativeFloor)
|
||||||
|
* static_cast<uint32>(VoxelDensityAblation::EStage::NativeFloor);
|
||||||
|
Mask |= ResolveValue(TEXT("voxel.TunnelAblateDisturbances="), GDisturbances)
|
||||||
|
* static_cast<uint32>(VoxelDensityAblation::EStage::Disturbances);
|
||||||
|
Mask |= ResolveValue(TEXT("voxel.TunnelAblateOriginSpine="), GOriginSpine)
|
||||||
|
* static_cast<uint32>(VoxelDensityAblation::EStage::OriginSpine);
|
||||||
|
Mask |= ResolveValue(TEXT("voxel.TunnelAblateBoundarySeal="), GBoundarySeal)
|
||||||
|
* static_cast<uint32>(VoxelDensityAblation::EStage::BoundarySeal);
|
||||||
|
Mask |= ResolveValue(TEXT("voxel.TunnelAblateLandingPosts="), GLandingPosts)
|
||||||
|
* static_cast<uint32>(VoxelDensityAblation::EStage::LandingPosts);
|
||||||
|
Mask |= ResolveValue(TEXT("voxel.TunnelAblateXYEdgeSeal="), GXYEdgeSeal)
|
||||||
|
* static_cast<uint32>(VoxelDensityAblation::EStage::XYEdgeSeal);
|
||||||
|
Mask |= ResolveValue(TEXT("voxel.TunnelAblatePitChimneySDF="), GPitChimneySDF)
|
||||||
|
* static_cast<uint32>(VoxelDensityAblation::EStage::PitChimneySDF);
|
||||||
|
return Mask;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace VoxelDensityAblation
|
||||||
|
{
|
||||||
|
uint32 GetResolvedMask()
|
||||||
|
{
|
||||||
|
uint32 Existing = GResolvedMask.load(std::memory_order_acquire);
|
||||||
|
if (Existing != UnresolvedMask)
|
||||||
|
{
|
||||||
|
return Existing;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if UE_BUILD_SHIPPING
|
||||||
|
constexpr uint32 Resolved = 0;
|
||||||
|
#else
|
||||||
|
const uint32 Resolved = ResolveMask();
|
||||||
|
#endif
|
||||||
|
uint32 Expected = UnresolvedMask;
|
||||||
|
if (!GResolvedMask.compare_exchange_strong(
|
||||||
|
Expected, Resolved, std::memory_order_release, std::memory_order_acquire))
|
||||||
|
{
|
||||||
|
return Expected;
|
||||||
|
}
|
||||||
|
|
||||||
|
UE_LOG(LogTemp, Display,
|
||||||
|
TEXT("[VoxelForgeTunnelAblation] resolved mask=0x%08x "
|
||||||
|
"(world-changing development measurement overrides; 0=all on)"),
|
||||||
|
Resolved);
|
||||||
|
return Resolved;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,6 +22,7 @@
|
|||||||
#include "VoxelStrateComposer.h"
|
#include "VoxelStrateComposer.h"
|
||||||
|
|
||||||
#include "VoxelDensityPrimitives.h" // VF_ApplyOriginSpine / seals / PassageCarving
|
#include "VoxelDensityPrimitives.h" // VF_ApplyOriginSpine / seals / PassageCarving
|
||||||
|
#include "VoxelDensityAblation.h" // fingerprinted development-only stage measurements
|
||||||
#include "VoxelCaveMorphology.h" // VoxelSDF::Capsule, VoxelHash
|
#include "VoxelCaveMorphology.h" // VoxelSDF::Capsule, VoxelHash
|
||||||
#include "VoxelGenerator.h" // VoxelGenLOD::Eff
|
#include "VoxelGenerator.h" // VoxelGenLOD::Eff
|
||||||
#include "VoxelHeightOp.h" // FVoxelHeightStack — SurfaceWorld's two height stacks
|
#include "VoxelHeightOp.h" // FVoxelHeightStack — SurfaceWorld's two height stacks
|
||||||
@@ -1933,6 +1934,10 @@ namespace
|
|||||||
EVoxelOpEffect EffectOverBox(const FBox& VoxelBox,
|
EVoxelOpEffect EffectOverBox(const FBox& VoxelBox,
|
||||||
const FVoxelOpContext& Ctx) const override
|
const FVoxelOpContext& Ctx) const override
|
||||||
{
|
{
|
||||||
|
if (VoxelDensityAblation::IsOriginSpineOff())
|
||||||
|
{
|
||||||
|
return EVoxelOpEffect::Identity;
|
||||||
|
}
|
||||||
const bool bRoomTouches = Ctx.bUseLatticeProof
|
const bool bRoomTouches = Ctx.bUseLatticeProof
|
||||||
? VoxelPassageGeometry::OriginLandingRoomTouchesLattice(
|
? VoxelPassageGeometry::OriginLandingRoomTouchesLattice(
|
||||||
VoxelBox, Ctx.LatticeOriginVoxels, Ctx.Step,
|
VoxelBox, Ctx.LatticeOriginVoxels, Ctx.Step,
|
||||||
@@ -2018,6 +2023,10 @@ namespace
|
|||||||
*/
|
*/
|
||||||
EVoxelTileClass ClassifyBox(const FBox& VoxelBox, const FVoxelOpContext&) const override
|
EVoxelTileClass ClassifyBox(const FBox& VoxelBox, const FVoxelOpContext&) const override
|
||||||
{
|
{
|
||||||
|
if (VoxelDensityAblation::IsBoundarySealOff())
|
||||||
|
{
|
||||||
|
return EVoxelTileClass::Mixed;
|
||||||
|
}
|
||||||
if (Thickness <= 0.0f || Base <= 0.0f) { return EVoxelTileClass::Mixed; }
|
if (Thickness <= 0.0f || Base <= 0.0f) { return EVoxelTileClass::Mixed; }
|
||||||
|
|
||||||
constexpr float SafetyMargin = 1.0f;
|
constexpr float SafetyMargin = 1.0f;
|
||||||
@@ -2039,6 +2048,10 @@ namespace
|
|||||||
// Hors de sa bande, le seal ne fait rien du tout ; à cheval, il ne peut qu'ajouter du solide.
|
// Hors de sa bande, le seal ne fait rien du tout ; à cheval, il ne peut qu'ajouter du solide.
|
||||||
EVoxelOpEffect EffectOverBox(const FBox& VoxelBox, const FVoxelOpContext&) const override
|
EVoxelOpEffect EffectOverBox(const FBox& VoxelBox, const FVoxelOpContext&) const override
|
||||||
{
|
{
|
||||||
|
if (VoxelDensityAblation::IsBoundarySealOff())
|
||||||
|
{
|
||||||
|
return EVoxelOpEffect::Identity;
|
||||||
|
}
|
||||||
if (Thickness <= 0.0f) { return EVoxelOpEffect::Identity; }
|
if (Thickness <= 0.0f) { return EVoxelOpEffect::Identity; }
|
||||||
const bool bTouchesTopBand = ((float)VoxelBox.Max.Z >= TopZ - Thickness) && ((float)VoxelBox.Min.Z <= TopZ);
|
const bool bTouchesTopBand = ((float)VoxelBox.Max.Z >= TopZ - Thickness) && ((float)VoxelBox.Min.Z <= TopZ);
|
||||||
const bool bTouchesBotBand = ((float)VoxelBox.Min.Z <= BotZ + Thickness) && ((float)VoxelBox.Max.Z >= BotZ);
|
const bool bTouchesBotBand = ((float)VoxelBox.Min.Z <= BotZ + Thickness) && ((float)VoxelBox.Max.Z >= BotZ);
|
||||||
@@ -2100,6 +2113,10 @@ namespace
|
|||||||
*/
|
*/
|
||||||
EVoxelTileClass ClassifyBox(const FBox& VoxelBox, const FVoxelOpContext& Ctx) const override
|
EVoxelTileClass ClassifyBox(const FBox& VoxelBox, const FVoxelOpContext& Ctx) const override
|
||||||
{
|
{
|
||||||
|
if (VoxelDensityAblation::IsXYEdgeSealOff())
|
||||||
|
{
|
||||||
|
return EVoxelTileClass::Mixed;
|
||||||
|
}
|
||||||
return VF_XYEdgeSealBoxIsForcedSolid(
|
return VF_XYEdgeSealBoxIsForcedSolid(
|
||||||
VoxelBox, Ctx.WorldRadiusVoxels, Ctx.EdgeSealThickness, Base)
|
VoxelBox, Ctx.WorldRadiusVoxels, Ctx.EdgeSealThickness, Base)
|
||||||
? EVoxelTileClass::AllSolid : EVoxelTileClass::Mixed;
|
? EVoxelTileClass::AllSolid : EVoxelTileClass::Mixed;
|
||||||
@@ -2114,6 +2131,10 @@ namespace
|
|||||||
// In the inner world it is Identity; a box that reaches the radial band can only gain rock.
|
// In the inner world it is Identity; a box that reaches the radial band can only gain rock.
|
||||||
EVoxelOpEffect EffectOverBox(const FBox& VoxelBox, const FVoxelOpContext& Ctx) const override
|
EVoxelOpEffect EffectOverBox(const FBox& VoxelBox, const FVoxelOpContext& Ctx) const override
|
||||||
{
|
{
|
||||||
|
if (VoxelDensityAblation::IsXYEdgeSealOff())
|
||||||
|
{
|
||||||
|
return EVoxelOpEffect::Identity;
|
||||||
|
}
|
||||||
return VF_XYEdgeSealBoxTouchesBand(
|
return VF_XYEdgeSealBoxTouchesBand(
|
||||||
VoxelBox, Ctx.WorldRadiusVoxels, Ctx.EdgeSealThickness)
|
VoxelBox, Ctx.WorldRadiusVoxels, Ctx.EdgeSealThickness)
|
||||||
? EVoxelOpEffect::FillOnly : EVoxelOpEffect::Identity;
|
? EVoxelOpEffect::FillOnly : EVoxelOpEffect::Identity;
|
||||||
@@ -2177,29 +2198,39 @@ namespace
|
|||||||
EVoxelOpEffect EffectOverBox(const FBox& VoxelBox,
|
EVoxelOpEffect EffectOverBox(const FBox& VoxelBox,
|
||||||
const FVoxelOpContext& Ctx) const override
|
const FVoxelOpContext& Ctx) const override
|
||||||
{
|
{
|
||||||
|
if (VoxelDensityAblation::IsPassageCarvingOff()
|
||||||
|
&& VoxelDensityAblation::IsLandingPostsOff()
|
||||||
|
&& VoxelDensityAblation::IsPassageStructuralPostsOff()
|
||||||
|
&& VoxelDensityAblation::IsOriginSpineOff())
|
||||||
|
{
|
||||||
|
return EVoxelOpEffect::Identity;
|
||||||
|
}
|
||||||
const UVoxelStrateManager* LiveManager = Manager.Get();
|
const UVoxelStrateManager* LiveManager = Manager.Get();
|
||||||
if (!LiveManager) { return EVoxelOpEffect::Identity; }
|
if (!LiveManager) { return EVoxelOpEffect::Identity; }
|
||||||
const bool bOriginFloor = Ctx.bUseLatticeProof
|
const bool bOriginFloor = !VoxelDensityAblation::IsLandingPostsOff()
|
||||||
|
&& (Ctx.bUseLatticeProof
|
||||||
? VoxelPassageGeometry::OriginLandingFloorTouchesLattice(
|
? VoxelPassageGeometry::OriginLandingFloorTouchesLattice(
|
||||||
VoxelBox, Ctx.LatticeOriginVoxels, Ctx.Step,
|
VoxelBox, Ctx.LatticeOriginVoxels, Ctx.Step,
|
||||||
TopZ, BotZ, Seal, SpineRadius)
|
TopZ, BotZ, Seal, SpineRadius)
|
||||||
: VoxelPassageGeometry::OriginLandingFloorTouchesBox(
|
: VoxelPassageGeometry::OriginLandingFloorTouchesBox(
|
||||||
VoxelBox, TopZ, BotZ, Seal, SpineRadius);
|
VoxelBox, TopZ, BotZ, Seal, SpineRadius));
|
||||||
const bool bPassageFloor = Ctx.bUseLatticeProof
|
const bool bPassageFloor = !VoxelDensityAblation::IsLandingPostsOff()
|
||||||
|
&& (Ctx.bUseLatticeProof
|
||||||
? LiveManager->AnyPassageLandingFloorNearLattice(
|
? LiveManager->AnyPassageLandingFloorNearLattice(
|
||||||
VoxelBox, Ctx.LatticeOriginVoxels, Ctx.Step)
|
VoxelBox, Ctx.LatticeOriginVoxels, Ctx.Step)
|
||||||
: LiveManager->AnyPassageLandingFloorNearBox(
|
: LiveManager->AnyPassageLandingFloorNearBox(
|
||||||
VoxelBox.Min, VoxelBox.Max);
|
VoxelBox.Min, VoxelBox.Max));
|
||||||
if (bOriginFloor || bPassageFloor)
|
if (bOriginFloor || bPassageFloor)
|
||||||
{
|
{
|
||||||
// The room carves air, while its support slab force-writes solid. Both
|
// The room carves air, while its support slab force-writes solid. Both
|
||||||
// hypotheses must therefore be killed for a box touching that slab.
|
// hypotheses must therefore be killed for a box touching that slab.
|
||||||
return EVoxelOpEffect::Both;
|
return EVoxelOpEffect::Both;
|
||||||
}
|
}
|
||||||
const bool bPassageCarve = Ctx.bUseLatticeProof
|
const bool bPassageCarve = !VoxelDensityAblation::IsPassageCarvingOff()
|
||||||
|
&& (Ctx.bUseLatticeProof
|
||||||
? LiveManager->AnyPassageNearLattice(
|
? LiveManager->AnyPassageNearLattice(
|
||||||
VoxelBox, Ctx.LatticeOriginVoxels, Ctx.Step)
|
VoxelBox, Ctx.LatticeOriginVoxels, Ctx.Step)
|
||||||
: LiveManager->AnyPassageNearBox(VoxelBox.Min, VoxelBox.Max);
|
: LiveManager->AnyPassageNearBox(VoxelBox.Min, VoxelBox.Max));
|
||||||
return bPassageCarve
|
return bPassageCarve
|
||||||
? EVoxelOpEffect::CarveOnly : EVoxelOpEffect::Identity;
|
? EVoxelOpEffect::CarveOnly : EVoxelOpEffect::Identity;
|
||||||
}
|
}
|
||||||
@@ -2212,6 +2243,10 @@ namespace
|
|||||||
float MaxCarveOverBox(const FBox& VoxelBox, const FVoxelOpContext& Ctx,
|
float MaxCarveOverBox(const FBox& VoxelBox, const FVoxelOpContext& Ctx,
|
||||||
const FVoxelBoxHypotheses& H) const override
|
const FVoxelBoxHypotheses& H) const override
|
||||||
{
|
{
|
||||||
|
if (VoxelDensityAblation::IsPassageCarvingOff())
|
||||||
|
{
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
const UVoxelStrateManager* LiveManager = Manager.Get();
|
const UVoxelStrateManager* LiveManager = Manager.Get();
|
||||||
if (!LiveManager) { return 0.0f; }
|
if (!LiveManager) { return 0.0f; }
|
||||||
if (!Ctx.bUseLatticeProof)
|
if (!Ctx.bUseLatticeProof)
|
||||||
@@ -3917,6 +3952,10 @@ namespace
|
|||||||
bool TryGetLastTunnelCoreWorldEvaluation(
|
bool TryGetLastTunnelCoreWorldEvaluation(
|
||||||
FTunnelCoreWorldEvaluation& OutEvaluation) const override
|
FTunnelCoreWorldEvaluation& OutEvaluation) const override
|
||||||
{
|
{
|
||||||
|
if (VoxelDensityAblation::IsTunnelCoreOff())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
FState& S = State();
|
FState& S = State();
|
||||||
if (!S.bLastTunnelCoreWorldEvaluationValid
|
if (!S.bLastTunnelCoreWorldEvaluationValid
|
||||||
|| S.LastTunnelCoreEvaluationPosition != S.LastWorldPosition)
|
|| S.LastTunnelCoreEvaluationPosition != S.LastWorldPosition)
|
||||||
@@ -4078,7 +4117,7 @@ namespace
|
|||||||
// un « frame » : sa portée est exactement UN opérateur, donc elle appartient à cet
|
// un « frame » : sa portée est exactement UN opérateur, donc elle appartient à cet
|
||||||
// opérateur.
|
// opérateur.
|
||||||
float WarpedX = WorldX, WarpedY = WorldY, WarpedZ = EffectiveZ;
|
float WarpedX = WorldX, WarpedY = WorldY, WarpedZ = EffectiveZ;
|
||||||
if (P.CaveWarpStrength > 0.0f)
|
if (!VoxelDensityAblation::IsCaveWarpOff() && P.CaveWarpStrength > 0.0f)
|
||||||
{
|
{
|
||||||
const float WF = P.CaveWarpFrequency;
|
const float WF = P.CaveWarpFrequency;
|
||||||
const float WS = P.CaveWarpStrength;
|
const float WS = P.CaveWarpStrength;
|
||||||
@@ -4308,6 +4347,8 @@ namespace
|
|||||||
// mais à des coordonnées NON warpées. Sous un modèle de frames il aurait fallu les sortir
|
// mais à des coordonnées NON warpées. Sous un modèle de frames il aurait fallu les sortir
|
||||||
// du frame tout en gardant le canal — exprimable, mais tordu. Dans un opérateur unique la
|
// du frame tout en gardant le canal — exprimable, mais tordu. Dans un opérateur unique la
|
||||||
// difficulté disparaît : le warp est une variable locale, pas un contexte hérité.
|
// difficulté disparaît : le warp est une variable locale, pas un contexte hérité.
|
||||||
|
if (!VoxelDensityAblation::IsPitChimneySDFOff())
|
||||||
|
{
|
||||||
auto EvaluatePit = [&](int32 PitIndex)
|
auto EvaluatePit = [&](int32 PitIndex)
|
||||||
{
|
{
|
||||||
const FCachedPit& Pit = GetCache().Pits[PitIndex];
|
const FCachedPit& Pit = GetCache().Pits[PitIndex];
|
||||||
@@ -4336,9 +4377,12 @@ namespace
|
|||||||
|
|
||||||
CaveSDF = VoxelSDF::SmoothMin(CaveSDF, PitSDF, Pit.BlendK);
|
CaveSDF = VoxelSDF::SmoothMin(CaveSDF, PitSDF, Pit.BlendK);
|
||||||
};
|
};
|
||||||
|
if (!VoxelDensityAblation::IsPitChimneySDFOff())
|
||||||
|
{
|
||||||
VF_ForEachChunkSDFSpatialCandidate(
|
VF_ForEachChunkSDFSpatialCandidate(
|
||||||
GetCache().PitSpatialIndex, GetCache().Pits.Num(),
|
GetCache().PitSpatialIndex, GetCache().Pits.Num(),
|
||||||
WorldX, WorldY, EvaluatePit, bUseSpatialIndex);
|
WorldX, WorldY, EvaluatePit, bUseSpatialIndex);
|
||||||
|
}
|
||||||
|
|
||||||
auto EvaluateChimney = [&](int32 ChimneyIndex)
|
auto EvaluateChimney = [&](int32 ChimneyIndex)
|
||||||
{
|
{
|
||||||
@@ -4367,9 +4411,13 @@ namespace
|
|||||||
|
|
||||||
CaveSDF = VoxelSDF::SmoothMin(CaveSDF, ChmSDF, Chim.BlendK);
|
CaveSDF = VoxelSDF::SmoothMin(CaveSDF, ChmSDF, Chim.BlendK);
|
||||||
};
|
};
|
||||||
|
if (!VoxelDensityAblation::IsPitChimneySDFOff())
|
||||||
|
{
|
||||||
VF_ForEachChunkSDFSpatialCandidate(
|
VF_ForEachChunkSDFSpatialCandidate(
|
||||||
GetCache().ChimneySpatialIndex, GetCache().Chimneys.Num(),
|
GetCache().ChimneySpatialIndex, GetCache().Chimneys.Num(),
|
||||||
WorldX, WorldY, EvaluateChimney, bUseSpatialIndex);
|
WorldX, WorldY, EvaluateChimney, bUseSpatialIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
InOut.Sdf = CaveSDF;
|
InOut.Sdf = CaveSDF;
|
||||||
|
|
||||||
@@ -4378,6 +4426,8 @@ namespace
|
|||||||
// build-time ownership now so the stack can preserve the authored support through its
|
// build-time ownership now so the stack can preserve the authored support through its
|
||||||
// remaining modifiers without a second floor-writing pass. TryGet... below reuses the
|
// remaining modifiers without a second floor-writing pass. TryGet... below reuses the
|
||||||
// exact same cached result for the generator's post-disturbance hand-off.
|
// exact same cached result for the generator's post-disturbance hand-off.
|
||||||
|
if (!VoxelDensityAblation::IsTunnelCoreOff())
|
||||||
|
{
|
||||||
const FTunnelSupportFloorColumn* SupportColumn = GetSupportColumn(
|
const FTunnelSupportFloorColumn* SupportColumn = GetSupportColumn(
|
||||||
S.LastWorldPosition);
|
S.LastWorldPosition);
|
||||||
S.LastTunnelCoreWorldEvaluation = VoxelCaveMorphology::EvaluateTunnelCoreWorld(
|
S.LastTunnelCoreWorldEvaluation = VoxelCaveMorphology::EvaluateTunnelCoreWorld(
|
||||||
@@ -4406,14 +4456,18 @@ namespace
|
|||||||
// These are the per-sample hand-offs needed by an op-major block. The scalar path
|
// These are the per-sample hand-offs needed by an op-major block. The scalar path
|
||||||
// continues to use FState directly; publishing them here only makes the dependency
|
// continues to use FState directly; publishing them here only makes the dependency
|
||||||
// explicit for a later modifier and does not alter Density/Sdf.
|
// explicit for a later modifier and does not alter Density/Sdf.
|
||||||
InOut.RoomCache = &GetCache();
|
|
||||||
InOut.NearestRoomIndex = S.NearestRoom;
|
|
||||||
InOut.bHasTunnelCoreWorldEvaluation = true;
|
InOut.bHasTunnelCoreWorldEvaluation = true;
|
||||||
InOut.TunnelCoreWorldSDF = S.LastTunnelCoreWorldEvaluation.SDF;
|
InOut.TunnelCoreWorldSDF = S.LastTunnelCoreWorldEvaluation.SDF;
|
||||||
InOut.bTunnelCoreSupportFloor = S.LastTunnelCoreWorldEvaluation.bSupportFloor;
|
InOut.bTunnelCoreSupportFloor = S.LastTunnelCoreWorldEvaluation.bSupportFloor;
|
||||||
InOut.bTunnelCoreRoomFloor = S.LastTunnelCoreWorldEvaluation.bRoomFloor;
|
InOut.bTunnelCoreRoomFloor = S.LastTunnelCoreWorldEvaluation.bRoomFloor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// These cache hand-offs remain valid when tunnel core is off; detail operators still
|
||||||
|
// need the room graph and nearest-room ownership.
|
||||||
|
InOut.RoomCache = &GetCache();
|
||||||
|
InOut.NearestRoomIndex = S.NearestRoom;
|
||||||
|
}
|
||||||
|
|
||||||
//---------------------------------------------------------------------
|
//---------------------------------------------------------------------
|
||||||
// L'ÉTAT PAR BOÎTE — SÉPARÉ DE `FState`, ET DÉLIBÉRÉMENT
|
// L'ÉTAT PAR BOÎTE — SÉPARÉ DE `FState`, ET DÉLIBÉRÉMENT
|
||||||
//---------------------------------------------------------------------
|
//---------------------------------------------------------------------
|
||||||
@@ -5418,7 +5472,7 @@ namespace
|
|||||||
&& P.VerticalScale != 1.0f)
|
&& P.VerticalScale != 1.0f)
|
||||||
? WorldZ / P.VerticalScale : WorldZ;
|
? WorldZ / P.VerticalScale : WorldZ;
|
||||||
FVector Query(WorldX, WorldY, EffectiveZ);
|
FVector Query(WorldX, WorldY, EffectiveZ);
|
||||||
if (P.CaveWarpStrength > 0.0f)
|
if (!VoxelDensityAblation::IsCaveWarpOff() && P.CaveWarpStrength > 0.0f)
|
||||||
{
|
{
|
||||||
const float Frequency = P.CaveWarpFrequency;
|
const float Frequency = P.CaveWarpFrequency;
|
||||||
Query.X += VoxelNoise::Perlin3D(
|
Query.X += VoxelNoise::Perlin3D(
|
||||||
@@ -8543,9 +8597,6 @@ namespace
|
|||||||
FWormFieldSource(const FStrateGenerationParams& InP, int32 Seed)
|
FWormFieldSource(const FStrateGenerationParams& InP, int32 Seed)
|
||||||
: P(InP)
|
: P(InP)
|
||||||
, SeedU((uint32)Seed)
|
, SeedU((uint32)Seed)
|
||||||
, WormLatticeStep(
|
|
||||||
InP.WormStrength > 0.0f && InP.WormThreshold > 0.0f
|
|
||||||
? VoxelWormField::GetLatticeStep() : 0)
|
|
||||||
{}
|
{}
|
||||||
|
|
||||||
EVoxelOpRole GetRole() const override { return EVoxelOpRole::FieldSource; }
|
EVoxelOpRole GetRole() const override { return EVoxelOpRole::FieldSource; }
|
||||||
@@ -8585,7 +8636,7 @@ namespace
|
|||||||
SeedU};
|
SeedU};
|
||||||
const float WormValue = VoxelWormField::Evaluate(
|
const float WormValue = VoxelWormField::Evaluate(
|
||||||
WorldX, WorldY, WorldZ, P.WormThreshold,
|
WorldX, WorldY, WorldZ, P.WormThreshold,
|
||||||
WormParameters, WormLatticeStep);
|
WormParameters);
|
||||||
if (WormValue < P.WormThreshold)
|
if (WormValue < P.WormThreshold)
|
||||||
{
|
{
|
||||||
const float t = 1.0f - (WormValue / P.WormThreshold);
|
const float t = 1.0f - (WormValue / P.WormThreshold);
|
||||||
@@ -8656,9 +8707,7 @@ namespace
|
|||||||
// explicit count cap keeps this check bounded. The network mask still uses the SDF
|
// explicit count cap keeps this check bounded. The network mask still uses the SDF
|
||||||
// interval's worst case, so this never assumes a room value that the preceding source
|
// interval's worst case, so this never assumes a room value that the preceding source
|
||||||
// did not prove.
|
// did not prove.
|
||||||
if (VoxelWormField::GetNoiseMode() == 0
|
if (Ctx.bUseLatticeProof && Ctx.bTightenWarpProof && Ctx.Step >= 1
|
||||||
&& WormLatticeStep <= 1
|
|
||||||
&& Ctx.bUseLatticeProof && Ctx.bTightenWarpProof && Ctx.Step >= 1
|
|
||||||
&& VoxelMath::IsFinite(P.WormFrequency)
|
&& VoxelMath::IsFinite(P.WormFrequency)
|
||||||
&& VoxelMath::IsFinite(P.WormHorizontalBias)
|
&& VoxelMath::IsFinite(P.WormHorizontalBias)
|
||||||
&& VoxelMath::IsFinite(P.WormThreshold)
|
&& VoxelMath::IsFinite(P.WormThreshold)
|
||||||
@@ -8778,7 +8827,6 @@ namespace
|
|||||||
private:
|
private:
|
||||||
FStrateGenerationParams P;
|
FStrateGenerationParams P;
|
||||||
uint32 SeedU;
|
uint32 SeedU;
|
||||||
int32 WormLatticeStep = 0;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#if WITH_EDITOR
|
#if WITH_EDITOR
|
||||||
@@ -9722,6 +9770,8 @@ namespace VoxelDensityOps
|
|||||||
// `Sdf < SDFBlendRadius·3` via VF_NearCaveSurface. Voir la note de l'étape B5 là-bas.
|
// `Sdf < SDFBlendRadius·3` via VF_NearCaveSurface. Voir la note de l'étape B5 là-bas.
|
||||||
// L'ORDRE EST CELUI DE L'ORIGINAL et il compte : chacun lit la densité que le précédent
|
// L'ORDRE EST CELUI DE L'ORIGINAL et il compte : chacun lit la densité que le précédent
|
||||||
// a laissée (le biais de sol est un ajout indépendant appliqué à cette accumulation).
|
// a laissée (le biais de sol est un ajout indépendant appliqué à cette accumulation).
|
||||||
|
if (!VoxelDensityAblation::IsDetailOpsOff())
|
||||||
|
{
|
||||||
OutStack.Add(MakeUnique<FCaveRoughnessMod>(P, Seed)); // 4b
|
OutStack.Add(MakeUnique<FCaveRoughnessMod>(P, Seed)); // 4b
|
||||||
OutStack.Add(MakeUnique<FCaveTerraceMod>(P, Seed, RoomPtr)); // 4c — terrasses
|
OutStack.Add(MakeUnique<FCaveTerraceMod>(P, Seed, RoomPtr)); // 4c — terrasses
|
||||||
OutStack.Add(MakeUnique<FLayerLineMod>(P, RoomPtr)); // 4c — lignes de strates
|
OutStack.Add(MakeUnique<FLayerLineMod>(P, RoomPtr)); // 4c — lignes de strates
|
||||||
@@ -9734,6 +9784,7 @@ namespace VoxelDensityOps
|
|||||||
OutStack.Add(MakeUnique<FDomeMod>(P, RoomPtr)); // 4g — dômes
|
OutStack.Add(MakeUnique<FDomeMod>(P, RoomPtr)); // 4g — dômes
|
||||||
OutStack.Add(MakeUnique<FPinchMod>(P, RoomPtr)); // 4h — pincement
|
OutStack.Add(MakeUnique<FPinchMod>(P, RoomPtr)); // 4h — pincement
|
||||||
OutStack.Add(MakeUnique<FFloorBiasMod>(P, RoomPtr)); // fin 4h — biais de sol
|
OutStack.Add(MakeUnique<FFloorBiasMod>(P, RoomPtr)); // fin 4h — biais de sol
|
||||||
|
}
|
||||||
if (P.WormStrength > 0.0f && P.WormThreshold > 0.0f)
|
if (P.WormStrength > 0.0f && P.WormThreshold > 0.0f)
|
||||||
{
|
{
|
||||||
OutStack.Add(MakeUnique<FWormFieldSource>(P, Seed));
|
OutStack.Add(MakeUnique<FWormFieldSource>(P, Seed));
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
#include "VoxelBiomeDefinition.h"
|
#include "VoxelBiomeDefinition.h"
|
||||||
#include "VoxelNoise.h" // T2.a: float, SIMD-batched gradient-noise core
|
#include "VoxelNoise.h" // T2.a: float, SIMD-batched gradient-noise core
|
||||||
#include "VoxelWormField.h"
|
#include "VoxelWormField.h"
|
||||||
|
#include "VoxelDensityAblation.h" // fingerprinted development-only stage measurements
|
||||||
#include "VoxelDensityPrimitives.h" // spine / seals / passage — shared with the operator stack
|
#include "VoxelDensityPrimitives.h" // spine / seals / passage — shared with the operator stack
|
||||||
#include "VoxelDensityOpStack.h" // OPSTACK Phase 1: the opt-in per-strate operator stack
|
#include "VoxelDensityOpStack.h" // OPSTACK Phase 1: the opt-in per-strate operator stack
|
||||||
#include "VoxelHeightOp.h" // IVoxelBiomeField — the adapter below implements it
|
#include "VoxelHeightOp.h" // IVoxelBiomeField — the adapter below implements it
|
||||||
@@ -985,6 +986,10 @@ static void ApplyDisturbances(float& MC, float X, float Y, float Z,
|
|||||||
const FStrateDisturbanceParams& D, uint32 Seed, bool bProtectVerticalShaftAir,
|
const FStrateDisturbanceParams& D, uint32 Seed, bool bProtectVerticalShaftAir,
|
||||||
bool bProtectAuthoredTunnelFloor)
|
bool bProtectAuthoredTunnelFloor)
|
||||||
{
|
{
|
||||||
|
if (VoxelDensityAblation::IsDisturbancesOff())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
VoxelDensityProfile::FScopedTimer ProfileTimer(
|
VoxelDensityProfile::FScopedTimer ProfileTimer(
|
||||||
VoxelDensityProfile::EBucket::ApplyDisturbances);
|
VoxelDensityProfile::EBucket::ApplyDisturbances);
|
||||||
const float InnerTop = D.StrateTopWorldZ - D.BoundarySealThickness;
|
const float InnerTop = D.StrateTopWorldZ - D.BoundarySealThickness;
|
||||||
@@ -2592,7 +2597,7 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co
|
|||||||
// Le seul point d'entrée de la pile dans le chemin de production. Elle rend la convention
|
// Le seul point d'entrée de la pile dans le chemin de production. Elle rend la convention
|
||||||
// MC (négatif = solide) comme les fonctions d'archétype, donc les disturbances et la couche
|
// MC (négatif = solide) comme les fonctions d'archétype, donc les disturbances et la couche
|
||||||
// de diff qui suivent ne voient aucune différence.
|
// de diff qui suivent ne voient aucune différence.
|
||||||
if (CP_UseOpStack)
|
if (!VoxelDensityAblation::IsTunnelCoreOff() && CP_UseOpStack)
|
||||||
{
|
{
|
||||||
const bool bCanUseFusedEvaluator =
|
const bool bCanUseFusedEvaluator =
|
||||||
GVoxelForgeUseFusedEvaluator != 0
|
GVoxelForgeUseFusedEvaluator != 0
|
||||||
@@ -2751,7 +2756,8 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co
|
|||||||
PreDisturbanceTunnelCore);
|
PreDisturbanceTunnelCore);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!bHavePreDisturbanceTunnelCore && ActiveTunnelCoreCache->bValid)
|
if (!VoxelDensityAblation::IsTunnelCoreOff()
|
||||||
|
&& !bHavePreDisturbanceTunnelCore && ActiveTunnelCoreCache->bValid)
|
||||||
{
|
{
|
||||||
const FTunnelSupportFloorColumn* SupportColumn = nullptr;
|
const FTunnelSupportFloorColumn* SupportColumn = nullptr;
|
||||||
FTunnelSupportFloorColumn EmptySupportColumn;
|
FTunnelSupportFloorColumn EmptySupportColumn;
|
||||||
@@ -2891,7 +2897,8 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co
|
|||||||
// bridge over a graph tunnel. Reassert the native cached tunnel core here, after every
|
// bridge over a graph tunnel. Reassert the native cached tunnel core here, after every
|
||||||
// solid floor writer but before the global XY seal. This cache is built once per chunk,
|
// solid floor writer but before the global XY seal. This cache is built once per chunk,
|
||||||
// never once per voxel.
|
// never once per voxel.
|
||||||
if (ActiveTunnelCoreCache->bValid || CP_UseOpStack)
|
if (!VoxelDensityAblation::IsTunnelCoreOff()
|
||||||
|
&& (ActiveTunnelCoreCache->bValid || CP_UseOpStack))
|
||||||
{
|
{
|
||||||
FTunnelCoreWorldEvaluation TunnelCore;
|
FTunnelCoreWorldEvaluation TunnelCore;
|
||||||
bool bHaveTunnelCore = bHavePreDisturbanceTunnelCore;
|
bool bHaveTunnelCore = bHavePreDisturbanceTunnelCore;
|
||||||
@@ -3181,7 +3188,7 @@ float UVoxelGenerator::GetDensityWithParams(float WorldX, float WorldY, float Wo
|
|||||||
float WarpedY = WorldY;
|
float WarpedY = WorldY;
|
||||||
float WarpedZ = EffectiveZ;
|
float WarpedZ = EffectiveZ;
|
||||||
|
|
||||||
if (Params.CaveWarpStrength > 0.0f)
|
if (!VoxelDensityAblation::IsCaveWarpOff() && Params.CaveWarpStrength > 0.0f)
|
||||||
{
|
{
|
||||||
const float WF = Params.CaveWarpFrequency;
|
const float WF = Params.CaveWarpFrequency;
|
||||||
const float WS = Params.CaveWarpStrength;
|
const float WS = Params.CaveWarpStrength;
|
||||||
@@ -3472,6 +3479,8 @@ float UVoxelGenerator::GetDensityWithParams(float WorldX, float WorldY, float Wo
|
|||||||
// transition as tunnel-to-room (no hard seam at PitTopZ)
|
// transition as tunnel-to-room (no hard seam at PitTopZ)
|
||||||
// - bNearCaveSurface becomes true inside the shaft — roughness clamp
|
// - bNearCaveSurface becomes true inside the shaft — roughness clamp
|
||||||
// prevents fill-back, so this is safe
|
// prevents fill-back, so this is safe
|
||||||
|
if (!VoxelDensityAblation::IsPitChimneySDFOff())
|
||||||
|
{
|
||||||
auto EvaluatePit = [&](int32 PitIndex)
|
auto EvaluatePit = [&](int32 PitIndex)
|
||||||
{
|
{
|
||||||
const FCachedPit& Pit = SDFCache.Pits[PitIndex];
|
const FCachedPit& Pit = SDFCache.Pits[PitIndex];
|
||||||
@@ -3540,6 +3549,7 @@ float UVoxelGenerator::GetDensityWithParams(float WorldX, float WorldY, float Wo
|
|||||||
VF_ForEachChunkSDFSpatialCandidate(
|
VF_ForEachChunkSDFSpatialCandidate(
|
||||||
SDFCache.ChimneySpatialIndex, SDFCache.Chimneys.Num(),
|
SDFCache.ChimneySpatialIndex, SDFCache.Chimneys.Num(),
|
||||||
WorldX, WorldY, EvaluateChimney, bUseSpatialIndex);
|
WorldX, WorldY, EvaluateChimney, bUseSpatialIndex);
|
||||||
|
}
|
||||||
|
|
||||||
// Convert SDF to density carving:
|
// Convert SDF to density carving:
|
||||||
// CaveSDF < 0 means we're inside a room/tunnel/pit → carve to air
|
// CaveSDF < 0 means we're inside a room/tunnel/pit → carve to air
|
||||||
@@ -3563,6 +3573,7 @@ float UVoxelGenerator::GetDensityWithParams(float WorldX, float WorldY, float Wo
|
|||||||
// post-disturbance tail can consume the result without allocating an FVoxelOpSample or
|
// post-disturbance tail can consume the result without allocating an FVoxelOpSample or
|
||||||
// invoking the interpreted stack a second time for this sample.
|
// invoking the interpreted stack a second time for this sample.
|
||||||
if (OutTunnelCore != nullptr
|
if (OutTunnelCore != nullptr
|
||||||
|
&& !VoxelDensityAblation::IsTunnelCoreOff()
|
||||||
&& Params.RoomDensity > 0.0f && Params.RoomSpacing > 0.0f)
|
&& Params.RoomDensity > 0.0f && Params.RoomSpacing > 0.0f)
|
||||||
{
|
{
|
||||||
// The no-column form is the exact scalar reference path: it performs the world-bound and
|
// The no-column form is the exact scalar reference path: it performs the world-bound and
|
||||||
@@ -3585,10 +3596,11 @@ float UVoxelGenerator::GetDensityWithParams(float WorldX, float WorldY, float Wo
|
|||||||
// seal / modifiers, so we jump to Step 5 instead of returning early.
|
// seal / modifiers, so we jump to Step 5 instead of returning early.
|
||||||
const float DetailThreshold = Params.SDFBlendRadius * 3.0f;
|
const float DetailThreshold = Params.SDFBlendRadius * 3.0f;
|
||||||
const bool bNearCaveSurface = (CaveSDF < DetailThreshold) && (CaveSDF < FLT_MAX);
|
const bool bNearCaveSurface = (CaveSDF < DetailThreshold) && (CaveSDF < FLT_MAX);
|
||||||
|
const bool bRunDetail = !VoxelDensityAblation::IsDetailOpsOff() && bNearCaveSurface;
|
||||||
if (bCollectFusedDiagnostics && VoxelDensityProfile::AreCountersEnabled())
|
if (bCollectFusedDiagnostics && VoxelDensityProfile::AreCountersEnabled())
|
||||||
{
|
{
|
||||||
VoxelDensityProfile::AddCounter(
|
VoxelDensityProfile::AddCounter(
|
||||||
bNearCaveSurface
|
bRunDetail
|
||||||
? VoxelDensityProfile::ECounter::FusedTunnelDetailSamples
|
? VoxelDensityProfile::ECounter::FusedTunnelDetailSamples
|
||||||
: VoxelDensityProfile::ECounter::FusedTunnelDetailSkipped);
|
: VoxelDensityProfile::ECounter::FusedTunnelDetailSkipped);
|
||||||
}
|
}
|
||||||
@@ -3606,7 +3618,7 @@ float UVoxelGenerator::GetDensityWithParams(float WorldX, float WorldY, float Wo
|
|||||||
// The noise type (fBM, Ridged, Mixed) and optional domain warping are
|
// The noise type (fBM, Ridged, Mixed) and optional domain warping are
|
||||||
// per-strate settings, so each strate can have fundamentally different
|
// per-strate settings, so each strate can have fundamentally different
|
||||||
// wall character — smooth lava tubes vs craggy erosion cliffs.
|
// wall character — smooth lava tubes vs craggy erosion cliffs.
|
||||||
if (bNearCaveSurface && Params.SurfaceRoughness > 0.0f)
|
if (bRunDetail && Params.SurfaceRoughness > 0.0f)
|
||||||
{
|
{
|
||||||
float RoughnessDepth = Params.SurfaceRoughness * 2.0f;
|
float RoughnessDepth = Params.SurfaceRoughness * 2.0f;
|
||||||
float DistFromSurface = FMath::Abs(CaveSDF);
|
float DistFromSurface = FMath::Abs(CaveSDF);
|
||||||
@@ -3729,7 +3741,7 @@ float UVoxelGenerator::GetDensityWithParams(float WorldX, float WorldY, float Wo
|
|||||||
//=========================================================================
|
//=========================================================================
|
||||||
// All terrain ops only affect density near cave walls.
|
// All terrain ops only affect density near cave walls.
|
||||||
// Deep-solid voxels skip this entire block (bNearCaveSurface = false).
|
// Deep-solid voxels skip this entire block (bNearCaveSurface = false).
|
||||||
if (bNearCaveSurface)
|
if (bRunDetail)
|
||||||
{
|
{
|
||||||
//=========================================================================
|
//=========================================================================
|
||||||
// PER-ROOM TERRAIN PARAMS
|
// PER-ROOM TERRAIN PARAMS
|
||||||
@@ -4276,10 +4288,7 @@ float UVoxelGenerator::GetDensityWithParams(float WorldX, float WorldY, float Wo
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool bWormBlockProofComputed = false;
|
bool bWormBlockProofComputed = false;
|
||||||
const int32 WormLatticeStep = VoxelWormField::GetLatticeStep();
|
const bool bSkipWormBlock = GVoxelForgeWormBlockSkip != 0
|
||||||
const bool bSkipWormBlock = VoxelWormField::GetNoiseMode() == 0
|
|
||||||
&& WormLatticeStep <= 1
|
|
||||||
&& GVoxelForgeWormBlockSkip != 0
|
|
||||||
&& VF_TryGetWormBlockSkip(
|
&& VF_TryGetWormBlockSkip(
|
||||||
DensityCacheOwnerId, SeedU, ParamsFingerprint, LayoutVersion,
|
DensityCacheOwnerId, SeedU, ParamsFingerprint, LayoutVersion,
|
||||||
WorldX, WorldY, WorldZ, Params, bWormBlockProofComputed);
|
WorldX, WorldY, WorldZ, Params, bWormBlockProofComputed);
|
||||||
@@ -4305,7 +4314,7 @@ float UVoxelGenerator::GetDensityWithParams(float WorldX, float WorldY, float Wo
|
|||||||
SeedU};
|
SeedU};
|
||||||
const float WormValue = VoxelWormField::Evaluate(
|
const float WormValue = VoxelWormField::Evaluate(
|
||||||
WorldX, WorldY, WorldZ, Params.WormThreshold,
|
WorldX, WorldY, WorldZ, Params.WormThreshold,
|
||||||
WormParameters, WormLatticeStep);
|
WormParameters);
|
||||||
|
|
||||||
if (WormValue < Params.WormThreshold)
|
if (WormValue < Params.WormThreshold)
|
||||||
{
|
{
|
||||||
@@ -4370,7 +4379,9 @@ float UVoxelGenerator::GetDensityWithParams(float WorldX, float WorldY, float Wo
|
|||||||
// the graph's own tunnel floor/core are structural route geometry. Direct callers of
|
// the graph's own tunnel floor/core are structural route geometry. Direct callers of
|
||||||
// GetDensityWithParams retain this historical tail; GetDensityAt skips it and applies the
|
// GetDensityWithParams retain this historical tail; GetDensityAt skips it and applies the
|
||||||
// same predicates once in its common MC-space post section below both generation paths.
|
// same predicates once in its common MC-space post section below both generation paths.
|
||||||
const bool bTunnelSupportFloor = VoxelCaveMorphology::IsTunnelSupportFloorWorldPoint(
|
const bool bTunnelCoreEnabled = !VoxelDensityAblation::IsTunnelCoreOff();
|
||||||
|
const bool bTunnelSupportFloor = bTunnelCoreEnabled
|
||||||
|
&& VoxelCaveMorphology::IsTunnelSupportFloorWorldPoint(
|
||||||
WorldX, WorldY, WorldZ, SDFCache, bUseSpatialIndex);
|
WorldX, WorldY, WorldZ, SDFCache, bUseSpatialIndex);
|
||||||
// Keep the direct legacy entry point capability-safe for callers outside the canonical
|
// Keep the direct legacy entry point capability-safe for callers outside the canonical
|
||||||
// operator-stack path; its support band follows the same relieved swept floor.
|
// operator-stack path; its support band follows the same relieved swept floor.
|
||||||
@@ -4388,7 +4399,8 @@ float UVoxelGenerator::GetDensityWithParams(float WorldX, float WorldY, float Wo
|
|||||||
}
|
}
|
||||||
Density = FMath::Max(Density, StructuralSolidDensity);
|
Density = FMath::Max(Density, StructuralSolidDensity);
|
||||||
}
|
}
|
||||||
const float TunnelCoreSDF = (Params.RoomDensity > 0.0f && Params.RoomSpacing > 0.0f)
|
const float TunnelCoreSDF = bTunnelCoreEnabled
|
||||||
|
&& (Params.RoomDensity > 0.0f && Params.RoomSpacing > 0.0f)
|
||||||
? VoxelCaveMorphology::EvaluateTunnelCoreWorldSDF(
|
? VoxelCaveMorphology::EvaluateTunnelCoreWorldSDF(
|
||||||
WorldX, WorldY, WorldZ, SDFCache, bUseSpatialIndex)
|
WorldX, WorldY, WorldZ, SDFCache, bUseSpatialIndex)
|
||||||
: FLT_MAX;
|
: FLT_MAX;
|
||||||
|
|||||||
@@ -8,11 +8,11 @@
|
|||||||
#include "VoxelTypes.h" // For CHUNK_SIZE, VOXEL_SIZE, WorldToChunkCoord
|
#include "VoxelTypes.h" // For CHUNK_SIZE, VOXEL_SIZE, WorldToChunkCoord
|
||||||
#include "VoxelCaveMorphology.h" // For VoxelSDF and VoxelHash
|
#include "VoxelCaveMorphology.h" // For VoxelSDF and VoxelHash
|
||||||
#include "VoxelDensityPrimitives.h" // Shared passage carve polarity/strength
|
#include "VoxelDensityPrimitives.h" // Shared passage carve polarity/strength
|
||||||
|
#include "VoxelDensityAblation.h" // Fingerprinted development-only stage overrides
|
||||||
#include "VoxelDensityProfile.h" // Opt-in targeted per-voxel attribution
|
#include "VoxelDensityProfile.h" // Opt-in targeted per-voxel attribution
|
||||||
#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 "VoxelWormField.h" // World-changing worm evaluator mode/lattice identity
|
|
||||||
#include "HAL/IConsoleManager.h"
|
#include "HAL/IConsoleManager.h"
|
||||||
#include "Misc/CommandLine.h"
|
#include "Misc/CommandLine.h"
|
||||||
#include "Misc/Crc.h"
|
#include "Misc/Crc.h"
|
||||||
@@ -2487,6 +2487,11 @@ void UVoxelStrateManager::GeneratePassages()
|
|||||||
|
|
||||||
float UVoxelStrateManager::EvaluateModifierSDF(float WorldX, float WorldY, float WorldZ) const
|
float UVoxelStrateManager::EvaluateModifierSDF(float WorldX, float WorldY, float WorldZ) const
|
||||||
{
|
{
|
||||||
|
if (VoxelDensityAblation::IsPassageCarvingOff())
|
||||||
|
{
|
||||||
|
return FLT_MAX;
|
||||||
|
}
|
||||||
|
|
||||||
const FIntVector ChunkCoord(
|
const FIntVector ChunkCoord(
|
||||||
FMath::FloorToInt(WorldX / (float)CHUNK_SIZE),
|
FMath::FloorToInt(WorldX / (float)CHUNK_SIZE),
|
||||||
FMath::FloorToInt(WorldY / (float)CHUNK_SIZE),
|
FMath::FloorToInt(WorldY / (float)CHUNK_SIZE),
|
||||||
@@ -2552,7 +2557,7 @@ float UVoxelStrateManager::EvaluateModifierSDF(float WorldX, float WorldY, float
|
|||||||
// projecting once here selects the nearest immutable floor segment for this sample.
|
// projecting once here selects the nearest immutable floor segment for this sample.
|
||||||
// Unknown/malformed descriptors deliberately retain the bare capsule and the legacy
|
// Unknown/malformed descriptors deliberately retain the bare capsule and the legacy
|
||||||
// diagnostic backstop remains available.
|
// diagnostic backstop remains available.
|
||||||
if (P.bNativeFloorEnabled)
|
if (P.bNativeFloorEnabled && !VoxelDensityAblation::IsNativeFloorOff())
|
||||||
{
|
{
|
||||||
float NativeFloorZ = 0.0f;
|
float NativeFloorZ = 0.0f;
|
||||||
float NativeSupportRadius = 0.0f;
|
float NativeSupportRadius = 0.0f;
|
||||||
@@ -2593,8 +2598,11 @@ void UVoxelStrateManager::ApplyPassageModifier(
|
|||||||
{
|
{
|
||||||
VoxelDensityProfile::FScopedTimer ProfileTimer(
|
VoxelDensityProfile::FScopedTimer ProfileTimer(
|
||||||
VoxelDensityProfile::EBucket::PassageModifier);
|
VoxelDensityProfile::EBucket::PassageModifier);
|
||||||
|
if (!VoxelDensityAblation::IsPassageCarvingOff())
|
||||||
|
{
|
||||||
const float ModSDF = EvaluateModifierSDF(WorldX, WorldY, WorldZ);
|
const float ModSDF = EvaluateModifierSDF(WorldX, WorldY, WorldZ);
|
||||||
VF_ApplyPassageCarving(Density, ModSDF, BaseDensity, SealThickness);
|
VF_ApplyPassageCarving(Density, ModSDF, BaseDensity, SealThickness);
|
||||||
|
}
|
||||||
ApplyPassageLandingAir(Density, WorldX, WorldY, WorldZ, BaseDensity, SealThickness);
|
ApplyPassageLandingAir(Density, WorldX, WorldY, WorldZ, BaseDensity, SealThickness);
|
||||||
|
|
||||||
const FIntVector ChunkCoord(
|
const FIntVector ChunkCoord(
|
||||||
@@ -2607,15 +2615,15 @@ void UVoxelStrateManager::ApplyPassageModifier(
|
|||||||
for (const int32 PassageIndex : Nearby)
|
for (const int32 PassageIndex : Nearby)
|
||||||
{
|
{
|
||||||
const FVoxelPassage& Passage = PassageData[PassageIndex];
|
const FVoxelPassage& Passage = PassageData[PassageIndex];
|
||||||
if (!VoxelPassageGeometry::VerticalShaftConnectorAirMarker()
|
const bool bLandingFloor = !VoxelDensityAblation::IsLandingPostsOff()
|
||||||
&& (VF_IsPassageLandingFloor(Position, Passage.UpperLanding)
|
&& (VF_IsPassageLandingFloor(Position, Passage.UpperLanding)
|
||||||
|| VF_IsPassageLandingFloor(Position, Passage.LowerLanding)
|
|| VF_IsPassageLandingFloor(Position, Passage.LowerLanding));
|
||||||
// A valid native profile owns its D-floor in the shape and in the final MC
|
const bool bLegacySupportFloor = !VoxelDensityAblation::IsPassageStructuralPostsOff()
|
||||||
// composition. Keep the old support slab only as a fail-closed fallback for a
|
&& bPassageSupportFloorWritesEnabled
|
||||||
// malformed/non-native descriptor; it must not patch a floor the shape already owns.
|
|
||||||
|| (bPassageSupportFloorWritesEnabled
|
|
||||||
&& !Passage.bNativeFloorEnabled
|
&& !Passage.bNativeFloorEnabled
|
||||||
&& VF_IsWalkableTunnelFloor(Passage, Position, this, PassageIndex))))
|
&& VF_IsWalkableTunnelFloor(Passage, Position, this, PassageIndex);
|
||||||
|
if (!VoxelPassageGeometry::VerticalShaftConnectorAirMarker()
|
||||||
|
&& (bLandingFloor || bLegacySupportFloor))
|
||||||
{
|
{
|
||||||
// This is the one bidirectional part of PassageCarveOp: a floor is a proved solid
|
// This is the one bidirectional part of PassageCarveOp: a floor is a proved solid
|
||||||
// support slab. It is deliberately applied after the air carve so a tube can never
|
// support slab. It is deliberately applied after the air carve so a tube can never
|
||||||
@@ -2636,6 +2644,10 @@ void UVoxelStrateManager::ApplyPassageCarvingOnly(
|
|||||||
float& Density, float WorldX, float WorldY, float WorldZ,
|
float& Density, float WorldX, float WorldY, float WorldZ,
|
||||||
float BaseDensity, float SealThickness) const
|
float BaseDensity, float SealThickness) const
|
||||||
{
|
{
|
||||||
|
if (VoxelDensityAblation::IsPassageCarvingOff())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
VoxelDensityProfile::FScopedTimer ProfileTimer(
|
VoxelDensityProfile::FScopedTimer ProfileTimer(
|
||||||
VoxelDensityProfile::EBucket::PassageModifier);
|
VoxelDensityProfile::EBucket::PassageModifier);
|
||||||
const float ModSDF = EvaluateModifierSDF(WorldX, WorldY, WorldZ);
|
const float ModSDF = EvaluateModifierSDF(WorldX, WorldY, WorldZ);
|
||||||
@@ -2646,6 +2658,10 @@ void UVoxelStrateManager::ApplyPassageLandingAir(
|
|||||||
float& Density, float WorldX, float WorldY, float WorldZ,
|
float& Density, float WorldX, float WorldY, float WorldZ,
|
||||||
float BaseDensity, float SealThickness) const
|
float BaseDensity, float SealThickness) const
|
||||||
{
|
{
|
||||||
|
if (VoxelDensityAblation::IsLandingPostsOff())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
VoxelDensityProfile::FScopedTimer ProfileTimer(
|
VoxelDensityProfile::FScopedTimer ProfileTimer(
|
||||||
VoxelDensityProfile::EBucket::PassageLandingAir);
|
VoxelDensityProfile::EBucket::PassageLandingAir);
|
||||||
const FIntVector ChunkCoord(
|
const FIntVector ChunkCoord(
|
||||||
@@ -2710,6 +2726,10 @@ void UVoxelStrateManager::ApplyPassageTunnelAir(
|
|||||||
float& Density, float WorldX, float WorldY, float WorldZ,
|
float& Density, float WorldX, float WorldY, float WorldZ,
|
||||||
float BaseDensity, float SealThickness) const
|
float BaseDensity, float SealThickness) const
|
||||||
{
|
{
|
||||||
|
if (VoxelDensityAblation::IsPassageStructuralPostsOff())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
VoxelDensityProfile::FScopedTimer ProfileTimer(
|
VoxelDensityProfile::FScopedTimer ProfileTimer(
|
||||||
VoxelDensityProfile::EBucket::PassageTunnelAir);
|
VoxelDensityProfile::EBucket::PassageTunnelAir);
|
||||||
const FVector Position(WorldX, WorldY, WorldZ);
|
const FVector Position(WorldX, WorldY, WorldZ);
|
||||||
@@ -2740,6 +2760,11 @@ void UVoxelStrateManager::ApplyPassageTunnelAirMC(
|
|||||||
void UVoxelStrateManager::ApplyPassageLandingFloorMC(
|
void UVoxelStrateManager::ApplyPassageLandingFloorMC(
|
||||||
float& Density, float WorldX, float WorldY, float WorldZ, float BaseDensity) const
|
float& Density, float WorldX, float WorldY, float WorldZ, float BaseDensity) const
|
||||||
{
|
{
|
||||||
|
if (VoxelDensityAblation::IsLandingPostsOff()
|
||||||
|
&& VoxelDensityAblation::IsPassageStructuralPostsOff())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
VoxelDensityProfile::FScopedTimer ProfileTimer(
|
VoxelDensityProfile::FScopedTimer ProfileTimer(
|
||||||
VoxelDensityProfile::EBucket::PassageLandingFloor);
|
VoxelDensityProfile::EBucket::PassageLandingFloor);
|
||||||
const FIntVector ChunkCoord(
|
const FIntVector ChunkCoord(
|
||||||
@@ -2752,12 +2777,15 @@ void UVoxelStrateManager::ApplyPassageLandingFloorMC(
|
|||||||
for (const int32 PassageIndex : Nearby)
|
for (const int32 PassageIndex : Nearby)
|
||||||
{
|
{
|
||||||
const FVoxelPassage& Passage = PassageData[PassageIndex];
|
const FVoxelPassage& Passage = PassageData[PassageIndex];
|
||||||
if (!VoxelPassageGeometry::VerticalShaftConnectorAirMarker()
|
const bool bLandingFloor = !VoxelDensityAblation::IsLandingPostsOff()
|
||||||
&& (VF_IsPassageLandingFloor(Position, Passage.UpperLanding)
|
&& (VF_IsPassageLandingFloor(Position, Passage.UpperLanding)
|
||||||
|| VF_IsPassageLandingFloor(Position, Passage.LowerLanding)
|
|| VF_IsPassageLandingFloor(Position, Passage.LowerLanding));
|
||||||
|| (bPassageSupportFloorWritesEnabled
|
const bool bLegacySupportFloor = !VoxelDensityAblation::IsPassageStructuralPostsOff()
|
||||||
|
&& bPassageSupportFloorWritesEnabled
|
||||||
&& !Passage.bNativeFloorEnabled
|
&& !Passage.bNativeFloorEnabled
|
||||||
&& VF_IsWalkableTunnelFloor(Passage, Position, this, PassageIndex))))
|
&& VF_IsWalkableTunnelFloor(Passage, Position, this, PassageIndex);
|
||||||
|
if (!VoxelPassageGeometry::VerticalShaftConnectorAirMarker()
|
||||||
|
&& (bLandingFloor || bLegacySupportFloor))
|
||||||
{
|
{
|
||||||
// Result is in MC convention here (negative = solid). This reassertion is the
|
// Result is in MC convention here (negative = solid). This reassertion is the
|
||||||
// structural floor backstop after the optional MC-space disturbance layer.
|
// structural floor backstop after the optional MC-space disturbance layer.
|
||||||
@@ -2790,6 +2818,13 @@ void UVoxelStrateManager::ApplyPassageStructuralPostsMC(
|
|||||||
float BaseDensity, float SealThickness,
|
float BaseDensity, float SealThickness,
|
||||||
bool bProtectAuthoredTunnelFloor) const
|
bool bProtectAuthoredTunnelFloor) const
|
||||||
{
|
{
|
||||||
|
const bool bDisableStructuralPosts = VoxelDensityAblation::IsPassageStructuralPostsOff();
|
||||||
|
const bool bDisableLandingPosts = VoxelDensityAblation::IsLandingPostsOff();
|
||||||
|
if (bDisableStructuralPosts && bDisableLandingPosts)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
VoxelDensityProfile::FScopedTimer ProfileTimer(
|
VoxelDensityProfile::FScopedTimer ProfileTimer(
|
||||||
VoxelDensityProfile::EBucket::PassageStructuralPosts);
|
VoxelDensityProfile::EBucket::PassageStructuralPosts);
|
||||||
|
|
||||||
@@ -2804,7 +2839,7 @@ void UVoxelStrateManager::ApplyPassageStructuralPostsMC(
|
|||||||
const FVector Position(WorldX, WorldY, WorldZ);
|
const FVector Position(WorldX, WorldY, WorldZ);
|
||||||
const bool bSuppressFloor =
|
const bool bSuppressFloor =
|
||||||
VoxelPassageGeometry::VerticalShaftConnectorAirMarker();
|
VoxelPassageGeometry::VerticalShaftConnectorAirMarker();
|
||||||
bool bAnyPassageFloor = false;
|
bool bAnyLandingFloor = false;
|
||||||
bool bLegacyTunnelSupportFloor = false;
|
bool bLegacyTunnelSupportFloor = false;
|
||||||
bool bAnyRoomFloor = false;
|
bool bAnyRoomFloor = false;
|
||||||
bool bWalkableAir = false;
|
bool bWalkableAir = false;
|
||||||
@@ -2819,36 +2854,37 @@ void UVoxelStrateManager::ApplyPassageStructuralPostsMC(
|
|||||||
const FVoxelPassage& Passage = PassageData[PassageIndex];
|
const FVoxelPassage& Passage = PassageData[PassageIndex];
|
||||||
|
|
||||||
const bool bTunnelSupportFloor =
|
const bool bTunnelSupportFloor =
|
||||||
bPassageSupportFloorWritesEnabled
|
!bDisableStructuralPosts
|
||||||
|
&& bPassageSupportFloorWritesEnabled
|
||||||
&& !Passage.bNativeFloorEnabled
|
&& !Passage.bNativeFloorEnabled
|
||||||
&& VF_IsWalkableTunnelFloor(Passage, Position, this, PassageIndex);
|
&& VF_IsWalkableTunnelFloor(Passage, Position, this, PassageIndex);
|
||||||
if (!bSuppressFloor
|
if (!bSuppressFloor && !bDisableLandingPosts
|
||||||
&& (VF_IsPassageLandingFloor(Position, Passage.UpperLanding)
|
&& (VF_IsPassageLandingFloor(Position, Passage.UpperLanding)
|
||||||
|| VF_IsPassageLandingFloor(Position, Passage.LowerLanding)
|
|| VF_IsPassageLandingFloor(Position, Passage.LowerLanding)))
|
||||||
|| bTunnelSupportFloor))
|
|
||||||
{
|
{
|
||||||
bAnyPassageFloor = true;
|
bAnyLandingFloor = true;
|
||||||
}
|
}
|
||||||
if (!bSuppressFloor && bTunnelSupportFloor)
|
if (!bSuppressFloor && !bDisableStructuralPosts && bTunnelSupportFloor)
|
||||||
{
|
{
|
||||||
bLegacyTunnelSupportFloor = true;
|
bLegacyTunnelSupportFloor = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!bSuppressFloor
|
if (!bSuppressFloor && !bDisableLandingPosts
|
||||||
&& (VF_IsPassageRoomFloor(Position, Passage.UpperLanding)
|
&& (VF_IsPassageRoomFloor(Position, Passage.UpperLanding)
|
||||||
|| VF_IsPassageRoomFloor(Position, Passage.LowerLanding)))
|
|| VF_IsPassageRoomFloor(Position, Passage.LowerLanding)))
|
||||||
{
|
{
|
||||||
bAnyRoomFloor = true;
|
bAnyRoomFloor = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!bWalkableAir
|
if (!bDisableStructuralPosts && !bWalkableAir
|
||||||
&& FVector::DistSquared(Position, Passage.BoundCenter) <= Passage.BoundRadiusSq
|
&& FVector::DistSquared(Position, Passage.BoundCenter) <= Passage.BoundRadiusSq
|
||||||
&& VF_IsWalkableTunnelAir(Passage, Position, this, PassageIndex))
|
&& VF_IsWalkableTunnelAir(Passage, Position, this, PassageIndex))
|
||||||
{
|
{
|
||||||
bWalkableAir = true;
|
bWalkableAir = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (FVector::DistSquared(Position, Passage.BoundCenter) <= Passage.BoundRadiusSq)
|
if (!bDisableLandingPosts
|
||||||
|
&& FVector::DistSquared(Position, Passage.BoundCenter) <= Passage.BoundRadiusSq)
|
||||||
{
|
{
|
||||||
const FVoxelPassageLanding* Landings[] = {
|
const FVoxelPassageLanding* Landings[] = {
|
||||||
&Passage.UpperLanding, &Passage.LowerLanding };
|
&Passage.UpperLanding, &Passage.LowerLanding };
|
||||||
@@ -2865,8 +2901,8 @@ void UVoxelStrateManager::ApplyPassageStructuralPostsMC(
|
|||||||
// budget. A neighbouring inter-strate landing must not reopen that finite support band; its
|
// budget. A neighbouring inter-strate landing must not reopen that finite support band; its
|
||||||
// carve remains valid everywhere else. Legacy callers leave this false and retain the old
|
// carve remains valid everywhere else. Legacy callers leave this false and retain the old
|
||||||
// passage-owned behavior.
|
// passage-owned behavior.
|
||||||
if (!bProtectAuthoredTunnelFloor
|
if (!bDisableLandingPosts && !bProtectAuthoredTunnelFloor
|
||||||
&& !bAnyPassageFloor && MinLandingSDF < FLT_MAX)
|
&& !bAnyLandingFloor && !bLegacyTunnelSupportFloor && MinLandingSDF < FLT_MAX)
|
||||||
{
|
{
|
||||||
float InternalDensity = -Density;
|
float InternalDensity = -Density;
|
||||||
VF_ApplyPassageLandingCarving(
|
VF_ApplyPassageLandingCarving(
|
||||||
@@ -2874,7 +2910,7 @@ void UVoxelStrateManager::ApplyPassageStructuralPostsMC(
|
|||||||
Density = -InternalDensity;
|
Density = -InternalDensity;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bAnyPassageFloor)
|
if (bAnyLandingFloor || bLegacyTunnelSupportFloor)
|
||||||
{
|
{
|
||||||
Density = FMath::Min(Density, -BaseDensity);
|
Density = FMath::Min(Density, -BaseDensity);
|
||||||
}
|
}
|
||||||
@@ -2901,6 +2937,10 @@ void UVoxelStrateManager::ApplyPassageNativeFloorMC(
|
|||||||
float& Density, float WorldX, float WorldY, float WorldZ,
|
float& Density, float WorldX, float WorldY, float WorldZ,
|
||||||
float BaseDensity) const
|
float BaseDensity) const
|
||||||
{
|
{
|
||||||
|
if (VoxelDensityAblation::IsNativeFloorOff())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
const FIntVector ChunkCoord(
|
const FIntVector ChunkCoord(
|
||||||
FMath::FloorToInt(WorldX / (float)CHUNK_SIZE),
|
FMath::FloorToInt(WorldX / (float)CHUNK_SIZE),
|
||||||
FMath::FloorToInt(WorldY / (float)CHUNK_SIZE),
|
FMath::FloorToInt(WorldY / (float)CHUNK_SIZE),
|
||||||
@@ -2955,6 +2995,10 @@ void UVoxelStrateManager::ApplyPassageLandingRoomFloorMC(
|
|||||||
float& Density, float WorldX, float WorldY, float WorldZ,
|
float& Density, float WorldX, float WorldY, float WorldZ,
|
||||||
float BaseDensity) const
|
float BaseDensity) const
|
||||||
{
|
{
|
||||||
|
if (VoxelDensityAblation::IsLandingPostsOff())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
VoxelDensityProfile::FScopedTimer ProfileTimer(
|
VoxelDensityProfile::FScopedTimer ProfileTimer(
|
||||||
VoxelDensityProfile::EBucket::PassageLandingRoomFloor);
|
VoxelDensityProfile::EBucket::PassageLandingRoomFloor);
|
||||||
const FIntVector ChunkCoord(
|
const FIntVector ChunkCoord(
|
||||||
@@ -2979,6 +3023,10 @@ void UVoxelStrateManager::ApplyPassageLandingRoomFloorMC(
|
|||||||
|
|
||||||
bool UVoxelStrateManager::AnyPassageNearBox(const FVector& MinVoxel, const FVector& MaxVoxel) const
|
bool UVoxelStrateManager::AnyPassageNearBox(const FVector& MinVoxel, const FVector& MaxVoxel) const
|
||||||
{
|
{
|
||||||
|
if (VoxelDensityAblation::IsPassageCarvingOff())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
// Le carve d'un passage atteint ModSDF < PASSAGE_BLEND_RADIUS (4, VoxelGenerator.cpp) au-delà de
|
// Le carve d'un passage atteint ModSDF < PASSAGE_BLEND_RADIUS (4, VoxelGenerator.cpp) au-delà de
|
||||||
// sa surface ; BoundRadius inclut déjà rayon + blend, on re-pad par sécurité (conservatif).
|
// sa surface ; BoundRadius inclut déjà rayon + blend, on re-pad par sécurité (conservatif).
|
||||||
constexpr float CarvePad = 4.0f;
|
constexpr float CarvePad = 4.0f;
|
||||||
@@ -5530,7 +5578,12 @@ FStrateGenerationParams UVoxelStrateManager::GetGenerationParams(const FIntVecto
|
|||||||
{
|
{
|
||||||
Result.WormStrength = 0.0f;
|
Result.WormStrength = 0.0f;
|
||||||
}
|
}
|
||||||
return VF_ApplyRuntimeRoughnessOverrides(Result);
|
Result = VF_ApplyRuntimeRoughnessOverrides(Result);
|
||||||
|
if (VoxelDensityAblation::IsCaveWarpOff())
|
||||||
|
{
|
||||||
|
Result.CaveWarpStrength = 0.0f;
|
||||||
|
}
|
||||||
|
return Result;
|
||||||
};
|
};
|
||||||
|
|
||||||
#if WITH_EDITOR
|
#if WITH_EDITOR
|
||||||
@@ -5885,11 +5938,9 @@ uint64 UVoxelStrateManager::GetGenerationParamsFingerprint() const
|
|||||||
HashValue(Slot.Definition->bUseOperatorStack);
|
HashValue(Slot.Definition->bUseOperatorStack);
|
||||||
HashValue(Slot.Definition->bEnableWorms);
|
HashValue(Slot.Definition->bEnableWorms);
|
||||||
HashValue(VF_WormsForceOff());
|
HashValue(VF_WormsForceOff());
|
||||||
// The selected evaluator is a world input even though it is deliberately not part of
|
// Development-only stage ablations change the field and therefore belong in every
|
||||||
// FStrateGenerationParams (the fixed 83-field memo key). Keep persistent/verdict keys
|
// persistent/verdict key even though they are not authored generation parameters.
|
||||||
// from crossing an explicit WormNoiseMode/WormLatticeStep experiment.
|
HashValue(VoxelDensityAblation::GetResolvedMask());
|
||||||
HashValue(VoxelWormField::GetNoiseMode());
|
|
||||||
HashValue(VoxelWormField::GetLatticeStep());
|
|
||||||
HashValue(Slot.Definition->StrateHeightInChunks);
|
HashValue(Slot.Definition->StrateHeightInChunks);
|
||||||
HashValue(Slot.Definition->TransitionType);
|
HashValue(Slot.Definition->TransitionType);
|
||||||
HashValue(Slot.Definition->TransitionBlendChunks);
|
HashValue(Slot.Definition->TransitionBlendChunks);
|
||||||
|
|||||||
@@ -1,98 +1,14 @@
|
|||||||
// VoxelWormField.cpp
|
// VoxelWormField.cpp
|
||||||
// Shared deterministic worm-field evaluation and the measured lattice experiment.
|
// Shared deterministic exact worm-field evaluation.
|
||||||
|
|
||||||
#include "VoxelWormField.h"
|
#include "VoxelWormField.h"
|
||||||
|
|
||||||
#include "VoxelCaveMorphology.h" // VoxelHash::SeedOffset
|
#include "VoxelCaveMorphology.h" // VoxelHash::SeedOffset
|
||||||
#include "VoxelGenerator.h" // per-worker canonical tile window
|
|
||||||
#include "VoxelNoise.h"
|
#include "VoxelNoise.h"
|
||||||
#include "VoxelTypes.h" // VOXEL_NOISE_SCALE
|
#include "VoxelTypes.h" // VOXEL_NOISE_SCALE
|
||||||
|
|
||||||
#include "HAL/IConsoleManager.h"
|
|
||||||
#include "Misc/CommandLine.h"
|
|
||||||
#include "Misc/Parse.h"
|
|
||||||
|
|
||||||
#include <atomic>
|
|
||||||
#include <cfloat>
|
|
||||||
#include <cstring>
|
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
// The selected production mode is resolved once, before a generation stack is allowed to
|
|
||||||
// observe it, so all workers in a process use one world definition. Mode 0 remains the exact
|
|
||||||
// legacy control for measurements and compatibility investigations.
|
|
||||||
int32 GVoxelForgeWormLatticeStep = 0;
|
|
||||||
std::atomic<int32> GResolvedWormLatticeStep(-1);
|
|
||||||
int32 GVoxelForgeWormNoiseMode = 1;
|
|
||||||
std::atomic<int32> GResolvedWormNoiseMode(-1);
|
|
||||||
|
|
||||||
// The value field has the same threshold/strength contract as legacy Perlin. This small
|
|
||||||
// amplitude retune keeps the authored TunnelNetwork player-fit margin after replacing the
|
|
||||||
// gradient evaluation with the cheaper scalar hash. It is a code default, not a CVar, so a
|
|
||||||
// client cannot silently choose a different world.
|
|
||||||
constexpr float CheapWormNoiseAmplitude = 1.0f;
|
|
||||||
|
|
||||||
FAutoConsoleVariableRef CVarVoxelForgeWormLatticeStep(
|
|
||||||
TEXT("voxel.WormLatticeStep"),
|
|
||||||
GVoxelForgeWormLatticeStep,
|
|
||||||
TEXT("WORLD-CHANGING development experiment. 0=exact, 2/4=coarse worm lattice with "
|
|
||||||
"trilinear interpolation. All multiplayer peers and regenerated worlds must use "
|
|
||||||
"the same value. Default 0 until measured."),
|
|
||||||
ECVF_Default);
|
|
||||||
|
|
||||||
FAutoConsoleVariableRef CVarVoxelForgeWormNoiseMode(
|
|
||||||
TEXT("voxel.WormNoiseMode"),
|
|
||||||
GVoxelForgeWormNoiseMode,
|
|
||||||
TEXT("WORLD-CHANGING development experiment. 0=legacy gradient Perlin, 1=cheap "
|
|
||||||
"deterministic value-noise worm fields. All multiplayer peers and regenerated "
|
|
||||||
"worlds must use the same value. Default 1 (the measured production mode)."),
|
|
||||||
ECVF_Default);
|
|
||||||
|
|
||||||
int32 ResolveLatticeStep()
|
|
||||||
{
|
|
||||||
int32 Existing = GResolvedWormLatticeStep.load(std::memory_order_acquire);
|
|
||||||
if (Existing >= 0)
|
|
||||||
{
|
|
||||||
return Existing;
|
|
||||||
}
|
|
||||||
|
|
||||||
int32 CommandLineValue = GVoxelForgeWormLatticeStep;
|
|
||||||
FParse::Value(
|
|
||||||
FCommandLine::Get(), TEXT("voxel.WormLatticeStep="), CommandLineValue);
|
|
||||||
const int32 Resolved = CommandLineValue == 2 || CommandLineValue == 4
|
|
||||||
? CommandLineValue : 0;
|
|
||||||
int32 Expected = -1;
|
|
||||||
if (!GResolvedWormLatticeStep.compare_exchange_strong(
|
|
||||||
Expected, Resolved, std::memory_order_release, std::memory_order_acquire))
|
|
||||||
{
|
|
||||||
return Expected;
|
|
||||||
}
|
|
||||||
GVoxelForgeWormLatticeStep = Resolved;
|
|
||||||
return Resolved;
|
|
||||||
}
|
|
||||||
|
|
||||||
int32 ResolveNoiseMode()
|
|
||||||
{
|
|
||||||
int32 Existing = GResolvedWormNoiseMode.load(std::memory_order_acquire);
|
|
||||||
if (Existing >= 0)
|
|
||||||
{
|
|
||||||
return Existing;
|
|
||||||
}
|
|
||||||
|
|
||||||
int32 CommandLineValue = GVoxelForgeWormNoiseMode;
|
|
||||||
FParse::Value(
|
|
||||||
FCommandLine::Get(), TEXT("voxel.WormNoiseMode="), CommandLineValue);
|
|
||||||
const int32 Resolved = CommandLineValue == 1 ? 1 : 0;
|
|
||||||
int32 Expected = -1;
|
|
||||||
if (!GResolvedWormNoiseMode.compare_exchange_strong(
|
|
||||||
Expected, Resolved, std::memory_order_release, std::memory_order_acquire))
|
|
||||||
{
|
|
||||||
return Expected;
|
|
||||||
}
|
|
||||||
GVoxelForgeWormNoiseMode = Resolved;
|
|
||||||
return Resolved;
|
|
||||||
}
|
|
||||||
|
|
||||||
FORCEINLINE FVector3f MakeWormNoiseBase(
|
FORCEINLINE FVector3f MakeWormNoiseBase(
|
||||||
float WorldX,
|
float WorldX,
|
||||||
float WorldY,
|
float WorldY,
|
||||||
@@ -117,16 +33,11 @@ namespace
|
|||||||
float WorldY,
|
float WorldY,
|
||||||
float WorldZ,
|
float WorldZ,
|
||||||
const VoxelWormField::FParameters& Parameters,
|
const VoxelWormField::FParameters& Parameters,
|
||||||
int32 NoiseMode,
|
|
||||||
float NoiseScale)
|
float NoiseScale)
|
||||||
{
|
{
|
||||||
const FVector3f NoiseBase = MakeWormNoiseBase(
|
const FVector3f NoiseBase = MakeWormNoiseBase(
|
||||||
WorldX, WorldY, WorldZ, Parameters);
|
WorldX, WorldY, WorldZ, Parameters);
|
||||||
return FMath::Abs(
|
return FMath::Abs(VoxelNoise::Perlin3D(NoiseBase) * NoiseScale);
|
||||||
(NoiseMode == 0
|
|
||||||
? VoxelNoise::Perlin3D(NoiseBase)
|
|
||||||
: VoxelNoise::ValueNoise3D(NoiseBase, Parameters.Seed, 0x4D3A2F19u))
|
|
||||||
* NoiseScale);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
FORCEINLINE float EvaluateN2Field(
|
FORCEINLINE float EvaluateN2Field(
|
||||||
@@ -134,17 +45,12 @@ namespace
|
|||||||
float WorldY,
|
float WorldY,
|
||||||
float WorldZ,
|
float WorldZ,
|
||||||
const VoxelWormField::FParameters& Parameters,
|
const VoxelWormField::FParameters& Parameters,
|
||||||
int32 NoiseMode,
|
|
||||||
float NoiseScale)
|
float NoiseScale)
|
||||||
{
|
{
|
||||||
const FVector3f NoiseBase = MakeWormNoiseBase(
|
const FVector3f NoiseBase = MakeWormNoiseBase(
|
||||||
WorldX, WorldY, WorldZ, Parameters);
|
WorldX, WorldY, WorldZ, Parameters);
|
||||||
const FVector3f N2Position = NoiseBase + FVector3f(137.0f, 259.0f, 431.0f);
|
const FVector3f N2Position = NoiseBase + FVector3f(137.0f, 259.0f, 431.0f);
|
||||||
return FMath::Abs(
|
return FMath::Abs(VoxelNoise::Perlin3D(N2Position) * NoiseScale);
|
||||||
(NoiseMode == 0
|
|
||||||
? VoxelNoise::Perlin3D(N2Position)
|
|
||||||
: VoxelNoise::ValueNoise3D(N2Position, Parameters.Seed, 0xA7C15E2Du))
|
|
||||||
* NoiseScale);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
FORCEINLINE float EvaluateExactField(
|
FORCEINLINE float EvaluateExactField(
|
||||||
@@ -154,288 +60,22 @@ namespace
|
|||||||
float Threshold,
|
float Threshold,
|
||||||
const VoxelWormField::FParameters& Parameters)
|
const VoxelWormField::FParameters& Parameters)
|
||||||
{
|
{
|
||||||
const int32 NoiseMode = ResolveNoiseMode();
|
const float NoiseScale = VOXEL_NOISE_SCALE;
|
||||||
const float NoiseScale = VOXEL_NOISE_SCALE
|
|
||||||
* (NoiseMode == 1 ? CheapWormNoiseAmplitude : 1.0f);
|
|
||||||
const float N1 = EvaluateN1Field(
|
const float N1 = EvaluateN1Field(
|
||||||
WorldX, WorldY, WorldZ, Parameters, NoiseMode, NoiseScale);
|
WorldX, WorldY, WorldZ, Parameters, NoiseScale);
|
||||||
|
|
||||||
// N2 is non-negative, so this preserves the old exact evaluator's short-circuit.
|
// N2 is non-negative, so this preserves the exact evaluator's short-circuit.
|
||||||
if (N1 >= Threshold)
|
if (N1 >= Threshold)
|
||||||
{
|
{
|
||||||
return N1;
|
return N1;
|
||||||
}
|
}
|
||||||
return N1 + EvaluateN2Field(
|
return N1 + EvaluateN2Field(
|
||||||
WorldX, WorldY, WorldZ, Parameters, NoiseMode, NoiseScale);
|
WorldX, WorldY, WorldZ, Parameters, NoiseScale);
|
||||||
}
|
|
||||||
|
|
||||||
struct FLatticeNode
|
|
||||||
{
|
|
||||||
int32 X = 0;
|
|
||||||
int32 Y = 0;
|
|
||||||
int32 Z = 0;
|
|
||||||
float Value = 0.0f;
|
|
||||||
bool bValid = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
// The shared density grid is traversed z/y/x. Retaining two adjacent lattice planes is
|
|
||||||
// necessary for the z transition; 2048 entries evicted the previous plane and made the
|
|
||||||
// experiment slower than exact evaluation. This is fixed storage, not a heap allocation, and
|
|
||||||
// remains deterministic on every worker.
|
|
||||||
constexpr uint32 LatticeCacheSize = 32768;
|
|
||||||
static_assert((LatticeCacheSize & (LatticeCacheSize - 1u)) == 0,
|
|
||||||
"The lattice cache size must remain a power of two for the mask lookup.");
|
|
||||||
constexpr int32 TileLatticeAxis = 32;
|
|
||||||
static_assert(static_cast<uint32>(TileLatticeAxis * TileLatticeAxis * TileLatticeAxis)
|
|
||||||
== LatticeCacheSize,
|
|
||||||
"The direct tile lattice must use the fixed cache storage exactly.");
|
|
||||||
|
|
||||||
struct FLatticeCache
|
|
||||||
{
|
|
||||||
uint32 Seed = 0;
|
|
||||||
uint32 FrequencyBits = 0;
|
|
||||||
uint32 HorizontalBiasBits = 0;
|
|
||||||
uint32 VerticalScaleBits = 0;
|
|
||||||
int32 Step = 0;
|
|
||||||
bool bUseTileWindow = false;
|
|
||||||
FIntVector TileBaseGrid = FIntVector::ZeroValue;
|
|
||||||
int32 TileSampleStep = 0;
|
|
||||||
int32 TileCellsPerAxis = 0;
|
|
||||||
bool bContextValid = false;
|
|
||||||
FLatticeNode Nodes[LatticeCacheSize];
|
|
||||||
|
|
||||||
void SetContext(
|
|
||||||
const VoxelWormField::FParameters& Parameters,
|
|
||||||
int32 InStep,
|
|
||||||
bool InUseTileWindow,
|
|
||||||
const FIntVector& InTileOrigin,
|
|
||||||
int32 InTileSampleStep,
|
|
||||||
int32 InTileCellsPerAxis)
|
|
||||||
{
|
|
||||||
uint32 FrequencyBitsIn = 0;
|
|
||||||
uint32 HorizontalBiasBitsIn = 0;
|
|
||||||
uint32 VerticalScaleBitsIn = 0;
|
|
||||||
FMemory::Memcpy(&FrequencyBitsIn, &Parameters.Frequency, sizeof(uint32));
|
|
||||||
FMemory::Memcpy(&HorizontalBiasBitsIn, &Parameters.HorizontalBias, sizeof(uint32));
|
|
||||||
FMemory::Memcpy(&VerticalScaleBitsIn, &Parameters.VerticalScale, sizeof(uint32));
|
|
||||||
const double WormStepD = static_cast<double>(InStep);
|
|
||||||
const FIntVector InTileBaseGrid(
|
|
||||||
FMath::FloorToInt(static_cast<double>(InTileOrigin.X) / WormStepD) - 2,
|
|
||||||
FMath::FloorToInt(static_cast<double>(InTileOrigin.Y) / WormStepD) - 2,
|
|
||||||
FMath::FloorToInt(static_cast<double>(InTileOrigin.Z) / WormStepD) - 2);
|
|
||||||
if (bContextValid
|
|
||||||
&& Seed == Parameters.Seed
|
|
||||||
&& FrequencyBits == FrequencyBitsIn
|
|
||||||
&& HorizontalBiasBits == HorizontalBiasBitsIn
|
|
||||||
&& VerticalScaleBits == VerticalScaleBitsIn
|
|
||||||
&& Step == InStep
|
|
||||||
&& bUseTileWindow == InUseTileWindow
|
|
||||||
&& TileBaseGrid == InTileBaseGrid
|
|
||||||
&& TileSampleStep == InTileSampleStep
|
|
||||||
&& TileCellsPerAxis == InTileCellsPerAxis)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Seed = Parameters.Seed;
|
|
||||||
FrequencyBits = FrequencyBitsIn;
|
|
||||||
HorizontalBiasBits = HorizontalBiasBitsIn;
|
|
||||||
VerticalScaleBits = VerticalScaleBitsIn;
|
|
||||||
Step = InStep;
|
|
||||||
bUseTileWindow = InUseTileWindow;
|
|
||||||
TileBaseGrid = InTileBaseGrid;
|
|
||||||
TileSampleStep = InTileSampleStep;
|
|
||||||
TileCellsPerAxis = InTileCellsPerAxis;
|
|
||||||
bContextValid = true;
|
|
||||||
for (FLatticeNode& Node : Nodes)
|
|
||||||
{
|
|
||||||
Node.bValid = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static uint32 Hash(int32 X, int32 Y, int32 Z)
|
|
||||||
{
|
|
||||||
uint32 H = static_cast<uint32>(X) * 0x9E3779B1u;
|
|
||||||
H ^= static_cast<uint32>(Y) * 0x85EBCA77u;
|
|
||||||
H ^= static_cast<uint32>(Z) * 0xC2B2AE3Du;
|
|
||||||
H ^= H >> 16;
|
|
||||||
H *= 0x7FEB352Du;
|
|
||||||
H ^= H >> 15;
|
|
||||||
return H;
|
|
||||||
}
|
|
||||||
|
|
||||||
float Get(int32 X, int32 Y, int32 Z,
|
|
||||||
const VoxelWormField::FParameters& Parameters,
|
|
||||||
int32 NoiseMode,
|
|
||||||
float NoiseScale)
|
|
||||||
{
|
|
||||||
const auto EvaluateNode = [this, &Parameters, NoiseMode, NoiseScale](FLatticeNode& Node,
|
|
||||||
int32 NodeX, int32 NodeY,
|
|
||||||
int32 NodeZ) -> float
|
|
||||||
{
|
|
||||||
if (Node.bValid && Node.X == NodeX && Node.Y == NodeY && Node.Z == NodeZ)
|
|
||||||
{
|
|
||||||
return Node.Value;
|
|
||||||
}
|
|
||||||
Node.X = NodeX;
|
|
||||||
Node.Y = NodeY;
|
|
||||||
Node.Z = NodeZ;
|
|
||||||
const double StepD = static_cast<double>(Step);
|
|
||||||
const float WorldX = static_cast<float>(static_cast<double>(NodeX) * StepD);
|
|
||||||
const float WorldY = static_cast<float>(static_cast<double>(NodeY) * StepD);
|
|
||||||
const float WorldZ = static_cast<float>(static_cast<double>(NodeZ) * StepD);
|
|
||||||
// The lattice stores N1 only. The requested voxel uses this interpolated N1 for
|
|
||||||
// the threshold decision; N2 remains an exact point query only in the carve
|
|
||||||
// region. This removes the first noise call on the hot above-threshold majority
|
|
||||||
// without making the second noise unconditional.
|
|
||||||
Node.Value = EvaluateN1Field(
|
|
||||||
WorldX, WorldY, WorldZ, Parameters, NoiseMode, NoiseScale);
|
|
||||||
Node.bValid = true;
|
|
||||||
return Node.Value;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (bUseTileWindow)
|
|
||||||
{
|
|
||||||
const int32 LocalX = X - TileBaseGrid.X;
|
|
||||||
const int32 LocalY = Y - TileBaseGrid.Y;
|
|
||||||
const int32 LocalZ = Z - TileBaseGrid.Z;
|
|
||||||
if (LocalX >= 0 && LocalX < TileLatticeAxis
|
|
||||||
&& LocalY >= 0 && LocalY < TileLatticeAxis
|
|
||||||
&& LocalZ >= 0 && LocalZ < TileLatticeAxis)
|
|
||||||
{
|
|
||||||
FLatticeNode& Node = Nodes[
|
|
||||||
(LocalZ * TileLatticeAxis + LocalY) * TileLatticeAxis + LocalX];
|
|
||||||
return EvaluateNode(Node, X, Y, Z);
|
|
||||||
}
|
|
||||||
|
|
||||||
// A malformed or unusually large tile window must not alter the field's finite
|
|
||||||
// fallback semantics. It is outside the fixed direct table, so evaluate N1 once
|
|
||||||
// for this sample rather than attempting an unbounded allocation.
|
|
||||||
const double StepD = static_cast<double>(Step);
|
|
||||||
return EvaluateN1Field(
|
|
||||||
static_cast<float>(static_cast<double>(X) * StepD),
|
|
||||||
static_cast<float>(static_cast<double>(Y) * StepD),
|
|
||||||
static_cast<float>(static_cast<double>(Z) * StepD),
|
|
||||||
Parameters, NoiseMode, NoiseScale);
|
|
||||||
}
|
|
||||||
|
|
||||||
const uint32 Start = Hash(X, Y, Z) & (LatticeCacheSize - 1u);
|
|
||||||
for (uint32 Probe = 0; Probe < LatticeCacheSize; ++Probe)
|
|
||||||
{
|
|
||||||
FLatticeNode& Node = Nodes[(Start + Probe) & (LatticeCacheSize - 1u)];
|
|
||||||
if (Node.bValid)
|
|
||||||
{
|
|
||||||
if (Node.X == X && Node.Y == Y && Node.Z == Z)
|
|
||||||
{
|
|
||||||
return Node.Value;
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
Node.X = X;
|
|
||||||
Node.Y = Y;
|
|
||||||
Node.Z = Z;
|
|
||||||
return EvaluateNode(Node, X, Y, Z);
|
|
||||||
}
|
|
||||||
|
|
||||||
// A saturated cache is exceptionally unlikely for one density block. Falling back to
|
|
||||||
// a direct N1 evaluation keeps the result bounded if a caller exceeds it.
|
|
||||||
const double StepD = static_cast<double>(Step);
|
|
||||||
return EvaluateN1Field(
|
|
||||||
static_cast<float>(static_cast<double>(X) * StepD),
|
|
||||||
static_cast<float>(static_cast<double>(Y) * StepD),
|
|
||||||
static_cast<float>(static_cast<double>(Z) * StepD),
|
|
||||||
Parameters, NoiseMode, NoiseScale);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
thread_local FLatticeCache GLatticeCache;
|
|
||||||
|
|
||||||
FORCEINLINE bool CanUseLattice(float WorldX, float WorldY, float WorldZ, int32 Step)
|
|
||||||
{
|
|
||||||
if (Step <= 1 || !FMath::IsFinite(WorldX) || !FMath::IsFinite(WorldY)
|
|
||||||
|| !FMath::IsFinite(WorldZ))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The integer grid indices must survive the conversion back to a finite float world
|
|
||||||
// coordinate. Normal VoxelForge worlds are far inside this envelope; pathological input
|
|
||||||
// takes the exact path rather than changing overflow behaviour.
|
|
||||||
constexpr double MaxGridCoordinate = 1000000000.0;
|
|
||||||
return FMath::Abs(static_cast<double>(WorldX) / static_cast<double>(Step))
|
|
||||||
<= MaxGridCoordinate
|
|
||||||
&& FMath::Abs(static_cast<double>(WorldY) / static_cast<double>(Step))
|
|
||||||
<= MaxGridCoordinate
|
|
||||||
&& FMath::Abs(static_cast<double>(WorldZ) / static_cast<double>(Step))
|
|
||||||
<= MaxGridCoordinate;
|
|
||||||
}
|
|
||||||
|
|
||||||
FORCEINLINE float Lerp(float A, float B, float Alpha)
|
|
||||||
{
|
|
||||||
return A + (B - A) * Alpha;
|
|
||||||
}
|
|
||||||
|
|
||||||
float EvaluateLattice(
|
|
||||||
float WorldX,
|
|
||||||
float WorldY,
|
|
||||||
float WorldZ,
|
|
||||||
const VoxelWormField::FParameters& Parameters,
|
|
||||||
int32 Step,
|
|
||||||
float NoiseScale)
|
|
||||||
{
|
|
||||||
const int32 NoiseMode = ResolveNoiseMode();
|
|
||||||
const bool bUseTileWindow = VoxelGenLOD::TileCellsPerAxis > 0
|
|
||||||
&& VoxelGenLOD::TileCellsPerAxis <= CHUNK_SIZE
|
|
||||||
&& VoxelGenLOD::SampleStep > 0;
|
|
||||||
GLatticeCache.SetContext(
|
|
||||||
Parameters,
|
|
||||||
Step,
|
|
||||||
bUseTileWindow,
|
|
||||||
VoxelGenLOD::TileOriginVoxels,
|
|
||||||
VoxelGenLOD::SampleStep,
|
|
||||||
VoxelGenLOD::TileCellsPerAxis);
|
|
||||||
|
|
||||||
const double StepD = static_cast<double>(Step);
|
|
||||||
const double GX = static_cast<double>(WorldX) / StepD;
|
|
||||||
const double GY = static_cast<double>(WorldY) / StepD;
|
|
||||||
const double GZ = static_cast<double>(WorldZ) / StepD;
|
|
||||||
const int32 X0 = FMath::FloorToInt(GX);
|
|
||||||
const int32 Y0 = FMath::FloorToInt(GY);
|
|
||||||
const int32 Z0 = FMath::FloorToInt(GZ);
|
|
||||||
const float Tx = static_cast<float>(GX - static_cast<double>(X0));
|
|
||||||
const float Ty = static_cast<float>(GY - static_cast<double>(Y0));
|
|
||||||
const float Tz = static_cast<float>(GZ - static_cast<double>(Z0));
|
|
||||||
|
|
||||||
const float V000 = GLatticeCache.Get(X0, Y0, Z0, Parameters, NoiseMode, NoiseScale);
|
|
||||||
const float V100 = GLatticeCache.Get(X0 + 1, Y0, Z0, Parameters, NoiseMode, NoiseScale);
|
|
||||||
const float V010 = GLatticeCache.Get(X0, Y0 + 1, Z0, Parameters, NoiseMode, NoiseScale);
|
|
||||||
const float V110 = GLatticeCache.Get(X0 + 1, Y0 + 1, Z0, Parameters, NoiseMode, NoiseScale);
|
|
||||||
const float V001 = GLatticeCache.Get(X0, Y0, Z0 + 1, Parameters, NoiseMode, NoiseScale);
|
|
||||||
const float V101 = GLatticeCache.Get(X0 + 1, Y0, Z0 + 1, Parameters, NoiseMode, NoiseScale);
|
|
||||||
const float V011 = GLatticeCache.Get(X0, Y0 + 1, Z0 + 1, Parameters, NoiseMode, NoiseScale);
|
|
||||||
const float V111 = GLatticeCache.Get(X0 + 1, Y0 + 1, Z0 + 1, Parameters, NoiseMode, NoiseScale);
|
|
||||||
|
|
||||||
const float X00 = Lerp(V000, V100, Tx);
|
|
||||||
const float X10 = Lerp(V010, V110, Tx);
|
|
||||||
const float X01 = Lerp(V001, V101, Tx);
|
|
||||||
const float X11 = Lerp(V011, V111, Tx);
|
|
||||||
return Lerp(Lerp(X00, X10, Ty), Lerp(X01, X11, Ty), Tz);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace VoxelWormField
|
namespace VoxelWormField
|
||||||
{
|
{
|
||||||
int32 GetLatticeStep()
|
|
||||||
{
|
|
||||||
return ResolveLatticeStep();
|
|
||||||
}
|
|
||||||
|
|
||||||
int32 GetNoiseMode()
|
|
||||||
{
|
|
||||||
return ResolveNoiseMode();
|
|
||||||
}
|
|
||||||
|
|
||||||
float EvaluateExact(
|
float EvaluateExact(
|
||||||
float WorldX,
|
float WorldX,
|
||||||
float WorldY,
|
float WorldY,
|
||||||
@@ -451,21 +91,8 @@ namespace VoxelWormField
|
|||||||
float WorldY,
|
float WorldY,
|
||||||
float WorldZ,
|
float WorldZ,
|
||||||
float Threshold,
|
float Threshold,
|
||||||
const FParameters& Parameters,
|
const FParameters& Parameters)
|
||||||
int32 LatticeStep)
|
|
||||||
{
|
{
|
||||||
const int32 NoiseMode = ResolveNoiseMode();
|
return EvaluateExactField(WorldX, WorldY, WorldZ, Threshold, Parameters);
|
||||||
const bool bUseLattice = CanUseLattice(WorldX, WorldY, WorldZ, LatticeStep);
|
|
||||||
const float NoiseScale = VOXEL_NOISE_SCALE
|
|
||||||
* (NoiseMode == 1 ? CheapWormNoiseAmplitude : 1.0f);
|
|
||||||
const float N1 = bUseLattice
|
|
||||||
? EvaluateLattice(WorldX, WorldY, WorldZ, Parameters, LatticeStep, NoiseScale)
|
|
||||||
: EvaluateN1Field(WorldX, WorldY, WorldZ, Parameters, NoiseMode, NoiseScale);
|
|
||||||
if (N1 >= Threshold)
|
|
||||||
{
|
|
||||||
return N1;
|
|
||||||
}
|
|
||||||
return N1 + EvaluateN2Field(
|
|
||||||
WorldX, WorldY, WorldZ, Parameters, NoiseMode, NoiseScale);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
// VoxelDensityAblation.h
|
||||||
|
// Development-only, world-changing measurement overrides for TunnelNetwork density stages.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
|
||||||
|
namespace VoxelDensityAblation
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* A bit in this mask means that the named stage is forced off. These are measurement
|
||||||
|
* overrides, not gameplay settings: they change the generated field, are resolved once per
|
||||||
|
* process, and must match across peers and regenerated worlds.
|
||||||
|
*/
|
||||||
|
enum class EStage : uint32
|
||||||
|
{
|
||||||
|
CaveWarp = 1u << 0,
|
||||||
|
DetailOps = 1u << 1,
|
||||||
|
RoomSDF = 1u << 2,
|
||||||
|
TunnelSDF = 1u << 3,
|
||||||
|
TunnelCore = 1u << 4,
|
||||||
|
PassageCarving = 1u << 5,
|
||||||
|
PassageStructuralPosts= 1u << 6,
|
||||||
|
NativeFloor = 1u << 7,
|
||||||
|
Disturbances = 1u << 8,
|
||||||
|
OriginSpine = 1u << 9,
|
||||||
|
BoundarySeal = 1u << 10,
|
||||||
|
LandingPosts = 1u << 11,
|
||||||
|
XYEdgeSeal = 1u << 12,
|
||||||
|
PitChimneySDF = 1u << 13,
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Resolve command-line/CVar values once and return the process-wide mask. */
|
||||||
|
VOXELFORGE_API uint32 GetResolvedMask();
|
||||||
|
|
||||||
|
FORCEINLINE bool IsForcedOff(EStage Stage)
|
||||||
|
{
|
||||||
|
return (GetResolvedMask() & static_cast<uint32>(Stage)) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
FORCEINLINE bool IsCaveWarpOff() { return IsForcedOff(EStage::CaveWarp); }
|
||||||
|
FORCEINLINE bool IsDetailOpsOff() { return IsForcedOff(EStage::DetailOps); }
|
||||||
|
FORCEINLINE bool IsRoomSDFOff() { return IsForcedOff(EStage::RoomSDF); }
|
||||||
|
FORCEINLINE bool IsTunnelSDFOff() { return IsForcedOff(EStage::TunnelSDF); }
|
||||||
|
FORCEINLINE bool IsTunnelCoreOff() { return IsForcedOff(EStage::TunnelCore); }
|
||||||
|
FORCEINLINE bool IsPassageCarvingOff() { return IsForcedOff(EStage::PassageCarving); }
|
||||||
|
FORCEINLINE bool IsPassageStructuralPostsOff()
|
||||||
|
{
|
||||||
|
return IsForcedOff(EStage::PassageStructuralPosts);
|
||||||
|
}
|
||||||
|
FORCEINLINE bool IsNativeFloorOff() { return IsForcedOff(EStage::NativeFloor); }
|
||||||
|
FORCEINLINE bool IsDisturbancesOff() { return IsForcedOff(EStage::Disturbances); }
|
||||||
|
FORCEINLINE bool IsOriginSpineOff() { return IsForcedOff(EStage::OriginSpine); }
|
||||||
|
FORCEINLINE bool IsBoundarySealOff() { return IsForcedOff(EStage::BoundarySeal); }
|
||||||
|
FORCEINLINE bool IsLandingPostsOff() { return IsForcedOff(EStage::LandingPosts); }
|
||||||
|
FORCEINLINE bool IsXYEdgeSealOff() { return IsForcedOff(EStage::XYEdgeSeal); }
|
||||||
|
FORCEINLINE bool IsPitChimneySDFOff() { return IsForcedOff(EStage::PitChimneySDF); }
|
||||||
|
}
|
||||||
@@ -27,6 +27,7 @@
|
|||||||
#include "CoreMinimal.h"
|
#include "CoreMinimal.h"
|
||||||
#include "VoxelTypes.h" // SmoothStep01
|
#include "VoxelTypes.h" // SmoothStep01
|
||||||
#include "VoxelPassageGeometry.h" // shared body-sized landing/tunnel geometry
|
#include "VoxelPassageGeometry.h" // shared body-sized landing/tunnel geometry
|
||||||
|
#include "VoxelDensityAblation.h" // development-only world-changing measurements
|
||||||
|
|
||||||
//=============================================================================
|
//=============================================================================
|
||||||
// SEAL DE FRONTIÈRE / BOUNDARY SEAL
|
// SEAL DE FRONTIÈRE / BOUNDARY SEAL
|
||||||
@@ -43,7 +44,7 @@ FORCEINLINE void VF_ApplyBoundarySeal(float& Density, float WorldZ,
|
|||||||
float StrateTopZ, float StrateBottomZ,
|
float StrateTopZ, float StrateBottomZ,
|
||||||
float Thickness, float BaseDensity)
|
float Thickness, float BaseDensity)
|
||||||
{
|
{
|
||||||
if (Thickness <= 0.0f) return;
|
if (VoxelDensityAblation::IsBoundarySealOff() || Thickness <= 0.0f) return;
|
||||||
|
|
||||||
const float DistTop = StrateTopZ - WorldZ; // + si on est sous le plafond
|
const float DistTop = StrateTopZ - WorldZ; // + si on est sous le plafond
|
||||||
const float DistBot = WorldZ - StrateBottomZ; // + si on est au-dessus du sol
|
const float DistBot = WorldZ - StrateBottomZ; // + si on est au-dessus du sol
|
||||||
@@ -71,6 +72,7 @@ FORCEINLINE void VF_ApplyBoundarySeal(float& Density, float WorldZ,
|
|||||||
FORCEINLINE void VF_ApplyPassageCarving(float& Density, float ModSDF,
|
FORCEINLINE void VF_ApplyPassageCarving(float& Density, float ModSDF,
|
||||||
float BaseDensity, float SealThickness)
|
float BaseDensity, float SealThickness)
|
||||||
{
|
{
|
||||||
|
if (VoxelDensityAblation::IsPassageCarvingOff()) return;
|
||||||
constexpr float PASSAGE_BLEND_RADIUS = 4.0f;
|
constexpr float PASSAGE_BLEND_RADIUS = 4.0f;
|
||||||
if (ModSDF >= PASSAGE_BLEND_RADIUS) return;
|
if (ModSDF >= PASSAGE_BLEND_RADIUS) return;
|
||||||
|
|
||||||
@@ -121,7 +123,7 @@ FORCEINLINE void VF_ApplyPassageLandingCarving(float& Density, float LandingSDF,
|
|||||||
FORCEINLINE void VF_ApplyOriginSpine(float& Density, float WorldX, float WorldY, float WorldZ,
|
FORCEINLINE void VF_ApplyOriginSpine(float& Density, float WorldX, float WorldY, float WorldZ,
|
||||||
float StrateTopZ, float StrateBottomZ, float SealThickness, float BaseDensity, float Radius)
|
float StrateTopZ, float StrateBottomZ, float SealThickness, float BaseDensity, float Radius)
|
||||||
{
|
{
|
||||||
if (Radius <= 0.0f) return;
|
if (VoxelDensityAblation::IsOriginSpineOff() || Radius <= 0.0f) return;
|
||||||
|
|
||||||
const VoxelPassageGeometry::FOriginLandingGeometry Geometry =
|
const VoxelPassageGeometry::FOriginLandingGeometry Geometry =
|
||||||
VoxelPassageGeometry::BuildOriginLandingGeometry(
|
VoxelPassageGeometry::BuildOriginLandingGeometry(
|
||||||
@@ -153,7 +155,7 @@ FORCEINLINE void VF_ApplyOriginLandingAir(float& Density,
|
|||||||
float WorldX, float WorldY, float WorldZ,
|
float WorldX, float WorldY, float WorldZ,
|
||||||
float StrateTopZ, float StrateBottomZ, float SealThickness, float BaseDensity, float Radius)
|
float StrateTopZ, float StrateBottomZ, float SealThickness, float BaseDensity, float Radius)
|
||||||
{
|
{
|
||||||
if (Radius <= 0.0f) return;
|
if (VoxelDensityAblation::IsLandingPostsOff() || Radius <= 0.0f) return;
|
||||||
const VoxelPassageGeometry::FOriginLandingGeometry Geometry =
|
const VoxelPassageGeometry::FOriginLandingGeometry Geometry =
|
||||||
VoxelPassageGeometry::BuildOriginLandingGeometry(
|
VoxelPassageGeometry::BuildOriginLandingGeometry(
|
||||||
StrateTopZ, StrateBottomZ, SealThickness, Radius);
|
StrateTopZ, StrateBottomZ, SealThickness, Radius);
|
||||||
@@ -169,7 +171,7 @@ FORCEINLINE void VF_ApplyOriginLandingFloor(float& Density,
|
|||||||
float WorldX, float WorldY, float WorldZ,
|
float WorldX, float WorldY, float WorldZ,
|
||||||
float StrateTopZ, float StrateBottomZ, float SealThickness, float BaseDensity, float Radius)
|
float StrateTopZ, float StrateBottomZ, float SealThickness, float BaseDensity, float Radius)
|
||||||
{
|
{
|
||||||
if (Radius <= 0.0f) return;
|
if (VoxelDensityAblation::IsLandingPostsOff() || Radius <= 0.0f) return;
|
||||||
const VoxelPassageGeometry::FOriginLandingGeometry Geometry =
|
const VoxelPassageGeometry::FOriginLandingGeometry Geometry =
|
||||||
VoxelPassageGeometry::BuildOriginLandingGeometry(
|
VoxelPassageGeometry::BuildOriginLandingGeometry(
|
||||||
StrateTopZ, StrateBottomZ, SealThickness, Radius);
|
StrateTopZ, StrateBottomZ, SealThickness, Radius);
|
||||||
@@ -221,7 +223,8 @@ FORCEINLINE void VF_ApplyOriginLandingFloorMC(float& Density,
|
|||||||
FORCEINLINE void VF_ApplyXYEdgeSeal(float& Density, float WorldX, float WorldY,
|
FORCEINLINE void VF_ApplyXYEdgeSeal(float& Density, float WorldX, float WorldY,
|
||||||
float WorldRadiusVoxels, float Thickness, float BaseDensity)
|
float WorldRadiusVoxels, float Thickness, float BaseDensity)
|
||||||
{
|
{
|
||||||
if (!(WorldRadiusVoxels > 0.0f) || !(Thickness > 0.0f) || !(BaseDensity > 0.0f)) return;
|
if (VoxelDensityAblation::IsXYEdgeSealOff()
|
||||||
|
|| !(WorldRadiusVoxels > 0.0f) || !(Thickness > 0.0f) || !(BaseDensity > 0.0f)) return;
|
||||||
if (!VoxelMath::IsFinite(WorldX) || !VoxelMath::IsFinite(WorldY)
|
if (!VoxelMath::IsFinite(WorldX) || !VoxelMath::IsFinite(WorldY)
|
||||||
|| !VoxelMath::IsFinite(WorldRadiusVoxels) || !VoxelMath::IsFinite(Thickness)
|
|| !VoxelMath::IsFinite(WorldRadiusVoxels) || !VoxelMath::IsFinite(Thickness)
|
||||||
|| !VoxelMath::IsFinite(BaseDensity)) return;
|
|| !VoxelMath::IsFinite(BaseDensity)) return;
|
||||||
@@ -256,7 +259,8 @@ FORCEINLINE void VF_ApplyXYEdgeSeal(float& Density, float WorldX, float WorldY,
|
|||||||
FORCEINLINE void VF_ApplyXYEdgeSealMC(float& Density, float WorldX, float WorldY,
|
FORCEINLINE void VF_ApplyXYEdgeSealMC(float& Density, float WorldX, float WorldY,
|
||||||
float WorldRadiusVoxels, float Thickness, float BaseDensity)
|
float WorldRadiusVoxels, float Thickness, float BaseDensity)
|
||||||
{
|
{
|
||||||
if (!(WorldRadiusVoxels > 0.0f) || !(Thickness > 0.0f)) return;
|
if (VoxelDensityAblation::IsXYEdgeSealOff()
|
||||||
|
|| !(WorldRadiusVoxels > 0.0f) || !(Thickness > 0.0f)) return;
|
||||||
|
|
||||||
float InternalDensity = -Density;
|
float InternalDensity = -Density;
|
||||||
VF_ApplyXYEdgeSeal(InternalDensity, WorldX, WorldY,
|
VF_ApplyXYEdgeSeal(InternalDensity, WorldX, WorldY,
|
||||||
|
|||||||
@@ -142,56 +142,6 @@ FORCEINLINE float Perlin3D(const FVector3f& P)
|
|||||||
return Perlin3D(P.X, P.Y, P.Z);
|
return Perlin3D(P.X, P.Y, P.Z);
|
||||||
}
|
}
|
||||||
|
|
||||||
// A smooth, deterministic value-noise sample for the worm-only cost experiment. It keeps the
|
|
||||||
// same unit lattice and quintic interpolation as Perlin3D, but uses one seeded scalar hash per
|
|
||||||
// corner instead of the eight gradient selections and dot products. It is intentionally not used
|
|
||||||
// by the terrain's other noise fields; WormNoiseMode gates it as an explicit world-changing test.
|
|
||||||
FORCEINLINE uint32 ValueNoiseHash(int32 X, int32 Y, int32 Z, uint32 Seed, uint32 Salt)
|
|
||||||
{
|
|
||||||
uint32 H = Detail::HashCorner(X, Y, Z) ^ Seed ^ Salt;
|
|
||||||
H ^= H >> 16;
|
|
||||||
H *= 0x7FEB352Du;
|
|
||||||
H ^= H >> 15;
|
|
||||||
return H;
|
|
||||||
}
|
|
||||||
|
|
||||||
FORCEINLINE float ValueNoise3D(const FVector3f& P, uint32 Seed, uint32 Salt)
|
|
||||||
{
|
|
||||||
const float XFloor = FMath::FloorToFloat(P.X);
|
|
||||||
const float YFloor = FMath::FloorToFloat(P.Y);
|
|
||||||
const float ZFloor = FMath::FloorToFloat(P.Z);
|
|
||||||
const int32 X = static_cast<int32>(XFloor);
|
|
||||||
const int32 Y = static_cast<int32>(YFloor);
|
|
||||||
const int32 Z = static_cast<int32>(ZFloor);
|
|
||||||
const float Fx = P.X - XFloor;
|
|
||||||
const float Fy = P.Y - YFloor;
|
|
||||||
const float Fz = P.Z - ZFloor;
|
|
||||||
const float Sx = Detail::Fade(Fx);
|
|
||||||
const float Sy = Detail::Fade(Fy);
|
|
||||||
const float Sz = Detail::Fade(Fz);
|
|
||||||
constexpr float HashToSigned = 2.0f / 16777216.0f;
|
|
||||||
const auto Sample = [Seed, Salt, HashToSigned](int32 IX, int32 IY, int32 IZ) -> float
|
|
||||||
{
|
|
||||||
return static_cast<float>(ValueNoiseHash(IX, IY, IZ, Seed, Salt) & 0x00FFFFFFu)
|
|
||||||
* HashToSigned - 1.0f;
|
|
||||||
};
|
|
||||||
const float V000 = Sample(X, Y, Z);
|
|
||||||
const float V100 = Sample(X + 1, Y, Z);
|
|
||||||
const float V010 = Sample(X, Y + 1, Z);
|
|
||||||
const float V110 = Sample(X + 1, Y + 1, Z);
|
|
||||||
const float V001 = Sample(X, Y, Z + 1);
|
|
||||||
const float V101 = Sample(X + 1, Y, Z + 1);
|
|
||||||
const float V011 = Sample(X, Y + 1, Z + 1);
|
|
||||||
const float V111 = Sample(X + 1, Y + 1, Z + 1);
|
|
||||||
const float X00 = Detail::Lerp(V000, V100, Sx);
|
|
||||||
const float X10 = Detail::Lerp(V010, V110, Sx);
|
|
||||||
const float X01 = Detail::Lerp(V001, V101, Sx);
|
|
||||||
const float X11 = Detail::Lerp(V011, V111, Sx);
|
|
||||||
const float Y0 = Detail::Lerp(X00, X10, Sy);
|
|
||||||
const float Y1 = Detail::Lerp(X01, X11, Sy);
|
|
||||||
return Detail::Lerp(Y0, Y1, Sz);
|
|
||||||
}
|
|
||||||
|
|
||||||
//=============================================================================
|
//=============================================================================
|
||||||
// LIPSCHITZ BOUND FOR THE ACTUAL 3D FIELD
|
// LIPSCHITZ BOUND FOR THE ACTUAL 3D FIELD
|
||||||
//=============================================================================
|
//=============================================================================
|
||||||
|
|||||||
@@ -16,16 +16,6 @@ namespace VoxelWormField
|
|||||||
uint32 Seed = 0;
|
uint32 Seed = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the selected development experiment mode.
|
|
||||||
*
|
|
||||||
* 0 is the exact field. 2 and 4 evaluate the exact field on that many-voxel lattice and
|
|
||||||
* trilinearly interpolate it. This is intentionally a WORLD-CHANGING development switch:
|
|
||||||
* it must not be changed between multiplayer peers or for an already generated world.
|
|
||||||
*/
|
|
||||||
VOXELFORGE_API int32 GetLatticeStep();
|
|
||||||
VOXELFORGE_API int32 GetNoiseMode();
|
|
||||||
|
|
||||||
/** Evaluate the exact two-noise field, retaining the N1 threshold short-circuit. */
|
/** Evaluate the exact two-noise field, retaining the N1 threshold short-circuit. */
|
||||||
VOXELFORGE_API float EvaluateExact(
|
VOXELFORGE_API float EvaluateExact(
|
||||||
float WorldX,
|
float WorldX,
|
||||||
@@ -34,12 +24,11 @@ namespace VoxelWormField
|
|||||||
float Threshold,
|
float Threshold,
|
||||||
const FParameters& Parameters);
|
const FParameters& Parameters);
|
||||||
|
|
||||||
/** Evaluate the exact field or its selected cached lattice approximation. */
|
/** Evaluate the exact two-noise field. */
|
||||||
VOXELFORGE_API float Evaluate(
|
VOXELFORGE_API float Evaluate(
|
||||||
float WorldX,
|
float WorldX,
|
||||||
float WorldY,
|
float WorldY,
|
||||||
float WorldZ,
|
float WorldZ,
|
||||||
float Threshold,
|
float Threshold,
|
||||||
const FParameters& Parameters,
|
const FParameters& Parameters);
|
||||||
int32 LatticeStep);
|
|
||||||
}
|
}
|
||||||
@@ -29,11 +29,11 @@
|
|||||||
#include "VoxelCaveMorphology.h"
|
#include "VoxelCaveMorphology.h"
|
||||||
#include "VoxelDiffLayer.h"
|
#include "VoxelDiffLayer.h"
|
||||||
#include "VoxelDensityOpStack.h"
|
#include "VoxelDensityOpStack.h"
|
||||||
|
#include "VoxelDensityAblation.h"
|
||||||
#include "VoxelDensityProfile.h"
|
#include "VoxelDensityProfile.h"
|
||||||
#include "VoxelGenerator.h"
|
#include "VoxelGenerator.h"
|
||||||
#include "VoxelMarchingCubesMesher.h"
|
#include "VoxelMarchingCubesMesher.h"
|
||||||
#include "VoxelNoise.h"
|
#include "VoxelNoise.h"
|
||||||
#include "VoxelWormField.h"
|
|
||||||
#include "VoxelSettings.h"
|
#include "VoxelSettings.h"
|
||||||
#include "VoxelStackSampler.h"
|
#include "VoxelStackSampler.h"
|
||||||
#include "VoxelStrateDefinition.h"
|
#include "VoxelStrateDefinition.h"
|
||||||
@@ -4202,8 +4202,8 @@ FString BuildManifestJson(
|
|||||||
{
|
{
|
||||||
Writer->WriteValue(TEXT("worm_strength"), static_cast<double>(Arguments.WormStrength));
|
Writer->WriteValue(TEXT("worm_strength"), static_cast<double>(Arguments.WormStrength));
|
||||||
}
|
}
|
||||||
Writer->WriteValue(TEXT("worm_lattice_step"), VoxelWormField::GetLatticeStep());
|
Writer->WriteValue(TEXT("tunnel_ablation_mask"),
|
||||||
Writer->WriteValue(TEXT("worm_noise_mode"), VoxelWormField::GetNoiseMode());
|
static_cast<int64>(VoxelDensityAblation::GetResolvedMask()));
|
||||||
WriteTunnelFloorOverrides(*Writer, Arguments);
|
WriteTunnelFloorOverrides(*Writer, Arguments);
|
||||||
Writer->WriteValue(TEXT("room_mouth_rise_override"), Arguments.bRoomMouthRiseOverride);
|
Writer->WriteValue(TEXT("room_mouth_rise_override"), Arguments.bRoomMouthRiseOverride);
|
||||||
if (Arguments.bRoomMouthRiseOverride)
|
if (Arguments.bRoomMouthRiseOverride)
|
||||||
@@ -4478,8 +4478,8 @@ FString BuildExploreJson(
|
|||||||
{
|
{
|
||||||
Writer->WriteValue(TEXT("worm_strength"), static_cast<double>(Arguments.WormStrength));
|
Writer->WriteValue(TEXT("worm_strength"), static_cast<double>(Arguments.WormStrength));
|
||||||
}
|
}
|
||||||
Writer->WriteValue(TEXT("worm_lattice_step"), VoxelWormField::GetLatticeStep());
|
Writer->WriteValue(TEXT("tunnel_ablation_mask"),
|
||||||
Writer->WriteValue(TEXT("worm_noise_mode"), VoxelWormField::GetNoiseMode());
|
static_cast<int64>(VoxelDensityAblation::GetResolvedMask()));
|
||||||
WriteTunnelFloorOverrides(*Writer, Arguments);
|
WriteTunnelFloorOverrides(*Writer, Arguments);
|
||||||
Writer->WriteValue(TEXT("room_mouth_rise_override"), Arguments.bRoomMouthRiseOverride);
|
Writer->WriteValue(TEXT("room_mouth_rise_override"), Arguments.bRoomMouthRiseOverride);
|
||||||
if (Arguments.bRoomMouthRiseOverride)
|
if (Arguments.bRoomMouthRiseOverride)
|
||||||
|
|||||||
Reference in new issue
Block a user