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