1use crate::{Rotation3D, Vector3D};
6
7use core::{fmt, hash};
8
9#[cfg(feature = "bytemuck")]
10use bytemuck::{Pod, Zeroable};
11#[cfg(feature = "malloc_size_of")]
12use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
13#[cfg(feature = "serde")]
14use serde::{Deserialize, Serialize};
15
16#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
25#[repr(C)]
26pub struct RigidTransform3D<T, Src, Dst> {
27 pub rotation: Rotation3D<T, Src, Dst>,
28 pub translation: Vector3D<T, Dst>,
29}
30
31impl<T, Src, Dst> RigidTransform3D<T, Src, Dst> {
32 #[inline]
34 pub const fn new(rotation: Rotation3D<T, Src, Dst>, translation: Vector3D<T, Dst>) -> Self {
35 Self {
36 rotation,
37 translation,
38 }
39 }
40}
41
42impl<T: Copy, Src, Dst> RigidTransform3D<T, Src, Dst> {
43 pub fn cast_unit<Src2, Dst2>(&self) -> RigidTransform3D<T, Src2, Dst2> {
44 RigidTransform3D {
45 rotation: self.rotation.cast_unit(),
46 translation: self.translation.cast_unit(),
47 }
48 }
49}
50
51#[cfg(any(feature = "std", feature = "libm"))]
52mod float {
53 use num_traits::real::Real;
54
55 use super::RigidTransform3D;
56 use crate::{approxeq::ApproxEq, Rotation3D, Transform3D, Trig, UnknownUnit, Vector3D};
57
58 impl<T: Real + ApproxEq<T>, Src, Dst> RigidTransform3D<T, Src, Dst> {
59 #[inline]
61 pub fn identity() -> Self {
62 Self {
63 rotation: Rotation3D::identity(),
64 translation: Vector3D::zero(),
65 }
66 }
67
68 #[inline]
70 pub fn new_from_reversed(
71 translation: Vector3D<T, Src>,
72 rotation: Rotation3D<T, Src, Dst>,
73 ) -> Self {
74 let translation = rotation.transform_vector3d(translation);
84 Self {
85 rotation,
86 translation,
87 }
88 }
89
90 #[inline]
91 pub fn from_rotation(rotation: Rotation3D<T, Src, Dst>) -> Self {
92 Self {
93 rotation,
94 translation: Vector3D::zero(),
95 }
96 }
97
98 #[inline]
99 pub fn from_translation(translation: Vector3D<T, Dst>) -> Self {
100 Self {
101 translation,
102 rotation: Rotation3D::identity(),
103 }
104 }
105
106 #[inline]
110 pub fn decompose_reversed(&self) -> (Vector3D<T, Src>, Rotation3D<T, Src, Dst>) {
111 let translation = self.rotation.inverse().transform_vector3d(self.translation);
119 (translation, self.rotation)
120 }
121
122 #[inline]
127 pub fn then<Dst2>(
128 &self,
129 other: &RigidTransform3D<T, Dst, Dst2>,
130 ) -> RigidTransform3D<T, Src, Dst2> {
131 let t_prime = other.rotation.transform_vector3d(self.translation);
144 let r_prime = self.rotation.then(&other.rotation);
145 let t_prime2 = t_prime + other.translation;
146 RigidTransform3D {
147 rotation: r_prime,
148 translation: t_prime2,
149 }
150 }
151
152 #[inline]
154 pub fn inverse(&self) -> RigidTransform3D<T, Dst, Src> {
155 RigidTransform3D::new_from_reversed(-self.translation, self.rotation.inverse())
168 }
169
170 pub fn to_transform(&self) -> Transform3D<T, Src, Dst>
171 where
172 T: Trig,
173 {
174 self.rotation
175 .to_transform()
176 .then(&self.translation.to_transform())
177 }
178
179 #[inline]
181 pub fn to_untyped(&self) -> RigidTransform3D<T, UnknownUnit, UnknownUnit> {
182 RigidTransform3D {
183 rotation: self.rotation.to_untyped(),
184 translation: self.translation.to_untyped(),
185 }
186 }
187
188 #[inline]
190 pub fn from_untyped(transform: &RigidTransform3D<T, UnknownUnit, UnknownUnit>) -> Self {
191 RigidTransform3D {
192 rotation: Rotation3D::from_untyped(&transform.rotation),
193 translation: Vector3D::from_untyped(transform.translation),
194 }
195 }
196 }
197
198 impl<T: Real + ApproxEq<T>, Src, Dst> From<Rotation3D<T, Src, Dst>>
199 for RigidTransform3D<T, Src, Dst>
200 {
201 fn from(rot: Rotation3D<T, Src, Dst>) -> Self {
202 Self::from_rotation(rot)
203 }
204 }
205
206 impl<T: Real + ApproxEq<T>, Src, Dst> From<Vector3D<T, Dst>> for RigidTransform3D<T, Src, Dst> {
207 fn from(t: Vector3D<T, Dst>) -> Self {
208 Self::from_translation(t)
209 }
210 }
211}
212
213impl<T: fmt::Debug, Src, Dst> fmt::Debug for RigidTransform3D<T, Src, Dst> {
214 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
215 f.debug_struct("RigidTransform3D")
216 .field("rotation", &self.rotation)
217 .field("translation", &self.translation)
218 .finish()
219 }
220}
221
222impl<T: PartialEq, Src, Dst> PartialEq for RigidTransform3D<T, Src, Dst> {
223 fn eq(&self, other: &Self) -> bool {
224 self.rotation == other.rotation && self.translation == other.translation
225 }
226}
227impl<T: Eq, Src, Dst> Eq for RigidTransform3D<T, Src, Dst> {}
228
229impl<T: hash::Hash, Src, Dst> hash::Hash for RigidTransform3D<T, Src, Dst> {
230 fn hash<H: hash::Hasher>(&self, state: &mut H) {
231 self.rotation.hash(state);
232 self.translation.hash(state);
233 }
234}
235
236impl<T: Copy, Src, Dst> Copy for RigidTransform3D<T, Src, Dst> {}
237
238impl<T: Clone, Src, Dst> Clone for RigidTransform3D<T, Src, Dst> {
239 fn clone(&self) -> Self {
240 RigidTransform3D {
241 rotation: self.rotation.clone(),
242 translation: self.translation.clone(),
243 }
244 }
245}
246
247#[cfg(feature = "arbitrary")]
248impl<'a, T, Src, Dst> arbitrary::Arbitrary<'a> for RigidTransform3D<T, Src, Dst>
249where
250 T: arbitrary::Arbitrary<'a>,
251{
252 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
253 Ok(RigidTransform3D {
254 rotation: arbitrary::Arbitrary::arbitrary(u)?,
255 translation: arbitrary::Arbitrary::arbitrary(u)?,
256 })
257 }
258}
259
260#[cfg(feature = "bytemuck")]
261unsafe impl<T: Zeroable, Src, Dst> Zeroable for RigidTransform3D<T, Src, Dst> {}
262
263#[cfg(feature = "bytemuck")]
264unsafe impl<T: Pod, Src: 'static, Dst: 'static> Pod for RigidTransform3D<T, Src, Dst> {}
265
266#[cfg(feature = "malloc_size_of")]
267impl<T: MallocSizeOf, Src, Dst> MallocSizeOf for RigidTransform3D<T, Src, Dst> {
268 fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
269 self.rotation.size_of(ops) + self.translation.size_of(ops)
270 }
271}
272
273#[cfg(test)]
274#[cfg(any(feature = "std", feature = "libm"))]
275mod test {
276 use super::RigidTransform3D;
277 use crate::default::{Rotation3D, Transform3D, Vector3D};
278
279 #[test]
280 fn test_rigid_construction() {
281 let translation = Vector3D::new(12.1, 17.8, -5.5);
282 let rotation = Rotation3D::unit_quaternion(0.5, -7.8, 2.2, 4.3);
283
284 let rigid = RigidTransform3D::new(rotation, translation);
285 assert!(rigid
286 .to_transform()
287 .approx_eq(&rotation.to_transform().then(&translation.to_transform())));
288
289 let rigid = RigidTransform3D::new_from_reversed(translation, rotation);
290 assert!(rigid
291 .to_transform()
292 .approx_eq(&translation.to_transform().then(&rotation.to_transform())));
293 }
294
295 #[test]
296 fn test_rigid_decomposition() {
297 let translation = Vector3D::new(12.1, 17.8, -5.5);
298 let rotation = Rotation3D::unit_quaternion(0.5, -7.8, 2.2, 4.3);
299
300 let rigid = RigidTransform3D::new(rotation, translation);
301 let (t2, r2) = rigid.decompose_reversed();
302 assert!(rigid
303 .to_transform()
304 .approx_eq(&t2.to_transform().then(&r2.to_transform())));
305 }
306
307 #[test]
308 fn test_rigid_inverse() {
309 let translation = Vector3D::new(12.1, 17.8, -5.5);
310 let rotation = Rotation3D::unit_quaternion(0.5, -7.8, 2.2, 4.3);
311
312 let rigid = RigidTransform3D::new(rotation, translation);
313 let inverse = rigid.inverse();
314 assert!(rigid
315 .then(&inverse)
316 .to_transform()
317 .approx_eq(&Transform3D::identity()));
318 assert!(inverse
319 .to_transform()
320 .approx_eq(&rigid.to_transform().inverse().unwrap()));
321 }
322
323 #[test]
324 fn test_rigid_multiply() {
325 let translation = Vector3D::new(12.1, 17.8, -5.5);
326 let rotation = Rotation3D::unit_quaternion(0.5, -7.8, 2.2, 4.3);
327 let translation2 = Vector3D::new(9.3, -3.9, 1.1);
328 let rotation2 = Rotation3D::unit_quaternion(0.1, 0.2, 0.3, -0.4);
329 let rigid = RigidTransform3D::new(rotation, translation);
330 let rigid2 = RigidTransform3D::new(rotation2, translation2);
331
332 assert!(rigid
333 .then(&rigid2)
334 .to_transform()
335 .approx_eq(&rigid.to_transform().then(&rigid2.to_transform())));
336 assert!(rigid2
337 .then(&rigid)
338 .to_transform()
339 .approx_eq(&rigid2.to_transform().then(&rigid.to_transform())));
340 }
341}