standalone: speed round closed -- full-payload whole view 2.16 s @8 workers; UV/colour/groups cost 0.62 s

Takeover after the PC reset. Verified at 1 and 8 workers: density, geometry, full payload and
topology hashes equal for all nine outputs; routes 5642/5642; crater transaction 17 ms; player
neighbourhood mesh-ready 23 ms after streaming starts.
- The plugin's materials consume UV, the packed biome/slope/blend colours and the ceiling/ground
  sections, so the viewer-only skips stay off by default (diagnostic only: 1.53 s without them).
- UVs staged as FVector2f (matches the game stream; -20 MB, no speed change). Far-bin reserve 24x.
- LOD6 is the long pole: 32.7 M density samples, 1.83 M vertices, ~1.1 s.

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-04 13:27:03 +02:00
1 parent ee5b05b357
commit 6ec4a8f856
10 files changed
+417 -166

No files matched your search

+1 -1
View File
@@ -1856,7 +1856,7 @@ private:
ActiveSpatialBinSize=BinResolution;
// Primitive bounds overlap multiple cells. Reserve from the measured bins-per-primitive
// density for each grid instead of repeatedly rehashing from a 2x primitive guess.
const size_t BinsPerPrimitive=bBuildAxisBins?96u:24u;
const size_t BinsPerPrimitive=bBuildAxisBins?96u:24u;
SpatialBins.reserve(std::max<size_t>(Primitives.size()*BinsPerPrimitive,1u));
if(!bBuildAxisBins)
{
+51 -13
View File
@@ -1704,7 +1704,8 @@ 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,bool bShardAudit,float CameraYaw,float CameraPitch,
bool bHashDensity,bool bSkipUvAndColors,bool bSkipMaterialGroups,
bool bShardAudit,float CameraYaw,float CameraPitch,
const std::string& BakeCachePath,
const FStandaloneMeshCacheMetadata& CacheMetadata)
{
@@ -1723,6 +1724,8 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
{return Tile.Level<World.FullResClipLevels;}));
std::array<std::vector<FHashedTile>,MaxLevels+1> HashRows;
std::array<std::vector<FHashedTile>,MaxLevels+1> MeshHashRows;
std::array<std::vector<FHashedTile>,MaxLevels+1> PayloadHashRows;
std::array<std::vector<FHashedTile>,MaxLevels+1> TopologyHashRows;
std::array<uint64,MaxLevels+1> SamplesByLevel{};
std::array<uint64,MaxLevels+1> VerticesByLevel{};
std::array<uint64,MaxLevels+1> IndicesByLevel{};
@@ -1731,6 +1734,7 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
std::array<double,MaxLevels+1> FirstCompletionByLevel{};
std::array<double,MaxLevels+1> LastCompletionByLevel{};
std::array<uint64,MaxLevels+1> OutputBytesByLevel{};
std::array<uint64,MaxLevels+1> MeshPayloadAllocatedBytesByLevel{};
FirstCompletionByLevel.fill(std::numeric_limits<double>::infinity());
std::unordered_set<FVoxelTileKey,FStandaloneTileKeyHash> PlayerNeighborhoodTiles;
for(const FVoxelTileKey& Tile:Desired.Tiles)
@@ -1744,14 +1748,16 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
if(!BakeCachePath.empty())CacheTiles.reserve(Desired.Tiles.size());
const auto Start=std::chrono::steady_clock::now();
FStandaloneStreamingGenerator Streamer(World,WorkerCount,
bHashDensity||!BakeCachePath.empty(),Cache,MaxLevel);
bHashDensity||!BakeCachePath.empty(),Cache,MaxLevel,
bSkipUvAndColors,bSkipMaterialGroups);
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 hash=%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 uv_colors=%s material_groups=%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",
bSkipUvAndColors?"skipped":"full",bSkipMaterialGroups?"skipped":"full",
(bHashDensity||!BakeCachePath.empty())?"on":"off");
std::fflush(stdout);
int32 Finished=0,FullDetailFinished=0;
@@ -1790,8 +1796,11 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
LastCompletionByLevel[Level]=std::max(LastCompletionByLevel[Level],CompletedSeconds);
OutputBytesByLevel[Level]+=static_cast<uint64>(Tile.Vertices.capacity())*sizeof(FStandaloneCpuVertex)
+static_cast<uint64>(Tile.Indices.capacity())*sizeof(uint32);
MeshPayloadAllocatedBytesByLevel[Level]+=Tile.MeshPayloadAllocatedBytes;
if(Tile.bHasDensityHash)HashRows[Level].push_back({Tile.Key,Tile.DensityHash});
if(Tile.bHasMeshHash)MeshHashRows[Level].push_back({Tile.Key,Tile.MeshHash});
if(Tile.bHasPayloadHash)PayloadHashRows[Level].push_back({Tile.Key,Tile.PayloadHash});
if(Tile.bHasTopologyHash)TopologyHashRows[Level].push_back({Tile.Key,Tile.TopologyHash});
}
if(Level<World.FullResClipLevels)++FullDetailFinished;
if(bShardAudit&&Level==0)
@@ -1820,16 +1829,22 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
std::chrono::steady_clock::now()-Start).count();
std::vector<FHashedTile> FullRows;
std::vector<FHashedTile> FullMeshRows;
std::vector<FHashedTile> FullPayloadRows;
std::vector<FHashedTile> FullTopologyRows;
for(int32 Level=0;Level<World.FullResClipLevels&&Level<=MaxLevels;++Level)
{
FullRows.insert(FullRows.end(),HashRows[Level].begin(),HashRows[Level].end());
FullMeshRows.insert(FullMeshRows.end(),MeshHashRows[Level].begin(),MeshHashRows[Level].end());
FullPayloadRows.insert(FullPayloadRows.end(),PayloadHashRows[Level].begin(),PayloadHashRows[Level].end());
FullTopologyRows.insert(FullTopologyRows.end(),TopologyHashRows[Level].begin(),TopologyHashRows[Level].end());
}
if(bHashDensity||!BakeCachePath.empty())
std::printf("[StreamFullDetail] wall=%.3fs generated=%d/%d resident_tiles_cpu_only=1 density_hash=0x%016llx mesh_hash=0x%016llx\n",
std::printf("[StreamFullDetail] wall=%.3fs generated=%d/%d resident_tiles_cpu_only=1 density_hash=0x%016llx mesh_hash=0x%016llx payload_hash=0x%016llx topology_hash=0x%016llx\n",
FullDetailSeconds,FullDetailFinished,FullDetailExpected,
static_cast<unsigned long long>(HashTileRows(FullRows)),
static_cast<unsigned long long>(HashTileRows(FullMeshRows)));
static_cast<unsigned long long>(HashTileRows(FullMeshRows)),
static_cast<unsigned long long>(HashTileRows(FullPayloadRows)),
static_cast<unsigned long long>(HashTileRows(FullTopologyRows)));
else
std::printf("[StreamFullDetail] wall=%.3fs generated=%d/%d resident_tiles_cpu_only=1 hash=off\n",
FullDetailSeconds,FullDetailFinished,FullDetailExpected);
@@ -1846,26 +1861,40 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
std::printf("[PlayerNeighborhood] lod0_tiles=%zu/%zu mesh_ready=missing collision_cook=unavailable_in_standalone\n",
PlayerNeighborhoodReady.size(),PlayerNeighborhoodTiles.size());
const double TotalWorkerSeconds=std::accumulate(WorkerSecondsByLevel.begin(),WorkerSecondsByLevel.end(),0.0);
const uint64 TotalMeshPayloadAllocatedBytes=std::accumulate(
MeshPayloadAllocatedBytesByLevel.begin(),MeshPayloadAllocatedBytesByLevel.end(),uint64{0});
const uint64 TotalViewerOutputBytes=std::accumulate(
OutputBytesByLevel.begin(),OutputBytesByLevel.end(),uint64{0});
const double WorkerBusyPercent=100.0*TotalWorkerSeconds
/(std::max(1,WorkerCount)*std::max(0.001,FullClipSeconds));
std::vector<FHashedTile> AllRows;
std::vector<FHashedTile> AllMeshRows;
std::vector<FHashedTile> AllPayloadRows;
std::vector<FHashedTile> AllTopologyRows;
if(bHashDensity||!BakeCachePath.empty())
for(int32 Level=0;Level<=MaxLevels;++Level)
{
AllRows.insert(AllRows.end(),HashRows[Level].begin(),HashRows[Level].end());
AllMeshRows.insert(AllMeshRows.end(),MeshHashRows[Level].begin(),MeshHashRows[Level].end());
AllPayloadRows.insert(AllPayloadRows.end(),PayloadHashRows[Level].begin(),PayloadHashRows[Level].end());
AllTopologyRows.insert(AllTopologyRows.end(),TopologyHashRows[Level].begin(),TopologyHashRows[Level].end());
}
if(bHashDensity||!BakeCachePath.empty())
std::printf("[StreamFullClipmap] wall=%.3fs generated=%d/%d worker_sum=%.3fs worker_busy=%.1f%% worker_idle=%.1f%% tiles=%d density_hash=0x%016llx mesh_hash=0x%016llx\n",
std::printf("[StreamFullClipmap] wall=%.3fs generated=%d/%d worker_sum=%.3fs worker_busy=%.1f%% worker_idle=%.1f%% payload_alloc=%.2fMiB viewer_output=%.2fMiB tiles=%d density_hash=0x%016llx mesh_hash=0x%016llx payload_hash=0x%016llx topology_hash=0x%016llx\n",
FullClipSeconds,Finished,DesiredTotal,TotalWorkerSeconds,WorkerBusyPercent,
std::max(0.0,100.0-WorkerBusyPercent),
static_cast<double>(TotalMeshPayloadAllocatedBytes)/1048576.0,
static_cast<double>(TotalViewerOutputBytes)/1048576.0,
DesiredTotal,static_cast<unsigned long long>(HashTileRows(AllRows)),
static_cast<unsigned long long>(HashTileRows(AllMeshRows)));
static_cast<unsigned long long>(HashTileRows(AllMeshRows)),
static_cast<unsigned long long>(HashTileRows(AllPayloadRows)),
static_cast<unsigned long long>(HashTileRows(AllTopologyRows)));
else
std::printf("[StreamFullClipmap] wall=%.3fs generated=%d/%d worker_sum=%.3fs worker_busy=%.1f%% worker_idle=%.1f%% tiles=%d hash=off\n",
std::printf("[StreamFullClipmap] wall=%.3fs generated=%d/%d worker_sum=%.3fs worker_busy=%.1f%% worker_idle=%.1f%% payload_alloc=%.2fMiB viewer_output=%.2fMiB tiles=%d hash=off\n",
FullClipSeconds,Finished,DesiredTotal,TotalWorkerSeconds,WorkerBusyPercent,
std::max(0.0,100.0-WorkerBusyPercent),DesiredTotal);
std::max(0.0,100.0-WorkerBusyPercent),
static_cast<double>(TotalMeshPayloadAllocatedBytes)/1048576.0,
static_cast<double>(TotalViewerOutputBytes)/1048576.0,DesiredTotal);
std::printf("[StreamCacheUse] hits=%llu requested=%d\n",
static_cast<unsigned long long>(Streamer.GetCacheHits()),DesiredTotal);
if(bShardAudit)
@@ -1882,22 +1911,26 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
{
if(TilesByLevel[Level]==0)continue;
if(bHashDensity||!BakeCachePath.empty())
std::printf("[StreamLevel] lod=%d tiles=%llu samples=%.3fM worker_sum=%.3fs first_ready=%.3fs complete=%.3fs worker_busy=%.1f%% output=%.2fMiB vertices=%llu indices=%llu density_hash=0x%016llx mesh_hash=0x%016llx\n",
std::printf("[StreamLevel] lod=%d tiles=%llu samples=%.3fM worker_sum=%.3fs first_ready=%.3fs complete=%.3fs worker_busy=%.1f%% payload_alloc=%.2fMiB viewer_output=%.2fMiB vertices=%llu indices=%llu density_hash=0x%016llx mesh_hash=0x%016llx payload_hash=0x%016llx topology_hash=0x%016llx\n",
Level,static_cast<unsigned long long>(TilesByLevel[Level]),
static_cast<double>(SamplesByLevel[Level])/1.0e6,WorkerSecondsByLevel[Level],
FirstCompletionByLevel[Level],LastCompletionByLevel[Level],
100.0*WorkerSecondsByLevel[Level]/(WorkerCount*std::max(0.001,LastCompletionByLevel[Level])),
static_cast<double>(MeshPayloadAllocatedBytesByLevel[Level])/1048576.0,
static_cast<double>(OutputBytesByLevel[Level])/1048576.0,
static_cast<unsigned long long>(VerticesByLevel[Level]),
static_cast<unsigned long long>(IndicesByLevel[Level]),
static_cast<unsigned long long>(HashTileRows(HashRows[Level])),
static_cast<unsigned long long>(HashTileRows(MeshHashRows[Level])));
static_cast<unsigned long long>(HashTileRows(MeshHashRows[Level])),
static_cast<unsigned long long>(HashTileRows(PayloadHashRows[Level])),
static_cast<unsigned long long>(HashTileRows(TopologyHashRows[Level])));
else
std::printf("[StreamLevel] lod=%d tiles=%llu samples=%.3fM worker_sum=%.3fs first_ready=%.3fs complete=%.3fs worker_busy=%.1f%% output=%.2fMiB vertices=%llu indices=%llu hash=off\n",
std::printf("[StreamLevel] lod=%d tiles=%llu samples=%.3fM worker_sum=%.3fs first_ready=%.3fs complete=%.3fs worker_busy=%.1f%% payload_alloc=%.2fMiB viewer_output=%.2fMiB vertices=%llu indices=%llu hash=off\n",
Level,static_cast<unsigned long long>(TilesByLevel[Level]),
static_cast<double>(SamplesByLevel[Level])/1.0e6,WorkerSecondsByLevel[Level],
FirstCompletionByLevel[Level],LastCompletionByLevel[Level],
100.0*WorkerSecondsByLevel[Level]/(WorkerCount*std::max(0.001,LastCompletionByLevel[Level])),
static_cast<double>(MeshPayloadAllocatedBytesByLevel[Level])/1048576.0,
static_cast<double>(OutputBytesByLevel[Level])/1048576.0,
static_cast<unsigned long long>(VerticesByLevel[Level]),
static_cast<unsigned long long>(IndicesByLevel[Level]));
@@ -2004,6 +2037,8 @@ int main(int Argc,char** Argv)
bool bStreamBenchmark=false;
bool bShardAudit=false;
bool bStreamHash=false;
bool bSkipUvAndColors=false;
bool bSkipMaterialGroups=false;
bool bEditBenchmark=false;
bool bRoundWallCompare=false;
int32 RoundCompareTileCount=16;
@@ -2037,6 +2072,8 @@ int main(int Argc,char** Argv)
else if(Arg=="--edit-benchmark")
{bEditBenchmark=true;bStreamBenchmark=true;App.bShapeWorld=true;}
else if(Arg=="--stream-hash")bStreamHash=true;
else if(Arg=="--skip-uv-colors")bSkipUvAndColors=true;
else if(Arg=="--skip-material-groups")bSkipMaterialGroups=true;
else if(Arg=="--shard-audit")bShardAudit=true;
else if(Arg=="--compare-round-walls")
{bRoundWallCompare=true;App.bRoundTunnels=true;}
@@ -2125,7 +2162,8 @@ int main(int Argc,char** Argv)
FVector(App.Position.X*4.0,App.Position.Y*4.0,App.Position.Z*4.0),
App.WorkerCount,StreamBenchmarkMaxLevel,App.bRoundTunnels,
App.bRoundTunnelEnhanced,(!App.bShapeWorld&&App.MeshCache.IsLoaded())?&App.MeshCache:nullptr,
bStreamHash,bShardAudit,App.Yaw,App.Pitch,BakeMeshCachePath,App.MakeMeshCacheMetadata());
bStreamHash,bSkipUvAndColors,bSkipMaterialGroups,bShardAudit,
App.Yaw,App.Pitch,BakeMeshCachePath,App.MakeMeshCacheMetadata());
}
if(BenchmarkLevel>=0)
return RunTileBenchmark(*App.World,App.Position,BenchmarkLevel,BenchmarkCount,
+104 -3
View File
@@ -42,9 +42,94 @@ struct FStandaloneCpuTile
bool bHasDensityHash=false;
uint64 MeshHash=14695981039346656037ull;
bool bHasMeshHash=false;
uint64 PayloadHash=14695981039346656037ull;
bool bHasPayloadHash=false;
uint64 TopologyHash=14695981039346656037ull;
bool bHasTopologyHash=false;
uint64 MeshPayloadAllocatedBytes=0;
std::array<uint64,VoxelDensityProfile::BucketCount> ProfileCycles{};
};
inline uint64 HashStandaloneMeshPayload(const FVoxelMeshData& Mesh)
{
uint64 Hash=14695981039346656037ull;
const auto AddWord=[&](uint64 Word,int32 Bytes)
{
for(int32 I=0;I<Bytes;++I)
{
Hash^=static_cast<uint8>(Word>>(I*8));
Hash*=1099511628211ull;
}
};
const auto AddDouble=[&](double Value)
{ AddWord(std::bit_cast<uint64>(Value),8); };
AddWord(static_cast<uint64>(Mesh.Vertices.Num()),8);
for(const FVector& Position:Mesh.Vertices)
{ AddDouble(Position.X);AddDouble(Position.Y);AddDouble(Position.Z); }
AddWord(static_cast<uint64>(Mesh.Normals.Num()),8);
for(const FVector& Normal:Mesh.Normals)
{ AddDouble(Normal.X);AddDouble(Normal.Y);AddDouble(Normal.Z); }
AddWord(static_cast<uint64>(Mesh.UVs.Num()),8);
for(const auto& UV:Mesh.UVs)
{ AddDouble(static_cast<double>(UV.X));AddDouble(static_cast<double>(UV.Y)); }
AddWord(static_cast<uint64>(Mesh.Colors.Num()),8);
for(const FColor& Color:Mesh.Colors)
{ AddWord(Color.R,1);AddWord(Color.G,1);AddWord(Color.B,1);AddWord(Color.A,1); }
AddWord(static_cast<uint64>(Mesh.Triangles.Num()),8);
for(const int32 Index:Mesh.Triangles)AddWord(static_cast<uint32>(Index),4);
AddWord(static_cast<uint32>(Mesh.NumSurfaceVertices),4);
AddWord(static_cast<uint32>(Mesh.NumSkirtTriangles),4);
AddWord(static_cast<uint32>(Mesh.NumCeilingTriangles),4);
return Hash;
}
inline uint64 HashStandaloneMeshTopology(const FVoxelMeshData& Mesh)
{
uint64 Hash=14695981039346656037ull;
const auto AddWord=[&](uint64 Word,int32 Bytes)
{
for(int32 I=0;I<Bytes;++I)
{
Hash^=static_cast<uint8>(Word>>(I*8));
Hash*=1099511628211ull;
}
};
using FTriangleKey=std::array<uint64,3>;
const auto VertexKey=[&](int32 Index)
{
uint64 VertexHash=14695981039346656037ull;
const auto AddVertexWord=[&](uint64 Word)
{
for(int32 Byte=0;Byte<8;++Byte)
{
VertexHash^=static_cast<uint8>(Word>>(Byte*8));
VertexHash*=1099511628211ull;
}
};
if(Index<0||Index>=Mesh.Vertices.Num()||Index>=Mesh.Normals.Num())
{ AddVertexWord(UINT64_MAX);return VertexHash; }
const FVector& Position=Mesh.Vertices[Index];
const FVector& Normal=Mesh.Normals[Index];
AddVertexWord(std::bit_cast<uint64>(static_cast<double>(Position.X)));
AddVertexWord(std::bit_cast<uint64>(static_cast<double>(Position.Y)));
AddVertexWord(std::bit_cast<uint64>(static_cast<double>(Position.Z)));
AddVertexWord(std::bit_cast<uint64>(static_cast<double>(Normal.X)));
AddVertexWord(std::bit_cast<uint64>(static_cast<double>(Normal.Y)));
AddVertexWord(std::bit_cast<uint64>(static_cast<double>(Normal.Z)));
return VertexHash;
};
std::vector<FTriangleKey> Triangles;
Triangles.reserve(static_cast<size_t>(Mesh.Triangles.Num()/3));
for(int32 I=0;I+2<Mesh.Triangles.Num();I+=3)
Triangles.push_back({VertexKey(Mesh.Triangles[I]),
VertexKey(Mesh.Triangles[I+1]),VertexKey(Mesh.Triangles[I+2])});
std::sort(Triangles.begin(),Triangles.end());
AddWord(static_cast<uint64>(Triangles.size()),8);
for(const FTriangleKey& Triangle:Triangles)
for(const uint64 VertexHash:Triangle)AddWord(VertexHash,8);
return Hash;
}
inline uint64 HashStandaloneCpuTileMesh(const FStandaloneCpuTile& Tile)
{
uint64 Hash=14695981039346656037ull;
@@ -310,9 +395,12 @@ public:
explicit FStandaloneStreamingGenerator(FStandaloneOwnerWorld& InWorld,int32 InWorkerCount=8,
bool bInHashDensity=false,
const FStandaloneMeshCache* InMeshCache=nullptr,
int32 InMaxLevelFilter=-1)
int32 InMaxLevelFilter=-1,
bool bInSkipUvAndColors=false,
bool bInSkipMaterialGroups=false)
:World(&InWorld),WorkerCount(FMath::Clamp(InWorkerCount,1,16)),bHashDensity(bInHashDensity),
MeshCache(InMeshCache),MaxLevelFilter(InMaxLevelFilter)
MeshCache(InMeshCache),MaxLevelFilter(InMaxLevelFilter),
bSkipUvAndColors(bInSkipUvAndColors),bSkipMaterialGroups(bInSkipMaterialGroups)
{
VoxelGenLOD::InitializeConsoleSwitches();
UVoxelMarchingCubesMesher::InitializeConsoleSwitches();
@@ -454,7 +542,8 @@ private:
}
UVoxelMarchingCubesMesher Mesher;
Mesher.SetGenerator(World->Generator.get());
Mesher.bStandaloneCompactPayload=true;
Mesher.bStandaloneSkipUvAndColors=bSkipUvAndColors;
Mesher.bStandaloneSkipMaterialGroups=bSkipMaterialGroups;
Mesher.bGenerateSkirts=World->bGenerateSkirts;
Mesher.SkirtCells=static_cast<float>(World->SkirtCells);
Mesher.LODOctaveDrop=World->LODOctaveDrop;
@@ -490,6 +579,16 @@ private:
}
}
Out.DensitySamples=static_cast<uint64>(FMath::Max<int64>(0,SampleCount));
Out.MeshPayloadAllocatedBytes=static_cast<uint64>(Mesh.Vertices.GetAllocatedSize())
+static_cast<uint64>(Mesh.Normals.GetAllocatedSize())
+static_cast<uint64>(Mesh.UVs.GetAllocatedSize())
+static_cast<uint64>(Mesh.Colors.GetAllocatedSize())
+static_cast<uint64>(Mesh.Triangles.GetAllocatedSize());
if(bHashDensity)
{
Out.PayloadHash=HashStandaloneMeshPayload(Mesh);Out.bHasPayloadHash=true;
Out.TopologyHash=HashStandaloneMeshTopology(Mesh);Out.bHasTopologyHash=true;
}
Out.Vertices.reserve(static_cast<size_t>(Mesh.Vertices.Num()));
for(int32 I=0;I<Mesh.Vertices.Num();++I)
{
@@ -534,6 +633,8 @@ private:
bool bHashDensity=false;
const FStandaloneMeshCache* MeshCache=nullptr;
int32 MaxLevelFilter=-1;
bool bSkipUvAndColors=false;
bool bSkipMaterialGroups=false;
std::atomic<uint64> CacheHitCount{0};
mutable std::mutex Mutex;
std::condition_variable Wake;
@@ -159,8 +159,8 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
// mémoïsé par colonne quantifiée au pas de la grille → ≤ (colonnes touchées) appels ;
// ~zéro sur une tuile de sol pur, borné par lattice² sur une tuile de cap.
static thread_local TArray<uint8> VertexClasses; // 0 = sol, 1 = sky-cap
static thread_local TMap<FIntVector, FVector2f> SurfColMemo; // (Xq,Yq,chunkZ) → (TerrainZ, CeilSurf) ; X=FLT_MAX ⇒ pas SurfaceWorld
if (!bStandaloneCompactPayload)
static thread_local TMap<FIntVector, FVector2f> SurfColMemo; // (Xq,Yq,chunkZ) → (TerrainZ, CeilSurf)
if (!bStandaloneSkipMaterialGroups)
{
VertexClasses.Reset();
SurfColMemo.Reset();
@@ -194,10 +194,10 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
}
MeshData.Normals.Add(Normal);
if (!bStandaloneCompactPayload)
if (!bStandaloneSkipUvAndColors)
{
// UVs planaires — le triplanar mapping se fait dans le matériau.
MeshData.UVs.Add(FVector2D(WorldPos.X / VOXEL_SIZE, WorldPos.Y / VOXEL_SIZE));
MeshData.UVs.Add(FVector2f(WorldPos.X / VOXEL_SIZE, WorldPos.Y / VOXEL_SIZE));
//-----------------------------------------------------------------
// VERTEX COLOUR — masks for biome palette, slope and border blend.
@@ -211,7 +211,7 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
MeshData.Colors.Add(FColor(R, G, Bc, A));
}
if (!bStandaloneCompactPayload)
if (!bStandaloneSkipMaterialGroups)
{
// F17 — classify down-facing vertices for material-only ceiling grouping.
uint8 SurfClass = 0;
@@ -1241,7 +1241,7 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
const int32 Idx2 = GetOrCreateVertex(EdgeVertices[E2], EdgeNormals[E2]);
TArray<int32>* Dst = &GroundTris;
if (!bStandaloneCompactPayload)
if (!bStandaloneSkipMaterialGroups)
{
// F17 — majority vote (at least two ceiling vertices => ceiling triangle).
const int32 CapVotes = (int32)VertexClasses[Idx0]
@@ -1314,9 +1314,9 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
const int32 Idx = MeshData.Vertices.Num();
MeshData.Vertices.Add(Pos);
MeshData.Normals.Add(Nrm);
if (!bStandaloneCompactPayload)
if (!bStandaloneSkipUvAndColors)
{
MeshData.UVs.Add(FVector2D(Pos.X / VOXEL_SIZE, Pos.Y / VOXEL_SIZE));
MeshData.UVs.Add(FVector2f(Pos.X / VOXEL_SIZE, Pos.Y / VOXEL_SIZE));
MeshData.Colors.Add(Col); // parallel with the other vertex arrays
}
return Idx;
@@ -1342,8 +1342,8 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
const FVector NA = MeshData.Normals[iA];
const FVector NB = MeshData.Normals[iB];
const FColor CA = bStandaloneCompactPayload ? FColor::White : MeshData.Colors[iA];
const FColor CB = bStandaloneCompactPayload ? FColor::White : MeshData.Colors[iB];
const FColor CA = bStandaloneSkipUvAndColors ? FColor::White : MeshData.Colors[iA];
const FColor CB = bStandaloneSkipUvAndColors ? FColor::White : MeshData.Colors[iB];
const int32 iA2 = AddSkirtVert(PA - NA * SkirtDepth, NA, CA);
const int32 iB2 = AddSkirtVert(PB - NB * SkirtDepth, NB, CB);
@@ -116,10 +116,10 @@ public:
// SETTINGS
//=========================================================================
// Standalone CPU streaming consumes positions, normals and indices only. When enabled,
// GenerateMesh omits UVs, colors and material-only ceiling grouping. Runtime callers leave
// this false and keep the complete FVoxelMeshData contract.
bool bStandaloneCompactPayload = false;
// Optional standalone benchmark switches. Runtime callers leave both false and retain the
// complete FVoxelMeshData contract consumed by the game materials and section setup.
bool bStandaloneSkipUvAndColors = false;
bool bStandaloneSkipMaterialGroups = false;
// Seuil de l'isosurface dans le champ de densité.
// Convention MC: densité < IsoLevel = solide, >= = air.
@@ -366,7 +366,7 @@ struct FVoxelMeshData
{
TArray<FVector> Vertices; // Positions monde
TArray<int32> Triangles; // Indices, 3 par triangle
TArray<FVector2D> UVs; // Coords de texture (une par vertex)
TArray<FVector2f> UVs; // Packed-for-upload float UVs (one per vertex)
TArray<FVector> Normals; // Normale lissée (gradient de densité)
TArray<FColor> Colors; // Masques matériau F6 (R=palette biome dominant, G=pente,
// B=poids de fondu de bordure, A=palette biome voisin)