OverviewFilesCommitsIssuesDocsReleasesPeers

proc-nodes / src / nodes / utility_nodes.cpp

321 lines · cpp ref:
#include "utility_nodes.h"
#include <godot_cpp/variant/utility_functions.hpp>
#include "../graph/graph_executor.h"

namespace godot {

// --- AccumulateFieldNode ---

void AccumulateFieldNode::_bind_methods() {
	ClassDB::bind_method(D_METHOD("set_data_type", "t"), &AccumulateFieldNode::set_data_type);
	ClassDB::bind_method(D_METHOD("get_data_type"), &AccumulateFieldNode::get_data_type);
	ADD_PROPERTY(PropertyInfo(Variant::INT, "data_type"), "set_data_type", "get_data_type");
	ClassDB::bind_method(D_METHOD("set_domain", "d"), &AccumulateFieldNode::set_domain);
	ClassDB::bind_method(D_METHOD("get_domain"), &AccumulateFieldNode::get_domain);
	ADD_PROPERTY(PropertyInfo(Variant::INT, "domain"), "set_domain", "get_domain");
}

// FLOAT, INT, FLOAT_VECTOR and TRANSFORM -- the four Blender offers. The Value
// input and all three outputs take the type together.
void AccumulateFieldNode::set_data_type(int p_t) {
	SocketType st;
	Variant zero;
	switch (p_t) {
		case 10: st = SocketType::Float; zero = 0.0f; break;
		case 11: st = SocketType::Int; zero = 0; break;
		case 48: st = SocketType::Vector; zero = Vector3(); break;
		case 20: st = SocketType::Matrix; zero = Projection(); break;
		default:
			UtilityFunctions::push_warning(
					"AccumulateField: Blender offers only FLOAT, INT, FLOAT_VECTOR and "
					"TRANSFORM; data_type ", p_t, " is not one of them.");
			return;
	}
	data_type = p_t;
	input_defs.ptrw()[0].type = st;
	input_defs.ptrw()[0].default_value = zero;
	for (int o = 0; o < 3; o++) {
		output_defs.ptrw()[o].type = st;
	}
}

AccumulateFieldNode::AccumulateFieldNode() {
	// Blender names it "Group ID"; ours said "Group Index", which resolves by
	// neither name nor meaning.
	input_defs.push_back({ "Value", SocketType::Float, 0.0f });
	input_defs.push_back({ "Group ID", SocketType::Int, 0 });
	// THREE OUTPUTS. Ours had one, sitting at index 0 -- which is LEADING -- so
	// a graph reading Leading got the input straight back and links to Trailing
	// and Total addressed sockets that did not exist.
	output_defs.push_back({ "Leading", SocketType::Float, Variant() });
	output_defs.push_back({ "Trailing", SocketType::Float, Variant() });
	output_defs.push_back({ "Total", SocketType::Float, Variant() });
}

void AccumulateFieldNode::execute(ProcEvalContext &p_ctx, int p_node_idx) {
	SocketValue val = p_ctx.read_input(p_node_idx, 0);
	SocketValue grp = p_ctx.read_input(p_node_idx, 1);

	int n = MAX(val.field_size, grp.field_size);
	if (n == 0) {
		n = 1;
	}
	const AttrDomain out_domain = val.is_field ? val.domain : grp.domain;

	// ELEMENTS ARE ACCUMULATED IN INDEX ORDER, grouped by Group ID.
	//
	//   Leading   accumulate THEN write -- inclusive
	//   Trailing  write THEN accumulate -- exclusive, starts at the identity
	//   Total     the group's whole accumulation, broadcast back over it
	//
	// Blender keys the running values in a map, so groups need not be
	// contiguous and an id may be any int. Two passes: one to accumulate, one
	// to broadcast the totals.
	if (data_type == 48) {
		PackedVector3Array lead, trail, total;
		lead.resize(n);
		trail.resize(n);
		total.resize(n);
		HashMap<int32_t, Vector3> acc;
		for (int i = 0; i < n; i++) {
			const int32_t g = grp.int_at(i);
			Vector3 *a = acc.getptr(g);
			if (a == nullptr) {
				acc.insert(g, Vector3());
				a = acc.getptr(g);
			}
			trail.ptrw()[i] = *a;
			*a += val.vec_at(i);
			lead.ptrw()[i] = *a;
		}
		for (int i = 0; i < n; i++) {
			total.ptrw()[i] = acc[grp.int_at(i)];
		}
		p_ctx.write_vec_field(p_node_idx, 0, lead, out_domain);
		p_ctx.write_vec_field(p_node_idx, 1, trail, out_domain);
		p_ctx.write_vec_field(p_node_idx, 2, total, out_domain);
		return;
	}

	if (data_type == 20) {
		// TRANSFORM accumulates by MATRIX PRODUCT from the IDENTITY, not by
		// sum from zero -- Blender's AccumulationInfo specialises both.
		PackedFloat32Array lead, trail, total;
		lead.resize(n * 16);
		trail.resize(n * 16);
		total.resize(n * 16);
		HashMap<int32_t, Projection> acc;
		auto put = [](PackedFloat32Array &dst, int i, const Projection &m) {
			float *w = dst.ptrw();
			for (int c = 0; c < 4; c++) {
				for (int r = 0; r < 4; r++) {
					w[i * 16 + c * 4 + r] = m.columns[c][r];
				}
			}
		};
		for (int i = 0; i < n; i++) {
			const int32_t g = grp.int_at(i);
			Projection *a = acc.getptr(g);
			if (a == nullptr) {
				acc.insert(g, Projection());
				a = acc.getptr(g);
			}
			put(trail, i, *a);
			*a = *a * val.mat_at(i);
			put(lead, i, *a);
		}
		for (int i = 0; i < n; i++) {
			put(total, i, acc[grp.int_at(i)]);
		}
		p_ctx.write_mat_field(p_node_idx, 0, lead, out_domain);
		p_ctx.write_mat_field(p_node_idx, 1, trail, out_domain);
		p_ctx.write_mat_field(p_node_idx, 2, total, out_domain);
		return;
	}

	if (data_type == 11) {
		PackedInt32Array lead, trail, total;
		lead.resize(n);
		trail.resize(n);
		total.resize(n);
		HashMap<int32_t, int32_t> acc;
		for (int i = 0; i < n; i++) {
			const int32_t g = grp.int_at(i);
			int32_t *a = acc.getptr(g);
			if (a == nullptr) {
				acc.insert(g, 0);
				a = acc.getptr(g);
			}
			trail.ptrw()[i] = *a;
			*a += val.int_at(i);
			lead.ptrw()[i] = *a;
		}
		for (int i = 0; i < n; i++) {
			total.ptrw()[i] = acc[grp.int_at(i)];
		}
		p_ctx.write_int_field(p_node_idx, 0, lead, out_domain);
		p_ctx.write_int_field(p_node_idx, 1, trail, out_domain);
		p_ctx.write_int_field(p_node_idx, 2, total, out_domain);
		return;
	}

	PackedFloat32Array lead, trail, total;
	lead.resize(n);
	trail.resize(n);
	total.resize(n);
	HashMap<int32_t, float> acc;
	for (int i = 0; i < n; i++) {
		const int32_t g = grp.int_at(i);
		float *a = acc.getptr(g);
		if (a == nullptr) {
			acc.insert(g, 0.0f);
			a = acc.getptr(g);
		}
		trail.ptrw()[i] = *a;
		*a += val.float_at(i);
		lead.ptrw()[i] = *a;
	}
	for (int i = 0; i < n; i++) {
		total.ptrw()[i] = acc[grp.int_at(i)];
	}
	p_ctx.write_float_field(p_node_idx, 0, lead, out_domain);
	p_ctx.write_float_field(p_node_idx, 1, trail, out_domain);
	p_ctx.write_float_field(p_node_idx, 2, total, out_domain);
}

// --- SampleIndexNode ---

void SampleIndexNode::_bind_methods() {
	ClassDB::bind_method(D_METHOD("set_data_type", "t"), &SampleIndexNode::set_data_type);
	ClassDB::bind_method(D_METHOD("get_data_type"), &SampleIndexNode::get_data_type);
	ADD_PROPERTY(PropertyInfo(Variant::INT, "data_type"), "set_data_type", "get_data_type");
	ClassDB::bind_method(D_METHOD("set_domain", "d"), &SampleIndexNode::set_domain);
	ClassDB::bind_method(D_METHOD("get_domain"), &SampleIndexNode::get_domain);
	ADD_PROPERTY(PropertyInfo(Variant::INT, "domain"), "set_domain", "get_domain");
	ClassDB::bind_method(D_METHOD("set_clamp", "c"), &SampleIndexNode::set_clamp);
	ClassDB::bind_method(D_METHOD("get_clamp"), &SampleIndexNode::get_clamp);
	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "clamp"), "set_clamp", "get_clamp");
}

