#ifndef PROC_NODES_NOISE_NODES_H
#define PROC_NODES_NOISE_NODES_H
#include "../graph/proc_node.h"
#include "erosion_wrapper_glsl.h" // HeightBlockMips, the shared sampler
#include <map>
#include <vector>
namespace godot {
// Simplex Noise — Ashima polynomial permutation (matches GPU + Blender GeoNodes group)
class NoiseTextureNode : public ProcNode {
GDCLASS(NoiseTextureNode, ProcNode);
protected:
static void _bind_methods();
float scale = 5.0f;
int detail = 4;
float roughness = 0.5f;
float lacunarity = 2.0f;
public:
String compile_glsl_prelude(int p_node_idx) const override;
StringName get_node_type() const override { return "SimplexNoise"; }
String get_node_title() const override { return "Simplex Noise"; }
String get_node_category() const override { return "Texture"; }
bool supports_gpu() const override { return true; }
void set_scale(float p_scale) { scale = p_scale; }
float get_scale() const { return scale; }
void set_detail(int p_detail) { detail = p_detail; }
int get_detail() const { return detail; }
void set_roughness(float p_rough) { roughness = p_rough; }
float get_roughness() const { return roughness; }
void set_lacunarity(float p_lac) { lacunarity = p_lac; }
float get_lacunarity() const { return lacunarity; }
void execute(ProcEvalContext &p_ctx, int p_node_idx) override;
String compile_glsl(const GlslEmitContext &p_ctx, int p_node_idx) const override;
NoiseTextureNode();
};
// Noise Texture — Blender's exact Perlin noise (Jenkins hash + fade + trilinear)
// Mirrors Blender's ShaderNodeTexNoise: same sockets, same defaults, same names.
// Includes all 5 fractal variants via noise_type property.
class PerlinNoiseNode : public ProcNode {
GDCLASS(PerlinNoiseNode, ProcNode);
protected:
static void _bind_methods();
int noise_dimensions = 3; // 1-4
int noise_type = 1; // 0=Multifractal, 1=fBM, 2=Hybrid, 3=Ridged, 4=Hetero
bool normalize = true;
public:
StringName get_node_type() const override { return "NoiseTexture"; }
String get_node_title() const override { return "Noise Texture"; }
String get_node_category() const override { return "Texture"; }
// ON since 2026-09-05. It used to emit the OLD arithmetic -- a different
// lattice, no per-dimension scale constant, no distortion -- from a
// hand-written GLSL Perlin that nothing compared against this node's C++.
// There is no second implementation now: compile_glsl calls into
// blender_perlin.inc, the same text execute() runs through the shim.
bool supports_gpu() const override { return true; }
String compile_glsl(const GlslEmitContext &p_ctx, int p_node_idx) const override;
String compile_glsl_prelude(int p_node_idx) const override;
void set_noise_dimensions(int p_dim) { noise_dimensions = CLAMP(p_dim, 1, 4); }
int get_noise_dimensions() const { return noise_dimensions; }
void set_noise_type(int p_type) { noise_type = CLAMP(p_type, 0, 4); }
int get_noise_type() const { return noise_type; }
void set_normalize(bool p_norm) { normalize = p_norm; }
bool get_normalize() const { return normalize; }
void execute(ProcEvalContext &p_ctx, int p_node_idx) override;
PerlinNoiseNode();
};
// Displace — displaces vertices along normals using Perlin fBM noise.
// Faithful mirror of Blender's ProcNodes_Displace group (ShaderNodeTexNoise fBM).
// Sockets match Blender exactly: Geometry, Strength, Scale, Detail, Roughness, Lacunarity.
// For other noise variants (Ridged, Hybrid, etc.), compose PerlinNoiseNode → SetPosition.
class DisplaceNode : public ProcNode {
GDCLASS(DisplaceNode, ProcNode);
protected:
static void _bind_methods();
public:
StringName get_node_type() const override { return "Displace"; }
String get_node_title() const override { return "Displace"; }
String get_node_category() const override { return "Geometry"; }
bool supports_gpu() const override { return true; }
String compile_glsl_prelude(int p_node_idx) const override;
// Strength, Scale, Detail, Roughness, Lacunarity -- five floats in
// param0..param4. See ProcNode::get_push_constant_sockets().
PackedInt32Array get_push_constant_sockets() const override {
PackedInt32Array m;
m.push_back(1); m.push_back(2); m.push_back(3); m.push_back(4); m.push_back(5);
return m;
}
void execute(ProcEvalContext &p_ctx, int p_node_idx) override;
String compile_glsl(const GlslEmitContext &p_ctx, int p_node_idx) const override;
DisplaceNode();
};
// Voronoi/Worley noise — distance to nearest cell center + cell color
// Voronoi Texture — Blender's, ported in src/shared/blender_voronoi.h.
//
// What was here was a hand-rolled 3x3x3 F1 with a hash of its own: two input
// sockets against Blender's nine, two outputs against five, 3D only, one
// feature, one metric, no fractal octaves and no randomness. It produced
// plausible cells, which is the worst way for a bridge to be wrong -- a graph
// crosses, evaluates, and is a different function from the one the author was
// looking at in Blender.
class VoronoiTextureNode : public ProcNode {
GDCLASS(VoronoiTextureNode, ProcNode);
int voronoi_dimensions = 3; // 1..4
int feature = 0; // SHD_VORONOI_*: F1, F2, SMOOTH_F1, DISTANCE_TO_EDGE, N_SPHERE_RADIUS
int metric = 0; // EUCLIDEAN, MANHATTAN, CHEBYCHEV, MINKOWSKI
bool normalize = false;
protected:
static void _bind_methods();
public:
StringName get_node_type() const override { return "VoronoiTexture"; }
String get_node_title() const override { return "Voronoi Texture"; }
String get_node_category() const override { return "Texture"; }
// ON since 2026-09-05. The GLSL used to be a THIRD arithmetic -- a
// hand-rolled 3x3x3 F1 with a hash of its own. It now calls into
// blender_voronoi.inc, the same text execute() runs through the shim.
bool supports_gpu() const override { return true; }
String compile_glsl(const GlslEmitContext &p_ctx, int p_node_idx) const override;
String compile_glsl_prelude(int p_node_idx) const override;
void set_voronoi_dimensions(int p_d) { voronoi_dimensions = p_d; }
int get_voronoi_dimensions() const { return voronoi_dimensions; }
void set_feature(int p_f) { feature = p_f; }
int get_feature() const { return feature; }
void set_metric(int p_m) { metric = p_m; }
int get_metric() const { return metric; }
void set_normalize(bool p_n) { normalize = p_n; }
bool get_normalize() const { return normalize; }
void execute(ProcEvalContext &p_ctx, int p_node_idx) override;
VoronoiTextureNode();
};
// Wave Texture — sine/saw/triangle wave patterns
// Wave Texture — Blender's, which is bands or rings in four directions each,
// three profiles, and a Perlin fBM distortion pass. Ours was two sockets, one
// output, and a wave along (x + y + z) that matches none of Blender's
// directions.
class WaveTextureNode : public ProcNode {
GDCLASS(WaveTextureNode, ProcNode);
protected:
static void _bind_methods();
int wave_type = 0; // 0 BANDS, 1 RINGS
int bands_direction = 0; // X, Y, Z, DIAGONAL
int rings_direction = 0; // X, Y, Z, SPHERICAL
int wave_profile = 0; // SIN, SAW, TRI
public:
StringName get_node_type() const override { return "WaveTexture"; }
String get_node_title() const override { return "Wave Texture"; }
String get_node_category() const override { return "Texture"; }
// ON since 2026-09-05. The GLSL was the old (x + y + z) wave -- one
// direction, no rings, no profile, no distortion. It now mirrors
// execute(), with the distortion octaves coming from blender_perlin.inc.
bool supports_gpu() const override { return true; }
String compile_glsl(const GlslEmitContext &p_ctx, int p_node_idx) const override;
String compile_glsl_prelude(int p_node_idx) const override;
void set_wave_type(int p_t) { wave_type = p_t; }
int get_wave_type() const { return wave_type; }
void set_bands_direction(int p_d) { bands_direction = p_d; }
int get_bands_direction() const { return bands_direction; }
void set_rings_direction(int p_d) { rings_direction = p_d; }
int get_rings_direction() const { return rings_direction; }
void set_wave_profile(int p_p) { wave_profile = p_p; }
int get_wave_profile() const { return wave_profile; }
void execute(ProcEvalContext &p_ctx, int p_node_idx) override;
WaveTextureNode();
};
// White Noise — uniform random noise per position
// White Noise — Blender's, which is `hash_float_to_float` and
// `hash_float_to_float3` and nothing else. That makes it the exact oracle for
// the hash every other texture stands on, which is why it is ported first.
//
// Ours was `fract(sin(dot(...)))` with one input socket and one output: the
// right shape, unrelated numbers.
class WhiteNoiseNode : public ProcNode {
GDCLASS(WhiteNoiseNode, ProcNode);
int noise_dimensions = 3; // 1..4, matching Blender's noise_dimensions
protected:
static void _bind_methods();
public:
StringName get_node_type() const override { return "WhiteNoise"; }
String get_node_title() const override { return "White Noise"; }
String get_node_category() const override { return "Texture"; }
// ON since 2026-09-05, and this is the first of the five texture nodes to
// come back, because it is the one with nothing above the hash. There is
// no second implementation to drift from: compile_glsl emits calls into
// blender_hash.inc, which is the same text execute() runs through the C++
// shim. What used to be here -- "the GLSL is NOT this function" -- was
// true of a hand-written copy, and the way to stop saying it was to
// remove the copy rather than to write a better one.
bool supports_gpu() const override { return true; }
String compile_glsl(const GlslEmitContext &p_ctx, int p_node_idx) const override;
String compile_glsl_prelude(int p_node_idx) const override;
void set_noise_dimensions(int p_d) { noise_dimensions = p_d; }
int get_noise_dimensions() const { return noise_dimensions; }
void execute(ProcEvalContext &p_ctx, int p_node_idx) override;
WhiteNoiseNode();
};
// Gradient Texture — linear/radial/spherical gradient
class GradientTextureNode : public ProcNode {
GDCLASS(GradientTextureNode, ProcNode);
protected:
static void _bind_methods();
int gradient_type = 0; // 0=LINEAR, 1=RADIAL, 2=SPHERICAL
public:
StringName get_node_type() const override { return "GradientTexture"; }
String get_node_title() const override { return "Gradient Texture"; }
String get_node_category() const override { return "Texture"; }
bool supports_gpu() const override { return true; }
void set_gradient_type(int p_type) { gradient_type = p_type; }
int get_gradient_type() const { return gradient_type; }
void execute(ProcEvalContext &p_ctx, int p_node_idx) override;
String compile_glsl(const GlslEmitContext &p_ctx, int p_node_idx) const override;
GradientTextureNode();
};
// GradientNoise — iq's gradient noise with analytical derivatives.
// Returns height + dh/dx + dh/dz per vertex. Outputs float field (height)
// and vec3 field (height, dh/dx, dh/dz). Essential for erosion pipeline.
class GradientNoiseNode : public ProcNode {
GDCLASS(GradientNoiseNode, ProcNode);
protected:
static void _bind_methods();
public:
StringName get_node_type() const override { return "GradientNoise"; }
String get_node_title() const override { return "Gradient Noise"; }
String get_node_category() const override { return "Noise"; }
bool supports_gpu() const override { return true; }
String compile_glsl(const GlslEmitContext &p_ctx, int p_node_idx) const override;
void execute(ProcEvalContext &p_ctx, int p_node_idx) override;
GradientNoiseNode();
};
// FractalTerrain — complete eroded terrain generator in one node.
// Exact translation of runevision's Shadertoy: base FractalNoise + Erosion.
// Takes a flat MeshGrid, outputs fully displaced + eroded terrain.
// All-in-one for convenience. Parameters match Shadertoy defaults.
class FractalTerrainNode : public ProcNode {
GDCLASS(FractalTerrainNode, ProcNode);
protected:
static void _bind_methods();
public:
StringName get_node_type() const override { return "FractalTerrain"; }
String get_node_title() const override { return "Fractal Terrain"; }
String get_node_category() const override { return "Noise"; }
void execute(ProcEvalContext &p_ctx, int p_node_idx) override;
FractalTerrainNode();
};
// Erosion Noise — directional erosion gullies with analytical derivatives.
// clayjohn (2018) → Fewes (2023) → runevision (2025). MIT licensed.
// Cosine-wave stripes along terrain slope with multi-octave branching.
// Each octave modifies the slope via analytical derivatives, causing
// natural gully branching without recomputing slope from geometry.
//
// Key: cos(waveInput) for height = peaks get no erosion (cos=1 at zero slope).
// slopePower sharpens peaks. cellScale controls gully length.
// ---------------------------------------------------------------------------
// HeightfieldNode — terrain from a baked heightmap tile.
//
// Emits gn_height / gn_slope, the SAME two names GradientNoiseNode emits, so it
// drops straight into ErosionNoise's Height/Slope sockets (7/8) with nothing
// else changed. Swap the node, get a different terrain: that is the whole point
// of a composed density graph, and it is why this is a node rather than another
// flag on the generator.
//
// The tile is the node's own argument. dc-terrain never learns what a
// heightfield is -- it transports the floats as opaque custom data and meshes
// whatever density body it is handed.
//
// ONE TILE FOR NOW. The sampler already addresses in world metres against an
// origin, so a grid of tiles is a change to hblk_texel's addressing (pick the
// tile, then the texel) and nothing above it. Confirm one first.
class HeightfieldNode : public ProcNode {
GDCLASS(HeightfieldNode, ProcNode);
String tile_path_;
int n_ = 0;
float mpp_ = 30.0f, ox_ = 0.0f, oz_ = 0.0f;
PackedFloat32Array data_;
HeightBlockMips mips_;
// ---- THE TILE LIST -------------------------------------------------------
//
// The 900 km world is 119x119 = 14,161 tiles and 841 are generated, so the
// set is INCOMPLETE and it GROWS. A stitched block cannot express that: it
// fixes membership at bake time and there is no solid 119x119 to stitch.
//
// So tiles are held as a LIST keyed by world position, absent cells read as
// a flat STUB rather than as an error, and add_tile() works at runtime.
// Those three are one requirement, not three: you cannot ship an incomplete
// world without a stub, and a stub is only useful if it can later be
// replaced without a reload.
//
// ⚠ ADDITIVE AND INERT. data_/mips_/get_gpu_data() are untouched and still
// describe the single tile_path_ tile, so every existing map emits exactly
// the GLSL it emitted before. Nothing consumes this list yet; the GPU-side
// addressing change ("pick the tile, then the texel") is its own step.
struct HTile {
int n = 0; // stored side INCLUDING the 1 px apron
float mpp = 30.0f, ox = 0.0f, oz = 0.0f;
std::vector<float> h; // n*n, row = z, column = x
};
std::map<int64_t, HTile> tiles_;
float stub_height_ = 0.0f;
// The world the table is sized to. map_dc_diffusion.gd: root span =
// 28 * 2^(OCTREE_DEPTH-1) * VOXEL_SIZE, OCTREE_DEPTH 16. A PROPERTY, not a
// constant, because sizing the table to the tiles you hold is what makes a
// new tile recompile the shader.
float world_span_m_ = 28.0f * 32768.0f;
HeightGridPack grid_; // rebuilt whenever tiles_ changes
void _rebuild_grid();
void _grid_upsert(int p_tx, int p_ty);
// ⚠ AXIS SWAP, and it is stitch_block.py's convention, not a choice here:
// "tx indexes the FIRST array axis -> WORLD Z; ty indexes the SECOND ->
// WORLD X". Getting this backwards mirrors the world and looks plausible.
static int64_t _key(int tx, int ty) {
return ((int64_t)tx << 32) ^ (uint32_t)ty;
}
bool _load_hblk(const String &p_path, HTile &out) const;
protected:
static void _bind_methods();
public:
HeightfieldNode();
StringName get_node_type() const override { return "Heightfield"; }
String get_node_title() const override { return "Heightfield"; }
String get_node_category() const override { return "Terrain"; }
bool supports_gpu() const override { return true; }
String compile_glsl(const GlslEmitContext &p_ctx, int p_node_idx) const override;
String compile_glsl_prelude(int p_node_idx) const override;
void execute(ProcEvalContext &p_ctx, int p_node_idx) override;
void set_tile_path(const String &p_path);
String get_tile_path() const { return tile_path_; }
// ---- tile list (see the note above) --------------------------------------
/// Load one per-tile .hblk into the grid. Safe to call at RUNTIME.
bool add_tile(const String &p_path);
/// Height for absent cells. A flat stub, not an error.
void set_stub_height(float p_h) { stub_height_ = p_h; }
float get_stub_height() const { return stub_height_; }
int tile_count() const { return (int)tiles_.size(); }
bool has_tile(int p_tx, int p_ty) const { return tiles_.count(_key(p_tx, p_ty)) > 0; }
/// World-metre sample across the whole grid; stub where no tile is loaded.
float sample_height(float p_x, float p_z) const;
void clear_tiles() { tiles_.clear(); _rebuild_grid(); }
void set_world_span(float p_m) { world_span_m_ = p_m; _rebuild_grid(); }
float get_world_span() const { return world_span_m_; }
bool has_grid() const { return grid_.ok && grid_.tiles > 0; }
/// The packed layout's scalars, for the engine's CPU twin. Keys match
/// HeightGridDesc's fields.
Dictionary get_grid_info() const;
// The opaque floats dc-terrain uploads. THE GRID WINS when tiles are loaded:
// data_/mips_ still describe the single tile_path_ tile, so a graph that has
// neither keeps emitting exactly what it emitted before.
PackedFloat32Array get_gpu_data() const;
int get_tile_size() const { return n_; }
float get_tile_mpp() const { return mpp_; }
Vector2 get_tile_origin() const { return Vector2(ox_, oz_); }
};
class ErosionNoiseNode : public ProcNode {
GDCLASS(ErosionNoiseNode, ProcNode);
protected:
static void _bind_methods();
public:
StringName get_node_type() const override { return "ErosionNoise"; }
String get_node_title() const override { return "Erosion Noise"; }
String get_node_category() const override { return "Noise"; }
bool supports_gpu() const override { return true; }
String compile_glsl(const GlslEmitContext &p_ctx, int p_node_idx) const override;
void execute(ProcEvalContext &p_ctx, int p_node_idx) override;
ErosionNoiseNode();
};
// AltitudeColor — colors vertices by height using the Shadertoy material recipe.
// cliff(grey) → dirt(brown) → grass(green) → snow(white) → sand → water(blue).
// Takes geometry, reads Y height, outputs geometry with vertex colors.
class AltitudeColorNode : public ProcNode {
GDCLASS(AltitudeColorNode, ProcNode);
protected:
static void _bind_methods();
public:
StringName get_node_type() const override { return "AltitudeColor"; }
String get_node_title() const override { return "Altitude Color"; }
String get_node_category() const override { return "Utility"; }
bool supports_gpu() const override { return true; }
String compile_glsl(const GlslEmitContext &p_ctx, int p_node_idx) const override;
void execute(ProcEvalContext &p_ctx, int p_node_idx) override;
AltitudeColorNode();
};
// SlopeGradient — computes terrain slope direction from vertex positions.
// Takes geometry, outputs per-vertex 2D slope direction (as vec3 with Y=0).
// Uses finite differences from neighboring vertices.
class SlopeGradientNode : public ProcNode {
GDCLASS(SlopeGradientNode, ProcNode);
protected:
static void _bind_methods();
public:
StringName get_node_type() const override { return "SlopeGradient"; }
String get_node_title() const override { return "Slope Gradient"; }
String get_node_category() const override { return "Geometry"; }
void execute(ProcEvalContext &p_ctx, int p_node_idx) override;
SlopeGradientNode();
};
} // namespace godot
#endif // PROC_NODES_NOISE_NODES_H