OverviewFilesCommitsIssuesDocsReleasesPeers

proc-nodes / src / nodes / instance_nodes.cpp

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