Files
Fr0zkaandClaude Opus 5.5 05f62cff36 standalone: the owner's world outside Unreal -- bit-identical density, D3D11 fly-through viewer
Standalone/ (C++20, MSVC /O2 /arch:AVX2 /fp:precise, Build.ps1): copied density pipeline, op stack,
cave morphology, passages, strate manager/composer, noise, DetSinCos, mesher, clipmap selection,
with a UE compatibility layer (Compat/). Owner data exported once to Standalone/data/owner_world.json
by a default-off explore-commandlet exporter (the only plugin-source change). 25 cm voxels.
Parity: seed 0, 3,436 samples (strate edges, negative/far coords, rooms, tunnels, passages,
landings): bit-identical. Viewer: full 7-level clipmap, per-level toggles 1-7, T = round winding
tunnels (standalone option), N/P seed, F wireframe.
Timing NOT yet a baseline: 515 s for 12,054 tiles (L0 70 s, L6 296 s); the compat TMap/TSet are
vector-backed with linear lookup, which the plugin's caches do not expect.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
2026-10-02 12:14:04 +02:00

180 lines
8.5 KiB
C++

#pragma once
#include "OwnerWorldLoader.h"
#include "VoxelClipmapDesiredTiles.h"
#include "VoxelMarchingCubesMesher.h"
#include <atomic>
#include <chrono>
#include <thread>
struct FStandaloneCpuVertex { float Position[3]; float Normal[3]; };
struct FStandaloneCpuLevel
{
std::vector<FStandaloneCpuVertex> Vertices;
std::vector<uint32> Indices;
uint64 VertexCount = 0;
uint64 IndexCount = 0;
uint64 TileCount = 0;
uint64 NonEmptyTiles = 0;
uint64 DensitySamples = 0;
double Seconds = 0.0;
};
struct FStandaloneCpuClipmap
{
std::array<FStandaloneCpuLevel,9> Levels;
FIntVector CenterChunk = FIntVector::ZeroValue;
int32 MinimumChunkZ = MIN_int32;
int32 MaximumChunkZ = MAX_int32;
int32 WorkerCount = 6;
double TotalSeconds = 0.0;
bool bCancelled = false;
};
namespace StandaloneWorldMesher
{
using FClock = std::chrono::steady_clock;
inline FIntVector CameraChunk(const FVector& PositionVoxels)
{
return FIntVector(
FMath::FloorToInt(PositionVoxels.X / CHUNK_SIZE),
FMath::FloorToInt(PositionVoxels.Y / CHUNK_SIZE),
FMath::FloorToInt(PositionVoxels.Z / CHUNK_SIZE));
}
inline FStandaloneCpuClipmap Build(
FStandaloneOwnerWorld& World, const FVector& CameraPositionVoxels, int32 WorkerCount = 6,
const std::function<void(int32,FStandaloneCpuLevel&&)>& OnLevelReady = {},
bool bRetainMeshData = true, std::atomic<bool>* Cancel = nullptr)
{
FStandaloneCpuClipmap Output;
Output.WorkerCount = FMath::Clamp(WorkerCount,1,32);
Output.CenterChunk = CameraChunk(CameraPositionVoxels);
bool bHasStrate = false;
int32 TopChunkZ = 0, BottomChunkZ = 0;
if (World.Settings->bClampViewToStrate)
bHasStrate = World.Manager->GetStrateChunkZBounds(
Output.CenterChunk.Z,TopChunkZ,BottomChunkZ);
VoxelClipmapDesiredTiles::ResolveVerticalBounds(
Output.CenterChunk.Z,World.Settings->ViewDistanceUp,World.Settings->ViewDistanceDown,
World.Settings->StrateViewMarginChunks,World.Settings->bClampViewToStrate,bHasStrate,
TopChunkZ,BottomChunkZ,Output.MinimumChunkZ,Output.MaximumChunkZ);
VoxelClipmapDesiredTiles::FParameters Parameters;
Parameters.ClipRadius = World.ClipRadius;
Parameters.MaxClipLevel = FMath::Clamp(World.MaxClipLevel,0,8);
Parameters.RenderDistanceChunks = World.RenderDistanceChunks;
TArray<FVoxelTileKey> AllTiles;
VoxelClipmapDesiredTiles::Build(
Output.CenterChunk,Output.MinimumChunkZ,Output.MaximumChunkZ,Parameters,AllTiles);
std::array<std::vector<FVoxelTileKey>,9> TilesByLevel;
for (const FVoxelTileKey& Tile : AllTiles)
TilesByLevel[static_cast<size_t>(Tile.Level)].push_back(Tile);
UVoxelMarchingCubesMesher::InitializeConsoleSwitches();
const auto TotalStart = FClock::now();
for (int32 Level = 0; Level <= Parameters.MaxClipLevel; ++Level)
{
if (Cancel && Cancel->load(std::memory_order_relaxed)) { Output.bCancelled = true; break; }
const auto LevelStart = FClock::now();
const std::vector<FVoxelTileKey>& Tiles = TilesByLevel[static_cast<size_t>(Level)];
FStandaloneCpuLevel& LevelOutput = Output.Levels[static_cast<size_t>(Level)];
LevelOutput.TileCount = Tiles.size();
struct FTileResult { FVoxelMeshData Mesh; uint64 Samples = 0; };
std::vector<FTileResult> Results(Tiles.size());
std::atomic<size_t> Next{0};
const int32 ThreadCount = FMath::Min<int32>(Output.WorkerCount,
FMath::Max<int32>(1,static_cast<int32>(Tiles.size())));
std::vector<std::thread> Threads;
Threads.reserve(static_cast<size_t>(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 (;;)
{
if (Cancel && Cancel->load(std::memory_order_relaxed)) break;
const size_t Index = Next.fetch_add(1,std::memory_order_relaxed);
if (Index >= Tiles.size()) break;
const FVoxelTileKey& Tile = Tiles[Index];
const int32 Extent = Tile.ExtentVoxels();
const int32 Cells = Level < World.FullResClipLevels
? CHUNK_SIZE : FMath::Max(1,World.CoarseTileCells);
const int32 Step = FMath::Max(1,Extent / Cells);
int32 BandMin = MIN_int32, BandMax = MAX_int32;
if (World.StrateContentCutMinLevel < 9
&& Level >= World.StrateContentCutMinLevel && bHasStrate)
{
BandMin = BottomChunkZ * CHUNK_SIZE;
BandMax = (TopChunkZ + 1) * CHUNK_SIZE;
}
int64 Samples = 0;
Results[Index].Mesh = Mesher.GenerateMesh(
Tile.OriginVoxels(),Step,Cells,nullptr,BandMin,BandMax,&Samples);
Results[Index].Samples = static_cast<uint64>(FMath::Max<int64>(0,Samples));
}
});
}
for (std::thread& Thread : Threads) Thread.join();
if (Cancel && Cancel->load(std::memory_order_relaxed)) { Output.bCancelled = true; break; }
size_t VertexCount = 0, IndexCount = 0;
for (const FTileResult& Result : Results)
{
if (!Result.Mesh.Triangles.IsEmpty()) ++LevelOutput.NonEmptyTiles;
VertexCount += static_cast<size_t>(Result.Mesh.Vertices.Num());
IndexCount += static_cast<size_t>(Result.Mesh.Triangles.Num());
LevelOutput.DensitySamples += Result.Samples;
}
LevelOutput.Vertices.reserve(VertexCount);
LevelOutput.Indices.reserve(IndexCount);
for (FTileResult& Result : Results)
{
const uint32 BaseVertex = static_cast<uint32>(LevelOutput.Vertices.size());
for (int32 I = 0; I < Result.Mesh.Vertices.Num(); ++I)
{
const FVector& P = Result.Mesh.Vertices[I];
const FVector& N = Result.Mesh.Normals[I];
LevelOutput.Vertices.push_back({
{static_cast<float>(P.X*0.01),static_cast<float>(P.Y*0.01),static_cast<float>(P.Z*0.01)},
{static_cast<float>(N.X),static_cast<float>(N.Y),static_cast<float>(N.Z)}});
}
for (int32 Index : Result.Mesh.Triangles)
LevelOutput.Indices.push_back(BaseVertex + static_cast<uint32>(Index));
}
LevelOutput.Seconds = std::chrono::duration<double>(FClock::now()-LevelStart).count();
LevelOutput.VertexCount = LevelOutput.Vertices.size();
LevelOutput.IndexCount = LevelOutput.Indices.size();
std::printf("clip level %d: %.3f s, %llu tiles (%llu nonempty), %llu vertices, %.2f M density samples\n",
Level,LevelOutput.Seconds,
static_cast<unsigned long long>(LevelOutput.TileCount),
static_cast<unsigned long long>(LevelOutput.NonEmptyTiles),
static_cast<unsigned long long>(LevelOutput.VertexCount),
static_cast<double>(LevelOutput.DensitySamples)/1.0e6);
std::fflush(stdout);
if (OnLevelReady) OnLevelReady(Level,std::move(LevelOutput));
if (!bRetainMeshData)
{
FStandaloneCpuLevel& Metadata = Output.Levels[static_cast<size_t>(Level)];
Metadata.Vertices.clear();
Metadata.Indices.clear();
Metadata.Vertices.shrink_to_fit();
Metadata.Indices.shrink_to_fit();
}
}
Output.TotalSeconds = std::chrono::duration<double>(FClock::now()-TotalStart).count();
std::printf("clipmap total: %.3f s on %d workers, %zu tiles\n",Output.TotalSeconds,
Output.WorkerCount,static_cast<size_t>(AllTiles.Num()));
return Output;
}
}