OverviewFilesCommitsIssuesDocsReleasesPeers

proc-nodes / src / nodes / rotation_nodes.cpp

568 lines · cpp ref:
// Blender's rotation and matrix node family.
//
// These need SocketType::Rotation and SocketType::Matrix, which is why they did
// not exist before: a rotation spelled as three euler floats does not round-trip
// (euler loses the winding a quaternion keeps) and a 4x4 has no vector spelling.
//
// FunctionNodeMatrixSVD is deliberately absent. A 4x4 singular value
// decomposition is a real numerical routine, not a one-liner, and a wrong one
// is worse than a missing one.

#include "rotation_nodes.h"
#include "../gpu/glsl_emit_context.h"

#include "../graph/graph_executor.h"

#include <godot_cpp/classes/global_constants.hpp>
#include <godot_cpp/variant/basis.hpp>

namespace godot {

// EULER ORDER. Blender's euler is "XYZ" and Godot's matching order is named
// ZYX, because the two name the same thing from opposite ends: Blender by the
// order the rotations are APPLIED (X first), Godot by the order the matrices are
// MULTIPLIED (Rz * Ry * Rx). Same rotation, opposite spelling.
//
// This was got wrong twice. First by leaving godot-cpp on its defaults, which
// are YXZ -- Quaternion::from_euler's own comment says "R = Y(a1).X(a2).Z(a3)".
// Then by "fixing" it to EULER_ORDER_XYZ, which reads correct, passed a test
// that only checked the spelling, and was still a different rotation.
//
// Settled by measurement rather than by reading the names: all six orders were
// evaluated against Blender's own node output for euler (0.3, 0.5, 0.7), and
// ZYX matched to 1e-6 while XYZ was off by 0.36.
#define PN_EULER_ORDER EulerOrder::EULER_ORDER_ZYX

static inline Quaternion pn_quat_from_euler(const Vector3 &p_euler) {
	return Basis::from_euler(p_euler, PN_EULER_ORDER).get_rotation_quaternion();
}

static inline Vector3 pn_euler_from_quat(const Quaternion &p_q) {
	return Basis(p_q.normalized()).get_euler(PN_EULER_ORDER);
}


// ---------------------------------------------------------------------------
// THE FIELD LIFT
//
// Blender's rotation and matrix nodes are FIELD FUNCTIONS: hand any of them a
// per-element input and every output is per-element too. All eighteen here read
// one value and wrote one value, and there was not a single `is_field` in this
// file -- so a field put through any of them COLLAPSED. Everything downstream
// was then evaluated at one sample and returned a constant, with no null node
// and no warning to say so.
//
// The bridged mountain's cell space is exactly that shape,
// Position -> Rotate Vector -> Voronoi, and this is what made its Voronoi a
// constant over all 39366 vertices -- which reads as "the voronoi contributes
// nothing" rather than as a broken node.
//
// The lift is uniform because the operations are: read every input once, take
// the element count from whichever inputs are fields, and evaluate the same
// expression per element. A node with no field input writes a scalar exactly as
// before, so nothing that worked changes.
// ---------------------------------------------------------------------------

struct PnIn {
	Vector<SocketValue> v;
	bool field = false;
	int n = 1;
	AttrDomain domain = AttrDomain::Point;

	PnIn(ProcEvalContext &p_ctx, int p_node, int p_count) {
		for (int i = 0; i < p_count; i++) {
			SocketValue s = p_ctx.read_input(p_node, i);
			if (s.is_field) {
				if (!field) {
					domain = s.domain;
				}
				field = true;
				n = MAX(n, s.field_size);
			}
			v.push_back(s);
		}
	}

