OverviewFilesCommitsIssuesDocsReleasesPeers

dc-terrain / libdc / include / dc_density.h

194 lines · c ref:
#ifndef DC_DENSITY_H
#define DC_DENSITY_H

// Density function for Dual Contouring terrain generation.
// Pure C++ — no Godot dependencies. Evaluates a 7-layer noise field
// ported from voxel_terrain.gd.
//
// Positive density = solid, negative = air.
//
// Layers:
//   1. Domain warping (organic coordinate distortion)
//   2. Ground plane bias (weak downward pull)
//   3. Primary 3D noise (dominant shape — caves, masses)
//   4. Worm tunnels (two perpendicular noises carve tubes)
//   5. Ridged noise (pillars, arches, bridges)
//   6. Detail noise (surface roughness)
//   7. Voronoi floors/ceilings (flat walkable areas)
//   + Height clamping (solid floor, open sky)
//   + Flat spawn area (safe zone near origin)

#include "dc_edit.h"

#include <memory>
#include <vector>

// CSG primitive for density field composition.
// Primitives in the same group are combined sequentially (first = base, rest apply op).
// Each group result is UNION'd with the terrain density.
struct CSGPrimitive {
    enum Type : uint8_t { CSG_SPHERE = 0, CSG_BOX = 1 };
    enum Op : uint8_t { CSG_UNION = 0, CSG_SUBTRACT = 1, CSG_INTERSECT = 2 };
    Type type = CSG_SPHERE;
    Op op = CSG_UNION;
    int group = 0;
    float center[3] = {0.0f, 0.0f, 0.0f};
    float size[3] = {1.0f, 1.0f, 1.0f};  // sphere: [0]=radius, box: half-extents
};

struct DCDensityNoise; // forward declaration (defined in dc_density.cpp)

struct DensityConfig {
    int seed = 42;
    float voxel_size = 1.0f;  // used for Nyquist-aware octave clamping

    // When false, skip all noise layers — density = flat ground plane + CSG only.
    bool use_noise = true;

    // Frequencies are in cycles/meter. Nyquist limit: max freq = 0.5/voxel_size.
    // With fbm octaves and lacunarity=2, highest octave freq = base * 2^(oct-1).
    // Defaults tuned for voxel_size=1.0 (Nyquist=0.5):
    //   primary: 0.02 * 4 = 0.08 (wavelength 12.5m = 12.5 voxels) ✓
    //   tunnel:  0.03 * 2 = 0.06 (wavelength 16.7m) ✓
    //   ridge:   0.03 * 2 = 0.06 (wavelength 16.7m) ✓
    //   detail:  0.06 * 2 = 0.12 (wavelength 8.3m = 8 voxels) ✓
    //   warp:    0.015 * 2 = 0.03 (wavelength 33m) ✓, strength 4 = ±4 voxels
    float primary_frequency = 0.02f;
    float primary_strength = 1.2f;
    float tunnel_frequency = 0.03f;
    float tunnel_strength = 1.5f;
    float tunnel_radius = 0.3f;
    float ridge_frequency = 0.03f;
    float ridge_strength = 0.5f;
    float warp_frequency = 0.015f;
    float warp_strength = 4.0f;       // ±4m warp (was 8 — caused massive aliasing)
    float detail_frequency = 0.06f;
    float detail_strength = 0.15f;
    float ground_bias = 0.06f;        // slightly stronger ground plane
    float solid_floor_depth = 30.0f;
    float sky_height = 25.0f;
    float flat_center_radius = 12.0f;
    int voronoi_cell_count = 6;
    float floor_strength = 0.7f;
    float floor_height_min = -15.0f;
    float floor_height_max = 10.0f;
    float room_height = 8.0f;
    float terrain_size = 60.0f;

    // Spherical/planetoid mode
    bool spherical = false;
    float planet_radius = 250000.0f;
    float planet_center[3] = {0.0f, 0.0f, 0.0f};

    // Floating island mode
    bool floating_islands = false;
    float island_radius = 100.0f;      // XZ radius of main island
    float island_depth_ratio = 3.0f;   // Y depth = radius * ratio (iceberg)
    int island_count = 3;              // number of islands
    float island_spacing = 300.0f;     // distance between island centers

    // Cap on noise octaves (0 = no cap, use Nyquist limit).
    // Lower values = smoother surface while preserving large-scale features.
    // E.g., 6 keeps mountains but removes meter-scale bumps.
    int max_noise_octaves = 0;

