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}