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::f32::Angle as Angle32;
12use cu29::units::si::f32::Length as Length32;
13use cu29::units::si::f64::Angle as Angle64;
14use cu29::units::si::f64::Length as Length64;
15use cu29::units::si::length::meter;
16use serde::{Deserialize, Serialize};
17
18#[cfg(feature = "glam")]
19use glam::{Affine3A, DAffine3, DMat4, DVec3, Mat4, Vec3, Vec3A};
20
21mod geometry;
22pub use geometry::{
23 BBox, BBox2d, BBox2f, BBox2i, BBox2u, BBox3d, BBox3f, Point2, Point2Iterator, Point2Soa,
24 Point2d, Point2dSoa, Point2f, Point2fSoa, Point2i, Point2iSoa, Point2u, Point2uSoa, Point3,
25 Point3Iterator, Point3Soa, Point3d, Point3dSoa, Point3f, Point3fSoa,
26};
27
28#[cfg(feature = "rerun")]
29mod rerun_components;
30
31#[derive(
36 Debug, Clone, Copy, Default, PartialEq, Serialize, Deserialize, Encode, Decode, Reflect,
37)]
38pub struct GeodeticPosition {
39 pub latitude: Angle64,
40 pub longitude: Angle64,
41}
42
43impl GeodeticPosition {
44 pub fn new(latitude: Angle64, longitude: Angle64) -> Self {
45 Self {
46 latitude,
47 longitude,
48 }
49 }
50
51 pub fn from_degrees(latitude_deg: f64, longitude_deg: f64) -> Self {
52 Self {
53 latitude: Angle64::new::<degree>(latitude_deg),
54 longitude: Angle64::new::<degree>(longitude_deg),
55 }
56 }
57
58 pub fn latitude_degrees(&self) -> f64 {
59 self.latitude.get::<degree>()
60 }
61
62 pub fn longitude_degrees(&self) -> f64 {
63 self.longitude.get::<degree>()
64 }
65}
66
67#[derive(Debug, Clone, Copy, Reflect)]
70#[reflect(opaque, from_reflect = false)]
71pub struct Transform3D<T: Copy + Debug + 'static> {
72 #[cfg(feature = "glam")]
73 inner: TransformInner<T>,
74 #[cfg(not(feature = "glam"))]
75 pub mat: [[T; 4]; 4],
76}
77
78#[cfg(feature = "glam")]
79#[derive(Debug, Clone, Copy)]
80enum TransformInner<T: Copy + Debug + 'static> {
81 F32(Affine3A),
82 F64(DAffine3),
83 _Phantom(core::marker::PhantomData<T>),
84}
85
86const fn const_sin_cos(mut angle: f64) -> (f64, f64) {
87 const PI: f64 = core::f64::consts::PI;
88 const FRAC_PI_2: f64 = core::f64::consts::FRAC_PI_2;
89 const TAU: f64 = core::f64::consts::TAU;
90
91 angle %= TAU;
92 if angle > PI {
93 angle -= TAU;
94 } else if angle < -PI {
95 angle += TAU;
96 }
97
98 let mut cos_sign = 1.0;
99 if angle > FRAC_PI_2 {
100 angle = PI - angle;
101 cos_sign = -1.0;
102 } else if angle < -FRAC_PI_2 {
103 angle = -PI - angle;
104 cos_sign = -1.0;
105 }
106
107 let x2 = angle * angle;
110 let mut sin_poly = 1.0 / 51_090_942_171_709_440_000.0;
111 sin_poly = -1.0 / 121_645_100_408_832_000.0 + x2 * sin_poly;
112 sin_poly = 1.0 / 355_687_428_096_000.0 + x2 * sin_poly;
113 sin_poly = -1.0 / 1_307_674_368_000.0 + x2 * sin_poly;
114 sin_poly = 1.0 / 6_227_020_800.0 + x2 * sin_poly;
115 sin_poly = -1.0 / 39_916_800.0 + x2 * sin_poly;
116 sin_poly = 1.0 / 362_880.0 + x2 * sin_poly;
117 sin_poly = -1.0 / 5_040.0 + x2 * sin_poly;
118 sin_poly = 1.0 / 120.0 + x2 * sin_poly;
119 sin_poly = -1.0 / 6.0 + x2 * sin_poly;
120 let sin = angle * (1.0 + x2 * sin_poly);
121
122 let mut cos_poly = 1.0 / 2_432_902_008_176_640_000.0;
123 cos_poly = -1.0 / 6_402_373_705_728_000.0 + x2 * cos_poly;
124 cos_poly = 1.0 / 20_922_789_888_000.0 + x2 * cos_poly;
125 cos_poly = -1.0 / 87_178_291_200.0 + x2 * cos_poly;
126 cos_poly = 1.0 / 479_001_600.0 + x2 * cos_poly;
127 cos_poly = -1.0 / 3_628_800.0 + x2 * cos_poly;
128 cos_poly = 1.0 / 40_320.0 + x2 * cos_poly;
129 cos_poly = -1.0 / 720.0 + x2 * cos_poly;
130 cos_poly = 1.0 / 24.0 + x2 * cos_poly;
131 cos_poly = -1.0 / 2.0 + x2 * cos_poly;
132 let cos = 1.0 + x2 * cos_poly;
133
134 (sin, cos_sign * cos)
135}
136
137macro_rules! impl_const_transform {
138 ($ty:ty, $len:ty, $ang:ty, $variant:ident, $affine:ty, $vec:ty) => {
139 impl Transform3D<$ty> {
140 const fn from_rows(rows: [[$ty; 4]; 3]) -> Self {
141 #[cfg(feature = "glam")]
142 {
143 Self {
144 inner: TransformInner::$variant(<$affine>::from_cols(
145 <$vec>::new(rows[0][0], rows[1][0], rows[2][0]),
146 <$vec>::new(rows[0][1], rows[1][1], rows[2][1]),
147 <$vec>::new(rows[0][2], rows[1][2], rows[2][2]),
148 <$vec>::new(rows[0][3], rows[1][3], rows[2][3]),
149 )),
150 }
151 }
152 #[cfg(not(feature = "glam"))]
153 {
154 Self {
155 mat: [
156 rows[0],
157 rows[1],
158 rows[2],
159 [0.0 as $ty, 0.0 as $ty, 0.0 as $ty, 1.0 as $ty],
160 ],
161 }
162 }
163 }
164
165 const fn rows(self) -> [[$ty; 4]; 3] {
166 #[cfg(feature = "glam")]
167 {
168 match self.inner {
169 TransformInner::$variant(affine) => {
170 let r = affine.matrix3;
171 let x = r.x_axis.to_array();
172 let y = r.y_axis.to_array();
173 let z = r.z_axis.to_array();
174 let t = affine.translation.to_array();
175 [
176 [x[0], y[0], z[0], t[0]],
177 [x[1], y[1], z[1], t[1]],
178 [x[2], y[2], z[2], t[2]],
179 ]
180 }
181 _ => panic!("invalid Transform3D storage variant"),
182 }
183 }
184 #[cfg(not(feature = "glam"))]
185 {
186 [self.mat[0], self.mat[1], self.mat[2]]
187 }
188 }
189
190 pub const fn identity() -> Self {
192 Self::from_rows([
193 [1.0 as $ty, 0.0 as $ty, 0.0 as $ty, 0.0 as $ty],
194 [0.0 as $ty, 1.0 as $ty, 0.0 as $ty, 0.0 as $ty],
195 [0.0 as $ty, 0.0 as $ty, 1.0 as $ty, 0.0 as $ty],
196 ])
197 }
198
199 pub const fn from_translation_euler_xyz(
205 translation: [$len; 3],
206 rotation: [$ang; 3],
207 ) -> Self {
208 let (sx, cx) = const_sin_cos(rotation[0].value as f64);
209 let (sy, cy) = const_sin_cos(rotation[1].value as f64);
210 let (sz, cz) = const_sin_cos(rotation[2].value as f64);
211 let sx = sx as $ty;
212 let cx = cx as $ty;
213 let sy = sy as $ty;
214 let cy = cy as $ty;
215 let sz = sz as $ty;
216 let cz = cz as $ty;
217
218 Self::from_rows([
219 [
220 cy * cz,
221 cz * sx * sy - cx * sz,
222 sx * sz + cx * cz * sy,
223 translation[0].value,
224 ],
225 [
226 cy * sz,
227 cx * cz + sx * sy * sz,
228 cx * sy * sz - cz * sx,
229 translation[1].value,
230 ],
231 [-sy, cy * sx, cx * cy, translation[2].value],
232 ])
233 }
234
235 pub const fn compose(self, rhs: Self) -> Self {
239 let lhs = self.rows();
240 let rhs = rhs.rows();
241 let mut result = [[0.0 as $ty; 4]; 3];
242 let mut row = 0;
243 while row < 3 {
244 let mut column = 0;
245 while column < 3 {
246 result[row][column] = lhs[row][0] * rhs[0][column]
247 + lhs[row][1] * rhs[1][column]
248 + lhs[row][2] * rhs[2][column];
249 column += 1;
250 }
251 result[row][3] = lhs[row][0] * rhs[0][3]
252 + lhs[row][1] * rhs[1][3]
253 + lhs[row][2] * rhs[2][3]
254 + lhs[row][3];
255 row += 1;
256 }
257 Self::from_rows(result)
258 }
259 }
260 };
261}
262
263impl_const_transform!(f32, Length32, Angle32, F32, Affine3A, Vec3A);
264impl_const_transform!(f64, Length64, Angle64, F64, DAffine3, DVec3);
265
266pub type Pose<T> = Transform3D<T>;
267
268macro_rules! impl_transform_accessors {
269 ($ty:ty, $len:ty, $variant:ident) => {
270 impl Transform3D<$ty> {
271 pub fn translation(&self) -> [$len; 3] {
273 let position = self.position();
274 [position.x, position.y, position.z]
275 }
276
277 pub fn rotation(&self) -> [[$ty; 3]; 3] {
279 #[cfg(feature = "glam")]
280 {
281 match &self.inner {
282 TransformInner::$variant(affine) => {
283 let r = &affine.matrix3;
284 [
285 [r.x_axis.x, r.y_axis.x, r.z_axis.x],
286 [r.x_axis.y, r.y_axis.y, r.z_axis.y],
287 [r.x_axis.z, r.y_axis.z, r.z_axis.z],
288 ]
289 }
290 _ => unreachable!(),
291 }
292 }
293 #[cfg(not(feature = "glam"))]
294 {
295 [
296 [self.mat[0][0], self.mat[0][1], self.mat[0][2]],
297 [self.mat[1][0], self.mat[1][1], self.mat[1][2]],
298 [self.mat[2][0], self.mat[2][1], self.mat[2][2]],
299 ]
300 }
301 }
302 }
303 };
304}
305
306impl<T: Copy + Debug + Default + 'static> Serialize for Transform3D<T>
308where
309 T: Serialize,
310{
311 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
312 where
313 S: serde::Serializer,
314 {
315 #[cfg(feature = "glam")]
316 {
317 let mat = self.to_matrix();
318 mat.serialize(serializer)
319 }
320 #[cfg(not(feature = "glam"))]
321 {
322 self.mat.serialize(serializer)
323 }
324 }
325}
326
327impl<'de, T: Copy + Debug + 'static> Deserialize<'de> for Transform3D<T>
328where
329 T: Deserialize<'de> + Default,
330{
331 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
332 where
333 D: serde::Deserializer<'de>,
334 {
335 let mat: [[T; 4]; 4] = Deserialize::deserialize(deserializer)?;
336 Ok(Self::from_matrix(mat))
337 }
338}
339
340impl<T: Copy + Debug + Default + 'static> Encode for Transform3D<T>
342where
343 T: Encode,
344{
345 fn encode<E: bincode::enc::Encoder>(
346 &self,
347 encoder: &mut E,
348 ) -> Result<(), bincode::error::EncodeError> {
349 #[cfg(feature = "glam")]
350 {
351 let mat = self.to_matrix();
352 mat.encode(encoder)
353 }
354 #[cfg(not(feature = "glam"))]
355 {
356 self.mat.encode(encoder)
357 }
358 }
359}
360
361impl<T: Copy + Debug + 'static> Decode<()> for Transform3D<T>
362where
363 T: Decode<()> + Default,
364{
365 fn decode<D: bincode::de::Decoder<Context = ()>>(
366 decoder: &mut D,
367 ) -> Result<Self, bincode::error::DecodeError> {
368 let mat: [[T; 4]; 4] = Decode::decode(decoder)?;
369 Ok(Self::from_matrix(mat))
370 }
371}
372
373impl<T: Copy + Debug + Default + 'static> Transform3D<T> {
374 pub fn from_matrix(mat: [[T; 4]; 4]) -> Self {
381 #[cfg(feature = "glam")]
382 {
383 Self {
384 inner: TransformInner::from_matrix(mat),
385 }
386 }
387 #[cfg(not(feature = "glam"))]
388 {
389 Self { mat }
390 }
391 }
392
393 pub fn to_matrix(self) -> [[T; 4]; 4] {
397 #[cfg(feature = "glam")]
398 {
399 self.inner.to_matrix()
400 }
401 #[cfg(not(feature = "glam"))]
402 {
403 self.mat
404 }
405 }
406
407 #[cfg(not(feature = "glam"))]
409 pub fn mat_mut(&mut self) -> &mut [[T; 4]; 4] {
410 &mut self.mat
411 }
412}
413
414#[cfg(feature = "glam")]
415impl<T: Copy + Debug + Default + 'static> TransformInner<T> {
416 fn from_matrix(mat: [[T; 4]; 4]) -> Self {
417 use core::any::TypeId;
418
419 if TypeId::of::<T>() == TypeId::of::<f32>() {
422 let mat_f32: [[f32; 4]; 4] = unsafe { core::mem::transmute_copy(&mat) };
425 let glam_mat = Mat4::from_cols_array_2d(&mat_f32).transpose();
427 let affine = Affine3A::from_mat4(glam_mat);
428 unsafe { core::mem::transmute_copy(&TransformInner::<T>::F32(affine)) }
430 } else if TypeId::of::<T>() == TypeId::of::<f64>() {
431 let mat_f64: [[f64; 4]; 4] = unsafe { core::mem::transmute_copy(&mat) };
434 let glam_mat = DMat4::from_cols_array_2d(&mat_f64).transpose();
436 let affine = DAffine3::from_mat4(glam_mat);
437 unsafe { core::mem::transmute_copy(&TransformInner::<T>::F64(affine)) }
439 } else {
440 panic!("Transform3D only supports f32 and f64 types when using glam feature");
441 }
442 }
443
444 fn to_matrix(self) -> [[T; 4]; 4] {
445 match self {
446 TransformInner::F32(affine) => {
447 let mat = Mat4::from(affine);
448 let mat_array = mat.transpose().to_cols_array_2d();
450 unsafe { core::mem::transmute_copy(&mat_array) }
452 }
453 TransformInner::F64(affine) => {
454 let mat = DMat4::from(affine);
455 let mat_array = mat.transpose().to_cols_array_2d();
457 unsafe { core::mem::transmute_copy(&mat_array) }
459 }
460 TransformInner::_Phantom(_) => unreachable!(),
461 }
462 }
463}
464
465impl_transform_accessors!(f32, Length32, F32);
466impl_transform_accessors!(f64, Length64, F64);
467
468macro_rules! impl_transform_points {
469 ($ty:ty, $len:ty, $variant:ident, $vec:ty) => {
470 impl Transform3D<$ty> {
471 pub fn position(&self) -> Point3<$len> {
474 #[cfg(feature = "glam")]
475 {
476 match &self.inner {
477 TransformInner::$variant(affine) => {
478 let t = affine.translation;
479 Point3::new(
480 <$len>::new::<meter>(t.x as $ty),
481 <$len>::new::<meter>(t.y as $ty),
482 <$len>::new::<meter>(t.z as $ty),
483 )
484 }
485 _ => unreachable!(),
486 }
487 }
488 #[cfg(not(feature = "glam"))]
489 {
490 Point3::new(
491 <$len>::new::<meter>(self.mat[0][3]),
492 <$len>::new::<meter>(self.mat[1][3]),
493 <$len>::new::<meter>(self.mat[2][3]),
494 )
495 }
496 }
497
498 pub fn transform_point(&self, p: Point3<$len>) -> Point3<$len> {
500 #[cfg(feature = "glam")]
501 {
502 match &self.inner {
503 TransformInner::$variant(affine) => {
504 let out = affine.transform_point3(<$vec>::new(
505 p.x.raw(),
506 p.y.raw(),
507 p.z.raw(),
508 ));
509 Point3::new(
510 <$len>::new::<meter>(out.x),
511 <$len>::new::<meter>(out.y),
512 <$len>::new::<meter>(out.z),
513 )
514 }
515 _ => unreachable!(),
516 }
517 }
518 #[cfg(not(feature = "glam"))]
519 {
520 let m = &self.mat;
521 let (x, y, z) = (p.x.raw(), p.y.raw(), p.z.raw());
522 Point3::new(
523 <$len>::new::<meter>(m[0][0] * x + m[0][1] * y + m[0][2] * z + m[0][3]),
524 <$len>::new::<meter>(m[1][0] * x + m[1][1] * y + m[1][2] * z + m[1][3]),
525 <$len>::new::<meter>(m[2][0] * x + m[2][1] * y + m[2][2] * z + m[2][3]),
526 )
527 }
528 }
529
530 pub fn transform_vector(&self, v: Point3<$len>) -> Point3<$len> {
533 #[cfg(feature = "glam")]
534 {
535 match &self.inner {
536 TransformInner::$variant(affine) => {
537 let out = affine.transform_vector3(<$vec>::new(
538 v.x.raw(),
539 v.y.raw(),
540 v.z.raw(),
541 ));
542 Point3::new(
543 <$len>::new::<meter>(out.x),
544 <$len>::new::<meter>(out.y),
545 <$len>::new::<meter>(out.z),
546 )
547 }
548 _ => unreachable!(),
549 }
550 }
551 #[cfg(not(feature = "glam"))]
552 {
553 let m = &self.mat;
554 let (x, y, z) = (v.x.raw(), v.y.raw(), v.z.raw());
555 Point3::new(
556 <$len>::new::<meter>(m[0][0] * x + m[0][1] * y + m[0][2] * z),
557 <$len>::new::<meter>(m[1][0] * x + m[1][1] * y + m[1][2] * z),
558 <$len>::new::<meter>(m[2][0] * x + m[2][1] * y + m[2][2] * z),
559 )
560 }
561 }
562
563 pub fn transform_points<const N: usize>(&self, points: &mut Point3Soa<$len, N>) {
567 #[cfg(feature = "glam")]
568 let m = match &self.inner {
569 TransformInner::$variant(affine) => {
570 let r = &affine.matrix3;
571 let t = affine.translation;
572 [
573 [r.x_axis.x, r.y_axis.x, r.z_axis.x, t.x],
574 [r.x_axis.y, r.y_axis.y, r.z_axis.y, t.y],
575 [r.x_axis.z, r.y_axis.z, r.z_axis.z, t.z],
576 ]
577 }
578 _ => unreachable!(),
579 };
580 #[cfg(not(feature = "glam"))]
581 let m = [self.mat[0], self.mat[1], self.mat[2]];
582
583 let n = points.len();
584 for i in 0..n {
585 let (x, y, z) = (points.x[i].raw(), points.y[i].raw(), points.z[i].raw());
586 points.x[i] =
587 <$len>::new::<meter>(m[0][0] * x + m[0][1] * y + m[0][2] * z + m[0][3]);
588 points.y[i] =
589 <$len>::new::<meter>(m[1][0] * x + m[1][1] * y + m[1][2] * z + m[1][3]);
590 points.z[i] =
591 <$len>::new::<meter>(m[2][0] * x + m[2][1] * y + m[2][2] * z + m[2][3]);
592 }
593 }
594 }
595 };
596}
597
598impl_transform_points!(f32, Length32, F32, Vec3);
599impl_transform_points!(f64, Length64, F64, DVec3);
600
601impl<T: Copy + Debug + Default + 'static> Default for Transform3D<T> {
602 fn default() -> Self {
603 Self::from_matrix([[T::default(); 4]; 4])
604 }
605}
606
607macro_rules! impl_transform_mul {
608 ($ty:ty, $zero:expr, $variant:ident) => {
609 impl Mul for Transform3D<$ty> {
610 type Output = Self;
611
612 fn mul(self, rhs: Self) -> Self::Output {
613 #[cfg(feature = "glam")]
614 {
615 match (&self.inner, &rhs.inner) {
616 (TransformInner::$variant(a), TransformInner::$variant(b)) => Self {
617 inner: TransformInner::$variant(*a * *b),
618 },
619 _ => unreachable!(),
620 }
621 }
622 #[cfg(not(feature = "glam"))]
623 {
624 let mut result = [[$zero; 4]; 4];
625 for i in 0..4 {
626 for j in 0..4 {
627 let mut sum = $zero;
628 for k in 0..4 {
629 sum += self.mat[i][k] * rhs.mat[k][j];
630 }
631 result[i][j] = sum;
632 }
633 }
634 Self { mat: result }
635 }
636 }
637 }
638 };
639}
640
641impl_transform_mul!(f32, 0.0f32, F32);
642impl_transform_mul!(f64, 0.0f64, F64);
643
644impl Mul for &Transform3D<f32> {
646 type Output = Transform3D<f32>;
647
648 fn mul(self, rhs: Self) -> Self::Output {
649 *self * *rhs
650 }
651}
652
653impl Mul<Transform3D<f32>> for &Transform3D<f32> {
654 type Output = Transform3D<f32>;
655
656 fn mul(self, rhs: Transform3D<f32>) -> Self::Output {
657 *self * rhs
658 }
659}
660
661impl Mul<&Transform3D<f32>> for Transform3D<f32> {
662 type Output = Transform3D<f32>;
663
664 fn mul(self, rhs: &Transform3D<f32>) -> Self::Output {
665 self * *rhs
666 }
667}
668
669impl Mul for &Transform3D<f64> {
671 type Output = Transform3D<f64>;
672
673 fn mul(self, rhs: Self) -> Self::Output {
674 *self * *rhs
675 }
676}
677
678impl Mul<Transform3D<f64>> for &Transform3D<f64> {
679 type Output = Transform3D<f64>;
680
681 fn mul(self, rhs: Transform3D<f64>) -> Self::Output {
682 *self * rhs
683 }
684}
685
686impl Mul<&Transform3D<f64>> for Transform3D<f64> {
687 type Output = Transform3D<f64>;
688
689 fn mul(self, rhs: &Transform3D<f64>) -> Self::Output {
690 self * *rhs
691 }
692}
693
694macro_rules! impl_transform_inverse {
695 ($ty:ty, $zero:expr, $one:expr, $variant:ident) => {
696 impl Transform3D<$ty> {
697 pub fn inverse(&self) -> Self {
699 #[cfg(feature = "glam")]
700 {
701 match &self.inner {
702 TransformInner::$variant(affine) => Self {
703 inner: TransformInner::$variant(affine.inverse()),
704 },
705 _ => unreachable!(),
706 }
707 }
708 #[cfg(not(feature = "glam"))]
709 {
710 let mat = self.mat;
711 let r = [
713 [mat[0][0], mat[0][1], mat[0][2]],
714 [mat[1][0], mat[1][1], mat[1][2]],
715 [mat[2][0], mat[2][1], mat[2][2]],
716 ];
717
718 let t = [mat[0][3], mat[1][3], mat[2][3]];
720
721 let r_inv = [
723 [r[0][0], r[1][0], r[2][0]],
724 [r[0][1], r[1][1], r[2][1]],
725 [r[0][2], r[1][2], r[2][2]],
726 ];
727
728 let t_inv = [
730 -(r_inv[0][0] * t[0] + r_inv[0][1] * t[1] + r_inv[0][2] * t[2]),
731 -(r_inv[1][0] * t[0] + r_inv[1][1] * t[1] + r_inv[1][2] * t[2]),
732 -(r_inv[2][0] * t[0] + r_inv[2][1] * t[1] + r_inv[2][2] * t[2]),
733 ];
734
735 let mut inv_mat = [[$zero; 4]; 4];
737
738 for i in 0..3 {
740 for j in 0..3 {
741 inv_mat[i][j] = r_inv[i][j];
742 }
743 }
744
745 inv_mat[0][3] = t_inv[0];
747 inv_mat[1][3] = t_inv[1];
748 inv_mat[2][3] = t_inv[2];
749
750 inv_mat[3][3] = $one;
752
753 Self { mat: inv_mat }
754 }
755 }
756 }
757 };
758}
759
760impl_transform_inverse!(f32, 0.0f32, 1.0f32, F32);
761impl_transform_inverse!(f64, 0.0f64, 1.0f64, F64);
762
763#[cfg(feature = "faer")]
764mod faer_integration {
765 use super::Transform3D;
766 use faer::prelude::*;
767
768 impl From<&Transform3D<f64>> for Mat<f64> {
769 fn from(p: &Transform3D<f64>) -> Self {
770 let mat_array = p.to_matrix();
771 let mut mat: Mat<f64> = Mat::zeros(4, 4);
772 for (r, row) in mat_array.iter().enumerate() {
773 for (c, item) in row.iter().enumerate() {
774 *mat.get_mut(r, c) = *item;
775 }
776 }
777 mat
778 }
779 }
780
781 impl From<Mat<f64>> for Transform3D<f64> {
782 fn from(mat: Mat<f64>) -> Self {
783 assert_eq!(mat.nrows(), 4);
784 assert_eq!(mat.ncols(), 4);
785 let mut transform = [[0.0; 4]; 4];
786 for (r, row) in transform.iter_mut().enumerate() {
787 for (c, val) in row.iter_mut().enumerate() {
788 *val = *mat.get(r, c);
789 }
790 }
791 Self::from_matrix(transform)
792 }
793 }
794}
795
796#[cfg(feature = "nalgebra")]
798mod nalgebra_integration {
799 use super::Transform3D;
800 use nalgebra::{Isometry3, Matrix3, Matrix4, Rotation3, Translation3, Vector3};
801
802 impl From<&Transform3D<f64>> for Isometry3<f64> {
803 fn from(pose: &Transform3D<f64>) -> Self {
804 let mat_array = pose.to_matrix();
805 let flat_transform: [f64; 16] = core::array::from_fn(|i| mat_array[i / 4][i % 4]);
806 let matrix = Matrix4::from_row_slice(&flat_transform);
807
808 let rotation_matrix: Matrix3<f64> = matrix.fixed_view::<3, 3>(0, 0).into();
809 let rotation = Rotation3::from_matrix_unchecked(rotation_matrix);
810
811 let translation_vector: Vector3<f64> = matrix.fixed_view::<3, 1>(0, 3).into();
812 let translation = Translation3::from(translation_vector);
813
814 Isometry3::from_parts(translation, rotation.into())
815 }
816 }
817
818 impl From<Isometry3<f64>> for Transform3D<f64> {
819 fn from(iso: Isometry3<f64>) -> Self {
820 let matrix = iso.to_homogeneous();
821 let transform = core::array::from_fn(|r| core::array::from_fn(|c| matrix[(r, c)]));
822 Transform3D::from_matrix(transform)
823 }
824 }
825}
826
827#[cfg(feature = "glam")]
829mod glam_integration {
830 use super::Transform3D;
831 use glam::{Affine3A, DAffine3, DMat4, Mat4};
832
833 impl From<Transform3D<f64>> for DAffine3 {
834 fn from(p: Transform3D<f64>) -> Self {
835 let mat = p.to_matrix();
837 DAffine3::from_mat4(DMat4::from_cols_array_2d(&mat).transpose())
838 }
839 }
840
841 impl From<DAffine3> for Transform3D<f64> {
842 fn from(aff: DAffine3) -> Self {
843 Transform3D::from_matrix(DMat4::from(aff).transpose().to_cols_array_2d())
845 }
846 }
847
848 impl From<Transform3D<f32>> for Affine3A {
849 fn from(p: Transform3D<f32>) -> Self {
850 let mat = p.to_matrix();
852 Affine3A::from_mat4(Mat4::from_cols_array_2d(&mat).transpose())
853 }
854 }
855
856 impl From<Affine3A> for Transform3D<f32> {
857 fn from(aff: Affine3A) -> Self {
858 Transform3D::from_matrix(Mat4::from(aff).transpose().to_cols_array_2d())
860 }
861 }
862}
863
864#[cfg(feature = "nalgebra")]
865#[allow(unused_imports)]
866pub use nalgebra_integration::*;
867
868#[cfg(feature = "faer")]
869#[allow(unused_imports)]
870pub use faer_integration::*;
871
872#[cfg(test)]
873mod tests {
874 use super::*;
875
876 const CONST_PARENT_TO_INTERMEDIATE: Transform3D<f32> =
877 Transform3D::<f32>::from_translation_euler_xyz(
878 [
879 Length32 { value: 1.0 },
880 Length32 { value: 2.0 },
881 Length32 { value: 3.0 },
882 ],
883 [
884 Angle32 { value: 0.0 },
885 Angle32 { value: 0.0 },
886 Angle32 {
887 value: core::f32::consts::FRAC_PI_2,
888 },
889 ],
890 );
891 const CONST_INTERMEDIATE_TO_CHILD: Transform3D<f32> =
892 Transform3D::<f32>::from_translation_euler_xyz(
893 [
894 Length32 { value: 1.0 },
895 Length32 { value: 0.0 },
896 Length32 { value: 0.0 },
897 ],
898 [Angle32 { value: 0.0 }; 3],
899 );
900 const CONST_PARENT_TO_CHILD: Transform3D<f32> =
901 CONST_PARENT_TO_INTERMEDIATE.compose(CONST_INTERMEDIATE_TO_CHILD);
902 const CONST_TRANSFORM_F64: Transform3D<f64> = Transform3D::<f64>::from_translation_euler_xyz(
903 [Length64 { value: 0.0 }; 3],
904 [
905 Angle64 {
906 value: core::f64::consts::PI / 6.0,
907 },
908 Angle64 {
909 value: -core::f64::consts::PI / 9.0,
910 },
911 Angle64 {
912 value: core::f64::consts::PI / 18.0,
913 },
914 ],
915 );
916
917 fn assert_matrix_close<const N: usize, T: Copy + Into<f64>>(
918 lhs: [[T; N]; N],
919 rhs: [[T; N]; N],
920 eps: f64,
921 ) {
922 for i in 0..N {
923 for j in 0..N {
924 let lhs = lhs[i][j].into();
925 let rhs = rhs[i][j].into();
926 assert!(
927 (lhs - rhs).abs() <= eps,
928 "Element at [{},{}] differs: {} vs expected {}",
929 i,
930 j,
931 lhs,
932 rhs
933 );
934 }
935 }
936 }
937
938 #[test]
939 fn const_sin_cos_matches_runtime_trigonometry() {
940 for step in -64..=64 {
941 let angle = f64::from(step) * core::f64::consts::PI / 8.0;
942 let (actual_sin, actual_cos) = const_sin_cos(angle);
943 let (expected_sin, expected_cos) = angle.sin_cos();
944 assert!((actual_sin - expected_sin).abs() <= 1e-14);
945 assert!((actual_cos - expected_cos).abs() <= 1e-14);
946 }
947 }
948
949 #[test]
950 fn const_transform_construction_and_composition_are_semantic() {
951 assert_point_close(
952 CONST_PARENT_TO_CHILD.position(),
953 Point3f::from_meters(1.0, 3.0, 3.0),
954 1e-5,
955 );
956 assert_point_close(
957 CONST_PARENT_TO_CHILD.transform_vector(Point3f::from_meters(1.0, 0.0, 0.0)),
958 Point3f::from_meters(0.0, 1.0, 0.0),
959 1e-5,
960 );
961
962 let point = Point3::new(
963 Length64::new::<meter>(1.0),
964 Length64::new::<meter>(2.0),
965 Length64::new::<meter>(3.0),
966 );
967 let transformed = CONST_TRANSFORM_F64.transform_vector(point);
968 assert!(transformed.x.raw().is_finite());
969 assert!(transformed.y.raw().is_finite());
970 assert!(transformed.z.raw().is_finite());
971 }
972
973 #[test]
974 fn test_pose_default() {
975 let pose: Transform3D<f32> = Transform3D::default();
976 let mat = pose.to_matrix();
977
978 #[cfg(feature = "glam")]
981 {
982 let expected = [
985 [0.0, 0.0, 0.0, 0.0],
986 [0.0, 0.0, 0.0, 0.0],
987 [0.0, 0.0, 0.0, 0.0],
988 [0.0, 0.0, 0.0, 1.0], ];
990 assert_eq!(mat, expected, "Default pose with glam should have w=1");
991 }
992
993 #[cfg(not(feature = "glam"))]
994 {
995 assert_eq!(
996 mat, [[0.0; 4]; 4],
997 "Default pose without glam should be a zero matrix"
998 );
999 }
1000 }
1001
1002 #[test]
1003 fn test_transform_inverse_f32() {
1004 let transform = Transform3D::<f32>::from_matrix([
1006 [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], ]);
1011
1012 let inverse = transform.inverse();
1014
1015 let expected_inverse = Transform3D::<f32>::from_matrix([
1017 [1.0, 0.0, 0.0, -2.0], [0.0, 1.0, 0.0, -3.0],
1019 [0.0, 0.0, 1.0, -4.0],
1020 [0.0, 0.0, 0.0, 1.0],
1021 ]);
1022
1023 let epsilon = 1e-5;
1025 let inv_mat = inverse.to_matrix();
1026 let exp_mat = expected_inverse.to_matrix();
1027 assert_matrix_close(inv_mat, exp_mat, epsilon);
1028 }
1029
1030 #[test]
1031 fn test_transform_inverse_f64() {
1032 let transform = Transform3D::<f64>::from_matrix([
1034 [0.0, -1.0, 0.0, 5.0], [1.0, 0.0, 0.0, 6.0],
1036 [0.0, 0.0, 1.0, 7.0],
1037 [0.0, 0.0, 0.0, 1.0],
1038 ]);
1039
1040 let inverse = transform.inverse();
1042
1043 let expected_inverse = Transform3D::<f64>::from_matrix([
1045 [0.0, 1.0, 0.0, -6.0], [-1.0, 0.0, 0.0, 5.0],
1047 [0.0, 0.0, 1.0, -7.0],
1048 [0.0, 0.0, 0.0, 1.0],
1049 ]);
1050
1051 let epsilon = 1e-10;
1053 let inv_mat = inverse.to_matrix();
1054 let exp_mat = expected_inverse.to_matrix();
1055 assert_matrix_close(inv_mat, exp_mat, epsilon);
1056 }
1057
1058 #[test]
1059 fn test_transform_inverse_identity() {
1060 let identity = Transform3D::<f32>::from_matrix([
1062 [1.0, 0.0, 0.0, 0.0],
1063 [0.0, 1.0, 0.0, 0.0],
1064 [0.0, 0.0, 1.0, 0.0],
1065 [0.0, 0.0, 0.0, 1.0],
1066 ]);
1067
1068 let inverse = identity.inverse();
1070
1071 let epsilon = 1e-5;
1073 let inv_mat = inverse.to_matrix();
1074 let id_mat = identity.to_matrix();
1075 assert_matrix_close(inv_mat, id_mat, epsilon);
1076 }
1077
1078 #[test]
1079 fn test_transform_multiplication_f32() {
1080 let t1 = Transform3D::<f32>::from_matrix([
1082 [1.0, 0.0, 0.0, 2.0], [0.0, 1.0, 0.0, 3.0],
1084 [0.0, 0.0, 1.0, 4.0],
1085 [0.0, 0.0, 0.0, 1.0],
1086 ]);
1087
1088 let t2 = Transform3D::<f32>::from_matrix([
1089 [0.0, -1.0, 0.0, 5.0], [1.0, 0.0, 0.0, 6.0],
1091 [0.0, 0.0, 1.0, 7.0],
1092 [0.0, 0.0, 0.0, 1.0],
1093 ]);
1094
1095 let result = t1 * t2;
1097
1098 let expected = Transform3D::<f32>::from_matrix([
1100 [0.0, -1.0, 0.0, 7.0], [1.0, 0.0, 0.0, 9.0],
1102 [0.0, 0.0, 1.0, 11.0],
1103 [0.0, 0.0, 0.0, 1.0],
1104 ]);
1105
1106 let epsilon = 1e-5;
1108 let res_mat = result.to_matrix();
1109 let exp_mat = expected.to_matrix();
1110 assert_matrix_close(res_mat, exp_mat, epsilon);
1111 }
1112
1113 #[test]
1114 fn test_transform_multiplication_f64() {
1115 let t1 = Transform3D::<f64>::from_matrix([
1117 [1.0, 0.0, 0.0, 2.0], [0.0, 1.0, 0.0, 3.0],
1119 [0.0, 0.0, 1.0, 4.0],
1120 [0.0, 0.0, 0.0, 1.0],
1121 ]);
1122
1123 let t2 = Transform3D::<f64>::from_matrix([
1124 [0.0, -1.0, 0.0, 5.0], [1.0, 0.0, 0.0, 6.0],
1126 [0.0, 0.0, 1.0, 7.0],
1127 [0.0, 0.0, 0.0, 1.0],
1128 ]);
1129
1130 let result = t1 * t2;
1132
1133 let expected = Transform3D::<f64>::from_matrix([
1135 [0.0, -1.0, 0.0, 7.0], [1.0, 0.0, 0.0, 9.0],
1137 [0.0, 0.0, 1.0, 11.0],
1138 [0.0, 0.0, 0.0, 1.0],
1139 ]);
1140
1141 let epsilon = 1e-10;
1143 let res_mat = result.to_matrix();
1144 let exp_mat = expected.to_matrix();
1145 assert_matrix_close(res_mat, exp_mat, epsilon);
1146 }
1147
1148 #[test]
1149 fn test_transform_reference_multiplication() {
1150 let t1 = Transform3D::<f32>::from_matrix([
1152 [1.0, 0.0, 0.0, 2.0],
1153 [0.0, 1.0, 0.0, 3.0],
1154 [0.0, 0.0, 1.0, 4.0],
1155 [0.0, 0.0, 0.0, 1.0],
1156 ]);
1157
1158 let t2 = Transform3D::<f32>::from_matrix([
1159 [0.0, -1.0, 0.0, 5.0],
1160 [1.0, 0.0, 0.0, 6.0],
1161 [0.0, 0.0, 1.0, 7.0],
1162 [0.0, 0.0, 0.0, 1.0],
1163 ]);
1164
1165 let result = t1 * t2;
1167
1168 let expected = Transform3D::<f32>::from_matrix([
1170 [0.0, -1.0, 0.0, 7.0],
1171 [1.0, 0.0, 0.0, 9.0],
1172 [0.0, 0.0, 1.0, 11.0],
1173 [0.0, 0.0, 0.0, 1.0],
1174 ]);
1175
1176 let epsilon = 1e-5;
1178 let res_mat = result.to_matrix();
1179 let exp_mat = expected.to_matrix();
1180 assert_matrix_close(res_mat, exp_mat, epsilon);
1181 }
1182
1183 #[cfg(feature = "faer")]
1184 #[test]
1185 fn test_pose_faer_conversion() {
1186 use faer::prelude::*;
1187
1188 let pose = Transform3D::from_matrix([
1189 [1.0, 2.0, 3.0, 4.0],
1190 [5.0, 6.0, 7.0, 8.0],
1191 [9.0, 10.0, 11.0, 12.0],
1192 [13.0, 14.0, 15.0, 16.0],
1193 ]);
1194
1195 let mat: Mat<f64> = (&pose).into();
1196 let pose_from_mat = Transform3D::from(mat);
1197
1198 assert_eq!(
1199 pose.to_matrix(),
1200 pose_from_mat.to_matrix(),
1201 "Faer conversion should be lossless"
1202 );
1203 }
1204
1205 #[cfg(feature = "nalgebra")]
1206 #[test]
1207 fn test_pose_nalgebra_conversion() {
1208 use nalgebra::Isometry3;
1209
1210 let pose = Transform3D::from_matrix([
1211 [1.0, 0.0, 0.0, 2.0],
1212 [0.0, 1.0, 0.0, 3.0],
1213 [0.0, 0.0, 1.0, 4.0],
1214 [0.0, 0.0, 0.0, 1.0],
1215 ]);
1216
1217 let iso: Isometry3<f64> = (&pose.clone()).into();
1218 let pose_from_iso: Transform3D<f64> = iso.into();
1219
1220 assert_eq!(
1221 pose.to_matrix(),
1222 pose_from_iso.to_matrix(),
1223 "Nalgebra conversion should be lossless"
1224 );
1225 }
1226
1227 #[cfg(feature = "glam")]
1228 #[test]
1229 fn test_pose_glam_conversion() {
1230 use glam::DAffine3;
1231
1232 let pose = Transform3D::from_matrix([
1233 [1.0, 0.0, 0.0, 5.0],
1234 [0.0, 1.0, 0.0, 6.0],
1235 [0.0, 0.0, 1.0, 7.0],
1236 [0.0, 0.0, 0.0, 1.0],
1237 ]);
1238 let aff: DAffine3 = pose.into();
1239 assert_eq!(aff.translation[0], 5.0);
1240 assert_eq!(aff.translation[1], 6.0);
1241 assert_eq!(aff.translation[2], 7.0);
1242 let pose_from_aff: Transform3D<f64> = aff.into();
1243
1244 assert_eq!(
1245 pose.to_matrix(),
1246 pose_from_aff.to_matrix(),
1247 "Glam conversion should be lossless"
1248 );
1249 }
1250
1251 #[cfg(feature = "glam")]
1252 #[test]
1253 fn test_matrix_format_issue() {
1254 use glam::Mat4;
1255
1256 let row_major = [
1258 [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], ];
1263
1264 let col_major = [
1267 [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], ];
1272
1273 let mat_from_row = Mat4::from_cols_array_2d(&row_major);
1275 let mat_from_col = Mat4::from_cols_array_2d(&col_major);
1276
1277 assert_ne!(mat_from_row.w_axis.x, 5.0); assert_eq!(mat_from_col.w_axis.x, 5.0);
1282 assert_eq!(mat_from_col.w_axis.y, 6.0);
1283 assert_eq!(mat_from_col.w_axis.z, 7.0);
1284
1285 let mat_transposed = Mat4::from_cols_array_2d(&row_major).transpose();
1287 assert_eq!(mat_transposed.w_axis.x, 5.0);
1288 assert_eq!(mat_transposed.w_axis.y, 6.0);
1289 assert_eq!(mat_transposed.w_axis.z, 7.0);
1290 }
1291
1292 fn quarter_turn_and_shift() -> Transform3D<f32> {
1295 Transform3D::from_matrix([
1296 [0.0, -1.0, 0.0, 2.0],
1297 [1.0, 0.0, 0.0, 3.0],
1298 [0.0, 0.0, 1.0, 4.0],
1299 [0.0, 0.0, 0.0, 1.0],
1300 ])
1301 }
1302
1303 fn assert_point_close(lhs: Point3f, rhs: Point3f, eps: f32) {
1304 assert!(
1305 (lhs.x - rhs.x).raw().abs() <= eps
1306 && (lhs.y - rhs.y).raw().abs() <= eps
1307 && (lhs.z - rhs.z).raw().abs() <= eps,
1308 "{lhs:?} differs from {rhs:?}"
1309 );
1310 }
1311
1312 #[test]
1313 fn transform_point_rotates_and_translates() {
1314 let t = quarter_turn_and_shift();
1315 let p = Point3f::from_meters(1.0, 0.0, 0.0);
1316 assert_point_close(
1317 t.transform_point(p),
1318 Point3f::from_meters(2.0, 4.0, 4.0),
1319 1e-5,
1320 );
1321 assert_point_close(
1322 t.transform_vector(p),
1323 Point3f::from_meters(0.0, 1.0, 0.0),
1324 1e-5,
1325 );
1326 assert_point_close(t.position(), Point3f::from_meters(2.0, 3.0, 4.0), 1e-5);
1327 }
1328
1329 #[test]
1330 fn transform_accessors_are_backend_independent() {
1331 let t = quarter_turn_and_shift();
1332
1333 assert_eq!(
1334 t.translation(),
1335 [
1336 Length32::new::<meter>(2.0),
1337 Length32::new::<meter>(3.0),
1338 Length32::new::<meter>(4.0),
1339 ]
1340 );
1341 assert_eq!(
1342 t.rotation(),
1343 [[0.0, -1.0, 0.0], [1.0, 0.0, 0.0], [0.0, 0.0, 1.0]]
1344 );
1345 }
1346
1347 #[test]
1348 fn from_matrix_is_row_major_and_round_trips() {
1349 let mat = [
1353 [0.0, -1.0, 0.0, 2.0],
1354 [1.0, 0.0, 0.0, 3.0],
1355 [0.0, 0.0, 1.0, 4.0],
1356 [0.0, 0.0, 0.0, 1.0],
1357 ];
1358 let t = Transform3D::<f32>::from_matrix(mat);
1359
1360 assert_eq!(t.to_matrix(), mat);
1362
1363 assert_eq!(
1365 t.translation(),
1366 [
1367 Length32::new::<meter>(2.0),
1368 Length32::new::<meter>(3.0),
1369 Length32::new::<meter>(4.0),
1370 ]
1371 );
1372 assert_eq!(
1373 t.rotation(),
1374 [[0.0, -1.0, 0.0], [1.0, 0.0, 0.0], [0.0, 0.0, 1.0]]
1375 );
1376 }
1377
1378 #[test]
1379 fn transform_point_identity_is_a_fixed_point() {
1380 let identity = Transform3D::<f32>::from_matrix([
1381 [1.0, 0.0, 0.0, 0.0],
1382 [0.0, 1.0, 0.0, 0.0],
1383 [0.0, 0.0, 1.0, 0.0],
1384 [0.0, 0.0, 0.0, 1.0],
1385 ]);
1386 let p = Point3f::from_meters(1.5, -2.0, 0.25);
1387 assert_eq!(identity.transform_point(p), p);
1388 assert_eq!(identity.transform_vector(p), p);
1389 assert_eq!(identity.position(), Point3f::default());
1390 }
1391
1392 #[test]
1393 fn transform_points_matches_transform_point() {
1394 let t = quarter_turn_and_shift();
1395 let points = [
1396 Point3f::from_meters(1.0, 0.0, 0.0),
1397 Point3f::from_meters(-1.5, 2.0, 0.25),
1398 Point3f::from_meters(0.0, 0.0, 0.0),
1399 ];
1400 let mut set = Point3fSoa::<4>::default();
1401 for p in points {
1402 set.push(p);
1403 }
1404
1405 t.transform_points(&mut set);
1406 for (i, p) in points.iter().enumerate() {
1407 assert_point_close(set.get(i), t.transform_point(*p), 1e-5);
1408 }
1409 }
1410
1411 #[test]
1412 fn geodetic_position_converts_to_and_from_degrees() {
1413 let position = GeodeticPosition::from_degrees(59.319_221, 18.075_631);
1414
1415 assert_eq!(position.latitude.get::<degree>(), 59.319_221);
1416 assert_eq!(position.longitude.get::<degree>(), 18.075_631);
1417 assert_eq!(position.latitude_degrees(), 59.319_221);
1418 assert_eq!(position.longitude_degrees(), 18.075_631);
1419 }
1420}