OverviewFilesCommitsIssuesDocsReleasesPeers

proc-nodes / src / nodes / attribute_nodes.h

233 lines · c ref:
#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