1use crate::approxeq::ApproxEq;
6use crate::trig::Trig;
7use crate::{Rotation3D, Transform3D, UnknownUnit, Vector3D};
8
9use core::{fmt, hash};
10
11#[cfg(feature = "bytemuck")]
12use bytemuck::{Pod, Zeroable};
13use num_traits::real::Real;
14#[cfg(feature = "serde")]
15use serde::{Deserialize, Serialize};
16
17#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
26#[repr(C)]
27pub struct RigidTransform3D<T, Src, Dst> {
28 pub rotation: Rotation3D<T, Src, Dst>,
29 pub translation: Vector3D<T, Dst>,
30}
31
32impl<T, Src, Dst> RigidTransform3D<T, Src, Dst> {
33 #[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
43impl<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
52impl<T: Real + ApproxEq<T>, Src, Dst> RigidTransform3D<T, Src, Dst> {
53 #[inline]
55 pub fn identity() -> Self {
56 Self {
57 rotation: Rotation3D::identity(),
58 translation: Vector3D::zero(),
59 }
60 }
61
62 #[inline]
64 pub fn new_from_reversed(
65 translation: Vector3D<T, Src>,
66 rotation: Rotation3D<T, Src, Dst>,
67 ) -> Self {
68 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 #[inline]
104 pub fn decompose_reversed(&self) -> (Vector3D<T, Src>, Rotation3D<T, Src, Dst>) {
105 let translation = self.rotation.inverse().transform_vector3d(self.translation);
113 (translation, self.rotation)
114 }
115
116 #[inline]
121 pub fn then<Dst2>(
122 &self,
123 other: &RigidTransform3D<T, Dst, Dst2>,
124 ) -> RigidTransform3D<T, Src, Dst2> {
125 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 #[inline]
148 pub fn inverse(&self) -> RigidTransform3D<T, Dst, Src> {
149 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 #[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 #[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
192impl<T: fmt::Debug, Src, Dst> fmt::Debug for RigidTransform3D<T, Src, Dst> {
193 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
194 f.debug_struct("RigidTransform3D")
195 .field("rotation", &self.rotation)
196 .field("translation", &self.translation)
197 .finish()
198 }
199}
200
201impl<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}
206impl<T: Eq, Src, Dst> Eq for RigidTransform3D<T, Src, Dst> {}
207
208impl<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
215impl<T: Copy, Src, Dst> Copy for RigidTransform3D<T, Src, Dst> {}
216
217impl<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")]
227impl<'a, T, Src, Dst> arbitrary::Arbitrary<'a> for RigidTransform3D<T, Src, Dst>
228where
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")]
240unsafe impl<T: Zeroable, Src, Dst> Zeroable for RigidTransform3D<T, Src, Dst> {}
241
242#[cfg(feature = "bytemuck")]
243unsafe impl<T: Pod, Src: 'static, Dst: 'static> Pod for RigidTransform3D<T, Src, Dst> {}
244
245impl<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
253impl<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)]
260mod 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}