	float f(int s, int e) const { return v[s].float_at(e); }
	Vector3 vec(int s, int e) const { return v[s].vec_at(e); }
	Quaternion quat(int s, int e) const { return v[s].quat_at(e); }
	Projection mat(int s, int e) const { return v[s].mat_at(e); }
};

template <typename F>
static void _emit_f(ProcEvalContext &c, int nd, int sk, const PnIn &in, F fn) {
	if (!in.field) {
		c.write_float(nd, sk, fn(0));
		return;
	}
	PackedFloat32Array a;
	a.resize(in.n);
	for (int e = 0; e < in.n; e++) {
		a.ptrw()[e] = fn(e);
	}
	c.write_float_field(nd, sk, a, in.domain);
}

template <typename F>
static void _emit_vec(ProcEvalContext &c, int nd, int sk, const PnIn &in, F fn) {
	if (!in.field) {
		c.write_vector(nd, sk, fn(0));
		return;
	}
	PackedVector3Array a;
	a.resize(in.n);
	for (int e = 0; e < in.n; e++) {
		a.ptrw()[e] = fn(e);
	}
	c.write_vec_field(nd, sk, a, in.domain);
}

template <typename F>
static void _emit_rot(ProcEvalContext &c, int nd, int sk, const PnIn &in, F fn) {
	if (!in.field) {
		c.write_rotation(nd, sk, fn(0));
		return;
	}
	PackedVector4Array a;
	a.resize(in.n);
	for (int e = 0; e < in.n; e++) {
		Quaternion q = fn(e);
		a.ptrw()[e] = Vector4(q.x, q.y, q.z, q.w);
	}
	c.write_rot_field(nd, sk, a, in.domain);
}

template <typename F>
static void _emit_mat(ProcEvalContext &c, int nd, int sk, const PnIn &in, F fn) {
	if (!in.field) {
		c.write_matrix(nd, sk, fn(0));
		return;
	}
	// 16 floats per element, column-major, matching SocketValue::mat_at.
	PackedFloat32Array a;
	a.resize(in.n * 16);
	for (int e = 0; e < in.n; e++) {
		Projection m = fn(e);
		for (int col = 0; col < 4; col++) {
			for (int row = 0; row < 4; row++) {
				a.ptrw()[e * 16 + col * 4 + row] = m.columns[col][row];
			}
		}
	}
	c.write_mat_field(nd, sk, a, in.domain);
}

void AxisAngleToRotationNode::_bind_methods() {}

AxisAngleToRotationNode::AxisAngleToRotationNode() {
	input_defs.push_back({ "Axis", SocketType::Vector, Vector3(0, 0, 1) });
	input_defs.push_back({ "Angle", SocketType::Float, 0.0f });
	output_defs.push_back({ "Rotation", SocketType::Rotation, Variant() });
}

void AxisAngleToRotationNode::execute(ProcEvalContext &p_ctx, int p_node_idx) {
	PnIn in(p_ctx, p_node_idx, 2);
	_emit_rot(p_ctx, p_node_idx, 0, in, [&](int e) {
		Vector3 axis = in.vec(0, e);
		if (axis.length_squared() < 1e-12f) axis = Vector3(0, 0, 1);
		return Quaternion(axis.normalized(), in.f(1, e));
	});
}

void EulerToRotationNode::_bind_methods() {}

EulerToRotationNode::EulerToRotationNode() {
	input_defs.push_back({ "Euler", SocketType::Vector, Vector3() });
	output_defs.push_back({ "Rotation", SocketType::Rotation, Variant() });
}

void EulerToRotationNode::execute(ProcEvalContext &p_ctx, int p_node_idx) {
	PnIn in(p_ctx, p_node_idx, 1);
	_emit_rot(p_ctx, p_node_idx, 0, in, [&](int e) { return pn_quat_from_euler(in.vec(0, e)); });
}

void QuaternionToRotationNode::_bind_methods() {}

QuaternionToRotationNode::QuaternionToRotationNode() {
	input_defs.push_back({ "W", SocketType::Float, 1.0f });
	input_defs.push_back({ "X", SocketType::Float, 0.0f });
	input_defs.push_back({ "Y", SocketType::Float, 0.0f });
	input_defs.push_back({ "Z", SocketType::Float, 0.0f });
	output_defs.push_back({ "Rotation", SocketType::Rotation, Variant() });
}

void QuaternionToRotationNode::execute(ProcEvalContext &p_ctx, int p_node_idx) {
	PnIn in(p_ctx, p_node_idx, 4);
	_emit_rot(p_ctx, p_node_idx, 0, in, [&](int e) {
		Quaternion q(in.f(1, e), in.f(2, e), in.f(3, e), in.f(0, e));
		if (q.length_squared() < 1e-12f) q = Quaternion();
		return q.normalized();
	});
}

void RotationToAxisAngleNode::_bind_methods() {}

RotationToAxisAngleNode::RotationToAxisAngleNode() {
	input_defs.push_back({ "Rotation", SocketType::Rotation, Variant() });
	output_defs.push_back({ "Axis", SocketType::Vector, Variant() });
	output_defs.push_back({ "Angle", SocketType::Float, Variant() });
}

void RotationToAxisAngleNode::execute(ProcEvalContext &p_ctx, int p_node_idx) {
	PnIn in(p_ctx, p_node_idx, 1);
	_emit_vec(p_ctx, p_node_idx, 0, in, [&](int e) { return in.quat(0, e).normalized().get_axis(); });
	_emit_f(p_ctx, p_node_idx, 1, in, [&](int e) { return in.quat(0, e).normalized().get_angle(); });
}

void RotationToEulerNode::_bind_methods() {}

RotationToEulerNode::RotationToEulerNode() {
	input_defs.push_back({ "Rotation", SocketType::Rotation, Variant() });
	output_defs.push_back({ "Euler", SocketType::Vector, Variant() });
}

void RotationToEulerNode::execute(ProcEvalContext &p_ctx, int p_node_idx) {
	PnIn in(p_ctx, p_node_idx, 1);
	_emit_vec(p_ctx, p_node_idx, 0, in, [&](int e) { return pn_euler_from_quat(in.quat(0, e)); });
}

void RotationToQuaternionNode::_bind_methods() {}

RotationToQuaternionNode::RotationToQuaternionNode() {
	input_defs.push_back({ "Rotation", SocketType::Rotation, Variant() });
	output_defs.push_back({ "W", SocketType::Float, Variant() });
	output_defs.push_back({ "X", SocketType::Float, Variant() });
	output_defs.push_back({ "Y", SocketType::Float, Variant() });
	output_defs.push_back({ "Z", SocketType::Float, Variant() });
}

void RotationToQuaternionNode::execute(ProcEvalContext &p_ctx, int p_node_idx) {
	PnIn in(p_ctx, p_node_idx, 1);
	_emit_f(p_ctx, p_node_idx, 0, in, [&](int e) { return in.quat(0, e).w; });
	_emit_f(p_ctx, p_node_idx, 1, in, [&](int e) { return in.quat(0, e).x; });
	_emit_f(p_ctx, p_node_idx, 2, in, [&](int e) { return in.quat(0, e).y; });
	_emit_f(p_ctx, p_node_idx, 3, in, [&](int e) { return in.quat(0, e).z; });
}

void InvertRotationNode::_bind_methods() {}

InvertRotationNode::InvertRotationNode() {
	input_defs.push_back({ "Rotation", SocketType::Rotation, Variant() });
	output_defs.push_back({ "Rotation", SocketType::Rotation, Variant() });
}

void InvertRotationNode::execute(ProcEvalContext &p_ctx, int p_node_idx) {
	PnIn in(p_ctx, p_node_idx, 1);
	_emit_rot(p_ctx, p_node_idx, 0, in, [&](int e) { return in.quat(0, e).normalized().inverse(); });
}

void RotateRotationNode::_bind_methods() {}

RotateRotationNode::RotateRotationNode() {
	input_defs.push_back({ "Rotation", SocketType::Rotation, Variant() });
	input_defs.push_back({ "Rotate By", SocketType::Rotation, Variant() });
	output_defs.push_back({ "Rotation", SocketType::Rotation, Variant() });
}

void RotateRotationNode::execute(ProcEvalContext &p_ctx, int p_node_idx) {
	PnIn in(p_ctx, p_node_idx, 2);
	_emit_rot(p_ctx, p_node_idx, 0, in, [&](int e) {
		return (in.quat(1, e).normalized() * in.quat(0, e).normalized()).normalized();
	});
}

void RotateVectorNode::_bind_methods() {}

RotateVectorNode::RotateVectorNode() {
	input_defs.push_back({ "Vector", SocketType::Vector, Vector3() });
	input_defs.push_back({ "Rotation", SocketType::Rotation, Variant() });
	output_defs.push_back({ "Vector", SocketType::Vector, Variant() });
}

void RotateVectorNode::execute(ProcEvalContext &p_ctx, int p_node_idx) {
	PnIn in(p_ctx, p_node_idx, 2);
	_emit_vec(p_ctx, p_node_idx, 0, in, [&](int e) {
		return in.quat(1, e).normalized().xform(in.vec(0, e));
	});
}

void InputRotationNode::_bind_methods() {
	ClassDB::bind_method(D_METHOD("set_rotation", "q"), &InputRotationNode::set_rotation);
	ClassDB::bind_method(D_METHOD("get_rotation"), &InputRotationNode::get_rotation);
	ADD_PROPERTY(PropertyInfo(Variant::QUATERNION, "rotation"), "set_rotation", "get_rotation");
}

InputRotationNode::InputRotationNode() {
	output_defs.push_back({ "Rotation", SocketType::Rotation, Variant() });
}

void InputRotationNode::execute(ProcEvalContext &p_ctx, int p_node_idx) {
	p_ctx.write_rotation(p_node_idx, 0, rotation);
}

void RotateEulerNode::_bind_methods() {}

RotateEulerNode::RotateEulerNode() {
	input_defs.push_back({ "Rotation", SocketType::Vector, Vector3() });
	input_defs.push_back({ "Rotate By", SocketType::Vector, Vector3() });
	output_defs.push_back({ "Rotation", SocketType::Vector, Variant() });
}

void RotateEulerNode::execute(ProcEvalContext &p_ctx, int p_node_idx) {
	PnIn in(p_ctx, p_node_idx, 2);
	_emit_vec(p_ctx, p_node_idx, 0, in, [&](int e) {
		Quaternion a = pn_quat_from_euler(in.vec(0, e));
		Quaternion b = pn_quat_from_euler(in.vec(1, e));
		return pn_euler_from_quat((b * a).normalized());
	});
}

void VectorRotateNode::_bind_methods() {
	ClassDB::bind_method(D_METHOD("set_rotation_type", "t"), &VectorRotateNode::set_rotation_type);
	ClassDB::bind_method(D_METHOD("get_rotation_type"), &VectorRotateNode::get_rotation_type);
	ADD_PROPERTY(PropertyInfo(Variant::INT, "rotation_type"), "set_rotation_type", "get_rotation_type");
	ClassDB::bind_method(D_METHOD("set_invert", "i"), &VectorRotateNode::set_invert);
	ClassDB::bind_method(D_METHOD("get_invert"), &VectorRotateNode::get_invert);
	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "invert"), "set_invert", "get_invert");
}

