refactor(sheets): remove the far sheet ring from the runtime

The owner dropped the far sheet ring. Remove it from the streaming path:
- UVoxelMarchingCubesMesher::GenerateSheetMesh (declaration + definition);
- sheet-tile branches in LoadTile / GenerateTileResult / SyncRemeshTile and
  the bSheetTile / SheetChunkZ / Hole* parameters;
- the SheetHole* XY-hole state and its per-crossing re-queue block;
- VF_MaxMarchingCubesLevel (equal to the clamped MaxClipLevel without sheets);
- the sheet-only band arming and the 'sheet' field of the desired-level
  startup-trace events (no tool reads it).

AVoxelWorld now always passes bFarSheetRing=false to the tile selector, and
the descendant index covers levels up to the MaxClipLevel clamp (8) instead
of the old sheet maximum (12); no tile above 8 can exist any more.

Kept on purpose: UVoxelSettings::bFarSheetRing / FarSheetSpanLevels
(UPROPERTYs, now marked unused), the voxel.TestFarSheetRing switch and the
far_sheet_ring / sheet_tile fields the test harness compares (sheet_tile is
always false), and the selector's sheet parameters in
VoxelClipmapDesiredTiles.h, which two automation tests still exercise.
This commit is contained in:
Claude committed 2026-09-28 13:50:55 +00:00
1 parent 1de44af7cc
commit 4753416d4a
7 files changed
+40 -417

No files matched your search

