blob: 85f8099e4c84225e9345c4fc134beba8ac4f4092 [file] [log] [blame]
/*
Conversion from quaternions to Euler rotation sequences.
From: http://bediyap.com/programming/convert-quaternion-to-euler-rotations/
*/
use crate::{DQuat, Quat};
/// Euler rotation sequences.
///
/// The angles are applied starting from the right.
/// E.g. XYZ will first apply the z-axis rotation.
///
/// YXZ can be used for yaw (y-axis), pitch (x-axis), roll (z-axis).
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum EulerRot {
/// Intrinsic three-axis rotation ZYX
ZYX,
/// Intrinsic three-axis rotation ZXY
ZXY,
/// Intrinsic three-axis rotation YXZ
YXZ,
/// Intrinsic three-axis rotation YZX
YZX,
/// Intrinsic three-axis rotation XYZ
XYZ,
/// Intrinsic three-axis rotation XZY
XZY,
}
impl Default for EulerRot {
/// Default `YXZ` as yaw (y-axis), pitch (x-axis), roll (z-axis).
fn default() -> Self {
Self::YXZ
}
}
/// Conversion from quaternion to euler angles.
pub(crate) trait EulerFromQuaternion<Q: Copy>: Sized + Copy {
type Output;
/// Compute the angle of the first axis (X-x-x)
fn first(self, q: Q) -> Self::Output;
/// Compute then angle of the second axis (x-X-x)
fn second(self, q: Q) -> Self::Output;
/// Compute then angle of the third axis (x-x-X)
fn third(self, q: Q) -> Self::Output;
/// Compute all angles of a rotation in the notation order
fn convert_quat(self, q: Q) -> (Self::Output, Self::Output, Self::Output);
#[doc(hidden)]
fn sine_theta(self, q: Q) -> Self::Output;
}
/// Conversion from euler angles to quaternion.
pub(crate) trait EulerToQuaternion<T>: Copy {
type Output;
/// Create the rotation quaternion for the three angles of this euler rotation sequence.
fn new_quat(self, u: T, v: T, w: T) -> Self::Output;
}
macro_rules! impl_from_quat {
($t:ident, $quat:ident) => {
impl EulerFromQuaternion<$quat> for EulerRot {
type Output = $t;
fn sine_theta(self, q: $quat) -> $t {
use EulerRot::*;
match self {
ZYX => -2.0 * (q.x * q.z - q.w * q.y),
ZXY => 2.0 * (q.y * q.z + q.w * q.x),
YXZ => -2.0 * (q.y * q.z - q.w * q.x),
YZX => 2.0 * (q.x * q.y + q.w * q.z),
XYZ => 2.0 * (q.x * q.z + q.w * q.y),
XZY => -2.0 * (q.x * q.y - q.w * q.z),
}
.clamp(-1.0, 1.0)
}
fn first(self, q: $quat) -> $t {
use crate::$t::math;
use EulerRot::*;
let sine_theta = self.sine_theta(q);
if math::abs(sine_theta) > 0.99999 {
let scale = 2.0 * math::signum(sine_theta);
match self {
ZYX => scale * math::atan2(-q.x, q.w),
ZXY => scale * math::atan2(q.y, q.w),
YXZ => scale * math::atan2(-q.z, q.w),
YZX => scale * math::atan2(q.x, q.w),
XYZ => scale * math::atan2(q.z, q.w),
XZY => scale * math::atan2(-q.y, q.w),
}
} else {
match self {
ZYX => math::atan2(
2.0 * (q.x * q.y + q.w * q.z),
q.w * q.w + q.x * q.x - q.y * q.y - q.z * q.z,
),
ZXY => math::atan2(
-2.0 * (q.x * q.y - q.w * q.z),
q.w * q.w - q.x * q.x + q.y * q.y - q.z * q.z,
),
YXZ => math::atan2(
2.0 * (q.x * q.z + q.w * q.y),
q.w * q.w - q.x * q.x - q.y * q.y + q.z * q.z,
),
YZX => math::atan2(
-2.0 * (q.x * q.z - q.w * q.y),
q.w * q.w + q.x * q.x - q.y * q.y - q.z * q.z,
),
XYZ => math::atan2(
-2.0 * (q.y * q.z - q.w * q.x),
q.w * q.w - q.x * q.x - q.y * q.y + q.z * q.z,
),
XZY => math::atan2(
2.0 * (q.y * q.z + q.w * q.x),
q.w * q.w - q.x * q.x + q.y * q.y - q.z * q.z,
),
}
}
}
fn second(self, q: $quat) -> $t {
use crate::$t::math;
math::asin(self.sine_theta(q))
}
fn third(self, q: $quat) -> $t {
use crate::$t::math;
use EulerRot::*;
if math::abs(self.sine_theta(q)) > 0.99999 {
0.0
} else {
match self {
ZYX => math::atan2(
2.0 * (q.y * q.z + q.w * q.x),
q.w * q.w - q.x * q.x - q.y * q.y + q.z * q.z,
),
ZXY => math::atan2(
-2.0 * (q.x * q.z - q.w * q.y),
q.w * q.w - q.x * q.x - q.y * q.y + q.z * q.z,
),
YXZ => math::atan2(
2.0 * (q.x * q.y + q.w * q.z),
q.w * q.w - q.x * q.x + q.y * q.y - q.z * q.z,
),
YZX => math::atan2(
-2.0 * (q.y * q.z - q.w * q.x),
q.w * q.w - q.x * q.x + q.y * q.y - q.z * q.z,
),
XYZ => math::atan2(
-2.0 * (q.x * q.y - q.w * q.z),
q.w * q.w + q.x * q.x - q.y * q.y - q.z * q.z,
),
XZY => math::atan2(
2.0 * (q.x * q.z + q.w * q.y),
q.w * q.w + q.x * q.x - q.y * q.y - q.z * q.z,
),
}
}
}
fn convert_quat(self, q: $quat) -> ($t, $t, $t) {
use crate::$t::math;
use EulerRot::*;
let sine_theta = self.sine_theta(q);
let second = math::asin(sine_theta);
if math::abs(sine_theta) > 0.99999 {
let scale = 2.0 * math::signum(sine_theta);
return match self {
ZYX => (scale * math::atan2(-q.x, q.w), second, 0.0),
ZXY => (scale * math::atan2(q.y, q.w), second, 0.0),
YXZ => (scale * math::atan2(-q.z, q.w), second, 0.0),
YZX => (scale * math::atan2(q.x, q.w), second, 0.0),
XYZ => (scale * math::atan2(q.z, q.w), second, 0.0),
XZY => (scale * math::atan2(-q.y, q.w), second, 0.0),
};
}
let first = match self {
ZYX => math::atan2(
2.0 * (q.x * q.y + q.w * q.z),
q.w * q.w + q.x * q.x - q.y * q.y - q.z * q.z,
),
ZXY => math::atan2(
-2.0 * (q.x * q.y - q.w * q.z),
q.w * q.w - q.x * q.x + q.y * q.y - q.z * q.z,
),
YXZ => math::atan2(
2.0 * (q.x * q.z + q.w * q.y),
q.w * q.w - q.x * q.x - q.y * q.y + q.z * q.z,
),
YZX => math::atan2(
-2.0 * (q.x * q.z - q.w * q.y),
q.w * q.w + q.x * q.x - q.y * q.y - q.z * q.z,
),
XYZ => math::atan2(
-2.0 * (q.y * q.z - q.w * q.x),
q.w * q.w - q.x * q.x - q.y * q.y + q.z * q.z,
),
XZY => math::atan2(
2.0 * (q.y * q.z + q.w * q.x),
q.w * q.w - q.x * q.x + q.y * q.y - q.z * q.z,
),
};
let third = match self {
ZYX => math::atan2(
2.0 * (q.y * q.z + q.w * q.x),
q.w * q.w - q.x * q.x - q.y * q.y + q.z * q.z,
),
ZXY => math::atan2(
-2.0 * (q.x * q.z - q.w * q.y),
q.w * q.w - q.x * q.x - q.y * q.y + q.z * q.z,
),
YXZ => math::atan2(
2.0 * (q.x * q.y + q.w * q.z),
q.w * q.w - q.x * q.x + q.y * q.y - q.z * q.z,
),
YZX => math::atan2(
-2.0 * (q.y * q.z - q.w * q.x),
q.w * q.w - q.x * q.x + q.y * q.y - q.z * q.z,
),
XYZ => math::atan2(
-2.0 * (q.x * q.y - q.w * q.z),
q.w * q.w + q.x * q.x - q.y * q.y - q.z * q.z,
),
XZY => math::atan2(
2.0 * (q.x * q.z + q.w * q.y),
q.w * q.w + q.x * q.x - q.y * q.y - q.z * q.z,
),
};
(first, second, third)
}
}
// End - impl EulerFromQuaternion
};
}
macro_rules! impl_to_quat {
($t:ty, $quat:ident) => {
impl EulerToQuaternion<$t> for EulerRot {
type Output = $quat;
#[inline(always)]
fn new_quat(self, u: $t, v: $t, w: $t) -> $quat {
use EulerRot::*;
#[inline(always)]
fn rot_x(a: $t) -> $quat {
$quat::from_rotation_x(a)
}
#[inline(always)]
fn rot_y(a: $t) -> $quat {
$quat::from_rotation_y(a)
}
#[inline(always)]
fn rot_z(a: $t) -> $quat {
$quat::from_rotation_z(a)
}
match self {
ZYX => rot_z(u) * rot_y(v) * rot_x(w),
ZXY => rot_z(u) * rot_x(v) * rot_y(w),
YXZ => rot_y(u) * rot_x(v) * rot_z(w),
YZX => rot_y(u) * rot_z(v) * rot_x(w),
XYZ => rot_x(u) * rot_y(v) * rot_z(w),
XZY => rot_x(u) * rot_z(v) * rot_y(w),
}
.normalize()
}
}
// End - impl EulerToQuaternion
};
}
impl_from_quat!(f32, Quat);
impl_from_quat!(f64, DQuat);
impl_to_quat!(f32, Quat);
impl_to_quat!(f64, DQuat);