OverviewFilesCommitsIssuesDocsReleasesPeers

proc-nodes / src / nodes / math_nodes.h

431 lines · c ref:
#ifndef PROC_NODES_MATH_NODES_H
#define PROC_NODES_MATH_NODES_H

#include "../graph/proc_node.h"

namespace godot {

// Math operations for scalars
// The operation is BLENDER'S DNA enum value, not one of our own -- see
// src/shared/blender_math_ops.h. There used to be a MathOp enum here with its
// own numbering and a hand-written table in the serializer reconciling the two,
// which listed 21 of Blender's 41 operations and turned the other 20 into ADD.

class MathNode : public ProcNode {
	GDCLASS(MathNode, ProcNode);

protected:
	static void _bind_methods();

	int operation = 0; // bmath::MATH_*

public:
	StringName get_node_type() const override { return "Math"; }
	String get_node_title() const override { return "Math"; }
	String get_node_category() const override { return "Math"; }
	// The GLSL was a DIFFERENT, smaller operation set under the old numbering,
	// so after renumbering to Blender's it would have computed the wrong
	// operation with nothing to say so. It is all 41 now, against the same
	// numbering bmath::math_op uses, and every one of them is compared to that
	// function on a real device by the standalone -lvulkan gate.
	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_operation(int p_op) { operation = p_op; }
	int get_operation() const { return operation; }

	void execute(ProcEvalContext &p_ctx, int p_node_idx) override;

	MathNode();
};

// Vector math operations
// Blender's DNA enum value, as for MathNode. The old table listed 11 of
// Blender's 30 vector operations.

class VectorMathNode : public ProcNode {
	GDCLASS(VectorMathNode, ProcNode);

protected:
	static void _bind_methods();

	int operation = 0; // bmath::VEC_*

public:
	StringName get_node_type() const override { return "VectorMath"; }
	String get_node_title() const override { return "Vector Math"; }
	String get_node_category() const override { return "Math"; }
	// The GLSL implements a DIFFERENT, smaller operation set under the old
	// numbering. After renumbering to Blender's it would compute the wrong
	// operation on the GPU with nothing to say so. Off until it is ported.
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_operation(int p_op) { operation = p_op; }
	int get_operation() const { return operation; }

	void execute(ProcEvalContext &p_ctx, int p_node_idx) override;

	VectorMathNode();
};

// Combine XYZ — 3 floats → Vector3
class CombineXYZNode : public ProcNode {
	GDCLASS(CombineXYZNode, ProcNode);

protected:
	static void _bind_methods();

public:
	StringName get_node_type() const override { return "CombineXYZ"; }
	String get_node_title() const override { return "Combine XYZ"; }
	String get_node_category() const override { return "Math"; }
	// Emits a real statement through GlslEmitContext now; it used to return an
	// expression FRAGMENT referencing identifiers nothing declared, which made
	// every shader using it fail to compile.
	bool supports_gpu() const override { return true; }

	void execute(ProcEvalContext &p_ctx, int p_node_idx) override;
	String compile_glsl(const GlslEmitContext &p_ctx, int p_node_idx) const override;

	CombineXYZNode();
};

// Separate XYZ — Vector3 → 3 floats
class SeparateXYZNode : public ProcNode {
	GDCLASS(SeparateXYZNode, ProcNode);

protected:
	static void _bind_methods();

public:
	StringName get_node_type() const override { return "SeparateXYZ"; }
	String get_node_title() const override { return "Separate XYZ"; }
	String get_node_category() const override { return "Math"; }
	// Emits a real statement through GlslEmitContext now; it used to return an
	// expression FRAGMENT referencing identifiers nothing declared, which made
	// every shader using it fail to compile.
	bool supports_gpu() const override { return true; }

	void execute(ProcEvalContext &p_ctx, int p_node_idx) override;
	String compile_glsl(const GlslEmitContext &p_ctx, int p_node_idx) const override;

	SeparateXYZNode();
};

// Boolean math operations
enum class BooleanMathOp : int32_t {
	And = 0,
	Or,
	Not,
	Xor,
	Nand,
	Nor,
	Xnor,
};

class BooleanMathNode : public ProcNode {
	GDCLASS(BooleanMathNode, ProcNode);

protected:
	static void _bind_methods();

