OverviewFilesCommitsIssuesDocsReleasesPeers

proc-nodes / src / graph / socket_value.h

290 lines · c ref:
#ifndef PROC_NODES_SOCKET_VALUE_H
#define PROC_NODES_SOCKET_VALUE_H

#include "../core/attribute_storage.h"
#include "../core/proc_geometry.h"
#include <godot_cpp/variant/color.hpp>
#include <godot_cpp/variant/string.hpp>
#include <godot_cpp/variant/variant.hpp>
#include <godot_cpp/variant/packed_vector4_array.hpp>
#include <godot_cpp/variant/projection.hpp>
#include <godot_cpp/variant/quaternion.hpp>
#include <godot_cpp/variant/vector3.hpp>

namespace godot {

enum class SocketType : int32_t {
	Float = 0,
	Int = 1,
	Bool = 2,
	Vector = 3,
	Color = 4,
	String = 5,
	Geometry = 6,
	Object = 7,
	// Blender carries ROTATION and MATRIX as first-class socket types and the
	// whole rotation/matrix node family is typed on them, so representing a
	// rotation as three euler floats would not round-trip: euler loses the
	// winding a quaternion keeps, and there is no vector spelling of a 4x4.
	Rotation = 8, // quaternion
	Matrix = 9,   // 4x4
};

const char *socket_type_name(SocketType p_type);

// Blender's CD_PROP_* code -> our socket type, with that type's zero. Shared
// because every ITEM LIST needs it: Capture Attribute's items, and the repeat
// and simulation zones' state items, all describe their sockets this way.
SocketType socket_type_from_cd_prop(int p_cd, Variant &r_zero);

struct SocketDef {
	StringName name;
	SocketType type;
	Variant default_value;
	// A MULTI-INPUT SOCKET accepts any number of links, kept in the order they
	// were made. Blender has this as a socket KIND (`is_multi_input`) and used
	// it for Join Geometry, for zone and capture item lists, and for Group
	// Output's growing interface. Without it, two links to one socket meant the
	// second REPLACED the first, so a graph joining three geometries silently
	// kept one -- and our workaround, a second discrete "Geometry B" socket,
	// matched nothing on Blender's node.
	bool multi = false;
};

// Value flowing between sockets.
// Can hold a single scalar OR a per-element field (array).
// When is_field=true, the array variants hold per-element data.
// Nodes check is_field to decide scalar vs element-wise evaluation.
struct SocketValue {
	SocketType type = SocketType::Float;

	// Scalar values
	float f = 0.0f;
	int32_t i = 0;
	bool b = false;
	Vector3 vec;
	Color color;
	String str;
	Quaternion quat;
	Projection mat;
	Ref<ProcGeometry> geometry;

	// Field (per-element) values
	//
	// A field is an array plus a statement of WHAT IT IS PER. Without the
	// domain an array of 9 booleans over a 3x3 grid could be per vertex or per
	// anything else of that count, and a consumer working on faces has no way
	// to use it correctly -- which is why nothing could produce or consume a
	// per-face field. Blender types every field by domain; so does this now.
	//
	// Point is the default because every field produced before this was
	// per-vertex, so untouched code keeps its existing meaning.
	bool is_field = false;
	AttrDomain domain = AttrDomain::Point;
	int field_size = 0;

	// A CONTEXT FIELD: "ask node lazy_node for this, once you know what geometry
	// you are working on". Blender's fields are lazy -- Position is not an array,
	// it is a function evaluated on the consumer's domain -- which is why its
	// Position node has no Geometry input and ours needs one.
	//
	// Nodes run in topological order, so a field producer runs BEFORE any
	// consumer exists to give it context. It therefore leaves this promise
	// behind, and the consumer redeems it with ProcEvalContext::materialize once
	// it knows its own geometry.
	bool is_lazy = false;
	int lazy_node = -1;
	int lazy_socket = 0;
	PackedFloat32Array float_array;
	PackedInt32Array int_array;
	PackedVector3Array vec_array;
	PackedColorArray color_array;
	PackedVector4Array quat_array;   // x, y, z, w per element
	// 16 floats per element, column-major like Projection. There is no packed
	// array of matrices, and a TypedArray would allocate per element.
	PackedFloat32Array mat_array;

	SocketValue() = default;

