measure(perf): per-component ablation of the TunnelNetwork path; exact worms restored

Round (Codex; reviewed by me against PERF-SAMPLED.md).

Revert: voxel.WormNoiseMode / voxel.WormLatticeStep, their storage and their switches are
REMOVED. VoxelWormField has one path again, exact Perlin with the N1 short circuit. Kept:
bEnableWorms (asset), WormsForceOff (measurement only), the exact block skip (default 0).

Ablation instrument: 14 development-only, world-changing force-off bits (voxel.TunnelAblate*),
applied on the fused, op-stack/classifier, cached SDF/core and modifier/floor paths. They resolve
once per process, are hashed into the generation fingerprint, and are compiled to zero in
UE_BUILD_SHIPPING (VoxelDensityAblation.cpp). They are a measurement instrument, never a gameplay
or multiplayer switch.

Defaults exact: canonical export OBJ b3e5f4c3..9b377, worms-region 311CA42C / 75,482 triangles,
capability 20,830 / 10,909, and 841/841 keyed tiles identical to NextHeadStaticTrace_20260913.

Static worker seconds saved by removing ONE component (two off runs vs the interleaved all-on
33.06 s; baseline noise 0.27%):
  tunnel SDF 11.4%, cave warp 10.3%, passage carving 10.1%, landing posts 9.9%,
  passage structural posts 5.6%, worms 2.4%, native floor 2.2%, origin spine 1.6%,
  room SDF 1.6%, everything else <= 1.1%
Moving (2+2 runs each) confirms the top five at 6.0-11.6%. Tunnel SDF off cuts LOD0 generation
p95 104 -> 83 ms: it owns the slow tail. The positive savings sum to 58% of the static total; the
other 42% is shared. The rows are not additive.

Unexplained: tunnel-core OFF made the static total 2.9x SLOWER (95.5 s) while LOD0 generation got
faster (p50 64.7 -> 60.6 ms) and triangles fell. The report blames the changed field, which does
not fit; more likely a slow fallback path in coarse tiles. That row is not a cost measurement.

Ledger: every Unreal process exited 0 and verified the loaded DLL; two PowerShell parser errors
happened before any process started. No crashes or retries.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TaFdXhTdYMkW1xrV6ApT6Y
This commit is contained in:
Fr0zkaandClaude Opus 5 committed 2026-09-14 09:39:57 +02:00
1 parent c132243b30
commit 6ccf3718ff
12 files changed
+833 -695

No files matched your search

