1use bincode::{Decode, Encode};
2use core::fmt::Debug;
3use serde::{Deserialize, Serialize};
4use std::ops::Mul;
5use uom::si::angle::radian;
6use uom::si::f32::Angle as Angle32;
7use uom::si::f32::Length as Length32;
8use uom::si::f64::Angle as Angle64;
9use uom::si::f64::Length as Length64;
10use uom::si::length::meter;
11
12#[cfg(feature = "glam")]
13use glam::{Affine3A, DAffine3, DMat4, Mat4};
14
15#[derive(Debug, Clone, Copy)]
18pub struct Transform3D<T: Copy + Debug + 'static> {
19 #[cfg(feature = "glam")]
20 inner: TransformInner<T>,
21 #[cfg(not(feature = "glam"))]
22 pub mat: [[T; 4]; 4],
23}
24
25#[cfg(feature = "glam")]
26#[derive(Debug, Clone, Copy)]
27enum TransformInner<T: Copy + Debug + 'static> {
28 F32(Affine3A),
29 F64(DAffine3),
30 _Phantom(std::marker::PhantomData<T>),
31}
32
33pub type Pose<T> = Transform3D<T>;
34
35impl<T: Copy + Debug + Default + 'static> Serialize for Transform3D<T>
37where
38 T: Serialize,
39{
40 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
41 where
42 S: serde::Serializer,
43 {
44 #[cfg(feature = "glam")]
45 {
46 let mat = self.to_matrix();
47 mat.serialize(serializer)
48 }
49 #[cfg(not(feature = "glam"))]
50 {
51 self.mat.serialize(serializer)
52 }
53 }
54}
55
56impl<'de, T: Copy + Debug + 'static> Deserialize<'de> for Transform3D<T>
57where
58 T: Deserialize<'de> + Default,
59{
60 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
61 where
62 D: serde::Deserializer<'de>,
63 {
64 let mat: [[T; 4]; 4] = Deserialize::deserialize(deserializer)?;
65 Ok(Self::from_matrix(mat))
66 }
67}
68
69impl<T: Copy + Debug + Default + 'static> Encode for Transform3D<T>
71where
72 T: Encode,
73{
74 fn encode<E: bincode::enc::Encoder>(
75 &self,
76 encoder: &mut E,
77 ) -> Result<(), bincode::error::EncodeError> {
78 #[cfg(feature = "glam")]
79 {
80 let mat = self.to_matrix();
81 mat.encode(encoder)
82 }
83 #[cfg(not(feature = "glam"))]
84 {
85 self.mat.encode(encoder)
86 }
87 }
88}
89
90impl<T: Copy + Debug + 'static> Decode<()> for Transform3D<T>
91where
92 T: Decode<()> + Default,
93{
94 fn decode<D: bincode::de::Decoder<Context = ()>>(
95 decoder: &mut D,
96 ) -> Result<Self, bincode::error::DecodeError> {
97 let mat: [[T; 4]; 4] = Decode::decode(decoder)?;
98 Ok(Self::from_matrix(mat))
99 }
100}
101
102impl<T: Copy + Debug + Default + 'static> Transform3D<T> {
103 pub fn from_matrix(mat: [[T; 4]; 4]) -> Self {
105 #[cfg(feature = "glam")]
106 {
107 Self {
108 inner: TransformInner::from_matrix(mat),
109 }
110 }
111 #[cfg(not(feature = "glam"))]
112 {
113 Self { mat }
114 }
115 }
116
117 pub fn to_matrix(self) -> [[T; 4]; 4] {
119 #[cfg(feature = "glam")]
120 {
121 self.inner.to_matrix()
122 }
123 #[cfg(not(feature = "glam"))]
124 {
125 self.mat
126 }
127 }
128
129 #[cfg(not(feature = "glam"))]
131 pub fn mat_mut(&mut self) -> &mut [[T; 4]; 4] {
132 &mut self.mat
133 }
134}
135
136#[cfg(feature = "glam")]
137impl<T: Copy + Debug + Default + 'static> TransformInner<T> {
138 fn from_matrix(mat: [[T; 4]; 4]) -> Self {
139 use std::any::TypeId;
140
141 if TypeId::of::<T>() == TypeId::of::<f32>() {
144 let mat_f32: [[f32; 4]; 4] = unsafe { std::mem::transmute_copy(&mat) };
146 let glam_mat = Mat4::from_cols_array_2d(&mat_f32);
147 let affine = Affine3A::from_mat4(glam_mat);
148 unsafe { std::mem::transmute_copy(&TransformInner::<T>::F32(affine)) }
149 } else if TypeId::of::<T>() == TypeId::of::<f64>() {
150 let mat_f64: [[f64; 4]; 4] = unsafe { std::mem::transmute_copy(&mat) };
152 let glam_mat = DMat4::from_cols_array_2d(&mat_f64);
154 let affine = DAffine3::from_mat4(glam_mat);
155 unsafe { std::mem::transmute_copy(&TransformInner::<T>::F64(affine)) }
156 } else {
157 panic!("Transform3D only supports f32 and f64 types when using glam feature");
158 }
159 }
160
161 fn to_matrix(self) -> [[T; 4]; 4] {
162 match self {
163 TransformInner::F32(affine) => {
164 let mat = Mat4::from(affine);
165 let mat_array = mat.to_cols_array_2d();
166 unsafe { std::mem::transmute_copy(&mat_array) }
167 }
168 TransformInner::F64(affine) => {
169 let mat = DMat4::from(affine);
170 let mat_array = mat.to_cols_array_2d();
171 unsafe { std::mem::transmute_copy(&mat_array) }
172 }
173 TransformInner::_Phantom(_) => unreachable!(),
174 }
175 }
176}
177
178impl Transform3D<f64> {
179 pub fn translation(&self) -> [Length64; 3] {
180 let mat = self.to_matrix();
181 [
182 Length64::new::<meter>(mat[0][3]),
183 Length64::new::<meter>(mat[1][3]),
184 Length64::new::<meter>(mat[2][3]),
185 ]
186 }
187
188 pub fn rotation(&self) -> [[Angle64; 3]; 3] {
189 let mat = self.to_matrix();
190 [
191 [
192 Angle64::new::<radian>(mat[0][0]),
193 Angle64::new::<radian>(mat[0][1]),
194 Angle64::new::<radian>(mat[0][2]),
195 ],
196 [
197 Angle64::new::<radian>(mat[1][0]),
198 Angle64::new::<radian>(mat[1][1]),
199 Angle64::new::<radian>(mat[1][2]),
200 ],
201 [
202 Angle64::new::<radian>(mat[2][0]),
203 Angle64::new::<radian>(mat[2][1]),
204 Angle64::new::<radian>(mat[2][2]),
205 ],
206 ]
207 }
208}
209
210impl Transform3D<f32> {
211 pub fn translation(&self) -> [Length32; 3] {
212 let mat = self.to_matrix();
213 [
214 Length32::new::<meter>(mat[0][3]),
215 Length32::new::<meter>(mat[1][3]),
216 Length32::new::<meter>(mat[2][3]),
217 ]
218 }
219
220 pub fn rotation(&self) -> [[Angle32; 3]; 3] {
221 let mat = self.to_matrix();
222 [
223 [
224 Angle32::new::<radian>(mat[0][0]),
225 Angle32::new::<radian>(mat[0][1]),
226 Angle32::new::<radian>(mat[0][2]),
227 ],
228 [
229 Angle32::new::<radian>(mat[1][0]),
230 Angle32::new::<radian>(mat[1][1]),
231 Angle32::new::<radian>(mat[1][2]),
232 ],
233 [
234 Angle32::new::<radian>(mat[2][0]),
235 Angle32::new::<radian>(mat[2][1]),
236 Angle32::new::<radian>(mat[2][2]),
237 ],
238 ]
239 }
240}
241
242impl<T: Copy + Debug + Default + 'static> Default for Transform3D<T> {
243 fn default() -> Self {
244 Self::from_matrix([[T::default(); 4]; 4])
245 }
246}
247
248impl Mul for Transform3D<f32> {
250 type Output = Self;
251
252 fn mul(self, rhs: Self) -> Self::Output {
253 #[cfg(feature = "glam")]
254 {
255 match (&self.inner, &rhs.inner) {
256 (TransformInner::F32(a), TransformInner::F32(b)) => Self {
257 inner: TransformInner::F32(*a * *b),
258 },
259 _ => unreachable!(),
260 }
261 }
262 #[cfg(not(feature = "glam"))]
263 {
264 let mut result = [[0.0f32; 4]; 4];
265 for i in 0..4 {
266 for j in 0..4 {
267 let mut sum = 0.0;
268 for k in 0..4 {
269 sum += self.mat[i][k] * rhs.mat[k][j];
270 }
271 result[i][j] = sum;
272 }
273 }
274 Self { mat: result }
275 }
276 }
277}
278
279impl Mul for Transform3D<f64> {
281 type Output = Self;
282
283 fn mul(self, rhs: Self) -> Self::Output {
284 #[cfg(feature = "glam")]
285 {
286 match (&self.inner, &rhs.inner) {
287 (TransformInner::F64(a), TransformInner::F64(b)) => Self {
288 inner: TransformInner::F64(*a * *b),
289 },
290 _ => unreachable!(),
291 }
292 }
293 #[cfg(not(feature = "glam"))]
294 {
295 let mut result = [[0.0f64; 4]; 4];
296 for i in 0..4 {
297 for j in 0..4 {
298 let mut sum = 0.0;
299 for k in 0..4 {
300 sum += self.mat[i][k] * rhs.mat[k][j];
301 }
302 result[i][j] = sum;
303 }
304 }
305 Self { mat: result }
306 }
307 }
308}
309
310impl Mul for &Transform3D<f32> {
312 type Output = Transform3D<f32>;
313
314 fn mul(self, rhs: Self) -> Self::Output {
315 *self * *rhs
316 }
317}
318
319impl Mul<Transform3D<f32>> for &Transform3D<f32> {
320 type Output = Transform3D<f32>;
321
322 fn mul(self, rhs: Transform3D<f32>) -> Self::Output {
323 *self * rhs
324 }
325}
326
327impl Mul<&Transform3D<f32>> for Transform3D<f32> {
328 type Output = Transform3D<f32>;
329
330 fn mul(self, rhs: &Transform3D<f32>) -> Self::Output {
331 self * *rhs
332 }
333}
334
335impl Mul for &Transform3D<f64> {
337 type Output = Transform3D<f64>;
338
339 fn mul(self, rhs: Self) -> Self::Output {
340 *self * *rhs
341 }
342}
343
344impl Mul<Transform3D<f64>> for &Transform3D<f64> {
345 type Output = Transform3D<f64>;
346
347 fn mul(self, rhs: Transform3D<f64>) -> Self::Output {
348 *self * rhs
349 }
350}
351
352impl Mul<&Transform3D<f64>> for Transform3D<f64> {
353 type Output = Transform3D<f64>;
354
355 fn mul(self, rhs: &Transform3D<f64>) -> Self::Output {
356 self * *rhs
357 }
358}
359
360impl Transform3D<f32> {
361 pub fn inverse(&self) -> Self {
363 #[cfg(feature = "glam")]
364 {
365 match &self.inner {
366 TransformInner::F32(affine) => Self {
367 inner: TransformInner::F32(affine.inverse()),
368 },
369 _ => unreachable!(),
370 }
371 }
372 #[cfg(not(feature = "glam"))]
373 {
374 let mat = self.mat;
375 let r = [
377 [mat[0][0], mat[0][1], mat[0][2]],
378 [mat[1][0], mat[1][1], mat[1][2]],
379 [mat[2][0], mat[2][1], mat[2][2]],
380 ];
381
382 let t = [mat[0][3], mat[1][3], mat[2][3]];
384
385 let r_inv = [
387 [r[0][0], r[1][0], r[2][0]],
388 [r[0][1], r[1][1], r[2][1]],
389 [r[0][2], r[1][2], r[2][2]],
390 ];
391
392 let t_inv = [
394 -(r_inv[0][0] * t[0] + r_inv[0][1] * t[1] + r_inv[0][2] * t[2]),
395 -(r_inv[1][0] * t[0] + r_inv[1][1] * t[1] + r_inv[1][2] * t[2]),
396 -(r_inv[2][0] * t[0] + r_inv[2][1] * t[1] + r_inv[2][2] * t[2]),
397 ];
398
399 let mut inv_mat = [[0.0f32; 4]; 4];
401
402 for i in 0..3 {
404 for j in 0..3 {
405 inv_mat[i][j] = r_inv[i][j];
406 }
407 }
408
409 inv_mat[0][3] = t_inv[0];
411 inv_mat[1][3] = t_inv[1];
412 inv_mat[2][3] = t_inv[2];
413
414 inv_mat[3][3] = 1.0;
416
417 Self { mat: inv_mat }
418 }
419 }
420}
421
422impl Transform3D<f64> {
423 pub fn inverse(&self) -> Self {
425 #[cfg(feature = "glam")]
426 {
427 match &self.inner {
428 TransformInner::F64(affine) => Self {
429 inner: TransformInner::F64(affine.inverse()),
430 },
431 _ => unreachable!(),
432 }
433 }
434 #[cfg(not(feature = "glam"))]
435 {
436 let mat = self.mat;
437 let r = [
439 [mat[0][0], mat[0][1], mat[0][2]],
440 [mat[1][0], mat[1][1], mat[1][2]],
441 [mat[2][0], mat[2][1], mat[2][2]],
442 ];
443
444 let t = [mat[0][3], mat[1][3], mat[2][3]];
446
447 let r_inv = [
449 [r[0][0], r[1][0], r[2][0]],
450 [r[0][1], r[1][1], r[2][1]],
451 [r[0][2], r[1][2], r[2][2]],
452 ];
453
454 let t_inv = [
456 -(r_inv[0][0] * t[0] + r_inv[0][1] * t[1] + r_inv[0][2] * t[2]),
457 -(r_inv[1][0] * t[0] + r_inv[1][1] * t[1] + r_inv[1][2] * t[2]),
458 -(r_inv[2][0] * t[0] + r_inv[2][1] * t[1] + r_inv[2][2] * t[2]),
459 ];
460
461 let mut inv_mat = [[0.0f64; 4]; 4];
463
464 for i in 0..3 {
466 for j in 0..3 {
467 inv_mat[i][j] = r_inv[i][j];
468 }
469 }
470
471 inv_mat[0][3] = t_inv[0];
473 inv_mat[1][3] = t_inv[1];
474 inv_mat[2][3] = t_inv[2];
475
476 inv_mat[3][3] = 1.0;
478
479 Self { mat: inv_mat }
480 }
481 }
482}
483
484#[cfg(feature = "faer")]
485mod faer_integration {
486 use super::Transform3D;
487 use faer::prelude::*;
488
489 impl From<&Transform3D<f64>> for Mat<f64> {
490 fn from(p: &Transform3D<f64>) -> Self {
491 let mat_array = p.to_matrix();
492 let mut mat: Mat<f64> = Mat::zeros(4, 4);
493 for (r, row) in mat_array.iter().enumerate() {
494 for (c, item) in row.iter().enumerate() {
495 *mat.get_mut(r, c) = *item;
496 }
497 }
498 mat
499 }
500 }
501
502 impl From<Mat<f64>> for Transform3D<f64> {
503 fn from(mat: Mat<f64>) -> Self {
504 assert_eq!(mat.nrows(), 4);
505 assert_eq!(mat.ncols(), 4);
506 let mut transform = [[0.0; 4]; 4];
507 for (r, row) in transform.iter_mut().enumerate() {
508 for (c, val) in row.iter_mut().enumerate() {
509 *val = *mat.get(r, c);
510 }
511 }
512 Self::from_matrix(transform)
513 }
514 }
515}
516
517#[cfg(feature = "nalgebra")]
519mod nalgebra_integration {
520 use super::Transform3D;
521 use nalgebra::{Isometry3, Matrix3, Matrix4, Rotation3, Translation3, Vector3};
522
523 impl From<&Transform3D<f64>> for Isometry3<f64> {
524 fn from(pose: &Transform3D<f64>) -> Self {
525 let mat_array = pose.to_matrix();
526 let flat_transform: [f64; 16] = std::array::from_fn(|i| mat_array[i / 4][i % 4]);
527 let matrix = Matrix4::from_row_slice(&flat_transform);
528
529 let rotation_matrix: Matrix3<f64> = matrix.fixed_view::<3, 3>(0, 0).into();
530 let rotation = Rotation3::from_matrix_unchecked(rotation_matrix);
531
532 let translation_vector: Vector3<f64> = matrix.fixed_view::<3, 1>(0, 3).into();
533 let translation = Translation3::from(translation_vector);
534
535 Isometry3::from_parts(translation, rotation.into())
536 }
537 }
538
539 impl From<Isometry3<f64>> for Transform3D<f64> {
540 fn from(iso: Isometry3<f64>) -> Self {
541 let matrix = iso.to_homogeneous();
542 let transform = std::array::from_fn(|r| std::array::from_fn(|c| matrix[(r, c)]));
543 Transform3D::from_matrix(transform)
544 }
545 }
546}
547
548#[cfg(feature = "glam")]
550mod glam_integration {
551 use super::Transform3D;
552 use glam::{Affine3A, DAffine3};
553
554 impl From<Transform3D<f64>> for DAffine3 {
555 fn from(p: Transform3D<f64>) -> Self {
556 let mat = p.to_matrix();
557 let mut aff = DAffine3::IDENTITY;
558 aff.matrix3.x_axis.x = mat[0][0];
559 aff.matrix3.x_axis.y = mat[0][1];
560 aff.matrix3.x_axis.z = mat[0][2];
561
562 aff.matrix3.y_axis.x = mat[1][0];
563 aff.matrix3.y_axis.y = mat[1][1];
564 aff.matrix3.y_axis.z = mat[1][2];
565
566 aff.matrix3.z_axis.x = mat[2][0];
567 aff.matrix3.z_axis.y = mat[2][1];
568 aff.matrix3.z_axis.z = mat[2][2];
569
570 aff.translation.x = mat[3][0];
571 aff.translation.y = mat[3][1];
572 aff.translation.z = mat[3][2];
573
574 aff
575 }
576 }
577
578 impl From<DAffine3> for Transform3D<f64> {
579 fn from(aff: DAffine3) -> Self {
580 let mut transform = [[0.0f64; 4]; 4];
581
582 transform[0][0] = aff.matrix3.x_axis.x;
583 transform[0][1] = aff.matrix3.x_axis.y;
584 transform[0][2] = aff.matrix3.x_axis.z;
585
586 transform[1][0] = aff.matrix3.y_axis.x;
587 transform[1][1] = aff.matrix3.y_axis.y;
588 transform[1][2] = aff.matrix3.y_axis.z;
589
590 transform[2][0] = aff.matrix3.z_axis.x;
591 transform[2][1] = aff.matrix3.z_axis.y;
592 transform[2][2] = aff.matrix3.z_axis.z;
593
594 transform[3][0] = aff.translation.x;
595 transform[3][1] = aff.translation.y;
596 transform[3][2] = aff.translation.z;
597 transform[3][3] = 1.0;
598
599 Transform3D::from_matrix(transform)
600 }
601 }
602
603 impl From<Transform3D<f32>> for Affine3A {
604 fn from(p: Transform3D<f32>) -> Self {
605 let mat = p.to_matrix();
606 let mut aff = Affine3A::IDENTITY;
607 aff.matrix3.x_axis.x = mat[0][0];
608 aff.matrix3.x_axis.y = mat[0][1];
609 aff.matrix3.x_axis.z = mat[0][2];
610
611 aff.matrix3.y_axis.x = mat[1][0];
612 aff.matrix3.y_axis.y = mat[1][1];
613 aff.matrix3.y_axis.z = mat[1][2];
614
615 aff.matrix3.z_axis.x = mat[2][0];
616 aff.matrix3.z_axis.y = mat[2][1];
617 aff.matrix3.z_axis.z = mat[2][2];
618
619 aff.translation.x = mat[0][3];
620 aff.translation.y = mat[1][3];
621 aff.translation.z = mat[2][3];
622
623 aff
624 }
625 }
626
627 impl From<Affine3A> for Transform3D<f32> {
628 fn from(aff: Affine3A) -> Self {
629 let mut transform = [[0.0f32; 4]; 4];
630
631 transform[0][0] = aff.matrix3.x_axis.x;
632 transform[0][1] = aff.matrix3.x_axis.y;
633 transform[0][2] = aff.matrix3.x_axis.z;
634
635 transform[1][0] = aff.matrix3.y_axis.x;
636 transform[1][1] = aff.matrix3.y_axis.y;
637 transform[1][2] = aff.matrix3.y_axis.z;
638
639 transform[2][0] = aff.matrix3.z_axis.x;
640 transform[2][1] = aff.matrix3.z_axis.y;
641 transform[2][2] = aff.matrix3.z_axis.z;
642
643 transform[0][3] = aff.translation.x;
644 transform[1][3] = aff.translation.y;
645 transform[2][3] = aff.translation.z;
646 transform[3][3] = 1.0;
647
648 Transform3D::from_matrix(transform)
649 }
650 }
651}
652
653#[cfg(feature = "nalgebra")]
654#[allow(unused_imports)]
655pub use nalgebra_integration::*;
656
657#[cfg(feature = "faer")]
658#[allow(unused_imports)]
659pub use faer_integration::*;
660
661#[cfg(test)]
662mod tests {
663 use super::*;
664
665 #[test]
666 fn test_pose_default() {
667 let pose: Transform3D<f32> = Transform3D::default();
668 let mat = pose.to_matrix();
669
670 #[cfg(feature = "glam")]
673 {
674 let expected = [
677 [0.0, 0.0, 0.0, 0.0],
678 [0.0, 0.0, 0.0, 0.0],
679 [0.0, 0.0, 0.0, 0.0],
680 [0.0, 0.0, 0.0, 1.0], ];
682 assert_eq!(mat, expected, "Default pose with glam should have w=1");
683 }
684
685 #[cfg(not(feature = "glam"))]
686 {
687 assert_eq!(
688 mat, [[0.0; 4]; 4],
689 "Default pose without glam should be a zero matrix"
690 );
691 }
692 }
693
694 #[test]
695 fn test_transform_inverse_f32() {
696 let transform = Transform3D::<f32>::from_matrix([
698 [1.0, 0.0, 0.0, 2.0], [0.0, 1.0, 0.0, 3.0], [0.0, 0.0, 1.0, 4.0], [0.0, 0.0, 0.0, 1.0], ]);
703
704 let inverse = transform.inverse();
706
707 let expected_inverse = Transform3D::<f32>::from_matrix([
709 [1.0, 0.0, 0.0, -2.0], [0.0, 1.0, 0.0, -3.0],
711 [0.0, 0.0, 1.0, -4.0],
712 [0.0, 0.0, 0.0, 1.0],
713 ]);
714
715 let epsilon = 1e-5;
717 let inv_mat = inverse.to_matrix();
718 let exp_mat = expected_inverse.to_matrix();
719 for i in 0..4 {
720 for j in 0..4 {
721 assert!(
722 (inv_mat[i][j] - exp_mat[i][j]).abs() < epsilon,
723 "Element at [{},{}] differs: {} vs expected {}",
724 i,
725 j,
726 inv_mat[i][j],
727 exp_mat[i][j]
728 );
729 }
730 }
731 }
732
733 #[test]
734 fn test_transform_inverse_f64() {
735 let transform = Transform3D::<f64>::from_matrix([
737 [0.0, -1.0, 0.0, 5.0], [1.0, 0.0, 0.0, 6.0],
739 [0.0, 0.0, 1.0, 7.0],
740 [0.0, 0.0, 0.0, 1.0],
741 ]);
742
743 let inverse = transform.inverse();
745
746 let expected_inverse = Transform3D::<f64>::from_matrix([
748 [0.0, 1.0, 0.0, -6.0], [-1.0, 0.0, 0.0, 5.0],
750 [0.0, 0.0, 1.0, -7.0],
751 [0.0, 0.0, 0.0, 1.0],
752 ]);
753
754 let epsilon = 1e-10;
756 let inv_mat = inverse.to_matrix();
757 let exp_mat = expected_inverse.to_matrix();
758 for i in 0..4 {
759 for j in 0..4 {
760 assert!(
761 (inv_mat[i][j] - exp_mat[i][j]).abs() < epsilon,
762 "Element at [{},{}] differs: {} vs expected {}",
763 i,
764 j,
765 inv_mat[i][j],
766 exp_mat[i][j]
767 );
768 }
769 }
770 }
771
772 #[test]
773 fn test_transform_inverse_identity() {
774 let identity = Transform3D::<f32>::from_matrix([
776 [1.0, 0.0, 0.0, 0.0],
777 [0.0, 1.0, 0.0, 0.0],
778 [0.0, 0.0, 1.0, 0.0],
779 [0.0, 0.0, 0.0, 1.0],
780 ]);
781
782 let inverse = identity.inverse();
784
785 let epsilon = 1e-5;
787 let inv_mat = inverse.to_matrix();
788 let id_mat = identity.to_matrix();
789 for i in 0..4 {
790 for j in 0..4 {
791 assert!(
792 (inv_mat[i][j] - id_mat[i][j]).abs() < epsilon,
793 "Element at [{},{}] differs: {} vs expected {}",
794 i,
795 j,
796 inv_mat[i][j],
797 id_mat[i][j]
798 );
799 }
800 }
801 }
802
803 #[test]
804 fn test_transform_multiplication_f32() {
805 let t1 = Transform3D::<f32>::from_matrix([
807 [1.0, 0.0, 0.0, 2.0], [0.0, 1.0, 0.0, 3.0],
809 [0.0, 0.0, 1.0, 4.0],
810 [0.0, 0.0, 0.0, 1.0],
811 ]);
812
813 let t2 = Transform3D::<f32>::from_matrix([
814 [0.0, -1.0, 0.0, 5.0], [1.0, 0.0, 0.0, 6.0],
816 [0.0, 0.0, 1.0, 7.0],
817 [0.0, 0.0, 0.0, 1.0],
818 ]);
819
820 let result = t1 * t2;
822
823 let expected = Transform3D::<f32>::from_matrix([
825 [0.0, -1.0, 0.0, 7.0], [1.0, 0.0, 0.0, 9.0],
827 [0.0, 0.0, 1.0, 11.0],
828 [0.0, 0.0, 0.0, 1.0],
829 ]);
830
831 let epsilon = 1e-5;
833 let res_mat = result.to_matrix();
834 let exp_mat = expected.to_matrix();
835 for i in 0..4 {
836 for j in 0..4 {
837 assert!(
838 (res_mat[i][j] - exp_mat[i][j]).abs() < epsilon,
839 "Element at [{},{}] differs: {} vs expected {}",
840 i,
841 j,
842 res_mat[i][j],
843 exp_mat[i][j]
844 );
845 }
846 }
847 }
848
849 #[test]
850 fn test_transform_multiplication_f64() {
851 let t1 = Transform3D::<f64>::from_matrix([
853 [1.0, 0.0, 0.0, 2.0], [0.0, 1.0, 0.0, 3.0],
855 [0.0, 0.0, 1.0, 4.0],
856 [0.0, 0.0, 0.0, 1.0],
857 ]);
858
859 let t2 = Transform3D::<f64>::from_matrix([
860 [0.0, -1.0, 0.0, 5.0], [1.0, 0.0, 0.0, 6.0],
862 [0.0, 0.0, 1.0, 7.0],
863 [0.0, 0.0, 0.0, 1.0],
864 ]);
865
866 let result = t1 * t2;
868
869 let expected = Transform3D::<f64>::from_matrix([
871 [0.0, -1.0, 0.0, 7.0], [1.0, 0.0, 0.0, 9.0],
873 [0.0, 0.0, 1.0, 11.0],
874 [0.0, 0.0, 0.0, 1.0],
875 ]);
876
877 let epsilon = 1e-10;
879 let res_mat = result.to_matrix();
880 let exp_mat = expected.to_matrix();
881 for i in 0..4 {
882 for j in 0..4 {
883 assert!(
884 (res_mat[i][j] - exp_mat[i][j]).abs() < epsilon,
885 "Element at [{},{}] differs: {} vs expected {}",
886 i,
887 j,
888 res_mat[i][j],
889 exp_mat[i][j]
890 );
891 }
892 }
893 }
894
895 #[test]
896 fn test_transform_reference_multiplication() {
897 let t1 = Transform3D::<f32>::from_matrix([
899 [1.0, 0.0, 0.0, 2.0],
900 [0.0, 1.0, 0.0, 3.0],
901 [0.0, 0.0, 1.0, 4.0],
902 [0.0, 0.0, 0.0, 1.0],
903 ]);
904
905 let t2 = Transform3D::<f32>::from_matrix([
906 [0.0, -1.0, 0.0, 5.0],
907 [1.0, 0.0, 0.0, 6.0],
908 [0.0, 0.0, 1.0, 7.0],
909 [0.0, 0.0, 0.0, 1.0],
910 ]);
911
912 let result = t1 * t2;
914
915 let expected = Transform3D::<f32>::from_matrix([
917 [0.0, -1.0, 0.0, 7.0],
918 [1.0, 0.0, 0.0, 9.0],
919 [0.0, 0.0, 1.0, 11.0],
920 [0.0, 0.0, 0.0, 1.0],
921 ]);
922
923 let epsilon = 1e-5;
925 let res_mat = result.to_matrix();
926 let exp_mat = expected.to_matrix();
927 for i in 0..4 {
928 for j in 0..4 {
929 assert!(
930 (res_mat[i][j] - exp_mat[i][j]).abs() < epsilon,
931 "Element at [{},{}] differs: {} vs expected {}",
932 i,
933 j,
934 res_mat[i][j],
935 exp_mat[i][j]
936 );
937 }
938 }
939 }
940
941 #[cfg(feature = "faer")]
942 #[test]
943 fn test_pose_faer_conversion() {
944 use faer::prelude::*;
945
946 let pose = Transform3D::from_matrix([
947 [1.0, 2.0, 3.0, 4.0],
948 [5.0, 6.0, 7.0, 8.0],
949 [9.0, 10.0, 11.0, 12.0],
950 [13.0, 14.0, 15.0, 16.0],
951 ]);
952
953 let mat: Mat<f64> = (&pose).into();
954 let pose_from_mat = Transform3D::from(mat);
955
956 assert_eq!(
957 pose.to_matrix(),
958 pose_from_mat.to_matrix(),
959 "Faer conversion should be lossless"
960 );
961 }
962
963 #[cfg(feature = "nalgebra")]
964 #[test]
965 fn test_pose_nalgebra_conversion() {
966 use nalgebra::Isometry3;
967
968 let pose = Transform3D::from_matrix([
969 [1.0, 0.0, 0.0, 2.0],
970 [0.0, 1.0, 0.0, 3.0],
971 [0.0, 0.0, 1.0, 4.0],
972 [0.0, 0.0, 0.0, 1.0],
973 ]);
974
975 let iso: Isometry3<f64> = (&pose.clone()).into();
976 let pose_from_iso: Transform3D<f64> = iso.into();
977
978 assert_eq!(
979 pose.to_matrix(),
980 pose_from_iso.to_matrix(),
981 "Nalgebra conversion should be lossless"
982 );
983 }
984
985 #[cfg(feature = "glam")]
986 #[test]
987 fn test_pose_glam_conversion() {
988 use glam::DAffine3;
989
990 let pose = Transform3D::from_matrix([
991 [1.0, 0.0, 0.0, 0.0],
992 [0.0, 1.0, 0.0, 0.0],
993 [0.0, 0.0, 1.0, 0.0],
994 [5.0, 6.0, 7.0, 1.0],
995 ]);
996 let aff: DAffine3 = pose.into();
997 assert_eq!(aff.translation[0], 5.0);
998 let pose_from_aff: Transform3D<f64> = aff.into();
999
1000 assert_eq!(
1001 pose.to_matrix(),
1002 pose_from_aff.to_matrix(),
1003 "Glam conversion should be lossless"
1004 );
1005 }
1006
1007 #[cfg(feature = "glam")]
1008 #[test]
1009 fn test_matrix_format_issue() {
1010 use glam::Mat4;
1011
1012 let row_major = [
1014 [1.0, 0.0, 0.0, 5.0], [0.0, 1.0, 0.0, 6.0], [0.0, 0.0, 1.0, 7.0], [0.0, 0.0, 0.0, 1.0], ];
1019
1020 let col_major = [
1023 [1.0, 0.0, 0.0, 0.0], [0.0, 1.0, 0.0, 0.0], [0.0, 0.0, 1.0, 0.0], [5.0, 6.0, 7.0, 1.0], ];
1028
1029 let mat_from_row = Mat4::from_cols_array_2d(&row_major);
1031 let mat_from_col = Mat4::from_cols_array_2d(&col_major);
1032
1033 assert_ne!(mat_from_row.w_axis.x, 5.0); assert_eq!(mat_from_col.w_axis.x, 5.0);
1038 assert_eq!(mat_from_col.w_axis.y, 6.0);
1039 assert_eq!(mat_from_col.w_axis.z, 7.0);
1040
1041 let mat_transposed = Mat4::from_cols_array_2d(&row_major).transpose();
1043 assert_eq!(mat_transposed.w_axis.x, 5.0);
1044 assert_eq!(mat_transposed.w_axis.y, 6.0);
1045 assert_eq!(mat_transposed.w_axis.z, 7.0);
1046 }
1047}