	BooleanMathOp operation = BooleanMathOp::And;

public:
	StringName get_node_type() const override { return "BooleanMath"; }
	String get_node_title() const override { return "Boolean Math"; }
	String get_node_category() const override { return "Math"; }
	// Emits a real statement through GlslEmitContext now; it used to return an
	// expression FRAGMENT referencing identifiers nothing declared, which made
	// every shader using it fail to compile.
	bool supports_gpu() const override { return true; }

	void set_operation(int p_op) { operation = static_cast<BooleanMathOp>(p_op); }
	int get_operation() const { return static_cast<int>(operation); }

	void execute(ProcEvalContext &p_ctx, int p_node_idx) override;
	String compile_glsl(const GlslEmitContext &p_ctx, int p_node_idx) const override;

	BooleanMathNode();
};

// Compare operations
enum class CompareOp : int32_t {
	Less = 0,
	LessEqual,
	Greater,
	GreaterEqual,
	Equal,
	NotEqual,
};

class CompareNode : public ProcNode {
	GDCLASS(CompareNode, ProcNode);

protected:
	static void _bind_methods();

	CompareOp operation = CompareOp::Less;

public:
	StringName get_node_type() const override { return "Compare"; }
	String get_node_title() const override { return "Compare"; }
	String get_node_category() const override { return "Math"; }
	// Emits a real statement through GlslEmitContext now; it used to return an
	// expression FRAGMENT referencing identifiers nothing declared, which made
	// every shader using it fail to compile.
	bool supports_gpu() const override { return true; }

	void set_operation(int p_op) { operation = static_cast<CompareOp>(p_op); }
	int get_operation() const { return static_cast<int>(operation); }

	void execute(ProcEvalContext &p_ctx, int p_node_idx) override;
	String compile_glsl(const GlslEmitContext &p_ctx, int p_node_idx) const override;

	CompareNode();
};

// Clamp — clamp float to range
class ClampNode : public ProcNode {
	GDCLASS(ClampNode, ProcNode);

protected:
	static void _bind_methods();

public:
	StringName get_node_type() const override { return "Clamp"; }
	String get_node_title() const override { return "Clamp"; }
	String get_node_category() const override { return "Math"; }
	// Emits a real statement through GlslEmitContext now; it used to return an
	// expression FRAGMENT referencing identifiers nothing declared, which made
	// every shader using it fail to compile.
	bool supports_gpu() const override { return true; }

	void execute(ProcEvalContext &p_ctx, int p_node_idx) override;
	String compile_glsl(const GlslEmitContext &p_ctx, int p_node_idx) const override;

	ClampNode();
};

// Map Range — remap value from one range to another
// Map Range — Blender's, which is twelve inputs (a float set and a vector set),
// two outputs, four interpolations and a clamp checkbox. Ours was five inputs,
// one output, one interpolation and no clamp, so a clamped window passed values
// straight through instead of holding them at the end of the range.
class MapRangeNode : public ProcNode {
	GDCLASS(MapRangeNode, ProcNode);

	int data_type = 0;          // 0 = FLOAT, 1 = FLOAT_VECTOR
	int interpolation_type = 0; // LINEAR, STEPPED, SMOOTHSTEP, SMOOTHERSTEP
	bool clamp_result = false;

protected:
	static void _bind_methods();

public:
	StringName get_node_type() const override { return "MapRange"; }
	String get_node_title() const override { return "Map Range"; }
	String get_node_category() const override { return "Math"; }
	// The GLSL is the old single-interpolation, unclamped arithmetic.
	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_data_type(int p_t) { data_type = p_t; }
	int get_data_type() const { return data_type; }
	void set_interpolation_type(int p_t) { interpolation_type = p_t; }
	int get_interpolation_type() const { return interpolation_type; }
	void set_clamp(bool p_c) { clamp_result = p_c; }
	bool get_clamp() const { return clamp_result; }

	void execute(ProcEvalContext &p_ctx, int p_node_idx) override;

