OverviewFilesCommitsIssuesDocsReleasesPeers

proc-nodes / src / nodes / input_nodes.cpp

290 lines · cpp ref:
#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