#ifndef DC_TERRAIN_GENERATOR_PROCNODES_H
#define DC_TERRAIN_GENERATOR_PROCNODES_H
#include "dc_terrain_generator.h"
#include <godot_cpp/variant/string.hpp>
#include <string>
// HeightGridDesc: the packed tile grid's layout, shared with the GLSL emitter
// and the C++ twin so the three cannot describe the buffer differently.
#include "erosion_wrapper_glsl.h"
namespace godot {
// Bridges a proc-nodes ProcNodeGraph to dc-terrain's DCGenerator interface.
// Uses ClassDB reflection (no compile-time dependency on proc-nodes).
//
// GPU path: compiles the graph to a GLSL density function via
// ProcNodeGraph::compile_to_glsl_function(), then wraps it with an adapter
// to match dc-terrain's evaluate_density(float x, float y, float z) signature.
//
// Inspector: auto-discovers Float/Int socket defaults from the graph and
// exposes them as grouped inspector properties (e.g. "Gradient Noise/Scale").
// Changing a property updates the graph, marks GLSL dirty, and triggers rebuild.
class DCTerrainGeneratorProcNodes : public DCTerrainGenerator {
GDCLASS(DCTerrainGeneratorProcNodes, DCTerrainGenerator)
// The ProcNodeGraph resource (stored as generic Resource for cross-extension)
Ref<Resource> graph_;
// Terrain height: XZ world coords divided by this to get UV-like space.
float terrain_height_ = 32.0f;
// Floor/ceiling: the terrain surface fills [floor_y, ceiling_y].
// Valleys at floor_y, peaks at ceiling_y.
float floor_y_ = 0.0f;
float ceiling_y_ = 10.0f;
// Mountain layer: low-frequency noise that creates large-scale terrain shape.
// The erosion detail rides on top of this.
// RELIEF FIELD — makes mountain amplitude a FUNCTION OF PLACE.
//
// mountain_height_ is one scalar for the whole world, so every region gets the
// same amplitude and you cannot have an 8 km range and a 400 m one in the same
// scene: adding octaves adds DETAIL, never a different amount of relief
// somewhere else. This is a second, much lower-frequency noise that scales the
// mountain term, so "how mountainous is it here" varies across the map.
//
// relief_scale_ = 0 DISABLES it (amplitude is uniform, exactly as before), so
// every existing generator keeps its current output.
// PERSISTENCE, which was hardcoded 0.5 and is why the terrain is smooth
// underfoot. Octave k has amplitude gain^k of an amplitude set by
// mountain_height * y_range — so at 0.5, by the time the wavelength reaches
// player scale the amplitude is metres and the ground is a ramp. Measured:
// raising mountain_octaves 4 -> 12 moved local roughness 0.72 -> 0.98 m per
// 2 m stride; it cannot fix this, because it only adds ever-tinier ripples.
// 0.5 is the previous behaviour exactly.
// ── THE EROSION FILTER'S OWN KNOBS ────────────────────────────────────
// Baked literals inside erosion_node_eval until now, because GpuCompiler's
// push-constant block is param0..param6 -- seven floats, a hard cap, and
// rounding and onset alone need eight. That cap only ever applied to SOCKET
// parameters; pn_fade_div and pn_erosion_scale already escaped it by being
// globals the wrapper sets. These take the same door.
//
// Defaults are runevision's reference values, so an untouched generator
// produces exactly the previous surface.
// scales the incoming height field into the filter's space
// frequency step per gully octave
// scales the gully contribution down
// LOWER keeps high-frequency gullies to steeper slopes
// only gullies above this are normalised -- keeps peaks pointy
// flat offset, times the accumulated octave magnitude
// mixes that offset toward -fade_target
// rounding applied in creases
// rounding applied on ridges
// the INPUT height function's feature size -- must move when terrain_height does
// rounding multiplier compounded per octave
// slope at which gullies begin at all
// per-octave slope gate
// the same, for the ridge map
// per-octave ridge gate
// the slope magnitude to pretend the input has
// 0 = use the real slope, 1 = fully assumed
// ── Baked heightfield (hyper-games #50 section 5.3) ────────────────
// A terrain-diffusion block in METRES, from terrain-lookdev/stitch_block.py.
// When loaded it REPLACES the mountain layer: pn_terrain_offset stops being
// the multifractal and becomes the field, which is exactly where the model's
// large forms belong -- the erosion filter then carves the model's landscape
// instead of an fBm blob, and the fine gn base noise is untouched.
//
// The geometry lives here as literals baked into the emitted GLSL, so
// changing block invalidates the shader (a recompile), not a uniform.
String heightfield_path_;
std::vector<float> hblk_;
int hblk_n_ = 0;
float hblk_mpp_ = 30.0f, hblk_ox_ = 0.0f, hblk_oz_ = 0.0f;
bool _load_heightfield(const String &p_path);
// HEIGHTFIELD ONLY — the isolation switch.
//
// Density becomes height(x,z) - y and nothing else: no graph, no erosion, no
// noise library, no filter-space conversion. It exists because "the terrain
// is just perlin noise" has exactly one cheap explanation -- the GPU shader
// failed and DCCompute fell back to the CPU path, which evaluates the fBm
// mountain -- and the only way to tell that apart from a wrong field is to
// remove everything that could be wrong except the field itself.
//
// Both paths implement it, so it also answers WHICH path ran: they cannot
// disagree here, because there is nothing to disagree about.
bool heightfield_only_ = false;
// LOOKDEV WRAPPER — emit the SHARED wrapper instead of this file's.
//
// _recompile_glsl assembles ~47 KB of engine-specific GLSL: a multifractal
// mountain layer, a relief layer, a 4-octave detail fBm, a y-mapping and a
// clip. The lookdev is none of that -- it is the heightfield and the erosion
// filter, full stop. Swapping ONE term inside the engine wrapper and calling
// it "identical to lookdev" was wrong: the detail layer alone puts +/-30 m of
// fBm into the output that the lookdev never had.
//
// With this on the generator emits, instead:
// noise library (GpuCompiler::get_noise_library_glsl)
// hblk sampler (ours -- storage is per-consumer)
// erosion_wrapper_glsl(heightmap_source = true)
// evaluate_density(x,y,z) { return gpu_wrapper(x,y,z); }
//
// gpu_wrapper is the function erosion_render compiles and test_erosion_parity
// guards, so the engine runs the lookdev's shader rather than a transcription
// of it. That removes the entire hand-assembled wrapper as a source of error.
// DETAIL LAYER — high frequency with its OWN amplitude, in METRES.
//
// Separate from the mountain fBm because the two want different amplitude
// laws: ranges want a big smooth cascade, ground wants centimetre-to-metre
// roughness everywhere. Expressing it as a fraction of [floor_y, ceiling_y]
// like mountain_height does would mean typing 0.0005 for 5 m over a 10 km
// band, so this is metres and converted internally.
//
// NOT MULTIPLIED BY THE RELIEF FIELD, deliberately. relief_amp scales the
// mountains so an Everest region and a Vermont region can share a map — but
// a Vermont hillside still has rock underfoot. Scaling detail by relief too
// made the low-relief lobes glassy, which is where the player spawns.
// LACUNARITY, hardcoded 2.0 until now — and 2.0 is exactly why the terrain
// reads as self-similar. With frequency doubling and gain constant, every
// octave is a scaled copy of the last and they all land on harmonics of each
// other. A non-integer ratio (~1.87) stops them lining up. Applied to BOTH
// the mountain and detail fBm, since the repetition shows in both.
// Gully band position: coarsest gully wavelength is
// terrain_height * erosion_scale * cell_scale, in world metres. Was baked at
// 0.15 inside erosion_node_eval, which locked the gully band to a fixed
// fraction of the terrain scale -- so raising terrain_height for view
// distance dragged the gullies up with it.
// Multifractal blend for the MOUNTAIN layer. 0 = plain fBm (bit-identical to
// fractal_noise_2d, verified), 1 = each octave's amplitude weighted by the
// elevation accumulated so far, giving rugged peaks above smooth valleys.
// Measured at 1.0: roughness on high ground is 5.8x that on low ground,
// against 0.98 -- i.e. homogeneous -- for fBm.
// SIGNED-SQRT HEIGHT SPACE — opt in, default off.
//
// Real terrain's local roughness grows with local elevation; xandergos/
// terrain-diffusion measures this directly and works in z' = sign(z)*sqrt(|z|)
// so the dependence disappears (their elevation_dataset.py:269).
//
// Applied to the WORLD-HEIGHT side, because this function evaluates d at a
// given y rather than solving for the surface:
// un = (y - floor_y) / y_range // 0..1 across the band
// y_mapped = sign(un) * sqrt(|un|) * height_scale
// Endpoints are unchanged (H=0 -> floor_y, H=height_scale -> ceiling_y), so no
// other parameter shifts meaning. The surface becomes
// y = floor_y + y_range * (H/height_scale)^2
// so dy/dH = 2*y_range*H/height_scale^2: a CONSTANT noise amplitude produces
// elevation-proportional relief, ~5x at the top of the band versus the floor.
// That is what relief_scale/relief_floor were bolted on to fake.
bool height_space_sqrt_ = false;
// EROSION ON OR OFF — a real toggle, not a strength of zero.
//
// The heightmap IS the terrain: the Heightfield node samples the baked tile
// and displaces the surface to it, which is a complete map by itself. The
// erosion filter runs on a finer grid than the model's 30/90 m and
// synthesises the detail below its resolution. Turning it off must not move
// the ground, only remove that detail.
//
// False skips the ErosionNoise node when emitting, and because nothing else
// in a heightfield graph mentions noise, the whole noise library goes with
// it -- 60 KB of shader down to 5.6 KB. Strength 0 would cost the same
// octave loop and leave the library in place.
bool erosion_enabled_ = true;
// EROSION-MINIMAL — the wrapper with nothing in it but the erosion.
//
// The general wrapper carries a mountain layer, a relief envelope and a
// detail fBm, because it was written when the mountain WAS the terrain. On
// a heightfield map the baked tile is the terrain and those three layers are
// dead weight: mountain_height is 0 and detail_scale is 0, so they emit
// nothing but their comments and a det_offset of zero that y_mapped still
// subtracts.
//
// Dead weight is not free. Every one of them is a branch that can be
// mis-gated, and mis-gating is exactly what hid the erosion filter's own
// parameters inside want_mountain (hyper-games #53 section 2). This path has
// no branches to mis-gate: heightfield in, filter, y-map, clip.
bool erosion_minimal_ = false;
// ── Layer and filter parameters ───────────────────────────────────────
float mountain_scale_ = 1.0f;
float mountain_height_ = 0.5f;
float mountain_gain_ = 0.5f;
float mountain_variance_ = 0.0f;
float relief_scale_ = 0.0f;
float relief_floor_ = 0.15f;
float detail_scale_ = 0.0f;
float detail_height_ = 0.0f;
float detail_gain_ = 0.5f;
float noise_lacunarity_ = 2.0f;
float erosion_scale_ = 0.15f;
// The FIELD's half-relief in metres, for the SLOPE normalisation only.
// 0 = fall back to the playable y_range. See erosion_wrapper_glsl.h.
float field_half_range_ = 0.0f;
float input_height_amplitude_ = 0.25f;
float octave_lacunarity_ = 2.0f;
float gully_depth_ = 0.5f;
float fine_gully_slope_restriction_ = 1.5f;
float gully_normalize_threshold_ = 0.5f;
float height_offset_ = -0.65f;
float height_offset_follows_altitude_ = 0.0f;
float crease_rounding_ = 0.1f;
float ridge_rounding_ = 0.0f;
// Detail-layer slope mask: full detail below lo, none above hi.
// Units are gn-per-p, the same as pn_terrain_offset.yz.
// ── SUBMARINE TERRAIN ──────────────────────────────────────────────
// Below a smooth coastal buffer the seabed stops being a heightfield and
// becomes VOLUMETRIC: a 3D fBm added to the density, after Geiss (GPU Gems
// 3 ch.1). A heightfield cannot contain a sea cave, an arch or an overhang,
// and the whole world is meant to be reachable.
//
// The buffer is why it is depth-gated rather than global. Beaches and the
// waterline need a single-valued surface -- water meeting a cave mouth at
// the shoreline has no clean coastline -- so the first tens of metres of
// depth stay a pure heightfield and the volumetric term fades in below.
//
// amp 0 DISABLES it, so a map that has not opted in is bit-identical.
float submarine_amp_ = 0.0f; // metres of 3D perturbation
float submarine_wavelength_ = 3000.0f;
int submarine_octaves_ = 4;
float submarine_roughness_ = 0.5f;
float submarine_buffer_start_ = 50.0f; // depth where it begins
float submarine_buffer_end_ = 400.0f; // depth where it is full
float submarine_water_y_ = 700.0f;
// Depth over which erosion fades out below the waterline, metres. Erosion
// is subaerial: it stops AT the shoreline in reality, but a hard cut leaves
// a visible seam along every coast, so it ramps. 0 disables the mask.
float erosion_mask_depth_ = 150.0f;
// MUST MATCH ErosionNoiseNode's height_scale (its pc.param1). The depth
// conversion below needs it and the generator cannot see the node's
// parameters, so it is mirrored here -- and a mismatch shows up as an
// erosion mask that fades over the wrong depth, not as an error.
float erosion_height_scale_ = 5.0f;
float detail_slope_lo_ = 0.30f;
float detail_slope_hi_ = 1.20f;
// --- hypsometric land/sea curve (hypso_curve.inc) ---
float hypso_enabled_ = 0.0f; // 0 = off, and off is BIT-IDENTICAL to before this landed
float hypso_sea_ = 0.20f; // input at the middle of the continental slope; sets land fraction (0.20 -> 33.7%)
float hypso_shelf_width_ = 0.12f; // gentle shelf below sea
float hypso_slope_width_ = 0.05f; // steep drop half-width // half-width of the slope; smaller = sharper coast
float hypso_abyss_ = -0.62f; // OUTPUT value of the abyssal plain
float hypso_plat_slope_ = 0.13f; // continental platform gradient (gentle)
float hypso_mtn_onset_ = 0.35f; // where the mountain front begins
float hypso_mtn_width_ = 0.10f; // half-width of that front
float hypso_mtn_slope_ = 1.30f; // mountain gradient (steep)
float hypso_peak_onset_ = 0.60f; // where the summit lift begins
float hypso_peak_gain_ = 0.30f; // output added at h = 1
float input_feature_rounding_ = 0.1f;
float rounding_per_octave_ = 2.0f;
float gully_onset_slope_ = 1.25f;
float octave_onset_slope_ = 1.25f;
float ridge_onset_slope_ = 2.8f;
float ridge_octave_onset_ = 1.5f;
float assumed_slope_ = 0.7f;
float assumed_slope_mix_ = 1.0f;
int mountain_octaves_ = 2;
int detail_octaves_ = 4;
bool lookdev_wrapper_ = false;
// Cached GLSL shader source (adapter-wrapped for dc-terrain).
// Compiled eagerly on main thread — worker only reads cache.
std::string cached_glsl_;
// Bumped on every hblk_ mutation. See ProcNodesBridge::gpu_custom_data_version.
uint32_t hblk_version_ = 0;
// The packed tile grid's layout, when the node supplies one. Empty means
// hblk_ is a single square and every CPU site addresses it the old way.
HeightGridDesc hgrid_;
// ONE CPU heightfield sampler. There were FOUR copies of this bilinear --
// two plain and two inside lambdas -- and the tile grid would have needed a
// fifth. Collapsing them is what makes "does the CPU read the same field as
// the GPU?" a question with one answer.
float _hblk_sample_cpu(float x, float z) const;
public:
// The ASSEMBLED shader string, exactly as handed to the compute pipeline.
// Exposed because it was unreachable: _recompile_glsl builds it with
// godot_cpp String concatenation, so nothing outside a running engine could
// see it, and every test of "does the shader compile" was therefore testing
// an approximation. That gap shipped a duplicate global declaration
// (pn_terrain_offset) which failed to compile and produced a world with no
// terrain and a clean-looking log. Dump this and run glslc on it.
String get_compiled_glsl() const { return String(cached_glsl_.c_str()); }
private:
// ── Auto-discovered graph parameters ──────────────────────────────
struct GraphParam {
int node_idx;
int socket_idx;
String node_title; // "Gradient Noise"
String socket_name; // "Scale"
Variant::Type type; // FLOAT or INT
Variant default_value;
};
Vector<GraphParam> discovered_params_;
void _discover_params();
void _collect_node_data();
// Bridge: implements DCGenerator interface
// THE CONSTANTS OF THE DENSITY WRAPPER THAT DO NOT DEPEND ON THE POINT.
//
// Resolved from the graph and the LOD once per grid instead of once per
// voxel. Every field is named as the local it replaced, so _eval_at's
// arithmetic is the same text it was when this was one function -- see the
// note above _resolve_eval_params in the .cpp.
struct EvalParams {
float lod_voxel_size = 0.0f;
float terrain_height = 32.0f, floor_y = 0.0f, ceiling_y = 10.0f;
float mtn_scale = 0.0f, mtn_height = 0.0f;
int mtn_octaves = 0;
float base_freq = 3.0f, gn_detail = 0.0f, gn_gain = 0.1f;
float en_height_scale = 5.0f, en_erosion_str = 0.16f, en_slope_power = 0.6f;
float en_cell_scale = 1.0f;
int en_octaves = 5;
float en_gain = 0.5f;
float inv_scale = 0.0f, y_range = 0.0f, y_scale = 0.0f, inv_y_scale = 0.0f;
bool graph_has_erosion = false;
};
EvalParams _resolve_eval_params(float lod_voxel_size) const;
float _eval_at(const EvalParams &p, float x, float y, float z) const;
class ProcNodesBridge : public DCGenerator {
DCTerrainGeneratorProcNodes *owner_;
public:
ProcNodesBridge(DCTerrainGeneratorProcNodes *o) : owner_(o) {}
float evaluate(float x, float y, float z, float lod_voxel_size) override;
void evaluate_batch(const float *p_xyz, int p_count, float p_lod_voxel_size,
float *r_out) override;
bool has_gpu_shader() const override;
const char *gpu_shader_source() const override;
const float *gpu_custom_data() const override;
int gpu_custom_data_count() const override;
uint32_t gpu_custom_data_version() const override;
};
ProcNodesBridge bridge_{ this };
void _recompile_glsl();
// WHY THE LAST COMPILE PRODUCED NOTHING, empty when it produced something.
// Bound so a headless gate can assert on it: "visible" has to mean
// answerable, not just printed. See _recompile_glsl in the .cpp.
//
// ⚠ THE INITIAL VALUE IS NOT EMPTY, and that is the point. _recompile_glsl
// only runs from set_graph()/_set(), so a generator nobody has configured
// has never attempted a compile -- and "" would make that indistinguishable
// from "compiled cleanly". Four states, four answers: "never compiled",
// "no graph assigned", a specific failure, or empty for success.
String last_compile_error_ = "never compiled (no graph has been assigned)";
protected:
static void _bind_methods();
// Dynamic inspector properties from graph
bool _set(const StringName &p_name, const Variant &p_value);
bool _get(const StringName &p_name, Variant &r_ret) const;
void _get_property_list(List<PropertyInfo> *p_list) const;
public:
DCGenerator *get_native_generator() override { return &bridge_; }
String get_last_compile_error() const { return last_compile_error_; }
bool has_compiled_shader() const { return !cached_glsl_.empty(); }
void set_graph(const Ref<Resource> &p_graph);
Ref<Resource> get_graph() const { return graph_; }
void set_terrain_height(float p_height);
float get_terrain_height() const { return terrain_height_; }
void set_floor_y(float p_val);
float get_floor_y() const { return floor_y_; }
void set_ceiling_y(float p_val);
float get_ceiling_y() const { return ceiling_y_; }
void set_heightfield_path(const String &p_path);
String get_heightfield_path() const { return heightfield_path_; }
void set_heightfield_only(bool p_val);
void set_erosion_enabled(bool p_val);
bool get_erosion_enabled() const { return erosion_enabled_; }
void set_erosion_minimal(bool p_val);
bool get_erosion_minimal() const { return erosion_minimal_; }
void set_mountain_scale(float p_val);
float get_mountain_scale() const { return mountain_scale_; }
void set_mountain_height(float p_val);
float get_mountain_height() const { return mountain_height_; }
void set_mountain_gain(float p_val);
float get_mountain_gain() const { return mountain_gain_; }
void set_mountain_variance(float p_val);
float get_mountain_variance() const { return mountain_variance_; }
void set_relief_scale(float p_val);
float get_relief_scale() const { return relief_scale_; }
void set_relief_floor(float p_val);
float get_relief_floor() const { return relief_floor_; }
void set_detail_scale(float p_val);
float get_detail_scale() const { return detail_scale_; }
void set_detail_height(float p_val);
float get_detail_height() const { return detail_height_; }
void set_detail_gain(float p_val);
float get_detail_gain() const { return detail_gain_; }
void set_noise_lacunarity(float p_val);
float get_noise_lacunarity() const { return noise_lacunarity_; }
void set_erosion_scale(float p_val);
float get_erosion_scale() const { return erosion_scale_; }
void set_field_half_range(float p_val);
float get_field_half_range() const { return field_half_range_; }
void set_input_height_amplitude(float p_val);
float get_input_height_amplitude() const { return input_height_amplitude_; }
void set_octave_lacunarity(float p_val);
float get_octave_lacunarity() const { return octave_lacunarity_; }
void set_gully_depth(float p_val);
float get_gully_depth() const { return gully_depth_; }
void set_fine_gully_slope_restriction(float p_val);
float get_fine_gully_slope_restriction() const { return fine_gully_slope_restriction_; }
void set_gully_normalize_threshold(float p_val);
float get_gully_normalize_threshold() const { return gully_normalize_threshold_; }
void set_height_offset(float p_val);
float get_height_offset() const { return height_offset_; }
void set_height_offset_follows_altitude(float p_val);
float get_height_offset_follows_altitude() const { return height_offset_follows_altitude_; }
void set_crease_rounding(float p_val);
float get_crease_rounding() const { return crease_rounding_; }
void set_submarine_amp(float v);
float get_submarine_amp() const { return submarine_amp_; }
void set_submarine_wavelength(float v);
float get_submarine_wavelength() const { return submarine_wavelength_; }
void set_submarine_octaves(int v);
int get_submarine_octaves() const { return submarine_octaves_; }
void set_submarine_buffer(float a, float b);
void set_submarine_water_y(float v);
void set_erosion_mask_depth(float v);
float get_erosion_mask_depth() const { return erosion_mask_depth_; }
float get_submarine_water_y() const { return submarine_water_y_; }
void set_detail_slope_lo(float p_val);
float get_detail_slope_lo() const { return detail_slope_lo_; }
void set_detail_slope_hi(float p_val);
float get_detail_slope_hi() const { return detail_slope_hi_; }
void set_hypso_enabled(float p_val);
float get_hypso_enabled() const { return hypso_enabled_; }
void set_hypso_sea(float p_val);
float get_hypso_sea() const { return hypso_sea_; }
void set_hypso_shelf_width(float p_val);
float get_hypso_shelf_width() const { return hypso_shelf_width_; }
void set_hypso_slope_width(float p_val);
float get_hypso_slope_width() const { return hypso_slope_width_; }
void set_hypso_abyss(float p_val);
float get_hypso_abyss() const { return hypso_abyss_; }
void set_hypso_plat_slope(float p_val);
float get_hypso_plat_slope() const { return hypso_plat_slope_; }
void set_hypso_mtn_onset(float p_val);
float get_hypso_mtn_onset() const { return hypso_mtn_onset_; }
void set_hypso_mtn_width(float p_val);
float get_hypso_mtn_width() const { return hypso_mtn_width_; }
void set_hypso_mtn_slope(float p_val);
float get_hypso_mtn_slope() const { return hypso_mtn_slope_; }
void set_hypso_peak_onset(float p_val);
float get_hypso_peak_onset() const { return hypso_peak_onset_; }
void set_hypso_peak_gain(float p_val);
float get_hypso_peak_gain() const { return hypso_peak_gain_; }
// Sea level in OUTPUT units -- hypso_curve(sea). The curve is
// monotonic so this is exact; do NOT also hand-set a water height.
float get_waterline_norm() const;
void set_ridge_rounding(float p_val);
float get_ridge_rounding() const { return ridge_rounding_; }
void set_input_feature_rounding(float p_val);
float get_input_feature_rounding() const { return input_feature_rounding_; }
void set_rounding_per_octave(float p_val);
float get_rounding_per_octave() const { return rounding_per_octave_; }
void set_gully_onset_slope(float p_val);
float get_gully_onset_slope() const { return gully_onset_slope_; }
void set_octave_onset_slope(float p_val);
float get_octave_onset_slope() const { return octave_onset_slope_; }
void set_ridge_onset_slope(float p_val);
float get_ridge_onset_slope() const { return ridge_onset_slope_; }
void set_ridge_octave_onset(float p_val);
float get_ridge_octave_onset() const { return ridge_octave_onset_; }
void set_assumed_slope(float p_val);
float get_assumed_slope() const { return assumed_slope_; }
void set_assumed_slope_mix(float p_val);
float get_assumed_slope_mix() const { return assumed_slope_mix_; }
void set_mountain_octaves(int p_val);
int get_mountain_octaves() const { return mountain_octaves_; }
void set_detail_octaves(int p_val);
int get_detail_octaves() const { return detail_octaves_; }
void set_lookdev_wrapper(bool p_val);
bool get_lookdev_wrapper() const { return lookdev_wrapper_; }
bool get_heightfield_only() const { return heightfield_only_; }
void set_height_space_sqrt(bool p_val);
bool get_height_space_sqrt() const { return height_space_sqrt_; }
// Force recompilation of GLSL shader (must call from main thread)
void recompile() { _recompile_glsl(); emit_changed(); }
};
} // namespace godot
#endif // DC_TERRAIN_GENERATOR_PROCNODES_H