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
180 lines
8.5 KiB
C++
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;
|
|
}
|
|
}
|