nalgebra/geometry/
transform.rs

1use approx::{AbsDiffEq, RelativeEq, UlpsEq};
2use std::any::Any;
3use std::fmt::{self, Debug};
4use std::hash;
5use std::marker::PhantomData;
6
7#[cfg(feature = "serde-serialize-no-std")]
8use serde::{Deserialize, Deserializer, Serialize, Serializer};
9
10use simba::scalar::RealField;
11
12use crate::base::allocator::Allocator;
13use crate::base::dimension::{DimNameAdd, DimNameSum, U1};
14use crate::base::storage::Owned;
15use crate::base::{Const, DefaultAllocator, DimName, OMatrix, SVector};
16
17use crate::geometry::Point;
18
19/// Trait implemented by phantom types identifying the projective transformation type.
20///
21/// NOTE: this trait is not intended to be implemented outside of the `nalgebra` crate.
22pub trait TCategory: Any + Debug + Copy + PartialEq + Send {
23    /// Indicates whether a `Transform` with the category `Self` has a bottom-row different from
24    /// `0 0 .. 1`.
25    #[inline]
26    fn has_normalizer() -> bool {
27        true
28    }
29
30    /// Checks that the given matrix is a valid homogeneous representation of an element of the
31    /// category `Self`.
32    fn check_homogeneous_invariants<T: RealField, D: DimName>(mat: &OMatrix<T, D, D>) -> bool
33    where
34        T::Epsilon: Clone,
35        DefaultAllocator: Allocator<D, D>;
36}
37
38/// Traits that gives the `Transform` category that is compatible with the result of the
39/// multiplication of transformations with categories `Self` and `Other`.
40pub trait TCategoryMul<Other: TCategory>: TCategory {
41    /// The transform category that results from the multiplication of a `Transform<Self>` to a
42    /// `Transform<Other>`. This is usually equal to `Self` or `Other`, whichever is the most
43    /// general category.
44    type Representative: TCategory;
45}
46
47/// Indicates that `Self` is a more general `Transform` category than `Other`.
48pub trait SuperTCategoryOf<Other: TCategory>: TCategory {}
49
50/// Indicates that `Self` is a more specific `Transform` category than `Other`.
51///
52/// Automatically implemented based on `SuperTCategoryOf`.
53pub trait SubTCategoryOf<Other: TCategory>: TCategory {}
54impl<T1, T2> SubTCategoryOf<T2> for T1
55where
56    T1: TCategory,
57    T2: SuperTCategoryOf<T1>,
58{
59}
60
61/// Tag representing the most general (not necessarily inversible) `Transform` type.
62#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
63pub enum TGeneral {}
64
65/// Tag representing the most general inversible `Transform` type.
66#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
67pub enum TProjective {}
68
69/// Tag representing an affine `Transform`. Its bottom-row is equal to `(0, 0 ... 0, 1)`.
70#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
71pub enum TAffine {}
72
73impl TCategory for TGeneral {
74    #[inline]
75    fn check_homogeneous_invariants<T: RealField, D: DimName>(_: &OMatrix<T, D, D>) -> bool
76    where
77        T::Epsilon: Clone,
78        DefaultAllocator: Allocator<D, D>,
79    {
80        true
81    }
82}
83
84impl TCategory for TProjective {
85    #[inline]
86    fn check_homogeneous_invariants<T: RealField, D: DimName>(mat: &OMatrix<T, D, D>) -> bool
87    where
88        T::Epsilon: Clone,
89        DefaultAllocator: Allocator<D, D>,
90    {
91        mat.is_invertible()
92    }
93}
94
95impl TCategory for TAffine {
96    #[inline]
97    fn has_normalizer() -> bool {
98        false
99    }
100
101    #[inline]
102    fn check_homogeneous_invariants<T: RealField, D: DimName>(mat: &OMatrix<T, D, D>) -> bool
103    where
104        T::Epsilon: Clone,
105        DefaultAllocator: Allocator<D, D>,
106    {
107        let last = D::dim() - 1;
108        mat.is_invertible()
109            && mat[(last, last)] == T::one()
110            && (0..last).all(|i| mat[(last, i)].is_zero())
111    }
112}
113
114macro_rules! category_mul_impl(
115    ($($a: ident * $b: ident => $c: ty);* $(;)*) => {$(
116        impl TCategoryMul<$a> for $b {
117            type Representative = $c;
118        }
119    )*}
120);
121
122// We require stability upon multiplication.
123impl<T: TCategory> TCategoryMul<T> for T {
124    type Representative = T;
125}
126
127category_mul_impl!(
128//  TGeneral * TGeneral    => TGeneral;
129    TGeneral * TProjective => TGeneral;
130    TGeneral * TAffine     => TGeneral;
131
132    TProjective * TGeneral    => TGeneral;
133//  TProjective * TProjective => TProjective;
134    TProjective * TAffine     => TProjective;
135
136    TAffine * TGeneral    => TGeneral;
137    TAffine * TProjective => TProjective;
138//  TAffine * TAffine     => TAffine;
139);
140
141macro_rules! super_tcategory_impl(
142    ($($a: ident >= $b: ident);* $(;)*) => {$(
143        impl SuperTCategoryOf<$b> for $a { }
144    )*}
145);
146
147impl<T: TCategory> SuperTCategoryOf<T> for T {}
148
149super_tcategory_impl!(
150    TGeneral    >= TProjective;
151    TGeneral    >= TAffine;
152    TProjective >= TAffine;
153);
154
155/// A transformation matrix in homogeneous coordinates.
156///
157/// It is stored as a matrix with dimensions `(D + 1, D + 1)`, e.g., it stores a 4x4 matrix for a
158/// 3D transformation.
159#[repr(C)]
160pub struct Transform<T: RealField, C: TCategory, const D: usize>
161where
162    Const<D>: DimNameAdd<U1>,
163    DefaultAllocator: Allocator<DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>,
164{
165    matrix: OMatrix<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>,
166    _phantom: PhantomData<C>,
167}
168
169impl<T: RealField + Debug, C: TCategory, const D: usize> Debug for Transform<T, C, D>
170where
171    Const<D>: DimNameAdd<U1>,
172    DefaultAllocator: Allocator<DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>,
173{
174    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
175        self.matrix.fmt(formatter)
176    }
177}
178
179impl<T: RealField + hash::Hash, C: TCategory, const D: usize> hash::Hash for Transform<T, C, D>
180where
181    Const<D>: DimNameAdd<U1>,
182    DefaultAllocator: Allocator<DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>,
183    Owned<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>: hash::Hash,
184{
185    fn hash<H: hash::Hasher>(&self, state: &mut H) {
186        self.matrix.hash(state);
187    }
188}
189
190impl<T: RealField + Copy, C: TCategory, const D: usize> Copy for Transform<T, C, D>
191where
192    Const<D>: DimNameAdd<U1>,
193    DefaultAllocator: Allocator<DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>,
194    Owned<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>: Copy,
195{
196}
197
198impl<T: RealField, C: TCategory, const D: usize> Clone for Transform<T, C, D>
199where
200    Const<D>: DimNameAdd<U1>,
201    DefaultAllocator: Allocator<DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>,
202{
203    #[inline]
204    fn clone(&self) -> Self {
205        Transform::from_matrix_unchecked(self.matrix.clone())
206    }
207}
208
209#[cfg(feature = "bytemuck")]
210unsafe impl<T, C: TCategory, const D: usize> bytemuck::Zeroable for Transform<T, C, D>
211where
212    T: RealField + bytemuck::Zeroable,
213    Const<D>: DimNameAdd<U1>,
214    DefaultAllocator: Allocator<DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>,
215    OMatrix<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>: bytemuck::Zeroable,
216{
217}
218
219#[cfg(feature = "bytemuck")]
220unsafe impl<T, C: TCategory, const D: usize> bytemuck::Pod for Transform<T, C, D>
221where
222    T: RealField + bytemuck::Pod,
223    Const<D>: DimNameAdd<U1>,
224    DefaultAllocator: Allocator<DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>,
225    OMatrix<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>: bytemuck::Pod,
226    Owned<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>: Copy,
227{
228}
229
230#[cfg(feature = "serde-serialize-no-std")]
231impl<T: RealField, C: TCategory, const D: usize> Serialize for Transform<T, C, D>
232where
233    Const<D>: DimNameAdd<U1>,
234    DefaultAllocator: Allocator<DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>,
235    Owned<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>: Serialize,
236{
237    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
238    where
239        S: Serializer,
240    {
241        self.matrix.serialize(serializer)
242    }
243}
244
245#[cfg(feature = "serde-serialize-no-std")]
246impl<'a, T: RealField, C: TCategory, const D: usize> Deserialize<'a> for Transform<T, C, D>
247where
248    Const<D>: DimNameAdd<U1>,
249    DefaultAllocator: Allocator<DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>,
250    Owned<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>: Deserialize<'a>,
251{
252    fn deserialize<Des>(deserializer: Des) -> Result<Self, Des::Error>
253    where
254        Des: Deserializer<'a>,
255    {
256        let matrix = OMatrix::<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>::deserialize(
257            deserializer,
258        )?;
259
260        Ok(Transform::from_matrix_unchecked(matrix))
261    }
262}
263
264impl<T: RealField + Eq, C: TCategory, const D: usize> Eq for Transform<T, C, D>
265where
266    Const<D>: DimNameAdd<U1>,
267    DefaultAllocator: Allocator<DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>,
268{
269}
270
271impl<T: RealField, C: TCategory, const D: usize> PartialEq for Transform<T, C, D>
272where
273    Const<D>: DimNameAdd<U1>,
274    DefaultAllocator: Allocator<DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>,
275{
276    #[inline]
277    fn eq(&self, right: &Self) -> bool {
278        self.matrix == right.matrix
279    }
280}
281
282impl<T: RealField, C: TCategory, const D: usize> Transform<T, C, D>
283where
284    Const<D>: DimNameAdd<U1>,
285    DefaultAllocator: Allocator<DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>,
286{
287    /// Creates a new transformation from the given homogeneous matrix. The transformation category
288    /// of `Self` is not checked to be verified by the given matrix.
289    #[inline]
290    pub fn from_matrix_unchecked(
291        matrix: OMatrix<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>,
292    ) -> Self {
293        Transform {
294            matrix,
295            _phantom: PhantomData,
296        }
297    }
298
299    /// Retrieves the underlying matrix.
300    ///
301    /// # Examples
302    /// ```
303    /// # use nalgebra::{Matrix3, Transform2};
304    ///
305    /// let m = Matrix3::new(1.0, 2.0, 0.0,
306    ///                      3.0, 4.0, 0.0,
307    ///                      0.0, 0.0, 1.0);
308    /// let t = Transform2::from_matrix_unchecked(m);
309    /// assert_eq!(t.into_inner(), m);
310    /// ```
311    #[inline]
312    pub fn into_inner(self) -> OMatrix<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>> {
313        self.matrix
314    }
315
316    /// Retrieves the underlying matrix.
317    /// Deprecated: Use [`Transform::into_inner`] instead.
318    #[deprecated(note = "use `.into_inner()` instead")]
319    #[inline]
320    pub fn unwrap(self) -> OMatrix<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>> {
321        self.matrix
322    }
323
324    /// A reference to the underlying matrix.
325    ///
326    /// # Examples
327    /// ```
328    /// # use nalgebra::{Matrix3, Transform2};
329    ///
330    /// let m = Matrix3::new(1.0, 2.0, 0.0,
331    ///                      3.0, 4.0, 0.0,
332    ///                      0.0, 0.0, 1.0);
333    /// let t = Transform2::from_matrix_unchecked(m);
334    /// assert_eq!(*t.matrix(), m);
335    /// ```
336    #[inline]
337    #[must_use]
338    pub fn matrix(&self) -> &OMatrix<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>> {
339        &self.matrix
340    }
341
342    /// A mutable reference to the underlying matrix.
343    ///
344    /// It is `_unchecked` because direct modifications of this matrix may break invariants
345    /// identified by this transformation category.
346    ///
347    /// # Examples
348    /// ```
349    /// # use nalgebra::{Matrix3, Transform2};
350    ///
351    /// let m = Matrix3::new(1.0, 2.0, 0.0,
352    ///                      3.0, 4.0, 0.0,
353    ///                      0.0, 0.0, 1.0);
354    /// let mut t = Transform2::from_matrix_unchecked(m);
355    /// t.matrix_mut_unchecked().m12 = 42.0;
356    /// t.matrix_mut_unchecked().m23 = 90.0;
357    ///
358    ///
359    /// let expected = Matrix3::new(1.0, 42.0, 0.0,
360    ///                             3.0, 4.0,  90.0,
361    ///                             0.0, 0.0,  1.0);
362    /// assert_eq!(*t.matrix(), expected);
363    /// ```
364    #[inline]
365    pub fn matrix_mut_unchecked(
366        &mut self,
367    ) -> &mut OMatrix<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>> {
368        &mut self.matrix
369    }
370
371    /// Sets the category of this transform.
372    ///
373    /// This can be done only if the new category is more general than the current one, e.g., a
374    /// transform with category `TProjective` cannot be converted to a transform with category
375    /// `TAffine` because not all projective transformations are affine (the other way-round is
376    /// valid though).
377    #[inline]
378    pub fn set_category<CNew: SuperTCategoryOf<C>>(self) -> Transform<T, CNew, D> {
379        Transform::from_matrix_unchecked(self.matrix)
380    }
381
382    /// Clones this transform into one that owns its data.
383    #[inline]
384    #[deprecated(
385        note = "This method is redundant with automatic `Copy` and the `.clone()` method and will be removed in a future release."
386    )]
387    pub fn clone_owned(&self) -> Transform<T, C, D> {
388        Transform::from_matrix_unchecked(self.matrix.clone_owned())
389    }
390
391    /// Converts this transform into its equivalent homogeneous transformation matrix.
392    ///
393    /// # Examples
394    /// ```
395    /// # use nalgebra::{Matrix3, Transform2};
396    ///
397    /// let m = Matrix3::new(1.0, 2.0, 0.0,
398    ///                      3.0, 4.0, 0.0,
399    ///                      0.0, 0.0, 1.0);
400    /// let t = Transform2::from_matrix_unchecked(m);
401    /// assert_eq!(t.to_homogeneous(), m);
402    /// ```
403    #[inline]
404    #[must_use]
405    pub fn to_homogeneous(&self) -> OMatrix<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>> {
406        self.matrix().clone_owned()
407    }
408
409    /// Attempts to invert this transformation. You may use `.inverse` instead of this
410    /// transformation has a subcategory of `TProjective` (i.e. if it is a `Projective{2,3}` or `Affine{2,3}`).
411    ///
412    /// # Examples
413    /// ```
414    /// # #[macro_use] extern crate approx;
415    /// # use nalgebra::{Matrix3, Transform2};
416    ///
417    /// let m = Matrix3::new(2.0, 2.0, -0.3,
418    ///                      3.0, 4.0, 0.1,
419    ///                      0.0, 0.0, 1.0);
420    /// let t = Transform2::from_matrix_unchecked(m);
421    /// let inv_t = t.try_inverse().unwrap();
422    /// assert_relative_eq!(t * inv_t, Transform2::identity());
423    /// assert_relative_eq!(inv_t * t, Transform2::identity());
424    ///
425    /// // Non-invertible case.
426    /// let m = Matrix3::new(0.0, 2.0, 1.0,
427    ///                      3.0, 0.0, 5.0,
428    ///                      0.0, 0.0, 0.0);
429    /// let t = Transform2::from_matrix_unchecked(m);
430    /// assert!(t.try_inverse().is_none());
431    /// ```
432    #[inline]
433    #[must_use = "Did you mean to use try_inverse_mut()?"]
434    pub fn try_inverse(self) -> Option<Transform<T, C, D>> {
435        self.matrix
436            .try_inverse()
437            .map(Transform::from_matrix_unchecked)
438    }
439
440    /// Inverts this transformation. Use `.try_inverse` if this transform has the `TGeneral`
441    /// category (i.e., a `Transform{2,3}` may not be invertible).
442    ///
443    /// # Examples
444    /// ```
445    /// # #[macro_use] extern crate approx;
446    /// # use nalgebra::{Matrix3, Projective2};
447    ///
448    /// let m = Matrix3::new(2.0, 2.0, -0.3,
449    ///                      3.0, 4.0, 0.1,
450    ///                      0.0, 0.0, 1.0);
451    /// let proj = Projective2::from_matrix_unchecked(m);
452    /// let inv_t = proj.inverse();
453    /// assert_relative_eq!(proj * inv_t, Projective2::identity());
454    /// assert_relative_eq!(inv_t * proj, Projective2::identity());
455    /// ```
456    #[inline]
457    #[must_use = "Did you mean to use inverse_mut()?"]
458    pub fn inverse(self) -> Transform<T, C, D>
459    where
460        C: SubTCategoryOf<TProjective>,
461    {
462        // TODO: specialize for TAffine?
463        Transform::from_matrix_unchecked(self.matrix.try_inverse().unwrap())
464    }
465
466    /// Attempts to invert this transformation in-place. You may use `.inverse_mut` instead of this
467    /// transformation has a subcategory of `TProjective`.
468    ///
469    /// # Examples
470    /// ```
471    /// # #[macro_use] extern crate approx;
472    /// # use nalgebra::{Matrix3, Transform2};
473    ///
474    /// let m = Matrix3::new(2.0, 2.0, -0.3,
475    ///                      3.0, 4.0, 0.1,
476    ///                      0.0, 0.0, 1.0);
477    /// let t = Transform2::from_matrix_unchecked(m);
478    /// let mut inv_t = t;
479    /// assert!(inv_t.try_inverse_mut());
480    /// assert_relative_eq!(t * inv_t, Transform2::identity());
481    /// assert_relative_eq!(inv_t * t, Transform2::identity());
482    ///
483    /// // Non-invertible case.
484    /// let m = Matrix3::new(0.0, 2.0, 1.0,
485    ///                      3.0, 0.0, 5.0,
486    ///                      0.0, 0.0, 0.0);
487    /// let mut t = Transform2::from_matrix_unchecked(m);
488    /// assert!(!t.try_inverse_mut());
489    /// ```
490    #[inline]
491    pub fn try_inverse_mut(&mut self) -> bool {
492        self.matrix.try_inverse_mut()
493    }
494
495    /// Inverts this transformation in-place. Use `.try_inverse_mut` if this transform has the
496    /// `TGeneral` category (it may not be invertible).
497    ///
498    /// # Examples
499    /// ```
500    /// # #[macro_use] extern crate approx;
501    /// # use nalgebra::{Matrix3, Projective2};
502    ///
503    /// let m = Matrix3::new(2.0, 2.0, -0.3,
504    ///                      3.0, 4.0, 0.1,
505    ///                      0.0, 0.0, 1.0);
506    /// let proj = Projective2::from_matrix_unchecked(m);
507    /// let mut inv_t = proj;
508    /// inv_t.inverse_mut();
509    /// assert_relative_eq!(proj * inv_t, Projective2::identity());
510    /// assert_relative_eq!(inv_t * proj, Projective2::identity());
511    /// ```
512    #[inline]
513    pub fn inverse_mut(&mut self)
514    where
515        C: SubTCategoryOf<TProjective>,
516    {
517        let _ = self.matrix.try_inverse_mut();
518    }
519}
520
521impl<T, C, const D: usize> Transform<T, C, D>
522where
523    T: RealField,
524    C: TCategory,
525    Const<D>: DimNameAdd<U1>,
526    DefaultAllocator: Allocator<DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>
527        + Allocator<DimNameSum<Const<D>, U1>>, // + Allocator<D, D>
528                                               // + Allocator<D>
529{
530    /// Transform the given point by this transformation.
531    ///
532    /// This is the same as the multiplication `self * pt`.
533    #[inline]
534    #[must_use]
535    pub fn transform_point(&self, pt: &Point<T, D>) -> Point<T, D> {
536        self * pt
537    }
538
539    /// Transform the given vector by this transformation, ignoring the
540    /// translational component of the transformation.
541    ///
542    /// This is the same as the multiplication `self * v`.
543    #[inline]
544    #[must_use]
545    pub fn transform_vector(&self, v: &SVector<T, D>) -> SVector<T, D> {
546        self * v
547    }
548}
549
550impl<T: RealField, C: TCategory, const D: usize> Transform<T, C, D>
551where
552    Const<D>: DimNameAdd<U1>,
553    C: SubTCategoryOf<TProjective>,
554    DefaultAllocator: Allocator<DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>
555        + Allocator<DimNameSum<Const<D>, U1>>, // + Allocator<D, D>
556                                               // + Allocator<D>
557{
558    /// Transform the given point by the inverse of this transformation.
559    /// This may be cheaper than inverting the transformation and transforming
560    /// the point.
561    #[inline]
562    #[must_use]
563    pub fn inverse_transform_point(&self, pt: &Point<T, D>) -> Point<T, D> {
564        self.clone().inverse() * pt
565    }
566
567    /// Transform the given vector by the inverse of this transformation.
568    /// This may be cheaper than inverting the transformation and transforming
569    /// the vector.
570    #[inline]
571    #[must_use]
572    pub fn inverse_transform_vector(&self, v: &SVector<T, D>) -> SVector<T, D> {
573        self.clone().inverse() * v
574    }
575}
576
577impl<T: RealField, const D: usize> Transform<T, TGeneral, D>
578where
579    Const<D>: DimNameAdd<U1>,
580    DefaultAllocator: Allocator<DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>,
581{
582    /// A mutable reference to underlying matrix. Use `.matrix_mut_unchecked` instead if this
583    /// transformation category is not `TGeneral`.
584    #[inline]
585    pub fn matrix_mut(
586        &mut self,
587    ) -> &mut OMatrix<T, DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>> {
588        self.matrix_mut_unchecked()
589    }
590}
591
592impl<T: RealField, C: TCategory, const D: usize> AbsDiffEq for Transform<T, C, D>
593where
594    Const<D>: DimNameAdd<U1>,
595    T::Epsilon: Clone,
596    DefaultAllocator: Allocator<DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>,
597{
598    type Epsilon = T::Epsilon;
599
600    #[inline]
601    fn default_epsilon() -> Self::Epsilon {
602        T::default_epsilon()
603    }
604
605    #[inline]
606    fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool {
607        self.matrix.abs_diff_eq(&other.matrix, epsilon)
608    }
609}
610
611impl<T: RealField, C: TCategory, const D: usize> RelativeEq for Transform<T, C, D>
612where
613    Const<D>: DimNameAdd<U1>,
614    T::Epsilon: Clone,
615    DefaultAllocator: Allocator<DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>,
616{
617    #[inline]
618    fn default_max_relative() -> Self::Epsilon {
619        T::default_max_relative()
620    }
621
622    #[inline]
623    fn relative_eq(
624        &self,
625        other: &Self,
626        epsilon: Self::Epsilon,
627        max_relative: Self::Epsilon,
628    ) -> bool {
629        self.matrix
630            .relative_eq(&other.matrix, epsilon, max_relative)
631    }
632}
633
634impl<T: RealField, C: TCategory, const D: usize> UlpsEq for Transform<T, C, D>
635where
636    Const<D>: DimNameAdd<U1>,
637    T::Epsilon: Clone,
638    DefaultAllocator: Allocator<DimNameSum<Const<D>, U1>, DimNameSum<Const<D>, U1>>,
639{
640    #[inline]
641    fn default_max_ulps() -> u32 {
642        T::default_max_ulps()
643    }
644
645    #[inline]
646    fn ulps_eq(&self, other: &Self, epsilon: Self::Epsilon, max_ulps: u32) -> bool {
647        self.matrix.ulps_eq(&other.matrix, epsilon, max_ulps)
648    }
649}
650
651#[cfg(test)]
652mod tests {
653    use super::*;
654    use crate::base::Matrix4;
655
656    #[test]
657    fn checks_homogeneous_invariants_of_square_identity_matrix() {
658        assert!(TAffine::check_homogeneous_invariants(
659            &Matrix4::<f32>::identity()
660        ));
661    }
662}