Impl TryFrom vector for directions and add InvalidDirectionError (#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,
}
```
This commit is contained in:
Joona Aalto 2023-12-06 02:05:37 +02:00 committed by GitHub
parent 5508915e9b
commit f683b802f1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 127 additions and 10 deletions

View file

@ -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
@ -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 {
@ -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(

View file

@ -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
@ -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 {
@ -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)
);
}
}

View file

@ -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 {