VectorRotateNode::VectorRotateNode() {
	input_defs.push_back({ "Vector", SocketType::Vector, Vector3() });
	input_defs.push_back({ "Center", SocketType::Vector, Vector3() });
	input_defs.push_back({ "Axis", SocketType::Vector, Vector3(0, 0, 1) });
	input_defs.push_back({ "Angle", SocketType::Float, 0.0f });
	// Blender's fifth socket, only read by EULER_XYZ.
	input_defs.push_back({ "Rotation", SocketType::Vector, Vector3() });
	output_defs.push_back({ "Vector", SocketType::Vector, Variant() });
}

void VectorRotateNode::execute(ProcEvalContext &p_ctx, int p_node_idx) {
	PnIn in(p_ctx, p_node_idx, 5);
	_emit_vec(p_ctx, p_node_idx, 0, in, [&](int e) {
		Vector3 v = in.vec(0, e);
		Vector3 c = in.vec(1, e);

		// EULER_XYZ builds a matrix from the Rotation socket and INVERTS the
		// matrix rather than negating the angles -- for a composed rotation
		// those are the same thing only because the inverse of a rotation
		// matrix is its transpose, which is what invert_m3 gives here.
		if (rotation_type == 4) {
			Basis b = Basis::from_euler(in.vec(4, e), PN_EULER_ORDER);
			if (invert) {
				b = b.inverse();
			}
			return c + b.xform(v - c);
		}

		Vector3 axis;
		switch (rotation_type) {
			case 1: axis = Vector3(1, 0, 0); break;
			case 2: axis = Vector3(0, 1, 0); break;
			case 3: axis = Vector3(0, 0, 1); break;
			default: axis = in.vec(2, e); break;
		}
		float angle = in.f(3, e);
		if (invert) {
			angle = -angle;
		}
		if (axis.length_squared() < 1e-12f) {
			return v;
		}
		return c + Quaternion(axis.normalized(), angle).xform(v - c);
	});
}

