Stopped by the owner's decision (in-game lit captures were eating the round); he judges the look in game instead. 1. Fall-through with the research field: fixed. Deep drop, gate on: 2 holds (0.50 s), min foot clearance +43 cm; gate off: penetrates (-278 cm). Stress fixture offsets converted to metres. 2. VOXEL_SIZE 25 -> 12.5 cm. Physical sizes kept in metres (player, steps, gate, insets, research field sizes, dig radii, test thresholds); class defaults that count chunks/voxels doubled (ClipRadius 6, RenderDistanceChunks 1536, ViewDistance*, StrateViewMargin 6, PawnCollisionRadius 2, SkirtCells 4, EdgeSealThickness 128, decoration spacings, density volume res 256). Asset values override these defaults: the owner must double his own. Ring scale, six workers, idle, step-2f field before the look changes: 64 m cube (519^3) 4.55 s; 32 m cube (259^3) 0.95 s. 3. In game: tiles go through the brick evaluator (proved by a log marker); profile switch rebuilds the loaded world (~5,800 tiles); full-detail fill 12.4 s after spawn in the visible-window test. 4. Look: round winding tunnels, per-column ellipsoid/lattice caches. All step-2f geometry gates pass at 12.5 cm (0 crumbs, 1 solid component per profile, solid share 69-87%). 259^3 ring, six workers: 1.08-2.02 s (worst HyperComplex) -- the 600 ms gate FAILS. Lit captures failed (hidden/off-screen window, then blank frames); not pursued. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
145 lines
7.6 KiB
C++
145 lines
7.6 KiB
C++
// 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 "VoxelTypes.h" // Pour FVoxelMeshData, CHUNK_SIZE, VOXEL_SIZE, etc.
|
||
#include "VoxelGenerator.h"
|
||
#include "VoxelMarchingCubesMesher.generated.h"
|
||
|
||
// 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();
|
||
}
|
||
};
|
||
|
||
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);
|
||
|
||
//=========================================================================
|
||
// 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; }
|
||
|
||
// 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; }
|
||
|
||
private:
|
||
FORCEINLINE bool ShouldAbortWork() const
|
||
{
|
||
return ShutdownFlag && ShutdownFlag->load(std::memory_order_relaxed);
|
||
}
|
||
|
||
const std::atomic<bool>* ShutdownFlag = nullptr;
|
||
const FVoxelSharedDensityGrid* SharedDensityGrid = nullptr;
|
||
|
||
public:
|
||
|
||
//=========================================================================
|
||
// SETTINGS
|
||
//=========================================================================
|
||
|
||
// 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;
|
||
};
|