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
No files matched your search
@@ -31,3 +31,4 @@
|
||||
/Standalone/data/*.log
|
||||
/Standalone/data/*.err
|
||||
/Standalone/data/*.vfmesh
|
||||
/Standalone/Research/**/*.log
|
||||
@@ -58,9 +58,14 @@ $RspLines += @('/link','d3d11.lib','dxgi.lib','d3dcompiler.lib','user32.lib','gd
|
||||
|
||||
Push-Location $BuildDir
|
||||
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
|
||||
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 {
|
||||
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.
|
||||
|
After Width: | Height: | Size: 3.9 MiB |
|
After Width: | Height: | Size: 3.9 MiB |
|
After Width: | Height: | Size: 3.9 MiB |
|
After Width: | Height: | Size: 3.9 MiB |
|
After Width: | Height: | Size: 3.9 MiB |
|
After Width: | Height: | Size: 3.9 MiB |
|
After Width: | Height: | Size: 3.9 MiB |
|
After Width: | Height: | Size: 3.9 MiB |
|
After Width: | Height: | Size: 3.9 MiB |
|
After Width: | Height: | Size: 3.9 MiB |
|
After Width: | Height: | Size: 3.9 MiB |
|
After Width: | Height: | Size: 3.9 MiB |
|
After Width: | Height: | Size: 3.9 MiB |
|
After Width: | Height: | Size: 3.9 MiB |
|
After Width: | Height: | Size: 3.9 MiB |
|
After Width: | Height: | Size: 3.9 MiB |
|
After Width: | Height: | Size: 3.9 MiB |
|
After Width: | Height: | Size: 3.9 MiB |
|
After Width: | Height: | Size: 3.9 MiB |
|
After Width: | Height: | Size: 3.9 MiB |
|
After Width: | Height: | Size: 3.9 MiB |
|
After Width: | Height: | Size: 3.9 MiB |
|
After Width: | Height: | Size: 3.9 MiB |
|
After Width: | Height: | Size: 3.9 MiB |
|
After Width: | Height: | Size: 3.9 MiB |
|
After Width: | Height: | Size: 3.9 MiB |
|
After Width: | Height: | Size: 3.9 MiB |
@@ -1,6 +1,7 @@
|
||||
#include "OwnerWorldLoader.h"
|
||||
#include "StreamingWorld.h"
|
||||
#include "StandaloneResearchTunnels.h"
|
||||
#include "StandaloneShapeWorld.h"
|
||||
#include "VoxelDensityProfile.h"
|
||||
|
||||
#ifdef TEXT
|
||||
@@ -179,6 +180,7 @@ namespace
|
||||
ID3D11RasterizerState* SolidRasterizer=nullptr;
|
||||
ID3D11RasterizerState* WireRasterizer=nullptr;
|
||||
std::unique_ptr<FStandaloneOwnerWorld> World;
|
||||
std::unique_ptr<FStandaloneShapeWorld> ShapeWorld;
|
||||
std::unique_ptr<FStandaloneStreamingGenerator> Streamer;
|
||||
FStandaloneMeshCache MeshCache;
|
||||
std::string DataPath;
|
||||
@@ -203,6 +205,11 @@ namespace
|
||||
bool bParityChecked=false;
|
||||
bool bRoundTunnels=false;
|
||||
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 bCaptured=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)
|
||||
{
|
||||
FStandaloneShapeWorldAccess::SetActive(nullptr);
|
||||
ShapeWorld.reset();
|
||||
World=std::make_unique<FStandaloneOwnerWorld>();
|
||||
if(!StandaloneOwnerLoader::Load(DataPath,*World,Error))return false;
|
||||
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,
|
||||
bRoundTunnelEnhanced,Error))return false;
|
||||
if(bShapeWorld&&!BuildShapeWorldForCamera(Error))return false;
|
||||
LoadMeshCache();
|
||||
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 Metadata;
|
||||
@@ -503,7 +543,7 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
||||
void LoadMeshCache()
|
||||
{
|
||||
MeshCache.Clear();
|
||||
if(bForceGenerate||!World)return;
|
||||
if(bForceGenerate||bShapeWorld||!World)return;
|
||||
const char* ModeSuffix=!bRoundTunnels?"_base":
|
||||
(bRoundTunnelEnhanced?"_round_detailed":"_round_legacy");
|
||||
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();
|
||||
ReleaseMeshes();
|
||||
GenerationStart=std::chrono::steady_clock::now();
|
||||
std::string 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;}
|
||||
const FVector CameraVoxels(Position.X*4.0,Position.Y*4.0,Position.Z*4.0);
|
||||
BuildTargetChunk=StandaloneWorldMesher::CameraChunk(CameraVoxels);
|
||||
GenerationStart=std::chrono::steady_clock::now();
|
||||
bFullDetailReported=false;bInitialClipmapReported=false;bAutoCaptureWritten=false;
|
||||
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(const FVoxelTileKey& Tile:DesiredTiles)++DesiredByLevel[Tile.Level];
|
||||
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);
|
||||
FCrossingWork& InitialWork=CrossingWork[CrossingIndex];
|
||||
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;
|
||||
const char* TunnelMode=!bRoundTunnels?"":(bRoundTunnelEnhanced
|
||||
?" 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",
|
||||
Seed,TunnelMode,Update.DesiredTiles,
|
||||
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,bShapeWorld?" [shape intervals]":"",Update.DesiredTiles,
|
||||
Update.FullResTiles,Streamer->GetWorkerCount(),MaxBatchUploadsPerFrame,BatchUploadBudgetMilliseconds);
|
||||
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);
|
||||
}
|
||||
|
||||
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)
|
||||
{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);
|
||||
FStandaloneDesiredTileSet NewDesired=StandaloneWorldMesher::BuildDesiredSet(*World,CameraVoxels);
|
||||
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;
|
||||
std::unordered_set<FVoxelTileKey,FStandaloneTileKeyHash> NewSet;
|
||||
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",
|
||||
Elapsed,ResidentTiles.size(),Streamer->FullResDesiredCount(),World->FullResClipLevels-1);
|
||||
ReportTileTimings();std::fflush(stdout);
|
||||
StartFarShapeGeneration();
|
||||
}
|
||||
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()
|
||||
{
|
||||
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)"):"")
|
||||
<<" | "<<VisibleIndices/3<<" tris | "<<FrameMs<<" ms | "<<(bGenerationRunning?"generating":"ready")
|
||||
<<" | 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(!bWasDown&&W=='F')GApp->bWireframe=!GApp->bWireframe;
|
||||
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)
|
||||
{
|
||||
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(
|
||||
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;
|
||||
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;
|
||||
Buckets.reserve(To.size());
|
||||
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);
|
||||
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));
|
||||
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;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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,
|
||||
int32 WorkerCount,int32 MaxLevel,bool bRoundTunnels,
|
||||
bool bEnhancedWalls,const FStandaloneMeshCache* Cache,
|
||||
bool bHashDensity,
|
||||
const std::string& BakeCachePath,
|
||||
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> VerticesByLevel{};
|
||||
std::array<uint64,MaxLevels+1> IndicesByLevel{};
|
||||
std::array<uint64,MaxLevels+1> TilesByLevel{};
|
||||
std::array<double,MaxLevels+1> WorkerSecondsByLevel{};
|
||||
std::vector<FStandaloneCpuTile> CacheTiles;
|
||||
if(!BakeCachePath.empty())CacheTiles.reserve(Desired.Tiles.size());
|
||||
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 double LOD0Width=static_cast<double>(2*World.ClipRadius+2)
|
||||
*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,
|
||||
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);
|
||||
int32 Finished=0,FullDetailFinished=0;
|
||||
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;
|
||||
VerticesByLevel[Level]+=Tile.Vertices.size();
|
||||
IndicesByLevel[Level]+=Tile.Indices.size();
|
||||
++TilesByLevel[Level];
|
||||
WorkerSecondsByLevel[Level]+=Tile.WorkerSeconds;
|
||||
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;
|
||||
for(int32 Level=0;Level<World.FullResClipLevels&&Level<=MaxLevels;++Level)
|
||||
FullRows.insert(FullRows.end(),HashRows[Level].begin(),HashRows[Level].end());
|
||||
if(bHashDensity||!BakeCachePath.empty())
|
||||
std::printf("[StreamFullDetail] wall=%.3fs generated=%d/%d resident_tiles_cpu_only=1 density_hash=0x%016llx\n",
|
||||
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);
|
||||
}
|
||||
}
|
||||
const double FullClipSeconds=std::chrono::duration<double>(
|
||||
std::chrono::steady_clock::now()-Start).count();
|
||||
std::vector<FHashedTile> AllRows;
|
||||
if(bHashDensity||!BakeCachePath.empty())
|
||||
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",
|
||||
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",
|
||||
static_cast<unsigned long long>(Streamer.GetCacheHits()),DesiredTotal);
|
||||
for(int32 Level=0;Level<=MaxLevels;++Level)
|
||||
{
|
||||
if(HashRows[Level].empty())continue;
|
||||
const uint64 LevelHash=HashTileRows(HashRows[Level]);
|
||||
std::printf("[StreamLevel] lod=%d tiles=%zu 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,
|
||||
WorkerSecondsByLevel[Level],static_cast<unsigned long long>(VerticesByLevel[Level]),
|
||||
if(TilesByLevel[Level]==0)continue;
|
||||
if(bHashDensity||!BakeCachePath.empty())
|
||||
std::printf("[StreamLevel] lod=%d tiles=%llu samples=%.3fM worker_sum=%.3fs vertices=%llu indices=%llu density_hash=0x%016llx\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]),
|
||||
static_cast<unsigned long long>(LevelHash));
|
||||
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())
|
||||
{
|
||||
@@ -1447,7 +1684,9 @@ int main(int Argc,char** Argv)
|
||||
{if(!JoinedCommandLine.empty())JoinedCommandLine.push_back(' ');JoinedCommandLine+=Argv[I];}
|
||||
FCommandLine::Set(JoinedCommandLine);
|
||||
int32 BenchmarkLevel=-1,BenchmarkCount=64,StreamBenchmarkMaxLevel=-1;
|
||||
int32 ShapeCompareLevel=-1,ShapeCompareTileLimit=0;
|
||||
bool bStreamBenchmark=false;
|
||||
bool bStreamHash=false;
|
||||
bool bRoundWallCompare=false;
|
||||
int32 RoundCompareTileCount=16;
|
||||
std::string BakeMeshCachePath;
|
||||
@@ -1459,10 +1698,25 @@ int main(int Argc,char** Argv)
|
||||
else if(Arg=="--compare-evaluators")App.bCompareEvaluators=true;
|
||||
else if(Arg=="--parity-only")App.bParityOnly=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-legacy")
|
||||
{App.bRoundTunnels=true;App.bRoundTunnelEnhanced=false;}
|
||||
else if(Arg=="--stream-benchmark")bStreamBenchmark=true;
|
||||
else if(Arg=="--stream-hash")bStreamHash=true;
|
||||
else if(Arg=="--compare-round-walls")
|
||||
{bRoundWallCompare=true;App.bRoundTunnels=true;}
|
||||
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,
|
||||
App.bRoundTunnels,App.bRoundTunnelEnhanced,Error))
|
||||
{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)
|
||||
return RunRoundWallComparison(*App.World,App.Position,RoundCompareTileCount);
|
||||
if(bStreamBenchmark)
|
||||
@@ -1526,12 +1791,12 @@ int main(int Argc,char** Argv)
|
||||
*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",
|
||||
ExportClipRadius,App.ClipRadius,FullDetailWidth);
|
||||
if(!App.bForceGenerate)App.LoadMeshCache();
|
||||
if(!App.bForceGenerate&&!App.bShapeWorld)App.LoadMeshCache();
|
||||
return RunStreamingBenchmark(*App.World,
|
||||
FVector(App.Position.X*4.0,App.Position.Y*4.0,App.Position.Z*4.0),
|
||||
App.WorkerCount,StreamBenchmarkMaxLevel,App.bRoundTunnels,
|
||||
App.bRoundTunnelEnhanced,App.MeshCache.IsLoaded()?&App.MeshCache:nullptr,
|
||||
BakeMeshCachePath,App.MakeMeshCacheMetadata());
|
||||
App.bRoundTunnelEnhanced,(!App.bShapeWorld&&App.MeshCache.IsLoaded())?&App.MeshCache:nullptr,
|
||||
bStreamHash,BakeMeshCachePath,App.MakeMeshCacheMetadata());
|
||||
}
|
||||
if(BenchmarkLevel>=0)
|
||||
return RunTileBenchmark(*App.World,App.Position,BenchmarkLevel,BenchmarkCount,
|
||||
@@ -1546,7 +1811,7 @@ int main(int Argc,char** Argv)
|
||||
GApp=&App;
|
||||
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);
|
||||
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",
|
||||
App.DataPath.c_str(),App.Position.X,App.Position.Y,App.Position.Z);
|
||||
std::fflush(stdout);
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include "VoxelGenerator.h"
|
||||
#include "StandaloneResearchTunnels.h"
|
||||
#include "StandaloneShapeWorld.h"
|
||||
#include "VoxelSettings.h"
|
||||
#include "VoxelStrateManager.h"
|
||||
#include "VoxelStrateDefinition.h"
|
||||
@@ -2849,6 +2850,12 @@ bool UVoxelGenerator::EvaluateDensityChunk(
|
||||
float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ,
|
||||
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
|
||||
// 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.
|
||||
@@ -6469,6 +6476,19 @@ EVoxelTileClass UVoxelGenerator::ClassifyTile(const FIntVector& OriginVoxels, in
|
||||
VoxelDensityProfile::AddCounter(
|
||||
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
|
||||
// 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
|
||||
|
||||