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 {
376 #[cfg(feature = "glam")]
377 {
378 Self {
379 inner: TransformInner::from_matrix(mat),
380 }
381 }
382 #[cfg(not(feature = "glam"))]
383 {
384 Self { mat }
385 }
386 }
387
388 pub fn to_matrix(self) -> [[T; 4]; 4] {
390 #[cfg(feature = "glam")]
391 {
392 self.inner.to_matrix()
393 }
394 #[cfg(not(feature = "glam"))]
395 {
396 self.mat
397 }
398 }
399
400 #[cfg(not(feature = "glam"))]
402 pub fn mat_mut(&mut self) -> &mut [[T; 4]; 4] {
403 &mut self.mat
404 }
405}
406
407#[cfg(feature = "glam")]
408impl<T: Copy + Debug + Default + 'static> TransformInner<T> {
409 fn from_matrix(mat: [[T; 4]; 4]) -> Self {
410 use core::any::TypeId;
411
412 if TypeId::of::<T>() == TypeId::of::<f32>() {
415 let mat_f32: [[f32; 4]; 4] = unsafe { core::mem::transmute_copy(&mat) };
418 let glam_mat = Mat4::from_cols_array_2d(&mat_f32);
419 let affine = Affine3A::from_mat4(glam_mat);
420 unsafe { core::mem::transmute_copy(&TransformInner::<T>::F32(affine)) }
422 } else if TypeId::of::<T>() == TypeId::of::<f64>() {
423 let mat_f64: [[f64; 4]; 4] = unsafe { core::mem::transmute_copy(&mat) };
426 let glam_mat = DMat4::from_cols_array_2d(&mat_f64);
428 let affine = DAffine3::from_mat4(glam_mat);
429 unsafe { core::mem::transmute_copy(&TransformInner::<T>::F64(affine)) }
431 } else {
432 panic!("Transform3D only supports f32 and f64 types when using glam feature");
433 }
434 }
435
436 fn to_matrix(self) -> [[T; 4]; 4] {
437 match self {
438 TransformInner::F32(affine) => {
439 let mat = Mat4::from(affine);
440 let mat_array = mat.to_cols_array_2d();
441 unsafe { core::mem::transmute_copy(&mat_array) }
443 }
444 TransformInner::F64(affine) => {
445 let mat = DMat4::from(affine);
446 let mat_array = mat.to_cols_array_2d();
447 unsafe { core::mem::transmute_copy(&mat_array) }
449 }
450 TransformInner::_Phantom(_) => unreachable!(),
451 }
452 }
453}
454
455impl_transform_accessors!(f32, Length32, F32);
456impl_transform_accessors!(f64, Length64, F64);
457
458macro_rules! impl_transform_points {
459 ($ty:ty, $len:ty, $variant:ident, $vec:ty) => {
460 impl Transform3D<$ty> {
461 pub fn position(&self) -> Point3<$len> {
464 #[cfg(feature = "glam")]
465 {
466 match &self.inner {
467 TransformInner::$variant(affine) => {
468 let t = affine.translation;
469 Point3::new(
470 <$len>::new::<meter>(t.x as $ty),
471 <$len>::new::<meter>(t.y as $ty),
472 <$len>::new::<meter>(t.z as $ty),
473 )
474 }
475 _ => unreachable!(),
476 }
477 }
478 #[cfg(not(feature = "glam"))]
479 {
480 Point3::new(
481 <$len>::new::<meter>(self.mat[0][3]),
482 <$len>::new::<meter>(self.mat[1][3]),
483 <$len>::new::<meter>(self.mat[2][3]),
484 )
485 }
486 }
487
488 pub fn transform_point(&self, p: Point3<$len>) -> Point3<$len> {
490 #[cfg(feature = "glam")]
491 {
492 match &self.inner {
493 TransformInner::$variant(affine) => {
494 let out = affine.transform_point3(<$vec>::new(
495 p.x.raw(),
496 p.y.raw(),
497 p.z.raw(),
498 ));
499 Point3::new(
500 <$len>::new::<meter>(out.x),
501 <$len>::new::<meter>(out.y),
502 <$len>::new::<meter>(out.z),
503 )
504 }
505 _ => unreachable!(),
506 }
507 }
508 #[cfg(not(feature = "glam"))]
509 {
510 let m = &self.mat;
511 let (x, y, z) = (p.x.raw(), p.y.raw(), p.z.raw());
512 Point3::new(
513 <$len>::new::<meter>(m[0][0] * x + m[0][1] * y + m[0][2] * z + m[0][3]),
514 <$len>::new::<meter>(m[1][0] * x + m[1][1] * y + m[1][2] * z + m[1][3]),
515 <$len>::new::<meter>(m[2][0] * x + m[2][1] * y + m[2][2] * z + m[2][3]),
516 )
517 }
518 }
519
520 pub fn transform_vector(&self, v: Point3<$len>) -> Point3<$len> {
523 #[cfg(feature = "glam")]
524 {
525 match &self.inner {
526 TransformInner::$variant(affine) => {
527 let out = affine.transform_vector3(<$vec>::new(
528 v.x.raw(),
529 v.y.raw(),
530 v.z.raw(),
531 ));
532 Point3::new(
533 <$len>::new::<meter>(out.x),
534 <$len>::new::<meter>(out.y),
535 <$len>::new::<meter>(out.z),
536 )
537 }
538 _ => unreachable!(),
539 }
540 }
541 #[cfg(not(feature = "glam"))]
542 {
543 let m = &self.mat;
544 let (x, y, z) = (v.x.raw(), v.y.raw(), v.z.raw());
545 Point3::new(
546 <$len>::new::<meter>(m[0][0] * x + m[0][1] * y + m[0][2] * z),
547 <$len>::new::<meter>(m[1][0] * x + m[1][1] * y + m[1][2] * z),
548 <$len>::new::<meter>(m[2][0] * x + m[2][1] * y + m[2][2] * z),
549 )
550 }
551 }
552
553 pub fn transform_points<const N: usize>(&self, points: &mut Point3Soa<$len, N>) {
557 #[cfg(feature = "glam")]
558 let m = match &self.inner {
559 TransformInner::$variant(affine) => {
560 let r = &affine.matrix3;
561 let t = affine.translation;
562 [
563 [r.x_axis.x, r.y_axis.x, r.z_axis.x, t.x],
564 [r.x_axis.y, r.y_axis.y, r.z_axis.y, t.y],
565 [r.x_axis.z, r.y_axis.z, r.z_axis.z, t.z],
566 ]
567 }
568 _ => unreachable!(),
569 };
570 #[cfg(not(feature = "glam"))]
571 let m = [self.mat[0], self.mat[1], self.mat[2]];
572
573 let n = points.len();
574 for i in 0..n {
575 let (x, y, z) = (points.x[i].raw(), points.y[i].raw(), points.z[i].raw());
576 points.x[i] =
577 <$len>::new::<meter>(m[0][0] * x + m[0][1] * y + m[0][2] * z + m[0][3]);
578 points.y[i] =
579 <$len>::new::<meter>(m[1][0] * x + m[1][1] * y + m[1][2] * z + m[1][3]);
580 points.z[i] =
581 <$len>::new::<meter>(m[2][0] * x + m[2][1] * y + m[2][2] * z + m[2][3]);
582 }
583 }
584 }
585 };
586}
587
588impl_transform_points!(f32, Length32, F32, Vec3);
589impl_transform_points!(f64, Length64, F64, DVec3);
590
591impl<T: Copy + Debug + Default + 'static> Default for Transform3D<T> {
592 fn default() -> Self {
593 Self::from_matrix([[T::default(); 4]; 4])
594 }
595}
596
597macro_rules! impl_transform_mul {
598 ($ty:ty, $zero:expr, $variant:ident) => {
599 impl Mul for Transform3D<$ty> {
600 type Output = Self;
601
602 fn mul(self, rhs: Self) -> Self::Output {
603 #[cfg(feature = "glam")]
604 {
605 match (&self.inner, &rhs.inner) {
606 (TransformInner::$variant(a), TransformInner::$variant(b)) => Self {
607 inner: TransformInner::$variant(*a * *b),
608 },
609 _ => unreachable!(),
610 }
611 }
612 #[cfg(not(feature = "glam"))]
613 {
614 let mut result = [[$zero; 4]; 4];
615 for i in 0..4 {
616 for j in 0..4 {
617 let mut sum = $zero;
618 for k in 0..4 {
619 sum += self.mat[i][k] * rhs.mat[k][j];
620 }
621 result[i][j] = sum;
622 }
623 }
624 Self { mat: result }
625 }
626 }
627 }
628 };
629}
630
631impl_transform_mul!(f32, 0.0f32, F32);
632impl_transform_mul!(f64, 0.0f64, F64);
633
634impl Mul for &Transform3D<f32> {
636 type Output = Transform3D<f32>;
637
638 fn mul(self, rhs: Self) -> Self::Output {
639 *self * *rhs
640 }
641}
642
643impl Mul<Transform3D<f32>> for &Transform3D<f32> {
644 type Output = Transform3D<f32>;
645
646 fn mul(self, rhs: Transform3D<f32>) -> Self::Output {
647 *self * rhs
648 }
649}
650
651impl Mul<&Transform3D<f32>> for Transform3D<f32> {
652 type Output = Transform3D<f32>;
653
654 fn mul(self, rhs: &Transform3D<f32>) -> Self::Output {
655 self * *rhs
656 }
657}
658
659impl Mul for &Transform3D<f64> {
661 type Output = Transform3D<f64>;
662
663 fn mul(self, rhs: Self) -> Self::Output {
664 *self * *rhs
665 }
666}
667
668impl Mul<Transform3D<f64>> for &Transform3D<f64> {
669 type Output = Transform3D<f64>;
670
671 fn mul(self, rhs: Transform3D<f64>) -> Self::Output {
672 *self * rhs
673 }
674}
675
676impl Mul<&Transform3D<f64>> for Transform3D<f64> {
677 type Output = Transform3D<f64>;
678
679 fn mul(self, rhs: &Transform3D<f64>) -> Self::Output {
680 self * *rhs
681 }
682}
683
684macro_rules! impl_transform_inverse {
685 ($ty:ty, $zero:expr, $one:expr, $variant:ident) => {
686 impl Transform3D<$ty> {
687 pub fn inverse(&self) -> Self {
689 #[cfg(feature = "glam")]
690 {
691 match &self.inner {
692 TransformInner::$variant(affine) => Self {
693 inner: TransformInner::$variant(affine.inverse()),
694 },
695 _ => unreachable!(),
696 }
697 }
698 #[cfg(not(feature = "glam"))]
699 {
700 let mat = self.mat;
701 let r = [
703 [mat[0][0], mat[0][1], mat[0][2]],
704 [mat[1][0], mat[1][1], mat[1][2]],
705 [mat[2][0], mat[2][1], mat[2][2]],
706 ];
707
708 let t = [mat[0][3], mat[1][3], mat[2][3]];
710
711 let r_inv = [
713 [r[0][0], r[1][0], r[2][0]],
714 [r[0][1], r[1][1], r[2][1]],
715 [r[0][2], r[1][2], r[2][2]],
716 ];
717
718 let t_inv = [
720 -(r_inv[0][0] * t[0] + r_inv[0][1] * t[1] + r_inv[0][2] * t[2]),
721 -(r_inv[1][0] * t[0] + r_inv[1][1] * t[1] + r_inv[1][2] * t[2]),
722 -(r_inv[2][0] * t[0] + r_inv[2][1] * t[1] + r_inv[2][2] * t[2]),
723 ];
724
725 let mut inv_mat = [[$zero; 4]; 4];
727
728 for i in 0..3 {
730 for j in 0..3 {
731 inv_mat[i][j] = r_inv[i][j];
732 }
733 }
734
735 inv_mat[0][3] = t_inv[0];
737 inv_mat[1][3] = t_inv[1];
738 inv_mat[2][3] = t_inv[2];
739
740 inv_mat[3][3] = $one;
742
743 Self { mat: inv_mat }
744 }
745 }
746 }
747 };
748}
749
750impl_transform_inverse!(f32, 0.0f32, 1.0f32, F32);
751impl_transform_inverse!(f64, 0.0f64, 1.0f64, F64);
752
753#[cfg(feature = "faer")]
754mod faer_integration {
755 use super::Transform3D;
756 use faer::prelude::*;
757
758 impl From<&Transform3D<f64>> for Mat<f64> {
759 fn from(p: &Transform3D<f64>) -> Self {
760 let mat_array = p.to_matrix();
761 let mut mat: Mat<f64> = Mat::zeros(4, 4);
762 for (r, row) in mat_array.iter().enumerate() {
763 for (c, item) in row.iter().enumerate() {
764 *mat.get_mut(r, c) = *item;
765 }
766 }
767 mat
768 }
769 }
770
771 impl From<Mat<f64>> for Transform3D<f64> {
772 fn from(mat: Mat<f64>) -> Self {
773 assert_eq!(mat.nrows(), 4);
774 assert_eq!(mat.ncols(), 4);
775 let mut transform = [[0.0; 4]; 4];
776 for (r, row) in transform.iter_mut().enumerate() {
777 for (c, val) in row.iter_mut().enumerate() {
778 *val = *mat.get(r, c);
779 }
780 }
781 Self::from_matrix(transform)
782 }
783 }
784}
785
786#[cfg(feature = "nalgebra")]
788mod nalgebra_integration {
789 use super::Transform3D;
790 use nalgebra::{Isometry3, Matrix3, Matrix4, Rotation3, Translation3, Vector3};
791
792 impl From<&Transform3D<f64>> for Isometry3<f64> {
793 fn from(pose: &Transform3D<f64>) -> Self {
794 let mat_array = pose.to_matrix();
795 let flat_transform: [f64; 16] = core::array::from_fn(|i| mat_array[i / 4][i % 4]);
796 let matrix = Matrix4::from_row_slice(&flat_transform);
797
798 let rotation_matrix: Matrix3<f64> = matrix.fixed_view::<3, 3>(0, 0).into();
799 let rotation = Rotation3::from_matrix_unchecked(rotation_matrix);
800
801 let translation_vector: Vector3<f64> = matrix.fixed_view::<3, 1>(0, 3).into();
802 let translation = Translation3::from(translation_vector);
803
804 Isometry3::from_parts(translation, rotation.into())
805 }
806 }
807
808 impl From<Isometry3<f64>> for Transform3D<f64> {
809 fn from(iso: Isometry3<f64>) -> Self {
810 let matrix = iso.to_homogeneous();
811 let transform = core::array::from_fn(|r| core::array::from_fn(|c| matrix[(r, c)]));
812 Transform3D::from_matrix(transform)
813 }
814 }
815}
816
817#[cfg(feature = "glam")]
819mod glam_integration {
820 use super::Transform3D;
821 use glam::{Affine3A, DAffine3};
822
823 impl From<Transform3D<f64>> for DAffine3 {
824 fn from(p: Transform3D<f64>) -> Self {
825 let mat = p.to_matrix();
826 let mut aff = DAffine3::IDENTITY;
827 aff.matrix3.x_axis.x = mat[0][0];
828 aff.matrix3.x_axis.y = mat[0][1];
829 aff.matrix3.x_axis.z = mat[0][2];
830
831 aff.matrix3.y_axis.x = mat[1][0];
832 aff.matrix3.y_axis.y = mat[1][1];
833 aff.matrix3.y_axis.z = mat[1][2];
834
835 aff.matrix3.z_axis.x = mat[2][0];
836 aff.matrix3.z_axis.y = mat[2][1];
837 aff.matrix3.z_axis.z = mat[2][2];
838
839 aff.translation.x = mat[3][0];
840 aff.translation.y = mat[3][1];
841 aff.translation.z = mat[3][2];
842
843 aff
844 }
845 }
846
847 impl From<DAffine3> for Transform3D<f64> {
848 fn from(aff: DAffine3) -> Self {
849 let mut transform = [[0.0f64; 4]; 4];
850
851 transform[0][0] = aff.matrix3.x_axis.x;
852 transform[0][1] = aff.matrix3.x_axis.y;
853 transform[0][2] = aff.matrix3.x_axis.z;
854
855 transform[1][0] = aff.matrix3.y_axis.x;
856 transform[1][1] = aff.matrix3.y_axis.y;
857 transform[1][2] = aff.matrix3.y_axis.z;
858
859 transform[2][0] = aff.matrix3.z_axis.x;
860 transform[2][1] = aff.matrix3.z_axis.y;
861 transform[2][2] = aff.matrix3.z_axis.z;
862
863 transform[3][0] = aff.translation.x;
864 transform[3][1] = aff.translation.y;
865 transform[3][2] = aff.translation.z;
866 transform[3][3] = 1.0;
867
868 Transform3D::from_matrix(transform)
869 }
870 }
871
872 impl From<Transform3D<f32>> for Affine3A {
873 fn from(p: Transform3D<f32>) -> Self {
874 let mat = p.to_matrix();
875 let mut aff = Affine3A::IDENTITY;
876 aff.matrix3.x_axis.x = mat[0][0];
877 aff.matrix3.x_axis.y = mat[0][1];
878 aff.matrix3.x_axis.z = mat[0][2];
879
880 aff.matrix3.y_axis.x = mat[1][0];
881 aff.matrix3.y_axis.y = mat[1][1];
882 aff.matrix3.y_axis.z = mat[1][2];
883
884 aff.matrix3.z_axis.x = mat[2][0];
885 aff.matrix3.z_axis.y = mat[2][1];
886 aff.matrix3.z_axis.z = mat[2][2];
887
888 aff.translation.x = mat[0][3];
889 aff.translation.y = mat[1][3];
890 aff.translation.z = mat[2][3];
891
892 aff
893 }
894 }
895
896 impl From<Affine3A> for Transform3D<f32> {
897 fn from(aff: Affine3A) -> Self {
898 let mut transform = [[0.0f32; 4]; 4];
899
900 transform[0][0] = aff.matrix3.x_axis.x;
901 transform[0][1] = aff.matrix3.x_axis.y;
902 transform[0][2] = aff.matrix3.x_axis.z;
903
904 transform[1][0] = aff.matrix3.y_axis.x;
905 transform[1][1] = aff.matrix3.y_axis.y;
906 transform[1][2] = aff.matrix3.y_axis.z;
907
908 transform[2][0] = aff.matrix3.z_axis.x;
909 transform[2][1] = aff.matrix3.z_axis.y;
910 transform[2][2] = aff.matrix3.z_axis.z;
911
912 transform[0][3] = aff.translation.x;
913 transform[1][3] = aff.translation.y;
914 transform[2][3] = aff.translation.z;
915 transform[3][3] = 1.0;
916
917 Transform3D::from_matrix(transform)
918 }
919 }
920}
921
922#[cfg(feature = "nalgebra")]
923#[allow(unused_imports)]
924pub use nalgebra_integration::*;
925
926#[cfg(feature = "faer")]
927#[allow(unused_imports)]
928pub use faer_integration::*;
929
930#[cfg(test)]
931mod tests {
932 use super::*;
933
934 const CONST_PARENT_TO_INTERMEDIATE: Transform3D<f32> =
935 Transform3D::<f32>::from_translation_euler_xyz(
936 [
937 Length32 { value: 1.0 },
938 Length32 { value: 2.0 },
939 Length32 { value: 3.0 },
940 ],
941 [
942 Angle32 { value: 0.0 },
943 Angle32 { value: 0.0 },
944 Angle32 {
945 value: core::f32::consts::FRAC_PI_2,
946 },
947 ],
948 );
949 const CONST_INTERMEDIATE_TO_CHILD: Transform3D<f32> =
950 Transform3D::<f32>::from_translation_euler_xyz(
951 [
952 Length32 { value: 1.0 },
953 Length32 { value: 0.0 },
954 Length32 { value: 0.0 },
955 ],
956 [Angle32 { value: 0.0 }; 3],
957 );
958 const CONST_PARENT_TO_CHILD: Transform3D<f32> =
959 CONST_PARENT_TO_INTERMEDIATE.compose(CONST_INTERMEDIATE_TO_CHILD);
960 const CONST_TRANSFORM_F64: Transform3D<f64> = Transform3D::<f64>::from_translation_euler_xyz(
961 [Length64 { value: 0.0 }; 3],
962 [
963 Angle64 {
964 value: core::f64::consts::PI / 6.0,
965 },
966 Angle64 {
967 value: -core::f64::consts::PI / 9.0,
968 },
969 Angle64 {
970 value: core::f64::consts::PI / 18.0,
971 },
972 ],
973 );
974
975 fn assert_matrix_close<const N: usize, T: Copy + Into<f64>>(
976 lhs: [[T; N]; N],
977 rhs: [[T; N]; N],
978 eps: f64,
979 ) {
980 for i in 0..N {
981 for j in 0..N {
982 let lhs = lhs[i][j].into();
983 let rhs = rhs[i][j].into();
984 assert!(
985 (lhs - rhs).abs() <= eps,
986 "Element at [{},{}] differs: {} vs expected {}",
987 i,
988 j,
989 lhs,
990 rhs
991 );
992 }
993 }
994 }
995
996 #[test]
997 fn const_sin_cos_matches_runtime_trigonometry() {
998 for step in -64..=64 {
999 let angle = f64::from(step) * core::f64::consts::PI / 8.0;
1000 let (actual_sin, actual_cos) = const_sin_cos(angle);
1001 let (expected_sin, expected_cos) = angle.sin_cos();
1002 assert!((actual_sin - expected_sin).abs() <= 1e-14);
1003 assert!((actual_cos - expected_cos).abs() <= 1e-14);
1004 }
1005 }
1006
1007 #[test]
1008 fn const_transform_construction_and_composition_are_semantic() {
1009 assert_point_close(
1010 CONST_PARENT_TO_CHILD.position(),
1011 Point3f::from_meters(1.0, 3.0, 3.0),
1012 1e-5,
1013 );
1014 assert_point_close(
1015 CONST_PARENT_TO_CHILD.transform_vector(Point3f::from_meters(1.0, 0.0, 0.0)),
1016 Point3f::from_meters(0.0, 1.0, 0.0),
1017 1e-5,
1018 );
1019
1020 let point = Point3::new(
1021 Length64::new::<meter>(1.0),
1022 Length64::new::<meter>(2.0),
1023 Length64::new::<meter>(3.0),
1024 );
1025 let transformed = CONST_TRANSFORM_F64.transform_vector(point);
1026 assert!(transformed.x.raw().is_finite());
1027 assert!(transformed.y.raw().is_finite());
1028 assert!(transformed.z.raw().is_finite());
1029 }
1030
1031 #[test]
1032 fn test_pose_default() {
1033 let pose: Transform3D<f32> = Transform3D::default();
1034 let mat = pose.to_matrix();
1035
1036 #[cfg(feature = "glam")]
1039 {
1040 let expected = [
1043 [0.0, 0.0, 0.0, 0.0],
1044 [0.0, 0.0, 0.0, 0.0],
1045 [0.0, 0.0, 0.0, 0.0],
1046 [0.0, 0.0, 0.0, 1.0], ];
1048 assert_eq!(mat, expected, "Default pose with glam should have w=1");
1049 }
1050
1051 #[cfg(not(feature = "glam"))]
1052 {
1053 assert_eq!(
1054 mat, [[0.0; 4]; 4],
1055 "Default pose without glam should be a zero matrix"
1056 );
1057 }
1058 }
1059
1060 #[test]
1061 fn test_transform_inverse_f32() {
1062 let transform = Transform3D::<f32>::from_matrix([
1064 [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], ]);
1069
1070 let inverse = transform.inverse();
1072
1073 let expected_inverse = Transform3D::<f32>::from_matrix([
1075 [1.0, 0.0, 0.0, -2.0], [0.0, 1.0, 0.0, -3.0],
1077 [0.0, 0.0, 1.0, -4.0],
1078 [0.0, 0.0, 0.0, 1.0],
1079 ]);
1080
1081 let epsilon = 1e-5;
1083 let inv_mat = inverse.to_matrix();
1084 let exp_mat = expected_inverse.to_matrix();
1085 assert_matrix_close(inv_mat, exp_mat, epsilon);
1086 }
1087
1088 #[test]
1089 fn test_transform_inverse_f64() {
1090 let transform = Transform3D::<f64>::from_matrix([
1092 [0.0, -1.0, 0.0, 5.0], [1.0, 0.0, 0.0, 6.0],
1094 [0.0, 0.0, 1.0, 7.0],
1095 [0.0, 0.0, 0.0, 1.0],
1096 ]);
1097
1098 let inverse = transform.inverse();
1100
1101 let expected_inverse = Transform3D::<f64>::from_matrix([
1103 [0.0, 1.0, 0.0, -6.0], [-1.0, 0.0, 0.0, 5.0],
1105 [0.0, 0.0, 1.0, -7.0],
1106 [0.0, 0.0, 0.0, 1.0],
1107 ]);
1108
1109 let epsilon = 1e-10;
1111 let inv_mat = inverse.to_matrix();
1112 let exp_mat = expected_inverse.to_matrix();
1113 assert_matrix_close(inv_mat, exp_mat, epsilon);
1114 }
1115
1116 #[test]
1117 fn test_transform_inverse_identity() {
1118 let identity = Transform3D::<f32>::from_matrix([
1120 [1.0, 0.0, 0.0, 0.0],
1121 [0.0, 1.0, 0.0, 0.0],
1122 [0.0, 0.0, 1.0, 0.0],
1123 [0.0, 0.0, 0.0, 1.0],
1124 ]);
1125
1126 let inverse = identity.inverse();
1128
1129 let epsilon = 1e-5;
1131 let inv_mat = inverse.to_matrix();
1132 let id_mat = identity.to_matrix();
1133 assert_matrix_close(inv_mat, id_mat, epsilon);
1134 }
1135
1136 #[test]
1137 fn test_transform_multiplication_f32() {
1138 let t1 = Transform3D::<f32>::from_matrix([
1140 [1.0, 0.0, 0.0, 2.0], [0.0, 1.0, 0.0, 3.0],
1142 [0.0, 0.0, 1.0, 4.0],
1143 [0.0, 0.0, 0.0, 1.0],
1144 ]);
1145
1146 let t2 = Transform3D::<f32>::from_matrix([
1147 [0.0, -1.0, 0.0, 5.0], [1.0, 0.0, 0.0, 6.0],
1149 [0.0, 0.0, 1.0, 7.0],
1150 [0.0, 0.0, 0.0, 1.0],
1151 ]);
1152
1153 let result = t1 * t2;
1155
1156 let expected = Transform3D::<f32>::from_matrix([
1158 [0.0, -1.0, 0.0, 7.0], [1.0, 0.0, 0.0, 9.0],
1160 [0.0, 0.0, 1.0, 11.0],
1161 [0.0, 0.0, 0.0, 1.0],
1162 ]);
1163
1164 let epsilon = 1e-5;
1166 let res_mat = result.to_matrix();
1167 let exp_mat = expected.to_matrix();
1168 assert_matrix_close(res_mat, exp_mat, epsilon);
1169 }
1170
1171 #[test]
1172 fn test_transform_multiplication_f64() {
1173 let t1 = Transform3D::<f64>::from_matrix([
1175 [1.0, 0.0, 0.0, 2.0], [0.0, 1.0, 0.0, 3.0],
1177 [0.0, 0.0, 1.0, 4.0],
1178 [0.0, 0.0, 0.0, 1.0],
1179 ]);
1180
1181 let t2 = Transform3D::<f64>::from_matrix([
1182 [0.0, -1.0, 0.0, 5.0], [1.0, 0.0, 0.0, 6.0],
1184 [0.0, 0.0, 1.0, 7.0],
1185 [0.0, 0.0, 0.0, 1.0],
1186 ]);
1187
1188 let result = t1 * t2;
1190
1191 let expected = Transform3D::<f64>::from_matrix([
1193 [0.0, -1.0, 0.0, 7.0], [1.0, 0.0, 0.0, 9.0],
1195 [0.0, 0.0, 1.0, 11.0],
1196 [0.0, 0.0, 0.0, 1.0],
1197 ]);
1198
1199 let epsilon = 1e-10;
1201 let res_mat = result.to_matrix();
1202 let exp_mat = expected.to_matrix();
1203 assert_matrix_close(res_mat, exp_mat, epsilon);
1204 }
1205
1206 #[test]
1207 fn test_transform_reference_multiplication() {
1208 let t1 = Transform3D::<f32>::from_matrix([
1210 [1.0, 0.0, 0.0, 2.0],
1211 [0.0, 1.0, 0.0, 3.0],
1212 [0.0, 0.0, 1.0, 4.0],
1213 [0.0, 0.0, 0.0, 1.0],
1214 ]);
1215
1216 let t2 = Transform3D::<f32>::from_matrix([
1217 [0.0, -1.0, 0.0, 5.0],
1218 [1.0, 0.0, 0.0, 6.0],
1219 [0.0, 0.0, 1.0, 7.0],
1220 [0.0, 0.0, 0.0, 1.0],
1221 ]);
1222
1223 let result = t1 * t2;
1225
1226 let expected = Transform3D::<f32>::from_matrix([
1228 [0.0, -1.0, 0.0, 7.0],
1229 [1.0, 0.0, 0.0, 9.0],
1230 [0.0, 0.0, 1.0, 11.0],
1231 [0.0, 0.0, 0.0, 1.0],
1232 ]);
1233
1234 let epsilon = 1e-5;
1236 let res_mat = result.to_matrix();
1237 let exp_mat = expected.to_matrix();
1238 assert_matrix_close(res_mat, exp_mat, epsilon);
1239 }
1240
1241 #[cfg(feature = "faer")]
1242 #[test]
1243 fn test_pose_faer_conversion() {
1244 use faer::prelude::*;
1245
1246 let pose = Transform3D::from_matrix([
1247 [1.0, 2.0, 3.0, 4.0],
1248 [5.0, 6.0, 7.0, 8.0],
1249 [9.0, 10.0, 11.0, 12.0],
1250 [13.0, 14.0, 15.0, 16.0],
1251 ]);
1252
1253 let mat: Mat<f64> = (&pose).into();
1254 let pose_from_mat = Transform3D::from(mat);
1255
1256 assert_eq!(
1257 pose.to_matrix(),
1258 pose_from_mat.to_matrix(),
1259 "Faer conversion should be lossless"
1260 );
1261 }
1262
1263 #[cfg(feature = "nalgebra")]
1264 #[test]
1265 fn test_pose_nalgebra_conversion() {
1266 use nalgebra::Isometry3;
1267
1268 let pose = Transform3D::from_matrix([
1269 [1.0, 0.0, 0.0, 2.0],
1270 [0.0, 1.0, 0.0, 3.0],
1271 [0.0, 0.0, 1.0, 4.0],
1272 [0.0, 0.0, 0.0, 1.0],
1273 ]);
1274
1275 let iso: Isometry3<f64> = (&pose.clone()).into();
1276 let pose_from_iso: Transform3D<f64> = iso.into();
1277
1278 assert_eq!(
1279 pose.to_matrix(),
1280 pose_from_iso.to_matrix(),
1281 "Nalgebra conversion should be lossless"
1282 );
1283 }
1284
1285 #[cfg(feature = "glam")]
1286 #[test]
1287 fn test_pose_glam_conversion() {
1288 use glam::DAffine3;
1289
1290 let pose = Transform3D::from_matrix([
1291 [1.0, 0.0, 0.0, 0.0],
1292 [0.0, 1.0, 0.0, 0.0],
1293 [0.0, 0.0, 1.0, 0.0],
1294 [5.0, 6.0, 7.0, 1.0],
1295 ]);
1296 let aff: DAffine3 = pose.into();
1297 assert_eq!(aff.translation[0], 5.0);
1298 let pose_from_aff: Transform3D<f64> = aff.into();
1299
1300 assert_eq!(
1301 pose.to_matrix(),
1302 pose_from_aff.to_matrix(),
1303 "Glam conversion should be lossless"
1304 );
1305 }
1306
1307 #[cfg(feature = "glam")]
1308 #[test]
1309 fn test_matrix_format_issue() {
1310 use glam::Mat4;
1311
1312 let row_major = [
1314 [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], ];
1319
1320 let col_major = [
1323 [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], ];
1328
1329 let mat_from_row = Mat4::from_cols_array_2d(&row_major);
1331 let mat_from_col = Mat4::from_cols_array_2d(&col_major);
1332
1333 assert_ne!(mat_from_row.w_axis.x, 5.0); assert_eq!(mat_from_col.w_axis.x, 5.0);
1338 assert_eq!(mat_from_col.w_axis.y, 6.0);
1339 assert_eq!(mat_from_col.w_axis.z, 7.0);
1340
1341 let mat_transposed = Mat4::from_cols_array_2d(&row_major).transpose();
1343 assert_eq!(mat_transposed.w_axis.x, 5.0);
1344 assert_eq!(mat_transposed.w_axis.y, 6.0);
1345 assert_eq!(mat_transposed.w_axis.z, 7.0);
1346 }
1347
1348 fn quarter_turn_and_shift() -> Transform3D<f32> {
1352 #[cfg(feature = "glam")]
1353 {
1354 Transform3D::from_matrix([
1355 [0.0, 1.0, 0.0, 0.0],
1356 [-1.0, 0.0, 0.0, 0.0],
1357 [0.0, 0.0, 1.0, 0.0],
1358 [2.0, 3.0, 4.0, 1.0],
1359 ])
1360 }
1361 #[cfg(not(feature = "glam"))]
1362 {
1363 Transform3D::from_matrix([
1364 [0.0, -1.0, 0.0, 2.0],
1365 [1.0, 0.0, 0.0, 3.0],
1366 [0.0, 0.0, 1.0, 4.0],
1367 [0.0, 0.0, 0.0, 1.0],
1368 ])
1369 }
1370 }
1371
1372 fn assert_point_close(lhs: Point3f, rhs: Point3f, eps: f32) {
1373 assert!(
1374 (lhs.x - rhs.x).raw().abs() <= eps
1375 && (lhs.y - rhs.y).raw().abs() <= eps
1376 && (lhs.z - rhs.z).raw().abs() <= eps,
1377 "{lhs:?} differs from {rhs:?}"
1378 );
1379 }
1380
1381 #[test]
1382 fn transform_point_rotates_and_translates() {
1383 let t = quarter_turn_and_shift();
1384 let p = Point3f::from_meters(1.0, 0.0, 0.0);
1385 assert_point_close(
1386 t.transform_point(p),
1387 Point3f::from_meters(2.0, 4.0, 4.0),
1388 1e-5,
1389 );
1390 assert_point_close(
1391 t.transform_vector(p),
1392 Point3f::from_meters(0.0, 1.0, 0.0),
1393 1e-5,
1394 );
1395 assert_point_close(t.position(), Point3f::from_meters(2.0, 3.0, 4.0), 1e-5);
1396 }
1397
1398 #[test]
1399 fn transform_accessors_are_backend_independent() {
1400 let t = quarter_turn_and_shift();
1401
1402 assert_eq!(
1403 t.translation(),
1404 [
1405 Length32::new::<meter>(2.0),
1406 Length32::new::<meter>(3.0),
1407 Length32::new::<meter>(4.0),
1408 ]
1409 );
1410 assert_eq!(
1411 t.rotation(),
1412 [[0.0, -1.0, 0.0], [1.0, 0.0, 0.0], [0.0, 0.0, 1.0]]
1413 );
1414 }
1415
1416 #[test]
1417 fn transform_point_identity_is_a_fixed_point() {
1418 let identity = Transform3D::<f32>::from_matrix([
1419 [1.0, 0.0, 0.0, 0.0],
1420 [0.0, 1.0, 0.0, 0.0],
1421 [0.0, 0.0, 1.0, 0.0],
1422 [0.0, 0.0, 0.0, 1.0],
1423 ]);
1424 let p = Point3f::from_meters(1.5, -2.0, 0.25);
1425 assert_eq!(identity.transform_point(p), p);
1426 assert_eq!(identity.transform_vector(p), p);
1427 assert_eq!(identity.position(), Point3f::default());
1428 }
1429
1430 #[test]
1431 fn transform_points_matches_transform_point() {
1432 let t = quarter_turn_and_shift();
1433 let points = [
1434 Point3f::from_meters(1.0, 0.0, 0.0),
1435 Point3f::from_meters(-1.5, 2.0, 0.25),
1436 Point3f::from_meters(0.0, 0.0, 0.0),
1437 ];
1438 let mut set = Point3fSoa::<4>::default();
1439 for p in points {
1440 set.push(p);
1441 }
1442
1443 t.transform_points(&mut set);
1444 for (i, p) in points.iter().enumerate() {
1445 assert_point_close(set.get(i), t.transform_point(*p), 1e-5);
1446 }
1447 }
1448
1449 #[test]
1450 fn geodetic_position_converts_to_and_from_degrees() {
1451 let position = GeodeticPosition::from_degrees(59.319_221, 18.075_631);
1452
1453 assert_eq!(position.latitude.get::<degree>(), 59.319_221);
1454 assert_eq!(position.longitude.get::<degree>(), 18.075_631);
1455 assert_eq!(position.latitude_degrees(), 59.319_221);
1456 assert_eq!(position.longitude_degrees(), 18.075_631);
1457 }
1458}