perf(generation): exact worm block skip with a proven 3D Perlin bound -- measured NEUTRAL
The sampler put the worm carve's first noise call (VoxelGenerator.cpp:3985, FusedWorm) at 19.4% of LOD0. The worm only carves where N1 + N2 < T with N2 >= 0, so where N1 >= T it contributes nothing, bit for bit. A block proven to have N1 >= T everywhere can skip both noise calls exactly. - The Lipschitz bound for this Perlin3D implementation: L = (15/4) * sqrt(3) * 1.25 = 8.118988160, in scaled-N1 units, with a 1e-3 float-rounding margin. Adversarial sampling found a worst ratio of 3.187 (0.39 L). The existing 2D floor-relief bound (8.5) is a component bound of a different field and was confirmed not to transfer. - A soundness test that can fail (VoxelForgeWormBlockSkipTest): 6,476 proved blocks, 355,945 exact lattice samples, 0 violations. A deliberately halved L proves a bad block; the full L rejects it. - voxel.WormBlockSkip, default 1. Field exact: export OBJ byte-identical (b3e5f4c3..9b377), 841/841 keyed tiles identical to NextHeadStaticTrace_20260913, capability 20,830 / 10,909. RESULT, stated plainly: no measurable speed gain. The skip rate is 4.07% of worm-eligible LOD0 samples and 0% at LOD1-4, because the bound is ~2.5x looser than the worst observed ratio at the block size used. Paired on-vs-off, two interleaved runs each: static worker +2.15%, generation p50 +1.8%; moving worker -1.42%, request p50 -2.5%. Within noise. The next round turns the default OFF. The proven bound and its test are kept for a tighter skip later (smaller or hierarchical blocks, or a per-cell bound). Run ledger (from Codex's report): the first build failed on an ambiguous test overload and was fixed; two timing runs are void because the orchestrator stopped the session so the owner could use the PC; the eight valid timing runs, the sampler run and the diagnostics runs completed. No new crash folders. PERF-SAMPLED.md now lists all eight earlier-round UECC crash records. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TaFdXhTdYMkW1xrV6ApT6Y
This commit is contained in:
1 parent
5489b5e311
commit
26b34765d5
9 files changed
+987
-24
No files matched your search
@@ -0,0 +1,350 @@
|
||||
// Soundness and empirical coverage for the worker-local worm N1 block proof.
|
||||
|
||||
#if WITH_DEV_AUTOMATION_TESTS
|
||||
|
||||
#include "Misc/AutomationTest.h"
|
||||
|
||||
#include "VoxelCaveMorphology.h"
|
||||
#include "VoxelNoise.h"
|
||||
|
||||
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
|
||||
FVoxelForgeWormBlockSkipTest,
|
||||
"VoxelForge.Determinism.WormBlockSkipSoundness",
|
||||
EAutomationTestFlags_ApplicationContextMask | EAutomationTestFlags::EngineFilter)
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr float WormFrequency = 0.015f;
|
||||
constexpr float WormHorizontalBias = 3.0f;
|
||||
constexpr float WormThreshold = 0.06f;
|
||||
constexpr float WormVerticalScale = 1.0f;
|
||||
constexpr int32 TestCellsPerAxis = 32;
|
||||
constexpr int32 TestTileCount = 192;
|
||||
constexpr int32 TestRandomPairCount = 500000;
|
||||
|
||||
FORCEINLINE uint32 NextTestBits(uint32& State)
|
||||
{
|
||||
State += 0x9E3779B9u;
|
||||
uint32 Z = State;
|
||||
Z = (Z ^ (Z >> 16)) * 0x7FEB352Du;
|
||||
Z = (Z ^ (Z >> 15)) * 0x846CA68Bu;
|
||||
return Z ^ (Z >> 16);
|
||||
}
|
||||
|
||||
FORCEINLINE float NextTestUnit(uint32& State)
|
||||
{
|
||||
return static_cast<float>(NextTestBits(State)) / 4294967296.0f;
|
||||
}
|
||||
|
||||
FORCEINLINE float ScaledN1(const FVector3f& Position)
|
||||
{
|
||||
return FMath::Abs(VoxelNoise::Perlin3D(Position) * VOXEL_NOISE_SCALE);
|
||||
}
|
||||
|
||||
FORCEINLINE FVector3f MakeWormNoisePosition(
|
||||
int32 WorldX, int32 WorldY, int32 WorldZ, uint32 Seed)
|
||||
{
|
||||
float EffectiveZ = static_cast<float>(WorldZ);
|
||||
if (WormVerticalScale != 1.0f && WormVerticalScale > 0.0f)
|
||||
{
|
||||
EffectiveZ /= WormVerticalScale;
|
||||
}
|
||||
const float WormZFreq = WormFrequency * WormHorizontalBias;
|
||||
return FVector3f(
|
||||
static_cast<float>(WorldX) * WormFrequency
|
||||
+ VoxelHash::SeedOffset(Seed, 1.0f),
|
||||
static_cast<float>(WorldY) * WormFrequency
|
||||
+ VoxelHash::SeedOffset(Seed, 1.7f),
|
||||
EffectiveZ * WormZFreq + VoxelHash::SeedOffset(Seed, 2.3f));
|
||||
}
|
||||
|
||||
FORCEINLINE double FadePrime(double T)
|
||||
{
|
||||
return 30.0 * T * T * (1.0 - T) * (1.0 - T);
|
||||
}
|
||||
|
||||
FORCEINLINE double GradientSupport(double X, double Y, double Z)
|
||||
{
|
||||
const double AX = FMath::Abs(X);
|
||||
const double AY = FMath::Abs(Y);
|
||||
const double AZ = FMath::Abs(Z);
|
||||
return FMath::Max(FMath::Max(AX + AY, AX + AZ), AY + AZ);
|
||||
}
|
||||
|
||||
// Supremum of the exact finite support relaxation used in the header derivation. This is
|
||||
// intentionally independent of the hash: all eight corners may choose any member of the
|
||||
// actual GradDot set, so this is the field-wide proof envelope rather than an average sample.
|
||||
double SupportRelaxedXDerivative(double X, double Y, double Z)
|
||||
{
|
||||
const double D = FadePrime(X);
|
||||
const double SY = Y * Y * Y * (Y * (Y * 6.0 - 15.0) + 10.0);
|
||||
const double SZ = Z * Z * Z * (Z * (Z * 6.0 - 15.0) + 10.0);
|
||||
double Result = 0.0;
|
||||
for (int32 J = 0; J < 2; ++J)
|
||||
{
|
||||
for (int32 K = 0; K < 2; ++K)
|
||||
{
|
||||
const double Weight = (J != 0 ? SY : 1.0 - SY)
|
||||
* (K != 0 ? SZ : 1.0 - SZ);
|
||||
const double YLocal = Y - static_cast<double>(J);
|
||||
const double ZLocal = Z - static_cast<double>(K);
|
||||
Result += Weight * (
|
||||
GradientSupport(1.0 - X - D * X, -D * YLocal, -D * ZLocal)
|
||||
+ GradientSupport(X - D * (1.0 - X), D * YLocal, D * ZLocal));
|
||||
}
|
||||
}
|
||||
return Result;
|
||||
}
|
||||
}
|
||||
|
||||
bool FVoxelForgeWormBlockSkipTest::RunTest(const FString& Parameters)
|
||||
{
|
||||
(void)Parameters;
|
||||
|
||||
const double FullL = VoxelNoise::WormN1NoiseSpaceLipschitzBound;
|
||||
const double HalfL = FullL * 0.5;
|
||||
TestTrue(TEXT("the production N1 Lipschitz bound is finite and positive"),
|
||||
FMath::IsFinite(FullL) && FullL > 0.0);
|
||||
TestTrue(TEXT("the actual 3D partial bound is 15/4"),
|
||||
FMath::IsNearlyEqual(VoxelNoise::Perlin3DPartialAbsBound, 3.75, 1.0e-12));
|
||||
TestTrue(TEXT("VOXEL_NOISE_SCALE is included in the N1 bound"),
|
||||
FMath::IsNearlyEqual(
|
||||
FullL,
|
||||
static_cast<double>(VOXEL_NOISE_SCALE)
|
||||
* VoxelNoise::Perlin3DPartialAbsBound
|
||||
* static_cast<double>(1.732050807568877293527446341505872L),
|
||||
1.0e-12));
|
||||
|
||||
// Check the finite support maximization densely, including corners and every cell face. The
|
||||
// closed-form case split is recorded beside the production constant; this grid is a regression
|
||||
// tripwire for changing GradDot/Fade/Lerp without updating that proof.
|
||||
double MaxRelaxedSupport = 0.0;
|
||||
FVector3f MaxRelaxedPoint = FVector3f::ZeroVector;
|
||||
constexpr int32 SupportGrid = 128;
|
||||
for (int32 IX = 0; IX <= SupportGrid; ++IX)
|
||||
{
|
||||
const double X = static_cast<double>(IX) / SupportGrid;
|
||||
for (int32 IY = 0; IY <= SupportGrid; ++IY)
|
||||
{
|
||||
const double Y = static_cast<double>(IY) / SupportGrid;
|
||||
for (int32 IZ = 0; IZ <= SupportGrid; ++IZ)
|
||||
{
|
||||
const double Z = static_cast<double>(IZ) / SupportGrid;
|
||||
const double Value = SupportRelaxedXDerivative(X, Y, Z);
|
||||
if (Value > MaxRelaxedSupport)
|
||||
{
|
||||
MaxRelaxedSupport = Value;
|
||||
MaxRelaxedPoint = FVector3f(
|
||||
static_cast<float>(X), static_cast<float>(Y), static_cast<float>(Z));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
AddInfo(FString::Printf(
|
||||
TEXT("3D support relaxation max=%.12f at (%.6f,%.6f,%.6f), bound=%.12f; "
|
||||
"the exact case split gives 15/4 at (0.5,0.5,0.5)."),
|
||||
MaxRelaxedSupport, MaxRelaxedPoint.X, MaxRelaxedPoint.Y, MaxRelaxedPoint.Z,
|
||||
VoxelNoise::Perlin3DPartialAbsBound));
|
||||
TestTrue(TEXT("the dense support relaxation stays below the proven partial bound"),
|
||||
MaxRelaxedSupport <= VoxelNoise::Perlin3DPartialAbsBound + 1.0e-10);
|
||||
TestTrue(TEXT("the support relaxation reaches the stated supremum"),
|
||||
MaxRelaxedSupport >= VoxelNoise::Perlin3DPartialAbsBound - 1.0e-10);
|
||||
|
||||
// Empirical attack: samples straddle lattice faces/corners at several scales, then a large
|
||||
// deterministic random-pair population probes ordinary cells and arbitrary directions.
|
||||
double MaxEmpiricalRatio = 0.0;
|
||||
FVector3f WorstA = FVector3f::ZeroVector;
|
||||
FVector3f WorstB = FVector3f::ZeroVector;
|
||||
uint64 PairCount = 0;
|
||||
auto ObservePair = [&](const FVector3f& A, const FVector3f& B)
|
||||
{
|
||||
const double DX = static_cast<double>(A.X) - static_cast<double>(B.X);
|
||||
const double DY = static_cast<double>(A.Y) - static_cast<double>(B.Y);
|
||||
const double DZ = static_cast<double>(A.Z) - static_cast<double>(B.Z);
|
||||
const double Distance = FMath::Sqrt(DX * DX + DY * DY + DZ * DZ);
|
||||
if (!(Distance > 0.0))
|
||||
{
|
||||
return;
|
||||
}
|
||||
++PairCount;
|
||||
const double Ratio = FMath::Abs(
|
||||
static_cast<double>(ScaledN1(A)) - static_cast<double>(ScaledN1(B))) / Distance;
|
||||
if (Ratio > MaxEmpiricalRatio)
|
||||
{
|
||||
MaxEmpiricalRatio = Ratio;
|
||||
WorstA = A;
|
||||
WorstB = B;
|
||||
}
|
||||
};
|
||||
|
||||
constexpr float FaceEpsilons[] = { 1.0e-5f, 1.0e-4f, 1.0e-3f, 1.0e-2f, 5.0e-2f };
|
||||
constexpr float FaceCoordinates[] = {
|
||||
1.0e-3f, 1.0e-2f, 0.1f, 0.25f, 0.5f, 0.75f, 0.9f, 0.99f, 0.999f };
|
||||
for (int32 X = -12; X < 12; ++X)
|
||||
{
|
||||
for (int32 Y = -12; Y < 12; ++Y)
|
||||
{
|
||||
for (int32 Z = -12; Z < 12; ++Z)
|
||||
{
|
||||
for (const float Epsilon : FaceEpsilons)
|
||||
{
|
||||
for (const float V : FaceCoordinates)
|
||||
{
|
||||
for (const float W : FaceCoordinates)
|
||||
{
|
||||
ObservePair(
|
||||
FVector3f(X + 1.0f - Epsilon, Y + V, Z + W),
|
||||
FVector3f(X + 1.0f + Epsilon, Y + V, Z + W));
|
||||
ObservePair(
|
||||
FVector3f(X + V, Y + 1.0f - Epsilon, Z + W),
|
||||
FVector3f(X + V, Y + 1.0f + Epsilon, Z + W));
|
||||
ObservePair(
|
||||
FVector3f(X + V, Y + W, Z + 1.0f - Epsilon),
|
||||
FVector3f(X + V, Y + W, Z + 1.0f + Epsilon));
|
||||
}
|
||||
}
|
||||
ObservePair(
|
||||
FVector3f(X + 1.0f - Epsilon, Y + 1.0f - Epsilon, Z + 1.0f - Epsilon),
|
||||
FVector3f(X + 1.0f + Epsilon, Y + 1.0f + Epsilon, Z + 1.0f + Epsilon));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint32 RandomState = 0xA5B3571Du;
|
||||
for (int32 Index = 0; Index < TestRandomPairCount; ++Index)
|
||||
{
|
||||
const FVector3f A(
|
||||
NextTestUnit(RandomState) * 128.0f - 64.0f,
|
||||
NextTestUnit(RandomState) * 128.0f - 64.0f,
|
||||
NextTestUnit(RandomState) * 128.0f - 64.0f);
|
||||
const FVector3f B(
|
||||
A.X + NextTestUnit(RandomState) * 0.5f - 0.25f,
|
||||
A.Y + NextTestUnit(RandomState) * 0.5f - 0.25f,
|
||||
A.Z + NextTestUnit(RandomState) * 0.5f - 0.25f);
|
||||
ObservePair(A, B);
|
||||
}
|
||||
AddInfo(FString::Printf(
|
||||
TEXT("Empirical scaled-N1 Lipschitz attack: pairs=%llu worst=%.9f, worst/L=%.9f, "
|
||||
"L=%.9f; worst A=(%.6f,%.6f,%.6f) B=(%.6f,%.6f,%.6f)."),
|
||||
static_cast<unsigned long long>(PairCount), MaxEmpiricalRatio,
|
||||
MaxEmpiricalRatio / FullL, FullL,
|
||||
WorstA.X, WorstA.Y, WorstA.Z, WorstB.X, WorstB.Y, WorstB.Z));
|
||||
TestTrue(TEXT("empirical near-face/corner/random ratios stay below L"),
|
||||
MaxEmpiricalRatio <= FullL + 0.01);
|
||||
|
||||
// Brute-force the exact mesher lattice for a large deterministic set of tile contexts. Every
|
||||
// block admitted by the production proof is checked at every lattice point it covers.
|
||||
uint64 BlocksScanned = 0;
|
||||
uint64 ProductionProofs = 0;
|
||||
uint64 ProductionLatticeSamples = 0;
|
||||
uint64 ProductionViolations = 0;
|
||||
for (int32 TileIndex = 0; TileIndex < TestTileCount; ++TileIndex)
|
||||
{
|
||||
const FIntVector TileOrigin(
|
||||
(TileIndex % 17 - 8) * 64,
|
||||
((TileIndex / 17) % 13 - 6) * 64,
|
||||
((TileIndex / 221) % 9 - 4) * 64);
|
||||
const uint32 Seed = 1337u + static_cast<uint32>(TileIndex) * 0x9E3779B9u;
|
||||
|
||||
for (int32 BlockZ = 0; BlockZ < 9; ++BlockZ)
|
||||
{
|
||||
for (int32 BlockY = 0; BlockY < 9; ++BlockY)
|
||||
{
|
||||
for (int32 BlockX = 0; BlockX < 9; ++BlockX)
|
||||
{
|
||||
++BlocksScanned;
|
||||
const int32 MinX = BlockX * VoxelNoise::WormBlockSampleSide - 1;
|
||||
const int32 MinY = BlockY * VoxelNoise::WormBlockSampleSide - 1;
|
||||
const int32 MinZ = BlockZ * VoxelNoise::WormBlockSampleSide - 1;
|
||||
const int32 MaxX = FMath::Min(
|
||||
TestCellsPerAxis + 1,
|
||||
MinX + VoxelNoise::WormBlockSampleSide - 1);
|
||||
const int32 MaxY = FMath::Min(
|
||||
TestCellsPerAxis + 1,
|
||||
MinY + VoxelNoise::WormBlockSampleSide - 1);
|
||||
const int32 MaxZ = FMath::Min(
|
||||
TestCellsPerAxis + 1,
|
||||
MinZ + VoxelNoise::WormBlockSampleSide - 1);
|
||||
const FVector3f Q0 = MakeWormNoisePosition(
|
||||
TileOrigin.X + MinX, TileOrigin.Y + MinY, TileOrigin.Z + MinZ, Seed);
|
||||
const FVector3f Q1 = MakeWormNoisePosition(
|
||||
TileOrigin.X + MaxX, TileOrigin.Y + MaxY, TileOrigin.Z + MaxZ, Seed);
|
||||
const FVector3f QMin(
|
||||
FMath::Min(Q0.X, Q1.X), FMath::Min(Q0.Y, Q1.Y), FMath::Min(Q0.Z, Q1.Z));
|
||||
const FVector3f QMax(
|
||||
FMath::Max(Q0.X, Q1.X), FMath::Max(Q0.Y, Q1.Y), FMath::Max(Q0.Z, Q1.Z));
|
||||
const FVector3f QCenter(
|
||||
static_cast<float>((static_cast<double>(QMin.X) + QMax.X) * 0.5),
|
||||
static_cast<float>((static_cast<double>(QMin.Y) + QMax.Y) * 0.5),
|
||||
static_cast<float>((static_cast<double>(QMin.Z) + QMax.Z) * 0.5));
|
||||
|
||||
if (!VoxelNoise::ProvesScaledAbsPerlin3DAbove(
|
||||
QCenter, QMin, QMax, WormThreshold, FullL))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
++ProductionProofs;
|
||||
for (int32 Z = MinZ; Z <= MaxZ; ++Z)
|
||||
{
|
||||
for (int32 Y = MinY; Y <= MaxY; ++Y)
|
||||
{
|
||||
for (int32 X = MinX; X <= MaxX; ++X)
|
||||
{
|
||||
++ProductionLatticeSamples;
|
||||
const float ExactN1 = ScaledN1(MakeWormNoisePosition(
|
||||
TileOrigin.X + X, TileOrigin.Y + Y, TileOrigin.Z + Z, Seed));
|
||||
if (ExactN1 < WormThreshold)
|
||||
{
|
||||
++ProductionViolations;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
AddInfo(FString::Printf(
|
||||
TEXT("Production block audit: tiles=%d blocks=%llu proofs=%llu lattice_samples=%llu "
|
||||
"violations=%llu, side=%d, threshold=%.6f."),
|
||||
TestTileCount, static_cast<unsigned long long>(BlocksScanned),
|
||||
static_cast<unsigned long long>(ProductionProofs),
|
||||
static_cast<unsigned long long>(ProductionLatticeSamples),
|
||||
static_cast<unsigned long long>(ProductionViolations),
|
||||
VoxelNoise::WormBlockSampleSide, WormThreshold));
|
||||
TestTrue(TEXT("the production-bound block audit found at least one proof"),
|
||||
ProductionProofs > 0);
|
||||
TestEqual(TEXT("every production-bound proof is sound on its exact lattice"),
|
||||
ProductionViolations, static_cast<uint64>(0));
|
||||
|
||||
// Fail-capable negative control. This pair is from a real low-coordinate hash cell. Its exact
|
||||
// scaled-N1 slope is above L/2; the threshold is selected between the two exact values, so a
|
||||
// deliberately halved bound admits a proof that the second lattice point violates.
|
||||
const FVector3f WitnessCenter(1510.5478515625f, 2191.43701171875f, 7279.404296875f);
|
||||
const FVector3f WitnessPoint(1510.54931640625f, 2191.437255859375f, 7279.50390625f);
|
||||
const FVector3f WitnessMin(
|
||||
2.0f * WitnessCenter.X - WitnessPoint.X,
|
||||
2.0f * WitnessCenter.Y - WitnessPoint.Y,
|
||||
2.0f * WitnessCenter.Z - WitnessPoint.Z);
|
||||
const FVector3f WitnessMax = WitnessPoint;
|
||||
constexpr float WitnessThreshold = 0.015f;
|
||||
const float WitnessCenterN1 = ScaledN1(WitnessCenter);
|
||||
const float WitnessPointN1 = ScaledN1(WitnessPoint);
|
||||
const bool bHalfProof = VoxelNoise::ProvesScaledAbsPerlin3DAbove(
|
||||
WitnessCenter, WitnessMin, WitnessMax, WitnessThreshold, HalfL);
|
||||
const bool bFullProof = VoxelNoise::ProvesScaledAbsPerlin3DAbove(
|
||||
WitnessCenter, WitnessMin, WitnessMax, WitnessThreshold, FullL);
|
||||
AddInfo(FString::Printf(
|
||||
TEXT("Halved-L negative control: center_N1=%.9f point_N1=%.9f threshold=%.6f "
|
||||
"half_proof=%d full_proof=%d."),
|
||||
WitnessCenterN1, WitnessPointN1, WitnessThreshold,
|
||||
bHalfProof ? 1 : 0, bFullProof ? 1 : 0));
|
||||
TestTrue(TEXT("the deliberately halved L admits the negative-control block"), bHalfProof);
|
||||
TestFalse(TEXT("the full L rejects the negative-control block"), bFullProof);
|
||||
TestTrue(TEXT("the negative-control lattice point really violates N1 >= threshold"),
|
||||
WitnessPointN1 < WitnessThreshold);
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif // WITH_DEV_AUTOMATION_TESTS
|
||||
@@ -688,6 +688,9 @@ namespace VoxelDensityProfile
|
||||
case ECounter::FusedTunnelSamples: return TEXT("FusedTunnelSamples");
|
||||
case ECounter::FusedTunnelDetailSamples: return TEXT("FusedTunnelDetailSamples");
|
||||
case ECounter::FusedTunnelDetailSkipped: return TEXT("FusedTunnelDetailSkipped");
|
||||
case ECounter::WormEligibleSamples: return TEXT("WormEligibleSamples");
|
||||
case ECounter::WormBlockProofs: return TEXT("WormBlockProofs");
|
||||
case ECounter::WormBlockSkippedSamples: return TEXT("WormBlockSkippedSamples");
|
||||
case ECounter::OpBlockBuilds: return TEXT("OpBlockBuilds");
|
||||
case ECounter::OpBlockSamples: return TEXT("OpBlockSamples");
|
||||
case ECounter::OpBlockOperators: return TEXT("OpBlockOperators");
|
||||
|
||||
@@ -85,6 +85,32 @@ namespace
|
||||
}
|
||||
}
|
||||
|
||||
// A proved N1-above-threshold block skips both worm noise calls. The switch is parsed once
|
||||
// on the same scalar entry point as the other generation A/B switches; 0 is the exact legacy
|
||||
// path, and 1 is the default after the proof/soundness test passes.
|
||||
int32 GVoxelForgeWormBlockSkip = 1;
|
||||
FAutoConsoleVariableRef CVarVoxelForgeWormBlockSkip(
|
||||
TEXT("voxel.WormBlockSkip"),
|
||||
GVoxelForgeWormBlockSkip,
|
||||
TEXT("Skip worm N1/N2 when a worker-local noise-space block proof says N1 >= threshold."));
|
||||
bool GVoxelForgeWormBlockSkipSwitchParsed = false;
|
||||
|
||||
void VF_ParseWormBlockSkipSwitch()
|
||||
{
|
||||
if (GVoxelForgeWormBlockSkipSwitchParsed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
GVoxelForgeWormBlockSkipSwitchParsed = true;
|
||||
int32 CommandLineValue = GVoxelForgeWormBlockSkip;
|
||||
if (FParse::Value(
|
||||
FCommandLine::Get(), TEXT("voxel.WormBlockSkip="), CommandLineValue))
|
||||
{
|
||||
GVoxelForgeWormBlockSkip = CommandLineValue;
|
||||
}
|
||||
GVoxelForgeWormBlockSkip = GVoxelForgeWormBlockSkip != 0 ? 1 : 0;
|
||||
}
|
||||
|
||||
// A tile-sized cache window is the production default on the coarse paths where it wins.
|
||||
// Keep the switches in the generator module so native and op-stack sources make the same A/B
|
||||
// decision, including when a headless harness applies command-line CVars after module startup.
|
||||
@@ -309,6 +335,257 @@ struct FSurfaceColumnCache
|
||||
// donc un ClassifyTile qui rend Mixed laisse ses colonnes chaudes pour le GenerateMesh qui suit.
|
||||
static thread_local FSurfaceColumnCache GSurfColCache;
|
||||
|
||||
//=============================================================================
|
||||
// WORM N1 BLOCK PROOF (worker-local, scalar GetDensityAt hand-off)
|
||||
//=============================================================================
|
||||
// GenerateMesh installs TileOriginVoxels/SampleStep/TileCellsPerAxis around every density-grid
|
||||
// fill. Keep the verdicts here, rather than in the mesher, because the scalar GetDensityAt path
|
||||
// owns the actual worm call and is also used by standalone/classifier queries. The cache is
|
||||
// thread_local, direct-indexed by the block coordinate, and invalidated by every field identity
|
||||
// that can affect N1. A miss or an invalid context always returns false and therefore executes
|
||||
// the old N1/N2 code.
|
||||
namespace
|
||||
{
|
||||
constexpr int32 VF_WormBlockCacheAxis =
|
||||
(CHUNK_SIZE + 3 + VoxelNoise::WormBlockSampleSide - 1)
|
||||
/ VoxelNoise::WormBlockSampleSide;
|
||||
constexpr int32 VF_WormBlockCacheEntryCount =
|
||||
VF_WormBlockCacheAxis * VF_WormBlockCacheAxis * VF_WormBlockCacheAxis;
|
||||
|
||||
static_assert(VF_WormBlockCacheAxis == 9,
|
||||
"The fixed worm block table must cover the 32-cell mesher lattice plus its halo.");
|
||||
|
||||
FORCEINLINE uint32 VF_FloatBits(float Value)
|
||||
{
|
||||
uint32 Bits = 0;
|
||||
FMemory::Memcpy(&Bits, &Value, sizeof(Bits));
|
||||
return Bits;
|
||||
}
|
||||
|
||||
struct FWormBlockSkipEntry
|
||||
{
|
||||
bool bValid = false;
|
||||
bool bSkip = false;
|
||||
};
|
||||
|
||||
struct FWormBlockSkipCache
|
||||
{
|
||||
bool bContextValid = false;
|
||||
uint64 OwnerId = 0;
|
||||
uint32 Seed = 0;
|
||||
uint32 ParamsFingerprint = 0xFFFFFFFFu;
|
||||
uint32 LayoutVersion = 0xFFFFFFFFu;
|
||||
FIntVector TileOrigin = FIntVector::ZeroValue;
|
||||
int32 Step = 0;
|
||||
int32 Cells = 0;
|
||||
uint32 WormFrequencyBits = 0;
|
||||
uint32 WormHorizontalBiasBits = 0;
|
||||
uint32 WormThresholdBits = 0;
|
||||
uint32 VerticalScaleBits = 0;
|
||||
FWormBlockSkipEntry Entries[VF_WormBlockCacheEntryCount];
|
||||
|
||||
void SetContext(
|
||||
uint64 InOwnerId, uint32 InSeed, uint32 InParamsFingerprint,
|
||||
uint32 InLayoutVersion, const FIntVector& InTileOrigin,
|
||||
int32 InStep, int32 InCells, const FStrateGenerationParams& Params)
|
||||
{
|
||||
const uint32 InWormFrequencyBits = VF_FloatBits(Params.WormFrequency);
|
||||
const uint32 InWormHorizontalBiasBits = VF_FloatBits(Params.WormHorizontalBias);
|
||||
const uint32 InWormThresholdBits = VF_FloatBits(Params.WormThreshold);
|
||||
const uint32 InVerticalScaleBits = VF_FloatBits(Params.VerticalScale);
|
||||
const bool bChanged =
|
||||
!bContextValid
|
||||
|| OwnerId != InOwnerId
|
||||
|| Seed != InSeed
|
||||
|| ParamsFingerprint != InParamsFingerprint
|
||||
|| LayoutVersion != InLayoutVersion
|
||||
|| TileOrigin != InTileOrigin
|
||||
|| Step != InStep
|
||||
|| Cells != InCells
|
||||
|| WormFrequencyBits != InWormFrequencyBits
|
||||
|| WormHorizontalBiasBits != InWormHorizontalBiasBits
|
||||
|| WormThresholdBits != InWormThresholdBits
|
||||
|| VerticalScaleBits != InVerticalScaleBits;
|
||||
if (!bChanged)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bContextValid = true;
|
||||
OwnerId = InOwnerId;
|
||||
Seed = InSeed;
|
||||
ParamsFingerprint = InParamsFingerprint;
|
||||
LayoutVersion = InLayoutVersion;
|
||||
TileOrigin = InTileOrigin;
|
||||
Step = InStep;
|
||||
Cells = InCells;
|
||||
WormFrequencyBits = InWormFrequencyBits;
|
||||
WormHorizontalBiasBits = InWormHorizontalBiasBits;
|
||||
WormThresholdBits = InWormThresholdBits;
|
||||
VerticalScaleBits = InVerticalScaleBits;
|
||||
FMemory::Memzero(Entries, sizeof(Entries));
|
||||
}
|
||||
};
|
||||
|
||||
static thread_local FWormBlockSkipCache GWormBlockSkipCache;
|
||||
|
||||
FORCEINLINE FVector3f VF_WormN1NoisePosition(
|
||||
float WorldX, float WorldY, float WorldZ,
|
||||
const FStrateGenerationParams& Params, uint32 SeedU)
|
||||
{
|
||||
float EffectiveZ = WorldZ;
|
||||
if (Params.VerticalScale != 1.0f && Params.VerticalScale > 0.0f)
|
||||
{
|
||||
EffectiveZ = WorldZ / Params.VerticalScale;
|
||||
}
|
||||
const float WormZFreq = Params.WormFrequency * Params.WormHorizontalBias;
|
||||
return FVector3f(
|
||||
WorldX * Params.WormFrequency + VoxelHash::SeedOffset(SeedU, 1.0f),
|
||||
WorldY * Params.WormFrequency + VoxelHash::SeedOffset(SeedU, 1.7f),
|
||||
EffectiveZ * WormZFreq + VoxelHash::SeedOffset(SeedU, 2.3f));
|
||||
}
|
||||
|
||||
FORCEINLINE bool VF_TryGetWormBlockSkip(
|
||||
uint64 OwnerId, uint32 SeedU, uint32 ParamsFingerprint, uint32 LayoutVersion,
|
||||
float WorldX, float WorldY, float WorldZ,
|
||||
const FStrateGenerationParams& Params, bool& bOutProofComputed)
|
||||
{
|
||||
bOutProofComputed = false;
|
||||
|
||||
// Point queries and malformed authored parameters stay on the exact original path. In
|
||||
// particular, VerticalScale <= 0 has legacy "no division" semantics; declining the proof
|
||||
// here avoids making a new promise for malformed data.
|
||||
if (!FMath::IsFinite(WorldX) || !FMath::IsFinite(WorldY) || !FMath::IsFinite(WorldZ)
|
||||
|| WorldX != FMath::FloorToFloat(WorldX)
|
||||
|| WorldY != FMath::FloorToFloat(WorldY)
|
||||
|| WorldZ != FMath::FloorToFloat(WorldZ)
|
||||
|| !FMath::IsFinite(Params.WormFrequency)
|
||||
|| !FMath::IsFinite(Params.WormHorizontalBias)
|
||||
|| !FMath::IsFinite(Params.WormThreshold)
|
||||
|| !FMath::IsFinite(Params.VerticalScale)
|
||||
|| !(Params.WormThreshold > 0.0f)
|
||||
|| !(Params.VerticalScale > 0.0f)
|
||||
|| !FMath::IsFinite(Params.WormFrequency * Params.WormHorizontalBias))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const int32 Step = VoxelGenLOD::SampleStep;
|
||||
const int32 Cells = VoxelGenLOD::TileCellsPerAxis;
|
||||
if (Step <= 0 || Cells <= 0 || Cells > CHUNK_SIZE)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// FMath::RoundToInt is only reached after the finite/integer check and a range guard. The
|
||||
// tile context itself is int32, so coordinates outside this range cannot belong to it.
|
||||
constexpr float SafeInt32Min = -2147483008.0f;
|
||||
constexpr float SafeInt32Max = 2147483008.0f;
|
||||
if (WorldX < SafeInt32Min || WorldX > SafeInt32Max
|
||||
|| WorldY < SafeInt32Min || WorldY > SafeInt32Max
|
||||
|| WorldZ < SafeInt32Min || WorldZ > SafeInt32Max)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
const int32 IX = FMath::RoundToInt(WorldX);
|
||||
const int32 IY = FMath::RoundToInt(WorldY);
|
||||
const int32 IZ = FMath::RoundToInt(WorldZ);
|
||||
const FIntVector TileOrigin = VoxelGenLOD::TileOriginVoxels;
|
||||
const int64 DX = static_cast<int64>(IX) - static_cast<int64>(TileOrigin.X);
|
||||
const int64 DY = static_cast<int64>(IY) - static_cast<int64>(TileOrigin.Y);
|
||||
const int64 DZ = static_cast<int64>(IZ) - static_cast<int64>(TileOrigin.Z);
|
||||
if (DX % Step != 0 || DY % Step != 0 || DZ % Step != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const int32 LocalX = static_cast<int32>(DX / Step);
|
||||
const int32 LocalY = static_cast<int32>(DY / Step);
|
||||
const int32 LocalZ = static_cast<int32>(DZ / Step);
|
||||
const int32 MinLocalLattice = -1;
|
||||
const int32 MaxLocalLattice = Cells + 1;
|
||||
if (LocalX < MinLocalLattice || LocalX > MaxLocalLattice
|
||||
|| LocalY < MinLocalLattice || LocalY > MaxLocalLattice
|
||||
|| LocalZ < MinLocalLattice || LocalZ > MaxLocalLattice)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const int32 BlockX = (LocalX + 1) / VoxelNoise::WormBlockSampleSide;
|
||||
const int32 BlockY = (LocalY + 1) / VoxelNoise::WormBlockSampleSide;
|
||||
const int32 BlockZ = (LocalZ + 1) / VoxelNoise::WormBlockSampleSide;
|
||||
if (BlockX < 0 || BlockX >= VF_WormBlockCacheAxis
|
||||
|| BlockY < 0 || BlockY >= VF_WormBlockCacheAxis
|
||||
|| BlockZ < 0 || BlockZ >= VF_WormBlockCacheAxis)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
GWormBlockSkipCache.SetContext(
|
||||
OwnerId, SeedU, ParamsFingerprint, LayoutVersion, TileOrigin, Step, Cells, Params);
|
||||
FWormBlockSkipEntry& Entry = GWormBlockSkipCache.Entries[
|
||||
(BlockZ * VF_WormBlockCacheAxis + BlockY) * VF_WormBlockCacheAxis + BlockX];
|
||||
if (Entry.bValid)
|
||||
{
|
||||
return Entry.bSkip;
|
||||
}
|
||||
|
||||
const int32 BlockMinX = BlockX * VoxelNoise::WormBlockSampleSide - 1;
|
||||
const int32 BlockMinY = BlockY * VoxelNoise::WormBlockSampleSide - 1;
|
||||
const int32 BlockMinZ = BlockZ * VoxelNoise::WormBlockSampleSide - 1;
|
||||
const int32 BlockMaxX = FMath::Min(MaxLocalLattice,
|
||||
BlockMinX + VoxelNoise::WormBlockSampleSide - 1);
|
||||
const int32 BlockMaxY = FMath::Min(MaxLocalLattice,
|
||||
BlockMinY + VoxelNoise::WormBlockSampleSide - 1);
|
||||
const int32 BlockMaxZ = FMath::Min(MaxLocalLattice,
|
||||
BlockMinZ + VoxelNoise::WormBlockSampleSide - 1);
|
||||
|
||||
const auto WorldAt = [Step, &TileOrigin](int32 Local, int32 Origin) -> int64
|
||||
{
|
||||
return static_cast<int64>(Origin) + static_cast<int64>(Local) * Step;
|
||||
};
|
||||
const int64 MinWorldX64 = WorldAt(BlockMinX, TileOrigin.X);
|
||||
const int64 MinWorldY64 = WorldAt(BlockMinY, TileOrigin.Y);
|
||||
const int64 MinWorldZ64 = WorldAt(BlockMinZ, TileOrigin.Z);
|
||||
const int64 MaxWorldX64 = WorldAt(BlockMaxX, TileOrigin.X);
|
||||
const int64 MaxWorldY64 = WorldAt(BlockMaxY, TileOrigin.Y);
|
||||
const int64 MaxWorldZ64 = WorldAt(BlockMaxZ, TileOrigin.Z);
|
||||
if (MinWorldX64 < MIN_int32 || MinWorldX64 > MAX_int32
|
||||
|| MinWorldY64 < MIN_int32 || MinWorldY64 > MAX_int32
|
||||
|| MinWorldZ64 < MIN_int32 || MinWorldZ64 > MAX_int32
|
||||
|| MaxWorldX64 < MIN_int32 || MaxWorldX64 > MAX_int32
|
||||
|| MaxWorldY64 < MIN_int32 || MaxWorldY64 > MAX_int32
|
||||
|| MaxWorldZ64 < MIN_int32 || MaxWorldZ64 > MAX_int32)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const FVector3f Q0 = VF_WormN1NoisePosition(
|
||||
static_cast<float>(static_cast<int32>(MinWorldX64)),
|
||||
static_cast<float>(static_cast<int32>(MinWorldY64)),
|
||||
static_cast<float>(static_cast<int32>(MinWorldZ64)), Params, SeedU);
|
||||
const FVector3f Q1 = VF_WormN1NoisePosition(
|
||||
static_cast<float>(static_cast<int32>(MaxWorldX64)),
|
||||
static_cast<float>(static_cast<int32>(MaxWorldY64)),
|
||||
static_cast<float>(static_cast<int32>(MaxWorldZ64)), Params, SeedU);
|
||||
const FVector3f QMin(
|
||||
FMath::Min(Q0.X, Q1.X), FMath::Min(Q0.Y, Q1.Y), FMath::Min(Q0.Z, Q1.Z));
|
||||
const FVector3f QMax(
|
||||
FMath::Max(Q0.X, Q1.X), FMath::Max(Q0.Y, Q1.Y), FMath::Max(Q0.Z, Q1.Z));
|
||||
const FVector3f QCenter(
|
||||
static_cast<float>((static_cast<double>(QMin.X) + static_cast<double>(QMax.X)) * 0.5),
|
||||
static_cast<float>((static_cast<double>(QMin.Y) + static_cast<double>(QMax.Y)) * 0.5),
|
||||
static_cast<float>((static_cast<double>(QMin.Z) + static_cast<double>(QMax.Z)) * 0.5));
|
||||
|
||||
Entry.bSkip = VoxelNoise::ProvesScaledAbsPerlin3DAbove(
|
||||
QCenter, QMin, QMax, Params.WormThreshold,
|
||||
VoxelNoise::WormN1NoiseSpaceLipschitzBound);
|
||||
Entry.bValid = true;
|
||||
bOutProofComputed = true;
|
||||
return Entry.bSkip;
|
||||
}
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
// FRACTAL NOISE (fBm — fractional Brownian motion)
|
||||
//=============================================================================
|
||||
@@ -1812,6 +2089,7 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co
|
||||
TRACE_CPUPROFILER_EVENT_SCOPE(VoxelForge_GetDensityAt);
|
||||
VF_ParseFastIsFiniteSwitch();
|
||||
VF_ParseFusedEvaluatorSwitch();
|
||||
VF_ParseWormBlockSkipSwitch();
|
||||
VoxelDensityProfile::FScopedTimer DensityProfileTimer(
|
||||
VoxelDensityProfile::EBucket::GetDensityAt);
|
||||
VoxelDensityProfile::FScopedTimer DensityPrologueTimer(
|
||||
@@ -3980,30 +4258,58 @@ float UVoxelGenerator::GetDensityWithParams(float WorldX, float WorldY, float Wo
|
||||
|
||||
if (NetworkMask > 0.0f)
|
||||
{
|
||||
float WormZFreq = Params.WormFrequency * Params.WormHorizontalBias;
|
||||
|
||||
float N1 = FMath::Abs(VoxelNoise::Perlin3D(FVector3f(
|
||||
WorldX * Params.WormFrequency + VoxelHash::SeedOffset(SeedU, 1.0f),
|
||||
WorldY * Params.WormFrequency + VoxelHash::SeedOffset(SeedU, 1.7f),
|
||||
EffectiveZ * WormZFreq + VoxelHash::SeedOffset(SeedU, 2.3f)
|
||||
)) * VOXEL_NOISE_SCALE);
|
||||
|
||||
// N2 >= 0, so if N1 alone already clears the threshold the sum can't carve —
|
||||
// skip the second Perlin entirely (most voxels; bit-identical output).
|
||||
if (N1 < Params.WormThreshold)
|
||||
const bool bCollectWormDiagnostics =
|
||||
bCollectFusedDiagnostics && VoxelDensityProfile::AreCountersEnabled();
|
||||
if (bCollectWormDiagnostics)
|
||||
{
|
||||
float N2 = FMath::Abs(VoxelNoise::Perlin3D(FVector3f(
|
||||
WorldX * Params.WormFrequency + VoxelHash::SeedOffset(SeedU, 1.0f) + 137.0f,
|
||||
WorldY * Params.WormFrequency + VoxelHash::SeedOffset(SeedU, 1.7f) + 259.0f,
|
||||
EffectiveZ * WormZFreq + VoxelHash::SeedOffset(SeedU, 2.3f) + 431.0f
|
||||
VoxelDensityProfile::AddCounter(
|
||||
VoxelDensityProfile::ECounter::WormEligibleSamples);
|
||||
}
|
||||
|
||||
bool bWormBlockProofComputed = false;
|
||||
const bool bSkipWormBlock = GVoxelForgeWormBlockSkip != 0
|
||||
&& VF_TryGetWormBlockSkip(
|
||||
DensityCacheOwnerId, SeedU, ParamsFingerprint, LayoutVersion,
|
||||
WorldX, WorldY, WorldZ, Params, bWormBlockProofComputed);
|
||||
if (bWormBlockProofComputed && bSkipWormBlock && bCollectWormDiagnostics)
|
||||
{
|
||||
VoxelDensityProfile::AddCounter(
|
||||
VoxelDensityProfile::ECounter::WormBlockProofs);
|
||||
}
|
||||
if (bSkipWormBlock)
|
||||
{
|
||||
if (bCollectWormDiagnostics)
|
||||
{
|
||||
VoxelDensityProfile::AddCounter(
|
||||
VoxelDensityProfile::ECounter::WormBlockSkippedSamples);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const float WormZFreq = Params.WormFrequency * Params.WormHorizontalBias;
|
||||
const float N1 = FMath::Abs(VoxelNoise::Perlin3D(FVector3f(
|
||||
WorldX * Params.WormFrequency + VoxelHash::SeedOffset(SeedU, 1.0f),
|
||||
WorldY * Params.WormFrequency + VoxelHash::SeedOffset(SeedU, 1.7f),
|
||||
EffectiveZ * WormZFreq + VoxelHash::SeedOffset(SeedU, 2.3f)
|
||||
)) * VOXEL_NOISE_SCALE);
|
||||
|
||||
float WormValue = N1 + N2;
|
||||
|
||||
if (WormValue < Params.WormThreshold)
|
||||
// N2 >= 0, so if N1 alone already clears the threshold the sum can't carve —
|
||||
// skip the second Perlin entirely (most voxels; bit-identical output).
|
||||
if (N1 < Params.WormThreshold)
|
||||
{
|
||||
float t = 1.0f - (WormValue / Params.WormThreshold);
|
||||
Density -= t * Params.WormStrength * NetworkMask;
|
||||
const float N2 = FMath::Abs(VoxelNoise::Perlin3D(FVector3f(
|
||||
WorldX * Params.WormFrequency + VoxelHash::SeedOffset(SeedU, 1.0f) + 137.0f,
|
||||
WorldY * Params.WormFrequency + VoxelHash::SeedOffset(SeedU, 1.7f) + 259.0f,
|
||||
EffectiveZ * WormZFreq + VoxelHash::SeedOffset(SeedU, 2.3f) + 431.0f
|
||||
)) * VOXEL_NOISE_SCALE);
|
||||
|
||||
const float WormValue = N1 + N2;
|
||||
|
||||
if (WormValue < Params.WormThreshold)
|
||||
{
|
||||
const float t = 1.0f - (WormValue / Params.WormThreshold);
|
||||
Density -= t * Params.WormStrength * NetworkMask;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3860,6 +3860,16 @@ void AVoxelWorld::GenerateTileResult(const FVoxelTileKey& Tile, const FIntVector
|
||||
CycleDelta(VoxelDensityProfile::EBucket::RoomGraphBuild));
|
||||
const double EvaluationSeconds = FMath::Max(
|
||||
0.0, TileSeconds - CacheBuildSeconds);
|
||||
const uint64 WormEligibleSamples = CounterDelta(
|
||||
VoxelDensityProfile::ECounter::WormEligibleSamples);
|
||||
const uint64 WormBlockProofs = CounterDelta(
|
||||
VoxelDensityProfile::ECounter::WormBlockProofs);
|
||||
const uint64 WormBlockSkippedSamples = CounterDelta(
|
||||
VoxelDensityProfile::ECounter::WormBlockSkippedSamples);
|
||||
const double WormBlockSkipRate = WormEligibleSamples > 0
|
||||
? static_cast<double>(WormBlockSkippedSamples)
|
||||
/ static_cast<double>(WormEligibleSamples)
|
||||
: 0.0;
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeTileProfile] tile=(%d,%d,%d) level=%d step=%d cells=%d sheet=%d aborted=%d empty=%d "
|
||||
"verdict=%d classify=%.6f mesh=%.6f streams=%.6f seconds=%.6f "
|
||||
@@ -3876,7 +3886,9 @@ void AVoxelWorld::GenerateTileResult(const FVoxelTileKey& Tile, const FIntVector
|
||||
"cache_op_local=%llu cache_classifier_shared=%llu cache_classifier_local=%llu "
|
||||
"cache_unknown=%llu room_candidates=%llu room_evaluated=%llu "
|
||||
"tunnel_candidates=%llu tunnel_evaluated=%llu tunnel_core_candidates=%llu "
|
||||
"tunnel_core_evaluated=%llu support_column_candidates=%llu"),
|
||||
"tunnel_core_evaluated=%llu support_column_candidates=%llu "
|
||||
"worm_eligible=%llu worm_block_proofs=%llu worm_block_skipped=%llu "
|
||||
"worm_skip_rate=%.6f"),
|
||||
Tile.Coord.X, Tile.Coord.Y, Tile.Coord.Z, Tile.Level, Step, Cells,
|
||||
bSheetTile ? 1 : 0, Result.bAborted ? 1 : 0,
|
||||
Result.bEmpty ? 1 : 0, ClassifyVerdict, ClassifySeconds, MeshSeconds, StreamSeconds,
|
||||
@@ -3918,7 +3930,11 @@ void AVoxelWorld::GenerateTileResult(const FVoxelTileKey& Tile, const FIntVector
|
||||
static_cast<unsigned long long>(CounterDelta(VoxelDensityProfile::ECounter::CaveTunnelEvaluated)),
|
||||
static_cast<unsigned long long>(CounterDelta(VoxelDensityProfile::ECounter::TunnelCoreCandidates)),
|
||||
static_cast<unsigned long long>(CounterDelta(VoxelDensityProfile::ECounter::TunnelCoreEvaluated)),
|
||||
static_cast<unsigned long long>(CounterDelta(VoxelDensityProfile::ECounter::TunnelSupportColumnCandidates)));
|
||||
static_cast<unsigned long long>(CounterDelta(VoxelDensityProfile::ECounter::TunnelSupportColumnCandidates)),
|
||||
static_cast<unsigned long long>(WormEligibleSamples),
|
||||
static_cast<unsigned long long>(WormBlockProofs),
|
||||
static_cast<unsigned long long>(WormBlockSkippedSamples),
|
||||
WormBlockSkipRate);
|
||||
|
||||
if (bProfileOps)
|
||||
{
|
||||
|
||||
@@ -150,6 +150,9 @@ namespace VoxelDensityProfile
|
||||
FusedTunnelSamples,
|
||||
FusedTunnelDetailSamples,
|
||||
FusedTunnelDetailSkipped,
|
||||
WormEligibleSamples,
|
||||
WormBlockProofs,
|
||||
WormBlockSkippedSamples,
|
||||
OpBlockBuilds,
|
||||
OpBlockSamples,
|
||||
OpBlockOperators,
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "VoxelTypes.h"
|
||||
|
||||
#if !defined(VF_NOISE_USE_SIMD)
|
||||
#if PLATFORM_ENABLE_VECTORINTRINSICS && PLATFORM_CPU_X86_FAMILY
|
||||
@@ -141,6 +142,104 @@ FORCEINLINE float Perlin3D(const FVector3f& P)
|
||||
return Perlin3D(P.X, P.Y, P.Z);
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
// LIPSCHITZ BOUND FOR THE ACTUAL 3D FIELD
|
||||
//=============================================================================
|
||||
// This is deliberately derived from the implementation above, rather than borrowed from a
|
||||
// textbook Perlin constant. GradDot's h&15 table has the twelve UNNORMALIZED gradients
|
||||
//
|
||||
// (±1,±1,0), (±1,0,±1), (0,±1,±1)
|
||||
//
|
||||
// (the four duplicate selectors do not add any vectors). For one partial derivative, write the
|
||||
// four x-edges of the trilinear interpolation as independent gradient pairs. With
|
||||
// d = Fade'(x) = 30 x²(1-x)², each edge contributes the support of the actual gradient set at
|
||||
//
|
||||
// c0 = (1-x-dx, -dy, -dz), c1 = (x-d(1-x), dy, dz).
|
||||
//
|
||||
// The support is max(|a|+|b|, |a|+|c|, |b|+|c|), because every allowed gradient has exactly two
|
||||
// non-zero ±1 components. Taking the four y/z edge weights and the exact fade derivative, a
|
||||
// finite case split over those three support branches has supremum 15/4 on [0,1]^3 (attained at
|
||||
// x=y=z=1/2). This maximization permits every corner hash to choose any member of the actual
|
||||
// set, so it is an upper bound for every hash cell; it is also the supremum of that finite support
|
||||
// relaxation, not the looser 1 + 4*max(Fade') = 8.5 bound used by the unrelated 2D floor relief.
|
||||
//
|
||||
// The vector bound below follows the same per-axis-bound × sqrt(dimension) method used by the
|
||||
// floor-relief proof. Abs is 1-Lipschitz and VOXEL_NOISE_SCALE is positive, so the scaled N1
|
||||
// field has this bound in noise-space coordinates. WormBlockSkip computes its radius after the
|
||||
// anisotropic world->noise transform, so the HBias and VerticalScale factors are included there.
|
||||
static constexpr double Perlin3DPartialAbsBound = 15.0 / 4.0;
|
||||
static constexpr double Perlin3DGradientBound =
|
||||
Perlin3DPartialAbsBound * 1.732050807568877293527446341505872L;
|
||||
static constexpr double WormN1NoiseSpaceLipschitzBound =
|
||||
static_cast<double>(VOXEL_NOISE_SCALE) * Perlin3DGradientBound;
|
||||
|
||||
// A four-point side gives the natural small worker-local blocks for the mesher's 35-point
|
||||
// (32 cells + one-sample normal halo) LOD0 lattice. The cache is only an optimization; a failed
|
||||
// proof falls through to the original per-sample N1/N2 code.
|
||||
static constexpr int32 WormBlockSampleSide = 4;
|
||||
|
||||
// The center value is evaluated with the same float Perlin expression as N1. The endpoint noise
|
||||
// coordinates are already float results of monotone positive/negative affine transforms, so their
|
||||
// min/max box contains every float lattice sample exactly. The 1e-3 scaled-N1 margin covers the
|
||||
// remaining center-evaluation/interpolation rounding with substantial headroom; the proof remains
|
||||
// strict because both the Lipschitz term and this margin are subtracted before comparing with T.
|
||||
static constexpr double WormBlockOutputRoundMargin = 1.0e-3;
|
||||
|
||||
FORCEINLINE bool ProvesScaledAbsPerlin3DAbove(
|
||||
const FVector3f& CenterNoisePosition,
|
||||
const FVector3f& MinNoisePosition,
|
||||
const FVector3f& MaxNoisePosition,
|
||||
float Threshold,
|
||||
double ScaledFieldLipschitzBound)
|
||||
{
|
||||
if (!FMath::IsFinite(CenterNoisePosition.X)
|
||||
|| !FMath::IsFinite(CenterNoisePosition.Y)
|
||||
|| !FMath::IsFinite(CenterNoisePosition.Z)
|
||||
|| !FMath::IsFinite(MinNoisePosition.X)
|
||||
|| !FMath::IsFinite(MinNoisePosition.Y)
|
||||
|| !FMath::IsFinite(MinNoisePosition.Z)
|
||||
|| !FMath::IsFinite(MaxNoisePosition.X)
|
||||
|| !FMath::IsFinite(MaxNoisePosition.Y)
|
||||
|| !FMath::IsFinite(MaxNoisePosition.Z)
|
||||
|| !FMath::IsFinite(Threshold)
|
||||
|| !(Threshold > 0.0f)
|
||||
|| !FMath::IsFinite(ScaledFieldLipschitzBound)
|
||||
|| !(ScaledFieldLipschitzBound > 0.0))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const double Dx = FMath::Max(
|
||||
FMath::Abs(static_cast<double>(MinNoisePosition.X)
|
||||
- static_cast<double>(CenterNoisePosition.X)),
|
||||
FMath::Abs(static_cast<double>(MaxNoisePosition.X)
|
||||
- static_cast<double>(CenterNoisePosition.X)));
|
||||
const double Dy = FMath::Max(
|
||||
FMath::Abs(static_cast<double>(MinNoisePosition.Y)
|
||||
- static_cast<double>(CenterNoisePosition.Y)),
|
||||
FMath::Abs(static_cast<double>(MaxNoisePosition.Y)
|
||||
- static_cast<double>(CenterNoisePosition.Y)));
|
||||
const double Dz = FMath::Max(
|
||||
FMath::Abs(static_cast<double>(MinNoisePosition.Z)
|
||||
- static_cast<double>(CenterNoisePosition.Z)),
|
||||
FMath::Abs(static_cast<double>(MaxNoisePosition.Z)
|
||||
- static_cast<double>(CenterNoisePosition.Z)));
|
||||
const double Radius = FMath::Sqrt(Dx * Dx + Dy * Dy + Dz * Dz);
|
||||
|
||||
// Keep this operation in float and in the same order as the production N1 expression. The
|
||||
// double conversion is only for the conservative comparison below.
|
||||
const float CenterN1 = FMath::Abs(
|
||||
Perlin3D(CenterNoisePosition) * VOXEL_NOISE_SCALE);
|
||||
if (!FMath::IsFinite(CenterN1))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return static_cast<double>(CenterN1)
|
||||
- ScaledFieldLipschitzBound * Radius
|
||||
>= static_cast<double>(Threshold) + WormBlockOutputRoundMargin;
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
// 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
|
||||
|
||||
Reference in new issue
Block a user