	MapRangeNode();
};

// Mix — linear interpolation between two floats
// Mix — Blender's, which is FOUR data types over ten input sockets and four
// output ones, with a nineteen-mode colour blend behind the RGBA one.
//
// Ours was a single float lerp with three sockets and no properties, so a
// colour Mix came across as MIX whatever it said in Blender, a vector Mix read
// the float sockets, and the non-uniform vector factor did not exist.
class MixNode : public ProcNode {
	GDCLASS(MixNode, ProcNode);

	// THE RNA ENUM VALUE, which for this property is NOT the DNA socket-type
	// enum: RNA reports FLOAT 0, VECTOR 1, RGBA 2, ROTATION 14, while SOCK_* is
	// 1, 2, 3, 11. The serializer reads the RNA value off the node, so this
	// side must read RNA's numbering too. Measured, not assumed -- Math's
	// `operation` RNA values DO equal its DNA ones, so the two cannot be
	// inferred from each other.
	int data_type = 0;
	int blend_type = 0;   // bramp::RAMP_*
	bool clamp_factor = true;
	bool clamp_result = false;
	int factor_mode = 0;  // 0 UNIFORM, 1 NON_UNIFORM

protected:
	static void _bind_methods();

public:
	StringName get_node_type() const override { return "Mix"; }
	String get_node_title() const override { return "Mix"; }
	String get_node_category() const override { return "Math"; }
	// ALL FOUR CONFIGURATIONS since 2026-09-05. It used to answer per
	// configuration, true for Float and Vector only, because Colour needs
	// ramp_blend's nineteen modes with the HSV and HSL conversions under them
	// and Rotation needs a quaternion slerp. Both exist now:
	// blender_ramp_blend.inc for the first, and a port of godot's own
	// Quaternion::slerp for the second.
	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_data_type(int p_t) { data_type = p_t; }
	int get_data_type() const { return data_type; }
	void set_blend_type(int p_t) { blend_type = p_t; }
	int get_blend_type() const { return blend_type; }
	void set_clamp_factor(bool p_c) { clamp_factor = p_c; }
	bool get_clamp_factor() const { return clamp_factor; }
	void set_clamp_result(bool p_c) { clamp_result = p_c; }
	bool get_clamp_result() const { return clamp_result; }
	void set_factor_mode(int p_m) { factor_mode = p_m; }
	int get_factor_mode() const { return factor_mode; }

	void execute(ProcEvalContext &p_ctx, int p_node_idx) override;

	MixNode();
};

// ColorRamp — maps a float onto a gradient, as Blender's Color Ramp does.
//
// THE STOPS ARE THE NODE'S DATA, and they are held FLAT: five floats per stop,
// position then r, g, b, a. One array rather than a parallel positions/colours
// pair because the two must not be able to disagree in length, and because a
// flat float array is what survives both a `.tres` and the bridge's JSON
// without a bespoke encoding on either side.
class ColorRampNode : public ProcNode {
	GDCLASS(ColorRampNode, ProcNode);

	PackedFloat32Array ramp_stops;
	int interpolation = 0; // COLBAND_INTERP_*, Blender's DNA values
	int color_mode = 0; // COLBAND_BLEND_*
	int hue_interpolation = 0; // COLBAND_HUE_*

protected:
	static void _bind_methods();

public:
	StringName get_node_type() const override { return "ColorRamp"; }
	String get_node_title() const override { return "Color Ramp"; }
	String get_node_category() const override { return "Math"; }

	void set_ramp_stops(const PackedFloat32Array &p_stops);
	PackedFloat32Array get_ramp_stops() const { return ramp_stops; }
	void set_interpolation(int p_i) { interpolation = p_i; }
	int get_interpolation() const { return interpolation; }
	void set_color_mode(int p_m) { color_mode = p_m; }
	int get_color_mode() const { return color_mode; }
	void set_hue_interpolation(int p_h) { hue_interpolation = p_h; }
	int get_hue_interpolation() const { return hue_interpolation; }

	void execute(ProcEvalContext &p_ctx, int p_node_idx) override;