void CombineMatrixNode::_bind_methods() {}

CombineMatrixNode::CombineMatrixNode() {
	input_defs.push_back({ "Column 1 Row 1", SocketType::Float, 1.0f });
	input_defs.push_back({ "Column 1 Row 2", SocketType::Float, 0.0f });
	input_defs.push_back({ "Column 1 Row 3", SocketType::Float, 0.0f });
	input_defs.push_back({ "Column 1 Row 4", SocketType::Float, 0.0f });
	input_defs.push_back({ "Column 2 Row 1", SocketType::Float, 0.0f });
	input_defs.push_back({ "Column 2 Row 2", SocketType::Float, 1.0f });
	input_defs.push_back({ "Column 2 Row 3", SocketType::Float, 0.0f });
	input_defs.push_back({ "Column 2 Row 4", SocketType::Float, 0.0f });
	input_defs.push_back({ "Column 3 Row 1", SocketType::Float, 0.0f });
	input_defs.push_back({ "Column 3 Row 2", SocketType::Float, 0.0f });
	input_defs.push_back({ "Column 3 Row 3", SocketType::Float, 1.0f });
	input_defs.push_back({ "Column 3 Row 4", SocketType::Float, 0.0f });
	input_defs.push_back({ "Column 4 Row 1", SocketType::Float, 0.0f });
	input_defs.push_back({ "Column 4 Row 2", SocketType::Float, 0.0f });
	input_defs.push_back({ "Column 4 Row 3", SocketType::Float, 0.0f });
	input_defs.push_back({ "Column 4 Row 4", SocketType::Float, 1.0f });
	output_defs.push_back({ "Matrix", SocketType::Matrix, Variant() });
}

