euclid/
rigid.rs

1//! All matrix multiplication in this module is in row-vector notation,
2//! i.e. a vector `v` is transformed with `v * T`, and if you want to apply `T1`
3//! before `T2` you use `T1 * T2`
4
5use crate::approxeq::ApproxEq;
6use crate::trig::Trig;
7use crate::{Rotation3D, Transform3D, UnknownUnit, Vector3D};
8use num_traits::Float;
9#[cfg(feature = "serde")]
10use serde::{Deserialize, Serialize};
11
12/// A rigid transformation. All lengths are preserved under such a transformation.
13///
14///
15/// Internally, this is a rotation and a translation, with the rotation
16/// applied first (i.e. `Rotation * Translation`, in row-vector notation)
17///
18/// This can be more efficient to use over full matrices, especially if you
19/// have to deal with the decomposed quantities often.
20#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
21#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
22#[repr(C)]
23pub struct RigidTransform3D<T, Src, Dst> {
24    pub rotation: Rotation3D<T, Src, Dst>,
25    pub translation: Vector3D<T, Dst>,
26}
27
28impl<T, Src, Dst> RigidTransform3D<T, Src, Dst> {
29    /// Construct a new rigid transformation, where the `rotation` applies first
30    #[inline]
31    pub const fn new(rotation: Rotation3D<T, Src, Dst>, translation: Vector3D<T, Dst>) -> Self {
32        Self {
33            rotation,
34            translation,
35        }
36    }
37}
38
39impl<T: Copy, Src, Dst> RigidTransform3D<T, Src, Dst> {
40    pub fn cast_unit<Src2, Dst2>(&self) -> RigidTransform3D<T, Src2, Dst2> {
41        RigidTransform3D {
42            rotation: self.rotation.cast_unit(),
43            translation: self.translation.cast_unit(),
44        }
45    }
46}
47
48impl<T: Float + ApproxEq<T>, Src, Dst> RigidTransform3D<T, Src, Dst> {
49    /// Construct an identity transform
50    #[inline]
51    pub fn identity() -> Self {
52        Self {
53            rotation: Rotation3D::identity(),
54            translation: Vector3D::zero(),
55        }
56    }
57
58    /// Construct a new rigid transformation, where the `translation` applies first
59    #[inline]
60    pub fn new_from_reversed(
61        translation: Vector3D<T, Src>,
62        rotation: Rotation3D<T, Src, Dst>,
63    ) -> Self {
64        // T * R
65        //   = (R * R^-1) * T * R
66        //   = R * (R^-1 * T * R)
67        //   = R * T'
68        //
69        // T' = (R^-1 * T * R) is also a translation matrix
70        // It is equivalent to the translation matrix obtained by rotating the
71        // translation by R
72
73        let translation = rotation.transform_vector3d(translation);
74        Self {
75            rotation,
76            translation,
77        }
78    }
79
80    #[inline]
81    pub fn from_rotation(rotation: Rotation3D<T, Src, Dst>) -> Self {
82        Self {
83            rotation,
84            translation: Vector3D::zero(),
85        }
86    }
87
88    #[inline]
89    pub fn from_translation(translation: Vector3D<T, Dst>) -> Self {
90        Self {
91            translation,
92            rotation: Rotation3D::identity(),
93        }
94    }
95
96    /// Decompose this into a translation and an rotation to be applied in the opposite order
97    ///
98    /// i.e., the translation is applied _first_
99    #[inline]
100    pub fn decompose_reversed(&self) -> (Vector3D<T, Src>, Rotation3D<T, Src, Dst>) {
101        // self = R * T
102        //      = R * T * (R^-1 * R)
103        //      = (R * T * R^-1) * R)
104        //      = T' * R
105        //
106        // T' = (R^ * T * R^-1) is T rotated by R^-1
107
108        let translation = self.rotation.inverse().transform_vector3d(self.translation);
109        (translation, self.rotation)
110    }
111
112    /// Returns the multiplication of the two transforms such that
113    /// other's transformation applies after self's transformation.
114    ///
115    /// i.e., this produces `self * other` in row-vector notation
116    #[inline]
117    pub fn then<Dst2>(
118        &self,
119        other: &RigidTransform3D<T, Dst, Dst2>,
120    ) -> RigidTransform3D<T, Src, Dst2> {
121        // self = R1 * T1
122        // other = R2 * T2
123        // result = R1 * T1 * R2 * T2
124        //        = R1 * (R2 * R2^-1) * T1 * R2 * T2
125        //        = (R1 * R2) * (R2^-1 * T1 * R2) * T2
126        //        = R' * T' * T2
127        //        = R' * T''
128        //
129        // (R2^-1 * T2 * R2^) = T' = T2 rotated by R2
130        // R1 * R2  = R'
131        // T' * T2 = T'' = vector addition of translations T2 and T'
132
133        let t_prime = other.rotation.transform_vector3d(self.translation);
134        let r_prime = self.rotation.then(&other.rotation);
135        let t_prime2 = t_prime + other.translation;
136        RigidTransform3D {
137            rotation: r_prime,
138            translation: t_prime2,
139        }
140    }
141
142    /// Inverts the transformation
143    #[inline]
144    pub fn inverse(&self) -> RigidTransform3D<T, Dst, Src> {
145        // result = (self)^-1
146        //        = (R * T)^-1
147        //        = T^-1 * R^-1
148        //        = (R^-1 * R) * T^-1 * R^-1
149        //        = R^-1 * (R * T^-1 * R^-1)
150        //        = R' * T'
151        //
152        // T' = (R * T^-1 * R^-1) = (-T) rotated by R^-1
153        // R' = R^-1
154        //
155        // An easier way of writing this is to use new_from_reversed() with R^-1 and T^-1
156
157        RigidTransform3D::new_from_reversed(-self.translation, self.rotation.inverse())
158    }
159
160    pub fn to_transform(&self) -> Transform3D<T, Src, Dst>
161    where
162        T: Trig,
163    {
164        self.rotation.to_transform().then(&self.translation.to_transform())
165    }
166
167    /// Drop the units, preserving only the numeric value.
168    #[inline]
169    pub fn to_untyped(&self) -> RigidTransform3D<T, UnknownUnit, UnknownUnit> {
170        RigidTransform3D {
171            rotation: self.rotation.to_untyped(),
172            translation: self.translation.to_untyped(),
173        }
174    }
175
176    /// Tag a unitless value with units.
177    #[inline]
178    pub fn from_untyped(transform: &RigidTransform3D<T, UnknownUnit, UnknownUnit>) -> Self {
179        RigidTransform3D {
180            rotation: Rotation3D::from_untyped(&transform.rotation),
181            translation: Vector3D::from_untyped(transform.translation),
182        }
183    }
184}
185
186impl<T: Float + ApproxEq<T>, Src, Dst> From<Rotation3D<T, Src, Dst>>
187    for RigidTransform3D<T, Src, Dst>
188{
189    fn from(rot: Rotation3D<T, Src, Dst>) -> Self {
190        Self::from_rotation(rot)
191    }
192}
193
194impl<T: Float + ApproxEq<T>, Src, Dst> From<Vector3D<T, Dst>> for RigidTransform3D<T, Src, Dst> {
195    fn from(t: Vector3D<T, Dst>) -> Self {
196        Self::from_translation(t)
197    }
198}
199
200#[cfg(test)]
201mod test {
202    use super::RigidTransform3D;
203    use crate::default::{Rotation3D, Transform3D, Vector3D};
204
205    #[test]
206    fn test_rigid_construction() {
207        let translation = Vector3D::new(12.1, 17.8, -5.5);
208        let rotation = Rotation3D::unit_quaternion(0.5, -7.8, 2.2, 4.3);
209
210        let rigid = RigidTransform3D::new(rotation, translation);
211        assert!(rigid.to_transform().approx_eq(
212            &rotation.to_transform().then(&translation.to_transform())
213        ));
214
215        let rigid = RigidTransform3D::new_from_reversed(translation, rotation);
216        assert!(rigid.to_transform().approx_eq(
217            &translation.to_transform().then(&rotation.to_transform())
218        ));
219    }
220
221    #[test]
222    fn test_rigid_decomposition() {
223        let translation = Vector3D::new(12.1, 17.8, -5.5);
224        let rotation = Rotation3D::unit_quaternion(0.5, -7.8, 2.2, 4.3);
225
226        let rigid = RigidTransform3D::new(rotation, translation);
227        let (t2, r2) = rigid.decompose_reversed();
228        assert!(rigid
229            .to_transform()
230            .approx_eq(&t2.to_transform().then(&r2.to_transform())));
231    }
232
233    #[test]
234    fn test_rigid_inverse() {
235        let translation = Vector3D::new(12.1, 17.8, -5.5);
236        let rotation = Rotation3D::unit_quaternion(0.5, -7.8, 2.2, 4.3);
237
238        let rigid = RigidTransform3D::new(rotation, translation);
239        let inverse = rigid.inverse();
240        assert!(rigid
241            .then(&inverse)
242            .to_transform()
243            .approx_eq(&Transform3D::identity()));
244        assert!(inverse
245            .to_transform()
246            .approx_eq(&rigid.to_transform().inverse().unwrap()));
247    }
248
249    #[test]
250    fn test_rigid_multiply() {
251        let translation = Vector3D::new(12.1, 17.8, -5.5);
252        let rotation = Rotation3D::unit_quaternion(0.5, -7.8, 2.2, 4.3);
253        let translation2 = Vector3D::new(9.3, -3.9, 1.1);
254        let rotation2 = Rotation3D::unit_quaternion(0.1, 0.2, 0.3, -0.4);
255        let rigid = RigidTransform3D::new(rotation, translation);
256        let rigid2 = RigidTransform3D::new(rotation2, translation2);
257
258        assert!(rigid
259            .then(&rigid2)
260            .to_transform()
261            .approx_eq(&rigid.to_transform().then(&rigid2.to_transform())));
262        assert!(rigid2
263            .then(&rigid)
264            .to_transform()
265            .approx_eq(&rigid2.to_transform().then(&rigid.to_transform())));
266    }
267}