	// Element access: returns scalar at index, or broadcasts scalar if not a field.
	// Handles type conversion: int field can be read as float, etc.
	float float_at(int idx) const {
		if (!is_field) return f;
		if (float_array.size() > idx) return float_array[idx];
		if (int_array.size() > idx) return (float)int_array[idx];
		return f;
	}
	int32_t int_at(int idx) const {
		if (!is_field) return i;
		if (int_array.size() > idx) return int_array[idx];
		if (float_array.size() > idx) return (int32_t)float_array[idx];
		return i;
	}
	bool bool_at(int idx) const {
		if (!is_field) return b;
		if (int_array.size() > idx) return int_array[idx] != 0;
		if (float_array.size() > idx) return float_array[idx] != 0.0f;
		return b;
	}
	Vector3 vec_at(int idx) const {
		if (!is_field) return vec;
		if (vec_array.size() > idx) return vec_array[idx];
		// A COLOUR field read as a vector is Blender's rgb with alpha dropped.
		// `color_at` had the mirror of this (a float field reads as grey) and
		// this direction had nothing, so a colour field fell through to the
		// non-field scalar and read as (0, 0, 0) -- an offset that does not
		// move, which looks like a missing attribute rather than a missing
		// conversion. Measured: an RGBA (0.2, 0.4, 0.6, 1) offset moves
		// Blender's vertex by (0.2, 0.4, 0.6).
		if (color_array.size() > idx) {
			const Color &c = color_array[idx];
			return Vector3(c.r, c.g, c.b);
		}
		return vec;
	}
	Color color_at(int idx) const {
		if (!is_field) return color;
		if (color_array.size() > idx) return color_array[idx];
		// Float field → grayscale
		if (float_array.size() > idx) { float v = float_array[idx]; return Color(v, v, v, 1.0f); }
		if (int_array.size() > idx) { float v = (float)int_array[idx]; return Color(v, v, v, 1.0f); }
		return color;
	}

	Quaternion quat_at(int idx) const {
		if (!is_field) return quat;
		if (quat_array.size() > idx) {
			const Vector4 &q = quat_array[idx];
			return Quaternion(q.x, q.y, q.z, q.w);
		}
		return quat;
	}
	Projection mat_at(int idx) const {
		if (!is_field) return mat;
		if (mat_array.size() >= (idx + 1) * 16) {
			Projection p;
			for (int c = 0; c < 4; c++) {
				for (int r = 0; r < 4; r++) {
					p.columns[c][r] = mat_array[idx * 16 + c * 4 + r];
				}
			}
			return p;
		}
		return mat;
	}

	static SocketValue from_rotation(const Quaternion &v) {
		SocketValue sv;
		sv.type = SocketType::Rotation;
		sv.quat = v;
		return sv;
	}

	static SocketValue from_matrix(const Projection &v) {
		SocketValue sv;
		sv.type = SocketType::Matrix;
		sv.mat = v;
		return sv;
	}

	static SocketValue from_float(float v) {
		SocketValue sv;
		sv.type = SocketType::Float;
		sv.f = v;
		return sv;
	}

	static SocketValue from_int(int32_t v) {
		SocketValue sv;
		sv.type = SocketType::Int;
		sv.i = v;
		return sv;
	}

	static SocketValue from_bool(bool v) {
		SocketValue sv;
		sv.type = SocketType::Bool;
		sv.b = v;
		return sv;
	}

	static SocketValue from_vector(const Vector3 &v) {
		SocketValue sv;
		sv.type = SocketType::Vector;
		sv.vec = v;
		return sv;
	}

	static SocketValue from_color(const Color &v) {
		SocketValue sv;
		sv.type = SocketType::Color;
		sv.color = v;
		return sv;
	}

	static SocketValue from_string(const String &v) {
		SocketValue sv;
		sv.type = SocketType::String;
		sv.str = v;
		return sv;
	}

	static SocketValue from_geometry(const Ref<ProcGeometry> &v) {
		SocketValue sv;
		sv.type = SocketType::Geometry;
		sv.geometry = v;
		return sv;
	}

	// Field factories (per-element arrays)
	static SocketValue from_float_field(const PackedFloat32Array &arr) {
		SocketValue sv;
		sv.type = SocketType::Float;
		sv.is_field = true;
		sv.field_size = arr.size();
		sv.float_array = arr;
		return sv;
	}

	static SocketValue from_int_field(const PackedInt32Array &arr) {
		SocketValue sv;
		sv.type = SocketType::Int;
		sv.is_field = true;
		sv.field_size = arr.size();
		sv.int_array = arr;
		return sv;
	}

	static SocketValue from_vec_field(const PackedVector3Array &arr) {
		SocketValue sv;
		sv.type = SocketType::Vector;
		sv.is_field = true;
		sv.field_size = arr.size();
		sv.vec_array = arr;
		return sv;
	}

	static SocketValue from_color_field(const PackedColorArray &arr) {
		SocketValue sv;
		sv.type = SocketType::Color;
		sv.is_field = true;
		sv.field_size = arr.size();
		sv.color_array = arr;
		return sv;
	}

	// Type conversions
	float to_float() const;
	int32_t to_int() const;
	bool to_bool() const;
	Vector3 to_vector() const;
	Color to_color() const;

	Variant to_variant() const;
	static SocketValue from_variant(const Variant &p_val, SocketType p_type);
};

} // namespace godot

#endif // PROC_NODES_SOCKET_VALUE_H