void CombineMatrixNode::execute(ProcEvalContext &p_ctx, int p_node_idx) {
	PnIn in(p_ctx, p_node_idx, 16);
	_emit_mat(p_ctx, p_node_idx, 0, in, [&](int e) {
		Projection p;
		for (int c = 0; c < 4; c++) {
			for (int r = 0; r < 4; r++) {
				p.columns[c][r] = in.f(c * 4 + r, e);
			}
		}
		return p;
	});
}

void SeparateMatrixNode::_bind_methods() {}

SeparateMatrixNode::SeparateMatrixNode() {
	input_defs.push_back({ "Matrix", SocketType::Matrix, Variant() });
	output_defs.push_back({ "Column 1 Row 1", SocketType::Float, Variant() });
	output_defs.push_back({ "Column 1 Row 2", SocketType::Float, Variant() });
	output_defs.push_back({ "Column 1 Row 3", SocketType::Float, Variant() });
	output_defs.push_back({ "Column 1 Row 4", SocketType::Float, Variant() });
	output_defs.push_back({ "Column 2 Row 1", SocketType::Float, Variant() });
	output_defs.push_back({ "Column 2 Row 2", SocketType::Float, Variant() });
	output_defs.push_back({ "Column 2 Row 3", SocketType::Float, Variant() });
	output_defs.push_back({ "Column 2 Row 4", SocketType::Float, Variant() });
	output_defs.push_back({ "Column 3 Row 1", SocketType::Float, Variant() });
	output_defs.push_back({ "Column 3 Row 2", SocketType::Float, Variant() });
	output_defs.push_back({ "Column 3 Row 3", SocketType::Float, Variant() });
	output_defs.push_back({ "Column 3 Row 4", SocketType::Float, Variant() });
	output_defs.push_back({ "Column 4 Row 1", SocketType::Float, Variant() });
	output_defs.push_back({ "Column 4 Row 2", SocketType::Float, Variant() });
	output_defs.push_back({ "Column 4 Row 3", SocketType::Float, Variant() });
	output_defs.push_back({ "Column 4 Row 4", SocketType::Float, Variant() });
}