void SampleIndexNode::set_data_type(int p_t) {
	SocketType st;
	Variant zero;
	switch (p_t) {
		case 10: st = SocketType::Float; zero = 0.0f; break;
		case 11: case 45: st = SocketType::Int; zero = 0; break;
		case 50: st = SocketType::Bool; zero = false; break;
		case 48: case 49: st = SocketType::Vector; zero = Vector3(); break;
		case 47: case 17: st = SocketType::Color; zero = Color(0, 0, 0, 1); break;
		case 52: st = SocketType::Rotation; zero = Quaternion(); break;
		case 20: st = SocketType::Matrix; zero = Projection(); break;
		default:
			UtilityFunctions::push_warning(
					"SampleIndex: unrecognised data_type ", p_t,
					"; the Value sockets are left as they were.");
			return;
	}
	data_type = p_t;
	input_defs.ptrw()[1].type = st;
	input_defs.ptrw()[1].default_value = zero;
	output_defs.ptrw()[0].type = st;
}

SampleIndexNode::SampleIndexNode() {
	// GEOMETRY COMES FIRST and ours did not have it, so both remaining sockets
	// were one place early: Blender's Geometry landed on our Value and its
	// Value on our Index. Every bridged Sample Index was mis-wired end to end.
	input_defs.push_back({ "Geometry", SocketType::Geometry, Variant() });
	input_defs.push_back({ "Value", SocketType::Float, 0.0f });
	input_defs.push_back({ "Index", SocketType::Int, 0 });
	output_defs.push_back({ "Value", SocketType::Float, Variant() });
}

