#include "input_nodes.h"
#include "../graph/graph_executor.h"
#include "../gpu/glsl_emit_context.h"
namespace godot {
// --- GroupInputNode ---
void GroupInputNode::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_interface", "spec"), &GroupInputNode::set_interface);
ClassDB::bind_method(D_METHOD("get_interface"), &GroupInputNode::get_interface);
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "interface"), "set_interface", "get_interface");
}
GroupInputNode::GroupInputNode() {
// Default: one geometry output
output_defs.push_back({ "Geometry", SocketType::Geometry, Variant() });
}
// JSON has no Vector3 and no Color, so a vector parameter arrives as an Array.
// Convert here rather than at the call site, so the sockets hold a typed value
// whatever route the spec came in by -- importer, editor, or a saved resource.
static Variant _typed(const Variant &p_val, SocketType p_type) {
if (p_val.get_type() != Variant::ARRAY) {
return p_val;
}
Array arr = p_val;
if (p_type == SocketType::Vector && arr.size() >= 3) {
return Vector3((float)arr[0], (float)arr[1], (float)arr[2]);
}
if (p_type == SocketType::Color) {
if (arr.size() >= 4) {
return Color((float)arr[0], (float)arr[1], (float)arr[2], (float)arr[3]);
}
if (arr.size() >= 3) {
return Color((float)arr[0], (float)arr[1], (float)arr[2], 1.0f);
}
}
if (p_type == SocketType::Rotation && arr.size() >= 4) {
return Quaternion((float)arr[0], (float)arr[1], (float)arr[2], (float)arr[3]);
}
return p_val;
}
void GroupInputNode::set_interface(const Array &p_spec) {
interface_spec = p_spec;
if (p_spec.is_empty()) {
return; // keep the bare Geometry output
}
output_defs.clear();
for (int i = 0; i < p_spec.size(); i++) {
Dictionary d = p_spec[i];
SocketType type = (SocketType)(int)d.get("type", (int)SocketType::Float);
output_defs.push_back({ StringName(String(d.get("name", "Input"))), type,
_typed(d.get("value", Variant()), type) });
}
}
String GroupInputNode::compile_glsl(const GlslEmitContext &p_ctx, int p_node_idx) const {
// One declaration per PARAMETER output, from output_defs -- the same values
// execute() writes. The Geometry output has no spelling and needs none: the
// emitted function's `pos` is the geometry, and declare() returns empty for
// it, which is the signal to skip rather than an error.
String s;
for (int i = 0; i < output_defs.size(); i++) {
if (output_defs[i].type == SocketType::Geometry) {
continue;
}
const String decl = p_ctx.declare(p_node_idx, i);
if (decl.is_empty()) {
continue;
}
// String(" = ") rather than a bare literal: a NAMED const String plus a
// char array is an ambiguous overload in godot-cpp (String and
// StringName convert equally well), which the prvalue form every
// other node happens to use never runs into.
s += decl + String(" = ") + p_ctx.literal_value(output_defs[i].type, output_defs[i].default_value) + String(";\n\t");
}
// A graph whose interface is geometry alone emits nothing here, and an
// empty string is how compile_to_function spells "this node lied about
// supporting the GPU". Give it a statement that says so instead.
if (s.is_empty()) {
return "// Group Input: geometry only, carried as `pos`";
}
return s;
}
void GroupInputNode::execute(ProcEvalContext &p_ctx, int p_node_idx) {
// The graph's entry point: the geometry the CALLER handed in. For a graph
// bridged from Blender this is the object's own mesh, which is what its
// Group Input carries -- and every Blender geometry tree starts there.
//
// This used to always instantiate an EMPTY ProcGeometry, with a comment
// saying external overrides were "handled by the executor". They were not:
// execute()'s p_input_overrides parameter was never read. So a bridged
// graph imported with the right nodes and the right links and then baked to
// nothing, with no error anywhere to say why.
//
// Every OTHER socket is a parameter the graph exposes, and its value comes
// from the modifier instance the graph was serialized with. Writing them
// here is what makes the value chain live: before this the node declared
// one socket, so a link from parameter port 3 read past the end of its row
// in the executor's flat socket array and the graph baked its undisplaced
// cage with no error anywhere.
for (int i = 0; i < output_defs.size(); i++) {
if (output_defs[i].type == SocketType::Geometry) {
Ref<ProcGeometry> geo = p_ctx.input_geometry;
if (geo.is_null()) {
geo.instantiate();
}
p_ctx.write_geometry(p_node_idx, i, geo);
continue;
}
p_ctx.write_output(p_node_idx, i,
SocketValue::from_variant(output_defs[i].default_value, output_defs[i].type));
}
}
// --- GroupOutputNode ---
void GroupOutputNode::_bind_methods() {
// Bound so a TEST can build a graph that returns a value. The importer sets
// this from a Blender tree's interface; without the binding the only way to
// get a float-returning graph is to round-trip through Blender, which makes
// a proc-nodes-side gate depend on the bridge.
ClassDB::bind_method(D_METHOD("set_interface", "spec"), &GroupOutputNode::set_interface);
}
GroupOutputNode::GroupOutputNode() {
// Default: one geometry input
input_defs.push_back({ "Geometry", SocketType::Geometry, Variant() });
// One geometry output (passthrough for execution result)
output_defs.push_back({ "Geometry", SocketType::Geometry, Variant() });
}
void GroupOutputNode::set_interface(const Array &p_spec) {
if (p_spec.is_empty()) {
return; // keep the bare Geometry input
}
input_defs.clear();
for (int i = 0; i < p_spec.size(); i++) {
Dictionary d = p_spec[i];
SocketType type = (SocketType)(int)d.get("type", (int)SocketType::Float);
input_defs.push_back({ StringName(String(d.get("name", "Output"))), type,
_typed(d.get("value", Variant()), type) });
}
}
void GroupOutputNode::execute(ProcEvalContext &p_ctx, int p_node_idx) {
// Pass through geometry input to output
Ref<ProcGeometry> geo = p_ctx.read_geometry(p_node_idx, 0);
p_ctx.write_geometry(p_node_idx, 0, geo);
}
String ValueNode::compile_glsl(const GlslEmitContext &p_ctx, int p_node_idx) const {
return p_ctx.declare(p_node_idx, 0) + " = " + String::num(value, 9) + ";";
}
String VectorNode::compile_glsl(const GlslEmitContext &p_ctx, int p_node_idx) const {
return p_ctx.declare(p_node_idx, 0) + " = vec3(" + String::num(value.x, 9) + ", " +
String::num(value.y, 9) + ", " + String::num(value.z, 9) + ");";
}
String IntegerNode::compile_glsl(const GlslEmitContext &p_ctx, int p_node_idx) const {
return p_ctx.declare(p_node_idx, 0) + " = " + itos(value) + ";";
}
String BooleanNode::compile_glsl(const GlslEmitContext &p_ctx, int p_node_idx) const {
return p_ctx.declare(p_node_idx, 0) + String(" = ") + (value ? "true" : "false") + ";";
}
String ColorNode::compile_glsl(const GlslEmitContext &p_ctx, int p_node_idx) const {
return p_ctx.declare(p_node_idx, 0) + " = vec4(" + String::num(value.r, 9) + ", " +
String::num(value.g, 9) + ", " + String::num(value.b, 9) + ", " +
String::num(value.a, 9) + ");";
}
// --- ValueNode ---
void ValueNode::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_value", "value"), &ValueNode::set_value);
ClassDB::bind_method(D_METHOD("get_value"), &ValueNode::get_value);
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "value"), "set_value", "get_value");
}
ValueNode::ValueNode() {
output_defs.push_back({ "Value", SocketType::Float, 0.0f });
}
void ValueNode::execute(ProcEvalContext &p_ctx, int p_node_idx) {
p_ctx.write_float(p_node_idx, 0, value);
}
// --- VectorNode ---
void VectorNode::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_value", "value"), &VectorNode::set_value);
ClassDB::bind_method(D_METHOD("get_value"), &VectorNode::get_value);
ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "value"), "set_value", "get_value");
}
VectorNode::VectorNode() {
output_defs.push_back({ "Vector", SocketType::Vector, Vector3() });
}
void VectorNode::execute(ProcEvalContext &p_ctx, int p_node_idx) {
p_ctx.write_vector(p_node_idx, 0, value);
}
// --- IntegerNode ---
void IntegerNode::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_value", "value"), &IntegerNode::set_value);
ClassDB::bind_method(D_METHOD("get_value"), &IntegerNode::get_value);
ADD_PROPERTY(PropertyInfo(Variant::INT, "value"), "set_value", "get_value");
}
IntegerNode::IntegerNode() {
output_defs.push_back({ "Value", SocketType::Int, 0 });
}
void IntegerNode::execute(ProcEvalContext &p_ctx, int p_node_idx) {
p_ctx.write_int(p_node_idx, 0, value);
}
// --- BooleanNode ---
void BooleanNode::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_value", "value"), &BooleanNode::set_value);
ClassDB::bind_method(D_METHOD("get_value"), &BooleanNode::get_value);
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "value"), "set_value", "get_value");
}
BooleanNode::BooleanNode() {
output_defs.push_back({ "Value", SocketType::Bool, false });
}
void BooleanNode::execute(ProcEvalContext &p_ctx, int p_node_idx) {
p_ctx.write_bool(p_node_idx, 0, value);
}
// --- ColorNode ---
void ColorNode::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_value", "value"), &ColorNode::set_value);
ClassDB::bind_method(D_METHOD("get_value"), &ColorNode::get_value);
ADD_PROPERTY(PropertyInfo(Variant::COLOR, "value"), "set_value", "get_value");
}
ColorNode::ColorNode() {
output_defs.push_back({ "Color", SocketType::Color, Color(1, 1, 1, 1) });
}
void ColorNode::execute(ProcEvalContext &p_ctx, int p_node_idx) {
p_ctx.write_color(p_node_idx, 0, value);
}
// --- StringNode ---
void StringNode::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_value", "value"), &StringNode::set_value);
ClassDB::bind_method(D_METHOD("get_value"), &StringNode::get_value);
ADD_PROPERTY(PropertyInfo(Variant::STRING, "value"), "set_value", "get_value");
}
StringNode::StringNode() {
output_defs.push_back({ "Value", SocketType::String, String("") });
}
void StringNode::execute(ProcEvalContext &p_ctx, int p_node_idx) {
p_ctx.write_output(p_node_idx, 0, SocketValue::from_string(value));
}
// --- ObjectInfoNode ---
void ObjectInfoNode::_bind_methods() {
}
ObjectInfoNode::ObjectInfoNode() {
output_defs.push_back({ "Position", SocketType::Vector, Vector3() });
output_defs.push_back({ "Name", SocketType::String, String("") });
}
void ObjectInfoNode::execute(ProcEvalContext &p_ctx, int p_node_idx) {
p_ctx.write_vector(p_node_idx, 0, Vector3(0, 0, 0));
p_ctx.write_output(p_node_idx, 1, SocketValue::from_string(String("")));
}
} // namespace godot