void SeparateMatrixNode::execute(ProcEvalContext &p_ctx, int p_node_idx) {
	PnIn in(p_ctx, p_node_idx, 1);
	for (int c = 0; c < 4; c++) {
		for (int r = 0; r < 4; r++) {
			_emit_f(p_ctx, p_node_idx, c * 4 + r, in,
					[&](int e) { return in.mat(0, e).columns[c][r]; });
		}
	}
}

void InvertMatrixNode::_bind_methods() {}

InvertMatrixNode::InvertMatrixNode() {
	input_defs.push_back({ "Matrix", SocketType::Matrix, Variant() });
	output_defs.push_back({ "Matrix", SocketType::Matrix, Variant() });
	output_defs.push_back({ "Invertible", SocketType::Bool, Variant() });
}

void InvertMatrixNode::execute(ProcEvalContext &p_ctx, int p_node_idx) {
	PnIn in(p_ctx, p_node_idx, 1);
	_emit_mat(p_ctx, p_node_idx, 0, in, [&](int e) {
		Projection p = in.mat(0, e);
		return Math::abs(p.determinant()) > 1e-12 ? p.inverse() : Projection();
	});
	// Invertible is a BOOL socket; a bool field is carried as an int field.
	if (!in.field) {
		p_ctx.write_bool(p_node_idx, 1, Math::abs(in.mat(0, 0).determinant()) > 1e-12);
	} else {
		PackedInt32Array a;
		a.resize(in.n);
		for (int e = 0; e < in.n; e++) {
			a.ptrw()[e] = Math::abs(in.mat(0, e).determinant()) > 1e-12 ? 1 : 0;
		}
		p_ctx.write_int_field(p_node_idx, 1, a, in.domain);
	}
}

void MatrixDeterminantNode::_bind_methods() {}

MatrixDeterminantNode::MatrixDeterminantNode() {
	input_defs.push_back({ "Matrix", SocketType::Matrix, Variant() });
	output_defs.push_back({ "Determinant", SocketType::Float, Variant() });
}

void MatrixDeterminantNode::execute(ProcEvalContext &p_ctx, int p_node_idx) {
	PnIn in(p_ctx, p_node_idx, 1);
	_emit_f(p_ctx, p_node_idx, 0, in, [&](int e) { return (float)in.mat(0, e).determinant(); });
}

void MatrixMultiplyNode::_bind_methods() {}

