Files
VoxelForge/Standalone/Source/WorldMesher.h
T
Fr0zkaandClaude Opus 5.5 ca85e876ad feat(core): ore vein records and a materialAt query, off by default (L50); research R18, R19
Versioned capsule vein records (stable ID, resource ID, endpoints, radius, seeded irregularity) resolved
into the snapshot only when requested, placed beside the route and away from passages, landings and
landmarks (forced-passage control rejected). The bound field table gains materialAt (host + two resource
IDs + Q16 weights) behind a per-tile BVH; mesh export v2 carries per-vertex materials; a measurement-only
yield computes removed resource volume (0 on already-air space). With veins on, every density, mesh,
payload and topology hash is unchanged; demo off: owner hashes identical to L47. fast/medium/full PASS.
Research R18 (cavern-graph planner) and R19 (why FloatingIslands costs ~10x SurfaceWorld).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-10-24 18:25:03 +02:00

189 lines
8.7 KiB
C++

#pragma once
#include "OwnerWorldLoader.h"
#include "VoxelClipmapDesiredTiles.h"
#include "VoxelMarchingCubesMesher.h"
#include <atomic>
#include <array>
#include <chrono>
#include <cstdint>
#include <thread>
struct FStandaloneCpuVertex
{
float Position[3];
float Normal[3];
std::uint16_t HostMaterialId=1;
std::array<std::uint16_t,2> ResourceMaterialIds{};
std::array<std::uint16_t,2> ResourceWeightsQ16{};
};
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;
}
}