From 0f8b169276c66d3f1bf5e698183b7b367e86d4ce Mon Sep 17 00:00:00 2001 From: Cameron Hart Date: Sun, 6 Oct 2019 22:56:04 +1100 Subject: [PATCH] Removed the dependency on the `approx` crate. This was primiarily used for unit tests. It has been replaced by a simple `assert_approx_eq` macro and a `FloatCompare` trait defined in the test `support` module which is implemented for all `glam` types. Each `glam` type has had an `abs_diff_eq` method added which returns true if the absolute difference of each element is less than or equal to a given `max_abs_diff` value. This is mostly sufficient for the purpose of `glam`'s tests. Fixes #6. --- .travis.yml | 4 +- Cargo.toml | 5 +- README.md | 6 +- src/f32/glam_approx.rs | 280 ---------------------------------------- src/f32/mat2.rs | 14 ++ src/f32/mat3.rs | 16 +++ src/f32/mat4.rs | 17 +++ src/f32/mod.rs | 5 - src/f32/quat.rs | 14 ++ src/f32/vec2.rs | 18 ++- src/f32/vec3.rs | 16 ++- src/f32/vec3_sse2.rs | 2 +- src/f32/vec4.rs | 16 ++- src/f32/vec4_sse2.rs | 2 +- src/lib.rs | 7 +- src/macros.rs | 12 +- tests/mat2.rs | 45 +++---- tests/mat3.rs | 47 ++++--- tests/mat4.rs | 71 +++++----- tests/quat.rs | 107 ++++++++------- tests/support/macros.rs | 31 +++++ tests/support/mod.rs | 117 +++++++++++++++++ tests/transform.rs | 13 +- tests/vec2.rs | 7 - tests/vec3.rs | 12 +- tests/vec4.rs | 5 +- 26 files changed, 414 insertions(+), 475 deletions(-) delete mode 100644 src/f32/glam_approx.rs create mode 100644 tests/support/macros.rs diff --git a/.travis.yml b/.travis.yml index a55bb37e..ecd6dd53 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,8 +14,8 @@ rust: # cache: cargo env: - - CARGO_FEATURES="approx mint rand serde" - - CARGO_FEATURES="approx mint rand serde scalar-math" + - CARGO_FEATURES="mint rand serde" + - CARGO_FEATURES="mint rand serde scalar-math" matrix: allow_failures: diff --git a/Cargo.toml b/Cargo.toml index e1ccb2e2..2297f17b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,9 +17,7 @@ maintenance = { status = "experimental" } [features] default = ["std"] - -# approx is needed for unit tests -std = ["approx"] +std = [] # enable additional glam checks glam-assert = [] @@ -32,7 +30,6 @@ scalar-math = [] transform-types = [] [dependencies] -approx = { version = "0.3", optional = true, default-features = false } mint = { version = "0.5", optional = true, default-features = false } rand = { version = "0.7", optional = true, default-features = false } serde = { version = "1.0", optional = true, features = ["derive"] } diff --git a/README.md b/README.md index a58d654f..4a6099c1 100644 --- a/README.md +++ b/README.md @@ -45,10 +45,8 @@ one element it is more efficient to convert from tuples or arrays: let (x, y, z) = v.into(); ``` -### Default features +### Optional features -* `approx` - implementations of the `AbsDiffEq` and `UlpsEq` traits for all - `glam` types. This is primarily used for unit testing. * `mint` - for interoperating with other 3D math libraries * `rand` - implementations of `Distribution` trait for all `glam` types. This is primarily used for unit testing. @@ -94,7 +92,7 @@ performance. * Only single precision floating point (`f32`) arithmetic is supported * No traits or generics for simplicity of implementation and usage -* All dependencies are optional (e.g. approx, rand and serde) +* All dependencies are optional (e.g. `mint`, `rand` and `serde`) * Follows the [Rust API Guidelines] where possible * Aiming for 100% test [coverage][coveralls.io] * Common functionality is benchmarked using [Criterion.rs] diff --git a/src/f32/glam_approx.rs b/src/f32/glam_approx.rs deleted file mode 100644 index 4b395ace..00000000 --- a/src/f32/glam_approx.rs +++ /dev/null @@ -1,280 +0,0 @@ -use crate::f32::{Mat2, Mat3, Mat4, Quat, Vec2, Vec3, Vec4}; -use approx::{AbsDiffEq, RelativeEq, UlpsEq}; - -impl AbsDiffEq for Quat { - type Epsilon = ::Epsilon; - fn default_epsilon() -> Self::Epsilon { - f32::default_epsilon() - } - fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool { - let q1 = self.as_ref(); - let q2 = other.as_ref(); - q1.abs_diff_eq(q2, epsilon) - } -} - -impl RelativeEq for Quat { - fn default_max_relative() -> Self::Epsilon { - f32::default_max_relative() - } - fn relative_eq( - &self, - other: &Self, - epsilon: Self::Epsilon, - max_relative: Self::Epsilon, - ) -> bool { - let q1 = self.as_ref(); - let q2 = other.as_ref(); - q1.relative_eq(q2, epsilon, max_relative) - } -} - -impl UlpsEq for Quat { - fn default_max_ulps() -> u32 { - f32::default_max_ulps() - } - fn ulps_eq(&self, other: &Self, epsilon: Self::Epsilon, max_ulps: u32) -> bool { - let q1 = self.as_ref(); - let q2 = other.as_ref(); - q1.ulps_eq(q2, epsilon, max_ulps) - } -} - -impl AbsDiffEq for Vec2 { - type Epsilon = ::Epsilon; - fn default_epsilon() -> Self::Epsilon { - f32::default_epsilon() - } - fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool { - let v1 = self.as_ref(); - let v2 = other.as_ref(); - v1.abs_diff_eq(v2, epsilon) - } -} - -impl RelativeEq for Vec2 { - fn default_max_relative() -> Self::Epsilon { - f32::default_max_relative() - } - fn relative_eq( - &self, - other: &Self, - epsilon: Self::Epsilon, - max_relative: Self::Epsilon, - ) -> bool { - let v1 = self.as_ref(); - let v2 = other.as_ref(); - v1.relative_eq(v2, epsilon, max_relative) - } -} - -impl UlpsEq for Vec2 { - fn default_max_ulps() -> u32 { - f32::default_max_ulps() - } - fn ulps_eq(&self, other: &Self, epsilon: Self::Epsilon, max_ulps: u32) -> bool { - let v1 = self.as_ref(); - let v2 = other.as_ref(); - v1.ulps_eq(v2, epsilon, max_ulps) - } -} - -impl AbsDiffEq for Vec3 { - type Epsilon = ::Epsilon; - fn default_epsilon() -> Self::Epsilon { - f32::default_epsilon() - } - fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool { - let v1 = self.as_ref(); - let v2 = other.as_ref(); - v1.abs_diff_eq(v2, epsilon) - } -} - -impl RelativeEq for Vec3 { - fn default_max_relative() -> Self::Epsilon { - f32::default_max_relative() - } - fn relative_eq( - &self, - other: &Self, - epsilon: Self::Epsilon, - max_relative: Self::Epsilon, - ) -> bool { - let v1 = self.as_ref(); - let v2 = other.as_ref(); - v1.relative_eq(v2, epsilon, max_relative) - } -} - -impl UlpsEq for Vec3 { - fn default_max_ulps() -> u32 { - f32::default_max_ulps() - } - fn ulps_eq(&self, other: &Self, epsilon: Self::Epsilon, max_ulps: u32) -> bool { - let v1 = self.as_ref(); - let v2 = other.as_ref(); - v1.ulps_eq(v2, epsilon, max_ulps) - } -} - -impl AbsDiffEq for Vec4 { - type Epsilon = ::Epsilon; - fn default_epsilon() -> Self::Epsilon { - f32::default_epsilon() - } - fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool { - let v1 = self.as_ref(); - let v2 = other.as_ref(); - v1.abs_diff_eq(v2, epsilon) - } -} - -impl RelativeEq for Vec4 { - fn default_max_relative() -> Self::Epsilon { - f32::default_max_relative() - } - fn relative_eq( - &self, - other: &Self, - epsilon: Self::Epsilon, - max_relative: Self::Epsilon, - ) -> bool { - let v1 = self.as_ref(); - let v2 = other.as_ref(); - v1.relative_eq(v2, epsilon, max_relative) - } -} - -impl UlpsEq for Vec4 { - fn default_max_ulps() -> u32 { - f32::default_max_ulps() - } - fn ulps_eq(&self, other: &Self, epsilon: Self::Epsilon, max_ulps: u32) -> bool { - let v1 = self.as_ref(); - let v2 = other.as_ref(); - v1.ulps_eq(v2, epsilon, max_ulps) - } -} - -impl AbsDiffEq for Mat2 { - type Epsilon = ::Epsilon; - fn default_epsilon() -> Self::Epsilon { - f32::default_epsilon() - } - fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool { - let m1 = self.as_ref(); - let m2 = other.as_ref(); - m1.abs_diff_eq(m2, epsilon) - } -} - -impl RelativeEq for Mat2 { - fn default_max_relative() -> Self::Epsilon { - f32::default_max_relative() - } - fn relative_eq( - &self, - other: &Self, - epsilon: Self::Epsilon, - max_relative: Self::Epsilon, - ) -> bool { - let m1 = self.as_ref(); - let m2 = other.as_ref(); - m1.relative_eq(m2, epsilon, max_relative) - } -} - -impl UlpsEq for Mat2 { - fn default_max_ulps() -> u32 { - f32::default_max_ulps() - } - fn ulps_eq(&self, other: &Self, epsilon: Self::Epsilon, max_ulps: u32) -> bool { - let m1 = self.as_ref(); - let m2 = other.as_ref(); - m1.ulps_eq(m2, epsilon, max_ulps) - } -} - -impl AbsDiffEq for Mat3 { - type Epsilon = ::Epsilon; - fn default_epsilon() -> Self::Epsilon { - f32::default_epsilon() - } - fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool { - self.x_axis().abs_diff_eq(&other.x_axis(), epsilon) - && self.y_axis().abs_diff_eq(&other.y_axis(), epsilon) - && self.z_axis().abs_diff_eq(&other.z_axis(), epsilon) - } -} - -impl RelativeEq for Mat3 { - fn default_max_relative() -> Self::Epsilon { - f32::default_max_relative() - } - fn relative_eq( - &self, - other: &Self, - epsilon: Self::Epsilon, - max_relative: Self::Epsilon, - ) -> bool { - self.x_axis() - .relative_eq(&other.x_axis(), epsilon, max_relative) - && self - .y_axis() - .relative_eq(&other.y_axis(), epsilon, max_relative) - && self - .z_axis() - .relative_eq(&other.z_axis(), epsilon, max_relative) - } -} - -impl UlpsEq for Mat3 { - fn default_max_ulps() -> u32 { - f32::default_max_ulps() - } - fn ulps_eq(&self, other: &Self, epsilon: Self::Epsilon, max_ulps: u32) -> bool { - self.x_axis().ulps_eq(&other.x_axis(), epsilon, max_ulps) - && self.y_axis().ulps_eq(&other.y_axis(), epsilon, max_ulps) - && self.z_axis().ulps_eq(&other.z_axis(), epsilon, max_ulps) - } -} - -impl AbsDiffEq for Mat4 { - type Epsilon = ::Epsilon; - fn default_epsilon() -> Self::Epsilon { - f32::default_epsilon() - } - fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool { - let m1 = self.as_ref(); - let m2 = other.as_ref(); - m1.abs_diff_eq(m2, epsilon) - } -} - -impl RelativeEq for Mat4 { - fn default_max_relative() -> Self::Epsilon { - f32::default_max_relative() - } - fn relative_eq( - &self, - other: &Self, - epsilon: Self::Epsilon, - max_relative: Self::Epsilon, - ) -> bool { - let m1 = self.as_ref(); - let m2 = other.as_ref(); - m1.relative_eq(m2, epsilon, max_relative) - } -} - -impl UlpsEq for Mat4 { - fn default_max_ulps() -> u32 { - f32::default_max_ulps() - } - fn ulps_eq(&self, other: &Self, epsilon: Self::Epsilon, max_ulps: u32) -> bool { - let m1 = self.as_ref(); - let m2 = other.as_ref(); - m1.ulps_eq(m2, epsilon, max_ulps) - } -} diff --git a/src/f32/mat2.rs b/src/f32/mat2.rs index 75a0b1b4..040ac4b1 100644 --- a/src/f32/mat2.rs +++ b/src/f32/mat2.rs @@ -185,6 +185,20 @@ impl Mat2 { let s = Vec4::splat(rhs); Mat2(self.0 * s) } + + /// Returns true if the absolute difference of all elements between `self` + /// and `rhs` is less than or equal to `max_abs_diff`. + /// + /// This can be used to compare if two `Mat2`'s contain similar elements. It + /// works best when comparing with a known value. The `max_abs_diff` that + /// should be used used depends on the values being compared against. + /// + /// For more on floating point comparisons see + /// https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/ + #[inline] + pub fn abs_diff_eq(&self, rhs: Self, max_abs_diff: f32) -> bool { + self.0.abs_diff_eq(rhs.0, max_abs_diff) + } } #[cfg(feature = "rand")] diff --git a/src/f32/mat3.rs b/src/f32/mat3.rs index 66c73ac1..e66151ac 100644 --- a/src/f32/mat3.rs +++ b/src/f32/mat3.rs @@ -341,6 +341,22 @@ impl Mat3 { // TODO: optimise self.mul_vec3(rhs.extend(0.0)).truncate() } + + /// Returns true if the absolute difference of all elements between `self` + /// and `rhs` is less than or equal to `max_abs_diff`. + /// + /// This can be used to compare if two `Mat3`'s contain similar elements. It + /// works best when comparing with a known value. The `max_abs_diff` that + /// should be used used depends on the values being compared against. + /// + /// For more on floating point comparisons see + /// https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/ + #[inline] + pub fn abs_diff_eq(&self, rhs: Self, max_abs_diff: f32) -> bool { + self.x_axis.abs_diff_eq(rhs.x_axis, max_abs_diff) + && self.y_axis.abs_diff_eq(rhs.y_axis, max_abs_diff) + && self.z_axis.abs_diff_eq(rhs.z_axis, max_abs_diff) + } } #[cfg(feature = "rand")] diff --git a/src/f32/mat4.rs b/src/f32/mat4.rs index 9f797f87..abf3aa74 100644 --- a/src/f32/mat4.rs +++ b/src/f32/mat4.rs @@ -591,6 +591,23 @@ impl Mat4 { // rhs w = 0 res } + + /// Returns true if the absolute difference of all elements between `self` + /// and `rhs` is less than or equal to `max_abs_diff`. + /// + /// This can be used to compare if two `Mat4`'s contain similar elements. It + /// works best when comparing with a known value. The `max_abs_diff` that + /// should be used used depends on the values being compared against. + /// + /// For more on floating point comparisons see + /// https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/ + #[inline] + pub fn abs_diff_eq(&self, rhs: Self, max_abs_diff: f32) -> bool { + self.x_axis.abs_diff_eq(rhs.x_axis, max_abs_diff) + && self.y_axis.abs_diff_eq(rhs.y_axis, max_abs_diff) + && self.z_axis.abs_diff_eq(rhs.z_axis, max_abs_diff) + && self.w_axis.abs_diff_eq(rhs.w_axis, max_abs_diff) + } } #[cfg(feature = "rand")] diff --git a/src/f32/mod.rs b/src/f32/mod.rs index 67531e9c..61b6def3 100644 --- a/src/f32/mod.rs +++ b/src/f32/mod.rs @@ -38,11 +38,6 @@ pub use vec4_f32::*; #[cfg(all(target_feature = "sse2", not(feature = "scalar-math")))] pub use vec4_sse2::*; -#[cfg(feature = "approx")] -mod glam_approx; -#[cfg(feature = "approx")] -pub use glam_approx::*; - #[cfg(feature = "mint")] mod glam_mint; #[cfg(feature = "mint")] diff --git a/src/f32/quat.rs b/src/f32/quat.rs index ed89e92c..1933bf13 100644 --- a/src/f32/quat.rs +++ b/src/f32/quat.rs @@ -266,6 +266,20 @@ impl Quat { positive_w_angle < THRESHOLD_ANGLE } + /// Returns true if the absolute difference of all elements between `self` + /// and `rhs` is less than or equal to `max_abs_diff`. + /// + /// This can be used to compare if two `Quat`'s contain similar elements. It + /// works best when comparing with a known value. The `max_abs_diff` that + /// should be used used depends on the values being compared against. + /// + /// For more on floating point comparisons see + /// https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/ + #[inline] + pub fn abs_diff_eq(self, rhs: Self, max_abs_diff: f32) -> bool { + self.0.abs_diff_eq(rhs.0, max_abs_diff) + } + #[inline] pub fn lerp(self, end: Self, t: f32) -> Self { glam_assert!(self.is_normalized()); diff --git a/src/f32/vec2.rs b/src/f32/vec2.rs index 83c1e41f..5819c426 100644 --- a/src/f32/vec2.rs +++ b/src/f32/vec2.rs @@ -51,12 +51,26 @@ impl Vec2 { /// Returns whether the `Vec2` is normalized to length `1.0` or not. /// - /// Uses a precision threshold of `0.00001`. + /// Uses a precision threshold of `core::f32::EPSILON`. #[inline] pub fn is_normalized(self) -> bool { is_normalized!(self) } + /// Returns true if the absolute difference of all elements between `self` + /// and `rhs` is less than or equal to `max_abs_diff`. + /// + /// This can be used to compare if two `Vec2`'s contain similar elements. It + /// works best when comparing with a known value. The `max_abs_diff` that + /// should be used used depends on the values being compared against. + /// + /// For more on floating point comparisons see + /// https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/ + #[inline] + pub fn abs_diff_eq(self, rhs: Self, max_abs_diff: f32) -> bool { + abs_diff_eq!(self, rhs, max_abs_diff) + } + /// Creates a new `Vec2`. #[inline] pub fn new(x: f32, y: f32) -> Vec2 { @@ -296,7 +310,7 @@ impl Vec2 { Self(b.0 - (self.0 * a.0), b.1 - (self.1 * a.1)) } - /// Returns a new `Vec2` containing the absolute value of each component of the original + /// Returns a new `Vec2` containing the absolute value of each element of the original /// `Vec2`. #[inline] pub fn abs(self) -> Self { diff --git a/src/f32/vec3.rs b/src/f32/vec3.rs index 7e8ca2d4..bdb15a7f 100644 --- a/src/f32/vec3.rs +++ b/src/f32/vec3.rs @@ -37,11 +37,25 @@ impl Vec3 { /// Returns whether the `Vec3` is normalized to length `1.0` or not. /// - /// Uses a precision threshold of `0.00001`. + /// Uses a precision threshold of `core::f32::EPSILON`. #[inline] pub fn is_normalized(self) -> bool { is_normalized!(self) } + + /// Returns true if the absolute difference of all elements between `self` + /// and `rhs` is less than or equal to `max_abs_diff`. + /// + /// This can be used to compare if two `Vec3`'s contain similar elements. It + /// works best when comparing with a known value. The `max_abs_diff` that + /// should be used used depends on the values being compared against. + /// + /// For more on floating point comparisons see + /// https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/ + #[inline] + pub fn abs_diff_eq(self, rhs: Self, max_abs_diff: f32) -> bool { + abs_diff_eq!(self, rhs, max_abs_diff) + } } impl AsRef<[f32; 3]> for Vec3 { diff --git a/src/f32/vec3_sse2.rs b/src/f32/vec3_sse2.rs index caf6012d..26795471 100644 --- a/src/f32/vec3_sse2.rs +++ b/src/f32/vec3_sse2.rs @@ -360,7 +360,7 @@ impl Vec3 { unsafe { Self(_mm_sub_ps(b.0, _mm_mul_ps(self.0, a.0))) } } - /// Returns a new `Vec3` containing the absolute value of each component of the original + /// Returns a new `Vec3` containing the absolute value of each element of the original /// `Vec3`. #[inline] pub fn abs(self) -> Self { diff --git a/src/f32/vec4.rs b/src/f32/vec4.rs index ee2d6800..378e3a4a 100644 --- a/src/f32/vec4.rs +++ b/src/f32/vec4.rs @@ -37,11 +37,25 @@ impl Vec4 { /// Returns whether the `Vec4` is normalized to length `1.0` or not. /// - /// Uses a precision threshold of `0.00001`. + /// Uses a precision threshold of `core::f32::EPSILON`. #[inline] pub fn is_normalized(self) -> bool { is_normalized!(self) } + + /// Returns true if the absolute difference of all elements between `self` + /// and `rhs` is less than or equal to `max_abs_diff`. + /// + /// This can be used to compare if two `Vec4`'s contain similar elements. It + /// works best when comparing with a known value. The `max_abs_diff` that + /// should be used used depends on the values being compared against. + /// + /// For more on floating point comparisons see + /// https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/ + #[inline] + pub fn abs_diff_eq(self, rhs: Self, max_abs_diff: f32) -> bool { + abs_diff_eq!(self, rhs, max_abs_diff) + } } impl AsRef<[f32; 4]> for Vec4 { diff --git a/src/f32/vec4_sse2.rs b/src/f32/vec4_sse2.rs index 7c39d329..2213c37d 100644 --- a/src/f32/vec4_sse2.rs +++ b/src/f32/vec4_sse2.rs @@ -396,7 +396,7 @@ impl Vec4 { unsafe { Self(_mm_sub_ps(b.0, _mm_mul_ps(self.0, a.0))) } } - /// Returns a new `Vec4` containing the absolute value of each component of the original + /// Returns a new `Vec4` containing the absolute value of each element of the original /// `Vec4`. #[inline] pub fn abs(self) -> Self { diff --git a/src/lib.rs b/src/lib.rs index 5479c6d0..cb573f9d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -35,12 +35,11 @@ of rotation: with the left thumb pointing in the positive direction of the axis the left fingers curl around the axis in the direction of the rotation. ``` -use approx::assert_ulps_eq; use glam::{Mat3, Vec3}; // rotate +x 90 degrees clockwise around y giving -z let m = Mat3::from_rotation_y(90.0_f32.to_radians()); let v = m * Vec3::unit_x(); -assert_ulps_eq!(v, -Vec3::unit_z()); +assert!(v.abs_diff_eq(-Vec3::unit_z(), core::f32::EPSILON)); ``` ## Size and alignment of types @@ -111,9 +110,7 @@ assert_eq!(format!("{}", a), "(1, 2, 3)"); All `glam` dependencies are optional, however some are required for tests and benchmarks. -* `"std"` - the default feature, includes `"approx"` and `"rand"`. -* `"approx` - provides different methods of comparing floating point values. - Used in some unit tests. +* `"std"` - the default feature, has no dependencies. * `"rand"` - used to generate random values. Used in benchmarks. * `"serde"` - used for serialization and deserialization of types. * `"mint"` - used for interoperating with other linear algebra libraries. diff --git a/src/macros.rs b/src/macros.rs index 3fb9902c..e6b7ade9 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -8,10 +8,16 @@ macro_rules! glam_assert { } macro_rules! is_normalized { - ($self:expr, threshold => $threshold:expr) => { - ($self.length_squared() - 1.0).abs() < $threshold + ($self:expr, $max_diff:expr) => { + ($self.length_squared() - 1.0).abs() <= $max_diff }; ($self:expr) => { - is_normalized!($self, threshold => 0.00001) + is_normalized!($self, core::f32::EPSILON) + }; +} + +macro_rules! abs_diff_eq { + ($self:expr, $rhs:expr, $max_abs_diff:expr) => { + ($self - $rhs).abs().cmple(Self::splat($max_abs_diff)).all() }; } diff --git a/tests/mat2.rs b/tests/mat2.rs index 91fb8292..8882f66d 100644 --- a/tests/mat2.rs +++ b/tests/mat2.rs @@ -1,8 +1,7 @@ mod support; -use approx::assert_ulps_eq; use glam::f32::*; -use support::*; +use support::deg; const IDENTITY: [[f32; 2]; 2] = [[1.0, 0.0], [0.0, 1.0]]; @@ -62,29 +61,21 @@ fn test_mat2_from_axes() { fn test_mat2_mul() { let mat_a = Mat2::from_angle(deg(90.0)); let res_a = mat_a * Vec2::unit_y(); - assert_ulps_eq!(vec2(-1.0, 0.0), res_a); + assert_approx_eq!(vec2(-1.0, 0.0), res_a); let res_b = mat_a * Vec2::unit_x(); - assert_ulps_eq!(vec2(0.0, 1.0), res_b); + assert_approx_eq!(vec2(0.0, 1.0), res_b); } #[test] fn test_from_scale() { let m = Mat2::from_scale(Vec2::new(2.0, 4.0)); - assert_ulps_eq!(m * Vec2::new(1.0, 1.0), Vec2::new(2.0, 4.0)); - assert_ulps_eq!(Vec2::unit_x() * 2.0, m.x_axis()); - assert_ulps_eq!(Vec2::unit_y() * 4.0, m.y_axis()); + assert_approx_eq!(m * Vec2::new(1.0, 1.0), Vec2::new(2.0, 4.0)); + assert_approx_eq!(Vec2::unit_x() * 2.0, m.x_axis()); + assert_approx_eq!(Vec2::unit_y() * 4.0, m.y_axis()); let rot = Mat2::from_scale_angle(Vec2::new(4.0, 2.0), deg(180.0)); - assert_ulps_eq!( - Vec2::unit_x() * -4.0, - rot * Vec2::unit_x(), - epsilon = 1.0e-6 - ); - assert_ulps_eq!( - Vec2::unit_y() * -2.0, - rot * Vec2::unit_y(), - epsilon = 1.0e-6 - ); + assert_approx_eq!(Vec2::unit_x() * -4.0, rot * Vec2::unit_x(), 1.0e-6); + assert_approx_eq!(Vec2::unit_y() * -2.0, rot * Vec2::unit_y(), 1.0e-6); } #[test] @@ -108,23 +99,23 @@ fn test_mat2_det() { #[test] fn test_mat2_inverse() { let inv = Mat2::identity().inverse(); - assert_ulps_eq!(Mat2::identity(), inv); + assert_approx_eq!(Mat2::identity(), inv); let rot = Mat2::from_angle(deg(90.0)); let rot_inv = rot.inverse(); - assert_ulps_eq!(Mat2::identity(), rot * rot_inv); - assert_ulps_eq!(Mat2::identity(), rot_inv * rot); + assert_approx_eq!(Mat2::identity(), rot * rot_inv); + assert_approx_eq!(Mat2::identity(), rot_inv * rot); let scale = Mat2::from_scale(vec2(4.0, 5.0)); let scale_inv = scale.inverse(); - assert_ulps_eq!(Mat2::identity(), scale * scale_inv); - assert_ulps_eq!(Mat2::identity(), scale_inv * scale); + assert_approx_eq!(Mat2::identity(), scale * scale_inv); + assert_approx_eq!(Mat2::identity(), scale_inv * scale); let m = scale * rot; let m_inv = m.inverse(); - assert_ulps_eq!(Mat2::identity(), m * m_inv); - assert_ulps_eq!(Mat2::identity(), m_inv * m); - assert_ulps_eq!(m_inv, rot_inv * scale_inv); + assert_approx_eq!(Mat2::identity(), m * m_inv); + assert_approx_eq!(Mat2::identity(), m_inv * m); + assert_approx_eq!(m_inv, rot_inv * scale_inv); } #[test] @@ -140,11 +131,11 @@ fn test_mat2_ops() { ); assert_eq!(Mat2::from_cols_array_2d(&[[2.0, 4.0], [6.0, 8.0]]), m0 + m0); assert_eq!(Mat2::zero(), m0 - m0); - assert_ulps_eq!( + assert_approx_eq!( Mat2::from_cols_array_2d(&[[1.0, 2.0], [3.0, 4.0]]), m0 * Mat2::identity() ); - assert_ulps_eq!( + assert_approx_eq!( Mat2::from_cols_array_2d(&[[1.0, 2.0], [3.0, 4.0]]), Mat2::identity() * m0 ); diff --git a/tests/mat3.rs b/tests/mat3.rs index 115d9287..569309c3 100644 --- a/tests/mat3.rs +++ b/tests/mat3.rs @@ -1,6 +1,5 @@ mod support; -use approx::assert_ulps_eq; use glam::f32::*; use support::deg; @@ -73,20 +72,20 @@ fn test_mat3_from_axes() { fn test_from_rotation() { let rot_x1 = Mat3::from_rotation_x(deg(180.0)); let rot_x2 = Mat3::from_axis_angle(Vec3::unit_x(), deg(180.0)); - assert_ulps_eq!(rot_x1, rot_x2); + assert_approx_eq!(rot_x1, rot_x2); let rot_y1 = Mat3::from_rotation_y(deg(180.0)); let rot_y2 = Mat3::from_axis_angle(Vec3::unit_y(), deg(180.0)); - assert_ulps_eq!(rot_y1, rot_y2); + assert_approx_eq!(rot_y1, rot_y2); let rot_z1 = Mat3::from_rotation_z(deg(180.0)); let rot_z2 = Mat3::from_axis_angle(Vec3::unit_z(), deg(180.0)); - assert_ulps_eq!(rot_z1, rot_z2); + assert_approx_eq!(rot_z1, rot_z2); } #[test] fn test_mat3_mul() { let mat_a = Mat3::from_axis_angle(Vec3::unit_z(), deg(90.0)); let result3 = mat_a * Vec3::unit_y(); - assert_ulps_eq!(vec3(-1.0, 0.0, 0.0), result3); + assert_approx_eq!(vec3(-1.0, 0.0, 0.0), result3); } #[test] @@ -97,32 +96,32 @@ fn test_from_ypr() { let roll = deg(90.0); let y0 = Mat3::from_rotation_y(yaw); let y1 = Mat3::from_rotation_ypr(yaw, zero, zero); - assert_ulps_eq!(y0, y1); + assert_approx_eq!(y0, y1); let x0 = Mat3::from_rotation_x(pitch); let x1 = Mat3::from_rotation_ypr(zero, pitch, zero); - assert_ulps_eq!(x0, x1); + assert_approx_eq!(x0, x1); let z0 = Mat3::from_rotation_z(roll); let z1 = Mat3::from_rotation_ypr(zero, zero, roll); - assert_ulps_eq!(z0, z1); + assert_approx_eq!(z0, z1); let yx0 = y0 * x0; let yx1 = Mat3::from_rotation_ypr(yaw, pitch, zero); - assert_ulps_eq!(yx0, yx1); + assert_approx_eq!(yx0, yx1); let yxz0 = y0 * x0 * z0; let yxz1 = Mat3::from_rotation_ypr(yaw, pitch, roll); - assert_ulps_eq!(yxz0, yxz1); + assert_approx_eq!(yxz0, yxz1); } #[test] fn test_from_scale() { let m = Mat3::from_scale(Vec3::new(2.0, 4.0, 8.0)); - assert_ulps_eq!(m * Vec3::new(1.0, 1.0, 1.0), Vec3::new(2.0, 4.0, 8.0)); - assert_ulps_eq!(Vec3::unit_x() * 2.0, m.x_axis()); - assert_ulps_eq!(Vec3::unit_y() * 4.0, m.y_axis()); - assert_ulps_eq!(Vec3::unit_z() * 8.0, m.z_axis()); + assert_approx_eq!(m * Vec3::new(1.0, 1.0, 1.0), Vec3::new(2.0, 4.0, 8.0)); + assert_approx_eq!(Vec3::unit_x() * 2.0, m.x_axis()); + assert_approx_eq!(Vec3::unit_y() * 4.0, m.y_axis()); + assert_approx_eq!(Vec3::unit_z() * 8.0, m.z_axis()); } #[test] @@ -156,29 +155,29 @@ fn test_mat3_inverse() { // assert_eq!(None, Mat3::zero().inverse()); let inv = Mat3::identity().inverse(); // assert_ne!(None, inv); - assert_ulps_eq!(Mat3::identity(), inv); + assert_approx_eq!(Mat3::identity(), inv); let rotz = Mat3::from_rotation_z(deg(90.0)); let rotz_inv = rotz.inverse(); // assert_ne!(None, rotz_inv); // let rotz_inv = rotz_inv.unwrap(); - assert_ulps_eq!(Mat3::identity(), rotz * rotz_inv); - assert_ulps_eq!(Mat3::identity(), rotz_inv * rotz); + assert_approx_eq!(Mat3::identity(), rotz * rotz_inv); + assert_approx_eq!(Mat3::identity(), rotz_inv * rotz); let scale = Mat3::from_scale(vec3(4.0, 5.0, 6.0)); let scale_inv = scale.inverse(); // assert_ne!(None, scale_inv); // let scale_inv = scale_inv.unwrap(); - assert_ulps_eq!(Mat3::identity(), scale * scale_inv); - assert_ulps_eq!(Mat3::identity(), scale_inv * scale); + assert_approx_eq!(Mat3::identity(), scale * scale_inv); + assert_approx_eq!(Mat3::identity(), scale_inv * scale); let m = scale * rotz; let m_inv = m.inverse(); // assert_ne!(None, m_inv); // let m_inv = m_inv.unwrap(); - assert_ulps_eq!(Mat3::identity(), m * m_inv); - assert_ulps_eq!(Mat3::identity(), m_inv * m); - assert_ulps_eq!(m_inv, rotz_inv * scale_inv); + assert_approx_eq!(Mat3::identity(), m * m_inv); + assert_approx_eq!(Mat3::identity(), m_inv * m); + assert_approx_eq!(m_inv, rotz_inv * scale_inv); } #[test] @@ -189,8 +188,8 @@ fn test_mat3_ops() { assert_eq!(m0x2, 2.0 * m0); assert_eq!(m0x2, m0 + m0); assert_eq!(Mat3::zero(), m0 - m0); - assert_ulps_eq!(m0, m0 * Mat3::identity()); - assert_ulps_eq!(m0, Mat3::identity() * m0); + assert_approx_eq!(m0, m0 * Mat3::identity()); + assert_approx_eq!(m0, Mat3::identity() * m0); } #[cfg(feature = "serde")] diff --git a/tests/mat4.rs b/tests/mat4.rs index a2bb0c21..f3427d4c 100644 --- a/tests/mat4.rs +++ b/tests/mat4.rs @@ -1,6 +1,5 @@ mod support; -use approx::assert_ulps_eq; use glam::f32::*; use support::deg; @@ -105,24 +104,24 @@ fn test_mat4_translation() { fn test_from_rotation() { let rot_x1 = Mat4::from_rotation_x(deg(180.0)); let rot_x2 = Mat4::from_axis_angle(Vec3::unit_x(), deg(180.0)); - assert_ulps_eq!(rot_x1, rot_x2); + assert_approx_eq!(rot_x1, rot_x2); let rot_y1 = Mat4::from_rotation_y(deg(180.0)); let rot_y2 = Mat4::from_axis_angle(Vec3::unit_y(), deg(180.0)); - assert_ulps_eq!(rot_y1, rot_y2); + assert_approx_eq!(rot_y1, rot_y2); let rot_z1 = Mat4::from_rotation_z(deg(180.0)); let rot_z2 = Mat4::from_axis_angle(Vec3::unit_z(), deg(180.0)); - assert_ulps_eq!(rot_z1, rot_z2); + assert_approx_eq!(rot_z1, rot_z2); } #[test] fn test_mat4_mul() { let mat_a = Mat4::from_axis_angle(Vec3::unit_z(), deg(90.0)); let result3 = mat_a.transform_vector3(Vec3::unit_y()); - assert_ulps_eq!(vec3(-1.0, 0.0, 0.0), result3); - assert_ulps_eq!(result3, (mat_a * Vec3::unit_y().extend(0.0)).truncate()); + assert_approx_eq!(vec3(-1.0, 0.0, 0.0), result3); + assert_approx_eq!(result3, (mat_a * Vec3::unit_y().extend(0.0)).truncate()); let result4 = mat_a * Vec4::unit_y(); - assert_ulps_eq!(vec4(-1.0, 0.0, 0.0, 0.0), result4); - assert_ulps_eq!(result4, mat_a * Vec4::unit_y()); + assert_approx_eq!(vec4(-1.0, 0.0, 0.0, 0.0), result4); + assert_approx_eq!(result4, mat_a * Vec4::unit_y()); let mat_b = Mat4::from_scale_rotation_translation( Vec3::new(0.5, 1.5, 2.0), @@ -130,12 +129,12 @@ fn test_mat4_mul() { Vec3::new(1.0, 2.0, 3.0), ); let result3 = mat_b.transform_vector3(Vec3::unit_y()); - assert_ulps_eq!(vec3(0.0, 0.0, 1.5), result3, epsilon = 1.0e-6); - assert_ulps_eq!(result3, (mat_b * Vec3::unit_y().extend(0.0)).truncate()); + assert_approx_eq!(vec3(0.0, 0.0, 1.5), result3, 1.0e-6); + assert_approx_eq!(result3, (mat_b * Vec3::unit_y().extend(0.0)).truncate()); let result3 = mat_b.transform_point3(Vec3::unit_y()); - assert_ulps_eq!(vec3(1.0, 2.0, 4.5), result3, epsilon = 1.0e-6); - assert_ulps_eq!(result3, (mat_b * Vec3::unit_y().extend(1.0)).truncate()); + assert_approx_eq!(vec3(1.0, 2.0, 4.5), result3, 1.0e-6); + assert_approx_eq!(result3, (mat_b * Vec3::unit_y().extend(1.0)).truncate()); } #[test] @@ -146,36 +145,36 @@ fn test_from_ypr() { let roll = deg(90.0); let y0 = Mat4::from_rotation_y(yaw); let y1 = Mat4::from_rotation_ypr(yaw, zero, zero); - assert_ulps_eq!(y0, y1); + assert_approx_eq!(y0, y1); let x0 = Mat4::from_rotation_x(pitch); let x1 = Mat4::from_rotation_ypr(zero, pitch, zero); - assert_ulps_eq!(x0, x1); + assert_approx_eq!(x0, x1); let z0 = Mat4::from_rotation_z(roll); let z1 = Mat4::from_rotation_ypr(zero, zero, roll); - assert_ulps_eq!(z0, z1); + assert_approx_eq!(z0, z1); let yx0 = y0 * x0; let yx1 = Mat4::from_rotation_ypr(yaw, pitch, zero); - assert_ulps_eq!(yx0, yx1); + assert_approx_eq!(yx0, yx1); let yxz0 = y0 * x0 * z0; let yxz1 = Mat4::from_rotation_ypr(yaw, pitch, roll); - assert_ulps_eq!(yxz0, yxz1); + assert_approx_eq!(yxz0, yxz1); } #[test] fn test_from_scale() { let m = Mat4::from_scale(Vec3::new(2.0, 4.0, 8.0)); - assert_ulps_eq!( + assert_approx_eq!( m.transform_point3(Vec3::new(1.0, 1.0, 1.0)), Vec3::new(2.0, 4.0, 8.0) ); - assert_ulps_eq!(Vec4::unit_x() * 2.0, m.x_axis()); - assert_ulps_eq!(Vec4::unit_y() * 4.0, m.y_axis()); - assert_ulps_eq!(Vec4::unit_z() * 8.0, m.z_axis()); - assert_ulps_eq!(Vec4::unit_w(), m.w_axis()); + assert_approx_eq!(Vec4::unit_x() * 2.0, m.x_axis()); + assert_approx_eq!(Vec4::unit_y() * 4.0, m.y_axis()); + assert_approx_eq!(Vec4::unit_z() * 8.0, m.z_axis()); + assert_approx_eq!(Vec4::unit_w(), m.w_axis()); } #[test] @@ -211,36 +210,36 @@ fn test_mat4_inverse() { // assert_eq!(None, Mat4::zero().inverse()); let inv = Mat4::identity().inverse(); // assert_ne!(None, inv); - assert_ulps_eq!(Mat4::identity(), inv); + assert_approx_eq!(Mat4::identity(), inv); let rotz = Mat4::from_rotation_z(deg(90.0)); let rotz_inv = rotz.inverse(); // assert_ne!(None, rotz_inv); // let rotz_inv = rotz_inv.unwrap(); - assert_ulps_eq!(Mat4::identity(), rotz * rotz_inv); - assert_ulps_eq!(Mat4::identity(), rotz_inv * rotz); + assert_approx_eq!(Mat4::identity(), rotz * rotz_inv); + assert_approx_eq!(Mat4::identity(), rotz_inv * rotz); let trans = Mat4::from_translation(vec3(1.0, 2.0, 3.0)); let trans_inv = trans.inverse(); // assert_ne!(None, trans_inv); // let trans_inv = trans_inv.unwrap(); - assert_ulps_eq!(Mat4::identity(), trans * trans_inv); - assert_ulps_eq!(Mat4::identity(), trans_inv * trans); + assert_approx_eq!(Mat4::identity(), trans * trans_inv); + assert_approx_eq!(Mat4::identity(), trans_inv * trans); let scale = Mat4::from_scale(vec3(4.0, 5.0, 6.0)); let scale_inv = scale.inverse(); // assert_ne!(None, scale_inv); // let scale_inv = scale_inv.unwrap(); - assert_ulps_eq!(Mat4::identity(), scale * scale_inv); - assert_ulps_eq!(Mat4::identity(), scale_inv * scale); + assert_approx_eq!(Mat4::identity(), scale * scale_inv); + assert_approx_eq!(Mat4::identity(), scale_inv * scale); let m = scale * rotz * trans; let m_inv = m.inverse(); // assert_ne!(None, m_inv); // let m_inv = m_inv.unwrap(); - assert_ulps_eq!(Mat4::identity(), m * m_inv, epsilon = 1.0e-5); - assert_ulps_eq!(Mat4::identity(), m_inv * m, epsilon = 1.0e-5); - assert_ulps_eq!(m_inv, trans_inv * rotz_inv * scale_inv); + assert_approx_eq!(Mat4::identity(), m * m_inv, 1.0e-5); + assert_approx_eq!(Mat4::identity(), m_inv * m, 1.0e-5); + assert_approx_eq!(m_inv, trans_inv * rotz_inv * scale_inv, 1.0e-6); } #[test] @@ -251,8 +250,8 @@ fn test_mat4_look_at() { let lh = Mat4::look_at_lh(eye, center, up); let rh = Mat4::look_at_rh(eye, center, up); let point = Vec3::new(1.0, 0.0, 0.0); - assert_ulps_eq!(lh.transform_point3(point), Vec3::new(0.0, 1.0, 5.0)); - assert_ulps_eq!(rh.transform_point3(point), Vec3::new(0.0, 1.0, -5.0)); + assert_approx_eq!(lh.transform_point3(point), Vec3::new(0.0, 1.0, 5.0)); + assert_approx_eq!(rh.transform_point3(point), Vec3::new(0.0, 1.0, -5.0)); } #[test] @@ -268,8 +267,8 @@ fn test_mat4_ops() { assert_eq!(m0x2, 2.0 * m0); assert_eq!(m0x2, m0 + m0); assert_eq!(Mat4::zero(), m0 - m0); - assert_ulps_eq!(m0, m0 * Mat4::identity()); - assert_ulps_eq!(m0, Mat4::identity() * m0); + assert_approx_eq!(m0, m0 * Mat4::identity()); + assert_approx_eq!(m0, Mat4::identity() * m0); } #[cfg(feature = "serde")] diff --git a/tests/quat.rs b/tests/quat.rs index b01cff51..e2450358 100644 --- a/tests/quat.rs +++ b/tests/quat.rs @@ -1,6 +1,5 @@ mod support; -use approx::assert_ulps_eq; use glam::f32::{quat, Mat3, Mat4, Quat, Vec3, Vec4}; use support::{deg, rad}; @@ -23,53 +22,53 @@ fn test_quat_rotation() { let roll = deg(90.0); let y0 = Quat::from_rotation_y(yaw); let (axis, angle) = y0.to_axis_angle(); - assert_ulps_eq!(axis, Vec3::unit_y()); - assert_ulps_eq!(angle, yaw); + assert_approx_eq!(axis, Vec3::unit_y(), 1.0e-6); + assert_approx_eq!(angle, yaw); let y1 = Quat::from_rotation_ypr(yaw, zero, zero); - assert_ulps_eq!(y0, y1); + assert_approx_eq!(y0, y1); let y2 = Quat::from_axis_angle(Vec3::unit_y(), yaw); - assert_ulps_eq!(y0, y2); + assert_approx_eq!(y0, y2); let y3 = Quat::from_rotation_mat3(&Mat3::from_rotation_y(yaw)); - assert_ulps_eq!(y0, y3); + assert_approx_eq!(y0, y3); let y4 = Quat::from_rotation_mat3(&Mat3::from_quat(y0)); - assert_ulps_eq!(y0, y4); + assert_approx_eq!(y0, y4); let x0 = Quat::from_rotation_x(pitch); let (axis, angle) = x0.to_axis_angle(); - assert_ulps_eq!(axis, Vec3::unit_x()); - assert_ulps_eq!(angle, pitch); + assert_approx_eq!(axis, Vec3::unit_x()); + assert_approx_eq!(angle, pitch); let x1 = Quat::from_rotation_ypr(zero, pitch, zero); - assert_ulps_eq!(x0, x1); + assert_approx_eq!(x0, x1); let x2 = Quat::from_axis_angle(Vec3::unit_x(), pitch); - assert_ulps_eq!(x0, x2); + assert_approx_eq!(x0, x2); let x3 = Quat::from_rotation_mat4(&Mat4::from_rotation_x(deg(180.0))); - assert_ulps_eq!(Quat::from_rotation_x(deg(180.0)), x3); + assert_approx_eq!(Quat::from_rotation_x(deg(180.0)), x3); let z0 = Quat::from_rotation_z(roll); let (axis, angle) = z0.to_axis_angle(); - assert_ulps_eq!(axis, Vec3::unit_z()); - assert_ulps_eq!(angle, roll); + assert_approx_eq!(axis, Vec3::unit_z()); + assert_approx_eq!(angle, roll); let z1 = Quat::from_rotation_ypr(zero, zero, roll); - assert_ulps_eq!(z0, z1); + assert_approx_eq!(z0, z1); let z2 = Quat::from_axis_angle(Vec3::unit_z(), roll); - assert_ulps_eq!(z0, z2); + assert_approx_eq!(z0, z2); let z3 = Quat::from_rotation_mat4(&Mat4::from_rotation_z(roll)); - assert_ulps_eq!(z0, z3); + assert_approx_eq!(z0, z3); let yx0 = y0 * x0; let yx1 = Quat::from_rotation_ypr(yaw, pitch, zero); - assert_ulps_eq!(yx0, yx1); + assert_approx_eq!(yx0, yx1); let yxz0 = y0 * x0 * z0; let yxz1 = Quat::from_rotation_ypr(yaw, pitch, roll); - assert_ulps_eq!(yxz0, yxz1); + assert_approx_eq!(yxz0, yxz1); // use the conjugate of z0 to remove the rotation from yxz0 let yx2 = yxz0 * z0.conjugate(); - assert_ulps_eq!(yx0, yx2); + assert_approx_eq!(yx0, yx2); let yxz2 = Quat::from_rotation_mat4(&Mat4::from_quat(yxz0)); - assert_ulps_eq!(yxz0, yxz2); + assert_approx_eq!(yxz0, yxz2); // if near identity, just returns x axis and 0 rotation let (axis, angle) = Quat::identity().to_axis_angle(); @@ -99,64 +98,64 @@ fn test_quat_new() { #[test] fn test_quat_mul_vec() { let qrz = Quat::from_rotation_z(deg(90.0)); - assert_ulps_eq!(Vec3::unit_y(), qrz * Vec3::unit_x()); - assert_ulps_eq!(Vec3::unit_y(), -qrz * Vec3::unit_x()); - assert_ulps_eq!(-Vec3::unit_x(), qrz * Vec3::unit_y()); - assert_ulps_eq!(-Vec3::unit_x(), -qrz * Vec3::unit_y()); + assert_approx_eq!(Vec3::unit_y(), qrz * Vec3::unit_x()); + assert_approx_eq!(Vec3::unit_y(), -qrz * Vec3::unit_x()); + assert_approx_eq!(-Vec3::unit_x(), qrz * Vec3::unit_y()); + assert_approx_eq!(-Vec3::unit_x(), -qrz * Vec3::unit_y()); // check vec3 * mat3 is the same let mrz = Mat3::from_quat(qrz); - assert_ulps_eq!(Vec3::unit_y(), mrz * Vec3::unit_x()); - // assert_ulps_eq!(Vec3::unit_y(), -mrz * Vec3::unit_x()); - assert_ulps_eq!(-Vec3::unit_x(), mrz * Vec3::unit_y()); + assert_approx_eq!(Vec3::unit_y(), mrz * Vec3::unit_x()); + // assert_approx_eq!(Vec3::unit_y(), -mrz * Vec3::unit_x()); + assert_approx_eq!(-Vec3::unit_x(), mrz * Vec3::unit_y()); let qrx = Quat::from_rotation_x(deg(90.0)); - assert_ulps_eq!(Vec3::unit_x(), qrx * Vec3::unit_x()); - assert_ulps_eq!(Vec3::unit_x(), -qrx * Vec3::unit_x()); - assert_ulps_eq!(Vec3::unit_z(), qrx * Vec3::unit_y()); - assert_ulps_eq!(Vec3::unit_z(), -qrx * Vec3::unit_y()); + assert_approx_eq!(Vec3::unit_x(), qrx * Vec3::unit_x()); + assert_approx_eq!(Vec3::unit_x(), -qrx * Vec3::unit_x()); + assert_approx_eq!(Vec3::unit_z(), qrx * Vec3::unit_y()); + assert_approx_eq!(Vec3::unit_z(), -qrx * Vec3::unit_y()); // check vec3 * mat3 is the same let mrx = Mat3::from_quat(qrx); - assert_ulps_eq!(Vec3::unit_x(), mrx * Vec3::unit_x()); - assert_ulps_eq!(Vec3::unit_z(), mrx * Vec3::unit_y()); + assert_approx_eq!(Vec3::unit_x(), mrx * Vec3::unit_x()); + assert_approx_eq!(Vec3::unit_z(), mrx * Vec3::unit_y()); let qrxz = qrz * qrx; - assert_ulps_eq!(Vec3::unit_y(), qrxz * Vec3::unit_x()); - assert_ulps_eq!(Vec3::unit_z(), qrxz * Vec3::unit_y()); + assert_approx_eq!(Vec3::unit_y(), qrxz * Vec3::unit_x()); + assert_approx_eq!(Vec3::unit_z(), qrxz * Vec3::unit_y()); let mrxz = mrz * mrx; - assert_ulps_eq!(Vec3::unit_y(), mrxz * Vec3::unit_x()); - assert_ulps_eq!(Vec3::unit_z(), mrxz * Vec3::unit_y()); + assert_approx_eq!(Vec3::unit_y(), mrxz * Vec3::unit_x()); + assert_approx_eq!(Vec3::unit_z(), mrxz * Vec3::unit_y()); let qrzx = qrx * qrz; - assert_ulps_eq!(Vec3::unit_z(), qrzx * Vec3::unit_x()); - assert_ulps_eq!(-Vec3::unit_x(), qrzx * Vec3::unit_y()); + assert_approx_eq!(Vec3::unit_z(), qrzx * Vec3::unit_x()); + assert_approx_eq!(-Vec3::unit_x(), qrzx * Vec3::unit_y()); let mrzx = qrx * qrz; - assert_ulps_eq!(Vec3::unit_z(), mrzx * Vec3::unit_x()); - assert_ulps_eq!(-Vec3::unit_x(), mrzx * Vec3::unit_y()); + assert_approx_eq!(Vec3::unit_z(), mrzx * Vec3::unit_x()); + assert_approx_eq!(-Vec3::unit_x(), mrzx * Vec3::unit_y()); } #[test] fn test_quat_funcs() { let q0 = Quat::from_rotation_ypr(deg(45.0), deg(180.0), deg(90.0)); assert!(q0.is_normalized()); - assert_ulps_eq!(q0.length_squared(), 1.0); - assert_ulps_eq!(q0.length(), 1.0); - assert_ulps_eq!(q0.length_reciprocal(), 1.0); - assert_ulps_eq!(q0, q0.normalize()); + assert_approx_eq!(q0.length_squared(), 1.0); + assert_approx_eq!(q0.length(), 1.0); + assert_approx_eq!(q0.length_reciprocal(), 1.0); + assert_approx_eq!(q0, q0.normalize()); - assert_ulps_eq!(q0.dot(q0), 1.0); - assert_ulps_eq!(q0.dot(q0), 1.0); + assert_approx_eq!(q0.dot(q0), 1.0); + assert_approx_eq!(q0.dot(q0), 1.0); let q1 = Quat::from(Vec4::from(q0) * 2.0); assert!(!q1.is_normalized()); - assert_ulps_eq!(q1.length_squared(), 4.0); - assert_ulps_eq!(q1.length(), 2.0); - assert_ulps_eq!(q1.length_reciprocal(), 0.5); - assert_ulps_eq!(q0, q1.normalize()); - assert_ulps_eq!(q0.dot(q1), 2.0); + assert_approx_eq!(q1.length_squared(), 4.0, 1.0e-6); + assert_approx_eq!(q1.length(), 2.0); + assert_approx_eq!(q1.length_reciprocal(), 0.5); + assert_approx_eq!(q0, q1.normalize()); + assert_approx_eq!(q0.dot(q1), 2.0, 1.0e-6); } #[test] @@ -165,7 +164,7 @@ fn test_quat_lerp() { let q1 = Quat::from_rotation_y(deg(90.0)); assert_eq!(q0, q0.lerp(q1, 0.0)); assert_eq!(q1, q0.lerp(q1, 1.0)); - assert_ulps_eq!(Quat::from_rotation_y(deg(45.0)), q0.lerp(q1, 0.5)); + assert_approx_eq!(Quat::from_rotation_y(deg(45.0)), q0.lerp(q1, 0.5)); } #[test] diff --git a/tests/support/macros.rs b/tests/support/macros.rs new file mode 100644 index 00000000..7a105412 --- /dev/null +++ b/tests/support/macros.rs @@ -0,0 +1,31 @@ +#[macro_export] +macro_rules! assert_approx_eq { + ($a:expr, $b:expr) => {{ + use support::FloatCompare; + let eps = core::f32::EPSILON; + let (a, b) = (&$a, &$b); + assert!( + a.approx_eq(b, eps), + "assertion failed: `(left !== right)` \ + (left: `{:?}`, right: `{:?}`, expect diff: `{:?}`, real diff: `{:?}`)", + *a, + *b, + eps, + a.abs_diff(b) + ); + }}; + ($a:expr, $b:expr, $eps:expr) => {{ + use support::FloatCompare; + let (a, b) = (&$a, &$b); + let eps = $eps; + assert!( + a.approx_eq(b, $eps), + "assertion failed: `(left !== right)` \ + (left: `{:?}`, right: `{:?}`, expect diff: `{:?}`, real diff: `{:?}`)", + *a, + *b, + eps, + a.abs_diff(b) + ); + }}; +} diff --git a/tests/support/mod.rs b/tests/support/mod.rs index 62189de3..74a77451 100644 --- a/tests/support/mod.rs +++ b/tests/support/mod.rs @@ -1,3 +1,8 @@ +#[macro_use] +mod macros; + +use glam::{Mat2, Mat3, Mat4, Quat, Vec2, Vec3, Vec4}; + /// Helper function for migrating away from `glam::angle::deg`. #[allow(dead_code)] #[inline] @@ -11,3 +16,115 @@ pub fn deg(angle: f32) -> f32 { pub fn rad(angle: f32) -> f32 { angle } + +/// Trait used by the `assert_approx_eq` macro for floating point comparisons. +pub trait FloatCompare { + /// Return true if the absolute difference between `self` and `other` is + /// less then or equal to `max_abs_diff`. + fn approx_eq(&self, other: &Rhs, max_abs_diff: f32) -> bool; + /// Returns the absolute difference of `self` and `other` which is printed + /// if `assert_approx_eq` fails. + fn abs_diff(&self, other: &Rhs) -> Rhs; +} + +impl FloatCompare for f32 { + #[inline] + fn approx_eq(&self, other: &f32, max_abs_diff: f32) -> bool { + (self - other).abs() <= max_abs_diff + } + #[inline] + fn abs_diff(&self, other: &f32) -> f32 { + (self - other).abs() + } +} + +impl FloatCompare for Mat2 { + #[inline] + fn approx_eq(&self, other: &Mat2, max_abs_diff: f32) -> bool { + self.abs_diff_eq(*other, max_abs_diff) + } + #[inline] + fn abs_diff(&self, other: &Mat2) -> Mat2 { + Mat2::from_cols( + (self.x_axis() - other.x_axis()).abs(), + (self.y_axis() - other.y_axis()).abs(), + ) + } +} + +impl FloatCompare for Mat3 { + #[inline] + fn approx_eq(&self, other: &Mat3, max_abs_diff: f32) -> bool { + self.abs_diff_eq(*other, max_abs_diff) + } + #[inline] + fn abs_diff(&self, other: &Mat3) -> Mat3 { + Mat3::from_cols( + (self.x_axis() - other.x_axis()).abs(), + (self.y_axis() - other.y_axis()).abs(), + (self.z_axis() - other.z_axis()).abs(), + ) + } +} + +impl FloatCompare for Mat4 { + #[inline] + fn approx_eq(&self, other: &Mat4, max_abs_diff: f32) -> bool { + self.abs_diff_eq(*other, max_abs_diff) + } + #[inline] + fn abs_diff(&self, other: &Mat4) -> Mat4 { + Mat4::from_cols( + (self.x_axis() - other.x_axis()).abs(), + (self.y_axis() - other.y_axis()).abs(), + (self.z_axis() - other.z_axis()).abs(), + (self.w_axis() - other.w_axis()).abs(), + ) + } +} + +impl FloatCompare for Quat { + #[inline] + fn approx_eq(&self, other: &Quat, max_abs_diff: f32) -> bool { + self.abs_diff_eq(*other, max_abs_diff) + } + #[inline] + fn abs_diff(&self, other: &Quat) -> Quat { + let a: Vec4 = (*self).into(); + let b: Vec4 = (*other).into(); + (a - b).abs().into() + } +} + +impl FloatCompare for Vec2 { + #[inline] + fn approx_eq(&self, other: &Vec2, max_abs_diff: f32) -> bool { + self.abs_diff_eq(*other, max_abs_diff) + } + #[inline] + fn abs_diff(&self, other: &Vec2) -> Vec2 { + (*self - *other).abs() + } +} + +impl FloatCompare for Vec3 { + #[inline] + fn approx_eq(&self, other: &Vec3, max_abs_diff: f32) -> bool { + self.abs_diff_eq(*other, max_abs_diff) + } + #[inline] + fn abs_diff(&self, other: &Vec3) -> Vec3 { + (*self - *other).abs() + } +} + +impl FloatCompare for Vec4 { + #[inline] + fn approx_eq(&self, other: &Vec4, max_abs_diff: f32) -> bool { + self.abs_diff_eq(*other, max_abs_diff) + } + #[inline] + fn abs_diff(&self, other: &Vec4) -> Vec4 { + (*self - *other).abs() + } +} diff --git a/tests/transform.rs b/tests/transform.rs index 864f4475..caf1dd60 100644 --- a/tests/transform.rs +++ b/tests/transform.rs @@ -1,6 +1,5 @@ #[cfg(feature = "transform-types")] mod transform { - use approx::assert_ulps_eq; use glam::f32::*; #[test] @@ -48,11 +47,11 @@ mod transform { ); let v0 = Vec3::unit_y(); let v1 = tr * v0; - assert_ulps_eq!(v1, Vec3::unit_x() * 2.0); - assert_ulps_eq!(v1, tr * v0); + assert_approx_eq!(v1, Vec3::unit_x() * 2.0); + assert_approx_eq!(v1, tr * v0); let inv_tr = tr.inverse(); let v2 = inv_tr * v1; - assert_ulps_eq!(v0, v2); + assert_approx_eq!(v0, v2); assert_eq!(tr * TransformRT::identity(), tr); assert_eq!(tr * inv_tr, TransformRT::identity()); @@ -66,11 +65,11 @@ mod transform { let srt = TransformSRT::new(s, r, t); let v0 = Vec3::unit_x(); let v1 = srt * v0; - assert_ulps_eq!(v1, (r * (v0 * s)) + t); - assert_ulps_eq!(v1, srt * v0); + assert_approx_eq!(v1, (r * (v0 * s)) + t); + assert_approx_eq!(v1, srt * v0); let inv_srt = srt.inverse(); let v2 = inv_srt * v1; - assert_ulps_eq!(v0, v2); + assert_approx_eq!(v0, v2); assert_eq!(srt * TransformSRT::identity(), srt); assert_eq!(srt * inv_srt, TransformSRT::identity()); diff --git a/tests/vec2.rs b/tests/vec2.rs index 4351940a..817b1e31 100644 --- a/tests/vec2.rs +++ b/tests/vec2.rs @@ -305,13 +305,6 @@ fn test_vec2_rand() { assert_eq!(a, b.into()); } -#[test] -fn test_vec2_sign() { - assert_eq!(Vec2::zero().sign(), Vec2::one()); - assert_eq!(Vec2::one().sign(), Vec2::one()); - assert_eq!((-Vec2::one()).sign(), -Vec2::one()); -} - #[test] fn test_vec2_abs() { assert_eq!(Vec2::zero().abs(), Vec2::zero()); diff --git a/tests/vec3.rs b/tests/vec3.rs index b3d1572e..4c9df31a 100644 --- a/tests/vec3.rs +++ b/tests/vec3.rs @@ -1,4 +1,5 @@ -use approx::assert_ulps_eq; +mod support; + use glam::*; #[cfg(feature = "rand")] use rand::{Rng, SeedableRng}; @@ -112,7 +113,7 @@ fn test_vec3_funcs() { vec3(2.0, 3.0, 4.0).length_reciprocal() ); assert!(vec3(2.0, 3.0, 4.0).normalize().is_normalized()); - assert_ulps_eq!( + assert_approx_eq!( vec3(2.0, 3.0, 4.0) / (2.0_f32 * 2.0 + 3.0 * 3.0 + 4.0 * 4.0).sqrt(), vec3(2.0, 3.0, 4.0).normalize() ); @@ -346,13 +347,6 @@ fn test_vec3_rand() { assert_eq!(a, b.into()); } -#[test] -fn test_vec3_sign() { - assert_eq!(Vec3::zero().sign(), Vec3::one()); - assert_eq!(Vec3::one().sign(), Vec3::one()); - assert_eq!((-Vec3::one()).sign(), -Vec3::one()); -} - #[test] fn test_vec3_abs() { assert_eq!(Vec3::zero().abs(), Vec3::zero()); diff --git a/tests/vec4.rs b/tests/vec4.rs index 6f8285b3..24ef53ce 100644 --- a/tests/vec4.rs +++ b/tests/vec4.rs @@ -1,4 +1,5 @@ -use approx::assert_ulps_eq; +mod support; + use glam::*; #[cfg(feature = "rand")] use rand::{Rng, SeedableRng}; @@ -119,7 +120,7 @@ fn test_vec4_funcs() { vec4(2.0, 3.0, 4.0, 5.0).length_reciprocal() ); assert!(vec4(2.0, 3.0, 4.0, 5.0).normalize().is_normalized()); - assert_ulps_eq!( + assert_approx_eq!( vec4(2.0, 3.0, 4.0, 5.0) / (2.0_f32 * 2.0 + 3.0 * 3.0 + 4.0 * 4.0 + 5.0 * 5.0).sqrt(), vec4(2.0, 3.0, 4.0, 5.0).normalize() );