perf(gen): 5x fewer candidates, 16x cheaper floor -- and 0% end-to-end. Read the last section.
Owner, after playing 0576e80: "slightly faster, still not on par... I can outrun the
generation and fall." Also, unprompted: "the tunnels seem forced, with a forced
flat -- it technically looks good, but could be a reason." He was right that it
was a reason; it was not THE reason.
WHAT LANDED, all real and all measured
Conservative spatial index (immutable per-chunk uniform bins, baked conservative
bounds, squared rejects, deterministic ordering):
candidates scanned/sample 25.140 -> 5.192
candidates evaluated/sample 13.879 -> 2.992
cave tunnels 11.228/6.069 -> 1.525/0.723
tunnel core 11.228/5.831 -> 1.216/0.567
The earlier flat AABB attempt failed because loose bounds culled almost nothing
while adding a pass. Technique from GPU Gems 3 ch.32 broad-phase.
The forced-flat floor, which the owner spotted by eye: a floor decision is
inherently per-COLUMN and was being recomputed for every Z sample in that column.
Now resolved once per integer XY column and reused.
TunnelCorePosts 2.29 us/sample -> 0.138 us/sample (16x)
The floor is unchanged in behaviour: still applied only after a tunnel passes its
world bounds and walkable projection, still walkable, look untouched.
FIRST MEASUREMENT OF OUR MEMORY FOOTPRINT
The owner: "it shouldn't be pressured on such system, even less when not running
anything." He is right, and it is ours:
total resident worker cache 431,162,116 B = 411 MiB
15 workers 27.41 MiB each
average valid entry 278,704 B
largest entry 541,344 B
static slot storage 2.4 MiB (dynamic: 428.6 MB)
Cutting 4096 -> 128 slots only saved static storage. The weight is 272 KB PER
CACHED CHUNK STATE, ~102 live per worker. Nobody has yet asked why one cached
chunk state needs 272 KB. That is the open question, not the slot count.
⚠️ AND THE PART THAT MATTERS MOST: THIS BOUGHT NOTHING END-TO-END
128^3 TunnelNetwork, seed 1337 slot 1, same region, unprofiled:
before this commit 2.623 s
after, 3 runs 2.705 / 2.678 / 2.682 s
Flat. A 5x candidate reduction and a 16x cheaper floor moved real generation time
by zero.
The reason is visible in the numbers and I should have caught it earlier: the
PROFILED path reports 14.74 us/sample while the real unprofiled cost is
1.28 us/voxel. **The instrument inflates by ~11x.** Every bucket we have been
ranking and optimising for two rounds was measured under that inflation, so the
ranking does not reflect where real time goes. We optimised what the profiler
exaggerated.
⇒ Before any further optimisation: make the profiler cheap enough that profiled
and unprofiled totals agree within a small factor, or switch to sampling. An
alarming number is the instrument until proven otherwise -- three for three in
this project, and now four.
Still true: ~21x slower than the pre-op-stack build, and generation still loses
to a walking player. Geometry changed by 40 triangles (153,692 -> 153,732), which
is allowed; two full exports hash identically (37E00BEE..0A7DF). Build 42/42.
No CPU-feature dispatch. Nothing outside Plugins/VoxelForge was touched.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012aheo2crrZmHuYtPpdY2Cb
This commit is contained in:
1 parent
0576e800c4
commit
ee89c4d8c2
11 files changed
+2380
-388
No files matched your search
File diff suppressed because it is too large.
Load diff
@@ -90,21 +90,20 @@ namespace
|
|||||||
static constexpr float VF_PerlinAbsBound = 1.5f;
|
static constexpr float VF_PerlinAbsBound = 1.5f;
|
||||||
|
|
||||||
/** La même enveloppe que `FractalNoise3D` de VoxelGenerator.cpp (qui y est `static`, donc
|
/** La même enveloppe que `FractalNoise3D` de VoxelGenerator.cpp (qui y est `static`, donc
|
||||||
* invisible ici). Le détour par `FVector` est délibéré — voir l'en-tête de ce fichier. */
|
* invisible ici). Les coordonnées de bruit restent en float : FVector est double dans UE5
|
||||||
FORCEINLINE float HFractal3D(const FVector& Position, int32 Octaves = 4,
|
* et son aller-retour ne servait qu'à conserver l'ancien arrondi, exigence abandonnée. */
|
||||||
|
FORCEINLINE float HFractal3D(const FVector3f& Position, int32 Octaves = 4,
|
||||||
float Lacunarity = 2.0f, float Persistence = 0.5f)
|
float Lacunarity = 2.0f, float Persistence = 0.5f)
|
||||||
{
|
{
|
||||||
return VoxelNoise::FBM((float)Position.X, (float)Position.Y, (float)Position.Z,
|
return VoxelNoise::FBM(Position.X, Position.Y, Position.Z,
|
||||||
Octaves, Lacunarity, Persistence);
|
Octaves, Lacunarity, Persistence);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Idem pour `RidgedNoise3D` (également `static` dans VoxelGenerator.cpp). Le bruit cellulaire,
|
/** Idem pour `RidgedNoise3D` (également `static` dans VoxelGenerator.cpp). */
|
||||||
* lui, n'a pas besoin d'enveloppe : son corps a migré dans VoxelCaveMorphology.h et s'appelle
|
FORCEINLINE float HRidged3D(const FVector3f& Position, int32 Octaves = 4,
|
||||||
* `VoxelNoise::Cellular3D`, avec la MÊME signature `const FVector&` que l'original. */
|
|
||||||
FORCEINLINE float HRidged3D(const FVector& Position, int32 Octaves = 4,
|
|
||||||
float Lacunarity = 2.0f, float Persistence = 0.5f)
|
float Lacunarity = 2.0f, float Persistence = 0.5f)
|
||||||
{
|
{
|
||||||
return VoxelNoise::Ridged((float)Position.X, (float)Position.Y, (float)Position.Z,
|
return VoxelNoise::Ridged(Position.X, Position.Y, Position.Z,
|
||||||
Octaves, Lacunarity, Persistence);
|
Octaves, Lacunarity, Persistence);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -285,12 +284,12 @@ namespace
|
|||||||
void Eval(float WorldX, float WorldY, float WorldZ, FVoxelOpSample& InOut) const override
|
void Eval(float WorldX, float WorldY, float WorldZ, FVoxelOpSample& InOut) const override
|
||||||
{
|
{
|
||||||
const float Frequency = 1.0f / CellSize;
|
const float Frequency = 1.0f / CellSize;
|
||||||
const FVector NoisePos(
|
const FVector3f NoisePos(
|
||||||
WorldX * Frequency + VoxelHash::SeedOffset(SeedU, 3.17f),
|
WorldX * Frequency + VoxelHash::SeedOffset(SeedU, 3.17f),
|
||||||
WorldY * Frequency + VoxelHash::SeedOffset(SeedU, 7.31f),
|
WorldY * Frequency + VoxelHash::SeedOffset(SeedU, 7.31f),
|
||||||
WorldZ * Frequency + VoxelHash::SeedOffset(SeedU, 11.47f));
|
WorldZ * Frequency + VoxelHash::SeedOffset(SeedU, 11.47f));
|
||||||
const float Ribbon = FMath::Clamp(
|
const float Ribbon = FMath::Clamp(
|
||||||
FMath::Abs(VoxelNoise::FBM((float)NoisePos.X, (float)NoisePos.Y, (float)NoisePos.Z,
|
FMath::Abs(VoxelNoise::FBM(NoisePos.X, NoisePos.Y, NoisePos.Z,
|
||||||
VoxelGenLOD::Eff(3), 2.0f, 0.5f))
|
VoxelGenLOD::Eff(3), 2.0f, 0.5f))
|
||||||
* VOXEL_NOISE_SCALE,
|
* VOXEL_NOISE_SCALE,
|
||||||
0.0f, 2.0f);
|
0.0f, 2.0f);
|
||||||
@@ -343,12 +342,12 @@ namespace
|
|||||||
void Eval(float WorldX, float WorldY, float WorldZ, FVoxelOpSample& InOut) const override
|
void Eval(float WorldX, float WorldY, float WorldZ, FVoxelOpSample& InOut) const override
|
||||||
{
|
{
|
||||||
if (!(Strength > 0.0f)) { return; }
|
if (!(Strength > 0.0f)) { return; }
|
||||||
const FVector NoisePos(
|
const FVector3f NoisePos(
|
||||||
WorldX * Frequency + VoxelHash::SeedOffset(SeedU, 13.2f),
|
WorldX * Frequency + VoxelHash::SeedOffset(SeedU, 13.2f),
|
||||||
WorldY * Frequency + VoxelHash::SeedOffset(SeedU, 17.8f),
|
WorldY * Frequency + VoxelHash::SeedOffset(SeedU, 17.8f),
|
||||||
WorldZ * Frequency + VoxelHash::SeedOffset(SeedU, 23.4f));
|
WorldZ * Frequency + VoxelHash::SeedOffset(SeedU, 23.4f));
|
||||||
const float Noise01 = FMath::Clamp(
|
const float Noise01 = FMath::Clamp(
|
||||||
VoxelNoise::FBM((float)NoisePos.X, (float)NoisePos.Y, (float)NoisePos.Z,
|
VoxelNoise::FBM(NoisePos.X, NoisePos.Y, NoisePos.Z,
|
||||||
VoxelGenLOD::Eff(Octaves), 2.0f, 0.5f) * 0.5f + 0.5f,
|
VoxelGenLOD::Eff(Octaves), 2.0f, 0.5f) * 0.5f + 0.5f,
|
||||||
0.0f, 1.0f);
|
0.0f, 1.0f);
|
||||||
const float Delta = Strength * Noise01;
|
const float Delta = Strength * Noise01;
|
||||||
@@ -693,17 +692,16 @@ namespace
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Les deux surfaces, transcrites au caractère près depuis GetSlabDensity — y compris le
|
// The surface coordinates stay in float; the old FVector round-trip only preserved
|
||||||
// détour par FVector, qui est le même piège d'arrondi que dans FSdfRoughnessMod
|
// legacy rounding and is no longer part of the output contract.
|
||||||
// (float → double → float sous /fp:fast). Ne pas « simplifier ».
|
|
||||||
float SurfaceFloor(float WorldX, float WorldY) const
|
float SurfaceFloor(float WorldX, float WorldY) const
|
||||||
{
|
{
|
||||||
if (FloorRoughness <= 0.0f) { return FloorZ; }
|
if (FloorRoughness <= 0.0f) { return FloorZ; }
|
||||||
const float FF = FloorFrequency;
|
const float FF = FloorFrequency;
|
||||||
const FVector NoisePos(WorldX * FF + VoxelHash::SeedOffset(SeedU, 7.3f),
|
const FVector3f NoisePos(WorldX * FF + VoxelHash::SeedOffset(SeedU, 7.3f),
|
||||||
WorldY * FF + VoxelHash::SeedOffset(SeedU, 11.1f),
|
WorldY * FF + VoxelHash::SeedOffset(SeedU, 11.1f),
|
||||||
0.0f);
|
0.0f);
|
||||||
const float N = VoxelNoise::FBM((float)NoisePos.X, (float)NoisePos.Y, (float)NoisePos.Z,
|
const float N = VoxelNoise::FBM(NoisePos.X, NoisePos.Y, NoisePos.Z,
|
||||||
VoxelGenLOD::Eff(3), 2.0f, 0.5f)
|
VoxelGenLOD::Eff(3), 2.0f, 0.5f)
|
||||||
* VOXEL_NOISE_SCALE * FloorRoughness;
|
* VOXEL_NOISE_SCALE * FloorRoughness;
|
||||||
return FloorZ + N;
|
return FloorZ + N;
|
||||||
@@ -715,10 +713,10 @@ namespace
|
|||||||
if (CeilRoughness > 0.0f)
|
if (CeilRoughness > 0.0f)
|
||||||
{
|
{
|
||||||
const float CF = CeilFrequency;
|
const float CF = CeilFrequency;
|
||||||
const FVector NoisePos(WorldX * CF + VoxelHash::SeedOffset(SeedU, 17.3f) + 1000.0f,
|
const FVector3f NoisePos(WorldX * CF + VoxelHash::SeedOffset(SeedU, 17.3f) + 1000.0f,
|
||||||
WorldY * CF + VoxelHash::SeedOffset(SeedU, 19.7f) + 2000.0f,
|
WorldY * CF + VoxelHash::SeedOffset(SeedU, 19.7f) + 2000.0f,
|
||||||
3000.0f);
|
3000.0f);
|
||||||
const float Raw = VoxelNoise::FBM((float)NoisePos.X, (float)NoisePos.Y, (float)NoisePos.Z,
|
const float Raw = VoxelNoise::FBM(NoisePos.X, NoisePos.Y, NoisePos.Z,
|
||||||
VoxelGenLOD::Eff(3), 2.0f, 0.5f)
|
VoxelGenLOD::Eff(3), 2.0f, 0.5f)
|
||||||
* VOXEL_NOISE_SCALE;
|
* VOXEL_NOISE_SCALE;
|
||||||
// abs() ⇒ les formations ne pendent QUE vers le bas.
|
// abs() ⇒ les formations ne pendent QUE vers le bas.
|
||||||
@@ -1199,7 +1197,7 @@ namespace
|
|||||||
const float f = P.OverhangFrequency;
|
const float f = P.OverhangFrequency;
|
||||||
// Bruit de forme d'étagère [0,1] ; le terme en Z fait onduler la portée avec la hauteur
|
// Bruit de forme d'étagère [0,1] ; le terme en Z fait onduler la portée avec la hauteur
|
||||||
// (déchiqueté, pas une lèvre lisse).
|
// (déchiqueté, pas une lèvre lisse).
|
||||||
const float Ns = HFractal3D(FVector(
|
const float Ns = HFractal3D(FVector3f(
|
||||||
WorldX * f + VoxelHash::SeedOffset(SeedU, 17.3f),
|
WorldX * f + VoxelHash::SeedOffset(SeedU, 17.3f),
|
||||||
WorldY * f + VoxelHash::SeedOffset(SeedU, 23.9f),
|
WorldY * f + VoxelHash::SeedOffset(SeedU, 23.9f),
|
||||||
WorldZ * f * P.OverhangZScale + VoxelHash::SeedOffset(SeedU, 5.1f)), 3) * 0.5f + 0.5f; // [0,1]
|
WorldZ * f * P.OverhangZScale + VoxelHash::SeedOffset(SeedU, 5.1f)), 3) * 0.5f + 0.5f; // [0,1]
|
||||||
@@ -1259,24 +1257,10 @@ namespace
|
|||||||
{
|
{
|
||||||
if (Strength <= 0.0f || InOut.Sdf >= ApplyWithin) { return; }
|
if (Strength <= 0.0f || InOut.Sdf >= ApplyWithin) { return; }
|
||||||
|
|
||||||
// ⚠️ LE DÉTOUR PAR FVector EST DÉLIBÉRÉ — ne pas « simplifier ».
|
// Keep hot noise coordinates in float. The old FVector round-trip only preserved
|
||||||
// L'original écrit `FractalNoise3D(FVector(WorldX * 0.12f, ...), Eff(3))`, et
|
// legacy rounding; output compatibility with that field is intentionally abandoned.
|
||||||
// FractalNoise3D fait `VoxelNoise::FBM((float)Position.X, ...)`. FVector étant en
|
const FVector3f NoisePos(WorldX * Frequency, WorldY * Frequency, WorldZ * Frequency);
|
||||||
// DOUBLE (UE5), le produit flottant y transite par un double avant d'être re-arrondi
|
InOut.Sdf += VoxelNoise::FBM(NoisePos.X, NoisePos.Y, NoisePos.Z,
|
||||||
// en float. Passer directement des floats saute cet aller-retour, et sous /fp:fast
|
|
||||||
// les deux chemins ne s'arrondissent pas au même endroit : ~1 ULP d'écart sur le SDF,
|
|
||||||
// qui ressort en 1 ULP sur la densité finale. Reproduire le détour, c'est reproduire
|
|
||||||
// l'arrondi. VERIFIE par MazeEquivalence : le détour conserve l'identité binaire avec
|
|
||||||
// GetMazeDensity (0 écart sur 20,000 échantillons). Si cela change, le candidat suivant
|
|
||||||
// est une contraction FMA entre unités de compilation.
|
|
||||||
//
|
|
||||||
// THE FVector ROUND-TRIP IS DELIBERATE — do not "simplify" it. MazeEquivalence
|
|
||||||
// verifies the resulting path against GetMazeDensity (0 differences / 20,000 samples).
|
|
||||||
// The original goes float -> double (FVector is double in UE5) -> float; going straight
|
|
||||||
// through floats skips a rounding step, and under /fp:fast the two paths round in
|
|
||||||
// different places. Reproducing the detour reproduces the rounding.
|
|
||||||
const FVector NoisePos(WorldX * Frequency, WorldY * Frequency, WorldZ * Frequency);
|
|
||||||
InOut.Sdf += VoxelNoise::FBM((float)NoisePos.X, (float)NoisePos.Y, (float)NoisePos.Z,
|
|
||||||
VoxelGenLOD::Eff(BaseOctaves), 2.0f, 0.5f)
|
VoxelGenLOD::Eff(BaseOctaves), 2.0f, 0.5f)
|
||||||
* VOXEL_NOISE_SCALE * Strength;
|
* VOXEL_NOISE_SCALE * Strength;
|
||||||
}
|
}
|
||||||
@@ -1300,9 +1284,8 @@ namespace
|
|||||||
}
|
}
|
||||||
if (Strength <= 0.0f) { return; }
|
if (Strength <= 0.0f) { return; }
|
||||||
|
|
||||||
// The noise input is float after the deliberate FVector round-trip in Eval. If an
|
// If an authored frequency/box product overflows, Eval can publish a non-finite SDF
|
||||||
// authored frequency/box product overflows, Eval can publish a non-finite SDF and no
|
// and no finite interval is a proof. Unknown costs the skip and protects the geometry.
|
||||||
// finite interval is a proof. Unknown costs the skip and protects the geometry.
|
|
||||||
const float MaxAbsCoord = FMath::Max3(
|
const float MaxAbsCoord = FMath::Max3(
|
||||||
FMath::Max(FMath::Abs((float)VoxelBox.Min.X), FMath::Abs((float)VoxelBox.Max.X)),
|
FMath::Max(FMath::Abs((float)VoxelBox.Min.X), FMath::Abs((float)VoxelBox.Max.X)),
|
||||||
FMath::Max(FMath::Abs((float)VoxelBox.Min.Y), FMath::Abs((float)VoxelBox.Max.Y)),
|
FMath::Max(FMath::Abs((float)VoxelBox.Min.Y), FMath::Abs((float)VoxelBox.Max.Y)),
|
||||||
@@ -2546,10 +2529,10 @@ namespace
|
|||||||
// partagé par toutes les îles proches — chacune échantillonne une autre partie du champ,
|
// partagé par toutes les îles proches — chacune échantillonne une autre partie du champ,
|
||||||
// d'où des silhouettes distinctes.
|
// d'où des silhouettes distinctes.
|
||||||
const float WarpAmp = (P.IslandMinRadius + P.IslandMaxRadius) * 0.5f * 0.35f;
|
const float WarpAmp = (P.IslandMinRadius + P.IslandMaxRadius) * 0.5f * 0.35f;
|
||||||
const float WX = WorldX + HFractal3D(FVector(WorldX * 0.04f + VoxelHash::SeedOffset(Salt, 0.0007f),
|
const float WX = WorldX + HFractal3D(FVector3f(WorldX * 0.04f + VoxelHash::SeedOffset(Salt, 0.0007f),
|
||||||
WorldY * 0.04f, WorldZ * 0.012f), VoxelGenLOD::Eff(3))
|
WorldY * 0.04f, WorldZ * 0.012f), VoxelGenLOD::Eff(3))
|
||||||
* VOXEL_NOISE_SCALE * WarpAmp;
|
* VOXEL_NOISE_SCALE * WarpAmp;
|
||||||
const float WY = WorldY + HFractal3D(FVector(WorldX * 0.04f + 31.0f, WorldY * 0.04f + 7.0f,
|
const float WY = WorldY + HFractal3D(FVector3f(WorldX * 0.04f + 31.0f, WorldY * 0.04f + 7.0f,
|
||||||
WorldZ * 0.012f), VoxelGenLOD::Eff(3))
|
WorldZ * 0.012f), VoxelGenLOD::Eff(3))
|
||||||
* VOXEL_NOISE_SCALE * WarpAmp;
|
* VOXEL_NOISE_SCALE * WarpAmp;
|
||||||
|
|
||||||
@@ -2931,8 +2914,112 @@ namespace
|
|||||||
/** The warped SDF-space position belonging to the current voxel. */
|
/** The warped SDF-space position belonging to the current voxel. */
|
||||||
FVector LastWarpedPosition = FVector::ZeroVector;
|
FVector LastWarpedPosition = FVector::ZeroVector;
|
||||||
|
|
||||||
/** The unwarped position belonging to the current voxel. */
|
/** The unwarped position belonging to the current voxel. */
|
||||||
FVector LastWorldPosition = FVector::ZeroVector;
|
FVector LastWorldPosition = FVector::ZeroVector;
|
||||||
|
|
||||||
|
// Support-floor projection depends only on XY for one immutable SDF cache. The
|
||||||
|
// mesher traverses Z outside X/Y, so keep a small worker-local LRU of XY boxes rather
|
||||||
|
// than only the immediately previous column; each integer column is then projected
|
||||||
|
// once and both structural posts reuse its interval for every Z sample.
|
||||||
|
struct FSupportColumnBox
|
||||||
|
{
|
||||||
|
static constexpr int32 Halo = CHUNK_SIZE + 8;
|
||||||
|
static constexpr int32 Dim = 2 * Halo + 1;
|
||||||
|
|
||||||
|
const FChunkSDFCache* Cache = nullptr;
|
||||||
|
int32 BaseX = 0;
|
||||||
|
int32 BaseY = 0;
|
||||||
|
uint32 LastUse = 0;
|
||||||
|
bool bValid = false;
|
||||||
|
TArray<int32> SlotToEntry;
|
||||||
|
TArray<FTunnelSupportFloorColumn> Columns;
|
||||||
|
|
||||||
|
void Clear()
|
||||||
|
{
|
||||||
|
Cache = nullptr;
|
||||||
|
bValid = false;
|
||||||
|
LastUse = 0;
|
||||||
|
SlotToEntry.Reset();
|
||||||
|
Columns.Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
FTunnelSupportFloorColumn* AcquireColumn(
|
||||||
|
int32 X, int32 Y, const FChunkSDFCache* InCache,
|
||||||
|
uint32 InUse, bool& bOutNew)
|
||||||
|
{
|
||||||
|
bOutNew = false;
|
||||||
|
if (!bValid || Cache != InCache
|
||||||
|
|| X < BaseX || X >= BaseX + Dim
|
||||||
|
|| Y < BaseY || Y >= BaseY + Dim)
|
||||||
|
{
|
||||||
|
Cache = InCache;
|
||||||
|
BaseX = X - Halo;
|
||||||
|
BaseY = Y - Halo;
|
||||||
|
LastUse = InUse;
|
||||||
|
bValid = true;
|
||||||
|
SlotToEntry.Init(INDEX_NONE, Dim * Dim);
|
||||||
|
Columns.Reset();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LastUse = InUse;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int32 Slot = (Y - BaseY) * Dim + (X - BaseX);
|
||||||
|
int32& EntryIndex = SlotToEntry[Slot];
|
||||||
|
if (EntryIndex == INDEX_NONE)
|
||||||
|
{
|
||||||
|
EntryIndex = Columns.AddDefaulted();
|
||||||
|
bOutNew = true;
|
||||||
|
}
|
||||||
|
return &Columns[EntryIndex];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct FSupportColumnCache
|
||||||
|
{
|
||||||
|
static constexpr int32 NumBoxes = 4;
|
||||||
|
FSupportColumnBox Boxes[NumBoxes];
|
||||||
|
uint32 Clock = 0;
|
||||||
|
|
||||||
|
void Invalidate()
|
||||||
|
{
|
||||||
|
for (FSupportColumnBox& Box : Boxes)
|
||||||
|
{
|
||||||
|
Box.Clear();
|
||||||
|
}
|
||||||
|
Clock = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
FSupportColumnBox& AcquireBox(
|
||||||
|
int32 X, int32 Y, const FChunkSDFCache* Cache)
|
||||||
|
{
|
||||||
|
++Clock;
|
||||||
|
for (FSupportColumnBox& Box : Boxes)
|
||||||
|
{
|
||||||
|
if (Box.bValid && Box.Cache == Cache
|
||||||
|
&& X >= Box.BaseX && X < Box.BaseX + FSupportColumnBox::Dim
|
||||||
|
&& Y >= Box.BaseY && Y < Box.BaseY + FSupportColumnBox::Dim)
|
||||||
|
{
|
||||||
|
Box.LastUse = Clock;
|
||||||
|
return Box;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FSupportColumnBox* Victim = &Boxes[0];
|
||||||
|
for (FSupportColumnBox& Box : Boxes)
|
||||||
|
{
|
||||||
|
if (!Box.bValid || Box.LastUse < Victim->LastUse)
|
||||||
|
{
|
||||||
|
Victim = &Box;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Victim->Clear();
|
||||||
|
return *Victim;
|
||||||
|
}
|
||||||
|
} SupportColumns;
|
||||||
|
|
||||||
|
FTunnelSupportFloorColumn FractionalSupportColumn;
|
||||||
|
|
||||||
/** ÉTAPE C1 — les params de la strate avec l'op de CETTE salle appliqué par-dessus.
|
/** ÉTAPE C1 — les params de la strate avec l'op de CETTE salle appliqué par-dessus.
|
||||||
* Mémo par voxel : invalidé au début de chaque `Eval`, calculé au PREMIER modificateur
|
* Mémo par voxel : invalidé au début de chaque `Eval`, calculé au PREMIER modificateur
|
||||||
@@ -3081,6 +3168,7 @@ namespace
|
|||||||
{
|
{
|
||||||
S.Cache = FChunkSDFCache();
|
S.Cache = FChunkSDFCache();
|
||||||
S.ActiveCache = &S.Cache;
|
S.ActiveCache = &S.Cache;
|
||||||
|
S.SupportColumns.Invalidate();
|
||||||
InOut.Sdf = FLT_MAX;
|
InOut.Sdf = FLT_MAX;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -3088,6 +3176,7 @@ namespace
|
|||||||
if (!(P.RoomDensity > 0.0f && P.RoomSpacing > 0.0f))
|
if (!(P.RoomDensity > 0.0f && P.RoomSpacing > 0.0f))
|
||||||
{
|
{
|
||||||
S.ActiveCache = &S.Cache;
|
S.ActiveCache = &S.Cache;
|
||||||
|
S.SupportColumns.Invalidate();
|
||||||
return; // Sdf reste FLT_MAX
|
return; // Sdf reste FLT_MAX
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3106,15 +3195,15 @@ namespace
|
|||||||
{
|
{
|
||||||
const float WF = P.CaveWarpFrequency;
|
const float WF = P.CaveWarpFrequency;
|
||||||
const float WS = P.CaveWarpStrength;
|
const float WS = P.CaveWarpStrength;
|
||||||
WarpedX += VoxelNoise::Perlin3D(FVector(
|
WarpedX += VoxelNoise::Perlin3D(FVector3f(
|
||||||
WorldX * WF + VoxelHash::SeedOffset(SeedU, 0.37f),
|
WorldX * WF + VoxelHash::SeedOffset(SeedU, 0.37f),
|
||||||
WorldY * WF + 1.3f,
|
WorldY * WF + 1.3f,
|
||||||
EffectiveZ * WF + 5.7f)) * VOXEL_NOISE_SCALE * WS;
|
EffectiveZ * WF + 5.7f)) * VOXEL_NOISE_SCALE * WS;
|
||||||
WarpedY += VoxelNoise::Perlin3D(FVector(
|
WarpedY += VoxelNoise::Perlin3D(FVector3f(
|
||||||
WorldX * WF + 7.1f,
|
WorldX * WF + 7.1f,
|
||||||
WorldY * WF + VoxelHash::SeedOffset(SeedU, 0.59f),
|
WorldY * WF + VoxelHash::SeedOffset(SeedU, 0.59f),
|
||||||
EffectiveZ * WF + 2.3f)) * VOXEL_NOISE_SCALE * WS;
|
EffectiveZ * WF + 2.3f)) * VOXEL_NOISE_SCALE * WS;
|
||||||
WarpedZ += VoxelNoise::Perlin3D(FVector(
|
WarpedZ += VoxelNoise::Perlin3D(FVector3f(
|
||||||
WorldX * WF + 11.3f,
|
WorldX * WF + 11.3f,
|
||||||
WorldY * WF + 9.7f,
|
WorldY * WF + 9.7f,
|
||||||
EffectiveZ * WF + VoxelHash::SeedOffset(SeedU, 0.41f))) * VOXEL_NOISE_SCALE * WS;
|
EffectiveZ * WF + VoxelHash::SeedOffset(SeedU, 0.41f))) * VOXEL_NOISE_SCALE * WS;
|
||||||
@@ -3219,7 +3308,8 @@ namespace
|
|||||||
SharedCache->RegionMinX = RegionMinX;
|
SharedCache->RegionMinX = RegionMinX;
|
||||||
SharedCache->RegionMinY = RegionMinY;
|
SharedCache->RegionMinY = RegionMinY;
|
||||||
SharedCache->RegionSize = RegionSize;
|
SharedCache->RegionSize = RegionSize;
|
||||||
const float Expansion = P.CaveWarpStrength + 2.0f;
|
const float Expansion = FMath::Abs(P.CaveWarpStrength)
|
||||||
|
* VOXEL_NOISE_SCALE * VF_PerlinAbsBound + 2.0f;
|
||||||
VoxelCaveMorphology::BuildChunkCache(
|
VoxelCaveMorphology::BuildChunkCache(
|
||||||
SharedCache->Cache,
|
SharedCache->Cache,
|
||||||
(float)RegionMinX - Expansion,
|
(float)RegionMinX - Expansion,
|
||||||
@@ -3228,6 +3318,8 @@ namespace
|
|||||||
(float)(RegionMinY + RegionSize) + Expansion,
|
(float)(RegionMinY + RegionSize) + Expansion,
|
||||||
P, SeedU, StrateIdx, TerrainOps);
|
P, SeedU, StrateIdx, TerrainOps);
|
||||||
SharedCache->bValid = true;
|
SharedCache->bValid = true;
|
||||||
|
VoxelDensityOps::ReportWorkerRoomGraphCacheFootprint();
|
||||||
|
S.SupportColumns.Invalidate();
|
||||||
}
|
}
|
||||||
S.ActiveCache = &SharedCache->Cache;
|
S.ActiveCache = &SharedCache->Cache;
|
||||||
|
|
||||||
@@ -3749,9 +3841,48 @@ namespace
|
|||||||
|
|
||||||
bool IsTunnelSupportFloor() const
|
bool IsTunnelSupportFloor() const
|
||||||
{
|
{
|
||||||
const FVector& Position = State().LastWorldPosition;
|
FState& S = State();
|
||||||
return VoxelCaveMorphology::IsTunnelSupportFloorWorldPoint(
|
const FVector& Position = S.LastWorldPosition;
|
||||||
Position.X, Position.Y, Position.Z, GetCache());
|
const FChunkSDFCache& Cache = GetCache();
|
||||||
|
if (VoxelDensityProfile::IsEnabled())
|
||||||
|
{
|
||||||
|
VoxelDensityProfile::AddCounter(
|
||||||
|
VoxelDensityProfile::ECounter::TunnelSupportFloorQueries);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Density-grid samples are integer XY. Keep fractional callers exact by using a
|
||||||
|
// scratch column; they are uncommon gradient/debug queries and must not alias a
|
||||||
|
// neighbouring integer column.
|
||||||
|
const bool bIntegerXY =
|
||||||
|
Position.X == FMath::FloorToFloat(static_cast<float>(Position.X))
|
||||||
|
&& Position.Y == FMath::FloorToFloat(static_cast<float>(Position.Y));
|
||||||
|
if (bIntegerXY)
|
||||||
|
{
|
||||||
|
const int32 IX = FMath::FloorToInt(static_cast<float>(Position.X));
|
||||||
|
const int32 IY = FMath::FloorToInt(static_cast<float>(Position.Y));
|
||||||
|
FState::FSupportColumnBox& Box = S.SupportColumns.AcquireBox(IX, IY, &Cache);
|
||||||
|
bool bNewColumn = false;
|
||||||
|
FTunnelSupportFloorColumn* Column = Box.AcquireColumn(
|
||||||
|
IX, IY, &Cache, S.SupportColumns.Clock, bNewColumn);
|
||||||
|
if (bNewColumn)
|
||||||
|
{
|
||||||
|
VoxelCaveMorphology::BuildTunnelSupportFloorColumn(
|
||||||
|
static_cast<float>(Position.X),
|
||||||
|
static_cast<float>(Position.Y),
|
||||||
|
Cache,
|
||||||
|
*Column);
|
||||||
|
}
|
||||||
|
return VoxelCaveMorphology::IsTunnelSupportFloorColumnZ(
|
||||||
|
static_cast<float>(Position.Z), *Column);
|
||||||
|
}
|
||||||
|
|
||||||
|
VoxelCaveMorphology::BuildTunnelSupportFloorColumn(
|
||||||
|
static_cast<float>(Position.X),
|
||||||
|
static_cast<float>(Position.Y),
|
||||||
|
Cache,
|
||||||
|
S.FractionalSupportColumn);
|
||||||
|
return VoxelCaveMorphology::IsTunnelSupportFloorColumnZ(
|
||||||
|
static_cast<float>(Position.Z), S.FractionalSupportColumn);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -3947,15 +4078,14 @@ namespace
|
|||||||
|
|
||||||
const float RF = P.RoughnessFrequency;
|
const float RF = P.RoughnessFrequency;
|
||||||
|
|
||||||
// ⚠️ LE DÉTOUR PAR FVector EST DÉLIBÉRÉ (même raison que dans FSdfRoughnessMod) :
|
// Keep both hot noise-coordinate chains in float. The old FVector round-trip only
|
||||||
// FVector est en DOUBLE, donc chaque produit transite par un double avant d'être
|
// preserved legacy rounding; output compatibility with that field is abandoned.
|
||||||
// re-arrondi en float à l'appel du bruit. Sauter l'aller-retour change l'arrondi.
|
FVector3f MainPos(
|
||||||
FVector MainPos(
|
|
||||||
WorldX * RF + VoxelHash::SeedOffset(SeedU, 11.3f),
|
WorldX * RF + VoxelHash::SeedOffset(SeedU, 11.3f),
|
||||||
WorldY * RF + VoxelHash::SeedOffset(SeedU, 13.7f),
|
WorldY * RF + VoxelHash::SeedOffset(SeedU, 13.7f),
|
||||||
EffectiveZ * RF + VoxelHash::SeedOffset(SeedU, 17.1f)
|
EffectiveZ * RF + VoxelHash::SeedOffset(SeedU, 17.1f)
|
||||||
);
|
);
|
||||||
FVector FinePos(
|
FVector3f FinePos(
|
||||||
WorldX * RF * 3.0f + VoxelHash::SeedOffset(SeedU, 19.1f) + 2000.0f,
|
WorldX * RF * 3.0f + VoxelHash::SeedOffset(SeedU, 19.1f) + 2000.0f,
|
||||||
WorldY * RF * 3.0f + VoxelHash::SeedOffset(SeedU, 23.7f) + 2500.0f,
|
WorldY * RF * 3.0f + VoxelHash::SeedOffset(SeedU, 23.7f) + 2500.0f,
|
||||||
EffectiveZ * RF * 3.0f + VoxelHash::SeedOffset(SeedU, 29.3f) + 3000.0f
|
EffectiveZ * RF * 3.0f + VoxelHash::SeedOffset(SeedU, 29.3f) + 3000.0f
|
||||||
@@ -3969,25 +4099,25 @@ namespace
|
|||||||
const float WF = P.DomainWarpFrequency;
|
const float WF = P.DomainWarpFrequency;
|
||||||
const float WS = P.DomainWarpStrength;
|
const float WS = P.DomainWarpStrength;
|
||||||
|
|
||||||
const float WarpX = VoxelNoise::Perlin3D(FVector(
|
const float WarpX = VoxelNoise::Perlin3D(FVector3f(
|
||||||
WorldX * WF + VoxelHash::SeedOffset(SeedU, 5.2f),
|
WorldX * WF + VoxelHash::SeedOffset(SeedU, 5.2f),
|
||||||
WorldY * WF + VoxelHash::SeedOffset(SeedU, 1.3f),
|
WorldY * WF + VoxelHash::SeedOffset(SeedU, 1.3f),
|
||||||
EffectiveZ * WF + VoxelHash::SeedOffset(SeedU, 9.7f)
|
EffectiveZ * WF + VoxelHash::SeedOffset(SeedU, 9.7f)
|
||||||
)) * VOXEL_NOISE_SCALE * WS;
|
)) * VOXEL_NOISE_SCALE * WS;
|
||||||
|
|
||||||
const float WarpY = VoxelNoise::Perlin3D(FVector(
|
const float WarpY = VoxelNoise::Perlin3D(FVector3f(
|
||||||
WorldX * WF + 100.0f + VoxelHash::SeedOffset(SeedU, 7.7f),
|
WorldX * WF + 100.0f + VoxelHash::SeedOffset(SeedU, 7.7f),
|
||||||
WorldY * WF + 200.0f + VoxelHash::SeedOffset(SeedU, 3.1f),
|
WorldY * WF + 200.0f + VoxelHash::SeedOffset(SeedU, 3.1f),
|
||||||
EffectiveZ * WF + 300.0f
|
EffectiveZ * WF + 300.0f
|
||||||
)) * VOXEL_NOISE_SCALE * WS;
|
)) * VOXEL_NOISE_SCALE * WS;
|
||||||
|
|
||||||
const float WarpZ = VoxelNoise::Perlin3D(FVector(
|
const float WarpZ = VoxelNoise::Perlin3D(FVector3f(
|
||||||
WorldX * WF + 400.0f,
|
WorldX * WF + 400.0f,
|
||||||
WorldY * WF + 500.0f + VoxelHash::SeedOffset(SeedU, 11.9f),
|
WorldY * WF + 500.0f + VoxelHash::SeedOffset(SeedU, 11.9f),
|
||||||
EffectiveZ * WF + 600.0f + VoxelHash::SeedOffset(SeedU, 13.3f)
|
EffectiveZ * WF + 600.0f + VoxelHash::SeedOffset(SeedU, 13.3f)
|
||||||
)) * VOXEL_NOISE_SCALE * WS;
|
)) * VOXEL_NOISE_SCALE * WS;
|
||||||
|
|
||||||
const FVector WarpOffset(WarpX, WarpY, WarpZ);
|
const FVector3f WarpOffset(WarpX, WarpY, WarpZ);
|
||||||
MainPos += WarpOffset;
|
MainPos += WarpOffset;
|
||||||
FinePos += WarpOffset;
|
FinePos += WarpOffset;
|
||||||
}
|
}
|
||||||
@@ -4161,7 +4291,7 @@ namespace
|
|||||||
float NoisedZ = WorldZ;
|
float NoisedZ = WorldZ;
|
||||||
if (LP.TerraceNoiseDisplacement > 0.0f)
|
if (LP.TerraceNoiseDisplacement > 0.0f)
|
||||||
{
|
{
|
||||||
const float DispNoise = HFractal3D(FVector(
|
const float DispNoise = HFractal3D(FVector3f(
|
||||||
WorldX * 0.04f + VoxelHash::SeedOffset(SeedU, 31.1f),
|
WorldX * 0.04f + VoxelHash::SeedOffset(SeedU, 31.1f),
|
||||||
WorldY * 0.04f + VoxelHash::SeedOffset(SeedU, 37.3f),
|
WorldY * 0.04f + VoxelHash::SeedOffset(SeedU, 37.3f),
|
||||||
WorldZ * 0.02f + VoxelHash::SeedOffset(SeedU, 41.7f)
|
WorldZ * 0.02f + VoxelHash::SeedOffset(SeedU, 41.7f)
|
||||||
@@ -4432,7 +4562,7 @@ namespace
|
|||||||
? (WorldZ / P.VerticalScale) : WorldZ;
|
? (WorldZ / P.VerticalScale) : WorldZ;
|
||||||
|
|
||||||
// Fréquence en Z à 0.15× celle de XY ⇒ les motifs s'étirent horizontalement.
|
// Fréquence en Z à 0.15× celle de XY ⇒ les motifs s'étirent horizontalement.
|
||||||
const float OverhangNoise = HFractal3D(FVector(
|
const float OverhangNoise = HFractal3D(FVector3f(
|
||||||
WorldX * LP.OverhangFrequency + VoxelHash::SeedOffset(SeedU, 53.1f),
|
WorldX * LP.OverhangFrequency + VoxelHash::SeedOffset(SeedU, 53.1f),
|
||||||
WorldY * LP.OverhangFrequency + VoxelHash::SeedOffset(SeedU, 59.3f),
|
WorldY * LP.OverhangFrequency + VoxelHash::SeedOffset(SeedU, 59.3f),
|
||||||
EffectiveZ * LP.OverhangFrequency * 0.15f + VoxelHash::SeedOffset(SeedU, 61.7f)
|
EffectiveZ * LP.OverhangFrequency * 0.15f + VoxelHash::SeedOffset(SeedU, 61.7f)
|
||||||
@@ -4533,7 +4663,7 @@ namespace
|
|||||||
const float EffectiveZ = (P.VerticalScale != 1.0f && P.VerticalScale > 0.0f)
|
const float EffectiveZ = (P.VerticalScale != 1.0f && P.VerticalScale > 0.0f)
|
||||||
? (WorldZ / P.VerticalScale) : WorldZ;
|
? (WorldZ / P.VerticalScale) : WorldZ;
|
||||||
|
|
||||||
const float VertGrad = VoxelNoise::Perlin3D(FVector(
|
const float VertGrad = VoxelNoise::Perlin3D(FVector3f(
|
||||||
WorldX * 0.05f + VoxelHash::SeedOffset(SeedU, 71.3f),
|
WorldX * 0.05f + VoxelHash::SeedOffset(SeedU, 71.3f),
|
||||||
WorldY * 0.05f + VoxelHash::SeedOffset(SeedU, 73.7f),
|
WorldY * 0.05f + VoxelHash::SeedOffset(SeedU, 73.7f),
|
||||||
EffectiveZ * 0.15f + VoxelHash::SeedOffset(SeedU, 79.1f) // 3× plus vite en Z
|
EffectiveZ * 0.15f + VoxelHash::SeedOffset(SeedU, 79.1f) // 3× plus vite en Z
|
||||||
@@ -4610,7 +4740,7 @@ namespace
|
|||||||
? (WorldZ / P.VerticalScale) : WorldZ;
|
? (WorldZ / P.VerticalScale) : WorldZ;
|
||||||
|
|
||||||
const float SF = LP.ScallopFrequency;
|
const float SF = LP.ScallopFrequency;
|
||||||
const float ScallopNoise = VoxelNoise::Cellular3D(FVector(
|
const float ScallopNoise = VoxelNoise::Cellular3D(FVector3f(
|
||||||
WorldX * SF + VoxelHash::SeedOffset(SeedU, 83.1f),
|
WorldX * SF + VoxelHash::SeedOffset(SeedU, 83.1f),
|
||||||
WorldY * SF + VoxelHash::SeedOffset(SeedU, 89.3f),
|
WorldY * SF + VoxelHash::SeedOffset(SeedU, 89.3f),
|
||||||
EffectiveZ * SF + VoxelHash::SeedOffset(SeedU, 97.7f)
|
EffectiveZ * SF + VoxelHash::SeedOffset(SeedU, 97.7f)
|
||||||
@@ -4710,45 +4840,21 @@ namespace
|
|||||||
if (!Cache.Rooms.IsValidIndex(NearestRoomIdx)) { return; }
|
if (!Cache.Rooms.IsValidIndex(NearestRoomIdx)) { return; }
|
||||||
const FCachedRoom& Room = Cache.Rooms[NearestRoomIdx];
|
const FCachedRoom& Room = Cache.Rooms[NearestRoomIdx];
|
||||||
|
|
||||||
const int32 MaxArches = 3;
|
|
||||||
const FVector VoxPos(WorldX, WorldY, WorldZ);
|
const FVector VoxPos(WorldX, WorldY, WorldZ);
|
||||||
|
|
||||||
for (int32 i = 0; i < MaxArches; i++)
|
for (int32 i = 0; i < 3; i++)
|
||||||
{
|
{
|
||||||
const uint32 AH = VoxelHash::Mix(Room.Hash ^ (0xA4C400u + (uint32)i * 7369u));
|
const FCachedArch& Arch = Room.Arches[i];
|
||||||
|
if (!Arch.bActive) { continue; }
|
||||||
if (VoxelHash::ToFloat01(AH) > LP.ArchDensity) { continue; }
|
const float ArchSDF = VoxelSDF::Capsule(
|
||||||
|
VoxPos, Arch.EndpointA, Arch.EndpointB, Arch.Radius);
|
||||||
const uint32 AH2 = VoxelHash::Mix(AH ^ 0xA4C4u);
|
|
||||||
const float ArcCX = Room.Center.X + VoxelHash::ToFloatSigned(AH2) * Room.RadiusXY * 0.3f;
|
|
||||||
const float ArcCY = Room.Center.Y
|
|
||||||
+ VoxelHash::ToFloatSigned(VoxelHash::Mix(AH2)) * Room.RadiusXY * 0.3f;
|
|
||||||
|
|
||||||
const uint32 AH3 = VoxelHash::Mix(AH2 ^ 0xB41Du);
|
|
||||||
const float ArcCZ = Room.Center.Z + VoxelHash::ToFloatSigned(AH3) * Room.RadiusZ * 0.4f;
|
|
||||||
|
|
||||||
const uint32 AH4 = VoxelHash::Mix(AH3 ^ 0xCAFEu);
|
|
||||||
const float Angle = VoxelHash::ToFloat01(AH4) * PI;
|
|
||||||
const float HalfSpan = Room.RadiusXY
|
|
||||||
* (0.5f + VoxelHash::ToFloat01(VoxelHash::Mix(AH4)) * 0.35f);
|
|
||||||
|
|
||||||
const float CosA = FMath::Cos(Angle);
|
|
||||||
const float SinA = FMath::Sin(Angle);
|
|
||||||
const FVector ArchA(ArcCX - CosA * HalfSpan, ArcCY - SinA * HalfSpan, ArcCZ);
|
|
||||||
const FVector ArchB(ArcCX + CosA * HalfSpan, ArcCY + SinA * HalfSpan, ArcCZ);
|
|
||||||
|
|
||||||
const uint32 AH5 = VoxelHash::Mix(AH4 ^ 0xF00Du);
|
|
||||||
const float ArchRadius = FMath::Lerp(LP.ArchMinRadius, LP.ArchMaxRadius,
|
|
||||||
VoxelHash::ToFloat01(AH5));
|
|
||||||
|
|
||||||
const float ArchSDF = VoxelSDF::Capsule(VoxPos, ArchA, ArchB, ArchRadius);
|
|
||||||
|
|
||||||
const float ArchBlend = 2.0f;
|
const float ArchBlend = 2.0f;
|
||||||
if (ArchSDF < ArchBlend)
|
if (ArchSDF < ArchBlend)
|
||||||
{
|
{
|
||||||
float Fill = FMath::Clamp((ArchBlend - ArchSDF) / (ArchBlend * 2.0f), 0.0f, 1.0f);
|
float Fill = FMath::Clamp((ArchBlend - ArchSDF) / (ArchBlend * 2.0f), 0.0f, 1.0f);
|
||||||
Fill = SmoothStep01(Fill);
|
Fill = SmoothStep01(Fill);
|
||||||
InOut.Density += Fill * LP.BaseDensity * 1.5f;
|
InOut.Density += Fill * Arch.BaseDensity * 1.5f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4997,45 +5103,22 @@ namespace
|
|||||||
if (!Cache.Rooms.IsValidIndex(NearestRoomIdx)) { return; } // cf. FCaveArchMod
|
if (!Cache.Rooms.IsValidIndex(NearestRoomIdx)) { return; } // cf. FCaveArchMod
|
||||||
const FCachedRoom& Room = Cache.Rooms[NearestRoomIdx];
|
const FCachedRoom& Room = Cache.Rooms[NearestRoomIdx];
|
||||||
|
|
||||||
const float Spread = 0.85f;
|
for (int32 i = 0; i < 3; i++)
|
||||||
const int32 MaxPinches = 3;
|
|
||||||
|
|
||||||
for (int32 i = 0; i < MaxPinches; i++)
|
|
||||||
{
|
{
|
||||||
const uint32 PnH = VoxelHash::Mix(Room.Hash ^ (0xF1C400u + (uint32)i * 5417u));
|
const FCachedPinch& Pinch = Room.Pinches[i];
|
||||||
|
if (!Pinch.bActive) { continue; }
|
||||||
|
const float DXPn = WorldX - Pinch.CenterX;
|
||||||
|
const float DYPn = WorldY - Pinch.CenterY;
|
||||||
|
const float DZPn = WorldZ - Pinch.CenterZ;
|
||||||
|
|
||||||
if (VoxelHash::ToFloat01(PnH) > LP.PinchDensity) { continue; }
|
if (FMath::Abs(DXPn) + FMath::Abs(DYPn) + FMath::Abs(DZPn) > Pinch.MaxExtent) { continue; }
|
||||||
|
|
||||||
const uint32 PnH2 = VoxelHash::Mix(PnH ^ 0xF1C4u);
|
const float Along = DXPn * Pinch.CosAngle + DYPn * Pinch.SinAngle;
|
||||||
const float PnX = Room.Center.X + VoxelHash::ToFloatSigned(PnH2) * Room.RadiusXY * Spread;
|
const float Across = -DXPn * Pinch.SinAngle + DYPn * Pinch.CosAngle;
|
||||||
const float PnY = Room.Center.Y
|
|
||||||
+ VoxelHash::ToFloatSigned(VoxelHash::Mix(PnH2)) * Room.RadiusXY * Spread;
|
|
||||||
|
|
||||||
const uint32 PnH3 = VoxelHash::Mix(PnH2 ^ 0x5432u);
|
const float NAlong = Along / Pinch.HalfLength;
|
||||||
const float PnZ = Room.Center.Z + VoxelHash::ToFloatSigned(PnH3) * Room.RadiusZ * 0.5f;
|
const float NAcross = Across / Pinch.HalfNarrow;
|
||||||
|
const float NUp = DZPn / Pinch.HalfVertical;
|
||||||
const uint32 PnH4 = VoxelHash::Mix(PnH3 ^ 0x9A3Bu);
|
|
||||||
const float PnAngle = VoxelHash::ToFloat01(PnH4) * PI;
|
|
||||||
const float CosPN = FMath::Cos(PnAngle);
|
|
||||||
const float SinPN = FMath::Sin(PnAngle);
|
|
||||||
|
|
||||||
const float DXPn = WorldX - PnX;
|
|
||||||
const float DYPn = WorldY - PnY;
|
|
||||||
const float DZPn = WorldZ - PnZ;
|
|
||||||
|
|
||||||
const float MaxExtent = FMath::Max(LP.PinchLength, LP.PinchStrength) + 5.0f;
|
|
||||||
if (FMath::Abs(DXPn) + FMath::Abs(DYPn) + FMath::Abs(DZPn) > MaxExtent) { continue; }
|
|
||||||
|
|
||||||
const float Along = DXPn * CosPN + DYPn * SinPN;
|
|
||||||
const float Across = -DXPn * SinPN + DYPn * CosPN;
|
|
||||||
|
|
||||||
const float HalfLength = LP.PinchLength * 0.5f;
|
|
||||||
const float HalfNarrow = LP.PinchStrength;
|
|
||||||
const float HalfVertical = LP.PinchStrength * 1.5f;
|
|
||||||
|
|
||||||
const float NAlong = Along / HalfLength;
|
|
||||||
const float NAcross = Across / HalfNarrow;
|
|
||||||
const float NUp = DZPn / HalfVertical;
|
|
||||||
const float EllipDist = NAlong * NAlong + NAcross * NAcross + NUp * NUp;
|
const float EllipDist = NAlong * NAlong + NAcross * NAcross + NUp * NUp;
|
||||||
|
|
||||||
if (EllipDist < 1.0f)
|
if (EllipDist < 1.0f)
|
||||||
@@ -5044,7 +5127,7 @@ namespace
|
|||||||
Fill = SmoothStep01(Fill);
|
Fill = SmoothStep01(Fill);
|
||||||
const float AxisDist = FMath::Sqrt(NAcross * NAcross + NUp * NUp);
|
const float AxisDist = FMath::Sqrt(NAcross * NAcross + NUp * NUp);
|
||||||
const float SideFactor = FMath::Clamp(AxisDist * 2.0f, 0.0f, 1.0f);
|
const float SideFactor = FMath::Clamp(AxisDist * 2.0f, 0.0f, 1.0f);
|
||||||
InOut.Density += Fill * SideFactor * LP.BaseDensity * 1.5f;
|
InOut.Density += Fill * SideFactor * Pinch.BaseDensity * 1.5f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5192,7 +5275,7 @@ namespace
|
|||||||
|
|
||||||
const float WormZFreq = P.WormFrequency * P.WormHorizontalBias;
|
const float WormZFreq = P.WormFrequency * P.WormHorizontalBias;
|
||||||
|
|
||||||
const float N1 = FMath::Abs(VoxelNoise::Perlin3D(FVector(
|
const float N1 = FMath::Abs(VoxelNoise::Perlin3D(FVector3f(
|
||||||
WorldX * P.WormFrequency + VoxelHash::SeedOffset(SeedU, 1.0f),
|
WorldX * P.WormFrequency + VoxelHash::SeedOffset(SeedU, 1.0f),
|
||||||
WorldY * P.WormFrequency + VoxelHash::SeedOffset(SeedU, 1.7f),
|
WorldY * P.WormFrequency + VoxelHash::SeedOffset(SeedU, 1.7f),
|
||||||
EffectiveZ * WormZFreq + VoxelHash::SeedOffset(SeedU, 2.3f)
|
EffectiveZ * WormZFreq + VoxelHash::SeedOffset(SeedU, 2.3f)
|
||||||
@@ -5202,7 +5285,7 @@ namespace
|
|||||||
// second Perlin (le cas courant ; sortie bit-identique). Transcrit tel quel.
|
// second Perlin (le cas courant ; sortie bit-identique). Transcrit tel quel.
|
||||||
if (N1 >= P.WormThreshold) { return; }
|
if (N1 >= P.WormThreshold) { return; }
|
||||||
|
|
||||||
const float N2 = FMath::Abs(VoxelNoise::Perlin3D(FVector(
|
const float N2 = FMath::Abs(VoxelNoise::Perlin3D(FVector3f(
|
||||||
WorldX * P.WormFrequency + VoxelHash::SeedOffset(SeedU, 1.0f) + 137.0f,
|
WorldX * P.WormFrequency + VoxelHash::SeedOffset(SeedU, 1.0f) + 137.0f,
|
||||||
WorldY * P.WormFrequency + VoxelHash::SeedOffset(SeedU, 1.7f) + 259.0f,
|
WorldY * P.WormFrequency + VoxelHash::SeedOffset(SeedU, 1.7f) + 259.0f,
|
||||||
EffectiveZ * WormZFreq + VoxelHash::SeedOffset(SeedU, 2.3f) + 431.0f
|
EffectiveZ * WormZFreq + VoxelHash::SeedOffset(SeedU, 2.3f) + 431.0f
|
||||||
@@ -5440,10 +5523,61 @@ VoxelDensityOps::FRoomBoxDiagnostic VoxelDensityOps::GetLastRoomBoxDiagnostic()
|
|||||||
return D;
|
return D;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void VoxelDensityOps::ReportWorkerRoomGraphCacheFootprint()
|
||||||
|
{
|
||||||
|
if (!VoxelDensityProfile::IsEnabled())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64 DynamicBytes = 0;
|
||||||
|
uint64 EntryBytes = 0;
|
||||||
|
uint64 LargestEntryBytes = 0;
|
||||||
|
uint64 ValidEntries = 0;
|
||||||
|
for (int32 Index = 0; Index < RoomGraphCacheSlotCount; ++Index)
|
||||||
|
{
|
||||||
|
const FRoomGraphCacheEntry& Entry = GRoomGraphCache[Index];
|
||||||
|
if (!Entry.bValid)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
++ValidEntries;
|
||||||
|
const uint64 EntryDynamicBytes = static_cast<uint64>(Entry.Cache.GetAllocatedSize());
|
||||||
|
const uint64 FullEntryBytes = static_cast<uint64>(sizeof(FRoomGraphCacheEntry))
|
||||||
|
+ EntryDynamicBytes;
|
||||||
|
DynamicBytes += EntryDynamicBytes;
|
||||||
|
EntryBytes += FullEntryBytes;
|
||||||
|
LargestEntryBytes = FMath::Max(LargestEntryBytes, FullEntryBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
VoxelDensityProfile::SetWorkerRoomGraphCacheFootprint(
|
||||||
|
RoomGraphCacheSlotCount,
|
||||||
|
ValidEntries,
|
||||||
|
static_cast<uint64>(sizeof(GRoomGraphCache)),
|
||||||
|
DynamicBytes,
|
||||||
|
EntryBytes,
|
||||||
|
LargestEntryBytes,
|
||||||
|
ValidEntries);
|
||||||
|
}
|
||||||
|
|
||||||
//=============================================================================
|
//=============================================================================
|
||||||
// FVoxelOpStack
|
// FVoxelOpStack
|
||||||
//=============================================================================
|
//=============================================================================
|
||||||
|
|
||||||
|
SIZE_T FVoxelOpStack::GetAllocatedSize() const
|
||||||
|
{
|
||||||
|
SIZE_T Bytes = static_cast<SIZE_T>(Ops.GetAllocatedSize());
|
||||||
|
for (const FOpEntry& Entry : Ops)
|
||||||
|
{
|
||||||
|
if (Entry.Op.IsValid())
|
||||||
|
{
|
||||||
|
Bytes += FMemory::GetAllocSize(const_cast<IVoxelDensityOp*>(Entry.Op.Get()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Bytes;
|
||||||
|
}
|
||||||
|
|
||||||
bool FVoxelOpStack::ValidateChannelOrder(FString* OutError) const
|
bool FVoxelOpStack::ValidateChannelOrder(FString* OutError) const
|
||||||
{
|
{
|
||||||
if (OutError != nullptr) { OutError->Reset(); }
|
if (OutError != nullptr) { OutError->Reset(); }
|
||||||
|
|||||||
@@ -5,9 +5,57 @@ namespace VoxelDensityProfile
|
|||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
std::atomic<bool> GEnabled(false);
|
std::atomic<bool> GEnabled(false);
|
||||||
std::atomic<uint64> GCalls[BucketCount];
|
|
||||||
std::atomic<uint64> GCycles[BucketCount];
|
// The old implementation performed a contended atomic fetch_add for every timer and
|
||||||
std::atomic<uint64> GCounters[CounterCount];
|
// counter. A TunnelNetwork sample can close twenty-plus ranges and add several
|
||||||
|
// candidate counters, so profiling changed the workload it was supposed to measure.
|
||||||
|
// Keep one accumulator per worker and only aggregate at Snapshot(). Commandlet resets
|
||||||
|
// happen between completed jobs, so the deliberately light-weight list is sufficient.
|
||||||
|
struct FThreadState
|
||||||
|
{
|
||||||
|
uint64 Calls[BucketCount]{};
|
||||||
|
uint64 Cycles[BucketCount]{};
|
||||||
|
uint64 Counters[CounterCount]{};
|
||||||
|
|
||||||
|
uint64 TunnelCacheCapacityEntries = 0;
|
||||||
|
uint64 TunnelCacheValidEntries = 0;
|
||||||
|
uint64 TunnelCacheStaticBytes = 0;
|
||||||
|
uint64 TunnelCacheDynamicBytes = 0;
|
||||||
|
uint64 TunnelCacheEntryBytes = 0;
|
||||||
|
uint64 TunnelCacheLargestEntryBytes = 0;
|
||||||
|
uint64 TunnelCacheLargestWorkerValidEntries = 0;
|
||||||
|
|
||||||
|
uint64 RoomGraphCacheCapacityEntries = 0;
|
||||||
|
uint64 RoomGraphCacheValidEntries = 0;
|
||||||
|
uint64 RoomGraphCacheStaticBytes = 0;
|
||||||
|
uint64 RoomGraphCacheDynamicBytes = 0;
|
||||||
|
uint64 RoomGraphCacheEntryBytes = 0;
|
||||||
|
uint64 RoomGraphCacheLargestEntryBytes = 0;
|
||||||
|
uint64 RoomGraphCacheLargestWorkerValidEntries = 0;
|
||||||
|
|
||||||
|
std::atomic<FThreadState*> Next{nullptr};
|
||||||
|
};
|
||||||
|
|
||||||
|
std::atomic<FThreadState*> GThreadStates{nullptr};
|
||||||
|
thread_local FThreadState* GThreadState = nullptr;
|
||||||
|
|
||||||
|
FThreadState& GetThreadState()
|
||||||
|
{
|
||||||
|
if (GThreadState == nullptr)
|
||||||
|
{
|
||||||
|
GThreadState = new FThreadState();
|
||||||
|
FThreadState* Head = GThreadStates.load(std::memory_order_relaxed);
|
||||||
|
do
|
||||||
|
{
|
||||||
|
GThreadState->Next.store(Head, std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
while (!GThreadStates.compare_exchange_weak(
|
||||||
|
Head, GThreadState,
|
||||||
|
std::memory_order_release,
|
||||||
|
std::memory_order_relaxed));
|
||||||
|
}
|
||||||
|
return *GThreadState;
|
||||||
|
}
|
||||||
|
|
||||||
int32 ToIndex(EBucket Bucket)
|
int32 ToIndex(EBucket Bucket)
|
||||||
{
|
{
|
||||||
@@ -30,28 +78,78 @@ namespace VoxelDensityProfile
|
|||||||
|
|
||||||
void Reset()
|
void Reset()
|
||||||
{
|
{
|
||||||
for (int32 Index = 0; Index < BucketCount; ++Index)
|
for (FThreadState* State = GThreadStates.load(std::memory_order_acquire);
|
||||||
|
State != nullptr;
|
||||||
|
State = State->Next.load(std::memory_order_acquire))
|
||||||
{
|
{
|
||||||
GCalls[Index].store(0, std::memory_order_relaxed);
|
for (int32 Index = 0; Index < BucketCount; ++Index)
|
||||||
GCycles[Index].store(0, std::memory_order_relaxed);
|
{
|
||||||
}
|
State->Calls[Index] = 0;
|
||||||
for (int32 Index = 0; Index < CounterCount; ++Index)
|
State->Cycles[Index] = 0;
|
||||||
{
|
}
|
||||||
GCounters[Index].store(0, std::memory_order_relaxed);
|
for (int32 Index = 0; Index < CounterCount; ++Index)
|
||||||
|
{
|
||||||
|
State->Counters[Index] = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FSnapshot Snapshot()
|
FSnapshot Snapshot()
|
||||||
{
|
{
|
||||||
FSnapshot Result;
|
FSnapshot Result;
|
||||||
for (int32 Index = 0; Index < BucketCount; ++Index)
|
for (FThreadState* State = GThreadStates.load(std::memory_order_acquire);
|
||||||
|
State != nullptr;
|
||||||
|
State = State->Next.load(std::memory_order_acquire))
|
||||||
{
|
{
|
||||||
Result.Calls[Index] = GCalls[Index].load(std::memory_order_relaxed);
|
for (int32 Index = 0; Index < BucketCount; ++Index)
|
||||||
Result.Cycles[Index] = GCycles[Index].load(std::memory_order_relaxed);
|
{
|
||||||
}
|
Result.Calls[Index] += State->Calls[Index];
|
||||||
for (int32 Index = 0; Index < CounterCount; ++Index)
|
Result.Cycles[Index] += State->Cycles[Index];
|
||||||
{
|
}
|
||||||
Result.Counters[Index] = GCounters[Index].load(std::memory_order_relaxed);
|
for (int32 Index = 0; Index < CounterCount; ++Index)
|
||||||
|
{
|
||||||
|
Result.Counters[Index] += State->Counters[Index];
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint64 TunnelWorkerBytes = State->TunnelCacheStaticBytes
|
||||||
|
+ State->TunnelCacheDynamicBytes;
|
||||||
|
if (State->TunnelCacheCapacityEntries > 0)
|
||||||
|
{
|
||||||
|
++Result.TunnelCacheWorkers;
|
||||||
|
Result.TunnelCacheCapacityEntries += State->TunnelCacheCapacityEntries;
|
||||||
|
Result.TunnelCacheValidEntries += State->TunnelCacheValidEntries;
|
||||||
|
Result.TunnelCacheStaticBytes += State->TunnelCacheStaticBytes;
|
||||||
|
Result.TunnelCacheDynamicBytes += State->TunnelCacheDynamicBytes;
|
||||||
|
Result.TunnelCacheEntryBytes += State->TunnelCacheEntryBytes;
|
||||||
|
Result.TunnelCacheLargestEntryBytes = FMath::Max(
|
||||||
|
Result.TunnelCacheLargestEntryBytes,
|
||||||
|
State->TunnelCacheLargestEntryBytes);
|
||||||
|
Result.TunnelCacheLargestWorkerValidEntries = FMath::Max(
|
||||||
|
Result.TunnelCacheLargestWorkerValidEntries,
|
||||||
|
State->TunnelCacheValidEntries);
|
||||||
|
Result.TunnelCacheLargestWorkerBytes = FMath::Max(
|
||||||
|
Result.TunnelCacheLargestWorkerBytes, TunnelWorkerBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint64 RoomGraphWorkerBytes = State->RoomGraphCacheStaticBytes
|
||||||
|
+ State->RoomGraphCacheDynamicBytes;
|
||||||
|
if (State->RoomGraphCacheCapacityEntries > 0)
|
||||||
|
{
|
||||||
|
++Result.RoomGraphCacheWorkers;
|
||||||
|
Result.RoomGraphCacheCapacityEntries += State->RoomGraphCacheCapacityEntries;
|
||||||
|
Result.RoomGraphCacheValidEntries += State->RoomGraphCacheValidEntries;
|
||||||
|
Result.RoomGraphCacheStaticBytes += State->RoomGraphCacheStaticBytes;
|
||||||
|
Result.RoomGraphCacheDynamicBytes += State->RoomGraphCacheDynamicBytes;
|
||||||
|
Result.RoomGraphCacheEntryBytes += State->RoomGraphCacheEntryBytes;
|
||||||
|
Result.RoomGraphCacheLargestEntryBytes = FMath::Max(
|
||||||
|
Result.RoomGraphCacheLargestEntryBytes,
|
||||||
|
State->RoomGraphCacheLargestEntryBytes);
|
||||||
|
Result.RoomGraphCacheLargestWorkerValidEntries = FMath::Max(
|
||||||
|
Result.RoomGraphCacheLargestWorkerValidEntries,
|
||||||
|
State->RoomGraphCacheValidEntries);
|
||||||
|
Result.RoomGraphCacheLargestWorkerBytes = FMath::Max(
|
||||||
|
Result.RoomGraphCacheLargestWorkerBytes, RoomGraphWorkerBytes);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return Result;
|
return Result;
|
||||||
}
|
}
|
||||||
@@ -61,10 +159,52 @@ namespace VoxelDensityProfile
|
|||||||
const int32 Index = static_cast<int32>(Counter);
|
const int32 Index = static_cast<int32>(Counter);
|
||||||
if (Index >= 0 && Index < CounterCount)
|
if (Index >= 0 && Index < CounterCount)
|
||||||
{
|
{
|
||||||
GCounters[Index].fetch_add(Amount, std::memory_order_relaxed);
|
GetThreadState().Counters[Index] += Amount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AddMeasurement(EBucket Bucket, uint64 Cycles, uint64 Calls)
|
||||||
|
{
|
||||||
|
const int32 Index = ToIndex(Bucket);
|
||||||
|
FThreadState& State = GetThreadState();
|
||||||
|
State.Calls[Index] += Calls;
|
||||||
|
State.Cycles[Index] += Cycles;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetWorkerTunnelCacheFootprint(
|
||||||
|
uint64 CapacityEntries, uint64 ValidEntries,
|
||||||
|
uint64 StaticBytes, uint64 DynamicBytes,
|
||||||
|
uint64 EntryBytes, uint64 LargestEntryBytes,
|
||||||
|
uint64 LargestWorkerValidEntries)
|
||||||
|
{
|
||||||
|
if (!IsEnabled()) { return; }
|
||||||
|
FThreadState& State = GetThreadState();
|
||||||
|
State.TunnelCacheCapacityEntries = CapacityEntries;
|
||||||
|
State.TunnelCacheValidEntries = ValidEntries;
|
||||||
|
State.TunnelCacheStaticBytes = StaticBytes;
|
||||||
|
State.TunnelCacheDynamicBytes = DynamicBytes;
|
||||||
|
State.TunnelCacheEntryBytes = EntryBytes;
|
||||||
|
State.TunnelCacheLargestEntryBytes = LargestEntryBytes;
|
||||||
|
State.TunnelCacheLargestWorkerValidEntries = LargestWorkerValidEntries;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetWorkerRoomGraphCacheFootprint(
|
||||||
|
uint64 CapacityEntries, uint64 ValidEntries,
|
||||||
|
uint64 StaticBytes, uint64 DynamicBytes,
|
||||||
|
uint64 EntryBytes, uint64 LargestEntryBytes,
|
||||||
|
uint64 LargestWorkerValidEntries)
|
||||||
|
{
|
||||||
|
if (!IsEnabled()) { return; }
|
||||||
|
FThreadState& State = GetThreadState();
|
||||||
|
State.RoomGraphCacheCapacityEntries = CapacityEntries;
|
||||||
|
State.RoomGraphCacheValidEntries = ValidEntries;
|
||||||
|
State.RoomGraphCacheStaticBytes = StaticBytes;
|
||||||
|
State.RoomGraphCacheDynamicBytes = DynamicBytes;
|
||||||
|
State.RoomGraphCacheEntryBytes = EntryBytes;
|
||||||
|
State.RoomGraphCacheLargestEntryBytes = LargestEntryBytes;
|
||||||
|
State.RoomGraphCacheLargestWorkerValidEntries = LargestWorkerValidEntries;
|
||||||
|
}
|
||||||
|
|
||||||
const TCHAR* CounterName(ECounter Counter)
|
const TCHAR* CounterName(ECounter Counter)
|
||||||
{
|
{
|
||||||
switch (Counter)
|
switch (Counter)
|
||||||
@@ -79,6 +219,10 @@ namespace VoxelDensityProfile
|
|||||||
case ECounter::CaveTunnelEvaluated:return TEXT("CaveTunnelEvaluated");
|
case ECounter::CaveTunnelEvaluated:return TEXT("CaveTunnelEvaluated");
|
||||||
case ECounter::TunnelCoreCandidates:return TEXT("TunnelCoreCandidates");
|
case ECounter::TunnelCoreCandidates:return TEXT("TunnelCoreCandidates");
|
||||||
case ECounter::TunnelCoreEvaluated:return TEXT("TunnelCoreEvaluated");
|
case ECounter::TunnelCoreEvaluated:return TEXT("TunnelCoreEvaluated");
|
||||||
|
case ECounter::TunnelSupportColumnBuilds:return TEXT("TunnelSupportColumnBuilds");
|
||||||
|
case ECounter::TunnelSupportColumnCandidates:return TEXT("TunnelSupportColumnCandidates");
|
||||||
|
case ECounter::TunnelSupportFloorQueries:return TEXT("TunnelSupportFloorQueries");
|
||||||
|
case ECounter::TunnelSupportFloorChecks:return TEXT("TunnelSupportFloorChecks");
|
||||||
case ECounter::PassageCandidates: return TEXT("PassageCandidates");
|
case ECounter::PassageCandidates: return TEXT("PassageCandidates");
|
||||||
case ECounter::PassageEvaluated: return TEXT("PassageEvaluated");
|
case ECounter::PassageEvaluated: return TEXT("PassageEvaluated");
|
||||||
case ECounter::Count: break;
|
case ECounter::Count: break;
|
||||||
@@ -130,6 +274,20 @@ namespace VoxelDensityProfile
|
|||||||
{ TEXT("CaveTunnelFloorOp"), EBucket::CaveTunnelFloorOp },
|
{ TEXT("CaveTunnelFloorOp"), EBucket::CaveTunnelFloorOp },
|
||||||
{ TEXT("CaveTunnelAirOp"), EBucket::CaveTunnelAirOp },
|
{ TEXT("CaveTunnelAirOp"), EBucket::CaveTunnelAirOp },
|
||||||
{ TEXT("XYEdgeSealOp"), EBucket::XYEdgeSealOp },
|
{ TEXT("XYEdgeSealOp"), EBucket::XYEdgeSealOp },
|
||||||
|
{ TEXT("DensityPrologue"), EBucket::DensityPrologue },
|
||||||
|
{ TEXT("DensityCore"), EBucket::DensityCore },
|
||||||
|
{ TEXT("DensityDisturbances"), EBucket::DensityDisturbances },
|
||||||
|
{ TEXT("DensityStructuralPosts"), EBucket::DensityStructuralPosts },
|
||||||
|
{ TEXT("DensityBoundarySeal"), EBucket::DensityBoundarySeal },
|
||||||
|
{ TEXT("DensityDiffLayer"), EBucket::DensityDiffLayer },
|
||||||
|
{ TEXT("DensityTail"), EBucket::DensityTail },
|
||||||
|
{ TEXT("MesherGenerateMesh"), EBucket::MesherGenerateMesh },
|
||||||
|
{ TEXT("MesherDensityGrid"), EBucket::MesherDensityGrid },
|
||||||
|
{ TEXT("MesherCellClassification"), EBucket::MesherCellClassification },
|
||||||
|
{ TEXT("MesherGradientNormals"), EBucket::MesherGradientNormals },
|
||||||
|
{ TEXT("MesherVertexInterpolation"), EBucket::MesherVertexInterpolation },
|
||||||
|
{ TEXT("MesherStreamBuilding"), EBucket::MesherStreamBuilding },
|
||||||
|
{ TEXT("MesherOther"), EBucket::MesherOther },
|
||||||
};
|
};
|
||||||
for (const FNameBucket& Entry : Names)
|
for (const FNameBucket& Entry : Names)
|
||||||
{
|
{
|
||||||
@@ -179,17 +337,34 @@ namespace VoxelDensityProfile
|
|||||||
case EBucket::CaveTunnelAirOp: return TEXT("CaveTunnelAirOp");
|
case EBucket::CaveTunnelAirOp: return TEXT("CaveTunnelAirOp");
|
||||||
case EBucket::XYEdgeSealOp: return TEXT("XYEdgeSealOp");
|
case EBucket::XYEdgeSealOp: return TEXT("XYEdgeSealOp");
|
||||||
case EBucket::OtherOp: return TEXT("OtherOp");
|
case EBucket::OtherOp: return TEXT("OtherOp");
|
||||||
|
case EBucket::DensityPrologue: return TEXT("DensityPrologue");
|
||||||
|
case EBucket::DensityCore: return TEXT("DensityCore");
|
||||||
|
case EBucket::DensityDisturbances:return TEXT("DensityDisturbances");
|
||||||
|
case EBucket::DensityStructuralPosts:return TEXT("DensityStructuralPosts");
|
||||||
|
case EBucket::DensityBoundarySeal:return TEXT("DensityBoundarySeal");
|
||||||
|
case EBucket::DensityDiffLayer: return TEXT("DensityDiffLayer");
|
||||||
|
case EBucket::DensityTail: return TEXT("DensityTail");
|
||||||
|
case EBucket::MesherGenerateMesh:return TEXT("MesherGenerateMesh");
|
||||||
|
case EBucket::MesherDensityGrid:return TEXT("MesherDensityGrid");
|
||||||
|
case EBucket::MesherCellClassification:return TEXT("MesherCellClassification");
|
||||||
|
case EBucket::MesherGradientNormals:return TEXT("MesherGradientNormals");
|
||||||
|
case EBucket::MesherVertexInterpolation:return TEXT("MesherVertexInterpolation");
|
||||||
|
case EBucket::MesherStreamBuilding:return TEXT("MesherStreamBuilding");
|
||||||
|
case EBucket::MesherOther: return TEXT("MesherOther");
|
||||||
case EBucket::Count: break;
|
case EBucket::Count: break;
|
||||||
}
|
}
|
||||||
return TEXT("Unknown");
|
return TEXT("Unknown");
|
||||||
}
|
}
|
||||||
|
|
||||||
FScopedTimer::~FScopedTimer()
|
FScopedTimer::~FScopedTimer()
|
||||||
|
{
|
||||||
|
End();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FScopedTimer::End()
|
||||||
{
|
{
|
||||||
if (!bActive) { return; }
|
if (!bActive) { return; }
|
||||||
const int32 Index = ToIndex(Bucket);
|
AddMeasurement(Bucket, FPlatformTime::Cycles64() - StartCycles, 1);
|
||||||
GCalls[Index].fetch_add(1, std::memory_order_relaxed);
|
bActive = false;
|
||||||
GCycles[Index].fetch_add(FPlatformTime::Cycles64() - StartCycles,
|
|
||||||
std::memory_order_relaxed);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -181,10 +181,10 @@ thread_local int32 VoxelGenLOD::SampleStep = 1;
|
|||||||
// 4-wide via SSE (Perlin3D_x4). These thin wrappers keep every call site unchanged. They
|
// 4-wide via SSE (Perlin3D_x4). These thin wrappers keep every call site unchanged. They
|
||||||
// sample a DIFFERENT (float hash-gradient) noise field than the old FMath::PerlinNoise3D,
|
// sample a DIFFERENT (float hash-gradient) noise field than the old FMath::PerlinNoise3D,
|
||||||
// so worlds re-tune once — but the fBm/Ridged math/contracts ([-1,1]) are identical.
|
// so worlds re-tune once — but the fBm/Ridged math/contracts ([-1,1]) are identical.
|
||||||
static float FractalNoise3D(const FVector& Position, int32 Octaves = 4,
|
static float FractalNoise3D(const FVector3f& Position, int32 Octaves = 4,
|
||||||
float Lacunarity = 2.0f, float Persistence = 0.5f)
|
float Lacunarity = 2.0f, float Persistence = 0.5f)
|
||||||
{
|
{
|
||||||
return VoxelNoise::FBM((float)Position.X, (float)Position.Y, (float)Position.Z,
|
return VoxelNoise::FBM(Position.X, Position.Y, Position.Z,
|
||||||
Octaves, Lacunarity, Persistence);
|
Octaves, Lacunarity, Persistence);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,10 +199,10 @@ static float FractalNoise3D(const FVector& Position, int32 Octaves = 4,
|
|||||||
// Returns approximately [-1, 1] to match FractalNoise3D's range.
|
// Returns approximately [-1, 1] to match FractalNoise3D's range.
|
||||||
// Character: craggy cliffs, natural erosion patterns, sharp corridors.
|
// Character: craggy cliffs, natural erosion patterns, sharp corridors.
|
||||||
|
|
||||||
static float RidgedNoise3D(const FVector& Position, int32 Octaves = 4,
|
static float RidgedNoise3D(const FVector3f& Position, int32 Octaves = 4,
|
||||||
float Lacunarity = 2.0f, float Persistence = 0.5f)
|
float Lacunarity = 2.0f, float Persistence = 0.5f)
|
||||||
{
|
{
|
||||||
return VoxelNoise::Ridged((float)Position.X, (float)Position.Y, (float)Position.Z,
|
return VoxelNoise::Ridged(Position.X, Position.Y, Position.Z,
|
||||||
Octaves, Lacunarity, Persistence);
|
Octaves, Lacunarity, Persistence);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,9 +225,9 @@ static float RidgedNoise3D(const FVector& Position, int32 Octaves = 4,
|
|||||||
// qui voit déjà `VoxelHash` : la pile d'opérateurs a besoin exactement du même bruit pour la
|
// qui voit déjà `VoxelHash` : la pile d'opérateurs a besoin exactement du même bruit pour la
|
||||||
// rugosité (4b, type Cellular) et pour les festons (4f), et deux copies d'une fonction pure finissent
|
// rugosité (4b, type Cellular) et pour les festons (4f), et deux copies d'une fonction pure finissent
|
||||||
// par diverger — c'est littéralement `AUDIT §C1`. Ce forwarder garde les ~3 sites d'appel ci-dessous
|
// par diverger — c'est littéralement `AUDIT §C1`. Ce forwarder garde les ~3 sites d'appel ci-dessous
|
||||||
// inchangés, comme l'ont fait FractalNoise3D et RidgedNoise3D lors de T2.a. Aucun changement de
|
// inchangés, comme l'ont fait FractalNoise3D et RidgedNoise3D lors de T2.a. Le chemin chaud garde
|
||||||
// comportement : corps identique, mêmes doubles de `FVector`, même ordre d'opérations.
|
// maintenant les coordonnées en float : la compatibilité d'arrondi historique est abandonnée.
|
||||||
static float CellularNoise3D(const FVector& Position)
|
static float CellularNoise3D(const FVector3f& Position)
|
||||||
{
|
{
|
||||||
return VoxelNoise::Cellular3D(Position);
|
return VoxelNoise::Cellular3D(Position);
|
||||||
}
|
}
|
||||||
@@ -483,6 +483,16 @@ namespace
|
|||||||
|
|
||||||
static thread_local FTunnelCoreCacheState GTunnelCoreCache;
|
static thread_local FTunnelCoreCacheState GTunnelCoreCache;
|
||||||
|
|
||||||
|
static void BuildTunnelSupportColumnsForChunk(
|
||||||
|
FChunkSDFCache& Cache,
|
||||||
|
const FIntVector& ChunkCoord);
|
||||||
|
|
||||||
|
static const FTunnelSupportFloorColumn* FindTunnelSupportColumn(
|
||||||
|
const FChunkSDFCache& Cache,
|
||||||
|
int32 X,
|
||||||
|
int32 Y,
|
||||||
|
const FTunnelSupportFloorColumn& EmptyColumn);
|
||||||
|
|
||||||
// A mesher tile samples an expanded grid (-1..Cells+1), so one worker visits several exact
|
// A mesher tile samples an expanded grid (-1..Cells+1), so one worker visits several exact
|
||||||
// chunk coordinates even while it is generating one tile. The old CP_* cache retained only
|
// chunk coordinates even while it is generating one tile. The old CP_* cache retained only
|
||||||
// the last coordinate and consequently rebuilt the whole TunnelNetwork stack whenever the
|
// the last coordinate and consequently rebuilt the whole TunnelNetwork stack whenever the
|
||||||
@@ -491,12 +501,11 @@ namespace
|
|||||||
// result. Native TunnelNetwork/Underwater states are immutable after construction; the active
|
// result. Native TunnelNetwork/Underwater states are immutable after construction; the active
|
||||||
// pointer below lets the hot sample path read the stored stack and tunnel-core cache without
|
// pointer below lets the hot sample path read the stored stack and tunnel-core cache without
|
||||||
// moving them on every voxel.
|
// moving them on every voxel.
|
||||||
// The largest production coarse tile can touch thousands of exact chunk keys once its
|
// The cache is open-addressed and bounded. The 128-slot budget is evidence-sized: the
|
||||||
// gradient halo is included. A 128-slot direct map repeatedly evicts nearby keys (the LOD
|
// 128³ production export measured at most 102 live tunnel entries on one worker, leaving
|
||||||
// probe measured 1,734 misses for only 6,859 samples at L4), rebuilding the room/tunnel cache
|
// 26 slots of headroom; the old 4,096-slot experiment reserved 3.34 MiB per worker before
|
||||||
// in the hot loop. Keep the same deterministic key and open-addressed lookup, but size the
|
// one operator or SDF allocation existed. Collisions only cause a safe deterministic rebuild.
|
||||||
// bounded table for the measured L4 working set rather than the old 128-slot guess.
|
constexpr int32 TunnelDensityCacheSlotCount = 128;
|
||||||
constexpr int32 TunnelDensityCacheSlotCount = 4096;
|
|
||||||
|
|
||||||
struct FTunnelNetworkDensityCacheEntry
|
struct FTunnelNetworkDensityCacheEntry
|
||||||
{
|
{
|
||||||
@@ -514,6 +523,49 @@ namespace
|
|||||||
FTunnelCoreCacheState TunnelCore;
|
FTunnelCoreCacheState TunnelCore;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
void ReportTunnelDensityCacheFootprint(
|
||||||
|
const FTunnelNetworkDensityCacheEntry* Entries)
|
||||||
|
{
|
||||||
|
if (!VoxelDensityProfile::IsEnabled() || Entries == nullptr)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64 DynamicBytes = 0;
|
||||||
|
uint64 EntryBytes = 0;
|
||||||
|
uint64 LargestEntryBytes = 0;
|
||||||
|
uint64 ValidEntries = 0;
|
||||||
|
for (int32 Index = 0; Index < TunnelDensityCacheSlotCount; ++Index)
|
||||||
|
{
|
||||||
|
const FTunnelNetworkDensityCacheEntry& Entry = Entries[Index];
|
||||||
|
if (!Entry.bValid)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
++ValidEntries;
|
||||||
|
const uint64 StackBytes = static_cast<uint64>(Entry.OpStack.GetAllocatedSize());
|
||||||
|
const uint64 TunnelCoreBytes = static_cast<uint64>(
|
||||||
|
Entry.TunnelCore.Cache.GetAllocatedSize());
|
||||||
|
const uint64 EntryDynamicBytes = StackBytes + TunnelCoreBytes;
|
||||||
|
const uint64 FullEntryBytes = static_cast<uint64>(
|
||||||
|
sizeof(FTunnelNetworkDensityCacheEntry)) + EntryDynamicBytes;
|
||||||
|
DynamicBytes += EntryDynamicBytes;
|
||||||
|
EntryBytes += FullEntryBytes;
|
||||||
|
LargestEntryBytes = FMath::Max(LargestEntryBytes, FullEntryBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
VoxelDensityProfile::SetWorkerTunnelCacheFootprint(
|
||||||
|
TunnelDensityCacheSlotCount,
|
||||||
|
ValidEntries,
|
||||||
|
static_cast<uint64>(sizeof(FTunnelNetworkDensityCacheEntry))
|
||||||
|
* TunnelDensityCacheSlotCount,
|
||||||
|
DynamicBytes,
|
||||||
|
EntryBytes,
|
||||||
|
LargestEntryBytes,
|
||||||
|
ValidEntries);
|
||||||
|
}
|
||||||
|
|
||||||
FORCEINLINE int32 TunnelDensityCacheSlot(const FIntVector& Chunk)
|
FORCEINLINE int32 TunnelDensityCacheSlot(const FIntVector& Chunk)
|
||||||
{
|
{
|
||||||
uint32 Hash = static_cast<uint32>(Chunk.X) * 0x9E3779B9u;
|
uint32 Hash = static_cast<uint32>(Chunk.X) * 0x9E3779B9u;
|
||||||
@@ -547,7 +599,8 @@ namespace
|
|||||||
const float ChunkMinY = static_cast<float>(ChunkCoord.Y * CHUNK_SIZE);
|
const float ChunkMinY = static_cast<float>(ChunkCoord.Y * CHUNK_SIZE);
|
||||||
const float ChunkMaxX = ChunkMinX + static_cast<float>(CHUNK_SIZE);
|
const float ChunkMaxX = ChunkMinX + static_cast<float>(CHUNK_SIZE);
|
||||||
const float ChunkMaxY = ChunkMinY + static_cast<float>(CHUNK_SIZE);
|
const float ChunkMaxY = ChunkMinY + static_cast<float>(CHUNK_SIZE);
|
||||||
const float Expansion = FMath::Max(Params.CaveWarpStrength, 0.0f) + 2.0f;
|
const float Expansion = FMath::Abs(Params.CaveWarpStrength)
|
||||||
|
* VOXEL_NOISE_SCALE * 1.5f + 2.0f;
|
||||||
const int32 StrateIndex = Manager.GetStrateIndex(
|
const int32 StrateIndex = Manager.GetStrateIndex(
|
||||||
(static_cast<float>(ChunkCoord.Z) + 0.5f)
|
(static_cast<float>(ChunkCoord.Z) + 0.5f)
|
||||||
* static_cast<float>(CHUNK_SIZE) * VOXEL_SIZE);
|
* static_cast<float>(CHUNK_SIZE) * VOXEL_SIZE);
|
||||||
@@ -558,6 +611,7 @@ namespace
|
|||||||
ChunkMaxX + Expansion, ChunkMaxY + Expansion,
|
ChunkMaxX + Expansion, ChunkMaxY + Expansion,
|
||||||
Params, static_cast<uint32>(Manager.GetWorldSeed()),
|
Params, static_cast<uint32>(Manager.GetWorldSeed()),
|
||||||
StrateIndex, nullptr);
|
StrateIndex, nullptr);
|
||||||
|
BuildTunnelSupportColumnsForChunk(OutState.Cache, ChunkCoord);
|
||||||
OutState.OwnerId = OwnerId;
|
OutState.OwnerId = OwnerId;
|
||||||
OutState.ManagerLifetimeId = ManagerLifetimeId;
|
OutState.ManagerLifetimeId = ManagerLifetimeId;
|
||||||
OutState.Chunk = ChunkCoord;
|
OutState.Chunk = ChunkCoord;
|
||||||
@@ -566,6 +620,67 @@ namespace
|
|||||||
OutState.bValid = true;
|
OutState.bValid = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void BuildTunnelSupportColumnsForChunk(
|
||||||
|
FChunkSDFCache& Cache,
|
||||||
|
const FIntVector& ChunkCoord)
|
||||||
|
{
|
||||||
|
const int32 MinX = ChunkCoord.X * CHUNK_SIZE - 1;
|
||||||
|
const int32 MinY = ChunkCoord.Y * CHUNK_SIZE - 1;
|
||||||
|
const int32 Cells = CHUNK_SIZE + 2;
|
||||||
|
Cache.SupportColumnMinX = MinX;
|
||||||
|
Cache.SupportColumnMinY = MinY;
|
||||||
|
Cache.SupportColumnCellsX = Cells;
|
||||||
|
Cache.SupportColumnCellsY = Cells;
|
||||||
|
Cache.SupportColumnEntries.Init(INDEX_NONE, Cells * Cells);
|
||||||
|
Cache.SupportColumns.Reset();
|
||||||
|
|
||||||
|
// Empty columns are represented only by INDEX_NONE in the slot map, so
|
||||||
|
// each stored column retains the inline bands for actual graph tubes.
|
||||||
|
for (int32 Y = 0; Y < Cells; ++Y)
|
||||||
|
{
|
||||||
|
for (int32 X = 0; X < Cells; ++X)
|
||||||
|
{
|
||||||
|
FTunnelSupportFloorColumn Column;
|
||||||
|
VoxelCaveMorphology::BuildTunnelSupportFloorColumn(
|
||||||
|
static_cast<float>(MinX + X),
|
||||||
|
static_cast<float>(MinY + Y),
|
||||||
|
Cache,
|
||||||
|
Column);
|
||||||
|
if (Column.Intervals.Num() == 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int32 EntryIndex = Cache.SupportColumns.Add(MoveTemp(Column));
|
||||||
|
Cache.SupportColumnEntries[Y * Cells + X] = EntryIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const FTunnelSupportFloorColumn* FindTunnelSupportColumn(
|
||||||
|
const FChunkSDFCache& Cache,
|
||||||
|
int32 X,
|
||||||
|
int32 Y,
|
||||||
|
const FTunnelSupportFloorColumn& EmptyColumn)
|
||||||
|
{
|
||||||
|
if (Cache.SupportColumnCellsX <= 0
|
||||||
|
|| X < Cache.SupportColumnMinX
|
||||||
|
|| X >= Cache.SupportColumnMinX + Cache.SupportColumnCellsX
|
||||||
|
|| Y < Cache.SupportColumnMinY
|
||||||
|
|| Y >= Cache.SupportColumnMinY + Cache.SupportColumnCellsY)
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int32 Slot =
|
||||||
|
(Y - Cache.SupportColumnMinY) * Cache.SupportColumnCellsX
|
||||||
|
+ (X - Cache.SupportColumnMinX);
|
||||||
|
const int32 EntryIndex = Cache.SupportColumnEntries[Slot];
|
||||||
|
return EntryIndex == INDEX_NONE
|
||||||
|
? &EmptyColumn
|
||||||
|
: &Cache.SupportColumns[EntryIndex];
|
||||||
|
}
|
||||||
|
|
||||||
struct FVoxelStackParamRefs
|
struct FVoxelStackParamRefs
|
||||||
{
|
{
|
||||||
const FSlabGenerationParams* Slab = nullptr;
|
const FSlabGenerationParams* Slab = nullptr;
|
||||||
@@ -739,6 +854,8 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co
|
|||||||
{
|
{
|
||||||
VoxelDensityProfile::FScopedTimer DensityProfileTimer(
|
VoxelDensityProfile::FScopedTimer DensityProfileTimer(
|
||||||
VoxelDensityProfile::EBucket::GetDensityAt);
|
VoxelDensityProfile::EBucket::GetDensityAt);
|
||||||
|
VoxelDensityProfile::FScopedTimer DensityPrologueTimer(
|
||||||
|
VoxelDensityProfile::EBucket::DensityPrologue);
|
||||||
// ── STRATE SYSTEM ──
|
// ── STRATE SYSTEM ──
|
||||||
// Query per-chunk params from the manager so each strate has different caves.
|
// Query per-chunk params from the manager so each strate has different caves.
|
||||||
float Result;
|
float Result;
|
||||||
@@ -762,6 +879,9 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co
|
|||||||
// to reach the next layer. No caves, no spine, no vertical seal — just rock + passage
|
// to reach the next layer. No caves, no spine, no vertical seal — just rock + passage
|
||||||
// tube/landing;
|
// tube/landing;
|
||||||
// the global XY edge seal is applied below for bounded worlds.
|
// the global XY edge seal is applied below for bounded worlds.
|
||||||
|
DensityPrologueTimer.End();
|
||||||
|
VoxelDensityProfile::FScopedTimer DensityCoreTimer(
|
||||||
|
VoxelDensityProfile::EBucket::DensityCore);
|
||||||
float Density = 8.0f; // bedrock solidity (positive = solid)
|
float Density = 8.0f; // bedrock solidity (positive = solid)
|
||||||
StrateManager->ApplyPassageModifier(
|
StrateManager->ApplyPassageModifier(
|
||||||
Density, WorldX, WorldY, WorldZ, 8.0f, 0.0f);
|
Density, WorldX, WorldY, WorldZ, 8.0f, 0.0f);
|
||||||
@@ -1161,6 +1281,7 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co
|
|||||||
CP_ActiveTunnelDensityCache = &CachedEntry;
|
CP_ActiveTunnelDensityCache = &CachedEntry;
|
||||||
ActiveOpStack = &CachedEntry.OpStack;
|
ActiveOpStack = &CachedEntry.OpStack;
|
||||||
ActiveTunnelCoreCache = &CachedEntry.TunnelCore;
|
ActiveTunnelCoreCache = &CachedEntry.TunnelCore;
|
||||||
|
ReportTunnelDensityCacheFootprint(CP_TunnelDensityCache);
|
||||||
}
|
}
|
||||||
else if (!bLoadedTunnelDensityCache)
|
else if (!bLoadedTunnelDensityCache)
|
||||||
{
|
{
|
||||||
@@ -1170,6 +1291,10 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DensityPrologueTimer.End();
|
||||||
|
VoxelDensityProfile::FScopedTimer DensityCoreTimer(
|
||||||
|
VoxelDensityProfile::EBucket::DensityCore);
|
||||||
|
|
||||||
// Le seul point d'entrée de la pile dans le chemin de production. Elle rend la convention
|
// Le seul point d'entrée de la pile dans le chemin de production. Elle rend la convention
|
||||||
// MC (négatif = solide) comme les fonctions d'archétype, donc les disturbances et la couche
|
// MC (négatif = solide) comme les fonctions d'archétype, donc les disturbances et la couche
|
||||||
// de diff qui suivent ne voient aucune différence.
|
// de diff qui suivent ne voient aucune différence.
|
||||||
@@ -1226,21 +1351,28 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co
|
|||||||
default:
|
default:
|
||||||
// Underwater shares tunnel rock (water table is a render-side overlay).
|
// Underwater shares tunnel rock (water table is a render-side overlay).
|
||||||
Result = GetDensityWithParams(WorldX, WorldY, WorldZ, CP_Tunnel,
|
Result = GetDensityWithParams(WorldX, WorldY, WorldZ, CP_Tunnel,
|
||||||
CP_TunnelFP, LayoutVersion); break;
|
CP_TunnelFP, LayoutVersion,
|
||||||
|
/*bApplyLegacyStructuralPosts=*/false); break;
|
||||||
}
|
}
|
||||||
|
DensityCoreTimer.End();
|
||||||
|
|
||||||
// Disturbance layer (the "wow" post-process) — cached params, MC convention. The
|
// Disturbance layer (the "wow" post-process) — cached params, MC convention. The
|
||||||
// vertical shaft tree opens a walkable route before this shared pass, so a bridge/ridge
|
// vertical shaft tree opens a walkable route before this shared pass, so a bridge/ridge
|
||||||
// must not refill it. Both native and operator-stack paths use the same marker helper.
|
// must not refill it. Both native and operator-stack paths use the same marker helper.
|
||||||
|
VoxelDensityProfile::FScopedTimer DensityDisturbancesTimer(
|
||||||
|
VoxelDensityProfile::EBucket::DensityDisturbances);
|
||||||
const bool bProtectVerticalShaftAir =
|
const bool bProtectVerticalShaftAir =
|
||||||
VoxelPassageGeometry::VerticalShaftConnectorAirMarker();
|
VoxelPassageGeometry::VerticalShaftConnectorAirMarker();
|
||||||
ApplyDisturbances(Result, WorldX, WorldY, WorldZ, CP_Dist, (uint32)Seed,
|
ApplyDisturbances(Result, WorldX, WorldY, WorldZ, CP_Dist, (uint32)Seed,
|
||||||
bProtectVerticalShaftAir);
|
bProtectVerticalShaftAir);
|
||||||
|
DensityDisturbancesTimer.End();
|
||||||
|
|
||||||
// A disturbance is allowed to add visual detail, but it must not refill the landing's
|
// A disturbance is allowed to add visual detail, but it must not refill the landing's
|
||||||
// measured air volume or turn its support slab back into a hole. Reassert the landing
|
// measured air volume or turn its support slab back into a hole. Reassert the landing
|
||||||
// air first, then the structural floor, in MC space before the final XY seal. The two
|
// air first, then the structural floor, in MC space before the final XY seal. The two
|
||||||
// calls use the same landing geometry already evaluated by the legacy and op-stack post.
|
// calls use the same landing geometry already evaluated by the legacy and op-stack post.
|
||||||
|
VoxelDensityProfile::FScopedTimer DensityStructuralPostsTimer(
|
||||||
|
VoxelDensityProfile::EBucket::DensityStructuralPosts);
|
||||||
float LandingBaseDensity = CP_Dist.BaseDensity;
|
float LandingBaseDensity = CP_Dist.BaseDensity;
|
||||||
#if WITH_EDITOR
|
#if WITH_EDITOR
|
||||||
// A composer parent can deliberately use a different structural base than the authored
|
// A composer parent can deliberately use a different structural base than the authored
|
||||||
@@ -1326,12 +1458,26 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co
|
|||||||
// never once per voxel.
|
// never once per voxel.
|
||||||
if (ActiveTunnelCoreCache->bValid)
|
if (ActiveTunnelCoreCache->bValid)
|
||||||
{
|
{
|
||||||
|
const FTunnelSupportFloorColumn* SupportColumn = nullptr;
|
||||||
|
FTunnelSupportFloorColumn EmptySupportColumn;
|
||||||
|
const bool bIntegerXY =
|
||||||
|
WorldX == FMath::FloorToFloat(WorldX)
|
||||||
|
&& WorldY == FMath::FloorToFloat(WorldY);
|
||||||
|
if (bIntegerXY)
|
||||||
|
{
|
||||||
|
const int32 IX = FMath::FloorToInt(WorldX);
|
||||||
|
const int32 IY = FMath::FloorToInt(WorldY);
|
||||||
|
SupportColumn = FindTunnelSupportColumn(
|
||||||
|
ActiveTunnelCoreCache->Cache, IX, IY, EmptySupportColumn);
|
||||||
|
}
|
||||||
|
|
||||||
FTunnelCoreWorldEvaluation TunnelCore;
|
FTunnelCoreWorldEvaluation TunnelCore;
|
||||||
{
|
{
|
||||||
VoxelDensityProfile::FScopedTimer ProfileTimer(
|
VoxelDensityProfile::FScopedTimer ProfileTimer(
|
||||||
VoxelDensityProfile::EBucket::TunnelCorePosts);
|
VoxelDensityProfile::EBucket::TunnelCorePosts);
|
||||||
TunnelCore = VoxelCaveMorphology::EvaluateTunnelCoreWorld(
|
TunnelCore = VoxelCaveMorphology::EvaluateTunnelCoreWorld(
|
||||||
WorldX, WorldY, WorldZ, ActiveTunnelCoreCache->Cache);
|
WorldX, WorldY, WorldZ, ActiveTunnelCoreCache->Cache,
|
||||||
|
SupportColumn);
|
||||||
}
|
}
|
||||||
const bool bTunnelSupportFloor = TunnelCore.bSupportFloor;
|
const bool bTunnelSupportFloor = TunnelCore.bSupportFloor;
|
||||||
if (bTunnelSupportFloor)
|
if (bTunnelSupportFloor)
|
||||||
@@ -1362,6 +1508,7 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co
|
|||||||
StrateManager->ApplyPassageLandingRoomFloorMC(
|
StrateManager->ApplyPassageLandingRoomFloorMC(
|
||||||
Result, WorldX, WorldY, WorldZ, LandingBaseDensity);
|
Result, WorldX, WorldY, WorldZ, LandingBaseDensity);
|
||||||
}
|
}
|
||||||
|
DensityStructuralPostsTimer.End();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -1371,16 +1518,23 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co
|
|||||||
// aussi. La calculer une fois évite un CRC par voxel sur un chemin qui n'en a aucun besoin.
|
// aussi. La calculer une fois évite un CRC par voxel sur un chemin qui n'en a aucun besoin.
|
||||||
// `LayoutVersion = 0` : sans `StrateManager` il n'y a pas de layout, donc rien qui puisse
|
// `LayoutVersion = 0` : sans `StrateManager` il n'y a pas de layout, donc rien qui puisse
|
||||||
// périmer — et l'empreinte constante suffit à distinguer ce cache de tous les autres.
|
// périmer — et l'empreinte constante suffit à distinguer ce cache de tous les autres.
|
||||||
|
DensityPrologueTimer.End();
|
||||||
|
VoxelDensityProfile::FScopedTimer DensityCoreTimer(
|
||||||
|
VoxelDensityProfile::EBucket::DensityCore);
|
||||||
static const FStrateGenerationParams FallbackParams;
|
static const FStrateGenerationParams FallbackParams;
|
||||||
static const uint32 FallbackFP = FCrc::MemCrc32(&FallbackParams, sizeof(FallbackParams));
|
static const uint32 FallbackFP = FCrc::MemCrc32(&FallbackParams, sizeof(FallbackParams));
|
||||||
Result = GetDensityWithParams(WorldX, WorldY, WorldZ, FallbackParams, FallbackFP, 0);
|
Result = GetDensityWithParams(WorldX, WorldY, WorldZ, FallbackParams, FallbackFP, 0);
|
||||||
|
DensityCoreTimer.End();
|
||||||
}
|
}
|
||||||
|
|
||||||
// The edge is the final generated structural invariant. Passage carving is ordered before it
|
// The edge is the final generated structural invariant. Passage carving is ordered before it
|
||||||
// in every archetype/stack, and this MC-facing pass also covers out-of-layout air plus any
|
// in every archetype/stack, and this MC-facing pass also covers out-of-layout air plus any
|
||||||
// disturbance that might otherwise carve back into the shell. Player edits remain separate
|
// disturbance that might otherwise carve back into the shell. Player edits remain separate
|
||||||
// below and are deliberately still the user override path.
|
// below and are deliberately still the user override path.
|
||||||
|
VoxelDensityProfile::FScopedTimer DensityBoundarySealTimer(
|
||||||
|
VoxelDensityProfile::EBucket::DensityBoundarySeal);
|
||||||
VF_ApplyXYEdgeSealMC(Result, WorldX, WorldY, WorldRadiusVoxels, EdgeSealThickness, 8.0f);
|
VF_ApplyXYEdgeSealMC(Result, WorldX, WorldY, WorldRadiusVoxels, EdgeSealThickness, 8.0f);
|
||||||
|
DensityBoundarySealTimer.End();
|
||||||
|
|
||||||
//=========================================================================
|
//=========================================================================
|
||||||
// PLAYER MODIFICATIONS (diff layer)
|
// PLAYER MODIFICATIONS (diff layer)
|
||||||
@@ -1391,6 +1545,8 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co
|
|||||||
// we subtract the diff offset:
|
// we subtract the diff offset:
|
||||||
// Carve (diff < 0) → Result -= negative → Result increases → more air ✓
|
// Carve (diff < 0) → Result -= negative → Result increases → more air ✓
|
||||||
// Fill (diff > 0) → Result -= positive → Result decreases → more solid ✓
|
// Fill (diff > 0) → Result -= positive → Result decreases → more solid ✓
|
||||||
|
VoxelDensityProfile::FScopedTimer DensityDiffLayerTimer(
|
||||||
|
VoxelDensityProfile::EBucket::DensityDiffLayer);
|
||||||
if (DiffLayer && DiffLayer->HasAnyMods())
|
if (DiffLayer && DiffLayer->HasAnyMods())
|
||||||
{
|
{
|
||||||
// ── PER-CHUNK MOD SNAPSHOT ──
|
// ── PER-CHUNK MOD SNAPSHOT ──
|
||||||
@@ -1422,14 +1578,18 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co
|
|||||||
Result -= UVoxelDiffLayer::EvaluateMods(Slot.Mods, WorldX, WorldY, WorldZ);
|
Result -= UVoxelDiffLayer::EvaluateMods(Slot.Mods, WorldX, WorldY, WorldZ);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
DensityDiffLayerTimer.End();
|
||||||
|
|
||||||
|
VoxelDensityProfile::FScopedTimer DensityTailTimer(
|
||||||
|
VoxelDensityProfile::EBucket::DensityTail);
|
||||||
VoxelPassageGeometry::ResetVerticalShaftConnectorAirMarker();
|
VoxelPassageGeometry::ResetVerticalShaftConnectorAirMarker();
|
||||||
return Result;
|
return Result;
|
||||||
}
|
}
|
||||||
|
|
||||||
float UVoxelGenerator::GetDensityWithParams(float WorldX, float WorldY, float WorldZ,
|
float UVoxelGenerator::GetDensityWithParams(float WorldX, float WorldY, float WorldZ,
|
||||||
const FStrateGenerationParams& Params,
|
const FStrateGenerationParams& Params,
|
||||||
uint32 ParamsFingerprint, uint32 LayoutVersion) const
|
uint32 ParamsFingerprint, uint32 LayoutVersion,
|
||||||
|
bool bApplyLegacyStructuralPosts) const
|
||||||
{
|
{
|
||||||
//=========================================================================
|
//=========================================================================
|
||||||
// STRATE DENSITY FUNCTION (Morphology Pipeline)
|
// STRATE DENSITY FUNCTION (Morphology Pipeline)
|
||||||
@@ -1505,15 +1665,15 @@ float UVoxelGenerator::GetDensityWithParams(float WorldX, float WorldY, float Wo
|
|||||||
// Three independent Perlin fields offset by irrational-ish numbers
|
// Three independent Perlin fields offset by irrational-ish numbers
|
||||||
// so the X/Y/Z warp channels don't correlate with each other.
|
// so the X/Y/Z warp channels don't correlate with each other.
|
||||||
// Single octave to keep per-voxel cost low (3 Perlin calls total).
|
// Single octave to keep per-voxel cost low (3 Perlin calls total).
|
||||||
WarpedX += VoxelNoise::Perlin3D(FVector(
|
WarpedX += VoxelNoise::Perlin3D(FVector3f(
|
||||||
WorldX * WF + VoxelHash::SeedOffset(SeedU, 0.37f),
|
WorldX * WF + VoxelHash::SeedOffset(SeedU, 0.37f),
|
||||||
WorldY * WF + 1.3f,
|
WorldY * WF + 1.3f,
|
||||||
EffectiveZ * WF + 5.7f)) * VOXEL_NOISE_SCALE * WS;
|
EffectiveZ * WF + 5.7f)) * VOXEL_NOISE_SCALE * WS;
|
||||||
WarpedY += VoxelNoise::Perlin3D(FVector(
|
WarpedY += VoxelNoise::Perlin3D(FVector3f(
|
||||||
WorldX * WF + 7.1f,
|
WorldX * WF + 7.1f,
|
||||||
WorldY * WF + VoxelHash::SeedOffset(SeedU, 0.59f),
|
WorldY * WF + VoxelHash::SeedOffset(SeedU, 0.59f),
|
||||||
EffectiveZ * WF + 2.3f)) * VOXEL_NOISE_SCALE * WS;
|
EffectiveZ * WF + 2.3f)) * VOXEL_NOISE_SCALE * WS;
|
||||||
WarpedZ += VoxelNoise::Perlin3D(FVector(
|
WarpedZ += VoxelNoise::Perlin3D(FVector3f(
|
||||||
WorldX * WF + 11.3f,
|
WorldX * WF + 11.3f,
|
||||||
WorldY * WF + 9.7f,
|
WorldY * WF + 9.7f,
|
||||||
EffectiveZ * WF + VoxelHash::SeedOffset(SeedU, 0.41f))) * VOXEL_NOISE_SCALE * WS;
|
EffectiveZ * WF + VoxelHash::SeedOffset(SeedU, 0.41f))) * VOXEL_NOISE_SCALE * WS;
|
||||||
@@ -1619,7 +1779,8 @@ float UVoxelGenerator::GetDensityWithParams(float WorldX, float WorldY, float Wo
|
|||||||
const float ChunkMinY = CacheChunkY * (float)CHUNK_SIZE;
|
const float ChunkMinY = CacheChunkY * (float)CHUNK_SIZE;
|
||||||
const float ChunkMaxX = ChunkMinX + (float)CHUNK_SIZE;
|
const float ChunkMaxX = ChunkMinX + (float)CHUNK_SIZE;
|
||||||
const float ChunkMaxY = ChunkMinY + (float)CHUNK_SIZE;
|
const float ChunkMaxY = ChunkMinY + (float)CHUNK_SIZE;
|
||||||
const float Expansion = Params.CaveWarpStrength + 2.0f;
|
const float Expansion = FMath::Abs(Params.CaveWarpStrength)
|
||||||
|
* VOXEL_NOISE_SCALE * 1.5f + 2.0f;
|
||||||
|
|
||||||
const float SMinX = ChunkMinX - Expansion;
|
const float SMinX = ChunkMinX - Expansion;
|
||||||
const float SMinY = ChunkMinY - Expansion;
|
const float SMinY = ChunkMinY - Expansion;
|
||||||
@@ -1786,12 +1947,12 @@ float UVoxelGenerator::GetDensityWithParams(float WorldX, float WorldY, float Wo
|
|||||||
float RF = Params.RoughnessFrequency;
|
float RF = Params.RoughnessFrequency;
|
||||||
|
|
||||||
// Base noise input positions (with seed offsets for uniqueness)
|
// Base noise input positions (with seed offsets for uniqueness)
|
||||||
FVector MainPos(
|
FVector3f MainPos(
|
||||||
WorldX * RF + VoxelHash::SeedOffset(SeedU, 11.3f),
|
WorldX * RF + VoxelHash::SeedOffset(SeedU, 11.3f),
|
||||||
WorldY * RF + VoxelHash::SeedOffset(SeedU, 13.7f),
|
WorldY * RF + VoxelHash::SeedOffset(SeedU, 13.7f),
|
||||||
EffectiveZ * RF + VoxelHash::SeedOffset(SeedU, 17.1f)
|
EffectiveZ * RF + VoxelHash::SeedOffset(SeedU, 17.1f)
|
||||||
);
|
);
|
||||||
FVector FinePos(
|
FVector3f FinePos(
|
||||||
WorldX * RF * 3.0f + VoxelHash::SeedOffset(SeedU, 19.1f) + 2000.0f,
|
WorldX * RF * 3.0f + VoxelHash::SeedOffset(SeedU, 19.1f) + 2000.0f,
|
||||||
WorldY * RF * 3.0f + VoxelHash::SeedOffset(SeedU, 23.7f) + 2500.0f,
|
WorldY * RF * 3.0f + VoxelHash::SeedOffset(SeedU, 23.7f) + 2500.0f,
|
||||||
EffectiveZ * RF * 3.0f + VoxelHash::SeedOffset(SeedU, 29.3f) + 3000.0f
|
EffectiveZ * RF * 3.0f + VoxelHash::SeedOffset(SeedU, 29.3f) + 3000.0f
|
||||||
@@ -1807,26 +1968,26 @@ float UVoxelGenerator::GetDensityWithParams(float WorldX, float WorldY, float Wo
|
|||||||
float WS = Params.DomainWarpStrength;
|
float WS = Params.DomainWarpStrength;
|
||||||
|
|
||||||
// Sample three independent noise fields for X, Y, Z warp
|
// Sample three independent noise fields for X, Y, Z warp
|
||||||
float WarpX = VoxelNoise::Perlin3D(FVector(
|
float WarpX = VoxelNoise::Perlin3D(FVector3f(
|
||||||
WorldX * WF + VoxelHash::SeedOffset(SeedU, 5.2f),
|
WorldX * WF + VoxelHash::SeedOffset(SeedU, 5.2f),
|
||||||
WorldY * WF + VoxelHash::SeedOffset(SeedU, 1.3f),
|
WorldY * WF + VoxelHash::SeedOffset(SeedU, 1.3f),
|
||||||
EffectiveZ * WF + VoxelHash::SeedOffset(SeedU, 9.7f)
|
EffectiveZ * WF + VoxelHash::SeedOffset(SeedU, 9.7f)
|
||||||
)) * VOXEL_NOISE_SCALE * WS;
|
)) * VOXEL_NOISE_SCALE * WS;
|
||||||
|
|
||||||
float WarpY = VoxelNoise::Perlin3D(FVector(
|
float WarpY = VoxelNoise::Perlin3D(FVector3f(
|
||||||
WorldX * WF + 100.0f + VoxelHash::SeedOffset(SeedU, 7.7f),
|
WorldX * WF + 100.0f + VoxelHash::SeedOffset(SeedU, 7.7f),
|
||||||
WorldY * WF + 200.0f + VoxelHash::SeedOffset(SeedU, 3.1f),
|
WorldY * WF + 200.0f + VoxelHash::SeedOffset(SeedU, 3.1f),
|
||||||
EffectiveZ * WF + 300.0f
|
EffectiveZ * WF + 300.0f
|
||||||
)) * VOXEL_NOISE_SCALE * WS;
|
)) * VOXEL_NOISE_SCALE * WS;
|
||||||
|
|
||||||
float WarpZ = VoxelNoise::Perlin3D(FVector(
|
float WarpZ = VoxelNoise::Perlin3D(FVector3f(
|
||||||
WorldX * WF + 400.0f,
|
WorldX * WF + 400.0f,
|
||||||
WorldY * WF + 500.0f + VoxelHash::SeedOffset(SeedU, 11.9f),
|
WorldY * WF + 500.0f + VoxelHash::SeedOffset(SeedU, 11.9f),
|
||||||
EffectiveZ * WF + 600.0f + VoxelHash::SeedOffset(SeedU, 13.3f)
|
EffectiveZ * WF + 600.0f + VoxelHash::SeedOffset(SeedU, 13.3f)
|
||||||
)) * VOXEL_NOISE_SCALE * WS;
|
)) * VOXEL_NOISE_SCALE * WS;
|
||||||
|
|
||||||
// Apply warp to both noise positions
|
// Apply warp to both noise positions
|
||||||
FVector WarpOffset(WarpX, WarpY, WarpZ);
|
FVector3f WarpOffset(WarpX, WarpY, WarpZ);
|
||||||
MainPos += WarpOffset;
|
MainPos += WarpOffset;
|
||||||
FinePos += WarpOffset;
|
FinePos += WarpOffset;
|
||||||
}
|
}
|
||||||
@@ -1978,7 +2139,7 @@ float UVoxelGenerator::GetDensityWithParams(float WorldX, float WorldY, float Wo
|
|||||||
float NoisedZ = WorldZ;
|
float NoisedZ = WorldZ;
|
||||||
if (Params.TerraceNoiseDisplacement > 0.0f)
|
if (Params.TerraceNoiseDisplacement > 0.0f)
|
||||||
{
|
{
|
||||||
float DispNoise = FractalNoise3D(FVector(
|
float DispNoise = FractalNoise3D(FVector3f(
|
||||||
WorldX * 0.04f + VoxelHash::SeedOffset(SeedU, 31.1f),
|
WorldX * 0.04f + VoxelHash::SeedOffset(SeedU, 31.1f),
|
||||||
WorldY * 0.04f + VoxelHash::SeedOffset(SeedU, 37.3f),
|
WorldY * 0.04f + VoxelHash::SeedOffset(SeedU, 37.3f),
|
||||||
WorldZ * 0.02f + VoxelHash::SeedOffset(SeedU, 41.7f)
|
WorldZ * 0.02f + VoxelHash::SeedOffset(SeedU, 41.7f)
|
||||||
@@ -2104,7 +2265,7 @@ float UVoxelGenerator::GetDensityWithParams(float WorldX, float WorldY, float Wo
|
|||||||
if (DistFromSurface < OverhangRange)
|
if (DistFromSurface < OverhangRange)
|
||||||
{
|
{
|
||||||
// Low Z frequency (0.15x of XY) → features extend horizontally
|
// Low Z frequency (0.15x of XY) → features extend horizontally
|
||||||
float OverhangNoise = FractalNoise3D(FVector(
|
float OverhangNoise = FractalNoise3D(FVector3f(
|
||||||
WorldX * Params.OverhangFrequency + VoxelHash::SeedOffset(SeedU, 53.1f),
|
WorldX * Params.OverhangFrequency + VoxelHash::SeedOffset(SeedU, 53.1f),
|
||||||
WorldY * Params.OverhangFrequency + VoxelHash::SeedOffset(SeedU, 59.3f),
|
WorldY * Params.OverhangFrequency + VoxelHash::SeedOffset(SeedU, 59.3f),
|
||||||
EffectiveZ * Params.OverhangFrequency * 0.15f + VoxelHash::SeedOffset(SeedU, 61.7f)
|
EffectiveZ * Params.OverhangFrequency * 0.15f + VoxelHash::SeedOffset(SeedU, 61.7f)
|
||||||
@@ -2144,7 +2305,7 @@ float UVoxelGenerator::GetDensityWithParams(float WorldX, float WorldY, float Wo
|
|||||||
// Near the surface (CaveSDF ≈ 0), the sign of CaveSDF tells us which
|
// Near the surface (CaveSDF ≈ 0), the sign of CaveSDF tells us which
|
||||||
// side we're on: negative = inside cave, positive = solid rock.
|
// side we're on: negative = inside cave, positive = solid rock.
|
||||||
// We use a noise-modulated vertical gradient to detect steep faces.
|
// We use a noise-modulated vertical gradient to detect steep faces.
|
||||||
float VertGrad = VoxelNoise::Perlin3D(FVector(
|
float VertGrad = VoxelNoise::Perlin3D(FVector3f(
|
||||||
WorldX * 0.05f + VoxelHash::SeedOffset(SeedU, 71.3f),
|
WorldX * 0.05f + VoxelHash::SeedOffset(SeedU, 71.3f),
|
||||||
WorldY * 0.05f + VoxelHash::SeedOffset(SeedU, 73.7f),
|
WorldY * 0.05f + VoxelHash::SeedOffset(SeedU, 73.7f),
|
||||||
EffectiveZ * 0.15f + VoxelHash::SeedOffset(SeedU, 79.1f) // 3x faster in Z → detects vertical features
|
EffectiveZ * 0.15f + VoxelHash::SeedOffset(SeedU, 79.1f) // 3x faster in Z → detects vertical features
|
||||||
@@ -2185,7 +2346,7 @@ float UVoxelGenerator::GetDensityWithParams(float WorldX, float WorldY, float Wo
|
|||||||
{
|
{
|
||||||
// Cellular noise: returns ~[-1, 1] where positive = cell interior (bowl)
|
// Cellular noise: returns ~[-1, 1] where positive = cell interior (bowl)
|
||||||
float SF = Params.ScallopFrequency;
|
float SF = Params.ScallopFrequency;
|
||||||
float ScallopNoise = CellularNoise3D(FVector(
|
float ScallopNoise = CellularNoise3D(FVector3f(
|
||||||
WorldX * SF + VoxelHash::SeedOffset(SeedU, 83.1f),
|
WorldX * SF + VoxelHash::SeedOffset(SeedU, 83.1f),
|
||||||
WorldY * SF + VoxelHash::SeedOffset(SeedU, 89.3f),
|
WorldY * SF + VoxelHash::SeedOffset(SeedU, 89.3f),
|
||||||
EffectiveZ * SF + VoxelHash::SeedOffset(SeedU, 97.7f)
|
EffectiveZ * SF + VoxelHash::SeedOffset(SeedU, 97.7f)
|
||||||
@@ -2212,47 +2373,21 @@ float UVoxelGenerator::GetDensityWithParams(float WorldX, float WorldY, float Wo
|
|||||||
&& NearestRoomIdx >= 0)
|
&& NearestRoomIdx >= 0)
|
||||||
{
|
{
|
||||||
const FCachedRoom& Room = SDFCache.Rooms[NearestRoomIdx];
|
const FCachedRoom& Room = SDFCache.Rooms[NearestRoomIdx];
|
||||||
const int32 MaxArches = 3;
|
|
||||||
const FVector VoxPos(WorldX, WorldY, WorldZ);
|
const FVector VoxPos(WorldX, WorldY, WorldZ);
|
||||||
|
|
||||||
for (int32 i = 0; i < MaxArches; i++)
|
for (int32 i = 0; i < 3; i++)
|
||||||
{
|
{
|
||||||
uint32 AH = VoxelHash::Mix(Room.Hash ^ (0xA4C400u + (uint32)i * 7369u));
|
const FCachedArch& Arch = Room.Arches[i];
|
||||||
|
if (!Arch.bActive) { continue; }
|
||||||
if (VoxelHash::ToFloat01(AH) > Params.ArchDensity) continue;
|
const float ArchSDF = VoxelSDF::Capsule(
|
||||||
|
VoxPos, Arch.EndpointA, Arch.EndpointB, Arch.Radius);
|
||||||
// Arch center XY: small offset from room center
|
|
||||||
uint32 AH2 = VoxelHash::Mix(AH ^ 0xA4C4u);
|
|
||||||
float ArcCX = Room.Center.X + VoxelHash::ToFloatSigned(AH2) * Room.RadiusXY * 0.3f;
|
|
||||||
float ArcCY = Room.Center.Y + VoxelHash::ToFloatSigned(VoxelHash::Mix(AH2)) * Room.RadiusXY * 0.3f;
|
|
||||||
|
|
||||||
// Arch height: mid-room Z (bridging the open space)
|
|
||||||
uint32 AH3 = VoxelHash::Mix(AH2 ^ 0xB41Du);
|
|
||||||
float ArcCZ = Room.Center.Z + VoxelHash::ToFloatSigned(AH3) * Room.RadiusZ * 0.4f;
|
|
||||||
|
|
||||||
// Arch direction and span: stretch across most of the room
|
|
||||||
uint32 AH4 = VoxelHash::Mix(AH3 ^ 0xCAFEu);
|
|
||||||
float Angle = VoxelHash::ToFloat01(AH4) * PI;
|
|
||||||
float HalfSpan = Room.RadiusXY * (0.5f + VoxelHash::ToFloat01(VoxelHash::Mix(AH4)) * 0.35f);
|
|
||||||
|
|
||||||
float CosA = FMath::Cos(Angle);
|
|
||||||
float SinA = FMath::Sin(Angle);
|
|
||||||
FVector ArchA(ArcCX - CosA * HalfSpan, ArcCY - SinA * HalfSpan, ArcCZ);
|
|
||||||
FVector ArchB(ArcCX + CosA * HalfSpan, ArcCY + SinA * HalfSpan, ArcCZ);
|
|
||||||
|
|
||||||
// Arch thickness
|
|
||||||
uint32 AH5 = VoxelHash::Mix(AH4 ^ 0xF00Du);
|
|
||||||
float ArchRadius = FMath::Lerp(Params.ArchMinRadius, Params.ArchMaxRadius,
|
|
||||||
VoxelHash::ToFloat01(AH5));
|
|
||||||
|
|
||||||
float ArchSDF = VoxelSDF::Capsule(VoxPos, ArchA, ArchB, ArchRadius);
|
|
||||||
|
|
||||||
const float ArchBlend = 2.0f;
|
const float ArchBlend = 2.0f;
|
||||||
if (ArchSDF < ArchBlend)
|
if (ArchSDF < ArchBlend)
|
||||||
{
|
{
|
||||||
float Fill = FMath::Clamp((ArchBlend - ArchSDF) / (ArchBlend * 2.0f), 0.0f, 1.0f);
|
float Fill = FMath::Clamp((ArchBlend - ArchSDF) / (ArchBlend * 2.0f), 0.0f, 1.0f);
|
||||||
Fill = SmoothStep01(Fill);
|
Fill = SmoothStep01(Fill);
|
||||||
Density += Fill * Params.BaseDensity * 1.5f;
|
Density += Fill * Arch.BaseDensity * 1.5f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2356,58 +2491,32 @@ float UVoxelGenerator::GetDensityWithParams(float WorldX, float WorldY, float Wo
|
|||||||
&& NearestRoomIdx >= 0)
|
&& NearestRoomIdx >= 0)
|
||||||
{
|
{
|
||||||
const FCachedRoom& Room = SDFCache.Rooms[NearestRoomIdx];
|
const FCachedRoom& Room = SDFCache.Rooms[NearestRoomIdx];
|
||||||
// Pinches on the perimeter of the room (near tunnel entry points)
|
for (int32 i = 0; i < 3; i++)
|
||||||
const float Spread = 0.85f;
|
|
||||||
const int32 MaxPinches = 3;
|
|
||||||
|
|
||||||
for (int32 i = 0; i < MaxPinches; i++)
|
|
||||||
{
|
{
|
||||||
uint32 PnH = VoxelHash::Mix(Room.Hash ^ (0xF1C400u + (uint32)i * 5417u));
|
const FCachedPinch& Pinch = Room.Pinches[i];
|
||||||
|
if (!Pinch.bActive) { continue; }
|
||||||
if (VoxelHash::ToFloat01(PnH) > Params.PinchDensity) continue;
|
const float DXPn = WorldX - Pinch.CenterX;
|
||||||
|
const float DYPn = WorldY - Pinch.CenterY;
|
||||||
// XY: near room perimeter (where tunnels meet the room)
|
const float DZPn = WorldZ - Pinch.CenterZ;
|
||||||
uint32 PnH2 = VoxelHash::Mix(PnH ^ 0xF1C4u);
|
|
||||||
float PnX = Room.Center.X + VoxelHash::ToFloatSigned(PnH2) * Room.RadiusXY * Spread;
|
|
||||||
float PnY = Room.Center.Y + VoxelHash::ToFloatSigned(VoxelHash::Mix(PnH2)) * Room.RadiusXY * Spread;
|
|
||||||
|
|
||||||
// Z: mid-height within the room
|
|
||||||
uint32 PnH3 = VoxelHash::Mix(PnH2 ^ 0x5432u);
|
|
||||||
float PnZ = Room.Center.Z + VoxelHash::ToFloatSigned(PnH3) * Room.RadiusZ * 0.5f;
|
|
||||||
|
|
||||||
// Pinch direction aligned toward room center (squeezes inward)
|
|
||||||
uint32 PnH4 = VoxelHash::Mix(PnH3 ^ 0x9A3Bu);
|
|
||||||
float PnAngle = VoxelHash::ToFloat01(PnH4) * PI;
|
|
||||||
float CosPN = FMath::Cos(PnAngle);
|
|
||||||
float SinPN = FMath::Sin(PnAngle);
|
|
||||||
|
|
||||||
float DXPn = WorldX - PnX;
|
|
||||||
float DYPn = WorldY - PnY;
|
|
||||||
float DZPn = WorldZ - PnZ;
|
|
||||||
|
|
||||||
// Quick reject
|
// Quick reject
|
||||||
float MaxExtent = FMath::Max(Params.PinchLength, Params.PinchStrength) + 5.0f;
|
if (FMath::Abs(DXPn) + FMath::Abs(DYPn) + FMath::Abs(DZPn) > Pinch.MaxExtent) continue;
|
||||||
if (FMath::Abs(DXPn) + FMath::Abs(DYPn) + FMath::Abs(DZPn) > MaxExtent) continue;
|
|
||||||
|
|
||||||
float Along = DXPn * CosPN + DYPn * SinPN;
|
const float Along = DXPn * Pinch.CosAngle + DYPn * Pinch.SinAngle;
|
||||||
float Across = -DXPn * SinPN + DYPn * CosPN;
|
const float Across = -DXPn * Pinch.SinAngle + DYPn * Pinch.CosAngle;
|
||||||
|
|
||||||
float HalfLength = Params.PinchLength * 0.5f;
|
const float NAlong = Along / Pinch.HalfLength;
|
||||||
float HalfNarrow = Params.PinchStrength;
|
const float NAcross = Across / Pinch.HalfNarrow;
|
||||||
float HalfVertical = Params.PinchStrength * 1.5f;
|
const float NUp = DZPn / Pinch.HalfVertical;
|
||||||
|
const float EllipDist = NAlong * NAlong + NAcross * NAcross + NUp * NUp;
|
||||||
float NAlong = Along / HalfLength;
|
|
||||||
float NAcross = Across / HalfNarrow;
|
|
||||||
float NUp = DZPn / HalfVertical;
|
|
||||||
float EllipDist = NAlong * NAlong + NAcross * NAcross + NUp * NUp;
|
|
||||||
|
|
||||||
if (EllipDist < 1.0f)
|
if (EllipDist < 1.0f)
|
||||||
{
|
{
|
||||||
float Fill = 1.0f - EllipDist;
|
float Fill = 1.0f - EllipDist;
|
||||||
Fill = SmoothStep01(Fill);
|
Fill = SmoothStep01(Fill);
|
||||||
float AxisDist = FMath::Sqrt(NAcross * NAcross + NUp * NUp);
|
const float AxisDist = FMath::Sqrt(NAcross * NAcross + NUp * NUp);
|
||||||
float SideFactor = FMath::Clamp(AxisDist * 2.0f, 0.0f, 1.0f);
|
const float SideFactor = FMath::Clamp(AxisDist * 2.0f, 0.0f, 1.0f);
|
||||||
Density += Fill * SideFactor * Params.BaseDensity * 1.5f;
|
Density += Fill * SideFactor * Pinch.BaseDensity * 1.5f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2480,7 +2589,7 @@ float UVoxelGenerator::GetDensityWithParams(float WorldX, float WorldY, float Wo
|
|||||||
{
|
{
|
||||||
float WormZFreq = Params.WormFrequency * Params.WormHorizontalBias;
|
float WormZFreq = Params.WormFrequency * Params.WormHorizontalBias;
|
||||||
|
|
||||||
float N1 = FMath::Abs(VoxelNoise::Perlin3D(FVector(
|
float N1 = FMath::Abs(VoxelNoise::Perlin3D(FVector3f(
|
||||||
WorldX * Params.WormFrequency + VoxelHash::SeedOffset(SeedU, 1.0f),
|
WorldX * Params.WormFrequency + VoxelHash::SeedOffset(SeedU, 1.0f),
|
||||||
WorldY * Params.WormFrequency + VoxelHash::SeedOffset(SeedU, 1.7f),
|
WorldY * Params.WormFrequency + VoxelHash::SeedOffset(SeedU, 1.7f),
|
||||||
EffectiveZ * WormZFreq + VoxelHash::SeedOffset(SeedU, 2.3f)
|
EffectiveZ * WormZFreq + VoxelHash::SeedOffset(SeedU, 2.3f)
|
||||||
@@ -2490,7 +2599,7 @@ float UVoxelGenerator::GetDensityWithParams(float WorldX, float WorldY, float Wo
|
|||||||
// skip the second Perlin entirely (most voxels; bit-identical output).
|
// skip the second Perlin entirely (most voxels; bit-identical output).
|
||||||
if (N1 < Params.WormThreshold)
|
if (N1 < Params.WormThreshold)
|
||||||
{
|
{
|
||||||
float N2 = FMath::Abs(VoxelNoise::Perlin3D(FVector(
|
float N2 = FMath::Abs(VoxelNoise::Perlin3D(FVector3f(
|
||||||
WorldX * Params.WormFrequency + VoxelHash::SeedOffset(SeedU, 1.0f) + 137.0f,
|
WorldX * Params.WormFrequency + VoxelHash::SeedOffset(SeedU, 1.0f) + 137.0f,
|
||||||
WorldY * Params.WormFrequency + VoxelHash::SeedOffset(SeedU, 1.7f) + 259.0f,
|
WorldY * Params.WormFrequency + VoxelHash::SeedOffset(SeedU, 1.7f) + 259.0f,
|
||||||
EffectiveZ * WormZFreq + VoxelHash::SeedOffset(SeedU, 2.3f) + 431.0f
|
EffectiveZ * WormZFreq + VoxelHash::SeedOffset(SeedU, 2.3f) + 431.0f
|
||||||
@@ -2523,39 +2632,54 @@ float UVoxelGenerator::GetDensityWithParams(float WorldX, float WorldY, float Wo
|
|||||||
//=========================================================================
|
//=========================================================================
|
||||||
if (StrateManager)
|
if (StrateManager)
|
||||||
{
|
{
|
||||||
StrateManager->ApplyPassageModifier(
|
if (bApplyLegacyStructuralPosts)
|
||||||
Density, WorldX, WorldY, WorldZ,
|
{
|
||||||
Params.BaseDensity, Params.BoundarySealThickness);
|
StrateManager->ApplyPassageModifier(
|
||||||
|
Density, WorldX, WorldY, WorldZ,
|
||||||
|
Params.BaseDensity, Params.BoundarySealThickness);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// GetDensityAt owns one common MC-space structural tail after either the legacy or
|
||||||
|
// operator-stack branch. Keep the actual tube carve here, but do not repeat its
|
||||||
|
// landing air/floor/tunnel-air posts before the shared disturbance/post sequence.
|
||||||
|
StrateManager->ApplyPassageCarvingOnly(
|
||||||
|
Density, WorldX, WorldY, WorldZ,
|
||||||
|
Params.BaseDensity, Params.BoundarySealThickness);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Terrain operations and passage support floors are allowed to write solid density, but the
|
if (bApplyLegacyStructuralPosts)
|
||||||
// graph's own tunnel floor/core are structural route geometry. Close the rounded bottom first,
|
|
||||||
// then reopen only the core above its half-voxel air clearance. Both predicates are shared
|
|
||||||
// with the operator stack and are evaluated from the per-chunk cache, never per-voxel rebuilt.
|
|
||||||
const bool bTunnelSupportFloor =
|
|
||||||
VoxelCaveMorphology::IsTunnelSupportFloorWorldPoint(
|
|
||||||
WorldX, WorldY, WorldZ, SDFCache);
|
|
||||||
if (bTunnelSupportFloor)
|
|
||||||
{
|
{
|
||||||
Density = FMath::Max(Density, FMath::Max(Params.BaseDensity * 2.0f, 1.0f));
|
// Terrain operations and passage support floors are allowed to write solid density, but
|
||||||
}
|
// the graph's own tunnel floor/core are structural route geometry. Direct callers of
|
||||||
const float TunnelCoreSDF = (Params.RoomDensity > 0.0f && Params.RoomSpacing > 0.0f)
|
// GetDensityWithParams retain this historical tail; GetDensityAt skips it and applies the
|
||||||
? VoxelCaveMorphology::EvaluateTunnelCoreWorldSDF(WorldX, WorldY, WorldZ, SDFCache)
|
// same predicates once in its common MC-space post section below both generation paths.
|
||||||
: FLT_MAX;
|
const bool bTunnelSupportFloor =
|
||||||
if (!bTunnelSupportFloor
|
VoxelCaveMorphology::IsTunnelSupportFloorWorldPoint(
|
||||||
&& TunnelCoreSDF < -VoxelPassageGeometry::CaveTunnelAirCoreInsetVoxels)
|
WorldX, WorldY, WorldZ, SDFCache);
|
||||||
{
|
if (bTunnelSupportFloor)
|
||||||
Density = FMath::Min(
|
{
|
||||||
Density,
|
Density = FMath::Max(Density, FMath::Max(Params.BaseDensity * 2.0f, 1.0f));
|
||||||
-FMath::Max(Params.BaseDensity * 2.0f, 1.0f));
|
}
|
||||||
}
|
const float TunnelCoreSDF = (Params.RoomDensity > 0.0f && Params.RoomSpacing > 0.0f)
|
||||||
ApplyOriginLandingFloor(Density, WorldX, WorldY, WorldZ,
|
? VoxelCaveMorphology::EvaluateTunnelCoreWorldSDF(WorldX, WorldY, WorldZ, SDFCache)
|
||||||
Params.StrateTopWorldZ, Params.StrateBottomWorldZ,
|
: FLT_MAX;
|
||||||
Params.BoundarySealThickness, Params.BaseDensity, OriginSpineRadius);
|
if (!bTunnelSupportFloor
|
||||||
|
&& TunnelCoreSDF < -VoxelPassageGeometry::CaveTunnelAirCoreInsetVoxels)
|
||||||
|
{
|
||||||
|
Density = FMath::Min(
|
||||||
|
Density,
|
||||||
|
-FMath::Max(Params.BaseDensity * 2.0f, 1.0f));
|
||||||
|
}
|
||||||
|
ApplyOriginLandingFloor(Density, WorldX, WorldY, WorldZ,
|
||||||
|
Params.StrateTopWorldZ, Params.StrateBottomWorldZ,
|
||||||
|
Params.BoundarySealThickness, Params.BaseDensity, OriginSpineRadius);
|
||||||
|
|
||||||
// Fourth structural post: the XY edge wins over a passage near the rim.
|
// Fourth structural post: the XY edge wins over a passage near the rim.
|
||||||
VF_ApplyXYEdgeSeal(Density, WorldX, WorldY,
|
VF_ApplyXYEdgeSeal(Density, WorldX, WorldY,
|
||||||
WorldRadiusVoxels, EdgeSealThickness, Params.BaseDensity);
|
WorldRadiusVoxels, EdgeSealThickness, Params.BaseDensity);
|
||||||
|
}
|
||||||
|
|
||||||
// Convention MC: négatif = solide, positif = air.
|
// Convention MC: négatif = solide, positif = air.
|
||||||
// La logique interne utilise positif = solide (plus lisible), donc on négate.
|
// La logique interne utilise positif = solide (plus lisible), donc on négate.
|
||||||
@@ -2609,7 +2733,7 @@ float UVoxelGenerator::GetSlabDensity(float WorldX, float WorldY, float WorldZ,
|
|||||||
if (Params.FloorRoughness > 0.0f)
|
if (Params.FloorRoughness > 0.0f)
|
||||||
{
|
{
|
||||||
float FF = Params.FloorRoughnessFrequency;
|
float FF = Params.FloorRoughnessFrequency;
|
||||||
FloorNoise = FractalNoise3D(FVector(
|
FloorNoise = FractalNoise3D(FVector3f(
|
||||||
WorldX * FF + VoxelHash::SeedOffset(SeedU, 7.3f),
|
WorldX * FF + VoxelHash::SeedOffset(SeedU, 7.3f),
|
||||||
WorldY * FF + VoxelHash::SeedOffset(SeedU, 11.1f),
|
WorldY * FF + VoxelHash::SeedOffset(SeedU, 11.1f),
|
||||||
0.0f // XY-pur : plus aucune dépendance en Z / no Z dependence
|
0.0f // XY-pur : plus aucune dépendance en Z / no Z dependence
|
||||||
@@ -2640,7 +2764,7 @@ float UVoxelGenerator::GetSlabDensity(float WorldX, float WorldY, float WorldZ,
|
|||||||
if (Params.CeilingRoughness > 0.0f)
|
if (Params.CeilingRoughness > 0.0f)
|
||||||
{
|
{
|
||||||
float CF = Params.CeilingRoughnessFrequency;
|
float CF = Params.CeilingRoughnessFrequency;
|
||||||
float RawNoise = FractalNoise3D(FVector(
|
float RawNoise = FractalNoise3D(FVector3f(
|
||||||
WorldX * CF + VoxelHash::SeedOffset(SeedU, 17.3f) + 1000.0f,
|
WorldX * CF + VoxelHash::SeedOffset(SeedU, 17.3f) + 1000.0f,
|
||||||
WorldY * CF + VoxelHash::SeedOffset(SeedU, 19.7f) + 2000.0f,
|
WorldY * CF + VoxelHash::SeedOffset(SeedU, 19.7f) + 2000.0f,
|
||||||
3000.0f // XY-pur : décalage de décorrélation seul / offset only
|
3000.0f // XY-pur : décalage de décorrélation seul / offset only
|
||||||
@@ -2872,7 +2996,7 @@ float UVoxelGenerator::GetMazeDensity(float WorldX, float WorldY, float WorldZ,
|
|||||||
// Wall roughness: perturb the corridor surface.
|
// Wall roughness: perturb the corridor surface.
|
||||||
if (Params.SurfaceRoughness > 0.0f && MazeSDF < R + Params.SurfaceRoughness + 2.0f)
|
if (Params.SurfaceRoughness > 0.0f && MazeSDF < R + Params.SurfaceRoughness + 2.0f)
|
||||||
{
|
{
|
||||||
MazeSDF += FractalNoise3D(FVector(WorldX * 0.12f, WorldY * 0.12f, WorldZ * 0.12f), VoxelGenLOD::Eff(3))
|
MazeSDF += FractalNoise3D(FVector3f(WorldX * 0.12f, WorldY * 0.12f, WorldZ * 0.12f), VoxelGenLOD::Eff(3))
|
||||||
* VOXEL_NOISE_SCALE * Params.SurfaceRoughness;
|
* VOXEL_NOISE_SCALE * Params.SurfaceRoughness;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2933,8 +3057,8 @@ float UVoxelGenerator::SampleSurfaceStructuralZ(float WorldX, float WorldY,
|
|||||||
if (Params.HeightWarpStrength > 0.0f)
|
if (Params.HeightWarpStrength > 0.0f)
|
||||||
{
|
{
|
||||||
const float WF = Params.HeightWarpFrequency;
|
const float WF = Params.HeightWarpFrequency;
|
||||||
const float wx = VoxelNoise::Perlin3D(FVector(WorldX * WF + VoxelHash::SeedOffset(SeedU, 0.31f), WorldY * WF + 4.2f, VoxelHash::SeedOffset(SeedU, 1.7f)));
|
const float wx = VoxelNoise::Perlin3D(FVector3f(WorldX * WF + VoxelHash::SeedOffset(SeedU, 0.31f), WorldY * WF + 4.2f, VoxelHash::SeedOffset(SeedU, 1.7f)));
|
||||||
const float wy = VoxelNoise::Perlin3D(FVector(WorldX * WF + 8.6f, WorldY * WF + VoxelHash::SeedOffset(SeedU, 0.53f), VoxelHash::SeedOffset(SeedU, 2.9f)));
|
const float wy = VoxelNoise::Perlin3D(FVector3f(WorldX * WF + 8.6f, WorldY * WF + VoxelHash::SeedOffset(SeedU, 0.53f), VoxelHash::SeedOffset(SeedU, 2.9f)));
|
||||||
QX += wx * VOXEL_NOISE_SCALE * Params.HeightWarpStrength;
|
QX += wx * VOXEL_NOISE_SCALE * Params.HeightWarpStrength;
|
||||||
QY += wy * VOXEL_NOISE_SCALE * Params.HeightWarpStrength;
|
QY += wy * VOXEL_NOISE_SCALE * Params.HeightWarpStrength;
|
||||||
}
|
}
|
||||||
@@ -2944,12 +3068,12 @@ float UVoxelGenerator::SampleSurfaceStructuralZ(float WorldX, float WorldY,
|
|||||||
const float Relief = SampleRelief(WorldX, WorldY, Params.ReliefFrequency, Params.ReliefContrast);
|
const float Relief = SampleRelief(WorldX, WorldY, Params.ReliefFrequency, Params.ReliefContrast);
|
||||||
const float M = FMath::Lerp(1.0f, Relief, Params.ReliefStrength);
|
const float M = FMath::Lerp(1.0f, Relief, Params.ReliefStrength);
|
||||||
|
|
||||||
float Cont = FractalNoise3D(FVector(
|
float Cont = FractalNoise3D(FVector3f(
|
||||||
QX * Params.ContinentFrequency + VoxelHash::SeedOffset(SeedU, 3.1f),
|
QX * Params.ContinentFrequency + VoxelHash::SeedOffset(SeedU, 3.1f),
|
||||||
QY * Params.ContinentFrequency + VoxelHash::SeedOffset(SeedU, 5.7f),
|
QY * Params.ContinentFrequency + VoxelHash::SeedOffset(SeedU, 5.7f),
|
||||||
VoxelHash::SeedOffset(SeedU, 0.7f)), 4); // [-1,1]
|
VoxelHash::SeedOffset(SeedU, 0.7f)), 4); // [-1,1]
|
||||||
|
|
||||||
float Detail = FractalNoise3D(FVector(
|
float Detail = FractalNoise3D(FVector3f(
|
||||||
WorldX * Params.DetailFrequency + 11.0f,
|
WorldX * Params.DetailFrequency + 11.0f,
|
||||||
WorldY * Params.DetailFrequency + 22.0f,
|
WorldY * Params.DetailFrequency + 22.0f,
|
||||||
VoxelHash::SeedOffset(SeedU, 1.3f)), 3); // [-1,1]
|
VoxelHash::SeedOffset(SeedU, 1.3f)), 3); // [-1,1]
|
||||||
@@ -2957,7 +3081,7 @@ float UVoxelGenerator::SampleSurfaceStructuralZ(float WorldX, float WorldY,
|
|||||||
float Mountain = 0.0f;
|
float Mountain = 0.0f;
|
||||||
if (Params.MountainStrength > 0.0f)
|
if (Params.MountainStrength > 0.0f)
|
||||||
{
|
{
|
||||||
float Ridge = RidgedNoise3D(FVector(
|
float Ridge = RidgedNoise3D(FVector3f(
|
||||||
QX * Params.MountainFrequency + 99.0f,
|
QX * Params.MountainFrequency + 99.0f,
|
||||||
QY * Params.MountainFrequency + 77.0f,
|
QY * Params.MountainFrequency + 77.0f,
|
||||||
VoxelHash::SeedOffset(SeedU, 0.9f)), 4); // [-1,1]
|
VoxelHash::SeedOffset(SeedU, 0.9f)), 4); // [-1,1]
|
||||||
@@ -3072,8 +3196,8 @@ float UVoxelGenerator::ComputeSurfaceCeiling(float WorldX, float WorldY,
|
|||||||
if (Params.CeilingWarpStrength > 0.0f)
|
if (Params.CeilingWarpStrength > 0.0f)
|
||||||
{
|
{
|
||||||
const float WF = Params.CeilingWarpFrequency;
|
const float WF = Params.CeilingWarpFrequency;
|
||||||
const float wx = VoxelNoise::Perlin3D(FVector(WorldX * WF + VoxelHash::SeedOffset(SeedU, 0.71f), WorldY * WF + 2.3f, VoxelHash::SeedOffset(SeedU, 3.3f)));
|
const float wx = VoxelNoise::Perlin3D(FVector3f(WorldX * WF + VoxelHash::SeedOffset(SeedU, 0.71f), WorldY * WF + 2.3f, VoxelHash::SeedOffset(SeedU, 3.3f)));
|
||||||
const float wy = VoxelNoise::Perlin3D(FVector(WorldX * WF + 6.1f, WorldY * WF + VoxelHash::SeedOffset(SeedU, 0.19f), VoxelHash::SeedOffset(SeedU, 4.7f)));
|
const float wy = VoxelNoise::Perlin3D(FVector3f(WorldX * WF + 6.1f, WorldY * WF + VoxelHash::SeedOffset(SeedU, 0.19f), VoxelHash::SeedOffset(SeedU, 4.7f)));
|
||||||
QX += wx * VOXEL_NOISE_SCALE * Params.CeilingWarpStrength;
|
QX += wx * VOXEL_NOISE_SCALE * Params.CeilingWarpStrength;
|
||||||
QY += wy * VOXEL_NOISE_SCALE * Params.CeilingWarpStrength;
|
QY += wy * VOXEL_NOISE_SCALE * Params.CeilingWarpStrength;
|
||||||
}
|
}
|
||||||
@@ -3081,7 +3205,7 @@ float UVoxelGenerator::ComputeSurfaceCeiling(float WorldX, float WorldY,
|
|||||||
// Broad SIGNED swell: raises/lowers the whole cap → big inverted hills and valleys.
|
// Broad SIGNED swell: raises/lowers the whole cap → big inverted hills and valleys.
|
||||||
if (Params.CeilingUndulation > 0.0f)
|
if (Params.CeilingUndulation > 0.0f)
|
||||||
{
|
{
|
||||||
const float Swell = FractalNoise3D(FVector(
|
const float Swell = FractalNoise3D(FVector3f(
|
||||||
QX * Params.CeilingUndulationFrequency + VoxelHash::SeedOffset(SeedU, 1.9f),
|
QX * Params.CeilingUndulationFrequency + VoxelHash::SeedOffset(SeedU, 1.9f),
|
||||||
QY * Params.CeilingUndulationFrequency + 13.0f,
|
QY * Params.CeilingUndulationFrequency + 13.0f,
|
||||||
VoxelHash::SeedOffset(SeedU, 0.5f)), 3); // [-1,1]
|
VoxelHash::SeedOffset(SeedU, 0.5f)), 3); // [-1,1]
|
||||||
@@ -3093,14 +3217,14 @@ float UVoxelGenerator::ComputeSurfaceCeiling(float WorldX, float WorldY,
|
|||||||
float Hang = 0.0f;
|
float Hang = 0.0f;
|
||||||
if (Params.CeilingRoughness > 0.0f)
|
if (Params.CeilingRoughness > 0.0f)
|
||||||
{
|
{
|
||||||
Hang += FMath::Abs(FractalNoise3D(FVector(
|
Hang += FMath::Abs(FractalNoise3D(FVector3f(
|
||||||
WorldX * Params.CeilingRoughnessFrequency + 5.0f,
|
WorldX * Params.CeilingRoughnessFrequency + 5.0f,
|
||||||
WorldY * Params.CeilingRoughnessFrequency + 6.0f,
|
WorldY * Params.CeilingRoughnessFrequency + 6.0f,
|
||||||
VoxelHash::SeedOffset(SeedU, 2.1f)), 3)) * VOXEL_NOISE_SCALE * Params.CeilingRoughness;
|
VoxelHash::SeedOffset(SeedU, 2.1f)), 3)) * VOXEL_NOISE_SCALE * Params.CeilingRoughness;
|
||||||
}
|
}
|
||||||
if (Params.CeilingRidgeStrength > 0.0f)
|
if (Params.CeilingRidgeStrength > 0.0f)
|
||||||
{
|
{
|
||||||
float Ridge = RidgedNoise3D(FVector(
|
float Ridge = RidgedNoise3D(FVector3f(
|
||||||
QX * Params.CeilingRidgeFrequency + 31.0f,
|
QX * Params.CeilingRidgeFrequency + 31.0f,
|
||||||
QY * Params.CeilingRidgeFrequency + 47.0f,
|
QY * Params.CeilingRidgeFrequency + 47.0f,
|
||||||
VoxelHash::SeedOffset(SeedU, 1.1f)), 4); // [-1,1]
|
VoxelHash::SeedOffset(SeedU, 1.1f)), 4); // [-1,1]
|
||||||
@@ -3132,7 +3256,7 @@ float UVoxelGenerator::SurfaceDensityFromColumn(float WorldX, float WorldY, floa
|
|||||||
const uint32 SeedU = (uint32)Seed;
|
const uint32 SeedU = (uint32)Seed;
|
||||||
const float f = S.OverhangFrequency;
|
const float f = S.OverhangFrequency;
|
||||||
// Shelf-shape noise [0,1]; the Z term makes the reach fold/curl with height (ragged, not a lip).
|
// Shelf-shape noise [0,1]; the Z term makes the reach fold/curl with height (ragged, not a lip).
|
||||||
const float Ns = FractalNoise3D(FVector(
|
const float Ns = FractalNoise3D(FVector3f(
|
||||||
WorldX * f + VoxelHash::SeedOffset(SeedU, 17.3f),
|
WorldX * f + VoxelHash::SeedOffset(SeedU, 17.3f),
|
||||||
WorldY * f + VoxelHash::SeedOffset(SeedU, 23.9f),
|
WorldY * f + VoxelHash::SeedOffset(SeedU, 23.9f),
|
||||||
WorldZ * f * S.OverhangZScale + VoxelHash::SeedOffset(SeedU, 5.1f)), 3) * 0.5f + 0.5f; // [0,1]
|
WorldZ * f * S.OverhangZScale + VoxelHash::SeedOffset(SeedU, 5.1f)), 3) * 0.5f + 0.5f; // [0,1]
|
||||||
@@ -4146,7 +4270,7 @@ float UVoxelGenerator::SampleRelief(float WorldX, float WorldY, float Frequency,
|
|||||||
const uint32 SeedU = (uint32)Seed;
|
const uint32 SeedU = (uint32)Seed;
|
||||||
// Same offsets/octaves as the original SurfaceWorld relief so existing worlds are
|
// Same offsets/octaves as the original SurfaceWorld relief so existing worlds are
|
||||||
// unchanged (this is the function that code path now calls).
|
// unchanged (this is the function that code path now calls).
|
||||||
float R = FractalNoise3D(FVector(
|
float R = FractalNoise3D(FVector3f(
|
||||||
WorldX * Frequency + VoxelHash::SeedOffset(SeedU, 7.3f),
|
WorldX * Frequency + VoxelHash::SeedOffset(SeedU, 7.3f),
|
||||||
WorldY * Frequency + VoxelHash::SeedOffset(SeedU, 2.1f),
|
WorldY * Frequency + VoxelHash::SeedOffset(SeedU, 2.1f),
|
||||||
VoxelHash::SeedOffset(SeedU, 0.5f)), 2) * 0.5f + 0.5f; // [0,1]
|
VoxelHash::SeedOffset(SeedU, 0.5f)), 2) * 0.5f + 0.5f; // [0,1]
|
||||||
@@ -4157,7 +4281,7 @@ float UVoxelGenerator::SampleRelief(float WorldX, float WorldY, float Frequency,
|
|||||||
float UVoxelGenerator::SampleMoisture(float WorldX, float WorldY, float Frequency) const
|
float UVoxelGenerator::SampleMoisture(float WorldX, float WorldY, float Frequency) const
|
||||||
{
|
{
|
||||||
const uint32 SeedU = (uint32)Seed;
|
const uint32 SeedU = (uint32)Seed;
|
||||||
const float N = FractalNoise3D(FVector(
|
const float N = FractalNoise3D(FVector3f(
|
||||||
WorldX * Frequency + VoxelHash::SeedOffset(SeedU, 4.7f),
|
WorldX * Frequency + VoxelHash::SeedOffset(SeedU, 4.7f),
|
||||||
WorldY * Frequency + VoxelHash::SeedOffset(SeedU, 8.9f),
|
WorldY * Frequency + VoxelHash::SeedOffset(SeedU, 8.9f),
|
||||||
VoxelHash::SeedOffset(SeedU, 1.3f)), 2) * 0.5f + 0.5f; // [0,1]
|
VoxelHash::SeedOffset(SeedU, 1.3f)), 2) * 0.5f + 0.5f; // [0,1]
|
||||||
@@ -4241,8 +4365,8 @@ FBiomeSample UVoxelGenerator::SampleBiomeAt(float WorldX, float WorldY, const FB
|
|||||||
{
|
{
|
||||||
const uint32 SeedU = (uint32)Seed;
|
const uint32 SeedU = (uint32)Seed;
|
||||||
const float WF = MP.WarpFrequency;
|
const float WF = MP.WarpFrequency;
|
||||||
const float wx = VoxelNoise::Perlin3D(FVector(WorldX * WF + VoxelHash::SeedOffset(SeedU, 0.27f), WorldY * WF + 3.1f, VoxelHash::SeedOffset(SeedU, 1.1f)));
|
const float wx = VoxelNoise::Perlin3D(FVector3f(WorldX * WF + VoxelHash::SeedOffset(SeedU, 0.27f), WorldY * WF + 3.1f, VoxelHash::SeedOffset(SeedU, 1.1f)));
|
||||||
const float wy = VoxelNoise::Perlin3D(FVector(WorldX * WF + 7.7f, WorldY * WF + VoxelHash::SeedOffset(SeedU, 0.61f), VoxelHash::SeedOffset(SeedU, 2.3f)));
|
const float wy = VoxelNoise::Perlin3D(FVector3f(WorldX * WF + 7.7f, WorldY * WF + VoxelHash::SeedOffset(SeedU, 0.61f), VoxelHash::SeedOffset(SeedU, 2.3f)));
|
||||||
QX += wx * VOXEL_NOISE_SCALE * MP.WarpStrength;
|
QX += wx * VOXEL_NOISE_SCALE * MP.WarpStrength;
|
||||||
QY += wy * VOXEL_NOISE_SCALE * MP.WarpStrength;
|
QY += wy * VOXEL_NOISE_SCALE * MP.WarpStrength;
|
||||||
}
|
}
|
||||||
@@ -4373,8 +4497,8 @@ FBiomeSample UVoxelGenerator::ResolveBiomeSampleAt(float WorldX, float WorldY, i
|
|||||||
{
|
{
|
||||||
const uint32 SeedU = (uint32)Seed;
|
const uint32 SeedU = (uint32)Seed;
|
||||||
const float WF = MP.WarpFrequency;
|
const float WF = MP.WarpFrequency;
|
||||||
const float wx = VoxelNoise::Perlin3D(FVector(WorldX * WF + VoxelHash::SeedOffset(SeedU, 0.27f), WorldY * WF + 3.1f, VoxelHash::SeedOffset(SeedU, 1.1f)));
|
const float wx = VoxelNoise::Perlin3D(FVector3f(WorldX * WF + VoxelHash::SeedOffset(SeedU, 0.27f), WorldY * WF + 3.1f, VoxelHash::SeedOffset(SeedU, 1.1f)));
|
||||||
const float wy = VoxelNoise::Perlin3D(FVector(WorldX * WF + 7.7f, WorldY * WF + VoxelHash::SeedOffset(SeedU, 0.61f), VoxelHash::SeedOffset(SeedU, 2.3f)));
|
const float wy = VoxelNoise::Perlin3D(FVector3f(WorldX * WF + 7.7f, WorldY * WF + VoxelHash::SeedOffset(SeedU, 0.61f), VoxelHash::SeedOffset(SeedU, 2.3f)));
|
||||||
QX += wx * VOXEL_NOISE_SCALE * MP.WarpStrength;
|
QX += wx * VOXEL_NOISE_SCALE * MP.WarpStrength;
|
||||||
QY += wy * VOXEL_NOISE_SCALE * MP.WarpStrength;
|
QY += wy * VOXEL_NOISE_SCALE * MP.WarpStrength;
|
||||||
}
|
}
|
||||||
@@ -4861,7 +4985,7 @@ float UVoxelGenerator::GetVerticalShaftDensity(float WorldX, float WorldY, float
|
|||||||
// Wall roughness.
|
// Wall roughness.
|
||||||
if (Params.SurfaceRoughness > 0.0f && CaveSDF < Params.SurfaceRoughness + 4.0f)
|
if (Params.SurfaceRoughness > 0.0f && CaveSDF < Params.SurfaceRoughness + 4.0f)
|
||||||
{
|
{
|
||||||
CaveSDF += FractalNoise3D(FVector(WorldX * 0.1f, WorldY * 0.1f, WorldZ * 0.1f), VoxelGenLOD::Eff(3))
|
CaveSDF += FractalNoise3D(FVector3f(WorldX * 0.1f, WorldY * 0.1f, WorldZ * 0.1f), VoxelGenLOD::Eff(3))
|
||||||
* VOXEL_NOISE_SCALE * Params.SurfaceRoughness;
|
* VOXEL_NOISE_SCALE * Params.SurfaceRoughness;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4986,9 +5110,9 @@ float UVoxelGenerator::GetFloatingIslandDensity(float WorldX, float WorldY, floa
|
|||||||
// NOTE : `SeedOffset` quantifie la clé de site par ×100, donc 0.0007 → site 0. Unique aujourd'hui
|
// NOTE : `SeedOffset` quantifie la clé de site par ×100, donc 0.0007 → site 0. Unique aujourd'hui
|
||||||
// (toutes les autres clés du plugin sont ≥ 0.19) ; la prochaine clé sous 0.005 devra en choisir
|
// (toutes les autres clés du plugin sont ≥ 0.19) ; la prochaine clé sous 0.005 devra en choisir
|
||||||
// une autre plutôt que de collisionner en silence.
|
// une autre plutôt que de collisionner en silence.
|
||||||
const float WX = WorldX + FractalNoise3D(FVector(WorldX * 0.04f + VoxelHash::SeedOffset(S, 0.0007f), WorldY * 0.04f, WorldZ * 0.012f), VoxelGenLOD::Eff(3))
|
const float WX = WorldX + FractalNoise3D(FVector3f(WorldX * 0.04f + VoxelHash::SeedOffset(S, 0.0007f), WorldY * 0.04f, WorldZ * 0.012f), VoxelGenLOD::Eff(3))
|
||||||
* VOXEL_NOISE_SCALE * WarpAmp;
|
* VOXEL_NOISE_SCALE * WarpAmp;
|
||||||
const float WY = WorldY + FractalNoise3D(FVector(WorldX * 0.04f + 31.0f, WorldY * 0.04f + 7.0f, WorldZ * 0.012f), VoxelGenLOD::Eff(3))
|
const float WY = WorldY + FractalNoise3D(FVector3f(WorldX * 0.04f + 31.0f, WorldY * 0.04f + 7.0f, WorldZ * 0.012f), VoxelGenLOD::Eff(3))
|
||||||
* VOXEL_NOISE_SCALE * WarpAmp;
|
* VOXEL_NOISE_SCALE * WarpAmp;
|
||||||
|
|
||||||
// Per-island constants (existence roll, jitter, radius, Z anchor, taper) are pure functions
|
// Per-island constants (existence roll, jitter, radius, Z anchor, taper) are pure functions
|
||||||
@@ -5075,7 +5199,7 @@ float UVoxelGenerator::GetFloatingIslandDensity(float WorldX, float WorldY, floa
|
|||||||
// Craggy shells.
|
// Craggy shells.
|
||||||
if (Params.SurfaceRoughness > 0.0f && IslandSDF < Params.SurfaceRoughness + BlendK + 2.0f)
|
if (Params.SurfaceRoughness > 0.0f && IslandSDF < Params.SurfaceRoughness + BlendK + 2.0f)
|
||||||
{
|
{
|
||||||
IslandSDF += FractalNoise3D(FVector(WorldX * 0.08f, WorldY * 0.08f, WorldZ * 0.08f), VoxelGenLOD::Eff(4))
|
IslandSDF += FractalNoise3D(FVector3f(WorldX * 0.08f, WorldY * 0.08f, WorldZ * 0.08f), VoxelGenLOD::Eff(4))
|
||||||
* VOXEL_NOISE_SCALE * Params.SurfaceRoughness;
|
* VOXEL_NOISE_SCALE * Params.SurfaceRoughness;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
#include "VoxelMarchingCubesMesher.h"
|
#include "VoxelMarchingCubesMesher.h"
|
||||||
#include "MarchingCubesTables.h"
|
#include "MarchingCubesTables.h"
|
||||||
|
#include "VoxelDensityProfile.h"
|
||||||
|
|
||||||
//=============================================================================
|
//=============================================================================
|
||||||
// MAIN ALGORITHM
|
// MAIN ALGORITHM
|
||||||
@@ -17,6 +18,10 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
|||||||
FVoxelMeshData MeshData;
|
FVoxelMeshData MeshData;
|
||||||
if (OutCaptureGrid) { OutCaptureGrid->Reset(); }
|
if (OutCaptureGrid) { OutCaptureGrid->Reset(); }
|
||||||
if (!Generator) return MeshData;
|
if (!Generator) return MeshData;
|
||||||
|
VoxelDensityProfile::FScopedTimer MesherProfileTimer(
|
||||||
|
VoxelDensityProfile::EBucket::MesherGenerateMesh);
|
||||||
|
VoxelDensityProfile::FScopedTimer MesherSetupTimer(
|
||||||
|
VoxelDensityProfile::EBucket::MesherOther);
|
||||||
|
|
||||||
// Cell size in voxels. No upper clamp: coarse clipmap levels use bigger steps (the EXTENT
|
// Cell size in voxels. No upper clamp: coarse clipmap levels use bigger steps (the EXTENT
|
||||||
// grows). Coarse tiles also use FEWER cells (InCellsPerAxis) for cheaper gen.
|
// grows). Coarse tiles also use FEWER cells (InCellsPerAxis) for cheaper gen.
|
||||||
@@ -218,12 +223,16 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
|||||||
const int32 GzLo = CzLo - 1; // coins CzLo..CzHi+1, gradients ±1
|
const int32 GzLo = CzLo - 1; // coins CzLo..CzHi+1, gradients ±1
|
||||||
const int32 GzHi = CzHi + 2; // (== -1..GridDim sans bande)
|
const int32 GzHi = CzHi + 2; // (== -1..GridDim sans bande)
|
||||||
|
|
||||||
|
MesherSetupTimer.End();
|
||||||
|
|
||||||
// Réutilise le tampon entre tuiles (thread_local) : SetNumUninitialized garde la
|
// Réutilise le tampon entre tuiles (thread_local) : SetNumUninitialized garde la
|
||||||
// capacité, donc plus de malloc/free de ~170 Ko (35³ floats) par tuile.
|
// capacité, donc plus de malloc/free de ~170 Ko (35³ floats) par tuile.
|
||||||
static thread_local TArray<float> DensityGrid;
|
static thread_local TArray<float> DensityGrid;
|
||||||
DensityGrid.SetNumUninitialized(MDim * MDim * MDim);
|
DensityGrid.SetNumUninitialized(MDim * MDim * MDim);
|
||||||
// Bande de strate : seules les rangées Z réellement lues (cellules CzLo..CzHi + marges de
|
// Bande de strate : seules les rangées Z réellement lues (cellules CzLo..CzHi + marges de
|
||||||
// gradient) sont échantillonnées — le reste du tampon reste non initialisé et non lu.
|
// gradient) sont échantillonnées — le reste du tampon reste non initialisé et non lu.
|
||||||
|
VoxelDensityProfile::FScopedTimer MesherDensityGridTimer(
|
||||||
|
VoxelDensityProfile::EBucket::MesherDensityGrid);
|
||||||
for (int32 gz = GzLo; gz <= GzHi; gz++)
|
for (int32 gz = GzLo; gz <= GzHi; gz++)
|
||||||
{
|
{
|
||||||
if (ShouldAbortWork()) return FVoxelMeshData();
|
if (ShouldAbortWork()) return FVoxelMeshData();
|
||||||
@@ -241,6 +250,7 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
MesherDensityGridTimer.End();
|
||||||
|
|
||||||
// ── CAPTURE-DURING-MESHING ──
|
// ── CAPTURE-DURING-MESHING ──
|
||||||
// Si demandé et que la tuile est pleine résolution (CellsPerAxis==CHUNK_SIZE ⇒ Step==1<<Level,
|
// Si demandé et que la tuile est pleine résolution (CellsPerAxis==CHUNK_SIZE ⇒ Step==1<<Level,
|
||||||
@@ -290,6 +300,13 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
|||||||
static thread_local TArray<int32> CapTris;
|
static thread_local TArray<int32> CapTris;
|
||||||
GroundTris.Reset();
|
GroundTris.Reset();
|
||||||
CapTris.Reset();
|
CapTris.Reset();
|
||||||
|
const bool bProfileMesher = VoxelDensityProfile::IsEnabled();
|
||||||
|
uint64 CellClassificationCycles = 0;
|
||||||
|
uint64 GradientCycles = 0;
|
||||||
|
uint64 VertexInterpolationCycles = 0;
|
||||||
|
uint64 StreamBuildingCycles = 0;
|
||||||
|
uint64 CellClassificationCalls = 0;
|
||||||
|
uint64 SurfaceCellCalls = 0;
|
||||||
for (int32 cz = CzLo; cz <= CzHi; cz++) // bande de strate : cf. CzLo/CzHi plus haut
|
for (int32 cz = CzLo; cz <= CzHi; cz++) // bande de strate : cf. CzLo/CzHi plus haut
|
||||||
{
|
{
|
||||||
if (ShouldAbortWork()) return FVoxelMeshData();
|
if (ShouldAbortWork()) return FVoxelMeshData();
|
||||||
@@ -298,6 +315,8 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
|||||||
if (ShouldAbortWork()) return FVoxelMeshData();
|
if (ShouldAbortWork()) return FVoxelMeshData();
|
||||||
for (int32 cx = 0; cx < CellsPerAxis; cx++)
|
for (int32 cx = 0; cx < CellsPerAxis; cx++)
|
||||||
{
|
{
|
||||||
|
const uint64 ClassificationStart = bProfileMesher
|
||||||
|
? FPlatformTime::Cycles64() : 0;
|
||||||
// PASSE 1 : densités aux 8 coins + index de cas MC SEULEMENT.
|
// PASSE 1 : densités aux 8 coins + index de cas MC SEULEMENT.
|
||||||
// ~70% des cellules d'un chunk sont tout-roc ou tout-air (aucune surface) ;
|
// ~70% des cellules d'un chunk sont tout-roc ou tout-air (aucune surface) ;
|
||||||
// on les rejette ICI, AVANT de payer les 8 positions + 8 gradients (48 lectures
|
// on les rejette ICI, AVANT de payer les 8 positions + 8 gradients (48 lectures
|
||||||
@@ -314,9 +333,18 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
|||||||
if (D >= IsoLevel) CaseIndex |= (1 << i);
|
if (D >= IsoLevel) CaseIndex |= (1 << i);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (MCEdgeTable[CaseIndex] == 0) continue; // Pas de surface ici → skip
|
const bool bHasSurface = MCEdgeTable[CaseIndex] != 0;
|
||||||
|
if (bProfileMesher)
|
||||||
|
{
|
||||||
|
CellClassificationCycles += FPlatformTime::Cycles64() - ClassificationStart;
|
||||||
|
++CellClassificationCalls;
|
||||||
|
}
|
||||||
|
if (!bHasSurface) continue; // Pas de surface ici → skip
|
||||||
|
if (bProfileMesher) { ++SurfaceCellCalls; }
|
||||||
|
|
||||||
// PASSE 2 : positions + gradients aux 8 coins (uniquement si surface présente).
|
// PASSE 2 : positions + gradients aux 8 coins (uniquement si surface présente).
|
||||||
|
const uint64 GradientStart = bProfileMesher
|
||||||
|
? FPlatformTime::Cycles64() : 0;
|
||||||
FVector Positions[8];
|
FVector Positions[8];
|
||||||
FVector Gradients[8];
|
FVector Gradients[8];
|
||||||
for (int32 i = 0; i < 8; i++)
|
for (int32 i = 0; i < 8; i++)
|
||||||
@@ -328,10 +356,16 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
|||||||
+ FVector(GX * Step, GY * Step, GZ * Step) * VOXEL_SIZE;
|
+ FVector(GX * Step, GY * Step, GZ * Step) * VOXEL_SIZE;
|
||||||
Gradients[i] = GradAt(GX, GY, GZ);
|
Gradients[i] = GradAt(GX, GY, GZ);
|
||||||
}
|
}
|
||||||
|
if (bProfileMesher)
|
||||||
|
{
|
||||||
|
GradientCycles += FPlatformTime::Cycles64() - GradientStart;
|
||||||
|
}
|
||||||
|
|
||||||
// Interpolation des positions + normales sur les arêtes traversées. t = point de
|
// Interpolation des positions + normales sur les arêtes traversées. t = point de
|
||||||
// traversée de l'iso entre les deux coins (clampé, milieu si densités quasi-égales) ;
|
// traversée de l'iso entre les deux coins (clampé, milieu si densités quasi-égales) ;
|
||||||
// la normale interpole les gradients de coin par le même t.
|
// la normale interpole les gradients de coin par le même t.
|
||||||
|
const uint64 VertexInterpolationStart = bProfileMesher
|
||||||
|
? FPlatformTime::Cycles64() : 0;
|
||||||
FVector EdgeVertices[12];
|
FVector EdgeVertices[12];
|
||||||
FVector EdgeNormals[12];
|
FVector EdgeNormals[12];
|
||||||
for (int32 i = 0; i < 12; i++)
|
for (int32 i = 0; i < 12; i++)
|
||||||
@@ -348,9 +382,16 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
|||||||
EdgeNormals[i] = Gradients[A] + T * (Gradients[B] - Gradients[A]);
|
EdgeNormals[i] = Gradients[A] + T * (Gradients[B] - Gradients[A]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (bProfileMesher)
|
||||||
|
{
|
||||||
|
VertexInterpolationCycles +=
|
||||||
|
FPlatformTime::Cycles64() - VertexInterpolationStart;
|
||||||
|
}
|
||||||
|
|
||||||
// Génère les triangles avec vertices dédupliqués.
|
// Génère les triangles avec vertices dédupliqués.
|
||||||
// Ordre 0, 2, 1 (pas 0, 1, 2) pour le winding attendu par RealtimeMesh.
|
// Ordre 0, 2, 1 (pas 0, 1, 2) pour le winding attendu par RealtimeMesh.
|
||||||
|
const uint64 StreamBuildingStart = bProfileMesher
|
||||||
|
? FPlatformTime::Cycles64() : 0;
|
||||||
for (int32 i = 0; MCTriTable[CaseIndex][i] != -1; i += 3)
|
for (int32 i = 0; MCTriTable[CaseIndex][i] != -1; i += 3)
|
||||||
{
|
{
|
||||||
const int32 E0 = MCTriTable[CaseIndex][i];
|
const int32 E0 = MCTriTable[CaseIndex][i];
|
||||||
@@ -369,10 +410,33 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
|
|||||||
Dst.Add(Idx2);
|
Dst.Add(Idx2);
|
||||||
Dst.Add(Idx1);
|
Dst.Add(Idx1);
|
||||||
}
|
}
|
||||||
|
if (bProfileMesher)
|
||||||
|
{
|
||||||
|
StreamBuildingCycles += FPlatformTime::Cycles64() - StreamBuildingStart;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (bProfileMesher)
|
||||||
|
{
|
||||||
|
VoxelDensityProfile::AddMeasurement(
|
||||||
|
VoxelDensityProfile::EBucket::MesherCellClassification,
|
||||||
|
CellClassificationCycles, CellClassificationCalls);
|
||||||
|
VoxelDensityProfile::AddMeasurement(
|
||||||
|
VoxelDensityProfile::EBucket::MesherGradientNormals,
|
||||||
|
GradientCycles, SurfaceCellCalls);
|
||||||
|
VoxelDensityProfile::AddMeasurement(
|
||||||
|
VoxelDensityProfile::EBucket::MesherVertexInterpolation,
|
||||||
|
VertexInterpolationCycles, SurfaceCellCalls);
|
||||||
|
VoxelDensityProfile::AddMeasurement(
|
||||||
|
VoxelDensityProfile::EBucket::MesherStreamBuilding,
|
||||||
|
StreamBuildingCycles, SurfaceCellCalls);
|
||||||
|
}
|
||||||
|
|
||||||
|
VoxelDensityProfile::FScopedTimer MesherFinalisationTimer(
|
||||||
|
VoxelDensityProfile::EBucket::MesherOther);
|
||||||
|
|
||||||
//=========================================================================
|
//=========================================================================
|
||||||
// SKIRTS — boucheurs de fissures aux coutures de LOD (clipmap)
|
// SKIRTS — boucheurs de fissures aux coutures de LOD (clipmap)
|
||||||
//=========================================================================
|
//=========================================================================
|
||||||
|
|||||||
@@ -439,16 +439,16 @@ namespace VoxelMazeTopology
|
|||||||
// point le plus bas qui voit déjà le hash, et le générateur comme la pile d'opérateurs l'appellent.
|
// point le plus bas qui voit déjà le hash, et le générateur comme la pile d'opérateurs l'appellent.
|
||||||
//
|
//
|
||||||
// Ce corps était `static float CellularNoise3D(const FVector&)` dans VoxelGenerator.cpp, invisible
|
// Ce corps était `static float CellularNoise3D(const FVector&)` dans VoxelGenerator.cpp, invisible
|
||||||
// à la pile d'opérateurs. Déplacement LITTÉRAL : mêmes opérations, même ordre, même passage par
|
// à la pile d'opérateurs. Il vit ici pour que les deux chemins partagent la même implémentation.
|
||||||
// `FVector` (donc par des doubles) — l'égalité binaire du portage TunnelNetwork en dépend.
|
// The hot overload uses FVector3f: noise coordinates are floats, so no double round-trip is paid
|
||||||
// `UVoxelGenerator`'s copy is now a one-line forwarder; the body moved verbatim.
|
// before the hash/lattice arithmetic. Output compatibility with the old field is not a contract.
|
||||||
//
|
//
|
||||||
// Algorithme : distance au point-feature le plus proche dans une grille hachée.
|
// Algorithme : distance au point-feature le plus proche dans une grille hachée.
|
||||||
// 1. cellule entière du point 2. voisinage 3×3×3 3. rendre (F2 − F1), normalisé ~[-1, 1]
|
// 1. cellule entière du point 2. voisinage 3×3×3 3. rendre (F2 − F1), normalisé ~[-1, 1]
|
||||||
// F2−F1 donne des frontières de cellules lisses avec des arêtes entre elles.
|
// F2−F1 donne des frontières de cellules lisses avec des arêtes entre elles.
|
||||||
namespace VoxelNoise
|
namespace VoxelNoise
|
||||||
{
|
{
|
||||||
FORCEINLINE float Cellular3D(const FVector& Position)
|
FORCEINLINE float Cellular3D(const FVector3f& Position)
|
||||||
{
|
{
|
||||||
// Integer cell coordinates
|
// Integer cell coordinates
|
||||||
int32 CellX = FMath::FloorToInt(Position.X);
|
int32 CellX = FMath::FloorToInt(Position.X);
|
||||||
@@ -508,6 +508,11 @@ namespace VoxelNoise
|
|||||||
// Result is in [0, ~1.0]. Map to [-1, 1] for compatibility with other noise types.
|
// Result is in [0, ~1.0]. Map to [-1, 1] for compatibility with other noise types.
|
||||||
return Result * 2.0f - 1.0f;
|
return Result * 2.0f - 1.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FORCEINLINE float Cellular3D(const FVector& Position)
|
||||||
|
{
|
||||||
|
return Cellular3D(FVector3f((float)Position.X, (float)Position.Y, (float)Position.Z));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//=============================================================================
|
//=============================================================================
|
||||||
@@ -524,6 +529,32 @@ namespace VoxelNoise
|
|||||||
// With caching: build once, evaluate 32K times with just SDF math.
|
// With caching: build once, evaluate 32K times with just SDF math.
|
||||||
// This is the single biggest CPU performance win for cave generation.
|
// This is the single biggest CPU performance win for cave generation.
|
||||||
|
|
||||||
|
// Room-relative detail geometry whose hash/trigonometry is constant for a cached room.
|
||||||
|
// These are built once with the room's terrain-op parameters and then only evaluated per voxel.
|
||||||
|
struct FCachedArch
|
||||||
|
{
|
||||||
|
bool bActive = false;
|
||||||
|
FVector EndpointA = FVector::ZeroVector;
|
||||||
|
FVector EndpointB = FVector::ZeroVector;
|
||||||
|
float Radius = 0.0f;
|
||||||
|
float BaseDensity = 0.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct FCachedPinch
|
||||||
|
{
|
||||||
|
bool bActive = false;
|
||||||
|
float CenterX = 0.0f;
|
||||||
|
float CenterY = 0.0f;
|
||||||
|
float CenterZ = 0.0f;
|
||||||
|
float CosAngle = 1.0f;
|
||||||
|
float SinAngle = 0.0f;
|
||||||
|
float MaxExtent = 0.0f;
|
||||||
|
float HalfLength = 0.0f;
|
||||||
|
float HalfNarrow = 0.0f;
|
||||||
|
float HalfVertical = 0.0f;
|
||||||
|
float BaseDensity = 0.0f;
|
||||||
|
};
|
||||||
|
|
||||||
// A room in the cache — everything needed for per-voxel SDF evaluation.
|
// A room in the cache — everything needed for per-voxel SDF evaluation.
|
||||||
// Internal details (CellX, CellY) used during cache building are NOT stored here.
|
// Internal details (CellX, CellY) used during cache building are NOT stored here.
|
||||||
struct FCachedRoom
|
struct FCachedRoom
|
||||||
@@ -568,6 +599,11 @@ struct FCachedRoom
|
|||||||
FVector ShapeA = FVector::ZeroVector;
|
FVector ShapeA = FVector::ZeroVector;
|
||||||
FVector ShapeB = FVector::ZeroVector;
|
FVector ShapeB = FVector::ZeroVector;
|
||||||
float ShapeR = 0.0f;
|
float ShapeR = 0.0f;
|
||||||
|
|
||||||
|
// Hash-derived room details. The old evaluator rebuilt these hashes and angles for every
|
||||||
|
// surface sample; the cache build is the only place where their geometry can change.
|
||||||
|
FCachedArch Arches[3];
|
||||||
|
FCachedPinch Pinches[3];
|
||||||
};
|
};
|
||||||
|
|
||||||
// A local flat-floor bridge over the overlap of two room bodies. It is not a new graph edge: it
|
// A local flat-floor bridge over the overlap of two room bodies. It is not a new graph edge: it
|
||||||
@@ -605,11 +641,112 @@ struct FCachedTunnel
|
|||||||
TArray<float> WorldControlRadii;
|
TArray<float> WorldControlRadii;
|
||||||
FVector WorldBoundCenter = FVector::ZeroVector;
|
FVector WorldBoundCenter = FVector::ZeroVector;
|
||||||
float WorldBoundRadiusSq = 0.0f;
|
float WorldBoundRadiusSq = 0.0f;
|
||||||
|
// Centerline AABBs and scalar influence radii used by the immutable broad phase. The
|
||||||
|
// AABB test is a cheap lower bound on distance to the whole chain; the existing squared
|
||||||
|
// sphere test remains the exact conservative second-stage reject.
|
||||||
|
FVector SDFCenterlineMin = FVector::ZeroVector;
|
||||||
|
FVector SDFCenterlineMax = FVector::ZeroVector;
|
||||||
|
float SDFInfluenceRadius = 0.0f;
|
||||||
|
FVector WorldCenterlineMin = FVector::ZeroVector;
|
||||||
|
FVector WorldCenterlineMax = FVector::ZeroVector;
|
||||||
|
float WorldInfluenceRadius = 0.0f;
|
||||||
// Bounding sphere for quick per-voxel rejection
|
// Bounding sphere for quick per-voxel rejection
|
||||||
FVector BoundCenter; // Center of the bounding sphere
|
FVector BoundCenter; // Center of the bounding sphere
|
||||||
float BoundRadiusSq; // Squared radius — if voxel is further, skip this tunnel
|
float BoundRadiusSq; // Squared radius — if voxel is further, skip this tunnel
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// A deterministic uniform-grid broad phase. Build() is called while a chunk cache is being
|
||||||
|
// assembled; after that the arrays are immutable and workers only read a bin range. Entries are
|
||||||
|
// supplied in candidate order, so each bin is already sorted by candidate ID and no runtime sort
|
||||||
|
// or allocation is needed. A failed/overflowing build leaves bValid=false and callers fall back
|
||||||
|
// to the exact legacy scan rather than risking a false negative.
|
||||||
|
struct FCaveSpatialIndexEntry
|
||||||
|
{
|
||||||
|
int32 CandidateIndex = INDEX_NONE;
|
||||||
|
FVector Min = FVector::ZeroVector;
|
||||||
|
FVector Max = FVector::ZeroVector;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct FCaveSpatialIndexBin
|
||||||
|
{
|
||||||
|
int32 First = 0;
|
||||||
|
int32 Count = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct FCaveSpatialIndex
|
||||||
|
{
|
||||||
|
static constexpr int32 BinSizeVoxels = 16;
|
||||||
|
|
||||||
|
bool bValid = false;
|
||||||
|
int32 MinBinX = 0;
|
||||||
|
int32 MinBinY = 0;
|
||||||
|
int32 MinBinZ = 0;
|
||||||
|
int32 CellsX = 0;
|
||||||
|
int32 CellsY = 0;
|
||||||
|
int32 CellsZ = 0;
|
||||||
|
TArray<FCaveSpatialIndexBin> Bins;
|
||||||
|
TArray<int32> CandidateIndices;
|
||||||
|
|
||||||
|
void Reset();
|
||||||
|
void Build(const TArray<FCaveSpatialIndexEntry>& Entries);
|
||||||
|
|
||||||
|
// Returns false only when the index is invalid. A valid query outside its extent returns
|
||||||
|
// true with an empty range, which is the normal cheap reject for that sample.
|
||||||
|
bool GetBin(float X, float Y, float Z, int32& OutFirst, int32& OutCount) const;
|
||||||
|
|
||||||
|
// Build-time/column-query helper. Coordinates are already integer cell coordinates.
|
||||||
|
bool GetBinAt(int32 CellX, int32 CellY, int32 CellZ,
|
||||||
|
int32& OutFirst, int32& OutCount) const;
|
||||||
|
|
||||||
|
// A support-floor column is Z-dependent but not Z-search-dependent. Gather the unique
|
||||||
|
// tunnel IDs from every height bin crossing one XY cell once, then reuse the resulting floor
|
||||||
|
// intervals for all samples in that column.
|
||||||
|
template <typename AllocatorType>
|
||||||
|
bool GetColumnCandidates(float X, float Y, TArray<int32, AllocatorType>& OutCandidates) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct FTunnelSupportFloorInterval
|
||||||
|
{
|
||||||
|
int32 TunnelIndex = INDEX_NONE;
|
||||||
|
float FloorZ = 0.0f;
|
||||||
|
float MinZ = 0.0f;
|
||||||
|
float MaxZ = 0.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct FTunnelSupportFloorColumn
|
||||||
|
{
|
||||||
|
TArray<FTunnelSupportFloorInterval, TInlineAllocator<16>> Intervals;
|
||||||
|
|
||||||
|
FTunnelSupportFloorColumn() = default;
|
||||||
|
|
||||||
|
FTunnelSupportFloorColumn(const FTunnelSupportFloorColumn& Other)
|
||||||
|
: Intervals(Other.Intervals)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
FTunnelSupportFloorColumn& operator=(const FTunnelSupportFloorColumn& Other)
|
||||||
|
{
|
||||||
|
Intervals = Other.Intervals;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
FTunnelSupportFloorColumn(FTunnelSupportFloorColumn&& Other)
|
||||||
|
: Intervals(MoveTemp(Other.Intervals))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
FTunnelSupportFloorColumn& operator=(FTunnelSupportFloorColumn&& Other)
|
||||||
|
{
|
||||||
|
Intervals = MoveTemp(Other.Intervals);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Reset()
|
||||||
|
{
|
||||||
|
Intervals.Reset();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// A pre-baked pit shaft — position and dimensions resolved during BuildChunkCache.
|
// A pre-baked pit shaft — position and dimensions resolved during BuildChunkCache.
|
||||||
//
|
//
|
||||||
// Pits are included in the main SDF evaluation (SmoothMin alongside rooms and
|
// Pits are included in the main SDF evaluation (SmoothMin alongside rooms and
|
||||||
@@ -667,6 +804,27 @@ struct FChunkSDFCache
|
|||||||
TArray<FCachedPit> Pits;
|
TArray<FCachedPit> Pits;
|
||||||
TArray<FCachedChimney> Chimneys;
|
TArray<FCachedChimney> Chimneys;
|
||||||
TArray<FCachedColumn> Columns;
|
TArray<FCachedColumn> Columns;
|
||||||
|
|
||||||
|
// Immutable broad-phase indices. The world index is also the source for per-column support
|
||||||
|
// intervals; its Z bins are traversed only when a column is first built.
|
||||||
|
FCaveSpatialIndex RoomIndex;
|
||||||
|
FCaveSpatialIndex RoomFloorJoinIndex;
|
||||||
|
FCaveSpatialIndex TunnelIndex;
|
||||||
|
FCaveSpatialIndex TunnelWorldIndex;
|
||||||
|
|
||||||
|
// Sparse immutable support-column table. SupportColumnEntries is a dense
|
||||||
|
// integer-XY slot map whose values index only non-empty SupportColumns.
|
||||||
|
int32 SupportColumnMinX = 0;
|
||||||
|
int32 SupportColumnMinY = 0;
|
||||||
|
int32 SupportColumnCellsX = 0;
|
||||||
|
int32 SupportColumnCellsY = 0;
|
||||||
|
TArray<int32> SupportColumnEntries;
|
||||||
|
TArray<FTunnelSupportFloorColumn> SupportColumns;
|
||||||
|
|
||||||
|
// Allocator-backed bytes owned by this cache, including the nested tunnel chains and index
|
||||||
|
// arrays. The returned value excludes sizeof(FChunkSDFCache) itself so callers can add the
|
||||||
|
// enclosing entry's inline storage exactly once.
|
||||||
|
SIZE_T GetAllocatedSize() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Combined world-space tunnel post query; one cache scan supplies both support and air tests. */
|
/** Combined world-space tunnel post query; one cache scan supplies both support and air tests. */
|
||||||
@@ -766,7 +924,8 @@ namespace VoxelCaveMorphology
|
|||||||
|
|
||||||
VOXELFORGE_API FTunnelCoreWorldEvaluation EvaluateTunnelCoreWorld(
|
VOXELFORGE_API FTunnelCoreWorldEvaluation EvaluateTunnelCoreWorld(
|
||||||
float WorldX, float WorldY, float WorldZ,
|
float WorldX, float WorldY, float WorldZ,
|
||||||
const FChunkSDFCache& Cache
|
const FChunkSDFCache& Cache,
|
||||||
|
const FTunnelSupportFloorColumn* SupportColumn = nullptr
|
||||||
);
|
);
|
||||||
|
|
||||||
// True for the finite support slab beneath a world-space graph tunnel. The native density
|
// True for the finite support slab beneath a world-space graph tunnel. The native density
|
||||||
@@ -777,6 +936,30 @@ namespace VoxelCaveMorphology
|
|||||||
const FChunkSDFCache& Cache
|
const FChunkSDFCache& Cache
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Build/query the same support-floor predicate at column granularity. This changes only
|
||||||
|
// where the deterministic projection is computed; it does not change the floor interval.
|
||||||
|
VOXELFORGE_API void BuildTunnelSupportFloorColumn(
|
||||||
|
float WorldX, float WorldY,
|
||||||
|
const FChunkSDFCache& Cache,
|
||||||
|
FTunnelSupportFloorColumn& OutColumn
|
||||||
|
);
|
||||||
|
|
||||||
|
VOXELFORGE_API bool IsTunnelSupportFloorColumnZ(
|
||||||
|
float WorldZ,
|
||||||
|
const FTunnelSupportFloorColumn& Column
|
||||||
|
);
|
||||||
|
|
||||||
|
// Returns the exact projected floor band for one tunnel at this XY column.
|
||||||
|
// The world evaluator uses FloorZ for its per-tunnel air ownership rule and
|
||||||
|
// Min/Max for the support slab itself.
|
||||||
|
VOXELFORGE_API bool GetTunnelSupportFloorColumnBand(
|
||||||
|
int32 TunnelIndex,
|
||||||
|
const FTunnelSupportFloorColumn& Column,
|
||||||
|
float& OutFloorZ,
|
||||||
|
float& OutMinZ,
|
||||||
|
float& OutMaxZ
|
||||||
|
);
|
||||||
|
|
||||||
// CONVENIENCE WRAPPER: builds a temporary cache and evaluates in one call.
|
// CONVENIENCE WRAPPER: builds a temporary cache and evaluates in one call.
|
||||||
// Use this for one-off queries (debug visualization, single-point sampling).
|
// Use this for one-off queries (debug visualization, single-point sampling).
|
||||||
// For chunk generation, use BuildChunkCache + EvaluateSDFCached instead.
|
// For chunk generation, use BuildChunkCache + EvaluateSDFCached instead.
|
||||||
|
|||||||
@@ -130,6 +130,10 @@ public:
|
|||||||
|
|
||||||
int32 Num() const { return Ops.Num(); }
|
int32 Num() const { return Ops.Num(); }
|
||||||
|
|
||||||
|
// Allocator-backed bytes owned by the stack, including each concrete operator allocation.
|
||||||
|
// The enclosing cache entry adds sizeof(FVoxelOpStack) separately.
|
||||||
|
SIZE_T GetAllocatedSize() const;
|
||||||
|
|
||||||
/** Hoist chunk-constant work for every op. Une fois par chunk et par worker.
|
/** Hoist chunk-constant work for every op. Une fois par chunk et par worker.
|
||||||
* Non-const : ça MUTE l'état par-chunk des opérateurs, et le prétendre const serait un
|
* Non-const : ça MUTE l'état par-chunk des opérateurs, et le prétendre const serait un
|
||||||
* mensonge utile qui finirait par masquer une course. */
|
* mensonge utile qui finirait par masquer une course. */
|
||||||
@@ -449,6 +453,9 @@ namespace VoxelDensityOps
|
|||||||
|
|
||||||
VOXELFORGE_API FRoomBoxDiagnostic GetLastRoomBoxDiagnostic();
|
VOXELFORGE_API FRoomBoxDiagnostic GetLastRoomBoxDiagnostic();
|
||||||
|
|
||||||
|
/** Current worker-local room-graph cache footprint; profiling/diagnostic use only. */
|
||||||
|
VOXELFORGE_API void ReportWorkerRoomGraphCacheFootprint();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FloatingIslands — 8 ops, et **la pile tourne à l'ENVERS** :
|
* FloatingIslands — 8 ops, et **la pile tourne à l'ENVERS** :
|
||||||
* ConstantVoid → IslandBlob → SdfRoughness → SdfFill → [structural post ×4]
|
* ConstantVoid → IslandBlob → SdfRoughness → SdfFill → [structural post ×4]
|
||||||
|
|||||||
@@ -47,6 +47,25 @@ namespace VoxelDensityProfile
|
|||||||
CaveTunnelAirOp,
|
CaveTunnelAirOp,
|
||||||
XYEdgeSealOp,
|
XYEdgeSealOp,
|
||||||
OtherOp,
|
OtherOp,
|
||||||
|
// Non-overlapping top-level phases. The named operation buckets above are intentionally
|
||||||
|
// nested diagnostics; these phase buckets are the accounting ledger that must add up to
|
||||||
|
// GetDensityAt without double-counting a stack op inside its enclosing phase.
|
||||||
|
DensityPrologue,
|
||||||
|
DensityCore,
|
||||||
|
DensityDisturbances,
|
||||||
|
DensityStructuralPosts,
|
||||||
|
DensityBoundarySeal,
|
||||||
|
DensityDiffLayer,
|
||||||
|
DensityTail,
|
||||||
|
// Mesher-side accounting. GenerateMesh is the inclusive total; the following buckets
|
||||||
|
// cover its regular-grid work and its non-grid setup/finalisation.
|
||||||
|
MesherGenerateMesh,
|
||||||
|
MesherDensityGrid,
|
||||||
|
MesherCellClassification,
|
||||||
|
MesherGradientNormals,
|
||||||
|
MesherVertexInterpolation,
|
||||||
|
MesherStreamBuilding,
|
||||||
|
MesherOther,
|
||||||
Count
|
Count
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -64,6 +83,10 @@ namespace VoxelDensityProfile
|
|||||||
CaveTunnelEvaluated,
|
CaveTunnelEvaluated,
|
||||||
TunnelCoreCandidates,
|
TunnelCoreCandidates,
|
||||||
TunnelCoreEvaluated,
|
TunnelCoreEvaluated,
|
||||||
|
TunnelSupportColumnBuilds,
|
||||||
|
TunnelSupportColumnCandidates,
|
||||||
|
TunnelSupportFloorQueries,
|
||||||
|
TunnelSupportFloorChecks,
|
||||||
PassageCandidates,
|
PassageCandidates,
|
||||||
PassageEvaluated,
|
PassageEvaluated,
|
||||||
Count
|
Count
|
||||||
@@ -76,6 +99,29 @@ namespace VoxelDensityProfile
|
|||||||
uint64 Calls[BucketCount]{};
|
uint64 Calls[BucketCount]{};
|
||||||
uint64 Cycles[BucketCount]{};
|
uint64 Cycles[BucketCount]{};
|
||||||
uint64 Counters[CounterCount]{};
|
uint64 Counters[CounterCount]{};
|
||||||
|
|
||||||
|
// Opt-in worker-cache footprint snapshot. These are current per-thread values (not
|
||||||
|
// allocation-event counters), so Snapshot() reports the resident cache footprint at the
|
||||||
|
// end of a profiling run rather than multiplying bytes by cache churn.
|
||||||
|
uint64 TunnelCacheWorkers = 0;
|
||||||
|
uint64 TunnelCacheCapacityEntries = 0;
|
||||||
|
uint64 TunnelCacheValidEntries = 0;
|
||||||
|
uint64 TunnelCacheStaticBytes = 0;
|
||||||
|
uint64 TunnelCacheDynamicBytes = 0;
|
||||||
|
uint64 TunnelCacheEntryBytes = 0;
|
||||||
|
uint64 TunnelCacheLargestEntryBytes = 0;
|
||||||
|
uint64 TunnelCacheLargestWorkerValidEntries = 0;
|
||||||
|
uint64 TunnelCacheLargestWorkerBytes = 0;
|
||||||
|
|
||||||
|
uint64 RoomGraphCacheWorkers = 0;
|
||||||
|
uint64 RoomGraphCacheCapacityEntries = 0;
|
||||||
|
uint64 RoomGraphCacheValidEntries = 0;
|
||||||
|
uint64 RoomGraphCacheStaticBytes = 0;
|
||||||
|
uint64 RoomGraphCacheDynamicBytes = 0;
|
||||||
|
uint64 RoomGraphCacheEntryBytes = 0;
|
||||||
|
uint64 RoomGraphCacheLargestEntryBytes = 0;
|
||||||
|
uint64 RoomGraphCacheLargestWorkerValidEntries = 0;
|
||||||
|
uint64 RoomGraphCacheLargestWorkerBytes = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
VOXELFORGE_API void SetEnabled(bool bEnabled);
|
VOXELFORGE_API void SetEnabled(bool bEnabled);
|
||||||
@@ -83,6 +129,18 @@ namespace VoxelDensityProfile
|
|||||||
VOXELFORGE_API void Reset();
|
VOXELFORGE_API void Reset();
|
||||||
VOXELFORGE_API FSnapshot Snapshot();
|
VOXELFORGE_API FSnapshot Snapshot();
|
||||||
VOXELFORGE_API void AddCounter(ECounter Counter, uint64 Amount = 1);
|
VOXELFORGE_API void AddCounter(ECounter Counter, uint64 Amount = 1);
|
||||||
|
VOXELFORGE_API void AddMeasurement(EBucket Bucket, uint64 Cycles, uint64 Calls = 1);
|
||||||
|
|
||||||
|
VOXELFORGE_API void SetWorkerTunnelCacheFootprint(
|
||||||
|
uint64 CapacityEntries, uint64 ValidEntries,
|
||||||
|
uint64 StaticBytes, uint64 DynamicBytes,
|
||||||
|
uint64 EntryBytes, uint64 LargestEntryBytes,
|
||||||
|
uint64 LargestWorkerValidEntries);
|
||||||
|
VOXELFORGE_API void SetWorkerRoomGraphCacheFootprint(
|
||||||
|
uint64 CapacityEntries, uint64 ValidEntries,
|
||||||
|
uint64 StaticBytes, uint64 DynamicBytes,
|
||||||
|
uint64 EntryBytes, uint64 LargestEntryBytes,
|
||||||
|
uint64 LargestWorkerValidEntries);
|
||||||
VOXELFORGE_API const TCHAR* CounterName(ECounter Counter);
|
VOXELFORGE_API const TCHAR* CounterName(ECounter Counter);
|
||||||
VOXELFORGE_API EBucket BucketFromName(const TCHAR* Name);
|
VOXELFORGE_API EBucket BucketFromName(const TCHAR* Name);
|
||||||
VOXELFORGE_API const TCHAR* BucketName(EBucket Bucket);
|
VOXELFORGE_API const TCHAR* BucketName(EBucket Bucket);
|
||||||
@@ -111,6 +169,10 @@ namespace VoxelDensityProfile
|
|||||||
|
|
||||||
~FScopedTimer();
|
~FScopedTimer();
|
||||||
|
|
||||||
|
// End a range before its lexical scope ends. This lets GetDensityAt split its enclosing
|
||||||
|
// total into disjoint prologue/core/post phases without moving the large cache block.
|
||||||
|
void End();
|
||||||
|
|
||||||
FScopedTimer(const FScopedTimer&) = delete;
|
FScopedTimer(const FScopedTimer&) = delete;
|
||||||
FScopedTimer& operator=(const FScopedTimer&) = delete;
|
FScopedTimer& operator=(const FScopedTimer&) = delete;
|
||||||
|
|
||||||
|
|||||||
@@ -160,7 +160,8 @@ public:
|
|||||||
*/
|
*/
|
||||||
float GetDensityWithParams(float WorldX, float WorldY, float WorldZ,
|
float GetDensityWithParams(float WorldX, float WorldY, float WorldZ,
|
||||||
const FStrateGenerationParams& Params,
|
const FStrateGenerationParams& Params,
|
||||||
uint32 ParamsFingerprint, uint32 LayoutVersion) const;
|
uint32 ParamsFingerprint, uint32 LayoutVersion,
|
||||||
|
bool bApplyLegacyStructuralPosts = true) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Densité pour une strate Slab (FlatPlain / CrystalChamber).
|
* Densité pour une strate Slab (FlatPlain / CrystalChamber).
|
||||||
|
|||||||
@@ -132,6 +132,15 @@ FORCEINLINE float Perlin3D(const FVector& P)
|
|||||||
return Perlin3D((float)P.X, (float)P.Y, (float)P.Z);
|
return Perlin3D((float)P.X, (float)P.Y, (float)P.Z);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hot density callers already own float voxel coordinates. Keeping this overload separate from
|
||||||
|
// FVector avoids constructing UE5's double-precision FVector merely to round the coordinates back
|
||||||
|
// to float at the noise entry point. It is the same scalar formula and remains deterministic on
|
||||||
|
// every build of the module.
|
||||||
|
FORCEINLINE float Perlin3D(const FVector3f& P)
|
||||||
|
{
|
||||||
|
return Perlin3D(P.X, P.Y, P.Z);
|
||||||
|
}
|
||||||
|
|
||||||
//=============================================================================
|
//=============================================================================
|
||||||
// 4-WIDE BATCH — the SIMD multiplier. Computes 4 independent Perlin samples.
|
// 4-WIDE BATCH — the SIMD multiplier. Computes 4 independent Perlin samples.
|
||||||
// Inputs are 4-element arrays; unused lanes must be zero-filled by the caller
|
// Inputs are 4-element arrays; unused lanes must be zero-filled by the caller
|
||||||
|
|||||||
@@ -173,6 +173,7 @@ struct FExploreArguments
|
|||||||
bool bRender = true;
|
bool bRender = true;
|
||||||
bool bWalk = true;
|
bool bWalk = true;
|
||||||
bool bExport = true;
|
bool bExport = true;
|
||||||
|
bool bUseOperatorStack = true;
|
||||||
bool bProfileDensity = false;
|
bool bProfileDensity = false;
|
||||||
bool bProfileLod = false;
|
bool bProfileLod = false;
|
||||||
bool bFailureFocusRender = false;
|
bool bFailureFocusRender = false;
|
||||||
@@ -212,6 +213,9 @@ bool ParseArguments(const FString& Params, FExploreArguments& OutArguments, FStr
|
|||||||
FParse::Value(*Params, TEXT("archetype="), ArchetypeText);
|
FParse::Value(*Params, TEXT("archetype="), ArchetypeText);
|
||||||
FParse::Value(*Params, TEXT("slot="), OutArguments.Slot);
|
FParse::Value(*Params, TEXT("slot="), OutArguments.Slot);
|
||||||
FParse::Value(*Params, TEXT("modes="), ModesText);
|
FParse::Value(*Params, TEXT("modes="), ModesText);
|
||||||
|
int32 UseOperatorStack = 1;
|
||||||
|
FParse::Value(*Params, TEXT("opstack="), UseOperatorStack);
|
||||||
|
OutArguments.bUseOperatorStack = UseOperatorStack != 0;
|
||||||
OutArguments.bProfileDensity = FParse::Param(*Params, TEXT("profiledensity"));
|
OutArguments.bProfileDensity = FParse::Param(*Params, TEXT("profiledensity"));
|
||||||
OutArguments.bProfileLod = FParse::Param(*Params, TEXT("profilelod"));
|
OutArguments.bProfileLod = FParse::Param(*Params, TEXT("profilelod"));
|
||||||
FParse::Value(*Params, TEXT("out="), OutText);
|
FParse::Value(*Params, TEXT("out="), OutText);
|
||||||
@@ -528,9 +532,9 @@ struct FExploreWorld
|
|||||||
Definition->GeneratorType = Index == Arguments.Slot
|
Definition->GeneratorType = Index == Arguments.Slot
|
||||||
? Arguments.Archetype
|
? Arguments.Archetype
|
||||||
: ECaveGeneratorType::TunnelNetwork;
|
: ECaveGeneratorType::TunnelNetwork;
|
||||||
// Exercise the production operator-stack opt-in where it is implemented (Maze),
|
// Explicitly select the production switch so the same synthetic layout, seed and
|
||||||
// while the runtime switch safely falls back for other archetypes.
|
// mesher invocation can be measured through both density branches.
|
||||||
Definition->bUseOperatorStack = true;
|
Definition->bUseOperatorStack = Arguments.bUseOperatorStack;
|
||||||
|
|
||||||
const TSoftObjectPtr<UVoxelStrateDefinition> SoftDefinition(Definition);
|
const TSoftObjectPtr<UVoxelStrateDefinition> SoftDefinition(Definition);
|
||||||
Settings->FixedStrates.Add(Index, SoftDefinition);
|
Settings->FixedStrates.Add(Index, SoftDefinition);
|
||||||
@@ -3270,6 +3274,7 @@ FString BuildManifestJson(
|
|||||||
Writer->WriteValue(TEXT("seed"), Arguments.Seed);
|
Writer->WriteValue(TEXT("seed"), Arguments.Seed);
|
||||||
Writer->WriteValue(TEXT("archetype"), ArchetypeName(Arguments.Archetype));
|
Writer->WriteValue(TEXT("archetype"), ArchetypeName(Arguments.Archetype));
|
||||||
Writer->WriteValue(TEXT("slot"), Arguments.Slot);
|
Writer->WriteValue(TEXT("slot"), Arguments.Slot);
|
||||||
|
Writer->WriteValue(TEXT("operator_stack"), Arguments.bUseOperatorStack);
|
||||||
Writer->WriteValue(TEXT("surface_roughness_override"), Arguments.bSurfaceRoughnessOverride);
|
Writer->WriteValue(TEXT("surface_roughness_override"), Arguments.bSurfaceRoughnessOverride);
|
||||||
if (Arguments.bSurfaceRoughnessOverride)
|
if (Arguments.bSurfaceRoughnessOverride)
|
||||||
{
|
{
|
||||||
@@ -3478,6 +3483,7 @@ FString BuildExploreJson(
|
|||||||
Writer->WriteValue(TEXT("seed"), Arguments.Seed);
|
Writer->WriteValue(TEXT("seed"), Arguments.Seed);
|
||||||
Writer->WriteValue(TEXT("archetype"), ArchetypeName(Arguments.Archetype));
|
Writer->WriteValue(TEXT("archetype"), ArchetypeName(Arguments.Archetype));
|
||||||
Writer->WriteValue(TEXT("slot"), Arguments.Slot);
|
Writer->WriteValue(TEXT("slot"), Arguments.Slot);
|
||||||
|
Writer->WriteValue(TEXT("operator_stack"), Arguments.bUseOperatorStack);
|
||||||
Writer->WriteValue(TEXT("surface_roughness_override"), Arguments.bSurfaceRoughnessOverride);
|
Writer->WriteValue(TEXT("surface_roughness_override"), Arguments.bSurfaceRoughnessOverride);
|
||||||
if (Arguments.bSurfaceRoughnessOverride)
|
if (Arguments.bSurfaceRoughnessOverride)
|
||||||
{
|
{
|
||||||
@@ -3494,10 +3500,11 @@ FString BuildExploreJson(
|
|||||||
Writer->WriteValue(TEXT("out_directory"), Arguments.OutDirectory);
|
Writer->WriteValue(TEXT("out_directory"), Arguments.OutDirectory);
|
||||||
Writer->WriteValue(TEXT("canonical_invocation"), FString::Printf(
|
Writer->WriteValue(TEXT("canonical_invocation"), FString::Printf(
|
||||||
TEXT("UnrealEditor-Cmd VoxelM.uproject -run=VoxelForgeExplore -seed=%d -archetype=%s "
|
TEXT("UnrealEditor-Cmd VoxelM.uproject -run=VoxelForgeExplore -seed=%d -archetype=%s "
|
||||||
"-slot=%d -modes=%s -out=Saved/VoxelForge/Explore/<runid>"),
|
"-slot=%d -opstack=%d -modes=%s -out=Saved/VoxelForge/Explore/<runid>"),
|
||||||
Arguments.Seed,
|
Arguments.Seed,
|
||||||
ArchetypeName(Arguments.Archetype),
|
ArchetypeName(Arguments.Archetype),
|
||||||
Arguments.Slot,
|
Arguments.Slot,
|
||||||
|
Arguments.bUseOperatorStack ? 1 : 0,
|
||||||
*Arguments.CanonicalModes()));
|
*Arguments.CanonicalModes()));
|
||||||
Writer->WriteObjectEnd();
|
Writer->WriteObjectEnd();
|
||||||
|
|
||||||
@@ -4123,6 +4130,102 @@ int32 UVoxelForgeExploreCommandlet::Main(const FString& Params)
|
|||||||
static_cast<unsigned long long>(DensityCalls), DensityTotalUs,
|
static_cast<unsigned long long>(DensityCalls), DensityTotalUs,
|
||||||
DensityCalls > 0 ? DensityTotalUs / static_cast<double>(DensityCalls) : 0.0);
|
DensityCalls > 0 ? DensityTotalUs / static_cast<double>(DensityCalls) : 0.0);
|
||||||
|
|
||||||
|
const uint64 CacheTotalStaticBytes = Profile.TunnelCacheStaticBytes
|
||||||
|
+ Profile.RoomGraphCacheStaticBytes;
|
||||||
|
const uint64 CacheTotalDynamicBytes = Profile.TunnelCacheDynamicBytes
|
||||||
|
+ Profile.RoomGraphCacheDynamicBytes;
|
||||||
|
const uint64 CacheTotalBytes = CacheTotalStaticBytes + CacheTotalDynamicBytes;
|
||||||
|
const uint64 CacheWorkers = FMath::Max(
|
||||||
|
Profile.TunnelCacheWorkers, Profile.RoomGraphCacheWorkers);
|
||||||
|
const uint64 CacheValidEntries = Profile.TunnelCacheValidEntries
|
||||||
|
+ Profile.RoomGraphCacheValidEntries;
|
||||||
|
const uint64 CacheEntryBytes = Profile.TunnelCacheEntryBytes
|
||||||
|
+ Profile.RoomGraphCacheEntryBytes;
|
||||||
|
const uint64 CacheCapacityEntries = Profile.TunnelCacheCapacityEntries
|
||||||
|
+ Profile.RoomGraphCacheCapacityEntries;
|
||||||
|
const uint64 CacheLargestEntryBytes = FMath::Max(
|
||||||
|
Profile.TunnelCacheLargestEntryBytes,
|
||||||
|
Profile.RoomGraphCacheLargestEntryBytes);
|
||||||
|
const uint64 CacheLargestWorkerBytes = FMath::Max(
|
||||||
|
Profile.TunnelCacheLargestWorkerBytes,
|
||||||
|
Profile.RoomGraphCacheLargestWorkerBytes);
|
||||||
|
UE_LOG(LogTemp, Display,
|
||||||
|
TEXT("[VoxelForgeCacheMemory] workers=%llu capacity_entries=%llu valid_entries=%llu "
|
||||||
|
"static_bytes=%llu dynamic_bytes=%llu total_bytes=%llu "
|
||||||
|
"avg_valid_entry_bytes=%.1f largest_entry_bytes=%llu "
|
||||||
|
"avg_worker_bytes=%.1f largest_worker_bytes=%llu "
|
||||||
|
"tunnel_avg_entry_bytes=%.1f tunnel_largest_entry_bytes=%llu "
|
||||||
|
"tunnel_largest_worker_valid=%llu "
|
||||||
|
"roomgraph_avg_entry_bytes=%.1f roomgraph_largest_entry_bytes=%llu "
|
||||||
|
"roomgraph_largest_worker_valid=%llu "
|
||||||
|
"tunnel_workers=%llu tunnel_capacity=%llu tunnel_valid=%llu "
|
||||||
|
"tunnel_static=%llu tunnel_dynamic=%llu roomgraph_workers=%llu "
|
||||||
|
"roomgraph_capacity=%llu roomgraph_valid=%llu roomgraph_static=%llu "
|
||||||
|
"roomgraph_dynamic=%llu"),
|
||||||
|
static_cast<unsigned long long>(CacheWorkers),
|
||||||
|
static_cast<unsigned long long>(CacheCapacityEntries),
|
||||||
|
static_cast<unsigned long long>(CacheValidEntries),
|
||||||
|
static_cast<unsigned long long>(CacheTotalStaticBytes),
|
||||||
|
static_cast<unsigned long long>(CacheTotalDynamicBytes),
|
||||||
|
static_cast<unsigned long long>(CacheTotalBytes),
|
||||||
|
CacheValidEntries > 0
|
||||||
|
? static_cast<double>(CacheEntryBytes) / static_cast<double>(CacheValidEntries)
|
||||||
|
: 0.0,
|
||||||
|
static_cast<unsigned long long>(CacheLargestEntryBytes),
|
||||||
|
CacheWorkers > 0
|
||||||
|
? static_cast<double>(CacheTotalBytes) / static_cast<double>(CacheWorkers)
|
||||||
|
: 0.0,
|
||||||
|
static_cast<unsigned long long>(CacheLargestWorkerBytes),
|
||||||
|
Profile.TunnelCacheValidEntries > 0
|
||||||
|
? static_cast<double>(Profile.TunnelCacheEntryBytes)
|
||||||
|
/ static_cast<double>(Profile.TunnelCacheValidEntries)
|
||||||
|
: 0.0,
|
||||||
|
static_cast<unsigned long long>(Profile.TunnelCacheLargestEntryBytes),
|
||||||
|
static_cast<unsigned long long>(Profile.TunnelCacheLargestWorkerValidEntries),
|
||||||
|
Profile.RoomGraphCacheValidEntries > 0
|
||||||
|
? static_cast<double>(Profile.RoomGraphCacheEntryBytes)
|
||||||
|
/ static_cast<double>(Profile.RoomGraphCacheValidEntries)
|
||||||
|
: 0.0,
|
||||||
|
static_cast<unsigned long long>(Profile.RoomGraphCacheLargestEntryBytes),
|
||||||
|
static_cast<unsigned long long>(Profile.RoomGraphCacheLargestWorkerValidEntries),
|
||||||
|
static_cast<unsigned long long>(Profile.TunnelCacheWorkers),
|
||||||
|
static_cast<unsigned long long>(Profile.TunnelCacheCapacityEntries),
|
||||||
|
static_cast<unsigned long long>(Profile.TunnelCacheValidEntries),
|
||||||
|
static_cast<unsigned long long>(Profile.TunnelCacheStaticBytes),
|
||||||
|
static_cast<unsigned long long>(Profile.TunnelCacheDynamicBytes),
|
||||||
|
static_cast<unsigned long long>(Profile.RoomGraphCacheWorkers),
|
||||||
|
static_cast<unsigned long long>(Profile.RoomGraphCacheCapacityEntries),
|
||||||
|
static_cast<unsigned long long>(Profile.RoomGraphCacheValidEntries),
|
||||||
|
static_cast<unsigned long long>(Profile.RoomGraphCacheStaticBytes),
|
||||||
|
static_cast<unsigned long long>(Profile.RoomGraphCacheDynamicBytes));
|
||||||
|
|
||||||
|
const VoxelDensityProfile::EBucket DensityPhases[] = {
|
||||||
|
VoxelDensityProfile::EBucket::DensityPrologue,
|
||||||
|
VoxelDensityProfile::EBucket::DensityCore,
|
||||||
|
VoxelDensityProfile::EBucket::DensityDisturbances,
|
||||||
|
VoxelDensityProfile::EBucket::DensityStructuralPosts,
|
||||||
|
VoxelDensityProfile::EBucket::DensityBoundarySeal,
|
||||||
|
VoxelDensityProfile::EBucket::DensityDiffLayer,
|
||||||
|
VoxelDensityProfile::EBucket::DensityTail,
|
||||||
|
};
|
||||||
|
uint64 DensityAttributionCycles = 0;
|
||||||
|
uint64 DensityAttributionCalls = 0;
|
||||||
|
for (const VoxelDensityProfile::EBucket Phase : DensityPhases)
|
||||||
|
{
|
||||||
|
const int32 PhaseIndex = static_cast<int32>(Phase);
|
||||||
|
DensityAttributionCycles += Profile.Cycles[PhaseIndex];
|
||||||
|
DensityAttributionCalls += Profile.Calls[PhaseIndex];
|
||||||
|
}
|
||||||
|
const double DensityAttributionUs =
|
||||||
|
static_cast<double>(DensityAttributionCycles) * CycleToMicroseconds;
|
||||||
|
const double DensityUnattributedUs = DensityTotalUs - DensityAttributionUs;
|
||||||
|
UE_LOG(LogTemp, Display,
|
||||||
|
TEXT("[VoxelForgeDensityProfile] attribution_sum_us=%.3f unattributed_us=%.3f "
|
||||||
|
"coverage=%.2f%% phase_calls=%llu"),
|
||||||
|
DensityAttributionUs, DensityUnattributedUs,
|
||||||
|
DensityTotalUs > 0.0 ? DensityAttributionUs * 100.0 / DensityTotalUs : 0.0,
|
||||||
|
static_cast<unsigned long long>(DensityAttributionCalls));
|
||||||
|
|
||||||
for (int32 Index = 0; Index < VoxelDensityProfile::CounterCount; ++Index)
|
for (int32 Index = 0; Index < VoxelDensityProfile::CounterCount; ++Index)
|
||||||
{
|
{
|
||||||
const VoxelDensityProfile::ECounter Counter =
|
const VoxelDensityProfile::ECounter Counter =
|
||||||
@@ -4151,6 +4254,37 @@ int32 UVoxelForgeExploreCommandlet::Main(const FString& Params)
|
|||||||
static_cast<unsigned long long>(Calls), TotalUs,
|
static_cast<unsigned long long>(Calls), TotalUs,
|
||||||
TotalUs / static_cast<double>(Calls));
|
TotalUs / static_cast<double>(Calls));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const VoxelDensityProfile::EBucket MesherBuckets[] = {
|
||||||
|
VoxelDensityProfile::EBucket::MesherDensityGrid,
|
||||||
|
VoxelDensityProfile::EBucket::MesherCellClassification,
|
||||||
|
VoxelDensityProfile::EBucket::MesherGradientNormals,
|
||||||
|
VoxelDensityProfile::EBucket::MesherVertexInterpolation,
|
||||||
|
VoxelDensityProfile::EBucket::MesherStreamBuilding,
|
||||||
|
VoxelDensityProfile::EBucket::MesherOther,
|
||||||
|
};
|
||||||
|
uint64 MesherAttributionCycles = 0;
|
||||||
|
uint64 MesherAttributionCalls = 0;
|
||||||
|
for (const VoxelDensityProfile::EBucket Bucket : MesherBuckets)
|
||||||
|
{
|
||||||
|
const int32 BucketIndex = static_cast<int32>(Bucket);
|
||||||
|
MesherAttributionCycles += Profile.Cycles[BucketIndex];
|
||||||
|
MesherAttributionCalls += Profile.Calls[BucketIndex];
|
||||||
|
}
|
||||||
|
const int32 MesherTotalIndex = static_cast<int32>(
|
||||||
|
VoxelDensityProfile::EBucket::MesherGenerateMesh);
|
||||||
|
const double MesherTotalUs = static_cast<double>(
|
||||||
|
Profile.Cycles[MesherTotalIndex]) * CycleToMicroseconds;
|
||||||
|
const double MesherAttributionUs = static_cast<double>(
|
||||||
|
MesherAttributionCycles) * CycleToMicroseconds;
|
||||||
|
UE_LOG(LogTemp, Display,
|
||||||
|
TEXT("[VoxelForgeMesherProfile] total_calls=%llu total_us=%.3f "
|
||||||
|
"attribution_sum_us=%.3f unattributed_us=%.3f coverage=%.2f%% "
|
||||||
|
"phase_calls=%llu"),
|
||||||
|
static_cast<unsigned long long>(Profile.Calls[MesherTotalIndex]),
|
||||||
|
MesherTotalUs, MesherAttributionUs, MesherTotalUs - MesherAttributionUs,
|
||||||
|
MesherTotalUs > 0.0 ? MesherAttributionUs * 100.0 / MesherTotalUs : 0.0,
|
||||||
|
static_cast<unsigned long long>(MesherAttributionCalls));
|
||||||
VoxelDensityProfile::SetEnabled(false);
|
VoxelDensityProfile::SetEnabled(false);
|
||||||
}
|
}
|
||||||
return bRequestedModeFailed ? 2 : 0;
|
return bRequestedModeFailed ? 2 : 0;
|
||||||
|
|||||||
Reference in new issue
Block a user