#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