use crate::dense::error::OperationError;
use crate::physics::basic::vector::angular::{RotationHand, RotationSeq};
use crate::physics::basic::vector::angular::{TAG_X, TAG_Y, TAG_Z};
use crate::physics::basic::{Angular, AngularType, Coef, Vector3};
use crate::spatial_geometry::cos_matrix::CosMatrix;
use crate::spatial_geometry::quaternion::Quaternion;
pub struct EulerAngles {
pub yaw: Angular,
pub pitch: Angular,
pub roll: Angular,
}
impl EulerAngles {
pub fn new(yaw: Angular, pitch: Angular, roll: Angular) -> Self {
Self { yaw, pitch, roll }
}
pub fn from_array(arr: [f64; 3], angular_type: AngularType) -> Self {
let coef = Vector3::<Coef>::new(Coef::new(arr[0]), Coef::new(arr[1]), Coef::new(arr[2]));
let angulars = Vector3::<Angular>::from_vector_coef(coef, angular_type);
Self {
yaw: angulars.x,
pitch: angulars.y,
roll: angulars.z,
}
}
pub fn to_cos_matrix(
&self,
seq: RotationSeq,
hand: RotationHand,
) -> Result<CosMatrix, OperationError> {
let angulars = Vector3::<Angular>::new(self.roll, self.pitch, self.yaw);
angulars.to_cos_matrix(seq, hand)
}
pub fn to_quaternion(
&self,
seq: RotationSeq,
hand: RotationHand,
) -> Result<Quaternion, OperationError> {
let code = seq.value()?;
let tags = [(code >> 4) & 0x3, (code >> 2) & 0x3, code & 0x3];
let angles: Vec<f64> = tags.iter().map(|&tag| {
match tag {
TAG_X => self.roll.as_rad(), TAG_Y => self.pitch.as_rad(), TAG_Z => self.yaw.as_rad(), _ => 0.0,
}
}).collect();
let half_angles = [angles[0] * 0.5, angles[1] * 0.5, angles[2] * 0.5];
let (s1, c1) = half_angles[0].sin_cos();
let (s2, c2) = half_angles[1].sin_cos();
let (s3, c3) = half_angles[2].sin_cos();
let q1 = axis_quaternion(tags[0], c1, s1);
let q2 = axis_quaternion(tags[1], c2, s2);
let q3 = axis_quaternion(tags[2], c3, s3);
let mut quat = q3 * q2 * q1;
quat = quat.normalize();
if let RotationHand::Left = hand {
quat = quat.conjugate();
}
Ok(quat)
}
}
fn axis_quaternion(axis: u8, cos_half: f64, sin_half: f64) -> Quaternion {
match axis {
TAG_X => Quaternion::new(cos_half, sin_half, 0.0, 0.0), TAG_Y => Quaternion::new(cos_half, 0.0, sin_half, 0.0), TAG_Z => Quaternion::new(cos_half, 0.0, 0.0, sin_half), _ => Quaternion::new(1.0, 0.0, 0.0, 0.0), }
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
use std::f64::consts::PI;
use crate::physics::basic::vector::angular::{TAG_X, TAG_Y, TAG_Z};
#[test]
fn test_new() {
let yaw = Angular::from_rad(PI / 4.0);
let pitch = Angular::from_rad(PI / 6.0);
let roll = Angular::from_rad(PI / 3.0);
let euler = EulerAngles::new(yaw, pitch, roll);
assert_relative_eq!(euler.yaw.as_rad(), PI / 4.0);
assert_relative_eq!(euler.pitch.as_rad(), PI / 6.0);
assert_relative_eq!(euler.roll.as_rad(), PI / 3.0);
}
#[test]
fn test_from_array_radians() {
let arr = [PI / 4.0, PI / 6.0, PI / 3.0];
let euler = EulerAngles::from_array(arr, AngularType::Rad);
assert_relative_eq!(euler.yaw.as_rad(), PI / 4.0);
assert_relative_eq!(euler.pitch.as_rad(), PI / 6.0);
assert_relative_eq!(euler.roll.as_rad(), PI / 3.0);
}
#[test]
fn test_from_array_degrees() {
let arr = [90.0, 45.0, 30.0];
let euler = EulerAngles::from_array(arr, AngularType::Deg);
assert_relative_eq!(euler.yaw.as_rad(), PI / 2.0); assert_relative_eq!(euler.pitch.as_rad(), PI / 4.0); assert_relative_eq!(euler.roll.as_rad(), PI / 6.0); }
#[test]
fn test_zero_values() {
let arr = [0.0, 0.0, 0.0];
let euler_rad = EulerAngles::from_array(arr, AngularType::Rad);
assert_relative_eq!(euler_rad.yaw.as_rad(), 0.0);
assert_relative_eq!(euler_rad.pitch.as_rad(), 0.0);
assert_relative_eq!(euler_rad.roll.as_rad(), 0.0);
let euler_deg = EulerAngles::from_array(arr, AngularType::Deg);
assert_relative_eq!(euler_deg.yaw.as_deg(), 0.0);
assert_relative_eq!(euler_deg.pitch.as_deg(), 0.0);
assert_relative_eq!(euler_deg.roll.as_deg(), 0.0);
}
#[test]
fn test_to_cos_matrix_zero_angles() {
let euler = EulerAngles::from_array([0.0, 0.0, 0.0], AngularType::Rad);
let build = |axes: &[u8]| {
let mut s = RotationSeq::default();
for a in axes {
match a {
0 => {
s.x().unwrap();
}
1 => {
s.y().unwrap();
}
_ => {
s.z().unwrap();
}
}
}
s
};
let rotation_seqs = vec![
build(&[0, 1, 2]), build(&[2, 1, 0]), build(&[0, 2, 1]), build(&[1, 0, 2]), build(&[2, 0, 1]), build(&[1, 2, 0]), ];
let hands = [RotationHand::Right, RotationHand::Left];
for seq in rotation_seqs.iter() {
for hand in hands.iter() {
let cos_matrix = euler.to_cos_matrix(*seq, *hand).unwrap();
let identity = vec![1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0];
let arr = cos_matrix.to_array();
for r in 0..3 {
for c in 0..3 {
let idx = r * 3 + c;
assert_relative_eq!(arr[r][c], identity[idx], epsilon = 1e-10);
}
}
}
}
}
#[test]
fn test_to_cos_matrix_xyz_sequence() {
let euler = EulerAngles::from_array([PI / 2.0, 0.0, 0.0], AngularType::Rad);
let mut s = RotationSeq::default();
s.x().unwrap();
s.y().unwrap();
s.z().unwrap();
let cos_matrix = euler.to_cos_matrix(s, RotationHand::Right).unwrap();
let expected = vec![0.0, -1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0];
let arr = cos_matrix.to_array();
for r in 0..3 {
for c in 0..3 {
let idx = r * 3 + c;
assert_relative_eq!(arr[r][c], expected[idx], epsilon = 1e-10);
}
}
}
#[test]
fn test_to_cos_matrix_zxy_sequence() {
let euler = EulerAngles::from_array([0.0, PI / 2.0, 0.0], AngularType::Rad);
let mut s = RotationSeq::default();
s.z().unwrap();
s.x().unwrap();
s.y().unwrap();
let cos_matrix = euler.to_cos_matrix(s, RotationHand::Right).unwrap();
let expected = vec![1.0, 0.0, 0.0, 0.0, 0.0, -1.0, 0.0, 1.0, 0.0];
let arr = cos_matrix.to_array();
for r in 0..3 {
for c in 0..3 {
let idx = r * 3 + c;
assert_relative_eq!(arr[r][c], expected[idx], epsilon = 1e-10);
}
}
}
#[test]
fn test_to_cos_matrix_handedness() {
let euler = EulerAngles::from_array([PI / 4.0, PI / 4.0, PI / 4.0], AngularType::Rad);
let mut s = RotationSeq::default();
s.x().unwrap();
s.y().unwrap();
s.z().unwrap();
let cos_matrix_right = euler.to_cos_matrix(s, RotationHand::Right).unwrap();
let mut s2 = RotationSeq::default();
s2.x().unwrap();
s2.y().unwrap();
s2.z().unwrap();
let cos_matrix_left = euler.to_cos_matrix(s2, RotationHand::Left).unwrap();
let mut all_equal = true;
let arr_r = cos_matrix_right.to_array();
let arr_l = cos_matrix_left.to_array();
for r in 0..3 {
for c in 0..3 {
if (arr_r[r][c] - arr_l[r][c]).abs() >= 1e-10 {
all_equal = false;
break;
}
}
}
assert!(!all_equal, "右手系和左手系的方向余弦矩阵应该不同");
}
#[test]
fn test_euler_to_quaternion_basic_cases() {
let euler_zero = EulerAngles::new(
Angular::from_rad(0.0),
Angular::from_rad(0.0),
Angular::from_rad(0.0),
);
let mut seq_zyx = RotationSeq::default();
seq_zyx.z().unwrap(); seq_zyx.y().unwrap(); seq_zyx.x().unwrap();
let q_zero = euler_zero.to_quaternion(seq_zyx, RotationHand::Right).unwrap();
assert_relative_eq!(q_zero.q0, 1.0, epsilon = 1e-10);
assert_relative_eq!(q_zero.q1, 0.0, epsilon = 1e-10);
assert_relative_eq!(q_zero.q2, 0.0, epsilon = 1e-10);
assert_relative_eq!(q_zero.q3, 0.0, epsilon = 1e-10);
let euler_x = EulerAngles::new(
Angular::from_rad(0.0),
Angular::from_rad(0.0),
Angular::from_rad(PI / 2.0), );
let q_x = euler_x.to_quaternion(seq_zyx, RotationHand::Right).unwrap();
let exp = Quaternion::new(0.7071067811865476,0.7071067811865475,0.0,0.0);
assert_relative_eq!(q_x.q0, exp.q0, epsilon = 1e-10);
assert_relative_eq!(q_x.q1, exp.q1, epsilon = 1e-10);
assert_relative_eq!(q_x.q2, exp.q2, epsilon = 1e-10);
assert_relative_eq!(q_x.q3, exp.q3, epsilon = 1e-10);
}
#[test]
fn test_euler_to_quaternion_zyx_sequence() {
let euler = EulerAngles::new(
Angular::from_deg(30.0), Angular::from_deg(15.0), Angular::from_deg(10.0), );
let mut seq_zyx = RotationSeq::default();
seq_zyx.z().unwrap(); seq_zyx.y().unwrap(); seq_zyx.x().unwrap();
let q = euler.to_quaternion(seq_zyx, RotationHand::Right).unwrap();
assert_relative_eq!(q.norm(), 1.0, epsilon = 1e-10);
let exp = Quaternion::new(0.951081535, 0.117117116, 0.103245633, 0.266585240);
assert_relative_eq!(q.q0, exp.q0, epsilon = 1e-4);
assert_relative_eq!(q.q1, exp.q1, epsilon = 1e-4);
assert_relative_eq!(q.q2, exp.q2, epsilon = 1e-4);
assert_relative_eq!(q.q3, exp.q3, epsilon = 1e-4);
}
#[test]
fn test_euler_to_quaternion_xyz_sequence() {
let euler = EulerAngles::new(
Angular::from_deg(30.0), Angular::from_deg(15.0), Angular::from_deg(10.0), );
let mut seq_xyz = RotationSeq::default();
seq_xyz.x().unwrap(); seq_xyz.y().unwrap(); seq_xyz.z().unwrap();
let q = euler.to_quaternion(seq_xyz, RotationHand::Right).unwrap();
assert_relative_eq!(q.norm(), 1.0, epsilon = 1e-10);
let exp = Quaternion::new(0.956979520, 0.049793014, 0.147843925, 0.244377402);
assert_relative_eq!(q.q0, exp.q0, epsilon = 1e-4);
assert_relative_eq!(q.q1, exp.q1, epsilon = 1e-4);
assert_relative_eq!(q.q2, exp.q2, epsilon = 1e-3);
assert_relative_eq!(q.q3, exp.q3, epsilon = 1e-3);
}
#[test]
fn test_euler_to_quaternion_handedness() {
let euler = EulerAngles::new(
Angular::from_deg(45.0),
Angular::from_deg(30.0),
Angular::from_deg(15.0),
);
let mut seq_zyx = RotationSeq::default();
seq_zyx.z().unwrap(); seq_zyx.y().unwrap(); seq_zyx.x().unwrap();
let q_right = euler.to_quaternion(seq_zyx, RotationHand::Right).unwrap();
let q_left = euler.to_quaternion(seq_zyx, RotationHand::Left).unwrap();
assert_relative_eq!(q_left.q0, q_right.q0, epsilon = 1e-10);
assert_relative_eq!(q_left.q1, -q_right.q1, epsilon = 1e-10);
assert_relative_eq!(q_left.q2, -q_right.q2, epsilon = 1e-10);
assert_relative_eq!(q_left.q3, -q_right.q3, epsilon = 1e-10);
}
#[test]
fn test_euler_to_quaternion_sequence_consistency() {
let euler = EulerAngles::new(
Angular::from_deg(20.0),
Angular::from_deg(40.0),
Angular::from_deg(60.0),
);
let sequences = [
(TAG_X, TAG_Y, TAG_Z), (TAG_Z, TAG_Y, TAG_X), (TAG_Z, TAG_X, TAG_Z), ];
for (i, j, k) in sequences {
let mut seq = RotationSeq::default();
match i { TAG_X => seq.x(), TAG_Y => seq.y(), _ => seq.z() }.unwrap();
match j { TAG_X => seq.x(), TAG_Y => seq.y(), _ => seq.z() }.unwrap();
match k { TAG_X => seq.x(), TAG_Y => seq.y(), _ => seq.z() }.unwrap();
let q = euler.to_quaternion(seq, RotationHand::Right).unwrap();
assert_relative_eq!(q.norm(), 1.0, epsilon = 1e-10);
let norm_sq = q.q0 * q.q0 + q.q1 * q.q1 + q.q2 * q.q2 + q.q3 * q.q3;
assert_relative_eq!(norm_sq, 1.0, epsilon = 1e-10);
}
}
#[test]
fn test_euler_to_quaternion_extreme_angles() {
let test_cases = [
(180.0, 90.0, 45.0),
(-30.0, -60.0, -90.0),
(400.0, 500.0, 600.0),
(0.001, 0.002, 0.003),
];
let mut seq_zyx = RotationSeq::default();
seq_zyx.z().unwrap(); seq_zyx.y().unwrap(); seq_zyx.x().unwrap();
for (yaw_deg, pitch_deg, roll_deg) in test_cases {
let euler = EulerAngles::new(
Angular::from_deg(yaw_deg),
Angular::from_deg(pitch_deg),
Angular::from_deg(roll_deg),
);
let q = euler.to_quaternion(seq_zyx, RotationHand::Right).unwrap();
assert_relative_eq!(q.norm(), 1.0, epsilon = 1e-8);
}
}
#[test]
fn test_euler_to_quaternion_gimbal_lock() {
let euler_gimbal = EulerAngles::new(
Angular::from_deg(30.0),
Angular::from_deg(90.0), Angular::from_deg(20.0),
);
let mut seq_zyx = RotationSeq::default();
seq_zyx.z().unwrap(); seq_zyx.y().unwrap(); seq_zyx.x().unwrap();
let q = euler_gimbal.to_quaternion(seq_zyx, RotationHand::Right).unwrap();
assert_relative_eq!(q.norm(), 1.0, epsilon = 1e-10);
}
#[test]
fn test_euler_to_quaternion_back_and_forth() {
let original = EulerAngles::new(
Angular::from_deg(25.0),
Angular::from_deg(35.0),
Angular::from_deg(45.0),
);
let mut seq_zyx = RotationSeq::default();
seq_zyx.z().unwrap(); seq_zyx.y().unwrap(); seq_zyx.x().unwrap();
let q = original.to_quaternion(seq_zyx, RotationHand::Right).unwrap();
assert_relative_eq!(q.norm(), 1.0, epsilon = 1e-10);
}
#[test]
fn test_simple_case() {
let euler = EulerAngles::new(
Angular::from_deg(0.0), Angular::from_deg(0.0), Angular::from_deg(90.0), );
let mut seq_xyz = RotationSeq::default();
seq_xyz.x().unwrap(); seq_xyz.y().unwrap(); seq_xyz.z().unwrap();
let q = euler.to_quaternion(seq_xyz, RotationHand::Right).unwrap();
let expected = Quaternion::new(0.7071067811865476, 0.7071067811865476, 0.0, 0.0);
assert_relative_eq!(q.q0, expected.q0, epsilon = 1e-10);
assert_relative_eq!(q.q1, expected.q1, epsilon = 1e-10);
assert_relative_eq!(q.q2, expected.q2, epsilon = 1e-10);
assert_relative_eq!(q.q3, expected.q3, epsilon = 1e-10);
}
#[test]
fn test_axis_quaternion_fallback_branch() {
let invalid_axes: [u8; 3] = [0x0, 0x4, 0xFF];
for axis in invalid_axes.iter() {
let q = super::axis_quaternion(*axis, 0.12345, 0.67890);
let expected = Quaternion::new(1.0, 0.0, 0.0, 0.0);
assert_relative_eq!(q.q0, expected.q0, epsilon = 1e-12);
assert_relative_eq!(q.q1, expected.q1, epsilon = 1e-12);
assert_relative_eq!(q.q2, expected.q2, epsilon = 1e-12);
assert_relative_eq!(q.q3, expected.q3, epsilon = 1e-12);
assert_relative_eq!(q.norm(), 1.0, epsilon = 1e-12);
}
}
#[test]
fn test_to_quaternion_angle_mapping_fallback_branch() {
let code: u8 = (TAG_Z << 4) | (0 << 2) | TAG_Y; let seq: RotationSeq = unsafe { std::mem::transmute::<(u8, u8), RotationSeq>((code, 3u8)) };
let euler = EulerAngles::new(
Angular::from_rad(0.3), Angular::from_rad(-0.5), Angular::from_rad(0.7), );
let q = euler.to_quaternion(seq, RotationHand::Right).unwrap();
assert_relative_eq!(q.norm(), 1.0, epsilon = 1e-12);
let tags = [ (code >> 4) & 0x3, (code >> 2) & 0x3, code & 0x3 ];
let angles = [
if tags[0] == TAG_X { euler.roll.as_rad() } else if tags[0] == TAG_Y { euler.pitch.as_rad() } else { euler.yaw.as_rad() },
if tags[1] == TAG_X { euler.roll.as_rad() } else if tags[1] == TAG_Y { euler.pitch.as_rad() } else if tags[1] == TAG_Z { euler.yaw.as_rad() } else { 0.0 },
if tags[2] == TAG_X { euler.roll.as_rad() } else if tags[2] == TAG_Y { euler.pitch.as_rad() } else { euler.yaw.as_rad() },
];
let half = [angles[0]*0.5, angles[1]*0.5, angles[2]*0.5];
let (s1,c1) = half[0].sin_cos();
let (s2,c2) = half[1].sin_cos();
let (s3,c3) = half[2].sin_cos();
let q1 = super::axis_quaternion(tags[0], c1, s1);
let q2 = super::axis_quaternion(tags[1], c2, s2); let q3 = super::axis_quaternion(tags[2], c3, s3);
let exp = (q3 * q2 * q1).normalize();
assert_relative_eq!(q.q0, exp.q0, epsilon = 1e-12);
assert_relative_eq!(q.q1, exp.q1, epsilon = 1e-12);
assert_relative_eq!(q.q2, exp.q2, epsilon = 1e-12);
assert_relative_eq!(q.q3, exp.q3, epsilon = 1e-12);
}
}