#include "instance_nodes.h"
#include "../core/proc_geometry.h"
#include "../core/proc_instances.h"
#include "../core/proc_mesh.h"
#include "../core/proc_pointcloud.h"
#include "../graph/graph_executor.h"
namespace godot {
// Geometry nodes is the source of truth for these four. Ours had no Selection at
// all, called the instance input "Geometry" where Blender says "Instances",
// carried Rotation as EULER DEGREES where Blender uses a rotation, and had no
// Local Space or pivot. Every one of those is now Blender's.
//
// Local Space is the difference between transforming an instance in its OWN
// frame and in the world's, and Blender defaults it to true.
static void _apply_instance_basis(Transform3D &r_xform, const Basis &p_delta,
const Vector3 &p_pivot, bool p_local) {
if (p_local) {
// Pivot is in the instance's own space; hold that point still.
Vector3 world_pivot = r_xform.xform(p_pivot);
r_xform.basis = r_xform.basis * p_delta;
r_xform.origin = world_pivot - r_xform.basis.xform(p_pivot);
} else {
r_xform.basis = p_delta * r_xform.basis;
r_xform.origin = p_pivot + p_delta.xform(r_xform.origin - p_pivot);
}
}
// --- InstanceOnPointsNode ---
void InstanceOnPointsNode::_bind_methods() {
}
InstanceOnPointsNode::InstanceOnPointsNode() {
input_defs.push_back({ "Points", SocketType::Geometry, Variant() });
input_defs.push_back({ "Selection", SocketType::Bool, true });
input_defs.push_back({ "Instance", SocketType::Geometry, Variant() });
input_defs.push_back({ "Pick Instance", SocketType::Bool, false });
input_defs.push_back({ "Instance Index", SocketType::Int, 0 });
input_defs.push_back({ "Rotation", SocketType::Rotation, Variant() });
input_defs.push_back({ "Scale", SocketType::Vector, Vector3(1, 1, 1) });
output_defs.push_back({ "Instances", SocketType::Geometry, Variant() });
}
void InstanceOnPointsNode::execute(ProcEvalContext &p_ctx, int p_node_idx) {
Ref<ProcGeometry> points_geo = p_ctx.read_geometry(p_node_idx, 0);
SocketValue sel = p_ctx.read_input(p_node_idx, 1);
Ref<ProcGeometry> instance_geo = p_ctx.read_geometry(p_node_idx, 2);
SocketValue rot = p_ctx.read_input(p_node_idx, 5);
SocketValue scl = p_ctx.read_input(p_node_idx, 6);
if (points_geo.is_null() || instance_geo.is_null()) {
p_ctx.write_geometry(p_node_idx, 0, Ref<ProcGeometry>());
return;
}
// Get point positions from pointcloud or mesh vertices
PackedVector3Array positions;
if (points_geo->get_pointcloud().is_valid()) {
positions = points_geo->get_pointcloud()->positions;
} else if (points_geo->get_mesh().is_valid()) {
positions = points_geo->get_mesh()->positions;
}
if (positions.size() == 0) {
p_ctx.write_geometry(p_node_idx, 0, Ref<ProcGeometry>());
return;
}
Ref<ProcInstances> instances;
instances.instantiate();
instances->references.push_back(instance_geo);
// Pick Instance / Instance Index select among several references. With a
// single Instance input there is only reference 0 to pick, so they are
// accepted and have no effect -- stated rather than silently ignored.
for (int i = 0; i < positions.size(); i++) {
if (!sel.bool_at(i)) {
continue;
}
Transform3D xform;
xform.origin = positions[i];
xform.basis = Basis(rot.quat_at(i).normalized()).scaled(scl.vec_at(i));
instances->transforms.push_back(xform);
instances->reference_indices.push_back(0);
}
Ref<ProcGeometry> out;
out.instantiate();
out->set_instances(instances);
p_ctx.write_geometry(p_node_idx, 0, out);
}
// --- RotateInstancesNode ---
void RotateInstancesNode::_bind_methods() {
}
RotateInstancesNode::RotateInstancesNode() {
input_defs.push_back({ "Instances", SocketType::Geometry, Variant() });
input_defs.push_back({ "Selection", SocketType::Bool, true });
input_defs.push_back({ "Rotation", SocketType::Rotation, Variant() });
input_defs.push_back({ "Pivot Point", SocketType::Vector, Vector3() });
input_defs.push_back({ "Local Space", SocketType::Bool, true });
output_defs.push_back({ "Instances", SocketType::Geometry, Variant() });
}
void RotateInstancesNode::execute(ProcEvalContext &p_ctx, int p_node_idx) {
Ref<ProcGeometry> geo = p_ctx.read_geometry(p_node_idx, 0);
if (geo.is_null() || geo->get_instances().is_null()) {
p_ctx.write_geometry(p_node_idx, 0, geo);
return;
}
SocketValue sel = p_ctx.read_input(p_node_idx, 1);
SocketValue rot = p_ctx.read_input(p_node_idx, 2);
SocketValue pivot = p_ctx.read_input(p_node_idx, 3);
SocketValue local = p_ctx.read_input(p_node_idx, 4);
geo = geo->duplicate_geometry();
Ref<ProcInstances> inst = geo->get_instances();
for (int i = 0; i < inst->transforms.size(); i++) {
if (!sel.bool_at(i)) {
continue;
}
_apply_instance_basis(inst->transforms.ptrw()[i],
Basis(rot.quat_at(i).normalized()), pivot.vec_at(i), local.bool_at(i));
}
p_ctx.write_geometry(p_node_idx, 0, geo);
}
// --- ScaleInstancesNode ---
void ScaleInstancesNode::_bind_methods() {
}
ScaleInstancesNode::ScaleInstancesNode() {
input_defs.push_back({ "Instances", SocketType::Geometry, Variant() });
input_defs.push_back({ "Selection", SocketType::Bool, true });
input_defs.push_back({ "Scale", SocketType::Vector, Vector3(1, 1, 1) });
input_defs.push_back({ "Center", SocketType::Vector, Vector3() });
input_defs.push_back({ "Local Space", SocketType::Bool, true });
output_defs.push_back({ "Instances", SocketType::Geometry, Variant() });
}
void ScaleInstancesNode::execute(ProcEvalContext &p_ctx, int p_node_idx) {
Ref<ProcGeometry> geo = p_ctx.read_geometry(p_node_idx, 0);
if (geo.is_null() || geo->get_instances().is_null()) {
p_ctx.write_geometry(p_node_idx, 0, geo);
return;
}
SocketValue sel = p_ctx.read_input(p_node_idx, 1);
SocketValue scl = p_ctx.read_input(p_node_idx, 2);
SocketValue center = p_ctx.read_input(p_node_idx, 3);
SocketValue local = p_ctx.read_input(p_node_idx, 4);
geo = geo->duplicate_geometry();
Ref<ProcInstances> inst = geo->get_instances();
for (int i = 0; i < inst->transforms.size(); i++) {
if (!sel.bool_at(i)) {
continue;
}
_apply_instance_basis(inst->transforms.ptrw()[i],
Basis().scaled(scl.vec_at(i)), center.vec_at(i), local.bool_at(i));
}
p_ctx.write_geometry(p_node_idx, 0, geo);
}
// --- TranslateInstancesNode ---
void TranslateInstancesNode::_bind_methods() {
}
TranslateInstancesNode::TranslateInstancesNode() {
input_defs.push_back({ "Instances", SocketType::Geometry, Variant() });
input_defs.push_back({ "Selection", SocketType::Bool, true });
input_defs.push_back({ "Translation", SocketType::Vector, Vector3() });
input_defs.push_back({ "Local Space", SocketType::Bool, true });
output_defs.push_back({ "Instances", SocketType::Geometry, Variant() });
}
void TranslateInstancesNode::execute(ProcEvalContext &p_ctx, int p_node_idx) {
Ref<ProcGeometry> geo = p_ctx.read_geometry(p_node_idx, 0);
if (geo.is_null() || geo->get_instances().is_null()) {
p_ctx.write_geometry(p_node_idx, 0, geo);
return;
}
SocketValue sel = p_ctx.read_input(p_node_idx, 1);
SocketValue trans = p_ctx.read_input(p_node_idx, 2);
SocketValue local = p_ctx.read_input(p_node_idx, 3);
geo = geo->duplicate_geometry();
Ref<ProcInstances> inst = geo->get_instances();
for (int i = 0; i < inst->transforms.size(); i++) {
if (!sel.bool_at(i)) {
continue;
}
Transform3D &xform = inst->transforms.ptrw()[i];
Vector3 t = trans.vec_at(i);
xform.origin += local.bool_at(i) ? xform.basis.xform(t) : t;
}
p_ctx.write_geometry(p_node_idx, 0, geo);
}
// --- RealizeInstancesNode ---
void RealizeInstancesNode::_bind_methods() {
}
RealizeInstancesNode::RealizeInstancesNode() {
// Blender's four. Selection, Realize All and Depth are accepted and not yet
// acted on -- proc-nodes realizes every instance at every depth -- but they
// are declared so a Blender graph's links land instead of being dropped,
// which is the difference between a limitation and a silent one.
input_defs.push_back({ "Geometry", SocketType::Geometry, Variant() });
input_defs.push_back({ "Selection", SocketType::Bool, true });
input_defs.push_back({ "Realize All", SocketType::Bool, true });
input_defs.push_back({ "Depth", SocketType::Int, 0 });
output_defs.push_back({ "Geometry", SocketType::Geometry, Variant() });
}
void RealizeInstancesNode::execute(ProcEvalContext &p_ctx, int p_node_idx) {
Ref<ProcGeometry> geo = p_ctx.read_geometry(p_node_idx, 0);
if (geo.is_null() || geo->get_instances().is_null()) {
p_ctx.write_geometry(p_node_idx, 0, geo);
return;
}
Ref<ProcInstances> inst = geo->get_instances();
Ref<ProcMesh> merged;
merged.instantiate();
for (int i = 0; i < inst->instance_count(); i++) {
int ref_idx = (i < inst->reference_indices.size()) ? inst->reference_indices[i] : 0;
if (ref_idx >= inst->references.size()) {
continue;
}
Ref<ProcGeometry> ref_geo = inst->references[ref_idx];
if (ref_geo.is_null() || ref_geo->get_mesh().is_null()) {
continue;
}
Ref<ProcMesh> ref_mesh = ref_geo->get_mesh();
Transform3D xform = inst->transforms[i];
Basis normal_basis = xform.basis.inverse().transposed();
int base_vert = merged->positions.size();
// Transform and append positions
for (int v = 0; v < ref_mesh->positions.size(); v++) {
merged->positions.push_back(xform.xform(ref_mesh->positions[v]));
}
// Transform and append normals
for (int v = 0; v < ref_mesh->normals.size(); v++) {
merged->normals.push_back((normal_basis.xform(ref_mesh->normals[v])).normalized());
}
// Append UVs (no transformation needed)
for (int v = 0; v < ref_mesh->uvs.size(); v++) {
merged->uvs.push_back(ref_mesh->uvs[v]);
}
// Append colors
for (int v = 0; v < ref_mesh->colors.size(); v++) {
merged->colors.push_back(ref_mesh->colors[v]);
}
// Append indices with offset
for (int idx = 0; idx < ref_mesh->indices.size(); idx++) {
merged->indices.push_back(ref_mesh->indices[idx] + base_vert);
}
}
Ref<ProcGeometry> out;
out.instantiate();
out->set_mesh(merged);
p_ctx.write_geometry(p_node_idx, 0, out);
}
} // namespace godot