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