	ColorRampNode();
};

// FloatCurve — maps a float through a curve, as Blender's Float Curve does.
//
// THE CURVE IS THE NODE'S DATA, held as three parallel arrays so one shape
// serves this node and RGB Curves, which has four curves rather than one:
//
//   curve_points   two floats per point, x then y, all curves concatenated
//   curve_handles  one per point, Blender's handle_type FLAG value
//   curve_counts   points per curve -- length 1 here, 4 on RGB Curves
//
// A count array rather than one property per curve: eleven named properties for
// RGB Curves is worse to read and cannot be decoded by shared code.
class FloatCurveNode : public ProcNode {
	GDCLASS(FloatCurveNode, ProcNode);

	PackedFloat32Array curve_points;
	PackedInt32Array curve_handles;
	PackedInt32Array curve_counts;
	int extend = 16; // CUMA_EXTEND_EXTRAPOLATE, Blender's default
	bool use_clip = true; // CUMA_DO_CLIP, on for a fresh Blender node
	PackedFloat32Array clip_rect; // xmin, ymin, xmax, ymax

protected:
	static void _bind_methods();

public:
	StringName get_node_type() const override { return "FloatCurve"; }
	String get_node_title() const override { return "Float Curve"; }
	String get_node_category() const override { return "Math"; }

	void set_curve_points(const PackedFloat32Array &p) { curve_points = p; }
	PackedFloat32Array get_curve_points() const { return curve_points; }
	void set_curve_handles(const PackedInt32Array &p) { curve_handles = p; }
	PackedInt32Array get_curve_handles() const { return curve_handles; }
	void set_curve_counts(const PackedInt32Array &p) { curve_counts = p; }
	PackedInt32Array get_curve_counts() const { return curve_counts; }
	void set_extend(int p) { extend = p; }
	int get_extend() const { return extend; }
	void set_use_clip(bool p) { use_clip = p; }
	bool get_use_clip() const { return use_clip; }
	void set_clip_rect(const PackedFloat32Array &p) { clip_rect = p; }
	PackedFloat32Array get_clip_rect() const { return clip_rect; }

	void execute(ProcEvalContext &p_ctx, int p_node_idx) override;

	FloatCurveNode();
};

// RGBCurves — Blender's RGB Curves. FOUR curves, and the fourth is not a
// channel: cm[0..2] are R, G and B and cm[3] is the COMBINED curve, which runs
// FIRST and feeds each channel's own curve. Same three-array encoding as
// FloatCurveNode, with curve_counts holding four entries instead of one.
class RGBCurvesNode : public ProcNode {
	GDCLASS(RGBCurvesNode, ProcNode);

	PackedFloat32Array curve_points;
	PackedInt32Array curve_handles;
	PackedInt32Array curve_counts;
	int extend = 16; // CUMA_EXTEND_EXTRAPOLATE
	bool use_clip = true;
	PackedFloat32Array clip_rect;

protected:
	static void _bind_methods();

public:
	StringName get_node_type() const override { return "RGBCurves"; }
	String get_node_title() const override { return "RGB Curves"; }
	String get_node_category() const override { return "Math"; }

	void set_curve_points(const PackedFloat32Array &p) { curve_points = p; }
	PackedFloat32Array get_curve_points() const { return curve_points; }
	void set_curve_handles(const PackedInt32Array &p) { curve_handles = p; }
	PackedInt32Array get_curve_handles() const { return curve_handles; }
	void set_curve_counts(const PackedInt32Array &p) { curve_counts = p; }
	PackedInt32Array get_curve_counts() const { return curve_counts; }
	void set_extend(int p) { extend = p; }
	int get_extend() const { return extend; }
	void set_use_clip(bool p) { use_clip = p; }
	bool get_use_clip() const { return use_clip; }
	void set_clip_rect(const PackedFloat32Array &p) { clip_rect = p; }
	PackedFloat32Array get_clip_rect() const { return clip_rect; }

	void execute(ProcEvalContext &p_ctx, int p_node_idx) override;

	RGBCurvesNode();
};

} // namespace godot

#endif // PROC_NODES_MATH_NODES_H