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:
Fr0zkaandClaude Opus 5 committed 2026-09-14 09:39:57 +02:00
1 parent c132243b30
commit 6ccf3718ff
12 files changed
+833 -695

No files matched your search

+237 -10
View File
@@ -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,7 +7291,9 @@ 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.
BakeRoomFeature(CR, /*Max*/2, OpParams.PitDensity, if (!VoxelDensityAblation::IsPitChimneySDFOff())
{
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,
[&](float PX, float PY, float PitRadius, uint32 PH3) [&](float PX, float PY, float PitRadius, uint32 PH3)
@@ -7308,8 +7315,8 @@ void VoxelCaveMorphology::BuildChunkCache(
OutCache.Pits.Add(Pit); OutCache.Pits.Add(Pit);
}); });
// CHIMNEYS — mirror of pits: upward tubes anchored in the room's upper half. // CHIMNEYS — mirror of pits: upward tubes anchored in the room's upper half.
BakeRoomFeature(CR, /*Max*/2, OpParams.ChimneyDensity, BakeRoomFeature(CR, /*Max*/2, OpParams.ChimneyDensity,
0xC4F007u, 7919u, 0x1337u, 0xCAFEu, 0xC4F007u, 7919u, 0x1337u, 0xCAFEu,
/*XYScale*/0.6f, OpParams.ChimneyMinRadius, OpParams.ChimneyMaxRadius, /*XYScale*/0.6f, OpParams.ChimneyMinRadius, OpParams.ChimneyMaxRadius,
[&](float CX, float CY, float ChmRadius, uint32 CH3) [&](float CX, float CY, float ChmRadius, uint32 CH3)
@@ -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;
Cache.RoomSpatialIndex, Cache.Rooms.Num(), WorldX, WorldY, EvaluateRoom, if (!VoxelDensityAblation::IsRoomSDFOff())
bUseSpatialIndex); {
RoomCandidateCount = VF_ForEachSpatialCandidate(
Cache.RoomSpatialIndex, Cache.Rooms.Num(), WorldX, WorldY, EvaluateRoom,
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);
}; };
VF_ForEachSpatialCandidate( if (!VoxelDensityAblation::IsRoomSDFOff())
Cache.RoomFloorJoinSpatialIndex, Cache.RoomFloorJoins.Num(), {
WorldX, WorldY, EvaluateJoin, bUseSpatialIndex); VF_ForEachSpatialCandidate(
Cache.RoomFloorJoinSpatialIndex, Cache.RoomFloorJoins.Num(),
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;
Cache.TunnelSpatialIndex, Cache.Tunnels.Num(), WorldX, WorldY, EvaluateTunnel, if (!VoxelDensityAblation::IsTunnelSDFOff())
bUseSpatialIndex); {
TunnelCandidateCount = VF_ForEachSpatialCandidate(
Cache.TunnelSpatialIndex, Cache.Tunnels.Num(), WorldX, WorldY, EvaluateTunnel,
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;
}
}
+155 -104
View File
@@ -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()
? VoxelPassageGeometry::OriginLandingFloorTouchesLattice( && (Ctx.bUseLatticeProof
VoxelBox, Ctx.LatticeOriginVoxels, Ctx.Step, ? VoxelPassageGeometry::OriginLandingFloorTouchesLattice(
TopZ, BotZ, Seal, SpineRadius) VoxelBox, Ctx.LatticeOriginVoxels, Ctx.Step,
: VoxelPassageGeometry::OriginLandingFloorTouchesBox( TopZ, BotZ, Seal, SpineRadius)
VoxelBox, TopZ, BotZ, Seal, SpineRadius); : VoxelPassageGeometry::OriginLandingFloorTouchesBox(
const bool bPassageFloor = Ctx.bUseLatticeProof VoxelBox, TopZ, BotZ, Seal, SpineRadius));
? LiveManager->AnyPassageLandingFloorNearLattice( const bool bPassageFloor = !VoxelDensityAblation::IsLandingPostsOff()
VoxelBox, Ctx.LatticeOriginVoxels, Ctx.Step) && (Ctx.bUseLatticeProof
: LiveManager->AnyPassageLandingFloorNearBox( ? LiveManager->AnyPassageLandingFloorNearLattice(
VoxelBox.Min, VoxelBox.Max); VoxelBox, Ctx.LatticeOriginVoxels, Ctx.Step)
: LiveManager->AnyPassageLandingFloorNearBox(
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()
? LiveManager->AnyPassageNearLattice( && (Ctx.bUseLatticeProof
VoxelBox, Ctx.LatticeOriginVoxels, Ctx.Step) ? LiveManager->AnyPassageNearLattice(
: LiveManager->AnyPassageNearBox(VoxelBox.Min, VoxelBox.Max); VoxelBox, Ctx.LatticeOriginVoxels, Ctx.Step)
: 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,68 +4347,77 @@ 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é.
auto EvaluatePit = [&](int32 PitIndex) if (!VoxelDensityAblation::IsPitChimneySDFOff())
{ {
const FCachedPit& Pit = GetCache().Pits[PitIndex]; auto EvaluatePit = [&](int32 PitIndex)
const float DZ = WorldZ - Pit.TopZ;
if (DZ >= Pit.BlendK) { return; }
if (-DZ > Pit.Depth + Pit.BlendK) { return; }
const float DX = WorldX - Pit.CenterX;
const float DY = WorldY - Pit.CenterY;
const float XYDistSq = DX * DX + DY * DY;
if (XYDistSq > Pit.BoundXYRadiusSq) { return; }
float PitSDF;
if (DZ <= 0.0f)
{ {
const float DepthBelow = -DZ; const FCachedPit& Pit = GetCache().Pits[PitIndex];
float FlareFactor = FMath::Clamp(1.0f - DepthBelow / Pit.FlareDist, 0.0f, 1.0f); const float DZ = WorldZ - Pit.TopZ;
FlareFactor = FlareFactor * FlareFactor; if (DZ >= Pit.BlendK) { return; }
const float EffRadius = Pit.Radius + Pit.FlareExtra * FlareFactor; if (-DZ > Pit.Depth + Pit.BlendK) { return; }
PitSDF = FMath::Sqrt(XYDistSq) - EffRadius;
}
else
{
PitSDF = FMath::Sqrt(XYDistSq) - (Pit.Radius + Pit.FlareExtra);
}
CaveSDF = VoxelSDF::SmoothMin(CaveSDF, PitSDF, Pit.BlendK); const float DX = WorldX - Pit.CenterX;
}; const float DY = WorldY - Pit.CenterY;
VF_ForEachChunkSDFSpatialCandidate( const float XYDistSq = DX * DX + DY * DY;
GetCache().PitSpatialIndex, GetCache().Pits.Num(), if (XYDistSq > Pit.BoundXYRadiusSq) { return; }
WorldX, WorldY, EvaluatePit, bUseSpatialIndex);
auto EvaluateChimney = [&](int32 ChimneyIndex) float PitSDF;
if (DZ <= 0.0f)
{
const float DepthBelow = -DZ;
float FlareFactor = FMath::Clamp(1.0f - DepthBelow / Pit.FlareDist, 0.0f, 1.0f);
FlareFactor = FlareFactor * FlareFactor;
const float EffRadius = Pit.Radius + Pit.FlareExtra * FlareFactor;
PitSDF = FMath::Sqrt(XYDistSq) - EffRadius;
}
else
{
PitSDF = FMath::Sqrt(XYDistSq) - (Pit.Radius + Pit.FlareExtra);
}
CaveSDF = VoxelSDF::SmoothMin(CaveSDF, PitSDF, Pit.BlendK);
};
if (!VoxelDensityAblation::IsPitChimneySDFOff())
{ {
const FCachedChimney& Chim = GetCache().Chimneys[ChimneyIndex]; VF_ForEachChunkSDFSpatialCandidate(
const float DZ = WorldZ - Chim.BottomZ; GetCache().PitSpatialIndex, GetCache().Pits.Num(),
if (-DZ >= Chim.BlendK) { return; } WorldX, WorldY, EvaluatePit, bUseSpatialIndex);
if (DZ > Chim.Height + Chim.BlendK) { return; } }
const float DX = WorldX - Chim.CenterX; auto EvaluateChimney = [&](int32 ChimneyIndex)
const float DY = WorldY - Chim.CenterY;
const float XYDistSq = DX * DX + DY * DY;
if (XYDistSq > Chim.BoundXYRadiusSq) { return; }
float ChmSDF;
if (DZ >= 0.0f)
{ {
float FlareFactor = FMath::Clamp(1.0f - DZ / Chim.FlareDist, 0.0f, 1.0f); const FCachedChimney& Chim = GetCache().Chimneys[ChimneyIndex];
FlareFactor = FlareFactor * FlareFactor; const float DZ = WorldZ - Chim.BottomZ;
const float EffRadius = Chim.Radius + Chim.FlareExtra * FlareFactor; if (-DZ >= Chim.BlendK) { return; }
ChmSDF = FMath::Sqrt(XYDistSq) - EffRadius; if (DZ > Chim.Height + Chim.BlendK) { return; }
}
else
{
ChmSDF = FMath::Sqrt(XYDistSq) - (Chim.Radius + Chim.FlareExtra);
}
CaveSDF = VoxelSDF::SmoothMin(CaveSDF, ChmSDF, Chim.BlendK); const float DX = WorldX - Chim.CenterX;
}; const float DY = WorldY - Chim.CenterY;
VF_ForEachChunkSDFSpatialCandidate( const float XYDistSq = DX * DX + DY * DY;
GetCache().ChimneySpatialIndex, GetCache().Chimneys.Num(), if (XYDistSq > Chim.BoundXYRadiusSq) { return; }
WorldX, WorldY, EvaluateChimney, bUseSpatialIndex);
float ChmSDF;
if (DZ >= 0.0f)
{
float FlareFactor = FMath::Clamp(1.0f - DZ / Chim.FlareDist, 0.0f, 1.0f);
FlareFactor = FlareFactor * FlareFactor;
const float EffRadius = Chim.Radius + Chim.FlareExtra * FlareFactor;
ChmSDF = FMath::Sqrt(XYDistSq) - EffRadius;
}
else
{
ChmSDF = FMath::Sqrt(XYDistSq) - (Chim.Radius + Chim.FlareExtra);
}
CaveSDF = VoxelSDF::SmoothMin(CaveSDF, ChmSDF, Chim.BlendK);
};
if (!VoxelDensityAblation::IsPitChimneySDFOff())
{
VF_ForEachChunkSDFSpatialCandidate(
GetCache().ChimneySpatialIndex, GetCache().Chimneys.Num(),
WorldX, WorldY, EvaluateChimney, bUseSpatialIndex);
}
}
InOut.Sdf = CaveSDF; InOut.Sdf = CaveSDF;
@@ -4378,40 +4426,46 @@ 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.
const FTunnelSupportFloorColumn* SupportColumn = GetSupportColumn( if (!VoxelDensityAblation::IsTunnelCoreOff())
S.LastWorldPosition);
S.LastTunnelCoreWorldEvaluation = VoxelCaveMorphology::EvaluateTunnelCoreWorld(
WorldX, WorldY, WorldZ, GetCache(), SupportColumn,
bUseSpatialIndex);
S.LastTunnelCoreEvaluationPosition = S.LastWorldPosition;
S.bLastTunnelCoreWorldEvaluationValid = true;
if (S.LastTunnelCoreWorldEvaluation.bSupportFloor
|| S.LastTunnelCoreWorldEvaluation.bRoomFloor)
{ {
const float StructuralFloorMinimumDensity = const FTunnelSupportFloorColumn* SupportColumn = GetSupportColumn(
FMath::Max(P.BaseDensity * 2.0f, 1.0f); S.LastWorldPosition);
if (VoxelMath::IsFinite(StructuralFloorMinimumDensity) S.LastTunnelCoreWorldEvaluation = VoxelCaveMorphology::EvaluateTunnelCoreWorld(
&& StructuralFloorMinimumDensity > 0.0f) WorldX, WorldY, WorldZ, GetCache(), SupportColumn,
bUseSpatialIndex);
S.LastTunnelCoreEvaluationPosition = S.LastWorldPosition;
S.bLastTunnelCoreWorldEvaluationValid = true;
if (S.LastTunnelCoreWorldEvaluation.bSupportFloor
|| S.LastTunnelCoreWorldEvaluation.bRoomFloor)
{ {
InOut.bProtectedStructuralFloor = true; const float StructuralFloorMinimumDensity =
InOut.StructuralFloorMinimumDensity = StructuralFloorMinimumDensity; FMath::Max(P.BaseDensity * 2.0f, 1.0f);
if (VoxelDensityProfile::AreCountersEnabled()) if (VoxelMath::IsFinite(StructuralFloorMinimumDensity)
&& StructuralFloorMinimumDensity > 0.0f)
{ {
VoxelDensityProfile::AddCounter( InOut.bProtectedStructuralFloor = true;
VoxelDensityProfile::ECounter::TunnelAuthoredFloorSamples); InOut.StructuralFloorMinimumDensity = StructuralFloorMinimumDensity;
if (VoxelDensityProfile::AreCountersEnabled())
{
VoxelDensityProfile::AddCounter(
VoxelDensityProfile::ECounter::TunnelAuthoredFloorSamples);
}
} }
} }
// 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
// explicit for a later modifier and does not alter Density/Sdf.
InOut.bHasTunnelCoreWorldEvaluation = true;
InOut.TunnelCoreWorldSDF = S.LastTunnelCoreWorldEvaluation.SDF;
InOut.bTunnelCoreSupportFloor = S.LastTunnelCoreWorldEvaluation.bSupportFloor;
InOut.bTunnelCoreRoomFloor = S.LastTunnelCoreWorldEvaluation.bRoomFloor;
} }
// These are the per-sample hand-offs needed by an op-major block. The scalar path // These cache hand-offs remain valid when tunnel core is off; detail operators still
// continues to use FState directly; publishing them here only makes the dependency // need the room graph and nearest-room ownership.
// explicit for a later modifier and does not alter Density/Sdf.
InOut.RoomCache = &GetCache(); InOut.RoomCache = &GetCache();
InOut.NearestRoomIndex = S.NearestRoom; InOut.NearestRoomIndex = S.NearestRoom;
InOut.bHasTunnelCoreWorldEvaluation = true;
InOut.TunnelCoreWorldSDF = S.LastTunnelCoreWorldEvaluation.SDF;
InOut.bTunnelCoreSupportFloor = S.LastTunnelCoreWorldEvaluation.bSupportFloor;
InOut.bTunnelCoreRoomFloor = S.LastTunnelCoreWorldEvaluation.bRoomFloor;
} }
//--------------------------------------------------------------------- //---------------------------------------------------------------------
@@ -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));
+89 -77
View File
@@ -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,74 +3479,77 @@ 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
auto EvaluatePit = [&](int32 PitIndex) if (!VoxelDensityAblation::IsPitChimneySDFOff())
{ {
const FCachedPit& Pit = SDFCache.Pits[PitIndex]; auto EvaluatePit = [&](int32 PitIndex)
const float DZ = WorldZ - Pit.TopZ; // Negative = below anchor (shaft)
if (DZ >= Pit.BlendK) return; // Above even the blend fringe
if (-DZ > Pit.Depth + Pit.BlendK) return;
const float DX = WorldX - Pit.CenterX;
const float DY = WorldY - Pit.CenterY;
const float XYDistSq = DX * DX + DY * DY;
if (XYDistSq > Pit.BoundXYRadiusSq) return;
float PitSDF;
if (DZ <= 0.0f)
{ {
// Shaft: tapered cylinder with flared opening const FCachedPit& Pit = SDFCache.Pits[PitIndex];
float DepthBelow = -DZ; const float DZ = WorldZ - Pit.TopZ; // Negative = below anchor (shaft)
float FlareFactor = FMath::Clamp(1.0f - DepthBelow / Pit.FlareDist, 0.0f, 1.0f); if (DZ >= Pit.BlendK) return; // Above even the blend fringe
FlareFactor = FlareFactor * FlareFactor; if (-DZ > Pit.Depth + Pit.BlendK) return;
float EffRadius = Pit.Radius + Pit.FlareExtra * FlareFactor;
PitSDF = FMath::Sqrt(XYDistSq) - EffRadius; const float DX = WorldX - Pit.CenterX;
} const float DY = WorldY - Pit.CenterY;
else const float XYDistSq = DX * DX + DY * DY;
if (XYDistSq > Pit.BoundXYRadiusSq) return;
float PitSDF;
if (DZ <= 0.0f)
{
// Shaft: tapered cylinder with flared opening
float DepthBelow = -DZ;
float FlareFactor = FMath::Clamp(1.0f - DepthBelow / Pit.FlareDist, 0.0f, 1.0f);
FlareFactor = FlareFactor * FlareFactor;
float EffRadius = Pit.Radius + Pit.FlareExtra * FlareFactor;
PitSDF = FMath::Sqrt(XYDistSq) - EffRadius;
}
else
{
// Above anchor: only the blend fringe matters here.
// We still pass a SDF so SmoothMin can soften the rim from above.
// Treat this zone as a flat disc at TopZ (XY cylinder, no Z factor)
// so the blend fades horizontally into the room floor.
PitSDF = FMath::Sqrt(XYDistSq) - (Pit.Radius + Pit.FlareExtra);
}
CaveSDF = VoxelSDF::SmoothMin(CaveSDF, PitSDF, Pit.BlendK);
};
VF_ForEachChunkSDFSpatialCandidate(
SDFCache.PitSpatialIndex, SDFCache.Pits.Num(), WorldX, WorldY, EvaluatePit,
bUseSpatialIndex);
auto EvaluateChimney = [&](int32 ChimneyIndex)
{ {
// Above anchor: only the blend fringe matters here. const FCachedChimney& Chim = SDFCache.Chimneys[ChimneyIndex];
// We still pass a SDF so SmoothMin can soften the rim from above. const float DZ = WorldZ - Chim.BottomZ; // Positive = above anchor (shaft)
// Treat this zone as a flat disc at TopZ (XY cylinder, no Z factor) if (-DZ >= Chim.BlendK) return; // Below even the blend fringe
// so the blend fades horizontally into the room floor. if (DZ > Chim.Height + Chim.BlendK) return;
PitSDF = FMath::Sqrt(XYDistSq) - (Pit.Radius + Pit.FlareExtra);
}
CaveSDF = VoxelSDF::SmoothMin(CaveSDF, PitSDF, Pit.BlendK); const float DX = WorldX - Chim.CenterX;
}; const float DY = WorldY - Chim.CenterY;
VF_ForEachChunkSDFSpatialCandidate( const float XYDistSq = DX * DX + DY * DY;
SDFCache.PitSpatialIndex, SDFCache.Pits.Num(), WorldX, WorldY, EvaluatePit, if (XYDistSq > Chim.BoundXYRadiusSq) return;
bUseSpatialIndex);
auto EvaluateChimney = [&](int32 ChimneyIndex) float ChmSDF;
{ if (DZ >= 0.0f)
const FCachedChimney& Chim = SDFCache.Chimneys[ChimneyIndex]; {
const float DZ = WorldZ - Chim.BottomZ; // Positive = above anchor (shaft) float FlareFactor = FMath::Clamp(1.0f - DZ / Chim.FlareDist, 0.0f, 1.0f);
if (-DZ >= Chim.BlendK) return; // Below even the blend fringe FlareFactor = FlareFactor * FlareFactor;
if (DZ > Chim.Height + Chim.BlendK) return; float EffRadius = Chim.Radius + Chim.FlareExtra * FlareFactor;
ChmSDF = FMath::Sqrt(XYDistSq) - EffRadius;
}
else
{
// Below anchor: flat disc blend into room ceiling
ChmSDF = FMath::Sqrt(XYDistSq) - (Chim.Radius + Chim.FlareExtra);
}
const float DX = WorldX - Chim.CenterX; CaveSDF = VoxelSDF::SmoothMin(CaveSDF, ChmSDF, Chim.BlendK);
const float DY = WorldY - Chim.CenterY; };
const float XYDistSq = DX * DX + DY * DY; VF_ForEachChunkSDFSpatialCandidate(
if (XYDistSq > Chim.BoundXYRadiusSq) return; SDFCache.ChimneySpatialIndex, SDFCache.Chimneys.Num(),
WorldX, WorldY, EvaluateChimney, bUseSpatialIndex);
float ChmSDF; }
if (DZ >= 0.0f)
{
float FlareFactor = FMath::Clamp(1.0f - DZ / Chim.FlareDist, 0.0f, 1.0f);
FlareFactor = FlareFactor * FlareFactor;
float EffRadius = Chim.Radius + Chim.FlareExtra * FlareFactor;
ChmSDF = FMath::Sqrt(XYDistSq) - EffRadius;
}
else
{
// Below anchor: flat disc blend into room ceiling
ChmSDF = FMath::Sqrt(XYDistSq) - (Chim.Radius + Chim.FlareExtra);
}
CaveSDF = VoxelSDF::SmoothMin(CaveSDF, ChmSDF, Chim.BlendK);
};
VF_ForEachChunkSDFSpatialCandidate(
SDFCache.ChimneySpatialIndex, SDFCache.Chimneys.Num(),
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,8 +4379,10 @@ 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();
WorldX, WorldY, WorldZ, SDFCache, bUseSpatialIndex); const bool bTunnelSupportFloor = bTunnelCoreEnabled
&& VoxelCaveMorphology::IsTunnelSupportFloorWorldPoint(
WorldX, WorldY, WorldZ, SDFCache, bUseSpatialIndex);
// Keep the direct legacy entry point capability-safe for callers outside the canonical // Keep the direct legacy entry point capability-safe for callers outside the canonical
// operator-stack path; its support band follows the same relieved swept floor. // operator-stack path; its support band follows the same relieved swept floor.
if (bTunnelSupportFloor) if (bTunnelSupportFloor)
@@ -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);
const float ModSDF = EvaluateModifierSDF(WorldX, WorldY, WorldZ); if (!VoxelDensityAblation::IsPassageCarvingOff())
VF_ApplyPassageCarving(Density, ModSDF, BaseDensity, SealThickness); {
const float ModSDF = EvaluateModifierSDF(WorldX, WorldY, WorldZ);
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. && !Passage.bNativeFloorEnabled
|| (bPassageSupportFloorWritesEnabled && VF_IsWalkableTunnelFloor(Passage, Position, this, PassageIndex);
&& !Passage.bNativeFloorEnabled if (!VoxelPassageGeometry::VerticalShaftConnectorAirMarker()
&& VF_IsWalkableTunnelFloor(Passage, Position, this, PassageIndex)))) && (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()
&& !Passage.bNativeFloorEnabled && bPassageSupportFloorWritesEnabled
&& VF_IsWalkableTunnelFloor(Passage, Position, this, PassageIndex)))) && !Passage.bNativeFloorEnabled
&& 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);
+9 -382
View File
@@ -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,
-50
View File
@@ -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
//============================================================================= //=============================================================================
+2 -13
View File
@@ -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)