If what you are looking for is a way to decompose a 4x4 rotation matrix into 4D Euler angles, here is a C++ function that performs this operation (license: The Unlicense / Public Domain). The C++ code:
Euler4D Euler4D::from_basis(const Basis4D &p_basis, const bool p_use_special_cases) {
// This process produces a LOT of floating-point error.
// It's also likely that this could be optimized a lot.
// With 32-bit floats, this has a margin of error of 0.1%.
Basis4D basis = p_basis.orthonormalized();
// Stage 0: Determine if this is a special case. This is optional, for UX.
// This ensures that single rotations in the inspector don't transform into
// other rotations. Godot does the same thing for 3D euler angles.
if (p_use_special_cases) {
if (Math::is_zero_approx(basis.y.z) && Math::is_zero_approx(basis.z.y) && Math::is_zero_approx(basis.z.x) && Math::is_zero_approx(basis.x.z) && Math::is_zero_approx(basis.x.w) && Math::is_zero_approx(basis.w.x) && Math::is_zero_approx(basis.w.y) && Math::is_zero_approx(basis.y.w)) {
// Special case: No YZ, ZX, XW, or WY rotations, so the only rotations may be in XY or ZW.
return Euler4D(0.0, 0.0, Math::atan2(basis.x.y, basis.x.x), 0.0, 0.0, Math::atan2(basis.z.w, basis.z.z));
}
if (Math::is_zero_approx(basis.z.x) && Math::is_zero_approx(basis.x.z) && Math::is_zero_approx(basis.x.y) && Math::is_zero_approx(basis.y.x) && Math::is_zero_approx(basis.w.y) && Math::is_zero_approx(basis.y.w) && Math::is_zero_approx(basis.z.w) && Math::is_zero_approx(basis.w.z)) {
// Special case: No ZX, XY, WY, or ZW rotations, so the only rotations may be in YZ or XW.
return Euler4D(Math::atan2(basis.y.z, basis.y.y), 0.0, 0.0, Math::atan2(basis.x.w, basis.x.x), 0.0, 0.0);
}
if (Math::is_zero_approx(basis.y.z) && Math::is_zero_approx(basis.z.y) && Math::is_zero_approx(basis.x.y) && Math::is_zero_approx(basis.y.x) && Math::is_zero_approx(basis.x.w) && Math::is_zero_approx(basis.w.x) && Math::is_zero_approx(basis.z.w) && Math::is_zero_approx(basis.w.z)) {
// Special case: No YZ, XY, XW, or ZW rotations, so the only rotations may be in ZX or WY.
return Euler4D(0.0, Math::atan2(basis.z.x, basis.z.z), 0.0, 0.0, Math::atan2(basis.w.y, basis.w.w), 0.0);
}
}
// Stage 1: The outer rotations (and yz is special).
real_t yz = -Math::asin(basis.z.y);
real_t zx = Math::atan2(basis.z.x, basis.z.z);
real_t wy = Math::atan2(basis.w.y, basis.y.y);
// Un-rotate by perpendicular ZX and WY, the outermost rotations.
basis = Basis4D::from_zx(-zx) * basis * Basis4D::from_wy(-wy);
// Stage 2: The inner rotations (and xw is special).
real_t xw = -Math::asin(basis.w.x);
real_t zw = Math::atan2(basis.z.w, basis.z.z);
real_t xy = Math::atan2(basis.x.y, basis.y.y);
return Euler4D(yz, zx, xy, xw, wy, zw);
}
Note that this code is designed with the conventions of Y-up, YZ is pitch, and uses ZX and WY planes instead of XZ and YW. Also note that basis components are column-major (basis.x.y is X column Y row).
Note that this is not the same as a generalized 4D geometric algebra rotor, which can describe and interpolate between rotations. This approach heavily depends on the conventions used, cannot be smoothly interpolated, and is prone to gimbal lock.