@@ -30,6 +30,7 @@
#include "VoxelCaveMorphology.h"
#include "VoxelDensityProfile.h"
#include "VoxelDensityPrimitives.h"
#include "VoxelDensityAblation.h"
#include "VoxelStrateMeasure.h"
#include "VoxelTypes.h" // Pour VOXEL_NOISE_SCALE, SmoothStep01
#include "VoxelStrateTypes.h"
@@ -3166,6 +3167,10 @@ namespace
const FStrateGenerationParams& Params,
uint32 Seed)
{
if (VoxelDensityAblation::IsCaveWarpOff())
{
return WorldPoint;
}
float EffectiveZ = WorldPoint.Z;
if (Params.VerticalScale > 0.0f && Params.VerticalScale != 1.0f)
{
@@ -7286,7 +7291,9 @@ void VoxelCaveMorphology::BuildChunkCache(
CR.RoomOp->ApplyTo(OpParams, CR.RoomOpWeight);
// PITS — downward shafts anchored in the room's lower half.
BakeRoomFeature(CR, /*Max*/2, OpParams.PitDensity,
if (!VoxelDensityAblation::IsPitChimneySDFOff())
{
BakeRoomFeature(CR, /*Max*/2, OpParams.PitDensity,
0xDE1A7Eu, 6271u, 0xABCDu, 0x5EEDu,
/*XYScale*/0.6f, OpParams.PitMinRadius, OpParams.PitMaxRadius,
[&](float PX, float PY, float PitRadius, uint32 PH3)
@@ -7308,8 +7315,8 @@ void VoxelCaveMorphology::BuildChunkCache(
OutCache.Pits.Add(Pit);
});
// CHIMNEYS — mirror of pits: upward tubes anchored in the room's upper half.
BakeRoomFeature(CR, /*Max*/2, OpParams.ChimneyDensity,
// CHIMNEYS — mirror of pits: upward tubes anchored in the room's upper half.
BakeRoomFeature(CR, /*Max*/2, OpParams.ChimneyDensity,
0xC4F007u, 7919u, 0x1337u, 0xCAFEu,
/*XYScale*/0.6f, OpParams.ChimneyMinRadius, OpParams.ChimneyMaxRadius,
[&](float CX, float CY, float ChmRadius, uint32 CH3)
@@ -7330,6 +7337,7 @@ void VoxelCaveMorphology::BuildChunkCache(
Chim.BoundXYRadiusSq = MaxXYR * MaxXYR;
OutCache.Chimneys.Add(Chim);
});
}
// COLUMNS — full-height solid cylinders (no Z anchor, no flare).
BakeRoomFeature(CR, /*Max*/4, OpParams.ColumnDensity,
@@ -7481,9 +7489,13 @@ float VoxelCaveMorphology::EvaluateSDFCached(
MinSDF = VoxelSDF::SmoothMin(MinSDF, RoomSDF, BlendK);
};
const int32 RoomCandidateCount = VF_ForEachSpatialCandidate(
Cache.RoomSpatialIndex, Cache.Rooms.Num(), WorldX, WorldY, EvaluateRoom,
bUseSpatialIndex);
int32 RoomCandidateCount = 0;
if (!VoxelDensityAblation::IsRoomSDFOff())
{
RoomCandidateCount = VF_ForEachSpatialCandidate(
Cache.RoomSpatialIndex, Cache.Rooms.Num(), WorldX, WorldY, EvaluateRoom,
bUseSpatialIndex);
}
if (VoxelDensityProfile::AreCountersEnabled())
{
VoxelDensityProfile::AddCounter(
@@ -7512,9 +7524,12 @@ float VoxelCaveMorphology::EvaluateSDFCached(
const float JoinSDF = FMath::Max(HorizontalSDF, VerticalSDF);
MinSDF = VoxelSDF::SmoothMin(MinSDF, JoinSDF, BlendK);
};
VF_ForEachSpatialCandidate(
Cache.RoomFloorJoinSpatialIndex, Cache.RoomFloorJoins.Num(),
WorldX, WorldY, EvaluateJoin, bUseSpatialIndex);
if (!VoxelDensityAblation::IsRoomSDFOff())
{
VF_ForEachSpatialCandidate(
Cache.RoomFloorJoinSpatialIndex, Cache.RoomFloorJoins.Num(),
WorldX, WorldY, EvaluateJoin, bUseSpatialIndex);
}
//=========================================================================
// Tunnel SDFs
@@ -7553,9 +7568,13 @@ float VoxelCaveMorphology::EvaluateSDFCached(
MinSDF = VoxelSDF::SmoothMin(MinSDF, TunnelSDF, BlendK);
};
const int32 TunnelCandidateCount = VF_ForEachSpatialCandidate(
Cache.TunnelSpatialIndex, Cache.Tunnels.Num(), WorldX, WorldY, EvaluateTunnel,
bUseSpatialIndex);
int32 TunnelCandidateCount = 0;
if (!VoxelDensityAblation::IsTunnelSDFOff())
{
TunnelCandidateCount = VF_ForEachSpatialCandidate(
Cache.TunnelSpatialIndex, Cache.Tunnels.Num(), WorldX, WorldY, EvaluateTunnel,
bUseSpatialIndex);
}
if (VoxelDensityProfile::AreCountersEnabled())
{
VoxelDensityProfile::AddCounter(
@@ -7577,6 +7596,10 @@ float VoxelCaveMorphology::EvaluateTunnelCoreSDF(
const FChunkSDFCache& Cache,
bool bUseSpatialIndex)
{
if (VoxelDensityAblation::IsTunnelCoreOff())
{
return FLT_MAX;
}
const FVector Pos(WorldX, WorldY, WorldZ);
float MinSDF = FLT_MAX;
const FCachedTunnel* TunnelData = Cache.Tunnels.GetData();
@@ -7618,6 +7641,10 @@ FTunnelCoreWorldEvaluation VoxelCaveMorphology::EvaluateTunnelCoreWorld(
const FTunnelSupportFloorColumn* SupportColumn,
bool bUseSpatialIndex)
{
if (VoxelDensityAblation::IsTunnelCoreOff())
{
return FTunnelCoreWorldEvaluation();
}
VoxelDensityProfile::FScopedTimer ProfileTimer(
VoxelDensityProfile::EBucket::TunnelCoreWorld);
const FVector Pos(WorldX, WorldY, WorldZ);
@@ -7756,6 +7783,10 @@ bool VoxelCaveMorphology::IsTunnelSupportFloorWorldPoint(
const FChunkSDFCache& Cache,
bool bUseSpatialIndex)
{
if (VoxelDensityAblation::IsTunnelCoreOff())
{
return false;
}
const FVector Pos(WorldX, WorldY, WorldZ);
const FCachedTunnel* TunnelData = Cache.Tunnels.GetData();
@@ -7875,6 +7906,11 @@ void VoxelCaveMorphology::BuildTunnelSupportFloorColumn(
FTunnelSupportFloorColumn& OutColumn,
bool bUseSpatialIndex)
{
if (VoxelDensityAblation::IsTunnelCoreOff())
{
OutColumn.Reset();
return;
}
VoxelDensityProfile::FScopedTimer ProfileTimer(
VoxelDensityProfile::EBucket::TunnelCoreSupport);
OutColumn.Reset();
@@ -0,0 +1,131 @@
// VoxelDensityAblation.cpp
// Development-only TunnelNetwork ablation controls.
#include "VoxelDensityAblation.h"
#include "HAL/IConsoleManager.h"
#include "Misc/CommandLine.h"
#include "Misc/Parse.h"
#include <atomic>
namespace
{
constexpr uint32 UnresolvedMask = 0xFFFFFFFFu;
std::atomic<uint32> GResolvedMask(UnresolvedMask);
#if !UE_BUILD_SHIPPING
int32 GCaveWarp = 0;
int32 GDetailOps = 0;
int32 GRoomSDF = 0;
int32 GTunnelSDF = 0;
int32 GTunnelCore = 0;
int32 GPassageCarving = 0;
int32 GPassageStructuralPosts = 0;
int32 GNativeFloor = 0;
int32 GDisturbances = 0;
int32 GOriginSpine = 0;
int32 GBoundarySeal = 0;
int32 GLandingPosts = 0;
int32 GXYEdgeSeal = 0;
int32 GPitChimneySDF = 0;
#define VF_ABLATION_CVAR(Name, Storage) \
FAutoConsoleVariableRef CVar##Storage( \
Name, Storage, \
TEXT("WORLD-CHANGING DEVELOPMENT-ONLY measurement override. Forces this " \
"TunnelNetwork density stage off; never use as a gameplay switch. " \
"All peers and regenerated worlds must use the same value. Resolved once " \
"per process. 0=on, 1=off."), ECVF_Default)
VF_ABLATION_CVAR(TEXT("voxel.TunnelAblateCaveWarp"), GCaveWarp);
VF_ABLATION_CVAR(TEXT("voxel.TunnelAblateDetailOps"), GDetailOps);
VF_ABLATION_CVAR(TEXT("voxel.TunnelAblateRoomSDF"), GRoomSDF);
VF_ABLATION_CVAR(TEXT("voxel.TunnelAblateTunnelSDF"), GTunnelSDF);
VF_ABLATION_CVAR(TEXT("voxel.TunnelAblateTunnelCore"), GTunnelCore);
VF_ABLATION_CVAR(TEXT("voxel.TunnelAblatePassageCarving"), GPassageCarving);
VF_ABLATION_CVAR(TEXT("voxel.TunnelAblatePassageStructuralPosts"), GPassageStructuralPosts);
VF_ABLATION_CVAR(TEXT("voxel.TunnelAblateNativeFloor"), GNativeFloor);
VF_ABLATION_CVAR(TEXT("voxel.TunnelAblateDisturbances"), GDisturbances);
VF_ABLATION_CVAR(TEXT("voxel.TunnelAblateOriginSpine"), GOriginSpine);
VF_ABLATION_CVAR(TEXT("voxel.TunnelAblateBoundarySeal"), GBoundarySeal);
VF_ABLATION_CVAR(TEXT("voxel.TunnelAblateLandingPosts"), GLandingPosts);
VF_ABLATION_CVAR(TEXT("voxel.TunnelAblateXYEdgeSeal"), GXYEdgeSeal);
VF_ABLATION_CVAR(TEXT("voxel.TunnelAblatePitChimneySDF"), GPitChimneySDF);
#undef VF_ABLATION_CVAR
uint32 ResolveValue(const TCHAR* Name, int32& Storage)
{
int32 Value = Storage;
FParse::Value(FCommandLine::Get(), Name, Value);
Storage = Value != 0 ? 1 : 0;
return Storage != 0 ? 1u : 0u;
}
uint32 ResolveMask()
{
uint32 Mask = 0;
Mask |= ResolveValue(TEXT("voxel.TunnelAblateCaveWarp="), GCaveWarp)
* static_cast<uint32>(VoxelDensityAblation::EStage::CaveWarp);
Mask |= ResolveValue(TEXT("voxel.TunnelAblateDetailOps="), GDetailOps)
* static_cast<uint32>(VoxelDensityAblation::EStage::DetailOps);
Mask |= ResolveValue(TEXT("voxel.TunnelAblateRoomSDF="), GRoomSDF)
* static_cast<uint32>(VoxelDensityAblation::EStage::RoomSDF);
Mask |= ResolveValue(TEXT("voxel.TunnelAblateTunnelSDF="), GTunnelSDF)
* static_cast<uint32>(VoxelDensityAblation::EStage::TunnelSDF);
Mask |= ResolveValue(TEXT("voxel.TunnelAblateTunnelCore="), GTunnelCore)
* static_cast<uint32>(VoxelDensityAblation::EStage::TunnelCore);
Mask |= ResolveValue(TEXT("voxel.TunnelAblatePassageCarving="), GPassageCarving)
* static_cast<uint32>(VoxelDensityAblation::EStage::PassageCarving);
Mask |= ResolveValue(
TEXT("voxel.TunnelAblatePassageStructuralPosts="), GPassageStructuralPosts)
* static_cast<uint32>(VoxelDensityAblation::EStage::PassageStructuralPosts);
Mask |= ResolveValue(TEXT("voxel.TunnelAblateNativeFloor="), GNativeFloor)
* static_cast<uint32>(VoxelDensityAblation::EStage::NativeFloor);
Mask |= ResolveValue(TEXT("voxel.TunnelAblateDisturbances="), GDisturbances)
* static_cast<uint32>(VoxelDensityAblation::EStage::Disturbances);
Mask |= ResolveValue(TEXT("voxel.TunnelAblateOriginSpine="), GOriginSpine)
* static_cast<uint32>(VoxelDensityAblation::EStage::OriginSpine);
Mask |= ResolveValue(TEXT("voxel.TunnelAblateBoundarySeal="), GBoundarySeal)
* static_cast<uint32>(VoxelDensityAblation::EStage::BoundarySeal);
Mask |= ResolveValue(TEXT("voxel.TunnelAblateLandingPosts="), GLandingPosts)
* static_cast<uint32>(VoxelDensityAblation::EStage::LandingPosts);
Mask |= ResolveValue(TEXT("voxel.TunnelAblateXYEdgeSeal="), GXYEdgeSeal)
* static_cast<uint32>(VoxelDensityAblation::EStage::XYEdgeSeal);
Mask |= ResolveValue(TEXT("voxel.TunnelAblatePitChimneySDF="), GPitChimneySDF)
* static_cast<uint32>(VoxelDensityAblation::EStage::PitChimneySDF);
return Mask;
}
#endif
}
namespace VoxelDensityAblation
{
uint32 GetResolvedMask()
{
uint32 Existing = GResolvedMask.load(std::memory_order_acquire);
if (Existing != UnresolvedMask)
{
return Existing;
}
#if UE_BUILD_SHIPPING
constexpr uint32 Resolved = 0;
#else
const uint32 Resolved = ResolveMask();
#endif
uint32 Expected = UnresolvedMask;
if (!GResolvedMask.compare_exchange_strong(
Expected, Resolved, std::memory_order_release, std::memory_order_acquire))
{
return Expected;
}
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeTunnelAblation] resolved mask=0x%08x "
"(world-changing development measurement overrides; 0=all on)"),
Resolved);
return Resolved;
}
}
+155 -104
View File
@@ -22,6 +22,7 @@
#include "VoxelStrateComposer.h"
#include "VoxelDensityPrimitives.h" // VF_ApplyOriginSpine / seals / PassageCarving
#include "VoxelDensityAblation.h" // fingerprinted development-only stage measurements
#include "VoxelCaveMorphology.h" // VoxelSDF::Capsule, VoxelHash
#include "VoxelGenerator.h" // VoxelGenLOD::Eff
#include "VoxelHeightOp.h" // FVoxelHeightStack — SurfaceWorld's two height stacks
@@ -1933,6 +1934,10 @@ namespace
EVoxelOpEffect EffectOverBox(const FBox& VoxelBox,
const FVoxelOpContext& Ctx) const override
{
if (VoxelDensityAblation::IsOriginSpineOff())
{
return EVoxelOpEffect::Identity;
}
const bool bRoomTouches = Ctx.bUseLatticeProof
? VoxelPassageGeometry::OriginLandingRoomTouchesLattice(
VoxelBox, Ctx.LatticeOriginVoxels, Ctx.Step,
@@ -2018,6 +2023,10 @@ namespace
*/
EVoxelTileClass ClassifyBox(const FBox& VoxelBox, const FVoxelOpContext&) const override
{
if (VoxelDensityAblation::IsBoundarySealOff())
{
return EVoxelTileClass::Mixed;
}
if (Thickness <= 0.0f || Base <= 0.0f) { return EVoxelTileClass::Mixed; }
constexpr float SafetyMargin = 1.0f;
@@ -2039,6 +2048,10 @@ namespace
// Hors de sa bande, le seal ne fait rien du tout ; à cheval, il ne peut qu'ajouter du solide.
EVoxelOpEffect EffectOverBox(const FBox& VoxelBox, const FVoxelOpContext&) const override
{
if (VoxelDensityAblation::IsBoundarySealOff())
{
return EVoxelOpEffect::Identity;
}
if (Thickness <= 0.0f) { return EVoxelOpEffect::Identity; }
const bool bTouchesTopBand = ((float)VoxelBox.Max.Z >= TopZ - Thickness) && ((float)VoxelBox.Min.Z <= TopZ);
const bool bTouchesBotBand = ((float)VoxelBox.Min.Z <= BotZ + Thickness) && ((float)VoxelBox.Max.Z >= BotZ);
@@ -2100,6 +2113,10 @@ namespace
*/
EVoxelTileClass ClassifyBox(const FBox& VoxelBox, const FVoxelOpContext& Ctx) const override
{
if (VoxelDensityAblation::IsXYEdgeSealOff())
{
return EVoxelTileClass::Mixed;
}
return VF_XYEdgeSealBoxIsForcedSolid(
VoxelBox, Ctx.WorldRadiusVoxels, Ctx.EdgeSealThickness, Base)
? EVoxelTileClass::AllSolid : EVoxelTileClass::Mixed;
@@ -2114,6 +2131,10 @@ namespace
// In the inner world it is Identity; a box that reaches the radial band can only gain rock.
EVoxelOpEffect EffectOverBox(const FBox& VoxelBox, const FVoxelOpContext& Ctx) const override
{
if (VoxelDensityAblation::IsXYEdgeSealOff())
{
return EVoxelOpEffect::Identity;
}
return VF_XYEdgeSealBoxTouchesBand(
VoxelBox, Ctx.WorldRadiusVoxels, Ctx.EdgeSealThickness)
? EVoxelOpEffect::FillOnly : EVoxelOpEffect::Identity;
@@ -2177,29 +2198,39 @@ namespace
EVoxelOpEffect EffectOverBox(const FBox& VoxelBox,
const FVoxelOpContext& Ctx) const override
{
if (VoxelDensityAblation::IsPassageCarvingOff()
&& VoxelDensityAblation::IsLandingPostsOff()
&& VoxelDensityAblation::IsPassageStructuralPostsOff()
&& VoxelDensityAblation::IsOriginSpineOff())
{
return EVoxelOpEffect::Identity;
}
const UVoxelStrateManager* LiveManager = Manager.Get();
if (!LiveManager) { return EVoxelOpEffect::Identity; }
const bool bOriginFloor = Ctx.bUseLatticeProof
? VoxelPassageGeometry::OriginLandingFloorTouchesLattice(
VoxelBox, Ctx.LatticeOriginVoxels, Ctx.Step,
TopZ, BotZ, Seal, SpineRadius)
: VoxelPassageGeometry::OriginLandingFloorTouchesBox(
VoxelBox, TopZ, BotZ, Seal, SpineRadius);
const bool bPassageFloor = Ctx.bUseLatticeProof
? LiveManager->AnyPassageLandingFloorNearLattice(
VoxelBox, Ctx.LatticeOriginVoxels, Ctx.Step)
: LiveManager->AnyPassageLandingFloorNearBox(
VoxelBox.Min, VoxelBox.Max);
const bool bOriginFloor = !VoxelDensityAblation::IsLandingPostsOff()
&& (Ctx.bUseLatticeProof
? VoxelPassageGeometry::OriginLandingFloorTouchesLattice(
VoxelBox, Ctx.LatticeOriginVoxels, Ctx.Step,
TopZ, BotZ, Seal, SpineRadius)
: VoxelPassageGeometry::OriginLandingFloorTouchesBox(
VoxelBox, TopZ, BotZ, Seal, SpineRadius));
const bool bPassageFloor = !VoxelDensityAblation::IsLandingPostsOff()
&& (Ctx.bUseLatticeProof
? LiveManager->AnyPassageLandingFloorNearLattice(
VoxelBox, Ctx.LatticeOriginVoxels, Ctx.Step)
: LiveManager->AnyPassageLandingFloorNearBox(
VoxelBox.Min, VoxelBox.Max));
if (bOriginFloor || bPassageFloor)
{
// The room carves air, while its support slab force-writes solid. Both
// hypotheses must therefore be killed for a box touching that slab.
return EVoxelOpEffect::Both;
}
const bool bPassageCarve = Ctx.bUseLatticeProof
? LiveManager->AnyPassageNearLattice(
VoxelBox, Ctx.LatticeOriginVoxels, Ctx.Step)
: LiveManager->AnyPassageNearBox(VoxelBox.Min, VoxelBox.Max);
const bool bPassageCarve = !VoxelDensityAblation::IsPassageCarvingOff()
&& (Ctx.bUseLatticeProof
? LiveManager->AnyPassageNearLattice(
VoxelBox, Ctx.LatticeOriginVoxels, Ctx.Step)
: LiveManager->AnyPassageNearBox(VoxelBox.Min, VoxelBox.Max));
return bPassageCarve
? EVoxelOpEffect::CarveOnly : EVoxelOpEffect::Identity;
}
@@ -2212,6 +2243,10 @@ namespace
float MaxCarveOverBox(const FBox& VoxelBox, const FVoxelOpContext& Ctx,
const FVoxelBoxHypotheses& H) const override
{
if (VoxelDensityAblation::IsPassageCarvingOff())
{
return 0.0f;
}
const UVoxelStrateManager* LiveManager = Manager.Get();
if (!LiveManager) { return 0.0f; }
if (!Ctx.bUseLatticeProof)
@@ -3917,6 +3952,10 @@ namespace
bool TryGetLastTunnelCoreWorldEvaluation(
FTunnelCoreWorldEvaluation& OutEvaluation) const override
{
if (VoxelDensityAblation::IsTunnelCoreOff())
{
return false;
}
FState& S = State();
if (!S.bLastTunnelCoreWorldEvaluationValid
|| S.LastTunnelCoreEvaluationPosition != S.LastWorldPosition)
@@ -4078,7 +4117,7 @@ namespace
// un « frame » : sa portée est exactement UN opérateur, donc elle appartient à cet
// opérateur.
float WarpedX = WorldX, WarpedY = WorldY, WarpedZ = EffectiveZ;
if (P.CaveWarpStrength > 0.0f)
if (!VoxelDensityAblation::IsCaveWarpOff() && P.CaveWarpStrength > 0.0f)
{
const float WF = P.CaveWarpFrequency;
const float WS = P.CaveWarpStrength;
@@ -4308,68 +4347,77 @@ namespace
// mais à des coordonnées NON warpées. Sous un modèle de frames il aurait fallu les sortir
// du frame tout en gardant le canal — exprimable, mais tordu. Dans un opérateur unique la
// difficulté disparaît : le warp est une variable locale, pas un contexte hérité.
auto EvaluatePit = [&](int32 PitIndex)
if (!VoxelDensityAblation::IsPitChimneySDFOff())
{
const FCachedPit& Pit = GetCache().Pits[PitIndex];
const float DZ = WorldZ - Pit.TopZ;
if (DZ >= Pit.BlendK) { return; }
if (-DZ > Pit.Depth + Pit.BlendK) { return; }
const float DX = WorldX - Pit.CenterX;
const float DY = WorldY - Pit.CenterY;
const float XYDistSq = DX * DX + DY * DY;
if (XYDistSq > Pit.BoundXYRadiusSq) { return; }
float PitSDF;
if (DZ <= 0.0f)
auto EvaluatePit = [&](int32 PitIndex)
{
const float DepthBelow = -DZ;
float FlareFactor = FMath::Clamp(1.0f - DepthBelow / Pit.FlareDist, 0.0f, 1.0f);
FlareFactor = FlareFactor * FlareFactor;
const float EffRadius = Pit.Radius + Pit.FlareExtra * FlareFactor;
PitSDF = FMath::Sqrt(XYDistSq) - EffRadius;
}
else
{
PitSDF = FMath::Sqrt(XYDistSq) - (Pit.Radius + Pit.FlareExtra);
}
const FCachedPit& Pit = GetCache().Pits[PitIndex];
const float DZ = WorldZ - Pit.TopZ;
if (DZ >= Pit.BlendK) { return; }
if (-DZ > Pit.Depth + Pit.BlendK) { return; }
CaveSDF = VoxelSDF::SmoothMin(CaveSDF, PitSDF, Pit.BlendK);
};
VF_ForEachChunkSDFSpatialCandidate(
GetCache().PitSpatialIndex, GetCache().Pits.Num(),
WorldX, WorldY, EvaluatePit, bUseSpatialIndex);
const float DX = WorldX - Pit.CenterX;
const float DY = WorldY - Pit.CenterY;
const float XYDistSq = DX * DX + DY * DY;
if (XYDistSq > Pit.BoundXYRadiusSq) { return; }
auto EvaluateChimney = [&](int32 ChimneyIndex)
float PitSDF;
if (DZ <= 0.0f)
{
const float DepthBelow = -DZ;
float FlareFactor = FMath::Clamp(1.0f - DepthBelow / Pit.FlareDist, 0.0f, 1.0f);
FlareFactor = FlareFactor * FlareFactor;
const float EffRadius = Pit.Radius + Pit.FlareExtra * FlareFactor;
PitSDF = FMath::Sqrt(XYDistSq) - EffRadius;
}
else
{
PitSDF = FMath::Sqrt(XYDistSq) - (Pit.Radius + Pit.FlareExtra);
}
CaveSDF = VoxelSDF::SmoothMin(CaveSDF, PitSDF, Pit.BlendK);
};
if (!VoxelDensityAblation::IsPitChimneySDFOff())
{
const FCachedChimney& Chim = GetCache().Chimneys[ChimneyIndex];
const float DZ = WorldZ - Chim.BottomZ;
if (-DZ >= Chim.BlendK) { return; }
if (DZ > Chim.Height + Chim.BlendK) { return; }
VF_ForEachChunkSDFSpatialCandidate(
GetCache().PitSpatialIndex, GetCache().Pits.Num(),
WorldX, WorldY, EvaluatePit, bUseSpatialIndex);
}
const float DX = WorldX - Chim.CenterX;
const float DY = WorldY - Chim.CenterY;
const float XYDistSq = DX * DX + DY * DY;
if (XYDistSq > Chim.BoundXYRadiusSq) { return; }
float ChmSDF;
if (DZ >= 0.0f)
auto EvaluateChimney = [&](int32 ChimneyIndex)
{
float FlareFactor = FMath::Clamp(1.0f - DZ / Chim.FlareDist, 0.0f, 1.0f);
FlareFactor = FlareFactor * FlareFactor;
const float EffRadius = Chim.Radius + Chim.FlareExtra * FlareFactor;
ChmSDF = FMath::Sqrt(XYDistSq) - EffRadius;
}
else
{
ChmSDF = FMath::Sqrt(XYDistSq) - (Chim.Radius + Chim.FlareExtra);
}
const FCachedChimney& Chim = GetCache().Chimneys[ChimneyIndex];
const float DZ = WorldZ - Chim.BottomZ;
if (-DZ >= Chim.BlendK) { return; }
if (DZ > Chim.Height + Chim.BlendK) { return; }
CaveSDF = VoxelSDF::SmoothMin(CaveSDF, ChmSDF, Chim.BlendK);
};
VF_ForEachChunkSDFSpatialCandidate(
GetCache().ChimneySpatialIndex, GetCache().Chimneys.Num(),
WorldX, WorldY, EvaluateChimney, bUseSpatialIndex);
const float DX = WorldX - Chim.CenterX;
const float DY = WorldY - Chim.CenterY;
const float XYDistSq = DX * DX + DY * DY;
if (XYDistSq > Chim.BoundXYRadiusSq) { return; }
float ChmSDF;
if (DZ >= 0.0f)
{
float FlareFactor = FMath::Clamp(1.0f - DZ / Chim.FlareDist, 0.0f, 1.0f);
FlareFactor = FlareFactor * FlareFactor;
const float EffRadius = Chim.Radius + Chim.FlareExtra * FlareFactor;
ChmSDF = FMath::Sqrt(XYDistSq) - EffRadius;
}
else
{
ChmSDF = FMath::Sqrt(XYDistSq) - (Chim.Radius + Chim.FlareExtra);
}
CaveSDF = VoxelSDF::SmoothMin(CaveSDF, ChmSDF, Chim.BlendK);
};
if (!VoxelDensityAblation::IsPitChimneySDFOff())
{
VF_ForEachChunkSDFSpatialCandidate(
GetCache().ChimneySpatialIndex, GetCache().Chimneys.Num(),
WorldX, WorldY, EvaluateChimney, bUseSpatialIndex);
}
}
InOut.Sdf = CaveSDF;
@@ -4378,40 +4426,46 @@ namespace
// build-time ownership now so the stack can preserve the authored support through its
// remaining modifiers without a second floor-writing pass. TryGet... below reuses the
// exact same cached result for the generator's post-disturbance hand-off.
const FTunnelSupportFloorColumn* SupportColumn = GetSupportColumn(
S.LastWorldPosition);
S.LastTunnelCoreWorldEvaluation = VoxelCaveMorphology::EvaluateTunnelCoreWorld(
WorldX, WorldY, WorldZ, GetCache(), SupportColumn,
bUseSpatialIndex);
S.LastTunnelCoreEvaluationPosition = S.LastWorldPosition;
S.bLastTunnelCoreWorldEvaluationValid = true;
if (S.LastTunnelCoreWorldEvaluation.bSupportFloor
|| S.LastTunnelCoreWorldEvaluation.bRoomFloor)
if (!VoxelDensityAblation::IsTunnelCoreOff())
{
const float StructuralFloorMinimumDensity =
FMath::Max(P.BaseDensity * 2.0f, 1.0f);
if (VoxelMath::IsFinite(StructuralFloorMinimumDensity)
&& StructuralFloorMinimumDensity > 0.0f)
const FTunnelSupportFloorColumn* SupportColumn = GetSupportColumn(
S.LastWorldPosition);
S.LastTunnelCoreWorldEvaluation = VoxelCaveMorphology::EvaluateTunnelCoreWorld(
WorldX, WorldY, WorldZ, GetCache(), SupportColumn,
bUseSpatialIndex);
S.LastTunnelCoreEvaluationPosition = S.LastWorldPosition;
S.bLastTunnelCoreWorldEvaluationValid = true;
if (S.LastTunnelCoreWorldEvaluation.bSupportFloor
|| S.LastTunnelCoreWorldEvaluation.bRoomFloor)
{
InOut.bProtectedStructuralFloor = true;
InOut.StructuralFloorMinimumDensity = StructuralFloorMinimumDensity;
if (VoxelDensityProfile::AreCountersEnabled())
const float StructuralFloorMinimumDensity =
FMath::Max(P.BaseDensity * 2.0f, 1.0f);
if (VoxelMath::IsFinite(StructuralFloorMinimumDensity)
&& StructuralFloorMinimumDensity > 0.0f)
{
VoxelDensityProfile::AddCounter(
VoxelDensityProfile::ECounter::TunnelAuthoredFloorSamples);
InOut.bProtectedStructuralFloor = true;
InOut.StructuralFloorMinimumDensity = StructuralFloorMinimumDensity;
if (VoxelDensityProfile::AreCountersEnabled())
{
VoxelDensityProfile::AddCounter(
VoxelDensityProfile::ECounter::TunnelAuthoredFloorSamples);
}
}
}
// These are the per-sample hand-offs needed by an op-major block. The scalar path
// continues to use FState directly; publishing them here only makes the dependency
// explicit for a later modifier and does not alter Density/Sdf.
InOut.bHasTunnelCoreWorldEvaluation = true;
InOut.TunnelCoreWorldSDF = S.LastTunnelCoreWorldEvaluation.SDF;
InOut.bTunnelCoreSupportFloor = S.LastTunnelCoreWorldEvaluation.bSupportFloor;
InOut.bTunnelCoreRoomFloor = S.LastTunnelCoreWorldEvaluation.bRoomFloor;
}
// These are the per-sample hand-offs needed by an op-major block. The scalar path
// continues to use FState directly; publishing them here only makes the dependency
// explicit for a later modifier and does not alter Density/Sdf.
// These cache hand-offs remain valid when tunnel core is off; detail operators still
// need the room graph and nearest-room ownership.
InOut.RoomCache = &GetCache();
InOut.NearestRoomIndex = S.NearestRoom;
InOut.bHasTunnelCoreWorldEvaluation = true;
InOut.TunnelCoreWorldSDF = S.LastTunnelCoreWorldEvaluation.SDF;
InOut.bTunnelCoreSupportFloor = S.LastTunnelCoreWorldEvaluation.bSupportFloor;
InOut.bTunnelCoreRoomFloor = S.LastTunnelCoreWorldEvaluation.bRoomFloor;
}
//---------------------------------------------------------------------
@@ -5418,7 +5472,7 @@ namespace
&& P.VerticalScale != 1.0f)
? WorldZ / P.VerticalScale : WorldZ;
FVector Query(WorldX, WorldY, EffectiveZ);
if (P.CaveWarpStrength > 0.0f)
if (!VoxelDensityAblation::IsCaveWarpOff() && P.CaveWarpStrength > 0.0f)
{
const float Frequency = P.CaveWarpFrequency;
Query.X += VoxelNoise::Perlin3D(
@@ -8543,9 +8597,6 @@ namespace
FWormFieldSource(const FStrateGenerationParams& InP, int32 Seed)
: P(InP)
, SeedU((uint32)Seed)
, WormLatticeStep(
InP.WormStrength > 0.0f && InP.WormThreshold > 0.0f
? VoxelWormField::GetLatticeStep() : 0)
{}
EVoxelOpRole GetRole() const override { return EVoxelOpRole::FieldSource; }
@@ -8585,7 +8636,7 @@ namespace
SeedU};
const float WormValue = VoxelWormField::Evaluate(
WorldX, WorldY, WorldZ, P.WormThreshold,
WormParameters, WormLatticeStep);
WormParameters);
if (WormValue < P.WormThreshold)
{
const float t = 1.0f - (WormValue / P.WormThreshold);
@@ -8656,9 +8707,7 @@ namespace
// explicit count cap keeps this check bounded. The network mask still uses the SDF
// interval's worst case, so this never assumes a room value that the preceding source
// did not prove.
if (VoxelWormField::GetNoiseMode() == 0
&& WormLatticeStep <= 1
&& Ctx.bUseLatticeProof && Ctx.bTightenWarpProof && Ctx.Step >= 1
if (Ctx.bUseLatticeProof && Ctx.bTightenWarpProof && Ctx.Step >= 1
&& VoxelMath::IsFinite(P.WormFrequency)
&& VoxelMath::IsFinite(P.WormHorizontalBias)
&& VoxelMath::IsFinite(P.WormThreshold)
@@ -8778,7 +8827,6 @@ namespace
private:
FStrateGenerationParams P;
uint32 SeedU;
int32 WormLatticeStep = 0;
};
#if WITH_EDITOR
@@ -9722,6 +9770,8 @@ namespace VoxelDensityOps
// `Sdf < SDFBlendRadius·3` via VF_NearCaveSurface. Voir la note de l'étape B5 là-bas.
// L'ORDRE EST CELUI DE L'ORIGINAL et il compte : chacun lit la densité que le précédent
// a laissée (le biais de sol est un ajout indépendant appliqué à cette accumulation).
if (!VoxelDensityAblation::IsDetailOpsOff())
{
OutStack.Add(MakeUnique<FCaveRoughnessMod>(P, Seed)); // 4b
OutStack.Add(MakeUnique<FCaveTerraceMod>(P, Seed, RoomPtr)); // 4c — terrasses
OutStack.Add(MakeUnique<FLayerLineMod>(P, RoomPtr)); // 4c — lignes de strates
@@ -9734,6 +9784,7 @@ namespace VoxelDensityOps
OutStack.Add(MakeUnique<FDomeMod>(P, RoomPtr)); // 4g — dômes
OutStack.Add(MakeUnique<FPinchMod>(P, RoomPtr)); // 4h — pincement
OutStack.Add(MakeUnique<FFloorBiasMod>(P, RoomPtr)); // fin 4h — biais de sol
}
if (P.WormStrength > 0.0f && P.WormThreshold > 0.0f)
{
OutStack.Add(MakeUnique<FWormFieldSource>(P, Seed));
+89 -77
View File
@@ -14,6 +14,7 @@
#include "VoxelBiomeDefinition.h"
#include "VoxelNoise.h" // T2.a: float, SIMD-batched gradient-noise core
#include "VoxelWormField.h"
#include "VoxelDensityAblation.h" // fingerprinted development-only stage measurements
#include "VoxelDensityPrimitives.h" // spine / seals / passage — shared with the operator stack
#include "VoxelDensityOpStack.h" // OPSTACK Phase 1: the opt-in per-strate operator stack
#include "VoxelHeightOp.h" // IVoxelBiomeField — the adapter below implements it
@@ -985,6 +986,10 @@ static void ApplyDisturbances(float& MC, float X, float Y, float Z,
const FStrateDisturbanceParams& D, uint32 Seed, bool bProtectVerticalShaftAir,
bool bProtectAuthoredTunnelFloor)
{
if (VoxelDensityAblation::IsDisturbancesOff())
{
return;
}
VoxelDensityProfile::FScopedTimer ProfileTimer(
VoxelDensityProfile::EBucket::ApplyDisturbances);
const float InnerTop = D.StrateTopWorldZ - D.BoundarySealThickness;
@@ -2592,7 +2597,7 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co
// 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
// de diff qui suivent ne voient aucune différence.
if (CP_UseOpStack)
if (!VoxelDensityAblation::IsTunnelCoreOff() && CP_UseOpStack)
{
const bool bCanUseFusedEvaluator =
GVoxelForgeUseFusedEvaluator != 0
@@ -2751,7 +2756,8 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co
PreDisturbanceTunnelCore);
}
}
if (!bHavePreDisturbanceTunnelCore && ActiveTunnelCoreCache->bValid)
if (!VoxelDensityAblation::IsTunnelCoreOff()
&& !bHavePreDisturbanceTunnelCore && ActiveTunnelCoreCache->bValid)
{
const FTunnelSupportFloorColumn* SupportColumn = nullptr;
FTunnelSupportFloorColumn EmptySupportColumn;
@@ -2891,7 +2897,8 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co
// bridge over a graph tunnel. Reassert the native cached tunnel core here, after every
// solid floor writer but before the global XY seal. This cache is built once per chunk,
// never once per voxel.
if (ActiveTunnelCoreCache->bValid || CP_UseOpStack)
if (!VoxelDensityAblation::IsTunnelCoreOff()
&& (ActiveTunnelCoreCache->bValid || CP_UseOpStack))
{
FTunnelCoreWorldEvaluation TunnelCore;
bool bHaveTunnelCore = bHavePreDisturbanceTunnelCore;
@@ -3181,7 +3188,7 @@ float UVoxelGenerator::GetDensityWithParams(float WorldX, float WorldY, float Wo
float WarpedY = WorldY;
float WarpedZ = EffectiveZ;
if (Params.CaveWarpStrength > 0.0f)
if (!VoxelDensityAblation::IsCaveWarpOff() && Params.CaveWarpStrength > 0.0f)
{
const float WF = Params.CaveWarpFrequency;
const float WS = Params.CaveWarpStrength;
@@ -3472,74 +3479,77 @@ float UVoxelGenerator::GetDensityWithParams(float WorldX, float WorldY, float Wo
// transition as tunnel-to-room (no hard seam at PitTopZ)
// - bNearCaveSurface becomes true inside the shaft — roughness clamp
// prevents fill-back, so this is safe
auto EvaluatePit = [&](int32 PitIndex)
if (!VoxelDensityAblation::IsPitChimneySDFOff())
{
const FCachedPit& Pit = SDFCache.Pits[PitIndex];
const float DZ = WorldZ - Pit.TopZ; // Negative = below anchor (shaft)
if (DZ >= Pit.BlendK) return; // Above even the blend fringe
if (-DZ > Pit.Depth + Pit.BlendK) return;
const float DX = WorldX - Pit.CenterX;
const float DY = WorldY - Pit.CenterY;
const float XYDistSq = DX * DX + DY * DY;
if (XYDistSq > Pit.BoundXYRadiusSq) return;
float PitSDF;
if (DZ <= 0.0f)
auto EvaluatePit = [&](int32 PitIndex)
{
// Shaft: tapered cylinder with flared opening
float DepthBelow = -DZ;
float FlareFactor = FMath::Clamp(1.0f - DepthBelow / Pit.FlareDist, 0.0f, 1.0f);
FlareFactor = FlareFactor * FlareFactor;
float EffRadius = Pit.Radius + Pit.FlareExtra * FlareFactor;
PitSDF = FMath::Sqrt(XYDistSq) - EffRadius;
}
else
const FCachedPit& Pit = SDFCache.Pits[PitIndex];
const float DZ = WorldZ - Pit.TopZ; // Negative = below anchor (shaft)
if (DZ >= Pit.BlendK) return; // Above even the blend fringe
if (-DZ > Pit.Depth + Pit.BlendK) return;
const float DX = WorldX - Pit.CenterX;
const float DY = WorldY - Pit.CenterY;
const float XYDistSq = DX * DX + DY * DY;
if (XYDistSq > Pit.BoundXYRadiusSq) return;
float PitSDF;
if (DZ <= 0.0f)
{
// Shaft: tapered cylinder with flared opening
float DepthBelow = -DZ;
float FlareFactor = FMath::Clamp(1.0f - DepthBelow / Pit.FlareDist, 0.0f, 1.0f);
FlareFactor = FlareFactor * FlareFactor;
float EffRadius = Pit.Radius + Pit.FlareExtra * FlareFactor;
PitSDF = FMath::Sqrt(XYDistSq) - EffRadius;
}
else
{
// Above anchor: only the blend fringe matters here.
// We still pass a SDF so SmoothMin can soften the rim from above.
// Treat this zone as a flat disc at TopZ (XY cylinder, no Z factor)
// so the blend fades horizontally into the room floor.
PitSDF = FMath::Sqrt(XYDistSq) - (Pit.Radius + Pit.FlareExtra);
}
CaveSDF = VoxelSDF::SmoothMin(CaveSDF, PitSDF, Pit.BlendK);
};
VF_ForEachChunkSDFSpatialCandidate(
SDFCache.PitSpatialIndex, SDFCache.Pits.Num(), WorldX, WorldY, EvaluatePit,
bUseSpatialIndex);
auto EvaluateChimney = [&](int32 ChimneyIndex)
{
// Above anchor: only the blend fringe matters here.
// We still pass a SDF so SmoothMin can soften the rim from above.
// Treat this zone as a flat disc at TopZ (XY cylinder, no Z factor)
// so the blend fades horizontally into the room floor.
PitSDF = FMath::Sqrt(XYDistSq) - (Pit.Radius + Pit.FlareExtra);
}
const FCachedChimney& Chim = SDFCache.Chimneys[ChimneyIndex];
const float DZ = WorldZ - Chim.BottomZ; // Positive = above anchor (shaft)
if (-DZ >= Chim.BlendK) return; // Below even the blend fringe
if (DZ > Chim.Height + Chim.BlendK) return;
CaveSDF = VoxelSDF::SmoothMin(CaveSDF, PitSDF, Pit.BlendK);
};
VF_ForEachChunkSDFSpatialCandidate(
SDFCache.PitSpatialIndex, SDFCache.Pits.Num(), WorldX, WorldY, EvaluatePit,
bUseSpatialIndex);
const float DX = WorldX - Chim.CenterX;
const float DY = WorldY - Chim.CenterY;
const float XYDistSq = DX * DX + DY * DY;
if (XYDistSq > Chim.BoundXYRadiusSq) return;
auto EvaluateChimney = [&](int32 ChimneyIndex)
{
const FCachedChimney& Chim = SDFCache.Chimneys[ChimneyIndex];
const float DZ = WorldZ - Chim.BottomZ; // Positive = above anchor (shaft)
if (-DZ >= Chim.BlendK) return; // Below even the blend fringe
if (DZ > Chim.Height + Chim.BlendK) return;
float ChmSDF;
if (DZ >= 0.0f)
{
float FlareFactor = FMath::Clamp(1.0f - DZ / Chim.FlareDist, 0.0f, 1.0f);
FlareFactor = FlareFactor * FlareFactor;
float EffRadius = Chim.Radius + Chim.FlareExtra * FlareFactor;
ChmSDF = FMath::Sqrt(XYDistSq) - EffRadius;
}
else
{
// Below anchor: flat disc blend into room ceiling
ChmSDF = FMath::Sqrt(XYDistSq) - (Chim.Radius + Chim.FlareExtra);
}
const float DX = WorldX - Chim.CenterX;
const float DY = WorldY - Chim.CenterY;
const float XYDistSq = DX * DX + DY * DY;
if (XYDistSq > Chim.BoundXYRadiusSq) return;
float ChmSDF;
if (DZ >= 0.0f)
{
float FlareFactor = FMath::Clamp(1.0f - DZ / Chim.FlareDist, 0.0f, 1.0f);
FlareFactor = FlareFactor * FlareFactor;
float EffRadius = Chim.Radius + Chim.FlareExtra * FlareFactor;
ChmSDF = FMath::Sqrt(XYDistSq) - EffRadius;
}
else
{
// Below anchor: flat disc blend into room ceiling
ChmSDF = FMath::Sqrt(XYDistSq) - (Chim.Radius + Chim.FlareExtra);
}
CaveSDF = VoxelSDF::SmoothMin(CaveSDF, ChmSDF, Chim.BlendK);
};
VF_ForEachChunkSDFSpatialCandidate(
SDFCache.ChimneySpatialIndex, SDFCache.Chimneys.Num(),
WorldX, WorldY, EvaluateChimney, bUseSpatialIndex);
CaveSDF = VoxelSDF::SmoothMin(CaveSDF, ChmSDF, Chim.BlendK);
};
VF_ForEachChunkSDFSpatialCandidate(
SDFCache.ChimneySpatialIndex, SDFCache.Chimneys.Num(),
WorldX, WorldY, EvaluateChimney, bUseSpatialIndex);
}
// Convert SDF to density carving:
// CaveSDF < 0 means we're inside a room/tunnel/pit → carve to air
@@ -3563,6 +3573,7 @@ float UVoxelGenerator::GetDensityWithParams(float WorldX, float WorldY, float Wo
// post-disturbance tail can consume the result without allocating an FVoxelOpSample or
// invoking the interpreted stack a second time for this sample.
if (OutTunnelCore != nullptr
&& !VoxelDensityAblation::IsTunnelCoreOff()
&& Params.RoomDensity > 0.0f && Params.RoomSpacing > 0.0f)
{
// The no-column form is the exact scalar reference path: it performs the world-bound and
@@ -3585,10 +3596,11 @@ float UVoxelGenerator::GetDensityWithParams(float WorldX, float WorldY, float Wo
// seal / modifiers, so we jump to Step 5 instead of returning early.
const float DetailThreshold = Params.SDFBlendRadius * 3.0f;
const bool bNearCaveSurface = (CaveSDF < DetailThreshold) && (CaveSDF < FLT_MAX);
const bool bRunDetail = !VoxelDensityAblation::IsDetailOpsOff() && bNearCaveSurface;
if (bCollectFusedDiagnostics && VoxelDensityProfile::AreCountersEnabled())
{
VoxelDensityProfile::AddCounter(
bNearCaveSurface
bRunDetail
? VoxelDensityProfile::ECounter::FusedTunnelDetailSamples
: VoxelDensityProfile::ECounter::FusedTunnelDetailSkipped);
}
@@ -3606,7 +3618,7 @@ float UVoxelGenerator::GetDensityWithParams(float WorldX, float WorldY, float Wo
// The noise type (fBM, Ridged, Mixed) and optional domain warping are
// per-strate settings, so each strate can have fundamentally different
// wall character — smooth lava tubes vs craggy erosion cliffs.
if (bNearCaveSurface && Params.SurfaceRoughness > 0.0f)
if (bRunDetail && Params.SurfaceRoughness > 0.0f)
{
float RoughnessDepth = Params.SurfaceRoughness * 2.0f;
float DistFromSurface = FMath::Abs(CaveSDF);
@@ -3729,7 +3741,7 @@ float UVoxelGenerator::GetDensityWithParams(float WorldX, float WorldY, float Wo
//=========================================================================
// All terrain ops only affect density near cave walls.
// Deep-solid voxels skip this entire block (bNearCaveSurface = false).
if (bNearCaveSurface)
if (bRunDetail)
{
//=========================================================================
// PER-ROOM TERRAIN PARAMS
@@ -4276,10 +4288,7 @@ float UVoxelGenerator::GetDensityWithParams(float WorldX, float WorldY, float Wo
}
bool bWormBlockProofComputed = false;
const int32 WormLatticeStep = VoxelWormField::GetLatticeStep();
const bool bSkipWormBlock = VoxelWormField::GetNoiseMode() == 0
&& WormLatticeStep <= 1
&& GVoxelForgeWormBlockSkip != 0
const bool bSkipWormBlock = GVoxelForgeWormBlockSkip != 0
&& VF_TryGetWormBlockSkip(
DensityCacheOwnerId, SeedU, ParamsFingerprint, LayoutVersion,
WorldX, WorldY, WorldZ, Params, bWormBlockProofComputed);
@@ -4305,7 +4314,7 @@ float UVoxelGenerator::GetDensityWithParams(float WorldX, float WorldY, float Wo
SeedU};
const float WormValue = VoxelWormField::Evaluate(
WorldX, WorldY, WorldZ, Params.WormThreshold,
WormParameters, WormLatticeStep);
WormParameters);
if (WormValue < Params.WormThreshold)
{
@@ -4370,8 +4379,10 @@ float UVoxelGenerator::GetDensityWithParams(float WorldX, float WorldY, float Wo
// the graph's own tunnel floor/core are structural route geometry. Direct callers of
// GetDensityWithParams retain this historical tail; GetDensityAt skips it and applies the
// same predicates once in its common MC-space post section below both generation paths.
const bool bTunnelSupportFloor = VoxelCaveMorphology::IsTunnelSupportFloorWorldPoint(
WorldX, WorldY, WorldZ, SDFCache, bUseSpatialIndex);
const bool bTunnelCoreEnabled = !VoxelDensityAblation::IsTunnelCoreOff();
const bool bTunnelSupportFloor = bTunnelCoreEnabled
&& VoxelCaveMorphology::IsTunnelSupportFloorWorldPoint(
WorldX, WorldY, WorldZ, SDFCache, bUseSpatialIndex);
// Keep the direct legacy entry point capability-safe for callers outside the canonical
// operator-stack path; its support band follows the same relieved swept floor.
if (bTunnelSupportFloor)
@@ -4388,7 +4399,8 @@ float UVoxelGenerator::GetDensityWithParams(float WorldX, float WorldY, float Wo
}
Density = FMath::Max(Density, StructuralSolidDensity);
}
const float TunnelCoreSDF = (Params.RoomDensity > 0.0f && Params.RoomSpacing > 0.0f)
const float TunnelCoreSDF = bTunnelCoreEnabled
&& (Params.RoomDensity > 0.0f && Params.RoomSpacing > 0.0f)
? VoxelCaveMorphology::EvaluateTunnelCoreWorldSDF(
WorldX, WorldY, WorldZ, SDFCache, bUseSpatialIndex)
: FLT_MAX;
@@ -8,11 +8,11 @@
#include "VoxelTypes.h" // For CHUNK_SIZE, VOXEL_SIZE, WorldToChunkCoord
#include "VoxelCaveMorphology.h" // For VoxelSDF and VoxelHash
#include "VoxelDensityPrimitives.h" // Shared passage carve polarity/strength
#include "VoxelDensityAblation.h" // Fingerprinted development-only stage overrides
#include "VoxelDensityProfile.h" // Opt-in targeted per-voxel attribution
#include "VoxelStartupTrace.h"
#include "VoxelTerrainOpDefinition.h" // For UVoxelTerrainOpDefinition::ApplyTo
#include "VoxelBiomeDefinition.h" // For UVoxelBiomeDefinition (biome context flatten)
#include "VoxelWormField.h" // World-changing worm evaluator mode/lattice identity
#include "HAL/IConsoleManager.h"
#include "Misc/CommandLine.h"
#include "Misc/Crc.h"
@@ -2487,6 +2487,11 @@ void UVoxelStrateManager::GeneratePassages()
float UVoxelStrateManager::EvaluateModifierSDF(float WorldX, float WorldY, float WorldZ) const
{
if (VoxelDensityAblation::IsPassageCarvingOff())
{
return FLT_MAX;
}
const FIntVector ChunkCoord(
FMath::FloorToInt(WorldX / (float)CHUNK_SIZE),
FMath::FloorToInt(WorldY / (float)CHUNK_SIZE),
@@ -2552,7 +2557,7 @@ float UVoxelStrateManager::EvaluateModifierSDF(float WorldX, float WorldY, float
// projecting once here selects the nearest immutable floor segment for this sample.
// Unknown/malformed descriptors deliberately retain the bare capsule and the legacy
// diagnostic backstop remains available.
if (P.bNativeFloorEnabled)
if (P.bNativeFloorEnabled && !VoxelDensityAblation::IsNativeFloorOff())
{
float NativeFloorZ = 0.0f;
float NativeSupportRadius = 0.0f;
@@ -2593,8 +2598,11 @@ void UVoxelStrateManager::ApplyPassageModifier(
{
VoxelDensityProfile::FScopedTimer ProfileTimer(
VoxelDensityProfile::EBucket::PassageModifier);
const float ModSDF = EvaluateModifierSDF(WorldX, WorldY, WorldZ);
VF_ApplyPassageCarving(Density, ModSDF, BaseDensity, SealThickness);
if (!VoxelDensityAblation::IsPassageCarvingOff())
{
const float ModSDF = EvaluateModifierSDF(WorldX, WorldY, WorldZ);
VF_ApplyPassageCarving(Density, ModSDF, BaseDensity, SealThickness);
}
ApplyPassageLandingAir(Density, WorldX, WorldY, WorldZ, BaseDensity, SealThickness);
const FIntVector ChunkCoord(
@@ -2607,15 +2615,15 @@ void UVoxelStrateManager::ApplyPassageModifier(
for (const int32 PassageIndex : Nearby)
{
const FVoxelPassage& Passage = PassageData[PassageIndex];
if (!VoxelPassageGeometry::VerticalShaftConnectorAirMarker()
const bool bLandingFloor = !VoxelDensityAblation::IsLandingPostsOff()
&& (VF_IsPassageLandingFloor(Position, Passage.UpperLanding)
|| VF_IsPassageLandingFloor(Position, Passage.LowerLanding)
// A valid native profile owns its D-floor in the shape and in the final MC
// composition. Keep the old support slab only as a fail-closed fallback for a
// malformed/non-native descriptor; it must not patch a floor the shape already owns.
|| (bPassageSupportFloorWritesEnabled
&& !Passage.bNativeFloorEnabled
&& VF_IsWalkableTunnelFloor(Passage, Position, this, PassageIndex))))
|| VF_IsPassageLandingFloor(Position, Passage.LowerLanding));
const bool bLegacySupportFloor = !VoxelDensityAblation::IsPassageStructuralPostsOff()
&& bPassageSupportFloorWritesEnabled
&& !Passage.bNativeFloorEnabled
&& VF_IsWalkableTunnelFloor(Passage, Position, this, PassageIndex);
if (!VoxelPassageGeometry::VerticalShaftConnectorAirMarker()
&& (bLandingFloor || bLegacySupportFloor))
{
// This is the one bidirectional part of PassageCarveOp: a floor is a proved solid
// support slab. It is deliberately applied after the air carve so a tube can never
@@ -2636,6 +2644,10 @@ void UVoxelStrateManager::ApplyPassageCarvingOnly(
float& Density, float WorldX, float WorldY, float WorldZ,
float BaseDensity, float SealThickness) const
{
if (VoxelDensityAblation::IsPassageCarvingOff())
{
return;
}
VoxelDensityProfile::FScopedTimer ProfileTimer(
VoxelDensityProfile::EBucket::PassageModifier);
const float ModSDF = EvaluateModifierSDF(WorldX, WorldY, WorldZ);
@@ -2646,6 +2658,10 @@ void UVoxelStrateManager::ApplyPassageLandingAir(
float& Density, float WorldX, float WorldY, float WorldZ,
float BaseDensity, float SealThickness) const
{
if (VoxelDensityAblation::IsLandingPostsOff())
{
return;
}
VoxelDensityProfile::FScopedTimer ProfileTimer(
VoxelDensityProfile::EBucket::PassageLandingAir);
const FIntVector ChunkCoord(
@@ -2710,6 +2726,10 @@ void UVoxelStrateManager::ApplyPassageTunnelAir(
float& Density, float WorldX, float WorldY, float WorldZ,
float BaseDensity, float SealThickness) const
{
if (VoxelDensityAblation::IsPassageStructuralPostsOff())
{
return;
}
VoxelDensityProfile::FScopedTimer ProfileTimer(
VoxelDensityProfile::EBucket::PassageTunnelAir);
const FVector Position(WorldX, WorldY, WorldZ);
@@ -2740,6 +2760,11 @@ void UVoxelStrateManager::ApplyPassageTunnelAirMC(
void UVoxelStrateManager::ApplyPassageLandingFloorMC(
float& Density, float WorldX, float WorldY, float WorldZ, float BaseDensity) const
{
if (VoxelDensityAblation::IsLandingPostsOff()
&& VoxelDensityAblation::IsPassageStructuralPostsOff())
{
return;
}
VoxelDensityProfile::FScopedTimer ProfileTimer(
VoxelDensityProfile::EBucket::PassageLandingFloor);
const FIntVector ChunkCoord(
@@ -2752,12 +2777,15 @@ void UVoxelStrateManager::ApplyPassageLandingFloorMC(
for (const int32 PassageIndex : Nearby)
{
const FVoxelPassage& Passage = PassageData[PassageIndex];
if (!VoxelPassageGeometry::VerticalShaftConnectorAirMarker()
const bool bLandingFloor = !VoxelDensityAblation::IsLandingPostsOff()
&& (VF_IsPassageLandingFloor(Position, Passage.UpperLanding)
|| VF_IsPassageLandingFloor(Position, Passage.LowerLanding)
|| (bPassageSupportFloorWritesEnabled
&& !Passage.bNativeFloorEnabled
&& VF_IsWalkableTunnelFloor(Passage, Position, this, PassageIndex))))
|| VF_IsPassageLandingFloor(Position, Passage.LowerLanding));
const bool bLegacySupportFloor = !VoxelDensityAblation::IsPassageStructuralPostsOff()
&& bPassageSupportFloorWritesEnabled
&& !Passage.bNativeFloorEnabled
&& VF_IsWalkableTunnelFloor(Passage, Position, this, PassageIndex);
if (!VoxelPassageGeometry::VerticalShaftConnectorAirMarker()
&& (bLandingFloor || bLegacySupportFloor))
{
// Result is in MC convention here (negative = solid). This reassertion is the
// structural floor backstop after the optional MC-space disturbance layer.
@@ -2790,6 +2818,13 @@ void UVoxelStrateManager::ApplyPassageStructuralPostsMC(
float BaseDensity, float SealThickness,
bool bProtectAuthoredTunnelFloor) const
{
const bool bDisableStructuralPosts = VoxelDensityAblation::IsPassageStructuralPostsOff();
const bool bDisableLandingPosts = VoxelDensityAblation::IsLandingPostsOff();
if (bDisableStructuralPosts && bDisableLandingPosts)
{
return;
}
VoxelDensityProfile::FScopedTimer ProfileTimer(
VoxelDensityProfile::EBucket::PassageStructuralPosts);
@@ -2804,7 +2839,7 @@ void UVoxelStrateManager::ApplyPassageStructuralPostsMC(
const FVector Position(WorldX, WorldY, WorldZ);
const bool bSuppressFloor =
VoxelPassageGeometry::VerticalShaftConnectorAirMarker();
bool bAnyPassageFloor = false;
bool bAnyLandingFloor = false;
bool bLegacyTunnelSupportFloor = false;
bool bAnyRoomFloor = false;
bool bWalkableAir = false;
@@ -2819,36 +2854,37 @@ void UVoxelStrateManager::ApplyPassageStructuralPostsMC(
const FVoxelPassage& Passage = PassageData[PassageIndex];
const bool bTunnelSupportFloor =
bPassageSupportFloorWritesEnabled
!bDisableStructuralPosts
&& bPassageSupportFloorWritesEnabled
&& !Passage.bNativeFloorEnabled
&& VF_IsWalkableTunnelFloor(Passage, Position, this, PassageIndex);
if (!bSuppressFloor
if (!bSuppressFloor && !bDisableLandingPosts
&& (VF_IsPassageLandingFloor(Position, Passage.UpperLanding)
|| VF_IsPassageLandingFloor(Position, Passage.LowerLanding)
|| bTunnelSupportFloor))
|| VF_IsPassageLandingFloor(Position, Passage.LowerLanding)))
{
bAnyPassageFloor = true;
bAnyLandingFloor = true;
}
if (!bSuppressFloor && bTunnelSupportFloor)
if (!bSuppressFloor && !bDisableStructuralPosts && bTunnelSupportFloor)
{
bLegacyTunnelSupportFloor = true;
}
if (!bSuppressFloor
if (!bSuppressFloor && !bDisableLandingPosts
&& (VF_IsPassageRoomFloor(Position, Passage.UpperLanding)
|| VF_IsPassageRoomFloor(Position, Passage.LowerLanding)))
{
bAnyRoomFloor = true;
}
if (!bWalkableAir
if (!bDisableStructuralPosts && !bWalkableAir
&& FVector::DistSquared(Position, Passage.BoundCenter) <= Passage.BoundRadiusSq
&& VF_IsWalkableTunnelAir(Passage, Position, this, PassageIndex))
{
bWalkableAir = true;
}
if (FVector::DistSquared(Position, Passage.BoundCenter) <= Passage.BoundRadiusSq)
if (!bDisableLandingPosts
&& FVector::DistSquared(Position, Passage.BoundCenter) <= Passage.BoundRadiusSq)
{
const FVoxelPassageLanding* Landings[] = {
&Passage.UpperLanding, &Passage.LowerLanding };
@@ -2865,8 +2901,8 @@ void UVoxelStrateManager::ApplyPassageStructuralPostsMC(
// budget. A neighbouring inter-strate landing must not reopen that finite support band; its
// carve remains valid everywhere else. Legacy callers leave this false and retain the old
// passage-owned behavior.
if (!bProtectAuthoredTunnelFloor
&& !bAnyPassageFloor && MinLandingSDF < FLT_MAX)
if (!bDisableLandingPosts && !bProtectAuthoredTunnelFloor
&& !bAnyLandingFloor && !bLegacyTunnelSupportFloor && MinLandingSDF < FLT_MAX)
{
float InternalDensity = -Density;
VF_ApplyPassageLandingCarving(
@@ -2874,7 +2910,7 @@ void UVoxelStrateManager::ApplyPassageStructuralPostsMC(
Density = -InternalDensity;
}
if (bAnyPassageFloor)
if (bAnyLandingFloor || bLegacyTunnelSupportFloor)
{
Density = FMath::Min(Density, -BaseDensity);
}
@@ -2901,6 +2937,10 @@ void UVoxelStrateManager::ApplyPassageNativeFloorMC(
float& Density, float WorldX, float WorldY, float WorldZ,
float BaseDensity) const
{
if (VoxelDensityAblation::IsNativeFloorOff())
{
return;
}
const FIntVector ChunkCoord(
FMath::FloorToInt(WorldX / (float)CHUNK_SIZE),
FMath::FloorToInt(WorldY / (float)CHUNK_SIZE),
@@ -2955,6 +2995,10 @@ void UVoxelStrateManager::ApplyPassageLandingRoomFloorMC(
float& Density, float WorldX, float WorldY, float WorldZ,
float BaseDensity) const
{
if (VoxelDensityAblation::IsLandingPostsOff())
{
return;
}
VoxelDensityProfile::FScopedTimer ProfileTimer(
VoxelDensityProfile::EBucket::PassageLandingRoomFloor);
const FIntVector ChunkCoord(
@@ -2979,6 +3023,10 @@ void UVoxelStrateManager::ApplyPassageLandingRoomFloorMC(
bool UVoxelStrateManager::AnyPassageNearBox(const FVector& MinVoxel, const FVector& MaxVoxel) const
{
if (VoxelDensityAblation::IsPassageCarvingOff())
{
return false;
}
// Le carve d'un passage atteint ModSDF < PASSAGE_BLEND_RADIUS (4, VoxelGenerator.cpp) au-delà de
// sa surface ; BoundRadius inclut déjà rayon + blend, on re-pad par sécurité (conservatif).
constexpr float CarvePad = 4.0f;
@@ -5530,7 +5578,12 @@ FStrateGenerationParams UVoxelStrateManager::GetGenerationParams(const FIntVecto
{
Result.WormStrength = 0.0f;
}
return VF_ApplyRuntimeRoughnessOverrides(Result);
Result = VF_ApplyRuntimeRoughnessOverrides(Result);
if (VoxelDensityAblation::IsCaveWarpOff())
{
Result.CaveWarpStrength = 0.0f;
}
return Result;
};
#if WITH_EDITOR
@@ -5885,11 +5938,9 @@ uint64 UVoxelStrateManager::GetGenerationParamsFingerprint() const
HashValue(Slot.Definition->bUseOperatorStack);
HashValue(Slot.Definition->bEnableWorms);
HashValue(VF_WormsForceOff());
// The selected evaluator is a world input even though it is deliberately not part of
// FStrateGenerationParams (the fixed 83-field memo key). Keep persistent/verdict keys
// from crossing an explicit WormNoiseMode/WormLatticeStep experiment.
HashValue(VoxelWormField::GetNoiseMode());
HashValue(VoxelWormField::GetLatticeStep());
// Development-only stage ablations change the field and therefore belong in every
// persistent/verdict key even though they are not authored generation parameters.
HashValue(VoxelDensityAblation::GetResolvedMask());
HashValue(Slot.Definition->StrateHeightInChunks);
HashValue(Slot.Definition->TransitionType);
HashValue(Slot.Definition->TransitionBlendChunks);
+9 -382
View File
@@ -1,98 +1,14 @@
// VoxelWormField.cpp
// Shared deterministic worm-field evaluation and the measured lattice experiment.
// Shared deterministic exact worm-field evaluation.
#include "VoxelWormField.h"
#include "VoxelCaveMorphology.h" // VoxelHash::SeedOffset
#include "VoxelGenerator.h" // per-worker canonical tile window
#include "VoxelNoise.h"
#include "VoxelTypes.h" // VOXEL_NOISE_SCALE
#include "HAL/IConsoleManager.h"
#include "Misc/CommandLine.h"
#include "Misc/Parse.h"
#include <atomic>
#include <cfloat>
#include <cstring>
namespace
{
// The selected production mode is resolved once, before a generation stack is allowed to
// observe it, so all workers in a process use one world definition. Mode 0 remains the exact
// legacy control for measurements and compatibility investigations.
int32 GVoxelForgeWormLatticeStep = 0;
std::atomic<int32> GResolvedWormLatticeStep(-1);
int32 GVoxelForgeWormNoiseMode = 1;
std::atomic<int32> GResolvedWormNoiseMode(-1);
// The value field has the same threshold/strength contract as legacy Perlin. This small
// amplitude retune keeps the authored TunnelNetwork player-fit margin after replacing the
// gradient evaluation with the cheaper scalar hash. It is a code default, not a CVar, so a
// client cannot silently choose a different world.
constexpr float CheapWormNoiseAmplitude = 1.0f;
FAutoConsoleVariableRef CVarVoxelForgeWormLatticeStep(
TEXT("voxel.WormLatticeStep"),
GVoxelForgeWormLatticeStep,
TEXT("WORLD-CHANGING development experiment. 0=exact, 2/4=coarse worm lattice with "
"trilinear interpolation. All multiplayer peers and regenerated worlds must use "
"the same value. Default 0 until measured."),
ECVF_Default);
FAutoConsoleVariableRef CVarVoxelForgeWormNoiseMode(
TEXT("voxel.WormNoiseMode"),
GVoxelForgeWormNoiseMode,
TEXT("WORLD-CHANGING development experiment. 0=legacy gradient Perlin, 1=cheap "
"deterministic value-noise worm fields. All multiplayer peers and regenerated "
"worlds must use the same value. Default 1 (the measured production mode)."),
ECVF_Default);
int32 ResolveLatticeStep()
{
int32 Existing = GResolvedWormLatticeStep.load(std::memory_order_acquire);
if (Existing >= 0)
{
return Existing;
}
int32 CommandLineValue = GVoxelForgeWormLatticeStep;
FParse::Value(
FCommandLine::Get(), TEXT("voxel.WormLatticeStep="), CommandLineValue);
const int32 Resolved = CommandLineValue == 2 || CommandLineValue == 4
? CommandLineValue : 0;
int32 Expected = -1;
if (!GResolvedWormLatticeStep.compare_exchange_strong(
Expected, Resolved, std::memory_order_release, std::memory_order_acquire))
{
return Expected;
}
GVoxelForgeWormLatticeStep = Resolved;
return Resolved;
}
int32 ResolveNoiseMode()
{
int32 Existing = GResolvedWormNoiseMode.load(std::memory_order_acquire);
if (Existing >= 0)
{
return Existing;
}
int32 CommandLineValue = GVoxelForgeWormNoiseMode;
FParse::Value(
FCommandLine::Get(), TEXT("voxel.WormNoiseMode="), CommandLineValue);
const int32 Resolved = CommandLineValue == 1 ? 1 : 0;
int32 Expected = -1;
if (!GResolvedWormNoiseMode.compare_exchange_strong(
Expected, Resolved, std::memory_order_release, std::memory_order_acquire))
{
return Expected;
}
GVoxelForgeWormNoiseMode = Resolved;
return Resolved;
}
FORCEINLINE FVector3f MakeWormNoiseBase(
float WorldX,
float WorldY,
@@ -117,16 +33,11 @@ namespace
float WorldY,
float WorldZ,
const VoxelWormField::FParameters& Parameters,
int32 NoiseMode,
float NoiseScale)
{
const FVector3f NoiseBase = MakeWormNoiseBase(
WorldX, WorldY, WorldZ, Parameters);
return FMath::Abs(
(NoiseMode == 0
? VoxelNoise::Perlin3D(NoiseBase)
: VoxelNoise::ValueNoise3D(NoiseBase, Parameters.Seed, 0x4D3A2F19u))
* NoiseScale);
return FMath::Abs(VoxelNoise::Perlin3D(NoiseBase) * NoiseScale);
}
FORCEINLINE float EvaluateN2Field(
@@ -134,17 +45,12 @@ namespace
float WorldY,
float WorldZ,
const VoxelWormField::FParameters& Parameters,
int32 NoiseMode,
float NoiseScale)
{
const FVector3f NoiseBase = MakeWormNoiseBase(
WorldX, WorldY, WorldZ, Parameters);
const FVector3f N2Position = NoiseBase + FVector3f(137.0f, 259.0f, 431.0f);
return FMath::Abs(
(NoiseMode == 0
? VoxelNoise::Perlin3D(N2Position)
: VoxelNoise::ValueNoise3D(N2Position, Parameters.Seed, 0xA7C15E2Du))
* NoiseScale);
return FMath::Abs(VoxelNoise::Perlin3D(N2Position) * NoiseScale);
}
FORCEINLINE float EvaluateExactField(
@@ -154,288 +60,22 @@ namespace
float Threshold,
const VoxelWormField::FParameters& Parameters)
{
const int32 NoiseMode = ResolveNoiseMode();
const float NoiseScale = VOXEL_NOISE_SCALE
* (NoiseMode == 1 ? CheapWormNoiseAmplitude : 1.0f);
const float NoiseScale = VOXEL_NOISE_SCALE;
const float N1 = EvaluateN1Field(
WorldX, WorldY, WorldZ, Parameters, NoiseMode, NoiseScale);
WorldX, WorldY, WorldZ, Parameters, NoiseScale);
// N2 is non-negative, so this preserves the old exact evaluator's short-circuit.
// N2 is non-negative, so this preserves the exact evaluator's short-circuit.
if (N1 >= Threshold)
{
return N1;
}
return N1 + EvaluateN2Field(
WorldX, WorldY, WorldZ, Parameters, NoiseMode, NoiseScale);
}
struct FLatticeNode
{
int32 X = 0;
int32 Y = 0;
int32 Z = 0;
float Value = 0.0f;
bool bValid = false;
};
// The shared density grid is traversed z/y/x. Retaining two adjacent lattice planes is
// necessary for the z transition; 2048 entries evicted the previous plane and made the
// experiment slower than exact evaluation. This is fixed storage, not a heap allocation, and
// remains deterministic on every worker.
constexpr uint32 LatticeCacheSize = 32768;
static_assert((LatticeCacheSize & (LatticeCacheSize - 1u)) == 0,
"The lattice cache size must remain a power of two for the mask lookup.");
constexpr int32 TileLatticeAxis = 32;
static_assert(static_cast<uint32>(TileLatticeAxis * TileLatticeAxis * TileLatticeAxis)
== LatticeCacheSize,
"The direct tile lattice must use the fixed cache storage exactly.");
struct FLatticeCache
{
uint32 Seed = 0;
uint32 FrequencyBits = 0;
uint32 HorizontalBiasBits = 0;
uint32 VerticalScaleBits = 0;
int32 Step = 0;
bool bUseTileWindow = false;
FIntVector TileBaseGrid = FIntVector::ZeroValue;
int32 TileSampleStep = 0;
int32 TileCellsPerAxis = 0;
bool bContextValid = false;
FLatticeNode Nodes[LatticeCacheSize];
void SetContext(
const VoxelWormField::FParameters& Parameters,
int32 InStep,
bool InUseTileWindow,
const FIntVector& InTileOrigin,
int32 InTileSampleStep,
int32 InTileCellsPerAxis)
{
uint32 FrequencyBitsIn = 0;
uint32 HorizontalBiasBitsIn = 0;
uint32 VerticalScaleBitsIn = 0;
FMemory::Memcpy(&FrequencyBitsIn, &Parameters.Frequency, sizeof(uint32));
FMemory::Memcpy(&HorizontalBiasBitsIn, &Parameters.HorizontalBias, sizeof(uint32));
FMemory::Memcpy(&VerticalScaleBitsIn, &Parameters.VerticalScale, sizeof(uint32));
const double WormStepD = static_cast<double>(InStep);
const FIntVector InTileBaseGrid(
FMath::FloorToInt(static_cast<double>(InTileOrigin.X) / WormStepD) - 2,
FMath::FloorToInt(static_cast<double>(InTileOrigin.Y) / WormStepD) - 2,
FMath::FloorToInt(static_cast<double>(InTileOrigin.Z) / WormStepD) - 2);
if (bContextValid
&& Seed == Parameters.Seed
&& FrequencyBits == FrequencyBitsIn
&& HorizontalBiasBits == HorizontalBiasBitsIn
&& VerticalScaleBits == VerticalScaleBitsIn
&& Step == InStep
&& bUseTileWindow == InUseTileWindow
&& TileBaseGrid == InTileBaseGrid
&& TileSampleStep == InTileSampleStep
&& TileCellsPerAxis == InTileCellsPerAxis)
{
return;
}
Seed = Parameters.Seed;
FrequencyBits = FrequencyBitsIn;
HorizontalBiasBits = HorizontalBiasBitsIn;
VerticalScaleBits = VerticalScaleBitsIn;
Step = InStep;
bUseTileWindow = InUseTileWindow;
TileBaseGrid = InTileBaseGrid;
TileSampleStep = InTileSampleStep;
TileCellsPerAxis = InTileCellsPerAxis;
bContextValid = true;
for (FLatticeNode& Node : Nodes)
{
Node.bValid = false;
}
}
static uint32 Hash(int32 X, int32 Y, int32 Z)
{
uint32 H = static_cast<uint32>(X) * 0x9E3779B1u;
H ^= static_cast<uint32>(Y) * 0x85EBCA77u;
H ^= static_cast<uint32>(Z) * 0xC2B2AE3Du;
H ^= H >> 16;
H *= 0x7FEB352Du;
H ^= H >> 15;
return H;
}
float Get(int32 X, int32 Y, int32 Z,
const VoxelWormField::FParameters& Parameters,
int32 NoiseMode,
float NoiseScale)
{
const auto EvaluateNode = [this, &Parameters, NoiseMode, NoiseScale](FLatticeNode& Node,
int32 NodeX, int32 NodeY,
int32 NodeZ) -> float
{
if (Node.bValid && Node.X == NodeX && Node.Y == NodeY && Node.Z == NodeZ)
{
return Node.Value;
}
Node.X = NodeX;
Node.Y = NodeY;
Node.Z = NodeZ;
const double StepD = static_cast<double>(Step);
const float WorldX = static_cast<float>(static_cast<double>(NodeX) * StepD);
const float WorldY = static_cast<float>(static_cast<double>(NodeY) * StepD);
const float WorldZ = static_cast<float>(static_cast<double>(NodeZ) * StepD);
// The lattice stores N1 only. The requested voxel uses this interpolated N1 for
// the threshold decision; N2 remains an exact point query only in the carve
// region. This removes the first noise call on the hot above-threshold majority
// without making the second noise unconditional.
Node.Value = EvaluateN1Field(
WorldX, WorldY, WorldZ, Parameters, NoiseMode, NoiseScale);
Node.bValid = true;
return Node.Value;
};
if (bUseTileWindow)
{
const int32 LocalX = X - TileBaseGrid.X;
const int32 LocalY = Y - TileBaseGrid.Y;
const int32 LocalZ = Z - TileBaseGrid.Z;
if (LocalX >= 0 && LocalX < TileLatticeAxis
&& LocalY >= 0 && LocalY < TileLatticeAxis
&& LocalZ >= 0 && LocalZ < TileLatticeAxis)
{
FLatticeNode& Node = Nodes[
(LocalZ * TileLatticeAxis + LocalY) * TileLatticeAxis + LocalX];
return EvaluateNode(Node, X, Y, Z);
}
// A malformed or unusually large tile window must not alter the field's finite
// fallback semantics. It is outside the fixed direct table, so evaluate N1 once
// for this sample rather than attempting an unbounded allocation.
const double StepD = static_cast<double>(Step);
return EvaluateN1Field(
static_cast<float>(static_cast<double>(X) * StepD),
static_cast<float>(static_cast<double>(Y) * StepD),
static_cast<float>(static_cast<double>(Z) * StepD),
Parameters, NoiseMode, NoiseScale);
}
const uint32 Start = Hash(X, Y, Z) & (LatticeCacheSize - 1u);
for (uint32 Probe = 0; Probe < LatticeCacheSize; ++Probe)
{
FLatticeNode& Node = Nodes[(Start + Probe) & (LatticeCacheSize - 1u)];
if (Node.bValid)
{
if (Node.X == X && Node.Y == Y && Node.Z == Z)
{
return Node.Value;
}
continue;
}
Node.X = X;
Node.Y = Y;
Node.Z = Z;
return EvaluateNode(Node, X, Y, Z);
}
// A saturated cache is exceptionally unlikely for one density block. Falling back to
// a direct N1 evaluation keeps the result bounded if a caller exceeds it.
const double StepD = static_cast<double>(Step);
return EvaluateN1Field(
static_cast<float>(static_cast<double>(X) * StepD),
static_cast<float>(static_cast<double>(Y) * StepD),
static_cast<float>(static_cast<double>(Z) * StepD),
Parameters, NoiseMode, NoiseScale);
}
};
thread_local FLatticeCache GLatticeCache;
FORCEINLINE bool CanUseLattice(float WorldX, float WorldY, float WorldZ, int32 Step)
{
if (Step <= 1 || !FMath::IsFinite(WorldX) || !FMath::IsFinite(WorldY)
|| !FMath::IsFinite(WorldZ))
{
return false;
}
// The integer grid indices must survive the conversion back to a finite float world
// coordinate. Normal VoxelForge worlds are far inside this envelope; pathological input
// takes the exact path rather than changing overflow behaviour.
constexpr double MaxGridCoordinate = 1000000000.0;
return FMath::Abs(static_cast<double>(WorldX) / static_cast<double>(Step))
<= MaxGridCoordinate
&& FMath::Abs(static_cast<double>(WorldY) / static_cast<double>(Step))
<= MaxGridCoordinate
&& FMath::Abs(static_cast<double>(WorldZ) / static_cast<double>(Step))
<= MaxGridCoordinate;
}
FORCEINLINE float Lerp(float A, float B, float Alpha)
{
return A + (B - A) * Alpha;
}
float EvaluateLattice(
float WorldX,
float WorldY,
float WorldZ,
const VoxelWormField::FParameters& Parameters,
int32 Step,
float NoiseScale)
{
const int32 NoiseMode = ResolveNoiseMode();
const bool bUseTileWindow = VoxelGenLOD::TileCellsPerAxis > 0
&& VoxelGenLOD::TileCellsPerAxis <= CHUNK_SIZE
&& VoxelGenLOD::SampleStep > 0;
GLatticeCache.SetContext(
Parameters,
Step,
bUseTileWindow,
VoxelGenLOD::TileOriginVoxels,
VoxelGenLOD::SampleStep,
VoxelGenLOD::TileCellsPerAxis);
const double StepD = static_cast<double>(Step);
const double GX = static_cast<double>(WorldX) / StepD;
const double GY = static_cast<double>(WorldY) / StepD;
const double GZ = static_cast<double>(WorldZ) / StepD;
const int32 X0 = FMath::FloorToInt(GX);
const int32 Y0 = FMath::FloorToInt(GY);
const int32 Z0 = FMath::FloorToInt(GZ);
const float Tx = static_cast<float>(GX - static_cast<double>(X0));
const float Ty = static_cast<float>(GY - static_cast<double>(Y0));
const float Tz = static_cast<float>(GZ - static_cast<double>(Z0));
const float V000 = GLatticeCache.Get(X0, Y0, Z0, Parameters, NoiseMode, NoiseScale);
const float V100 = GLatticeCache.Get(X0 + 1, Y0, Z0, Parameters, NoiseMode, NoiseScale);
const float V010 = GLatticeCache.Get(X0, Y0 + 1, Z0, Parameters, NoiseMode, NoiseScale);
const float V110 = GLatticeCache.Get(X0 + 1, Y0 + 1, Z0, Parameters, NoiseMode, NoiseScale);
const float V001 = GLatticeCache.Get(X0, Y0, Z0 + 1, Parameters, NoiseMode, NoiseScale);
const float V101 = GLatticeCache.Get(X0 + 1, Y0, Z0 + 1, Parameters, NoiseMode, NoiseScale);
const float V011 = GLatticeCache.Get(X0, Y0 + 1, Z0 + 1, Parameters, NoiseMode, NoiseScale);
const float V111 = GLatticeCache.Get(X0 + 1, Y0 + 1, Z0 + 1, Parameters, NoiseMode, NoiseScale);
const float X00 = Lerp(V000, V100, Tx);
const float X10 = Lerp(V010, V110, Tx);
const float X01 = Lerp(V001, V101, Tx);
const float X11 = Lerp(V011, V111, Tx);
return Lerp(Lerp(X00, X10, Ty), Lerp(X01, X11, Ty), Tz);
WorldX, WorldY, WorldZ, Parameters, NoiseScale);
}
}
namespace VoxelWormField
{
int32 GetLatticeStep()
{
return ResolveLatticeStep();
}
int32 GetNoiseMode()
{
return ResolveNoiseMode();
}
float EvaluateExact(
float WorldX,
float WorldY,
@@ -451,21 +91,8 @@ namespace VoxelWormField
float WorldY,
float WorldZ,
float Threshold,
const FParameters& Parameters,
int32 LatticeStep)
const FParameters& Parameters)
{
const int32 NoiseMode = ResolveNoiseMode();
const bool bUseLattice = CanUseLattice(WorldX, WorldY, WorldZ, LatticeStep);
const float NoiseScale = VOXEL_NOISE_SCALE
* (NoiseMode == 1 ? CheapWormNoiseAmplitude : 1.0f);
const float N1 = bUseLattice
? EvaluateLattice(WorldX, WorldY, WorldZ, Parameters, LatticeStep, NoiseScale)
: EvaluateN1Field(WorldX, WorldY, WorldZ, Parameters, NoiseMode, NoiseScale);
if (N1 >= Threshold)
{
return N1;
}
return N1 + EvaluateN2Field(
WorldX, WorldY, WorldZ, Parameters, NoiseMode, NoiseScale);
return EvaluateExactField(WorldX, WorldY, WorldZ, Threshold, Parameters);
}
}
@@ -0,0 +1,60 @@
// VoxelDensityAblation.h
// Development-only, world-changing measurement overrides for TunnelNetwork density stages.
#pragma once
#include "CoreMinimal.h"
#include <atomic>
namespace VoxelDensityAblation
{
/**
* A bit in this mask means that the named stage is forced off. These are measurement
* overrides, not gameplay settings: they change the generated field, are resolved once per
* process, and must match across peers and regenerated worlds.
*/
enum class EStage : uint32
{
CaveWarp = 1u << 0,
DetailOps = 1u << 1,
RoomSDF = 1u << 2,
TunnelSDF = 1u << 3,
TunnelCore = 1u << 4,
PassageCarving = 1u << 5,
PassageStructuralPosts= 1u << 6,
NativeFloor = 1u << 7,
Disturbances = 1u << 8,
OriginSpine = 1u << 9,
BoundarySeal = 1u << 10,
LandingPosts = 1u << 11,
XYEdgeSeal = 1u << 12,
PitChimneySDF = 1u << 13,
};
/** Resolve command-line/CVar values once and return the process-wide mask. */
VOXELFORGE_API uint32 GetResolvedMask();
FORCEINLINE bool IsForcedOff(EStage Stage)
{
return (GetResolvedMask() & static_cast<uint32>(Stage)) != 0;
}
FORCEINLINE bool IsCaveWarpOff() { return IsForcedOff(EStage::CaveWarp); }
FORCEINLINE bool IsDetailOpsOff() { return IsForcedOff(EStage::DetailOps); }
FORCEINLINE bool IsRoomSDFOff() { return IsForcedOff(EStage::RoomSDF); }
FORCEINLINE bool IsTunnelSDFOff() { return IsForcedOff(EStage::TunnelSDF); }
FORCEINLINE bool IsTunnelCoreOff() { return IsForcedOff(EStage::TunnelCore); }
FORCEINLINE bool IsPassageCarvingOff() { return IsForcedOff(EStage::PassageCarving); }
FORCEINLINE bool IsPassageStructuralPostsOff()
{
return IsForcedOff(EStage::PassageStructuralPosts);
}
FORCEINLINE bool IsNativeFloorOff() { return IsForcedOff(EStage::NativeFloor); }
FORCEINLINE bool IsDisturbancesOff() { return IsForcedOff(EStage::Disturbances); }
FORCEINLINE bool IsOriginSpineOff() { return IsForcedOff(EStage::OriginSpine); }
FORCEINLINE bool IsBoundarySealOff() { return IsForcedOff(EStage::BoundarySeal); }
FORCEINLINE bool IsLandingPostsOff() { return IsForcedOff(EStage::LandingPosts); }
FORCEINLINE bool IsXYEdgeSealOff() { return IsForcedOff(EStage::XYEdgeSeal); }
FORCEINLINE bool IsPitChimneySDFOff() { return IsForcedOff(EStage::PitChimneySDF); }
}
@@ -27,6 +27,7 @@
#include "CoreMinimal.h"
#include "VoxelTypes.h" // SmoothStep01
#include "VoxelPassageGeometry.h" // shared body-sized landing/tunnel geometry
#include "VoxelDensityAblation.h" // development-only world-changing measurements
//=============================================================================
// SEAL DE FRONTIÈRE / BOUNDARY SEAL
@@ -43,7 +44,7 @@ FORCEINLINE void VF_ApplyBoundarySeal(float& Density, float WorldZ,
float StrateTopZ, float StrateBottomZ,
float Thickness, float BaseDensity)
{
if (Thickness <= 0.0f) return;
if (VoxelDensityAblation::IsBoundarySealOff() || Thickness <= 0.0f) return;
const float DistTop = StrateTopZ - WorldZ; // + si on est sous le plafond
const float DistBot = WorldZ - StrateBottomZ; // + si on est au-dessus du sol
@@ -71,6 +72,7 @@ FORCEINLINE void VF_ApplyBoundarySeal(float& Density, float WorldZ,
FORCEINLINE void VF_ApplyPassageCarving(float& Density, float ModSDF,
float BaseDensity, float SealThickness)
{
if (VoxelDensityAblation::IsPassageCarvingOff()) return;
constexpr float PASSAGE_BLEND_RADIUS = 4.0f;
if (ModSDF >= PASSAGE_BLEND_RADIUS) return;
@@ -121,7 +123,7 @@ FORCEINLINE void VF_ApplyPassageLandingCarving(float& Density, float LandingSDF,
FORCEINLINE void VF_ApplyOriginSpine(float& Density, float WorldX, float WorldY, float WorldZ,
float StrateTopZ, float StrateBottomZ, float SealThickness, float BaseDensity, float Radius)
{
if (Radius <= 0.0f) return;
if (VoxelDensityAblation::IsOriginSpineOff() || Radius <= 0.0f) return;
const VoxelPassageGeometry::FOriginLandingGeometry Geometry =
VoxelPassageGeometry::BuildOriginLandingGeometry(
@@ -153,7 +155,7 @@ FORCEINLINE void VF_ApplyOriginLandingAir(float& Density,
float WorldX, float WorldY, float WorldZ,
float StrateTopZ, float StrateBottomZ, float SealThickness, float BaseDensity, float Radius)
{
if (Radius <= 0.0f) return;
if (VoxelDensityAblation::IsLandingPostsOff() || Radius <= 0.0f) return;
const VoxelPassageGeometry::FOriginLandingGeometry Geometry =
VoxelPassageGeometry::BuildOriginLandingGeometry(
StrateTopZ, StrateBottomZ, SealThickness, Radius);
@@ -169,7 +171,7 @@ FORCEINLINE void VF_ApplyOriginLandingFloor(float& Density,
float WorldX, float WorldY, float WorldZ,
float StrateTopZ, float StrateBottomZ, float SealThickness, float BaseDensity, float Radius)
{
if (Radius <= 0.0f) return;
if (VoxelDensityAblation::IsLandingPostsOff() || Radius <= 0.0f) return;
const VoxelPassageGeometry::FOriginLandingGeometry Geometry =
VoxelPassageGeometry::BuildOriginLandingGeometry(
StrateTopZ, StrateBottomZ, SealThickness, Radius);
@@ -221,7 +223,8 @@ FORCEINLINE void VF_ApplyOriginLandingFloorMC(float& Density,
FORCEINLINE void VF_ApplyXYEdgeSeal(float& Density, float WorldX, float WorldY,
float WorldRadiusVoxels, float Thickness, float BaseDensity)
{
if (!(WorldRadiusVoxels > 0.0f) || !(Thickness > 0.0f) || !(BaseDensity > 0.0f)) return;
if (VoxelDensityAblation::IsXYEdgeSealOff()
|| !(WorldRadiusVoxels > 0.0f) || !(Thickness > 0.0f) || !(BaseDensity > 0.0f)) return;
if (!VoxelMath::IsFinite(WorldX) || !VoxelMath::IsFinite(WorldY)
|| !VoxelMath::IsFinite(WorldRadiusVoxels) || !VoxelMath::IsFinite(Thickness)
|| !VoxelMath::IsFinite(BaseDensity)) return;
@@ -256,7 +259,8 @@ FORCEINLINE void VF_ApplyXYEdgeSeal(float& Density, float WorldX, float WorldY,
FORCEINLINE void VF_ApplyXYEdgeSealMC(float& Density, float WorldX, float WorldY,
float WorldRadiusVoxels, float Thickness, float BaseDensity)
{
if (!(WorldRadiusVoxels > 0.0f) || !(Thickness > 0.0f)) return;
if (VoxelDensityAblation::IsXYEdgeSealOff()
|| !(WorldRadiusVoxels > 0.0f) || !(Thickness > 0.0f)) return;
float InternalDensity = -Density;
VF_ApplyXYEdgeSeal(InternalDensity, WorldX, WorldY,
-50
View File
@@ -142,56 +142,6 @@ FORCEINLINE float Perlin3D(const FVector3f& P)
return Perlin3D(P.X, P.Y, P.Z);
}
// A smooth, deterministic value-noise sample for the worm-only cost experiment. It keeps the
// same unit lattice and quintic interpolation as Perlin3D, but uses one seeded scalar hash per
// corner instead of the eight gradient selections and dot products. It is intentionally not used
// by the terrain's other noise fields; WormNoiseMode gates it as an explicit world-changing test.
FORCEINLINE uint32 ValueNoiseHash(int32 X, int32 Y, int32 Z, uint32 Seed, uint32 Salt)
{
uint32 H = Detail::HashCorner(X, Y, Z) ^ Seed ^ Salt;
H ^= H >> 16;
H *= 0x7FEB352Du;
H ^= H >> 15;
return H;
}
FORCEINLINE float ValueNoise3D(const FVector3f& P, uint32 Seed, uint32 Salt)
{
const float XFloor = FMath::FloorToFloat(P.X);
const float YFloor = FMath::FloorToFloat(P.Y);
const float ZFloor = FMath::FloorToFloat(P.Z);
const int32 X = static_cast<int32>(XFloor);
const int32 Y = static_cast<int32>(YFloor);
const int32 Z = static_cast<int32>(ZFloor);
const float Fx = P.X - XFloor;
const float Fy = P.Y - YFloor;
const float Fz = P.Z - ZFloor;
const float Sx = Detail::Fade(Fx);
const float Sy = Detail::Fade(Fy);
const float Sz = Detail::Fade(Fz);
constexpr float HashToSigned = 2.0f / 16777216.0f;
const auto Sample = [Seed, Salt, HashToSigned](int32 IX, int32 IY, int32 IZ) -> float
{
return static_cast<float>(ValueNoiseHash(IX, IY, IZ, Seed, Salt) & 0x00FFFFFFu)
* HashToSigned - 1.0f;
};
const float V000 = Sample(X, Y, Z);
const float V100 = Sample(X + 1, Y, Z);
const float V010 = Sample(X, Y + 1, Z);
const float V110 = Sample(X + 1, Y + 1, Z);
const float V001 = Sample(X, Y, Z + 1);
const float V101 = Sample(X + 1, Y, Z + 1);
const float V011 = Sample(X, Y + 1, Z + 1);
const float V111 = Sample(X + 1, Y + 1, Z + 1);
const float X00 = Detail::Lerp(V000, V100, Sx);
const float X10 = Detail::Lerp(V010, V110, Sx);
const float X01 = Detail::Lerp(V001, V101, Sx);
const float X11 = Detail::Lerp(V011, V111, Sx);
const float Y0 = Detail::Lerp(X00, X10, Sy);
const float Y1 = Detail::Lerp(X01, X11, Sy);
return Detail::Lerp(Y0, Y1, Sz);
}
//=============================================================================
// LIPSCHITZ BOUND FOR THE ACTUAL 3D FIELD
//=============================================================================
+2 -13
View File
@@ -16,16 +16,6 @@ namespace VoxelWormField
uint32 Seed = 0;
};
/**
* Return the selected development experiment mode.
*
* 0 is the exact field. 2 and 4 evaluate the exact field on that many-voxel lattice and
* trilinearly interpolate it. This is intentionally a WORLD-CHANGING development switch:
* it must not be changed between multiplayer peers or for an already generated world.
*/
VOXELFORGE_API int32 GetLatticeStep();
VOXELFORGE_API int32 GetNoiseMode();
/** Evaluate the exact two-noise field, retaining the N1 threshold short-circuit. */
VOXELFORGE_API float EvaluateExact(
float WorldX,
@@ -34,12 +24,11 @@ namespace VoxelWormField
float Threshold,
const FParameters& Parameters);
/** Evaluate the exact field or its selected cached lattice approximation. */
/** Evaluate the exact two-noise field. */
VOXELFORGE_API float Evaluate(
float WorldX,
float WorldY,
float WorldZ,
float Threshold,
const FParameters& Parameters,
int32 LatticeStep);
const FParameters& Parameters);
}