diff --git a/src/geometry/transform.rs b/src/geometry/transform.rs index f62e0f24..11060977 100644 --- a/src/geometry/transform.rs +++ b/src/geometry/transform.rs @@ -1,3 +1,4 @@ +use approx::{AbsDiffEq, RelativeEq, UlpsEq}; use std::any::Any; use std::fmt::Debug; use std::marker::PhantomData; @@ -14,7 +15,7 @@ use base::{DefaultAllocator, MatrixN}; /// Trait implemented by phantom types identifying the projective transformation type. /// -/// NOTE: this trait is not intended to be implementable outside of the `nalgebra` crate. +/// NOTE: this trait is not intended to be implemented outside of the `nalgebra` crate. pub trait TCategory: Any + Debug + Copy + PartialEq + Send { /// Indicates whether a `Transform` with the category `Self` has a bottom-row different from /// `0 0 .. 1`. @@ -174,7 +175,8 @@ impl + Copy, C: TCategory> Copy for Transform, DimNameSum>, Owned, DimNameSum>: Copy, -{} +{ +} impl, C: TCategory> Clone for Transform where DefaultAllocator: Allocator, DimNameSum> @@ -236,13 +238,35 @@ where DefaultAllocator: Allocator, DimNameSum> } } - /// The underlying matrix. + /// Retrieves the underlying matrix. + /// + /// # Examples + /// ``` + /// # use nalgebra::{Matrix3, Transform2}; + /// + /// let m = Matrix3::new(1.0, 2.0, 0.0, + /// 3.0, 4.0, 0.0, + /// 0.0, 0.0, 1.0); + /// let t = Transform2::from_matrix_unchecked(m); + /// assert_eq!(t.unwrap(), m); + /// ``` #[inline] pub fn unwrap(self) -> MatrixN> { self.matrix } /// A reference to the underlying matrix. + /// + /// # Examples + /// ``` + /// # use nalgebra::{Matrix3, Transform2}; + /// + /// let m = Matrix3::new(1.0, 2.0, 0.0, + /// 3.0, 4.0, 0.0, + /// 0.0, 0.0, 1.0); + /// let t = Transform2::from_matrix_unchecked(m); + /// assert_eq!(*t.matrix(), m); + /// ``` #[inline] pub fn matrix(&self) -> &MatrixN> { &self.matrix @@ -252,6 +276,24 @@ where DefaultAllocator: Allocator, DimNameSum> /// /// It is `_unchecked` because direct modifications of this matrix may break invariants /// identified by this transformation category. + /// + /// # Examples + /// ``` + /// # use nalgebra::{Matrix3, Transform2}; + /// + /// let m = Matrix3::new(1.0, 2.0, 0.0, + /// 3.0, 4.0, 0.0, + /// 0.0, 0.0, 1.0); + /// let mut t = Transform2::from_matrix_unchecked(m); + /// t.matrix_mut_unchecked().m12 = 42.0; + /// t.matrix_mut_unchecked().m23 = 90.0; + /// + /// + /// let expected = Matrix3::new(1.0, 42.0, 0.0, + /// 3.0, 4.0, 90.0, + /// 0.0, 0.0, 1.0); + /// assert_eq!(*t.matrix(), expected); + /// ``` #[inline] pub fn matrix_mut_unchecked(&mut self) -> &mut MatrixN> { &mut self.matrix @@ -270,19 +312,54 @@ where DefaultAllocator: Allocator, DimNameSum> /// Clones this transform into one that owns its data. #[inline] - #[deprecated(note = "This method is a no-op and will be removed in a future release.")] + #[deprecated( + note = "This method is redundant with automatic `Copy` and the `.clone()` method and will be removed in a future release." + )] pub fn clone_owned(&self) -> Transform { Transform::from_matrix_unchecked(self.matrix.clone_owned()) } /// Converts this transform into its equivalent homogeneous transformation matrix. + /// + /// # Examples + /// ``` + /// # use nalgebra::{Matrix3, Transform2}; + /// + /// let m = Matrix3::new(1.0, 2.0, 0.0, + /// 3.0, 4.0, 0.0, + /// 0.0, 0.0, 1.0); + /// let t = Transform2::from_matrix_unchecked(m); + /// assert_eq!(t.unwrap(), m); + /// ``` #[inline] pub fn to_homogeneous(&self) -> MatrixN> { self.matrix().clone_owned() } /// Attempts to invert this transformation. You may use `.inverse` instead of this - /// transformation has a subcategory of `TProjective`. + /// transformation has a subcategory of `TProjective` (i.e. if it is a `Projective{2,3}` or `Affine{2,3}`). + /// + /// # Examples + /// ``` + /// # #[macro_use] extern crate approx; + /// # extern crate nalgebra; + /// # use nalgebra::{Matrix3, Transform2}; + /// + /// let m = Matrix3::new(2.0, 2.0, -0.3, + /// 3.0, 4.0, 0.1, + /// 0.0, 0.0, 1.0); + /// let t = Transform2::from_matrix_unchecked(m); + /// let inv_t = t.try_inverse().unwrap(); + /// assert_relative_eq!(t * inv_t, Transform2::identity()); + /// assert_relative_eq!(inv_t * t, Transform2::identity()); + /// + /// // Non-invertible case. + /// let m = Matrix3::new(0.0, 2.0, 1.0, + /// 3.0, 0.0, 5.0, + /// 0.0, 0.0, 0.0); + /// let t = Transform2::from_matrix_unchecked(m); + /// assert!(t.try_inverse().is_none()); + /// ``` #[inline] pub fn try_inverse(self) -> Option> { if let Some(m) = self.matrix.try_inverse() { @@ -293,7 +370,22 @@ where DefaultAllocator: Allocator, DimNameSum> } /// Inverts this transformation. Use `.try_inverse` if this transform has the `TGeneral` - /// category (it may not be invertible). + /// category (i.e., a `Transform{2,3}` may not be invertible). + /// + /// # Examples + /// ``` + /// # #[macro_use] extern crate approx; + /// # extern crate nalgebra; + /// # use nalgebra::{Matrix3, Projective2}; + /// + /// let m = Matrix3::new(2.0, 2.0, -0.3, + /// 3.0, 4.0, 0.1, + /// 0.0, 0.0, 1.0); + /// let proj = Projective2::from_matrix_unchecked(m); + /// let inv_t = proj.inverse(); + /// assert_relative_eq!(proj * inv_t, Projective2::identity()); + /// assert_relative_eq!(inv_t * proj, Projective2::identity()); + /// ``` #[inline] pub fn inverse(self) -> Transform where C: SubTCategoryOf { @@ -303,6 +395,29 @@ where DefaultAllocator: Allocator, DimNameSum> /// Attempts to invert this transformation in-place. You may use `.inverse_mut` instead of this /// transformation has a subcategory of `TProjective`. + /// + /// # Examples + /// ``` + /// # #[macro_use] extern crate approx; + /// # extern crate nalgebra; + /// # use nalgebra::{Matrix3, Transform2}; + /// + /// let m = Matrix3::new(2.0, 2.0, -0.3, + /// 3.0, 4.0, 0.1, + /// 0.0, 0.0, 1.0); + /// let t = Transform2::from_matrix_unchecked(m); + /// let mut inv_t = t; + /// assert!(inv_t.try_inverse_mut()); + /// assert_relative_eq!(t * inv_t, Transform2::identity()); + /// assert_relative_eq!(inv_t * t, Transform2::identity()); + /// + /// // Non-invertible case. + /// let m = Matrix3::new(0.0, 2.0, 1.0, + /// 3.0, 0.0, 5.0, + /// 0.0, 0.0, 0.0); + /// let mut t = Transform2::from_matrix_unchecked(m); + /// assert!(!t.try_inverse_mut()); + /// ``` #[inline] pub fn try_inverse_mut(&mut self) -> bool { self.matrix.try_inverse_mut() @@ -310,6 +425,22 @@ where DefaultAllocator: Allocator, DimNameSum> /// Inverts this transformation in-place. Use `.try_inverse_mut` if this transform has the /// `TGeneral` category (it may not be invertible). + /// + /// # Examples + /// ``` + /// # #[macro_use] extern crate approx; + /// # extern crate nalgebra; + /// # use nalgebra::{Matrix3, Projective2}; + /// + /// let m = Matrix3::new(2.0, 2.0, -0.3, + /// 3.0, 4.0, 0.1, + /// 0.0, 0.0, 1.0); + /// let proj = Projective2::from_matrix_unchecked(m); + /// let mut inv_t = proj; + /// inv_t.inverse_mut(); + /// assert_relative_eq!(proj * inv_t, Projective2::identity()); + /// assert_relative_eq!(inv_t * proj, Projective2::identity()); + /// ``` #[inline] pub fn inverse_mut(&mut self) where C: SubTCategoryOf { @@ -328,6 +459,63 @@ where DefaultAllocator: Allocator, DimNameSum> } } +impl, C: TCategory> AbsDiffEq for Transform +where + N::Epsilon: Copy, + DefaultAllocator: Allocator, DimNameSum>, +{ + type Epsilon = N::Epsilon; + + #[inline] + fn default_epsilon() -> Self::Epsilon { + N::default_epsilon() + } + + #[inline] + fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool { + self.matrix.abs_diff_eq(&other.matrix, epsilon) + } +} + +impl, C: TCategory> RelativeEq for Transform +where + N::Epsilon: Copy, + DefaultAllocator: Allocator, DimNameSum>, +{ + #[inline] + fn default_max_relative() -> Self::Epsilon { + N::default_max_relative() + } + + #[inline] + fn relative_eq( + &self, + other: &Self, + epsilon: Self::Epsilon, + max_relative: Self::Epsilon, + ) -> bool + { + self.matrix + .relative_eq(&other.matrix, epsilon, max_relative) + } +} + +impl, C: TCategory> UlpsEq for Transform +where + N::Epsilon: Copy, + DefaultAllocator: Allocator, DimNameSum>, +{ + #[inline] + fn default_max_ulps() -> u32 { + N::default_max_ulps() + } + + #[inline] + fn ulps_eq(&self, other: &Self, epsilon: Self::Epsilon, max_ulps: u32) -> bool { + self.matrix.ulps_eq(&other.matrix, epsilon, max_ulps) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/geometry/transform_alias.rs b/src/geometry/transform_alias.rs index 999b5224..6f529966 100644 --- a/src/geometry/transform_alias.rs +++ b/src/geometry/transform_alias.rs @@ -2,16 +2,16 @@ use base::dimension::{U2, U3}; use geometry::{TAffine, TGeneral, TProjective, Transform}; -/// A 2D general transformation that may not be inversible. Stored as an homogeneous 3x3 matrix. +/// A 2D general transformation that may not be invertible. Stored as an homogeneous 3x3 matrix. pub type Transform2 = Transform; -/// An inversible 2D general transformation. Stored as an homogeneous 3x3 matrix. +/// An invertible 2D general transformation. Stored as an homogeneous 3x3 matrix. pub type Projective2 = Transform; /// A 2D affine transformation. Stored as an homogeneous 3x3 matrix. pub type Affine2 = Transform; /// A 3D general transformation that may not be inversible. Stored as an homogeneous 4x4 matrix. pub type Transform3 = Transform; -/// An inversible 3D general transformation. Stored as an homogeneous 4x4 matrix. +/// An invertible 3D general transformation. Stored as an homogeneous 4x4 matrix. pub type Projective3 = Transform; /// A 3D affine transformation. Stored as an homogeneous 4x4 matrix. pub type Affine3 = Transform; diff --git a/src/geometry/transform_construction.rs b/src/geometry/transform_construction.rs index 0ab9e5f2..3244ad8a 100644 --- a/src/geometry/transform_construction.rs +++ b/src/geometry/transform_construction.rs @@ -12,6 +12,33 @@ impl, C: TCategory> Transform where DefaultAllocator: Allocator, DimNameSum> { /// Creates a new identity transform. + /// + /// # Example + /// + /// ``` + /// # use nalgebra::{Transform2, Projective2, Affine2, Transform3, Projective3, Affine3, Point2, Point3}; + /// + /// let pt = Point2::new(1.0, 2.0); + /// let t = Projective2::identity(); + /// assert_eq!(t * pt, pt); + /// + /// let aff = Affine2::identity(); + /// assert_eq!(aff * pt, pt); + /// + /// let aff = Transform2::identity(); + /// assert_eq!(aff * pt, pt); + /// + /// // Also works in 3D. + /// let pt = Point3::new(1.0, 2.0, 3.0); + /// let t = Projective3::identity(); + /// assert_eq!(t * pt, pt); + /// + /// let aff = Affine3::identity(); + /// assert_eq!(aff * pt, pt); + /// + /// let aff = Transform3::identity(); + /// assert_eq!(aff * pt, pt); + /// ``` #[inline] pub fn identity() -> Self { Self::from_matrix_unchecked(MatrixN::<_, DimNameSum>::identity())