MatrixMultiplyNode::MatrixMultiplyNode() {
	input_defs.push_back({ "Matrix", SocketType::Matrix, Variant() });
	input_defs.push_back({ "Matrix", SocketType::Matrix, Variant() });
	output_defs.push_back({ "Matrix", SocketType::Matrix, Variant() });
}

void MatrixMultiplyNode::execute(ProcEvalContext &p_ctx, int p_node_idx) {
	PnIn in(p_ctx, p_node_idx, 2);
	_emit_mat(p_ctx, p_node_idx, 0, in,
			[&](int e) { return in.mat(0, e) * in.mat(1, e); });
}

void TransposeMatrixNode::_bind_methods() {}

TransposeMatrixNode::TransposeMatrixNode() {
	input_defs.push_back({ "Matrix", SocketType::Matrix, Variant() });
	output_defs.push_back({ "Matrix", SocketType::Matrix, Variant() });
}

void TransposeMatrixNode::execute(ProcEvalContext &p_ctx, int p_node_idx) {
	PnIn in(p_ctx, p_node_idx, 1);
	_emit_mat(p_ctx, p_node_idx, 0, in, [&](int e) {
		Projection p = in.mat(0, e);
		Projection t;
		for (int c = 0; c < 4; c++) {
			for (int r = 0; r < 4; r++) {
				t.columns[c][r] = p.columns[r][c];
			}
		}
		return t;
	});
}

// --- GLSL for the two rotation nodes the density cone needs ------------------
//
// PN_EULER_ORDER is ZYX, so the basis is Rz * Ry * Rx and the quaternion is
// qz * qy * qx -- matrix product order, which is what "ZYX" names here. The
// composition is written out rather than fused so the order is readable at the
// point it matters.
static const char *PN_QUAT_GLSL = R"(
#ifndef PN_QUAT_ROT_HELPERS
#define PN_QUAT_ROT_HELPERS
vec4 pn_quat_axis(vec3 axis, float ang) {
    float h = ang * 0.5;
    return vec4(axis * sin(h), cos(h));
}
vec4 pn_quat_mul(vec4 a, vec4 b) {
    return vec4(a.w * b.xyz + b.w * a.xyz + cross(a.xyz, b.xyz),
                a.w * b.w - dot(a.xyz, b.xyz));
}
vec4 pn_quat_from_euler_zyx(vec3 e) {
    vec4 qz = pn_quat_axis(vec3(0.0, 0.0, 1.0), e.z);
    vec4 qy = pn_quat_axis(vec3(0.0, 1.0, 0.0), e.y);
    vec4 qx = pn_quat_axis(vec3(1.0, 0.0, 0.0), e.x);
    return pn_quat_mul(pn_quat_mul(qz, qy), qx);
}
vec3 pn_quat_xform(vec4 q, vec3 v) {
    q = normalize(q);
    vec3 t = 2.0 * cross(q.xyz, v);
    return v + q.w * t + cross(q.xyz, t);
}
#endif
)";

String EulerToRotationNode::compile_glsl_prelude(int) const {
	return String(PN_QUAT_GLSL);
}

String EulerToRotationNode::compile_glsl(const GlslEmitContext &p_ctx, int p_node_idx) const {
	return p_ctx.declare(p_node_idx, 0) + String(" = pn_quat_from_euler_zyx(") +
			p_ctx.in(p_node_idx, 0) + String(");");
}

String RotateVectorNode::compile_glsl_prelude(int) const {
	return String(PN_QUAT_GLSL);
}

String RotateVectorNode::compile_glsl(const GlslEmitContext &p_ctx, int p_node_idx) const {
	// execute() normalizes the quaternion before xform; pn_quat_xform does the
	// same, so an unnormalized Rotation arriving from either side behaves alike.
	return p_ctx.declare(p_node_idx, 0) + String(" = pn_quat_xform(") +
			p_ctx.in(p_node_idx, 1) + String(", ") + p_ctx.in(p_node_idx, 0) + String(");");
}

} // namespace godot