Files
VoxelForge/Standalone/Source/VoxelForge/Public/VoxelMarchingCubesMesher.h
T

211 lines
11 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.
// VoxelMarchingCubesMesher.h
// Mesh lisse à partir du champ de densité du générateur.
//
// Principe:
// 1. Chaque cellule du chunk (cube 2x2x2 voxels) lit la densité aux 8 coins.
// 2. Un index 8 bits indique quels coins sont au-dessus du IsoLevel.
// 3. Les tables MC (EdgeTable + TriTable) donnent les triangles à générer.
// 4. On interpole la position du vertex le long de chaque arête traversée.
//
// LOD: Step > 1 échantillonne moins souvent → moins de triangles à distance.
#pragma once
#include "CoreMinimal.h"
#include <atomic>
#include <vector>
#include "VoxelTypes.h" // Pour FVoxelMeshData, CHUNK_SIZE, VOXEL_SIZE, etc.
#include "VoxelGenerator.h"
#include "VoxelMarchingCubesMesher.generated.h"
class FStandaloneShapeWorld;
// A run-scoped, read-only density lattice. The explorer owns the storage and installs the
// pointer only while its canonical tiles are being meshed. The lattice includes the one-point
// halo needed by every tile, so each world sample is generated once and copied into both adjacent
// tile scratch grids. It is deliberately not a UObject or a cache with eviction: ownership,
// lifetime, and iteration order remain explicit and deterministic.
struct FVoxelSharedDensityGrid
{
FIntVector OriginVoxels = FIntVector::ZeroValue;
int32 Step = 1;
int32 Dim = 0;
TArray<float> Samples;
void Reset()
{
OriginVoxels = FIntVector::ZeroValue;
Step = 1;
Dim = 0;
Samples.Reset();
}
};
// Optional, sampled timing output for the research audit; null in production calls.
// Mesures echantillonnees facultatives pour l'audit; pointeur nul en production.
struct FVoxelMarchingCubesStageTimings
{
double DensityGridMilliseconds = 0.0;
double CellClassificationMilliseconds = 0.0;
double GradientNormalsMilliseconds = 0.0;
double EdgeInterpolationMilliseconds = 0.0;
double VertexDedupStreamMilliseconds = 0.0;
double SkirtMilliseconds = 0.0;
double OtherMilliseconds = 0.0;
uint64 GradientFallbackDensityCalls = 0;
uint64 EdgeMidpointDensityCalls = 0;
uint64 GradientMemoHits = 0;
};
// Stable plan data copied onto one worker-owned mesher for hybrid LOD0 generation.
// Donnees stables du plan copiees dans un mailleur de worker pour le mode hybride LOD0.
struct FVoxelHybridSharpFeatureBox
{
uint64 StableId = 0;
int64 MinimumSubvoxels[3] = {};
int64 MaximumSubvoxels[3] = {};
};
UCLASS(BlueprintType)
class VOXELFORGE_API UVoxelMarchingCubesMesher : public UObject
{
GENERATED_BODY()
public:
// Parse command-line switches on the module's game-thread startup. GenerateMesh is a worker
// entry point and therefore only reads the resulting values.
static void InitializeConsoleSwitches();
/**
* Génère le mesh d'une TUILE de clipmap (voir FVoxelTileKey).
*
* @param OriginVoxels - Coin min de la tuile en coords VOXEL (= Tile.OriginVoxels()).
* @param Step - Taille de cellule en voxels. La tuile couvre CellsPerAxis*Step voxels.
* @param CellsPerAxis - Nombre de cellules par axe. Les tuiles GROSSIÈRES en utilisent MOINS
* (gen moins chère, maillage plus grossier au loin) tout en couvrant la
* même étendue (extent = CellsPerAxis*Step). Niveau 0 = CHUNK_SIZE.
* @param OutCaptureGrid - CAPTURE-DURING-MESHING (optionnel). Si non-null ET CellsPerAxis==CHUNK_SIZE
* (tuile pleine résolution, Step==1<<Level, donc 1:1 avec une cellule du clipmap
* de densité), on y recopie les CHUNK_SIZE³ points intérieurs de la grille de
* densité déjà échantillonnée, quantifiés via VF_QuantizeDensity. Cela évite à
* UVoxelDensityVolume de re-sampler GetDensityAt pour ces cellules (le mesher
* les a déjà calculées). Vidé puis rempli ; reste vide si non éligible.
* @param BandZMinVox / BandZMaxVox - COUPE DE CONTENU PAR STRATE (optionnel, voxels Z INCLUSIFS,
* cf. UVoxelSettings::StrateContentCutMinLevel) : seules les cellules dont
* l'intervalle Z chevauche la bande sont maillées. Le runtime ne passe
* jamais de bande aux tuiles LOD0, car elles possèdent la collision du pawn.
* Une tuile grossière qui
* chevauche une frontière de strate ne maille que la strate du joueur —
* supprime les trous d'aliasing (le bouchon seal/gap plus fin que Step
* tombait entre deux points du treillis) et le mélange de matériaux entre
* strates. Les cellules maillées restent identiques au bit près (mêmes
* échantillons monde purs). Jamais combiné avec OutCaptureGrid.
*/
FVoxelMeshData GenerateMesh(FIntVector OriginVoxels, int32 Step = 1, int32 CellsPerAxis = CHUNK_SIZE,
TArray<uint8>* OutCaptureGrid = nullptr,
int32 BandZMinVox = INT32_MIN, int32 BandZMaxVox = INT32_MAX,
int64* OutDensitySampleCount = nullptr,
FVoxelSharedDensityGrid* OutPreEditDensityGrid = nullptr,
const FVoxelSharedDensityGrid* InSharedDensityGrid = nullptr,
std::vector<float>* OutDebugDensitySamples = nullptr,
FVoxelMarchingCubesStageTimings* OutStageTimings = nullptr);
//=========================================================================
// SERVICES (injectés par AVoxelWorld)
//=========================================================================
// Le générateur fournit la densité par voxel.
UPROPERTY()
const UVoxelGenerator* Generator = nullptr;
void SetGenerator(const UVoxelGenerator* InGenerator) { Generator = InGenerator; }
// Core tile coordinates use 25 cm voxels; legacy generation keeps 12.5 cm.
// Les coordonnees des tuiles Core utilisent des voxels de 25 cm; le legacy reste a 12,5 cm.
void SetGeometryVoxelSizeCm(float InVoxelSizeCm) { GeometryVoxelSizeCm = InVoxelSizeCm; }
// AVoxelWorld installs its bShuttingDown flag here. The commandlet path leaves it null.
// This is intentionally read-only: workers may observe shutdown, but never mutate world state.
void SetShutdownFlag(const std::atomic<bool>* InShutdownFlag) { ShutdownFlag = InShutdownFlag; }
// Optional run-scoped shared lattice. The caller must keep it alive and immutable until all
// GenerateMesh calls finish. A malformed/incomplete lattice is ignored and falls back to
// the canonical generator path rather than changing geometry.
void SetSharedDensityGrid(const FVoxelSharedDensityGrid* InGrid) { SharedDensityGrid = InGrid; }
// Standalone's interval world is an explicit immutable sampling context. Game builds do not
// set this pointer and continue through Generator.
void SetStandaloneShapeWorld(const FStandaloneShapeWorld* InWorld) { ShapeWorldContext = InWorld; }
// The caller owns this mesher instance for one tile task; no shared mutable feature state.
// L'appelant possede cette instance pour une tuile; aucune donnee partagee modifiable.
void SetHybridSharpFeatures(TArray<FVoxelHybridSharpFeatureBox>&& InFeatures)
{
HybridSharpFeatures = MoveTemp(InFeatures);
bUseHybridSharpMesher = true;
}
// Version 2 keeps MC topology and projects eligible LOD0 edge roots onto exact box planes.
void SetHybridSharpFeatureSnap(TArray<FVoxelHybridSharpFeatureBox>&& InFeatures)
{
HybridSharpFeatures = MoveTemp(InFeatures);
bUseHybridSharpFeatureSnap = true;
}
private:
FORCEINLINE bool ShouldAbortWork() const
{
return ShutdownFlag && ShutdownFlag->load(std::memory_order_relaxed);
}
const std::atomic<bool>* ShutdownFlag = nullptr;
const FVoxelSharedDensityGrid* SharedDensityGrid = nullptr;
const FStandaloneShapeWorld* ShapeWorldContext = nullptr;
TArray<FVoxelHybridSharpFeatureBox> HybridSharpFeatures;
bool bUseHybridSharpMesher = false;
bool bUseHybridSharpFeatureSnap = false;
float GeometryVoxelSizeCm = VOXEL_SIZE;
public:
//=========================================================================
// SETTINGS
//=========================================================================
// Optional standalone benchmark switches. Runtime callers leave all false and retain the
// complete FVoxelMeshData contract consumed by the game materials and section setup.
bool bStandaloneSkipUvAndColors = false;
bool bStandaloneSkipUV = false;
bool bStandaloneSkipColors = false;
bool bStandaloneSkipMaterialGroups = false;
bool bStandaloneEnableGradientMemoization = true;
// Seuil de l'isosurface dans le champ de densité.
// Convention MC: densité < IsoLevel = solide, >= = air.
float IsoLevel = 0.0f;
// SKIRTS — bouchent les fissures aux frontières de tuiles entre niveaux de clipmap voisins
// (résolutions différentes → les iso-surfaces ne se rejoignent pas exactement). Une jupe
// (mur court) est extrudée vers le solide depuis chaque arête de surface posée sur une des 6
// faces externes de la tuile. Voir GenerateMesh.
bool bGenerateSkirts = true;
// Profondeur de la jupe, en CELLULES de la tuile (× Step × VOXEL_SIZE). Four L0 cells preserve
// the 0.5 m default extrusion at 12.5 cm per voxel.
float SkirtCells = 4.0f;
// Conservative 8x8x8 block classifier. Uniform blocks skip the marching-cubes cell loop;
// when every relevant block has the same verdict, the density grid is skipped as well.
// ClassifyTile is proof-only, so no samples, resolution, or geometry are removed.
// The tile classifier is the cheap, whole-tile proof. Keep the optional 8^3-block refinement
// available for focused diagnostics, but leave it disabled in streaming: repeating a proof on
// every mixed tile can cost more than the density grid it is meant to avoid.
bool bUseBlockEarlyOut = false;
// T2.b — LOD-aware octave reduction (opt-in, copied from UVoxelSettings::LODOctaveDrop).
// Octaves dropped from per-voxel volumetric noise PER Step doubling: a tile at Step=S
// drops LODOctaveDrop * log2(S) octaves (see VoxelGenLOD in VoxelGenerator.h).
// 0 (default) = off — every LOD samples full octaves, byte-identical to before.
// Réduction d'octaves sur les tuiles grossières ; 0 = désactivé.
int32 LODOctaveDrop = 0;
};