nalgebra/geometry/
isometry.rs

1// Needed otherwise the rkyv macros generate code incompatible with rust-2024
2#![cfg_attr(feature = "rkyv-serialize", allow(unsafe_op_in_unsafe_fn))]
3
4use approx::{AbsDiffEq, RelativeEq, UlpsEq};
5use std::fmt;
6use std::hash;
7
8#[cfg(feature = "serde-serialize-no-std")]
9use serde::{Deserialize, Serialize};
10
11use simba::scalar::{RealField, SubsetOf};
12use simba::simd::SimdRealField;
13
14use crate::base::allocator::Allocator;
15use crate::base::dimension::{DimNameAdd, DimNameSum, U1};
16use crate::base::storage::Owned;
17use crate::base::{Const, DefaultAllocator, OMatrix, SVector, Scalar, Unit};
18use crate::geometry::{AbstractRotation, Point, Translation};
19
20#[cfg(doc)]
21use crate::{Isometry3, Quaternion, Vector3, Vector4};
22
23#[cfg(feature = "rkyv-serialize")]
24use rkyv::bytecheck;
25
26/// A direct isometry, i.e., a rotation followed by a translation (aka. a rigid-body motion).
27///
28/// This is also known as an element of a Special Euclidean (SE) group.
29/// The `Isometry` type can either represent a 2D or 3D isometry.
30/// A 2D isometry is composed of:
31/// - A translation part of type [`Translation2`](crate::Translation2)
32/// - A rotation part which can either be a [`UnitComplex`](crate::UnitComplex) or a [`Rotation2`](crate::Rotation2).
33///
34/// A 3D isometry is composed of:
35/// - A translation part of type [`Translation3`](crate::Translation3)
36/// - A rotation part which can either be a [`UnitQuaternion`](crate::UnitQuaternion) or a [`Rotation3`](crate::Rotation3).
37///
38/// Note that instead of using the [`Isometry`](crate::Isometry) type in your code directly, you should use one
39/// of its aliases: [`Isometry2`](crate::Isometry2), [`Isometry3`](crate::Isometry3),
40/// [`IsometryMatrix2`](crate::IsometryMatrix2), [`IsometryMatrix3`](crate::IsometryMatrix3). Though
41/// keep in mind that all the documentation of all the methods of these aliases will also appears on
42/// this page.
43///
44/// # Construction
45/// * [From a 2D vector and/or an angle <span style="float:right;">`new`, `translation`, `rotation`…</span>](#construction-from-a-2d-vector-andor-a-rotation-angle)
46/// * [From a 3D vector and/or an axis-angle <span style="float:right;">`new`, `translation`, `rotation`…</span>](#construction-from-a-3d-vector-andor-an-axis-angle)
47/// * [From a 3D eye position and target point <span style="float:right;">`look_at`, `look_at_lh`, `face_towards`…</span>](#construction-from-a-3d-eye-position-and-target-point)
48/// * [From the translation and rotation parts <span style="float:right;">`from_parts`…</span>](#from-the-translation-and-rotation-parts)
49///
50/// # Transformation and composition
51/// Note that transforming vectors and points can be done by multiplication, e.g., `isometry * point`.
52/// Composing an isometry with another transformation can also be done by multiplication or division.
53///
54/// * [Transformation of a vector or a point <span style="float:right;">`transform_vector`, `inverse_transform_point`…</span>](#transformation-of-a-vector-or-a-point)
55/// * [Inversion and in-place composition <span style="float:right;">`inverse`, `append_rotation_wrt_point_mut`…</span>](#inversion-and-in-place-composition)
56/// * [Interpolation <span style="float:right;">`lerp_slerp`…</span>](#interpolation)
57///
58/// # Conversion to a matrix
59/// * [Conversion to a matrix <span style="float:right;">`to_matrix`…</span>](#conversion-to-a-matrix)
60///
61#[repr(C)]
62#[derive(Debug, Copy, Clone)]
63#[cfg_attr(feature = "serde-serialize-no-std", derive(Serialize, Deserialize))]
64#[cfg_attr(
65    feature = "serde-serialize-no-std",
66    serde(bound(serialize = "R: Serialize,
67                     DefaultAllocator: Allocator<Const<D>>,
68                     Owned<T, Const<D>>: Serialize,
69                     T: Scalar"))
70)]
71#[cfg_attr(
72    feature = "serde-serialize-no-std",
73    serde(bound(deserialize = "R: Deserialize<'de>,
74                       DefaultAllocator: Allocator<Const<D>>,
75                       Owned<T, Const<D>>: Deserialize<'de>,
76                       T: Scalar"))
77)]
78#[cfg_attr(feature = "rkyv-serialize", derive(bytecheck::CheckBytes))]
79#[cfg_attr(
80    feature = "rkyv-serialize-no-std",
81    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize),
82    archive(
83        as = "Isometry<T::Archived, R::Archived, D>",
84        bound(archive = "
85        T: rkyv::Archive,
86        R: rkyv::Archive,
87        Translation<T, D>: rkyv::Archive<Archived = Translation<T::Archived, D>>
88    ")
89    )
90)]
91#[cfg_attr(feature = "defmt", derive(defmt::Format))]
92pub struct Isometry<T, R, const D: usize> {
93    /// The pure rotational part of this isometry.
94    pub rotation: R,
95    /// The pure translational part of this isometry.
96    pub translation: Translation<T, D>,
97}
98
99impl<T: Scalar + hash::Hash, R: hash::Hash, const D: usize> hash::Hash for Isometry<T, R, D>
100where
101    Owned<T, Const<D>>: hash::Hash,
102{
103    fn hash<H: hash::Hasher>(&self, state: &mut H) {
104        self.translation.hash(state);
105        self.rotation.hash(state);
106    }
107}
108
109#[cfg(feature = "bytemuck")]
110unsafe impl<T: Scalar, R, const D: usize> bytemuck::Zeroable for Isometry<T, R, D>
111where
112    SVector<T, D>: bytemuck::Zeroable,
113    R: bytemuck::Zeroable,
114{
115}
116
117#[cfg(feature = "bytemuck")]
118unsafe impl<T: Scalar, R, const D: usize> bytemuck::Pod for Isometry<T, R, D>
119where
120    SVector<T, D>: bytemuck::Pod,
121    R: bytemuck::Pod,
122    T: Copy,
123{
124}
125
126/// # From the translation and rotation parts
127impl<T: Scalar, R: AbstractRotation<T, D>, const D: usize> Isometry<T, R, D> {
128    /// Creates a new isometry from its rotational and translational parts.
129    ///
130    /// # Example
131    ///
132    /// ```
133    /// # #[macro_use] extern crate approx;
134    /// # use std::f32;
135    /// # use nalgebra::{Isometry3, Translation3, UnitQuaternion, Vector3, Point3};
136    /// let tra = Translation3::new(0.0, 0.0, 3.0);
137    /// let rot = UnitQuaternion::from_scaled_axis(Vector3::y() * f32::consts::PI);
138    /// let iso = Isometry3::from_parts(tra, rot);
139    ///
140    /// assert_relative_eq!(iso * Point3::new(1.0, 2.0, 3.0), Point3::new(-1.0, 2.0, 0.0), epsilon = 1.0e-6);
141    /// ```
142    #[inline]
143    pub const fn from_parts(translation: Translation<T, D>, rotation: R) -> Self {
144        Self {
145            rotation,
146            translation,
147        }
148    }
149}
150
151/// # Inversion and in-place composition
152impl<T: SimdRealField, R: AbstractRotation<T, D>, const D: usize> Isometry<T, R, D>
153where
154    T::Element: SimdRealField,
155{
156    /// Inverts `self`.
157    ///
158    /// # Example
159    ///
160    /// ```
161    /// # use std::f32;
162    /// # use nalgebra::{Isometry2, Point2, Vector2};
163    /// let iso = Isometry2::new(Vector2::new(1.0, 2.0), f32::consts::FRAC_PI_2);
164    /// let inv = iso.inverse();
165    /// let pt = Point2::new(1.0, 2.0);
166    ///
167    /// assert_eq!(inv * (iso * pt), pt);
168    /// ```
169    #[inline]
170    #[must_use = "Did you mean to use inverse_mut()?"]
171    pub fn inverse(&self) -> Self {
172        let mut res = self.clone();
173        res.inverse_mut();
174        res
175    }
176
177    /// Inverts `self` in-place.
178    ///
179    /// # Example
180    ///
181    /// ```
182    /// # use std::f32;
183    /// # use nalgebra::{Isometry2, Point2, Vector2};
184    /// let mut iso = Isometry2::new(Vector2::new(1.0, 2.0), f32::consts::FRAC_PI_2);
185    /// let pt = Point2::new(1.0, 2.0);
186    /// let transformed_pt = iso * pt;
187    /// iso.inverse_mut();
188    ///
189    /// assert_eq!(iso * transformed_pt, pt);
190    /// ```
191    #[inline]
192    pub fn inverse_mut(&mut self) {
193        self.rotation.inverse_mut();
194        self.translation.inverse_mut();
195        self.translation.vector = self.rotation.transform_vector(&self.translation.vector);
196    }
197
198    /// Computes `self.inverse() * rhs` in a more efficient way.
199    ///
200    /// # Example
201    ///
202    /// ```
203    /// # use std::f32;
204    /// # use nalgebra::{Isometry2, Point2, Vector2};
205    /// let mut iso1 = Isometry2::new(Vector2::new(1.0, 2.0), f32::consts::FRAC_PI_2);
206    /// let mut iso2 = Isometry2::new(Vector2::new(10.0, 20.0), f32::consts::FRAC_PI_4);
207    ///
208    /// assert_eq!(iso1.inverse() * iso2, iso1.inv_mul(&iso2));
209    /// ```
210    #[inline]
211    #[must_use]
212    pub fn inv_mul(&self, rhs: &Isometry<T, R, D>) -> Self {
213        let inv_rot1 = self.rotation.inverse();
214        let tr_12 = &rhs.translation.vector - &self.translation.vector;
215        Isometry::from_parts(
216            inv_rot1.transform_vector(&tr_12).into(),
217            inv_rot1 * rhs.rotation.clone(),
218        )
219    }
220
221    /// Appends to `self` the given translation in-place.
222    ///
223    /// # Example
224    ///
225    /// ```
226    /// # use std::f32;
227    /// # use nalgebra::{Isometry2, Translation2, Vector2};
228    /// let mut iso = Isometry2::new(Vector2::new(1.0, 2.0), f32::consts::FRAC_PI_2);
229    /// let tra = Translation2::new(3.0, 4.0);
230    /// // Same as `iso = tra * iso`.
231    /// iso.append_translation_mut(&tra);
232    ///
233    /// assert_eq!(iso.translation, Translation2::new(4.0, 6.0));
234    /// ```
235    #[inline]
236    pub fn append_translation_mut(&mut self, t: &Translation<T, D>) {
237        self.translation.vector += &t.vector
238    }
239
240    /// Appends to `self` the given rotation in-place.
241    ///
242    /// # Example
243    ///
244    /// ```
245    /// # #[macro_use] extern crate approx;
246    /// # use std::f32;
247    /// # use nalgebra::{Isometry2, Translation2, UnitComplex, Vector2};
248    /// let mut iso = Isometry2::new(Vector2::new(1.0, 2.0), f32::consts::PI / 6.0);
249    /// let rot = UnitComplex::new(f32::consts::PI / 2.0);
250    /// // Same as `iso = rot * iso`.
251    /// iso.append_rotation_mut(&rot);
252    ///
253    /// assert_relative_eq!(iso, Isometry2::new(Vector2::new(-2.0, 1.0), f32::consts::PI * 2.0 / 3.0), epsilon = 1.0e-6);
254    /// ```
255    #[inline]
256    pub fn append_rotation_mut(&mut self, r: &R) {
257        self.rotation = r.clone() * self.rotation.clone();
258        self.translation.vector = r.transform_vector(&self.translation.vector);
259    }
260
261    /// Appends in-place to `self` a rotation centered at the point `p`, i.e., the rotation that
262    /// lets `p` invariant.
263    ///
264    /// # Example
265    ///
266    /// ```
267    /// # #[macro_use] extern crate approx;
268    /// # use std::f32;
269    /// # use nalgebra::{Isometry2, Translation2, UnitComplex, Vector2, Point2};
270    /// let mut iso = Isometry2::new(Vector2::new(1.0, 2.0), f32::consts::FRAC_PI_2);
271    /// let rot = UnitComplex::new(f32::consts::FRAC_PI_2);
272    /// let pt = Point2::new(1.0, 0.0);
273    /// iso.append_rotation_wrt_point_mut(&rot, &pt);
274    ///
275    /// assert_relative_eq!(iso * pt, Point2::new(-2.0, 0.0), epsilon = 1.0e-6);
276    /// ```
277    #[inline]
278    pub fn append_rotation_wrt_point_mut(&mut self, r: &R, p: &Point<T, D>) {
279        self.translation.vector -= &p.coords;
280        self.append_rotation_mut(r);
281        self.translation.vector += &p.coords;
282    }
283
284    /// Appends in-place to `self` a rotation centered at the point with coordinates
285    /// `self.translation`.
286    ///
287    /// # Example
288    ///
289    /// ```
290    /// # use std::f32;
291    /// # use nalgebra::{Isometry2, Translation2, UnitComplex, Vector2, Point2};
292    /// let mut iso = Isometry2::new(Vector2::new(1.0, 2.0), f32::consts::FRAC_PI_2);
293    /// let rot = UnitComplex::new(f32::consts::FRAC_PI_2);
294    /// iso.append_rotation_wrt_center_mut(&rot);
295    ///
296    /// // The translation part should not have changed.
297    /// assert_eq!(iso.translation.vector, Vector2::new(1.0, 2.0));
298    /// assert_eq!(iso.rotation, UnitComplex::new(f32::consts::PI));
299    /// ```
300    #[inline]
301    pub fn append_rotation_wrt_center_mut(&mut self, r: &R) {
302        self.rotation = r.clone() * self.rotation.clone();
303    }
304}
305
306/// # Transformation of a vector or a point
307impl<T: SimdRealField, R: AbstractRotation<T, D>, const D: usize> Isometry<T, R, D>
308where
309    T::Element: SimdRealField,
310{
311    /// Transform the given point by this isometry.
312    ///
313    /// This is the same as the multiplication `self * pt`.
314    ///
315    /// # Example
316    ///
317    /// ```
318    /// # #[macro_use] extern crate approx;
319    /// # use std::f32;
320    /// # use nalgebra::{Isometry3, Translation3, UnitQuaternion, Vector3, Point3};
321    /// let tra = Translation3::new(0.0, 0.0, 3.0);
322    /// let rot = UnitQuaternion::from_scaled_axis(Vector3::y() * f32::consts::FRAC_PI_2);
323    /// let iso = Isometry3::from_parts(tra, rot);
324    ///
325    /// let transformed_point = iso.transform_point(&Point3::new(1.0, 2.0, 3.0));
326    /// assert_relative_eq!(transformed_point, Point3::new(3.0, 2.0, 2.0), epsilon = 1.0e-6);
327    /// ```
328    #[inline]
329    #[must_use]
330    pub fn transform_point(&self, pt: &Point<T, D>) -> Point<T, D> {
331        self * pt
332    }
333
334    /// Transform the given vector by this isometry, ignoring the translation
335    /// component of the isometry.
336    ///
337    /// This is the same as the multiplication `self * v`.
338    ///
339    /// # Example
340    ///
341    /// ```
342    /// # #[macro_use] extern crate approx;
343    /// # use std::f32;
344    /// # use nalgebra::{Isometry3, Translation3, UnitQuaternion, Vector3};
345    /// let tra = Translation3::new(0.0, 0.0, 3.0);
346    /// let rot = UnitQuaternion::from_scaled_axis(Vector3::y() * f32::consts::FRAC_PI_2);
347    /// let iso = Isometry3::from_parts(tra, rot);
348    ///
349    /// let transformed_point = iso.transform_vector(&Vector3::new(1.0, 2.0, 3.0));
350    /// assert_relative_eq!(transformed_point, Vector3::new(3.0, 2.0, -1.0), epsilon = 1.0e-6);
351    /// ```
352    #[inline]
353    #[must_use]
354    pub fn transform_vector(&self, v: &SVector<T, D>) -> SVector<T, D> {
355        self * v
356    }
357
358    /// Transform the given point by the inverse of this isometry. This may be
359    /// less expensive than computing the entire isometry inverse and then
360    /// transforming the point.
361    ///
362    /// # Example
363    ///
364    /// ```
365    /// # #[macro_use] extern crate approx;
366    /// # use std::f32;
367    /// # use nalgebra::{Isometry3, Translation3, UnitQuaternion, Vector3, Point3};
368    /// let tra = Translation3::new(0.0, 0.0, 3.0);
369    /// let rot = UnitQuaternion::from_scaled_axis(Vector3::y() * f32::consts::FRAC_PI_2);
370    /// let iso = Isometry3::from_parts(tra, rot);
371    ///
372    /// let transformed_point = iso.inverse_transform_point(&Point3::new(1.0, 2.0, 3.0));
373    /// assert_relative_eq!(transformed_point, Point3::new(0.0, 2.0, 1.0), epsilon = 1.0e-6);
374    /// ```
375    #[inline]
376    #[must_use]
377    pub fn inverse_transform_point(&self, pt: &Point<T, D>) -> Point<T, D> {
378        self.rotation
379            .inverse_transform_point(&(pt - &self.translation.vector))
380    }
381
382    /// Transform the given vector by the inverse of this isometry, ignoring the
383    /// translation component of the isometry. This may be
384    /// less expensive than computing the entire isometry inverse and then
385    /// transforming the point.
386    ///
387    /// # Example
388    ///
389    /// ```
390    /// # #[macro_use] extern crate approx;
391    /// # use std::f32;
392    /// # use nalgebra::{Isometry3, Translation3, UnitQuaternion, Vector3};
393    /// let tra = Translation3::new(0.0, 0.0, 3.0);
394    /// let rot = UnitQuaternion::from_scaled_axis(Vector3::y() * f32::consts::FRAC_PI_2);
395    /// let iso = Isometry3::from_parts(tra, rot);
396    ///
397    /// let transformed_point = iso.inverse_transform_vector(&Vector3::new(1.0, 2.0, 3.0));
398    /// assert_relative_eq!(transformed_point, Vector3::new(-3.0, 2.0, 1.0), epsilon = 1.0e-6);
399    /// ```
400    #[inline]
401    #[must_use]
402    pub fn inverse_transform_vector(&self, v: &SVector<T, D>) -> SVector<T, D> {
403        self.rotation.inverse_transform_vector(v)
404    }
405
406    /// Transform the given unit vector by the inverse of this isometry, ignoring the
407    /// translation component of the isometry. This may be
408    /// less expensive than computing the entire isometry inverse and then
409    /// transforming the point.
410    ///
411    /// # Example
412    ///
413    /// ```
414    /// # #[macro_use] extern crate approx;
415    /// # use std::f32;
416    /// # use nalgebra::{Isometry3, Translation3, UnitQuaternion, Vector3};
417    /// let tra = Translation3::new(0.0, 0.0, 3.0);
418    /// let rot = UnitQuaternion::from_scaled_axis(Vector3::z() * f32::consts::FRAC_PI_2);
419    /// let iso = Isometry3::from_parts(tra, rot);
420    ///
421    /// let transformed_point = iso.inverse_transform_unit_vector(&Vector3::x_axis());
422    /// assert_relative_eq!(transformed_point, -Vector3::y_axis(), epsilon = 1.0e-6);
423    /// ```
424    #[inline]
425    #[must_use]
426    pub fn inverse_transform_unit_vector(&self, v: &Unit<SVector<T, D>>) -> Unit<SVector<T, D>> {
427        self.rotation.inverse_transform_unit_vector(v)
428    }
429}
430
431// NOTE: we don't require `R: Rotation<...>` here because this is not useful for the implementation
432// and makes it hard to use it, e.g., for Transform × Isometry implementation.
433// This is OK since all constructors of the isometry enforce the Rotation bound already (and
434// explicit struct construction is prevented by the dummy ZST field).
435/// # Conversion to a matrix
436impl<T: SimdRealField, R, const D: usize> Isometry<T, R, D> {
437    /// Converts this isometry into its equivalent homogeneous transformation matrix.
438    ///
439    /// This is the same as `self.to_matrix()`.
440    ///
441    /// # Example
442    ///
443    /// ```
444    /// # #[macro_use] extern crate approx;
445    /// # use std::f32;
446    /// # use nalgebra::{Isometry2, Vector2, Matrix3};
447    /// let iso = Isometry2::new(Vector2::new(10.0, 20.0), f32::consts::FRAC_PI_6);
448    /// let expected = Matrix3::new(0.8660254, -0.5,      10.0,
449    ///                             0.5,       0.8660254, 20.0,
450    ///                             0.0,       0.0,       1.0);
451    ///
452    /// assert_relative_eq!(iso.to_homogeneous(), expected, epsilon = 1.0e-6);
453    /// ```
454    #[inline]
455    #[must_use]
456    pub fn to_homogeneous(&self) -> OMatrix<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>
457    where
458        Const<D>: DimNameAdd<U1>,
459        R: SubsetOf<OMatrix<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>>,
460        DefaultAllocator: Allocator<DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>,
461    {
462        let mut res: OMatrix<T, _, _> = crate::convert_ref(&self.rotation);
463        res.fixed_view_mut::<D, 1>(0, D)
464            .copy_from(&self.translation.vector);
465
466        res
467    }
468
469    /// Converts this isometry into its equivalent homogeneous transformation matrix.
470    ///
471    /// This is the same as `self.to_homogeneous()`.
472    ///
473    /// # Example
474    ///
475    /// ```
476    /// # #[macro_use] extern crate approx;
477    /// # use std::f32;
478    /// # use nalgebra::{Isometry2, Vector2, Matrix3};
479    /// let iso = Isometry2::new(Vector2::new(10.0, 20.0), f32::consts::FRAC_PI_6);
480    /// let expected = Matrix3::new(0.8660254, -0.5,      10.0,
481    ///                             0.5,       0.8660254, 20.0,
482    ///                             0.0,       0.0,       1.0);
483    ///
484    /// assert_relative_eq!(iso.to_matrix(), expected, epsilon = 1.0e-6);
485    /// ```
486    #[inline]
487    #[must_use]
488    pub fn to_matrix(&self) -> OMatrix<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>
489    where
490        Const<D>: DimNameAdd<U1>,
491        R: SubsetOf<OMatrix<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>>,
492        DefaultAllocator: Allocator<DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>,
493    {
494        self.to_homogeneous()
495    }
496}
497
498impl<T: SimdRealField, R, const D: usize> Eq for Isometry<T, R, D> where
499    R: AbstractRotation<T, D> + Eq
500{
501}
502
503impl<T: SimdRealField, R, const D: usize> PartialEq for Isometry<T, R, D>
504where
505    R: AbstractRotation<T, D> + PartialEq,
506{
507    #[inline]
508    fn eq(&self, right: &Self) -> bool {
509        self.translation == right.translation && self.rotation == right.rotation
510    }
511}
512
513impl<T: RealField, R, const D: usize> AbsDiffEq for Isometry<T, R, D>
514where
515    R: AbstractRotation<T, D> + AbsDiffEq<Epsilon = T::Epsilon>,
516    T::Epsilon: Clone,
517{
518    type Epsilon = T::Epsilon;
519
520    #[inline]
521    fn default_epsilon() -> Self::Epsilon {
522        T::default_epsilon()
523    }
524
525    #[inline]
526    fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool {
527        self.translation
528            .abs_diff_eq(&other.translation, epsilon.clone())
529            && self.rotation.abs_diff_eq(&other.rotation, epsilon)
530    }
531}
532
533impl<T: RealField, R, const D: usize> RelativeEq for Isometry<T, R, D>
534where
535    R: AbstractRotation<T, D> + RelativeEq<Epsilon = T::Epsilon>,
536    T::Epsilon: Clone,
537{
538    #[inline]
539    fn default_max_relative() -> Self::Epsilon {
540        T::default_max_relative()
541    }
542
543    #[inline]
544    fn relative_eq(
545        &self,
546        other: &Self,
547        epsilon: Self::Epsilon,
548        max_relative: Self::Epsilon,
549    ) -> bool {
550        self.translation
551            .relative_eq(&other.translation, epsilon.clone(), max_relative.clone())
552            && self
553                .rotation
554                .relative_eq(&other.rotation, epsilon, max_relative)
555    }
556}
557
558impl<T: RealField, R, const D: usize> UlpsEq for Isometry<T, R, D>
559where
560    R: AbstractRotation<T, D> + UlpsEq<Epsilon = T::Epsilon>,
561    T::Epsilon: Clone,
562{
563    #[inline]
564    fn default_max_ulps() -> u32 {
565        T::default_max_ulps()
566    }
567
568    #[inline]
569    fn ulps_eq(&self, other: &Self, epsilon: Self::Epsilon, max_ulps: u32) -> bool {
570        self.translation
571            .ulps_eq(&other.translation, epsilon.clone(), max_ulps)
572            && self.rotation.ulps_eq(&other.rotation, epsilon, max_ulps)
573    }
574}
575
576/*
577 *
578 * Display
579 *
580 */
581impl<T: RealField + fmt::Display, R, const D: usize> fmt::Display for Isometry<T, R, D>
582where
583    R: fmt::Display,
584{
585    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
586        let precision = f.precision().unwrap_or(3);
587
588        writeln!(f, "Isometry {{")?;
589        write!(f, "{:.*}", precision, self.translation)?;
590        write!(f, "{:.*}", precision, self.rotation)?;
591        writeln!(f, "}}")
592    }
593}