Files
VoxelForge/Source/VoxelForge/Public/VoxelDensityPrimitives.h
T
Claude 26984d38d8 docs(comments): drop history narration, keep the why
Rewrite comments that told the story of past rounds instead of the
current code: dated "fixed on / tried on / reverted on" notes, "this used
to...", "stale paragraph removed", "Phase 1/2" progress labels, and the
"ClassifyTile still does not use ClassifyBox" claim (it does now, via
VF_ClassifyBoxWithWarpRetry). Every invariant, unit, sign convention,
trap and measurement that justified the design is kept, rephrased in the
present tense and in its original language (French or English).

Also fix comments that named symbols removed in the dead-code commit
(EvaluateSDF wrapper, FTunnelCoreWorldEvaluation support-floor fields,
BlendChunks -> TransitionBlendChunks) and the stale function name in the
VF_RollStrateParamsDetailed determinism checkf message.

Comment and message text only; no code token changed. Tests untouched.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01URDGLsu7c4nunqNFPJEFPU
2026-09-28 14:18:36 +00:00

383 lines
19 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// VoxelDensityPrimitives.h
// Les quatre post-traitements STRUCTURELS partagés par chaque archétype.
// The four STRUCTURAL post-processes every archetype shares.
//
// POURQUOI CE FICHIER EXISTE / WHY THIS FILE EXISTS
// Les trois primitives verticales étaient `static` dans VoxelGenerator.cpp et appelées à l'identique
// par les six fonctions de densité. La pile d'opérateurs a besoin des MÊMES, donc elles montent ici :
// UNE copie partagée. Le scellement XY est ajouté dans ce même fichier pour que les deux chemins
// partagent aussi la nouvelle invariance — les invariants de monde ne sont pas des choix créatifs.
//
// The three vertical primitives were `static` in VoxelGenerator.cpp and called identically by all six
// density functions. The operator stack needs the same ones, so they move here: ONE shared copy. The
// XY seal is added beside them so both paths share the new invariant too.
//
// ⚠️ CONVENTION DE SIGNE — la source n°1 de confusion du plugin.
// Ces primitives travaillent en convention INTERNE : **positif = SOLIDE, négatif = AIR**.
// C'est la convention dans laquelle chaque fonction d'archétype est écrite ; la négation vers la
// convention marching-cubes (négatif = solide) se fait UNE FOIS, sur le `return`.
// SIGN CONVENTION: these work in INTERNAL convention — **positive = SOLID**. The negate to MC
// convention happens ONCE, at the caller's return.
//
// Les trois corps déplacés restent identiques; le quatrième est la nouvelle frontière globale.
// The three moved bodies remain identical; the fourth is the new global boundary.
#pragma once
#include "CoreMinimal.h"
#include "VoxelTypes.h" // SmoothStep01
#include "VoxelPassageGeometry.h" // shared body-sized landing/tunnel geometry
#include "VoxelDensityAblation.h" // development-only world-changing measurements
#include "VoxelTilePostReach.h" // worker-local landing/structural post reach bits
//=============================================================================
// SEAL DE FRONTIÈRE / BOUNDARY SEAL
//=============================================================================
// Seal solide aux bords haut et bas de la strate. Fade smoothstep sur `Thickness` voxels depuis
// chaque bord. N'AJOUTE que de la densité (FMath::Max), jamais n'en enlève → le joueur ne peut
// jamais percer le seal "par accident", seulement via les passages.
//
// ⚠️ C'est un opérateur FORÇANT, pas seulement un FillOnly : à l'intérieur de la bande, avec
// SealFactor > 0 et BaseDensity > 0, le résultat est solide GARANTI quelle qu'ait été l'entrée.
// C'est exactement ce qu'encode `IVoxelDensityOp::ClassifyBox` (voir VoxelDensityOp.h), et la
// raison pour laquelle cette méthode existe.
FORCEINLINE void VF_ApplyBoundarySeal(float& Density, float WorldZ,
float StrateTopZ, float StrateBottomZ,
float Thickness, float BaseDensity)
{
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
if (DistTop >= 0.0f && DistTop < Thickness)
{
float SealFactor = 1.0f - (DistTop / Thickness);
SealFactor = SmoothStep01(SealFactor);
Density = FMath::Max(Density, SealFactor * BaseDensity);
}
if (DistBot >= 0.0f && DistBot < Thickness)
{
float SealFactor = 1.0f - (DistBot / Thickness);
SealFactor = SmoothStep01(SealFactor);
Density = FMath::Max(Density, SealFactor * BaseDensity);
}
}
//=============================================================================
// CARVE DE PASSAGE / PASSAGE CARVING
//=============================================================================
// Creuse un passage inter-strates. Évalué APRÈS le seal pour que les passages puissent percer à
// travers le bouchon solide. Le rayon de blend est hard-codé à 4.0f — à exposer via
// UVoxelSettings si on veut pouvoir le tweaker.
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;
float CarveFactor = FMath::Clamp(
(PASSAGE_BLEND_RADIUS - ModSDF) / (PASSAGE_BLEND_RADIUS * 2.0f),
0.0f, 1.0f);
CarveFactor = SmoothStep01(CarveFactor);
// FORCE the density toward guaranteed AIR so the passage punches through ANYTHING in
// its path (seals, columns, surface roughness, terrain ops). A plain subtraction can
// be out-paced by stacked density additions, leaving solid plugs mid-tunnel — which is
// why the shaft "didn't go all the way through". Lerp toward a strongly negative target
// and take the min so we only ever make it MORE air (never refill an existing cave).
const float AirTarget = -(BaseDensity * 2.0f + SealThickness + 4.0f);
Density = FMath::Min(Density, FMath::Lerp(Density, AirTarget, CarveFactor));
}
// Landing air is also reasserted after the MC-space disturbance post. Unlike the legacy tube
// carve above, this threshold is independent of the incoming density: applying it a second time
// is therefore bit-stable. The structural passage op and the post-disturbance backstop can share
// the same operation without turning an otherwise unchanged live-vs-direct op-stack comparison
// into a second, deeper smooth carve. The tube intentionally retains its old current-density
// interpolation because that is part of the legacy structural path's contract.
FORCEINLINE void VF_ApplyPassageLandingCarving(float& Density, float LandingSDF,
float BaseDensity, float SealThickness)
{
constexpr float PASSAGE_BLEND_RADIUS = 4.0f;
if (LandingSDF >= PASSAGE_BLEND_RADIUS) return;
float CarveFactor = FMath::Clamp(
(PASSAGE_BLEND_RADIUS - LandingSDF) / (PASSAGE_BLEND_RADIUS * 2.0f),
0.0f, 1.0f);
CarveFactor = SmoothStep01(CarveFactor);
const float AirTarget = -(BaseDensity * 2.0f + SealThickness + 4.0f);
const float LandingThreshold = FMath::Lerp(BaseDensity, AirTarget, CarveFactor);
Density = FMath::Min(Density, LandingThreshold);
}
//=============================================================================
// LANDING ROOM ORIGINE (0,0) / ORIGIN LANDING ROOM
//=============================================================================
// The old implementation carved a full-height cylinder. That made the origin visually open but
// left no floor anywhere except at the bottom, so it was a fall shaft rather than a landing. The
// origin primitive is now a top-anchored rounded room with a real flat floor in every strate.
// It never crosses either vertical seal. The inter-strate seal remains solid until a passage (or
// the player) opens it; there is no default vertical bore between rooms.
FORCEINLINE void VF_ApplyOriginSpine(float& Density, float WorldX, float WorldY, float WorldZ,
float StrateTopZ, float StrateBottomZ, float SealThickness, float BaseDensity, float Radius)
{
if (VoxelDensityAblation::IsOriginSpineOff() || Radius <= 0.0f) return;
const VoxelPassageGeometry::FOriginLandingGeometry Geometry =
VoxelPassageGeometry::BuildOriginLandingGeometry(
StrateTopZ, StrateBottomZ, SealThickness, Radius);
if (!Geometry.bValid) return;
const float RoomSDF = VoxelPassageGeometry::OriginLandingRoomSDF(
FVector(WorldX, WorldY, WorldZ), Geometry);
VF_ApplyPassageLandingCarving(
Density, RoomSDF, BaseDensity, SealThickness);
// The floor is intentionally written after the room carve. It is also reasserted after the
// passage/disturbance posts by the callers below; doing it here makes the standalone primitive
// itself a complete landing-room operation.
if (WorldZ <= Geometry.FloorZ + KINDA_SMALL_NUMBER
&& WorldZ > Geometry.FloorZ - Geometry.FloorThickness
&& FMath::Abs(WorldX) <= FMath::Max(Geometry.HalfWidth - 1.0f, 0.0f)
&& FMath::Abs(WorldY) <= FMath::Max(Geometry.HalfWidth - 1.0f, 0.0f))
{
if (!VoxelPassageGeometry::VerticalShaftConnectorAirMarker())
{
Density = FMath::Max(Density, BaseDensity);
}
}
}
/** Reassert only the origin-room air volume after a post-process that may add rock. */
FORCEINLINE void VF_ApplyOriginLandingAir(float& Density,
float WorldX, float WorldY, float WorldZ,
float StrateTopZ, float StrateBottomZ, float SealThickness, float BaseDensity, float Radius)
{
if (VoxelDensityAblation::IsLandingPostsOff() || Radius <= 0.0f) return;
const VoxelPassageGeometry::FOriginLandingGeometry Geometry =
VoxelPassageGeometry::BuildOriginLandingGeometry(
StrateTopZ, StrateBottomZ, SealThickness, Radius);
if (!Geometry.bValid) return;
const float RoomSDF = VoxelPassageGeometry::OriginLandingRoomSDF(
FVector(WorldX, WorldY, WorldZ), Geometry);
VF_ApplyPassageLandingCarving(
Density, RoomSDF, BaseDensity, SealThickness);
}
/** Reassert only the origin-room support slab after an air carve/post-process. */
FORCEINLINE void VF_ApplyOriginLandingFloor(float& Density,
float WorldX, float WorldY, float WorldZ,
float StrateTopZ, float StrateBottomZ, float SealThickness, float BaseDensity, float Radius)
{
if (VoxelDensityAblation::IsLandingPostsOff() || Radius <= 0.0f) return;
const VoxelPassageGeometry::FOriginLandingGeometry Geometry =
VoxelPassageGeometry::BuildOriginLandingGeometry(
StrateTopZ, StrateBottomZ, SealThickness, Radius);
if (!Geometry.bValid) return;
if (WorldZ <= Geometry.FloorZ + KINDA_SMALL_NUMBER
&& WorldZ > Geometry.FloorZ - Geometry.FloorThickness
&& FMath::Abs(WorldX) <= FMath::Max(Geometry.HalfWidth - 1.0f, 0.0f)
&& FMath::Abs(WorldY) <= FMath::Max(Geometry.HalfWidth - 1.0f, 0.0f))
{
if (!VoxelPassageGeometry::VerticalShaftConnectorAirMarker())
{
Density = FMath::Max(Density, BaseDensity);
}
}
}
/** MC-facing origin-room air backstop for the post-disturbance path. */
FORCEINLINE void VF_ApplyOriginLandingAirMC(float& Density,
float WorldX, float WorldY, float WorldZ,
float StrateTopZ, float StrateBottomZ, float SealThickness, float BaseDensity, float Radius)
{
if (!VoxelGenLOD::IsOriginLandingReachable()
&& !VoxelGenLOD::IsTilePostReachBypassActive())
{
VoxelGenLOD::CompareSkippedPost(Density, [&]()
{
VF_ApplyOriginLandingAirMC(
Density, WorldX, WorldY, WorldZ,
StrateTopZ, StrateBottomZ, SealThickness, BaseDensity, Radius);
}, VoxelGenLOD::ETilePostComparisonKind::OriginAir);
return;
}
float InternalDensity = -Density;
VF_ApplyOriginLandingAir(InternalDensity, WorldX, WorldY, WorldZ,
StrateTopZ, StrateBottomZ, SealThickness, BaseDensity, Radius);
Density = -InternalDensity;
}
/** MC-facing origin-room floor backstop for the post-disturbance path. */
FORCEINLINE void VF_ApplyOriginLandingFloorMC(float& Density,
float WorldX, float WorldY, float WorldZ,
float StrateTopZ, float StrateBottomZ, float SealThickness, float BaseDensity, float Radius)
{
if (!VoxelGenLOD::IsOriginLandingReachable()
&& !VoxelGenLOD::IsTilePostReachBypassActive())
{
VoxelGenLOD::CompareSkippedPost(Density, [&]()
{
VF_ApplyOriginLandingFloorMC(
Density, WorldX, WorldY, WorldZ,
StrateTopZ, StrateBottomZ, SealThickness, BaseDensity, Radius);
}, VoxelGenLOD::ETilePostComparisonKind::OriginFloor);
return;
}
float InternalDensity = -Density;
VF_ApplyOriginLandingFloor(InternalDensity, WorldX, WorldY, WorldZ,
StrateTopZ, StrateBottomZ, SealThickness, BaseDensity, Radius);
Density = -InternalDensity;
}
//=============================================================================
// XY EDGE SEAL / SCELLEMENT DE LA LIMITE XY
//=============================================================================
// The bounded-world rim is a radial solid ramp in actor-space XY. This helper uses the same
// internal convention as VF_ApplyBoundarySeal (positive = solid); the MC-facing wrapper below
// negates it so the output convention remains negative = solid.
//
// Radius == 0 is deliberately checked before any arithmetic: it is a true legacy no-op, not a
// nearly-invisible setting-dependent perturbation. The common interior case also returns from a
// squared-distance test and pays no sqrt.
FORCEINLINE void VF_ApplyXYEdgeSeal(float& Density, float WorldX, float WorldY,
float WorldRadiusVoxels, float Thickness, float BaseDensity)
{
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;
const float InnerRadius = WorldRadiusVoxels - Thickness;
const float InnerRadiusSq = InnerRadius * InnerRadius;
const float DistSq = WorldX * WorldX + WorldY * WorldY;
const float RadiusSq = WorldRadiusVoxels * WorldRadiusVoxels;
// The overwhelmingly common case: well inside the bounded world.
if (InnerRadius > 0.0f && DistSq <= InnerRadiusSq) return;
// Keep the outer-boundary comparison in squared space too. This makes the exact force branch
// and the ClassifyBox proof use the same predicate: at or beyond R the result is BaseDensity,
// with no sqrt-rounding ambiguity in the forced-solid margin.
if (DistSq >= RadiusSq)
{
Density = FMath::Max(Density, BaseDensity);
return;
}
const float DistXY = FMath::Sqrt(DistSq);
// 0 at the inner edge, 1 at the world radius; smoothstep removes a cylindrical cliff.
const float SealFactor = SmoothStep01(FMath::Clamp(
(DistXY - InnerRadius) / Thickness, 0.0f, 1.0f));
// At/beyond WorldRadiusVoxels the branch above already forced exactly BaseDensity.
Density = FMath::Max(Density, SealFactor * BaseDensity);
}
/** MC-facing form used after post-processes that already operate in the output convention. */
FORCEINLINE void VF_ApplyXYEdgeSealMC(float& Density, float WorldX, float WorldY,
float WorldRadiusVoxels, float Thickness, float BaseDensity)
{
if (VoxelDensityAblation::IsXYEdgeSealOff()
|| !(WorldRadiusVoxels > 0.0f) || !(Thickness > 0.0f)) return;
float InternalDensity = -Density;
VF_ApplyXYEdgeSeal(InternalDensity, WorldX, WorldY,
WorldRadiusVoxels, Thickness, BaseDensity);
Density = -InternalDensity;
}
//=============================================================================
// XY EDGE PROOF / PREUVE DE BOITE
//=============================================================================
// The closest point of an AABB to the XY origin gives the minimum radius over the whole box.
// The helpers below are shared by ClassifyBox, ClassifyTile's global shell early-out, and the
// soundness test. They intentionally require one voxel of radial margin in the ramp: at the
// mathematical inner edge the helper is a true no-op, so claiming AllSolid there would be false.
namespace VoxelDensityReach
{
constexpr float PassageBlend = 4.0f;
constexpr float EdgeProofMargin = 1.0f;
}
FORCEINLINE bool VF_IsValidXYEdgeSealProofInput(const FBox& VoxelBox,
float WorldRadiusVoxels, float Thickness, float BaseDensity)
{
return VoxelBox.IsValid
&& VoxelMath::IsFinite(VoxelBox.Min.X) && VoxelMath::IsFinite(VoxelBox.Min.Y)
&& VoxelMath::IsFinite(VoxelBox.Max.X) && VoxelMath::IsFinite(VoxelBox.Max.Y)
&& VoxelMath::IsFinite(WorldRadiusVoxels) && VoxelMath::IsFinite(Thickness)
&& VoxelMath::IsFinite(BaseDensity)
&& WorldRadiusVoxels > 0.0f && Thickness > 0.0f && BaseDensity > 0.0f;
}
FORCEINLINE float VF_XYEdgeClosestRadiusSq(const FBox& VoxelBox)
{
const float ClosestX = (VoxelBox.Min.X > 0.0f) ? VoxelBox.Min.X
: (VoxelBox.Max.X < 0.0f) ? VoxelBox.Max.X : 0.0f;
const float ClosestY = (VoxelBox.Min.Y > 0.0f) ? VoxelBox.Min.Y
: (VoxelBox.Max.Y < 0.0f) ? VoxelBox.Max.Y : 0.0f;
return ClosestX * ClosestX + ClosestY * ClosestY;
}
FORCEINLINE float VF_XYEdgeFarthestRadiusSq(const FBox& VoxelBox)
{
const float FarthestX = FMath::Max(FMath::Abs(VoxelBox.Min.X), FMath::Abs(VoxelBox.Max.X));
const float FarthestY = FMath::Max(FMath::Abs(VoxelBox.Min.Y), FMath::Abs(VoxelBox.Max.Y));
return FarthestX * FarthestX + FarthestY * FarthestY;
}
/**
* Return the actual positive lower-bound margin imposed by the edge seal over a box, or 0 when
* the box is not provably forced. The ramp bound is intentionally reduced by 2× after the
* one-voxel proof margin, so float evaluation error can only make this estimate too small.
*/
FORCEINLINE float VF_XYEdgeSealForcedMarginOverBox(const FBox& VoxelBox,
float WorldRadiusVoxels, float Thickness, float BaseDensity)
{
if (!VF_IsValidXYEdgeSealProofInput(VoxelBox, WorldRadiusVoxels, Thickness, BaseDensity)) return 0.0f;
const float ClosestRadiusSq = VF_XYEdgeClosestRadiusSq(VoxelBox);
const float RadiusSq = WorldRadiusVoxels * WorldRadiusVoxels;
if (ClosestRadiusSq >= RadiusSq)
{
// The per-voxel helper takes the >= WorldRadiusVoxels branch and forces BaseDensity.
return BaseDensity;
}
const float InnerRadius = WorldRadiusVoxels - Thickness;
const float ProofRadius = InnerRadius + VoxelDensityReach::EdgeProofMargin;
if (!(Thickness > VoxelDensityReach::EdgeProofMargin)
|| !(ClosestRadiusSq >= ProofRadius * ProofRadius)) return 0.0f;
const float GuaranteedFactor = SmoothStep01(
FMath::Clamp(VoxelDensityReach::EdgeProofMargin / Thickness, 0.0f, 1.0f));
return BaseDensity * GuaranteedFactor * 0.5f;
}
FORCEINLINE bool VF_XYEdgeSealBoxIsForcedSolid(const FBox& VoxelBox,
float WorldRadiusVoxels, float Thickness, float BaseDensity)
{
return VF_XYEdgeSealForcedMarginOverBox(
VoxelBox, WorldRadiusVoxels, Thickness, BaseDensity) > 0.0f;
}
FORCEINLINE bool VF_XYEdgeSealBoxTouchesBand(const FBox& VoxelBox,
float WorldRadiusVoxels, float Thickness)
{
if (!(WorldRadiusVoxels > 0.0f) || !(Thickness > 0.0f)
|| !VoxelMath::IsFinite(WorldRadiusVoxels) || !VoxelMath::IsFinite(Thickness)) return false;
const float InnerRadius = WorldRadiusVoxels - Thickness;
return VF_XYEdgeFarthestRadiusSq(VoxelBox) > InnerRadius * InnerRadius;
}
//=============================================================================
// PORTÉES / REACHES — les rayons dont ClassifyTile et EffectOverBox ont besoin
//=============================================================================
// The constants are named above so a future bounds test cannot let them drift from the hot path.