use nalgebra::Vector3;
use crate::math::SMatrix3;
use std::{fmt, ops};
use crate::FromAttitude;
use crate::attitude::ToAttitude;
use crate::attitude::attitude_types::{
ATTITUDE_EPSILON, EulerAngle, EulerAngleOrder, EulerAxis, Quaternion, RotationMatrix,
};
use crate::constants::{AngleFormat, DEG2RAD, RADIANS};
use crate::utils::BraheError;
fn is_so3_or_error(matrix: &SMatrix3, tol: f64) -> Result<(), BraheError> {
let det = matrix.determinant();
let is_orthogonal = matrix.is_orthogonal(tol);
let is_square = matrix.is_square();
if is_square && is_orthogonal && det > 0.0 {
Ok(())
} else {
Err(BraheError::Error(
format!(
"Matrix is not a proper rotation matrix. Determinant: {}, Orthogonal: {}",
det, is_orthogonal
)
.to_string(),
))
}
}
impl RotationMatrix {
#[allow(clippy::too_many_arguments)]
pub fn new(
r11: f64,
r12: f64,
r13: f64,
r21: f64,
r22: f64,
r23: f64,
r31: f64,
r32: f64,
r33: f64,
) -> Result<Self, BraheError> {
let data = SMatrix3::new(r11, r12, r13, r21, r22, r23, r31, r32, r33);
is_so3_or_error(&data, 1e-7)?;
Ok(Self { data })
}
pub fn from_matrix(matrix: SMatrix3) -> Result<Self, BraheError> {
let data = matrix;
is_so3_or_error(&data, 1e-7)?;
Ok(Self { data })
}
pub fn to_matrix(&self) -> SMatrix3 {
self.data
}
#[allow(non_snake_case)]
pub fn Rx(angle: f64, angle_format: AngleFormat) -> Self {
let angle = match angle_format {
AngleFormat::Degrees => angle * DEG2RAD,
AngleFormat::Radians => angle,
};
let data = SMatrix3::new(
1.0,
0.0,
0.0,
0.0,
angle.cos(),
angle.sin(),
0.0,
-angle.sin(),
angle.cos(),
);
Self { data }
}
#[allow(non_snake_case)]
pub fn Ry(angle: f64, angle_format: AngleFormat) -> Self {
let angle = match angle_format {
AngleFormat::Degrees => angle * DEG2RAD,
AngleFormat::Radians => angle,
};
let data = SMatrix3::new(
angle.cos(),
0.0,
-angle.sin(),
0.0,
1.0,
0.0,
angle.sin(),
0.0,
angle.cos(),
);
Self { data }
}
#[allow(non_snake_case)]
pub fn Rz(angle: f64, angle_format: AngleFormat) -> Self {
let angle = match angle_format {
AngleFormat::Degrees => angle * DEG2RAD,
AngleFormat::Radians => angle,
};
let data = SMatrix3::new(
angle.cos(),
angle.sin(),
0.0,
-angle.sin(),
angle.cos(),
0.0,
0.0,
0.0,
1.0,
);
Self { data }
}
}
impl fmt::Display for RotationMatrix {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "RotationMatrix: \n{}", self.data)
}
}
impl fmt::Debug for RotationMatrix {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "RotationMatrix: \n{:?}", self.data)
}
}
impl PartialEq for RotationMatrix {
fn eq(&self, other: &Self) -> bool {
(self.data[(0, 0)] - other.data[(0, 0)]).abs() <= ATTITUDE_EPSILON
&& (self.data[(0, 1)] - other.data[(0, 1)]).abs() <= ATTITUDE_EPSILON
&& (self.data[(0, 2)] - other.data[(0, 2)]).abs() <= ATTITUDE_EPSILON
&& (self.data[(1, 0)] - other.data[(1, 0)]).abs() <= ATTITUDE_EPSILON
&& (self.data[(1, 1)] - other.data[(1, 1)]).abs() <= ATTITUDE_EPSILON
&& (self.data[(1, 2)] - other.data[(1, 2)]).abs() <= ATTITUDE_EPSILON
&& (self.data[(2, 0)] - other.data[(2, 0)]).abs() <= ATTITUDE_EPSILON
&& (self.data[(2, 1)] - other.data[(2, 1)]).abs() <= ATTITUDE_EPSILON
&& (self.data[(2, 2)] - other.data[(2, 2)]).abs() <= ATTITUDE_EPSILON
}
}
impl ops::Mul<RotationMatrix> for RotationMatrix {
type Output = RotationMatrix;
fn mul(self, rhs: RotationMatrix) -> RotationMatrix {
RotationMatrix::from_matrix(self.data * rhs.data).unwrap()
}
}
impl ops::MulAssign<RotationMatrix> for RotationMatrix {
fn mul_assign(&mut self, rhs: RotationMatrix) {
self.data *= rhs.data;
}
}
impl ops::Mul<Vector3<f64>> for RotationMatrix {
type Output = Vector3<f64>;
fn mul(self, rhs: Vector3<f64>) -> Vector3<f64> {
self.data * rhs
}
}
impl ops::Index<(usize, usize)> for RotationMatrix {
type Output = f64;
fn index(&self, idx: (usize, usize)) -> &f64 {
&self.data[(idx.0, idx.1)]
}
}
impl From<Quaternion> for RotationMatrix {
fn from(q: Quaternion) -> Self {
q.to_rotation_matrix()
}
}
impl From<EulerAxis> for RotationMatrix {
fn from(e: EulerAxis) -> Self {
e.to_rotation_matrix()
}
}
impl From<EulerAngle> for RotationMatrix {
fn from(e: EulerAngle) -> Self {
e.to_rotation_matrix()
}
}
impl FromAttitude for RotationMatrix {
fn from_quaternion(q: Quaternion) -> Self {
q.to_rotation_matrix()
}
fn from_euler_axis(e: EulerAxis) -> Self {
e.to_rotation_matrix()
}
fn from_euler_angle(e: EulerAngle) -> Self {
e.to_rotation_matrix()
}
fn from_rotation_matrix(r: RotationMatrix) -> Self {
RotationMatrix { data: r.data }
}
}
impl ToAttitude for RotationMatrix {
fn to_quaternion(&self) -> Quaternion {
Quaternion::from_rotation_matrix(*self)
}
fn to_euler_axis(&self) -> EulerAxis {
EulerAxis::from_rotation_matrix(*self)
}
fn to_euler_angle(&self, order: EulerAngleOrder) -> EulerAngle {
let r11 = self.data[(0, 0)];
let r12 = self.data[(0, 1)];
let r13 = self.data[(0, 2)];
let r21 = self.data[(1, 0)];
let r22 = self.data[(1, 1)];
let r23 = self.data[(1, 2)];
let r31 = self.data[(2, 0)];
let r32 = self.data[(2, 1)];
let r33 = self.data[(2, 2)];
match order {
EulerAngleOrder::XYX => {
EulerAngle::new(order, r21.atan2(r31), r11.acos(), r12.atan2(-r13), RADIANS)
}
EulerAngleOrder::XYZ => {
EulerAngle::new(order, r23.atan2(r33), -r13.asin(), r12.atan2(r11), RADIANS)
}
EulerAngleOrder::XZX => {
EulerAngle::new(order, r31.atan2(-r21), r11.acos(), r13.atan2(r12), RADIANS)
}
EulerAngleOrder::XZY => EulerAngle::new(
order,
(-r32).atan2(r22),
r12.asin(),
(-r13).atan2(r11),
RADIANS,
),
EulerAngleOrder::YXY => {
EulerAngle::new(order, r12.atan2(-r32), r22.acos(), r21.atan2(r23), RADIANS)
}
EulerAngleOrder::YXZ => EulerAngle::new(
order,
(-r13).atan2(r33),
r23.asin(),
(-r21).atan2(r22),
RADIANS,
),
EulerAngleOrder::YZX => {
EulerAngle::new(order, r31.atan2(r11), -r21.asin(), r23.atan2(r22), RADIANS)
}
EulerAngleOrder::YZY => {
EulerAngle::new(order, r32.atan2(r12), r22.acos(), r23.atan2(-r21), RADIANS)
}
EulerAngleOrder::ZXY => {
EulerAngle::new(order, r12.atan2(r22), -r32.asin(), r31.atan2(r33), RADIANS)
}
EulerAngleOrder::ZXZ => {
EulerAngle::new(order, r13.atan2(r23), r33.acos(), r31.atan2(-r32), RADIANS)
}
EulerAngleOrder::ZYX => EulerAngle::new(
order,
(-r21).atan2(r11),
r31.asin(),
(-r32).atan2(r33),
RADIANS,
),
EulerAngleOrder::ZYZ => {
EulerAngle::new(order, r23.atan2(-r13), r33.acos(), r32.atan2(r31), RADIANS)
}
}
}
fn to_rotation_matrix(&self) -> RotationMatrix {
RotationMatrix { data: self.data }
}
}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
use super::*;
use crate::constants::{DEGREES, RADIANS};
use nalgebra::Matrix3;
use strum::IntoEnumIterator;
#[test]
fn test_new() {
let r = RotationMatrix::new(
1.0,
0.0,
0.0,
0.0,
std::f64::consts::FRAC_1_SQRT_2,
std::f64::consts::FRAC_1_SQRT_2,
0.0,
-std::f64::consts::FRAC_1_SQRT_2,
std::f64::consts::FRAC_1_SQRT_2,
);
assert!(r.is_ok());
let r = RotationMatrix::new(1.0, 0.0, 0.0, 0.0, 2.0, 0.0, 0.0, 0.0, 1.0);
assert!(r.is_err());
let r = RotationMatrix::new(1.0, 0.0, 0.0, 0.0, 2.0, 3.0, 0.0, 0.0, 1.0);
assert!(r.is_err());
}
#[test]
fn test_from_matrix() {
let matrix = Matrix3::new(
1.0,
0.0,
0.0,
0.0,
std::f64::consts::FRAC_1_SQRT_2,
std::f64::consts::FRAC_1_SQRT_2,
0.0,
-std::f64::consts::FRAC_1_SQRT_2,
std::f64::consts::FRAC_1_SQRT_2,
);
let r = RotationMatrix::from_matrix(matrix);
assert!(r.is_ok());
}
#[test]
fn test_to_matrix() {
let r = RotationMatrix::new(
1.0,
0.0,
0.0,
0.0,
std::f64::consts::FRAC_1_SQRT_2,
std::f64::consts::FRAC_1_SQRT_2,
0.0,
-std::f64::consts::FRAC_1_SQRT_2,
std::f64::consts::FRAC_1_SQRT_2,
)
.unwrap();
let matrix = r.to_matrix();
assert_eq!(
matrix,
Matrix3::new(
1.0,
0.0,
0.0,
0.0,
std::f64::consts::FRAC_1_SQRT_2,
std::f64::consts::FRAC_1_SQRT_2,
0.0,
-std::f64::consts::FRAC_1_SQRT_2,
std::f64::consts::FRAC_1_SQRT_2
)
);
}
#[test]
#[allow(non_snake_case)]
fn test_Rx() {
let r = RotationMatrix::Rx(45.0, DEGREES);
let expected = RotationMatrix::new(
1.0,
0.0,
0.0,
0.0,
std::f64::consts::FRAC_1_SQRT_2,
std::f64::consts::FRAC_1_SQRT_2,
0.0,
-std::f64::consts::FRAC_1_SQRT_2,
std::f64::consts::FRAC_1_SQRT_2,
)
.unwrap();
assert_eq!(r, expected);
}
#[test]
#[allow(non_snake_case)]
fn test_Ry() {
let r = RotationMatrix::Ry(45.0, DEGREES);
let expected = RotationMatrix::new(
std::f64::consts::FRAC_1_SQRT_2,
0.0,
-std::f64::consts::FRAC_1_SQRT_2,
0.0,
1.0,
0.0,
std::f64::consts::FRAC_1_SQRT_2,
0.0,
std::f64::consts::FRAC_1_SQRT_2,
)
.unwrap();
assert_eq!(r, expected);
}
#[test]
#[allow(non_snake_case)]
fn test_Rz() {
let r = RotationMatrix::Rz(45.0, DEGREES);
let expected = RotationMatrix::new(
std::f64::consts::FRAC_1_SQRT_2,
std::f64::consts::FRAC_1_SQRT_2,
0.0,
-std::f64::consts::FRAC_1_SQRT_2,
std::f64::consts::FRAC_1_SQRT_2,
0.0,
0.0,
0.0,
1.0,
)
.unwrap();
assert_eq!(r, expected);
}
#[test]
fn test_from_quaternion() {
let q = Quaternion::new(1.0, 0.0, 0.0, 0.0);
let r = RotationMatrix::from_quaternion(q);
let expected = RotationMatrix::new(1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0).unwrap();
assert_eq!(r, expected);
}
#[test]
#[allow(non_snake_case)]
fn test_from_euler_axis_Rx() {
let e = EulerAxis::new(Vector3::new(1.0, 0.0, 0.0), 45.0, DEGREES);
let r = RotationMatrix::from_euler_axis(e);
let expected = RotationMatrix::new(
1.0,
0.0,
0.0,
0.0,
std::f64::consts::FRAC_1_SQRT_2,
std::f64::consts::FRAC_1_SQRT_2,
0.0,
-std::f64::consts::FRAC_1_SQRT_2,
std::f64::consts::FRAC_1_SQRT_2,
)
.unwrap();
assert_eq!(r, expected);
}
#[test]
#[allow(non_snake_case)]
fn test_from_euler_axis_Ry() {
let e = EulerAxis::new(Vector3::new(0.0, 1.0, 0.0), 45.0, DEGREES);
let r = RotationMatrix::from_euler_axis(e);
let expected = RotationMatrix::new(
std::f64::consts::FRAC_1_SQRT_2,
0.0,
-std::f64::consts::FRAC_1_SQRT_2,
0.0,
1.0,
0.0,
std::f64::consts::FRAC_1_SQRT_2,
0.0,
std::f64::consts::FRAC_1_SQRT_2,
)
.unwrap();
assert_eq!(r, expected);
}
#[test]
#[allow(non_snake_case)]
fn test_from_euler_axis_Rz() {
let e = EulerAxis::new(Vector3::new(0.0, 0.0, 1.0), 45.0, DEGREES);
let r = RotationMatrix::from_euler_axis(e);
let expected = RotationMatrix::new(
std::f64::consts::FRAC_1_SQRT_2,
std::f64::consts::FRAC_1_SQRT_2,
0.0,
-std::f64::consts::FRAC_1_SQRT_2,
std::f64::consts::FRAC_1_SQRT_2,
0.0,
0.0,
0.0,
1.0,
)
.unwrap();
assert_eq!(r, expected);
}
#[test]
fn test_from_euler_angle() {
let e = EulerAngle::new(EulerAngleOrder::XYZ, 45.0, 0.0, 0.0, DEGREES);
let r = RotationMatrix::from_euler_angle(e);
let expected = RotationMatrix::new(
1.0,
0.0,
0.0,
0.0,
std::f64::consts::FRAC_1_SQRT_2,
std::f64::consts::FRAC_1_SQRT_2,
0.0,
-std::f64::consts::FRAC_1_SQRT_2,
std::f64::consts::FRAC_1_SQRT_2,
)
.unwrap();
assert_eq!(r, expected);
}
#[test]
fn test_from_euler_angle_all_orders() {
for order in EulerAngleOrder::iter() {
let e = EulerAngle::new(order, 45.0, 30.0, 60.0, DEGREES);
let r = RotationMatrix::from_euler_angle(e);
let e2 = r.to_euler_angle(order);
assert_eq!(e, e2);
}
}
#[test]
fn test_from_rotation_matrix() {
let r = RotationMatrix::new(
1.0,
0.0,
0.0,
0.0,
std::f64::consts::FRAC_1_SQRT_2,
std::f64::consts::FRAC_1_SQRT_2,
0.0,
-std::f64::consts::FRAC_1_SQRT_2,
std::f64::consts::FRAC_1_SQRT_2,
)
.unwrap();
let r2 = RotationMatrix::from_rotation_matrix(r);
assert_eq!(r, r2);
assert!(!std::ptr::eq(&r, &r2));
}
#[test]
fn test_to_quaternion() {
let r = RotationMatrix::new(
1.0,
0.0,
0.0,
0.0,
std::f64::consts::FRAC_1_SQRT_2,
std::f64::consts::FRAC_1_SQRT_2,
0.0,
-std::f64::consts::FRAC_1_SQRT_2,
std::f64::consts::FRAC_1_SQRT_2,
)
.unwrap();
let q = r.to_quaternion();
let expected = Quaternion::new(0.9238795325112867, 0.3826834323650898, 0.0, 0.0);
assert_eq!(q, expected);
}
#[test]
#[allow(non_snake_case)]
fn test_to_euler_axis_Rx() {
let r = RotationMatrix::new(
1.0,
0.0,
0.0,
0.0,
std::f64::consts::FRAC_1_SQRT_2,
std::f64::consts::FRAC_1_SQRT_2,
0.0,
-std::f64::consts::FRAC_1_SQRT_2,
std::f64::consts::FRAC_1_SQRT_2,
)
.unwrap();
let e = r.to_euler_axis();
let expected = EulerAxis::new(Vector3::new(1.0, 0.0, 0.0), 45.0, DEGREES);
assert_eq!(e, expected);
}
#[test]
#[allow(non_snake_case)]
fn test_to_euler_axis_Ry() {
let r = RotationMatrix::new(
std::f64::consts::FRAC_1_SQRT_2,
0.0,
-std::f64::consts::FRAC_1_SQRT_2,
0.0,
1.0,
0.0,
std::f64::consts::FRAC_1_SQRT_2,
0.0,
std::f64::consts::FRAC_1_SQRT_2,
)
.unwrap();
let e = r.to_euler_axis();
let expected = EulerAxis::new(Vector3::new(0.0, 1.0, 0.0), 45.0, DEGREES);
assert_eq!(e, expected);
}
#[test]
#[allow(non_snake_case)]
fn test_to_euler_axis_Rz() {
let r = RotationMatrix::new(
std::f64::consts::FRAC_1_SQRT_2,
std::f64::consts::FRAC_1_SQRT_2,
0.0,
-std::f64::consts::FRAC_1_SQRT_2,
std::f64::consts::FRAC_1_SQRT_2,
0.0,
0.0,
0.0,
1.0,
)
.unwrap();
let e = r.to_euler_axis();
let expected = EulerAxis::new(Vector3::new(0.0, 0.0, 1.0), 45.0, DEGREES);
assert_eq!(e, expected);
}
#[test]
fn test_to_euler_angle_circular_xyx() {
let r = RotationMatrix::Rx(30.0, DEGREES)
* RotationMatrix::Ry(45.0, DEGREES)
* RotationMatrix::Rx(60.0, DEGREES);
let e = r.to_euler_angle(EulerAngleOrder::XYX);
let r2 = e.to_rotation_matrix();
assert_eq!(r, r2);
}
#[test]
fn test_to_euler_angle_circular_xyz() {
let r = RotationMatrix::Rx(30.0, DEGREES)
* RotationMatrix::Ry(45.0, DEGREES)
* RotationMatrix::Rx(60.0, DEGREES);
let e = r.to_euler_angle(EulerAngleOrder::XYZ);
let r2 = e.to_rotation_matrix();
assert_eq!(r, r2);
}
#[test]
fn test_to_euler_angle_circular_xzx() {
let r = RotationMatrix::Rx(30.0, DEGREES)
* RotationMatrix::Ry(45.0, DEGREES)
* RotationMatrix::Rx(60.0, DEGREES);
let e = r.to_euler_angle(EulerAngleOrder::XZX);
let r2 = e.to_rotation_matrix();
assert_eq!(r, r2);
}
#[test]
fn test_to_euler_angle_circular_xzy() {
let r = RotationMatrix::Rx(30.0, DEGREES)
* RotationMatrix::Ry(45.0, DEGREES)
* RotationMatrix::Rx(60.0, DEGREES);
let e = r.to_euler_angle(EulerAngleOrder::XZY);
let r2 = e.to_rotation_matrix();
assert_eq!(r, r2);
}
#[test]
fn test_to_euler_angle_circular_yxy() {
let r = RotationMatrix::Rx(30.0, DEGREES)
* RotationMatrix::Ry(45.0, DEGREES)
* RotationMatrix::Rx(60.0, DEGREES);
let e = r.to_euler_angle(EulerAngleOrder::YXY);
let r2 = e.to_rotation_matrix();
assert_eq!(r, r2);
}
#[test]
fn test_to_euler_angle_circular_yxz() {
let r = RotationMatrix::Rx(30.0, DEGREES)
* RotationMatrix::Ry(45.0, DEGREES)
* RotationMatrix::Rx(60.0, DEGREES);
let e = r.to_euler_angle(EulerAngleOrder::YXZ);
let r2 = e.to_rotation_matrix();
assert_eq!(r, r2);
}
#[test]
fn test_to_euler_angle_circular_yzx() {
let r = RotationMatrix::Rx(30.0, DEGREES)
* RotationMatrix::Ry(45.0, DEGREES)
* RotationMatrix::Rx(60.0, DEGREES);
let e = r.to_euler_angle(EulerAngleOrder::YZX);
let r2 = e.to_rotation_matrix();
assert_eq!(r, r2);
}
#[test]
fn test_to_euler_angle_circular_yzy() {
let r = RotationMatrix::Rx(30.0, DEGREES)
* RotationMatrix::Ry(45.0, DEGREES)
* RotationMatrix::Rx(60.0, DEGREES);
let e = r.to_euler_angle(EulerAngleOrder::YZY);
let r2 = e.to_rotation_matrix();
assert_eq!(r, r2);
}
#[test]
fn test_to_euler_angle_circular_zxy() {
let r = RotationMatrix::Rx(30.0, DEGREES)
* RotationMatrix::Ry(45.0, DEGREES)
* RotationMatrix::Rx(60.0, DEGREES);
let e = r.to_euler_angle(EulerAngleOrder::ZXY);
let r2 = e.to_rotation_matrix();
assert_eq!(r, r2);
}
#[test]
fn test_to_euler_angle_circular_zxz() {
let r = RotationMatrix::Rx(30.0, DEGREES)
* RotationMatrix::Ry(45.0, DEGREES)
* RotationMatrix::Rx(60.0, DEGREES);
let e = r.to_euler_angle(EulerAngleOrder::ZXZ);
let r2 = e.to_rotation_matrix();
assert_eq!(r, r2);
}
#[test]
fn test_to_euler_angle_circular_zyx() {
let r = RotationMatrix::Rx(30.0, DEGREES)
* RotationMatrix::Ry(45.0, DEGREES)
* RotationMatrix::Rx(60.0, DEGREES);
let e = r.to_euler_angle(EulerAngleOrder::ZYX);
let r2 = e.to_rotation_matrix();
assert_eq!(r, r2);
}
#[test]
fn test_to_euler_angle_circular_zyz() {
let r = RotationMatrix::Rx(30.0, DEGREES)
* RotationMatrix::Ry(45.0, DEGREES)
* RotationMatrix::Rx(60.0, DEGREES);
let e = r.to_euler_angle(EulerAngleOrder::ZYZ);
let r2 = e.to_rotation_matrix();
assert_eq!(r, r2);
}
#[test]
fn test_to_rotation_matrix() {
let r = RotationMatrix::new(
1.0,
0.0,
0.0,
0.0,
std::f64::consts::FRAC_1_SQRT_2,
std::f64::consts::FRAC_1_SQRT_2,
0.0,
-std::f64::consts::FRAC_1_SQRT_2,
std::f64::consts::FRAC_1_SQRT_2,
)
.unwrap();
let r2 = r.to_rotation_matrix();
assert_eq!(r, r2);
assert!(!std::ptr::eq(&r, &r2));
}
}