1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113
use derive_more::{AsMut, AsRef, Deref, DerefMut, From, Into}; use nalgebra::{Matrix3, Matrix4, Rotation3, Unit, Vector3}; use num_traits::Float; /// Contains a member of the lie algebra so(3), a representation of the tangent space /// of 3d rotation. This is also known as the lie algebra of the 3d rotation group SO(3). /// /// This is only intended to be used in optimization problems where it is desirable to /// have unconstranied variables representing the degrees of freedom of the rotation. /// In all other cases, a rotation matrix should be used to store rotations, since the /// conversion to and from a rotation matrix is non-trivial. #[derive(Debug, Clone, Copy, PartialEq, PartialOrd, AsMut, AsRef, Deref, DerefMut, From, Into)] pub struct Skew3(pub Vector3<f64>); impl Skew3 { /// Converts the Skew3 to a Rotation3 matrix. pub fn rotation(self) -> Rotation3<f64> { self.into() } /// This converts a matrix in skew-symmetric form into a Skew3. /// /// Warning: Does no check to ensure matrix is actually skew-symmetric. pub fn vee(mat: Matrix3<f64>) -> Self { Self(Vector3::new(mat.m32, mat.m13, mat.m21)) } /// This converts the Skew3 into its skew-symmetric matrix form. #[rustfmt::skip] pub fn hat(self) -> Matrix3<f64> { self.0.cross_matrix() } /// This converts the Skew3 into its squared skew-symmetric matrix form efficiently. #[rustfmt::skip] pub fn hat2(self) -> Matrix3<f64> { let w = self.0; let w11 = w.x * w.x; let w12 = w.x * w.y; let w13 = w.x * w.z; let w22 = w.y * w.y; let w23 = w.y * w.z; let w33 = w.z * w.z; Matrix3::new( -w22 - w33, w12, w13, w12, -w11 - w33, w23, w13, w23, -w11 - w22, ) } /// Computes the lie bracket [self, rhs]. pub fn bracket(self, rhs: Self) -> Self { Self::vee(self.hat() * rhs.hat() - rhs.hat() * self.hat()) } /// The jacobian of the output of a rotation in respect to the /// input of a rotation. /// /// `y = R * x` /// /// `dy/dx = R` /// /// The formula is pretty simple and is just the rotation matrix created /// from the exponential map of this so(3) element into SO(3). The result is converted /// to homogeneous form (by adding a new dimension with a `1` in the diagonal) so /// that it is compatible with homogeneous coordinates. /// /// If you have the rotation matrix already, please use the rotation matrix itself /// rather than calling this method. Calling this method will waste time converting /// the [`Skew`] back into a [`Rotation3`], which is non-trivial. pub fn jacobian_input(self) -> Matrix4<f64> { let rotation: Rotation3<f64> = self.into(); let matrix: Matrix3<f64> = rotation.into(); matrix.to_homogeneous() } /// The jacobian of the output of a rotation in respect to the /// rotation itself. /// /// `y = R * x` /// /// `dy/dR = -hat(y)` /// /// The derivative is purely based on the current output vector, and thus doesn't take `self`. /// /// Note that when working with homogeneous projective coordinates, only the first three components /// (the bearing) are relevant, hence the resulting matrix is a [`Matrix3`]. pub fn jacobian_self(y: Vector3<f64>) -> Matrix3<f64> { (-y).cross_matrix() } } /// This is the exponential map. impl From<Skew3> for Rotation3<f64> { fn from(w: Skew3) -> Self { // This check is done to avoid the degenerate case where the angle is near zero. let theta2 = w.0.norm_squared(); if theta2 <= f64::epsilon() { Rotation3::from_matrix(&(Matrix3::identity() + w.hat())) } else { let theta = theta2.sqrt(); let axis = Unit::new_unchecked(w.0 / theta); Self::from_axis_angle(&axis, theta) } } } /// This is the log map. impl From<Rotation3<f64>> for Skew3 { fn from(r: Rotation3<f64>) -> Self { Self(r.scaled_axis()) } }