Upload of all files, starting point
This commit is contained in:
@@ -0,0 +1,344 @@
|
||||
// MarchingCubesTables.h
|
||||
// Standard lookup tables for the Marching Cubes algorithm
|
||||
//
|
||||
// These tables are reference data — everyone uses the same ones.
|
||||
// Originally from Paul Bourke's Marching Cubes implementation.
|
||||
//
|
||||
// CUBE CORNER LAYOUT:
|
||||
// -------------------
|
||||
// 4-------5
|
||||
// /| /|
|
||||
// / | / |
|
||||
// 7-------6 |
|
||||
// | 0----|--1
|
||||
// | / | /
|
||||
// |/ |/
|
||||
// 3-------2
|
||||
//
|
||||
// Corner indices and their positions relative to (X, Y, Z):
|
||||
// 0: (0, 0, 0) 4: (0, 0, 1)
|
||||
// 1: (1, 0, 0) 5: (1, 0, 1)
|
||||
// 2: (1, 1, 0) 6: (1, 1, 1)
|
||||
// 3: (0, 1, 0) 7: (0, 1, 1)
|
||||
//
|
||||
// EDGE INDICES:
|
||||
// -------------
|
||||
// Edge 0: corner 0 - corner 1 (bottom front)
|
||||
// Edge 1: corner 1 - corner 2 (bottom right)
|
||||
// Edge 2: corner 2 - corner 3 (bottom back)
|
||||
// Edge 3: corner 3 - corner 0 (bottom left)
|
||||
// Edge 4: corner 4 - corner 5 (top front)
|
||||
// Edge 5: corner 5 - corner 6 (top right)
|
||||
// Edge 6: corner 6 - corner 7 (top back)
|
||||
// Edge 7: corner 7 - corner 4 (top left)
|
||||
// Edge 8: corner 0 - corner 4 (vertical left-front)
|
||||
// Edge 9: corner 1 - corner 5 (vertical right-front)
|
||||
// Edge 10: corner 2 - corner 6 (vertical right-back)
|
||||
// Edge 11: corner 3 - corner 7 (vertical left-back)
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
|
||||
// EdgeTable[caseIndex] = 12-bit bitmask
|
||||
// Each bit tells you if that edge is intersected by the surface.
|
||||
// Example: EdgeTable[1] = 0x109 = 0000 0001 0000 1001
|
||||
// → edges 0, 3, and 8 are intersected
|
||||
static const int32 MCEdgeTable[256] = {
|
||||
0x000, 0x109, 0x203, 0x30a, 0x406, 0x50f, 0x605, 0x70c,
|
||||
0x80c, 0x905, 0xa0f, 0xb06, 0xc0a, 0xd03, 0xe09, 0xf00,
|
||||
0x190, 0x099, 0x393, 0x29a, 0x596, 0x49f, 0x795, 0x69c,
|
||||
0x99c, 0x895, 0xb9f, 0xa96, 0xd9a, 0xc93, 0xf99, 0xe90,
|
||||
0x230, 0x339, 0x033, 0x13a, 0x636, 0x73f, 0x435, 0x53c,
|
||||
0xa3c, 0xb35, 0x83f, 0x936, 0xe3a, 0xf33, 0xc39, 0xd30,
|
||||
0x3a0, 0x2a9, 0x1a3, 0x0aa, 0x7a6, 0x6af, 0x5a5, 0x4ac,
|
||||
0xbac, 0xaa5, 0x9af, 0x8a6, 0xfaa, 0xea3, 0xda9, 0xca0,
|
||||
0x460, 0x569, 0x663, 0x76a, 0x066, 0x16f, 0x265, 0x36c,
|
||||
0xc6c, 0xd65, 0xe6f, 0xf66, 0x86a, 0x963, 0xa69, 0xb60,
|
||||
0x5f0, 0x4f9, 0x7f3, 0x6fa, 0x1f6, 0x0ff, 0x3f5, 0x2fc,
|
||||
0xdfc, 0xcf5, 0xfff, 0xef6, 0x9fa, 0x8f3, 0xbf9, 0xaf0,
|
||||
0x650, 0x759, 0x453, 0x55a, 0x256, 0x35f, 0x055, 0x15c,
|
||||
0xe5c, 0xf55, 0xc5f, 0xd56, 0xa5a, 0xb53, 0x859, 0x950,
|
||||
0x7c0, 0x6c9, 0x5c3, 0x4ca, 0x3c6, 0x2cf, 0x1c5, 0x0cc,
|
||||
0xfcc, 0xec5, 0xdcf, 0xcc6, 0xbca, 0xac3, 0x9c9, 0x8c0,
|
||||
0x8c0, 0x9c9, 0xac3, 0xbca, 0xcc6, 0xdcf, 0xec5, 0xfcc,
|
||||
0x0cc, 0x1c5, 0x2cf, 0x3c6, 0x4ca, 0x5c3, 0x6c9, 0x7c0,
|
||||
0x950, 0x859, 0xb53, 0xa5a, 0xd56, 0xc5f, 0xf55, 0xe5c,
|
||||
0x15c, 0x055, 0x35f, 0x256, 0x55a, 0x453, 0x759, 0x650,
|
||||
0xaf0, 0xbf9, 0x8f3, 0x9fa, 0xef6, 0xfff, 0xcf5, 0xdfc,
|
||||
0x2fc, 0x3f5, 0x0ff, 0x1f6, 0x6fa, 0x7f3, 0x4f9, 0x5f0,
|
||||
0xb60, 0xa69, 0x963, 0x86a, 0xf66, 0xe6f, 0xd65, 0xc6c,
|
||||
0x36c, 0x265, 0x16f, 0x066, 0x76a, 0x663, 0x569, 0x460,
|
||||
0xca0, 0xda9, 0xea3, 0xfaa, 0x8a6, 0x9af, 0xaa5, 0xbac,
|
||||
0x4ac, 0x5a5, 0x6af, 0x7a6, 0x0aa, 0x1a3, 0x2a9, 0x3a0,
|
||||
0xd30, 0xc39, 0xf33, 0xe3a, 0x936, 0x83f, 0xb35, 0xa3c,
|
||||
0x53c, 0x435, 0x73f, 0x636, 0x13a, 0x033, 0x339, 0x230,
|
||||
0xe90, 0xf99, 0xc93, 0xd9a, 0xa96, 0xb9f, 0x895, 0x99c,
|
||||
0x69c, 0x795, 0x49f, 0x596, 0x29a, 0x393, 0x099, 0x190,
|
||||
0xf00, 0xe09, 0xd03, 0xc0a, 0xb06, 0xa0f, 0x905, 0x80c,
|
||||
0x70c, 0x605, 0x50f, 0x406, 0x30a, 0x203, 0x109, 0x000
|
||||
};
|
||||
|
||||
// TriTable[caseIndex][up to 16]
|
||||
// Lists edge indices that form triangles, in groups of 3.
|
||||
// -1 terminates the list.
|
||||
// Example: TriTable[1] = {0, 8, 3, -1, ...}
|
||||
// → one triangle using edges 0, 8, and 3
|
||||
static const int32 MCTriTable[256][16] = {
|
||||
{-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 0, 1, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 1, 8, 3, 9, 8, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 0, 8, 3, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 9, 2, 10, 0, 2, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 2, 8, 3, 2, 10, 8, 10, 9, 8, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 0, 11, 2, 8, 11, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 1, 9, 0, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 1, 11, 2, 1, 9, 11, 9, 8, 11, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 3, 10, 1, 11, 10, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 0, 10, 1, 0, 8, 10, 8, 11, 10, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 3, 9, 0, 3, 11, 9, 11, 10, 9, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 9, 8, 10, 10, 8, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 4, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 4, 3, 0, 7, 3, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 0, 1, 9, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 4, 1, 9, 4, 7, 1, 7, 3, 1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 1, 2, 10, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 3, 4, 7, 3, 0, 4, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 9, 2, 10, 9, 0, 2, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 2, 10, 9, 2, 9, 7, 2, 7, 3, 7, 9, 4, -1, -1, -1, -1},
|
||||
{ 8, 4, 7, 3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{11, 4, 7, 11, 2, 4, 2, 0, 4, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 9, 0, 1, 8, 4, 7, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 4, 7, 11, 9, 4, 11, 9, 11, 2, 9, 2, 1, -1, -1, -1, -1},
|
||||
{ 3, 10, 1, 3, 11, 10, 7, 8, 4, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 1, 11, 10, 1, 4, 11, 1, 0, 4, 7, 11, 4, -1, -1, -1, -1},
|
||||
{ 4, 7, 8, 9, 0, 11, 9, 11, 10, 11, 0, 3, -1, -1, -1, -1},
|
||||
{ 4, 7, 11, 4, 11, 9, 9, 11, 10, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 9, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 9, 5, 4, 0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 0, 5, 4, 1, 5, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 8, 5, 4, 8, 3, 5, 3, 1, 5, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 1, 2, 10, 9, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 3, 0, 8, 1, 2, 10, 4, 9, 5, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 5, 2, 10, 5, 4, 2, 4, 0, 2, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 2, 10, 5, 3, 2, 5, 3, 5, 4, 3, 4, 8, -1, -1, -1, -1},
|
||||
{ 9, 5, 4, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 0, 11, 2, 0, 8, 11, 4, 9, 5, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 0, 5, 4, 0, 1, 5, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 2, 1, 5, 2, 5, 8, 2, 8, 11, 4, 8, 5, -1, -1, -1, -1},
|
||||
{10, 3, 11, 10, 1, 3, 9, 5, 4, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 4, 9, 5, 0, 8, 1, 8, 10, 1, 8, 11, 10, -1, -1, -1, -1},
|
||||
{ 5, 4, 0, 5, 0, 11, 5, 11, 10, 11, 0, 3, -1, -1, -1, -1},
|
||||
{ 5, 4, 8, 5, 8, 10, 10, 8, 11, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 9, 7, 8, 5, 7, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 9, 3, 0, 9, 5, 3, 5, 7, 3, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 0, 7, 8, 0, 1, 7, 1, 5, 7, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 1, 5, 3, 3, 5, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 9, 7, 8, 9, 5, 7, 10, 1, 2, -1, -1, -1, -1, -1, -1, -1},
|
||||
{10, 1, 2, 9, 5, 0, 5, 3, 0, 5, 7, 3, -1, -1, -1, -1},
|
||||
{ 8, 0, 2, 8, 2, 5, 8, 5, 7, 10, 5, 2, -1, -1, -1, -1},
|
||||
{ 2, 10, 5, 2, 5, 3, 3, 5, 7, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 7, 9, 5, 7, 8, 9, 3, 11, 2, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 9, 5, 7, 9, 7, 2, 9, 2, 0, 2, 7, 11, -1, -1, -1, -1},
|
||||
{ 2, 3, 11, 0, 1, 8, 1, 7, 8, 1, 5, 7, -1, -1, -1, -1},
|
||||
{11, 2, 1, 11, 1, 7, 7, 1, 5, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 9, 5, 8, 8, 5, 7, 10, 1, 3, 10, 3, 11, -1, -1, -1, -1},
|
||||
{ 5, 7, 0, 5, 0, 9, 7, 11, 0, 1, 0, 10, 11, 10, 0, -1},
|
||||
{11, 10, 0, 11, 0, 3, 10, 5, 0, 8, 0, 7, 5, 7, 0, -1},
|
||||
{11, 10, 5, 7, 11, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 0, 8, 3, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 9, 0, 1, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 1, 8, 3, 1, 9, 8, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 1, 6, 5, 2, 6, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 1, 6, 5, 1, 2, 6, 3, 0, 8, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 9, 6, 5, 9, 0, 6, 0, 2, 6, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 5, 9, 8, 5, 8, 2, 5, 2, 6, 3, 2, 8, -1, -1, -1, -1},
|
||||
{ 2, 3, 11, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{11, 0, 8, 11, 2, 0, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 0, 1, 9, 2, 3, 11, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 5, 10, 6, 1, 9, 2, 9, 11, 2, 9, 8, 11, -1, -1, -1, -1},
|
||||
{ 6, 3, 11, 6, 5, 3, 5, 1, 3, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 0, 8, 11, 0, 11, 5, 0, 5, 1, 5, 11, 6, -1, -1, -1, -1},
|
||||
{ 3, 11, 6, 0, 3, 6, 0, 6, 5, 0, 5, 9, -1, -1, -1, -1},
|
||||
{ 6, 5, 9, 6, 9, 11, 11, 9, 8, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 5, 10, 6, 4, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 4, 3, 0, 4, 7, 3, 6, 5, 10, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 1, 9, 0, 5, 10, 6, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1},
|
||||
{10, 6, 5, 1, 9, 7, 1, 7, 3, 7, 9, 4, -1, -1, -1, -1},
|
||||
{ 6, 1, 2, 6, 5, 1, 4, 7, 8, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 1, 2, 5, 5, 2, 6, 3, 0, 4, 3, 4, 7, -1, -1, -1, -1},
|
||||
{ 8, 4, 7, 9, 0, 5, 0, 6, 5, 0, 2, 6, -1, -1, -1, -1},
|
||||
{ 7, 3, 9, 7, 9, 4, 3, 2, 9, 5, 9, 6, 2, 6, 9, -1},
|
||||
{ 3, 11, 2, 7, 8, 4, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 5, 10, 6, 4, 7, 2, 4, 2, 0, 2, 7, 11, -1, -1, -1, -1},
|
||||
{ 0, 1, 9, 4, 7, 8, 2, 3, 11, 5, 10, 6, -1, -1, -1, -1},
|
||||
{ 9, 2, 1, 9, 11, 2, 9, 4, 11, 7, 11, 4, 5, 10, 6, -1},
|
||||
{ 8, 4, 7, 3, 11, 5, 3, 5, 1, 5, 11, 6, -1, -1, -1, -1},
|
||||
{ 5, 1, 11, 5, 11, 6, 1, 0, 11, 7, 11, 4, 0, 4, 11, -1},
|
||||
{ 0, 5, 9, 0, 6, 5, 0, 3, 6, 11, 6, 3, 8, 4, 7, -1},
|
||||
{ 6, 5, 9, 6, 9, 11, 4, 7, 9, 7, 11, 9, -1, -1, -1, -1},
|
||||
{10, 4, 9, 6, 4, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 4, 10, 6, 4, 9, 10, 0, 8, 3, -1, -1, -1, -1, -1, -1, -1},
|
||||
{10, 0, 1, 10, 6, 0, 6, 4, 0, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 8, 3, 1, 8, 1, 6, 8, 6, 4, 6, 1, 10, -1, -1, -1, -1},
|
||||
{ 1, 4, 9, 1, 2, 4, 2, 6, 4, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 3, 0, 8, 1, 2, 9, 2, 4, 9, 2, 6, 4, -1, -1, -1, -1},
|
||||
{ 0, 2, 4, 4, 2, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 8, 3, 2, 8, 2, 4, 4, 2, 6, -1, -1, -1, -1, -1, -1, -1},
|
||||
{10, 4, 9, 10, 6, 4, 11, 2, 3, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 0, 8, 2, 2, 8, 11, 4, 9, 10, 4, 10, 6, -1, -1, -1, -1},
|
||||
{ 3, 11, 2, 0, 1, 6, 0, 6, 4, 6, 1, 10, -1, -1, -1, -1},
|
||||
{ 6, 4, 1, 6, 1, 10, 4, 8, 1, 2, 1, 11, 8, 11, 1, -1},
|
||||
{ 9, 6, 4, 9, 3, 6, 9, 1, 3, 11, 6, 3, -1, -1, -1, -1},
|
||||
{ 8, 11, 1, 8, 1, 0, 11, 6, 1, 9, 1, 4, 6, 4, 1, -1},
|
||||
{ 3, 11, 6, 3, 6, 0, 0, 6, 4, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 6, 4, 8, 11, 6, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 7, 10, 6, 7, 8, 10, 8, 9, 10, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 0, 7, 3, 0, 10, 7, 0, 9, 10, 6, 7, 10, -1, -1, -1, -1},
|
||||
{10, 6, 7, 1, 10, 7, 1, 7, 8, 1, 8, 0, -1, -1, -1, -1},
|
||||
{10, 6, 7, 10, 7, 1, 1, 7, 3, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 1, 2, 6, 1, 6, 8, 1, 8, 9, 8, 6, 7, -1, -1, -1, -1},
|
||||
{ 2, 6, 9, 2, 9, 1, 6, 7, 9, 0, 9, 3, 7, 3, 9, -1},
|
||||
{ 7, 8, 0, 7, 0, 6, 6, 0, 2, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 7, 3, 2, 6, 7, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 2, 3, 11, 10, 6, 8, 10, 8, 9, 8, 6, 7, -1, -1, -1, -1},
|
||||
{ 2, 0, 7, 2, 7, 11, 0, 9, 7, 6, 7, 10, 9, 10, 7, -1},
|
||||
{ 1, 8, 0, 1, 7, 8, 1, 10, 7, 6, 7, 10, 2, 3, 11, -1},
|
||||
{11, 2, 1, 11, 1, 7, 10, 6, 1, 6, 7, 1, -1, -1, -1, -1},
|
||||
{ 8, 9, 6, 8, 6, 7, 9, 1, 6, 11, 6, 3, 1, 3, 6, -1},
|
||||
{ 0, 9, 1, 11, 6, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 7, 8, 0, 7, 0, 6, 3, 11, 0, 11, 6, 0, -1, -1, -1, -1},
|
||||
{ 7, 11, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 3, 0, 8, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 0, 1, 9, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 8, 1, 9, 8, 3, 1, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1},
|
||||
{10, 1, 2, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 1, 2, 10, 3, 0, 8, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 2, 9, 0, 2, 10, 9, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 6, 11, 7, 2, 10, 3, 10, 8, 3, 10, 9, 8, -1, -1, -1, -1},
|
||||
{ 7, 2, 3, 6, 2, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 7, 0, 8, 7, 6, 0, 6, 2, 0, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 2, 7, 6, 2, 3, 7, 0, 1, 9, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 1, 6, 2, 1, 8, 6, 1, 9, 8, 8, 7, 6, -1, -1, -1, -1},
|
||||
{10, 7, 6, 10, 1, 7, 1, 3, 7, -1, -1, -1, -1, -1, -1, -1},
|
||||
{10, 7, 6, 1, 7, 10, 1, 8, 7, 1, 0, 8, -1, -1, -1, -1},
|
||||
{ 0, 3, 7, 0, 7, 10, 0, 10, 9, 6, 10, 7, -1, -1, -1, -1},
|
||||
{ 7, 6, 10, 7, 10, 8, 8, 10, 9, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 6, 8, 4, 11, 8, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 3, 6, 11, 3, 0, 6, 0, 4, 6, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 8, 6, 11, 8, 4, 6, 9, 0, 1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 9, 4, 6, 9, 6, 3, 9, 3, 1, 11, 3, 6, -1, -1, -1, -1},
|
||||
{ 6, 8, 4, 6, 11, 8, 2, 10, 1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 1, 2, 10, 3, 0, 11, 0, 6, 11, 0, 4, 6, -1, -1, -1, -1},
|
||||
{ 4, 11, 8, 4, 6, 11, 0, 2, 9, 2, 10, 9, -1, -1, -1, -1},
|
||||
{10, 9, 3, 10, 3, 2, 9, 4, 3, 11, 3, 6, 4, 6, 3, -1},
|
||||
{ 8, 2, 3, 8, 4, 2, 4, 6, 2, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 0, 4, 2, 4, 6, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 1, 9, 0, 2, 3, 4, 2, 4, 6, 4, 3, 8, -1, -1, -1, -1},
|
||||
{ 1, 9, 4, 1, 4, 2, 2, 4, 6, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 8, 1, 3, 8, 6, 1, 8, 4, 6, 6, 10, 1, -1, -1, -1, -1},
|
||||
{10, 1, 0, 10, 0, 6, 6, 0, 4, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 4, 6, 3, 4, 3, 8, 6, 10, 3, 0, 3, 9, 10, 9, 3, -1},
|
||||
{10, 9, 4, 6, 10, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 4, 9, 5, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 0, 8, 3, 4, 9, 5, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 5, 0, 1, 5, 4, 0, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1},
|
||||
{11, 7, 6, 8, 3, 4, 3, 5, 4, 3, 1, 5, -1, -1, -1, -1},
|
||||
{ 9, 5, 4, 10, 1, 2, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 6, 11, 7, 1, 2, 10, 0, 8, 3, 4, 9, 5, -1, -1, -1, -1},
|
||||
{ 7, 6, 11, 5, 4, 10, 4, 2, 10, 4, 0, 2, -1, -1, -1, -1},
|
||||
{ 3, 4, 8, 3, 5, 4, 3, 2, 5, 10, 5, 2, 11, 7, 6, -1},
|
||||
{ 7, 2, 3, 7, 6, 2, 5, 4, 9, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 9, 5, 4, 0, 8, 6, 0, 6, 2, 6, 8, 7, -1, -1, -1, -1},
|
||||
{ 3, 6, 2, 3, 7, 6, 1, 5, 0, 5, 4, 0, -1, -1, -1, -1},
|
||||
{ 6, 2, 8, 6, 8, 7, 2, 1, 8, 4, 8, 5, 1, 5, 8, -1},
|
||||
{ 9, 5, 4, 10, 1, 6, 1, 7, 6, 1, 3, 7, -1, -1, -1, -1},
|
||||
{ 1, 6, 10, 1, 7, 6, 1, 0, 7, 8, 7, 0, 9, 5, 4, -1},
|
||||
{ 4, 0, 10, 4, 10, 5, 0, 3, 10, 6, 10, 7, 3, 7, 10, -1},
|
||||
{ 7, 6, 10, 7, 10, 8, 5, 4, 10, 4, 8, 10, -1, -1, -1, -1},
|
||||
{ 6, 9, 5, 6, 11, 9, 11, 8, 9, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 3, 6, 11, 0, 6, 3, 0, 5, 6, 0, 9, 5, -1, -1, -1, -1},
|
||||
{ 0, 11, 8, 0, 5, 11, 0, 1, 5, 5, 6, 11, -1, -1, -1, -1},
|
||||
{ 6, 11, 3, 6, 3, 5, 5, 3, 1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 1, 2, 10, 9, 5, 11, 9, 11, 8, 11, 5, 6, -1, -1, -1, -1},
|
||||
{ 0, 11, 3, 0, 6, 11, 0, 9, 6, 5, 6, 9, 1, 2, 10, -1},
|
||||
{11, 8, 5, 11, 5, 6, 8, 0, 5, 10, 5, 2, 0, 2, 5, -1},
|
||||
{ 6, 11, 3, 6, 3, 5, 2, 10, 3, 10, 5, 3, -1, -1, -1, -1},
|
||||
{ 5, 8, 9, 5, 2, 8, 5, 6, 2, 3, 8, 2, -1, -1, -1, -1},
|
||||
{ 9, 5, 6, 9, 6, 0, 0, 6, 2, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 1, 5, 8, 1, 8, 0, 5, 6, 8, 3, 8, 2, 6, 2, 8, -1},
|
||||
{ 1, 5, 6, 2, 1, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 1, 3, 6, 1, 6, 10, 3, 8, 6, 5, 6, 9, 8, 9, 6, -1},
|
||||
{10, 1, 0, 10, 0, 6, 9, 5, 0, 5, 6, 0, -1, -1, -1, -1},
|
||||
{ 0, 3, 8, 5, 6, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{10, 5, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{11, 5, 10, 7, 5, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{11, 5, 10, 11, 7, 5, 8, 3, 0, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 5, 11, 7, 5, 10, 11, 1, 9, 0, -1, -1, -1, -1, -1, -1, -1},
|
||||
{10, 7, 5, 10, 11, 7, 9, 8, 1, 8, 3, 1, -1, -1, -1, -1},
|
||||
{11, 1, 2, 11, 7, 1, 7, 5, 1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 0, 8, 3, 1, 2, 7, 1, 7, 5, 7, 2, 11, -1, -1, -1, -1},
|
||||
{ 9, 7, 5, 9, 2, 7, 9, 0, 2, 2, 11, 7, -1, -1, -1, -1},
|
||||
{ 7, 5, 2, 7, 2, 11, 5, 9, 2, 3, 2, 8, 9, 8, 2, -1},
|
||||
{ 2, 5, 10, 2, 3, 5, 3, 7, 5, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 8, 2, 0, 8, 5, 2, 8, 7, 5, 10, 2, 5, -1, -1, -1, -1},
|
||||
{ 9, 0, 1, 5, 10, 3, 5, 3, 7, 3, 10, 2, -1, -1, -1, -1},
|
||||
{ 9, 8, 2, 9, 2, 1, 8, 7, 2, 10, 2, 5, 7, 5, 2, -1},
|
||||
{ 1, 3, 5, 3, 7, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 0, 8, 7, 0, 7, 1, 1, 7, 5, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 9, 0, 3, 9, 3, 5, 5, 3, 7, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 9, 8, 7, 5, 9, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 5, 8, 4, 5, 10, 8, 10, 11, 8, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 5, 0, 4, 5, 11, 0, 5, 10, 11, 11, 3, 0, -1, -1, -1, -1},
|
||||
{ 0, 1, 9, 8, 4, 10, 8, 10, 11, 10, 4, 5, -1, -1, -1, -1},
|
||||
{10, 11, 4, 10, 4, 5, 11, 3, 4, 9, 4, 1, 3, 1, 4, -1},
|
||||
{ 2, 5, 1, 2, 8, 5, 2, 11, 8, 4, 5, 8, -1, -1, -1, -1},
|
||||
{ 0, 4, 11, 0, 11, 3, 4, 5, 11, 2, 11, 1, 5, 1, 11, -1},
|
||||
{ 0, 2, 5, 0, 5, 9, 2, 11, 5, 4, 5, 8, 11, 8, 5, -1},
|
||||
{ 9, 4, 5, 2, 11, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 2, 5, 10, 3, 5, 2, 3, 4, 5, 3, 8, 4, -1, -1, -1, -1},
|
||||
{ 5, 10, 2, 5, 2, 4, 4, 2, 0, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 3, 10, 2, 3, 5, 10, 3, 8, 5, 4, 5, 8, 0, 1, 9, -1},
|
||||
{ 5, 10, 2, 5, 2, 4, 1, 9, 2, 9, 4, 2, -1, -1, -1, -1},
|
||||
{ 8, 4, 5, 8, 5, 3, 3, 5, 1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 0, 4, 5, 1, 0, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 8, 4, 5, 8, 5, 3, 9, 0, 5, 0, 3, 5, -1, -1, -1, -1},
|
||||
{ 9, 4, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 4, 11, 7, 4, 9, 11, 9, 10, 11, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 0, 8, 3, 4, 9, 7, 9, 11, 7, 9, 10, 11, -1, -1, -1, -1},
|
||||
{ 1, 10, 11, 1, 11, 4, 1, 4, 0, 7, 4, 11, -1, -1, -1, -1},
|
||||
{ 3, 1, 4, 3, 4, 8, 1, 10, 4, 7, 4, 11, 10, 11, 4, -1},
|
||||
{ 4, 11, 7, 9, 11, 4, 9, 2, 11, 9, 1, 2, -1, -1, -1, -1},
|
||||
{ 9, 7, 4, 9, 11, 7, 9, 1, 11, 2, 11, 1, 0, 8, 3, -1},
|
||||
{11, 7, 4, 11, 4, 2, 2, 4, 0, -1, -1, -1, -1, -1, -1, -1},
|
||||
{11, 7, 4, 11, 4, 2, 8, 3, 4, 3, 2, 4, -1, -1, -1, -1},
|
||||
{ 2, 9, 10, 2, 7, 9, 2, 3, 7, 7, 4, 9, -1, -1, -1, -1},
|
||||
{ 9, 10, 7, 9, 7, 4, 10, 2, 7, 8, 7, 0, 2, 0, 7, -1},
|
||||
{ 3, 7, 10, 3, 10, 2, 7, 4, 10, 1, 10, 0, 4, 0, 10, -1},
|
||||
{ 1, 10, 2, 8, 7, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 4, 9, 1, 4, 1, 7, 7, 1, 3, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 4, 9, 1, 4, 1, 7, 0, 8, 1, 8, 7, 1, -1, -1, -1, -1},
|
||||
{ 4, 0, 3, 7, 4, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 4, 8, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 9, 10, 8, 10, 11, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 3, 0, 9, 3, 9, 11, 11, 9, 10, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 0, 1, 10, 0, 10, 8, 8, 10, 11, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 3, 1, 10, 11, 3, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 1, 2, 11, 1, 11, 9, 9, 11, 8, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 3, 0, 9, 3, 9, 11, 1, 2, 9, 2, 11, 9, -1, -1, -1, -1},
|
||||
{ 0, 2, 11, 8, 0, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 3, 2, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 2, 3, 8, 2, 8, 10, 10, 8, 9, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 9, 10, 2, 0, 9, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 2, 3, 8, 2, 8, 10, 0, 1, 8, 1, 10, 8, -1, -1, -1, -1},
|
||||
{ 1, 10, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 1, 3, 8, 9, 1, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 0, 9, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{ 0, 3, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1},
|
||||
{-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}
|
||||
};
|
||||
@@ -0,0 +1,62 @@
|
||||
// VoxelAtmosphereManager.h
|
||||
// Whole-strate ambiance: a managed height fog + skylight driven by the strate the
|
||||
// PLAYER is in, plus persistent ceiling/floor "layer" actors (e.g. seas of clouds)
|
||||
// that follow the player in XY and hug the strate boundaries.
|
||||
//
|
||||
// Unlike the per-chunk content spawner, this is PLAYER-scoped: it updates only when
|
||||
// the player changes strate, and the layer actors are single persistent instances —
|
||||
// no spawn/unload churn with chunk streaming.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "VoxelAtmosphereManager.generated.h"
|
||||
|
||||
class UVoxelStrateManager;
|
||||
class UVoxelStrateDefinition;
|
||||
class UExponentialHeightFogComponent;
|
||||
class USkyLightComponent;
|
||||
|
||||
UCLASS()
|
||||
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);
|
||||
|
||||
/** Call each frame with the player's world position. Cheap: only reacts on strate change. */
|
||||
void UpdateForPlayer(const FVector& PlayerWorldPos);
|
||||
|
||||
/** Tear down spawned layer actors + reset (season reset / shutdown). */
|
||||
void Reset();
|
||||
|
||||
private:
|
||||
void ApplyStrate(const UVoxelStrateDefinition* Def);
|
||||
|
||||
TWeakObjectPtr<AActor> Owner;
|
||||
|
||||
UPROPERTY()
|
||||
UVoxelStrateManager* StrateManager = nullptr;
|
||||
|
||||
UPROPERTY()
|
||||
UExponentialHeightFogComponent* Fog = nullptr;
|
||||
|
||||
UPROPERTY()
|
||||
USkyLightComponent* Sky = nullptr;
|
||||
|
||||
// Fully-authored atmosphere BP instance (replaces managed fog/sky when present).
|
||||
UPROPERTY()
|
||||
AActor* AtmosphereActorInstance = nullptr;
|
||||
|
||||
// Persistent per-strate layer actor instances (single each).
|
||||
UPROPERTY()
|
||||
AActor* CeilingActor = nullptr;
|
||||
|
||||
UPROPERTY()
|
||||
AActor* FloorActor = nullptr;
|
||||
|
||||
// Which strate's atmosphere is currently applied (INT32_MIN = none yet).
|
||||
int32 CurrentStrateIndex = INT32_MIN;
|
||||
};
|
||||
@@ -0,0 +1,395 @@
|
||||
// VoxelCaveMorphology.h
|
||||
// Hash-based cave room/tunnel generation and SDF evaluation.
|
||||
//
|
||||
// HOW IT WORKS:
|
||||
// =============
|
||||
// The world is divided into a coarse 2D grid (cell size = RoomSpacing).
|
||||
// Each cell deterministically either contains a room or doesn't, based on
|
||||
// a hash of (CellX, CellY, Seed, StrateIndex). No pre-computation needed —
|
||||
// room positions are derived on-the-fly from the hash, so this works for
|
||||
// infinite worlds without storing anything.
|
||||
//
|
||||
// ROOMS:
|
||||
// - Origin room: guaranteed large room at (0,0) per strate — the hub
|
||||
// - Hash rooms: ellipsoids, rounded boxes, or elongated capsules
|
||||
// - All blended with smooth-min for organic junctions
|
||||
//
|
||||
// TUNNELS:
|
||||
// - Connect pairs of rooms based on distance and probability
|
||||
// - Tapered (variable min/max radius at each endpoint)
|
||||
// - Curved (midpoint displaced perpendicular to tunnel direction)
|
||||
// - Horizontal bias (vertical connections penalized)
|
||||
// - Endpoint Z offset (tunnels enter rooms at different heights)
|
||||
// - Origin room forces connections to all nearby rooms
|
||||
//
|
||||
// PIPELINE POSITION:
|
||||
// Step 3 (cave warp) bends the SDF query coordinates before we evaluate.
|
||||
// This step (Step 4) builds the SDF skeleton of rooms + tunnels.
|
||||
// Surface roughness (Step 4b), terrain ops, and worm tunnels layer on top.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
|
||||
// Forward declarations
|
||||
struct FStrateGenerationParams;
|
||||
struct FStrateTerrainOpEntry;
|
||||
class UVoxelTerrainOpDefinition;
|
||||
|
||||
//=============================================================================
|
||||
// SDF PRIMITIVES
|
||||
//=============================================================================
|
||||
// Signed Distance Field functions.
|
||||
// Return value: negative = inside the shape, positive = outside.
|
||||
// The "distance" is how far from the surface — 0.0 = exactly on the surface.
|
||||
|
||||
namespace VoxelSDF
|
||||
{
|
||||
// Distance from point P to the surface of a sphere at Center with Radius.
|
||||
// Negative when P is inside the sphere.
|
||||
FORCEINLINE float Sphere(const FVector& P, const FVector& Center, float Radius)
|
||||
{
|
||||
return FVector::Dist(P, Center) - Radius;
|
||||
}
|
||||
|
||||
// Distance from P to an ellipsoid (squished sphere).
|
||||
// Radii = (RadiusX, RadiusY, RadiusZ) — different size per axis.
|
||||
// We scale the point into a unit sphere, compute distance, then scale back.
|
||||
// This isn't an exact SDF (distances are approximate near the surface),
|
||||
// but it's good enough for marching cubes density evaluation.
|
||||
FORCEINLINE float Ellipsoid(const FVector& P, const FVector& Center, const FVector& Radii)
|
||||
{
|
||||
// Transform to unit sphere space
|
||||
FVector Scaled = (P - Center) / Radii;
|
||||
float ScaledDist = Scaled.Size();
|
||||
// Approximate: scale the distance back by the average radius
|
||||
float AvgRadius = (Radii.X + Radii.Y + Radii.Z) / 3.0f;
|
||||
return (ScaledDist - 1.0f) * AvgRadius;
|
||||
}
|
||||
|
||||
// Distance from P to a capsule (line segment with thickness).
|
||||
// A and B are the endpoints, Radius is the tube thickness.
|
||||
// This is an EXACT SDF — used for tunnels.
|
||||
FORCEINLINE float Capsule(const FVector& P, const FVector& A, const FVector& B, float Radius)
|
||||
{
|
||||
FVector AB = B - A;
|
||||
FVector AP = P - A;
|
||||
// Project P onto line AB, clamped to [0,1] (stays within segment)
|
||||
float T = FMath::Clamp(FVector::DotProduct(AP, AB) / FMath::Max(FVector::DotProduct(AB, AB), KINDA_SMALL_NUMBER), 0.0f, 1.0f);
|
||||
// Closest point on the segment
|
||||
FVector Closest = A + AB * T;
|
||||
return FVector::Dist(P, Closest) - Radius;
|
||||
}
|
||||
|
||||
// Distance from P to a rounded box (box with rounded edges).
|
||||
// Center = box center, HalfExtent = half-size per axis, Rounding = edge radius.
|
||||
// This creates angular chambers (rectangular rooms with smooth corners).
|
||||
// Without rounding (Rounding=0), it's a sharp box.
|
||||
// With rounding, edges and corners are smoothed — essential for MC quality.
|
||||
FORCEINLINE float RoundedBox(const FVector& P, const FVector& Center, const FVector& HalfExtent, float Rounding)
|
||||
{
|
||||
// Vector from center to P, take absolute value (box symmetry)
|
||||
FVector D = FVector(
|
||||
FMath::Abs(P.X - Center.X),
|
||||
FMath::Abs(P.Y - Center.Y),
|
||||
FMath::Abs(P.Z - Center.Z)
|
||||
) - HalfExtent;
|
||||
|
||||
// Distance outside the box (positive when outside)
|
||||
float Outside = FVector(
|
||||
FMath::Max(D.X, 0.0f),
|
||||
FMath::Max(D.Y, 0.0f),
|
||||
FMath::Max(D.Z, 0.0f)
|
||||
).Size();
|
||||
|
||||
// Distance inside the box (negative when inside)
|
||||
float Inside = FMath::Min(FMath::Max(D.X, FMath::Max(D.Y, D.Z)), 0.0f);
|
||||
|
||||
return Outside + Inside - Rounding;
|
||||
}
|
||||
|
||||
// Distance from P to a tapered capsule (cone-like tube with spherical caps).
|
||||
// A and B are endpoints, RadiusA and RadiusB are the tube radius at each end.
|
||||
// When RadiusA != RadiusB, the tube narrows/widens along its length.
|
||||
// This creates natural-looking corridors that aren't perfectly uniform.
|
||||
FORCEINLINE float TaperedCapsule(const FVector& P, const FVector& A, const FVector& B, float RadiusA, float RadiusB)
|
||||
{
|
||||
FVector AB = B - A;
|
||||
FVector AP = P - A;
|
||||
float LenSq = FVector::DotProduct(AB, AB);
|
||||
// T = how far along AB the closest point is (0 = at A, 1 = at B)
|
||||
float T = (LenSq > KINDA_SMALL_NUMBER)
|
||||
? FMath::Clamp(FVector::DotProduct(AP, AB) / LenSq, 0.0f, 1.0f)
|
||||
: 0.0f;
|
||||
FVector Closest = A + AB * T;
|
||||
// Radius interpolates from RadiusA at A to RadiusB at B
|
||||
float R = FMath::Lerp(RadiusA, RadiusB, T);
|
||||
return FVector::Dist(P, Closest) - R;
|
||||
}
|
||||
|
||||
// Polynomial smooth minimum — blends two SDF shapes together.
|
||||
// K controls the blend radius: higher K = rounder, smoother junctions.
|
||||
// With K=0, this is just FMath::Min(A, B) (hard intersection).
|
||||
// With K=4, room/tunnel junctions look organic and natural.
|
||||
FORCEINLINE float SmoothMin(float A, float B, float K)
|
||||
{
|
||||
if (K <= 0.0f) return FMath::Min(A, B);
|
||||
float H = FMath::Max(K - FMath::Abs(A - B), 0.0f) / K;
|
||||
return FMath::Min(A, B) - H * H * H * K * (1.0f / 6.0f);
|
||||
}
|
||||
|
||||
// Polynomial smooth maximum — dual of SmoothMin, enforces the larger value softly.
|
||||
// Used for soft SDF intersections (e.g., floor cut planes) so the boundary
|
||||
// rounds off near tunnel/pit openings instead of creating a hard lip.
|
||||
// With K=0, this is just FMath::Max(A, B).
|
||||
FORCEINLINE float SmoothMax(float A, float B, float K)
|
||||
{
|
||||
return -SmoothMin(-A, -B, K);
|
||||
}
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
// HASH FUNCTIONS
|
||||
//=============================================================================
|
||||
// Deterministic integer hashing for room placement.
|
||||
// Given (CellX, CellY, Seed), always returns the same result.
|
||||
// No randomness — fully reproducible across sessions and clients.
|
||||
|
||||
namespace VoxelHash
|
||||
{
|
||||
// Mix a single uint32 — scrambles bits to reduce patterns
|
||||
FORCEINLINE uint32 Mix(uint32 X)
|
||||
{
|
||||
X ^= X >> 16;
|
||||
X *= 0x45d9f3bu;
|
||||
X ^= X >> 16;
|
||||
X *= 0x45d9f3bu;
|
||||
X ^= X >> 16;
|
||||
return X;
|
||||
}
|
||||
|
||||
// Hash a 2D cell coordinate with a seed → deterministic uint32
|
||||
FORCEINLINE uint32 Cell(int32 CellX, int32 CellY, uint32 Seed)
|
||||
{
|
||||
uint32 H = Seed;
|
||||
H ^= Mix((uint32)(CellX + 0x7FFFFFFF));
|
||||
H ^= Mix((uint32)(CellY + 0x7FFFFFFF) * 2654435761u);
|
||||
return Mix(H);
|
||||
}
|
||||
|
||||
// Hash a pair of cells (for tunnel connectivity decisions).
|
||||
// Order-independent: Cell(A,B) == Cell(B,A) so each tunnel is evaluated once.
|
||||
FORCEINLINE uint32 Pair(int32 AX, int32 AY, int32 BX, int32 BY, uint32 Seed)
|
||||
{
|
||||
// Sort the pair so (A,B) and (B,A) give the same hash
|
||||
int32 MinX = FMath::Min(AX, BX), MinY = FMath::Min(AY, BY);
|
||||
int32 MaxX = FMath::Max(AX, BX), MaxY = FMath::Max(AY, BY);
|
||||
uint32 H = Seed ^ 0xDEADBEEF;
|
||||
H ^= Mix((uint32)(MinX + 0x7FFFFFFF));
|
||||
H ^= Mix((uint32)(MinY + 0x7FFFFFFF) * 2654435761u);
|
||||
H ^= Mix((uint32)(MaxX + 0x7FFFFFFF) * 374761393u);
|
||||
H ^= Mix((uint32)(MaxY + 0x7FFFFFFF) * 668265263u);
|
||||
return Mix(H);
|
||||
}
|
||||
|
||||
// Convert a hash to a float in [0, 1) — uniform distribution
|
||||
FORCEINLINE float ToFloat01(uint32 Hash)
|
||||
{
|
||||
return (float)(Hash & 0xFFFFFF) / (float)0x1000000;
|
||||
}
|
||||
|
||||
// Convert a hash to a float in [-1, 1) — for centering
|
||||
FORCEINLINE float ToFloatSigned(uint32 Hash)
|
||||
{
|
||||
return ToFloat01(Hash) * 2.0f - 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
// PER-CHUNK SDF CACHE
|
||||
//=============================================================================
|
||||
// The room list and tunnel connections are IDENTICAL for all voxels in a chunk.
|
||||
// Without caching, EvaluateSDF rebuilds these 32,768 times per 32³ chunk:
|
||||
// - Hash every grid cell in the search area
|
||||
// - Determine room existence, position, size
|
||||
// - Run O(N²) nearest-neighbor backbone
|
||||
// - Decide O(N²) tunnel connections with hash lookups
|
||||
// - Compute tunnel radii, Z offsets, midpoint warping
|
||||
//
|
||||
// With caching: build once, evaluate 32K times with just SDF math.
|
||||
// This is the single biggest CPU performance win for cave generation.
|
||||
|
||||
// A room in the cache — everything needed for per-voxel SDF evaluation.
|
||||
// Internal details (CellX, CellY) used during cache building are NOT stored here.
|
||||
struct FCachedRoom
|
||||
{
|
||||
FVector Center; // World position of room center
|
||||
float RadiusXY; // Horizontal radius (used for SDF + shape selection)
|
||||
float RadiusZ; // Vertical radius (usually squished: RadiusXY * HeightRatio)
|
||||
uint32 Hash; // Cell hash — used for deterministic shape selection per voxel
|
||||
bool bIsOrigin; // True = origin room at (0,0), always uses ellipsoid shape
|
||||
float CullRadiusSq; // Squared distance beyond which this room can't affect a voxel
|
||||
|
||||
// Flat floor cut: base world Z of the soft floor plane.
|
||||
// Applied via SmoothMax so tunnels/pits pass through without a hard lip seam.
|
||||
// Set to -FLT_MAX when the hash-rolled floor cut = 1.0 (full bubble, no cut).
|
||||
float FloorCutZ;
|
||||
|
||||
// Floor relief: noise amplitude (voxels) and frequency for floor undulation.
|
||||
// 0 strength = perfectly flat floor (just the cut plane, no variation).
|
||||
// Stored per-room so EvaluateSDFCached can apply it without a params lookup.
|
||||
float FloorReliefStrength;
|
||||
float FloorReliefFrequency;
|
||||
uint32 FloorSeed; // Deterministic seed offset for this room's floor noise
|
||||
|
||||
// Per-room terrain operation, hash-rolled during BuildChunkCache from the
|
||||
// strate's probability pool (FStrateTerrainOpEntry::Probability).
|
||||
// null = this room has no terrain op (the "no op" slot in the probability pool).
|
||||
// Raw pointer is safe — assets stay loaded for the entire generation session.
|
||||
const UVoxelTerrainOpDefinition* RoomOp = nullptr;
|
||||
|
||||
// Intensity scale for this room's op (from FStrateTerrainOpEntry::Weight).
|
||||
// 1.0 = use op as configured, 0.5 = half intensity, 2.0 = double.
|
||||
float RoomOpWeight = 1.0f;
|
||||
};
|
||||
|
||||
// A pre-computed tunnel segment — all connection decisions and hash-derived
|
||||
// properties (radius, Z offset, midpoint warp) are resolved during cache build.
|
||||
struct FCachedTunnel
|
||||
{
|
||||
FVector EndpointA; // First room's connection point (with Z offset)
|
||||
FVector EndpointB; // Second room's connection point (with Z offset)
|
||||
float RadiusA; // Tube radius at endpoint A
|
||||
float RadiusB; // Tube radius at endpoint B
|
||||
// Warped midpoint — only used if bHasMidpoint is true.
|
||||
// Creates a two-segment curved path instead of a straight tube.
|
||||
FVector Midpoint;
|
||||
float RadiusMid; // Radius at the midpoint (average of A and B)
|
||||
bool bHasMidpoint; // True when TunnelWarpStrength > 0 and tunnel is long enough
|
||||
// Bounding sphere for quick per-voxel rejection
|
||||
FVector BoundCenter; // Center of the bounding sphere
|
||||
float BoundRadiusSq; // Squared radius — if voxel is further, skip this tunnel
|
||||
};
|
||||
|
||||
// A pre-baked pit shaft — position and dimensions resolved during BuildChunkCache.
|
||||
//
|
||||
// Pits are included in the main SDF evaluation (SmoothMin alongside rooms and
|
||||
// tunnels) rather than as a separate density subtraction. This lets SmoothMin
|
||||
// handle the pit-to-room junction organically — same mechanism as tunnel junctions.
|
||||
// No more hard seam at PitTopZ. BlendK drives the junction roundness and comes
|
||||
// from the strate's SDFBlendRadius, making it tweakable in the data asset.
|
||||
struct FCachedPit
|
||||
{
|
||||
float CenterX, CenterY; // XY center in world coords (unwarped)
|
||||
float TopZ; // World Z where the pit starts (anchored inside room air)
|
||||
float Radius; // Shaft cylinder radius
|
||||
float Depth; // Max depth the pit reaches below TopZ
|
||||
float FlareDist; // Over how many voxels the opening flares (Radius * 2)
|
||||
float FlareExtra; // Extra radius at the lip (Radius * 1)
|
||||
float BaseDensity; // Carving strength — matches the strate's BaseDensity
|
||||
float BlendK; // SmoothMin blend radius — set from Params.SDFBlendRadius
|
||||
float BoundXYRadiusSq; // XY rejection: skip if (dx^2+dy^2) > this
|
||||
};
|
||||
|
||||
// A pre-baked chimney shaft — inverse of a pit (carves upward from room ceiling).
|
||||
struct FCachedChimney
|
||||
{
|
||||
float CenterX, CenterY;
|
||||
float BottomZ; // World Z where the chimney starts (anchored inside room air)
|
||||
float Radius;
|
||||
float Height; // How far the chimney reaches above BottomZ
|
||||
float FlareDist;
|
||||
float FlareExtra;
|
||||
float BaseDensity;
|
||||
float BlendK; // SmoothMin blend radius — set from Params.SDFBlendRadius
|
||||
float BoundXYRadiusSq;
|
||||
};
|
||||
|
||||
// A pre-baked column — vertical solid cylinder inside a room.
|
||||
// Pre-baking fixes the NearestRoomIdx ownership issue for columns too:
|
||||
// a column's XY position is fixed, but which room "owns" the voxel can change
|
||||
// with depth, which would cause columns to appear/disappear mid-height.
|
||||
struct FCachedColumn
|
||||
{
|
||||
float CenterX, CenterY;
|
||||
float Radius;
|
||||
float BaseDensity;
|
||||
float BoundXYRadiusSq;
|
||||
};
|
||||
|
||||
// The complete SDF cache for a chunk region.
|
||||
// Built once per chunk by BuildChunkCache(), then passed to EvaluateSDFCached()
|
||||
// for every voxel in the chunk. Typically contains 5-15 rooms and 10-30 tunnels.
|
||||
struct FChunkSDFCache
|
||||
{
|
||||
TArray<FCachedRoom> Rooms;
|
||||
TArray<FCachedTunnel> Tunnels;
|
||||
TArray<FCachedPit> Pits;
|
||||
TArray<FCachedChimney> Chimneys;
|
||||
TArray<FCachedColumn> Columns;
|
||||
};
|
||||
|
||||
//=============================================================================
|
||||
// CAVE MORPHOLOGY EVALUATOR
|
||||
//=============================================================================
|
||||
// Two-phase evaluation: BuildChunkCache (once per chunk) + EvaluateSDFCached (per voxel).
|
||||
// The original EvaluateSDF is kept as a convenience wrapper for backward compatibility.
|
||||
|
||||
namespace VoxelCaveMorphology
|
||||
{
|
||||
// PHASE 1: Build the SDF cache for a rectangular region.
|
||||
// Collects all rooms, computes nearest-neighbor backbone, decides tunnel
|
||||
// connections, and pre-computes all tunnel geometry (radii, offsets, midpoints).
|
||||
// Also hash-rolls a terrain op per room from the optional probability pool.
|
||||
//
|
||||
// Call once per chunk before the voxel loop. The region bounds should cover
|
||||
// the chunk's XY extent PLUS CaveWarpStrength (warped queries can shift)
|
||||
// PLUS a small margin for gradient normal sampling (~2 voxels).
|
||||
// MaxInfluence (room/tunnel reach) is computed internally from Params.
|
||||
//
|
||||
// @param OutCache — filled with rooms and tunnels for this region
|
||||
// @param SearchMinX/Y, SearchMaxX/Y — XY world bounds to search (expanded by caller)
|
||||
// @param Params — strate generation parameters
|
||||
// @param Seed — world seed
|
||||
// @param StrateIndex — which strate (offsets hash for variety)
|
||||
// @param TerrainOps — optional probability pool; null = no per-room ops
|
||||
void BuildChunkCache(
|
||||
FChunkSDFCache& OutCache,
|
||||
float SearchMinX, float SearchMinY,
|
||||
float SearchMaxX, float SearchMaxY,
|
||||
const FStrateGenerationParams& Params,
|
||||
uint32 Seed, int32 StrateIndex,
|
||||
const TArray<FStrateTerrainOpEntry>* TerrainOps = nullptr
|
||||
);
|
||||
|
||||
// PHASE 2: Evaluate the SDF at a single world position using cached data.
|
||||
// Loops through cached rooms and tunnels, computes SDF primitives, applies
|
||||
// SmoothMin. Includes per-room/tunnel distance culling to skip primitives
|
||||
// that are too far to contribute.
|
||||
//
|
||||
// @param WorldX, WorldY, WorldZ — position in voxel coordinates (may be warped)
|
||||
// @param Cache — pre-built cache from BuildChunkCache
|
||||
// @param SDFBlendRadius — SmoothMin blend radius (from Params.SDFBlendRadius)
|
||||
// @param RoomShapeVariety — shape variety factor (from Params.RoomShapeVariety)
|
||||
// @param OutNearestRoomIdx — optional out: index of the room with minimum SDF
|
||||
// contribution. -1 if no room passed the cull test.
|
||||
// Used by the terrain ops system to look up the
|
||||
// per-room terrain op assigned to this voxel's room.
|
||||
// @return negative = inside cave, positive = solid rock
|
||||
float EvaluateSDFCached(
|
||||
float WorldX, float WorldY, float WorldZ,
|
||||
const FChunkSDFCache& Cache,
|
||||
float SDFBlendRadius,
|
||||
float RoomShapeVariety,
|
||||
int32* OutNearestRoomIdx = nullptr
|
||||
);
|
||||
|
||||
// CONVENIENCE WRAPPER: builds a temporary cache and evaluates in one call.
|
||||
// Use this for one-off queries (debug visualization, single-point sampling).
|
||||
// For chunk generation, use BuildChunkCache + EvaluateSDFCached instead.
|
||||
float EvaluateSDF(
|
||||
float WorldX, float WorldY, float WorldZ,
|
||||
const FStrateGenerationParams& Params,
|
||||
uint32 Seed, int32 StrateIndex
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
// VoxelChunk.h
|
||||
// Identifiant léger de chunk.
|
||||
//
|
||||
// Rôle: dans un monde density-only (pas de blocs), le chunk n'a plus rien
|
||||
// à stocker — la densité est évaluée à la volée par le générateur à partir
|
||||
// des coordonnées monde. On garde un struct fin pour:
|
||||
// - Servir de clé/valeur dans les collections de AVoxelWorld (Chunks, FChunkResult)
|
||||
// - Fournir l'helper GetWorldPosition() au mesher
|
||||
// - Laisser une place si on veut cacher des infos par chunk plus tard
|
||||
// (index de strate, LOD courant, etc.)
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "VoxelTypes.h"
|
||||
#include "VoxelChunk.generated.h"
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FVoxelChunk
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
// Coordonnée de chunk dans la grille mondiale (peut être négative).
|
||||
FIntVector ChunkCoord = FIntVector::ZeroValue;
|
||||
|
||||
FVoxelChunk() = default;
|
||||
explicit FVoxelChunk(const FIntVector& InCoord) : ChunkCoord(InCoord) {}
|
||||
|
||||
// Coin (0,0,0) du chunk en espace monde (cm).
|
||||
FVector GetWorldPosition() const
|
||||
{
|
||||
return ChunkToWorldPos(ChunkCoord);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,70 @@
|
||||
// VoxelContentManager.h
|
||||
// Per-chunk content population: deterministic decoration/actor scatter from the
|
||||
// strate content pools, plus self-contained aesthetic water surfaces.
|
||||
//
|
||||
// LIFECYCLE (driven by AVoxelWorld on the GAME THREAD):
|
||||
// - PopulateChunk(coord, meshdata) after a chunk's mesh is applied
|
||||
// - ClearChunk(coord) when a chunk unloads / is re-meshed
|
||||
// - ClearAll() on regenerate / season reset
|
||||
//
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "VoxelTypes.h"
|
||||
#include "VoxelContentManager.generated.h"
|
||||
|
||||
class UVoxelStrateManager;
|
||||
class UVoxelStrateDefinition;
|
||||
class UStaticMesh;
|
||||
class UStaticMeshComponent;
|
||||
|
||||
UCLASS()
|
||||
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);
|
||||
|
||||
/** 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. */
|
||||
void PopulateChunk(const FIntVector& ChunkCoord, const FVoxelMeshData& MeshData, int32 LODLevel = 0);
|
||||
|
||||
/** Destroy all spawned content (actors + water plane) for one chunk. */
|
||||
void ClearChunk(const FIntVector& ChunkCoord);
|
||||
|
||||
/** Destroy all spawned content for every chunk. */
|
||||
void ClearAll();
|
||||
|
||||
private:
|
||||
void SpawnDecorations(const FIntVector& ChunkCoord, const FVoxelMeshData& MeshData,
|
||||
const UVoxelStrateDefinition* Def, TArray<TWeakObjectPtr<AActor>>& Out);
|
||||
void SpawnWater(const FIntVector& ChunkCoord, const UVoxelStrateDefinition* Def);
|
||||
|
||||
TWeakObjectPtr<AActor> Owner;
|
||||
|
||||
UPROPERTY()
|
||||
UVoxelStrateManager* StrateManager = nullptr;
|
||||
|
||||
int32 Seed = 0;
|
||||
|
||||
// Engine unit plane (/Engine/BasicShapes/Plane) reused for every water surface.
|
||||
UPROPERTY()
|
||||
UStaticMesh* PlaneMesh = nullptr;
|
||||
|
||||
// Spawned decoration/ambient actors per chunk (weak — they live in the level).
|
||||
TMap<FIntVector, TArray<TWeakObjectPtr<AActor>>> SpawnedActors;
|
||||
|
||||
// Water surface component per chunk.
|
||||
UPROPERTY()
|
||||
TMap<FIntVector, UStaticMeshComponent*> WaterPlanes;
|
||||
};
|
||||
@@ -0,0 +1,260 @@
|
||||
// VoxelDiffLayer.h
|
||||
// Runtime terrain modification system (player carving & filling).
|
||||
//
|
||||
// HOW IT WORKS:
|
||||
// =============
|
||||
// The procedural density function generates cave terrain deterministically.
|
||||
// When the player carves or fills terrain, we DON'T modify the procedural
|
||||
// output — instead, we store modifications as a "diff layer" on top.
|
||||
//
|
||||
// During density evaluation:
|
||||
// Final density = Procedural density + Diff offset
|
||||
//
|
||||
// This keeps procedural generation deterministic while supporting freeform editing.
|
||||
// Modifications are stored spatially (per-chunk) for O(1) lookup.
|
||||
//
|
||||
// MODIFICATION LIFECYCLE:
|
||||
// 1. Player uses a tool at position P with radius R and strength S
|
||||
// 2. ApplyModification() stores the mod in all overlapping chunks
|
||||
// 3. Returns the list of affected chunk coords → world re-meshes those chunks
|
||||
// 4. During re-meshing, GetDensityOffset() returns the combined diff at each voxel
|
||||
//
|
||||
// PERSISTENCE:
|
||||
// Diffs are stored in memory. The game layer is responsible for saving/loading
|
||||
// them to disk (server-side) and clearing them on season reset.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "VoxelTypes.h"
|
||||
#include "VoxelDiffLayer.generated.h"
|
||||
|
||||
/**
|
||||
* EVoxelBrushShape — geometry of a modification brush.
|
||||
* Sphere : Center + Radius. Classic round brush.
|
||||
* Box : Center + BoxExtent (half-extents) + Falloff band.
|
||||
* Capsule : segment Center→CapsuleEnd + Radius (tube) + Falloff band. Good for tunnels.
|
||||
*/
|
||||
UENUM(BlueprintType)
|
||||
enum class EVoxelBrushShape : uint8
|
||||
{
|
||||
Sphere UMETA(DisplayName = "Sphere"),
|
||||
Box UMETA(DisplayName = "Box"),
|
||||
Capsule UMETA(DisplayName = "Capsule")
|
||||
};
|
||||
|
||||
/**
|
||||
* FVoxelModification — A single edit to the density field.
|
||||
*
|
||||
* Represents one brush stroke at a world position. Multiple overlapping
|
||||
* modifications combine additively.
|
||||
*
|
||||
* Strength convention (INTERNAL density, before MC negate):
|
||||
* Negative → subtract density → create air → CARVE (remove rock)
|
||||
* Positive → add density → create solid → FILL (add rock)
|
||||
*/
|
||||
USTRUCT(BlueprintType)
|
||||
struct VOXELFORGE_API FVoxelModification
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
// World-space center of the brush (endpoint A for Capsule).
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Modification")
|
||||
FVector Center = FVector::ZeroVector;
|
||||
|
||||
// Sphere radius / capsule tube radius, in voxels.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Modification", meta = (ClampMin = "0.5"))
|
||||
float Radius = 3.0f;
|
||||
|
||||
// Density change: negative = carve (remove rock), positive = fill (add rock).
|
||||
// -10 → strong carve (punches through BaseDensity=8) · +10 → strong fill
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Modification")
|
||||
float Strength = -10.0f;
|
||||
|
||||
// Brush geometry.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Modification")
|
||||
EVoxelBrushShape Shape = EVoxelBrushShape::Sphere;
|
||||
|
||||
// Box half-extents in voxels (Box shape only).
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Modification",
|
||||
meta = (EditCondition = "Shape == EVoxelBrushShape::Box"))
|
||||
FVector BoxExtent = FVector(5.0f, 5.0f, 5.0f);
|
||||
|
||||
// Capsule second endpoint in world voxel coords (Capsule shape only).
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Modification",
|
||||
meta = (EditCondition = "Shape == EVoxelBrushShape::Capsule"))
|
||||
FVector CapsuleEnd = FVector::ZeroVector;
|
||||
|
||||
// Soft falloff band (voxels) around the Box/Capsule surface for smooth edges.
|
||||
// Sphere ignores this — its whole Radius is the falloff.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Modification", meta = (ClampMin = "0.1"))
|
||||
float Falloff = 2.0f;
|
||||
|
||||
// Conservative world-space AABB the brush can affect (used to find overlapping chunks).
|
||||
void GetWorldBounds(FVector& OutMin, FVector& OutMax) const
|
||||
{
|
||||
switch (Shape)
|
||||
{
|
||||
case EVoxelBrushShape::Box:
|
||||
{
|
||||
const FVector E = BoxExtent + FVector(Falloff);
|
||||
OutMin = Center - E;
|
||||
OutMax = Center + E;
|
||||
break;
|
||||
}
|
||||
case EVoxelBrushShape::Capsule:
|
||||
{
|
||||
const float Pad = Radius + Falloff;
|
||||
OutMin = FVector(FMath::Min(Center.X, CapsuleEnd.X), FMath::Min(Center.Y, CapsuleEnd.Y), FMath::Min(Center.Z, CapsuleEnd.Z)) - FVector(Pad);
|
||||
OutMax = FVector(FMath::Max(Center.X, CapsuleEnd.X), FMath::Max(Center.Y, CapsuleEnd.Y), FMath::Max(Center.Z, CapsuleEnd.Z)) + FVector(Pad);
|
||||
break;
|
||||
}
|
||||
case EVoxelBrushShape::Sphere:
|
||||
default:
|
||||
OutMin = Center - FVector(Radius);
|
||||
OutMax = Center + FVector(Radius);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* UVoxelDiffLayer — Stores all player modifications as a spatial diff.
|
||||
*
|
||||
* Owned by AVoxelWorld, passed to the generator for density evaluation.
|
||||
* Modifications are grouped by chunk coord for fast spatial lookup —
|
||||
* when evaluating density for a chunk, we only check modifications
|
||||
* stored in that chunk (no global scan).
|
||||
*
|
||||
* When a modification sphere overlaps multiple chunks, it's stored in ALL
|
||||
* of them so each chunk's lookup is self-contained.
|
||||
*/
|
||||
UCLASS()
|
||||
class VOXELFORGE_API UVoxelDiffLayer : public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
//=========================================================================
|
||||
// BUDGET CONFIGURATION
|
||||
//=========================================================================
|
||||
|
||||
/**
|
||||
* Set the budget limits for player modifications.
|
||||
* Called by AVoxelWorld during BeginPlay from VoxelSettings values.
|
||||
* A value of 0 means unlimited (no cap).
|
||||
*
|
||||
* @param InMaxMods - Max number of operations (0 = unlimited)
|
||||
* @param InMaxRadius - Max brush radius per operation
|
||||
* @param InMaxVolume - Max cumulative brush volume in cubic voxels (0 = unlimited)
|
||||
*/
|
||||
void SetBudget(int32 InMaxMods, float InMaxRadius, float InMaxVolume);
|
||||
|
||||
/**
|
||||
* Check if a modification would be allowed under the current budget.
|
||||
* Does NOT consume budget — use this for UI feedback (greyed-out tool, etc.)
|
||||
*
|
||||
* @param Radius - The brush radius the player wants to use
|
||||
* @return true if the modification is allowed
|
||||
*/
|
||||
bool CanModify(float Radius) const;
|
||||
|
||||
/**
|
||||
* Get remaining modification count (how many more operations the player can do).
|
||||
* Returns -1 if unlimited.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Voxel Diff Layer")
|
||||
int32 GetRemainingModifications() const;
|
||||
|
||||
/**
|
||||
* Get remaining volume budget (cubic voxels of modification still allowed).
|
||||
* Returns -1.0 if unlimited.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Voxel Diff Layer")
|
||||
float GetRemainingVolume() const;
|
||||
|
||||
//=========================================================================
|
||||
// APPLY MODIFICATIONS
|
||||
//=========================================================================
|
||||
|
||||
/**
|
||||
* Apply a carve or fill operation.
|
||||
*
|
||||
* Stores the modification in every chunk the brush sphere overlaps.
|
||||
* Returns the list of affected chunk coordinates so the world knows
|
||||
* which chunks need re-meshing.
|
||||
*
|
||||
* Budget enforcement: if the modification would exceed any configured
|
||||
* limit (count, radius, or volume), it is REJECTED and an empty array
|
||||
* is returned. Check CanModify() first for UI feedback.
|
||||
*
|
||||
* @param Mod - The modification to apply
|
||||
* @return Array of chunk coordinates that need re-meshing (empty if rejected)
|
||||
*/
|
||||
TArray<FIntVector> ApplyModification(const FVoxelModification& Mod);
|
||||
|
||||
//=========================================================================
|
||||
// DENSITY EVALUATION
|
||||
//=========================================================================
|
||||
|
||||
/**
|
||||
* Get the combined density offset from all modifications at a world position.
|
||||
*
|
||||
* Called per-voxel by the generator during density evaluation.
|
||||
* Only checks modifications stored for the given chunk coordinate,
|
||||
* so it's fast when most chunks have no modifications.
|
||||
*
|
||||
* Each modification contributes with a smoothstep falloff from center to edge.
|
||||
* Multiple overlapping modifications combine additively.
|
||||
*
|
||||
* @param ChunkCoord - The chunk being evaluated (for spatial lookup)
|
||||
* @param WorldX, WorldY, WorldZ - The voxel position
|
||||
* @return Total density offset (negative = carve, positive = fill)
|
||||
*/
|
||||
float GetDensityOffset(const FIntVector& ChunkCoord,
|
||||
float WorldX, float WorldY, float WorldZ) const;
|
||||
|
||||
/**
|
||||
* Check if a chunk has any modifications (fast reject for hot path).
|
||||
*
|
||||
* @param ChunkCoord - The chunk to check
|
||||
* @return true if there are modifications affecting this chunk
|
||||
*/
|
||||
bool HasModifications(const FIntVector& ChunkCoord) const;
|
||||
|
||||
//=========================================================================
|
||||
// MANAGEMENT
|
||||
//=========================================================================
|
||||
|
||||
/** Clear ALL modifications (e.g., on season reset). */
|
||||
void Clear();
|
||||
|
||||
/** Get total number of stored modification entries (for debug/stats). */
|
||||
int32 GetTotalModificationCount() const;
|
||||
|
||||
/** Get number of chunks that have modifications. */
|
||||
int32 GetModifiedChunkCount() const;
|
||||
|
||||
private:
|
||||
//=========================================================================
|
||||
// STORAGE
|
||||
//=========================================================================
|
||||
|
||||
// Modifications grouped by chunk coordinate.
|
||||
// Each chunk's list contains ALL modifications that overlap it
|
||||
// (including mods centered in neighboring chunks whose radius reaches here).
|
||||
TMap<FIntVector, TArray<FVoxelModification>> ChunkMods;
|
||||
|
||||
//=========================================================================
|
||||
// BUDGET TRACKING
|
||||
//=========================================================================
|
||||
|
||||
// Configured limits (0 = unlimited)
|
||||
int32 BudgetMaxMods = 0;
|
||||
float BudgetMaxRadius = 50.0f;
|
||||
float BudgetMaxVolume = 0.0f;
|
||||
|
||||
// Running totals — reset when Clear() is called
|
||||
int32 ModificationCount = 0; // Total operations applied
|
||||
float AccumulatedVolume = 0.0f; // Sum of all brush sphere volumes (4/3 * PI * R^3)
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
// VoxelForgeModule.h
|
||||
// Module interface - this is boilerplate that every Unreal plugin needs
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Modules/ModuleManager.h"
|
||||
|
||||
/**
|
||||
* FVoxelForgeModule
|
||||
*
|
||||
* This is the module class for VoxelForge. Every Unreal plugin needs one.
|
||||
* It handles startup and shutdown of the plugin.
|
||||
*
|
||||
* For our purposes, this is just boilerplate - the real code is elsewhere.
|
||||
*/
|
||||
class FVoxelForgeModule : public IModuleInterface
|
||||
{
|
||||
public:
|
||||
// Called when the plugin loads (game/editor starts)
|
||||
virtual void StartupModule() override;
|
||||
|
||||
// Called when the plugin unloads (game/editor closes)
|
||||
virtual void ShutdownModule() override;
|
||||
};
|
||||
@@ -0,0 +1,123 @@
|
||||
// VoxelGenerator.h
|
||||
// Fournit le champ de densité du monde (positif = solide, négatif = air).
|
||||
//
|
||||
// Pipeline density-only: plus de grille de blocs, plus de surface heightfield.
|
||||
// Le mesher appelle GetDensityAt() par voxel pour reconstruire la géométrie.
|
||||
// Toute la logique de caves vit dans GetDensityWithParams / GetSlabDensity,
|
||||
// pilotée par les params de strate récupérés auprès du StrateManager.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "VoxelTypes.h"
|
||||
#include "VoxelStrateTypes.h"
|
||||
#include "VoxelGenerator.generated.h"
|
||||
|
||||
// Forward decls (évite les includes transitifs)
|
||||
class UVoxelSettings;
|
||||
class UVoxelStrateManager;
|
||||
class UVoxelDiffLayer;
|
||||
|
||||
/**
|
||||
* UVoxelGenerator
|
||||
*
|
||||
* Objet UObject léger — ne stocke pas de données lourdes.
|
||||
* Tient juste un pointeur sur les settings (pour le seed) et les services
|
||||
* dont il a besoin (StrateManager pour les params par chunk, DiffLayer pour
|
||||
* les carves du joueur).
|
||||
*/
|
||||
UCLASS(BlueprintType)
|
||||
class VOXELFORGE_API UVoxelGenerator : public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
//=========================================================================
|
||||
// SEED (source unique: Settings->Seed)
|
||||
//=========================================================================
|
||||
// On copie juste le seed au démarrage pour éviter un déréférencement
|
||||
// par voxel. Tout le reste vient des params de strate.
|
||||
int32 Seed = 0;
|
||||
|
||||
// Radius (voxels) of the guaranteed open vertical landing column carved at world
|
||||
// XY (0,0) in every strate — the (0,0) descent spine. Copied from VoxelSettings.
|
||||
// 0 disables the spine carve.
|
||||
float OriginSpineRadius = 14.0f;
|
||||
|
||||
//=========================================================================
|
||||
// SERVICES (injectés par AVoxelWorld::BeginPlay)
|
||||
//=========================================================================
|
||||
|
||||
// Manager des strates — fournit les params de cave par chunk.
|
||||
// Peut être nullptr: dans ce cas on utilise des params par défaut.
|
||||
UPROPERTY()
|
||||
const UVoxelStrateManager* StrateManager = nullptr;
|
||||
|
||||
// Diff layer des modifications joueur (carve/fill).
|
||||
// Peut être nullptr: dans ce cas pas de modifs appliquées.
|
||||
UPROPERTY()
|
||||
const UVoxelDiffLayer* DiffLayer = nullptr;
|
||||
|
||||
void SetStrateManager(const UVoxelStrateManager* InManager) { StrateManager = InManager; }
|
||||
void SetDiffLayer(const UVoxelDiffLayer* InDiffLayer) { DiffLayer = InDiffLayer; }
|
||||
|
||||
// Copie le seed depuis les settings. Appelé au démarrage et lors d'un ChangeSeed.
|
||||
void InitializeSettings(const UVoxelSettings* Settings);
|
||||
|
||||
//=========================================================================
|
||||
// API DE DENSITÉ
|
||||
//=========================================================================
|
||||
|
||||
/**
|
||||
* Densité combinée (strates + diff du joueur).
|
||||
*
|
||||
* Convention de sortie (MC): négatif = solide, positif = air.
|
||||
* C'est ce que le marching cubes attend comme champ scalaire.
|
||||
*
|
||||
* @param WorldX, WorldY, WorldZ - coordonnées monde en voxels (pas cm)
|
||||
*/
|
||||
float GetDensityAt(float WorldX, float WorldY, float WorldZ) const;
|
||||
|
||||
/**
|
||||
* Densité pour une strate TunnelNetwork (rooms + tunnels + worm noise).
|
||||
* Utilisée en interne par GetDensityAt quand la strate est de ce type.
|
||||
* Exposée pour permettre des tests isolés avec des params custom.
|
||||
*/
|
||||
float GetDensityWithParams(float WorldX, float WorldY, float WorldZ,
|
||||
const FStrateGenerationParams& Params) const;
|
||||
|
||||
/**
|
||||
* Densité pour une strate Slab (FlatPlain / CrystalChamber).
|
||||
* Vide horizontal entre un sol bruité et un plafond bruité.
|
||||
*/
|
||||
float GetSlabDensity(float WorldX, float WorldY, float WorldZ,
|
||||
const FSlabGenerationParams& Params) const;
|
||||
|
||||
/**
|
||||
* Densité pour une strate Maze — couloirs étroits sur un treillis 3D déterministe.
|
||||
* Pas de cache: évalué par voxel sur les quelques cellules voisines.
|
||||
*/
|
||||
float GetMazeDensity(float WorldX, float WorldY, float WorldZ,
|
||||
const FMazeGenerationParams& Params) const;
|
||||
|
||||
/**
|
||||
* Densité pour une strate SurfaceWorld — terrain à ciel ouvert (collines,
|
||||
* montagnes, plages) sous un plafond solide, avec nappe d'eau optionnelle.
|
||||
*/
|
||||
float GetSurfaceDensity(float WorldX, float WorldY, float WorldZ,
|
||||
const FSurfaceGenerationParams& Params) const;
|
||||
|
||||
/**
|
||||
* Densité pour une strate VerticalShafts — puits verticaux pleine hauteur,
|
||||
* vires horizontales, et connecteurs horizontaux occasionnels.
|
||||
*/
|
||||
float GetVerticalShaftDensity(float WorldX, float WorldY, float WorldZ,
|
||||
const FVerticalShaftParams& Params) const;
|
||||
|
||||
/**
|
||||
* Densité pour une strate FloatingIslands — masses de terre suspendues dans
|
||||
* un grand vide ouvert (îles flottantes), sommets aplatis, dessous rugueux.
|
||||
*/
|
||||
float GetFloatingIslandDensity(float WorldX, float WorldY, float WorldZ,
|
||||
const FFloatingIslandParams& Params) const;
|
||||
};
|
||||
@@ -0,0 +1,71 @@
|
||||
// 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 "VoxelTypes.h" // Pour FVoxelMeshData, CHUNK_SIZE, VOXEL_SIZE, etc.
|
||||
#include "VoxelChunk.h"
|
||||
#include "VoxelGenerator.h"
|
||||
#include "VoxelMarchingCubesMesher.generated.h"
|
||||
|
||||
UCLASS(BlueprintType)
|
||||
class VOXELFORGE_API UVoxelMarchingCubesMesher : public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Génère le mesh d'un chunk.
|
||||
*
|
||||
* @param Chunk - Le chunk à mesher (on n'utilise que ChunkCoord pour
|
||||
* calculer les positions monde; la densité vient du générateur).
|
||||
* @param Step - Pas d'échantillonnage (1=LOD0, 2=LOD1, 4=LOD2).
|
||||
*/
|
||||
FVoxelMeshData GenerateMesh(const FVoxelChunk& Chunk, int32 Step = 1);
|
||||
|
||||
//=========================================================================
|
||||
// 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; }
|
||||
|
||||
//=========================================================================
|
||||
// SETTINGS
|
||||
//=========================================================================
|
||||
|
||||
// Seuil de l'isosurface dans le champ de densité.
|
||||
// Convention MC: densité < IsoLevel = solide, >= = air.
|
||||
float IsoLevel = 0.0f;
|
||||
|
||||
// Distance d'échantillonnage (en voxels) pour calculer la normale par
|
||||
// différence centrée du gradient. Plus petit = plus détaillé mais bruité.
|
||||
float GradientOffset = 1.0f;
|
||||
|
||||
protected:
|
||||
//=========================================================================
|
||||
// DENSITY + NORMAL SAMPLING
|
||||
//=========================================================================
|
||||
|
||||
// Lit la densité à une position locale (via le générateur en coords monde).
|
||||
float GetDensity(const FVoxelChunk& Chunk, int32 X, int32 Y, int32 Z) const;
|
||||
|
||||
// Normale lissée: gradient central du champ de densité (pointe solide→air).
|
||||
FVector ComputeGradientNormal(float WorldX, float WorldY, float WorldZ) const;
|
||||
|
||||
// Interpolation linéaire le long d'une arête: trouve où la surface
|
||||
// traverse entre P1 (densité D1) et P2 (densité D2).
|
||||
FVector InterpolateEdge(const FVector& P1, const FVector& P2, float D1, float D2) const;
|
||||
};
|
||||
@@ -0,0 +1,137 @@
|
||||
// VoxelSettings.h
|
||||
// Settings du plugin VoxelForge.
|
||||
//
|
||||
// Data asset unique (assigné sur AVoxelWorld) qui regroupe tous les tuning
|
||||
// du monde: seed, streaming, LOD, pool de strates, budgets de carving.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Engine/DataAsset.h"
|
||||
#include "VoxelStrateDefinition.h"
|
||||
#include "VoxelSettings.generated.h"
|
||||
|
||||
UCLASS(BlueprintType)
|
||||
class UVoxelSettings : public UPrimaryDataAsset
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
|
||||
//=========================================================================
|
||||
// STREAMING (distance de vue)
|
||||
//=========================================================================
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Voxel|Streaming")
|
||||
int32 ViewDistanceXY = 16;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Voxel|Streaming")
|
||||
int32 ViewDistanceUp = 5;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Voxel|Streaming")
|
||||
int32 ViewDistanceDown = 5;
|
||||
|
||||
// Nombre max de tâches de génération/mesh en parallèle.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Voxel|Streaming")
|
||||
int32 MaxConcurrentTasks = 16;
|
||||
|
||||
// Nombre max de meshes appliqués par frame (évite les stutters d'upload GPU).
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Voxel|Streaming")
|
||||
int32 MaxMeshAppliesPerFrame = 4;
|
||||
|
||||
//=========================================================================
|
||||
// LOD
|
||||
//=========================================================================
|
||||
|
||||
// Distance en chunks pour LOD0 (pleine résolution, step=1).
|
||||
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).
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Voxel|LOD")
|
||||
int32 LOD1Distance = 8;
|
||||
|
||||
//=========================================================================
|
||||
// RENDERING
|
||||
//=========================================================================
|
||||
|
||||
// Matériau global par défaut (peut être override par strate).
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Voxel|Rendering")
|
||||
UMaterialInterface* VoxelMaterial;
|
||||
|
||||
//=========================================================================
|
||||
// STRATES
|
||||
//=========================================================================
|
||||
|
||||
// Seed du monde. Pilote toute la génération procédurale.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Voxel|Strates")
|
||||
int32 Seed = 0;
|
||||
|
||||
// Numéro de saison (incrementé par ChangeSeed). Purement informatif côté gameplay.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Voxel|Strates")
|
||||
int32 CurrentSeason = 1;
|
||||
|
||||
// Pool de définitions de strates disponibles pour l'assignation aléatoire.
|
||||
// Chaque définition a sa propre hauteur — les strates ne sont PAS uniformes.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Voxel|Strates")
|
||||
TArray<TSoftObjectPtr<UVoxelStrateDefinition>> StratePool;
|
||||
|
||||
// Strates fixées: {index → définition}. Ex: {5, CrystalCaverns} =
|
||||
// la strate 5 est toujours CrystalCaverns. Les autres sont tirées du pool.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Voxel|Strates")
|
||||
TMap<int32, TSoftObjectPtr<UVoxelStrateDefinition>> FixedStrates;
|
||||
|
||||
// Nombre total de strates à générer vers le bas.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Voxel|Strates", meta = (ClampMin = "1"))
|
||||
int32 TotalStrates = 10;
|
||||
|
||||
// Vertical SEPARATION between consecutive strates, in chunks of solid bedrock.
|
||||
// 0 = strates touch (just the seals between them). Higher = a thick rock layer
|
||||
// between each biome that the player must dig through at (0,0), and that the
|
||||
// auto-carved passages tunnel across.
|
||||
// 0 → adjacent layers (default) · 1-2 → a real bedrock band · 4+ → deep separation
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Voxel|Strates", meta = (ClampMin = "0", ClampMax = "16"))
|
||||
int32 InterStrateGapChunks = 0;
|
||||
|
||||
//=========================================================================
|
||||
// SPINE & INTER-STRATE CONNECTIONS (the (0,0) descent axis)
|
||||
//=========================================================================
|
||||
|
||||
// Radius (voxels) of the guaranteed open vertical "landing" column carved at
|
||||
// world XY (0,0) in EVERY strate, regardless of archetype. This is the prepared
|
||||
// space the player digs THROUGH the seal into when descending. 0 = disabled.
|
||||
// 10-14 → cozy shaft (default) · 20+ → wide landing chamber
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Voxel|Spine", meta = (ClampMin = "0.0"))
|
||||
float OriginSpineRadius = 14.0f;
|
||||
|
||||
// Auto-open the very top seal at (0,0) so the world starts with a hole to the
|
||||
// surface (the entry shaft). Lower boundaries are NOT auto-opened — the player
|
||||
// digs those. Disable for a fully sealed world the player must breach from above.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Voxel|Spine")
|
||||
bool bOpenSurfaceEntry = true;
|
||||
|
||||
// NOTE: inter-strate tunnel count + shape (style, width, length, placement, worming,
|
||||
// spiral, cascade) are now PER-STRATE — see UVoxelStrateDefinition::PassageConfig.
|
||||
// Each strate controls its own descent tunnels to the layer below it.
|
||||
|
||||
//=========================================================================
|
||||
// CARVING (modifications du joueur)
|
||||
//=========================================================================
|
||||
|
||||
// Nombre max d'opérations de modification par saison. 0 = illimité.
|
||||
// 500 = édition légère, 2000 = mining modéré, 0 = créatif.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Voxel|Carving",
|
||||
meta = (ClampMin = "0"))
|
||||
int32 MaxModifications = 0;
|
||||
|
||||
// Rayon max d'un brush (en voxels). Empêche les édits géants.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Voxel|Carving",
|
||||
meta = (ClampMin = "1.0"))
|
||||
float MaxBrushRadius = 15.0f;
|
||||
|
||||
// Volume total maximum (somme des volumes de brush). 0 = illimité.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Voxel|Carving",
|
||||
meta = (ClampMin = "0.0"))
|
||||
float MaxTotalVolume = 0.0f;
|
||||
};
|
||||
@@ -0,0 +1,335 @@
|
||||
// VoxelStrateDefinition.h
|
||||
// Data asset defining everything about a single strate (layer) type.
|
||||
//
|
||||
// HOW TO USE:
|
||||
// -----------
|
||||
// 1. Right-click in Content Browser → Miscellaneous → Data Asset
|
||||
// 2. Pick "VoxelStrateDefinition" as the class
|
||||
// 3. Fill in the fields — cave shape, visuals, content lists, audio, gameplay tags
|
||||
// 4. Reference it from VoxelSettings (StratePool or FixedStrates)
|
||||
//
|
||||
// Each asset is a strate TYPE (e.g., "CrystalCaverns").
|
||||
// Multiple strate slots can use the same definition.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Engine/DataAsset.h"
|
||||
#include "GameplayTagContainer.h"
|
||||
#include "VoxelStrateTypes.h"
|
||||
#include "VoxelStrateDefinition.generated.h"
|
||||
|
||||
/**
|
||||
* UVoxelStrateDefinition — The content bag for a strate type.
|
||||
*
|
||||
* This is the central piece of the strate system. Every field here
|
||||
* defines what makes a strate unique: how its caves form, what it
|
||||
* looks like, what lives in it, how it sounds, and what gameplay
|
||||
* rules apply.
|
||||
*
|
||||
* EXAMPLE STRATES:
|
||||
* - "Crystal Caverns": big open caves, blue fog, crystal decorations
|
||||
* - "Tight Tunnels": narrow caves, dark, spider creatures
|
||||
* - "Flooded Galleries": flat floors, water, diving required
|
||||
*/
|
||||
UCLASS(BlueprintType)
|
||||
class VOXELFORGE_API UVoxelStrateDefinition : public UPrimaryDataAsset
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
//=========================================================================
|
||||
// IDENTIFICATION
|
||||
//=========================================================================
|
||||
|
||||
// Human-readable name for this strate type (shown in editor and debug)
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Strate")
|
||||
FText StrateName;
|
||||
|
||||
// Short description for editor tooltips
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Strate", meta = (MultiLine = true))
|
||||
FText StrateDescription;
|
||||
|
||||
//=========================================================================
|
||||
// DIMENSIONS
|
||||
//=========================================================================
|
||||
|
||||
// How many chunks tall this strate is.
|
||||
// Each strate can have a different height!
|
||||
// Big open caverns = 6-8 chunks, tight tunnels = 3, vertical shaft = 12+
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Strate|Dimensions", meta = (ClampMin = "1", ClampMax = "32"))
|
||||
int32 StrateHeightInChunks = 4;
|
||||
|
||||
//=========================================================================
|
||||
// BOUNDARY TRANSITION
|
||||
//=========================================================================
|
||||
// Controls how this strate blends with the strate BELOW it.
|
||||
// Each boundary between two strates uses the UPPER strate's transition type.
|
||||
//
|
||||
// Gradient: Smooth param lerp — no visible boundary (default).
|
||||
// Hard: Abrupt switch — creates a cliff/ledge at the boundary.
|
||||
// Interleaved: 3D noise warps the boundary — fingers of one strate
|
||||
// reach into the other for an organic, interlocking look.
|
||||
//
|
||||
// NOTE: The BOTTOM-MOST strate's transition type is unused (nothing below it).
|
||||
|
||||
// Which transition style to use at this strate's lower boundary
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Strate|Boundary",
|
||||
meta = (ToolTip = "How this strate transitions to the strate below it. Gradient = smooth blend, Hard = sharp cliff, Interleaved = noisy interlocking fingers."))
|
||||
EVoxelStrateTransition TransitionType = EVoxelStrateTransition::Gradient;
|
||||
|
||||
// How many chunks the transition zone spans (used by Gradient and Interleaved).
|
||||
// For Hard transitions this is ignored (effectively 0).
|
||||
// Higher = wider, more gradual transition zone.
|
||||
// 1 → very narrow transition (almost hard)
|
||||
// 2 → moderate (good default)
|
||||
// 4 → wide, gentle blend across many chunks
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Strate|Boundary",
|
||||
meta = (ClampMin = "1", ClampMax = "8", EditCondition = "TransitionType != EVoxelStrateTransition::Hard"))
|
||||
int32 TransitionBlendChunks = 2;
|
||||
|
||||
//=========================================================================
|
||||
// GENERATOR TYPE
|
||||
//=========================================================================
|
||||
// Controls which density strategy this strate uses.
|
||||
//
|
||||
// TunnelNetwork → classic rooms + tunnels + worm noise (FStrateGenerationParams below)
|
||||
// FlatPlain → horizontal void between floor and ceiling (FSlabGenerationParams below)
|
||||
// CrystalChamber → same as FlatPlain but ceiling has heavy downward formations
|
||||
//
|
||||
// Changing this shows/hides the relevant parameter sections below.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Strate|Generation")
|
||||
ECaveGeneratorType GeneratorType = ECaveGeneratorType::TunnelNetwork;
|
||||
|
||||
//=========================================================================
|
||||
// TUNNEL NETWORK PARAMS (shown only for TunnelNetwork generator type)
|
||||
//=========================================================================
|
||||
// These params control how the density field is shaped for chunks in this strate.
|
||||
// The generator reads these values to produce unique cave geometry per layer.
|
||||
// Only relevant when GeneratorType == TunnelNetwork.
|
||||
|
||||
// Used by TunnelNetwork (rooms+tunnels) AND Underwater (same rock, flooded).
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Strate|Generation",
|
||||
meta = (EditCondition = "GeneratorType == ECaveGeneratorType::TunnelNetwork || GeneratorType == ECaveGeneratorType::Underwater"))
|
||||
FStrateGenerationParams GenerationParams;
|
||||
|
||||
//=========================================================================
|
||||
// SLAB PARAMS (shown only for FlatPlain and CrystalChamber generator types)
|
||||
//=========================================================================
|
||||
// Controls the floor height, ceiling height, roughness, and column density
|
||||
// for open-void strates. Only relevant when GeneratorType is FlatPlain or
|
||||
// CrystalChamber. Other generator types ignore these entirely.
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Strate|Generation",
|
||||
meta = (EditCondition = "GeneratorType == ECaveGeneratorType::FlatPlain || GeneratorType == ECaveGeneratorType::CrystalChamber"))
|
||||
FSlabGenerationParams SlabParams;
|
||||
|
||||
//=========================================================================
|
||||
// ARCHETYPE PARAMS (each shown only for its generator type)
|
||||
//=========================================================================
|
||||
// Tight branching corridors on a 3D lattice.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Strate|Generation",
|
||||
meta = (EditCondition = "GeneratorType == ECaveGeneratorType::Maze"))
|
||||
FMazeGenerationParams MazeParams;
|
||||
|
||||
// Open-sky terrain: hills/mountains/plains/beaches under a high ceiling, with water.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Strate|Generation",
|
||||
meta = (EditCondition = "GeneratorType == ECaveGeneratorType::SurfaceWorld"))
|
||||
FSurfaceGenerationParams SurfaceParams;
|
||||
|
||||
// Mostly-vertical shafts with ledges and sparse horizontal links.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Strate|Generation",
|
||||
meta = (EditCondition = "GeneratorType == ECaveGeneratorType::VerticalShafts"))
|
||||
FVerticalShaftParams VerticalShaftParams;
|
||||
|
||||
// Suspended land masses floating in a large open void.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Strate|Generation",
|
||||
meta = (EditCondition = "GeneratorType == ECaveGeneratorType::FloatingIslands"))
|
||||
FFloatingIslandParams FloatingIslandParams;
|
||||
|
||||
//=========================================================================
|
||||
// DISTURBANCES (the "wow" layer — applies on top of ANY archetype)
|
||||
//=========================================================================
|
||||
// Chasms, natural bridges and rock ridges layered onto whatever the archetype
|
||||
// produces, for surprise and variety. All features default to disabled.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Strate|Disturbances")
|
||||
FStrateDisturbanceParams Disturbances;
|
||||
|
||||
//=========================================================================
|
||||
// INTER-STRATE PASSAGES (how THIS strate connects DOWN to the next)
|
||||
//=========================================================================
|
||||
// Auto-carved descent tunnels from this strate to the one below it: count, style
|
||||
// (straight / worm / spiral / cascade), tapered width, length, placement. The (0,0)
|
||||
// spine descent is separate (player-dug); these are the extra shortcuts.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Strate|Passages")
|
||||
FStratePassageConfig PassageConfig;
|
||||
|
||||
//=========================================================================
|
||||
// WATER RENDERING (self-contained, aesthetic — no swim/flow simulation)
|
||||
//=========================================================================
|
||||
// The water table HEIGHT comes from the active generator params
|
||||
// (FSurfaceGenerationParams::WaterLevelRelative or FStrateGenerationParams::
|
||||
// WaterLevelRelative). These fields control whether/how it is rendered.
|
||||
|
||||
// Master switch for the water surface in this strate.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Strate|Water")
|
||||
bool bHasWater = false;
|
||||
|
||||
// Translucent material applied to the generated water surface planes.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Strate|Water",
|
||||
meta = (EditCondition = "bHasWater"))
|
||||
UMaterialInterface* WaterMaterial = nullptr;
|
||||
|
||||
// Water tint/colour, available to the material as a parameter if it reads it.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Strate|Water",
|
||||
meta = (EditCondition = "bHasWater"))
|
||||
FLinearColor WaterColor = FLinearColor(0.02f, 0.08f, 0.12f, 0.8f);
|
||||
|
||||
//=========================================================================
|
||||
// TERRAIN OPERATIONS
|
||||
//=========================================================================
|
||||
// Weighted list of terrain operation data assets to apply in this strate.
|
||||
//
|
||||
// HOW TO USE:
|
||||
// 1. Create terrain op assets (Content → Data Asset → VoxelTerrainOpDefinition)
|
||||
// 2. Add entries here — pick an asset and set its Weight
|
||||
// 3. Weight scales the op's intensity: 1.0 = as configured, 0.5 = half, 2.0 = double
|
||||
//
|
||||
// EXAMPLE:
|
||||
// "Crystal Caverns" strate might reference:
|
||||
// - DA_Op_TallColumns (Weight 1.0) → floor-to-ceiling pillars
|
||||
// - DA_Op_WideDomes (Weight 0.8) → cathedral ceilings
|
||||
// - DA_Op_LightScallop (Weight 0.5) → subtle erosion texture
|
||||
//
|
||||
// The same op asset can be shared across multiple strate definitions.
|
||||
// At generation time, these are merged into FStrateGenerationParams
|
||||
// before being passed to the density pipeline.
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Strate|Terrain Operations")
|
||||
TArray<FStrateTerrainOpEntry> TerrainOperations;
|
||||
|
||||
//=========================================================================
|
||||
// VISUALS
|
||||
//=========================================================================
|
||||
|
||||
// Material override for this strate (null = use the default VoxelMaterial)
|
||||
// Allows each strate to have its own rock/ground look
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Strate|Visuals")
|
||||
UMaterialInterface* OverrideMaterial = nullptr;
|
||||
|
||||
// Fog color for this strate (used by a future fog system or post-process)
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Strate|Visuals")
|
||||
FLinearColor FogColor = FLinearColor(0.05f, 0.05f, 0.1f, 1.0f);
|
||||
|
||||
// Fog density (0 = no fog, 1 = very thick)
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Strate|Visuals", meta = (ClampMin = "0.0", ClampMax = "1.0"))
|
||||
float FogDensity = 0.3f;
|
||||
|
||||
// Ambient light tint (color of the "base" underground light)
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Strate|Visuals")
|
||||
FLinearColor AmbientLightColor = FLinearColor(0.1f, 0.1f, 0.15f, 1.0f);
|
||||
|
||||
// Ambient light brightness
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Strate|Visuals", meta = (ClampMin = "0.0"))
|
||||
float AmbientLightIntensity = 0.5f;
|
||||
|
||||
// Render the managed height fog volumetrically (softer, light-shaft-friendly, costlier).
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Strate|Visuals")
|
||||
bool bVolumetricFog = false;
|
||||
|
||||
//=========================================================================
|
||||
// ATMOSPHERE LAYERS (persistent ceiling / floor actors — e.g. seas of clouds)
|
||||
//=========================================================================
|
||||
// These spawn ONCE when the player enters this strate (NOT per chunk, so they
|
||||
// don't churn with streaming) and follow the player in XY while hugging the
|
||||
// strate's ceiling / floor. Perfect for the "sky island" look: open space
|
||||
// between two cloud seas. Author the look as a Blueprint (a large plane with a
|
||||
// cloud/fog material, or a Niagara system). Leave null to disable.
|
||||
// This is also how you get "fog emitting from ceiling and floor".
|
||||
|
||||
// FULL atmosphere override: a Blueprint you author with your own ExponentialHeightFog,
|
||||
// SkyLight, PostProcessVolume, SkyAtmosphere, etc. — tuned exactly how you want.
|
||||
// When set, this REPLACES the simple Fog/Ambient knobs above for this strate (the
|
||||
// plugin's managed fog + skylight are disabled so there's no double-up). The plugin
|
||||
// spawns one instance when you enter the strate and keeps it anchored to the player.
|
||||
// Leave null to use the simple knobs instead.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Strate|Atmosphere")
|
||||
TSubclassOf<AActor> AtmosphereActor;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Strate|Atmosphere")
|
||||
TSubclassOf<AActor> CeilingLayerActor;
|
||||
|
||||
// Vertical offset (Unreal units) from the strate ceiling. Negative = below the ceiling.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Strate|Atmosphere",
|
||||
meta = (EditCondition = "CeilingLayerActor != nullptr"))
|
||||
float CeilingLayerZOffset = -200.0f;
|
||||
|
||||
// Rotation applied to the ceiling layer actor each update.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Strate|Atmosphere",
|
||||
meta = (EditCondition = "CeilingLayerActor != nullptr"))
|
||||
FRotator CeilingLayerRotation = FRotator::ZeroRotator;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Strate|Atmosphere")
|
||||
TSubclassOf<AActor> FloorLayerActor;
|
||||
|
||||
// Vertical offset (Unreal units) from the strate floor. Positive = above the floor.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Strate|Atmosphere",
|
||||
meta = (EditCondition = "FloorLayerActor != nullptr"))
|
||||
float FloorLayerZOffset = 200.0f;
|
||||
|
||||
// Rotation applied to the floor layer actor each update.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Strate|Atmosphere",
|
||||
meta = (EditCondition = "FloorLayerActor != nullptr"))
|
||||
FRotator FloorLayerRotation = FRotator::ZeroRotator;
|
||||
|
||||
//=========================================================================
|
||||
// CONTENT LISTS
|
||||
//=========================================================================
|
||||
// These arrays define what gets spawned in this strate.
|
||||
// Future systems (decoration placer, creature spawner) consume these lists.
|
||||
|
||||
// Decorations: actors placed ON cave surfaces (stalactites, mushrooms, crystals)
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Strate|Content")
|
||||
TArray<FStrateDecoration> Decorations;
|
||||
|
||||
// Ambient actors: things floating in cave space (fog volumes, particles, lights)
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Strate|Content")
|
||||
TArray<FStrateAmbientActor> AmbientActors;
|
||||
|
||||
// Creatures: enemies/NPCs that spawn in this strate
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Strate|Content")
|
||||
TArray<FStrateCreature> Creatures;
|
||||
|
||||
//=========================================================================
|
||||
// AUDIO
|
||||
//=========================================================================
|
||||
|
||||
// Ambient sound loop for this strate (dripping water, wind, etc.)
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Strate|Audio")
|
||||
USoundBase* AmbientSound = nullptr;
|
||||
|
||||
// Background music for this strate
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Strate|Audio")
|
||||
USoundBase* Music = nullptr;
|
||||
|
||||
// Music volume multiplier
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Strate|Audio", meta = (ClampMin = "0.0", ClampMax = "2.0"))
|
||||
float MusicVolume = 1.0f;
|
||||
|
||||
//=========================================================================
|
||||
// GAMEPLAY TAGS
|
||||
//=========================================================================
|
||||
// Extensible tag system for gameplay rules.
|
||||
// Game code checks these tags to apply effects (damage, movement restrictions, etc.)
|
||||
//
|
||||
// Examples:
|
||||
// "Strate.Hazard.Flooded" → water everywhere, need diving gear
|
||||
// "Strate.Hazard.Toxic" → air damages without mask
|
||||
// "Strate.Lighting.Dark" → almost no ambient light
|
||||
// "Strate.Rule.NeedEquipment.Rope" → climbing areas
|
||||
//
|
||||
// The plugin doesn't act on these — it just stores them.
|
||||
// Your game code reads them and applies the appropriate effects.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Strate|Gameplay")
|
||||
FGameplayTagContainer GameplayTags;
|
||||
};
|
||||
@@ -0,0 +1,324 @@
|
||||
// VoxelStrateManager.h
|
||||
// Runtime system that maps world Z coordinates to strate definitions.
|
||||
//
|
||||
// HOW IT WORKS:
|
||||
// -------------
|
||||
// 1. At world startup, Initialize() builds the strate layout:
|
||||
// - Fixed strates go into their assigned slots
|
||||
// - Random strates are shuffled from a pool using the world seed
|
||||
// - Each strate's height (in chunks) is accumulated to compute Z ranges
|
||||
//
|
||||
// 2. During gameplay, GetStrateAt(WorldZ) returns which strate definition
|
||||
// applies at a given depth. GetGenerationParams() returns blended params
|
||||
// (with smooth transitions at strate boundaries).
|
||||
//
|
||||
// 3. The generator, world manager, and future systems (decorations, creatures,
|
||||
// audio) all query this manager to know "what goes here."
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "VoxelStrateTypes.h"
|
||||
#include "VoxelStrateDefinition.h"
|
||||
#include "VoxelStrateManager.generated.h"
|
||||
|
||||
class UVoxelSettings;
|
||||
|
||||
/**
|
||||
* FVoxelPassage — A navigable connection between two strates.
|
||||
*
|
||||
* Generated deterministically from the world seed during Initialize().
|
||||
* Each passage is a path from a point in strate A down through the
|
||||
* boundary seal to a point in strate B. It's carved as a series of
|
||||
* capsule SDFs in the density function.
|
||||
*
|
||||
* Passages are the PRIMARY progression path — players find these
|
||||
* through exploration. The elevator is a FAST TRAVEL shortcut unlocked later.
|
||||
*/
|
||||
USTRUCT()
|
||||
struct FVoxelPassage
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
// Which strates this passage connects (indices into StrateLayout)
|
||||
int32 UpperStrateIndex = 0; // The strate above
|
||||
int32 LowerStrateIndex = 0; // The strate below
|
||||
|
||||
// World-space positions of the passage endpoints
|
||||
FVector UpperPoint = FVector::ZeroVector; // Entry in upper strate
|
||||
FVector LowerPoint = FVector::ZeroVector; // Exit in lower strate
|
||||
|
||||
// An optional midpoint for non-straight passages (sloped, curved).
|
||||
// Used by SlopedTunnel and CrackCrevice types. Ignored when ControlPoints is populated.
|
||||
FVector MidPoint = FVector::ZeroVector;
|
||||
|
||||
// Passage dimensions — how wide the carved tunnel is (in voxels).
|
||||
// Varies by type: VerticalShaft ~7-8, SpiralDescent ~4, CrackCrevice ~2-3, others ~5.
|
||||
float Radius = 5.0f;
|
||||
|
||||
// Whether this passage uses a midpoint (curved/sloped) or is straight.
|
||||
// Only relevant when ControlPoints is empty — if ControlPoints has entries,
|
||||
// the passage is evaluated as a capsule chain along those points instead.
|
||||
bool bHasMidPoint = false;
|
||||
|
||||
// The shape/style of this passage. Determines how control points are generated
|
||||
// and how the passage feels to navigate (shaft, spiral, ledges, crack, etc.).
|
||||
EVoxelPassageType PassageType = EVoxelPassageType::SlopedTunnel;
|
||||
|
||||
// Multi-segment control points for complex passage shapes.
|
||||
// Used by SpiralDescent (helix points) and CascadingDrops (ledge+drop points).
|
||||
// When non-empty, the SDF evaluator walks this array as a capsule chain
|
||||
// instead of using Upper→Mid→Lower logic.
|
||||
// Empty for simple types (SlopedTunnel, VerticalShaft, CrackCrevice).
|
||||
TArray<FVector> ControlPoints;
|
||||
|
||||
// Per-control-point tube radius (parallel to ControlPoints) for tapered tunnels —
|
||||
// wider chambers at the mouths, a squeeze in the middle, etc. If shorter than
|
||||
// ControlPoints, the uniform Radius is used as a fallback.
|
||||
TArray<float> ControlRadii;
|
||||
|
||||
// Bounding sphere enclosing the whole passage (+ radius + blend), in voxel coords.
|
||||
// Computed once in GeneratePassages; lets EvaluateModifierSDF reject far voxels with
|
||||
// a single squared-distance test instead of walking every segment per voxel.
|
||||
FVector BoundCenter = FVector::ZeroVector;
|
||||
float BoundRadiusSq = 0.0f;
|
||||
};
|
||||
|
||||
/**
|
||||
* FStrateSlot — Runtime info for one strate in the layout.
|
||||
*
|
||||
* Created during Initialize() and stored in the StrateLayout array.
|
||||
* Each slot knows its definition, its Z-range in chunk coordinates,
|
||||
* and its index in the sequence.
|
||||
*/
|
||||
USTRUCT()
|
||||
struct FStrateSlot
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
// The strate definition asset (content bag)
|
||||
UPROPERTY()
|
||||
UVoxelStrateDefinition* Definition = nullptr;
|
||||
|
||||
// Strate index (0 = topmost, increases downward)
|
||||
int32 StrateIndex = 0;
|
||||
|
||||
// Z chunk range (inclusive). TopZ > BottomZ since Z decreases downward.
|
||||
// Example: TopZ = 0, BottomZ = -3 means this strate spans chunks Z=0 to Z=-3
|
||||
int32 TopChunkZ = 0;
|
||||
int32 BottomChunkZ = 0;
|
||||
|
||||
// Height in chunks (from the definition)
|
||||
int32 HeightInChunks = 4;
|
||||
};
|
||||
|
||||
/**
|
||||
* UVoxelStrateManager — Maps depth to strate definitions at runtime.
|
||||
*/
|
||||
UCLASS(BlueprintType)
|
||||
class VOXELFORGE_API UVoxelStrateManager : public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
//=========================================================================
|
||||
// INITIALIZATION
|
||||
//=========================================================================
|
||||
|
||||
/**
|
||||
* Build the strate layout from settings and seed.
|
||||
*
|
||||
* STEPS:
|
||||
* 1. For each strate index (0 to TotalStrates-1):
|
||||
* - If it's in FixedStrates → use that definition
|
||||
* - Else → pick from the shuffled pool (seeded random)
|
||||
* 2. Stack strates top-to-bottom, accumulating heights
|
||||
* 3. Store the layout in StrateLayout array
|
||||
*
|
||||
* @param Settings - VoxelSettings with pool/fixed strate config
|
||||
* @param WorldSeed - Seed for randomizing non-fixed strates
|
||||
*/
|
||||
void Initialize(UVoxelSettings* Settings, int32 WorldSeed);
|
||||
|
||||
//=========================================================================
|
||||
// QUERIES
|
||||
//=========================================================================
|
||||
|
||||
/**
|
||||
* Get the strate definition at a world Z coordinate.
|
||||
*
|
||||
* @param WorldZ - Z position in world space (Unreal units)
|
||||
* @return The strate definition, or nullptr if above/below all strates
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Strate")
|
||||
UVoxelStrateDefinition* GetStrateAt(float WorldZ) const;
|
||||
|
||||
/**
|
||||
* Get the strate index at a world Z coordinate.
|
||||
*
|
||||
* @param WorldZ - Z position in world space
|
||||
* @return Strate index (0 = topmost), or -1 if outside strate range
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Strate")
|
||||
int32 GetStrateIndex(float WorldZ) const;
|
||||
|
||||
/**
|
||||
* Get the strate definition for a specific chunk coordinate.
|
||||
*
|
||||
* @param ChunkCoord - The chunk position
|
||||
* @return The strate definition, or nullptr if outside strate range
|
||||
*/
|
||||
UVoxelStrateDefinition* GetStrateForChunk(const FIntVector& ChunkCoord) const;
|
||||
|
||||
/**
|
||||
* Get generation params for a chunk, with boundary blending.
|
||||
*
|
||||
* BLENDING CONCEPT:
|
||||
* When a chunk is near a strate boundary (within BlendChunks of the edge),
|
||||
* the params are lerped between the two adjacent strates. This prevents
|
||||
* hard visual seams where one strate ends and another begins.
|
||||
*
|
||||
* @param ChunkCoord - The chunk position
|
||||
* @return Blended generation params for this chunk
|
||||
*/
|
||||
FStrateGenerationParams GetGenerationParams(const FIntVector& ChunkCoord) const;
|
||||
|
||||
/**
|
||||
* Get the generator type for a specific chunk.
|
||||
*
|
||||
* Returns TunnelNetwork if the chunk is outside all strates or if the
|
||||
* strate definition is null. The generator uses this to pick which
|
||||
* density function to call (GetDensityWithParams vs GetSlabDensity).
|
||||
*
|
||||
* @param ChunkCoord - The chunk position
|
||||
* @return The ECaveGeneratorType for the strate containing this chunk
|
||||
*/
|
||||
ECaveGeneratorType GetGeneratorTypeForChunk(const FIntVector& ChunkCoord) const;
|
||||
|
||||
/**
|
||||
* True if this chunk is in the solid-bedrock GAP between two strates (inside the
|
||||
* overall stack's Z range but not in any strate slot). Chunks above the top strate
|
||||
* or below the bottom strate are NOT gaps (they're open air). Driven by
|
||||
* VoxelSettings::InterStrateGapChunks.
|
||||
*/
|
||||
bool IsGapChunk(const FIntVector& ChunkCoord) const;
|
||||
|
||||
/**
|
||||
* Get slab generation params for a chunk, with runtime Z bounds filled in.
|
||||
*
|
||||
* Used when GetGeneratorTypeForChunk returns FlatPlain or CrystalChamber.
|
||||
* Does NOT blend between adjacent strates — slab strates use Hard transition
|
||||
* at their boundaries (blending between fundamentally different generator types
|
||||
* is not meaningful).
|
||||
*
|
||||
* @param ChunkCoord - The chunk position
|
||||
* @return FSlabGenerationParams with StrateTopWorldZ / StrateBottomWorldZ set
|
||||
*/
|
||||
FSlabGenerationParams GetSlabParamsForChunk(const FIntVector& ChunkCoord) const;
|
||||
|
||||
/**
|
||||
* Per-archetype param getters. Each copies the designer params from the strate
|
||||
* definition and fills in the runtime Z bounds (voxel coords). Like the slab
|
||||
* getter, these do NOT blend across boundaries — fundamentally different
|
||||
* archetypes meet at Hard boundaries.
|
||||
*/
|
||||
FMazeGenerationParams GetMazeParamsForChunk(const FIntVector& ChunkCoord) const;
|
||||
FSurfaceGenerationParams GetSurfaceParamsForChunk(const FIntVector& ChunkCoord) const;
|
||||
FVerticalShaftParams GetVerticalShaftParamsForChunk(const FIntVector& ChunkCoord) const;
|
||||
FFloatingIslandParams GetFloatingIslandParamsForChunk(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
|
||||
* the strate's Z range. Used by the water render system.
|
||||
*/
|
||||
float GetWaterLevelWorldZForChunk(const FIntVector& ChunkCoord) const;
|
||||
|
||||
/**
|
||||
* Unreal-unit (cm) world Z range of the strate containing WorldZ (Unreal units).
|
||||
* OutTopZ = ceiling, OutBottomZ = floor. Returns false if WorldZ is outside all strates.
|
||||
* Used by the atmosphere system to glue ceiling/floor layers to the strate.
|
||||
*/
|
||||
bool GetStrateUnrealZRange(float WorldZ, float& OutTopZ, float& OutBottomZ) const;
|
||||
|
||||
/**
|
||||
* Disturbance params for a chunk (chasms/bridges/ridges), with runtime Z bounds
|
||||
* filled in. Returns an all-disabled default when outside the strate range.
|
||||
*/
|
||||
FStrateDisturbanceParams GetDisturbanceParamsForChunk(const FIntVector& ChunkCoord) const;
|
||||
|
||||
/**
|
||||
* Get the full strate layout (for debug display or UI).
|
||||
*/
|
||||
const TArray<FStrateSlot>& GetLayout() const { return StrateLayout; }
|
||||
|
||||
/**
|
||||
* Get the total number of strates in the layout.
|
||||
*/
|
||||
int32 GetNumStrates() const { return StrateLayout.Num(); }
|
||||
|
||||
//=========================================================================
|
||||
// PASSAGES & ELEVATOR
|
||||
//=========================================================================
|
||||
|
||||
/**
|
||||
* Evaluate the SDF for all passages and the elevator shaft at a world position.
|
||||
* Called by the density function to carve these modifiers into the terrain.
|
||||
*
|
||||
* Returns: negative = inside a passage/shaft, positive = solid rock.
|
||||
* FLT_MAX = no modifier nearby.
|
||||
*/
|
||||
float EvaluateModifierSDF(float WorldX, float WorldY, float WorldZ) const;
|
||||
|
||||
/** Get all generated passages (for debug display). */
|
||||
const TArray<FVoxelPassage>& GetPassages() const { return Passages; }
|
||||
|
||||
protected:
|
||||
//=========================================================================
|
||||
// INTERNAL DATA
|
||||
//=========================================================================
|
||||
|
||||
// The stacked strate layout (index 0 = topmost strate)
|
||||
UPROPERTY()
|
||||
TArray<FStrateSlot> StrateLayout;
|
||||
|
||||
// Passages connecting consecutive strates
|
||||
TArray<FVoxelPassage> Passages;
|
||||
|
||||
// How many chunks at strate boundaries are blended (transition zone)
|
||||
int32 BlendChunks = 2;
|
||||
|
||||
// World seed (stored for passage generation)
|
||||
int32 CachedSeed = 0;
|
||||
|
||||
// Whether to auto-open the (0,0) entry shaft through the top of strate 0.
|
||||
// Copied from VoxelSettings::bOpenSurfaceEntry during Initialize().
|
||||
bool bOpenSurfaceEntry = true;
|
||||
|
||||
// Radius (voxels) of the surface entry shaft at (0,0). Copied from
|
||||
// VoxelSettings::OriginSpineRadius so the entry matches the spine landing.
|
||||
float OriginSpineRadius = 14.0f;
|
||||
|
||||
// Solid-bedrock gap between consecutive strates, in chunks. Copied from
|
||||
// VoxelSettings::InterStrateGapChunks during Initialize().
|
||||
int32 InterStrateGapChunks = 0;
|
||||
|
||||
// Per-passage tunnel shape now lives on each UVoxelStrateDefinition::PassageConfig
|
||||
// (the upper strate of each boundary controls its own descent tunnels).
|
||||
|
||||
//=========================================================================
|
||||
// HELPERS
|
||||
//=========================================================================
|
||||
|
||||
// Find which slot a chunk Z coordinate falls into.
|
||||
// Returns INDEX into StrateLayout, or -1 if not found.
|
||||
int32 FindSlotIndexForChunkZ(int32 ChunkZ) const;
|
||||
|
||||
// Build FStrateGenerationParams from a strate definition:
|
||||
// copies base GenerationParams, then applies all referenced terrain op assets.
|
||||
// This is the single point where terrain op data assets are merged into params.
|
||||
static FStrateGenerationParams BuildParamsFromDefinition(const UVoxelStrateDefinition* Definition);
|
||||
|
||||
// Generate passages between consecutive strates. Called from Initialize().
|
||||
void GeneratePassages();
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,350 @@
|
||||
// VoxelTerrainOpDefinition.h
|
||||
// Individual terrain operation data asset — one per operation variant.
|
||||
//
|
||||
// DESIGN:
|
||||
// ========
|
||||
// Each terrain operation (pit, arch, terrace, etc.) is its own asset.
|
||||
// The strate definition holds a weighted array of references to these.
|
||||
// This allows:
|
||||
// - Reuse: "DA_Op_DeepPit" can be referenced by multiple strates
|
||||
// - Composition: mix different ops per strate by drag-and-drop
|
||||
// - Tuning: tweak one asset, all strates using it update
|
||||
//
|
||||
// HOW PARAMS FLOW:
|
||||
// At generation time, the strate manager builds FStrateGenerationParams by:
|
||||
// 1. Starting from the strate definition's base params (terrain op fields = 0)
|
||||
// 2. For each terrain op reference: calling ApplyTo() which copies the op's
|
||||
// fields into the params, scaled by the entry's Weight.
|
||||
// 3. The generator reads the final params as usual — no code change needed.
|
||||
//
|
||||
// EDITOR UX:
|
||||
// The Type enum controls which param group is visible via EditCondition.
|
||||
// When you select "Pit", only pit-related fields appear in the Details panel.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Engine/DataAsset.h"
|
||||
#include "VoxelStrateTypes.h"
|
||||
#include "VoxelTerrainOpDefinition.generated.h"
|
||||
|
||||
/**
|
||||
* EVoxelTerrainOpType — Which terrain operation this asset configures.
|
||||
* Each type maps to a specific density modification in the generation pipeline.
|
||||
*/
|
||||
UENUM(BlueprintType)
|
||||
enum class EVoxelTerrainOpType : uint8
|
||||
{
|
||||
// Surface treatments (modify cave wall character)
|
||||
Terrace UMETA(DisplayName = "Terrace / Ledges"),
|
||||
LayerLines UMETA(DisplayName = "Layer Lines"),
|
||||
Ribbing UMETA(DisplayName = "Ribbing"),
|
||||
Cliff UMETA(DisplayName = "Cliff Sharpening"),
|
||||
Scallop UMETA(DisplayName = "Scallop / Erosion"),
|
||||
Overhang UMETA(DisplayName = "Overhang / Shelf"),
|
||||
|
||||
// Structural features (place geometry in caves)
|
||||
Arch UMETA(DisplayName = "Arch / Bridge"),
|
||||
Column UMETA(DisplayName = "Column / Pillar"),
|
||||
Pit UMETA(DisplayName = "Pit / Shaft (downward)"),
|
||||
Chimney UMETA(DisplayName = "Chimney / Shaft (upward)"),
|
||||
Dome UMETA(DisplayName = "Dome (cathedral ceiling)"),
|
||||
Pinch UMETA(DisplayName = "Pinch / Bottleneck"),
|
||||
};
|
||||
|
||||
/**
|
||||
* UVoxelTerrainOpDefinition — A single terrain operation, configured as a data asset.
|
||||
*
|
||||
* Create these in your Content folder (e.g., Content/TerrainOps/DA_Op_DeepPit).
|
||||
* Then reference them from your strate definitions with a weight.
|
||||
*
|
||||
* USAGE EXAMPLE:
|
||||
* 1. Create: Right-click Content → Miscellaneous → Data Asset → VoxelTerrainOpDefinition
|
||||
* 2. Set Type to "Pit", configure depth/radius/taper
|
||||
* 3. In your strate definition, add this asset to TerrainOperations with Weight=1.0
|
||||
*/
|
||||
UCLASS(BlueprintType)
|
||||
class VOXELFORGE_API UVoxelTerrainOpDefinition : public UPrimaryDataAsset
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
|
||||
//=========================================================================
|
||||
// OPERATION TYPE
|
||||
//=========================================================================
|
||||
|
||||
// Which terrain operation this asset configures.
|
||||
// Changing this shows/hides the relevant parameter group below.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Terrain Operation")
|
||||
EVoxelTerrainOpType Type = EVoxelTerrainOpType::Terrace;
|
||||
|
||||
//=========================================================================
|
||||
// TERRACE PARAMS (visible when Type == Terrace)
|
||||
//=========================================================================
|
||||
|
||||
// Vertical spacing between terrace steps (in voxels). 0 = disabled.
|
||||
// 3-5 → tight steps (staircase feel)
|
||||
// 8-12 → wide ledges (platforming scale)
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Terrace",
|
||||
meta = (EditCondition = "Type == EVoxelTerrainOpType::Terrace"))
|
||||
float TerraceStepHeight = 5.0f;
|
||||
|
||||
// Edge sharpness: 0 = rounded steps, 1 = razor sharp edges.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Terrace",
|
||||
meta = (EditCondition = "Type == EVoxelTerrainOpType::Terrace",
|
||||
ClampMin = "0.0", ClampMax = "1.0"))
|
||||
float TerraceHardness = 0.5f;
|
||||
|
||||
// Noise displacement on step edges (organic variation).
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Terrace",
|
||||
meta = (EditCondition = "Type == EVoxelTerrainOpType::Terrace",
|
||||
ClampMin = "0.0", ClampMax = "2.0"))
|
||||
float TerraceNoiseDisplacement = 0.5f;
|
||||
|
||||
//=========================================================================
|
||||
// LAYER LINES PARAMS (visible when Type == LayerLines)
|
||||
//=========================================================================
|
||||
|
||||
// Spacing between horizontal geological lines (in voxels). 0 = disabled.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Layer Lines",
|
||||
meta = (EditCondition = "Type == EVoxelTerrainOpType::LayerLines"))
|
||||
float LayerLineSpacing = 4.0f;
|
||||
|
||||
// Depth of the grooves (how much density is removed).
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Layer Lines",
|
||||
meta = (EditCondition = "Type == EVoxelTerrainOpType::LayerLines",
|
||||
ClampMin = "0.0", ClampMax = "2.0"))
|
||||
float LayerLineDepth = 0.3f;
|
||||
|
||||
//=========================================================================
|
||||
// RIBBING PARAMS (visible when Type == Ribbing)
|
||||
//=========================================================================
|
||||
|
||||
// Spacing between parallel ribs along Z (in voxels). 0 = disabled.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ribbing",
|
||||
meta = (EditCondition = "Type == EVoxelTerrainOpType::Ribbing"))
|
||||
float RibbingSpacing = 3.0f;
|
||||
|
||||
// Depth of the ridges.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Ribbing",
|
||||
meta = (EditCondition = "Type == EVoxelTerrainOpType::Ribbing",
|
||||
ClampMin = "0.0", ClampMax = "3.0"))
|
||||
float RibbingDepth = 0.4f;
|
||||
|
||||
//=========================================================================
|
||||
// CLIFF PARAMS (visible when Type == Cliff)
|
||||
//=========================================================================
|
||||
|
||||
// How much to amplify vertical gradients near cave surfaces.
|
||||
// 0 = no effect, 1 = maximum sheer cliff amplification.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Cliff",
|
||||
meta = (EditCondition = "Type == EVoxelTerrainOpType::Cliff",
|
||||
ClampMin = "0.0", ClampMax = "1.0"))
|
||||
float CliffStrength = 0.5f;
|
||||
|
||||
//=========================================================================
|
||||
// SCALLOP PARAMS (visible when Type == Scallop)
|
||||
//=========================================================================
|
||||
|
||||
// Strength of the erosion bowl patterns (cellular noise subtraction).
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Scallop",
|
||||
meta = (EditCondition = "Type == EVoxelTerrainOpType::Scallop",
|
||||
ClampMin = "0.0", ClampMax = "2.0"))
|
||||
float ScallopStrength = 0.5f;
|
||||
|
||||
// Frequency of the cellular noise (smaller = larger bowls).
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Scallop",
|
||||
meta = (EditCondition = "Type == EVoxelTerrainOpType::Scallop"))
|
||||
float ScallopFrequency = 0.1f;
|
||||
|
||||
//=========================================================================
|
||||
// OVERHANG PARAMS (visible when Type == Overhang)
|
||||
//=========================================================================
|
||||
|
||||
// Probability/strength of overhang generation (0 = none, 1 = max).
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Overhang",
|
||||
meta = (EditCondition = "Type == EVoxelTerrainOpType::Overhang",
|
||||
ClampMin = "0.0", ClampMax = "1.0"))
|
||||
float OverhangStrength = 0.5f;
|
||||
|
||||
// How far the overhang protrudes from the wall (in voxels).
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Overhang",
|
||||
meta = (EditCondition = "Type == EVoxelTerrainOpType::Overhang"))
|
||||
float OverhangDepth = 5.0f;
|
||||
|
||||
// Frequency of the noise that creates overhangs (lower = broader shelves).
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Overhang",
|
||||
meta = (EditCondition = "Type == EVoxelTerrainOpType::Overhang"))
|
||||
float OverhangFrequency = 0.06f;
|
||||
|
||||
//=========================================================================
|
||||
// ARCH PARAMS (visible when Type == Arch)
|
||||
//=========================================================================
|
||||
|
||||
// Probability per grid cell (0 = none, 0.3 = max recommended).
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Arch",
|
||||
meta = (EditCondition = "Type == EVoxelTerrainOpType::Arch",
|
||||
ClampMin = "0.0", ClampMax = "0.3"))
|
||||
float ArchDensity = 0.1f;
|
||||
|
||||
// Minimum arch thickness (radius of the capsule SDF).
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Arch",
|
||||
meta = (EditCondition = "Type == EVoxelTerrainOpType::Arch",
|
||||
ClampMin = "1.0"))
|
||||
float ArchMinRadius = 3.0f;
|
||||
|
||||
// Maximum arch thickness.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Arch",
|
||||
meta = (EditCondition = "Type == EVoxelTerrainOpType::Arch",
|
||||
ClampMin = "1.0"))
|
||||
float ArchMaxRadius = 6.0f;
|
||||
|
||||
|
||||
//=========================================================================
|
||||
// COLUMN PARAMS (visible when Type == Column)
|
||||
//=========================================================================
|
||||
|
||||
// Probability per grid cell (0 = none, 1 = every cell).
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Column",
|
||||
meta = (EditCondition = "Type == EVoxelTerrainOpType::Column",
|
||||
ClampMin = "0.0", ClampMax = "1.0"))
|
||||
float ColumnDensity = 0.15f;
|
||||
|
||||
// Minimum column radius.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Column",
|
||||
meta = (EditCondition = "Type == EVoxelTerrainOpType::Column",
|
||||
ClampMin = "0.5"))
|
||||
float ColumnMinRadius = 2.0f;
|
||||
|
||||
// Maximum column radius.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Column",
|
||||
meta = (EditCondition = "Type == EVoxelTerrainOpType::Column",
|
||||
ClampMin = "1.0"))
|
||||
float ColumnMaxRadius = 5.0f;
|
||||
|
||||
|
||||
//=========================================================================
|
||||
// PIT PARAMS (visible when Type == Pit)
|
||||
//=========================================================================
|
||||
|
||||
// Probability per grid cell.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Pit",
|
||||
meta = (EditCondition = "Type == EVoxelTerrainOpType::Pit",
|
||||
ClampMin = "0.0", ClampMax = "0.5"))
|
||||
float PitDensity = 0.1f;
|
||||
|
||||
// Minimum pit radius at the top.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Pit",
|
||||
meta = (EditCondition = "Type == EVoxelTerrainOpType::Pit",
|
||||
ClampMin = "1.0"))
|
||||
float PitMinRadius = 4.0f;
|
||||
|
||||
// Maximum pit radius at the top.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Pit",
|
||||
meta = (EditCondition = "Type == EVoxelTerrainOpType::Pit",
|
||||
ClampMin = "2.0"))
|
||||
float PitMaxRadius = 10.0f;
|
||||
|
||||
// How deep the pit extends downward (in voxels).
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Pit",
|
||||
meta = (EditCondition = "Type == EVoxelTerrainOpType::Pit",
|
||||
ClampMin = "5.0"))
|
||||
float PitDepth = 25.0f;
|
||||
|
||||
|
||||
//=========================================================================
|
||||
// CHIMNEY PARAMS (visible when Type == Chimney)
|
||||
//=========================================================================
|
||||
|
||||
// Probability per grid cell.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Chimney",
|
||||
meta = (EditCondition = "Type == EVoxelTerrainOpType::Chimney",
|
||||
ClampMin = "0.0", ClampMax = "1.0"))
|
||||
float ChimneyDensity = 0.1f;
|
||||
|
||||
// Minimum chimney radius.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Chimney",
|
||||
meta = (EditCondition = "Type == EVoxelTerrainOpType::Chimney",
|
||||
ClampMin = "0.5"))
|
||||
float ChimneyMinRadius = 2.0f;
|
||||
|
||||
// Maximum chimney radius.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Chimney",
|
||||
meta = (EditCondition = "Type == EVoxelTerrainOpType::Chimney",
|
||||
ClampMin = "1.0"))
|
||||
float ChimneyMaxRadius = 5.0f;
|
||||
|
||||
// How high the chimney extends upward (in voxels).
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Chimney",
|
||||
meta = (EditCondition = "Type == EVoxelTerrainOpType::Chimney",
|
||||
ClampMin = "5.0"))
|
||||
float ChimneyHeight = 20.0f;
|
||||
|
||||
|
||||
//=========================================================================
|
||||
// DOME PARAMS (visible when Type == Dome)
|
||||
//=========================================================================
|
||||
|
||||
// Probability per grid cell.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Dome",
|
||||
meta = (EditCondition = "Type == EVoxelTerrainOpType::Dome",
|
||||
ClampMin = "0.0", ClampMax = "1.0"))
|
||||
float DomeDensity = 0.15f;
|
||||
|
||||
// Minimum dome radius.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Dome",
|
||||
meta = (EditCondition = "Type == EVoxelTerrainOpType::Dome",
|
||||
ClampMin = "3.0"))
|
||||
float DomeMinRadius = 8.0f;
|
||||
|
||||
// Maximum dome radius.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Dome",
|
||||
meta = (EditCondition = "Type == EVoxelTerrainOpType::Dome",
|
||||
ClampMin = "4.0"))
|
||||
float DomeMaxRadius = 15.0f;
|
||||
|
||||
// Height-to-radius ratio (0.5 = flat, 1.0 = hemisphere, 1.5 = tall/gothic).
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Dome",
|
||||
meta = (EditCondition = "Type == EVoxelTerrainOpType::Dome",
|
||||
ClampMin = "0.3", ClampMax = "2.0"))
|
||||
float DomeHeightRatio = 0.8f;
|
||||
|
||||
|
||||
//=========================================================================
|
||||
// PINCH PARAMS (visible when Type == Pinch)
|
||||
//=========================================================================
|
||||
|
||||
// Probability per grid cell.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Pinch",
|
||||
meta = (EditCondition = "Type == EVoxelTerrainOpType::Pinch",
|
||||
ClampMin = "0.0", ClampMax = "1.0"))
|
||||
float PinchDensity = 0.15f;
|
||||
|
||||
// How much the passage narrows (voxels of added density from each side).
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Pinch",
|
||||
meta = (EditCondition = "Type == EVoxelTerrainOpType::Pinch",
|
||||
ClampMin = "1.0"))
|
||||
float PinchStrength = 5.0f;
|
||||
|
||||
// Length of the narrowed section along the passage.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Pinch",
|
||||
meta = (EditCondition = "Type == EVoxelTerrainOpType::Pinch",
|
||||
ClampMin = "3.0"))
|
||||
float PinchLength = 12.0f;
|
||||
|
||||
|
||||
//=========================================================================
|
||||
// APPLY TO GENERATION PARAMS
|
||||
//=========================================================================
|
||||
|
||||
/**
|
||||
* Copy this operation's parameters into a FStrateGenerationParams struct.
|
||||
* Only the fields matching this operation's Type are written.
|
||||
* Weight scales the primary "strength" or "density" field for intensity control.
|
||||
*
|
||||
* @param OutParams - The generation params to write into
|
||||
* @param Weight - Multiplier for the op's intensity (1.0 = full strength)
|
||||
*/
|
||||
void ApplyTo(FStrateGenerationParams& OutParams, float Weight = 1.0f) const;
|
||||
};
|
||||
@@ -0,0 +1,173 @@
|
||||
// VoxelTypes.h
|
||||
// Core type definitions, coordinate conversions, and mesh data.
|
||||
//
|
||||
// Ce header est le socle du plugin — tout le monde l'inclut.
|
||||
// Pas de dépendance sur des UClasses ici (on reste léger).
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
|
||||
//=============================================================================
|
||||
// CHUNK CONSTANTS
|
||||
//=============================================================================
|
||||
//
|
||||
// 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).
|
||||
|
||||
constexpr int32 CHUNK_SIZE = 32;
|
||||
constexpr int32 CHUNK_SIZE_SQUARED = CHUNK_SIZE * CHUNK_SIZE; // 1024
|
||||
constexpr int32 CHUNK_VOLUME = CHUNK_SIZE * CHUNK_SIZE * CHUNK_SIZE; // 32768
|
||||
|
||||
constexpr float VOXEL_SIZE = 25.0f;
|
||||
|
||||
//=============================================================================
|
||||
// FACE DIRECTIONS
|
||||
//=============================================================================
|
||||
//
|
||||
// Les 6 faces d'un cube. Gardé ici car le mesher peut encore en avoir besoin
|
||||
// (ex. normales orientées), et les strates pourraient s'en servir pour des
|
||||
// décorations "sur le sol" vs "au plafond".
|
||||
|
||||
enum class EVoxelFace : uint8
|
||||
{
|
||||
PositiveX, // East (+X)
|
||||
NegativeX, // West (-X)
|
||||
PositiveY, // North (+Y)
|
||||
NegativeY, // South (-Y)
|
||||
PositiveZ, // Up (+Z)
|
||||
NegativeZ, // Down (-Z)
|
||||
};
|
||||
|
||||
inline FIntVector GetFaceDirection(EVoxelFace Face)
|
||||
{
|
||||
switch (Face)
|
||||
{
|
||||
case EVoxelFace::PositiveX: return FIntVector( 1, 0, 0);
|
||||
case EVoxelFace::NegativeX: return FIntVector(-1, 0, 0);
|
||||
case EVoxelFace::PositiveY: return FIntVector( 0, 1, 0);
|
||||
case EVoxelFace::NegativeY: return FIntVector( 0, -1, 0);
|
||||
case EVoxelFace::PositiveZ: return FIntVector( 0, 0, 1);
|
||||
case EVoxelFace::NegativeZ: return FIntVector( 0, 0, -1);
|
||||
default: return FIntVector( 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
inline FVector GetFaceNormal(EVoxelFace Face)
|
||||
{
|
||||
const FIntVector Dir = GetFaceDirection(Face);
|
||||
return FVector(Dir.X, Dir.Y, Dir.Z);
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
// COORDINATE CONVERSION
|
||||
//=============================================================================
|
||||
//
|
||||
// Trois espaces:
|
||||
// WORLD (FVector, cm) → position Unreal
|
||||
// CHUNK (FIntVector) → quel chunk (32 voxels)
|
||||
// LOCAL (FIntVector 0-31) → position dans le chunk
|
||||
//
|
||||
// Relation: WorldPos = (ChunkCoord * CHUNK_SIZE + LocalPos) * VOXEL_SIZE
|
||||
|
||||
inline FIntVector WorldToChunkCoord(const FVector& WorldPos)
|
||||
{
|
||||
// FloorToInt (pas division int) pour gérer proprement les coords négatives:
|
||||
// -5 / 32 = 0 (faux — on veut -1)
|
||||
// floor(-5 / 32) = floor(-0.156) = -1 (correct)
|
||||
return FIntVector(
|
||||
FMath::FloorToInt((WorldPos.X / VOXEL_SIZE) / CHUNK_SIZE),
|
||||
FMath::FloorToInt((WorldPos.Y / VOXEL_SIZE) / CHUNK_SIZE),
|
||||
FMath::FloorToInt((WorldPos.Z / VOXEL_SIZE) / CHUNK_SIZE)
|
||||
);
|
||||
}
|
||||
|
||||
inline FIntVector WorldToLocalCoord(const FVector& WorldPos)
|
||||
{
|
||||
// ((x % n) + n) % n → modulo positif même pour x négatif.
|
||||
// (-5 % 32) = -5 en C++, mais on veut 27.
|
||||
return FIntVector(
|
||||
((FMath::FloorToInt(WorldPos.X / VOXEL_SIZE) % CHUNK_SIZE) + CHUNK_SIZE) % CHUNK_SIZE,
|
||||
((FMath::FloorToInt(WorldPos.Y / VOXEL_SIZE) % CHUNK_SIZE) + CHUNK_SIZE) % CHUNK_SIZE,
|
||||
((FMath::FloorToInt(WorldPos.Z / VOXEL_SIZE) % CHUNK_SIZE) + CHUNK_SIZE) % CHUNK_SIZE
|
||||
);
|
||||
}
|
||||
|
||||
inline FVector ChunkToWorldPos(const FIntVector& ChunkCoord)
|
||||
{
|
||||
return FVector(
|
||||
ChunkCoord.X * CHUNK_SIZE * VOXEL_SIZE,
|
||||
ChunkCoord.Y * CHUNK_SIZE * VOXEL_SIZE,
|
||||
ChunkCoord.Z * CHUNK_SIZE * VOXEL_SIZE
|
||||
);
|
||||
}
|
||||
|
||||
// Index 3D → 1D pour un tableau plat (voir doc CLAUDE.md: "x + y*SizeX + z*SizeX*SizeY").
|
||||
inline int32 LocalToIndex(int32 X, int32 Y, int32 Z)
|
||||
{
|
||||
return X + (Y * CHUNK_SIZE) + (Z * CHUNK_SIZE_SQUARED);
|
||||
}
|
||||
|
||||
inline FIntVector IndexToLocal(int32 Index)
|
||||
{
|
||||
return FIntVector(
|
||||
Index % CHUNK_SIZE,
|
||||
(Index / CHUNK_SIZE) % CHUNK_SIZE,
|
||||
Index / CHUNK_SIZE_SQUARED
|
||||
);
|
||||
}
|
||||
|
||||
inline bool IsValidLocalCoord(int32 X, int32 Y, int32 Z)
|
||||
{
|
||||
return X >= 0 && X < CHUNK_SIZE
|
||||
&& Y >= 0 && Y < CHUNK_SIZE
|
||||
&& Z >= 0 && Z < CHUNK_SIZE;
|
||||
}
|
||||
|
||||
inline bool IsValidLocalCoord(const FIntVector& Coord)
|
||||
{
|
||||
return IsValidLocalCoord(Coord.X, Coord.Y, Coord.Z);
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
// MATH HELPERS (partagés entre générateur, morphologie, manager)
|
||||
//=============================================================================
|
||||
|
||||
// Smoothstep classique: 3x² - 2x³. Maps 0→0, 1→1, avec pente nulle aux bornes.
|
||||
// Utilisé partout pour adoucir les transitions de densité (blend SDF, seal boundary).
|
||||
// Entrée: x doit être dans [0, 1] (clamp avant si besoin).
|
||||
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é.
|
||||
constexpr float VOXEL_NOISE_SCALE = 1.25f;
|
||||
|
||||
//=============================================================================
|
||||
// MESH DATA
|
||||
//=============================================================================
|
||||
//
|
||||
// Sortie du mesher: géométrie prête pour l'upload GPU.
|
||||
// Struct plain C++ (pas de USTRUCT) — uniquement utilisé entre tâches C++.
|
||||
// Si lighting est ajouté plus tard, ré-ajouter un champ Colors dédié.
|
||||
|
||||
struct FVoxelMeshData
|
||||
{
|
||||
TArray<FVector> Vertices; // Positions monde
|
||||
TArray<int32> Triangles; // Indices, 3 par triangle
|
||||
TArray<FVector2D> UVs; // Coords de texture (une par vertex)
|
||||
TArray<FVector> Normals; // Normale lissée (gradient de densité)
|
||||
|
||||
void Clear()
|
||||
{
|
||||
Vertices.Empty();
|
||||
Triangles.Empty();
|
||||
UVs.Empty();
|
||||
Normals.Empty();
|
||||
}
|
||||
|
||||
bool IsEmpty() const { return Vertices.Num() == 0; }
|
||||
};
|
||||
@@ -0,0 +1,426 @@
|
||||
// VoxelWorld.h
|
||||
// The main world manager - orchestrates chunk loading, generation, meshing, and rendering
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GameFramework/Actor.h"
|
||||
#include <atomic>
|
||||
#include "VoxelTypes.h"
|
||||
#include "VoxelChunk.h"
|
||||
#include "VoxelGenerator.h"
|
||||
#include "VoxelMarchingCubesMesher.h"
|
||||
#include "VoxelSettings.h"
|
||||
#include "VoxelStrateManager.h"
|
||||
#include "VoxelDiffLayer.h"
|
||||
#include "VoxelWorld.generated.h"
|
||||
|
||||
// Forward declaration
|
||||
class URealtimeMeshComponent;
|
||||
class URealtimeMeshSimple;
|
||||
class UVoxelDiffLayer;
|
||||
class UVoxelContentManager;
|
||||
class UVoxelAtmosphereManager;
|
||||
|
||||
/**
|
||||
* AVoxelWorld - The main voxel terrain actor
|
||||
*
|
||||
* RESPONSIBILITIES:
|
||||
* - Track which chunks should be loaded based on player position
|
||||
* - Create/destroy chunks as player moves
|
||||
* - Coordinate generation and meshing
|
||||
* - Manage mesh components for rendering
|
||||
*
|
||||
* LIFECYCLE:
|
||||
* - BeginPlay: Initialize generator, mesher
|
||||
* - Tick: Update chunks around player position
|
||||
*/
|
||||
struct FChunkResult
|
||||
{
|
||||
FIntVector ChunkCoord;
|
||||
FVoxelChunk Chunk;
|
||||
FVoxelMeshData MeshData;
|
||||
int32 LODLevel = 0; // 0=full, 1=half, 2=quarter resolution
|
||||
uint32 Epoch = 0; // Generation epoch — discard if stale
|
||||
};
|
||||
|
||||
UCLASS()
|
||||
class VOXELFORGE_API AVoxelWorld : public AActor
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
AVoxelWorld();
|
||||
|
||||
//=========================================================================
|
||||
// SETTINGS
|
||||
//=========================================================================
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Voxel World")
|
||||
UVoxelSettings* Settings;
|
||||
|
||||
//=========================================================================
|
||||
// COMPONENTS & REFERENCES
|
||||
//=========================================================================
|
||||
|
||||
/** The terrain generator */
|
||||
UPROPERTY()
|
||||
UVoxelGenerator* Generator;
|
||||
|
||||
/** The mesh builder */
|
||||
UPROPERTY()
|
||||
UVoxelMarchingCubesMesher* Mesher;
|
||||
|
||||
/** The strate manager — maps depth to strate definitions */
|
||||
UPROPERTY()
|
||||
UVoxelStrateManager* StrateManager;
|
||||
|
||||
/** Player terrain modifications (carving & filling).
|
||||
* Stores density diffs on top of procedural terrain.
|
||||
* Created in BeginPlay, passed to Generator for density evaluation. */
|
||||
UPROPERTY()
|
||||
UVoxelDiffLayer* DiffLayer;
|
||||
|
||||
/** Spawns per-chunk decorations/actors from the strate content pools and the
|
||||
* aesthetic water surfaces. Created in BeginPlay. */
|
||||
UPROPERTY()
|
||||
UVoxelContentManager* ContentManager;
|
||||
|
||||
/** Drives per-strate fog + ambient + persistent ceiling/floor layer actors
|
||||
* (e.g. seas of clouds) based on the strate the player is in. Created in BeginPlay
|
||||
* when bManageAtmosphere is true. */
|
||||
UPROPERTY()
|
||||
UVoxelAtmosphereManager* AtmosphereManager;
|
||||
|
||||
/** When true, VoxelForge spawns & drives its own height fog + skylight + ceiling/floor
|
||||
* layer actors from each strate's settings. Turn OFF if you manage fog/lighting
|
||||
* yourself in the level (avoids a duplicate ExponentialHeightFog). */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Voxel World")
|
||||
bool bManageAtmosphere = true;
|
||||
|
||||
//=========================================================================
|
||||
// CHUNK STORAGE
|
||||
//=========================================================================
|
||||
|
||||
/** All currently loaded chunks, keyed by chunk coordinate */
|
||||
TMap<FIntVector, FVoxelChunk> Chunks;
|
||||
|
||||
/** Mesh components for each chunk, keyed by chunk coordinate */
|
||||
UPROPERTY()
|
||||
TMap<FIntVector, URealtimeMeshComponent*> ChunkMeshes;
|
||||
|
||||
|
||||
|
||||
//=========================================================================
|
||||
// TERRAIN MODIFICATION (player carving & filling)
|
||||
//=========================================================================
|
||||
|
||||
/**
|
||||
* Carve terrain at a world position (remove rock, create air).
|
||||
* Applies a spherical brush that subtracts density.
|
||||
*
|
||||
* @param Position - World-space center of the carve brush
|
||||
* @param Radius - Brush radius in voxels (default 3)
|
||||
* @param Strength - How aggressively to carve (default 10, higher = deeper)
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Voxel World|Modification")
|
||||
void CarveAtPosition(FVector Position, float Radius = 3.0f, float Strength = 10.0f);
|
||||
|
||||
/**
|
||||
* Fill terrain at a world position (add rock, seal holes).
|
||||
* Applies a spherical brush that adds density.
|
||||
*
|
||||
* @param Position - World-space center of the fill brush
|
||||
* @param Radius - Brush radius in voxels (default 3)
|
||||
* @param Strength - How aggressively to fill (default 10, higher = more solid)
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Voxel World|Modification")
|
||||
void FillAtPosition(FVector Position, float Radius = 3.0f, float Strength = 10.0f);
|
||||
|
||||
/**
|
||||
* Box brush carve/fill. Position is world-space (Unreal units); ExtentVoxels is the
|
||||
* box half-size in voxels. Strength magnitude controls aggressiveness (sign forced).
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Voxel World|Modification")
|
||||
void CarveBox(FVector Position, FVector ExtentVoxels, float Strength = 12.0f);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "Voxel World|Modification")
|
||||
void FillBox(FVector Position, FVector ExtentVoxels, float Strength = 12.0f);
|
||||
|
||||
/**
|
||||
* Capsule brush carve/fill between two world-space points (Unreal units), with a
|
||||
* tube radius in voxels. Great for boring tunnels or laying solid pillars/walls.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Voxel World|Modification")
|
||||
void CarveCapsule(FVector WorldA, FVector WorldB, float RadiusVoxels = 3.0f, float Strength = 12.0f);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "Voxel World|Modification")
|
||||
void FillCapsule(FVector WorldA, FVector WorldB, float RadiusVoxels = 3.0f, float Strength = 12.0f);
|
||||
|
||||
/**
|
||||
* Apply a fully-specified modification (any shape). Centers/endpoints are expected
|
||||
* in VOXEL coordinates here (this is the low-level entry the helpers build on).
|
||||
* Returns nothing; re-meshes affected chunks.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Voxel World|Modification")
|
||||
void ApplyModification(const FVoxelModification& Modification);
|
||||
|
||||
/**
|
||||
* Clear all player modifications (e.g., on season reset).
|
||||
* Regenerates all currently loaded chunks to restore procedural terrain.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Voxel World|Modification")
|
||||
void ClearAllModifications();
|
||||
|
||||
//=========================================================================
|
||||
// SEED / SEASON MANAGEMENT
|
||||
//=========================================================================
|
||||
|
||||
/**
|
||||
* Change the world seed and regenerate everything.
|
||||
*
|
||||
* This is the "season reset" operation:
|
||||
* 1. Updates the seed in Settings, Generator, and StrateManager
|
||||
* 2. Clears ALL player modifications (carvings are meaningless in a new world)
|
||||
* 3. Resets the elevator depth (player must re-discover strates)
|
||||
* 4. Increments the season counter
|
||||
* 5. Unloads all chunks and lets Tick reload them with the new seed
|
||||
*
|
||||
* The game layer is responsible for calling this at season boundaries,
|
||||
* saving/loading the season counter, and any pre-reset cleanup (inventory, etc.)
|
||||
*
|
||||
* @param NewSeed - The new world seed
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Voxel World|Season")
|
||||
void ChangeSeed(int32 NewSeed);
|
||||
|
||||
/**
|
||||
* Get the current world seed.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Voxel World|Season")
|
||||
int32 GetCurrentSeed() const;
|
||||
|
||||
/**
|
||||
* Get the current season number (incremented each time ChangeSeed is called).
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Voxel World|Season")
|
||||
int32 GetCurrentSeason() const;
|
||||
|
||||
//=========================================================================
|
||||
// STRATE QUERIES (gameplay integration)
|
||||
//=========================================================================
|
||||
|
||||
/**
|
||||
* Get which strate index a world position is in.
|
||||
*
|
||||
* @param WorldPosition - Position in world space (Unreal units)
|
||||
* @return Strate index (0 = topmost), or -1 if outside all strates
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Voxel World|Strate")
|
||||
int32 GetStrateAtPosition(FVector WorldPosition) const;
|
||||
|
||||
//=========================================================================
|
||||
// LIVE EDIT (debug tuning in PIE)
|
||||
//=========================================================================
|
||||
|
||||
/** When true, editing your Strate Definition data assets during PIE
|
||||
* will automatically regenerate all chunks so you see the result live.
|
||||
* Also adds a "Regenerate" button in Details for manual refresh. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Live Edit")
|
||||
bool bLiveEditStrates = false;
|
||||
|
||||
/** Click to force-regenerate all chunks right now (useful during PIE). */
|
||||
UFUNCTION(CallInEditor, BlueprintCallable, Category = "Live Edit")
|
||||
void RegenerateAllChunks();
|
||||
|
||||
/** Re-read ALL of VoxelSettings (strate layout, inter-strate gap, passages, spine)
|
||||
* and rebuild from scratch, then regenerate every chunk. Use this after changing
|
||||
* passage / gap / spine settings so they apply live without restarting PIE —
|
||||
* RegenerateAllChunks alone keeps the existing layout & passages. */
|
||||
UFUNCTION(CallInEditor, BlueprintCallable, Category = "Live Edit")
|
||||
void RebuildStrates();
|
||||
|
||||
//=========================================================================
|
||||
// EDITOR BRUSH (manual carve/fill from the Details panel, works in PIE)
|
||||
//=========================================================================
|
||||
// The diff layer only exists while playing, so these buttons act during PIE.
|
||||
|
||||
/** World-space center (Unreal units) for the editor carve/fill buttons below. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Editor Brush")
|
||||
FVector EditorBrushCenter = FVector::ZeroVector;
|
||||
|
||||
/** Brush radius in voxels for the editor buttons. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Editor Brush", meta = (ClampMin = "0.5"))
|
||||
float EditorBrushRadius = 6.0f;
|
||||
|
||||
/** Brush strength for the editor buttons (sign forced by the button). */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Editor Brush", meta = (ClampMin = "0.1"))
|
||||
float EditorBrushStrength = 12.0f;
|
||||
|
||||
/** Carve a sphere at EditorBrushCenter using the brush settings above. */
|
||||
UFUNCTION(CallInEditor, BlueprintCallable, Category = "Editor Brush")
|
||||
void EditorCarveSphere();
|
||||
|
||||
/** Fill a sphere at EditorBrushCenter using the brush settings above. */
|
||||
UFUNCTION(CallInEditor, BlueprintCallable, Category = "Editor Brush")
|
||||
void EditorFillSphere();
|
||||
|
||||
/** Generation counter — incremented each time we regenerate.
|
||||
* Async tasks carry this value; stale results are discarded. */
|
||||
uint32 GenerationEpoch = 0;
|
||||
|
||||
/** Draw the inter-strate passages each frame (lines along the path + endpoint
|
||||
* spheres) so you can see where they spawned and verify they carve. PIE only. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Voxel World|Debug")
|
||||
bool bDebugDrawPassages = false;
|
||||
|
||||
#if WITH_EDITOR
|
||||
virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override;
|
||||
|
||||
/** Called by FCoreUObjectDelegates::OnObjectModified when ANY UObject is edited.
|
||||
* We filter for UVoxelStrateDefinition changes and regenerate if live edit is on. */
|
||||
void OnObjectModifiedInEditor(UObject* ModifiedObject);
|
||||
|
||||
/** Handle to unbind the delegate when EndPlay is called */
|
||||
FDelegateHandle OnObjectModifiedHandle;
|
||||
#endif
|
||||
|
||||
//=========================================================================
|
||||
// ACTOR LIFECYCLE
|
||||
//=========================================================================
|
||||
|
||||
virtual void BeginPlay() override;
|
||||
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
|
||||
virtual void Tick(float DeltaTime) override;
|
||||
|
||||
//=========================================================================
|
||||
// CHUNK MANAGEMENT - YOU IMPLEMENT THESE
|
||||
//=========================================================================
|
||||
|
||||
/**
|
||||
* Update which chunks are loaded based on a world position.
|
||||
*
|
||||
* CONCEPT:
|
||||
* - Figure out which chunk the position is in (the "center")
|
||||
* - Determine which chunks SHOULD exist (within view distance)
|
||||
* - Load any chunks that should exist but don't
|
||||
* - Unload any chunks that exist but shouldn't
|
||||
*
|
||||
* @param CenterPosition - Usually the player's position
|
||||
*/
|
||||
void UpdateChunksAroundPosition(const FVector& CenterPosition);
|
||||
|
||||
/**
|
||||
* Load a single chunk at the given coordinate.
|
||||
*
|
||||
* CONCEPT:
|
||||
* - Create chunk data
|
||||
* - Generate terrain
|
||||
* - Build mesh
|
||||
* - Create visual component
|
||||
*
|
||||
* @param ChunkCoord - Which chunk to load
|
||||
*/
|
||||
void LoadChunk(const FIntVector& ChunkCoord);
|
||||
|
||||
/**
|
||||
* Unload a single chunk.
|
||||
*
|
||||
* CONCEPT:
|
||||
* - Remove and destroy the mesh component
|
||||
* - Remove chunk data from storage
|
||||
*
|
||||
* @param ChunkCoord - Which chunk to unload
|
||||
*/
|
||||
void UnloadChunk(const FIntVector& ChunkCoord);
|
||||
|
||||
/**
|
||||
* Apply mesh data to a RealtimeMesh component.
|
||||
*
|
||||
* CONCEPT:
|
||||
* - Get or create the mesh component for this chunk
|
||||
* - Set the mesh data (vertices, triangles, etc.)
|
||||
*
|
||||
* @param ChunkCoord - Which chunk this mesh belongs to
|
||||
* @param MeshData - The generated mesh data
|
||||
*/
|
||||
void ApplyMeshToChunk(const FIntVector& ChunkCoord, const FVoxelMeshData& MeshData);
|
||||
|
||||
//=========================================================================
|
||||
// HELPERS
|
||||
//=========================================================================
|
||||
|
||||
/** Get the current player position (or zero if no player) */
|
||||
FVector GetPlayerPosition() const;
|
||||
|
||||
/** Check if a chunk coordinate is within view distance of a center chunk */
|
||||
bool IsChunkInRange(const FIntVector& ChunkCoord, const FIntVector& CenterChunk) const;
|
||||
|
||||
/**
|
||||
* Determine LOD level for a chunk based on its distance from the center.
|
||||
*
|
||||
* LOD CONCEPT:
|
||||
* Chunks close to the player get full resolution (LOD0, Step=1).
|
||||
* Chunks further away get coarser resolution (LOD1=Step 2, LOD2=Step 4).
|
||||
* This dramatically reduces triangle count for distant terrain without
|
||||
* visible quality loss (they're far away!).
|
||||
*
|
||||
* @param ChunkCoord - The chunk to evaluate
|
||||
* @param CenterChunk - The player's current chunk
|
||||
* @return LOD level: 0 (full), 1 (half), 2 (quarter)
|
||||
*/
|
||||
int32 GetLODForChunk(const FIntVector& ChunkCoord, const FIntVector& CenterChunk) const;
|
||||
|
||||
/**
|
||||
* Convert LOD level to marching cubes step size.
|
||||
* LOD0 → Step 1 (every voxel)
|
||||
* LOD1 → Step 2 (every 2nd voxel)
|
||||
* LOD2 → Step 4 (every 4th voxel)
|
||||
*/
|
||||
static int32 LODToStep(int32 LODLevel);
|
||||
|
||||
//=========================================================================
|
||||
// ASYNC
|
||||
//=========================================================================
|
||||
// MPSC: up to MaxConcurrentTasks ChunkGen worker threads Enqueue concurrently,
|
||||
// the game thread (ProcessPendingChunks) is the only consumer. The default Spsc
|
||||
// mode is NOT safe for multiple producers — concurrent Enqueues race on the tail
|
||||
// link and silently drop results, which leaks PendingChunkCoord slots until the
|
||||
// budget is exhausted and streaming stalls permanently. Mpsc guards the producer side.
|
||||
TQueue<FChunkResult, EQueueMode::Mpsc> ProcessQueue;
|
||||
TSet<FIntVector> PendingChunkCoord;
|
||||
|
||||
// Set to true during EndPlay — async tasks check this before accessing UObjects
|
||||
std::atomic<bool> bShuttingDown{false};
|
||||
|
||||
// Number of async tasks currently running — EndPlay waits for this to reach 0
|
||||
std::atomic<int32> ActiveTaskCount{0};
|
||||
|
||||
// Last known player chunk coord — used by LoadChunk to compute LOD
|
||||
FIntVector CurrentCenterChunk = FIntVector::ZeroValue;
|
||||
|
||||
// Track current LOD per loaded chunk (for LOD transitions)
|
||||
TMap<FIntVector, int32> ChunkLODs;
|
||||
|
||||
// --- Streaming work-avoidance (perf) ---
|
||||
// The desired chunk set only changes when the player crosses a chunk boundary.
|
||||
// We cache it and only rebuild/cull/sort on a real move, and go idle once every
|
||||
// desired chunk is streamed in — so a stationary player costs ~nothing per frame.
|
||||
FIntVector LastUpdateCenter = FIntVector(INT32_MAX, INT32_MAX, INT32_MAX);
|
||||
bool bAllChunksLoaded = false;
|
||||
TArray<FIntVector> DesiredSorted; // desired coords, nearest-first
|
||||
TSet<FIntVector> DesiredSet; // O(1) membership for the cull pass
|
||||
|
||||
void ProcessPendingChunks();
|
||||
|
||||
/**
|
||||
* Re-queue loaded chunks for async re-generation + re-meshing.
|
||||
* Used after terrain modifications: the old mesh stays visible until
|
||||
* the new async result comes back, so there's no visual pop.
|
||||
*
|
||||
* Chunks that aren't currently loaded are ignored (they'll generate
|
||||
* fresh with the diff layer when loaded normally).
|
||||
*
|
||||
* @param DirtyCoords - Chunk coordinates that need re-meshing
|
||||
*/
|
||||
void RemeshDirtyChunks(const TArray<FIntVector>& DirtyCoords);
|
||||
};
|
||||
Reference in New Issue
Block a user