@@ -9,6 +9,8 @@ namespace VoxelClipmapDesiredTiles
int32 ClipRadius = 3;
int32 MaxClipLevel = 4;
int32 RenderDistanceChunks = 0;
// Far sheet ring selection. AVoxelWorld always passes false (the runtime no longer meshes
// sheets); it stays here because the clipmap automation tests still exercise it.
bool bFarSheetRing = true;
int32 FarSheetSpanLevels = 2;
};
@@ -1126,218 +1126,3 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
return MeshData;
}
//=============================================================================
// F18 — FEUILLE DE CHAMP LOINTAIN (voir le doc de la déclaration dans le .h)
//=============================================================================
// Deux grilles déplacées (sol + cap) au lieu d'un marching cubes 3D. Conventions IDENTIQUES à
// GenerateMesh : positions monde cm, UVs planaires (voxels), masques F6 par GetBiomeMaterialAt,
// jupes double-face extrudées le long de −N par seau, indices sol‖cap + NumCeilingTriangles.
// Winding : le chemin MC émet (0,2,1) des tables Bourke ⇒ une surface orientée +Z s'écrit
// (x,y) → (x,y+1) → (x+1,y) ; le cap (orienté −Z) inverse.
FVoxelMeshData UVoxelMarchingCubesMesher::GenerateSheetMesh(FIntVector OriginVoxels, int32 StepXY,
int32 CellsXY, int32 StrateChunkZ,
int32 HoleMinXVox, int32 HoleMinYVox,
int32 HoleMaxXVox, int32 HoleMaxYVox)
{
FVoxelMeshData MeshData;
if (!Generator) return MeshData;
StepXY = FMath::Max(1, StepXY);
CellsXY = FMath::Clamp(CellsXY, 2, 512);
// TROU XY — cellule [x0, x0+Step]×[y0, y0+Step] entièrement dans le rectangle (Max exclusif)
// ⇒ sautée : la zone est couverte par les coquilles MC (voir VoxelWorld.h SheetHole*).
auto CellInHole = [&](int32 cx, int32 cy) -> bool
{
const int32 X0 = OriginVoxels.X + cx * StepXY;
const int32 Y0 = OriginVoxels.Y + cy * StepXY;
return X0 >= HoleMinXVox && X0 + StepXY <= HoleMaxXVox
&& Y0 >= HoleMinYVox && Y0 + StepXY <= HoleMaxYVox;
};
const int32 GridDim = CellsXY + 1; // points de grille par axe
const int32 MDim = GridDim + 2; // + anneau de marge ±1 (normales continues entre feuilles)
// Early-out : la validité SurfaceWorld ne dépend que de ChunkZ (les strates sont des couches
// horizontales) — une sonde suffit. La sentinelle par colonne plus bas reste par prudence.
{
float T0 = 0.0f, C0 = 0.0f;
if (!Generator->GetSurfaceHeightAt((float)OriginVoxels.X, (float)OriginVoxels.Y,
StrateChunkZ, T0, C0))
{
return MeshData; // pas une strate SurfaceWorld → feuille vide
}
}
// Colonnes (TerrainZ, CeilSurf) en Z-voxel monde absolu, marge incluse. X==FLT_MAX ⇒ invalide.
static thread_local TArray<FVector2f> SheetCols;
SheetCols.SetNumUninitialized(MDim * MDim);
for (int32 gy = -1; gy <= GridDim; ++gy)
{
if (ShouldAbortWork()) return FVoxelMeshData();
for (int32 gx = -1; gx <= GridDim; ++gx)
{
float Tz = 0.0f, Cz = 0.0f;
const bool bOk = Generator->GetSurfaceHeightAt(
(float)(OriginVoxels.X + gx * StepXY), (float)(OriginVoxels.Y + gy * StepXY),
StrateChunkZ, Tz, Cz);
SheetCols[(gy + 1) * MDim + (gx + 1)] = bOk ? FVector2f(Tz, Cz)
: FVector2f(FLT_MAX, -FLT_MAX);
}
}
auto Col = [&](int32 gx, int32 gy) -> const FVector2f&
{
return SheetCols[(gy + 1) * MDim + (gx + 1)];
};
// Index de vertex par point de grille (−1 = pas créé), sol et cap séparés — le partage de
// vertex est structurel (grille régulière), pas besoin de map de déduplication.
static thread_local TArray<int32> GroundIdx;
static thread_local TArray<int32> CapIdx;
GroundIdx.Init(-1, GridDim * GridDim);
CapIdx.Init(-1, GridDim * GridDim);
auto MakeVert = [&](int32 gx, int32 gy, bool bCap) -> int32
{
int32& Slot = (bCap ? CapIdx : GroundIdx)[gy * GridDim + gx];
if (Slot >= 0) return Slot;
const FVector2f C = Col(gx, gy);
const float H = bCap ? C.Y : C.X;
// Pente par différences centrales sur la grille de hauteurs (voxels/voxels — l'échelle cm
// se simplifie). Voisin invalide ⇒ composante plate (ne devrait pas arriver : cf. early-out).
auto HAt = [&](int32 x, int32 y) -> float
{
const FVector2f& N = Col(x, y);
return (N.X == FLT_MAX) ? H : (bCap ? N.Y : N.X);
};
const float Sx = (HAt(gx + 1, gy) - HAt(gx - 1, gy)) / (2.0f * (float)StepXY);
const float Sy = (HAt(gx, gy + 1) - HAt(gx, gy - 1)) / (2.0f * (float)StepXY);
// Sol : air au-dessus ⇒ normale vers le haut. Cap : l'air (l'intérieur de la strate) est
// EN-DESSOUS ⇒ normale vers le bas (−∇(z − C) = (Cx, Cy, −1)).
FVector Normal = bCap ? FVector(Sx, Sy, -1.0f) : FVector(-Sx, -Sy, 1.0f);
if (!Normal.Normalize())
{
Normal = FVector(0.0f, 0.0f, bCap ? -1.0f : 1.0f);
}
const float Xv = (float)(OriginVoxels.X + gx * StepXY);
const float Yv = (float)(OriginVoxels.Y + gy * StepXY);
const FVector WorldPos(Xv * VOXEL_SIZE, Yv * VOXEL_SIZE, H * VOXEL_SIZE);
Slot = MeshData.Vertices.Num();
MeshData.Vertices.Add(WorldPos);
MeshData.Normals.Add(Normal);
MeshData.UVs.Add(FVector2D(Xv, Yv)); // == WorldPos.XY / VOXEL_SIZE (convention MC)
// Mêmes masques F6 que GenerateMesh (palette biome dominant/voisin, pente, fondu).
int32 PalD = 0, PalN = 0; float BlendW = 0.0f;
Generator->GetBiomeMaterialAt(Xv, Yv, H, PalD, PalN, BlendW);
const uint8 Rr = (uint8)FMath::Clamp(PalD, 0, 255);
const uint8 Aa = (uint8)FMath::Clamp(PalN, 0, 255);
const uint8 Gg = (uint8)FMath::Clamp(FMath::RoundToInt((1.0f - FMath::Abs((float)Normal.Z)) * 255.0f), 0, 255);
const uint8 Bb = (uint8)FMath::Clamp(FMath::RoundToInt(BlendW * 255.0f), 0, 255);
MeshData.Colors.Add(FColor(Rr, Gg, Bb, Aa));
return Slot;
};
static thread_local TArray<int32> SheetGroundTris;
static thread_local TArray<int32> SheetCapTris;
SheetGroundTris.Reset();
SheetCapTris.Reset();
for (int32 cy = 0; cy < CellsXY; ++cy)
{
if (ShouldAbortWork()) return FVoxelMeshData();
for (int32 cx = 0; cx < CellsXY; ++cx)
{
if (CellInHole(cx, cy)) continue; // couverte par les coquilles MC proches
if (Col(cx, cy).X == FLT_MAX || Col(cx + 1, cy).X == FLT_MAX ||
Col(cx, cy + 1).X == FLT_MAX || Col(cx + 1, cy + 1).X == FLT_MAX)
{
continue;
}
// SOL (polygroup 0) — orienté +Z.
const int32 g00 = MakeVert(cx, cy, false);
const int32 g10 = MakeVert(cx + 1, cy, false);
const int32 g01 = MakeVert(cx, cy + 1, false);
const int32 g11 = MakeVert(cx + 1, cy + 1, false);
SheetGroundTris.Add(g00); SheetGroundTris.Add(g01); SheetGroundTris.Add(g10);
SheetGroundTris.Add(g10); SheetGroundTris.Add(g01); SheetGroundTris.Add(g11);
// CAP (polygroup 1) — orienté −Z ⇒ winding inversé.
const int32 c00 = MakeVert(cx, cy, true);
const int32 c10 = MakeVert(cx + 1, cy, true);
const int32 c01 = MakeVert(cx, cy + 1, true);
const int32 c11 = MakeVert(cx + 1, cy + 1, true);
SheetCapTris.Add(c00); SheetCapTris.Add(c10); SheetCapTris.Add(c01);
SheetCapTris.Add(c10); SheetCapTris.Add(c11); SheetCapTris.Add(c01);
}
}
// JUPES périmètre — mêmes règles que le chemin MC : extrusion le long de −N (vers le solide :
// bas pour le sol, HAUT pour le cap), double face, émises PAR SEAU (héritent le polygroup).
// Entre feuilles voisines les coins coïncident (mêmes échantillons monde) → pas de fissure ;
// la jupe couvre surtout la couture feuille ↔ anneau MC et les décalages de niveau.
if (bGenerateSkirts && (SheetGroundTris.Num() + SheetCapTris.Num()) > 0)
{
const float SkirtDepth = FMath::Max(1.0f, SkirtCells) * (float)StepXY * VOXEL_SIZE;
auto EmitEdgeSkirt = [&](int32 ax, int32 ay, int32 bx, int32 by, bool bCap, TArray<int32>& Tris)
{
if (Col(ax, ay).X == FLT_MAX || Col(bx, by).X == FLT_MAX) return;
const int32 iA = MakeVert(ax, ay, bCap);
const int32 iB = MakeVert(bx, by, bCap);
// COPIES par valeur — les Add ci-dessous réallouent Vertices/Normals/Colors.
const FVector PA = MeshData.Vertices[iA], PB = MeshData.Vertices[iB];
const FVector NA = MeshData.Normals[iA], NB = MeshData.Normals[iB];
const FColor CA = MeshData.Colors[iA], CB = MeshData.Colors[iB];
const int32 iA2 = MeshData.Vertices.Num();
MeshData.Vertices.Add(PA - NA * SkirtDepth);
MeshData.Normals.Add(NA);
MeshData.UVs.Add(FVector2D(PA.X / VOXEL_SIZE, PA.Y / VOXEL_SIZE));
MeshData.Colors.Add(CA);
const int32 iB2 = MeshData.Vertices.Num();
MeshData.Vertices.Add(PB - NB * SkirtDepth);
MeshData.Normals.Add(NB);
MeshData.UVs.Add(FVector2D(PB.X / VOXEL_SIZE, PB.Y / VOXEL_SIZE));
MeshData.Colors.Add(CB);
Tris.Add(iA); Tris.Add(iB); Tris.Add(iB2);
Tris.Add(iA); Tris.Add(iB2); Tris.Add(iA2);
Tris.Add(iA); Tris.Add(iB2); Tris.Add(iB);
Tris.Add(iA); Tris.Add(iA2); Tris.Add(iB2);
};
// Une jupe n'est émise que si la cellule de bord adjacente a réellement été maillée
// (pas dans le trou XY) — sinon mur flottant sans surface.
for (int32 c = 0; c < CellsXY; ++c)
{
if (ShouldAbortWork()) return FVoxelMeshData();
for (int32 Pass = 0; Pass < 2; ++Pass)
{
const bool bCap = (Pass == 1);
TArray<int32>& Tris = bCap ? SheetCapTris : SheetGroundTris;
if (!CellInHole(c, 0))
EmitEdgeSkirt(c, 0, c + 1, 0, bCap, Tris); // bord Y-min
if (!CellInHole(c, CellsXY - 1))
EmitEdgeSkirt(c, CellsXY, c + 1, CellsXY, bCap, Tris); // bord Y-max
if (!CellInHole(0, c))
EmitEdgeSkirt(0, c, 0, c + 1, bCap, Tris); // bord X-min
if (!CellInHole(CellsXY - 1, c))
EmitEdgeSkirt(CellsXY, c, CellsXY, c + 1, bCap, Tris); // bord X-max
}
}
}
// Concatène sol PUIS cap : un run d'indices contigu par polygroup (contrat F17/BuildTileStreamSet).
MeshData.Triangles.Reserve(SheetGroundTris.Num() + SheetCapTris.Num());
MeshData.Triangles.Append(SheetGroundTris);
MeshData.Triangles.Append(SheetCapTris);
MeshData.NumCeilingTriangles = SheetCapTris.Num() / 3;
return MeshData;
}
+30 -143
View File
@@ -59,7 +59,6 @@
#include "Serialization/JsonWriter.h"
#include "ProfilingDebugging/CpuProfilerTrace.h" // Unreal Insights scopes (Perf 0)
static int32 VF_MaxMarchingCubesLevel(const UVoxelSettings* Settings);
static bool VF_TileOverlapsVoxelBounds(const FVoxelTileKey& Tile,
const FVector& BoundsMin, const FVector& BoundsMax);
static bool VF_TileFootprintsOverlap(const FVoxelTileKey& A, const FVoxelTileKey& B);
@@ -2232,7 +2231,7 @@ void AVoxelWorld::ConfigureHeadlessStreamingTest()
void AVoxelWorld::RecordTileHash(const FVoxelTileKey& Tile, const FIntVector& OriginVoxels,
int32 Step, int32 Cells, int32 BandChunkLo, int32 BandChunkHi,
bool bSheetTile, const FVoxelMeshData& MeshData)
const FVoxelMeshData& MeshData)
{
if (!bTileHashDumpEnabled)
{
@@ -2246,7 +2245,6 @@ void AVoxelWorld::RecordTileHash(const FVoxelTileKey& Tile, const FIntVector& Or
Record.Cells = Cells;
Record.BandChunkLo = BandChunkLo;
Record.BandChunkHi = BandChunkHi;
Record.bSheetTile = bSheetTile;
Record.bEmpty = MeshData.IsEmpty();
Record.NumTriangles = MeshData.Triangles.Num() / 3;
Record.GeometryHash = VoxelForgeGeometryHash::Compute(MeshData);
@@ -2315,7 +2313,8 @@ void AVoxelWorld::WriteTileHashDump()
Writer->WriteValue(TEXT("cells"), Record.Cells);
Writer->WriteValue(TEXT("band_chunk_lo"), Record.BandChunkLo);
Writer->WriteValue(TEXT("band_chunk_hi"), Record.BandChunkHi);
Writer->WriteValue(TEXT("sheet_tile"), Record.bSheetTile);
// The parity harness still compares this field; the runtime has no sheet tiles.
Writer->WriteValue(TEXT("sheet_tile"), false);
Writer->WriteValue(TEXT("empty"), Record.bEmpty);
Writer->WriteValue(TEXT("triangle_count"), Record.NumTriangles);
Writer->WriteValue(TEXT("geometry_hash"), Record.GeometryHash);
@@ -4577,12 +4576,11 @@ void AVoxelWorld::Tick(float DeltaTime)
const double SpanZ = static_cast<double>(Max.Z - Min.Z + 1) * TileExtentMetres;
VoxelForgeStartupTrace::RecordEvent(TEXT("steady_desired_level"), FString::Printf(
TEXT("\"level\":%d,\"tiles\":%d,\"min_coord\":[%d,%d,%d],\"max_coord\":[%d,%d,%d],"
"\"tile_extent_m\":%s,\"coverage_extent_m\":[%s,%s,%s],\"sheet\":%d"),
"\"tile_extent_m\":%s,\"coverage_extent_m\":[%s,%s,%s]"),
Level, SteadyLevelCounts.FindChecked(Level),
Min.X, Min.Y, Min.Z, Max.X, Max.Y, Max.Z,
*FString::SanitizeFloat(TileExtentMetres),
*FString::SanitizeFloat(SpanX), *FString::SanitizeFloat(SpanY), *FString::SanitizeFloat(SpanZ),
Settings && Level > VF_MaxMarchingCubesLevel(Settings) ? 1 : 0));
*FString::SanitizeFloat(SpanX), *FString::SanitizeFloat(SpanY), *FString::SanitizeFloat(SpanZ)));
VoxelForgeStartupTrace::RecordEvent(TEXT("steady_loaded_level"), FString::Printf(
TEXT("\"level\":%d,\"tiles\":%d,\"vertices\":%llu,\"triangles\":%llu,\"geometry_bytes\":%llu"),
Level, LoadedLevelCounts.FindRef(Level),
@@ -6217,8 +6215,7 @@ void AVoxelWorld::SyncRemeshTile(const FVoxelTileKey& Tile)
Result.GenerationStartCycles = GenerateStartCycles;
GenerateTileResult(Tile, OriginVoxels, Step, Cells, GenerationEpoch, /*bWantCapture*/ false,
BandVoxLo, BandVoxHi, BandChunkLo, BandChunkHi,
/*bSheetTile*/ false, /*SheetChunkZ*/ 0,
/*Hole*/ 0, 0, 0, 0, Result, nullptr); // hole unused (not a sheet tile)
Result, nullptr);
Result.GenerationEndCycles = GenerateStartCycles != 0
? FPlatformTime::Cycles64() : 0;
@@ -6471,30 +6468,18 @@ static FORCEINLINE FIntVector VF_FloorDiv(const FIntVector& V, int32 D)
return FIntVector(VF_FloorDiv(V.X, D), VF_FloorDiv(V.Y, D), VF_FloorDiv(V.Z, D));
}
// F18 — the outer shell is either the MC render-distance level or the larger sheet ring. When
// the sheet span skips levels, VF_MaxMarchingCubesLevel fills them with MC bridge shells.
// Shared by desired selection and range culling so both use the same outer horizon.
// The outer shell is the MaxClipLevel ring stretched to RenderDistanceChunks. Shared by desired
// selection and range culling so both use the same outer horizon. The selector's far sheet ring
// is not part of the runtime, so it is always switched off here.
static FORCEINLINE void VF_OuterShell(const UVoxelSettings* Settings, int32 R, int32 MaxLevel,
int32& OutLevel, int32& OutRadius)
{
VoxelClipmapDesiredTiles::FParameters Parameters;
Parameters.RenderDistanceChunks = Settings ? Settings->RenderDistanceChunks : 0;
Parameters.bFarSheetRing = Settings && Settings->bFarSheetRing;
Parameters.FarSheetSpanLevels = Settings ? Settings->FarSheetSpanLevels : 2;
Parameters.bFarSheetRing = false;
VoxelClipmapDesiredTiles::OuterShell(Parameters, R, MaxLevel, OutLevel, OutRadius);
}
// When an expanded sheet ring skips LOD levels, keep an MC bridge at every intervening level.
static int32 VF_MaxMarchingCubesLevel(const UVoxelSettings* Settings)
{
const int32 Radius = Settings ? FMath::Max(1, Settings->ClipRadius) : 3;
const int32 MaxLevel = Settings ? FMath::Clamp(Settings->MaxClipLevel, 0, 8) : 4;
int32 OuterLevel = MaxLevel, OuterRadius = Radius;
VF_OuterShell(Settings, Radius, MaxLevel, OuterLevel, OuterRadius);
(void)OuterRadius;
return OuterLevel > MaxLevel ? OuterLevel - 1 : MaxLevel;
}
void AVoxelWorld::BuildDesiredTiles(const FIntVector& Center, const FVector& PlayerPosition,
APawn* PlayerPawn, const FVector& PlayerHeading,
TArray<FVoxelTileKey>& OutLeavers)
@@ -6512,8 +6497,7 @@ void AVoxelWorld::BuildDesiredTiles(const FIntVector& Center, const FVector& Pla
const int32 R = Settings ? FMath::Max(1, Settings->ClipRadius) : 3;
const int32 MaxLevel = Settings ? FMath::Clamp(Settings->MaxClipLevel, 0, 8) : 4;
// Render-distance tiles expand horizontally. The sheet ring retains its configured span;
// intermediate levels are emitted as marching-cubes bridge shells below it.
// Render-distance tiles expand horizontally.
// IsTileInClipRange shares the outer-shell calculation so culling sees this same horizon.
int32 StrateTopZ = 0, StrateBottomZ = 0;
bool bHasStrate = false;
@@ -6537,8 +6521,7 @@ void AVoxelWorld::BuildDesiredTiles(const FIntVector& Center, const FVector& Pla
TileParameters.ClipRadius = R;
TileParameters.MaxClipLevel = MaxLevel;
TileParameters.RenderDistanceChunks = Settings ? Settings->RenderDistanceChunks : 0;
TileParameters.bFarSheetRing = Settings && Settings->bFarSheetRing;
TileParameters.FarSheetSpanLevels = Settings ? Settings->FarSheetSpanLevels : 2;
TileParameters.bFarSheetRing = false;
VoxelClipmapDesiredTiles::Build(Center, ZLo, ZHi, TileParameters, DesiredSorted);
for (const FVoxelTileKey& Key : DesiredSorted)
{
@@ -6681,10 +6664,10 @@ void AVoxelWorld::BuildDesiredTiles(const FIntVector& Center, const FVector& Pla
DesiredTransitionMaxLevel = FMath::Max(DesiredTransitionMaxLevel, Pair.Key.Level);
}
// A worker from the previous desired epoch can finish late with a component at a level no
// longer present in TileComponents when this index is built. Index through the supported
// maximum sheet level (MaxClipLevel 8 + FarSheetSpanLevels 4) so that such a retired tile still
// longer present in TileComponents when this index is built. Index through the highest
// possible tile level (MaxClipLevel is clamped to 8) so that such a retired tile still
// finds all of its desired descendants when the result is reconciled.
constexpr int32 MaxSupportedTileLevel = 12;
constexpr int32 MaxSupportedTileLevel = 8;
const int32 DescendantIndexMaxLevel = FMath::Max(
DesiredTransitionMaxLevel, MaxSupportedTileLevel);
for (const FVoxelTileKey& Key : DesiredSorted)
@@ -6727,7 +6710,7 @@ void AVoxelWorld::CancelObsoleteTileWork()
}
// Fold every registered anchor's small level-0 box into the current desired set (§9.3). Runs inside
// BuildDesiredTiles after the player clipmap + sheet ring, keyed on the same DesiredStamp so the leaver
// BuildDesiredTiles after the player clipmap, keyed on the same DesiredStamp so the leaver
// sweep + delta cull handle anchor tiles leaving. Level-0 only (collision lives on level-0 tiles); empty
// tiles in the box are ~free (the trivial-tile reject skips gen). Dedup vs the player clipmap by stamp.
// §9.4: a tile the player clipmap did NOT stamp (bNew) that only a CollisionOnly anchor wants goes into
@@ -6839,7 +6822,7 @@ void AVoxelWorld::UnregisterStreamingAnchor(AActor* Actor)
bool AVoxelWorld::IsTileInClipRange(const FVoxelTileKey& Tile, const FIntVector& Center) const
{
// In range = the tile's centre falls within the OUTERMOST shell (VF_OuterShell — the
// render-distance/sheet-ring level+radius, same as BuildDesiredTiles). A loaded-but-not-
// render-distance level+radius, same as BuildDesiredTiles). A loaded-but-not-
// desired tile in range is mid-LOD-transition (wait for its replacement); one out of range
// has left the view entirely (cull immediately).
const int32 R = Settings ? FMath::Max(1, Settings->ClipRadius) : 3;
@@ -7021,13 +7004,13 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
const double SpanZ = static_cast<double>(Summary.Max.Z - Summary.Min.Z + 1) * TileExtentMetres;
VoxelForgeStartupTrace::RecordEvent(TEXT("desired_level"), FString::Printf(
TEXT("\"level\":%d,\"tiles\":%d,\"min_coord\":[%d,%d,%d],\"max_coord\":[%d,%d,%d],"
"\"tile_extent_m\":%s,\"coverage_extent_m\":[%s,%s,%s],\"sheet\":%d"),
"\"tile_extent_m\":%s,\"coverage_extent_m\":[%s,%s,%s]"),
Summary.Level, Summary.Count,
Summary.Min.X, Summary.Min.Y, Summary.Min.Z,
Summary.Max.X, Summary.Max.Y, Summary.Max.Z,
*FString::SanitizeFloat(TileExtentMetres),
*FString::SanitizeFloat(SpanX), *FString::SanitizeFloat(SpanY), *FString::SanitizeFloat(SpanZ),
Settings && Summary.Level > VF_MaxMarchingCubesLevel(Settings) ? 1 : 0));
*FString::SanitizeFloat(SpanX), *FString::SanitizeFloat(SpanY),
*FString::SanitizeFloat(SpanZ)));
}
bStartupTraceDesiredRecorded = true;
}
@@ -7044,13 +7027,8 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
// either way; everything else re-gens in place via BandRemeshQueue (no visual pop).
{
const int32 CutMin = Settings ? Settings->GetEffectiveStrateContentCutMinLevel() : 9;
// F18 — l'anneau feuille dépend aussi de la bande (sa strate de référence) : on l'arme
// dès que les feuilles sont actives, même si la coupe de contenu MC est désactivée.
const bool bSheetsWantBand = Settings && Settings->bFarSheetRing
&& Settings->RenderDistanceChunks > 0;
const int32 TopMC = VF_MaxMarchingCubesLevel(Settings);
int32 NewLo = MIN_int32, NewHi = MAX_int32;
if ((CutMin <= 8 || bSheetsWantBand) && StrateManager)
if (CutMin <= 8 && StrateManager)
{
int32 StrTopZ = 0, StrBotZ = 0;
if (StrateManager->GetStrateChunkZBounds(CenterChunk.Z, StrTopZ, StrBotZ))
@@ -7069,8 +7047,7 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
NewLo, NewHi, MeshBandChunkLo, MeshBandChunkHi, CutMin, CenterChunk.Z);
for (const FVoxelTileKey& T : LoadedTiles)
{
// Sheets (above the last MC bridge level) always depend on the strate band.
if (T.Level < CutMin && T.Level <= TopMC) continue;
if (T.Level < CutMin) continue;
const int32 CLo = T.Coord.Z << T.Level;
const int32 CHi = ((T.Coord.Z + 1) << T.Level) - 1;
const bool bInOld = CLo >= MeshBandChunkLo && CHi <= MeshBandChunkHi;
@@ -7087,44 +7064,6 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
}
}
// The sheet XY hole follows the outermost MC bridge level and shrinks by one tile, leaving
// an overlap at the mesh transition. Requeue sheets overlapping either the old or new hole
// when the player crosses a bridge-level tile.
{
int32 NewMinX = MAX_int32, NewMinY = MAX_int32;
int32 NewMaxX = MIN_int32, NewMaxY = MIN_int32;
const int32 TopMCLvl = VF_MaxMarchingCubesLevel(Settings);
if (Settings && Settings->bFarSheetRing && Settings->RenderDistanceChunks > 0)
{
const int32 RClip = FMath::Max(1, Settings->ClipRadius);
const int32 Shrink = FMath::Max(0, RClip - 1);
const int32 ExtM = CHUNK_SIZE << TopMCLvl; // tuile MaxLevel en voxels
const FIntVector CM = VF_FloorDiv(CenterChunk, 1 << TopMCLvl); // tuile MaxLevel du joueur
NewMinX = (CM.X - Shrink) * ExtM;
NewMinY = (CM.Y - Shrink) * ExtM;
NewMaxX = (CM.X + Shrink + 1) * ExtM; // EXCLUSIF
NewMaxY = (CM.Y + Shrink + 1) * ExtM;
}
if (NewMinX != SheetHoleMinXVox || NewMinY != SheetHoleMinYVox
|| NewMaxX != SheetHoleMaxXVox || NewMaxY != SheetHoleMaxYVox)
{
for (const FVoxelTileKey& T : LoadedTiles)
{
if (T.Level <= TopMCLvl) continue; // seules les feuilles portent le trou
const int32 Ext = CHUNK_SIZE << T.Level;
const int32 TMinX = T.Coord.X * Ext, TMinY = T.Coord.Y * Ext;
const bool bOldOv = TMinX < SheetHoleMaxXVox && TMinX + Ext > SheetHoleMinXVox
&& TMinY < SheetHoleMaxYVox && TMinY + Ext > SheetHoleMinYVox;
const bool bNewOv = TMinX < NewMaxX && TMinX + Ext > NewMinX
&& TMinY < NewMaxY && TMinY + Ext > NewMinY;
if ((bOldOv || bNewOv) && IsDesired(T)) BandRemeshQueue.Add(T);
}
SheetHoleMinXVox = NewMinX; SheetHoleMinYVox = NewMinY;
SheetHoleMaxXVox = NewMaxX; SheetHoleMaxYVox = NewMaxY;
bAllChunksLoaded = false;
}
}
// Cull rules — STRICT LOAD-BEFORE-UNLOAD so crossing a shell boundary NEVER leaves a hole:
// - out of clip range → left the view entirely, no replacement coming → cull now.
// - in range (mid-LOD-transition) → cull ONLY once EVERY desired tile that overlaps its
@@ -7391,20 +7330,9 @@ void AVoxelWorld::LoadTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority Wor
const int32 FullRes = Settings ? FMath::Max(1, Settings->FullResClipLevels) : 2;
const int32 Extent = CHUNK_SIZE << Tile.Level;
// F18 — sheet-ring keys follow the last MC bridge level and mesh as two heightfields
// (GenerateSheetMesh). Their sampling step matches that MC level; cells grow with sheet extent
// up to 128 per axis, after which the step increases.
const int32 TopMC = VF_MaxMarchingCubesLevel(Settings);
const bool bSheetTile = Tile.Level > TopMC;
int32 Cells = (Tile.Level < FullRes)
const int32 Cells = (Tile.Level < FullRes)
? CHUNK_SIZE
: (Settings ? FMath::Clamp(Settings->CoarseTileCells, 4, CHUNK_SIZE) : 16);
if (bSheetTile)
{
const int32 StepMC = FMath::Max(1, (CHUNK_SIZE << TopMC) / Cells);
Cells = FMath::Clamp(Extent / StepMC, 4, 128);
}
const int32 Step = FMath::Max(1, Extent / Cells);
const uint32 TaskEpoch = GenerationEpoch;
const uint32 TaskDesiredEpoch = DesiredEpoch;
@@ -7425,40 +7353,8 @@ void AVoxelWorld::LoadTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority Wor
// below the configured minimum and all LODs in the gap (no band) mesh full.
int32 BandVoxLo = INT32_MIN, BandVoxHi = INT32_MAX;
int32 BandChunkLo = MIN_int32, BandChunkHi = MAX_int32;
int32 SheetChunkZ = 0;
if (bSheetTile)
{
// F18 — la feuille a besoin de la STRATE de référence (les deux heightfields sont ceux de
// la strate du joueur) : pas de bande armée (gap inter-strates, ou bounds introuvables)
// ⇒ rien à mailler, tuile vide (re-queue automatique via BandRemeshQueue en atterrissant).
if (MeshBandChunkLo == MIN_int32)
{
FChunkResult Empty;
Empty.Tile = Tile;
Empty.Epoch = TaskEpoch;
Empty.DesiredEpoch = TaskDesiredEpoch;
Empty.ModificationEpoch = TaskModificationEpoch;
Empty.RequestStartCycles = RequestStartCycles;
Empty.WorkPriority = WorkPriority;
if (WorkPriority == EVoxelTileWorkPriority::CollisionCritical)
CriticalProcessQueue.Enqueue(MoveTemp(Empty));
else if (WorkPriority == EVoxelTileWorkPriority::Edited)
EditedProcessQueue.Enqueue(MoveTemp(Empty));
else if (WorkPriority == EVoxelTileWorkPriority::LOD0)
NearProcessQueue.Enqueue(MoveTemp(Empty));
else
ProcessQueue.Enqueue(MoveTemp(Empty));
return;
}
BandChunkLo = MeshBandChunkLo; // pour la résolution matériaux sol/cap dans ApplyMeshToTile
BandChunkHi = MeshBandChunkHi;
SheetChunkZ = MeshBandChunkLo + (MeshBandChunkHi - MeshBandChunkLo) / 2; // chunk au cœur de la strate
}
// F18 — trou XY courant (zone couverte par les coquilles MC, découpée des feuilles).
const int32 HoleMinX = SheetHoleMinXVox, HoleMinY = SheetHoleMinYVox;
const int32 HoleMaxX = SheetHoleMaxXVox, HoleMaxY = SheetHoleMaxYVox;
const int32 CutMin = Settings ? Settings->GetEffectiveStrateContentCutMinLevel() : 9;
if (!bSheetTile && Tile.Level >= CutMin && MeshBandChunkLo != MIN_int32)
if (Tile.Level >= CutMin && MeshBandChunkLo != MIN_int32)
{
BandChunkLo = MeshBandChunkLo;
BandChunkHi = MeshBandChunkHi;
@@ -7512,7 +7408,6 @@ void AVoxelWorld::LoadTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority Wor
: UE::Tasks::ETaskPriority::BackgroundNormal;
UE::Tasks::Launch(TEXT("ChunkGen"), [this, Tile, OriginVoxels, Step, Cells, TaskEpoch, bWantCapture,
BandVoxLo, BandVoxHi, BandChunkLo, BandChunkHi,
bSheetTile, SheetChunkZ, HoleMinX, HoleMinY, HoleMaxX, HoleMaxY,
RequestStartCycles, TaskDesiredEpoch, TaskModificationEpoch, Cancellation,
WorkPriority]()
{
@@ -7565,7 +7460,6 @@ void AVoxelWorld::LoadTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority Wor
Result.GenerationStartCycles = GenerationStartCycles;
GenerateTileResult(Tile, OriginVoxels, Step, Cells, TaskEpoch, bWantCapture,
BandVoxLo, BandVoxHi, BandChunkLo, BandChunkHi,
bSheetTile, SheetChunkZ, HoleMinX, HoleMinY, HoleMaxX, HoleMaxY,
Result, &Cancellation->bObsolete);
Result.GenerationEndCycles = GenerationStartCycles != 0
? FPlatformTime::Cycles64() : 0;
@@ -7611,8 +7505,6 @@ void AVoxelWorld::LoadTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority Wor
void AVoxelWorld::GenerateTileResult(const FVoxelTileKey& Tile, const FIntVector& OriginVoxels,
int32 Step, int32 Cells, uint32 Epoch, bool bWantCapture,
int32 BandVoxLo, int32 BandVoxHi, int32 BandChunkLo, int32 BandChunkHi,
bool bSheetTile, int32 SheetChunkZ,
int32 HoleMinX, int32 HoleMinY, int32 HoleMaxX, int32 HoleMaxY,
FChunkResult& Result,
const std::atomic<bool>* ObsoleteFlag)
{
@@ -7666,7 +7558,7 @@ void AVoxelWorld::GenerateTileResult(const FVoxelTileKey& Tile, const FIntVector
/ static_cast<double>(WormEligibleSamples)
: 0.0;
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeTileProfile] tile=(%d,%d,%d) level=%d step=%d cells=%d sheet=%d aborted=%d empty=%d triangles=%d ceiling_triangles=%d "
TEXT("[VoxelForgeTileProfile] tile=(%d,%d,%d) level=%d step=%d cells=%d aborted=%d empty=%d triangles=%d ceiling_triangles=%d "
"band=(%d,%d) "
"verdict=%d proof=%d air_proof=%d classify=%.6f mesh=%.6f streams=%.6f seconds=%.6f "
"cache_build=%.6f evaluation=%.6f "
@@ -7686,7 +7578,7 @@ void AVoxelWorld::GenerateTileResult(const FVoxelTileKey& Tile, const FIntVector
"worm_eligible=%llu worm_block_proofs=%llu worm_block_skipped=%llu "
"worm_skip_rate=%.6f"),
Tile.Coord.X, Tile.Coord.Y, Tile.Coord.Z, Tile.Level, Step, Cells,
bSheetTile ? 1 : 0, Result.bAborted ? 1 : 0,
Result.bAborted ? 1 : 0,
Result.bEmpty ? 1 : 0, Result.NumTriangles, Result.NumCeilingTriangles,
BandChunkLo, BandChunkHi,
ClassifyVerdict, Result.bSealedSolidProof ? 1 : 0,
@@ -7825,7 +7717,7 @@ void AVoxelWorld::GenerateTileResult(const FVoxelTileKey& Tile, const FIntVector
// (Gate IsoLevel == 0 : les verdicts du classifieur supposent l'iso MC à zéro exactement.)
bool bTrivialEmpty = false;
if (GVoxelForgeSealedSolidProof != 0
&& !bSheetTile && !bWantCapture && Generator && Mesher
&& !bWantCapture && Generator && Mesher
&& Mesher->IsoLevel == 0.0f)
{
TotalSealedSolidProofCandidates.fetch_add(1, std::memory_order_relaxed);
@@ -7867,7 +7759,7 @@ void AVoxelWorld::GenerateTileResult(const FVoxelTileKey& Tile, const FIntVector
}
const int32 OuterClassifierMode = FMath::Clamp(GVoxelForgeOuterClassifierMode, 0, 1);
if (!bTrivialEmpty && OuterClassifierMode != 0
&& !bSheetTile && !bWantCapture && Generator && Mesher && Mesher->IsoLevel == 0.0f)
&& !bWantCapture && Generator && Mesher && Mesher->IsoLevel == 0.0f)
{
TRACE_CPUPROFILER_EVENT_SCOPE(VoxelForge_ClassifyTile);
INC_DWORD_STAT(STAT_VoxelForgeTilesClassified);
@@ -7972,8 +7864,6 @@ void AVoxelWorld::GenerateTileResult(const FVoxelTileKey& Tile, const FIntVector
bTrivialEmpty = (ValidatedVerdict != EVoxelTileClass::Mixed);
}
// F18 — feuille : deux heightfields sol/cap échantillonnés par colonne (pas de marching
// cubes, pas de classifieur — la classe de surface est vraie par construction).
// `TilesMeshed` peut dépasser `TilesClassified` : les tuiles qui ratent cette porte sont
// maillées sans classification. / `TilesMeshed` may exceed `TilesClassified`: tiles that
// fail this gate are meshed without classification.
@@ -7982,10 +7872,7 @@ void AVoxelWorld::GenerateTileResult(const FVoxelTileKey& Tile, const FIntVector
{
TRACE_CPUPROFILER_EVENT_SCOPE(VoxelForge_GenerateMesh);
const double MeshStartSeconds = bMeasureTile ? FPlatformTime::Seconds() : 0.0;
MeshData = bSheetTile
? Mesher->GenerateSheetMesh(OriginVoxels, Step, Cells, SheetChunkZ,
HoleMinX, HoleMinY, HoleMaxX, HoleMaxY)
: Mesher->GenerateMesh(OriginVoxels, Step, Cells,
MeshData = Mesher->GenerateMesh(OriginVoxels, Step, Cells,
bWantCapture ? &Result.CaptureGrid : nullptr,
BandVoxLo, BandVoxHi,
&Result.MesherDensityCalls);
@@ -8041,7 +7928,7 @@ void AVoxelWorld::GenerateTileResult(const FVoxelTileKey& Tile, const FIntVector
// Parity instrumentation observes the same FVoxelMeshData that the runtime stream builder
// consumes. It is intentionally after the abort fence, so a canceled worker cannot publish a
// partial identity, and it records empty/proof tiles as well as visible meshes.
RecordTileHash(Tile, OriginVoxels, Step, Cells, BandChunkLo, BandChunkHi, bSheetTile, MeshData);
RecordTileHash(Tile, OriginVoxels, Step, Cells, BandChunkLo, BandChunkHi, MeshData);
Result.ClassifySeconds = ClassifySeconds;
Result.MeshSeconds = MeshSeconds;
@@ -78,35 +78,6 @@ public:
int32 BandZMinVox = INT32_MIN, int32 BandZMaxVox = INT32_MAX,
int64* OutDensitySampleCount = nullptr);
/**
* F18 — FEUILLE de champ lointain (anneau render-distance, cf. UVoxelSettings::bFarSheetRing).
* Dans une strate ouverte (SurfaceWorld) le champ lointain est exactement DEUX heightfields —
* TerrainZ (sol) et CeilSurf (plafond sky-cap), déjà calculés par colonne par l'oracle
* GetSurfaceHeightAt. On construit donc deux grilles déplacées régulières au lieu d'un marching
* cubes 3D : sol = polygroup 0, cap = polygroup 1 (classes vraies PAR CONSTRUCTION — pas de vote,
* pas de sonde de classification), mêmes conventions que GenerateMesh (positions monde cm, UVs
* planaires, masques couleur F6 biome/pente/fondu, normales du gradient de hauteur, jupes
* périmètre par seau, run d'indices sol‖cap + NumCeilingTriangles).
*
* @param OriginVoxels - Coin min de la tuile (voxels). Seul XY est utilisé (les hauteurs sont absolues).
* @param StepXY - Pas d'échantillonnage XY en voxels (aligné sur l'anneau MC pour la continuité).
* @param CellsXY - Cellules par axe XY (extent = CellsXY × StepXY).
* @param StrateChunkZ - Chunk Z DANS la strate de référence (le cœur de la bande) — identifie la
* strate dont on maille sol+cap. Hors SurfaceWorld ⇒ mesh vide.
* @param HoleMin/MaxX/YVox - TROU XY (voxels, Max EXCLUSIF ; sentinelles MAX/MIN = pas de trou) :
* les cellules ENTIÈREMENT dans ce rectangle (la zone couverte par les
* coquilles MC autour du joueur) sont sautées — sinon la feuille recouvre
* le terrain proche avec son échantillonnage grossier. Les cellules à
* cheval restent (anneau de recouvrement au raccord) ; pas de jupe sur
* les bords du trou (le terrain MC remplit derrière).
* Non couvert (accepté, cf. fable-idea F18) : passages/spine/chasms creusés (le heightfield pur ne
* les contient pas), diff layer — invisibles à distance de feuille, l'anneau MC proche les garde.
*/
FVoxelMeshData GenerateSheetMesh(FIntVector OriginVoxels, int32 StepXY, int32 CellsXY,
int32 StrateChunkZ,
int32 HoleMinXVox = INT32_MAX, int32 HoleMinYVox = INT32_MAX,
int32 HoleMaxXVox = INT32_MIN, int32 HoleMaxYVox = INT32_MIN);
//=========================================================================
// SERVICES (injectés par AVoxelWorld)
//=========================================================================
+4 -13
View File
@@ -183,22 +183,13 @@ public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Voxel|Clipmap", meta = (ClampMin = "0"))
int32 RenderDistanceChunks = 768;
// F18 — the render-distance ring streams per-surface SHEETS instead of MC tiles: in an open
// strate the far field is exactly two heightfields (TerrainZ + sky-cap CeilSurf, both already
// computed per column), so each far tile becomes two displaced grids (ground polygroup 0 /
// cap polygroup 1 — same materials), ~3-6× cheaper to generate and far fewer components (one
// sheet spans 2^FarSheetSpanLevels MC-tile footprints per axis). Non-open strates produce
// empty sheets (their far ring was enclosed rock anyway). Carved features (passages, chasms,
// spine) don't show at sheet distance. Needs the strate band armed (StrateContentCutMinLevel
// active); in the inter-strate gap the sheet ring blanks until you land. The ship default is OFF:
// the ring stays real MC tiles at level MaxClipLevel (pre-F18 behaviour). Only matters when
// RenderDistanceChunks > 0.
// UNUSED: the far sheet ring was removed from the runtime; the render-distance ring is always
// marching-cubes tiles at MaxClipLevel. Kept only so saved assets and the test harness's
// `voxel.TestFarSheetRing` switch still load; it has no effect.
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Voxel|Clipmap")
bool bFarSheetRing = false;
// Sheet tile size = MaxClipLevel + this many levels (2 → one sheet covers 4×4 MC-tile
// footprints → ~16× fewer far components). Sampling density stays that of the MaxClipLevel
// MC ring (cell count grows instead), capped at 128 cells/axis (beyond, cells coarsen).
// UNUSED (see bFarSheetRing). Kept so saved assets still load; it has no effect.
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Voxel|Clipmap", meta = (ClampMin = "1", ClampMax = "4", EditCondition = "bFarSheetRing"))
int32 FarSheetSpanLevels = 2;
+1 -1
View File
@@ -13,7 +13,7 @@ namespace VoxelForgeStartupTrace
int32 TileY = 0;
int32 TileZ = 0;
int32 Level = -1;
int32 Verdict = -1; // Mixed=0, AllSolid=1, AllAir=2, -1=not classified (sheet/early path)
int32 Verdict = -1; // Mixed=0, AllSolid=1, AllAir=2, -1=not classified (early path)
bool bSealedSolidProof = false;
bool bEmpty = true;
int32 Vertices = 0;
+3 -16
View File
@@ -160,7 +160,6 @@ struct FVoxelTileHashDumpRecord
int32 Cells = 0;
int32 BandChunkLo = MIN_int32;
int32 BandChunkHi = MAX_int32;
bool bSheetTile = false;
bool bEmpty = true;
int32 NumTriangles = 0;
FString GeometryHash;
@@ -567,7 +566,7 @@ private:
void LogReadyTransitionVisibilityMeasure();
void RecordTileHash(const FVoxelTileKey& Tile, const FIntVector& OriginVoxels,
int32 Step, int32 Cells, int32 BandChunkLo, int32 BandChunkHi,
bool bSheetTile, const FVoxelMeshData& MeshData);
const FVoxelMeshData& MeshData);
void WriteTileHashDump();
// Packed shader params, recomputed each Tick. ALL meaningful data is in .xyz — a material Vector
@@ -761,7 +760,7 @@ public:
EVoxelTileWorkPriority WorkPriority = EVoxelTileWorkPriority::Horizon);
/**
* Worker-side gen for one tile: classify → GenerateMesh/GenerateSheetMesh → BuildTileStreamSet.
* Worker-side gen for one tile: classify → GenerateMesh → BuildTileStreamSet.
* Fills Result (no enqueue, no bookkeeping). Called from the async ChunkGen task AND from the
* synchronous carve path (SyncRemeshTile) — reads Generator/Mesher only, so it's safe on either
* thread. See LoadTile for how the parameters are derived.
@@ -769,8 +768,6 @@ public:
void GenerateTileResult(const FVoxelTileKey& Tile, const FIntVector& OriginVoxels,
int32 Step, int32 Cells, uint32 Epoch, bool bWantCapture,
int32 BandVoxLo, int32 BandVoxHi, int32 BandChunkLo, int32 BandChunkHi,
bool bSheetTile, int32 SheetChunkZ,
int32 HoleMinX, int32 HoleMinY, int32 HoleMaxX, int32 HoleMaxY,
FChunkResult& Result,
const std::atomic<bool>* ObsoleteFlag = nullptr);
@@ -933,15 +930,6 @@ public:
// result cannot publish and the post-carve result is admitted immediately.
TSet<FVoxelTileKey> DirtyRemeshQueue;
// F18 — TROU XY de l'anneau feuille (voxels ; Max EXCLUSIF ; sentinelles MAX/MIN = pas de
// trou) : la zone couverte par les coquilles MC (boîte niveau-MaxClipLevel autour du joueur,
// rétrécie d'une tuile pour garder un anneau de recouvrement au raccord) est DÉCOUPÉE des
// feuilles — sinon une feuille partiellement couverte recouvre le terrain proche avec son
// échantillonnage grossier. Mis à jour par crossing ; changement ⇒ re-queue des feuilles
// chevauchantes via BandRemeshQueue (re-gen en place).
int32 SheetHoleMinXVox = MAX_int32, SheetHoleMinYVox = MAX_int32;
int32 SheetHoleMaxXVox = MIN_int32, SheetHoleMaxYVox = MIN_int32;
// Set to true during EndPlay — async tasks check this before accessing UObjects
std::atomic<bool> bShuttingDown{false};
@@ -1018,8 +1006,7 @@ public:
TArray<FReadyTransitionVisibilitySample> ReadyTransitionVisibilitySamples;
TArray<FFarFieldSubmitSample> FarFieldSubmitSamples;
// Clean performance accounting. LOD 0..8 covers every marching-cubes clip level; sheet tiles
// above the configured MC range never enter the outer classifier.
// Clean performance accounting. LOD 0..8 covers every marching-cubes clip level.
static constexpr int32 TrackedClassifierLODCount = 9;
std::atomic<uint64> TotalWorkerGenerationCycles{0};
std::atomic<uint64> TotalWorkerGenerationTasks{0};