research(standalone): shape world -- 64 m full detail in 0.96 s, whole view in 2.27 s, cold; look not there

Owner's rooms, tunnels, passages, landings and strate shells compiled as shapes, solved along
lattice lines with packed signs; rock detail only in a band sized by its supremum; far levels from
the same shapes; multithreaded meshing. 8 workers, BelowNormal, affinity 0xFF00, zero cache hits:
  full detail (960 tiles): 669 ms shapes + 290 ms mesh = 0.959 s   (was 20.3 s)
  full clipmap (6,498 tiles): 870 ms + 1.398 s = 2.268 s           (was 170.6 s)
Checks: 5,642 passage centreline samples open; smoothed and per-voxel-noise variants both fail the
detail audit; same hash across runs and 1 vs 6 workers. V toggles owner field / shape world.
Look: 6-12% of lattice signs changed; captures show flat planes without detail (passage floor, a
wall band), telescoping tunnel segments, blocky stair patches. Not accepted as the owner's world yet.

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-03 11:44:45 +02:00
1 parent 5441c7da5c
commit 8c068ee2cd
33 files changed
+1676 -32

No files matched your search

+1
View File
@@ -31,3 +31,4 @@
/Standalone/data/*.log /Standalone/data/*.log
/Standalone/data/*.err /Standalone/data/*.err
/Standalone/data/*.vfmesh /Standalone/data/*.vfmesh
/Standalone/Research/**/*.log
+6 -1
View File
@@ -58,9 +58,14 @@ $RspLines += @('/link','d3d11.lib','dxgi.lib','d3dcompiler.lib','user32.lib','gd
Push-Location $BuildDir Push-Location $BuildDir
try { try {
$CommandLine = 'call "{0}" >nul && start /belownormal /wait cl.exe @"{1}"' -f $VcVars,$ResponsePath # This PowerShell process already runs BelowNormal with affinity 0xFF00;
# 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 & $env:ComSpec /d /s /c $CommandLine
if ($LASTEXITCODE -ne 0) { throw "Standalone MSVC build failed with exit code $LASTEXITCODE" } if ($LASTEXITCODE -ne 0) { throw "Standalone MSVC build failed with exit code $LASTEXITCODE" }
if (-not (Test-Path -LiteralPath $OutputPath -PathType Leaf)) {
throw "Standalone MSVC build reported success but did not produce: $OutputPath"
}
} finally { } finally {
Pop-Location Pop-Location
} }
@@ -0,0 +1,146 @@
# Owner shape world: standalone results
The standalone path now builds the owner world from its room, tunnel, passage,
landing, floor, ceiling, and seal shapes. It does not read or write the mesh
cache. The active voxel lattice remains 25 cm; rock detail is evaluated only in
the surface band. `V` switches between the current owner density and the shape
world in the viewer.
## Method
- `Source/StandaloneShapeWorld.h` compiles the known shapes into column
intervals, combines their signs in packed words, and locates surface cells
with bit operations. Round and winding tunnels use line-solved/approximated
shape primitives; protected passage air is applied after solid overlays.
- The coherent rock detail has a proven maximum amplitude of 3.20 voxels and a
4-voxel evaluation band. Detail outside that band cannot change the base
sign. The far field is compiled from parent shapes and meshed coarsely.
- Existing marching-cubes meshing runs on worker threads. Benchmark runs use
`--force-generate`; shape-world mode also bypasses cache loading. The reports
below show zero cache hits.
- Builds are serial (no `/MP` flag). The build host sets BelowNormal priority
and affinity `0xFF00`; launched viewers use BelowNormal and `0xFF00` too.
## Cold generation timings
Each timing is shape compilation plus uncached mesh generation. The full-detail
area is the 64 m cube with 960 LOD0–1 tiles. The full clipmap contains 6,498
tiles through LOD6. Hashing is off for these timing runs.
| Cold job | Shape stage | Mesh generation wall | Total | Result |
| --- | ---: | ---: | ---: | --- |
| Full detail, 8 workers | 668.566 ms | 290 ms | **0.959 s** | 960/960 tiles; cache hits 0 |
| Full clipmap, 8 workers | 870.418 ms, including far parents | 1.398 s | **2.268 s** | 6,498/6,498 tiles; cache hits 0 |
Full-detail stage breakdown: local shapes 53.892 ms, intervals/signs 196.326
ms, surface band 417.952 ms. Full-clipmap breakdown: local shapes 53.281 ms,
intervals/signs 195.162 ms, surface band 424.921 ms, far parents 196.578 ms.
The measured full-detail total is close to the 1 s limit; the full clipmap has
more margin under 5 s.
Logs: `final/full_detail_cold_w8_final.stdout.log` and
`final/full_clipmap_cold_w8_final.stdout.log`.
## Similarity and captures
The viewer captures use explicit positions and yaws. Owner and shape images are
paired at the same camera settings. The far owner image is the existing cold
baseline capture at the default start view; its matching shape capture was
made at the same position and yaw.
| View | Owner capture | Shape capture |
| --- | --- | --- |
| Chamber, yaw 0° | `final/chamber_fixed_owner.png` | `final/chamber_fixed_shape.png` |
| Tunnel, yaw 90° | `final/tunnel_fixed_owner.png` | `final/tunnel_fixed_shape.png` |
| Passage, yaw 63° | `final/passage_fixed_owner.png` | `final/passage_fixed_shape.png` |
| Far view, yaw 0° | `final/far_owner.png` | `final/far_fixed_shape.png` |
Comparison results:
| View/LOD | Tiles | Lattice samples | Changed signs | Share changed | Mean / max surface distance* | Unmatched surface samples |
| --- | ---: | ---: | ---: | ---: | ---: | ---: |
| Chamber, LOD0–1 | 960 | 35,103,600 | 4,165,142 | 11.8653% | 1.1763 m / 6.5210 m | 69,626 |
| Tunnel, LOD0–1 | 960 | 35,103,600 | 4,165,142 | 11.8653% | 1.1763 m / 6.5210 m | 69,626 |
| Passage, LOD0–1 | 960 | 37,240,000 | 2,352,343 | 6.3167% | 1.0102 m / 6.7059 m | 35,520 |
| Far view, outer LOD6 | 4,770 | 9,470,835 | 856,305 | 9.0415% | 3.5551 m / 5.7456 m | 475,722 |
\* Surface distance samples every fourth mesh vertex. The mean and maximum
include matched samples only; unmatched samples are listed separately. The
distance check searches neighboring 1 m buckets, so treat it as a sampled
comparison rather than a continuous Hausdorff distance. Chamber and tunnel
share the same camera position and tile set; only yaw differs.
The captures show that the shape world retains broad passage structure, but
they also show a visible change in chamber profiles and rock relief. In
particular, this does not reproduce the painterly cave reference image. The
sign shares and sampled distances quantify that gap; the current result should
be treated as a fast, coherent alternate world rather than a visual match.
## Detail, walkability, and determinism
The final coherent detail audit reports `supremum=3.20 vox`, `band=4`, 476,494
surface sign flips, adjacent correlation 0.9899, and both detail checks PASS.
Two deliberately bad variants were also run:
| Variant | Surface-detail check | Spatial-scale check | Process result |
| --- | --- | --- | --- |
| Coherent detail | PASS | PASS | exit 0 |
| Smoothed detail | **FAIL** | PASS | exit 7, expected |
| Per-voxel noise | PASS | **FAIL** | exit 7, expected |
The route audit samples packed lattice signs along every exported passage
centerline: **5,642/5,642 samples remain open**, with native landing floors
retained. This is a centerline/sign check, not a character-capsule collision
simulation.
Three cold hash runs at the passage camera agree across two 6-worker runs and
one 1-worker run:
| Hash | 6 workers, run A | 6 workers, run B | 1 worker |
| --- | --- | --- | --- |
| Full detail | `0x88e5818a444c1ab3` | `0x88e5818a444c1ab3` | `0x88e5818a444c1ab3` |
| LOD0 | `0x3d37fc984f0ee55b` | `0x3d37fc984f0ee55b` | `0x3d37fc984f0ee55b` |
| LOD1 | `0xc314a1f6457f8a31` | `0xc314a1f6457f8a31` | `0xc314a1f6457f8a31` |
This confirms repeatability and worker-count independence on this machine; a
second hardware/architecture was not available for a cross-machine hash run.
## Viewer and evidence files
Press `V` in the standalone viewer to swap owner density and shape intervals;
the viewer rebuilds the resident field. The control is handled in
`Source/StandaloneViewer.cpp` near the keyboard input handler.
The logs under `final/` include the cold timings, audits, comparisons, captures,
and determinism runs. In particular:
- `audit_coherent_final.stdout.log`, `audit_smooth_bad_final.stdout.log`,
`audit_per_voxel_bad_final.stdout.log`
- `chamber_compare_final.stdout.log`, `tunnel_compare_final.stdout.log`,
`passage_compare_landingbox.stdout.log`, `far_compare_lod6.stdout.log`
- `determinism_final_w6_a.stdout.log`, `determinism_final_w6_b.stdout.log`,
`determinism_final_w1.stdout.log`
The LOD6 similarity command took 101.3 s because it also sampled nearest-surface
distances over the entire outer tile set. That is comparison-analysis time, not
world-generation time.
## Failed and abandoned attempts
- The original build script could print success after failing to leave an
executable. `Build.ps1` now invokes `cl.exe` directly, propagates its exit
code, and checks the output file. A launcher attempt before that correction
failed with Windows `CreateProcess error 2` because the executable was
absent.
- The first checked compile then found an `FIntVector` to `FVector` conversion
error in the optional tile-sort path. The type was corrected; the final
serial build succeeded. Visual Studio setup still emits a `vswhere.exe`-not-
recognized diagnostic, but `cl.exe` runs and produces the binary.
- An earlier smooth-detail audit passed before its gate was tightened. The
final smooth variant now exits 7 on the surface-detail check, as shown above.
- A fresh owner far-view capture was stopped after 55 s because the existing
uncached owner clipmap run takes about 183 s. The existing owner capture was
reused; the shape far capture completed in 2.65 s. No viewer started by
someone else was closed.
No commit, push, or stash was made.
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

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

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

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

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

File diff suppressed because it is too large. Load diff
+296 -31
View File
@@ -1,6 +1,7 @@
#include "OwnerWorldLoader.h" #include "OwnerWorldLoader.h"
#include "StreamingWorld.h" #include "StreamingWorld.h"
#include "StandaloneResearchTunnels.h" #include "StandaloneResearchTunnels.h"
#include "StandaloneShapeWorld.h"
#include "VoxelDensityProfile.h" #include "VoxelDensityProfile.h"
#ifdef TEXT #ifdef TEXT
@@ -179,6 +180,7 @@ namespace
ID3D11RasterizerState* SolidRasterizer=nullptr; ID3D11RasterizerState* SolidRasterizer=nullptr;
ID3D11RasterizerState* WireRasterizer=nullptr; ID3D11RasterizerState* WireRasterizer=nullptr;
std::unique_ptr<FStandaloneOwnerWorld> World; std::unique_ptr<FStandaloneOwnerWorld> World;
std::unique_ptr<FStandaloneShapeWorld> ShapeWorld;
std::unique_ptr<FStandaloneStreamingGenerator> Streamer; std::unique_ptr<FStandaloneStreamingGenerator> Streamer;
FStandaloneMeshCache MeshCache; FStandaloneMeshCache MeshCache;
std::string DataPath; std::string DataPath;
@@ -203,6 +205,11 @@ namespace
bool bParityChecked=false; bool bParityChecked=false;
bool bRoundTunnels=false; bool bRoundTunnels=false;
bool bRoundTunnelEnhanced=true; bool bRoundTunnelEnhanced=true;
bool bShapeWorld=false;
bool bShapeAuditOnly=false;
bool bShapeCompare=false;
bool bFarShapeStagePending=false;
FStandaloneShapeWorld::EDetailVariant ShapeDetailVariant=FStandaloneShapeWorld::EDetailVariant::Coherent;
bool bWireframe=false; bool bWireframe=false;
bool bCaptured=false; bool bCaptured=false;
bool bAutoCaptureWritten=false; bool bAutoCaptureWritten=false;
@@ -464,6 +471,8 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
bool ConfigureWorld(std::string& Error) bool ConfigureWorld(std::string& Error)
{ {
FStandaloneShapeWorldAccess::SetActive(nullptr);
ShapeWorld.reset();
World=std::make_unique<FStandaloneOwnerWorld>(); World=std::make_unique<FStandaloneOwnerWorld>();
if(!StandaloneOwnerLoader::Load(DataPath,*World,Error))return false; if(!StandaloneOwnerLoader::Load(DataPath,*World,Error))return false;
if(!bParityChecked&&Seed==World->Seed&&!bRoundTunnels)PrintParity(); if(!bParityChecked&&Seed==World->Seed&&!bRoundTunnels)PrintParity();
@@ -480,10 +489,41 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
} }
if(!PrepareStandaloneDensityWorld(*World,Seed,bRoundTunnels, if(!PrepareStandaloneDensityWorld(*World,Seed,bRoundTunnels,
bRoundTunnelEnhanced,Error))return false; bRoundTunnelEnhanced,Error))return false;
if(bShapeWorld&&!BuildShapeWorldForCamera(Error))return false;
LoadMeshCache(); LoadMeshCache();
return true; return true;
} }
bool BuildShapeWorldForCamera(std::string& Error,bool bIncludeFarWorld=false)
{
const FVector CameraVoxels(Position.X*4.0,Position.Y*4.0,Position.Z*4.0);
const FStandaloneDesiredTileSet Desired=StandaloneWorldMesher::BuildDesiredSet(*World,CameraVoxels);
FIntVector Min(INT32_MAX,INT32_MAX,INT32_MAX),Max(INT32_MIN,INT32_MIN,INT32_MIN);
FIntVector ClipMin(INT32_MAX,INT32_MAX,INT32_MAX),ClipMax(INT32_MIN,INT32_MIN,INT32_MIN);
uint64 FineTiles=0;
for(const FVoxelTileKey& Tile:Desired.Tiles)
{
const FIntVector Origin=Tile.OriginVoxels();
const int32 Extent=Tile.ExtentVoxels();
ClipMin.X=std::min(ClipMin.X,Origin.X);ClipMin.Y=std::min(ClipMin.Y,Origin.Y);ClipMin.Z=std::min(ClipMin.Z,Origin.Z);
ClipMax.X=std::max(ClipMax.X,Origin.X+Extent);ClipMax.Y=std::max(ClipMax.Y,Origin.Y+Extent);ClipMax.Z=std::max(ClipMax.Z,Origin.Z+Extent);
if(Tile.Level>=World->FullResClipLevels)continue;
Min.X=std::min(Min.X,Origin.X);Min.Y=std::min(Min.Y,Origin.Y);Min.Z=std::min(Min.Z,Origin.Z);
Max.X=std::max(Max.X,Origin.X+Extent);Max.Y=std::max(Max.Y,Origin.Y+Extent);Max.Z=std::max(Max.Z,Origin.Z+Extent);
++FineTiles;
}
if(!FineTiles){Error="no full-detail tiles are available for shape compilation";return false;}
ShapeWorld=std::make_unique<FStandaloneShapeWorld>();
if(!ShapeWorld->Build(*World,Min,Max,ClipMin,ClipMax,WorkerCount,ShapeDetailVariant,bIncludeFarWorld,Error))
{ShapeWorld.reset();return false;}
FStandaloneShapeWorldAccess::SetActive(ShapeWorld.get());
bFarShapeStagePending=bShapeWorld&&!ShapeWorld->HasFarWorld();
std::printf("[ShapeRegion] fine_tiles=%llu bounds=(%d,%d,%d)..(%d,%d,%d) clip=(%d,%d,%d)..(%d,%d,%d)\n",
static_cast<unsigned long long>(FineTiles),Min.X,Min.Y,Min.Z,Max.X,Max.Y,Max.Z,
ClipMin.X,ClipMin.Y,ClipMin.Z,ClipMax.X,ClipMax.Y,ClipMax.Z);
return true;
}
FStandaloneMeshCacheMetadata MakeMeshCacheMetadata()const FStandaloneMeshCacheMetadata MakeMeshCacheMetadata()const
{ {
FStandaloneMeshCacheMetadata Metadata; FStandaloneMeshCacheMetadata Metadata;
@@ -503,7 +543,7 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
void LoadMeshCache() void LoadMeshCache()
{ {
MeshCache.Clear(); MeshCache.Clear();
if(bForceGenerate||!World)return; if(bForceGenerate||bShapeWorld||!World)return;
const char* ModeSuffix=!bRoundTunnels?"_base": const char* ModeSuffix=!bRoundTunnels?"_base":
(bRoundTunnelEnhanced?"_round_detailed":"_round_legacy"); (bRoundTunnelEnhanced?"_round_detailed":"_round_legacy");
const std::filesystem::path CachePath=std::filesystem::path(DataPath).parent_path() const std::filesystem::path CachePath=std::filesystem::path(DataPath).parent_path()
@@ -529,12 +569,12 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
{ {
StopGeneration(); StopGeneration();
ReleaseMeshes(); ReleaseMeshes();
GenerationStart=std::chrono::steady_clock::now();
std::string Error; std::string Error;
if(!ConfigureWorld(Error)) if(!ConfigureWorld(Error))
{std::fprintf(stderr,"World load failed: %s\n",Error.c_str());MessageBoxA(Window,Error.c_str(),"VoxelForge viewer",MB_ICONERROR);return;} {std::fprintf(stderr,"World load failed: %s\n",Error.c_str());MessageBoxA(Window,Error.c_str(),"VoxelForge viewer",MB_ICONERROR);return;}
const FVector CameraVoxels(Position.X*4.0,Position.Y*4.0,Position.Z*4.0); const FVector CameraVoxels(Position.X*4.0,Position.Y*4.0,Position.Z*4.0);
BuildTargetChunk=StandaloneWorldMesher::CameraChunk(CameraVoxels); BuildTargetChunk=StandaloneWorldMesher::CameraChunk(CameraVoxels);
GenerationStart=std::chrono::steady_clock::now();
bFullDetailReported=false;bInitialClipmapReported=false;bAutoCaptureWritten=false; bFullDetailReported=false;bInitialClipmapReported=false;bAutoCaptureWritten=false;
if(bProfileAttribution) if(bProfileAttribution)
{ {
@@ -548,7 +588,8 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
for(int32 I=0;I<=MaxLevels;++I)DesiredByLevel[I]=0; for(int32 I=0;I<=MaxLevels;++I)DesiredByLevel[I]=0;
for(const FVoxelTileKey& Tile:DesiredTiles)++DesiredByLevel[Tile.Level]; for(const FVoxelTileKey& Tile:DesiredTiles)++DesiredByLevel[Tile.Level];
Streamer=std::make_unique<FStandaloneStreamingGenerator>(*World,WorkerCount,false, Streamer=std::make_unique<FStandaloneStreamingGenerator>(*World,WorkerCount,false,
MeshCache.IsLoaded()?&MeshCache:nullptr); (!bShapeWorld&&MeshCache.IsLoaded())?&MeshCache:nullptr,
bFarShapeStagePending?World->FullResClipLevels-1:-1);
const FStandaloneStreamingUpdate Update=Streamer->Start(CameraVoxels,{},CrossingIndex); const FStandaloneStreamingUpdate Update=Streamer->Start(CameraVoxels,{},CrossingIndex);
FCrossingWork& InitialWork=CrossingWork[CrossingIndex]; FCrossingWork& InitialWork=CrossingWork[CrossingIndex];
InitialWork.SubmittedJobs=Update.NewJobs;InitialWork.PendingJobs=Update.NewJobs; InitialWork.SubmittedJobs=Update.NewJobs;InitialWork.PendingJobs=Update.NewJobs;
@@ -556,8 +597,8 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
bGenerationRunning=true;bRebuildPending=false; bGenerationRunning=true;bRebuildPending=false;
const char* TunnelMode=!bRoundTunnels?"":(bRoundTunnelEnhanced const char* TunnelMode=!bRoundTunnels?"":(bRoundTunnelEnhanced
?" with detailed round/winding tunnels":" with legacy round/winding tunnels"); ?" with detailed round/winding tunnels":" with legacy round/winding tunnels");
std::printf("Streaming seed %d%s: %d desired tiles, %d full-detail tiles, %d workers; upload budget %d batches / %.1f ms per frame.\n", std::printf("Streaming seed %d%s%s: %d desired tiles, %d full-detail tiles, %d workers; upload budget %d batches / %.1f ms per frame.\n",
Seed,TunnelMode,Update.DesiredTiles, Seed,TunnelMode,bShapeWorld?" [shape intervals]":"",Update.DesiredTiles,
Update.FullResTiles,Streamer->GetWorkerCount(),MaxBatchUploadsPerFrame,BatchUploadBudgetMilliseconds); Update.FullResTiles,Streamer->GetWorkerCount(),MaxBatchUploadsPerFrame,BatchUploadBudgetMilliseconds);
std::fflush(stdout); std::fflush(stdout);
} }
@@ -569,6 +610,28 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
std::printf("Seed changed to %d; replacing the current streaming world.\n",Seed); std::printf("Seed changed to %d; replacing the current streaming world.\n",Seed);
} }
void StartFarShapeGeneration()
{
if(!bFarShapeStagePending||!ShapeWorld||ShapeWorld->HasFarWorld()||!Streamer)return;
ShapeWorld->BuildFarWorld();
bFarShapeStagePending=false;
Streamer->Stop();
std::vector<FVoxelTileKey> ResidentKeys;ResidentKeys.reserve(ResidentTiles.size());
for(const auto& Pair:ResidentTiles)ResidentKeys.push_back(Pair.first);
Streamer.reset();
Streamer=std::make_unique<FStandaloneStreamingGenerator>(*World,WorkerCount,false,nullptr,-1);
const FVector CameraVoxels(Position.X*4.0,Position.Y*4.0,Position.Z*4.0);
++CrossingIndex;
const FStandaloneStreamingUpdate Update=Streamer->Start(CameraVoxels,ResidentKeys,CrossingIndex);
FCrossingWork& Work=CrossingWork[CrossingIndex];
Work.SubmittedJobs=Update.NewJobs;Work.PendingJobs=Update.NewJobs;
Work.StartTime=std::chrono::steady_clock::now();
bGenerationRunning=true;
std::printf("[FarStreamStart] desired=%d retained=%d new_jobs=%d workers=%d\n",
Update.DesiredTiles,Update.RetainedResidentTiles,Update.NewJobs,WorkerCount);
std::fflush(stdout);
}
void MarkBatchDirty(const FStandaloneBatchKey& Key) void MarkBatchDirty(const FStandaloneBatchKey& Key)
{DirtyBatchSet.insert(Key);} {DirtyBatchSet.insert(Key);}
@@ -602,6 +665,12 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
const FVector CameraVoxels(Position.X*4.0,Position.Y*4.0,Position.Z*4.0); const FVector CameraVoxels(Position.X*4.0,Position.Y*4.0,Position.Z*4.0);
FStandaloneDesiredTileSet NewDesired=StandaloneWorldMesher::BuildDesiredSet(*World,CameraVoxels); FStandaloneDesiredTileSet NewDesired=StandaloneWorldMesher::BuildDesiredSet(*World,CameraVoxels);
if(NewDesired.CenterChunk==BuildTargetChunk)return; if(NewDesired.CenterChunk==BuildTargetChunk)return;
if(bShapeWorld)
{
bRebuildPending=true;
std::printf("[ShapeRegion] camera crossed its compiled full-detail bounds; rebuilding the interval region.\n");
return;
}
const FIntVector Previous=BuildTargetChunk; const FIntVector Previous=BuildTargetChunk;
std::unordered_set<FVoxelTileKey,FStandaloneTileKeyHash> NewSet; std::unordered_set<FVoxelTileKey,FStandaloneTileKeyHash> NewSet;
NewSet.reserve(NewDesired.Tiles.size()*2+1); NewSet.reserve(NewDesired.Tiles.size()*2+1);
@@ -835,6 +904,7 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
std::printf("[FullDetailReady] %.3f s after spawn; resident=%zu/%d tiles through LOD%d.\n", std::printf("[FullDetailReady] %.3f s after spawn; resident=%zu/%d tiles through LOD%d.\n",
Elapsed,ResidentTiles.size(),Streamer->FullResDesiredCount(),World->FullResClipLevels-1); Elapsed,ResidentTiles.size(),Streamer->FullResDesiredCount(),World->FullResClipLevels-1);
ReportTileTimings();std::fflush(stdout); ReportTileTimings();std::fflush(stdout);
StartFarShapeGeneration();
} }
if(!bInitialClipmapReported&&ResidentTiles.size()==DesiredTiles.size()&&DirtyBatchSet.empty()) if(!bInitialClipmapReported&&ResidentTiles.size()==DesiredTiles.size()&&DirtyBatchSet.empty())
{ {
@@ -873,7 +943,7 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
void UpdateTitle() void UpdateTitle()
{ {
uint64 VisibleIndices=0;for(const auto& Pair:StreamingBatches)if(bShowLevel[Pair.first.Level])VisibleIndices+=Pair.second.IndexCount; uint64 VisibleIndices=0;for(const auto& Pair:StreamingBatches)if(bShowLevel[Pair.first.Level])VisibleIndices+=Pair.second.IndexCount;
std::ostringstream S;S<<"VoxelForge standalone | seed "<<Seed<<" | "<<(bRoundTunnels?"round/winding research tunnels":"base world") std::ostringstream S;S<<"VoxelForge standalone | seed "<<Seed<<" | "<<(bShapeWorld?"shape-interval owner world":(bRoundTunnels?"round/winding research tunnels":"base world"))
<<(bRoundTunnels?(bRoundTunnelEnhanced?" (detailed walls)":" (legacy walls)"):"") <<(bRoundTunnels?(bRoundTunnelEnhanced?" (detailed walls)":" (legacy walls)"):"")
<<" | "<<VisibleIndices/3<<" tris | "<<FrameMs<<" ms | "<<(bGenerationRunning?"generating":"ready") <<" | "<<VisibleIndices/3<<" tris | "<<FrameMs<<" ms | "<<(bGenerationRunning?"generating":"ready")
<<" | pos "<<std::fixed<<std::setprecision(1)<<Position.X<<", "<<Position.Y<<", "<<Position.Z<<" m"; <<" | pos "<<std::fixed<<std::setprecision(1)<<Position.X<<", "<<Position.Y<<", "<<Position.Z<<" m";
@@ -945,6 +1015,7 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
if(W==VK_ESCAPE){if(GApp->bCaptured)GApp->Capture(false);else DestroyWindow(H);} if(W==VK_ESCAPE){if(GApp->bCaptured)GApp->Capture(false);else DestroyWindow(H);}
if(!bWasDown&&W=='F')GApp->bWireframe=!GApp->bWireframe; if(!bWasDown&&W=='F')GApp->bWireframe=!GApp->bWireframe;
if(!bWasDown&&W=='T'){GApp->bRoundTunnels=!GApp->bRoundTunnels;GApp->bRebuildPending=true;} if(!bWasDown&&W=='T'){GApp->bRoundTunnels=!GApp->bRoundTunnels;GApp->bRebuildPending=true;}
if(!bWasDown&&W=='V'){GApp->bShapeWorld=!GApp->bShapeWorld;GApp->bRebuildPending=true;}
if(!bWasDown&&W=='G'&&GApp->bRoundTunnels) if(!bWasDown&&W=='G'&&GApp->bRoundTunnels)
{ {
GApp->bRoundTunnelEnhanced=!GApp->bRoundTunnelEnhanced; GApp->bRoundTunnelEnhanced=!GApp->bRoundTunnelEnhanced;
@@ -1174,11 +1245,11 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
}; };
FDirectedSurfaceDistance DirectedSurfaceDistance( FDirectedSurfaceDistance DirectedSurfaceDistance(
const std::vector<FComparePoint>& From,const std::vector<FComparePoint>& To) const std::vector<FComparePoint>& From,const std::vector<FComparePoint>& To,
double BucketWidth=1.0,int32 SearchCells=3)
{ {
FDirectedSurfaceDistance Result; FDirectedSurfaceDistance Result;
if(From.empty()||To.empty()){Result.Unmatched=From.size();return Result;} if(From.empty()||To.empty()){Result.Unmatched=From.size();return Result;}
constexpr double BucketWidth=1.0;
std::unordered_map<FIntVector,std::vector<const FComparePoint*>,FCompareBucketHash> Buckets; std::unordered_map<FIntVector,std::vector<const FComparePoint*>,FCompareBucketHash> Buckets;
Buckets.reserve(To.size()); Buckets.reserve(To.size());
const auto BucketOf=[=](const FComparePoint& P) const auto BucketOf=[=](const FComparePoint& P)
@@ -1192,7 +1263,9 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
{ {
const FIntVector Center=BucketOf(P); const FIntVector Center=BucketOf(P);
double BestSquared=std::numeric_limits<double>::infinity(); double BestSquared=std::numeric_limits<double>::infinity();
for(int32 Z=-3;Z<=3;++Z)for(int32 Y=-3;Y<=3;++Y)for(int32 X=-3;X<=3;++X) for(int32 Z=-SearchCells;Z<=SearchCells;++Z)
for(int32 Y=-SearchCells;Y<=SearchCells;++Y)
for(int32 X=-SearchCells;X<=SearchCells;++X)
{ {
const auto It=Buckets.find(Center+FIntVector(X,Y,Z)); const auto It=Buckets.find(Center+FIntVector(X,Y,Z));
if(It==Buckets.end())continue; if(It==Buckets.end())continue;
@@ -1212,6 +1285,148 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
return Result; return Result;
} }
int RunOwnerShapeComparison(FStandaloneOwnerWorld& World,const FStandaloneShapeWorld& Shape,
V3 CameraPosition,int32 Workers,int32 LevelFilter,int32 TileLimit)
{
if(FStandaloneShapeWorldAccess::GetActive()!=&Shape)
{std::fprintf(stderr,"Shape comparison requires the compiled interval world to be active.\n");return 11;}
if(LevelFilter>=World.FullResClipLevels&&!Shape.HasFarWorld())
const_cast<FStandaloneShapeWorld&>(Shape).BuildFarWorld();
const FVector CameraVoxels(CameraPosition.X*4.0,CameraPosition.Y*4.0,CameraPosition.Z*4.0);
{
const int32 Offsets[][3]={{0,0,0},{4,0,0},{-4,0,0},{0,4,0},{0,-4,0},{0,0,4},{0,0,-4}};
for(const auto& Offset:Offsets)
{
const float X=static_cast<float>(CameraVoxels.X+Offset[0]);
const float Y=static_cast<float>(CameraVoxels.Y+Offset[1]);
const float Z=static_cast<float>(CameraVoxels.Z+Offset[2]);
float OwnerDensity=0.0f;
{FStandaloneShapeWorldAccess::FScopedBypass Bypass;
OwnerDensity=World.Generator->GetDensityAt(X,Y,Z);}
const float ShapeDensity=Shape.DensityAt(X,Y,Z);
std::printf("[ShapeProbe] voxels=(%.1f,%.1f,%.1f) owner=%.5f shape=%.5f owner_air=%d shape_air=%d protected_air=%d solid_overlay=%d\n",
X,Y,Z,OwnerDensity,ShapeDensity,OwnerDensity>=0.0f?1:0,ShapeDensity>=0.0f?1:0,
Shape.DebugCriticalAirAt(X,Y,Z)?1:0,Shape.DebugSolidOverlayAt(X,Y,Z)?1:0);
}
}
const FStandaloneDesiredTileSet Desired=StandaloneWorldMesher::BuildDesiredSet(World,CameraVoxels);
std::vector<FVoxelTileKey> Tiles;
for(const FVoxelTileKey& Tile:Desired.Tiles)
if(LevelFilter<0?Tile.Level<World.FullResClipLevels:Tile.Level==LevelFilter)
Tiles.push_back(Tile);
if(TileLimit>0&&Tiles.size()>static_cast<size_t>(TileLimit))
{
std::sort(Tiles.begin(),Tiles.end(),[&](const FVoxelTileKey& A,const FVoxelTileKey& B)
{
const FIntVector AO=A.OriginVoxels(),BO=B.OriginVoxels();
const double ADX=AO.X+A.ExtentVoxels()*0.5-CameraVoxels.X;
const double ADY=AO.Y+A.ExtentVoxels()*0.5-CameraVoxels.Y;
const double ADZ=AO.Z+A.ExtentVoxels()*0.5-CameraVoxels.Z;
const double BDX=BO.X+B.ExtentVoxels()*0.5-CameraVoxels.X;
const double BDY=BO.Y+B.ExtentVoxels()*0.5-CameraVoxels.Y;
const double BDZ=BO.Z+B.ExtentVoxels()*0.5-CameraVoxels.Z;
return ADX*ADX+ADY*ADY+ADZ*ADZ<BDX*BDX+BDY*BDY+BDZ*BDZ;
});
Tiles.resize(static_cast<size_t>(TileLimit));
}
if(Tiles.empty()){std::fprintf(stderr,"No tiles are available for the requested shape comparison level.\n");return 12;}
struct FTileComparison
{
uint64 Samples=0,Changed=0,MeshMismatch=0;
uint64 OldHash=14695981039346656037ull,NewHash=14695981039346656037ull;
std::vector<FComparePoint> OldSurface,NewSurface;
};
std::vector<FTileComparison> Results(Tiles.size());
int32 MinChunkZ=MIN_int32,MaxChunkZ=MAX_int32,TopChunkZ=0,BottomChunkZ=0;
if(World.Settings->bClampViewToStrate&&World.Manager->GetStrateChunkZBounds(
StandaloneWorldMesher::CameraChunk(CameraVoxels).Z,TopChunkZ,BottomChunkZ))
{MinChunkZ=BottomChunkZ*CHUNK_SIZE;MaxChunkZ=(TopChunkZ+1)*CHUNK_SIZE;}
std::atomic<size_t> Next{0};
std::vector<std::thread> Threads;
const int32 ThreadCount=std::clamp(Workers,1,8);
Threads.reserve(ThreadCount);
for(int32 Worker=0;Worker<ThreadCount;++Worker)
{
Threads.emplace_back([&]()
{
UVoxelMarchingCubesMesher Mesher;
Mesher.SetGenerator(World.Generator.get());
Mesher.bGenerateSkirts=World.bGenerateSkirts;
Mesher.SkirtCells=static_cast<float>(World.SkirtCells);
Mesher.LODOctaveDrop=World.LODOctaveDrop;
for(;;)
{
const size_t Index=Next.fetch_add(1,std::memory_order_relaxed);
if(Index>=Tiles.size())break;
const FVoxelTileKey& Tile=Tiles[Index];
const int32 Cells=Tile.Level<World.FullResClipLevels?CHUNK_SIZE:World.CoarseTileCells;
const int32 Step=std::max(1,Tile.ExtentVoxels()/Cells);
std::vector<float> NewDensities,OldDensities;
FVoxelMeshData NewMesh=Mesher.GenerateMesh(Tile.OriginVoxels(),Step,Cells,nullptr,
MinChunkZ,MaxChunkZ,nullptr,nullptr,nullptr,&NewDensities);
FVoxelMeshData OldMesh;
{
FStandaloneShapeWorldAccess::FScopedBypass Bypass;
OldMesh=Mesher.GenerateMesh(Tile.OriginVoxels(),Step,Cells,nullptr,
MinChunkZ,MaxChunkZ,nullptr,nullptr,nullptr,&OldDensities);
}
FTileComparison& R=Results[Index];
const size_t Shared=std::min(NewDensities.size(),OldDensities.size());
R.Samples=Shared;
R.MeshMismatch=std::max(NewDensities.size(),OldDensities.size())-Shared;
for(size_t I=0;I<Shared;++I)
{
if((NewDensities[I]<0.0f)!=(OldDensities[I]<0.0f))++R.Changed;
R.NewHash^=std::bit_cast<uint32>(NewDensities[I]);R.NewHash*=1099511628211ull;
R.OldHash^=std::bit_cast<uint32>(OldDensities[I]);R.OldHash*=1099511628211ull;
}
constexpr size_t VertexStride=4;
R.NewSurface.reserve(static_cast<size_t>(NewMesh.Vertices.Num())/VertexStride+1);
for(int32 I=0;I<NewMesh.Vertices.Num();I+=static_cast<int32>(VertexStride))
{const FVector& P=NewMesh.Vertices[I];R.NewSurface.push_back({P.X*0.01,P.Y*0.01,P.Z*0.01});}
R.OldSurface.reserve(static_cast<size_t>(OldMesh.Vertices.Num())/VertexStride+1);
for(int32 I=0;I<OldMesh.Vertices.Num();I+=static_cast<int32>(VertexStride))
{const FVector& P=OldMesh.Vertices[I];R.OldSurface.push_back({P.X*0.01,P.Y*0.01,P.Z*0.01});}
}
});
}
for(std::thread& Thread:Threads)Thread.join();
uint64 Samples=0,Changed=0,MeshMismatch=0;
uint64 OldHash=14695981039346656037ull,NewHash=14695981039346656037ull;
size_t OldVertexCount=0,NewVertexCount=0;
std::vector<FComparePoint> OldSurface,NewSurface;
for(const FTileComparison& R:Results)
{
Samples+=R.Samples;Changed+=R.Changed;MeshMismatch+=R.MeshMismatch;
OldHash^=R.OldHash;OldHash*=1099511628211ull;
NewHash^=R.NewHash;NewHash*=1099511628211ull;
OldVertexCount+=R.OldSurface.size();NewVertexCount+=R.NewSurface.size();
}
OldSurface.reserve(OldVertexCount);NewSurface.reserve(NewVertexCount);
for(FTileComparison& R:Results)
{
OldSurface.insert(OldSurface.end(),R.OldSurface.begin(),R.OldSurface.end());
NewSurface.insert(NewSurface.end(),R.NewSurface.begin(),R.NewSurface.end());
}
const FDirectedSurfaceDistance OldToNew=DirectedSurfaceDistance(OldSurface,NewSurface,1.0,3);
const FDirectedSurfaceDistance NewToOld=DirectedSurfaceDistance(NewSurface,OldSurface,1.0,3);
const uint64 DistanceSamples=OldToNew.Count+NewToOld.Count;
const double MeanDistance=DistanceSamples
?(OldToNew.Sum+NewToOld.Sum)/static_cast<double>(DistanceSamples):0.0;
std::printf("[OwnerShapeCompare] tiles=%zu fullres_levels=%d level_filter=%d lattice_samples=%llu changed_signs=%llu changed_share=%.6f%% old_hash=0x%016llx shape_hash=0x%016llx mesh_sample_mismatch=%llu old_surface_samples=%zu shape_surface_samples=%zu symmetric_vertex_mean_m=%.4f symmetric_vertex_max_m=%.4f unmatched_surface_samples=%llu vertex_stride=4 workers=%d\n",
Tiles.size(),World.FullResClipLevels,LevelFilter,static_cast<unsigned long long>(Samples),
static_cast<unsigned long long>(Changed),Samples?100.0*Changed/Samples:0.0,
static_cast<unsigned long long>(OldHash),static_cast<unsigned long long>(NewHash),
static_cast<unsigned long long>(MeshMismatch),OldSurface.size(),NewSurface.size(),
MeanDistance,std::max(OldToNew.Maximum,NewToOld.Maximum),
static_cast<unsigned long long>(OldToNew.Unmatched+NewToOld.Unmatched),ThreadCount);
std::fflush(stdout);
return Samples>0&&MeshMismatch==0?0:13;
}
int RunRoundWallComparison(FStandaloneOwnerWorld& World,V3 CameraPosition,int32 TileCount) int RunRoundWallComparison(FStandaloneOwnerWorld& World,V3 CameraPosition,int32 TileCount)
{ {
const FVector CameraVoxels(CameraPosition.X*4.0,CameraPosition.Y*4.0,CameraPosition.Z*4.0); const FVector CameraVoxels(CameraPosition.X*4.0,CameraPosition.Y*4.0,CameraPosition.Z*4.0);
@@ -1329,6 +1544,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, int RunStreamingBenchmark(FStandaloneOwnerWorld& World,const FVector& CameraPositionVoxels,
int32 WorkerCount,int32 MaxLevel,bool bRoundTunnels, int32 WorkerCount,int32 MaxLevel,bool bRoundTunnels,
bool bEnhancedWalls,const FStandaloneMeshCache* Cache, bool bEnhancedWalls,const FStandaloneMeshCache* Cache,
bool bHashDensity,
const std::string& BakeCachePath, const std::string& BakeCachePath,
const FStandaloneMeshCacheMetadata& CacheMetadata) const FStandaloneMeshCacheMetadata& CacheMetadata)
{ {
@@ -1349,18 +1565,21 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
std::array<uint64,MaxLevels+1> SamplesByLevel{}; std::array<uint64,MaxLevels+1> SamplesByLevel{};
std::array<uint64,MaxLevels+1> VerticesByLevel{}; std::array<uint64,MaxLevels+1> VerticesByLevel{};
std::array<uint64,MaxLevels+1> IndicesByLevel{}; std::array<uint64,MaxLevels+1> IndicesByLevel{};
std::array<uint64,MaxLevels+1> TilesByLevel{};
std::array<double,MaxLevels+1> WorkerSecondsByLevel{}; std::array<double,MaxLevels+1> WorkerSecondsByLevel{};
std::vector<FStandaloneCpuTile> CacheTiles; std::vector<FStandaloneCpuTile> CacheTiles;
if(!BakeCachePath.empty())CacheTiles.reserve(Desired.Tiles.size()); if(!BakeCachePath.empty())CacheTiles.reserve(Desired.Tiles.size());
const auto Start=std::chrono::steady_clock::now(); const auto Start=std::chrono::steady_clock::now();
FStandaloneStreamingGenerator Streamer(World,WorkerCount,true,Cache,MaxLevel); FStandaloneStreamingGenerator Streamer(World,WorkerCount,
bHashDensity||!BakeCachePath.empty(),Cache,MaxLevel);
const FStandaloneStreamingUpdate Update=Streamer.Start(CameraPositionVoxels); const FStandaloneStreamingUpdate Update=Streamer.Start(CameraPositionVoxels);
const double LOD0Width=static_cast<double>(2*World.ClipRadius+2) const double LOD0Width=static_cast<double>(2*World.ClipRadius+2)
*CHUNK_SIZE*VOXEL_SIZE/100.0; *CHUNK_SIZE*VOXEL_SIZE/100.0;
std::printf("[StreamBenchmarkConfig] clip_radius=%d lod0_cube=%.0fm voxel=25cm fullres_levels=%d max_level_filter=%d workers=%d full_detail_tiles=%d desired_tiles=%d variant=%s\n", std::printf("[StreamBenchmarkConfig] clip_radius=%d lod0_cube=%.0fm voxel=25cm fullres_levels=%d max_level_filter=%d workers=%d full_detail_tiles=%d desired_tiles=%d variant=%s hash=%s\n",
World.ClipRadius,LOD0Width,World.FullResClipLevels,MaxLevel<0?World.MaxClipLevel:MaxLevel, World.ClipRadius,LOD0Width,World.FullResClipLevels,MaxLevel<0?World.MaxClipLevel:MaxLevel,
Streamer.GetWorkerCount(),FullDetailExpected,Update.DesiredTiles, Streamer.GetWorkerCount(),FullDetailExpected,Update.DesiredTiles,
bRoundTunnels?(bEnhancedWalls?"round-detailed":"round-legacy"):"base"); bRoundTunnels?(bEnhancedWalls?"round-detailed":"round-legacy"):"base",
(bHashDensity||!BakeCachePath.empty())?"on":"off");
std::fflush(stdout); std::fflush(stdout);
int32 Finished=0,FullDetailFinished=0; int32 Finished=0,FullDetailFinished=0;
bool bFullDetailTimeReported=false; bool bFullDetailTimeReported=false;
@@ -1383,6 +1602,7 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
SamplesByLevel[Level]+=Tile.DensitySamples; SamplesByLevel[Level]+=Tile.DensitySamples;
VerticesByLevel[Level]+=Tile.Vertices.size(); VerticesByLevel[Level]+=Tile.Vertices.size();
IndicesByLevel[Level]+=Tile.Indices.size(); IndicesByLevel[Level]+=Tile.Indices.size();
++TilesByLevel[Level];
WorkerSecondsByLevel[Level]+=Tile.WorkerSeconds; WorkerSecondsByLevel[Level]+=Tile.WorkerSeconds;
if(Tile.bHasDensityHash)HashRows[Level].push_back({Tile.Key,Tile.DensityHash}); if(Tile.bHasDensityHash)HashRows[Level].push_back({Tile.Key,Tile.DensityHash});
} }
@@ -1397,31 +1617,48 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
std::vector<FHashedTile> FullRows; std::vector<FHashedTile> FullRows;
for(int32 Level=0;Level<World.FullResClipLevels&&Level<=MaxLevels;++Level) for(int32 Level=0;Level<World.FullResClipLevels&&Level<=MaxLevels;++Level)
FullRows.insert(FullRows.end(),HashRows[Level].begin(),HashRows[Level].end()); FullRows.insert(FullRows.end(),HashRows[Level].begin(),HashRows[Level].end());
std::printf("[StreamFullDetail] wall=%.3fs generated=%d/%d resident_tiles_cpu_only=1 density_hash=0x%016llx\n", if(bHashDensity||!BakeCachePath.empty())
FullDetailSeconds,FullDetailFinished,FullDetailExpected, std::printf("[StreamFullDetail] wall=%.3fs generated=%d/%d resident_tiles_cpu_only=1 density_hash=0x%016llx\n",
static_cast<unsigned long long>(HashTileRows(FullRows))); FullDetailSeconds,FullDetailFinished,FullDetailExpected,
static_cast<unsigned long long>(HashTileRows(FullRows)));
else
std::printf("[StreamFullDetail] wall=%.3fs generated=%d/%d resident_tiles_cpu_only=1 hash=off\n",
FullDetailSeconds,FullDetailFinished,FullDetailExpected);
std::fflush(stdout); std::fflush(stdout);
} }
} }
const double FullClipSeconds=std::chrono::duration<double>( const double FullClipSeconds=std::chrono::duration<double>(
std::chrono::steady_clock::now()-Start).count(); std::chrono::steady_clock::now()-Start).count();
std::vector<FHashedTile> AllRows; std::vector<FHashedTile> AllRows;
for(auto& LevelRows:HashRows)AllRows.insert(AllRows.end(),LevelRows.begin(),LevelRows.end()); if(bHashDensity||!BakeCachePath.empty())
std::printf("[StreamFullClipmap] wall=%.3fs generated=%d/%d worker_sum=%.3fs tiles=%d density_hash=0x%016llx\n", for(auto& LevelRows:HashRows)AllRows.insert(AllRows.end(),LevelRows.begin(),LevelRows.end());
FullClipSeconds,Finished,DesiredTotal, if(bHashDensity||!BakeCachePath.empty())
std::accumulate(WorkerSecondsByLevel.begin(),WorkerSecondsByLevel.end(),0.0), std::printf("[StreamFullClipmap] wall=%.3fs generated=%d/%d worker_sum=%.3fs tiles=%d density_hash=0x%016llx\n",
DesiredTotal,static_cast<unsigned long long>(HashTileRows(AllRows))); FullClipSeconds,Finished,DesiredTotal,
std::accumulate(WorkerSecondsByLevel.begin(),WorkerSecondsByLevel.end(),0.0),
DesiredTotal,static_cast<unsigned long long>(HashTileRows(AllRows)));
else
std::printf("[StreamFullClipmap] wall=%.3fs generated=%d/%d worker_sum=%.3fs tiles=%d hash=off\n",
FullClipSeconds,Finished,DesiredTotal,
std::accumulate(WorkerSecondsByLevel.begin(),WorkerSecondsByLevel.end(),0.0),DesiredTotal);
std::printf("[StreamCacheUse] hits=%llu requested=%d\n", std::printf("[StreamCacheUse] hits=%llu requested=%d\n",
static_cast<unsigned long long>(Streamer.GetCacheHits()),DesiredTotal); static_cast<unsigned long long>(Streamer.GetCacheHits()),DesiredTotal);
for(int32 Level=0;Level<=MaxLevels;++Level) for(int32 Level=0;Level<=MaxLevels;++Level)
{ {
if(HashRows[Level].empty())continue; if(TilesByLevel[Level]==0)continue;
const uint64 LevelHash=HashTileRows(HashRows[Level]); if(bHashDensity||!BakeCachePath.empty())
std::printf("[StreamLevel] lod=%d tiles=%zu samples=%.3fM worker_sum=%.3fs vertices=%llu indices=%llu density_hash=0x%016llx\n", std::printf("[StreamLevel] lod=%d tiles=%llu samples=%.3fM worker_sum=%.3fs vertices=%llu indices=%llu density_hash=0x%016llx\n",
Level,HashRows[Level].size(),static_cast<double>(SamplesByLevel[Level])/1.0e6, Level,static_cast<unsigned long long>(TilesByLevel[Level]),
WorkerSecondsByLevel[Level],static_cast<unsigned long long>(VerticesByLevel[Level]), static_cast<double>(SamplesByLevel[Level])/1.0e6,WorkerSecondsByLevel[Level],
static_cast<unsigned long long>(IndicesByLevel[Level]), static_cast<unsigned long long>(VerticesByLevel[Level]),
static_cast<unsigned long long>(LevelHash)); static_cast<unsigned long long>(IndicesByLevel[Level]),
static_cast<unsigned long long>(HashTileRows(HashRows[Level])));
else
std::printf("[StreamLevel] lod=%d tiles=%llu samples=%.3fM worker_sum=%.3fs vertices=%llu indices=%llu hash=off\n",
Level,static_cast<unsigned long long>(TilesByLevel[Level]),
static_cast<double>(SamplesByLevel[Level])/1.0e6,WorkerSecondsByLevel[Level],
static_cast<unsigned long long>(VerticesByLevel[Level]),
static_cast<unsigned long long>(IndicesByLevel[Level]));
} }
if(!BakeCachePath.empty()) if(!BakeCachePath.empty())
{ {
@@ -1447,7 +1684,9 @@ int main(int Argc,char** Argv)
{if(!JoinedCommandLine.empty())JoinedCommandLine.push_back(' ');JoinedCommandLine+=Argv[I];} {if(!JoinedCommandLine.empty())JoinedCommandLine.push_back(' ');JoinedCommandLine+=Argv[I];}
FCommandLine::Set(JoinedCommandLine); FCommandLine::Set(JoinedCommandLine);
int32 BenchmarkLevel=-1,BenchmarkCount=64,StreamBenchmarkMaxLevel=-1; int32 BenchmarkLevel=-1,BenchmarkCount=64,StreamBenchmarkMaxLevel=-1;
int32 ShapeCompareLevel=-1,ShapeCompareTileLimit=0;
bool bStreamBenchmark=false; bool bStreamBenchmark=false;
bool bStreamHash=false;
bool bRoundWallCompare=false; bool bRoundWallCompare=false;
int32 RoundCompareTileCount=16; int32 RoundCompareTileCount=16;
std::string BakeMeshCachePath; std::string BakeMeshCachePath;
@@ -1459,10 +1698,25 @@ int main(int Argc,char** Argv)
else if(Arg=="--compare-evaluators")App.bCompareEvaluators=true; else if(Arg=="--compare-evaluators")App.bCompareEvaluators=true;
else if(Arg=="--parity-only")App.bParityOnly=true; else if(Arg=="--parity-only")App.bParityOnly=true;
else if(Arg=="--density-hash")App.bDensityHash=true; else if(Arg=="--density-hash")App.bDensityHash=true;
else if(Arg=="--shape-world")App.bShapeWorld=true;
else if(Arg=="--shape-audit")App.bShapeWorld=true,App.bShapeAuditOnly=true;
else if(Arg=="--shape-compare")App.bShapeWorld=true,App.bShapeCompare=true;
else if(Arg.starts_with("--shape-compare-level="))
ShapeCompareLevel=std::atoi(std::string(Arg.substr(std::string_view("--shape-compare-level=").size())).c_str());
else if(Arg.starts_with("--shape-compare-tile-limit="))
ShapeCompareTileLimit=std::max(0,std::atoi(std::string(Arg.substr(std::string_view("--shape-compare-tile-limit=").size())).c_str()));
else if(Arg.starts_with("--shape-detail="))
{
const std::string_view Value=Arg.substr(15);
if(Value=="smooth")App.ShapeDetailVariant=FStandaloneShapeWorld::EDetailVariant::Smooth;
else if(Value=="per-voxel")App.ShapeDetailVariant=FStandaloneShapeWorld::EDetailVariant::PerVoxel;
else if(Value!="coherent"){std::fprintf(stderr,"Invalid --shape-detail value: %.*s\n",static_cast<int>(Value.size()),Value.data());return 2;}
}
else if(Arg=="--round-tunnels")App.bRoundTunnels=true; else if(Arg=="--round-tunnels")App.bRoundTunnels=true;
else if(Arg=="--round-tunnels-legacy") else if(Arg=="--round-tunnels-legacy")
{App.bRoundTunnels=true;App.bRoundTunnelEnhanced=false;} {App.bRoundTunnels=true;App.bRoundTunnelEnhanced=false;}
else if(Arg=="--stream-benchmark")bStreamBenchmark=true; else if(Arg=="--stream-benchmark")bStreamBenchmark=true;
else if(Arg=="--stream-hash")bStreamHash=true;
else if(Arg=="--compare-round-walls") else if(Arg=="--compare-round-walls")
{bRoundWallCompare=true;App.bRoundTunnels=true;} {bRoundWallCompare=true;App.bRoundTunnels=true;}
else if(Arg.starts_with("--round-compare-tiles=")) else if(Arg.starts_with("--round-compare-tiles="))
@@ -1518,6 +1772,17 @@ int main(int Argc,char** Argv)
if(App.bRoundTunnels&&!PrepareStandaloneDensityWorld(*App.World,App.Seed, if(App.bRoundTunnels&&!PrepareStandaloneDensityWorld(*App.World,App.Seed,
App.bRoundTunnels,App.bRoundTunnelEnhanced,Error)) App.bRoundTunnels,App.bRoundTunnelEnhanced,Error))
{std::fprintf(stderr,"Standalone world setup failed: %s\n",Error.c_str());return 2;} {std::fprintf(stderr,"Standalone world setup failed: %s\n",Error.c_str());return 2;}
const bool bNeedFarShapeWorld=bStreamBenchmark
?(StreamBenchmarkMaxLevel<0||StreamBenchmarkMaxLevel>=App.World->FullResClipLevels)
:(App.bShapeCompare?ShapeCompareLevel>=App.World->FullResClipLevels
:(BenchmarkLevel>=App.World->FullResClipLevels));
if(App.bShapeWorld&&(App.bShapeAuditOnly||App.bShapeCompare||bStreamBenchmark||BenchmarkLevel>=0)
&&!App.BuildShapeWorldForCamera(Error,bNeedFarShapeWorld))
{std::fprintf(stderr,"Shape interval build failed: %s\n",Error.c_str());return 2;}
if(App.bShapeAuditOnly)return App.ShapeWorld->IsDetailAuditPassing()?0:7;
if(App.bShapeCompare)
return RunOwnerShapeComparison(*App.World,*App.ShapeWorld,App.Position,App.WorkerCount,
ShapeCompareLevel,ShapeCompareTileLimit);
if(bRoundWallCompare) if(bRoundWallCompare)
return RunRoundWallComparison(*App.World,App.Position,RoundCompareTileCount); return RunRoundWallComparison(*App.World,App.Position,RoundCompareTileCount);
if(bStreamBenchmark) if(bStreamBenchmark)
@@ -1526,12 +1791,12 @@ int main(int Argc,char** Argv)
*CHUNK_SIZE*VOXEL_SIZE/100.0; *CHUNK_SIZE*VOXEL_SIZE/100.0;
std::printf("Standalone clip override: exported radius=%d (112 m cube) -> radius=%d (%.0f m LOD0 cube), voxel size=25 cm.\n", std::printf("Standalone clip override: exported radius=%d (112 m cube) -> radius=%d (%.0f m LOD0 cube), voxel size=25 cm.\n",
ExportClipRadius,App.ClipRadius,FullDetailWidth); ExportClipRadius,App.ClipRadius,FullDetailWidth);
if(!App.bForceGenerate)App.LoadMeshCache(); if(!App.bForceGenerate&&!App.bShapeWorld)App.LoadMeshCache();
return RunStreamingBenchmark(*App.World, return RunStreamingBenchmark(*App.World,
FVector(App.Position.X*4.0,App.Position.Y*4.0,App.Position.Z*4.0), FVector(App.Position.X*4.0,App.Position.Y*4.0,App.Position.Z*4.0),
App.WorkerCount,StreamBenchmarkMaxLevel,App.bRoundTunnels, App.WorkerCount,StreamBenchmarkMaxLevel,App.bRoundTunnels,
App.bRoundTunnelEnhanced,App.MeshCache.IsLoaded()?&App.MeshCache:nullptr, App.bRoundTunnelEnhanced,(!App.bShapeWorld&&App.MeshCache.IsLoaded())?&App.MeshCache:nullptr,
BakeMeshCachePath,App.MakeMeshCacheMetadata()); bStreamHash,BakeMeshCachePath,App.MakeMeshCacheMetadata());
} }
if(BenchmarkLevel>=0) if(BenchmarkLevel>=0)
return RunTileBenchmark(*App.World,App.Position,BenchmarkLevel,BenchmarkCount, return RunTileBenchmark(*App.World,App.Position,BenchmarkLevel,BenchmarkCount,
@@ -1546,7 +1811,7 @@ int main(int Argc,char** Argv)
GApp=&App; GApp=&App;
if(!App.Window||!App.InitializeDevice()){MessageBoxA(nullptr,"Direct3D 11 initialization failed.","VoxelForge standalone",MB_ICONERROR);return 3;} if(!App.Window||!App.InitializeDevice()){MessageBoxA(nullptr,"Direct3D 11 initialization failed.","VoxelForge standalone",MB_ICONERROR);return 3;}
ShowWindow(App.Window,SW_SHOW);UpdateWindow(App.Window); ShowWindow(App.Window,SW_SHOW);UpdateWindow(App.Window);
std::printf("Controls: hold right mouse to look; WASD move; Space/Ctrl (E/Q) up/down; Shift faster; wheel changes speed; F wireframe; G toggles legacy/detailed round walls; 1-9 toggle LOD; 0 toggle all levels; N/P change seed; T swaps base/round tunnels; Esc quit.\n"); std::printf("Controls: hold right mouse to look; WASD move; Space/Ctrl (E/Q) up/down; Shift faster; wheel changes speed; F wireframe; G toggles legacy/detailed round walls; 1-9 toggle LOD; 0 toggle all levels; N/P change seed; T swaps base/round tunnels; V swaps owner density/shape intervals; Esc quit.\n");
std::printf("Owner data: %s; starting at first-strate open-space sample (%.2f, %.2f, %.2f) m.\n", std::printf("Owner data: %s; starting at first-strate open-space sample (%.2f, %.2f, %.2f) m.\n",
App.DataPath.c_str(),App.Position.X,App.Position.Y,App.Position.Z); App.DataPath.c_str(),App.Position.X,App.Position.Y,App.Position.Z);
std::fflush(stdout); std::fflush(stdout);
@@ -6,6 +6,7 @@
#include "VoxelGenerator.h" #include "VoxelGenerator.h"
#include "StandaloneResearchTunnels.h" #include "StandaloneResearchTunnels.h"
#include "StandaloneShapeWorld.h"
#include "VoxelSettings.h" #include "VoxelSettings.h"
#include "VoxelStrateManager.h" #include "VoxelStrateManager.h"
#include "VoxelStrateDefinition.h" #include "VoxelStrateDefinition.h"
@@ -2849,6 +2850,12 @@ bool UVoxelGenerator::EvaluateDensityChunk(
float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ, float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ,
float* OutPreEditDensity) const float* OutPreEditDensity) const
{ {
if (const FStandaloneShapeWorld* ShapeWorld=FStandaloneShapeWorldAccess::GetActive())
{
const float ShapeDensity=ShapeWorld->DensityAt(WorldX,WorldY,WorldZ);
if (OutPreEditDensity) *OutPreEditDensity=ShapeDensity;
return ShapeDensity;
}
#if WITH_EDITOR #if WITH_EDITOR
// Step 2e and step 2d are separate editor-only, default-off side paths. The owner field stays // Step 2e and step 2d are separate editor-only, default-off side paths. The owner field stays
// authoritative unless a research commandlet or automation test explicitly selects one. // authoritative unless a research commandlet or automation test explicitly selects one.
@@ -6469,6 +6476,19 @@ EVoxelTileClass UVoxelGenerator::ClassifyTile(const FIntVector& OriginVoxels, in
VoxelDensityProfile::AddCounter( VoxelDensityProfile::AddCounter(
VoxelDensityProfile::ECounter::TileClassifyCalls); VoxelDensityProfile::ECounter::TileClassifyCalls);
if (const FStandaloneShapeWorld* ShapeWorld=FStandaloneShapeWorldAccess::GetActive())
{
const EVoxelTileClass ShapeVerdict=ShapeWorld->ClassifyBox(
OriginVoxels,NormalizedStep,NormalizedCellsPerAxis);
if (ShapeVerdict==EVoxelTileClass::AllSolid)
VoxelDensityProfile::AddCounter(VoxelDensityProfile::ECounter::TileClassifyAllSolid);
else if (ShapeVerdict==EVoxelTileClass::AllAir)
VoxelDensityProfile::AddCounter(VoxelDensityProfile::ECounter::TileClassifyAllAir);
else
VoxelDensityProfile::AddCounter(VoxelDensityProfile::ECounter::TileClassifyMixed);
return ShapeVerdict;
}
#if WITH_EDITOR #if WITH_EDITOR
// The ordinary tile classifier proves the live field's interval bounds. While the separate // The ordinary tile classifier proves the live field's interval bounds. While the separate
// research field is selected, decline those proofs until its own complete final-field interval // research field is selected, decline those proofs until its own complete final-field interval