Skip to content

Commit

Permalink
Impl TryFrom vector for directions and add InvalidDirectionError (#…
Browse files Browse the repository at this point in the history
…10884)

# Objective

Implement `TryFrom<Vec2>`/`TryFrom<Vec3>` for direction primitives as
considered in #10857.

## Solution

Implement `TryFrom` for the direction primitives.

These are all equivalent:

```rust
let dir2d = Direction2d::try_from(Vec2::new(0.5, 0.5)).unwrap();
let dir2d = Vec2::new(0.5, 0.5).try_into().unwrap(); // (assumes that the type is inferred)
let dir2d = Direction2d::new(Vec2::new(0.5, 0.5)).unwrap();
```

For error cases, an `Err(InvalidDirectionError)` is returned. It
contains the type of failure:

```rust
/// An error indicating that a direction is invalid.
#[derive(Debug, PartialEq)]
pub enum InvalidDirectionError {
    /// The length of the direction vector is zero or very close to zero.
    Zero,
    /// The length of the direction vector is `std::f32::INFINITY`.
    Infinite,
    /// The length of the direction vector is `NaN`.
    NaN,
}
```
  • Loading branch information
Jondolf authored Dec 6, 2023
1 parent 5508915 commit f683b80
Show file tree
Hide file tree
Showing 3 changed files with 127 additions and 10 deletions.
56 changes: 51 additions & 5 deletions crates/bevy_math/src/primitives/dim2.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,30 @@
use super::{Primitive2d, WindingOrder};
use super::{InvalidDirectionError, Primitive2d, WindingOrder};
use crate::Vec2;

/// A normalized vector pointing in a direction in 2D space
#[derive(Clone, Copy, Debug)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Direction2d(Vec2);

impl Direction2d {
/// Create a direction from a finite, nonzero [`Vec2`].
///
/// Returns `None` if the input is zero (or very close to zero), or non-finite.
pub fn new(value: Vec2) -> Option<Self> {
value.try_normalize().map(Self)
/// Returns [`Err(InvalidDirectionError)`](InvalidDirectionError) if the length
/// of the given vector is zero (or very close to zero), infinite, or `NaN`.
pub fn new(value: Vec2) -> Result<Self, InvalidDirectionError> {
value.try_normalize().map(Self).map_or_else(
|| {
if value.is_nan() {
Err(InvalidDirectionError::NaN)
} else if !value.is_finite() {
// If the direction is non-finite but also not NaN, it must be infinite
Err(InvalidDirectionError::Infinite)
} else {
// If the direction is invalid but neither NaN nor infinite, it must be zero
Err(InvalidDirectionError::Zero)
}
},
Ok,
)
}

/// Create a direction from a [`Vec2`] that is already normalized
Expand All @@ -20,6 +34,14 @@ impl Direction2d {
}
}

impl TryFrom<Vec2> for Direction2d {
type Error = InvalidDirectionError;

fn try_from(value: Vec2) -> Result<Self, Self::Error> {
Self::new(value)
}
}

impl std::ops::Deref for Direction2d {
type Target = Vec2;
fn deref(&self) -> &Self::Target {
Expand Down Expand Up @@ -324,6 +346,30 @@ impl RegularPolygon {
mod tests {
use super::*;

#[test]
fn direction_creation() {
assert_eq!(
Direction2d::new(Vec2::X * 12.5),
Ok(Direction2d::from_normalized(Vec2::X))
);
assert_eq!(
Direction2d::new(Vec2::new(0.0, 0.0)),
Err(InvalidDirectionError::Zero)
);
assert_eq!(
Direction2d::new(Vec2::new(std::f32::INFINITY, 0.0)),
Err(InvalidDirectionError::Infinite)
);
assert_eq!(
Direction2d::new(Vec2::new(std::f32::NEG_INFINITY, 0.0)),
Err(InvalidDirectionError::Infinite)
);
assert_eq!(
Direction2d::new(Vec2::new(std::f32::NAN, 0.0)),
Err(InvalidDirectionError::NaN)
);
}

#[test]
fn triangle_winding_order() {
let mut cw_triangle = Triangle2d::new(
Expand Down
61 changes: 56 additions & 5 deletions crates/bevy_math/src/primitives/dim3.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,30 @@
use super::Primitive3d;
use super::{InvalidDirectionError, Primitive3d};
use crate::Vec3;

/// A normalized vector pointing in a direction in 3D space
#[derive(Clone, Copy, Debug)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Direction3d(Vec3);

impl Direction3d {
/// Create a direction from a finite, nonzero [`Vec3`].
///
/// Returns `None` if the input is zero (or very close to zero), or non-finite.
pub fn new(value: Vec3) -> Option<Self> {
value.try_normalize().map(Self)
/// Returns [`Err(InvalidDirectionError)`](InvalidDirectionError) if the length
/// of the given vector is zero (or very close to zero), infinite, or `NaN`.
pub fn new(value: Vec3) -> Result<Self, InvalidDirectionError> {
value.try_normalize().map(Self).map_or_else(
|| {
if value.is_nan() {
Err(InvalidDirectionError::NaN)
} else if !value.is_finite() {
// If the direction is non-finite but also not NaN, it must be infinite
Err(InvalidDirectionError::Infinite)
} else {
// If the direction is invalid but neither NaN nor infinite, it must be zero
Err(InvalidDirectionError::Zero)
}
},
Ok,
)
}

/// Create a direction from a [`Vec3`] that is already normalized
Expand All @@ -20,6 +34,14 @@ impl Direction3d {
}
}

impl TryFrom<Vec3> for Direction3d {
type Error = InvalidDirectionError;

fn try_from(value: Vec3) -> Result<Self, Self::Error> {
Self::new(value)
}
}

impl std::ops::Deref for Direction3d {
type Target = Vec3;
fn deref(&self) -> &Self::Target {
Expand Down Expand Up @@ -332,3 +354,32 @@ impl Torus {
}
}
}

#[cfg(test)]
mod test {
use super::*;

#[test]
fn direction_creation() {
assert_eq!(
Direction3d::new(Vec3::X * 12.5),
Ok(Direction3d::from_normalized(Vec3::X))
);
assert_eq!(
Direction3d::new(Vec3::new(0.0, 0.0, 0.0)),
Err(InvalidDirectionError::Zero)
);
assert_eq!(
Direction3d::new(Vec3::new(std::f32::INFINITY, 0.0, 0.0)),
Err(InvalidDirectionError::Infinite)
);
assert_eq!(
Direction3d::new(Vec3::new(std::f32::NEG_INFINITY, 0.0, 0.0)),
Err(InvalidDirectionError::Infinite)
);
assert_eq!(
Direction3d::new(Vec3::new(std::f32::NAN, 0.0, 0.0)),
Err(InvalidDirectionError::NaN)
);
}
}
20 changes: 20 additions & 0 deletions crates/bevy_math/src/primitives/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,26 @@ pub trait Primitive2d {}
/// A marker trait for 3D primitives
pub trait Primitive3d {}

/// An error indicating that a direction is invalid.
#[derive(Debug, PartialEq)]
pub enum InvalidDirectionError {
/// The length of the direction vector is zero or very close to zero.
Zero,
/// The length of the direction vector is `std::f32::INFINITY`.
Infinite,
/// The length of the direction vector is `NaN`.
NaN,
}

impl std::fmt::Display for InvalidDirectionError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Direction can not be zero (or very close to zero), or non-finite."
)
}
}

/// The winding order for a set of points
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum WindingOrder {
Expand Down

0 comments on commit f683b80

Please sign in to comment.