standalone: per-tile speed at plugin level, streaming like the game, softer lighting
Cause found by profiling: room/SDF graph-cache churn (exact XY bounds + sample order rebuilt it constantly). Region grouping (one LOD3 tile: 1,084 -> 229 builds), taller-tile traversal for LOD6 (36,480 -> 12,160), extent-dependent cache slots, lambdas instead of recursive std::function. Serial ms/tile, 64 nearest tiles per level (before -> after): L0 50.4 -> 50.6, L1 112 -> 110, L2 24 -> 22, L3 223 -> 73, L4 269 -> 134, L5 393 -> 127, L6 283 -> 145. Hashes of all levels unchanged; parity 3,436/3,436 bit-identical. Full clipmap, 16 workers BelowNormal: full detail 66.6 s, all 12,054 tiles 175 s. In-stream L0 tail is high (median 83 ms, mean 198 ms, p95 669 ms) vs 51 ms serial. Streaming: recentre keeps the intersection, generates only missing tiles nearest/finest first; one crossing = exactly 441 new tiles in 6.1 s, no stale results. Lighting: normals already come from the density gradient; the striping was the squared headlamp; broad linear fill + warmer palette. Daytime CPU rule documented in Standalone/README.md. 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
3f300b912a
commit
81b7d6d0ff
24 files changed
+1096
-196
No files matched your search
@@ -7750,7 +7750,7 @@ void VoxelCaveMorphology::BuildChunkCache(
|
||||
GraphLowLink.Init(INDEX_NONE, NumRooms);
|
||||
GraphBridges.Init(0u, GraphEdges.Num());
|
||||
int32 GraphDiscoveryTime = 0;
|
||||
TFunction<void(int32, int32)> VisitGraph = [&](int32 Node, int32 ParentEdge)
|
||||
const auto VisitGraph = [&](auto&& VisitGraphSelf, int32 Node, int32 ParentEdge) -> void
|
||||
{
|
||||
GraphDiscovery[Node] = GraphDiscoveryTime;
|
||||
GraphLowLink[Node] = GraphDiscoveryTime;
|
||||
@@ -7765,7 +7765,7 @@ void VoxelCaveMorphology::BuildChunkCache(
|
||||
const int32 Other = Edge.RoomA == Node ? Edge.RoomB : Edge.RoomA;
|
||||
if (GraphDiscovery[Other] == INDEX_NONE)
|
||||
{
|
||||
VisitGraph(Other, EdgeIndex);
|
||||
VisitGraphSelf(VisitGraphSelf, Other, EdgeIndex);
|
||||
GraphLowLink[Node] = FMath::Min(
|
||||
GraphLowLink[Node], GraphLowLink[Other]);
|
||||
if (GraphLowLink[Other] > GraphDiscovery[Node])
|
||||
@@ -7784,7 +7784,7 @@ void VoxelCaveMorphology::BuildChunkCache(
|
||||
{
|
||||
if (GraphDiscovery[Node] == INDEX_NONE)
|
||||
{
|
||||
VisitGraph(Node, INDEX_NONE);
|
||||
VisitGraph(VisitGraph, Node, INDEX_NONE);
|
||||
}
|
||||
}
|
||||
for (int32 EdgeIndex = 0; EdgeIndex < GraphEdges.Num(); ++EdgeIndex)
|
||||
@@ -7803,7 +7803,7 @@ void VoxelCaveMorphology::BuildChunkCache(
|
||||
TArray<uint8, TInlineAllocator<128>> GraphForestEdges;
|
||||
GraphForestVisited.Init(0u, NumRooms);
|
||||
GraphForestEdges.Init(0u, GraphEdges.Num());
|
||||
TFunction<void(int32)> VisitWalkForest = [&](int32 Node)
|
||||
const auto VisitWalkForest = [&](auto&& VisitWalkForestSelf, int32 Node) -> void
|
||||
{
|
||||
GraphForestVisited[Node] = 1u;
|
||||
for (const int32 EdgeIndex : GraphAdjacency[Node])
|
||||
@@ -7815,14 +7815,14 @@ void VoxelCaveMorphology::BuildChunkCache(
|
||||
continue;
|
||||
}
|
||||
GraphForestEdges[EdgeIndex] = 1u;
|
||||
VisitWalkForest(Other);
|
||||
VisitWalkForestSelf(VisitWalkForestSelf, Other);
|
||||
}
|
||||
};
|
||||
for (int32 Node = 0; Node < NumRooms; ++Node)
|
||||
{
|
||||
if (GraphForestVisited[Node] == 0u)
|
||||
{
|
||||
VisitWalkForest(Node);
|
||||
VisitWalkForest(VisitWalkForest, Node);
|
||||
}
|
||||
}
|
||||
for (int32 EdgeIndex = 0; EdgeIndex < GraphEdges.Num(); ++EdgeIndex)
|
||||
|
||||
@@ -704,6 +704,28 @@ namespace VoxelDensityProfile
|
||||
case ECounter::MesherOutputAllocatedBytes: return TEXT("MesherOutputAllocatedBytes");
|
||||
case ECounter::OpBlockScratchBytes: return TEXT("OpBlockScratchBytes");
|
||||
case ECounter::OpBlockCopyBytes: return TEXT("OpBlockCopyBytes");
|
||||
case ECounter::SdfRebuildStrate: return TEXT("SdfRebuildStrate");
|
||||
case ECounter::SdfRebuildSeed: return TEXT("SdfRebuildSeed");
|
||||
case ECounter::SdfRebuildFingerprint: return TEXT("SdfRebuildFingerprint");
|
||||
case ECounter::SdfRebuildLayout: return TEXT("SdfRebuildLayout");
|
||||
case ECounter::SdfRebuildManagerLifetime: return TEXT("SdfRebuildManagerLifetime");
|
||||
case ECounter::SdfRebuildTerrainOps: return TEXT("SdfRebuildTerrainOps");
|
||||
case ECounter::SdfRebuildFusedPolicy: return TEXT("SdfRebuildFusedPolicy");
|
||||
case ECounter::SdfRebuildTilePolicy: return TEXT("SdfRebuildTilePolicy");
|
||||
case ECounter::SdfRebuildTileIdentity: return TEXT("SdfRebuildTileIdentity");
|
||||
case ECounter::SdfRebuildBoundsX: return TEXT("SdfRebuildBoundsX");
|
||||
case ECounter::SdfRebuildBoundsY: return TEXT("SdfRebuildBoundsY");
|
||||
case ECounter::SdfTileWindowAccepted: return TEXT("SdfTileWindowAccepted");
|
||||
case ECounter::SdfTileWindowRejected: return TEXT("SdfTileWindowRejected");
|
||||
case ECounter::TilePolicyKeyOrigin: return TEXT("TilePolicyKeyOrigin");
|
||||
case ECounter::TilePolicyKeyStep: return TEXT("TilePolicyKeyStep");
|
||||
case ECounter::TilePolicyKeyCells: return TEXT("TilePolicyKeyCells");
|
||||
case ECounter::TilePolicyKeyFingerprint: return TEXT("TilePolicyKeyFingerprint");
|
||||
case ECounter::TilePolicyKeyLayout: return TEXT("TilePolicyKeyLayout");
|
||||
case ECounter::TilePolicyKeyManager: return TEXT("TilePolicyKeyManager");
|
||||
case ECounter::TilePolicyKeyTerrainOps: return TEXT("TilePolicyKeyTerrainOps");
|
||||
case ECounter::TilePolicyKeyFusedPath: return TEXT("TilePolicyKeyFusedPath");
|
||||
case ECounter::TilePolicyKeyContext: return TEXT("TilePolicyKeyContext");
|
||||
case ECounter::Count: break;
|
||||
}
|
||||
return TEXT("Unknown");
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
#endif
|
||||
|
||||
#include <atomic>
|
||||
#include <array>
|
||||
|
||||
namespace VoxelMath
|
||||
{
|
||||
@@ -139,6 +140,24 @@ namespace
|
||||
TEXT("voxel.TileCacheWindowOpStackMinLOD"),
|
||||
GVoxelForgeTileCacheWindowOpStackMinLOD,
|
||||
TEXT("Minimum SampleStep LOD for the op-stack tile-sized room cache window."));
|
||||
int32 GVoxelForgeRoomGraphCacheSlots = 1;
|
||||
FAutoConsoleVariableRef CVarVoxelForgeRoomGraphCacheSlots(
|
||||
TEXT("voxel.RoomGraphCacheSlots"),
|
||||
GVoxelForgeRoomGraphCacheSlots,
|
||||
TEXT("Per-worker exact room-graph windows to retain during density sampling (1-4)."));
|
||||
bool GVoxelForgeRoomGraphCacheSlotsParsed = false;
|
||||
|
||||
void VF_ParseRoomGraphCacheSlots()
|
||||
{
|
||||
if (GVoxelForgeRoomGraphCacheSlotsParsed) return;
|
||||
GVoxelForgeRoomGraphCacheSlotsParsed = true;
|
||||
int32 CommandLineValue = GVoxelForgeRoomGraphCacheSlots;
|
||||
if (FParse::Value(FCommandLine::Get(), TEXT("voxel.RoomGraphCacheSlots="), CommandLineValue))
|
||||
{
|
||||
GVoxelForgeRoomGraphCacheSlots = CommandLineValue;
|
||||
}
|
||||
GVoxelForgeRoomGraphCacheSlots = FMath::Clamp(GVoxelForgeRoomGraphCacheSlots, 1, 4);
|
||||
}
|
||||
|
||||
int32 GVoxelForgeSpatialIndex = 1;
|
||||
FAutoConsoleVariableRef CVarVoxelForgeSpatialIndex(
|
||||
@@ -879,10 +898,23 @@ void VoxelGenLOD::InitializeConsoleSwitches()
|
||||
VF_ParseFusedEvaluatorSwitch();
|
||||
VF_ParseWormBlockSkipSwitch();
|
||||
VF_ParseTileCacheWindowSwitch();
|
||||
VF_ParseRoomGraphCacheSlots();
|
||||
VF_ParseSpatialIndexSwitch();
|
||||
VF_ParseChunkRoutingCacheSwitch();
|
||||
}
|
||||
|
||||
void VoxelGenLOD::SetUseFusedEvaluatorEnabled(bool bEnabled)
|
||||
{
|
||||
VF_ParseFusedEvaluatorSwitch();
|
||||
GVoxelForgeUseFusedEvaluator = bEnabled ? 1 : 0;
|
||||
}
|
||||
|
||||
bool VoxelGenLOD::IsUseFusedEvaluatorEnabled()
|
||||
{
|
||||
VF_ParseFusedEvaluatorSwitch();
|
||||
return GVoxelForgeUseFusedEvaluator != 0;
|
||||
}
|
||||
|
||||
bool VoxelGenLOD::IsTileCacheWindowEnabled(bool bFusedPath)
|
||||
{
|
||||
if (GVoxelForgeTileCacheWindow == 0)
|
||||
@@ -4171,38 +4203,31 @@ float UVoxelGenerator::GetDensityWithParams(float WorldX, float WorldY, float Wo
|
||||
//
|
||||
// Declared before the RoomDensity if-block so SDFCache and NearestRoomIdx
|
||||
// are also visible to the terrain ops block further below.
|
||||
thread_local FChunkSDFCache SDFCache;
|
||||
// Cache validity is tracked by the SEARCH BOX the cache was built for, NOT by chunk
|
||||
// equality. Gradient-normal sampling queries at WorldX±1 (and warp displacement) can
|
||||
// step a voxel outside the chunk; with chunk-equality keying that flipped the key and
|
||||
// rebuilt the (now expensive) cache every boundary cell. Since the stored rooms cover
|
||||
// the search box + MaxInfluence, any query INSIDE the box is correct — so we only
|
||||
// rebuild when the query actually leaves the box. Result: one build per chunk, no thrash.
|
||||
thread_local float CachedSMinX = 1.0f, CachedSMaxX = -1.0f; // start invalid (min > max)
|
||||
thread_local float CachedSMinY = 0.0f, CachedSMaxY = 0.0f;
|
||||
thread_local int32 CachedStrate = INT32_MIN;
|
||||
thread_local uint32 CachedSeed = 0;
|
||||
// ⚠️ AUDIT §C2 — `CachedFingerprint` et `CachedLayout` ci-dessous sont OBLIGATOIRES dans cette clé.
|
||||
// La clé ci-dessus décrit la GÉOMÉTRIE de la fenêtre (boîte, strate, seed) et rien de ce qui
|
||||
// détermine les PARAMS avec lesquels les salles ont été cuites. Comme `GetGenerationParams`
|
||||
// blende à l'intérieur d'une strate (`Alpha` = f(chunk Z), et f(chunk XY) aussi en
|
||||
// `Interleaved`), deux chunks voisins produisent la MÊME clé avec des params DIFFÉRENTS, et le
|
||||
// deuxième se sert des salles du premier. Non déterministe entre pairs, parce que l'ordre des
|
||||
// workers décide lequel est « le premier » — exactement ce que §2.6.1 interdit.
|
||||
//
|
||||
// Pourquoi ça ne casse PAS l'invariant de perf de §8.10 : la clé reste une BOÎTE, donc les
|
||||
// sondes de gradient à `WorldX ± 1` ne font toujours pas tourner le cache. Ce qui le fait
|
||||
// tourner en plus, c'est un changement RÉEL de params — une fois par chunk dans une bande de
|
||||
// transition, ce qui est exactement le nombre de reconstructions nécessaire.
|
||||
thread_local uint32 CachedFingerprint = 0xFFFFFFFFu;
|
||||
thread_local uint32 CachedLayout = 0xFFFFFFFFu;
|
||||
thread_local bool CachedUsesFusedCacheWindow = false;
|
||||
thread_local bool CachedUsesTileCacheWindow = false;
|
||||
thread_local FIntVector CachedTileOrigin = FIntVector::ZeroValue;
|
||||
thread_local int32 CachedTileStep = 0;
|
||||
thread_local int32 CachedTileCells = 0;
|
||||
thread_local uint64 CachedManagerLifetimeId = 0;
|
||||
thread_local const TArray<FStrateTerrainOpEntry>* CachedTerrainOps = nullptr;
|
||||
// Keep a small bounded set of exact room-graph windows. Tall 32-chunk tiles need two slots
|
||||
// for their two strata; other tile extents start with one. Every slot uses the full cache
|
||||
// identity/bounds.
|
||||
struct FRoomGraphCacheSlot
|
||||
{
|
||||
FChunkSDFCache Cache;
|
||||
float SMinX = 1.0f, SMaxX = -1.0f;
|
||||
float SMinY = 0.0f, SMaxY = 0.0f;
|
||||
int32 Strate = INT32_MIN;
|
||||
uint32 Seed = 0;
|
||||
uint32 Fingerprint = 0xFFFFFFFFu;
|
||||
uint32 Layout = 0xFFFFFFFFu;
|
||||
bool bUsesFusedCacheWindow = false;
|
||||
bool bUsesTileCacheWindow = false;
|
||||
FIntVector TileOrigin = FIntVector::ZeroValue;
|
||||
int32 TileStep = 0;
|
||||
int32 TileCells = 0;
|
||||
uint64 ManagerLifetimeId = 0;
|
||||
const TArray<FStrateTerrainOpEntry>* TerrainOps = nullptr;
|
||||
uint64 LastUse = 0;
|
||||
bool bValid = false;
|
||||
};
|
||||
static thread_local std::array<FRoomGraphCacheSlot, 4> RoomGraphCacheSlots{};
|
||||
static thread_local uint64 RoomGraphCacheUseSerial = 0;
|
||||
VF_ParseRoomGraphCacheSlots();
|
||||
// The envelope proof is constant for a cache-policy key. Do not run the complete room/edge
|
||||
// reach calculation for every density sample: that would turn a build-time guard into a
|
||||
// per-sample cost on the LOD0 floor.
|
||||
@@ -4267,6 +4292,32 @@ float UVoxelGenerator::GetDensityWithParams(float WorldX, float WorldY, float Wo
|
||||
|| CachedTilePolicyTerrainOps != TerrainOps;
|
||||
if (bTilePolicyKeyChanged)
|
||||
{
|
||||
if (VoxelDensityProfile::AreCountersEnabled())
|
||||
{
|
||||
const auto CountPolicyKeyChange = [](bool bCondition, VoxelDensityProfile::ECounter Counter)
|
||||
{
|
||||
if (bCondition) VoxelDensityProfile::AddCounter(Counter);
|
||||
};
|
||||
CountPolicyKeyChange(CachedTilePolicyOrigin != RequestedTileOrigin,
|
||||
VoxelDensityProfile::ECounter::TilePolicyKeyOrigin);
|
||||
CountPolicyKeyChange(CachedTilePolicySampleStep != RequestedTileStep
|
||||
|| CachedTilePolicyStep != RequestedTileStep,
|
||||
VoxelDensityProfile::ECounter::TilePolicyKeyStep);
|
||||
CountPolicyKeyChange(CachedTilePolicyCells != RequestedTileCells,
|
||||
VoxelDensityProfile::ECounter::TilePolicyKeyCells);
|
||||
CountPolicyKeyChange(CachedTilePolicyFingerprint != ParamsFingerprint,
|
||||
VoxelDensityProfile::ECounter::TilePolicyKeyFingerprint);
|
||||
CountPolicyKeyChange(CachedTilePolicyLayout != LayoutVersion,
|
||||
VoxelDensityProfile::ECounter::TilePolicyKeyLayout);
|
||||
CountPolicyKeyChange(CachedTilePolicyManagerLifetimeId != CurrentManagerLifetimeId,
|
||||
VoxelDensityProfile::ECounter::TilePolicyKeyManager);
|
||||
CountPolicyKeyChange(CachedTilePolicyTerrainOps != TerrainOps,
|
||||
VoxelDensityProfile::ECounter::TilePolicyKeyTerrainOps);
|
||||
CountPolicyKeyChange(CachedTilePolicyFusedPath != bUseFusedCacheWindow,
|
||||
VoxelDensityProfile::ECounter::TilePolicyKeyFusedPath);
|
||||
CountPolicyKeyChange(CachedTilePolicyHasContext != (bTileWindowEligible && bHasTileWindowContext),
|
||||
VoxelDensityProfile::ECounter::TilePolicyKeyContext);
|
||||
}
|
||||
CachedTilePolicyValid = true;
|
||||
CachedTilePolicyHasContext = bTileWindowEligible && bHasTileWindowContext;
|
||||
CachedTilePolicyFusedPath = bUseFusedCacheWindow;
|
||||
@@ -4280,6 +4331,12 @@ float UVoxelGenerator::GetDensityWithParams(float WorldX, float WorldY, float Wo
|
||||
CachedTilePolicyTerrainOps = TerrainOps;
|
||||
CachedTilePolicyResult = CachedTilePolicyHasContext
|
||||
&& VoxelCaveMorphology::IsRoomGraphWindowInvariant(Params, TerrainOps);
|
||||
if (VoxelDensityProfile::AreCountersEnabled() && CachedTilePolicyHasContext)
|
||||
{
|
||||
VoxelDensityProfile::AddCounter(CachedTilePolicyResult
|
||||
? VoxelDensityProfile::ECounter::SdfTileWindowAccepted
|
||||
: VoxelDensityProfile::ECounter::SdfTileWindowRejected);
|
||||
}
|
||||
}
|
||||
const bool bUseTileCacheWindow = CachedTilePolicyResult;
|
||||
|
||||
@@ -4290,36 +4347,137 @@ float UVoxelGenerator::GetDensityWithParams(float WorldX, float WorldY, float Wo
|
||||
|
||||
float CaveSDF = FLT_MAX;
|
||||
|
||||
// Get strate index for unique caves per strate. The index changes only at chunk-Z
|
||||
// boundaries and is also part of the worker-local graph-cache identity.
|
||||
int32 StrateIdx = 0;
|
||||
if (StrateManager)
|
||||
{
|
||||
thread_local int32 SI_ChunkZ = INT32_MAX;
|
||||
thread_local uint32 SI_Version = 0xFFFFFFFFu;
|
||||
thread_local int32 SI_Index = 0;
|
||||
const int32 QZ = FMath::FloorToInt(WorldZ / (float)CHUNK_SIZE);
|
||||
const uint32 LV = StrateManager->GetLayoutVersion();
|
||||
if (QZ != SI_ChunkZ || LV != SI_Version)
|
||||
{
|
||||
SI_ChunkZ = QZ;
|
||||
SI_Version = LV;
|
||||
SI_Index = StrateManager->GetStrateIndex(
|
||||
((float)QZ + 0.5f) * CHUNK_SIZE * VOXEL_SIZE);
|
||||
}
|
||||
StrateIdx = SI_Index;
|
||||
}
|
||||
|
||||
const float ChunkMinX = CacheRegionX * static_cast<float>(CacheRegionSize);
|
||||
const float ChunkMinY = CacheRegionY * static_cast<float>(CacheRegionSize);
|
||||
const float ChunkMaxX = ChunkMinX + static_cast<float>(CacheRegionSize);
|
||||
const float ChunkMaxY = ChunkMinY + static_cast<float>(CacheRegionSize);
|
||||
const float WarpMargin = FMath::Abs(Params.CaveWarpStrength)
|
||||
* VOXEL_NOISE_SCALE * 1.5f;
|
||||
float DesiredSMinX = ChunkMinX;
|
||||
float DesiredSMinY = ChunkMinY;
|
||||
float DesiredSMaxX = ChunkMaxX;
|
||||
float DesiredSMaxY = ChunkMaxY;
|
||||
if (bUseTileCacheWindow)
|
||||
{
|
||||
const float TileMinX = static_cast<float>(RequestedTileOrigin.X);
|
||||
const float TileMinY = static_cast<float>(RequestedTileOrigin.Y);
|
||||
const float TileExtent = static_cast<float>(
|
||||
static_cast<int64>(RequestedTileStep)
|
||||
* static_cast<int64>(RequestedTileCells));
|
||||
const float TileMaxX = TileMinX + TileExtent;
|
||||
const float TileMaxY = TileMinY + TileExtent;
|
||||
const float TileHalo = static_cast<float>(RequestedTileStep) + 2.0f;
|
||||
const float Expansion = WarpMargin + TileHalo;
|
||||
DesiredSMinX = TileMinX - Expansion;
|
||||
DesiredSMinY = TileMinY - Expansion;
|
||||
DesiredSMaxX = TileMaxX + Expansion;
|
||||
DesiredSMaxY = TileMaxY + Expansion;
|
||||
}
|
||||
else
|
||||
{
|
||||
const float Expansion = WarpMargin + 2.0f;
|
||||
DesiredSMinX = ChunkMinX - Expansion;
|
||||
DesiredSMinY = ChunkMinY - Expansion;
|
||||
DesiredSMaxX = ChunkMaxX + Expansion;
|
||||
DesiredSMaxY = ChunkMaxY + Expansion;
|
||||
}
|
||||
|
||||
const int64 RequestedTileExtent = static_cast<int64>(RequestedTileStep)
|
||||
* static_cast<int64>(RequestedTileCells);
|
||||
const bool bNeedsTwoStrataWindows = bHasTileWindowContext
|
||||
&& RequestedTileExtent == static_cast<int64>(CHUNK_SIZE) * 32;
|
||||
const int32 ActiveCacheSlots = FMath::Max(
|
||||
FMath::Clamp(GVoxelForgeRoomGraphCacheSlots, 1, 4),
|
||||
bNeedsTwoStrataWindows ? 2 : 1);
|
||||
FRoomGraphCacheSlot* CacheSlot = nullptr;
|
||||
FRoomGraphCacheSlot* EvictionSlot = &RoomGraphCacheSlots[0];
|
||||
for (int32 SlotIndex = 0; SlotIndex < ActiveCacheSlots; ++SlotIndex)
|
||||
{
|
||||
FRoomGraphCacheSlot& Candidate = RoomGraphCacheSlots[SlotIndex];
|
||||
if (!Candidate.bValid)
|
||||
{
|
||||
EvictionSlot = &Candidate;
|
||||
break;
|
||||
}
|
||||
if (Candidate.LastUse < EvictionSlot->LastUse)
|
||||
{
|
||||
EvictionSlot = &Candidate;
|
||||
}
|
||||
const bool bSameKey = Candidate.Strate == StrateIdx
|
||||
&& Candidate.Seed == static_cast<uint32>(Seed)
|
||||
&& Candidate.Fingerprint == ParamsFingerprint
|
||||
&& Candidate.Layout == LayoutVersion
|
||||
&& Candidate.ManagerLifetimeId == CurrentManagerLifetimeId
|
||||
&& Candidate.TerrainOps == TerrainOps
|
||||
&& Candidate.bUsesFusedCacheWindow == bUseFusedCacheWindow
|
||||
&& Candidate.bUsesTileCacheWindow == bUseTileCacheWindow
|
||||
&& (!bUseTileCacheWindow
|
||||
|| (Candidate.TileOrigin == RequestedTileOrigin
|
||||
&& Candidate.TileStep == RequestedTileStep
|
||||
&& Candidate.TileCells == RequestedTileCells));
|
||||
const bool bSameBounds = Candidate.SMinX == DesiredSMinX
|
||||
&& Candidate.SMaxX == DesiredSMaxX
|
||||
&& Candidate.SMinY == DesiredSMinY
|
||||
&& Candidate.SMaxY == DesiredSMaxY;
|
||||
const bool bContainsQuery = WarpedX >= Candidate.SMinX
|
||||
&& WarpedX <= Candidate.SMaxX
|
||||
&& WarpedY >= Candidate.SMinY
|
||||
&& WarpedY <= Candidate.SMaxY;
|
||||
if (bSameKey && bSameBounds && bContainsQuery)
|
||||
{
|
||||
CacheSlot = &Candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (CacheSlot == nullptr)
|
||||
{
|
||||
CacheSlot = EvictionSlot;
|
||||
}
|
||||
CacheSlot->LastUse = ++RoomGraphCacheUseSerial;
|
||||
FChunkSDFCache& SDFCache = CacheSlot->Cache;
|
||||
float& CachedSMinX = CacheSlot->SMinX;
|
||||
float& CachedSMaxX = CacheSlot->SMaxX;
|
||||
float& CachedSMinY = CacheSlot->SMinY;
|
||||
float& CachedSMaxY = CacheSlot->SMaxY;
|
||||
int32& CachedStrate = CacheSlot->Strate;
|
||||
uint32& CachedSeed = CacheSlot->Seed;
|
||||
uint32& CachedFingerprint = CacheSlot->Fingerprint;
|
||||
uint32& CachedLayout = CacheSlot->Layout;
|
||||
bool& CachedUsesFusedCacheWindow = CacheSlot->bUsesFusedCacheWindow;
|
||||
bool& CachedUsesTileCacheWindow = CacheSlot->bUsesTileCacheWindow;
|
||||
FIntVector& CachedTileOrigin = CacheSlot->TileOrigin;
|
||||
int32& CachedTileStep = CacheSlot->TileStep;
|
||||
int32& CachedTileCells = CacheSlot->TileCells;
|
||||
uint64& CachedManagerLifetimeId = CacheSlot->ManagerLifetimeId;
|
||||
const TArray<FStrateTerrainOpEntry>*& CachedTerrainOps = CacheSlot->TerrainOps;
|
||||
|
||||
if (Params.RoomDensity > 0.0f && Params.RoomSpacing > 0.0f)
|
||||
{
|
||||
// Get strate index for unique caves per strate.
|
||||
// MEMOISED: the index only changes at chunk-Z boundaries, yet this ran the strate layout's
|
||||
// linear scan PER VOXEL. Key = (chunk-Z, layout version) — the version guards against editor
|
||||
// rebuilds (RebuildStrates / live edit) serving a stale index. The lookup queries the BAND
|
||||
// CENTRE so the result is a pure function of the key (order-independent, window-invariant).
|
||||
int32 StrateIdx = 0;
|
||||
if (StrateManager)
|
||||
{
|
||||
thread_local int32 SI_ChunkZ = INT32_MAX;
|
||||
thread_local uint32 SI_Version = 0xFFFFFFFFu;
|
||||
thread_local int32 SI_Index = 0;
|
||||
const int32 QZ = FMath::FloorToInt(WorldZ / (float)CHUNK_SIZE);
|
||||
const uint32 LV = StrateManager->GetLayoutVersion();
|
||||
if (QZ != SI_ChunkZ || LV != SI_Version)
|
||||
{
|
||||
SI_ChunkZ = QZ;
|
||||
SI_Version = LV;
|
||||
// GetStrateIndex expects Unreal world units (divides by VOXEL_SIZE internally);
|
||||
// WorldZ here is in voxels, so multiply by VOXEL_SIZE.
|
||||
SI_Index = StrateManager->GetStrateIndex(((float)QZ + 0.5f) * CHUNK_SIZE * VOXEL_SIZE);
|
||||
}
|
||||
StrateIdx = SI_Index;
|
||||
}
|
||||
|
||||
// Rebuild only when the WARPED query (what EvaluateSDFCached uses) leaves the
|
||||
// cached search box, or the strate/seed changed.
|
||||
const bool bNeedRebuild =
|
||||
StrateIdx != CachedStrate || (uint32)Seed != CachedSeed ||
|
||||
!CacheSlot->bValid || StrateIdx != CachedStrate || (uint32)Seed != CachedSeed ||
|
||||
ParamsFingerprint != CachedFingerprint || LayoutVersion != CachedLayout ||
|
||||
CurrentManagerLifetimeId != CachedManagerLifetimeId ||
|
||||
TerrainOps != CachedTerrainOps ||
|
||||
@@ -4334,58 +4492,44 @@ float UVoxelGenerator::GetDensityWithParams(float WorldX, float WorldY, float Wo
|
||||
|
||||
if (bNeedRebuild)
|
||||
{
|
||||
// The lowered evaluator uses the same window-invariance contract as the prepared
|
||||
// room-graph source. With the switch enabled, the search box is the exact requesting
|
||||
// tile footprint plus the one-sample normal halo and deterministic warp margin. A
|
||||
// tile worker can visit neighbouring chunk keys in an arbitrary order without
|
||||
// rebuilding the graph at every coarse sample. Point queries without a tile context
|
||||
// retain the historical fused four-chunk / ordinary one-chunk fallback.
|
||||
const float ChunkMinX = CacheRegionX * (float)CacheRegionSize;
|
||||
const float ChunkMinY = CacheRegionY * (float)CacheRegionSize;
|
||||
const float ChunkMaxX = ChunkMinX + (float)CacheRegionSize;
|
||||
const float ChunkMaxY = ChunkMinY + (float)CacheRegionSize;
|
||||
const float WarpMargin = FMath::Abs(Params.CaveWarpStrength)
|
||||
* VOXEL_NOISE_SCALE * 1.5f;
|
||||
float SMinX = ChunkMinX;
|
||||
float SMinY = ChunkMinY;
|
||||
float SMaxX = ChunkMaxX;
|
||||
float SMaxY = ChunkMaxY;
|
||||
if (bUseTileCacheWindow)
|
||||
if (VoxelDensityProfile::AreCountersEnabled())
|
||||
{
|
||||
const float TileMinX = static_cast<float>(RequestedTileOrigin.X);
|
||||
const float TileMinY = static_cast<float>(RequestedTileOrigin.Y);
|
||||
const float TileExtent = static_cast<float>(
|
||||
static_cast<int64>(RequestedTileStep)
|
||||
* static_cast<int64>(RequestedTileCells));
|
||||
const float TileMaxX = TileMinX + TileExtent;
|
||||
const float TileMaxY = TileMinY + TileExtent;
|
||||
const float TileHalo = static_cast<float>(RequestedTileStep) + 2.0f;
|
||||
const float Expansion = WarpMargin + TileHalo;
|
||||
SMinX = TileMinX - Expansion;
|
||||
SMinY = TileMinY - Expansion;
|
||||
SMaxX = TileMaxX + Expansion;
|
||||
SMaxY = TileMaxY + Expansion;
|
||||
const auto CountIf = [](bool bCondition, VoxelDensityProfile::ECounter Counter)
|
||||
{
|
||||
if (bCondition) VoxelDensityProfile::AddCounter(Counter);
|
||||
};
|
||||
CountIf(StrateIdx != CachedStrate, VoxelDensityProfile::ECounter::SdfRebuildStrate);
|
||||
CountIf(static_cast<uint32>(Seed) != CachedSeed, VoxelDensityProfile::ECounter::SdfRebuildSeed);
|
||||
CountIf(ParamsFingerprint != CachedFingerprint, VoxelDensityProfile::ECounter::SdfRebuildFingerprint);
|
||||
CountIf(LayoutVersion != CachedLayout, VoxelDensityProfile::ECounter::SdfRebuildLayout);
|
||||
CountIf(CurrentManagerLifetimeId != CachedManagerLifetimeId,
|
||||
VoxelDensityProfile::ECounter::SdfRebuildManagerLifetime);
|
||||
CountIf(TerrainOps != CachedTerrainOps, VoxelDensityProfile::ECounter::SdfRebuildTerrainOps);
|
||||
CountIf(bUseFusedCacheWindow != CachedUsesFusedCacheWindow,
|
||||
VoxelDensityProfile::ECounter::SdfRebuildFusedPolicy);
|
||||
CountIf(bUseTileCacheWindow != CachedUsesTileCacheWindow,
|
||||
VoxelDensityProfile::ECounter::SdfRebuildTilePolicy);
|
||||
CountIf(bUseTileCacheWindow
|
||||
&& (RequestedTileOrigin != CachedTileOrigin
|
||||
|| RequestedTileStep != CachedTileStep
|
||||
|| RequestedTileCells != CachedTileCells),
|
||||
VoxelDensityProfile::ECounter::SdfRebuildTileIdentity);
|
||||
CountIf(WarpedX < CachedSMinX || WarpedX > CachedSMaxX,
|
||||
VoxelDensityProfile::ECounter::SdfRebuildBoundsX);
|
||||
CountIf(WarpedY < CachedSMinY || WarpedY > CachedSMaxY,
|
||||
VoxelDensityProfile::ECounter::SdfRebuildBoundsY);
|
||||
}
|
||||
else
|
||||
{
|
||||
const float Expansion = WarpMargin + 2.0f;
|
||||
SMinX = ChunkMinX - Expansion;
|
||||
SMinY = ChunkMinY - Expansion;
|
||||
SMaxX = ChunkMaxX + Expansion;
|
||||
SMaxY = ChunkMaxY + Expansion;
|
||||
}
|
||||
|
||||
VoxelCaveMorphology::BuildChunkCache(
|
||||
SDFCache,
|
||||
SMinX, SMinY, SMaxX, SMaxY,
|
||||
DesiredSMinX, DesiredSMinY, DesiredSMaxX, DesiredSMaxY,
|
||||
Params, (uint32)Seed, StrateIdx,
|
||||
TerrainOps,
|
||||
ERoomGraphBuildSite::GeneratorTile
|
||||
);
|
||||
UpdateTunnelCoreTileReach(SDFCache, bUseTileCacheWindow);
|
||||
|
||||
CachedSMinX = SMinX; CachedSMaxX = SMaxX;
|
||||
CachedSMinY = SMinY; CachedSMaxY = SMaxY;
|
||||
CachedSMinX = DesiredSMinX; CachedSMaxX = DesiredSMaxX;
|
||||
CachedSMinY = DesiredSMinY; CachedSMaxY = DesiredSMaxY;
|
||||
CachedStrate = StrateIdx;
|
||||
CachedSeed = (uint32)Seed;
|
||||
CachedFingerprint = ParamsFingerprint;
|
||||
@@ -4398,6 +4542,7 @@ float UVoxelGenerator::GetDensityWithParams(float WorldX, float WorldY, float Wo
|
||||
CachedTileCells = bUseTileCacheWindow ? RequestedTileCells : 0;
|
||||
CachedManagerLifetimeId = CurrentManagerLifetimeId;
|
||||
CachedTerrainOps = TerrainOps;
|
||||
CacheSlot->bValid = true;
|
||||
}
|
||||
|
||||
// Evaluate SDF using cached rooms and tunnels (WARPED coordinates).
|
||||
|
||||
@@ -103,13 +103,15 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
||||
int32 BandZMinVox, int32 BandZMaxVox,
|
||||
int64* OutDensitySampleCount,
|
||||
FVoxelSharedDensityGrid* OutPreEditDensityGrid,
|
||||
const FVoxelSharedDensityGrid* InSharedDensityGrid)
|
||||
const FVoxelSharedDensityGrid* InSharedDensityGrid,
|
||||
std::vector<float>* OutDebugDensitySamples)
|
||||
{
|
||||
TRACE_CPUPROFILER_EVENT_SCOPE(VoxelForge_MesherGenerateMesh);
|
||||
FVoxelMeshData MeshData;
|
||||
if (OutCaptureGrid) { OutCaptureGrid->Reset(); }
|
||||
if (OutPreEditDensityGrid) { OutPreEditDensityGrid->Reset(); }
|
||||
if (OutDensitySampleCount) { *OutDensitySampleCount = 0; }
|
||||
if (OutDebugDensitySamples) { OutDebugDensitySamples->clear(); }
|
||||
if (!Generator) return MeshData;
|
||||
VoxelDensityProfile::FScopedTimer MesherProfileTimer(
|
||||
VoxelDensityProfile::EBucket::MesherGenerateMesh);
|
||||
@@ -284,7 +286,7 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
||||
//
|
||||
// CellsPerAxis = nombre de cellules par axe ; GridDim = points de grille (+1).
|
||||
// Le point de grille (gx,gy,gz) correspond au voxel monde (gx,gy,gz)*Step.
|
||||
// Ordre de remplissage z→y→x : garde le cache SDF (search-box) bien chaud.
|
||||
// The grid fill order is selected below by tile extent to preserve room-cache locality.
|
||||
// On échantillonne avec un anneau de marge de 1 point de chaque côté (indices -1..GridDim)
|
||||
// pour pouvoir calculer les normales par GRADIENT DE GRILLE (T1.b) — différences centrales
|
||||
// sur la grille au lieu de 6 appels densité frais par vertex. La marge utilise les mêmes
|
||||
@@ -869,56 +871,114 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
||||
}
|
||||
#endif
|
||||
}
|
||||
else for (int32 gz = GzLo; gz <= GzHi; gz++)
|
||||
else
|
||||
{
|
||||
if (ShouldAbortWork())
|
||||
auto BuildAxisGroups = [&](int32 AxisOrigin, int32 RegionSize)
|
||||
{
|
||||
if (bUseOperatorBlock) { Generator->EndDensityBlock(); }
|
||||
return FVoxelMeshData();
|
||||
}
|
||||
for (int32 gy = -1; gy <= GridDim; gy++)
|
||||
{
|
||||
if (ShouldAbortWork())
|
||||
std::vector<std::vector<int32>> Groups;
|
||||
int32 PreviousRegion = INT32_MIN;
|
||||
for (int32 Index = -1; Index <= GridDim; ++Index)
|
||||
{
|
||||
if (bUseOperatorBlock) { Generator->EndDensityBlock(); }
|
||||
return FVoxelMeshData();
|
||||
}
|
||||
for (int32 gx = -1; gx <= GridDim; gx++)
|
||||
{
|
||||
const int32 LocalIndex = ((gz + 1) * MDim + (gy + 1)) * MDim + (gx + 1);
|
||||
if (bUseSharedDensityGrid)
|
||||
const int64 WorldCoordinate = static_cast<int64>(AxisOrigin)
|
||||
+ static_cast<int64>(Index) * Step;
|
||||
const int32 Region = FMath::FloorToInt(
|
||||
static_cast<float>(WorldCoordinate) / static_cast<float>(RegionSize));
|
||||
if (Groups.empty() || Region != PreviousRegion)
|
||||
{
|
||||
const int32 SharedX = SharedDeltaX + gx;
|
||||
const int32 SharedY = SharedDeltaY + gy;
|
||||
const int32 SharedZ = SharedDeltaZ + gz;
|
||||
DensityGrid[LocalIndex] = ReuseGrid->Samples[
|
||||
(SharedZ * ReuseGrid->Dim + SharedY) * ReuseGrid->Dim + SharedX];
|
||||
Groups.emplace_back();
|
||||
PreviousRegion = Region;
|
||||
}
|
||||
else
|
||||
Groups.back().push_back(Index);
|
||||
}
|
||||
return Groups;
|
||||
};
|
||||
const int32 RoomCacheRegionSize = CHUNK_SIZE
|
||||
* (VoxelGenLOD::IsUseFusedEvaluatorEnabled() ? 4 : 1);
|
||||
const auto XGroups = BuildAxisGroups(OriginVoxels.X,RoomCacheRegionSize);
|
||||
const auto YGroups = BuildAxisGroups(OriginVoxels.Y,RoomCacheRegionSize);
|
||||
auto SampleGridPoint = [&](int32 gx, int32 gy, int32 gz)
|
||||
{
|
||||
const int32 LocalIndex = ((gz + 1) * MDim + (gy + 1)) * MDim + (gx + 1);
|
||||
if (bUseSharedDensityGrid)
|
||||
{
|
||||
const int32 SharedX = SharedDeltaX + gx;
|
||||
const int32 SharedY = SharedDeltaY + gy;
|
||||
const int32 SharedZ = SharedDeltaZ + gz;
|
||||
DensityGrid[LocalIndex] = ReuseGrid->Samples[
|
||||
(SharedZ * ReuseGrid->Dim + SharedY) * ReuseGrid->Dim + SharedX];
|
||||
}
|
||||
else
|
||||
{
|
||||
const int32 ReachBlockX = FMath::Clamp(gx, 0, CellsPerAxis - 1)
|
||||
/ VoxelGenLOD::TileReachBlockCells;
|
||||
const int32 ReachBlockY = FMath::Clamp(gy, 0, CellsPerAxis - 1)
|
||||
/ VoxelGenLOD::TileReachBlockCells;
|
||||
const int32 ReachBlockZ = FMath::Clamp(gz, 0, CellsPerAxis - 1)
|
||||
/ VoxelGenLOD::TileReachBlockCells;
|
||||
VoxelGenLOD::TileReachBlockIndex =
|
||||
((ReachBlockZ * ReachBlocksPerAxis) + ReachBlockY)
|
||||
* ReachBlocksPerAxis + ReachBlockX;
|
||||
float PreEditDensity = 0.0f;
|
||||
DensityGrid[LocalIndex] = Generator->GetDensityAt(
|
||||
OriginVoxels.X + gx * Step,
|
||||
OriginVoxels.Y + gy * Step,
|
||||
OriginVoxels.Z + gz * Step,
|
||||
bCapturePreEditGrid ? &PreEditDensity : nullptr);
|
||||
if (bCapturePreEditGrid)
|
||||
{
|
||||
// World voxel = tile origin + grid offset scaled by the cell size (Step).
|
||||
const int32 ReachBlockX = FMath::Clamp(gx, 0, CellsPerAxis - 1)
|
||||
/ VoxelGenLOD::TileReachBlockCells;
|
||||
const int32 ReachBlockY = FMath::Clamp(gy, 0, CellsPerAxis - 1)
|
||||
/ VoxelGenLOD::TileReachBlockCells;
|
||||
const int32 ReachBlockZ = FMath::Clamp(gz, 0, CellsPerAxis - 1)
|
||||
/ VoxelGenLOD::TileReachBlockCells;
|
||||
VoxelGenLOD::TileReachBlockIndex =
|
||||
((ReachBlockZ * ReachBlocksPerAxis) + ReachBlockY)
|
||||
* ReachBlocksPerAxis + ReachBlockX;
|
||||
float PreEditDensity = 0.0f;
|
||||
DensityGrid[LocalIndex] = Generator->GetDensityAt(
|
||||
OriginVoxels.X + gx * Step,
|
||||
OriginVoxels.Y + gy * Step,
|
||||
OriginVoxels.Z + gz * Step,
|
||||
bCapturePreEditGrid ? &PreEditDensity : nullptr);
|
||||
if (bCapturePreEditGrid)
|
||||
OutPreEditDensityGrid->Samples[LocalIndex] = PreEditDensity;
|
||||
}
|
||||
if (OutDensitySampleCount)
|
||||
{
|
||||
++(*OutDensitySampleCount);
|
||||
}
|
||||
}
|
||||
};
|
||||
// Very coarse tiles revisit the same XY cache window across a tall Z run. Keep that
|
||||
// vertical run contiguous; SampleGridPoint writes by lattice index, so output bits do
|
||||
// not depend on this traversal. Finer tiles retain plane-first traversal, which has
|
||||
// lower overhead when a tile spans only one room-graph region.
|
||||
const int64 TileExtentVoxels = static_cast<int64>(Step) * CellsPerAxis;
|
||||
const bool bUseVerticalCacheTraversal = TileExtentVoxels > CHUNK_SIZE * 16;
|
||||
if (bUseVerticalCacheTraversal)
|
||||
{
|
||||
for (const std::vector<int32>& YGroup : YGroups)
|
||||
for (const std::vector<int32>& XGroup : XGroups)
|
||||
for (const int32 gy : YGroup)
|
||||
for (const int32 gx : XGroup)
|
||||
{
|
||||
for (int32 gz = GzLo; gz <= GzHi; ++gz)
|
||||
{
|
||||
if (ShouldAbortWork())
|
||||
{
|
||||
OutPreEditDensityGrid->Samples[LocalIndex] = PreEditDensity;
|
||||
if (bUseOperatorBlock) { Generator->EndDensityBlock(); }
|
||||
return FVoxelMeshData();
|
||||
}
|
||||
if (OutDensitySampleCount)
|
||||
SampleGridPoint(gx,gy,gz);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int32 gz = GzLo; gz <= GzHi; ++gz)
|
||||
{
|
||||
if (ShouldAbortWork())
|
||||
{
|
||||
if (bUseOperatorBlock) { Generator->EndDensityBlock(); }
|
||||
return FVoxelMeshData();
|
||||
}
|
||||
for (const std::vector<int32>& YGroup : YGroups)
|
||||
for (const std::vector<int32>& XGroup : XGroups)
|
||||
for (const int32 gy : YGroup)
|
||||
{
|
||||
if (ShouldAbortWork())
|
||||
{
|
||||
++(*OutDensitySampleCount);
|
||||
if (bUseOperatorBlock) { Generator->EndDensityBlock(); }
|
||||
return FVoxelMeshData();
|
||||
}
|
||||
for (const int32 gx : XGroup)
|
||||
{
|
||||
SampleGridPoint(gx,gy,gz);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -943,6 +1003,19 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
||||
{
|
||||
Generator->EndDensityBlock();
|
||||
}
|
||||
if (OutDebugDensitySamples)
|
||||
{
|
||||
const size_t SampleCount = static_cast<size_t>(GzHi - GzLo + 1)
|
||||
* static_cast<size_t>(MDim) * static_cast<size_t>(MDim);
|
||||
OutDebugDensitySamples->reserve(SampleCount);
|
||||
for (int32 gz = GzLo; gz <= GzHi; ++gz)
|
||||
for (int32 gy = -1; gy <= GridDim; ++gy)
|
||||
for (int32 gx = -1; gx <= GridDim; ++gx)
|
||||
{
|
||||
const int32 LocalIndex = ((gz + 1) * MDim + (gy + 1)) * MDim + (gx + 1);
|
||||
OutDebugDensitySamples->push_back(DensityGrid[LocalIndex]);
|
||||
}
|
||||
}
|
||||
MesherDensityGridTimer.End();
|
||||
|
||||
if (bUseProofBlocks)
|
||||
|
||||
@@ -172,6 +172,28 @@ namespace VoxelDensityProfile
|
||||
MesherOutputAllocatedBytes,
|
||||
OpBlockScratchBytes,
|
||||
OpBlockCopyBytes,
|
||||
SdfRebuildStrate,
|
||||
SdfRebuildSeed,
|
||||
SdfRebuildFingerprint,
|
||||
SdfRebuildLayout,
|
||||
SdfRebuildManagerLifetime,
|
||||
SdfRebuildTerrainOps,
|
||||
SdfRebuildFusedPolicy,
|
||||
SdfRebuildTilePolicy,
|
||||
SdfRebuildTileIdentity,
|
||||
SdfRebuildBoundsX,
|
||||
SdfRebuildBoundsY,
|
||||
SdfTileWindowAccepted,
|
||||
SdfTileWindowRejected,
|
||||
TilePolicyKeyOrigin,
|
||||
TilePolicyKeyStep,
|
||||
TilePolicyKeyCells,
|
||||
TilePolicyKeyFingerprint,
|
||||
TilePolicyKeyLayout,
|
||||
TilePolicyKeyManager,
|
||||
TilePolicyKeyTerrainOps,
|
||||
TilePolicyKeyFusedPath,
|
||||
TilePolicyKeyContext,
|
||||
Count
|
||||
};
|
||||
|
||||
|
||||
@@ -433,6 +433,8 @@ private:
|
||||
|
||||
namespace VoxelGenLOD
|
||||
{
|
||||
VOXELFORGE_API void SetUseFusedEvaluatorEnabled(bool bEnabled);
|
||||
VOXELFORGE_API bool IsUseFusedEvaluatorEnabled();
|
||||
// -1 uses the measured path/LOD policy; 0/1 force the spatial broad phase off/on for an A/B
|
||||
// run. The policy affects candidate enumeration only, never cache contents or ordering.
|
||||
VOXELFORGE_API bool ShouldUseSpatialIndex(bool bFusedPath);
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include <atomic>
|
||||
#include <vector>
|
||||
#include "VoxelTypes.h" // Pour FVoxelMeshData, CHUNK_SIZE, VOXEL_SIZE, etc.
|
||||
#include "VoxelGenerator.h"
|
||||
#include "VoxelMarchingCubesMesher.generated.h"
|
||||
@@ -78,7 +79,8 @@ public:
|
||||
int32 BandZMinVox = INT32_MIN, int32 BandZMaxVox = INT32_MAX,
|
||||
int64* OutDensitySampleCount = nullptr,
|
||||
FVoxelSharedDensityGrid* OutPreEditDensityGrid = nullptr,
|
||||
const FVoxelSharedDensityGrid* InSharedDensityGrid = nullptr);
|
||||
const FVoxelSharedDensityGrid* InSharedDensityGrid = nullptr,
|
||||
std::vector<float>* OutDebugDensitySamples = nullptr);
|
||||
|
||||
//=========================================================================
|
||||
// SERVICES (injectés par AVoxelWorld)
|
||||
|
||||
Reference in new issue
Block a user