wip(assembler): round S10 -- LOD8 horizon sampled at MC spacing: LOD8 worker time 18.8 s -> 4.3 s

Planar far horizon samples the LOD8 interior at the MC cell spacing, keeping
128-voxel boundary strips; far recipe v3. LOD8 p95 59.8 -> 13.9 ms, spawn
worker-seconds ~41 -> 26.9, full set 7.8 -> 7.1 s, ring 0.49 s, LOD0 1.43 s,
40 m/s ring 75/75. S3/S4 2 cm agreement, 100% cap coverage, W1/W8/W12 and
W1/W8/W16 determinism pass. Report: Standalone/Research/WorldAssemblerM3/Round14.

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-19 12:23:18 +02:00
1 parent e7586a1256
commit b90d63d0f7
14 files changed
+616 -86

No files matched your search

@@ -125,6 +125,11 @@ namespace VoxelForgeCore
std::uint64_t columnCacheMisses = 0;
double classificationMilliseconds = 0.0;
double columnComputeMilliseconds = 0.0;
double columnEvaluationExclusiveMilliseconds = 0.0;
double marchingCubesExclusiveMilliseconds = 0.0;
double groundRefinementExclusiveMilliseconds = 0.0;
double capRefinementExclusiveMilliseconds = 0.0;
double meshAssemblyMilliseconds = 0.0;
double meshingMilliseconds = 0.0;
double featureListMilliseconds = 0.0;
double farShellMilliseconds = 0.0;
@@ -146,6 +151,9 @@ namespace VoxelForgeCore
std::uint64_t capRefinedCells = 0;
std::uint64_t capRefinedTriangles = 0;
bool capRefined = false;
std::uint64_t columnEvaluationWorkerHash = 0;
bool columnCoordinateCaptureComplete = true;
std::vector<std::array<std::int64_t, 2>> evaluatedColumnCoordinates;
};
struct TileMesh
@@ -628,6 +636,9 @@ namespace VoxelForgeCore
std::uint64_t columnHits = 0;
std::uint64_t columnMisses = 0;
double columnComputeMilliseconds = 0.0;
bool captureCoordinates = false;
bool coordinateCaptureComplete = true;
std::vector<std::array<std::int64_t, 2>> evaluatedCoordinates;
};
struct FieldInfluenceBounds
@@ -676,8 +687,8 @@ namespace VoxelForgeCore
std::int64_t minimumYSubvoxels, std::int64_t minimumZSubvoxels,
std::int64_t maximumXSubvoxels, std::int64_t maximumYSubvoxels,
std::int64_t maximumZSubvoxels, std::int32_t stepVoxels) const noexcept;
static void resetThreadColumnCacheStats() noexcept;
static HeightfieldColumnCacheStats threadColumnCacheStats() noexcept;
static void resetThreadColumnCacheStats(bool captureCoordinates = false) noexcept;
static const HeightfieldColumnCacheStats& threadColumnCacheStats() noexcept;
FieldInterval interval(std::int64_t minimumXSubvoxels,
std::int64_t minimumYSubvoxels,
std::int64_t minimumZSubvoxels,
@@ -1002,11 +1013,17 @@ namespace VoxelForgeCore
// Validation control only; never participates in recipe/world identity.
// Controle de validation seulement, sans effet sur l'identite du monde.
bool useAssemblyFeatureQueries = true;
// Opt-in diagnostic only; records XY columns evaluated by this LOD8 tile.
// Diagnostic seulement; enregistre les colonnes XY evaluees par cette tuile LOD8.
bool captureColumnCoordinates = false;
// LOD8-only planar horizon interior grid; zero follows the MC step, 128 restores S9.
// Grille interieure LOD8 seulement; zero suit la maille MC, 128 restaure S9.
std::int32_t lod8HorizonInteriorStepVoxels = 0;
// Bump when the distant cap representation changes. LOD4-8 mesh hashes are salted;
// density and LOD0-3 hashes remain unchanged.
// Incremente quand le plafond lointain change. Les hashes LOD4-8 incluent cette version;
// densite et hashes LOD0-3 restent inchanges.
std::uint32_t farHorizonVersion = 2;
std::uint32_t farHorizonVersion = 3;
};
class VFCORE_API World
+190 -71
View File
@@ -11,6 +11,8 @@
#include <unordered_map>
#include <unordered_set>
#include <set>
#include <functional>
#include <thread>
namespace vfcore::detail
{
@@ -247,6 +249,45 @@ namespace VoxelForgeCore
bool Refined = false;
};
std::vector<int32> BuildNativeHorizonGridCoordinates(int32 TileExtent,int32 InteriorStep)
{
constexpr int32 BoundaryStep=128;
std::vector<int32> Coordinates;
if(TileExtent<=0)return Coordinates;
if(InteriorStep<=BoundaryStep)
{
Coordinates.reserve(static_cast<std::size_t>(TileExtent/InteriorStep)+1u);
for(int32 Position=0;Position<TileExtent;Position+=InteriorStep)
Coordinates.push_back(Position);
if(Coordinates.empty()||Coordinates.back()!=TileExtent)Coordinates.push_back(TileExtent);
return Coordinates;
}
if(TileExtent<2*InteriorStep)
{
Coordinates.reserve(static_cast<std::size_t>(TileExtent/BoundaryStep)+1u);
for(int32 Position=0;Position<TileExtent;Position+=BoundaryStep)
Coordinates.push_back(Position);
if(Coordinates.empty()||Coordinates.back()!=TileExtent)Coordinates.push_back(TileExtent);
return Coordinates;
}
// Keep the shared 128-voxel boundary lattice for LOD transitions while the LOD8
// interior follows the coarse MC cell spacing. Preserve the exact same coordinates
// in neighboring tiles, independent of which worker builds either side.
// Garder la bordure commune de 128 voxels, avec un interieur a la maille MC de LOD8.
for(int32 Position=0;Position<InteriorStep&&Position<TileExtent;Position+=BoundaryStep)
Coordinates.push_back(Position);
if(Coordinates.empty()||Coordinates.back()!=InteriorStep)
Coordinates.push_back(InteriorStep);
for(int32 Position=InteriorStep+InteriorStep;Position<TileExtent-InteriorStep;
Position+=InteriorStep)
Coordinates.push_back(Position);
for(int32 Position=TileExtent-InteriorStep;Position<TileExtent;Position+=BoundaryStep)
if(Coordinates.empty()||Coordinates.back()!=Position)Coordinates.push_back(Position);
if(Coordinates.empty()||Coordinates.back()!=TileExtent)Coordinates.push_back(TileExtent);
return Coordinates;
}
FVector NormalizeCapNormal(const FVector& Value)
{
const double Length=std::sqrt(Value.X*Value.X+Value.Y*Value.Y+Value.Z*Value.Z);
@@ -299,17 +340,28 @@ namespace VoxelForgeCore
}
FNativeCapRefinementStats RefineFarNativeGround(const FStandaloneOwnerWorld& Owner,
const WorldAssemblyManifest& Manifest,const FVoxelTileKey& Key,FVoxelMeshData& Mesh)
const WorldAssemblyManifest& Manifest,const FVoxelTileKey& Key,int32 MeshStepVoxels,
int32 InteriorStepOverride,FVoxelMeshData& Mesh)
{
FNativeCapRefinementStats Stats;
if(Key.Level<5||!Owner.Generator)return Stats;
const int32 GridStepVoxels=std::min(128,1<<Key.Level);
constexpr int32 BoundaryStepVoxels=128;
constexpr int64 Scale=WorldAssemblySubvoxelsPerVoxel;
const int64 GridStepSubvoxels=static_cast<int64>(GridStepVoxels)*Scale;
const FIntVector TileOrigin=Key.OriginVoxels();
const int32 TileExtent=Key.ExtentVoxels();
const int32 RequestedStep=Key.Level==8
?(InteriorStepOverride>0?InteriorStepOverride:MeshStepVoxels)
:std::min(BoundaryStepVoxels,1<<Key.Level);
const int32 GridStepVoxels=Key.Level==8
?((RequestedStep<BoundaryStepVoxels||RequestedStep%BoundaryStepVoxels!=0
||TileExtent<=0||TileExtent%RequestedStep!=0)
?BoundaryStepVoxels:std::max(BoundaryStepVoxels,RequestedStep))
:RequestedStep;
if(TileExtent<=0||TileExtent%GridStepVoxels!=0)return Stats;
const int32 Cells=TileExtent/GridStepVoxels;
const std::vector<int32> GridCoordinates=
BuildNativeHorizonGridCoordinates(TileExtent,GridStepVoxels);
const int32 GridSize=static_cast<int32>(GridCoordinates.size());
if(GridSize<2)return Stats;
const double TileMinZ=static_cast<double>(TileOrigin.Z)*Scale;
const double TileMaxZ=static_cast<double>(TileOrigin.Z+TileExtent)*Scale;
const int32 OldSurfaceVertices=std::clamp(Mesh.NumSurfaceVertices,0,Mesh.Vertices.Num());
@@ -324,8 +376,9 @@ namespace VoxelForgeCore
struct FGroundProfile
{
int32 Cells=0,GridStep=0;
int32 GridStep=0;
double MinimumZ=0.0,MaximumZ=0.0;
std::vector<int32> Coordinates;
std::vector<double> Ground,Cap;
};
std::vector<FGroundProfile> GroundProfiles;
@@ -344,14 +397,14 @@ namespace VoxelForgeCore
FColor Color=FColor::White;
int32 VertexIndex=INDEX_NONE;
};
std::vector<FGridSample> Grid(static_cast<std::size_t>(Cells+1)*(Cells+1));
const auto GridIndex=[Cells](int32 X,int32 Y)
{return static_cast<std::size_t>(Y)*(Cells+1)+X;};
for(int32 Y=0;Y<=Cells;++Y)
for(int32 X=0;X<=Cells;++X)
std::vector<FGridSample> Grid(static_cast<std::size_t>(GridSize)*GridSize);
const auto GridIndex=[GridSize](int32 X,int32 Y)
{return static_cast<std::size_t>(Y)*GridSize+X;};
for(int32 Y=0;Y<GridSize;++Y)
for(int32 X=0;X<GridSize;++X)
{
const int64 XSub=(static_cast<int64>(TileOrigin.X)+static_cast<int64>(X)*GridStepVoxels)*Scale;
const int64 YSub=(static_cast<int64>(TileOrigin.Y)+static_cast<int64>(Y)*GridStepVoxels)*Scale;
const int64 XSub=(static_cast<int64>(TileOrigin.X)+GridCoordinates[X])*Scale;
const int64 YSub=(static_cast<int64>(TileOrigin.Y)+GridCoordinates[Y])*Scale;
const auto Column=Block.point(XSub,YSub,0);
auto& Sample=Grid[GridIndex(X,Y)];
Sample.Ground=static_cast<double>(Column.groundHeightSubvoxels);
@@ -364,13 +417,13 @@ namespace VoxelForgeCore
static_cast<uint8>(std::clamp(std::lround(Blend*255.0f),0l,255l)),
static_cast<uint8>(std::clamp(Neighbor,0,255)));
}
for(int32 Y=0;Y<=Cells;++Y)
for(int32 X=0;X<=Cells;++X)
for(int32 Y=0;Y<GridSize;++Y)
for(int32 X=0;X<GridSize;++X)
{
const int32 Left=std::max(0,X-1),Right=std::min(Cells,X+1);
const int32 Down=std::max(0,Y-1),Up=std::min(Cells,Y+1);
const double DX=static_cast<double>(Right-Left)*GridStepSubvoxels;
const double DY=static_cast<double>(Up-Down)*GridStepSubvoxels;
const int32 Left=std::max(0,X-1),Right=std::min(GridSize-1,X+1);
const int32 Down=std::max(0,Y-1),Up=std::min(GridSize-1,Y+1);
const double DX=static_cast<double>(GridCoordinates[Right]-GridCoordinates[Left])*Scale;
const double DY=static_cast<double>(GridCoordinates[Up]-GridCoordinates[Down])*Scale;
const double SlopeX=DX>0.0?(Grid[GridIndex(Right,Y)].Ground
-Grid[GridIndex(Left,Y)].Ground)/DX:0.0;
const double SlopeY=DY>0.0?(Grid[GridIndex(X,Up)].Ground
@@ -382,7 +435,7 @@ namespace VoxelForgeCore
}
FGroundProfile Profile;
Profile.Cells=Cells;Profile.GridStep=GridStepVoxels;
Profile.GridStep=GridStepVoxels;Profile.Coordinates=GridCoordinates;
Profile.MinimumZ=static_cast<double>(std::min(Plan.bottomChunkZ,Plan.topChunkZ))*CHUNK_SIZE;
Profile.MaximumZ=static_cast<double>(std::max(Plan.bottomChunkZ,Plan.topChunkZ)+1)*CHUNK_SIZE;
Profile.Ground.reserve(Grid.size());Profile.Cap.reserve(Grid.size());
@@ -392,25 +445,25 @@ namespace VoxelForgeCore
// Partager les sommets du profil entre cellules reduit le payload d'horizon.
// Share regular profile vertices across adjacent cells to bound horizon payload.
for(int32 Y=0;Y<=Cells;++Y)
for(int32 X=0;X<=Cells;++X)
for(int32 Y=0;Y<GridSize;++Y)
for(int32 X=0;X<GridSize;++X)
{
auto& Sample=Grid[GridIndex(X,Y)];
if(Sample.Ground>=Sample.Cap||Sample.Ground<TileMinZ||Sample.Ground>TileMaxZ)continue;
const int64 XSub=(static_cast<int64>(TileOrigin.X)+static_cast<int64>(X)*GridStepVoxels)*Scale;
const int64 YSub=(static_cast<int64>(TileOrigin.Y)+static_cast<int64>(Y)*GridStepVoxels)*Scale;
const int64 XSub=(static_cast<int64>(TileOrigin.X)+GridCoordinates[X])*Scale;
const int64 YSub=(static_cast<int64>(TileOrigin.Y)+GridCoordinates[Y])*Scale;
const double VoxelSizeCm=static_cast<double>(Owner.CoreVoxelSizeMicrons)/10000.0;
Sample.VertexIndex=static_cast<int32>(AddedVertices.size());
AddedVertices.emplace_back(static_cast<double>(TileOrigin.X+X*GridStepVoxels)*VoxelSizeCm,
static_cast<double>(TileOrigin.Y+Y*GridStepVoxels)*VoxelSizeCm,
AddedVertices.emplace_back(static_cast<double>(TileOrigin.X+GridCoordinates[X])*VoxelSizeCm,
static_cast<double>(TileOrigin.Y+GridCoordinates[Y])*VoxelSizeCm,
Sample.Ground/Scale*VoxelSizeCm);
AddedNormals.push_back(Sample.Normal);
if(bHasUVs)AddedUVs.emplace_back(static_cast<float>(XSub/Scale),static_cast<float>(YSub/Scale));
if(bHasColors)AddedColors.push_back(Sample.Color);
}
for(int32 Y=0;Y<Cells;++Y)
for(int32 X=0;X<Cells;++X)
for(int32 Y=0;Y+1<GridSize;++Y)
for(int32 X=0;X+1<GridSize;++X)
{
const int32 CellX[4]={X,X+1,X+1,X};
const int32 CellY[4]={Y,Y,Y+1,Y+1};
@@ -433,8 +486,8 @@ namespace VoxelForgeCore
{
const int32 GX=CellX[Corner],GY=CellY[Corner];
const auto& Sample=Grid[GridIndex(GX,GY)];
PolygonA[Corner].XSub=static_cast<double>(TileOrigin.X+GX*GridStepVoxels)*Scale;
PolygonA[Corner].YSub=static_cast<double>(TileOrigin.Y+GY*GridStepVoxels)*Scale;
PolygonA[Corner].XSub=static_cast<double>(TileOrigin.X+GridCoordinates[GX])*Scale;
PolygonA[Corner].YSub=static_cast<double>(TileOrigin.Y+GridCoordinates[GY])*Scale;
PolygonA[Corner].GroundSub=Sample.Ground;
PolygonA[Corner].CapSub=Sample.Cap;
PolygonA[Corner].ZSub=Sample.Ground;
@@ -487,16 +540,29 @@ namespace VoxelForgeCore
const auto InterpolateProfile=[](const std::vector<double>& Values,const FGroundProfile& Profile,
double LocalX,double LocalY)
{
const double GridX=std::clamp(LocalX/Profile.GridStep,0.0,static_cast<double>(Profile.Cells));
const double GridY=std::clamp(LocalY/Profile.GridStep,0.0,static_cast<double>(Profile.Cells));
const int32 X=std::min(Profile.Cells-1,static_cast<int32>(std::floor(GridX)));
const int32 Y=std::min(Profile.Cells-1,static_cast<int32>(std::floor(GridY)));
const double TX=GridX-X,TY=GridY-Y;
const std::size_t Width=static_cast<std::size_t>(Profile.Cells+1);
const std::size_t Width=Profile.Coordinates.size();
const auto FindCell=[&](double Local)
{
const double Clamped=std::clamp(Local,static_cast<double>(Profile.Coordinates.front()),
static_cast<double>(Profile.Coordinates.back()));
const auto Upper=std::upper_bound(Profile.Coordinates.begin(),Profile.Coordinates.end(),Clamped);
if(Upper==Profile.Coordinates.begin())return std::pair<std::size_t,double>{0u,0.0};
const std::size_t Index=std::min<std::size_t>(Width-2u,
static_cast<std::size_t>(Upper-Profile.Coordinates.begin()-1));
const double Start=Profile.Coordinates[Index];
const double Span=Profile.Coordinates[Index+1]-Profile.Coordinates[Index];
return std::pair<std::size_t,double>{Index,Span>0.0?(Clamped-Start)/Span:0.0};
};
const auto [X,TX]=FindCell(LocalX);
const auto [Y,TY]=FindCell(LocalY);
const auto At=[&](int32 PX,int32 PY){return Values[static_cast<std::size_t>(PY)*Width+PX];};
if(TX>=TY)
return At(X,Y)*(1.0-TX)+At(X+1,Y)*(TX-TY)+At(X+1,Y+1)*TY;
return At(X,Y)*(1.0-TY)+At(X+1,Y+1)*TX+At(X,Y+1)*(TY-TX);
return At(static_cast<int32>(X),static_cast<int32>(Y))*(1.0-TX)
+At(static_cast<int32>(X+1u),static_cast<int32>(Y))*(TX-TY)
+At(static_cast<int32>(X+1u),static_cast<int32>(Y+1u))*TY;
return At(static_cast<int32>(X),static_cast<int32>(Y))*(1.0-TY)
+At(static_cast<int32>(X+1u),static_cast<int32>(Y+1u))*TX
+At(static_cast<int32>(X),static_cast<int32>(Y+1u))*(TY-TX);
};
for(int32 Triangle=0;Triangle<OldSurfaceTriangleCount;++Triangle)
{
@@ -517,12 +583,13 @@ namespace VoxelForgeCore
if(LocalX<0.0||LocalY<0.0||LocalX>TileExtent||LocalY>TileExtent)continue;
for(const auto& Profile:GroundProfiles)
{
if(LocalX>Profile.Cells*Profile.GridStep||LocalY>Profile.Cells*Profile.GridStep)continue;
if(LocalX>Profile.Coordinates.back()||LocalY>Profile.Coordinates.back())continue;
const double Ground=InterpolateProfile(Profile.Ground,Profile,LocalX,LocalY);
const double Cap=InterpolateProfile(Profile.Cap,Profile,LocalX,LocalY);
if(Cap-Ground<=1.0||Ground<TileMinZ/Scale||Ground>TileMaxZ/Scale
||Ground<Profile.MinimumZ||Ground>Profile.MaximumZ)continue;
const double ReplacementBand=std::max(32.0,2.0*Profile.GridStep);
const double ReplacementBand=std::max(32.0,
2.0*std::min(Profile.GridStep,BoundaryStepVoxels));
if(Z>=Ground-ReplacementBand)
{
RemoveOldGround[static_cast<std::size_t>(Triangle)]=1;
@@ -574,17 +641,28 @@ namespace VoxelForgeCore
}
FNativeCapRefinementStats RefineFarNativeCap(const FStandaloneOwnerWorld& Owner,
const WorldAssemblyManifest& Manifest,const FVoxelTileKey& Key,FVoxelMeshData& Mesh)
const WorldAssemblyManifest& Manifest,const FVoxelTileKey& Key,int32 MeshStepVoxels,
int32 InteriorStepOverride,FVoxelMeshData& Mesh)
{
FNativeCapRefinementStats Stats;
if(Key.Level<4||!Owner.Generator)return Stats;
const int32 GridStepVoxels=Key.Level<5?std::min(128,1<<Key.Level):128;
constexpr int64 Scale=WorldAssemblySubvoxelsPerVoxel;
const int64 GridStepSubvoxels=static_cast<int64>(GridStepVoxels)*Scale;
constexpr int32 BoundaryStepVoxels=128;
const int32 RequestedStep=Key.Level==8
?(InteriorStepOverride>0?InteriorStepOverride:MeshStepVoxels)
:(Key.Level<5?std::min(BoundaryStepVoxels,1<<Key.Level):BoundaryStepVoxels);
const FIntVector TileOrigin=Key.OriginVoxels();
const int32 TileExtent=Key.ExtentVoxels();
if(TileExtent<=0||TileExtent%GridStepVoxels!=0)return Stats;
const int32 Cells=TileExtent/GridStepVoxels;
const int32 EffectiveGridStep=Key.Level==8
?((RequestedStep<BoundaryStepVoxels||RequestedStep%BoundaryStepVoxels!=0
||TileExtent<=0||TileExtent%RequestedStep!=0)
?BoundaryStepVoxels:std::max(BoundaryStepVoxels,RequestedStep))
:RequestedStep;
constexpr int64 Scale=WorldAssemblySubvoxelsPerVoxel;
if(TileExtent<=0||TileExtent%EffectiveGridStep!=0)return Stats;
const std::vector<int32> GridCoordinates=
BuildNativeHorizonGridCoordinates(TileExtent,EffectiveGridStep);
const int32 GridSize=static_cast<int32>(GridCoordinates.size());
if(GridSize<2)return Stats;
const int64 TileMinZ=static_cast<int64>(TileOrigin.Z)*Scale;
const int64 TileMaxZ=static_cast<int64>(TileOrigin.Z+TileExtent)*Scale;
const int32 OldSurfaceVertices=std::clamp(Mesh.NumSurfaceVertices,0,Mesh.Vertices.Num());
@@ -612,14 +690,14 @@ namespace VoxelForgeCore
FColor Color=FColor::White;
int32 VertexIndex=INDEX_NONE;
};
std::vector<FGridSample> Grid(static_cast<std::size_t>(Cells+1)*(Cells+1));
const auto GridIndex=[Cells](int32 X,int32 Y)
{return static_cast<std::size_t>(Y)*(Cells+1)+X;};
for(int32 Y=0;Y<=Cells;++Y)
for(int32 X=0;X<=Cells;++X)
std::vector<FGridSample> Grid(static_cast<std::size_t>(GridSize)*GridSize);
const auto GridIndex=[GridSize](int32 X,int32 Y)
{return static_cast<std::size_t>(Y)*GridSize+X;};
for(int32 Y=0;Y<GridSize;++Y)
for(int32 X=0;X<GridSize;++X)
{
const int64 XSub=(static_cast<int64>(TileOrigin.X)+static_cast<int64>(X)*GridStepVoxels)*Scale;
const int64 YSub=(static_cast<int64>(TileOrigin.Y)+static_cast<int64>(Y)*GridStepVoxels)*Scale;
const int64 XSub=(static_cast<int64>(TileOrigin.X)+GridCoordinates[X])*Scale;
const int64 YSub=(static_cast<int64>(TileOrigin.Y)+GridCoordinates[Y])*Scale;
const auto Column=Block.point(XSub,YSub,0);
auto& Sample=Grid[GridIndex(X,Y)];
Sample.Ground=static_cast<double>(Column.groundHeightSubvoxels);
@@ -633,13 +711,13 @@ namespace VoxelForgeCore
const uint8 B=static_cast<uint8>(std::clamp(std::lround(Blend*255.0f),0l,255l));
Sample.Color=FColor(R,0,B,A);
}
for(int32 Y=0;Y<=Cells;++Y)
for(int32 X=0;X<=Cells;++X)
for(int32 Y=0;Y<GridSize;++Y)
for(int32 X=0;X<GridSize;++X)
{
const int32 Left=std::max(0,X-1),Right=std::min(Cells,X+1);
const int32 Down=std::max(0,Y-1),Up=std::min(Cells,Y+1);
const double DX=static_cast<double>(Right-Left)*GridStepSubvoxels;
const double DY=static_cast<double>(Up-Down)*GridStepSubvoxels;
const int32 Left=std::max(0,X-1),Right=std::min(GridSize-1,X+1);
const int32 Down=std::max(0,Y-1),Up=std::min(GridSize-1,Y+1);
const double DX=static_cast<double>(GridCoordinates[Right]-GridCoordinates[Left])*Scale;
const double DY=static_cast<double>(GridCoordinates[Up]-GridCoordinates[Down])*Scale;
const double SlopeX=DX>0.0?(Grid[GridIndex(Right,Y)].Cap-Grid[GridIndex(Left,Y)].Cap)/DX:0.0;
const double SlopeY=DY>0.0?(Grid[GridIndex(X,Up)].Cap-Grid[GridIndex(X,Down)].Cap)/DY:0.0;
auto& Sample=Grid[GridIndex(X,Y)];
@@ -655,10 +733,10 @@ namespace VoxelForgeCore
if(Sample.Cap<Sample.Ground||Sample.Cap<TileMinZ||Sample.Cap>TileMaxZ)continue;
Sample.VertexIndex=static_cast<int32>(AddedVertices.size());
const std::size_t Index=static_cast<std::size_t>(&Sample-Grid.data());
const int32 GX=static_cast<int32>(Index%static_cast<std::size_t>(Cells+1));
const int32 GY=static_cast<int32>(Index/static_cast<std::size_t>(Cells+1));
const double XSub=static_cast<double>(TileOrigin.X+GX*GridStepVoxels)*Scale;
const double YSub=static_cast<double>(TileOrigin.Y+GY*GridStepVoxels)*Scale;
const int32 GX=static_cast<int32>(Index%static_cast<std::size_t>(GridSize));
const int32 GY=static_cast<int32>(Index/static_cast<std::size_t>(GridSize));
const double XSub=static_cast<double>(TileOrigin.X+GridCoordinates[GX])*Scale;
const double YSub=static_cast<double>(TileOrigin.Y+GridCoordinates[GY])*Scale;
const double VoxelSizeCm=static_cast<double>(Owner.CoreVoxelSizeMicrons)/10000.0;
AddedVertices.emplace_back(XSub/Scale*VoxelSizeCm,YSub/Scale*VoxelSizeCm,
Sample.Cap/Scale*VoxelSizeCm);
@@ -668,8 +746,8 @@ namespace VoxelForgeCore
if(bHasColors)AddedColors.push_back(Sample.Color);
}
for(int32 Y=0;Y<Cells;++Y)
for(int32 X=0;X<Cells;++X)
for(int32 Y=0;Y+1<GridSize;++Y)
for(int32 X=0;X+1<GridSize;++X)
{
const int32 CellX[4]={X,X+1,X+1,X};
const int32 CellY[4]={Y,Y,Y+1,Y+1};
@@ -692,8 +770,8 @@ namespace VoxelForgeCore
{
const int32 GX=CellX[Corner],GY=CellY[Corner];
const auto& Sample=Grid[GridIndex(GX,GY)];
PolygonA[Corner].XSub=static_cast<double>(TileOrigin.X+GX*GridStepVoxels)*Scale;
PolygonA[Corner].YSub=static_cast<double>(TileOrigin.Y+GY*GridStepVoxels)*Scale;
PolygonA[Corner].XSub=static_cast<double>(TileOrigin.X+GridCoordinates[GX])*Scale;
PolygonA[Corner].YSub=static_cast<double>(TileOrigin.Y+GridCoordinates[GY])*Scale;
PolygonA[Corner].ZSub=Sample.Cap;PolygonA[Corner].GroundSub=Sample.Ground;
PolygonA[Corner].Normal=Sample.Normal;PolygonA[Corner].Color=Sample.Color;
}
@@ -1400,7 +1478,8 @@ namespace VoxelForgeCore
&&bHasNativeSurfaceHorizonPlan&&!bHasIslandPlanInTile;
const bool bSkipFarShellRepair=key.lod>=7&&bUseNativeSurfaceHorizon;
FStandaloneShapeWorld::ResetThreadClassificationStats();
VoxelForgeCore::Heightfield2D::resetThreadColumnCacheStats();
VoxelForgeCore::Heightfield2D::resetThreadColumnCacheStats(
options.captureColumnCoordinates&&key.lod==8);
bool bNativeSurfaceTile=false;
bool bNativeSurfaceBandSafe=false;
if(bHasWorldAssembly)
@@ -1427,7 +1506,7 @@ namespace VoxelForgeCore
{
out.diagnostics.meshingSkipped=true;
const auto Counts=FStandaloneShapeWorld::GetThreadClassificationStats();
const auto Columns=VoxelForgeCore::Heightfield2D::threadColumnCacheStats();
const auto& Columns=VoxelForgeCore::Heightfield2D::threadColumnCacheStats();
out.diagnostics.solidBoxes=Counts.SolidBoxes;
out.diagnostics.airBoxes=Counts.AirBoxes;
out.diagnostics.mixedBoxes=Counts.MixedBoxes;
@@ -1571,9 +1650,18 @@ namespace VoxelForgeCore
out.diagnostics.featureListMilliseconds=std::chrono::duration<double,std::milli>(
std::chrono::steady_clock::now()-FeatureListStart).count();
const ScopedWorldAssemblyFeatureQueriesV1 featureScope(featureQueries);
const double ColumnMillisecondsBeforeMeshing=
VoxelForgeCore::Heightfield2D::threadColumnCacheStats().columnComputeMilliseconds;
const auto MarchingCubesStart=std::chrono::steady_clock::now();
FVoxelMeshData mesh=mesher.GenerateMesh(origin,step,cells,nullptr,
MeshBandMinZVoxel,MeshBandMaxZVoxel,&sampleCount,nullptr,nullptr,
(options.hashDensity||options.captureDensitySamples)?&densityValues:nullptr);
const double MarchingCubesWallMilliseconds=std::chrono::duration<double,std::milli>(
std::chrono::steady_clock::now()-MarchingCubesStart).count();
const double ColumnMillisecondsAfterMeshing=
VoxelForgeCore::Heightfield2D::threadColumnCacheStats().columnComputeMilliseconds;
out.diagnostics.marchingCubesExclusiveMilliseconds=std::max(0.0,
MarchingCubesWallMilliseconds-(ColumnMillisecondsAfterMeshing-ColumnMillisecondsBeforeMeshing));
const double MeshingMilliseconds=std::chrono::duration<double,std::milli>(
std::chrono::steady_clock::now()-MeshingStart).count();
out.diagnostics.farHorizonVersion=key.lod>=4?options.farHorizonVersion:0;
@@ -1612,9 +1700,29 @@ namespace VoxelForgeCore
{
const auto HorizonStart=std::chrono::steady_clock::now();
FNativeCapRefinementStats GroundStats;
const double ColumnMillisecondsBeforeGround=
VoxelForgeCore::Heightfield2D::threadColumnCacheStats().columnComputeMilliseconds;
const auto GroundStart=std::chrono::steady_clock::now();
if(key.lod>=4)
GroundStats=RefineFarNativeGround(world,*impl_->worldAssembly,nativeKey,mesh);
const auto CapStats=RefineFarNativeCap(world,*impl_->worldAssembly,nativeKey,mesh);
GroundStats=RefineFarNativeGround(world,*impl_->worldAssembly,nativeKey,step,
options.lod8HorizonInteriorStepVoxels,mesh);
const double GroundWallMilliseconds=std::chrono::duration<double,std::milli>(
std::chrono::steady_clock::now()-GroundStart).count();
const double ColumnMillisecondsAfterGround=
VoxelForgeCore::Heightfield2D::threadColumnCacheStats().columnComputeMilliseconds;
out.diagnostics.groundRefinementExclusiveMilliseconds=std::max(0.0,
GroundWallMilliseconds-(ColumnMillisecondsAfterGround-ColumnMillisecondsBeforeGround));
const double ColumnMillisecondsBeforeCap=
VoxelForgeCore::Heightfield2D::threadColumnCacheStats().columnComputeMilliseconds;
const auto CapStart=std::chrono::steady_clock::now();
const auto CapStats=RefineFarNativeCap(world,*impl_->worldAssembly,nativeKey,step,
options.lod8HorizonInteriorStepVoxels,mesh);
const double CapWallMilliseconds=std::chrono::duration<double,std::milli>(
std::chrono::steady_clock::now()-CapStart).count();
const double ColumnMillisecondsAfterCap=
VoxelForgeCore::Heightfield2D::threadColumnCacheStats().columnComputeMilliseconds;
out.diagnostics.capRefinementExclusiveMilliseconds=std::max(0.0,
CapWallMilliseconds-(ColumnMillisecondsAfterCap-ColumnMillisecondsBeforeCap));
out.diagnostics.farHorizonMilliseconds=std::chrono::duration<double,std::milli>(
std::chrono::steady_clock::now()-HorizonStart).count();
out.diagnostics.capRefined=CapStats.Refined;
@@ -1629,7 +1737,7 @@ namespace VoxelForgeCore
if(bHasWorldAssembly)
{
const auto Counts=FStandaloneShapeWorld::GetThreadClassificationStats();
const auto Columns=VoxelForgeCore::Heightfield2D::threadColumnCacheStats();
const auto& Columns=VoxelForgeCore::Heightfield2D::threadColumnCacheStats();
out.diagnostics.fieldCalls=Columns.pointCalls;
out.diagnostics.floatFieldCalls=Counts.NativeFloatFieldCalls;
out.diagnostics.latticeFieldCalls=Counts.NativeLatticeFieldCalls;
@@ -1637,7 +1745,15 @@ namespace VoxelForgeCore
out.diagnostics.columnCacheHits=Columns.columnHits;
out.diagnostics.columnCacheMisses=Columns.columnMisses;
out.diagnostics.columnComputeMilliseconds=Columns.columnComputeMilliseconds;
out.diagnostics.columnEvaluationExclusiveMilliseconds=Columns.columnComputeMilliseconds;
out.diagnostics.classificationMilliseconds=Counts.Milliseconds;
if(options.captureColumnCoordinates&&key.lod==8)
{
out.diagnostics.columnEvaluationWorkerHash=static_cast<std::uint64_t>(
std::hash<std::thread::id>{}(std::this_thread::get_id()))+1u;
out.diagnostics.columnCoordinateCaptureComplete=Columns.coordinateCaptureComplete;
out.diagnostics.evaluatedColumnCoordinates=Columns.evaluatedCoordinates;
}
if(bNativeSurfaceTile)
{
out.diagnostics.solidBoxes=Counts.SolidBoxes;
@@ -1653,6 +1769,7 @@ namespace VoxelForgeCore
}
out.diagnostics.heightfieldBandLimited=bNativeHeightfieldBandLimited;
out.diagnostics.meshingMilliseconds=MeshingMilliseconds;
const auto MeshAssemblyStart=std::chrono::steady_clock::now();
out.meshPayloadAllocatedBytes=static_cast<std::uint64_t>(mesh.Vertices.GetAllocatedSize())
+static_cast<std::uint64_t>(mesh.Normals.GetAllocatedSize())
+static_cast<std::uint64_t>(mesh.UVs.GetAllocatedSize())
@@ -1696,6 +1813,8 @@ namespace VoxelForgeCore
}
out.hasDensityHash=out.hasMeshHash=out.hasPayloadHash=out.hasTopologyHash=true;
}
out.diagnostics.meshAssemblyMilliseconds=std::chrono::duration<double,std::milli>(
std::chrono::steady_clock::now()-MeshAssemblyStart).count();
return out;
}
+23 -3
View File
@@ -1487,6 +1487,17 @@ namespace world_assembly_detail
HeightfieldColumnStats.columnComputeMilliseconds+=std::chrono::duration<double,std::milli>(
std::chrono::steady_clock::now()-start).count();
++HeightfieldColumnStats.columnMisses;
if(HeightfieldColumnStats.captureCoordinates&&HeightfieldColumnStats.coordinateCaptureComplete)
{
try
{
HeightfieldColumnStats.evaluatedCoordinates.push_back({x,y});
}
catch(...)
{
HeightfieldColumnStats.coordinateCaptureComplete=false;
}
}
entry={&recipe,&plan,recipe.contentHash,plan.contentHash,x,y,column};
return column;
}
@@ -1954,10 +1965,19 @@ HeightfieldPointSample Heightfield2D::point(std::int64_t x, std::int64_t y,
return out;
}
void Heightfield2D::resetThreadColumnCacheStats() noexcept
{ world_assembly_detail::HeightfieldColumnStats={}; }
void Heightfield2D::resetThreadColumnCacheStats(bool captureCoordinates) noexcept
{
auto& Stats=world_assembly_detail::HeightfieldColumnStats;
Stats.pointCalls=0;
Stats.columnHits=0;
Stats.columnMisses=0;
Stats.columnComputeMilliseconds=0.0;
Stats.captureCoordinates=captureCoordinates;
Stats.coordinateCaptureComplete=true;
Stats.evaluatedCoordinates.clear();
}
HeightfieldColumnCacheStats Heightfield2D::threadColumnCacheStats() noexcept
const HeightfieldColumnCacheStats& Heightfield2D::threadColumnCacheStats() noexcept
{ return world_assembly_detail::HeightfieldColumnStats; }
HeightfieldBoxClass Heightfield2D::classifyLatticeBox(std::int64_t minimumX,