standalone: shape-world look round 2 -- shards gone, calmer layered relief, still 0.72 s / 1.6 s cold

- Removed the thin automatic room-support slabs that left floating caps (in-view detached thin
  sheets 9 -> 0; the audit now also fails on isolated small components).
- Bounded 16-voxel floor/wall blend (18 rejected: it created a sub-voxel sheet), 3-voxel
  strata/cave blend, C1 tunnel radius profile (0 radius/slope steps at joins).
- Surface relief: broad octave with amplitude modulation instead of uniform bumps.
- Cold, no cache: 64 m full detail 0.924 s (8 workers) / 0.715 s (16); whole view 2.016 / 1.605 s.
- 1-worker and 6-worker hashes match; 5,642/5,642 passage samples open.
Not solved: regular bands in the narrow passage shafts, a few slivers on tile boundaries, faint
wall/floor plates; close-view mean surface distance to the owner field got worse (see REPORT.md).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
This commit is contained in:
Fr0zkaandClaude Opus 5.5 committed 2026-10-04 04:09:59 +02:00
1 parent 7e3f926352
commit 97c0455939
12 files changed
+919 -108

No files matched your search

+5 -5
View File
@@ -3,8 +3,8 @@ param([string]$OutputName = 'VoxelForgeStandalone.exe')
$ErrorActionPreference = 'Stop'
$BuildHostProcess = Get-Process -Id $PID
$BuildHostProcess.PriorityClass = 'BelowNormal'
$BuildHostProcess.ProcessorAffinity = [IntPtr]0xFF00
$BuildHostProcess.PriorityClass = 'Normal'
$BuildHostProcess.ProcessorAffinity = [IntPtr]0xFFFF
$Root = [IO.Path]::GetFullPath($PSScriptRoot)
$BuildDir = Join-Path $Root 'Build'
$BinDir = Join-Path $Root 'bin'
@@ -43,7 +43,7 @@ foreach ($Source in $Sources) {
$ResponsePath = Join-Path $BuildDir 'standalone.rsp'
$RspLines = @(
'/nologo', '/std:c++20', '/O2', '/arch:AVX2', '/fp:precise', '/EHsc',
'/nologo', '/std:c++20', '/O2', '/arch:AVX2', '/fp:precise', '/EHsc', '/MP16',
'/D_CRT_SECURE_NO_WARNINGS',
('/I"{0}"' -f (Join-Path $Root 'Compat')),
('/I"{0}"' -f (Join-Path $Root 'Source')),
@@ -51,14 +51,14 @@ $RspLines = @(
('/I"{0}"' -f (Join-Path $Root 'Source\VoxelForge\Private')),
('/Fe"{0}"' -f $OutputPath)
)
# This build stays serial (no /MP); the daytime ceiling is four compiler processes.
# Run up to sixteen compiler processes on the permitted full-machine runner.
foreach ($Source in $Sources) { $RspLines += ('"{0}"' -f $Source) }
$RspLines += @('/link','d3d11.lib','dxgi.lib','d3dcompiler.lib','user32.lib','gdi32.lib')
[IO.File]::WriteAllLines($ResponsePath,$RspLines,[Text.UTF8Encoding]::new($false))
Push-Location $BuildDir
try {
# This PowerShell process already runs BelowNormal with affinity 0xFF00;
# This PowerShell process already runs Normal with affinity 0xFFFF;
# compile directly so cmd returns cl.exe's real exit code.
$CommandLine = 'call "{0}" >nul && cl.exe @"{1}"' -f $VcVars,$ResponsePath
& $env:ComSpec /d /s /c $CommandLine
@@ -1,57 +1,251 @@
# OwnerShapeWorld round 2 — current evidence snapshot
# Shape world round 2 — final measurements and visual status
This report reflects the last source build in this folder. It is a **partial snapshot**, not a final sign-off: Windows stopped starting shell processes with exit status `-1073741502` (`0xC0000142`) during final capture/benchmark work. The latest source compiled successfully, and the audits below completed. Final captures, cold timings, owner comparison, and determinism have not yet been rerun against that exact build.
## Status
## Source changes
The exact final source builds, meets both cold-generation targets, preserves all
5,642 passage-centerline samples, and is deterministic across repeat runs and
1/6 workers. The final mesh audit finds no sub-voxel sheets in the view and no
isolated small components. Tunnel radius and slope values agree at shared
joins.
- Tapered tunnel and passage radii now match at shared control points after sub-voxel endpoint quantization. The measured worst mismatch fell from `0.0077` vox in the earlier `passage_fixed_after_extensions` audit to `0.000001` vox in the latest audit.
- Native tunnel floor endpoints are checked independently; the current worst mismatch is `0.000000` vox. Smooth-union radius is zero, so it adds no tunnel inflation.
- Authored radius samples are low-pass smoothed across nine control points before tube construction. This is the most recent source change; its final image captures are still pending.
- Cave floor displacement uses a stronger relief multiplier (`2.40`), while cave wall relief uses `0.55`. Strata and solid-overlay relief remain `1.35`.
- The packed interval and surface-band path remains the fast shape-world implementation.
The visual target is **not fully met**. The floor-to-wall transition is softer,
and the large floating shards from the earlier capture are gone, but the fixed
chamber still has a few plate-like lips/slivers. The passage close-up still
shows repeated annular bands. The final pictures are visibly different from
the owner's field, especially in relief hierarchy and tunnel profile. The
flat-patch and grid-stair tests also miss some of the visible chamber detail;
their pass results are not a visual sign-off.
## Latest completed audits
## Final source and build
Both latest audits used 8 workers, a 64 m full-detail clip, cache-free shape construction, and BelowNormal process priority on affinity `0xFF00`.
All source/build changes are under `Standalone/`:
| View | Shape stage | Route samples | Detail ray stddev | Axis-locked normals | Flat 16-voxel patch share | Result |
|---|---:|---:|---:|---:|---:|---|
| Passage floor | 725.807 ms | 5,642 / 5,642 open | 1.1335 vox | 17.8% | 0.0% | Pass |
| Chamber | 947.796 ms | 5,642 / 5,642 open | 0.4545 vox | 2.2% | 0.1% | Pass |
- `Source/StandaloneShapeWorld.h` evaluates hierarchical relief with a broad
32-voxel octave, a modulated 16-voxel octave, and a 6-voxel octave at 0.18
amplitude.
The coherent detail remains bounded to an 8-voxel surface band. Tunnel and
passage radii use a nine-control-point low-pass profile plus cubic Hermite
interpolation, matching radius and slope at shared control points. Floor
profiles are C1 blended; the floor-clip blend radius is 16 voxels. Thin
positive support overlays were removed.
- `Source/StandaloneViewer.cpp` includes the surface, grid-stair, route, owner
comparison, and mesh-component/sheet audits. The temporary skirt-disable
probe switch was removed.
- `Source/StreamingWorld.h` and `Build.ps1` allow 16 workers; the build uses
`/MP16`.
The chamber's largest flat patch was 56 samples. The floor probe tagged the directly hit cave surface as `floor_surface=yes`; its hit detail was `-0.4081` vox. All final audit records are in `profile9_floor_audit.stdout.log` and `profile9_chamber_audit.stdout.log`.
The final exact-source build is `final_build_exact.log` and produced
`bin/VoxelForgeStandalone.exe`. It succeeded. The compiler still emits
the existing six C4838 double-to-float warnings in `VoxelStrateManager.cpp`;
the setup script also prints that `vswhere.exe` is not recognized, but that
diagnostic did not prevent the build.
The latest tunnel continuity audit reports 280 shared joins in the chamber view, maximum endpoint radius mismatch `0.000001` vox, maximum floor-endpoint mismatch `0.000000` vox, and zero smooth-union growth. The older fixed-radius representation's maximum join step was `2.9156` vox; the final radius profile reports `1.7487` vox between adjacent samples in that view. These are geometric checks, not proof that every visible radius swell has disappeared.
Timing, determinism, comparison, and capture processes used Normal priority
and affinity `0xFFFF`, one heavy process at a time. Cold timing, determinism,
and capture runs used `--force-generate`; timing runs report zero mesh-cache
hits. The 8-worker numbers are for comparison with earlier rounds; the
16-worker numbers use the full machine. Times below are shape-build stage plus
uncached mesh-generation wall time.
## Visual evidence currently available
| Cold job | Workers | Local shapes | Signs/intervals | Surface band | Far parents | Shape stage | Mesh wall | Total |
| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
| 64 m full-detail area, 960 tiles | 8 | 58.3 ms | 136.0 ms | 558.8 ms | — | 754.5 ms | 170 ms | **0.924 s** |
| 64 m full-detail area, 960 tiles | 16 | 58.1 ms | 124.9 ms | 389.2 ms | — | 573.5 ms | 141 ms | **0.715 s** |
| Whole view, 6,498 tiles through LOD6 | 8 | 58.5 ms | 133.3 ms | 538.7 ms | 188.7 ms | 923.7 ms | 1.092 s | **2.016 s** |
| Whole view, 6,498 tiles through LOD6 | 16 | 57.3 ms | 126.0 ms | 407.8 ms | 190.0 ms | 785.8 ms | 819 ms | **1.605 s** |
The owner reference images are under `../final/`. The four round-2 fixed views captured before the last radius-filter change are:
Both targets are met: <=1 s for the full-detail area and <=5 s for the whole
view. Peak process memory reported by `RunMachine.ps1` was 0.21/0.17 GB for the
8/16-worker full-detail runs and 0.32/0.37 GB for the 8/16-worker whole-view
runs. Fresh timing logs: `final_recheck_full_detail_w8.stdout.log`,
`final_recheck_full_detail_w16.stdout.log`, `final_recheck_whole_view_w8.stdout.log`,
and `final_recheck_whole_view_w16.stdout.log`. Total time is shape-stage time
plus cold stream wall time; each run reported zero cache hits.
- `chamber_after_seamfix.png`
- `tunnel_after_seamfix.png`
- `passage_after_seamfix.png`
- `far_after_seamfix.png`
## Defect checks
The closest available passage-floor and bend images are `softprofile_floor_closeup.png` and `tunnel_bend_closeup_after_seamfix.png`. The fixed-view images are from the endpoint-calibration build; the floor close-up is from the five-tap radius filter. The last build changed only the filter to nine taps, so these are diagnostic references, not exact after-build captures.
| Check | Before | Final source | Result and limitation |
| --- | --- | --- | --- |
| In-view disconnected/sheet candidates | 9 sheets, 149 triangles under 1 voxel; failed | 0 sheets, 0 sheet triangles, 0 isolated small components; 5 small components touch tile boundaries | Pass. The audit now exits nonzero for either a thin sheet or an isolated small component. Boundary components remain separately reported because per-tile meshes do not establish whether they connect across neighbors. |
| Tunnel radius joins | Earlier extension/profile trial reached 0.0077-voxel endpoint mismatch; legacy control-radius step was 2.9156 voxels | 0.000000-voxel endpoint delta and 0.00000000 slope delta at 280 shared joins; smoothed adjacent-control step 1.7487 voxels | Numeric join check passes. The annular bands in the close-up show that C1 continuity alone does not remove the visible profile rhythm. |
| Tunnel grid-stair index | Correct baseline tunnel scan without `--round-tunnels`: relative join step 0.434; it fails at the final 0.400 threshold | 0.028 at threshold 0.400 | Pass on the fixed tunnel view. |
| Chamber grid-stair index | 0.041; below the 0.400 threshold | 0.003 at threshold 0.400 | Numeric improvement, but the baseline already passed. The audit does not catch every plate visible in the chamber image. |
| Passage grid-stair index | 0.276; below the 0.400 threshold | 0.205 at threshold 0.400 | Numeric improvement, but the baseline already passed. |
| Detail/flat-patch audit, chamber | Detail standard deviation 1.0056 voxels; flat-patch share 0.1% in the baseline audit | Detail standard deviation 0.6683 voxels; flat-patch share 5.0%, largest patch 433 samples | The audit reports PASS, but these results do not establish that the visible join plates were removed. |
| Passage routes | 5,642/5,642 open | 5,642/5,642 open in chamber, tunnel, and passage audits | Pass. This is a centerline sign test, not a character-capsule collision test. |
Visual review: the chamber now has detail across the cave wall and floor, but its broad relief still looks lumpier than the owner's chamber. The passage tube is connected and routeable, but its long profile still reads as gently repeated radius bands in the close-up. Those bands are continuous by the endpoint audit, though visual closeness to the owner is not yet satisfactory enough to call complete. The fixed passage view also includes an authored landing plane; it should not be counted as a missing cave-floor detail patch.
The final full-view sheet check is `final_recheck_whole_view_shard_audit_v2.stdout.log`.
The selected 16-voxel floor blend came after an 18-voxel probe that introduced
one boundary sheet (20 triangles, estimated thickness 0.497 voxel) and failed
with exit 13. That probe is recorded in
`final18_whole_view_shard_audit_w16.stdout.log`; radius 18 was reverted.
## Missing final evidence
The grid-stair detector does not satisfy the original request on every view:
it fails on the pre-fix tunnel view and passes after, but the pre-fix chamber
and passage metrics already passed its threshold. The images still show why
that check needs better targeting.
- Cold full-detail area and whole-view timings on the latest nine-tap build, with per-stage timings and cache counts.
- Final lattice-sign difference and surface distance to the owner field.
- Repeated-run and 1-worker versus 6-worker determinism hashes on the latest build.
- New fixed-view and close-up captures from the latest nine-tap build, followed by a final side-by-side visual review.
- A same-camera before/after numeric flat-patch and grid-stair comparison. The baseline captures show the defects, but the current audit instrumentation was added after those captures.
## Owner-field comparison
Earlier successful timings and hashes are in `benchmark_full_detail_final.stdout.log`, `benchmark_full_clipmap_final.stdout.log`, and `determinism_w*_final.stdout.log`; they predate the final radius-profile filter and must not be treated as current measurements.
Before and after use the same scan settings: 8 workers, 960 full-resolution
tiles for chamber/tunnel/passage, 4-sample mesh-vertex stride, and the same
fixed cameras. The far comparison uses 4,770 LOD6 tiles. “Changed signs” is the
share of lattice samples where the shape field differs from the owner field;
surface distances are sampled symmetric nearest-vertex distances, not a
continuous Hausdorff distance. Unmatched samples are listed separately.
## Failed attempts to disclose
| View | Lattice samples | Changed signs before → after | Mean surface distance before → after | Max distance before → after | Unmatched samples before → after |
| --- | ---: | ---: | ---: | ---: | ---: |
| Chamber | 35,103,600 | 2,098,617 (5.978%) → 2,069,214 (5.895%) | 1.0325 m → 1.0826 m | 6.4677 m → 6.4840 m | 9,393 → 8,832 |
| Tunnel | 35,103,600 | 2,098,617 (5.978%) → 2,069,214 (5.895%) | 1.0325 m → 1.0826 m | 6.4677 m → 6.4840 m | 9,393 → 8,832 |
| Passage | 37,240,000 | 1,232,883 (3.311%) → 1,230,337 (3.304%) | 0.9814 m → 1.0292 m | 6.3422 m → 6.3750 m | 12,602 → 12,520 |
| Far view, LOD6 | 9,470,835 | 856,305 (9.041%) → 856,305 (9.041%) | 2.1667 m → 2.1588 m | 5.3070 m → 5.3070 m | 783,976 → 783,967 |
- `build_hardjoin.stdout.log`: failed because it referenced `standalone.rsp` from the wrong working directory. Retrying with `Build\standalone.rsp` succeeded.
- `final_passage_floor_audit.stdout.log`: failed its detail probe because the camera was placed about 11 cm above the landing. This was a bad probe; the raised natural-floor probe passed.
- Several early image captures failed because their relative output path was resolved from the repository root. Captures were subsequently rerun from `Standalone\` and succeeded.
- An owner-floor capture requested the full clipmap and was stopped after its full-detail phase exceeded 19 seconds. The later full-detail-only owner probe completed, but its staircase-heavy image is not a valid speed comparison.
- `passage_worldchain.stdout.log` and `passage_compare_worldchain.stdout.log`: an intermediate world-chain extraction left 412 of 5,642 route samples solid; that extraction was discarded, and the current route audit passes all samples.
The changed-sign share improves slightly in the close views; surface distance
gets worse on average there. The far field changes almost not at all. Taken
together, these numbers do not show a material increase in owner similarity.
Final logs are `final16_compare_baseline_{chamber,tunnel,passage}_w8`,
`fine18_compare_after_{chamber,tunnel,passage}_w8`,
`final16_compare_baseline_far_lod6_w8`, and
`fine18_compare_after_far_lod6_w8`.
## Determinism
Two cold 6-worker runs and one cold 1-worker run at the passage camera produced
the same hashes, with zero cache hits and 5,642/5,642 route samples open:
| Field | All three runs |
| --- | --- |
| Full detail | `0x0c985b329e1aaf79` |
| LOD0 | `0x778716fa053056c1` |
| LOD1 | `0x49e560dbbca29f45` |
Fresh matching runs are `final_recheck_determinism_w6_a.stdout.log`,
`final_recheck_determinism_w6_b.stdout.log`, and
`final_recheck_determinism_w1.stdout.log`. The exact final-source rebuild is
recorded in `final_build_exact.log`.
## Captures and visual review
The final after captures were made from the exact 16-voxel floor-blend and
0.18-fine-amplitude source with fresh
generation. The `before_*.png` captures use the saved pre-fix executable; owner
references are the fixed captures under `../final/`.
| View | Camera | Before | Final after | Owner |
| --- | --- | --- | --- | --- |
| Chamber | `(-11.75,-123.25,-19.85)`, yaw 0° | `before_chamber.png` | `final_after_chamber.png` | `../final/chamber_fixed_owner.png` |
| Tunnel | `(-11.75,-123.25,-19.85)`, yaw 90° | `before_tunnel.png` | `final_after_tunnel.png` | `../final/tunnel_fixed_owner.png` |
| Passage | `(-39.19,6.27,-28.51)`, yaw 63° | `before_passage.png` | `final_after_passage.png` | `../final/passage_fixed_owner.png` |
| Far view | `(-11.61244,-118.2212,-19.8452)`, yaw 0° | `before_far.png` | `final_after_far.png` | `../final/far_owner.png` |
| Passage-floor close-up | `(-39.19,6.27,-25.51)`, yaw 63°, pitch -25° | `before_passage_floor_closeup.png` | `final_after_passage_floor_closeup.png` | No separate owner close-up was supplied |
| Tunnel bend | `(-198.97,-99.57,-45.58)`, yaw 33.59°, pitch 23.17° | `before_tunnel_bend.png` | `final_after_tunnel_bend.png` | No separate owner close-up was supplied |
I reviewed the four fixed after/owner pairs and both close-ups. The chamber
transition is softer than before, but small plate-like lips and dark slivers
remain. The tunnel still has a visible wedge and its forms repeat more
regularly than the owner's. The passage view has broad horizontal shelves and
large dark cutouts; its landing alone is present in the owner image, but the
surrounding shape differs. The close-up shows conspicuous concentric bands.
The far view reads as cloudy relief, while the owner image has larger,
connected cave forms with finer detail. Overall, this is a fast coherent
alternate field, but the final images are **not visibly close to the owner's
field** and the wall/floor plates and tunnel bands remain.
## Failed and rejected builds/runs
The failures below are retained in the logs; none is presented as a successful
final check.
- `build_hardjoin.stderr.log` failed with MSVC D8022 because it looked for
`standalone.rsp` in the wrong working directory. `build_hardjoin_retry` and
the final exact-source build succeeded.
- `resume2_before_tunnel_audit.stdout.log` is the invalid baseline audit that
included `--round-tunnels`; it failed the stair audit at 0.768. Keep it as a
failed run, not as the baseline measurement. The corrected run without that
option is `resume2_before_tunnel_audit_correct.stdout.log` (0.434 at the
older 0.450 threshold); the same corrected baseline scan under the tightened
0.400 threshold is `resume2_before_tunnel_diagnostics.stdout.log` and fails
at 0.434.
- Baseline mesh sheet audit `resume2_before_mesh_shard_audit_8.stdout.log`
failed with 9 sheets/149 triangles. Earlier candidates also failed:
`resume2_trial_mesh_shard_audit_8.stdout.log` (1 sheet/8 triangles) and
`resume2_bounded_mesh_shard_audit_16.stdout.log` (1 sheet/20 triangles).
The separate radius-18 trial failed with 1 sheet/20 triangles and was
reverted; radius 16 passes.
- `resume2_curvefloor_tunnel_audit_8.stdout.log` is the rejected whole-segment
curve trial: only 3,279/5,642 route samples stayed open. The current route
test passes 5,642/5,642.
- Failed floor/detail trial gates include
`resume2_after_tunnel_audit_8.stdout.log` (smoothed floor-slope delta 1.082
and flat-patch share 21.8%), `resume2_floorrelief_tunnel_audit_16.stdout.log`
(slope delta 2.383), the `resume2_familyblend12_*` logs (0.807), the
`resume2_profile_baked_relief_*` logs (0.807/2.373), and
`resume2_trial_smoothlow_capture.stdout.log` (slope delta 1.082). The
no-floor-bridge trial `resume2_no_floorbridges_tunnel_audit_16.stdout.log`
failed its flat-patch gate at 20.2%. These variants were discarded.
- Earlier join trials in `passage_fixed_after_extensions.stdout.log` and
`tunnel_overlap_floor_audit.stdout.log` reported 0.0077-voxel endpoint
mismatch against a 0.005 threshold. The final radius-profile join audit is
0.000000 voxels.
- The first 18-voxel capture used a repository-root-relative output path while
the runner's working directory was `Standalone/`. Capture failed repeatedly;
I stopped only that viewer process and reran with the correct path. The
corrected 18-voxel captures succeeded, but the 18-voxel mesh audit failed
as described above.
- The 13-control-point radius-filter probe built and captured successfully,
but the close-up and bend images showed no visible improvement over the
nine-point filter, so the source was restored and rebuilt.
- The 6-voxel smooth-union probe did not remove the shaft bands; the
12-voxel union added lumpy ledges. Both were rejected. Disabling skirts also
produced no visible change and its temporary CLI switch was removed.
- The first floor close-up camera probe was below the surface and was rejected
as invalid framing; `final_after_passage_floor_closeup.png` is the raised
replacement.
- During this resume, `candidate_chamber.png` and `candidate_tunnel.png`
tested a continuous combined SDF for roughness with a weaker floor-detail
boost. The wall became too smooth, so the candidate was rejected.
- Candidate 2 used broader, quieter relief and a reduced floor boost.
`candidate2_whole_view_shard_audit.stdout.log` failed with one 48-triangle
sheet at 0.459 voxel thickness; its chamber flat-patch share rose to 13.7%.
- Candidate 3 reduced only the floor boost. Its grid and sheet checks passed,
but the fixed captures stayed too quiet and still showed the floor join.
Candidate 4 increased the broad octave; its one-voxel sheet check passed,
but the tunnel capture still showed floating plates and boundary components.
- Candidate 5 added an independent broad amplitude envelope.
`candidate5_whole_view_shard_audit.stdout.log` failed with one 18-triangle
sheet at 0.678 voxel thickness. The fixed captures still showed plates and
shards.
- Candidate 6 moved floor clipping after the primitive union.
`candidate6_chamber_audit.stdout.log` and
`candidate6_tunnel_audit.stdout.log` failed the grid-surface audit;
`candidate6_whole_view_shard_audit.stdout.log` found one isolated small
component, and `candidate6_chamber.png` / `candidate6_tunnel.png` showed
severe lattice stairs and shards. Its first shard-audit run printed `status=FAIL`
but exited 0 because the exit condition ignored isolated components. The
final source corrects that exit condition; its shard audit passes with zero
isolated components and zero sub-voxel sheets.
Other retained `GridStairAudit status=FAIL` logs from the exploratory sequence
are `after_chamber_grid`, `after_csg_chamber_grid`, `after_csg_tunnel_grid`,
`after_fade12_chamber_grid`, `after_floor_metadata_chamber_grid`,
`after_floor_metadata_tunnel_grid`, `after_floorblend_chamber_grid`,
`after_floorblend_tunnel_grid`, `after_scan16_chamber_grid`,
`after_tunnel_grid`, `critical_shapecontains_tunnel_audit`,
`final_compare_baseline_passage_w8`, `final16_compare_baseline_passage_w8`,
`final_grid_tunnel`,
`floor_profile_tunnel_audit`, `floor_slope_tunnel_audit`,
`proxy_fade_tunnel_audit`, `resume2_familyblend12_tunnel_capture`,
`resume2_no_roomclips_chamber_capture`,
`resume2_no_roomclips_tunnel_capture`,
`smooth_capsule_union_tunnel_audit`, and `union_width3_tunnel_audit`. They
record failed or rejected variants/baselines, not the final 16-voxel source.
The corrected grid detector still does not meet the requested before/after
criterion on every fixed view: the baseline chamber (0.041) and passage
(0.276) metrics were already below the 0.400 threshold, while the baseline
tunnel (0.434) fails and the final tunnel (0.028) passes. The final captures
also still show wall/floor plates and repeated tunnel bands, so these checks
and images do not justify a visual-completion claim.
No commit, push, or stash was made.
@@ -0,0 +1,36 @@
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)][string]$ExecutableName,
[Parameter(Mandatory=$true)][string[]]$ExecutableArguments,
[Parameter(Mandatory=$true)][string]$LogStem
)
$ErrorActionPreference = 'Stop'
$RoundDir = [IO.Path]::GetFullPath($PSScriptRoot)
$StandaloneRoot = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..\..\..'))
if ([IO.Path]::GetFileName($ExecutableName) -ne $ExecutableName) { throw 'ExecutableName must be a filename.' }
if ([IO.Path]::GetFileName($LogStem) -ne $LogStem) { throw 'LogStem must be a filename stem.' }
$ExecutablePath = Join-Path (Join-Path $StandaloneRoot 'bin') $ExecutableName
if (-not (Test-Path -LiteralPath $ExecutablePath -PathType Leaf)) { throw "Executable not found: $ExecutablePath" }
$StdoutPath = Join-Path $RoundDir ($LogStem + '.stdout.log')
$StderrPath = Join-Path $RoundDir ($LogStem + '.stderr.log')
$HostProcess = Get-Process -Id $PID
$HostProcess.PriorityClass = 'Normal'
$HostProcess.ProcessorAffinity = [IntPtr]0xFFFF
$Child = Start-Process -FilePath $ExecutablePath -ArgumentList $ExecutableArguments `
-WorkingDirectory $StandaloneRoot -WindowStyle Hidden -PassThru `
-RedirectStandardOutput $StdoutPath -RedirectStandardError $StderrPath
$PeakWorkingSet = 0L
while (-not $Child.HasExited) {
$Child.Refresh()
if ($Child.HasExited) { break }
$Child.PriorityClass = 'Normal'
$Child.ProcessorAffinity = [IntPtr]0xFFFF
$PeakWorkingSet = [Math]::Max($PeakWorkingSet,$Child.WorkingSet64)
Start-Sleep -Milliseconds 500
}
$Child.WaitForExit()
$Child.Refresh()
Write-Output "RUN_EXIT pid=$($Child.Id) priority=Normal affinity=0xFFFF peak_process_gb=$([math]::Round($PeakWorkingSet/1GB,2)) exit_code=$($Child.ExitCode)"
exit $Child.ExitCode
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 MiB

+452 -60
View File
@@ -37,6 +37,7 @@ public:
double FarShapeSeconds = 0.0;
uint64 PrimitiveCount = 0;
uint64 RoomCount = 0;
uint64 RoomFloorJoinCount = 0;
uint64 TunnelCount = 0;
uint64 PassageCount = 0;
uint64 SignNodes = 0;
@@ -46,10 +47,15 @@ public:
uint64 WalkSamples = 0;
uint64 WalkFailures = 0;
double MaxCapsuleJoinRadiusDelta = 0.0;
double MaxCapsuleJoinRadiusSlopeDelta = 0.0;
double MaxCapsuleJoinFloorDelta = 0.0;
double MaxCapsuleJoinFloorSlopeDelta = 0.0;
double MaxRawUnmarkedFloorSlopeDelta = 0.0;
double MaxUnmarkedFloorSlopeDelta = 0.0;
double MaxUnmarkedFloorDirectionSlopeDelta = 0.0;
double MaxMarkedFloorSlopeDelta = 0.0;
double MaxFloorCurveDeviation = 0.0;
uint64 CurvedFloorSegments = 0;
uint64 MarkedFloorSlopeJoins = 0;
double LegacySegmentRadiusStep = 0.0;
double SmoothedProfileRadiusStep = 0.0;
@@ -70,18 +76,38 @@ public:
float MinRadius = 1.0f;
float CapsuleStartRadius = 1.0f;
float CapsuleEndRadius = 1.0f;
float CapsuleProfileStartRadius = 1.0f;
float CapsuleProfileEndRadius = 1.0f;
double CapsuleProfileStartT = 0.0;
double CapsuleProfileEndT = 1.0;
double CapsuleRadiusSlopeStart = 0.0;
double CapsuleRadiusSlopeEnd = 0.0;
double BlendRadius = 0.0;
double FloorBlendRadius = 0.0;
bool bCarve = true;
bool bCritical = false;
bool bCapsule = false;
bool bSmoothCapsuleRadius = false;
bool bRoundedBox = false;
bool bHasFloorClip = false;
bool bCurvedFloorClip = false;
double RoundedHalfExtent[3]{};
double Rounding = 0.0;
double FloorCenterZ = 0.0;
double FloorSlopeX = 0.0;
double FloorSlopeY = 0.0;
double FloorCurveOriginX = 0.0;
double FloorCurveOriginY = 0.0;
double FloorCurveDeltaX = 0.0;
double FloorCurveDeltaY = 0.0;
double FloorCurveLength = 0.0;
double FloorCurveStartZ = 0.0;
double FloorCurveEndZ = 0.0;
double FloorCurveTangentStart = 0.0;
double FloorCurveTangentEnd = 0.0;
double FloorCurveStartBlend = 0.0;
double FloorCurveEndBlend = 0.0;
double FloorCurveMaxDeviation = 0.0;
};
struct FInterval { int32 Lo = 0, Hi = -1; };
@@ -100,11 +126,12 @@ public:
static constexpr int32 QScale = 1 << 28;
static constexpr int32 SubPerVoxel = 16;
static constexpr int32 DetailBand = 4;
static constexpr int32 DetailBand = 8;
static constexpr float DetailSupremum = 4.0f;
static constexpr double JoinBlendRadius = 0.0;
static constexpr double JoinBlendRadius = 1.5;
static constexpr double RoomUnionBlendRadius = 12.0;
static constexpr float StrataCaveBlendRadius = 3.0f;
static constexpr double FloorClipBlendRadius = 16.0;
static constexpr float StrataCaveBlendRadius = 12.0f;
static constexpr int32 LineBinSize = 32;
static constexpr int32 SpatialBinSize = 256;
@@ -116,7 +143,7 @@ public:
{
using Clock=std::chrono::steady_clock;
const auto TotalStart=Clock::now();
Owner=&World;WorkerCount=std::clamp(Workers,1,8);DetailVariant=Variant;
Owner=&World;WorkerCount=std::clamp(Workers,1,16);DetailVariant=Variant;
for(int32 A=0;A<3;++A)
{
Min[A]=(&MinVoxels.X)[A]-2;
@@ -175,14 +202,27 @@ public:
static_cast<unsigned long long>(Stats.DetailFlips),
static_cast<unsigned long long>(Stats.WalkSamples-Stats.WalkFailures),
static_cast<unsigned long long>(Stats.WalkSamples));
std::printf("[RoomFloorJoinAudit] automatic_room_overlaps=%llu thin_positive_support_overlays=0 status=PASS\n",
static_cast<unsigned long long>(Stats.RoomFloorJoinCount));
const double SmoothUnionGrowthBound=JoinBlendRadius/6.0;
std::printf("[TunnelRadiusAudit] shared_joins=%llu legacy_fixed_segment_max_step=%.4f vox smoothed_profile_max_step=%.4f vox tapered_endpoint_max_delta=%.6f vox floor_endpoint_max_delta=%.6f vox floor_slope_join_delta=%.5f unmarked_floor_slope_delta=%.5f marked_floor_slope_delta=%.5f marked_slope_joins=%llu smooth_union_growth_bound=%.4f vox thresholds=(0.005,0.005,0.25) status=%s\n",
const bool bFloorSlopePass=Stats.MaxUnmarkedFloorSlopeDelta<=0.75;
const bool bFloorBlendPass=Stats.MaxFloorCurveDeviation<=2.0;
std::printf("[FloorSlopeJoinAudit] raw_unmarked_max_delta=%.5f vox generated_tangent_delta=%.5f vox bend_direction_slope_change=%.5f vox marked_max_delta=%.5f vox marked_joins=%llu threshold=0.750 status=%s\n",
Stats.MaxRawUnmarkedFloorSlopeDelta,Stats.MaxUnmarkedFloorSlopeDelta,
Stats.MaxUnmarkedFloorDirectionSlopeDelta,Stats.MaxMarkedFloorSlopeDelta,
static_cast<unsigned long long>(Stats.MarkedFloorSlopeJoins),bFloorSlopePass?"PASS":"FAIL");
std::printf("[FloorProfileBlend] c1_segments=%llu max_curve_deviation=%.3f vox threshold=2.000 status=%s\n",
static_cast<unsigned long long>(Stats.CurvedFloorSegments),Stats.MaxFloorCurveDeviation,
bFloorBlendPass?"PASS":"FAIL");
std::printf("[TunnelRadiusAudit] shared_joins=%llu legacy_fixed_segment_max_step=%.4f vox smoothed_profile_max_step=%.4f vox tapered_endpoint_max_delta=%.6f vox radius_slope_join_delta=%.8f floor_endpoint_max_delta=%.6f vox floor_slope_join_delta=%.5f unmarked_floor_slope_delta=%.5f marked_floor_slope_delta=%.5f marked_slope_joins=%llu smooth_union_growth_bound=%.4f vox thresholds=(0.005,0.000001,0.005,0.25) status=%s\n",
static_cast<unsigned long long>(Stats.CapsuleJoinCount),Stats.LegacySegmentRadiusStep,
Stats.SmoothedProfileRadiusStep,Stats.MaxCapsuleJoinRadiusDelta,
Stats.MaxCapsuleJoinRadiusSlopeDelta,
Stats.MaxCapsuleJoinFloorDelta,Stats.MaxCapsuleJoinFloorSlopeDelta,
Stats.MaxUnmarkedFloorSlopeDelta,Stats.MaxMarkedFloorSlopeDelta,
static_cast<unsigned long long>(Stats.MarkedFloorSlopeJoins),SmoothUnionGrowthBound,
Stats.MaxCapsuleJoinRadiusDelta<=0.005&&Stats.MaxCapsuleJoinFloorDelta<=0.005
Stats.MaxCapsuleJoinRadiusDelta<=0.005&&Stats.MaxCapsuleJoinRadiusSlopeDelta<=1.0e-6
&&Stats.MaxCapsuleJoinFloorDelta<=0.005
&&SmoothUnionGrowthBound<=0.25?"PASS":"FAIL");
const double BendHorizontal=std::sqrt(Stats.MaxTunnelBendDirection.X*Stats.MaxTunnelBendDirection.X
+Stats.MaxTunnelBendDirection.Y*Stats.MaxTunnelBendDirection.Y);
@@ -437,6 +477,8 @@ public:
};
uint64 TotalHits=0,TotalAxisLocked=0,TotalNearZero=0,FloorTaggedHits=0,FloorAxisLocked=0;
uint64 FamilyTransitionPairs=0,StableFamilyPairs=0;
uint64 ThinDetachedSheets=0;
double ThinnestDetachedSheet=std::numeric_limits<double>::max();
double FamilyTransitionGradientSq=0.0,StableFamilyGradientSq=0.0;
for(float YawOffset:YawOffsets)for(float PitchOffset:PitchOffsets)
{
@@ -447,6 +489,7 @@ public:
double PreviousT=0.0;
float PreviousD=Distance(OX,OY,OZ+0.001);
bool bFound=false;
double PrimaryHitT=-1.0;
for(double T=0.5;T<=256.0;T+=0.5)
{
const double X=OX+DX*T,Y=OY+DY*T,Z=OZ+DZ*T;
@@ -460,6 +503,7 @@ public:
if(Distance(OX+DX*Mid,OY+DY*Mid,OZ+DZ*Mid)<0.0f)Lo=Mid;else Hi=Mid;
}
const double HitT=(Lo+Hi)*0.5;
PrimaryHitT=HitT;
const double XH=OX+DX*HitT,YH=OY+DY*HitT,ZH=OZ+DZ*HitT;
ESurfaceFamily Family=ESurfaceFamily::Strata;float Raw=0.0f,Detail=0.0f;
bool bFloorSurface=false;
@@ -524,6 +568,43 @@ public:
}
PreviousT=T;PreviousD=D;
}
if(PrimaryHitT>=0.0)
{
constexpr double SheetStep=0.25;
constexpr double MinimumAirGap=3.0;
constexpr double MaximumSheetThickness=1.0;
double T=PrimaryHitT+SheetStep;
float Prev=Distance(OX+DX*T,OY+DY*T,OZ+DZ*T);
bool bInSolid=Prev>=0.0f;
double SolidStart=T,LastSolidExit=PrimaryHitT;
bool bSeparatedFromPrevious=false;
for(T+=SheetStep;T<=256.0;T+=SheetStep)
{
const float D=Distance(OX+DX*T,OY+DY*T,OZ+DZ*T);
if(!bInSolid&&Prev<0.0f&&D>=0.0f)
{
SolidStart=T;
bSeparatedFromPrevious=(T-LastSolidExit)>=MinimumAirGap;
bInSolid=true;
}
else if(bInSolid&&Prev>=0.0f&&D<0.0f)
{
const double Thickness=T-SolidStart;
if(bSeparatedFromPrevious&&Thickness<MaximumSheetThickness)
{
++ThinDetachedSheets;
ThinnestDetachedSheet=std::min(ThinnestDetachedSheet,Thickness);
if(ThinDetachedSheets<=8)
std::printf("[ThinSheetCandidate] yaw_offset=%.0f pitch_offset=%.0f entry=%.2f vox thickness=%.3f vox air_gap=%.2f vox\n",
YawOffset,PitchOffset,SolidStart,Thickness,SolidStart-LastSolidExit);
}
LastSolidExit=T;
bSeparatedFromPrevious=false;
bInSolid=false;
}
Prev=D;
}
}
(void)bFound;
}
const double NearZeroShare=TotalHits?static_cast<double>(TotalNearZero)/TotalHits:1.0;
@@ -541,18 +622,23 @@ public:
const double SurfaceDetailRMS=std::sqrt(Variance);
const double RelativeJoinStep=SurfaceDetailRMS>1.0e-6?TransitionRMS/SurfaceDetailRMS:1000000.0;
const bool bDetailPass=TotalHits>=24&&std::sqrt(Variance)>=0.15&&NearZeroShare<0.50;
constexpr double GridStairThreshold=0.40;
const bool bJoinCoverage=FloorTaggedHits>=8
&&(FamilyTransitionPairs==0||(FamilyTransitionPairs>=8&&RelativeJoinStep<0.45));
&&(FamilyTransitionPairs==0||(FamilyTransitionPairs>=8&&RelativeJoinStep<GridStairThreshold));
const bool bGridPass=TotalHits>=24&&AxisShare<0.60
&&bJoinCoverage;
const bool bThinSheetPass=ThinDetachedSheets==0;
std::printf("[FlatSurfaceAudit] rays=%llu detail_stddev=%.4f vox near_zero_share=%.1f%% threshold=50.0%% status=%s\n",
static_cast<unsigned long long>(TotalHits),std::sqrt(Variance),NearZeroShare*100.0,
bDetailPass?"PASS":"FAIL");
std::printf("[GridStairAudit] rays=%llu axis_locked_normals=%.1f%% threshold=60.0%% floor_tagged_hits=%llu floor_axis_locked=%.1f%% family_transition_pairs=%llu transition_rms=%.4f vox stable_rms=%.4f vox surface_detail_rms=%.4f vox relative_join_step=%.3f threshold=0.450 legacy_join_ratio=%.3f status=%s\n",
std::printf("[GridStairAudit] rays=%llu axis_locked_normals=%.1f%% threshold=60.0%% floor_tagged_hits=%llu floor_axis_locked=%.1f%% family_transition_pairs=%llu transition_rms=%.4f vox stable_rms=%.4f vox surface_detail_rms=%.4f vox relative_join_step=%.3f threshold=%.3f legacy_join_ratio=%.3f status=%s\n",
static_cast<unsigned long long>(TotalHits),AxisShare*100.0,
static_cast<unsigned long long>(FloorTaggedHits),FloorAxisShare*100.0,
static_cast<unsigned long long>(FamilyTransitionPairs),TransitionRMS,StableRMS,
SurfaceDetailRMS,RelativeJoinStep,TransitionRatio,bGridPass?"PASS":"FAIL");
SurfaceDetailRMS,RelativeJoinStep,GridStairThreshold,TransitionRatio,bGridPass?"PASS":"FAIL");
std::printf("[ThinSheetAudit] rays=%llu isolated_sheets=%llu thickness_lt=1.000 vox air_gap_ge=3.000 vox thinnest=%.3f vox status=%s\n",
static_cast<unsigned long long>(TotalHits),static_cast<unsigned long long>(ThinDetachedSheets),
ThinDetachedSheets?ThinnestDetachedSheet:0.0,bThinSheetPass?"PASS":"FAIL");
for(size_t I=0;I<FamilyStats.size();++I)
{
const char* Name=I==0?"strata":I==1?"cave":"solid_overlay";
@@ -563,7 +649,9 @@ public:
Name,static_cast<unsigned long long>(S.Hits),M,V,
static_cast<unsigned long long>(S.NearZero),static_cast<unsigned long long>(S.AxisLocked));
}
return bDetailPass&&bGridPass;
const bool bFloorProfilePass=Stats.MaxUnmarkedFloorSlopeDelta<=0.75
&&Stats.MaxFloorCurveDeviation<=2.0;
return bDetailPass&&bGridPass&&bThinSheetPass&&bFloorProfilePass;
}
private:
@@ -606,8 +694,8 @@ private:
}
static float DetailBandFade(float SignedDistance)
{
// Keep full relief near the visible surface, then blend it to zero over
// the outer 1.5 voxels of the proxy support band.
// Keep the larger candidate mask conservative around smooth family unions;
// visible relief still fades to zero beyond its 4.125-voxel support radius.
constexpr float Edge=DetailSupremum+0.125f;
constexpr float Width=1.5f;
return SmoothUnit((Edge-std::abs(SignedDistance))/Width);
@@ -838,8 +926,7 @@ private:
const double D[3]={End[0]-Start[0],End[1]-Start[1],End[2]-Start[2]};
const double D2=D[0]*D[0]+D[1]*D[1]+D[2]*D[2];
const double T=D2>1.0e-12?(V[0]*D[0]+V[1]*D[1]+V[2]*D[2])/D2:0.0;
return static_cast<float>(P.CapsuleStartRadius
+(P.CapsuleEndRadius-P.CapsuleStartRadius)*T);
return CapsuleRadiusAtT(P,T,std::sqrt(std::max(0.0,D2)));
}
static void FitCapsuleRadiiAtEndpoints(FPrimitive& P,const FVector& PointA,float RadiusA,
@@ -867,6 +954,32 @@ private:
P.MinRadius=std::min(P.CapsuleStartRadius,P.CapsuleEndRadius);
}
static void SetCapsuleRadiusProfile(FPrimitive& P,const FVector& PointA,float RadiusA,
double SlopeA,const FVector& PointB,float RadiusB,double SlopeB)
{
const double Start[3]={P.CapsuleStartSub[0]/static_cast<double>(SubPerVoxel),
P.CapsuleStartSub[1]/static_cast<double>(SubPerVoxel),
P.CapsuleStartSub[2]/static_cast<double>(SubPerVoxel)};
const double End[3]={P.CapsuleEndSub[0]/static_cast<double>(SubPerVoxel),
P.CapsuleEndSub[1]/static_cast<double>(SubPerVoxel),
P.CapsuleEndSub[2]/static_cast<double>(SubPerVoxel)};
const double D[3]={End[0]-Start[0],End[1]-Start[1],End[2]-Start[2]};
const double D2=D[0]*D[0]+D[1]*D[1]+D[2]*D[2];
if(D2<=1.0e-12)return;
const auto Parameter=[&](const FVector& Point)
{
const double V[3]={Point.X-Start[0],Point.Y-Start[1],Point.Z-Start[2]};
return std::clamp((V[0]*D[0]+V[1]*D[1]+V[2]*D[2])/D2,0.0,1.0);
};
P.CapsuleProfileStartT=Parameter(PointA);
P.CapsuleProfileEndT=Parameter(PointB);
P.CapsuleProfileStartRadius=RadiusA;
P.CapsuleProfileEndRadius=RadiusB;
P.CapsuleRadiusSlopeStart=SlopeA;
P.CapsuleRadiusSlopeEnd=SlopeB;
P.bSmoothCapsuleRadius=P.CapsuleProfileEndT-P.CapsuleProfileStartT>1.0e-6;
}
static float SmoothedControlRadius(const TArray<float>& Radii,int32 Index,float Fallback,
float Minimum)
{
@@ -880,16 +993,163 @@ private:
+At(Index+4))*(1.0f/256.0f);
}
void AddFloorSegment(const FVector& A,const FVector& B,float Width)
static double ControlRadiusSlopeAt(const TArray<FVector>& Points,const TArray<float>& Radii,
int32 Index,float Fallback,float Minimum)
{
FVector F0=A,F1=B;F0.Z-=1.25;F1.Z-=1.25;
const auto D=Sub(F1,F0);const double Length=std::sqrt(Dot(D,D));
if(Length<1.0e-4){AddEllipsoid(F0,{Width,Width,1.25},{1,0,0},{0,1,0},{0,0,1},false);return;}
const auto U=Normalize(D);const std::array<double,3> Ref=std::abs(U[2])<0.90
?std::array<double,3>{0,0,1}:std::array<double,3>{0,1,0};
const auto V=Normalize(Cross(U,Ref)),W=Normalize(Cross(U,V));
const FVector C((F0.X+F1.X)*0.5,(F0.Y+F1.Y)*0.5,(F0.Z+F1.Z)*0.5);
AddEllipsoid(C,{Length*0.5+Width,Width,1.25},U,V,W,false);
if(Points.Num()<2||Index<0||Index>=Points.Num())return 0.0;
const auto RadiusAt=[&](int32 I)
{return static_cast<double>(SmoothedControlRadius(Radii,I,Fallback,Minimum));};
const auto LengthAt=[&](int32 I)
{
const FVector& A=Points[I];const FVector& B=Points[I+1];
const double DX=B.X-A.X,DY=B.Y-A.Y,DZ=B.Z-A.Z;
return std::sqrt(DX*DX+DY*DY+DZ*DZ);
};
if(Index==0)
{
const double Length=LengthAt(0);
return Length>1.0e-9?(RadiusAt(1)-RadiusAt(0))/Length:0.0;
}
if(Index+1==Points.Num())
{
const double Length=LengthAt(Index-1);
return Length>1.0e-9?(RadiusAt(Index)-RadiusAt(Index-1))/Length:0.0;
}
const double PrevLength=LengthAt(Index-1),NextLength=LengthAt(Index);
if(PrevLength<=1.0e-9||NextLength<=1.0e-9)return 0.0;
const double PrevSlope=(RadiusAt(Index)-RadiusAt(Index-1))/PrevLength;
const double NextSlope=(RadiusAt(Index+1)-RadiusAt(Index))/NextLength;
if(PrevSlope*NextSlope<=0.0)return 0.0;
const double W1=2.0*NextLength+PrevLength,W2=NextLength+2.0*PrevLength;
const double Harmonic=(W1+W2)/(W1/PrevSlope+W2/NextSlope);
const double Limit=3.0*std::min(std::abs(PrevSlope),std::abs(NextSlope));
return std::copysign(std::min(std::abs(Harmonic),Limit),Harmonic);
}
static double EvaluateFloorProfile(double A,double B,double TangentA,double TangentB,
double Length,double StartBlend,double EndBlend,double S)
{
const double Secant=(B-A)/Length;
if(S<0.0)return A+TangentA*S;
if(S>Length)return B+TangentB*(S-Length);
double Result=std::lerp(A,B,S/Length);
const auto Correction=[](double DeltaSlope,double Span,double T)
{
if(!(Span>0.0)||T<0.0||T>=1.0)return 0.0;
return DeltaSlope*Span*(T-2.0*T*T+T*T*T);
};
if(S<StartBlend)
Result+=Correction(TangentA-Secant,StartBlend,S/StartBlend);
if(Length-S<EndBlend)
Result-=Correction(TangentB-Secant,EndBlend,(Length-S)/EndBlend);
return Result;
}
static double FloorSurfaceRelief(double X,double Y,uint32 Seed)
{
// A shared XY field keeps independently authored floor clips from ending on
// separate flat planes. Its peak displacement stays under 1.2 voxels.
const float Broad=FStandaloneResearchTunnels::ValueNoise(
static_cast<float>(X),static_cast<float>(Y),0.0f,32,Seed^0x7F4A7C15u);
const float Mid=FStandaloneResearchTunnels::ValueNoise(
static_cast<float>(X),static_cast<float>(Y),0.0f,16,Seed^0x94D049BBu);
return 0.80*Broad+0.35*Mid;
}
static std::vector<double> BuildFloorTangents(const TArray<FVector>& Points,
const std::vector<float>& Floor,const std::vector<uint8_t>& MarkedSegments)
{
const size_t Count=Floor.size();std::vector<double> Tangent(Count,0.0);
if(Count<2||Count!=static_cast<size_t>(Points.Num())||MarkedSegments.size()+1!=Count)return Tangent;
std::vector<double> Length(Count-1,0.0),Slope(Count-1,0.0);
for(size_t I=0;I+1<Count;++I)
{
const FVector& A=Points[static_cast<int32>(I)];const FVector& B=Points[static_cast<int32>(I+1)];
Length[I]=std::hypot(B.X-A.X,B.Y-A.Y);
if(Length[I]>1.0e-6)Slope[I]=(Floor[I+1]-Floor[I])/Length[I];
}
for(size_t I=0;I<Count;++I)
{
const bool bHasPrev=I>0&&Length[I-1]>1.0e-6;
const bool bHasNext=I+1<Count&&Length[I]>1.0e-6;
if(I==0){Tangent[I]=MarkedSegments[0]?0.0:Slope[0];continue;}
if(I+1==Count){Tangent[I]=MarkedSegments[I-1]?0.0:Slope[I-1];continue;}
const bool bPrevMarked=MarkedSegments[I-1]!=0,bNextMarked=MarkedSegments[I]!=0;
if(bPrevMarked||bNextMarked)
{
if(!bPrevMarked&&bHasPrev)Tangent[I]=Slope[I-1];
else if(!bNextMarked&&bHasNext)Tangent[I]=Slope[I];
continue;
}
if(!bHasPrev||!bHasNext||Slope[I-1]*Slope[I]<=0.0)continue;
const double W0=2.0*Length[I]+Length[I-1],W1=Length[I]+2.0*Length[I-1];
const double Denominator=W0/Slope[I-1]+W1/Slope[I];
if(std::abs(Denominator)>1.0e-12)
Tangent[I]=(W0+W1)/Denominator;
}
return Tangent;
}
static void SetCurvedFloorClip(FPrimitive& P,const FVector& A,const FVector& B,
double FloorA,double FloorB,double TangentA,double TangentB)
{
if(!P.bHasFloorClip)return;
const double DX=B.X-A.X,DY=B.Y-A.Y,Length=std::hypot(DX,DY);
if(Length<=1.0e-6)return;
P.bCurvedFloorClip=true;P.FloorCurveOriginX=A.X;P.FloorCurveOriginY=A.Y;
P.FloorCurveDeltaX=DX;P.FloorCurveDeltaY=DY;P.FloorCurveLength=Length;
P.FloorCurveStartZ=FloorA;P.FloorCurveEndZ=FloorB;
P.FloorCurveTangentStart=TangentA;P.FloorCurveTangentEnd=TangentB;
P.FloorCurveStartBlend=std::min(8.0,Length*0.35);
P.FloorCurveEndBlend=std::min(8.0,Length*0.35);
for(int32 I=1;I<64;++I)
{
const double T=I/64.0;
const double Curve=EvaluateFloorProfile(FloorA,FloorB,TangentA,TangentB,
Length,P.FloorCurveStartBlend,P.FloorCurveEndBlend,Length*T);
const double Linear=std::lerp(FloorA,FloorB,T);
P.FloorCurveMaxDeviation=std::max(P.FloorCurveMaxDeviation,std::abs(Curve-Linear));
}
}
double FloorAt(const FPrimitive& P,double X,double Y) const
{
double Base=0.0;
if(P.bCurvedFloorClip)
{
const double T=((X-P.FloorCurveOriginX)*P.FloorCurveDeltaX
+(Y-P.FloorCurveOriginY)*P.FloorCurveDeltaY)
/(P.FloorCurveLength*P.FloorCurveLength);
Base=EvaluateFloorProfile(P.FloorCurveStartZ,P.FloorCurveEndZ,
P.FloorCurveTangentStart,P.FloorCurveTangentEnd,P.FloorCurveLength,
P.FloorCurveStartBlend,P.FloorCurveEndBlend,T*P.FloorCurveLength);
}
else
Base=P.FloorCenterZ+P.FloorSlopeX*(X-P.CenterSub[0]/16.0)
+P.FloorSlopeY*(Y-P.CenterSub[1]/16.0);
return Base;
}
double FloorSlopeAtEndpoint(const FPrimitive& P,bool bEnd) const
{
const double DX=P.bCurvedFloorClip?P.FloorCurveDeltaX
:P.CapsuleEndSub[0]-P.CapsuleStartSub[0];
const double DY=P.bCurvedFloorClip?P.FloorCurveDeltaY
:P.CapsuleEndSub[1]-P.CapsuleStartSub[1];
const double Length=P.bCurvedFloorClip?P.FloorCurveLength
:std::hypot(DX,DY)/SubPerVoxel;
if(Length<=1.0e-9)return 0.0;
const double OriginX=P.bCurvedFloorClip?P.FloorCurveOriginX
:P.CapsuleStartSub[0]/static_cast<double>(SubPerVoxel);
const double OriginY=P.bCurvedFloorClip?P.FloorCurveOriginY
:P.CapsuleStartSub[1]/static_cast<double>(SubPerVoxel);
const double X=OriginX+(bEnd?DX/(P.bCurvedFloorClip?1.0:SubPerVoxel):0.0);
const double Y=OriginY+(bEnd?DY/(P.bCurvedFloorClip?1.0:SubPerVoxel):0.0);
constexpr double Epsilon=0.125;
const double UX=DX/(Length*(P.bCurvedFloorClip?1.0:SubPerVoxel));
const double UY=DY/(Length*(P.bCurvedFloorClip?1.0:SubPerVoxel));
return (FloorAt(P,X+UX*Epsilon,Y+UY*Epsilon)
-FloorAt(P,X-UX*Epsilon,Y-UY*Epsilon))/(2.0*Epsilon);
}
void BuildOwnerPrimitives()
@@ -933,7 +1193,8 @@ private:
FVector(R.Center.X,R.Center.Y,R.FloorCutZ-Relief),Params,
static_cast<uint32>(Owner->Seed)).Z;
for(size_t I=RoomPrimitiveStart;I<Primitives.size();++I)
if(Primitives[I].bCarve)ApplyFloorClip(Primitives[I],FloorZ,0.0,0.0,3.0);
if(Primitives[I].bCarve)ApplyFloorClip(Primitives[I],FloorZ,0.0,0.0,
FloorClipBlendRadius);
}
// The packed signs still union room intervals exactly. Blend only
// the analytic cave field where neighboring authored rooms meet;
@@ -941,14 +1202,14 @@ private:
for(size_t I=RoomPrimitiveStart;I<Primitives.size();++I)
if(Primitives[I].bCarve)Primitives[I].BlendRadius=RoomUnionBlendRadius;
}
for(const FCachedRoomFloorJoin& J:Cache.RoomFloorJoins)
{
FVector A=J.Start,B=J.End;A.Z=J.FloorZ-1.25;B.Z=J.FloorZ-1.25;
AddFloorSegment(A,B,std::max(2.0f,J.Radius));
}
// Do not turn automatic room-overlap joins into thin positive ellipsoids.
// Their 2.5-voxel support plates can sit above the lower room floor and render as
// disconnected shards. Each room already carries its own clipped floor; the join
// metadata remains available to the owner generator and is audited here.
Stats.RoomFloorJoinCount+=static_cast<uint64>(Cache.RoomFloorJoins.Num());
const double RoomBlendRadius=ActiveBlendRadius;
ActiveBlendRadius=JoinBlendRadius;
ActiveFloorBlendRadius=3.0;
ActiveFloorBlendRadius=FloorClipBlendRadius;
for(const FCachedTunnel& T:Cache.Tunnels)
{
++Stats.TunnelCount;
@@ -958,6 +1219,34 @@ private:
const TArray<float>& Radii=bHasWorldChain?T.WorldControlRadii:T.ControlRadii;
const TArray<FTunnelFloorSegmentProfile>& FloorProfiles=bHasWorldChain
&&T.WorldFloorProfiles.Num()==Points.Num()-1?T.WorldFloorProfiles:T.FloorProfiles;
std::vector<float> RawFloor(static_cast<size_t>(Points.Num()));
std::vector<uint8_t> MarkedFloorSegments(Points.Num()>0?static_cast<size_t>(Points.Num()-1):0,0);
for(int32 I=0;I<Points.Num();++I)
RawFloor[static_cast<size_t>(I)]=static_cast<float>(Points[I].Z
-SmoothedControlRadius(Radii,I,T.RadiusA,2.0f));
if(FloorProfiles.Num()==Points.Num()-1)
{
for(int32 I=0;I+1<Points.Num();++I)
{
const FTunnelFloorSegmentProfile& Profile=FloorProfiles[I];
if(I==0)RawFloor[0]=Profile.StartFloorZ;
else RawFloor[static_cast<size_t>(I)]=0.5f*(FloorProfiles[I-1].EndFloorZ+Profile.StartFloorZ);
RawFloor[static_cast<size_t>(I+1)]=Profile.EndFloorZ;
MarkedFloorSegments[static_cast<size_t>(I)]=Profile.bLedgeTransition?1:0;
}
}
for(int32 I=0;I<Points.Num();++I)
{
const bool bPreviousUnmarked=I==0
||!MarkedFloorSegments[static_cast<size_t>(I-1)];
const bool bNextUnmarked=I+1>=Points.Num()
||!MarkedFloorSegments[static_cast<size_t>(I)];
if(bPreviousUnmarked&&bNextUnmarked)
RawFloor[static_cast<size_t>(I)]+=static_cast<float>(FloorSurfaceRelief(
Points[I].X,Points[I].Y,static_cast<uint32>(Owner->Seed)));
}
const std::vector<double> FloorTangents=BuildFloorTangents(
Points,RawFloor,MarkedFloorSegments);
for(int32 Join=1;Join+1<Points.Num();++Join)
{
const FVector A=Points[Join]-Points[Join-1],B=Points[Join+1]-Points[Join];
@@ -989,18 +1278,20 @@ private:
const FVector NextD=Points[Join+1]-Points[Join];
const double PrevXY=std::sqrt(PrevD.X*PrevD.X+PrevD.Y*PrevD.Y);
const double NextXY=std::sqrt(NextD.X*NextD.X+NextD.Y*NextD.Y);
const double PrevSlope=PrevXY>1.0e-6
const double RawPrevSlope=PrevXY>1.0e-6
?(FloorProfiles[Join-1].EndFloorZ-FloorProfiles[Join-1].StartFloorZ)/PrevXY:0.0;
const double NextSlope=NextXY>1.0e-6
const double RawNextSlope=NextXY>1.0e-6
?(FloorProfiles[Join].EndFloorZ-FloorProfiles[Join].StartFloorZ)/NextXY:0.0;
const double SlopeDelta=std::abs(PrevSlope-NextSlope);
Stats.MaxCapsuleJoinFloorSlopeDelta=std::max(Stats.MaxCapsuleJoinFloorSlopeDelta,SlopeDelta);
const double RawSlopeDelta=std::abs(RawPrevSlope-RawNextSlope);
if(FloorProfiles[Join-1].bLedgeTransition||FloorProfiles[Join].bLedgeTransition)
{
++Stats.MarkedFloorSlopeJoins;
Stats.MaxMarkedFloorSlopeDelta=std::max(Stats.MaxMarkedFloorSlopeDelta,SlopeDelta);
Stats.MaxMarkedFloorSlopeDelta=std::max(Stats.MaxMarkedFloorSlopeDelta,RawSlopeDelta);
}
else
{
Stats.MaxRawUnmarkedFloorSlopeDelta=std::max(Stats.MaxRawUnmarkedFloorSlopeDelta,RawSlopeDelta);
}
else Stats.MaxUnmarkedFloorSlopeDelta=std::max(Stats.MaxUnmarkedFloorSlopeDelta,SlopeDelta);
}
++Stats.CapsuleJoinCount;
}
@@ -1011,17 +1302,28 @@ private:
{
const float R0=SmoothedControlRadius(Radii,I,T.RadiusA,2.0f);
const float R1=SmoothedControlRadius(Radii,I+1,T.RadiusB,2.0f);
const double RadiusSlope0=ControlRadiusSlopeAt(Points,Radii,I,T.RadiusA,2.0f);
const double RadiusSlope1=ControlRadiusSlopeAt(Points,Radii,I+1,T.RadiusB,2.0f);
Stats.SmoothedProfileRadiusStep=std::max(Stats.SmoothedProfileRadiusStep,
static_cast<double>(std::abs(R1-R0)));
const FVector Segment=Points[I+1]-Points[I];
const double SegmentLength=std::sqrt(Segment.X*Segment.X+Segment.Y*Segment.Y+Segment.Z*Segment.Z);
const double Extension=ContinuousJoinExtension(R0,R1,SegmentLength);
float ZA=Points[I].Z-R0,ZB=Points[I+1].Z-R1;
if(FloorProfiles.IsValidIndex(I))ZA=FloorProfiles[I].StartFloorZ;
if(FloorProfiles.IsValidIndex(I))ZB=FloorProfiles[I].EndFloorZ;
const float ZA=RawFloor[static_cast<size_t>(I)];
const float ZB=RawFloor[static_cast<size_t>(I+1)];
AddTaperedCapsule(Points[I],Points[I+1],R0,R1,true,false,true,ZA,ZB,
I>0?Extension:0.0,I+2<Points.Num()?Extension:0.0);
if(!MarkedFloorSegments[static_cast<size_t>(I)])
{
SetCurvedFloorClip(Primitives.back(),Points[I],Points[I+1],ZA,ZB,
FloorTangents[static_cast<size_t>(I)],FloorTangents[static_cast<size_t>(I+1)]);
++Stats.CurvedFloorSegments;
Stats.MaxFloorCurveDeviation=std::max(Stats.MaxFloorCurveDeviation,
Primitives.back().FloorCurveMaxDeviation);
}
FitCapsuleRadiiAtEndpoints(Primitives.back(),Points[I],R0,Points[I+1],R1);
SetCapsuleRadiusProfile(Primitives.back(),Points[I],R0,RadiusSlope0,
Points[I+1],R1,RadiusSlope1);
}
for(int32 Join=1;Join+1<Points.Num();++Join)
{
@@ -1031,6 +1333,24 @@ private:
const float NextStart=CapsuleRadiusAt(Next,Points[Join]);
Stats.MaxCapsuleJoinRadiusDelta=std::max(Stats.MaxCapsuleJoinRadiusDelta,
static_cast<double>(std::abs(PreviousEnd-NextStart)));
Stats.MaxCapsuleJoinRadiusSlopeDelta=std::max(
Stats.MaxCapsuleJoinRadiusSlopeDelta,
std::abs(Previous.CapsuleRadiusSlopeEnd-Next.CapsuleRadiusSlopeStart));
if(!MarkedFloorSegments[static_cast<size_t>(Join-1)]
&&!MarkedFloorSegments[static_cast<size_t>(Join)])
{
const double TangentDelta=Previous.bCurvedFloorClip&&Next.bCurvedFloorClip
?std::abs(Previous.FloorCurveTangentEnd-Next.FloorCurveTangentStart)
:std::abs(FloorSlopeAtEndpoint(Previous,true)-FloorSlopeAtEndpoint(Next,false));
const double DirectionalSlopeChange=std::abs(FloorSlopeAtEndpoint(Previous,true)
-FloorSlopeAtEndpoint(Next,false));
Stats.MaxCapsuleJoinFloorSlopeDelta=std::max(
Stats.MaxCapsuleJoinFloorSlopeDelta,TangentDelta);
Stats.MaxUnmarkedFloorSlopeDelta=std::max(
Stats.MaxUnmarkedFloorSlopeDelta,TangentDelta);
Stats.MaxUnmarkedFloorDirectionSlopeDelta=std::max(
Stats.MaxUnmarkedFloorDirectionSlopeDelta,DirectionalSlopeChange);
}
}
}
else AddCapsule(T.EndpointA,T.EndpointB,std::max(T.RadiusA,T.RadiusB),true);
@@ -1052,13 +1372,25 @@ private:
}
}
const double PassageJoinBlend=ActiveBlendRadius;ActiveBlendRadius=JoinBlendRadius;
ActiveFloorBlendRadius=3.0;
ActiveFloorBlendRadius=FloorClipBlendRadius;
for(const FVoxelPassage& P:Owner->Manager->GetPassages())
{
++Stats.PassageCount;
const TArray<FVector>& Points=P.ControlPoints;
const TArray<float>& Radii=P.ControlRadii;
const size_t PassagePrimitiveStart=Primitives.size();
std::vector<float> RawFloor(static_cast<size_t>(Points.Num()));
std::vector<uint8_t> MarkedFloorSegments(Points.Num()>0?static_cast<size_t>(Points.Num()-1):0,0);
for(int32 I=0;I<Points.Num();++I)
RawFloor[static_cast<size_t>(I)]=static_cast<float>(Points[I].Z
-SmoothedControlRadius(Radii,I,P.Radius,6.0f));
if(P.NativeFloorProfileZ.Num()==Points.Num())
for(int32 I=0;I<Points.Num();++I)RawFloor[static_cast<size_t>(I)]=P.NativeFloorProfileZ[I];
for(int32 I=0;I<Points.Num();++I)
RawFloor[static_cast<size_t>(I)]+=static_cast<float>(FloorSurfaceRelief(
Points[I].X,Points[I].Y,static_cast<uint32>(Owner->Seed)));
const std::vector<double> FloorTangents=BuildFloorTangents(
Points,RawFloor,MarkedFloorSegments);
for(int32 Join=1;Join+1<Points.Num();++Join)
{
const float RPrev=std::max(6.0f,Radii.IsValidIndex(Join-1)?Radii[Join-1]:P.Radius);
@@ -1076,9 +1408,8 @@ private:
?(P.NativeFloorProfileZ[Join]-P.NativeFloorProfileZ[Join-1])/PrevXY:0.0;
const double NextSlope=NextXY>1.0e-6
?(P.NativeFloorProfileZ[Join+1]-P.NativeFloorProfileZ[Join])/NextXY:0.0;
const double SlopeDelta=std::abs(PrevSlope-NextSlope);
Stats.MaxCapsuleJoinFloorSlopeDelta=std::max(Stats.MaxCapsuleJoinFloorSlopeDelta,SlopeDelta);
Stats.MaxUnmarkedFloorSlopeDelta=std::max(Stats.MaxUnmarkedFloorSlopeDelta,SlopeDelta);
const double RawSlopeDelta=std::abs(PrevSlope-NextSlope);
Stats.MaxRawUnmarkedFloorSlopeDelta=std::max(Stats.MaxRawUnmarkedFloorSlopeDelta,RawSlopeDelta);
}
++Stats.CapsuleJoinCount;
}
@@ -1088,13 +1419,14 @@ private:
{
const float RadiusA=SmoothedControlRadius(Radii,I,P.Radius,6.0f);
const float RadiusB=SmoothedControlRadius(Radii,I+1,P.Radius,6.0f);
const double RadiusSlopeA=ControlRadiusSlopeAt(Points,Radii,I,P.Radius,6.0f);
const double RadiusSlopeB=ControlRadiusSlopeAt(Points,Radii,I+1,P.Radius,6.0f);
Stats.SmoothedProfileRadiusStep=std::max(Stats.SmoothedProfileRadiusStep,
static_cast<double>(std::abs(RadiusB-RadiusA)));
const FVector Segment=Points[I+1]-Points[I];
const double SegmentLength=std::sqrt(Segment.X*Segment.X+Segment.Y*Segment.Y+Segment.Z*Segment.Z);
float ZA=Points[I].Z-RadiusA,ZB=Points[I+1].Z-RadiusB;
if(P.NativeFloorProfileZ.IsValidIndex(I))ZA=P.NativeFloorProfileZ[I];
if(P.NativeFloorProfileZ.IsValidIndex(I+1))ZB=P.NativeFloorProfileZ[I+1];
const float ZA=RawFloor[static_cast<size_t>(I)];
const float ZB=RawFloor[static_cast<size_t>(I+1)];
const double Extension=ContinuousJoinExtension(RadiusA,RadiusB,SegmentLength);
const double CoreExtension=ContinuousJoinExtension(std::min(2.5f,RadiusA*0.45f),
std::min(2.5f,RadiusB*0.45f),SegmentLength);
@@ -1102,14 +1434,30 @@ private:
const double CoreExtendStart=I>0?CoreExtension:0.0,CoreExtendEnd=I+2<Points.Num()?CoreExtension:0.0;
AddTaperedCapsule(Points[I],Points[I+1],RadiusA,RadiusB,true,false,true,ZA,ZB,
ExtendStart,ExtendEnd);
SetCurvedFloorClip(Primitives.back(),Points[I],Points[I+1],ZA,ZB,
FloorTangents[static_cast<size_t>(I)],FloorTangents[static_cast<size_t>(I+1)]);
++Stats.CurvedFloorSegments;
Stats.MaxFloorCurveDeviation=std::max(Stats.MaxFloorCurveDeviation,
Primitives.back().FloorCurveMaxDeviation);
FitCapsuleRadiiAtEndpoints(Primitives.back(),Points[I],RadiusA,Points[I+1],RadiusB);
SetCapsuleRadiusProfile(Primitives.back(),Points[I],RadiusA,RadiusSlopeA,
Points[I+1],RadiusB,RadiusSlopeB);
const float CoreRadiusA=std::min(2.5f,RadiusA*0.45f);
const float CoreRadiusB=std::min(2.5f,RadiusB*0.45f);
const double CoreSlopeA=CoreRadiusA<2.5f-1.0e-4?RadiusSlopeA*0.45:0.0;
const double CoreSlopeB=CoreRadiusB<2.5f-1.0e-4?RadiusSlopeB*0.45:0.0;
AddTaperedCapsule(Points[I],Points[I+1],CoreRadiusA,
CoreRadiusB,true,true,true,ZA,ZB,
CoreExtendStart,CoreExtendEnd);
SetCurvedFloorClip(Primitives.back(),Points[I],Points[I+1],ZA,ZB,
FloorTangents[static_cast<size_t>(I)],FloorTangents[static_cast<size_t>(I+1)]);
++Stats.CurvedFloorSegments;
Stats.MaxFloorCurveDeviation=std::max(Stats.MaxFloorCurveDeviation,
Primitives.back().FloorCurveMaxDeviation);
FitCapsuleRadiiAtEndpoints(Primitives.back(),Points[I],CoreRadiusA,
Points[I+1],CoreRadiusB);
SetCapsuleRadiusProfile(Primitives.back(),Points[I],CoreRadiusA,CoreSlopeA,
Points[I+1],CoreRadiusB,CoreSlopeB);
}
for(int32 Join=1;Join+1<Points.Num();++Join)
{
@@ -1123,6 +1471,23 @@ private:
const float NextStart=CapsuleRadiusAt(Next,Points[Join]);
Stats.MaxCapsuleJoinRadiusDelta=std::max(Stats.MaxCapsuleJoinRadiusDelta,
static_cast<double>(std::abs(PreviousEnd-NextStart)));
Stats.MaxCapsuleJoinRadiusSlopeDelta=std::max(
Stats.MaxCapsuleJoinRadiusSlopeDelta,
std::abs(Previous.CapsuleRadiusSlopeEnd-Next.CapsuleRadiusSlopeStart));
if(Tube==0)
{
const double TangentDelta=Previous.bCurvedFloorClip&&Next.bCurvedFloorClip
?std::abs(Previous.FloorCurveTangentEnd-Next.FloorCurveTangentStart)
:std::abs(FloorSlopeAtEndpoint(Previous,true)-FloorSlopeAtEndpoint(Next,false));
const double DirectionalSlopeChange=std::abs(FloorSlopeAtEndpoint(Previous,true)
-FloorSlopeAtEndpoint(Next,false));
Stats.MaxCapsuleJoinFloorSlopeDelta=std::max(
Stats.MaxCapsuleJoinFloorSlopeDelta,TangentDelta);
Stats.MaxUnmarkedFloorSlopeDelta=std::max(
Stats.MaxUnmarkedFloorSlopeDelta,TangentDelta);
Stats.MaxUnmarkedFloorDirectionSlopeDelta=std::max(
Stats.MaxUnmarkedFloorDirectionSlopeDelta,DirectionalSlopeChange);
}
}
}
}
@@ -1382,8 +1747,7 @@ private:
if(!bInside)return false;
if(P.bHasFloorClip)
{
const double Floor=P.FloorCenterZ+P.FloorSlopeX*(X-P.CenterSub[0]/16.0)
+P.FloorSlopeY*(Y-P.CenterSub[1]/16.0);
const double Floor=FloorAt(P,X,Y);
if(P.FloorBlendRadius<=0.0)return Z>=Floor;
return SmoothMaxDistance(ShapeDistance(P,X,Y,Z),static_cast<float>(Floor-Z),
static_cast<float>(P.FloorBlendRadius))<=0.0f;
@@ -1391,6 +1755,30 @@ private:
return true;
}
static float CapsuleRadiusAtT(const FPrimitive& P,double T,double AxisLength)
{
T=std::clamp(T,0.0,1.0);
if(!P.bSmoothCapsuleRadius)
return static_cast<float>(P.CapsuleStartRadius
+(P.CapsuleEndRadius-P.CapsuleStartRadius)*T);
const double T0=P.CapsuleProfileStartT,T1=P.CapsuleProfileEndT;
if(T<T0)return static_cast<float>(P.CapsuleProfileStartRadius
+P.CapsuleRadiusSlopeStart*AxisLength*(T-T0));
if(T>T1)return static_cast<float>(P.CapsuleProfileEndRadius
+P.CapsuleRadiusSlopeEnd*AxisLength*(T-T1));
const double SpanT=std::max(1.0e-9,T1-T0);
const double U=std::clamp((T-T0)/SpanT,0.0,1.0),U2=U*U,U3=U2*U;
const double SpanDistance=AxisLength*SpanT;
const double H00=2.0*U3-3.0*U2+1.0;
const double H10=U3-2.0*U2+U;
const double H01=-2.0*U3+3.0*U2;
const double H11=U3-U2;
return static_cast<float>(H00*P.CapsuleProfileStartRadius
+H10*SpanDistance*P.CapsuleRadiusSlopeStart
+H01*P.CapsuleProfileEndRadius
+H11*SpanDistance*P.CapsuleRadiusSlopeEnd);
}
float ShapeDistance(const FPrimitive& P,double X,double Y,double Z) const
{
if(P.bCapsule)
@@ -1403,12 +1791,12 @@ private:
const double End=P.CapsuleEndSub[A]/static_cast<double>(SubPerVoxel);
V[A]=Position[A]-Start;D[A]=End-Start;D2+=D[A]*D[A];VD+=V[A]*D[A];
}
const double AxisLength=std::sqrt(std::max(0.0,D2));
const double T=D2>1.0e-12?std::clamp(VD/D2,0.0,1.0):0.0;
double DistanceSquared=0.0;
for(int32 A=0;A<3;++A)
{const double E=V[A]-T*D[A];DistanceSquared+=E*E;}
const double Radius=P.CapsuleStartRadius
+(P.CapsuleEndRadius-P.CapsuleStartRadius)*T;
const double Radius=CapsuleRadiusAtT(P,T,AxisLength);
return static_cast<float>(std::sqrt(DistanceSquared)-Radius);
}
if(P.bRoundedBox)
@@ -1455,13 +1843,13 @@ private:
H=std::min(Hi,static_cast<int32>(std::ceil(T1))+1);
}
if(L>H)return false;
if(P.bCarve&&P.bHasFloorClip)
if(P.bCarve&&P.bHasFloorClip&&(!P.bCurvedFloorClip||Axis==2))
{
double X0=Fixed[0],Y0=Fixed[1],Z0=Fixed[2];
if(Axis==0)X0=0.0;else if(Axis==1)Y0=0.0;else Z0=0.0;
const double Floor0=P.FloorCenterZ+P.FloorSlopeX*(X0-P.CenterSub[0]/16.0)
+P.FloorSlopeY*(Y0-P.CenterSub[1]/16.0);
const double Margin0=Z0-Floor0;
const double Floor0=FloorAt(P,X0,Y0);
const double SmoothAllowance=P.FloorBlendRadius>0.0?P.FloorBlendRadius/6.0:0.0;
const double Margin0=Z0-(Floor0-SmoothAllowance);
const double MarginSlope=Axis==0?-P.FloorSlopeX:Axis==1?-P.FloorSlopeY:1.0;
if(std::abs(MarginSlope)<1.0e-12)
{if(Margin0<0.0)return false;}
@@ -1814,8 +2202,7 @@ private:
float PrimitiveFloorInfluence=0.0f;
if(P.bHasFloorClip)
{
const double Floor=P.FloorCenterZ+P.FloorSlopeX*(X-P.CenterSub[0]/16.0)
+P.FloorSlopeY*(Y-P.CenterSub[1]/16.0);
const double Floor=FloorAt(P,X,Y);
const float FloorT=(static_cast<float>(std::abs(Z-Floor))-static_cast<float>(P.FloorBlendRadius))
/static_cast<float>(std::max(DetailBand*2,1));
PrimitiveFloorInfluence=1.0f-SmoothUnit(FloorT);
@@ -1866,6 +2253,7 @@ private:
const float CarveInfluence=SmoothUnit(0.5f+(Base-CarveOrOverlay)/(2.0f*FamilyBlendWidth));
const float CaveInfluence=CarveInfluence*CaveVsOverlay;
const float FloorInfluence=CaveInfluence*CaveFloorInfluence;
const float RockDistance=SmoothMinDistance(Base,CarveOrOverlay,StrataCaveBlendRadius);
if(OutFamily)
*OutFamily=Family;
if(OutSurfaceSDF)
@@ -1874,7 +2262,7 @@ private:
if(OutCaveInfluence)*OutCaveInfluence=CaveInfluence;
if(OutFloorInfluence)*OutFloorInfluence=FloorInfluence;
if(OutFloorSurface)*OutFloorSurface=FloorInfluence>=0.5f;
return SmoothMinDistance(Base,CarveOrOverlay,StrataCaveBlendRadius);
return RockDistance;
}
float OwnerCaveRoughness(double X,double Y,double Z,float SurfaceSDF,
@@ -1937,9 +2325,13 @@ private:
static float SurfaceRelief(float X,float Y,float Z,uint32 Seed)
{
return 0.70f*FStandaloneResearchTunnels::ValueNoise(X,Y,Z,3,Seed^0xA511E9B3u)
+1.00f*FStandaloneResearchTunnels::ValueNoise(X,Y,Z,6,Seed^0x63D83595u)
+1.00f*FStandaloneResearchTunnels::ValueNoise(X,Y,Z,12,Seed^0xB5297A4Du);
// The low band carries the broad rock shapes and also gently varies mid-band
// amplitude. The quieter 6-voxel octave keeps fine relief without restoring
// the same tight bubble pattern on every exposed surface.
const float Macro=FStandaloneResearchTunnels::ValueNoise(X,Y,Z,32,Seed^0xB5297A4Du);
const float Mid=FStandaloneResearchTunnels::ValueNoise(X,Y,Z,16,Seed^0x63D83595u);
const float Fine=FStandaloneResearchTunnels::ValueNoise(X,Y,Z,6,Seed^0xA511E9B3u);
return 1.00f*Macro+(0.80f+0.25f*Macro)*(0.85f*Mid+0.18f*Fine);
}
float DetailNoise(double X,double Y,double Z,float SurfaceSDF,
+194 -5
View File
@@ -48,6 +48,162 @@ namespace
float Dot(V3 A,V3 B){return A.X*B.X+A.Y*B.Y+A.Z*B.Z;}
V3 Cross(V3 A,V3 B){return {A.Y*B.Z-A.Z*B.Y,A.Z*B.X-A.X*B.Z,A.X*B.Y-A.Y*B.X};}
V3 Normalize(V3 A){return Mul(A,1.0f/std::sqrt(std::max(1.0e-20f,Dot(A,A))));}
struct FMeshSheetAudit
{
uint64 Components=0;
uint64 SmallComponents=0;
uint64 SmallComponentsInView=0;
uint64 BoundarySmallComponentsInView=0;
uint64 ThinSheets=0;
uint64 TrianglesInThinSheets=0;
double MinimumThicknessVoxels=std::numeric_limits<double>::max();
};
FMeshSheetAudit AuditThinMeshSheets(const FStandaloneCpuTile& Tile,
const FVector& CameraVoxels,float Yaw,float Pitch,bool bPrintCandidates)
{
FMeshSheetAudit Result;
const size_t VertexCount=Tile.Vertices.size();
if(VertexCount==0||Tile.Indices.size()<3)return Result;
std::vector<uint32> Parent(VertexCount);
std::vector<uint8> Rank(VertexCount,0);
std::iota(Parent.begin(),Parent.end(),0u);
auto Find=[&](uint32 Value)
{
uint32 Root=Value;
while(Parent[Root]!=Root)Root=Parent[Root];
while(Parent[Value]!=Value)
{const uint32 Next=Parent[Value];Parent[Value]=Root;Value=Next;}
return Root;
};
auto Union=[&](uint32 A,uint32 B)
{
A=Find(A);B=Find(B);if(A==B)return;
if(Rank[A]<Rank[B])std::swap(A,B);
Parent[B]=A;if(Rank[A]==Rank[B])++Rank[A];
};
const size_t TriangleCount=Tile.Indices.size()/3;
for(size_t T=0;T<TriangleCount;++T)
{
const uint32 A=Tile.Indices[T*3],B=Tile.Indices[T*3+1],C=Tile.Indices[T*3+2];
if(A>=VertexCount||B>=VertexCount||C>=VertexCount)continue;
Union(A,B);Union(B,C);
}
struct FComponent
{
uint64 Triangles=0,Vertices=0;
double Min[3]={std::numeric_limits<double>::max(),std::numeric_limits<double>::max(),std::numeric_limits<double>::max()};
double Max[3]={-std::numeric_limits<double>::max(),-std::numeric_limits<double>::max(),-std::numeric_limits<double>::max()};
double Sum[3]={0.0,0.0,0.0};
double Cov[6]={0.0,0.0,0.0,0.0,0.0,0.0};
};
std::vector<FComponent> Components(VertexCount);
for(size_t I=0;I<VertexCount;++I)
{
const uint32 Root=Find(static_cast<uint32>(I));
FComponent& C=Components[Root];++C.Vertices;
for(int32 A=0;A<3;++A)
{
const double P=Tile.Vertices[I].Position[A];
C.Min[A]=std::min(C.Min[A],P);C.Max[A]=std::max(C.Max[A],P);C.Sum[A]+=P;
}
}
for(size_t T=0;T<TriangleCount;++T)
{
const uint32 A=Tile.Indices[T*3],B=Tile.Indices[T*3+1],C=Tile.Indices[T*3+2];
if(A>=VertexCount||B>=VertexCount||C>=VertexCount)continue;
++Components[Find(A)].Triangles;
}
for(size_t I=0;I<VertexCount;++I)
{
const uint32 Root=Find(static_cast<uint32>(I));
FComponent& C=Components[Root];
if(C.Vertices==0)continue;
const double P[3]={Tile.Vertices[I].Position[0],Tile.Vertices[I].Position[1],Tile.Vertices[I].Position[2]};
const double D[3]={P[0]-C.Sum[0]/C.Vertices,P[1]-C.Sum[1]/C.Vertices,P[2]-C.Sum[2]/C.Vertices};
C.Cov[0]+=D[0]*D[0];C.Cov[1]+=D[1]*D[1];C.Cov[2]+=D[2]*D[2];
C.Cov[3]+=D[0]*D[1];C.Cov[4]+=D[0]*D[2];C.Cov[5]+=D[1]*D[2];
}
const double Camera[3]={CameraVoxels.X*0.25,CameraVoxels.Y*0.25,CameraVoxels.Z*0.25};
const double CP=std::cos(Pitch),Forward[3]={std::sin(Yaw)*CP,std::cos(Yaw)*CP,std::sin(Pitch)};
const double Right[3]={std::cos(Yaw),-std::sin(Yaw),0.0};
const double Up[3]={-std::sin(Yaw)*std::sin(Pitch),-std::cos(Yaw)*std::sin(Pitch),std::cos(Pitch)};
for(size_t Root=0;Root<VertexCount;++Root)
{
FComponent& C=Components[Root];if(C.Triangles==0||C.Vertices==0)continue;
++Result.Components;
const double Extent[3]={C.Max[0]-C.Min[0],C.Max[1]-C.Min[1],C.Max[2]-C.Min[2]};
const double MaxExtent=std::max({Extent[0],Extent[1],Extent[2]})/0.25;
const bool bSmall=C.Triangles<=256&&MaxExtent<=20.0;
if(bSmall)++Result.SmallComponents;
if(!bSmall||C.Vertices<4)continue;
double A[3][3]={{C.Cov[0],C.Cov[3],C.Cov[4]},
{C.Cov[3],C.Cov[1],C.Cov[5]},{C.Cov[4],C.Cov[5],C.Cov[2]}};
for(int32 Sweep=0;Sweep<12;++Sweep)
{
int32 P=0,Q=1;
if(std::abs(A[0][2])>std::abs(A[P][Q])){P=0;Q=2;}
if(std::abs(A[1][2])>std::abs(A[P][Q])){P=1;Q=2;}
if(std::abs(A[P][Q])<1.0e-16)break;
const double Tau=(A[Q][Q]-A[P][P])/(2.0*A[P][Q]);
const double T=std::copysign(1.0,Tau)/(std::abs(Tau)+std::sqrt(1.0+Tau*Tau));
const double Cos=1.0/std::sqrt(1.0+T*T),Sin=T*Cos;
const double App=A[P][P],Aqq=A[Q][Q],Apq=A[P][Q];
A[P][P]=App-T*Apq;A[Q][Q]=Aqq+T*Apq;A[P][Q]=A[Q][P]=0.0;
for(int32 K=0;K<3;++K)if(K!=P&&K!=Q)
{
const double Akp=A[K][P],Akq=A[K][Q];
A[K][P]=A[P][K]=Cos*Akp-Sin*Akq;
A[K][Q]=A[Q][K]=Sin*Akp+Cos*Akq;
}
}
const double Variance=std::max(0.0,std::min({A[0][0],A[1][1],A[2][2]})/C.Vertices);
const double ThicknessVoxels=3.5*std::sqrt(Variance)/0.25;
const double Center[3]={C.Sum[0]/C.Vertices,C.Sum[1]/C.Vertices,C.Sum[2]/C.Vertices};
const double Relative[3]={Center[0]-Camera[0],Center[1]-Camera[1],Center[2]-Camera[2]};
const double Depth=Relative[0]*Forward[0]+Relative[1]*Forward[1]+Relative[2]*Forward[2];
const double ScreenX=Relative[0]*Right[0]+Relative[1]*Right[1]+Relative[2]*Right[2];
const double ScreenY=Relative[0]*Up[0]+Relative[1]*Up[1]+Relative[2]*Up[2];
const bool bInView=Depth>0.0&&Depth<=64.0&&std::abs(ScreenX)<0.924*Depth
&&std::abs(ScreenY)<0.5774*Depth;
if(!bInView)continue;
const FIntVector TileOrigin=Tile.Key.OriginVoxels();
const int32 TileMin[3]={TileOrigin.X,TileOrigin.Y,TileOrigin.Z};
const int32 TileMax[3]={TileOrigin.X+Tile.Key.ExtentVoxels(),
TileOrigin.Y+Tile.Key.ExtentVoxels(),TileOrigin.Z+Tile.Key.ExtentVoxels()};
bool bTouchesTileBoundary=false;
for(int32 Axis=0;Axis<3;++Axis)
{
const double MinVoxels=C.Min[Axis]*4.0,MaxVoxels=C.Max[Axis]*4.0;
bTouchesTileBoundary|=MinVoxels<=TileMin[Axis]+0.5
||MaxVoxels>=TileMax[Axis]-0.5;
}
if(bTouchesTileBoundary)++Result.BoundarySmallComponentsInView;
else ++Result.SmallComponentsInView;
if(bPrintCandidates&&!bTouchesTileBoundary&&Result.SmallComponentsInView<=8)
std::printf("[SmallMeshComponent] level=%d tile=(%d,%d,%d) tris=%llu size=(%.2f,%.2f,%.2f) vox pca_thickness=%.3f vox center=(%.2f,%.2f,%.2f)m\n",
Tile.Key.Level,Tile.Key.Coord.X,Tile.Key.Coord.Y,Tile.Key.Coord.Z,
static_cast<unsigned long long>(C.Triangles),Extent[0]/0.25,Extent[1]/0.25,
Extent[2]/0.25,ThicknessVoxels,Center[0],Center[1],Center[2]);
else if(bPrintCandidates&&bTouchesTileBoundary
&&Result.BoundarySmallComponentsInView<=8)
std::printf("[BoundaryMeshComponent] level=%d tile=(%d,%d,%d) tris=%llu size=(%.2f,%.2f,%.2f) vox pca_thickness=%.3f vox center=(%.2f,%.2f,%.2f)m\n",
Tile.Key.Level,Tile.Key.Coord.X,Tile.Key.Coord.Y,Tile.Key.Coord.Z,
static_cast<unsigned long long>(C.Triangles),Extent[0]/0.25,Extent[1]/0.25,
Extent[2]/0.25,ThicknessVoxels,Center[0],Center[1],Center[2]);
if(ThicknessVoxels>=1.0)continue;
++Result.ThinSheets;Result.MinimumThicknessVoxels=std::min(Result.MinimumThicknessVoxels,ThicknessVoxels);
Result.TrianglesInThinSheets+=C.Triangles;
if(bPrintCandidates&&Result.ThinSheets==1)
std::printf("[ThinMeshSheetCandidate] level=%d tile=(%d,%d,%d) tris=%llu size=(%.2f,%.2f,%.2f) vox pca_thickness=%.3f vox center=(%.2f,%.2f,%.2f)m\n",
Tile.Key.Level,Tile.Key.Coord.X,Tile.Key.Coord.Y,Tile.Key.Coord.Z,
static_cast<unsigned long long>(C.Triangles),Extent[0]/0.25,Extent[1]/0.25,Extent[2]/0.25,
ThicknessVoxels,Center[0],Center[1],Center[2]);
}
return Result;
}
struct M4 { float M[4][4]{}; };
M4 Identity(){M4 R{};for(int I=0;I<4;++I)R.M[I][I]=1.0f;return R;}
M4 Transpose(M4 A){M4 B{};for(int I=0;I<4;++I)for(int J=0;J<4;++J)B.M[I][J]=A.M[J][I];return B;}
@@ -1348,7 +1504,7 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
std::atomic<size_t> Next{0};
std::vector<std::thread> Threads;
const int32 ThreadCount=std::clamp(Workers,1,8);
const int32 ThreadCount=std::clamp(Workers,1,16);
Threads.reserve(ThreadCount);
for(int32 Worker=0;Worker<ThreadCount;++Worker)
{
@@ -1547,7 +1703,7 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
int RunStreamingBenchmark(FStandaloneOwnerWorld& World,const FVector& CameraPositionVoxels,
int32 WorkerCount,int32 MaxLevel,bool bRoundTunnels,
bool bEnhancedWalls,const FStandaloneMeshCache* Cache,
bool bHashDensity,
bool bHashDensity,bool bShardAudit,float CameraYaw,float CameraPitch,
const std::string& BakeCachePath,
const FStandaloneMeshCacheMetadata& CacheMetadata)
{
@@ -1588,6 +1744,8 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
bool bFullDetailTimeReported=false;
double FullDetailSeconds=0.0;
const int32 DesiredTotal=Update.DesiredTiles;
FMeshSheetAudit MeshSheetStats;
uint64 MeshAuditedTiles=0;
while(Finished<DesiredTotal)
{
std::vector<FStandaloneCpuTile> Ready=Streamer.TakeReady();
@@ -1610,6 +1768,23 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
if(Tile.bHasDensityHash)HashRows[Level].push_back({Tile.Key,Tile.DensityHash});
}
if(Level<World.FullResClipLevels)++FullDetailFinished;
if(bShardAudit&&Level==0)
{
const FMeshSheetAudit TileAudit=AuditThinMeshSheets(Tile,CameraPositionVoxels,
CameraYaw,CameraPitch,
MeshSheetStats.ThinSheets==0
&&MeshSheetStats.SmallComponentsInView
+MeshSheetStats.BoundarySmallComponentsInView<8);
MeshSheetStats.Components+=TileAudit.Components;
MeshSheetStats.SmallComponents+=TileAudit.SmallComponents;
MeshSheetStats.ThinSheets+=TileAudit.ThinSheets;
MeshSheetStats.SmallComponentsInView+=TileAudit.SmallComponentsInView;
MeshSheetStats.BoundarySmallComponentsInView+=TileAudit.BoundarySmallComponentsInView;
MeshSheetStats.TrianglesInThinSheets+=TileAudit.TrianglesInThinSheets;
MeshSheetStats.MinimumThicknessVoxels=std::min(MeshSheetStats.MinimumThicknessVoxels,
TileAudit.MinimumThicknessVoxels);
++MeshAuditedTiles;
}
if(!BakeCachePath.empty())CacheTiles.push_back(std::move(Tile));
}
if(!bFullDetailTimeReported&&FullDetailFinished>=FullDetailExpected)
@@ -1646,6 +1821,16 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
std::accumulate(WorkerSecondsByLevel.begin(),WorkerSecondsByLevel.end(),0.0),DesiredTotal);
std::printf("[StreamCacheUse] hits=%llu requested=%d\n",
static_cast<unsigned long long>(Streamer.GetCacheHits()),DesiredTotal);
if(bShardAudit)
std::printf("[ThinMeshShardAudit] level=0 tiles=%llu view_components=%llu small_components=%llu isolated_in_view_small=%llu boundary_in_view_small=%llu thin_sheets=%llu sheet_triangles=%llu thickness_lt=1.000 vox pca_scale=3.5x_sigma status=%s\n",
static_cast<unsigned long long>(MeshAuditedTiles),
static_cast<unsigned long long>(MeshSheetStats.Components),
static_cast<unsigned long long>(MeshSheetStats.SmallComponents),
static_cast<unsigned long long>(MeshSheetStats.SmallComponentsInView),
static_cast<unsigned long long>(MeshSheetStats.BoundarySmallComponentsInView),
static_cast<unsigned long long>(MeshSheetStats.ThinSheets),
static_cast<unsigned long long>(MeshSheetStats.TrianglesInThinSheets),
MeshSheetStats.ThinSheets==0&&MeshSheetStats.SmallComponentsInView==0?"PASS":"FAIL");
for(int32 Level=0;Level<=MaxLevels;++Level)
{
if(TilesByLevel[Level]==0)continue;
@@ -1675,7 +1860,9 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
std::printf("[MeshCacheBake] saved %zu tiles, %.2f MiB to %s\n",CacheTiles.size(),
static_cast<double>(Bytes)/1048576.0,BakeCachePath.c_str());
}
return (Finished==DesiredTotal&&FullDetailFinished==FullDetailExpected)?0:6;
if(Finished!=DesiredTotal||FullDetailFinished!=FullDetailExpected)return 6;
if(bShardAudit&&(MeshSheetStats.ThinSheets!=0||MeshSheetStats.SmallComponentsInView!=0))return 13;
return 0;
}
}
@@ -1689,6 +1876,7 @@ int main(int Argc,char** Argv)
int32 BenchmarkLevel=-1,BenchmarkCount=64,StreamBenchmarkMaxLevel=-1;
int32 ShapeCompareLevel=-1,ShapeCompareTileLimit=0;
bool bStreamBenchmark=false;
bool bShardAudit=false;
bool bStreamHash=false;
bool bRoundWallCompare=false;
int32 RoundCompareTileCount=16;
@@ -1720,6 +1908,7 @@ int main(int Argc,char** Argv)
{App.bRoundTunnels=true;App.bRoundTunnelEnhanced=false;}
else if(Arg=="--stream-benchmark")bStreamBenchmark=true;
else if(Arg=="--stream-hash")bStreamHash=true;
else if(Arg=="--shard-audit")bShardAudit=true;
else if(Arg=="--compare-round-walls")
{bRoundWallCompare=true;App.bRoundTunnels=true;}
else if(Arg.starts_with("--round-compare-tiles="))
@@ -1735,7 +1924,7 @@ int main(int Argc,char** Argv)
else if(Arg.starts_with("--workers="))
{
const int32 Parsed=std::atoi(std::string(Arg.substr(10)).c_str());
App.WorkerCount=std::clamp(Parsed,1,8);
App.WorkerCount=std::clamp(Parsed,1,16);
}
else if(Arg.starts_with("--clip-radius="))
App.ClipRadius=std::clamp(std::atoi(std::string(Arg.substr(14)).c_str()),1,64);
@@ -1804,7 +1993,7 @@ int main(int Argc,char** Argv)
FVector(App.Position.X*4.0,App.Position.Y*4.0,App.Position.Z*4.0),
App.WorkerCount,StreamBenchmarkMaxLevel,App.bRoundTunnels,
App.bRoundTunnelEnhanced,(!App.bShapeWorld&&App.MeshCache.IsLoaded())?&App.MeshCache:nullptr,
bStreamHash,BakeMeshCachePath,App.MakeMeshCacheMetadata());
bStreamHash,bShardAudit,App.Yaw,App.Pitch,BakeMeshCachePath,App.MakeMeshCacheMetadata());
}
if(BenchmarkLevel>=0)
return RunTileBenchmark(*App.World,App.Position,BenchmarkLevel,BenchmarkCount,
+1 -1
View File
@@ -285,7 +285,7 @@ public:
bool bInHashDensity=false,
const FStandaloneMeshCache* InMeshCache=nullptr,
int32 InMaxLevelFilter=-1)
:World(&InWorld),WorkerCount(FMath::Clamp(InWorkerCount,1,8)),bHashDensity(bInHashDensity),
:World(&InWorld),WorkerCount(FMath::Clamp(InWorkerCount,1,16)),bHashDensity(bInHashDensity),
MeshCache(InMeshCache),MaxLevelFilter(InMaxLevelFilter)
{
VoxelGenLOD::InitializeConsoleSwitches();