#include "geometry_nodes.h"
#include "../graph/graph_executor.h"
namespace godot {
// --- TransformGeometryNode ---
void TransformGeometryNode::_bind_methods() {
}
TransformGeometryNode::TransformGeometryNode() {
// BLENDER'S LAYOUT, and the indices matter: a MENU socket sits at 1, so
// Translation is 2 and not 1. Ours was short by that socket and by the
// Transform matrix, and its Rotation was a Vector of euler DEGREES where
// Blender's is a quaternion Rotation socket -- a bridged rotation was
// therefore read as degrees-as-radians on the wrong type.
input_defs.push_back({ "Geometry", SocketType::Geometry, Variant() });
// Blender 5.x MENU socket: "Components" or "Matrix". Carried as an int
// because proc-nodes has no menu socket type; 0 is Components.
input_defs.push_back({ "Mode", SocketType::Int, 0 });
input_defs.push_back({ "Translation", SocketType::Vector, Vector3() });
input_defs.push_back({ "Rotation", SocketType::Rotation, Quaternion() });
input_defs.push_back({ "Scale", SocketType::Vector, Vector3(1, 1, 1) });
input_defs.push_back({ "Transform", SocketType::Matrix, Projection() });
output_defs.push_back({ "Geometry", SocketType::Geometry, Variant() });
}
void TransformGeometryNode::execute(ProcEvalContext &p_ctx, int p_node_idx) {
Ref<ProcGeometry> geo = p_ctx.read_geometry(p_node_idx, 0);
if (geo.is_null()) {
p_ctx.write_geometry(p_node_idx, 0, geo);
return;
}
const int mode = p_ctx.read_int(p_node_idx, 1);
Transform3D xform;
if (mode == 1) {
// MATRIX mode: the Transform socket IS the transform.
Projection m = p_ctx.read_matrix(p_node_idx, 5);
Basis b(Vector3(m.columns[0][0], m.columns[0][1], m.columns[0][2]),
Vector3(m.columns[1][0], m.columns[1][1], m.columns[1][2]),
Vector3(m.columns[2][0], m.columns[2][1], m.columns[2][2]));
// Godot's Basis takes ROWS; a Projection stores columns, so this is
// transposed on the way in.
b = b.transposed();
xform = Transform3D(b,
Vector3(m.columns[3][0], m.columns[3][1], m.columns[3][2]));
} else {
Vector3 translation = p_ctx.read_vector(p_node_idx, 2);
Quaternion rotation = p_ctx.read_rotation(p_node_idx, 3);
Vector3 scale = p_ctx.read_vector(p_node_idx, 4);
Basis basis(rotation.is_normalized() ? rotation : Quaternion());
basis.scale(scale);
xform = Transform3D(basis, translation);
}
// Duplicate to avoid mutating upstream
geo = geo->duplicate_geometry();
// Transform mesh positions and normals
if (geo->get_mesh().is_valid()) {
Ref<ProcMesh> mesh = geo->get_mesh();
Vector3 *pos = mesh->positions.ptrw();
int count = mesh->positions.size();
for (int i = 0; i < count; i++) {
pos[i] = xform.xform(pos[i]);
}
if (mesh->normals.size() == count) {
Vector3 *nor = mesh->normals.ptrw();
// From the composed transform, not from a local `basis` -- in
// MATRIX mode there is no separate basis to take.
Basis normal_basis = xform.basis.inverse().transposed();
for (int i = 0; i < count; i++) {
nor[i] = normal_basis.xform(nor[i]).normalized();
}
}
}
// Transform curve positions
if (geo->get_curves().is_valid()) {
Ref<ProcCurves> curves = geo->get_curves();
Vector3 *pos = curves->positions.ptrw();
for (int i = 0; i < curves->positions.size(); i++) {
pos[i] = xform.xform(pos[i]);
}
}
// Transform pointcloud positions
if (geo->get_pointcloud().is_valid()) {
Ref<ProcPointCloud> pc = geo->get_pointcloud();
Vector3 *pos = pc->positions.ptrw();
for (int i = 0; i < pc->positions.size(); i++) {
pos[i] = xform.xform(pos[i]);
}
}
// Transform instance transforms
if (geo->get_instances().is_valid()) {
Ref<ProcInstances> inst = geo->get_instances();
for (int i = 0; i < inst->transforms.size(); i++) {
inst->transforms.ptrw()[i] = xform * inst->transforms[i];
}
}
p_ctx.write_geometry(p_node_idx, 0, geo);
}
String TransformGeometryNode::compile_glsl(const GlslEmitContext &p_ctx, int p_node_idx) const {
// Full matrix transform requires more push constant space than available
// Fall back to CPU execution (returned empty means GpuCompiler skips this node)
return "";
}
// --- SetPositionNode ---
void SetPositionNode::_bind_methods() {
}
SetPositionNode::SetPositionNode() {
// Blender's Set Position exactly: Geometry, Selection, Position, Offset.
// Geometry nodes is the source of truth, so the layout matches even though
// nothing forces it to -- the bridge resolves sockets by name.
input_defs.push_back({ "Geometry", SocketType::Geometry, Variant() });
input_defs.push_back({ "Selection", SocketType::Bool, true });
input_defs.push_back({ "Position", SocketType::Vector, Vector3() });
input_defs.push_back({ "Offset", SocketType::Vector, Vector3() });
output_defs.push_back({ "Geometry", SocketType::Geometry, Variant() });
}
void SetPositionNode::execute(ProcEvalContext &p_ctx, int p_node_idx) {
Ref<ProcGeometry> geo = p_ctx.read_geometry(p_node_idx, 0);
if (geo.is_null()) {
p_ctx.write_geometry(p_node_idx, 0, geo);
return;
}
// new = Selection ? (Position + Offset) : old.
//
// Position is an IMPLICIT FIELD: unconnected it means "keep the position
// this element already has", not "move everything to (0,0,0)". A default
// cannot say that, so ask whether it is wired.
bool has_position = p_ctx.is_input_connected(p_node_idx, 2);
geo = geo->duplicate_geometry();
// Redeem any context fields against THIS node's geometry -- that is what
// makes a Position with no Geometry input mean "the positions of whatever
// this node is operating on".
SocketValue sel_val = p_ctx.materialize(p_ctx.read_input(p_node_idx, 1), geo);
SocketValue pos_val = p_ctx.materialize(p_ctx.read_input(p_node_idx, 2), geo);
SocketValue offset_val = p_ctx.materialize(p_ctx.read_input(p_node_idx, 3), geo);
if (geo->get_mesh().is_valid()) {
Ref<ProcMesh> mesh = geo->get_mesh();
Vector3 *pos = mesh->positions.ptrw();
int count = mesh->positions.size();
for (int i = 0; i < count; i++) {
if (!sel_val.bool_at(i)) {
continue;
}
Vector3 base = has_position ? pos_val.vec_at(i) : pos[i];
pos[i] = base + offset_val.vec_at(i);
}
mesh->recalculate_normals();
}
if (geo->get_curves().is_valid()) {
Ref<ProcCurves> curves = geo->get_curves();
Vector3 *pos = curves->positions.ptrw();
Vector3 offset = offset_val.to_vector(); // curves use uniform offset
for (int i = 0; i < curves->positions.size(); i++) {
pos[i] += offset;
}
}
if (geo->get_pointcloud().is_valid()) {
Ref<ProcPointCloud> pc = geo->get_pointcloud();
Vector3 *pos = pc->positions.ptrw();
Vector3 pc_offset = offset_val.to_vector();
for (int i = 0; i < pc->positions.size(); i++) {
pos[i] += pc_offset;
}
}
p_ctx.write_geometry(p_node_idx, 0, geo);
}
String SetPositionNode::compile_glsl(const GlslEmitContext &p_ctx, int p_node_idx) const {
// Offset is socket 3. In the batch path param() spells it
// `vec3(pc.param0, pc.param1, pc.param2)` -- the same text as before -- and
// in the density path it becomes the socket's literal or the producer's
// variable.
//
// ⛔ THE DENSITY PATH EMITTED `pos += vec3(0.0, 0.0, 0.0);`. The old text
// named push-constant SLOTS, and compile_to_function replaced `pc.paramN`
// with socket N's default: socket 0 is Geometry and sockets 1 and 2 are
// Bool and Vector, none of which has a float literal, so all three came out
// 0.0. Set Position was a no-op in every emitted density shader.
//
// It matches execute() only while Selection and Position are unconnected --
// `base = has_position ? pos_val : pos[i]`, and unconnected Position means
// "keep the position this element has", which is what `pos +=` does.
// GraphExecutor refuses the batch when either is wired.
return String("pos += ") + p_ctx.param(p_node_idx, 3) + ";";
}
// --- JoinGeometryNode ---
void JoinGeometryNode::_bind_methods() {
}
JoinGeometryNode::JoinGeometryNode() {
// ONE MULTI-INPUT SOCKET, as Blender has. Ours was "Geometry A" plus
// "Geometry B": two discrete sockets, so a three-way join lost one input,
// and "Geometry B" matched nothing on Blender's node -- which is what put
// it in the unresolved list and produced the claim that a multi-input could
// not be represented.
SocketDef geo;
geo.name = "Geometry";
geo.type = SocketType::Geometry;
geo.multi = true;
input_defs.push_back(geo);
output_defs.push_back({ "Geometry", SocketType::Geometry, Variant() });
}
void JoinGeometryNode::execute(ProcEvalContext &p_ctx, int p_node_idx) {
// Every link on the socket, in the order they were made.
Vector<SocketValue> parts = p_ctx.read_input_multi(p_node_idx, 0);
Ref<ProcGeometry> result;
for (int i = 0; i < parts.size(); i++) {
Ref<ProcGeometry> g = parts[i].geometry;
if (g.is_null()) {
continue;
}
if (result.is_null()) {
result = g->duplicate_geometry();
} else {
result->join(g);
}
}
if (result.is_null()) {
result.instantiate();
}
p_ctx.write_geometry(p_node_idx, 0, result);
}
// --- ConvexHullNode ---
void ConvexHullNode::_bind_methods() {
}
ConvexHullNode::ConvexHullNode() {
input_defs.push_back({ "Geometry", SocketType::Geometry, Variant() });
output_defs.push_back({ "Geometry", SocketType::Geometry, Variant() });
}
void ConvexHullNode::execute(ProcEvalContext &p_ctx, int p_node_idx) {
Ref<ProcGeometry> geo = p_ctx.read_geometry(p_node_idx, 0);
if (geo.is_null()) {
p_ctx.write_geometry(p_node_idx, 0, geo);
return;
}
// Placeholder: pass through input geometry.
// Full implementation would compute convex hull via Quickhull.
geo = geo->duplicate_geometry();
p_ctx.write_geometry(p_node_idx, 0, geo);
}
// --- GeometryToInstanceNode ---
void GeometryToInstanceNode::_bind_methods() {
}
GeometryToInstanceNode::GeometryToInstanceNode() {
input_defs.push_back({ "Geometry", SocketType::Geometry, Variant() });
output_defs.push_back({ "Geometry", SocketType::Geometry, Variant() });
}
void GeometryToInstanceNode::execute(ProcEvalContext &p_ctx, int p_node_idx) {
Ref<ProcGeometry> geo = p_ctx.read_geometry(p_node_idx, 0);
if (geo.is_null()) {
p_ctx.write_geometry(p_node_idx, 0, geo);
return;
}
// Store the input geometry as a single instance with identity transform.
Ref<ProcGeometry> result;
result.instantiate();
Ref<ProcInstances> inst = result->ensure_instances();
inst->references.push_back(geo);
inst->reference_indices.push_back(0);
inst->transforms.push_back(Transform3D());
p_ctx.write_geometry(p_node_idx, 0, result);
}
// --- ViewerNode ---
void ViewerNode::_bind_methods() {
}
ViewerNode::ViewerNode() {
input_defs.push_back({ "Geometry", SocketType::Geometry, Variant() });
output_defs.push_back({ "Geometry", SocketType::Geometry, Variant() });
}
void ViewerNode::execute(ProcEvalContext &p_ctx, int p_node_idx) {
// Debug pass-through: just forward the geometry unchanged.
Ref<ProcGeometry> geo = p_ctx.read_geometry(p_node_idx, 0);
p_ctx.write_geometry(p_node_idx, 0, geo);
}
} // namespace godot