#ifndef PROC_NODES_ATTRIBUTE_NODES_H
#define PROC_NODES_ATTRIBUTE_NODES_H
#include "../graph/proc_node.h"
namespace godot {
// Position — outputs per-vertex position from geometry (field input)
class PositionNode : public ProcNode {
GDCLASS(PositionNode, ProcNode);
protected:
static void _bind_methods();
public:
StringName get_node_type() const override { return "Position"; }
String get_node_title() const override { return "Position"; }
String get_node_category() const override { return "Input"; }
bool supports_gpu() const override { return true; }
// ⛔ IT CLAIMED GPU SUPPORT AND EMITTED NOTHING. supports_gpu() has been
// true here since it was written, with no compile_glsl override -- so the
// base returned "" and compile_to_function REFUSED any graph containing a
// Position node. The identical shape is recorded for Random Value in
// hyper-games #65: "supports_gpu() returned TRUE with no compile_glsl
// override at all, so the GPU path never emitted its arithmetic".
//
// Found 2026-09-06 building an authored cloud, where Position is what makes
// noise vary with place at all -- without it a Noise Texture samples its
// unconnected Vector socket, which is (0,0,0), and the whole volume is one
// constant. In a density function the position IS `pos`.
String compile_glsl(const GlslEmitContext &p_ctx, int p_node_idx) const override;
void execute(ProcEvalContext &p_ctx, int p_node_idx) override;
PositionNode();
};
// Normal — outputs per-vertex normal from geometry (field input)
class NormalNode : public ProcNode {
GDCLASS(NormalNode, ProcNode);
protected:
static void _bind_methods();
public:
StringName get_node_type() const override { return "Normal"; }
String get_node_title() const override { return "Normal"; }
String get_node_category() const override { return "Input"; }
bool supports_gpu() const override { return true; }
void execute(ProcEvalContext &p_ctx, int p_node_idx) override;
NormalNode();
};
// Index — outputs element index (0, 1, 2, ...)
class IndexNode : public ProcNode {
GDCLASS(IndexNode, ProcNode);
protected:
static void _bind_methods();
public:
StringName get_node_type() const override { return "Index"; }
String get_node_title() const override { return "Index"; }
String get_node_category() const override { return "Input"; }
bool supports_gpu() const override { return true; }
void execute(ProcEvalContext &p_ctx, int p_node_idx) override;
IndexNode();
};
// UV — outputs per-vertex UV coordinates as vec3 field (Z=0)
class UVNode : public ProcNode {
GDCLASS(UVNode, ProcNode);
protected:
static void _bind_methods();
public:
StringName get_node_type() const override { return "UV"; }
String get_node_title() const override { return "UV"; }
String get_node_category() const override { return "Input"; }
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;
UVNode();
};
// Store Named Attribute — writes a value to a named attribute on geometry
class StoreNamedAttributeNode : public ProcNode {
GDCLASS(StoreNamedAttributeNode, ProcNode);
protected:
static void _bind_methods();
StringName attr_name = "custom";
int attr_domain = 0; // AttrDomain, and Blender's own domain numbering
int attr_type = 0; // AttrType
// Blender's CD_PROP_* code, kept verbatim so the property round-trips
// through the bridge unchanged. FLOAT 10 is Blender's own default.
int data_type = 10;
public:
StringName get_node_type() const override { return "StoreNamedAttribute"; }
String get_node_title() const override { return "Store Named Attribute"; }
String get_node_category() const override { return "Attribute"; }
void set_attr_name(const String &p_name) { attr_name = p_name; }
String get_attr_name() const { return attr_name; }
void set_attr_domain(int p_domain) { attr_domain = p_domain; }
int get_attr_domain() const { return attr_domain; }
void set_attr_type(int p_type) { attr_type = p_type; }
int get_attr_type() const { return attr_type; }
// BLENDER'S Value SOCKET IS TYPED BY data_type, so this retypes socket 3.
// Without it a colour or vector link was refused outright by the bridge in
// one direction and silently narrowed to vec.x in the other.
void set_data_type(int p_type);
int get_data_type() const { return data_type; }
void execute(ProcEvalContext &p_ctx, int p_node_idx) override;
StoreNamedAttributeNode();
};
// Named Attribute — reads a named attribute value from geometry
class NamedAttributeNode : public ProcNode {
GDCLASS(NamedAttributeNode, ProcNode);
protected:
static void _bind_methods();
StringName attr_name = "custom";
// Blender's CD_PROP_* code. Its OUTPUT socket is typed by this, exactly as
// Store Named Attribute's Value input is.
int data_type = 10;
public:
StringName get_node_type() const override { return "NamedAttribute"; }
String get_node_title() const override { return "Named Attribute"; }
String get_node_category() const override { return "Attribute"; }
void set_attr_name(const String &p_name) { attr_name = p_name; }
String get_attr_name() const { return attr_name; }
void set_data_type(int p_type);
int get_data_type() const { return data_type; }
void execute(ProcEvalContext &p_ctx, int p_node_idx) override;
NamedAttributeNode();
};
// Capture Attribute — snapshot a field value as a named attribute
// Capture Attribute — Blender models this as an ITEM LIST. Each item has a
// name and a data type and adds ONE input and ONE output socket, so the node's
// shape is data, not a fixed declaration. Ours was Geometry + a single fixed
// "Value" -- which sat where Blender's SELECTION is, so a bridged selection
// drove the captured value and every item link landed past the end.
class CaptureAttributeNode : public ProcNode {
GDCLASS(CaptureAttributeNode, ProcNode);
protected:
static void _bind_methods();
PackedStringArray item_names;
PackedInt32Array item_types; // Blender CD_PROP_* per item
int domain = 0; // AttrDomain
void _rebuild_sockets();
public:
StringName get_node_type() const override { return "CaptureAttribute"; }
String get_node_title() const override { return "Capture Attribute"; }
String get_node_category() const override { return "Attribute"; }
void set_item_names(const PackedStringArray &p) { item_names = p; _rebuild_sockets(); }
PackedStringArray get_item_names() const { return item_names; }
void set_item_types(const PackedInt32Array &p) { item_types = p; _rebuild_sockets(); }
PackedInt32Array get_item_types() const { return item_types; }
void set_domain(int p_d) { domain = p_d; }
int get_domain() const { return domain; }
void execute(ProcEvalContext &p_ctx, int p_node_idx) override;
CaptureAttributeNode();
};
// Remove Named Attribute
class RemoveNamedAttributeNode : public ProcNode {
GDCLASS(RemoveNamedAttributeNode, ProcNode);
protected:
static void _bind_methods();
StringName attr_name = "custom";
public:
StringName get_node_type() const override { return "RemoveNamedAttribute"; }
String get_node_title() const override { return "Remove Named Attribute"; }
String get_node_category() const override { return "Attribute"; }
void set_attr_name(const String &p_name) { attr_name = p_name; }
String get_attr_name() const { return attr_name; }
void execute(ProcEvalContext &p_ctx, int p_node_idx) override;
RemoveNamedAttributeNode();
};
// Domain Size — outputs the element count for each domain
class DomainSizeNode : public ProcNode {
GDCLASS(DomainSizeNode, ProcNode);
protected:
static void _bind_methods();
public:
StringName get_node_type() const override { return "DomainSize"; }
String get_node_title() const override { return "Domain Size"; }
String get_node_category() const override { return "Attribute"; }
void execute(ProcEvalContext &p_ctx, int p_node_idx) override;
DomainSizeNode();
};
} // namespace godot
#endif // PROC_NODES_ATTRIBUTE_NODES_H