perf(gen): the landing work was per-voxel -- 342s to 2.6s, same world
The owner played64f5d61and reported the truth: "generation still takes ages... TunnelNetwork doesn't even finish generating, as big as the surface world" while SurfaceWorld completed in under 10s, and stopping PIE hung for 30+ seconds. e08b49a's 21x was real and irrelevant. It fixed per-CHUNK state rebuilds, which dominate the commandlet export path. The game is bound by per-VOXEL evaluation. Same symptom, different bottleneck, and nobody had measured the second one. WHY PIE HUNG bShuttingDown was never checked inside VoxelMarchingCubesMesher.cpp -- zero occurrences. A worker inside GenerateMesh could not abort, so 64f5d61's correct unconditional EndPlay wait was waiting on tiles physically unable to stop. The hang was not a teardown bug; it was evidence that one tile took tens of seconds. Added shutdown polling per slab/row in the mesher and through GenerateTileResult; aborted results carry Epoch + bAborted and ProcessQueue drops them. EndPlay still waits unconditionally -- the 3s mark only logs now. PIE stop 3.409s / 44.759s -> 4 ms teardown, 38 us worker drain WHERE THE PER-VOXEL COST WAS The owner's own comparison was the sharpest instrument available. Profiled both: GetDensityAt TunnelNetwork 12.64 us/sample SurfaceWorld 1.32 tunnel candidates evaluated 7.088 per sample 0 tunnel-core candidates evaluated 7.201 per sample 0 room candidates evaluated 1.000 per sample 0 largest buckets: RoomGraphSource 2.57, TunnelCorePosts 0.76, PassageCarveOp 0.22, PassageStructuralPosts 0.20 The three regressing commits map cleanly, and one is innocent:7a20eeedrainage tree -- cache-build work, NOT per-voxel413c15dlandings -- repeated full passage modification per sample55bf807landing rooms -- room SDF + tunnel/core evaluation per sample Fixes: FPassageCarveOp carves only, landing/support moved to the structural MC post; tunnel core support and SDF fused into one pass; room graph caches shared per worker on complete deterministic keys; tunnel density cache uses open addressing with 4096 slots. Room cache rebuilds fell to 9 per 42,875 samples, so what remains is genuine O(local cached room/tunnel count) work. TunnelNetwork 128^3, seed 1337 slot 1, same region: 342.2 s / 163.161 us/vox -> 2.623 s / 1.251 us/vox (130x) Geometry SHA-256 aaf95559..066b4, IDENTICAL to the pre-optimisation run at the same roughness. 153,692 triangles either way. Verified independently of the agent's own report. NOT FIXED, and the first one is the next real target: - L4 tiles still cost 16.93 s. Hit rate falls 99.9% (L0) -> 23.7% (L4), but 1024 -> 4096 slots only moved 18.39s -> 16.93s and the 1,927 misses ARE the distinct key count. That is low reuse, not eviction. A bigger cache will not save it. - TunnelNetwork is still ~10x SurfaceWorld (0.354s vs 0.028s on a matched 32^3). - The live PIE tile table is SurfaceWorld: this checkout's PIE map is configured that way. TunnelNetwork went through the same mesher and density path via the commandlet, but no TunnelNetwork tile has been timed in an actual game session. Export numbers are not presented as game numbers. Still outstanding, untouched here: the inter-strate passage has intersections it should not have, routes through open passage when it should not, and should descend from a low point of the current strate. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012aheo2crrZmHuYtPpdY2Cb
This commit is contained in:
1 parent
64f5d61e08
commit
0576e800c4
15 files changed
+1042
-131
No files matched your search
@@ -340,7 +340,6 @@ namespace
|
||||
EVoxelOpChannelMask ChannelWrites() const override { return VoxelOpChannels::Density; }
|
||||
bool IsAdditive() const override { return true; }
|
||||
void PrepareChunk(const FVoxelOpContext&) override {}
|
||||
|
||||
void Eval(float WorldX, float WorldY, float WorldZ, FVoxelOpSample& InOut) const override
|
||||
{
|
||||
if (!(Strength > 0.0f)) { return; }
|
||||
@@ -1816,7 +1815,9 @@ namespace
|
||||
{
|
||||
if (const UVoxelStrateManager* LiveManager = Manager.Get())
|
||||
{
|
||||
LiveManager->ApplyPassageModifier(InOut.Density, X, Y, Z, Base, Seal);
|
||||
// Landing/tunnel support is a shared MC-space post after disturbances. Calling the
|
||||
// legacy full modifier here repeats those same writers before the shared post.
|
||||
LiveManager->ApplyPassageCarvingOnly(InOut.Density, X, Y, Z, Base, Seal);
|
||||
}
|
||||
// Keep the source and op-stack operation order identical: the origin room is carved
|
||||
// before the boundary seal, then its support floor is reasserted after the passage
|
||||
@@ -2780,6 +2781,85 @@ namespace
|
||||
uint32 Salt;
|
||||
};
|
||||
|
||||
// The mesher's coarse samples are sparse enough that one sample can land in a different
|
||||
// exact 32-voxel chunk every time. Keeping one SDF cache inside every exact-chunk op-stack
|
||||
// therefore turns L4 into thousands of BuildChunkCache calls. These worker-local entries
|
||||
// share the immutable room graph cache by its complete deterministic input key. A wider
|
||||
// region is safe because BuildChunkCache's collect/store construction is window-invariant;
|
||||
// the key still includes every value that can change the graph or its terrain-op rolls.
|
||||
constexpr int32 RoomGraphCacheSlotCount = 128;
|
||||
|
||||
struct FRoomGraphCacheEntry
|
||||
{
|
||||
bool bValid = false;
|
||||
uint32 Seed = 0;
|
||||
int32 StrateIndex = INT32_MIN;
|
||||
uint32 ParamsFingerprint = 0xFFFFFFFFu;
|
||||
uint32 LayoutVersion = 0xFFFFFFFFu;
|
||||
uint64 ManagerLifetimeId = 0;
|
||||
const TArray<FStrateTerrainOpEntry>* TerrainOps = nullptr;
|
||||
int32 RegionMinX = 0;
|
||||
int32 RegionMinY = 0;
|
||||
int32 RegionSize = 0;
|
||||
FChunkSDFCache Cache;
|
||||
};
|
||||
|
||||
static thread_local FRoomGraphCacheEntry GRoomGraphCache[RoomGraphCacheSlotCount];
|
||||
|
||||
FORCEINLINE int32 RoomGraphCacheSlot(
|
||||
uint32 Seed, int32 StrateIndex, uint32 ParamsFingerprint, uint32 LayoutVersion,
|
||||
uint64 ManagerLifetimeId, int32 RegionMinX, int32 RegionMinY, int32 RegionSize)
|
||||
{
|
||||
uint32 Hash = Seed * 0x9E3779B9u;
|
||||
Hash ^= static_cast<uint32>(StrateIndex) * 0x85EBCA6Bu;
|
||||
Hash ^= ParamsFingerprint * 0xC2B2AE35u;
|
||||
Hash ^= LayoutVersion * 0x27D4EB2Fu;
|
||||
Hash ^= static_cast<uint32>(ManagerLifetimeId)
|
||||
^ static_cast<uint32>(ManagerLifetimeId >> 32);
|
||||
Hash ^= static_cast<uint32>(RegionMinX) * 0x165667B1u;
|
||||
Hash ^= static_cast<uint32>(RegionMinY) * 0xD3A2646Cu;
|
||||
Hash ^= static_cast<uint32>(RegionSize) * 0xFD7046C5u;
|
||||
Hash ^= Hash >> 16;
|
||||
return static_cast<int32>(Hash & (RoomGraphCacheSlotCount - 1));
|
||||
}
|
||||
|
||||
FRoomGraphCacheEntry* FindRoomGraphCacheEntry(
|
||||
uint32 Seed, int32 StrateIndex, uint32 ParamsFingerprint, uint32 LayoutVersion,
|
||||
uint64 ManagerLifetimeId, const TArray<FStrateTerrainOpEntry>* TerrainOps,
|
||||
int32 RegionMinX, int32 RegionMinY, int32 RegionSize)
|
||||
{
|
||||
const int32 StartSlot = RoomGraphCacheSlot(
|
||||
Seed, StrateIndex, ParamsFingerprint, LayoutVersion, ManagerLifetimeId,
|
||||
RegionMinX, RegionMinY, RegionSize);
|
||||
FRoomGraphCacheEntry* ReuseEntry = nullptr;
|
||||
for (int32 Probe = 0; Probe < RoomGraphCacheSlotCount; ++Probe)
|
||||
{
|
||||
FRoomGraphCacheEntry& Candidate = GRoomGraphCache[
|
||||
(StartSlot + Probe) & (RoomGraphCacheSlotCount - 1)];
|
||||
if (!Candidate.bValid)
|
||||
{
|
||||
return ReuseEntry != nullptr ? ReuseEntry : &Candidate;
|
||||
}
|
||||
if (Candidate.Seed == Seed
|
||||
&& Candidate.StrateIndex == StrateIndex
|
||||
&& Candidate.ParamsFingerprint == ParamsFingerprint
|
||||
&& Candidate.LayoutVersion == LayoutVersion
|
||||
&& Candidate.ManagerLifetimeId == ManagerLifetimeId
|
||||
&& Candidate.TerrainOps == TerrainOps
|
||||
&& Candidate.RegionMinX == RegionMinX
|
||||
&& Candidate.RegionMinY == RegionMinY
|
||||
&& Candidate.RegionSize == RegionSize)
|
||||
{
|
||||
return &Candidate;
|
||||
}
|
||||
if (ReuseEntry == nullptr && Candidate.ManagerLifetimeId != ManagerLifetimeId)
|
||||
{
|
||||
ReuseEntry = &Candidate;
|
||||
}
|
||||
}
|
||||
return ReuseEntry != nullptr ? ReuseEntry : &GRoomGraphCache[StartSlot];
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// RÔLE 1 — SOURCE : GRAPHE DE SALLES / ROOM GRAPH (TunnelNetwork)
|
||||
//=========================================================================
|
||||
@@ -2833,29 +2913,17 @@ namespace
|
||||
// Dans l'original tout cela vit dans des `thread_local` d'une seule fonction de 1080 lignes ;
|
||||
// ici la source les possède et les expose.
|
||||
//
|
||||
// ⚠️ `static` (donc PARTAGÉ ENTRE INSTANCES), pas un membre : c'est exactement ce que fait
|
||||
// l'original, et le contrôle 3 du test en DÉPEND — deux piles construites côte à côte se
|
||||
// partagent ce cache, et c'est l'empreinte de params dans la clé (pas une copie par pile) qui
|
||||
// les empêche de se servir mutuellement leurs salles. En faire un membre ferait passer ce
|
||||
// contrôle pour de mauvaises raisons.
|
||||
//
|
||||
// ⚠️ MÊME MOTIF QUE `FOverhangShelfMod` ← `FSurfaceColumnSource` et `FShaftLedgeMod` ←
|
||||
// `FShaftFieldSource` : un opérateur possède l'état, les autres le lisent par pointeur non
|
||||
// possédant remis à la construction. C'est un motif ÉTABLI dans ce fichier, pas une invention.
|
||||
//
|
||||
// Per-worker state, deliberately `static` (shared between instances) because that is what the
|
||||
// original does and what the test's stale-cache check rests on. Detail ops read it through a
|
||||
// non-owning pointer, the same way the surface and shaft ports already do.
|
||||
// The state belongs to this source instance. The outer tunnel cache retains the immutable
|
||||
// op stack, so retaining the source's prepared SDF window with that stack avoids rebuilding
|
||||
// the room graph every time a coarse tile switches between cached chunk keys. It remains
|
||||
// worker-local because each op stack lives in the thread-local density cache.
|
||||
struct FState
|
||||
{
|
||||
// Fallback only for the invalid-owner path. Normal evaluations point at the worker-local
|
||||
// shared cache bank below, so switching exact chunk op-stacks does not rebuild the room
|
||||
// graph merely because the stack object changed.
|
||||
FChunkSDFCache Cache;
|
||||
float CachedSMinX = 1.0f, CachedSMaxX = -1.0f; // invalide au départ (min > max)
|
||||
float CachedSMinY = 0.0f, CachedSMaxY = 0.0f;
|
||||
int32 CachedStrate = INT32_MIN;
|
||||
uint32 CachedSeed = 0;
|
||||
uint32 CachedFingerprint = 0xFFFFFFFFu;
|
||||
uint32 CachedLayout = 0xFFFFFFFFu;
|
||||
uint64 CachedManagerLifetimeId = 0;
|
||||
const FChunkSDFCache* ActiveCache = nullptr;
|
||||
|
||||
/** La salle de SDF minimal pour le dernier voxel évalué. -1 = aucune. */
|
||||
int32 NearestRoom = -1;
|
||||
@@ -2874,14 +2942,23 @@ namespace
|
||||
bool bLocalParamsValid = false;
|
||||
};
|
||||
|
||||
static FState& State()
|
||||
FState& State() const
|
||||
{
|
||||
thread_local FState S;
|
||||
return S;
|
||||
// The prepared SDF data lives in the worker-local keyed cache bank above. This state
|
||||
// is only scratch for the current EvalSample: Eval resets NearestRoom and the local
|
||||
// parameter memo before its detail ops consume them. A single TLS slot is therefore
|
||||
// safe even when this worker switches between immutable source instances, and avoids
|
||||
// a map lookup on every sample. A mutable member is not safe because stacks can be
|
||||
// shared by concurrent readers during generation.
|
||||
static thread_local FState Scratch;
|
||||
return Scratch;
|
||||
}
|
||||
|
||||
/** Le cache que la source vient de bâtir/servir pour ce voxel. Lu par les colonnes (4d). */
|
||||
const FChunkSDFCache& GetCache() const { return State().Cache; }
|
||||
const FChunkSDFCache& GetCache() const
|
||||
{
|
||||
return State().ActiveCache != nullptr ? *State().ActiveCache : State().Cache;
|
||||
}
|
||||
|
||||
/** L'index de la salle la plus proche pour le dernier voxel évalué. -1 = aucune.
|
||||
* Lu par les arches, les dômes, le pincement et le biais de sol. */
|
||||
@@ -2896,7 +2973,7 @@ namespace
|
||||
{
|
||||
const FVector& Position = State().LastWorldPosition;
|
||||
return VoxelCaveMorphology::EvaluateTunnelCoreWorldSDF(
|
||||
Position.X, Position.Y, Position.Z, State().Cache);
|
||||
Position.X, Position.Y, Position.Z, GetCache());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2933,9 +3010,10 @@ namespace
|
||||
if (!S.bLocalParamsValid)
|
||||
{
|
||||
S.LocalParams = P;
|
||||
if (S.NearestRoom >= 0 && S.Cache.Rooms.IsValidIndex(S.NearestRoom))
|
||||
const FChunkSDFCache& Cache = GetCache();
|
||||
if (S.NearestRoom >= 0 && Cache.Rooms.IsValidIndex(S.NearestRoom))
|
||||
{
|
||||
const FCachedRoom& NR = S.Cache.Rooms[S.NearestRoom];
|
||||
const FCachedRoom& NR = Cache.Rooms[S.NearestRoom];
|
||||
if (NR.RoomOp)
|
||||
{
|
||||
// N'écrit que les champs propres au type de l'op ; tout le reste garde la
|
||||
@@ -2964,7 +3042,7 @@ namespace
|
||||
*/
|
||||
float ProbeSdfUnwarped(float X, float Y, float Z) const
|
||||
{
|
||||
return VoxelCaveMorphology::EvaluateSDFCached(X, Y, Z, State().Cache, P.SDFBlendRadius);
|
||||
return VoxelCaveMorphology::EvaluateSDFCached(X, Y, Z, GetCache(), P.SDFBlendRadius);
|
||||
}
|
||||
|
||||
/** Le Z « effectif » : `VerticalScale` étire le monde AVANT le bruit. Pure fonction de Z et
|
||||
@@ -3002,11 +3080,16 @@ namespace
|
||||
if (ManagerLifetimeId != 0 && LiveManager == nullptr)
|
||||
{
|
||||
S.Cache = FChunkSDFCache();
|
||||
S.ActiveCache = &S.Cache;
|
||||
InOut.Sdf = FLT_MAX;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(P.RoomDensity > 0.0f && P.RoomSpacing > 0.0f)) { return; } // Sdf reste FLT_MAX
|
||||
if (!(P.RoomDensity > 0.0f && P.RoomSpacing > 0.0f))
|
||||
{
|
||||
S.ActiveCache = &S.Cache;
|
||||
return; // Sdf reste FLT_MAX
|
||||
}
|
||||
|
||||
const float EffectiveZ = EffZ(WorldZ);
|
||||
|
||||
@@ -3078,51 +3161,78 @@ namespace
|
||||
StrateIdx = SI_Index;
|
||||
}
|
||||
|
||||
const bool bNeedRebuild =
|
||||
StrateIdx != S.CachedStrate || SeedU != S.CachedSeed ||
|
||||
ParamsFingerprint != S.CachedFingerprint || LayoutVersion != S.CachedLayout ||
|
||||
ManagerLifetimeId != S.CachedManagerLifetimeId ||
|
||||
WarpedX < S.CachedSMinX || WarpedX > S.CachedSMaxX ||
|
||||
WarpedY < S.CachedSMinY || WarpedY > S.CachedSMaxY;
|
||||
// A coarse LOD samples a new exact 32-voxel chunk at nearly every point. The graph is
|
||||
// window-invariant, so group those samples into a larger deterministic XY region. The
|
||||
// step is supplied by the mesher through VoxelGenLOD TLS; ordinary point queries keep
|
||||
// the original one-chunk window. The cache key retains the exact params/strate/op-pool
|
||||
// identity, so no blended chunk can borrow another chunk's room graph.
|
||||
const int32 SampleStep = FMath::Max(VoxelGenLOD::SampleStep, 1);
|
||||
const int32 RegionChunks = SampleStep <= 1
|
||||
? 1
|
||||
: SampleStep >= 32
|
||||
? 4
|
||||
: FMath::Clamp((SampleStep + 3) / 4, 1, 4);
|
||||
const int32 RegionSize = CHUNK_SIZE * RegionChunks;
|
||||
const int32 RegionCellX = FMath::FloorToInt(WorldX / (float)RegionSize);
|
||||
const int32 RegionCellY = FMath::FloorToInt(WorldY / (float)RegionSize);
|
||||
const int32 RegionMinX = RegionCellX * RegionSize;
|
||||
const int32 RegionMinY = RegionCellY * RegionSize;
|
||||
|
||||
if (bNeedRebuild)
|
||||
const TArray<FStrateTerrainOpEntry>* TerrainOps = nullptr;
|
||||
if (LiveManager)
|
||||
{
|
||||
const int32 CacheChunkX = FMath::FloorToInt(WorldX / (float)CHUNK_SIZE);
|
||||
const int32 CacheChunkY = FMath::FloorToInt(WorldY / (float)CHUNK_SIZE);
|
||||
const float ChunkMinX = CacheChunkX * (float)CHUNK_SIZE;
|
||||
const float ChunkMinY = CacheChunkY * (float)CHUNK_SIZE;
|
||||
const float ChunkMaxX = ChunkMinX + (float)CHUNK_SIZE;
|
||||
const float ChunkMaxY = ChunkMinY + (float)CHUNK_SIZE;
|
||||
const float Expansion = P.CaveWarpStrength + 2.0f;
|
||||
|
||||
const float SMinX = ChunkMinX - Expansion;
|
||||
const float SMinY = ChunkMinY - Expansion;
|
||||
const float SMaxX = ChunkMaxX + Expansion;
|
||||
const float SMaxY = ChunkMaxY + Expansion;
|
||||
|
||||
const TArray<FStrateTerrainOpEntry>* TerrainOps = nullptr;
|
||||
if (LiveManager)
|
||||
{
|
||||
const int32 ChunkZ = FMath::FloorToInt(WorldZ / (float)CHUNK_SIZE);
|
||||
UVoxelStrateDefinition* Def = LiveManager->GetStrateForChunk(
|
||||
FIntVector(CacheChunkX, CacheChunkY, ChunkZ));
|
||||
if (Def) { TerrainOps = &Def->TerrainOperations; }
|
||||
}
|
||||
|
||||
VoxelCaveMorphology::BuildChunkCache(
|
||||
S.Cache, SMinX, SMinY, SMaxX, SMaxY, P, SeedU, StrateIdx, TerrainOps);
|
||||
|
||||
S.CachedSMinX = SMinX; S.CachedSMaxX = SMaxX;
|
||||
S.CachedSMinY = SMinY; S.CachedSMaxY = SMaxY;
|
||||
S.CachedStrate = StrateIdx;
|
||||
S.CachedSeed = SeedU;
|
||||
S.CachedFingerprint = ParamsFingerprint;
|
||||
S.CachedLayout = LayoutVersion;
|
||||
S.CachedManagerLifetimeId = ManagerLifetimeId;
|
||||
const int32 ChunkX = FMath::FloorToInt(WorldX / (float)CHUNK_SIZE);
|
||||
const int32 ChunkY = FMath::FloorToInt(WorldY / (float)CHUNK_SIZE);
|
||||
const int32 ChunkZ = FMath::FloorToInt(WorldZ / (float)CHUNK_SIZE);
|
||||
UVoxelStrateDefinition* Def = LiveManager->GetStrateForChunk(
|
||||
FIntVector(ChunkX, ChunkY, ChunkZ));
|
||||
if (Def) { TerrainOps = &Def->TerrainOperations; }
|
||||
}
|
||||
|
||||
FRoomGraphCacheEntry* SharedCache = FindRoomGraphCacheEntry(
|
||||
SeedU, StrateIdx, ParamsFingerprint, LayoutVersion, ManagerLifetimeId,
|
||||
TerrainOps, RegionMinX, RegionMinY, RegionSize);
|
||||
const bool bCacheMatches = SharedCache->bValid
|
||||
&& SharedCache->Seed == SeedU
|
||||
&& SharedCache->StrateIndex == StrateIdx
|
||||
&& SharedCache->ParamsFingerprint == ParamsFingerprint
|
||||
&& SharedCache->LayoutVersion == LayoutVersion
|
||||
&& SharedCache->ManagerLifetimeId == ManagerLifetimeId
|
||||
&& SharedCache->TerrainOps == TerrainOps
|
||||
&& SharedCache->RegionMinX == RegionMinX
|
||||
&& SharedCache->RegionMinY == RegionMinY
|
||||
&& SharedCache->RegionSize == RegionSize;
|
||||
if (!bCacheMatches)
|
||||
{
|
||||
if (VoxelDensityProfile::IsEnabled())
|
||||
{
|
||||
VoxelDensityProfile::AddCounter(
|
||||
VoxelDensityProfile::ECounter::SdfCacheBuild);
|
||||
}
|
||||
SharedCache->bValid = false;
|
||||
SharedCache->Seed = SeedU;
|
||||
SharedCache->StrateIndex = StrateIdx;
|
||||
SharedCache->ParamsFingerprint = ParamsFingerprint;
|
||||
SharedCache->LayoutVersion = LayoutVersion;
|
||||
SharedCache->ManagerLifetimeId = ManagerLifetimeId;
|
||||
SharedCache->TerrainOps = TerrainOps;
|
||||
SharedCache->RegionMinX = RegionMinX;
|
||||
SharedCache->RegionMinY = RegionMinY;
|
||||
SharedCache->RegionSize = RegionSize;
|
||||
const float Expansion = P.CaveWarpStrength + 2.0f;
|
||||
VoxelCaveMorphology::BuildChunkCache(
|
||||
SharedCache->Cache,
|
||||
(float)RegionMinX - Expansion,
|
||||
(float)RegionMinY - Expansion,
|
||||
(float)(RegionMinX + RegionSize) + Expansion,
|
||||
(float)(RegionMinY + RegionSize) + Expansion,
|
||||
P, SeedU, StrateIdx, TerrainOps);
|
||||
SharedCache->bValid = true;
|
||||
}
|
||||
S.ActiveCache = &SharedCache->Cache;
|
||||
|
||||
float CaveSDF = VoxelCaveMorphology::EvaluateSDFCached(
|
||||
WarpedX, WarpedY, WarpedZ, S.Cache, P.SDFBlendRadius, &S.NearestRoom);
|
||||
WarpedX, WarpedY, WarpedZ, GetCache(), P.SDFBlendRadius, &S.NearestRoom);
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// PITS & CHEMINÉES — coordonnées RÉELLES, SmoothMin dans le même canal SDF
|
||||
@@ -3132,7 +3242,7 @@ namespace
|
||||
// mais à des coordonnées NON warpées. Sous un modèle de frames il aurait fallu les sortir
|
||||
// du frame tout en gardant le canal — exprimable, mais tordu. Dans un opérateur unique la
|
||||
// difficulté disparaît : le warp est une variable locale, pas un contexte hérité.
|
||||
for (const FCachedPit& Pit : S.Cache.Pits)
|
||||
for (const FCachedPit& Pit : GetCache().Pits)
|
||||
{
|
||||
const float DZ = WorldZ - Pit.TopZ;
|
||||
if (DZ >= Pit.BlendK) { continue; }
|
||||
@@ -3160,7 +3270,7 @@ namespace
|
||||
CaveSDF = VoxelSDF::SmoothMin(CaveSDF, PitSDF, Pit.BlendK);
|
||||
}
|
||||
|
||||
for (const FCachedChimney& Chim : S.Cache.Chimneys)
|
||||
for (const FCachedChimney& Chim : GetCache().Chimneys)
|
||||
{
|
||||
const float DZ = WorldZ - Chim.BottomZ;
|
||||
if (-DZ >= Chim.BlendK) { continue; }
|
||||
@@ -3641,7 +3751,7 @@ namespace
|
||||
{
|
||||
const FVector& Position = State().LastWorldPosition;
|
||||
return VoxelCaveMorphology::IsTunnelSupportFloorWorldPoint(
|
||||
Position.X, Position.Y, Position.Z, State().Cache);
|
||||
Position.X, Position.Y, Position.Z, GetCache());
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -3820,6 +3930,7 @@ namespace
|
||||
EVoxelOpChannelMask ChannelWrites() const override { return VoxelOpChannels::Density; }
|
||||
bool IsAdditive() const override { return true; }
|
||||
void PrepareChunk(const FVoxelOpContext&) override {}
|
||||
const TCHAR* DebugName() const override { return TEXT("CaveRoughnessMod"); }
|
||||
|
||||
void Eval(float WorldX, float WorldY, float WorldZ, FVoxelOpSample& InOut) const override
|
||||
{
|
||||
@@ -4017,6 +4128,7 @@ namespace
|
||||
EVoxelOpResourceMask RequiredResources() const override { return VoxelOpResources::RoomGeometry; }
|
||||
bool IsAdditive() const override { return true; }
|
||||
void PrepareChunk(const FVoxelOpContext&) override {}
|
||||
const TCHAR* DebugName() const override { return TEXT("CaveTerraceMod"); }
|
||||
|
||||
void Eval(float WorldX, float WorldY, float WorldZ, FVoxelOpSample& InOut) const override
|
||||
{
|
||||
@@ -4127,6 +4239,7 @@ namespace
|
||||
EVoxelOpResourceMask RequiredResources() const override { return VoxelOpResources::RoomGeometry; }
|
||||
bool IsAdditive() const override { return true; }
|
||||
void PrepareChunk(const FVoxelOpContext&) override {}
|
||||
const TCHAR* DebugName() const override { return TEXT("LayerLineMod"); }
|
||||
|
||||
void Eval(float, float, float WorldZ, FVoxelOpSample& InOut) const override
|
||||
{
|
||||
@@ -4216,6 +4329,7 @@ namespace
|
||||
EVoxelOpResourceMask RequiredResources() const override { return VoxelOpResources::RoomGeometry; }
|
||||
bool IsAdditive() const override { return true; }
|
||||
void PrepareChunk(const FVoxelOpContext&) override {}
|
||||
const TCHAR* DebugName() const override { return TEXT("RibbingMod"); }
|
||||
|
||||
void Eval(float, float, float WorldZ, FVoxelOpSample& InOut) const override
|
||||
{
|
||||
@@ -4300,6 +4414,7 @@ namespace
|
||||
EVoxelOpResourceMask RequiredResources() const override { return VoxelOpResources::RoomGeometry; }
|
||||
bool IsAdditive() const override { return true; }
|
||||
void PrepareChunk(const FVoxelOpContext&) override {}
|
||||
const TCHAR* DebugName() const override { return TEXT("CaveOverhangMod"); }
|
||||
|
||||
void Eval(float WorldX, float WorldY, float WorldZ, FVoxelOpSample& InOut) const override
|
||||
{
|
||||
@@ -4401,6 +4516,7 @@ namespace
|
||||
EVoxelOpResourceMask RequiredResources() const override { return VoxelOpResources::RoomGeometry; }
|
||||
bool IsAdditive() const override { return true; }
|
||||
void PrepareChunk(const FVoxelOpContext&) override {}
|
||||
const TCHAR* DebugName() const override { return TEXT("CaveCliffMod"); }
|
||||
|
||||
void Eval(float WorldX, float WorldY, float WorldZ, FVoxelOpSample& InOut) const override
|
||||
{
|
||||
@@ -4476,6 +4592,7 @@ namespace
|
||||
EVoxelOpResourceMask RequiredResources() const override { return VoxelOpResources::RoomGeometry; }
|
||||
bool IsAdditive() const override { return true; }
|
||||
void PrepareChunk(const FVoxelOpContext&) override {}
|
||||
const TCHAR* DebugName() const override { return TEXT("ScallopMod"); }
|
||||
|
||||
void Eval(float WorldX, float WorldY, float WorldZ, FVoxelOpSample& InOut) const override
|
||||
{
|
||||
@@ -4568,6 +4685,7 @@ namespace
|
||||
EVoxelOpResourceMask RequiredResources() const override { return VoxelOpResources::RoomGeometry; }
|
||||
bool IsAdditive() const override { return true; }
|
||||
void PrepareChunk(const FVoxelOpContext&) override {}
|
||||
const TCHAR* DebugName() const override { return TEXT("CaveArchMod"); }
|
||||
|
||||
void Eval(float WorldX, float WorldY, float WorldZ, FVoxelOpSample& InOut) const override
|
||||
{
|
||||
@@ -4685,6 +4803,7 @@ namespace
|
||||
EVoxelOpResourceMask RequiredResources() const override { return VoxelOpResources::RoomGeometry; }
|
||||
bool IsAdditive() const override { return true; }
|
||||
void PrepareChunk(const FVoxelOpContext&) override {}
|
||||
const TCHAR* DebugName() const override { return TEXT("RoomColumnMod"); }
|
||||
|
||||
void Eval(float WorldX, float WorldY, float, FVoxelOpSample& InOut) const override
|
||||
{
|
||||
@@ -4749,6 +4868,7 @@ namespace
|
||||
EVoxelOpResourceMask RequiredResources() const override { return VoxelOpResources::RoomGeometry; }
|
||||
bool IsAdditive() const override { return true; }
|
||||
void PrepareChunk(const FVoxelOpContext&) override {}
|
||||
const TCHAR* DebugName() const override { return TEXT("DomeMod"); }
|
||||
|
||||
void Eval(float WorldX, float WorldY, float WorldZ, FVoxelOpSample& InOut) const override
|
||||
{
|
||||
@@ -4857,6 +4977,7 @@ namespace
|
||||
EVoxelOpResourceMask RequiredResources() const override { return VoxelOpResources::RoomGeometry; }
|
||||
bool IsAdditive() const override { return true; }
|
||||
void PrepareChunk(const FVoxelOpContext&) override {}
|
||||
const TCHAR* DebugName() const override { return TEXT("PinchMod"); }
|
||||
|
||||
void Eval(float WorldX, float WorldY, float WorldZ, FVoxelOpSample& InOut) const override
|
||||
{
|
||||
@@ -4977,6 +5098,7 @@ namespace
|
||||
EVoxelOpResourceMask RequiredResources() const override { return VoxelOpResources::RoomGeometry; }
|
||||
bool IsAdditive() const override { return true; }
|
||||
void PrepareChunk(const FVoxelOpContext&) override {}
|
||||
const TCHAR* DebugName() const override { return TEXT("FloorBiasMod"); }
|
||||
|
||||
void Eval(float, float, float WorldZ, FVoxelOpSample& InOut) const override
|
||||
{
|
||||
@@ -5815,10 +5937,9 @@ namespace VoxelDensityOps
|
||||
OutStack.AppendStructuralPost(P.StrateTopWorldZ, P.StrateBottomWorldZ,
|
||||
P.BoundarySealThickness, P.BaseDensity,
|
||||
SpineRadius, StrateManager, false);
|
||||
OutStack.Add(MakeUnique<FCaveTunnelFloorOp>(RoomPtr, P.BaseDensity));
|
||||
OutStack.Add(MakeUnique<FCaveTunnelAirOp>(
|
||||
RoomPtr, P.BaseDensity,
|
||||
VoxelPassageGeometry::CaveTunnelAirCoreInsetVoxels));
|
||||
// The generator's shared MC post evaluates the prepared tunnel-core cache after
|
||||
// disturbances. Keeping these pre-disturbance graph scans here duplicated both the
|
||||
// support and air queries for every voxel; the feature remains in that shared post.
|
||||
OutStack.Add(MakeUnique<FXYEdgeSealOp>(P.BaseDensity));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user