1use bincode::{Decode, Encode};
2use core::fmt::Debug;
3use core::ops::Mul;
4use cu29::prelude::*;
5use cu29::units::si::angle::radian;
6use cu29::units::si::f32::Angle as Angle32;
7use cu29::units::si::f32::Length as Length32;
8use cu29::units::si::f64::Angle as Angle64;
9use cu29::units::si::f64::Length as Length64;
10use cu29::units::si::length::meter;
11use serde::{Deserialize, Serialize};
12
13#[cfg(feature = "glam")]
14use glam::{Affine3A, DAffine3, DMat4, Mat4};
15
16#[cfg(feature = "rerun")]
17mod rerun_components;
18
19#[derive(Debug, Clone, Copy, Reflect)]
22#[reflect(opaque, from_reflect = false)]
23pub struct Transform3D<T: Copy + Debug + 'static> {
24 #[cfg(feature = "glam")]
25 inner: TransformInner<T>,
26 #[cfg(not(feature = "glam"))]
27 pub mat: [[T; 4]; 4],
28}
29
30#[cfg(feature = "glam")]
31#[derive(Debug, Clone, Copy)]
32enum TransformInner<T: Copy + Debug + 'static> {
33 F32(Affine3A),
34 F64(DAffine3),
35 _Phantom(std::marker::PhantomData<T>),
36}
37
38pub type Pose<T> = Transform3D<T>;
39
40macro_rules! impl_transform_accessors {
41 ($ty:ty, $len:ty, $ang:ty) => {
42 impl Transform3D<$ty> {
43 pub fn translation(&self) -> [$len; 3] {
44 let mat = self.to_matrix();
45 [
46 <$len>::new::<meter>(mat[0][3]),
47 <$len>::new::<meter>(mat[1][3]),
48 <$len>::new::<meter>(mat[2][3]),
49 ]
50 }
51
52 pub fn rotation(&self) -> [[$ang; 3]; 3] {
53 let mat = self.to_matrix();
54 [
55 [
56 <$ang>::new::<radian>(mat[0][0]),
57 <$ang>::new::<radian>(mat[0][1]),
58 <$ang>::new::<radian>(mat[0][2]),
59 ],
60 [
61 <$ang>::new::<radian>(mat[1][0]),
62 <$ang>::new::<radian>(mat[1][1]),
63 <$ang>::new::<radian>(mat[1][2]),
64 ],
65 [
66 <$ang>::new::<radian>(mat[2][0]),
67 <$ang>::new::<radian>(mat[2][1]),
68 <$ang>::new::<radian>(mat[2][2]),
69 ],
70 ]
71 }
72 }
73 };
74}
75
76impl<T: Copy + Debug + Default + 'static> Serialize for Transform3D<T>
78where
79 T: Serialize,
80{
81 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
82 where
83 S: serde::Serializer,
84 {
85 #[cfg(feature = "glam")]
86 {
87 let mat = self.to_matrix();
88 mat.serialize(serializer)
89 }
90 #[cfg(not(feature = "glam"))]
91 {
92 self.mat.serialize(serializer)
93 }
94 }
95}
96
97impl<'de, T: Copy + Debug + 'static> Deserialize<'de> for Transform3D<T>
98where
99 T: Deserialize<'de> + Default,
100{
101 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
102 where
103 D: serde::Deserializer<'de>,
104 {
105 let mat: [[T; 4]; 4] = Deserialize::deserialize(deserializer)?;
106 Ok(Self::from_matrix(mat))
107 }
108}
109
110impl<T: Copy + Debug + Default + 'static> Encode for Transform3D<T>
112where
113 T: Encode,
114{
115 fn encode<E: bincode::enc::Encoder>(
116 &self,
117 encoder: &mut E,
118 ) -> Result<(), bincode::error::EncodeError> {
119 #[cfg(feature = "glam")]
120 {
121 let mat = self.to_matrix();
122 mat.encode(encoder)
123 }
124 #[cfg(not(feature = "glam"))]
125 {
126 self.mat.encode(encoder)
127 }
128 }
129}
130
131impl<T: Copy + Debug + 'static> Decode<()> for Transform3D<T>
132where
133 T: Decode<()> + Default,
134{
135 fn decode<D: bincode::de::Decoder<Context = ()>>(
136 decoder: &mut D,
137 ) -> Result<Self, bincode::error::DecodeError> {
138 let mat: [[T; 4]; 4] = Decode::decode(decoder)?;
139 Ok(Self::from_matrix(mat))
140 }
141}
142
143impl<T: Copy + Debug + Default + 'static> Transform3D<T> {
144 pub fn from_matrix(mat: [[T; 4]; 4]) -> Self {
146 #[cfg(feature = "glam")]
147 {
148 Self {
149 inner: TransformInner::from_matrix(mat),
150 }
151 }
152 #[cfg(not(feature = "glam"))]
153 {
154 Self { mat }
155 }
156 }
157
158 pub fn to_matrix(self) -> [[T; 4]; 4] {
160 #[cfg(feature = "glam")]
161 {
162 self.inner.to_matrix()
163 }
164 #[cfg(not(feature = "glam"))]
165 {
166 self.mat
167 }
168 }
169
170 #[cfg(not(feature = "glam"))]
172 pub fn mat_mut(&mut self) -> &mut [[T; 4]; 4] {
173 &mut self.mat
174 }
175}
176
177#[cfg(feature = "glam")]
178impl<T: Copy + Debug + Default + 'static> TransformInner<T> {
179 fn from_matrix(mat: [[T; 4]; 4]) -> Self {
180 use std::any::TypeId;
181
182 if TypeId::of::<T>() == TypeId::of::<f32>() {
185 let mat_f32: [[f32; 4]; 4] = unsafe { std::mem::transmute_copy(&mat) };
188 let glam_mat = Mat4::from_cols_array_2d(&mat_f32);
189 let affine = Affine3A::from_mat4(glam_mat);
190 unsafe { std::mem::transmute_copy(&TransformInner::<T>::F32(affine)) }
192 } else if TypeId::of::<T>() == TypeId::of::<f64>() {
193 let mat_f64: [[f64; 4]; 4] = unsafe { std::mem::transmute_copy(&mat) };
196 let glam_mat = DMat4::from_cols_array_2d(&mat_f64);
198 let affine = DAffine3::from_mat4(glam_mat);
199 unsafe { std::mem::transmute_copy(&TransformInner::<T>::F64(affine)) }
201 } else {
202 panic!("Transform3D only supports f32 and f64 types when using glam feature");
203 }
204 }
205
206 fn to_matrix(self) -> [[T; 4]; 4] {
207 match self {
208 TransformInner::F32(affine) => {
209 let mat = Mat4::from(affine);
210 let mat_array = mat.to_cols_array_2d();
211 unsafe { std::mem::transmute_copy(&mat_array) }
213 }
214 TransformInner::F64(affine) => {
215 let mat = DMat4::from(affine);
216 let mat_array = mat.to_cols_array_2d();
217 unsafe { std::mem::transmute_copy(&mat_array) }
219 }
220 TransformInner::_Phantom(_) => unreachable!(),
221 }
222 }
223}
224
225impl_transform_accessors!(f32, Length32, Angle32);
226impl_transform_accessors!(f64, Length64, Angle64);
227
228impl<T: Copy + Debug + Default + 'static> Default for Transform3D<T> {
229 fn default() -> Self {
230 Self::from_matrix([[T::default(); 4]; 4])
231 }
232}
233
234macro_rules! impl_transform_mul {
235 ($ty:ty, $zero:expr, $variant:ident) => {
236 impl Mul for Transform3D<$ty> {
237 type Output = Self;
238
239 fn mul(self, rhs: Self) -> Self::Output {
240 #[cfg(feature = "glam")]
241 {
242 match (&self.inner, &rhs.inner) {
243 (TransformInner::$variant(a), TransformInner::$variant(b)) => Self {
244 inner: TransformInner::$variant(*a * *b),
245 },
246 _ => unreachable!(),
247 }
248 }
249 #[cfg(not(feature = "glam"))]
250 {
251 let mut result = [[$zero; 4]; 4];
252 for i in 0..4 {
253 for j in 0..4 {
254 let mut sum = $zero;
255 for k in 0..4 {
256 sum += self.mat[i][k] * rhs.mat[k][j];
257 }
258 result[i][j] = sum;
259 }
260 }
261 Self { mat: result }
262 }
263 }
264 }
265 };
266}
267
268impl_transform_mul!(f32, 0.0f32, F32);
269impl_transform_mul!(f64, 0.0f64, F64);
270
271impl Mul for &Transform3D<f32> {
273 type Output = Transform3D<f32>;
274
275 fn mul(self, rhs: Self) -> Self::Output {
276 *self * *rhs
277 }
278}
279
280impl Mul<Transform3D<f32>> for &Transform3D<f32> {
281 type Output = Transform3D<f32>;
282
283 fn mul(self, rhs: Transform3D<f32>) -> Self::Output {
284 *self * rhs
285 }
286}
287
288impl Mul<&Transform3D<f32>> for Transform3D<f32> {
289 type Output = Transform3D<f32>;
290
291 fn mul(self, rhs: &Transform3D<f32>) -> Self::Output {
292 self * *rhs
293 }
294}
295
296impl Mul for &Transform3D<f64> {
298 type Output = Transform3D<f64>;
299
300 fn mul(self, rhs: Self) -> Self::Output {
301 *self * *rhs
302 }
303}
304
305impl Mul<Transform3D<f64>> for &Transform3D<f64> {
306 type Output = Transform3D<f64>;
307
308 fn mul(self, rhs: Transform3D<f64>) -> Self::Output {
309 *self * rhs
310 }
311}
312
313impl Mul<&Transform3D<f64>> for Transform3D<f64> {
314 type Output = Transform3D<f64>;
315
316 fn mul(self, rhs: &Transform3D<f64>) -> Self::Output {
317 self * *rhs
318 }
319}
320
321macro_rules! impl_transform_inverse {
322 ($ty:ty, $zero:expr, $one:expr, $variant:ident) => {
323 impl Transform3D<$ty> {
324 pub fn inverse(&self) -> Self {
326 #[cfg(feature = "glam")]
327 {
328 match &self.inner {
329 TransformInner::$variant(affine) => Self {
330 inner: TransformInner::$variant(affine.inverse()),
331 },
332 _ => unreachable!(),
333 }
334 }
335 #[cfg(not(feature = "glam"))]
336 {
337 let mat = self.mat;
338 let r = [
340 [mat[0][0], mat[0][1], mat[0][2]],
341 [mat[1][0], mat[1][1], mat[1][2]],
342 [mat[2][0], mat[2][1], mat[2][2]],
343 ];
344
345 let t = [mat[0][3], mat[1][3], mat[2][3]];
347
348 let r_inv = [
350 [r[0][0], r[1][0], r[2][0]],
351 [r[0][1], r[1][1], r[2][1]],
352 [r[0][2], r[1][2], r[2][2]],
353 ];
354
355 let t_inv = [
357 -(r_inv[0][0] * t[0] + r_inv[0][1] * t[1] + r_inv[0][2] * t[2]),
358 -(r_inv[1][0] * t[0] + r_inv[1][1] * t[1] + r_inv[1][2] * t[2]),
359 -(r_inv[2][0] * t[0] + r_inv[2][1] * t[1] + r_inv[2][2] * t[2]),
360 ];
361
362 let mut inv_mat = [[$zero; 4]; 4];
364
365 for i in 0..3 {
367 for j in 0..3 {
368 inv_mat[i][j] = r_inv[i][j];
369 }
370 }
371
372 inv_mat[0][3] = t_inv[0];
374 inv_mat[1][3] = t_inv[1];
375 inv_mat[2][3] = t_inv[2];
376
377 inv_mat[3][3] = $one;
379
380 Self { mat: inv_mat }
381 }
382 }
383 }
384 };
385}
386
387impl_transform_inverse!(f32, 0.0f32, 1.0f32, F32);
388impl_transform_inverse!(f64, 0.0f64, 1.0f64, F64);
389
390#[cfg(feature = "faer")]
391mod faer_integration {
392 use super::Transform3D;
393 use faer::prelude::*;
394
395 impl From<&Transform3D<f64>> for Mat<f64> {
396 fn from(p: &Transform3D<f64>) -> Self {
397 let mat_array = p.to_matrix();
398 let mut mat: Mat<f64> = Mat::zeros(4, 4);
399 for (r, row) in mat_array.iter().enumerate() {
400 for (c, item) in row.iter().enumerate() {
401 *mat.get_mut(r, c) = *item;
402 }
403 }
404 mat
405 }
406 }
407
408 impl From<Mat<f64>> for Transform3D<f64> {
409 fn from(mat: Mat<f64>) -> Self {
410 assert_eq!(mat.nrows(), 4);
411 assert_eq!(mat.ncols(), 4);
412 let mut transform = [[0.0; 4]; 4];
413 for (r, row) in transform.iter_mut().enumerate() {
414 for (c, val) in row.iter_mut().enumerate() {
415 *val = *mat.get(r, c);
416 }
417 }
418 Self::from_matrix(transform)
419 }
420 }
421}
422
423#[cfg(feature = "nalgebra")]
425mod nalgebra_integration {
426 use super::Transform3D;
427 use nalgebra::{Isometry3, Matrix3, Matrix4, Rotation3, Translation3, Vector3};
428
429 impl From<&Transform3D<f64>> for Isometry3<f64> {
430 fn from(pose: &Transform3D<f64>) -> Self {
431 let mat_array = pose.to_matrix();
432 let flat_transform: [f64; 16] = std::array::from_fn(|i| mat_array[i / 4][i % 4]);
433 let matrix = Matrix4::from_row_slice(&flat_transform);
434
435 let rotation_matrix: Matrix3<f64> = matrix.fixed_view::<3, 3>(0, 0).into();
436 let rotation = Rotation3::from_matrix_unchecked(rotation_matrix);
437
438 let translation_vector: Vector3<f64> = matrix.fixed_view::<3, 1>(0, 3).into();
439 let translation = Translation3::from(translation_vector);
440
441 Isometry3::from_parts(translation, rotation.into())
442 }
443 }
444
445 impl From<Isometry3<f64>> for Transform3D<f64> {
446 fn from(iso: Isometry3<f64>) -> Self {
447 let matrix = iso.to_homogeneous();
448 let transform = std::array::from_fn(|r| std::array::from_fn(|c| matrix[(r, c)]));
449 Transform3D::from_matrix(transform)
450 }
451 }
452}
453
454#[cfg(feature = "glam")]
456mod glam_integration {
457 use super::Transform3D;
458 use glam::{Affine3A, DAffine3};
459
460 impl From<Transform3D<f64>> for DAffine3 {
461 fn from(p: Transform3D<f64>) -> Self {
462 let mat = p.to_matrix();
463 let mut aff = DAffine3::IDENTITY;
464 aff.matrix3.x_axis.x = mat[0][0];
465 aff.matrix3.x_axis.y = mat[0][1];
466 aff.matrix3.x_axis.z = mat[0][2];
467
468 aff.matrix3.y_axis.x = mat[1][0];
469 aff.matrix3.y_axis.y = mat[1][1];
470 aff.matrix3.y_axis.z = mat[1][2];
471
472 aff.matrix3.z_axis.x = mat[2][0];
473 aff.matrix3.z_axis.y = mat[2][1];
474 aff.matrix3.z_axis.z = mat[2][2];
475
476 aff.translation.x = mat[3][0];
477 aff.translation.y = mat[3][1];
478 aff.translation.z = mat[3][2];
479
480 aff
481 }
482 }
483
484 impl From<DAffine3> for Transform3D<f64> {
485 fn from(aff: DAffine3) -> Self {
486 let mut transform = [[0.0f64; 4]; 4];
487
488 transform[0][0] = aff.matrix3.x_axis.x;
489 transform[0][1] = aff.matrix3.x_axis.y;
490 transform[0][2] = aff.matrix3.x_axis.z;
491
492 transform[1][0] = aff.matrix3.y_axis.x;
493 transform[1][1] = aff.matrix3.y_axis.y;
494 transform[1][2] = aff.matrix3.y_axis.z;
495
496 transform[2][0] = aff.matrix3.z_axis.x;
497 transform[2][1] = aff.matrix3.z_axis.y;
498 transform[2][2] = aff.matrix3.z_axis.z;
499
500 transform[3][0] = aff.translation.x;
501 transform[3][1] = aff.translation.y;
502 transform[3][2] = aff.translation.z;
503 transform[3][3] = 1.0;
504
505 Transform3D::from_matrix(transform)
506 }
507 }
508
509 impl From<Transform3D<f32>> for Affine3A {
510 fn from(p: Transform3D<f32>) -> Self {
511 let mat = p.to_matrix();
512 let mut aff = Affine3A::IDENTITY;
513 aff.matrix3.x_axis.x = mat[0][0];
514 aff.matrix3.x_axis.y = mat[0][1];
515 aff.matrix3.x_axis.z = mat[0][2];
516
517 aff.matrix3.y_axis.x = mat[1][0];
518 aff.matrix3.y_axis.y = mat[1][1];
519 aff.matrix3.y_axis.z = mat[1][2];
520
521 aff.matrix3.z_axis.x = mat[2][0];
522 aff.matrix3.z_axis.y = mat[2][1];
523 aff.matrix3.z_axis.z = mat[2][2];
524
525 aff.translation.x = mat[0][3];
526 aff.translation.y = mat[1][3];
527 aff.translation.z = mat[2][3];
528
529 aff
530 }
531 }
532
533 impl From<Affine3A> for Transform3D<f32> {
534 fn from(aff: Affine3A) -> Self {
535 let mut transform = [[0.0f32; 4]; 4];
536
537 transform[0][0] = aff.matrix3.x_axis.x;
538 transform[0][1] = aff.matrix3.x_axis.y;
539 transform[0][2] = aff.matrix3.x_axis.z;
540
541 transform[1][0] = aff.matrix3.y_axis.x;
542 transform[1][1] = aff.matrix3.y_axis.y;
543 transform[1][2] = aff.matrix3.y_axis.z;
544
545 transform[2][0] = aff.matrix3.z_axis.x;
546 transform[2][1] = aff.matrix3.z_axis.y;
547 transform[2][2] = aff.matrix3.z_axis.z;
548
549 transform[0][3] = aff.translation.x;
550 transform[1][3] = aff.translation.y;
551 transform[2][3] = aff.translation.z;
552 transform[3][3] = 1.0;
553
554 Transform3D::from_matrix(transform)
555 }
556 }
557}
558
559#[cfg(feature = "nalgebra")]
560#[allow(unused_imports)]
561pub use nalgebra_integration::*;
562
563#[cfg(feature = "faer")]
564#[allow(unused_imports)]
565pub use faer_integration::*;
566
567#[cfg(test)]
568mod tests {
569 use super::*;
570
571 fn assert_matrix_close<const N: usize, T: Copy + Into<f64>>(
572 lhs: [[T; N]; N],
573 rhs: [[T; N]; N],
574 eps: f64,
575 ) {
576 for i in 0..N {
577 for j in 0..N {
578 let lhs = lhs[i][j].into();
579 let rhs = rhs[i][j].into();
580 assert!(
581 (lhs - rhs).abs() <= eps,
582 "Element at [{},{}] differs: {} vs expected {}",
583 i,
584 j,
585 lhs,
586 rhs
587 );
588 }
589 }
590 }
591
592 #[test]
593 fn test_pose_default() {
594 let pose: Transform3D<f32> = Transform3D::default();
595 let mat = pose.to_matrix();
596
597 #[cfg(feature = "glam")]
600 {
601 let expected = [
604 [0.0, 0.0, 0.0, 0.0],
605 [0.0, 0.0, 0.0, 0.0],
606 [0.0, 0.0, 0.0, 0.0],
607 [0.0, 0.0, 0.0, 1.0], ];
609 assert_eq!(mat, expected, "Default pose with glam should have w=1");
610 }
611
612 #[cfg(not(feature = "glam"))]
613 {
614 assert_eq!(
615 mat, [[0.0; 4]; 4],
616 "Default pose without glam should be a zero matrix"
617 );
618 }
619 }
620
621 #[test]
622 fn test_transform_inverse_f32() {
623 let transform = Transform3D::<f32>::from_matrix([
625 [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], ]);
630
631 let inverse = transform.inverse();
633
634 let expected_inverse = Transform3D::<f32>::from_matrix([
636 [1.0, 0.0, 0.0, -2.0], [0.0, 1.0, 0.0, -3.0],
638 [0.0, 0.0, 1.0, -4.0],
639 [0.0, 0.0, 0.0, 1.0],
640 ]);
641
642 let epsilon = 1e-5;
644 let inv_mat = inverse.to_matrix();
645 let exp_mat = expected_inverse.to_matrix();
646 assert_matrix_close(inv_mat, exp_mat, epsilon);
647 }
648
649 #[test]
650 fn test_transform_inverse_f64() {
651 let transform = Transform3D::<f64>::from_matrix([
653 [0.0, -1.0, 0.0, 5.0], [1.0, 0.0, 0.0, 6.0],
655 [0.0, 0.0, 1.0, 7.0],
656 [0.0, 0.0, 0.0, 1.0],
657 ]);
658
659 let inverse = transform.inverse();
661
662 let expected_inverse = Transform3D::<f64>::from_matrix([
664 [0.0, 1.0, 0.0, -6.0], [-1.0, 0.0, 0.0, 5.0],
666 [0.0, 0.0, 1.0, -7.0],
667 [0.0, 0.0, 0.0, 1.0],
668 ]);
669
670 let epsilon = 1e-10;
672 let inv_mat = inverse.to_matrix();
673 let exp_mat = expected_inverse.to_matrix();
674 assert_matrix_close(inv_mat, exp_mat, epsilon);
675 }
676
677 #[test]
678 fn test_transform_inverse_identity() {
679 let identity = Transform3D::<f32>::from_matrix([
681 [1.0, 0.0, 0.0, 0.0],
682 [0.0, 1.0, 0.0, 0.0],
683 [0.0, 0.0, 1.0, 0.0],
684 [0.0, 0.0, 0.0, 1.0],
685 ]);
686
687 let inverse = identity.inverse();
689
690 let epsilon = 1e-5;
692 let inv_mat = inverse.to_matrix();
693 let id_mat = identity.to_matrix();
694 assert_matrix_close(inv_mat, id_mat, epsilon);
695 }
696
697 #[test]
698 fn test_transform_multiplication_f32() {
699 let t1 = Transform3D::<f32>::from_matrix([
701 [1.0, 0.0, 0.0, 2.0], [0.0, 1.0, 0.0, 3.0],
703 [0.0, 0.0, 1.0, 4.0],
704 [0.0, 0.0, 0.0, 1.0],
705 ]);
706
707 let t2 = Transform3D::<f32>::from_matrix([
708 [0.0, -1.0, 0.0, 5.0], [1.0, 0.0, 0.0, 6.0],
710 [0.0, 0.0, 1.0, 7.0],
711 [0.0, 0.0, 0.0, 1.0],
712 ]);
713
714 let result = t1 * t2;
716
717 let expected = Transform3D::<f32>::from_matrix([
719 [0.0, -1.0, 0.0, 7.0], [1.0, 0.0, 0.0, 9.0],
721 [0.0, 0.0, 1.0, 11.0],
722 [0.0, 0.0, 0.0, 1.0],
723 ]);
724
725 let epsilon = 1e-5;
727 let res_mat = result.to_matrix();
728 let exp_mat = expected.to_matrix();
729 assert_matrix_close(res_mat, exp_mat, epsilon);
730 }
731
732 #[test]
733 fn test_transform_multiplication_f64() {
734 let t1 = Transform3D::<f64>::from_matrix([
736 [1.0, 0.0, 0.0, 2.0], [0.0, 1.0, 0.0, 3.0],
738 [0.0, 0.0, 1.0, 4.0],
739 [0.0, 0.0, 0.0, 1.0],
740 ]);
741
742 let t2 = Transform3D::<f64>::from_matrix([
743 [0.0, -1.0, 0.0, 5.0], [1.0, 0.0, 0.0, 6.0],
745 [0.0, 0.0, 1.0, 7.0],
746 [0.0, 0.0, 0.0, 1.0],
747 ]);
748
749 let result = t1 * t2;
751
752 let expected = Transform3D::<f64>::from_matrix([
754 [0.0, -1.0, 0.0, 7.0], [1.0, 0.0, 0.0, 9.0],
756 [0.0, 0.0, 1.0, 11.0],
757 [0.0, 0.0, 0.0, 1.0],
758 ]);
759
760 let epsilon = 1e-10;
762 let res_mat = result.to_matrix();
763 let exp_mat = expected.to_matrix();
764 assert_matrix_close(res_mat, exp_mat, epsilon);
765 }
766
767 #[test]
768 fn test_transform_reference_multiplication() {
769 let t1 = Transform3D::<f32>::from_matrix([
771 [1.0, 0.0, 0.0, 2.0],
772 [0.0, 1.0, 0.0, 3.0],
773 [0.0, 0.0, 1.0, 4.0],
774 [0.0, 0.0, 0.0, 1.0],
775 ]);
776
777 let t2 = Transform3D::<f32>::from_matrix([
778 [0.0, -1.0, 0.0, 5.0],
779 [1.0, 0.0, 0.0, 6.0],
780 [0.0, 0.0, 1.0, 7.0],
781 [0.0, 0.0, 0.0, 1.0],
782 ]);
783
784 let result = t1 * t2;
786
787 let expected = Transform3D::<f32>::from_matrix([
789 [0.0, -1.0, 0.0, 7.0],
790 [1.0, 0.0, 0.0, 9.0],
791 [0.0, 0.0, 1.0, 11.0],
792 [0.0, 0.0, 0.0, 1.0],
793 ]);
794
795 let epsilon = 1e-5;
797 let res_mat = result.to_matrix();
798 let exp_mat = expected.to_matrix();
799 assert_matrix_close(res_mat, exp_mat, epsilon);
800 }
801
802 #[cfg(feature = "faer")]
803 #[test]
804 fn test_pose_faer_conversion() {
805 use faer::prelude::*;
806
807 let pose = Transform3D::from_matrix([
808 [1.0, 2.0, 3.0, 4.0],
809 [5.0, 6.0, 7.0, 8.0],
810 [9.0, 10.0, 11.0, 12.0],
811 [13.0, 14.0, 15.0, 16.0],
812 ]);
813
814 let mat: Mat<f64> = (&pose).into();
815 let pose_from_mat = Transform3D::from(mat);
816
817 assert_eq!(
818 pose.to_matrix(),
819 pose_from_mat.to_matrix(),
820 "Faer conversion should be lossless"
821 );
822 }
823
824 #[cfg(feature = "nalgebra")]
825 #[test]
826 fn test_pose_nalgebra_conversion() {
827 use nalgebra::Isometry3;
828
829 let pose = Transform3D::from_matrix([
830 [1.0, 0.0, 0.0, 2.0],
831 [0.0, 1.0, 0.0, 3.0],
832 [0.0, 0.0, 1.0, 4.0],
833 [0.0, 0.0, 0.0, 1.0],
834 ]);
835
836 let iso: Isometry3<f64> = (&pose.clone()).into();
837 let pose_from_iso: Transform3D<f64> = iso.into();
838
839 assert_eq!(
840 pose.to_matrix(),
841 pose_from_iso.to_matrix(),
842 "Nalgebra conversion should be lossless"
843 );
844 }
845
846 #[cfg(feature = "glam")]
847 #[test]
848 fn test_pose_glam_conversion() {
849 use glam::DAffine3;
850
851 let pose = Transform3D::from_matrix([
852 [1.0, 0.0, 0.0, 0.0],
853 [0.0, 1.0, 0.0, 0.0],
854 [0.0, 0.0, 1.0, 0.0],
855 [5.0, 6.0, 7.0, 1.0],
856 ]);
857 let aff: DAffine3 = pose.into();
858 assert_eq!(aff.translation[0], 5.0);
859 let pose_from_aff: Transform3D<f64> = aff.into();
860
861 assert_eq!(
862 pose.to_matrix(),
863 pose_from_aff.to_matrix(),
864 "Glam conversion should be lossless"
865 );
866 }
867
868 #[cfg(feature = "glam")]
869 #[test]
870 fn test_matrix_format_issue() {
871 use glam::Mat4;
872
873 let row_major = [
875 [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], ];
880
881 let col_major = [
884 [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], ];
889
890 let mat_from_row = Mat4::from_cols_array_2d(&row_major);
892 let mat_from_col = Mat4::from_cols_array_2d(&col_major);
893
894 assert_ne!(mat_from_row.w_axis.x, 5.0); assert_eq!(mat_from_col.w_axis.x, 5.0);
899 assert_eq!(mat_from_col.w_axis.y, 6.0);
900 assert_eq!(mat_from_col.w_axis.z, 7.0);
901
902 let mat_transposed = Mat4::from_cols_array_2d(&row_major).transpose();
904 assert_eq!(mat_transposed.w_axis.x, 5.0);
905 assert_eq!(mat_transposed.w_axis.y, 6.0);
906 assert_eq!(mat_transposed.w_axis.z, 7.0);
907 }
908}