#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