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