nalgebra/geometry/
rotation.rs

1use approx::{AbsDiffEq, RelativeEq, UlpsEq};
2use num::{One, Zero};
3use std::fmt;
4use std::hash;
5#[cfg(feature = "abomonation-serialize")]
6use std::io::{Result as IOResult, Write};
7
8#[cfg(feature = "serde-serialize-no-std")]
9use serde::{Deserialize, Deserializer, Serialize, Serializer};
10
11#[cfg(feature = "serde-serialize-no-std")]
12use crate::base::storage::Owned;
13
14#[cfg(feature = "abomonation-serialize")]
15use abomonation::Abomonation;
16
17use simba::scalar::RealField;
18use simba::simd::SimdRealField;
19
20use crate::base::allocator::Allocator;
21use crate::base::dimension::{DimNameAdd, DimNameSum, U1};
22use crate::base::{Const, DefaultAllocator, OMatrix, SMatrix, SVector, Scalar, Unit};
23use crate::geometry::Point;
24
25/// A rotation matrix.
26///
27/// This is also known as an element of a Special Orthogonal (SO) group.
28/// The `Rotation` type can either represent a 2D or 3D rotation, represented as a matrix.
29/// For a rotation based on quaternions, see [`UnitQuaternion`](crate::UnitQuaternion) instead.
30///
31/// Note that instead of using the [`Rotation`](crate::Rotation) type in your code directly, you should use one
32/// of its aliases: [`Rotation2`](crate::Rotation2), or [`Rotation3`](crate::Rotation3). Though
33/// keep in mind that all the documentation of all the methods of these aliases will also appears on
34/// this page.
35///
36/// # Construction
37/// * [Identity <span style="float:right;">`identity`</span>](#identity)
38/// * [From a 2D rotation angle <span style="float:right;">`new`…</span>](#construction-from-a-2d-rotation-angle)
39/// * [From an existing 2D matrix or rotations <span style="float:right;">`from_matrix`, `rotation_between`, `powf`…</span>](#construction-from-an-existing-2d-matrix-or-rotations)
40/// * [From a 3D axis and/or angles <span style="float:right;">`new`, `from_euler_angles`, `from_axis_angle`…</span>](#construction-from-a-3d-axis-andor-angles)
41/// * [From a 3D eye position and target point <span style="float:right;">`look_at`, `look_at_lh`, `rotation_between`…</span>](#construction-from-a-3d-eye-position-and-target-point)
42/// * [From an existing 3D matrix or rotations <span style="float:right;">`from_matrix`, `rotation_between`, `powf`…</span>](#construction-from-an-existing-3d-matrix-or-rotations)
43///
44/// # Transformation and composition
45/// Note that transforming vectors and points can be done by multiplication, e.g., `rotation * point`.
46/// Composing an rotation with another transformation can also be done by multiplication or division.
47/// * [3D axis and angle extraction <span style="float:right;">`angle`, `euler_angles`, `scaled_axis`, `angle_to`…</span>](#3d-axis-and-angle-extraction)
48/// * [2D angle extraction <span style="float:right;">`angle`, `angle_to`…</span>](#2d-angle-extraction)
49/// * [Transformation of a vector or a point <span style="float:right;">`transform_vector`, `inverse_transform_point`…</span>](#transformation-of-a-vector-or-a-point)
50/// * [Transposition and inversion <span style="float:right;">`transpose`, `inverse`…</span>](#transposition-and-inversion)
51/// * [Interpolation <span style="float:right;">`slerp`…</span>](#interpolation)
52///
53/// # Conversion
54/// * [Conversion to a matrix <span style="float:right;">`matrix`, `to_homogeneous`…</span>](#conversion-to-a-matrix)
55///
56#[repr(C)]
57#[cfg_attr(
58    all(not(target_os = "cuda"), feature = "cuda"),
59    derive(cust::DeviceCopy)
60)]
61#[derive(Copy, Clone)]
62pub struct Rotation<T, const D: usize> {
63    matrix: SMatrix<T, D, D>,
64}
65
66impl<T: fmt::Debug, const D: usize> fmt::Debug for Rotation<T, D> {
67    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
68        self.matrix.fmt(formatter)
69    }
70}
71
72impl<T: Scalar + hash::Hash, const D: usize> hash::Hash for Rotation<T, D>
73where
74    <DefaultAllocator as Allocator<T, Const<D>, Const<D>>>::Buffer: hash::Hash,
75{
76    fn hash<H: hash::Hasher>(&self, state: &mut H) {
77        self.matrix.hash(state)
78    }
79}
80
81#[cfg(feature = "bytemuck")]
82unsafe impl<T, const D: usize> bytemuck::Zeroable for Rotation<T, D>
83where
84    T: Scalar + bytemuck::Zeroable,
85    SMatrix<T, D, D>: bytemuck::Zeroable,
86{
87}
88
89#[cfg(feature = "bytemuck")]
90unsafe impl<T, const D: usize> bytemuck::Pod for Rotation<T, D>
91where
92    T: Scalar + bytemuck::Pod,
93    SMatrix<T, D, D>: bytemuck::Pod,
94{
95}
96
97#[cfg(feature = "abomonation-serialize")]
98impl<T, const D: usize> Abomonation for Rotation<T, D>
99where
100    T: Scalar,
101    SMatrix<T, D, D>: Abomonation,
102{
103    unsafe fn entomb<W: Write>(&self, writer: &mut W) -> IOResult<()> {
104        self.matrix.entomb(writer)
105    }
106
107    fn extent(&self) -> usize {
108        self.matrix.extent()
109    }
110
111    unsafe fn exhume<'a, 'b>(&'a mut self, bytes: &'b mut [u8]) -> Option<&'b mut [u8]> {
112        self.matrix.exhume(bytes)
113    }
114}
115
116#[cfg(feature = "serde-serialize-no-std")]
117impl<T: Scalar, const D: usize> Serialize for Rotation<T, D>
118where
119    Owned<T, Const<D>, Const<D>>: Serialize,
120{
121    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
122    where
123        S: Serializer,
124    {
125        self.matrix.serialize(serializer)
126    }
127}
128
129#[cfg(feature = "serde-serialize-no-std")]
130impl<'a, T: Scalar, const D: usize> Deserialize<'a> for Rotation<T, D>
131where
132    Owned<T, Const<D>, Const<D>>: Deserialize<'a>,
133{
134    fn deserialize<Des>(deserializer: Des) -> Result<Self, Des::Error>
135    where
136        Des: Deserializer<'a>,
137    {
138        let matrix = SMatrix::<T, D, D>::deserialize(deserializer)?;
139
140        Ok(Self::from_matrix_unchecked(matrix))
141    }
142}
143
144impl<T, const D: usize> Rotation<T, D> {
145    /// Creates a new rotation from the given square matrix.
146    ///
147    /// The matrix orthonormality is not checked.
148    ///
149    /// # Example
150    /// ```
151    /// # use nalgebra::{Rotation2, Rotation3, Matrix2, Matrix3};
152    /// # use std::f32;
153    /// let mat = Matrix3::new(0.8660254, -0.5,      0.0,
154    ///                        0.5,       0.8660254, 0.0,
155    ///                        0.0,       0.0,       1.0);
156    /// let rot = Rotation3::from_matrix_unchecked(mat);
157    ///
158    /// assert_eq!(*rot.matrix(), mat);
159    ///
160    ///
161    /// let mat = Matrix2::new(0.8660254, -0.5,
162    ///                        0.5,       0.8660254);
163    /// let rot = Rotation2::from_matrix_unchecked(mat);
164    ///
165    /// assert_eq!(*rot.matrix(), mat);
166    /// ```
167    #[inline]
168    pub const fn from_matrix_unchecked(matrix: SMatrix<T, D, D>) -> Self {
169        Self { matrix }
170    }
171}
172
173/// # Conversion to a matrix
174impl<T: Scalar, const D: usize> Rotation<T, D> {
175    /// A reference to the underlying matrix representation of this rotation.
176    ///
177    /// # Example
178    /// ```
179    /// # use nalgebra::{Rotation2, Rotation3, Vector3, Matrix2, Matrix3};
180    /// # use std::f32;
181    /// let rot = Rotation3::from_axis_angle(&Vector3::z_axis(), f32::consts::FRAC_PI_6);
182    /// let expected = Matrix3::new(0.8660254, -0.5,      0.0,
183    ///                             0.5,       0.8660254, 0.0,
184    ///                             0.0,       0.0,       1.0);
185    /// assert_eq!(*rot.matrix(), expected);
186    ///
187    ///
188    /// let rot = Rotation2::new(f32::consts::FRAC_PI_6);
189    /// let expected = Matrix2::new(0.8660254, -0.5,
190    ///                             0.5,       0.8660254);
191    /// assert_eq!(*rot.matrix(), expected);
192    /// ```
193    #[inline]
194    #[must_use]
195    pub fn matrix(&self) -> &SMatrix<T, D, D> {
196        &self.matrix
197    }
198
199    /// A mutable reference to the underlying matrix representation of this rotation.
200    #[inline]
201    #[deprecated(note = "Use `.matrix_mut_unchecked()` instead.")]
202    pub unsafe fn matrix_mut(&mut self) -> &mut SMatrix<T, D, D> {
203        &mut self.matrix
204    }
205
206    /// A mutable reference to the underlying matrix representation of this rotation.
207    ///
208    /// This is suffixed by "_unchecked" because this allows the user to replace the
209    /// matrix by another one that is non-inversible or non-orthonormal. If one of
210    /// those properties is broken, subsequent method calls may return bogus results.
211    #[inline]
212    pub fn matrix_mut_unchecked(&mut self) -> &mut SMatrix<T, D, D> {
213        &mut self.matrix
214    }
215
216    /// Unwraps the underlying matrix.
217    ///
218    /// # Example
219    /// ```
220    /// # use nalgebra::{Rotation2, Rotation3, Vector3, Matrix2, Matrix3};
221    /// # use std::f32;
222    /// let rot = Rotation3::from_axis_angle(&Vector3::z_axis(), f32::consts::FRAC_PI_6);
223    /// let mat = rot.into_inner();
224    /// let expected = Matrix3::new(0.8660254, -0.5,      0.0,
225    ///                             0.5,       0.8660254, 0.0,
226    ///                             0.0,       0.0,       1.0);
227    /// assert_eq!(mat, expected);
228    ///
229    ///
230    /// let rot = Rotation2::new(f32::consts::FRAC_PI_6);
231    /// let mat = rot.into_inner();
232    /// let expected = Matrix2::new(0.8660254, -0.5,
233    ///                             0.5,       0.8660254);
234    /// assert_eq!(mat, expected);
235    /// ```
236    #[inline]
237    pub fn into_inner(self) -> SMatrix<T, D, D> {
238        self.matrix
239    }
240
241    /// Unwraps the underlying matrix.
242    /// Deprecated: Use [`Rotation::into_inner`] instead.
243    #[deprecated(note = "use `.into_inner()` instead")]
244    #[inline]
245    pub fn unwrap(self) -> SMatrix<T, D, D> {
246        self.matrix
247    }
248
249    /// Converts this rotation into its equivalent homogeneous transformation matrix.
250    ///
251    /// This is the same as `self.into()`.
252    ///
253    /// # Example
254    /// ```
255    /// # use nalgebra::{Rotation2, Rotation3, Vector3, Matrix3, Matrix4};
256    /// # use std::f32;
257    /// let rot = Rotation3::from_axis_angle(&Vector3::z_axis(), f32::consts::FRAC_PI_6);
258    /// let expected = Matrix4::new(0.8660254, -0.5,      0.0, 0.0,
259    ///                             0.5,       0.8660254, 0.0, 0.0,
260    ///                             0.0,       0.0,       1.0, 0.0,
261    ///                             0.0,       0.0,       0.0, 1.0);
262    /// assert_eq!(rot.to_homogeneous(), expected);
263    ///
264    ///
265    /// let rot = Rotation2::new(f32::consts::FRAC_PI_6);
266    /// let expected = Matrix3::new(0.8660254, -0.5,      0.0,
267    ///                             0.5,       0.8660254, 0.0,
268    ///                             0.0,       0.0,       1.0);
269    /// assert_eq!(rot.to_homogeneous(), expected);
270    /// ```
271    #[inline]
272    #[must_use]
273    pub fn to_homogeneous(&self) -> OMatrix<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>
274    where
275        T: Zero + One,
276        Const<D>: DimNameAdd<U1>,
277        DefaultAllocator: Allocator<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>,
278    {
279        // We could use `SMatrix::to_homogeneous()` here, but that would imply
280        // adding the additional traits `DimAdd` and `IsNotStaticOne`. Maybe
281        // these things will get nicer once specialization lands in Rust.
282        let mut res = OMatrix::<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>::identity();
283        res.fixed_slice_mut::<D, D>(0, 0).copy_from(&self.matrix);
284
285        res
286    }
287}
288
289/// # Transposition and inversion
290impl<T: Scalar, const D: usize> Rotation<T, D> {
291    /// Transposes `self`.
292    ///
293    /// Same as `.inverse()` because the inverse of a rotation matrix is its transform.
294    ///
295    /// # Example
296    /// ```
297    /// # #[macro_use] extern crate approx;
298    /// # use nalgebra::{Rotation2, Rotation3, Vector3};
299    /// let rot = Rotation3::new(Vector3::new(1.0, 2.0, 3.0));
300    /// let tr_rot = rot.transpose();
301    /// assert_relative_eq!(rot * tr_rot, Rotation3::identity(), epsilon = 1.0e-6);
302    /// assert_relative_eq!(tr_rot * rot, Rotation3::identity(), epsilon = 1.0e-6);
303    ///
304    /// let rot = Rotation2::new(1.2);
305    /// let tr_rot = rot.transpose();
306    /// assert_relative_eq!(rot * tr_rot, Rotation2::identity(), epsilon = 1.0e-6);
307    /// assert_relative_eq!(tr_rot * rot, Rotation2::identity(), epsilon = 1.0e-6);
308    /// ```
309    #[inline]
310    #[must_use = "Did you mean to use transpose_mut()?"]
311    pub fn transpose(&self) -> Self {
312        Self::from_matrix_unchecked(self.matrix.transpose())
313    }
314
315    /// Inverts `self`.
316    ///
317    /// Same as `.transpose()` because the inverse of a rotation matrix is its transform.
318    ///
319    /// # Example
320    /// ```
321    /// # #[macro_use] extern crate approx;
322    /// # use nalgebra::{Rotation2, Rotation3, Vector3};
323    /// let rot = Rotation3::new(Vector3::new(1.0, 2.0, 3.0));
324    /// let inv = rot.inverse();
325    /// assert_relative_eq!(rot * inv, Rotation3::identity(), epsilon = 1.0e-6);
326    /// assert_relative_eq!(inv * rot, Rotation3::identity(), epsilon = 1.0e-6);
327    ///
328    /// let rot = Rotation2::new(1.2);
329    /// let inv = rot.inverse();
330    /// assert_relative_eq!(rot * inv, Rotation2::identity(), epsilon = 1.0e-6);
331    /// assert_relative_eq!(inv * rot, Rotation2::identity(), epsilon = 1.0e-6);
332    /// ```
333    #[inline]
334    #[must_use = "Did you mean to use inverse_mut()?"]
335    pub fn inverse(&self) -> Self {
336        self.transpose()
337    }
338
339    /// Transposes `self` in-place.
340    ///
341    /// Same as `.inverse_mut()` because the inverse of a rotation matrix is its transform.
342    ///
343    /// # Example
344    /// ```
345    /// # #[macro_use] extern crate approx;
346    /// # use nalgebra::{Rotation2, Rotation3, Vector3};
347    /// let rot = Rotation3::new(Vector3::new(1.0, 2.0, 3.0));
348    /// let mut tr_rot = Rotation3::new(Vector3::new(1.0, 2.0, 3.0));
349    /// tr_rot.transpose_mut();
350    ///
351    /// assert_relative_eq!(rot * tr_rot, Rotation3::identity(), epsilon = 1.0e-6);
352    /// assert_relative_eq!(tr_rot * rot, Rotation3::identity(), epsilon = 1.0e-6);
353    ///
354    /// let rot = Rotation2::new(1.2);
355    /// let mut tr_rot = Rotation2::new(1.2);
356    /// tr_rot.transpose_mut();
357    ///
358    /// assert_relative_eq!(rot * tr_rot, Rotation2::identity(), epsilon = 1.0e-6);
359    /// assert_relative_eq!(tr_rot * rot, Rotation2::identity(), epsilon = 1.0e-6);
360    /// ```
361    #[inline]
362    pub fn transpose_mut(&mut self) {
363        self.matrix.transpose_mut()
364    }
365
366    /// Inverts `self` in-place.
367    ///
368    /// Same as `.transpose_mut()` because the inverse of a rotation matrix is its transform.
369    ///
370    /// # Example
371    /// ```
372    /// # #[macro_use] extern crate approx;
373    /// # use nalgebra::{Rotation2, Rotation3, Vector3};
374    /// let rot = Rotation3::new(Vector3::new(1.0, 2.0, 3.0));
375    /// let mut inv = Rotation3::new(Vector3::new(1.0, 2.0, 3.0));
376    /// inv.inverse_mut();
377    ///
378    /// assert_relative_eq!(rot * inv, Rotation3::identity(), epsilon = 1.0e-6);
379    /// assert_relative_eq!(inv * rot, Rotation3::identity(), epsilon = 1.0e-6);
380    ///
381    /// let rot = Rotation2::new(1.2);
382    /// let mut inv = Rotation2::new(1.2);
383    /// inv.inverse_mut();
384    ///
385    /// assert_relative_eq!(rot * inv, Rotation2::identity(), epsilon = 1.0e-6);
386    /// assert_relative_eq!(inv * rot, Rotation2::identity(), epsilon = 1.0e-6);
387    /// ```
388    #[inline]
389    pub fn inverse_mut(&mut self) {
390        self.transpose_mut()
391    }
392}
393
394/// # Transformation of a vector or a point
395impl<T: SimdRealField, const D: usize> Rotation<T, D>
396where
397    T::Element: SimdRealField,
398{
399    /// Rotate the given point.
400    ///
401    /// This is the same as the multiplication `self * pt`.
402    ///
403    /// # Example
404    /// ```
405    /// # #[macro_use] extern crate approx;
406    /// # use std::f32;
407    /// # use nalgebra::{Point3, Rotation2, Rotation3, UnitQuaternion, Vector3};
408    /// let rot = Rotation3::new(Vector3::y() * f32::consts::FRAC_PI_2);
409    /// let transformed_point = rot.transform_point(&Point3::new(1.0, 2.0, 3.0));
410    ///
411    /// assert_relative_eq!(transformed_point, Point3::new(3.0, 2.0, -1.0), epsilon = 1.0e-6);
412    /// ```
413    #[inline]
414    #[must_use]
415    pub fn transform_point(&self, pt: &Point<T, D>) -> Point<T, D> {
416        self * pt
417    }
418
419    /// Rotate the given vector.
420    ///
421    /// This is the same as the multiplication `self * v`.
422    ///
423    /// # Example
424    /// ```
425    /// # #[macro_use] extern crate approx;
426    /// # use std::f32;
427    /// # use nalgebra::{Rotation2, Rotation3, UnitQuaternion, Vector3};
428    /// let rot = Rotation3::new(Vector3::y() * f32::consts::FRAC_PI_2);
429    /// let transformed_vector = rot.transform_vector(&Vector3::new(1.0, 2.0, 3.0));
430    ///
431    /// assert_relative_eq!(transformed_vector, Vector3::new(3.0, 2.0, -1.0), epsilon = 1.0e-6);
432    /// ```
433    #[inline]
434    #[must_use]
435    pub fn transform_vector(&self, v: &SVector<T, D>) -> SVector<T, D> {
436        self * v
437    }
438
439    /// Rotate the given point by the inverse of this rotation. This may be
440    /// cheaper than inverting the rotation and then transforming the given
441    /// point.
442    ///
443    /// # Example
444    /// ```
445    /// # #[macro_use] extern crate approx;
446    /// # use std::f32;
447    /// # use nalgebra::{Point3, Rotation2, Rotation3, UnitQuaternion, Vector3};
448    /// let rot = Rotation3::new(Vector3::y() * f32::consts::FRAC_PI_2);
449    /// let transformed_point = rot.inverse_transform_point(&Point3::new(1.0, 2.0, 3.0));
450    ///
451    /// assert_relative_eq!(transformed_point, Point3::new(-3.0, 2.0, 1.0), epsilon = 1.0e-6);
452    /// ```
453    #[inline]
454    #[must_use]
455    pub fn inverse_transform_point(&self, pt: &Point<T, D>) -> Point<T, D> {
456        Point::from(self.inverse_transform_vector(&pt.coords))
457    }
458
459    /// Rotate the given vector by the inverse of this rotation. This may be
460    /// cheaper than inverting the rotation and then transforming the given
461    /// vector.
462    ///
463    /// # Example
464    /// ```
465    /// # #[macro_use] extern crate approx;
466    /// # use std::f32;
467    /// # use nalgebra::{Rotation2, Rotation3, UnitQuaternion, Vector3};
468    /// let rot = Rotation3::new(Vector3::y() * f32::consts::FRAC_PI_2);
469    /// let transformed_vector = rot.inverse_transform_vector(&Vector3::new(1.0, 2.0, 3.0));
470    ///
471    /// assert_relative_eq!(transformed_vector, Vector3::new(-3.0, 2.0, 1.0), epsilon = 1.0e-6);
472    /// ```
473    #[inline]
474    #[must_use]
475    pub fn inverse_transform_vector(&self, v: &SVector<T, D>) -> SVector<T, D> {
476        self.matrix().tr_mul(v)
477    }
478
479    /// Rotate the given vector by the inverse of this rotation. This may be
480    /// cheaper than inverting the rotation and then transforming the given
481    /// vector.
482    ///
483    /// # Example
484    /// ```
485    /// # #[macro_use] extern crate approx;
486    /// # use std::f32;
487    /// # use nalgebra::{Rotation2, Rotation3, UnitQuaternion, Vector3};
488    /// let rot = Rotation3::new(Vector3::z() * f32::consts::FRAC_PI_2);
489    /// let transformed_vector = rot.inverse_transform_unit_vector(&Vector3::x_axis());
490    ///
491    /// assert_relative_eq!(transformed_vector, -Vector3::y_axis(), epsilon = 1.0e-6);
492    /// ```
493    #[inline]
494    #[must_use]
495    pub fn inverse_transform_unit_vector(&self, v: &Unit<SVector<T, D>>) -> Unit<SVector<T, D>> {
496        Unit::new_unchecked(self.inverse_transform_vector(&**v))
497    }
498}
499
500impl<T: Scalar + Eq, const D: usize> Eq for Rotation<T, D> {}
501
502impl<T: Scalar + PartialEq, const D: usize> PartialEq for Rotation<T, D> {
503    #[inline]
504    fn eq(&self, right: &Self) -> bool {
505        self.matrix == right.matrix
506    }
507}
508
509impl<T, const D: usize> AbsDiffEq for Rotation<T, D>
510where
511    T: Scalar + AbsDiffEq,
512    T::Epsilon: Clone,
513{
514    type Epsilon = T::Epsilon;
515
516    #[inline]
517    fn default_epsilon() -> Self::Epsilon {
518        T::default_epsilon()
519    }
520
521    #[inline]
522    fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool {
523        self.matrix.abs_diff_eq(&other.matrix, epsilon)
524    }
525}
526
527impl<T, const D: usize> RelativeEq for Rotation<T, D>
528where
529    T: Scalar + RelativeEq,
530    T::Epsilon: Clone,
531{
532    #[inline]
533    fn default_max_relative() -> Self::Epsilon {
534        T::default_max_relative()
535    }
536
537    #[inline]
538    fn relative_eq(
539        &self,
540        other: &Self,
541        epsilon: Self::Epsilon,
542        max_relative: Self::Epsilon,
543    ) -> bool {
544        self.matrix
545            .relative_eq(&other.matrix, epsilon, max_relative)
546    }
547}
548
549impl<T, const D: usize> UlpsEq for Rotation<T, D>
550where
551    T: Scalar + UlpsEq,
552    T::Epsilon: Clone,
553{
554    #[inline]
555    fn default_max_ulps() -> u32 {
556        T::default_max_ulps()
557    }
558
559    #[inline]
560    fn ulps_eq(&self, other: &Self, epsilon: Self::Epsilon, max_ulps: u32) -> bool {
561        self.matrix.ulps_eq(&other.matrix, epsilon, max_ulps)
562    }
563}
564
565/*
566 *
567 * Display
568 *
569 */
570impl<T, const D: usize> fmt::Display for Rotation<T, D>
571where
572    T: RealField + fmt::Display,
573{
574    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
575        let precision = f.precision().unwrap_or(3);
576
577        writeln!(f, "Rotation matrix {{")?;
578        write!(f, "{:.*}", precision, self.matrix)?;
579        writeln!(f, "}}")
580    }
581}
582
583//          //         /*
584//          //          *
585//          //          * Absolute
586//          //          *
587//          //          */
588//          //         impl<T: Absolute> Absolute for $t<T> {
589//          //             type AbsoluteValue = $submatrix<T::AbsoluteValue>;
590//          //
591//          //             #[inline]
592//          //             fn abs(m: &$t<T>) -> $submatrix<T::AbsoluteValue> {
593//          //                 Absolute::abs(&m.submatrix)
594//          //             }
595//          //         }