void SampleIndexNode::execute(ProcEvalContext &p_ctx, int p_node_idx) {
	// Sample the Value FIELD on the given geometry, at Index. Ours returned the
	// input unchanged with a comment that the real thing "would sample the
	// attribute array" -- and read it from socket 0, which is the Geometry.
	Ref<ProcGeometry> geo = p_ctx.read_geometry(p_node_idx, 0);
	SocketValue val = geo.is_valid()
			? p_ctx.materialize(p_ctx.read_input(p_node_idx, 1), geo)
			: p_ctx.read_input(p_node_idx, 1);
	SocketValue idxv = p_ctx.read_input(p_node_idx, 2);

	const int n = val.is_field ? val.field_size : 1;
	// The Index is itself a field on the CONSUMER's domain, so one Sample Index
	// can gather a different element per output element.
	const bool idx_is_field = idxv.is_field;
	const int out_n = idx_is_field ? idxv.field_size : 1;

	auto pick = [&](int e) -> int {
		int i = idxv.int_at(e);
		if (clamp_index) {
			i = CLAMP(i, 0, MAX(n - 1, 0));
		}
		return i;
	};
	auto in_range = [&](int i) { return i >= 0 && i < n; };

	if (!idx_is_field) {
		const int i = pick(0);
		if (!in_range(i)) {
			// Blender returns the type's zero for an out-of-range index when
			// clamping is off; it does not wrap.
			p_ctx.write_output(p_node_idx, 0, SocketValue());
			return;
		}
		switch (data_type) {
			case 11: case 45: p_ctx.write_int(p_node_idx, 0, val.int_at(i)); break;
			case 50: p_ctx.write_bool(p_node_idx, 0, val.bool_at(i)); break;
			case 48: case 49: p_ctx.write_vector(p_node_idx, 0, val.vec_at(i)); break;
			case 47: case 17: p_ctx.write_color(p_node_idx, 0, val.color_at(i)); break;
			case 52: p_ctx.write_rotation(p_node_idx, 0, val.quat_at(i)); break;
			case 20: p_ctx.write_matrix(p_node_idx, 0, val.mat_at(i)); break;
			default: p_ctx.write_float(p_node_idx, 0, val.float_at(i)); break;
		}
		return;
	}

	switch (data_type) {
		case 11: case 45: case 50: {
			PackedInt32Array out;
			out.resize(out_n);
			for (int e = 0; e < out_n; e++) {
				const int i = pick(e);
				out.ptrw()[e] = in_range(i) ? (data_type == 50 ? (val.bool_at(i) ? 1 : 0)
															   : val.int_at(i))
											: 0;
			}
			p_ctx.write_int_field(p_node_idx, 0, out, idxv.domain);
		} break;
		case 48: case 49: {
			PackedVector3Array out;
			out.resize(out_n);
			for (int e = 0; e < out_n; e++) {
				const int i = pick(e);
				out.ptrw()[e] = in_range(i) ? val.vec_at(i) : Vector3();
			}
			p_ctx.write_vec_field(p_node_idx, 0, out, idxv.domain);
		} break;
		case 47: case 17: {
			PackedColorArray out;
			out.resize(out_n);
			for (int e = 0; e < out_n; e++) {
				const int i = pick(e);
				out.ptrw()[e] = in_range(i) ? val.color_at(i) : Color(0, 0, 0, 1);
			}
			p_ctx.write_color_field(p_node_idx, 0, out, idxv.domain);
		} break;
		default: {
			PackedFloat32Array out;
			out.resize(out_n);
			for (int e = 0; e < out_n; e++) {
				const int i = pick(e);
				out.ptrw()[e] = in_range(i) ? val.float_at(i) : 0.0f;
			}
			p_ctx.write_float_field(p_node_idx, 0, out, idxv.domain);
		} break;
	}
}

} // namespace godot