Skip to content

Commit

Permalink
Add try_normalize for all real vectors (#128)
Browse files Browse the repository at this point in the history
  • Loading branch information
emilk authored Feb 11, 2021
1 parent 9fdf0ed commit c22f6d2
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 3 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ The format is based on [Keep a Changelog], and this project adheres to
* Added `ZERO` constant for vectors and matrices.
* Added `clamp_length()`, `clamp_length_max()`, and `clamp_length_min` methods for `Vec2`, `Vec3`,
and `Vec4` for `f32` and `f64`.
* Added `try_normalize()` and `normalize_or_zero()` for all real vector types.

### Changed
* Deprecated `::unit_x/y/z()`, `::zero()`, `::one()`, `::identity()` functions in favor of constants.
Expand Down
23 changes: 21 additions & 2 deletions src/vec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -247,17 +247,36 @@ macro_rules! impl_vecn_float_methods {
/// Returns `self` normalized to length 1.0.
///
/// For valid results, `self` must _not_ be of length zero, nor very close to zero.
/// You can check the results with [`Self::is_finite`], or use [`Self::normalize_or_zero`].
///
/// See also [`Self::try_normalize`] and [`Self::normalize_or_zero`].
#[inline(always)]
pub fn normalize(self) -> Self {
Self($flttrait::normalize(self.0))
}

/// Returns `self` normalized to length 1.0 if possible, else returns `None`.
///
/// In particular, if the input is zero (or very close to zero), or non-finite,
/// the result of this operation will be `None`.
///
/// See also [`Self::normalize_or_zero`].
#[inline]
pub fn try_normalize(self) -> Option<Self> {
let rcp = self.length_recip();
if rcp.is_finite() && rcp > 0.0 {
Some(self * rcp)
} else {
None
}
}

/// Returns `self` normalized to length 1.0 if possible, else returns zero.
///
/// In particular, if the input is zero (or very close to zero), or non-finite,
/// the result of this operation will be zero.
#[inline(always)]
///
/// See also [`Self::try_normalize`].
#[inline]
pub fn normalize_or_zero(self) -> Self {
let rcp = self.length_recip();
if rcp.is_finite() && rcp > 0.0 {
Expand Down
29 changes: 28 additions & 1 deletion tests/support/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,36 @@ macro_rules! impl_vec_float_normalize_tests {
assert!(!from_x_y(NAN, 0.0).normalize().is_finite());
}

#[test]
fn test_try_normalize() {
assert_eq!(
from_x_y(-42.0, 0.0).try_normalize(),
Some(from_x_y(-1.0, 0.0))
);
assert_eq!(
from_x_y(MAX.sqrt(), 0.0).try_normalize(),
Some(from_x_y(1.0, 0.0))
);

// We expect `try_normalize` to return None when inputs are very small:
assert_eq!(from_x_y(0.0, 0.0).try_normalize(), None);
assert_eq!(from_x_y(MIN_POSITIVE, 0.0).try_normalize(), None);

// We expect `try_normalize` to return None when inputs are non-finite:
assert_eq!(from_x_y(INFINITY, 0.0).try_normalize(), None);
assert_eq!(from_x_y(NAN, 0.0).try_normalize(), None);

// We expect `try_normalize` to return None when inputs are very large:
assert_eq!(from_x_y(MAX, 0.0).try_normalize(), None);
assert_eq!(from_x_y(MAX, MAX).try_normalize(), None);
}

#[test]
fn test_normalize_or_zero() {
assert_eq!(from_x_y(-42.0, 0.0).normalize(), from_x_y(-1.0, 0.0));
assert_eq!(
from_x_y(-42.0, 0.0).normalize_or_zero(),
from_x_y(-1.0, 0.0)
);
assert_eq!(
from_x_y(MAX.sqrt(), 0.0).normalize_or_zero(),
from_x_y(1.0, 0.0)
Expand Down

0 comments on commit c22f6d2

Please sign in to comment.