Skip to main content

avian3d/math/
mod.rs

1//! Math types and traits used by the crate.
2//!
3//! Most of the math types are feature-dependent, so they will be different for `2d`/`3d` and `f32`/`f64`.
4
5#![allow(unused_imports)]
6
7mod drot2;
8pub use drot2::*;
9
10#[cfg(not(feature = "f64"))]
11mod single;
12#[cfg(not(feature = "f64"))]
13pub use single::*;
14
15#[cfg(feature = "f64")]
16mod double;
17#[cfg(feature = "f64")]
18pub use double::*;
19
20use approx::abs_diff_ne;
21use bevy_math::{prelude::*, *};
22use bevy_shape::*;
23use glam_matrix_extras::{SymmetricDMat2, SymmetricDMat3, SymmetricMat2, SymmetricMat3};
24
25/// The active dimension.
26///
27/// This is a constant value of `2` in 2D and `3` in 3D.
28#[cfg(feature = "2d")]
29pub const DIM: usize = 2;
30
31/// The active dimension.
32///
33/// This is a constant value of `2` in 2D and `3` in 3D.
34#[cfg(feature = "3d")]
35pub const DIM: usize = 3;
36
37/// The real number vector type used by Avian.
38///
39/// The type is chosen as follows:
40///
41/// | Precision | `2d`    | `3d`    |
42/// |-----------|---------|---------|
43/// | `f32`     | `Vec2`  | `Vec3`  |
44/// | `f64`     | `DVec2` | `DVec3` |
45#[cfg(feature = "2d")]
46pub type RVector = RVec2;
47
48/// The real number vector type used by Avian.
49///
50/// The type is chosen as follows:
51///
52/// | Precision | `2d`    | `3d`    |
53/// |-----------|---------|---------|
54/// | `f32`     | `Vec2`  | `Vec3`  |
55/// | `f64`     | `DVec2` | `DVec3` |
56#[cfg(feature = "3d")]
57pub type RVector = RVec3;
58
59/// The single-precision vector type used by Avian.
60///
61/// This is a type alias for `Vec2` in 2D and `Vec3` in 3D.
62#[cfg(feature = "2d")]
63pub(crate) use bevy_math::Vec2 as Vector;
64
65/// The single-precision vector type used by Avian.
66///
67/// This is a type alias for `Vec2` in 2D and `Vec3` in 3D.
68#[cfg(feature = "3d")]
69pub(crate) use bevy_math::Vec3 as Vector;
70
71/// The `i32` vector type used by Avian.
72///
73/// This is a type alias for `IVec2` in 2D and `IVec3` in 3D.
74#[cfg(feature = "2d")]
75pub(crate) use bevy_math::IVec2 as IVector;
76
77/// The `i32` vector type used by Avian.
78///
79/// This is a type alias for `IVec2` in 2D and `IVec3` in 3D.
80#[cfg(feature = "3d")]
81pub(crate) use bevy_math::IVec3 as IVector;
82
83/// The ray type used by Avian.
84///
85/// This is a type alias for `Ray2d` in 2D and `Ray3d` in 3D.
86#[cfg(feature = "2d")]
87pub(crate) type Ray = Ray2d;
88
89/// The ray type used by Avian.
90///
91/// This is a type alias for `Ray2d` in 2D and `Ray3d` in 3D.
92#[cfg(feature = "3d")]
93pub(crate) type Ray = Ray3d;
94
95/// The direction type used by Avian.
96///
97/// This is a type alias for `Dir2` in 2D and `Dir3` in 3D.
98#[cfg(feature = "2d")]
99pub(crate) type Dir = Dir2;
100
101/// The direction type used by Avian.
102///
103/// This is a type alias for `Dir2` in 2D and `Dir3` in 3D.
104#[cfg(feature = "3d")]
105pub(crate) type Dir = Dir3;
106
107/// The `f32` vector type for angular values used by Avian.
108///
109/// This is a type alias for `f32` in 2D and `Vec3` in 3D.
110#[cfg(feature = "2d")]
111pub(crate) type AngularVector = f32;
112
113/// The `f32` vector type for angular values used by Avian.
114///
115/// This is a type alias for `f32` in 2D and `Vec3` in 3D.
116#[cfg(feature = "3d")]
117pub(crate) type AngularVector = Vector;
118
119/// The `f32` rotation type used by Avian.
120///
121/// This is a type alias for `Rot2` in 2D and `Quat` in 3D.
122#[cfg(feature = "2d")]
123pub(crate) type Rot = Rot2;
124
125/// The `f32` rotation type used by Avian.
126///
127/// This is a type alias for `Rot2` in 2D and `Quat` in 3D.
128#[cfg(feature = "3d")]
129pub(crate) type Rot = Quat;
130
131/// The isometry type used by Avian.
132///
133/// This is a type alias for `Isometry2d` in 2D and `Isometry3d` in 3D.
134#[cfg(feature = "2d")]
135pub(crate) type Isometry = Isometry2d;
136
137/// The isometry type used by Avian.
138///
139/// This is a type alias for `Isometry2d` in 2D and `Isometry3d` in 3D.
140#[cfg(feature = "3d")]
141pub(crate) type Isometry = Isometry3d;
142
143/// The symmetric tensor type used by Avian.
144/// Often used for angular inertia.
145///
146/// This is a type alias for `f32` in 2D and [`SymmetricMat3`] in 3D.
147#[cfg(feature = "2d")]
148pub type SymmetricTensor = f32;
149
150/// The symmetric tensor type used by Avian.
151/// Often used for angular inertia.
152///
153/// This is a type alias for `f32` in 2D and [`SymmetricMat3`] in 3D.
154#[cfg(feature = "3d")]
155pub type SymmetricTensor = SymmetricMat3;
156
157/// Adjusts the precision of the math type to the [`Real`] number precision.
158pub trait ToRealPrecision {
159    /// The math type with the precision adjusted to [`Real`].
160    type Adjusted;
161
162    /// Adjusts the precision of [`self`] to the [`Real`] number precision.
163    #[must_use]
164    fn real(&self) -> Self::Adjusted;
165}
166
167/// Adjusts the precision of the math type to `f32`.
168pub trait ToF32Precision {
169    /// The math type with the precision adjusted to `f32`.
170    type F32;
171
172    /// Adjusts the precision of [`self`] to `f32`.
173    #[must_use]
174    fn f32(&self) -> Self::F32;
175}
176
177impl ToF32Precision for f32 {
178    type F32 = Self;
179    fn f32(&self) -> Self::F32 {
180        *self
181    }
182}
183
184impl ToF32Precision for f64 {
185    type F32 = f32;
186    fn f32(&self) -> Self::F32 {
187        *self as f32
188    }
189}
190
191impl ToF32Precision for DVec3 {
192    type F32 = Vec3;
193    fn f32(&self) -> Self::F32 {
194        self.as_vec3()
195    }
196}
197
198impl ToF32Precision for Vec3 {
199    type F32 = Self;
200    fn f32(&self) -> Self::F32 {
201        *self
202    }
203}
204
205impl ToF32Precision for DVec2 {
206    type F32 = Vec2;
207    fn f32(&self) -> Self::F32 {
208        self.as_vec2()
209    }
210}
211
212impl ToF32Precision for Vec2 {
213    type F32 = Self;
214    fn f32(&self) -> Self::F32 {
215        *self
216    }
217}
218
219impl ToF32Precision for Vec4 {
220    type F32 = Self;
221    fn f32(&self) -> Self::F32 {
222        *self
223    }
224}
225
226impl ToF32Precision for DRot2 {
227    type F32 = Rot2;
228    fn f32(&self) -> Self::F32 {
229        Rot2::from_sin_cos(self.sin as f32, self.cos as f32)
230    }
231}
232
233impl ToF32Precision for Rot2 {
234    type F32 = Self;
235    fn f32(&self) -> Self::F32 {
236        *self
237    }
238}
239
240impl ToF32Precision for DQuat {
241    type F32 = Quat;
242    fn f32(&self) -> Self::F32 {
243        self.as_quat()
244    }
245}
246
247impl ToF32Precision for Quat {
248    type F32 = Self;
249    fn f32(&self) -> Self::F32 {
250        *self
251    }
252}
253
254impl ToF32Precision for DMat2 {
255    type F32 = Mat2;
256    fn f32(&self) -> Self::F32 {
257        self.as_mat2()
258    }
259}
260
261impl ToF32Precision for Mat2 {
262    type F32 = Self;
263    fn f32(&self) -> Self::F32 {
264        *self
265    }
266}
267
268impl ToF32Precision for SymmetricDMat2 {
269    type F32 = SymmetricMat2;
270    fn f32(&self) -> Self::F32 {
271        SymmetricMat2 {
272            m00: self.m00 as f32,
273            m01: self.m01 as f32,
274            m11: self.m11 as f32,
275        }
276    }
277}
278
279impl ToF32Precision for SymmetricMat2 {
280    type F32 = Self;
281    fn f32(&self) -> Self::F32 {
282        *self
283    }
284}
285
286impl ToF32Precision for DMat3 {
287    type F32 = Mat3;
288    fn f32(&self) -> Self::F32 {
289        self.as_mat3()
290    }
291}
292
293impl ToF32Precision for Mat3 {
294    type F32 = Self;
295    fn f32(&self) -> Self::F32 {
296        *self
297    }
298}
299
300impl ToF32Precision for SymmetricDMat3 {
301    type F32 = SymmetricMat3;
302    fn f32(&self) -> Self::F32 {
303        SymmetricMat3 {
304            m00: self.m00 as f32,
305            m01: self.m01 as f32,
306            m02: self.m02 as f32,
307            m11: self.m11 as f32,
308            m12: self.m12 as f32,
309            m22: self.m22 as f32,
310        }
311    }
312}
313
314impl ToF32Precision for SymmetricMat3 {
315    type F32 = Self;
316    fn f32(&self) -> Self::F32 {
317        *self
318    }
319}
320
321#[cfg(feature = "2d")]
322pub(crate) fn cross(a: Vec2, b: Vec2) -> f32 {
323    a.perp_dot(b)
324}
325
326#[cfg(feature = "3d")]
327pub(crate) fn cross(a: Vec3, b: Vec3) -> Vec3 {
328    a.cross(b)
329}
330
331/// An extension trait for computing reciprocals without division by zero.
332pub trait RecipOrZero {
333    /// Computes the reciprocal of `self` if `self` is not zero,
334    /// and returns zero otherwise to avoid division by zero.
335    fn recip_or_zero(self) -> Self;
336}
337
338impl RecipOrZero for f32 {
339    #[inline]
340    fn recip_or_zero(self) -> Self {
341        if self != 0.0 && self.is_finite() {
342            self.recip()
343        } else {
344            0.0
345        }
346    }
347}
348
349impl RecipOrZero for f64 {
350    #[inline]
351    fn recip_or_zero(self) -> Self {
352        if self != 0.0 && self.is_finite() {
353            self.recip()
354        } else {
355            0.0
356        }
357    }
358}
359
360impl RecipOrZero for Vec2 {
361    #[inline]
362    fn recip_or_zero(self) -> Self {
363        Self::new(self.x.recip_or_zero(), self.y.recip_or_zero())
364    }
365}
366
367impl RecipOrZero for Vec3 {
368    #[inline]
369    fn recip_or_zero(self) -> Self {
370        Self::new(
371            self.x.recip_or_zero(),
372            self.y.recip_or_zero(),
373            self.z.recip_or_zero(),
374        )
375    }
376}
377
378impl RecipOrZero for DVec2 {
379    #[inline]
380    fn recip_or_zero(self) -> Self {
381        Self::new(self.x.recip_or_zero(), self.y.recip_or_zero())
382    }
383}
384
385impl RecipOrZero for DVec3 {
386    #[inline]
387    fn recip_or_zero(self) -> Self {
388        Self::new(
389            self.x.recip_or_zero(),
390            self.y.recip_or_zero(),
391            self.z.recip_or_zero(),
392        )
393    }
394}
395
396/// An extension trait for matrix types.
397pub trait MatExt {
398    /// The scalar type of the matrix.
399    type Real;
400
401    /// Computes the inverse of `self` if `self` is not zero,
402    /// and returns zero otherwise to avoid division by zero.
403    fn inverse_or_zero(self) -> Self;
404
405    /// Checks if the matrix is isotropic, meaning that it is invariant
406    /// under all rotations of the coordinate system.
407    ///
408    /// For second-order tensors, this means that the diagonal elements
409    /// are equal and the off-diagonal elements are zero.
410    fn is_isotropic(&self, epsilon: Self::Real) -> bool;
411}
412
413impl MatExt for Mat2 {
414    type Real = f32;
415
416    #[inline]
417    fn inverse_or_zero(self) -> Self {
418        if self.determinant() == 0.0 {
419            Self::ZERO
420        } else {
421            self.inverse()
422        }
423    }
424
425    #[inline]
426    fn is_isotropic(&self, epsilon: f32) -> bool {
427        // Extract diagonal elements.
428        let diag = Vec2::new(self.x_axis.x, self.y_axis.y);
429
430        // All diagonal elements must be approximately equal.
431        if abs_diff_ne!(diag.x, diag.y, epsilon = epsilon) {
432            return false;
433        }
434
435        // Extract off-diagonal elements.
436        let off_diag = [self.x_axis.y, self.y_axis.x];
437
438        // All off-diagonal elements must be approximately zero.
439        off_diag.iter().all(|&x| x.abs() < epsilon)
440    }
441}
442
443impl MatExt for DMat2 {
444    type Real = f64;
445
446    #[inline]
447    fn inverse_or_zero(self) -> Self {
448        if self.determinant() == 0.0 {
449            Self::ZERO
450        } else {
451            self.inverse()
452        }
453    }
454
455    #[inline]
456    fn is_isotropic(&self, epsilon: f64) -> bool {
457        // Extract diagonal elements.
458        let diag = DVec2::new(self.x_axis.x, self.y_axis.y);
459
460        // All diagonal elements must be approximately equal.
461        if abs_diff_ne!(diag.x, diag.y, epsilon = epsilon) {
462            return false;
463        }
464
465        // Extract off-diagonal elements.
466        let off_diag = [self.x_axis.y, self.y_axis.x];
467
468        // All off-diagonal elements must be approximately zero.
469        off_diag.iter().all(|&x| x.abs() < epsilon)
470    }
471}
472
473impl MatExt for SymmetricMat2 {
474    type Real = f32;
475
476    #[inline]
477    fn inverse_or_zero(self) -> Self {
478        if self.determinant() == 0.0 {
479            Self::ZERO
480        } else {
481            self.inverse()
482        }
483    }
484
485    #[inline]
486    fn is_isotropic(&self, epsilon: f32) -> bool {
487        // Extract diagonal elements.
488        let diag = Vec2::new(self.m00, self.m11);
489
490        // All diagonal elements must be approximately equal.
491        if abs_diff_ne!(diag.x, diag.y, epsilon = epsilon) {
492            return false;
493        }
494
495        // All off-diagonal elements must be approximately zero.
496        self.m01.abs() < epsilon
497    }
498}
499
500impl MatExt for SymmetricDMat2 {
501    type Real = f64;
502
503    #[inline]
504    fn inverse_or_zero(self) -> Self {
505        if self.determinant() == 0.0 {
506            Self::ZERO
507        } else {
508            self.inverse()
509        }
510    }
511
512    #[inline]
513    fn is_isotropic(&self, epsilon: f64) -> bool {
514        // Extract diagonal elements.
515        let diag = DVec2::new(self.m00, self.m11);
516
517        // All diagonal elements must be approximately equal.
518        if abs_diff_ne!(diag.x, diag.y, epsilon = epsilon) {
519            return false;
520        }
521
522        // All off-diagonal elements must be approximately zero.
523        self.m01.abs() < epsilon
524    }
525}
526
527impl MatExt for Mat3 {
528    type Real = f32;
529
530    #[inline]
531    fn inverse_or_zero(self) -> Self {
532        if self.determinant() == 0.0 {
533            Self::ZERO
534        } else {
535            self.inverse()
536        }
537    }
538
539    #[inline]
540    fn is_isotropic(&self, epsilon: f32) -> bool {
541        // Extract diagonal elements.
542        let diag = Vec3::new(self.x_axis.x, self.y_axis.y, self.z_axis.z);
543
544        // All diagonal elements must be approximately equal.
545        if abs_diff_ne!(diag.x, diag.y, epsilon = epsilon)
546            || abs_diff_ne!(diag.y, diag.z, epsilon = epsilon)
547        {
548            return false;
549        }
550
551        // Extract off-diagonal elements.
552        let off_diag = [
553            self.x_axis.y,
554            self.x_axis.z,
555            self.y_axis.x,
556            self.y_axis.z,
557            self.z_axis.x,
558            self.z_axis.y,
559        ];
560
561        // All off-diagonal elements must be approximately zero.
562        off_diag.iter().all(|&x| x.abs() < epsilon)
563    }
564}
565
566impl MatExt for DMat3 {
567    type Real = f64;
568
569    #[inline]
570    fn inverse_or_zero(self) -> Self {
571        if self.determinant() == 0.0 {
572            Self::ZERO
573        } else {
574            self.inverse()
575        }
576    }
577
578    #[inline]
579    fn is_isotropic(&self, epsilon: f64) -> bool {
580        // Extract diagonal elements.
581        let diag = DVec3::new(self.x_axis.x, self.y_axis.y, self.z_axis.z);
582
583        // All diagonal elements must be approximately equal.
584        if abs_diff_ne!(diag.x, diag.y, epsilon = epsilon)
585            || abs_diff_ne!(diag.y, diag.z, epsilon = epsilon)
586        {
587            return false;
588        }
589
590        // Extract off-diagonal elements.
591        let off_diag = [
592            self.x_axis.y,
593            self.x_axis.z,
594            self.y_axis.x,
595            self.y_axis.z,
596            self.z_axis.x,
597            self.z_axis.y,
598        ];
599
600        // All off-diagonal elements must be approximately zero.
601        off_diag.iter().all(|&x| x.abs() < epsilon)
602    }
603}
604
605impl MatExt for SymmetricMat3 {
606    type Real = f32;
607
608    #[inline]
609    fn inverse_or_zero(self) -> Self {
610        if self.determinant() == 0.0 {
611            Self::ZERO
612        } else {
613            self.inverse()
614        }
615    }
616
617    #[inline]
618    fn is_isotropic(&self, epsilon: f32) -> bool {
619        // Extract diagonal elements.
620        let diag = Vec3::new(self.m00, self.m11, self.m22);
621
622        // All diagonal elements must be approximately equal.
623        if abs_diff_ne!(diag.x, diag.y, epsilon = epsilon)
624            || abs_diff_ne!(diag.y, diag.z, epsilon = epsilon)
625        {
626            return false;
627        }
628
629        // Extract off-diagonal elements.
630        let off_diag = [self.m01, self.m02, self.m12];
631
632        // All off-diagonal elements must be approximately zero.
633        off_diag.iter().all(|&x| x.abs() < epsilon)
634    }
635}
636
637impl MatExt for SymmetricDMat3 {
638    type Real = f64;
639
640    #[inline]
641    fn inverse_or_zero(self) -> Self {
642        if self.determinant() == 0.0 {
643            Self::ZERO
644        } else {
645            self.inverse()
646        }
647    }
648
649    #[inline]
650    fn is_isotropic(&self, epsilon: f64) -> bool {
651        // Extract diagonal elements.
652        let diag = DVec3::new(self.m00, self.m11, self.m22);
653
654        // All diagonal elements must be approximately equal.
655        if abs_diff_ne!(diag.x, diag.y, epsilon = epsilon)
656            || abs_diff_ne!(diag.y, diag.z, epsilon = epsilon)
657        {
658            return false;
659        }
660
661        // Extract off-diagonal elements.
662        let off_diag = [self.m01, self.m02, self.m12];
663
664        // All off-diagonal elements must be approximately zero.
665        off_diag.iter().all(|&x| x.abs() < epsilon)
666    }
667}
668
669// TODO: Remove or refactor this
670#[allow(
671    dead_code,
672    reason = "Some internals need this, but only with specific features enabled."
673)]
674#[cfg(feature = "2d")]
675pub(crate) trait Rot2Ext {
676    /// Computes the chord length of the rotation, which is the straight-line
677    /// distance between the start and end points of the rotation on a unit circle.
678    fn chord_length(self) -> f32;
679
680    /// Adds the given counterclockiwise angle in radians to the [`Rotation`].
681    /// Uses small-angle approximation.
682    #[must_use]
683    fn add_angle_fast(&self, radians: f32) -> Self;
684}
685
686#[cfg(feature = "2d")]
687impl Rot2Ext for Rot2 {
688    #[inline]
689    fn chord_length(self) -> f32 {
690        // The chord length traveled by a point rotated by `θ` on a unit circle
691        // is `2 * sin(θ / 2)`.
692        //
693        // In 2D, `2 * sin(θ / 2) = sqrt(2 * (1 - cos(θ)))`, using the stored cosine.
694        //
695        // TODO: A "2D quaternion" that stores cos(theta / 2) and sin(theta / 2)
696        //       could avoid the sqrt and be more accurate in some places.
697        (2.0 * (1.0 - self.cos)).max(0.0).sqrt()
698    }
699
700    #[inline]
701    fn add_angle_fast(&self, radians: f32) -> Self {
702        let (sin, cos) = (self.sin + radians * self.cos, self.cos - radians * self.sin);
703        let magnitude_squared = sin * sin + cos * cos;
704        let magnitude_recip = if magnitude_squared > 0.0 {
705            magnitude_squared.sqrt().recip()
706        } else {
707            0.0
708        };
709        Rot2::from_sin_cos(sin * magnitude_recip, cos * magnitude_recip)
710    }
711}
712
713#[cfg(feature = "3d")]
714pub(crate) trait QuatExt {
715    /// Computes the chord length of the rotation, which is the straight-line
716    /// distance between the start and end points of the rotation on a unit sphere.
717    #[allow(
718        dead_code,
719        reason = "Some internals need this, but only with specific features enabled."
720    )]
721    fn chord_length(self) -> f32;
722
723    /// Returns `self` after an approximate normalization,
724    /// assuming the value is already nearly normalized.
725    /// Useful for preventing numerical error accumulation.
726    #[must_use]
727    fn fast_renormalize(self) -> Self;
728}
729
730#[cfg(feature = "3d")]
731impl QuatExt for Quat {
732    #[inline]
733    fn chord_length(self) -> f32 {
734        2.0 * self.xyz().length()
735    }
736
737    #[inline]
738    fn fast_renormalize(self) -> Self {
739        // First-order Tayor approximation
740        // 1/L = (L^2)^(-1/2) ≈ 1 - (L^2 - 1) / 2 = (3 - L^2) / 2
741        let length_squared = self.length_squared();
742        let approx_inv_length = 0.5 * (3.0 - length_squared);
743        self * approx_inv_length
744    }
745}
746
747#[allow(clippy::unnecessary_cast)]
748#[cfg(all(feature = "2d", any(feature = "parry-f32", feature = "parry-f64")))]
749pub(crate) fn pose_to_isometry(pose: &parry::math::Pose) -> Isometry2d {
750    let rotation = Rot2::from_sin_cos(pose.rotation.im as f32, pose.rotation.re as f32);
751    Isometry2d::new(pose.translation.f32(), rotation)
752}
753
754#[cfg(all(
755    feature = "default-collider",
756    any(feature = "parry-f32", feature = "parry-f64")
757))]
758use crate::prelude::*;
759
760#[cfg(all(
761    feature = "2d",
762    feature = "default-collider",
763    any(feature = "parry-f32", feature = "parry-f64")
764))]
765pub(crate) fn make_pose(
766    position: impl Into<Position>,
767    rotation: impl Into<Rot>,
768) -> parry::math::Pose2 {
769    let position: Position = position.into();
770    let rotation: Rot = rotation.into();
771    parry::math::Pose2::from_parts(
772        position.0,
773        parry::math::Rot2::from_cos_sin_unchecked(rotation.cos.real(), rotation.sin.real()),
774    )
775}
776
777#[cfg(all(
778    feature = "3d",
779    feature = "default-collider",
780    any(feature = "parry-f32", feature = "parry-f64")
781))]
782pub(crate) fn make_pose(
783    position: impl Into<Position>,
784    rotation: impl Into<Rot>,
785) -> parry::math::Pose3 {
786    let position: Position = position.into();
787    let rotation: Rot = rotation.into();
788    parry::math::Pose3::from_parts(position.0, rotation.real())
789}
790
791/// Computes the rotation matrix of the orthonormal basis computed from the given axis.
792///
793/// The `axis` must be a unit vector.
794#[inline]
795#[must_use]
796pub fn orthonormal_basis_from_vec(axis: Vector) -> Rot {
797    #[cfg(feature = "2d")]
798    {
799        let normal = axis.perp();
800        orthonormal_basis([axis, normal])
801    }
802    #[cfg(feature = "3d")]
803    {
804        let (normal1, normal2) = axis.any_orthonormal_pair();
805        orthonormal_basis([axis, normal1, normal2])
806    }
807}
808
809/// Computes the rotation matrix of the orthonormal basis computed from the given axes.
810///
811/// Each axis must be a unit vector.
812#[inline]
813#[must_use]
814pub fn orthonormal_basis(axes: [Vector; DIM]) -> Rot {
815    #[cfg(feature = "2d")]
816    {
817        Rot2::from_sin_cos(axes[1].x, axes[0].x)
818    }
819    #[cfg(feature = "3d")]
820    {
821        let mat = Mat3::from_cols(axes[0], axes[1], axes[2]);
822        Quat::from_mat3(&mat)
823    }
824}
825
826/// Returns a single-precision vector with each component
827/// being at least as small as the corresponding component
828/// of the input vector.
829///
830/// This is used for converting double-precision vectors
831/// to single-precision vectors for the minimum coordinates
832/// of bounding boxes, where they must contain the original
833/// shape even after precision loss far from the origin.
834#[inline(always)]
835#[must_use]
836pub fn next_down_vector(vec: RVector) -> Vector {
837    #[cfg(not(feature = "f64"))]
838    {
839        vec
840    }
841    #[cfg(feature = "f64")]
842    {
843        #[cfg(feature = "2d")]
844        {
845            let [x, y] = [vec.x as f32, vec.y as f32];
846            Vector::new(
847                if x as f64 <= vec.x { x } else { x.next_down() },
848                if y as f64 <= vec.y { y } else { y.next_down() },
849            )
850        }
851        #[cfg(feature = "3d")]
852        {
853            let [x, y, z] = [vec.x as f32, vec.y as f32, vec.z as f32];
854            Vector::new(
855                if x as f64 <= vec.x { x } else { x.next_down() },
856                if y as f64 <= vec.y { y } else { y.next_down() },
857                if z as f64 <= vec.z { z } else { z.next_down() },
858            )
859        }
860    }
861}
862
863/// Returns a single-precision vector with each component
864/// being at least as large as the corresponding component
865/// of the input vector.
866///
867/// This is used for converting double-precision vectors
868/// to single-precision vectors for the maximum coordinates
869/// of bounding boxes, where they must contain the original
870/// shape even after precision loss far from the origin.
871#[inline(always)]
872#[must_use]
873pub fn next_up_vector(vec: RVector) -> Vector {
874    #[cfg(not(feature = "f64"))]
875    {
876        vec
877    }
878    #[cfg(feature = "f64")]
879    {
880        #[cfg(feature = "2d")]
881        {
882            let [x, y] = [vec.x as f32, vec.y as f32];
883            Vector::new(
884                if x as f64 >= vec.x { x } else { x.next_up() },
885                if y as f64 >= vec.y { y } else { y.next_up() },
886            )
887        }
888        #[cfg(feature = "3d")]
889        {
890            let [x, y, z] = [vec.x as f32, vec.y as f32, vec.z as f32];
891            Vector::new(
892                if x as f64 >= vec.x { x } else { x.next_up() },
893                if y as f64 >= vec.y { y } else { y.next_up() },
894                if z as f64 >= vec.z { z } else { z.next_up() },
895            )
896        }
897    }
898}