This commit is contained in:
2026-06-16 03:39:22 +02:00
parent f030eec08a
commit db558d9e14
18 changed files with 1570 additions and 264 deletions
@@ -14,6 +14,8 @@
class UVoxelStrateManager;
class UVoxelStrateDefinition;
class UVoxelBiomeDefinition;
class UVoxelGenerator;
class UExponentialHeightFogComponent;
class USkyLightComponent;
@@ -23,23 +25,31 @@ class VOXELFORGE_API UVoxelAtmosphereManager : public UObject
GENERATED_BODY()
public:
/** Create the managed fog + skylight components on the owner actor. */
void Initialize(AActor* InOwner, UVoxelStrateManager* InStrateManager);
/** Create the managed fog + skylight components on the owner actor. Generator supplies
* the dominant-biome query so atmosphere can vary by biome within a strate. */
void Initialize(AActor* InOwner, UVoxelStrateManager* InStrateManager, UVoxelGenerator* InGenerator);
/** Call each frame with the player's world position. Cheap: only reacts on strate change. */
/** Call each frame with the player's world position. Cheap: only reacts on strate OR
* dominant-biome change. */
void UpdateForPlayer(const FVector& PlayerWorldPos);
/** Tear down spawned layer actors + reset (season reset / shutdown). */
void Reset();
private:
void ApplyStrate(const UVoxelStrateDefinition* Def);
// Full strate apply: layer actors + atmosphere BP + fog/sky. Biome retints fog/sky.
void ApplyStrate(const UVoxelStrateDefinition* Def, const UVoxelBiomeDefinition* Biome);
// Just the managed fog + skylight (biome override beats strate when set).
void ApplyFogSky(const UVoxelStrateDefinition* Def, const UVoxelBiomeDefinition* Biome);
TWeakObjectPtr<AActor> Owner;
UPROPERTY()
UVoxelStrateManager* StrateManager = nullptr;
UPROPERTY()
UVoxelGenerator* Generator = nullptr;
UPROPERTY()
UExponentialHeightFogComponent* Fog = nullptr;
@@ -59,4 +69,7 @@ private:
// Which strate's atmosphere is currently applied (INT32_MIN = none yet).
int32 CurrentStrateIndex = INT32_MIN;
// Dominant biome currently driving fog/sky (identity token for change detection).
TWeakObjectPtr<const UVoxelBiomeDefinition> CurrentBiome;
};
+47 -6
View File
@@ -6,22 +6,39 @@
// - PopulateChunk(coord, meshdata) after a chunk's mesh is applied
// - ClearChunk(coord) when a chunk unloads / is re-meshed
// - ClearAll() on regenerate / season reset
// - SetActiveStrate(playerStrate) each Tick (no-op unless the strate changed)
//
// DETERMINISM: every placement decision is a pure hash of (chunk, surface index,
// entry index, seed), so the same world re-populates identically. Spawning itself
// must run on the game thread (UWorld::SpawnActor), which it does — ApplyMeshToChunk
// is game-thread.
//
// RENDERING PATHS per decoration entry (FStrateDecoration):
// - ActorClass → real actors (lights, logic, interaction). Keep MaxLODLevel=0.
// - InstancedMesh → HISM instances batched per chunk: no tick, no per-actor cost,
// engine-culled. Safe to allow at LOD 1-2 so visual props don't
// pop out with LOD0 (emissive materials still glow at distance).
//
// STRATE LIGHT CULLING: a light in another strate can never legitimately reach the
// player (seals + bedrock gap are solid), but a shadowless light BLEEDS through rock
// and a shadowed one pays full cost to render black. So light components on spawned
// decoration actors are only visible while the player is in the SAME strate — the
// analytical form of occlusion culling, computed once per strate change.
#pragma once
#include "CoreMinimal.h"
#include "VoxelTypes.h"
#include "VoxelStrateTypes.h" // FStrateDecoration (resolved per dominant biome)
#include "VoxelContentManager.generated.h"
class UVoxelStrateManager;
class UVoxelStrateDefinition;
class UVoxelGenerator;
class UStaticMesh;
class UStaticMeshComponent;
class UHierarchicalInstancedStaticMeshComponent;
class UMaterialInterface;
UCLASS()
class VOXELFORGE_API UVoxelContentManager : public UObject
@@ -29,34 +46,55 @@ class VOXELFORGE_API UVoxelContentManager : public UObject
GENERATED_BODY()
public:
/** Wire up services. Owner is the AVoxelWorld actor that owns spawned content. */
void Initialize(AActor* InOwner, UVoxelStrateManager* InStrateManager, int32 InSeed);
/** Wire up services. Owner is the AVoxelWorld actor that owns spawned content.
* Generator supplies the dominant-biome query for per-biome content selection. */
void Initialize(AActor* InOwner, UVoxelStrateManager* InStrateManager,
UVoxelGenerator* InGenerator, int32 InSeed);
/** Update the seed used for placement hashing (season reset). */
void SetSeed(int32 InSeed) { Seed = InSeed; }
/** Populate decorations + water for a chunk. Clears any previous content first.
* Decorations are only scattered at LOD 0 (near chunks); water is placed at any LOD. */
* Each decoration entry spawns only while LOD <= its MaxLODLevel; water at any LOD. */
void PopulateChunk(const FIntVector& ChunkCoord, const FVoxelMeshData& MeshData, int32 LODLevel = 0);
/** Destroy all spawned content (actors + water plane) for one chunk. */
/** Destroy all spawned content (actors + instances + water plane) for one chunk. */
void ClearChunk(const FIntVector& ChunkCoord);
/** Destroy all spawned content for every chunk. */
void ClearAll();
/** Player strate changed → toggle decoration lights (strict: lights only live in the
* player's strate). Cheap no-op while the strate stays the same — call every Tick. */
void SetActiveStrate(int32 PlayerStrateIndex);
private:
void SpawnDecorations(const FIntVector& ChunkCoord, const FVoxelMeshData& MeshData,
const UVoxelStrateDefinition* Def, TArray<TWeakObjectPtr<AActor>>& Out);
void SpawnWater(const FIntVector& ChunkCoord, const UVoxelStrateDefinition* Def);
const TArray<FStrateDecoration>& Decorations, int32 LODLevel,
TArray<TWeakObjectPtr<AActor>>& Out);
void SpawnWater(const FIntVector& ChunkCoord, const UVoxelStrateDefinition* Def,
UMaterialInterface* WaterMaterial);
/** Strate index a chunk belongs to (center-Z lookup). */
int32 GetChunkStrateIndex(const FIntVector& ChunkCoord) const;
/** Show/hide every light component on a decoration actor. */
static void SetActorLightsEnabled(AActor* Actor, bool bEnabled);
TWeakObjectPtr<AActor> Owner;
UPROPERTY()
UVoxelStrateManager* StrateManager = nullptr;
UPROPERTY()
UVoxelGenerator* Generator = nullptr;
int32 Seed = 0;
// Player's current strate (INT32_MIN until first SetActiveStrate). Lights on spawned
// actors are enabled iff their chunk's strate matches this.
int32 ActiveStrateIndex = INT32_MIN;
// Engine unit plane (/Engine/BasicShapes/Plane) reused for every water surface.
UPROPERTY()
UStaticMesh* PlaneMesh = nullptr;
@@ -64,6 +102,9 @@ private:
// Spawned decoration/ambient actors per chunk (weak — they live in the level).
TMap<FIntVector, TArray<TWeakObjectPtr<AActor>>> SpawnedActors;
// HISM components per chunk (weak — registered components are owned by the actor).
TMap<FIntVector, TArray<TWeakObjectPtr<UHierarchicalInstancedStaticMeshComponent>>> ChunkInstances;
// Water surface component per chunk.
UPROPERTY()
TMap<FIntVector, UStaticMeshComponent*> WaterPlanes;
+75 -1
View File
@@ -11,12 +11,14 @@
#include "CoreMinimal.h"
#include "VoxelTypes.h"
#include "VoxelStrateTypes.h"
#include "VoxelBiomeTypes.h"
#include "VoxelGenerator.generated.h"
// Forward decls (évite les includes transitifs)
class UVoxelSettings;
class UVoxelStrateManager;
class UVoxelDiffLayer;
class UVoxelBiomeDefinition;
/**
* UVoxelGenerator
@@ -103,9 +105,18 @@ public:
/**
* Densité pour une strate SurfaceWorld — terrain à ciel ouvert (collines,
* montagnes, plages) sous un plafond solide, avec nappe d'eau optionnelle.
*
* Biome output-blend: the heightfield is evaluated with ParamsD (the voxel's dominant
* biome) and, when NeighborWeight > 0, also with ParamsN (its nearest neighbour); the
* two surface HEIGHTS are lerped. This stays seamless across ANY param difference
* (frequencies included). With no biomes, pass ParamsD == ParamsN and weight 0 →
* bit-identical to the single-param terrain. Structural fields (Z bounds, seal, base,
* water level) must be equal in both (forced from the strate).
*/
float GetSurfaceDensity(float WorldX, float WorldY, float WorldZ,
const FSurfaceGenerationParams& Params) const;
const FSurfaceGenerationParams& ParamsD,
const FSurfaceGenerationParams& ParamsN,
float NeighborWeight) const;
/**
* Densité pour une strate VerticalShafts — puits verticaux pleine hauteur,
@@ -120,4 +131,67 @@ public:
*/
float GetFloatingIslandDensity(float WorldX, float WorldY, float WorldZ,
const FFloatingIslandParams& Params) const;
//=========================================================================
// CLIMATE & BIOME FIELDS (pure XY, deterministic, window-invariant)
//=========================================================================
/**
* Relief ("elevation") field at a world XY → [0,1], contrast-shaped & smoothed.
* The single source of truth for the relief map M: SurfaceWorld terrain and the
* biome climate map both call this, so they agree when given the same freq/contrast.
*/
float SampleRelief(float WorldX, float WorldY, float Frequency, float Contrast) const;
/**
* Moisture field at a world XY → [0,1]. The second climate axis for biome placement.
*/
float SampleMoisture(float WorldX, float WorldY, float Frequency) const;
/**
* Resolve the biome at a world XY: dominant cell + nearest neighbour + blend weight.
* Warped Voronoi over a jittered grid; each cell's biome is chosen by its site's
* (relief, moisture) against the context's climate boxes. PURE function of (XY, seed,
* Ctx) — no chunk-window dependence (CODEMAP §8.4). Returns an empty sample when the
* context has no biomes. Used by the 2D preview bake and (next) the density path.
*/
FBiomeSample SampleBiomeAt(float WorldX, float WorldY, const FBiomeContext& Ctx) const;
/**
* Hot-path biome resolve: the dominant + neighbour biome and blend weight at a world
* XY for the given strate slice. Uses (and lazily rebuilds) Cache — a box-validated
* per-chunk grid — so the noise-heavy cell classification happens once per chunk, not
* per voxel. Bit-identical to SampleBiomeAt, so the baked preview matches the terrain.
*/
FBiomeSample ResolveBiomeSampleAt(float WorldX, float WorldY, int32 ChunkZ,
const FBiomeContext& Ctx, FChunkBiomeCache& Cache) const;
/**
* Dominant biome ASSET at a world XY for the given strate slice (chunk Z), or nullptr
* when the strate has no biomes / the point is outside the strate range. Game-thread
* helper for content + atmosphere selection — uses the same field as the density path.
*/
const UVoxelBiomeDefinition* GetDominantBiomeAt(float WorldX, float WorldY, int32 ChunkZ) const;
private:
/** Pick the biome (index into Ctx.Biomes) for a Voronoi site, by its climate. */
int32 ClassifyBiomeAtSite(float SiteX, float SiteY, const FBiomeContext& Ctx, uint32 SiteHash) const;
/** The SurfaceWorld heightfield: world XY → terrain surface Z (voxel coords). Pure
* per-XY; the part that's evaluated per biome and blended in GetSurfaceDensity. */
float ComputeSurfaceTerrainZ(float WorldX, float WorldY, const FSurfaceGenerationParams& Params) const;
/** The SurfaceWorld sky-cap ceiling surface Z at a world XY (also pure per-XY). */
float ComputeSurfaceCeiling(float WorldX, float WorldY, const FSurfaceGenerationParams& Params) const;
/** Final SurfaceWorld density from a column's precomputed terrain Z + ceiling: the
* cheap per-voxel Z-combine + origin spine + boundary seal + passage carving. The
* XY-only work (terrain/ceiling) is done once per column and cached (T1.a). */
float SurfaceDensityFromColumn(float WorldX, float WorldY, float WorldZ,
float TerrainZ, float CeilSurf,
const FSurfaceGenerationParams& Structural) const;
/** (Re)build the per-chunk biome cell grid covering chunk (X,Y) footprint + margin. */
void RebuildBiomeGrid(int32 ChunkX, int32 ChunkY, int32 ChunkZ,
const FBiomeContext& Ctx, FChunkBiomeCache& Cache) const;
};
+9 -3
View File
@@ -22,6 +22,7 @@ public:
// STREAMING (distance de vue)
//=========================================================================
// En CHUNKS (CHUNK_SIZE=32). Couverture linéaire = ViewDistanceXY × 32 × 25 cm.
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Voxel|Streaming")
int32 ViewDistanceXY = 16;
@@ -35,7 +36,10 @@ public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Voxel|Streaming")
int32 MaxConcurrentTasks = 16;
// Nombre max de meshes appliqués par frame (évite les stutters d'upload GPU).
// Nombre max de meshes appliqués (upload GPU) par frame. Seuls les vrais applies
// comptent (chunks vides/périmés se vident gratuitement). À 32³ chaque apply est léger
// (~0.1 ms) — tunable en live sur l'asset : montez (8-16) si le remplissage traîne,
// baissez si l'apparition des chunks fait des à-coups.
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Voxel|Streaming")
int32 MaxMeshAppliesPerFrame = 4;
@@ -47,8 +51,10 @@ public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Voxel|LOD")
int32 LOD0Distance = 4;
// Distance en chunks pour LOD1 (demi-résolution, step=2).
// Au-delà → LOD2 (quart-résolution, step=4).
// Distance en chunks pour LOD1 (demi-résolution, step=2). Au-delà → LOD2 (quart-rés,
// step=4). LOD2 = le plus lointain ; ces chunks ne projettent PLUS d'ombre (cf.
// ApplyMeshToChunk) → rapprocher LOD0/LOD1 pousse plus de chunks dans la bande
// LOD2 sans-ombre = moins de draws (levier fps gratuit, à doser visuellement).
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Voxel|LOD")
int32 LOD1Distance = 8;
@@ -17,8 +17,11 @@
#include "Engine/DataAsset.h"
#include "GameplayTagContainer.h"
#include "VoxelStrateTypes.h"
#include "VoxelBiomeTypes.h"
#include "VoxelStrateDefinition.generated.h"
class UVoxelBiomeDefinition;
/**
* UVoxelStrateDefinition — The content bag for a strate type.
*
@@ -147,6 +150,24 @@ public:
meta = (EditCondition = "GeneratorType == ECaveGeneratorType::FloatingIslands"))
FFloatingIslandParams FloatingIslandParams;
//=========================================================================
// BIOMES (vary terrain & content WITHIN this strate — any archetype)
//=========================================================================
// Optional. When empty, this strate generates exactly as before (no biome field,
// bit-identical output). When populated, a deterministic world-XY biome field
// (warped Voronoi + climate, see FBiomeMapParams) assigns regions; each biome can
// supply its OWN archetype params (a mini-strate-variant, output-blended for surface)
// plus its own decorations / atmosphere / water. Surface terrain wired today;
// content/atmosphere work for any archetype.
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Strate|Biomes")
TArray<UVoxelBiomeDefinition*> Biomes;
// World-XY biome field tuning (cell size, border warp/blend, climate fields).
// Only relevant when Biomes is non-empty. Bake AVoxelWorld::BakeBiomePreview to
// see the resulting map before regenerating the world.
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Strate|Biomes")
FBiomeMapParams BiomeMapParams;
//=========================================================================
// DISTURBANCES (the "wow" layer — applies on top of ANY archetype)
//=========================================================================
@@ -227,6 +227,14 @@ public:
FVerticalShaftParams GetVerticalShaftParamsForChunk(const FIntVector& ChunkCoord) const;
FFloatingIslandParams GetFloatingIslandParamsForChunk(const FIntVector& ChunkCoord) const;
/**
* Flatten the strate's Biomes[] + BiomeMapParams into a POD FBiomeContext for the
* biome field. Returns an empty (invalid) context when the strate has no biomes —
* callers treat that as "biomes disabled" and fall back to base params. The result
* is window-invariant (depends only on the strate at this Z, not the chunk window).
*/
FBiomeContext GetBiomeContextForChunk(const FIntVector& ChunkCoord) const;
/**
* World-space Z (voxel coords) of this strate's water surface, or -FLT_MAX if the
* strate has no water. Derived from the active archetype's WaterLevelRelative and
+77 -1
View File
@@ -304,6 +304,16 @@ struct VOXELFORGE_API FStrateGenerationParams
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Worm Tunnels")
float WormStrength = 10.0f;
// Worms only carve within this distance (voxels) of the room/tunnel network, fading
// smoothly to zero at the edge. Keeps worms as organic braids and shortcuts that HUG
// the cave system instead of spraying disconnected noise pockets through the whole
// strate (the far-field "confetti"). 0 = unlimited (legacy unmasked behaviour).
// 16 → tight braiding right along rooms/tunnels
// 24 → braids + short noodle shortcuts (good default)
// 48+ → loose, wandering side-passages
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Worm Tunnels", meta = (ClampMin = "0.0"))
float WormNetworkRange = 24.0f;
// ===== CAVE MORPHOLOGY (room-and-corridor) =====
//
// Cave shape is defined by SDF (Signed Distance Field) primitives:
@@ -489,6 +499,16 @@ struct VOXELFORGE_API FStrateGenerationParams
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Cave Morphology|Tunnels", meta = (ClampMin = "0.0", ClampMax = "1.0"))
float TunnelHorizontalBias = 0.5f;
// Topology of the guaranteed tunnel network.
// true → each room links to its best candidate among rooms CLOSER to (0,0): the whole
// network becomes a tree rooted at the origin room — every cave is reachable
// from the spine hub, tunnels flow inward like tributaries (intentional descent
// structure). TunnelDensity still adds loops on top.
// false → legacy nearest-neighbor pairing: organic scattered clusters, but connectivity
// between clusters is NOT guaranteed (isolated pockets are common).
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Cave Morphology|Tunnels")
bool bTunnelsFlowTowardOrigin = true;
// How much tunnel endpoints shift up/down within rooms (0-1).
// Fraction of the room's vertical radius. Each tunnel endpoint gets
// a hash-derived Z offset, so tunnels enter rooms at different heights.
@@ -884,6 +904,7 @@ struct VOXELFORGE_API FStrateGenerationParams
Result.WormHorizontalBias = FMath::Lerp(A.WormHorizontalBias, B.WormHorizontalBias, Alpha);
Result.WormThreshold = FMath::Lerp(A.WormThreshold, B.WormThreshold, Alpha);
Result.WormStrength = FMath::Lerp(A.WormStrength, B.WormStrength, Alpha);
Result.WormNetworkRange = FMath::Lerp(A.WormNetworkRange, B.WormNetworkRange, Alpha);
// Cave morphology
Result.RoomSpacing = FMath::Lerp(A.RoomSpacing, B.RoomSpacing, Alpha);
Result.RoomDensity = FMath::Lerp(A.RoomDensity, B.RoomDensity, Alpha);
@@ -903,6 +924,7 @@ struct VOXELFORGE_API FStrateGenerationParams
Result.MaxTunnelLength = FMath::Lerp(A.MaxTunnelLength, B.MaxTunnelLength, Alpha);
Result.TunnelWarpStrength = FMath::Lerp(A.TunnelWarpStrength, B.TunnelWarpStrength, Alpha);
Result.TunnelHorizontalBias = FMath::Lerp(A.TunnelHorizontalBias, B.TunnelHorizontalBias, Alpha);
Result.bTunnelsFlowTowardOrigin = (Alpha < 0.5f) ? A.bTunnelsFlowTowardOrigin : B.bTunnelsFlowTowardOrigin;
Result.TunnelEndpointZOffset = FMath::Lerp(A.TunnelEndpointZOffset, B.TunnelEndpointZOffset, Alpha);
Result.SDFBlendRadius = FMath::Lerp(A.SDFBlendRadius, B.SDFBlendRadius, Alpha);
Result.WaterLevelRelative = FMath::Lerp(A.WaterLevelRelative, B.WaterLevelRelative, Alpha);
@@ -976,6 +998,7 @@ struct VOXELFORGE_API FStrateGenerationParams
// Forward declaration — the actual data asset lives in VoxelTerrainOpDefinition.h.
// We only need a soft pointer here, so no #include needed.
class UVoxelTerrainOpDefinition;
class UStaticMesh;
/**
* FStrateTerrainOpEntry — A reference to a terrain operation with a weight.
@@ -1276,6 +1299,43 @@ struct VOXELFORGE_API FSurfaceGenerationParams
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Surface|Shape", meta = (ClampMin = "0.0"))
float SurfaceRoughness = 3.0f;
// ----- Macro relief & landforms -----
// Domain-warp the heightfield query by this many voxels of XY displacement before
// sampling continents/mountains. Bends straight coastlines and ridgelines into winding,
// organic landforms. 0 = no warp (axis-aligned blobby noise, the old look).
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Surface|Macro", meta = (ClampMin = "0.0"))
float HeightWarpStrength = 35.0f;
// Frequency of the domain-warp noise. Lower = broader, sweeping bends.
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Surface|Macro")
float HeightWarpFrequency = 0.008f;
// Macro "relief" map frequency: a very-low-frequency field that makes some regions flat
// plains and others mountainous highlands — distinct terrain depending on where you
// stand. (The cheap, continuous precursor to a full biome system.) Lower = larger regions.
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Surface|Macro")
float ReliefFrequency = 0.0015f;
// How strongly the relief map modulates terrain (0-1). 0 = uniform everywhere (old
// behaviour); 1 = full plains <-> mountains variation across the world.
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Surface|Macro", meta = (ClampMin = "0.0", ClampMax = "1.0"))
float ReliefStrength = 0.7f;
// Contrast of the relief map. >1 sharpens the plains/highland boundary (more distinct
// regions); ~1 keeps it a gradual blend.
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Surface|Macro", meta = (ClampMin = "0.25", ClampMax = "4.0"))
float ReliefContrast = 1.6f;
// Plateau/mesa terracing strength (0-1), applied in high-relief regions only. 0 = off
// (smooth slopes). Crank up for stepped mesas and layered cliffs in the mountainous areas.
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Surface|Macro", meta = (ClampMin = "0.0", ClampMax = "1.0"))
float TerraceStrength = 0.0f;
// Height of each terrace step in voxels (when TerraceStrength > 0).
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Surface|Macro", meta = (ClampMin = "1.0"))
float TerraceHeight = 12.0f;
// ----- Water -----
// Water table height as a fraction of strate height (0 = no water). Valleys below
@@ -1600,10 +1660,26 @@ struct VOXELFORGE_API FStrateDecoration
{
GENERATED_BODY()
// The actor class to spawn (e.g., BP_Stalactite, BP_CrystalCluster)
// The actor class to spawn (e.g., BP_Stalactite, BP_CrystalCluster).
// Real actors: lights, logic, interaction. They cost game-thread time per instance —
// keep MaxLODLevel at 0 for these, and prefer InstancedMesh for pure visual props.
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Decoration")
TSubclassOf<AActor> ActorClass;
// INSTANCED path: if set, this entry renders as batched HISM instances instead of
// spawning ActorClass (which is then ignored). No tick, no per-actor overhead,
// engine-culled — orders of magnitude cheaper. Use for everything that doesn't need
// logic/lights/interaction; an emissive material still glows at distance without a light.
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Decoration")
UStaticMesh* InstancedMesh = nullptr;
// Spawn while the chunk's LOD <= this (0 = LOD0 only, the old behaviour).
// Lets instanced visual props persist on LOD1-2 chunks instead of popping out with
// LOD0. NOTE: placement samples the LOD's mesh vertices, so instances re-scatter
// slightly on LOD transitions (masked by the terrain's own LOD pop).
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Decoration", meta = (ClampMin = "0", ClampMax = "2"))
int32 MaxLODLevel = 0;
// Which surface type this decoration can be placed on
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Decoration")
ESurfaceType SurfacePlacement = ESurfaceType::Any;
+9 -3
View File
@@ -14,7 +14,13 @@
//
// Taille de chunk: 32^3 = 32 768 voxels.
// Pourquoi 32 ? Puissance de 2 → astuces bit à bit + bon alignement GPU.
// VOXEL_SIZE = 25 cm/voxel (Unreal travaille en centimètres).
// VOXEL_SIZE = 25 cm/voxel (Unreal travaille en centimètres). 1 chunk = 8 m.
//
// NOTE: 64³ a été essayé ("B", 2026-06-16) pour couper les draw calls (8× moins de
// chunks) mais le streaming devenait trop saccadé (briques 8× plus lourdes → applies
// + spawns de contenu en gros à-coups sur le game thread). Reverté à 32³ : le fps se
// règle côté RENDU (ombres off sur LOD lointain, voir ApplyMeshToChunk), pas via la
// taille de chunk. La taille de chunk reste le levier streaming-vs-draws si besoin.
constexpr int32 CHUNK_SIZE = 32;
constexpr int32 CHUNK_SIZE_SQUARED = CHUNK_SIZE * CHUNK_SIZE; // 1024
@@ -142,8 +148,8 @@ inline float SmoothStep01(float x)
return x * x * (3.0f - 2.0f * x);
}
// UE's PerlinNoise3D renvoie ~[-0.8, 0.8] — ce facteur remet à ~[-1, 1]
// pour correspondre aux attentes des formules de densité.
// Le coeur de bruit (VoxelNoise::Perlin3D, T2.a) renvoie ~[-0.8, 0.8] comme l'ancien
// FMath::PerlinNoise3D — ce facteur remet à ~[-1, 1] pour les formules de densité.
constexpr float VOXEL_NOISE_SCALE = 1.25f;
//=============================================================================
+31
View File
@@ -273,6 +273,37 @@ public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Voxel World|Debug")
bool bDebugDrawPassages = false;
//=========================================================================
// BIOME MAP PREVIEW (bake the XY biome field to a PNG — works without PIE)
//=========================================================================
// Tune biome layout (cell size, warp, climate boxes) without flying around: set
// the strate + window, click Bake, open Saved/BiomePreview.png. Uses a transient
// generator seeded from VoxelSettings, so it works in the editor with no PIE.
/** The strate whose Biomes[] + BiomeMapParams to preview. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Biome Preview")
UVoxelStrateDefinition* BiomePreviewStrate = nullptr;
/** Width/height of the sampled window in VOXELS (centred on BiomePreviewCenter). */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Biome Preview", meta = (ClampMin = "1.0"))
float BiomePreviewWorldSize = 16000.0f;
/** Centre of the preview window in voxel coords (X,Y). */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Biome Preview")
FVector2D BiomePreviewCenter = FVector2D::ZeroVector;
/** Output image resolution (pixels per side). */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Biome Preview", meta = (ClampMin = "64", ClampMax = "2048"))
int32 BiomePreviewResolution = 512;
/** Which field to visualise: biome debug colours, relief, or moisture. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Biome Preview")
EBiomePreviewChannel BiomePreviewChannel = EBiomePreviewChannel::Biome;
/** Bake the selected channel to Saved/BiomePreview.png. */
UFUNCTION(CallInEditor, BlueprintCallable, Category = "Biome Preview")
void BakeBiomePreview();
#if WITH_EDITOR
virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override;