    // Separate cap for ridge octaves (0 = use max_noise_octaves).
    // Ridge noise uses 1-|x| which creates derivative discontinuities at peaks.
    // Fewer octaves = broader, smoother ridges without stair-stepping.
    // E.g., 3 gives continent-scale ridges only.
    int max_ridge_octaves = 0;

    // Terrain edits: shared snapshot, safe for workers to read concurrently.
    // Set via DCTerrain — workers get a copy of the shared_ptr (ref-counted).
    std::shared_ptr<const std::vector<TerrainEdit>> edits;

    // CSG primitives: sorted by group, evaluated per-group then UNION'd with terrain.
    std::vector<CSGPrimitive> csg_primitives;

    // Force direct density evaluation (Gildea path) even for noise-only terrain.
    // Slower (~3x) but gives precise bisection everywhere.
    bool force_direct_eval = false;

    // Use GPU compute pipeline for terrain generation.
    // Falls back to CPU if GPU initialization fails.
    bool use_gpu = false;  // default off until validated

    // CSG refinement factor: subdivide N× finer near CSG boundaries.
    // The octree prunes empty cells, so cost = O(surface_area), not O(volume).
    // 2 = 2× finer (64 effective cells at LOD0), 4 = 4× finer (128 cells).
    int csg_refinement = 2;
};

class DCDensity {
public:
    struct VoronoiCell {
        float x;
        float z;
        float floor_height;
    };

    DCDensity();
    ~DCDensity();
    DCDensity(const DCDensity&) = delete;
    DCDensity& operator=(const DCDensity&) = delete;
    DCDensity(DCDensity&&) noexcept;
    DCDensity& operator=(DCDensity&&) noexcept;

    // Configure (or reconfigure) all noise generators and Voronoi cells.
    void configure(const DensityConfig& config);

    // Evaluate density at world position (x, y, z).
    // Returns positive for solid, negative for air.
    // Uses pre-computed octave counts from configure() (base voxel_size).
    float evaluate(float x, float y, float z) const;

    // Evaluate with per-LOD Nyquist clamping.
    // lod_voxel_size: actual voxel size at this LOD level (e.g., 8m for LOD4).
    // Computes octave counts on-the-fly to avoid aliasing at coarse LODs.
    float evaluate(float x, float y, float z, float lod_voxel_size) const;

    // Access config for inspection (e.g., tests).
    const DensityConfig& config() const { return config_; }

    // GPU upload accessors
    // Get permutation tables for all 6 noise generators (6 x 512 = 3072 ints).
    // Order: primary, tunnel_a, tunnel_b, ridge, warp, detail.
    std::vector<int> get_perm_tables() const;

    // Get Voronoi cell data: returns {x, z, floor_height} triples.
    const std::vector<VoronoiCell>& get_voronoi_cells() const { return voronoi_cells_; }

    // Get pre-computed octave counts.
    int get_primary_octaves() const { return primary_octaves_; }
    int get_tunnel_octaves() const { return tunnel_octaves_; }
    int get_ridge_octaves() const { return ridge_octaves_; }
    int get_warp_octaves() const { return warp_octaves_; }
    int get_detail_octaves() const { return detail_octaves_; }

private:
    DensityConfig config_;
    std::unique_ptr<DCDensityNoise> noise_;

    // Pre-computed Nyquist-clamped octave counts (set in configure())
    int primary_octaves_ = 3;
    int tunnel_octaves_ = 2;
    int ridge_octaves_ = 2;
    int warp_octaves_ = 2;
    int detail_octaves_ = 2;

    std::vector<VoronoiCell> voronoi_cells_;

    // Floating island density evaluation
    float evaluate_floating_islands(float x, float y, float z, float lod_voxel_size) const;

    // Spherical density evaluation (called from evaluate when spherical=true)
    float evaluate_spherical(float x, float y, float z) const;
    // Per-LOD spherical evaluation with Nyquist-correct octave counts
    float evaluate_spherical_lod(float x, float y, float z, float lod_voxel_size) const;
    // Internal flat terrain evaluation with explicit octave counts
    float evaluate_internal(float x, float y, float z,
                            int p_oct, int t_oct, int r_oct, int w_oct, int d_oct) const;
};

#endif // DC_DENSITY_H