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:
1 parent
ee5b05b357
commit
6ec4a8f856
10 files changed
+417
-166
No files matched your search
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in new issue
Block a user