use crate::common::Angle;
use super::{UnitQuaternion, Vector3};
#[derive(Debug, Clone, Copy)]
pub enum EulerAngle {
XYZ(Angle, Angle, Angle),
XZY(Angle, Angle, Angle),
YXZ(Angle, Angle, Angle),
YZX(Angle, Angle, Angle),
ZXY(Angle, Angle, Angle),
ZYX(Angle, Angle, Angle),
XYX(Angle, Angle, Angle),
XZX(Angle, Angle, Angle),
YXY(Angle, Angle, Angle),
YZY(Angle, Angle, Angle),
ZXZ(Angle, Angle, Angle),
ZYZ(Angle, Angle, Angle),
}
impl EulerAngle {
#[must_use]
pub const fn identity() -> Self {
Self::XYZ(Angle::ZERO, Angle::ZERO, Angle::ZERO)
}
}
impl From<EulerAngle> for UnitQuaternion {
fn from(angle: EulerAngle) -> Self {
match angle {
EulerAngle::XYZ(first, second, third) => {
UnitQuaternion::from_axis_angle(&Vector3::x_axis(), first.radian())
* UnitQuaternion::from_axis_angle(&Vector3::y_axis(), second.radian())
* UnitQuaternion::from_axis_angle(&Vector3::z_axis(), third.radian())
}
EulerAngle::XZY(first, second, third) => {
UnitQuaternion::from_axis_angle(&Vector3::x_axis(), first.radian())
* UnitQuaternion::from_axis_angle(&Vector3::z_axis(), second.radian())
* UnitQuaternion::from_axis_angle(&Vector3::y_axis(), third.radian())
}
EulerAngle::YXZ(first, second, third) => {
UnitQuaternion::from_axis_angle(&Vector3::y_axis(), first.radian())
* UnitQuaternion::from_axis_angle(&Vector3::x_axis(), second.radian())
* UnitQuaternion::from_axis_angle(&Vector3::z_axis(), third.radian())
}
EulerAngle::YZX(first, second, third) => {
UnitQuaternion::from_axis_angle(&Vector3::y_axis(), first.radian())
* UnitQuaternion::from_axis_angle(&Vector3::z_axis(), second.radian())
* UnitQuaternion::from_axis_angle(&Vector3::x_axis(), third.radian())
}
EulerAngle::ZXY(first, second, third) => {
UnitQuaternion::from_axis_angle(&Vector3::z_axis(), first.radian())
* UnitQuaternion::from_axis_angle(&Vector3::x_axis(), second.radian())
* UnitQuaternion::from_axis_angle(&Vector3::y_axis(), third.radian())
}
EulerAngle::ZYX(first, second, third) => {
UnitQuaternion::from_axis_angle(&Vector3::z_axis(), first.radian())
* UnitQuaternion::from_axis_angle(&Vector3::y_axis(), second.radian())
* UnitQuaternion::from_axis_angle(&Vector3::x_axis(), third.radian())
}
EulerAngle::XYX(first, second, third) => {
UnitQuaternion::from_axis_angle(&Vector3::x_axis(), first.radian())
* UnitQuaternion::from_axis_angle(&Vector3::y_axis(), second.radian())
* UnitQuaternion::from_axis_angle(&Vector3::x_axis(), third.radian())
}
EulerAngle::XZX(first, second, third) => {
UnitQuaternion::from_axis_angle(&Vector3::x_axis(), first.radian())
* UnitQuaternion::from_axis_angle(&Vector3::z_axis(), second.radian())
* UnitQuaternion::from_axis_angle(&Vector3::x_axis(), third.radian())
}
EulerAngle::YXY(first, second, third) => {
UnitQuaternion::from_axis_angle(&Vector3::y_axis(), first.radian())
* UnitQuaternion::from_axis_angle(&Vector3::x_axis(), second.radian())
* UnitQuaternion::from_axis_angle(&Vector3::y_axis(), third.radian())
}
EulerAngle::YZY(first, second, third) => {
UnitQuaternion::from_axis_angle(&Vector3::y_axis(), first.radian())
* UnitQuaternion::from_axis_angle(&Vector3::z_axis(), second.radian())
* UnitQuaternion::from_axis_angle(&Vector3::y_axis(), third.radian())
}
EulerAngle::ZXZ(first, second, third) => {
UnitQuaternion::from_axis_angle(&Vector3::z_axis(), first.radian())
* UnitQuaternion::from_axis_angle(&Vector3::x_axis(), second.radian())
* UnitQuaternion::from_axis_angle(&Vector3::z_axis(), third.radian())
}
EulerAngle::ZYZ(first, second, third) => {
UnitQuaternion::from_axis_angle(&Vector3::z_axis(), first.radian())
* UnitQuaternion::from_axis_angle(&Vector3::y_axis(), second.radian())
* UnitQuaternion::from_axis_angle(&Vector3::z_axis(), third.radian())
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::common::{PI, deg, rad};
macro_rules! assert_approx_eq_quat {
($a:expr, $b:expr) => {
approx::assert_abs_diff_eq!($a.w, $b.w, epsilon = 1e-3);
approx::assert_abs_diff_eq!($a.i, $b.i, epsilon = 1e-3);
approx::assert_abs_diff_eq!($a.j, $b.j, epsilon = 1e-3);
approx::assert_abs_diff_eq!($a.k, $b.k, epsilon = 1e-3);
};
}
#[rstest::rstest]
#[case(0., 0. * deg)]
#[case(PI / 2., 90. * deg)]
#[case(0., 0. * rad)]
#[case(PI / 2., PI / 2. * rad)]
fn to_radians(#[case] expected: f32, #[case] angle: Angle) {
approx::assert_abs_diff_eq!(expected, angle.radian());
}
#[rstest::rstest]
#[case(UnitQuaternion::from_axis_angle(&Vector3::x_axis(), PI / 2.), EulerAngle::XYZ(90. * deg, 0. * deg, 0. * deg))]
#[case(UnitQuaternion::from_axis_angle(&Vector3::y_axis(), PI / 2.), EulerAngle::XYZ(0. * deg, 90. * deg, 0. * deg))]
#[case(UnitQuaternion::from_axis_angle(&Vector3::z_axis(), PI / 2.), EulerAngle::XYZ(0. * deg, 0. * deg, 90. * deg))]
#[case(UnitQuaternion::from_axis_angle(&Vector3::y_axis(), PI / 2.) * UnitQuaternion::from_axis_angle(&Vector3::z_axis(), PI / 2.), EulerAngle::XYZ(0. * deg, 90. * deg, 90. * deg))]
#[case(UnitQuaternion::from_axis_angle(&Vector3::x_axis(), PI / 2.) * UnitQuaternion::from_axis_angle(&Vector3::y_axis(), PI / 2.), EulerAngle::XYZ(90. * deg, 90. * deg, 0. * deg))]
fn xyz(#[case] expected: UnitQuaternion, #[case] angle: EulerAngle) {
let angle: UnitQuaternion = angle.into();
assert_approx_eq_quat!(expected, angle);
}
#[rstest::rstest]
#[case(UnitQuaternion::from_axis_angle(&Vector3::x_axis(), PI / 2.), EulerAngle::XZY(90. * deg, 0. * deg, 0. * deg))]
#[case(UnitQuaternion::from_axis_angle(&Vector3::z_axis(), PI / 2.), EulerAngle::XZY(0. * deg, 90. * deg, 0. * deg))]
#[case(UnitQuaternion::from_axis_angle(&Vector3::y_axis(), PI / 2.), EulerAngle::XZY(0. * deg, 0. * deg, 90. * deg))]
#[case(UnitQuaternion::from_axis_angle(&Vector3::z_axis(), PI / 2.) * UnitQuaternion::from_axis_angle(&Vector3::y_axis(), PI / 2.), EulerAngle::XZY(0. * deg, 90. * deg, 90. * deg))]
#[case(UnitQuaternion::from_axis_angle(&Vector3::x_axis(), PI / 2.) * UnitQuaternion::from_axis_angle(&Vector3::z_axis(), PI / 2.), EulerAngle::XZY(90. * deg, 90. * deg, 0. * deg))]
fn xzy(#[case] expected: UnitQuaternion, #[case] angle: EulerAngle) {
let angle: UnitQuaternion = angle.into();
assert_approx_eq_quat!(expected, angle);
}
#[rstest::rstest]
#[case(UnitQuaternion::from_axis_angle(&Vector3::y_axis(), PI / 2.), EulerAngle::YXZ(90. * deg, 0. * deg, 0. * deg))]
#[case(UnitQuaternion::from_axis_angle(&Vector3::x_axis(), PI / 2.), EulerAngle::YXZ(0. * deg, 90. * deg, 0. * deg))]
#[case(UnitQuaternion::from_axis_angle(&Vector3::z_axis(), PI / 2.), EulerAngle::YXZ(0. * deg, 0. * deg, 90. * deg))]
#[case(UnitQuaternion::from_axis_angle(&Vector3::x_axis(), PI / 2.) * UnitQuaternion::from_axis_angle(&Vector3::z_axis(), PI / 2.), EulerAngle::YXZ(0. * deg, 90. * deg, 90. * deg))]
#[case(UnitQuaternion::from_axis_angle(&Vector3::y_axis(), PI / 2.) * UnitQuaternion::from_axis_angle(&Vector3::x_axis(), PI / 2.), EulerAngle::YXZ(90. * deg, 90. * deg, 0. * deg))]
fn yxz(#[case] expected: UnitQuaternion, #[case] angle: EulerAngle) {
let angle: UnitQuaternion = angle.into();
assert_approx_eq_quat!(expected, angle);
}
#[rstest::rstest]
#[case(UnitQuaternion::from_axis_angle(&Vector3::y_axis(), PI / 2.), EulerAngle::YZX(90. * deg, 0. * deg, 0. * deg))]
#[case(UnitQuaternion::from_axis_angle(&Vector3::z_axis(), PI / 2.), EulerAngle::YZX(0. * deg, 90. * deg, 0. * deg))]
#[case(UnitQuaternion::from_axis_angle(&Vector3::x_axis(), PI / 2.), EulerAngle::YZX(0. * deg, 0. * deg, 90. * deg))]
#[case(UnitQuaternion::from_axis_angle(&Vector3::z_axis(), PI / 2.) * UnitQuaternion::from_axis_angle(&Vector3::x_axis(), PI / 2.), EulerAngle::YZX(0. * deg, 90. * deg, 90. * deg))]
#[case(UnitQuaternion::from_axis_angle(&Vector3::y_axis(), PI / 2.) * UnitQuaternion::from_axis_angle(&Vector3::z_axis(), PI / 2.), EulerAngle::YZX(90. * deg, 90. * deg, 0. * deg))]
fn yzx(#[case] expected: UnitQuaternion, #[case] angle: EulerAngle) {
let angle: UnitQuaternion = angle.into();
assert_approx_eq_quat!(expected, angle);
}
#[rstest::rstest]
#[case(UnitQuaternion::from_axis_angle(&Vector3::z_axis(), PI / 2.), EulerAngle::ZXY(90. * deg, 0. * deg, 0. * deg))]
#[case(UnitQuaternion::from_axis_angle(&Vector3::x_axis(), PI / 2.), EulerAngle::ZXY(0. * deg, 90. * deg, 0. * deg))]
#[case(UnitQuaternion::from_axis_angle(&Vector3::y_axis(), PI / 2.), EulerAngle::ZXY(0. * deg, 0. * deg, 90. * deg))]
#[case(UnitQuaternion::from_axis_angle(&Vector3::x_axis(), PI / 2.) * UnitQuaternion::from_axis_angle(&Vector3::y_axis(), PI / 2.), EulerAngle::ZXY(0. * deg, 90. * deg, 90. * deg))]
#[case(UnitQuaternion::from_axis_angle(&Vector3::z_axis(), PI / 2.) * UnitQuaternion::from_axis_angle(&Vector3::x_axis(), PI / 2.), EulerAngle::ZXY(90. * deg, 90. * deg, 0. * deg))]
fn zxy(#[case] expected: UnitQuaternion, #[case] angle: EulerAngle) {
let angle: UnitQuaternion = angle.into();
assert_approx_eq_quat!(expected, angle);
}
#[rstest::rstest]
#[case(UnitQuaternion::from_axis_angle(&Vector3::z_axis(), PI / 2.), EulerAngle::ZYX(90. * deg, 0. * deg, 0. * deg))]
#[case(UnitQuaternion::from_axis_angle(&Vector3::y_axis(), PI / 2.), EulerAngle::ZYX(0. * deg, 90. * deg, 0. * deg))]
#[case(UnitQuaternion::from_axis_angle(&Vector3::x_axis(), PI / 2.), EulerAngle::ZYX(0. * deg, 0. * deg, 90. * deg))]
#[case(UnitQuaternion::from_axis_angle(&Vector3::y_axis(), PI / 2.) * UnitQuaternion::from_axis_angle(&Vector3::x_axis(), PI / 2.), EulerAngle::ZYX(0. * deg, 90. * deg, 90. * deg))]
#[case(UnitQuaternion::from_axis_angle(&Vector3::z_axis(), PI / 2.) * UnitQuaternion::from_axis_angle(&Vector3::y_axis(), PI / 2.), EulerAngle::ZYX(90. * deg, 90. * deg, 0. * deg))]
fn zyx(#[case] expected: UnitQuaternion, #[case] angle: EulerAngle) {
let angle: UnitQuaternion = angle.into();
assert_approx_eq_quat!(expected, angle);
}
#[rstest::rstest]
#[case(UnitQuaternion::from_axis_angle(&Vector3::x_axis(), PI / 2.), EulerAngle::XYX(90. * deg, 0. * deg, 0. * deg))]
#[case(UnitQuaternion::from_axis_angle(&Vector3::y_axis(), PI / 2.), EulerAngle::XYX(0. * deg, 90. * deg, 0. * deg))]
#[case(UnitQuaternion::from_axis_angle(&Vector3::x_axis(), PI / 2.), EulerAngle::XYX(0. * deg, 0. * deg, 90. * deg))]
#[case(UnitQuaternion::from_axis_angle(&Vector3::y_axis(), PI / 2.) * UnitQuaternion::from_axis_angle(&Vector3::x_axis(), PI / 2.), EulerAngle::XYX(0. * deg, 90. * deg, 90. * deg))]
#[case(UnitQuaternion::from_axis_angle(&Vector3::x_axis(), PI / 2.) * UnitQuaternion::from_axis_angle(&Vector3::y_axis(), PI / 2.), EulerAngle::XYX(90. * deg, 90. * deg, 0. * deg))]
fn xyx(#[case] expected: UnitQuaternion, #[case] angle: EulerAngle) {
let angle: UnitQuaternion = angle.into();
assert_approx_eq_quat!(expected, angle);
}
#[rstest::rstest]
#[case(UnitQuaternion::from_axis_angle(&Vector3::x_axis(), PI / 2.), EulerAngle::XZX(90. * deg, 0. * deg, 0. * deg))]
#[case(UnitQuaternion::from_axis_angle(&Vector3::z_axis(), PI / 2.), EulerAngle::XZX(0. * deg, 90. * deg, 0. * deg))]
#[case(UnitQuaternion::from_axis_angle(&Vector3::x_axis(), PI / 2.), EulerAngle::XZX(0. * deg, 0. * deg, 90. * deg))]
#[case(UnitQuaternion::from_axis_angle(&Vector3::z_axis(), PI / 2.) * UnitQuaternion::from_axis_angle(&Vector3::x_axis(), PI / 2.), EulerAngle::XZX(0. * deg, 90. * deg, 90. * deg))]
#[case(UnitQuaternion::from_axis_angle(&Vector3::x_axis(), PI / 2.) * UnitQuaternion::from_axis_angle(&Vector3::z_axis(), PI / 2.), EulerAngle::XZX(90. * deg, 90. * deg, 0. * deg))]
fn xzx(#[case] expected: UnitQuaternion, #[case] angle: EulerAngle) {
let angle: UnitQuaternion = angle.into();
assert_approx_eq_quat!(expected, angle);
}
#[rstest::rstest]
#[case(UnitQuaternion::from_axis_angle(&Vector3::y_axis(), PI / 2.), EulerAngle::YXY(90. * deg, 0. * deg, 0. * deg))]
#[case(UnitQuaternion::from_axis_angle(&Vector3::x_axis(), PI / 2.), EulerAngle::YXY(0. * deg, 90. * deg, 0. * deg))]
#[case(UnitQuaternion::from_axis_angle(&Vector3::y_axis(), PI / 2.), EulerAngle::YXY(0. * deg, 0. * deg, 90. * deg))]
#[case(UnitQuaternion::from_axis_angle(&Vector3::x_axis(), PI / 2.) * UnitQuaternion::from_axis_angle(&Vector3::y_axis(), PI / 2.), EulerAngle::YXY(0. * deg, 90. * deg, 90. * deg))]
#[case(UnitQuaternion::from_axis_angle(&Vector3::y_axis(), PI / 2.) * UnitQuaternion::from_axis_angle(&Vector3::x_axis(), PI / 2.), EulerAngle::YXY(90. * deg, 90. * deg, 0. * deg))]
fn yxy(#[case] expected: UnitQuaternion, #[case] angle: EulerAngle) {
let angle: UnitQuaternion = angle.into();
assert_approx_eq_quat!(expected, angle);
}
#[rstest::rstest]
#[case(UnitQuaternion::from_axis_angle(&Vector3::y_axis(), PI / 2.), EulerAngle::YZY(90. * deg, 0. * deg, 0. * deg))]
#[case(UnitQuaternion::from_axis_angle(&Vector3::z_axis(), PI / 2.), EulerAngle::YZY(0. * deg, 90. * deg, 0. * deg))]
#[case(UnitQuaternion::from_axis_angle(&Vector3::y_axis(), PI / 2.), EulerAngle::YZY(0. * deg, 0. * deg, 90. * deg))]
#[case(UnitQuaternion::from_axis_angle(&Vector3::z_axis(), PI / 2.) * UnitQuaternion::from_axis_angle(&Vector3::y_axis(), PI / 2.), EulerAngle::YZY(0. * deg, 90. * deg, 90. * deg))]
#[case(UnitQuaternion::from_axis_angle(&Vector3::y_axis(), PI / 2.) * UnitQuaternion::from_axis_angle(&Vector3::z_axis(), PI / 2.), EulerAngle::YZY(90. * deg, 90. * deg, 0. * deg))]
fn yzy(#[case] expected: UnitQuaternion, #[case] angle: EulerAngle) {
let angle: UnitQuaternion = angle.into();
assert_approx_eq_quat!(expected, angle);
}
#[rstest::rstest]
#[case(UnitQuaternion::from_axis_angle(&Vector3::z_axis(), PI / 2.), EulerAngle::ZXZ(90. * deg, 0. * deg, 0. * deg))]
#[case(UnitQuaternion::from_axis_angle(&Vector3::x_axis(), PI / 2.), EulerAngle::ZXZ(0. * deg, 90. * deg, 0. * deg))]
#[case(UnitQuaternion::from_axis_angle(&Vector3::z_axis(), PI / 2.), EulerAngle::ZXZ(0. * deg, 0. * deg, 90. * deg))]
#[case(UnitQuaternion::from_axis_angle(&Vector3::x_axis(), PI / 2.) * UnitQuaternion::from_axis_angle(&Vector3::z_axis(), PI / 2.), EulerAngle::ZXZ(0. * deg, 90. * deg, 90. * deg))]
#[case(UnitQuaternion::from_axis_angle(&Vector3::z_axis(), PI / 2.) * UnitQuaternion::from_axis_angle(&Vector3::x_axis(), PI / 2.), EulerAngle::ZXZ(90. * deg, 90. * deg, 0. * deg))]
fn zxz(#[case] expected: UnitQuaternion, #[case] angle: EulerAngle) {
let angle: UnitQuaternion = angle.into();
assert_approx_eq_quat!(expected, angle);
}
#[rstest::rstest]
#[case(UnitQuaternion::from_axis_angle(&Vector3::z_axis(), PI / 2.), EulerAngle::ZYZ(90. * deg, 0. * deg, 0. * deg))]
#[case(UnitQuaternion::from_axis_angle(&Vector3::y_axis(), PI / 2.), EulerAngle::ZYZ(0. * deg, 90. * deg, 0. * deg))]
#[case(UnitQuaternion::from_axis_angle(&Vector3::z_axis(), PI / 2.), EulerAngle::ZYZ(0. * deg, 0. * deg, 90. * deg))]
#[case(UnitQuaternion::from_axis_angle(&Vector3::y_axis(), PI / 2.) * UnitQuaternion::from_axis_angle(&Vector3::z_axis(), PI / 2.), EulerAngle::ZYZ(0. * deg, 90. * deg, 90. * deg))]
#[case(UnitQuaternion::from_axis_angle(&Vector3::z_axis(), PI / 2.) * UnitQuaternion::from_axis_angle(&Vector3::y_axis(), PI / 2.), EulerAngle::ZYZ(90. * deg, 90. * deg, 0. * deg))]
fn zyz(#[case] expected: UnitQuaternion, #[case] angle: EulerAngle) {
let angle: UnitQuaternion = angle.into();
assert_approx_eq_quat!(expected, angle);
}
#[test]
fn identity() {
let angle: UnitQuaternion = EulerAngle::identity().into();
assert_eq!(UnitQuaternion::identity(), angle);
}
}