From 9bd6cc0a5e8d269eb1fb6446ddd0824c446d966b Mon Sep 17 00:00:00 2001 From: Joona Aalto Date: Mon, 26 Feb 2024 15:57:49 +0200 Subject: [PATCH] Add `Direction3dA` and move direction types out of `primitives` (#12018) # Objective Split up from #12017, add an aligned version of `Direction3d` for SIMD, and move direction types out of `primitives`. ## Solution Add `Direction3dA` and move direction types into a new `direction` module. --- ## Migration Guide The `Direction2d`, `Direction3d`, and `InvalidDirectionError` types have been moved out of `bevy::math::primitives`. Before: ```rust use bevy::math::primitives::Direction3d; ``` After: ```rust use bevy::math::Direction3d; ``` --------- Co-authored-by: Alice Cecile --- crates/bevy_gizmos/src/circles.rs | 2 +- crates/bevy_gizmos/src/gizmos.rs | 2 +- crates/bevy_gizmos/src/primitives/dim2.rs | 6 +- crates/bevy_gizmos/src/primitives/dim3.rs | 6 +- .../src/bounding/bounded2d/primitive_impls.rs | 16 +- .../src/bounding/bounded3d/primitive_impls.rs | 12 +- crates/bevy_math/src/bounding/raycast2d.rs | 2 +- crates/bevy_math/src/bounding/raycast3d.rs | 2 +- crates/bevy_math/src/direction.rs | 532 ++++++++++++++++++ crates/bevy_math/src/lib.rs | 3 + crates/bevy_math/src/primitives/dim2.rs | 140 +---- crates/bevy_math/src/primitives/dim3.rs | 148 +---- crates/bevy_math/src/primitives/mod.rs | 35 -- crates/bevy_math/src/ray.rs | 4 +- .../bevy_reflect/src/impls/math/direction.rs | 22 + .../src/impls/math/primitives2d.rs | 11 +- .../src/impls/math/primitives3d.rs | 11 +- crates/bevy_reflect/src/lib.rs | 1 + crates/bevy_render/src/camera/camera.rs | 4 +- .../src/mesh/primitives/dim3/plane.rs | 5 +- .../src/components/transform.rs | 3 +- examples/3d/3d_viewport_to_world.rs | 2 +- 22 files changed, 598 insertions(+), 371 deletions(-) create mode 100644 crates/bevy_math/src/direction.rs create mode 100644 crates/bevy_reflect/src/impls/math/direction.rs diff --git a/crates/bevy_gizmos/src/circles.rs b/crates/bevy_gizmos/src/circles.rs index 380f818237..c1aedc24cf 100644 --- a/crates/bevy_gizmos/src/circles.rs +++ b/crates/bevy_gizmos/src/circles.rs @@ -5,7 +5,7 @@ use crate::prelude::{GizmoConfigGroup, Gizmos}; use bevy_math::Mat2; -use bevy_math::{primitives::Direction3d, Quat, Vec2, Vec3}; +use bevy_math::{Direction3d, Quat, Vec2, Vec3}; use bevy_render::color::LegacyColor; use std::f32::consts::TAU; diff --git a/crates/bevy_gizmos/src/gizmos.rs b/crates/bevy_gizmos/src/gizmos.rs index 33c7981c28..011477fe66 100644 --- a/crates/bevy_gizmos/src/gizmos.rs +++ b/crates/bevy_gizmos/src/gizmos.rs @@ -8,7 +8,7 @@ use bevy_ecs::{ system::{Deferred, ReadOnlySystemParam, Res, Resource, SystemBuffer, SystemMeta, SystemParam}, world::{unsafe_world_cell::UnsafeWorldCell, World}, }; -use bevy_math::{primitives::Direction3d, Mat2, Quat, Vec2, Vec3}; +use bevy_math::{Direction3d, Mat2, Quat, Vec2, Vec3}; use bevy_render::color::LegacyColor; use bevy_transform::TransformPoint; diff --git a/crates/bevy_gizmos/src/primitives/dim2.rs b/crates/bevy_gizmos/src/primitives/dim2.rs index c759a96044..a47e01dbd4 100644 --- a/crates/bevy_gizmos/src/primitives/dim2.rs +++ b/crates/bevy_gizmos/src/primitives/dim2.rs @@ -5,10 +5,10 @@ use std::f32::consts::PI; use super::helpers::*; use bevy_math::primitives::{ - BoxedPolygon, BoxedPolyline2d, Capsule2d, Circle, Direction2d, Ellipse, Line2d, Plane2d, - Polygon, Polyline2d, Primitive2d, Rectangle, RegularPolygon, Segment2d, Triangle2d, + BoxedPolygon, BoxedPolyline2d, Capsule2d, Circle, Ellipse, Line2d, Plane2d, Polygon, + Polyline2d, Primitive2d, Rectangle, RegularPolygon, Segment2d, Triangle2d, }; -use bevy_math::{Mat2, Vec2}; +use bevy_math::{Direction2d, Mat2, Vec2}; use bevy_render::color::LegacyColor; use crate::prelude::{GizmoConfigGroup, Gizmos}; diff --git a/crates/bevy_gizmos/src/primitives/dim3.rs b/crates/bevy_gizmos/src/primitives/dim3.rs index a417b32ef0..78d9f75897 100644 --- a/crates/bevy_gizmos/src/primitives/dim3.rs +++ b/crates/bevy_gizmos/src/primitives/dim3.rs @@ -4,10 +4,10 @@ use super::helpers::*; use std::f32::consts::TAU; use bevy_math::primitives::{ - BoxedPolyline3d, Capsule3d, Cone, ConicalFrustum, Cuboid, Cylinder, Direction3d, Line3d, - Plane3d, Polyline3d, Primitive3d, Segment3d, Sphere, Torus, + BoxedPolyline3d, Capsule3d, Cone, ConicalFrustum, Cuboid, Cylinder, Line3d, Plane3d, + Polyline3d, Primitive3d, Segment3d, Sphere, Torus, }; -use bevy_math::{Quat, Vec3}; +use bevy_math::{Direction3d, Quat, Vec3}; use bevy_render::color::LegacyColor; use crate::prelude::{GizmoConfigGroup, Gizmos}; diff --git a/crates/bevy_math/src/bounding/bounded2d/primitive_impls.rs b/crates/bevy_math/src/bounding/bounded2d/primitive_impls.rs index ee942e52dd..97141d3c7c 100644 --- a/crates/bevy_math/src/bounding/bounded2d/primitive_impls.rs +++ b/crates/bevy_math/src/bounding/bounded2d/primitive_impls.rs @@ -1,10 +1,11 @@ //! Contains [`Bounded2d`] implementations for [geometric primitives](crate::primitives). -use glam::{Mat2, Vec2}; - -use crate::primitives::{ - BoxedPolygon, BoxedPolyline2d, Capsule2d, Circle, Direction2d, Ellipse, Line2d, Plane2d, - Polygon, Polyline2d, Rectangle, RegularPolygon, Segment2d, Triangle2d, +use crate::{ + primitives::{ + BoxedPolygon, BoxedPolyline2d, Capsule2d, Circle, Ellipse, Line2d, Plane2d, Polygon, + Polyline2d, Rectangle, RegularPolygon, Segment2d, Triangle2d, + }, + Direction2d, Mat2, Vec2, }; use super::{Aabb2d, Bounded2d, BoundingCircle}; @@ -262,9 +263,10 @@ mod tests { use crate::{ bounding::Bounded2d, primitives::{ - Capsule2d, Circle, Direction2d, Ellipse, Line2d, Plane2d, Polygon, Polyline2d, - Rectangle, RegularPolygon, Segment2d, Triangle2d, + Capsule2d, Circle, Ellipse, Line2d, Plane2d, Polygon, Polyline2d, Rectangle, + RegularPolygon, Segment2d, Triangle2d, }, + Direction2d, }; #[test] diff --git a/crates/bevy_math/src/bounding/bounded3d/primitive_impls.rs b/crates/bevy_math/src/bounding/bounded3d/primitive_impls.rs index 6730254793..ee9ee4db37 100644 --- a/crates/bevy_math/src/bounding/bounded3d/primitive_impls.rs +++ b/crates/bevy_math/src/bounding/bounded3d/primitive_impls.rs @@ -1,13 +1,12 @@ //! Contains [`Bounded3d`] implementations for [geometric primitives](crate::primitives). -use glam::{Mat3, Quat, Vec2, Vec3}; - use crate::{ bounding::{Bounded2d, BoundingCircle}, primitives::{ - BoxedPolyline3d, Capsule3d, Cone, ConicalFrustum, Cuboid, Cylinder, Direction3d, Line3d, - Plane3d, Polyline3d, Segment3d, Sphere, Torus, Triangle2d, + BoxedPolyline3d, Capsule3d, Cone, ConicalFrustum, Cuboid, Cylinder, Line3d, Plane3d, + Polyline3d, Segment3d, Sphere, Torus, Triangle2d, }, + Direction3d, Mat3, Quat, Vec2, Vec3, }; use super::{Aabb3d, Bounded3d, BoundingSphere}; @@ -311,9 +310,10 @@ mod tests { use crate::{ bounding::Bounded3d, primitives::{ - Capsule3d, Cone, ConicalFrustum, Cuboid, Cylinder, Direction3d, Line3d, Plane3d, - Polyline3d, Segment3d, Sphere, Torus, + Capsule3d, Cone, ConicalFrustum, Cuboid, Cylinder, Line3d, Plane3d, Polyline3d, + Segment3d, Sphere, Torus, }, + Direction3d, }; #[test] diff --git a/crates/bevy_math/src/bounding/raycast2d.rs b/crates/bevy_math/src/bounding/raycast2d.rs index ca7bce52f5..93650d4a69 100644 --- a/crates/bevy_math/src/bounding/raycast2d.rs +++ b/crates/bevy_math/src/bounding/raycast2d.rs @@ -1,5 +1,5 @@ use super::{Aabb2d, BoundingCircle, IntersectsVolume}; -use crate::{primitives::Direction2d, Ray2d, Vec2}; +use crate::{Direction2d, Ray2d, Vec2}; /// A raycast intersection test for 2D bounding volumes #[derive(Clone, Debug)] diff --git a/crates/bevy_math/src/bounding/raycast3d.rs b/crates/bevy_math/src/bounding/raycast3d.rs index 377d4646ee..1c3427295b 100644 --- a/crates/bevy_math/src/bounding/raycast3d.rs +++ b/crates/bevy_math/src/bounding/raycast3d.rs @@ -1,5 +1,5 @@ use super::{Aabb3d, BoundingSphere, IntersectsVolume}; -use crate::{primitives::Direction3d, Ray3d, Vec3}; +use crate::{Direction3d, Ray3d, Vec3}; /// A raycast intersection test for 3D bounding volumes #[derive(Clone, Debug)] diff --git a/crates/bevy_math/src/direction.rs b/crates/bevy_math/src/direction.rs new file mode 100644 index 0000000000..873735e920 --- /dev/null +++ b/crates/bevy_math/src/direction.rs @@ -0,0 +1,532 @@ +use crate::{ + primitives::{Primitive2d, Primitive3d}, + Quat, Vec2, Vec3, Vec3A, +}; + +/// 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 InvalidDirectionError { + /// Creates an [`InvalidDirectionError`] from the length of an invalid direction vector. + pub fn from_length(length: f32) -> Self { + if length.is_nan() { + InvalidDirectionError::NaN + } else if !length.is_finite() { + // If the direction is non-finite but also not NaN, it must be infinite + InvalidDirectionError::Infinite + } else { + // If the direction is invalid but neither NaN nor infinite, it must be zero + InvalidDirectionError::Zero + } + } +} + +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." + ) + } +} + +/// A normalized vector pointing in a direction in 2D space +#[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] +pub struct Direction2d(Vec2); +impl Primitive2d for Direction2d {} + +impl Direction2d { + /// A unit vector pointing along the positive X axis. + pub const X: Self = Self(Vec2::X); + /// A unit vector pointing along the positive Y axis. + pub const Y: Self = Self(Vec2::Y); + /// A unit vector pointing along the negative X axis. + pub const NEG_X: Self = Self(Vec2::NEG_X); + /// A unit vector pointing along the negative Y axis. + pub const NEG_Y: Self = Self(Vec2::NEG_Y); + + /// Create a direction from a finite, nonzero [`Vec2`]. + /// + /// 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::new_and_length(value).map(|(dir, _)| dir) + } + + /// Create a [`Direction2d`] from a [`Vec2`] that is already normalized. + /// + /// # Warning + /// + /// `value` must be normalized, i.e it's length must be `1.0`. + pub fn new_unchecked(value: Vec2) -> Self { + debug_assert!(value.is_normalized()); + + Self(value) + } + + /// Create a direction from a finite, nonzero [`Vec2`], also returning its original length. + /// + /// Returns [`Err(InvalidDirectionError)`](InvalidDirectionError) if the length + /// of the given vector is zero (or very close to zero), infinite, or `NaN`. + pub fn new_and_length(value: Vec2) -> Result<(Self, f32), InvalidDirectionError> { + let length = value.length(); + let direction = (length.is_finite() && length > 0.0).then_some(value / length); + + direction + .map(|dir| (Self(dir), length)) + .ok_or(InvalidDirectionError::from_length(length)) + } + + /// Create a direction from its `x` and `y` components. + /// + /// Returns [`Err(InvalidDirectionError)`](InvalidDirectionError) if the length + /// of the vector formed by the components is zero (or very close to zero), infinite, or `NaN`. + pub fn from_xy(x: f32, y: f32) -> Result { + Self::new(Vec2::new(x, y)) + } +} + +impl TryFrom for Direction2d { + type Error = InvalidDirectionError; + + fn try_from(value: Vec2) -> Result { + Self::new(value) + } +} + +impl std::ops::Deref for Direction2d { + type Target = Vec2; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl std::ops::Neg for Direction2d { + type Output = Self; + fn neg(self) -> Self::Output { + Self(-self.0) + } +} + +#[cfg(feature = "approx")] +impl approx::AbsDiffEq for Direction2d { + type Epsilon = f32; + fn default_epsilon() -> f32 { + f32::EPSILON + } + fn abs_diff_eq(&self, other: &Self, epsilon: f32) -> bool { + self.as_ref().abs_diff_eq(other.as_ref(), epsilon) + } +} + +#[cfg(feature = "approx")] +impl approx::RelativeEq for Direction2d { + fn default_max_relative() -> f32 { + f32::EPSILON + } + fn relative_eq(&self, other: &Self, epsilon: f32, max_relative: f32) -> bool { + self.as_ref() + .relative_eq(other.as_ref(), epsilon, max_relative) + } +} + +#[cfg(feature = "approx")] +impl approx::UlpsEq for Direction2d { + fn default_max_ulps() -> u32 { + 4 + } + fn ulps_eq(&self, other: &Self, epsilon: f32, max_ulps: u32) -> bool { + self.as_ref().ulps_eq(other.as_ref(), epsilon, max_ulps) + } +} + +/// A normalized vector pointing in a direction in 3D space +#[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] +pub struct Direction3d(Vec3); +impl Primitive3d for Direction3d {} + +impl Direction3d { + /// A unit vector pointing along the positive X axis. + pub const X: Self = Self(Vec3::X); + /// A unit vector pointing along the positive Y axis. + pub const Y: Self = Self(Vec3::Y); + /// A unit vector pointing along the positive Z axis. + pub const Z: Self = Self(Vec3::Z); + /// A unit vector pointing along the negative X axis. + pub const NEG_X: Self = Self(Vec3::NEG_X); + /// A unit vector pointing along the negative Y axis. + pub const NEG_Y: Self = Self(Vec3::NEG_Y); + /// A unit vector pointing along the negative Z axis. + pub const NEG_Z: Self = Self(Vec3::NEG_Z); + + /// Create a direction from a finite, nonzero [`Vec3`]. + /// + /// 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::new_and_length(value).map(|(dir, _)| dir) + } + + /// Create a [`Direction3d`] from a [`Vec3`] that is already normalized. + /// + /// # Warning + /// + /// `value` must be normalized, i.e it's length must be `1.0`. + pub fn new_unchecked(value: Vec3) -> Self { + debug_assert!(value.is_normalized()); + + Self(value) + } + + /// Create a direction from a finite, nonzero [`Vec3`], also returning its original length. + /// + /// Returns [`Err(InvalidDirectionError)`](InvalidDirectionError) if the length + /// of the given vector is zero (or very close to zero), infinite, or `NaN`. + pub fn new_and_length(value: Vec3) -> Result<(Self, f32), InvalidDirectionError> { + let length = value.length(); + let direction = (length.is_finite() && length > 0.0).then_some(value / length); + + direction + .map(|dir| (Self(dir), length)) + .ok_or(InvalidDirectionError::from_length(length)) + } + + /// Create a direction from its `x`, `y`, and `z` components. + /// + /// Returns [`Err(InvalidDirectionError)`](InvalidDirectionError) if the length + /// of the vector formed by the components is zero (or very close to zero), infinite, or `NaN`. + pub fn from_xyz(x: f32, y: f32, z: f32) -> Result { + Self::new(Vec3::new(x, y, z)) + } +} + +impl TryFrom for Direction3d { + type Error = InvalidDirectionError; + + fn try_from(value: Vec3) -> Result { + Self::new(value) + } +} + +impl From for Vec3 { + fn from(value: Direction3d) -> Self { + value.0 + } +} + +impl std::ops::Deref for Direction3d { + type Target = Vec3; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl std::ops::Neg for Direction3d { + type Output = Self; + fn neg(self) -> Self::Output { + Self(-self.0) + } +} + +impl std::ops::Mul for Direction3d { + type Output = Vec3; + fn mul(self, rhs: f32) -> Self::Output { + self.0 * rhs + } +} + +impl std::ops::Mul for Quat { + type Output = Direction3d; + + /// Rotates the [`Direction3d`] using a [`Quat`]. + fn mul(self, direction: Direction3d) -> Self::Output { + let rotated = self * *direction; + + // Make sure the result is normalized. + // This can fail for non-unit quaternions. + debug_assert!(rotated.is_normalized()); + + Direction3d::new_unchecked(rotated) + } +} + +#[cfg(feature = "approx")] +impl approx::AbsDiffEq for Direction3d { + type Epsilon = f32; + fn default_epsilon() -> f32 { + f32::EPSILON + } + fn abs_diff_eq(&self, other: &Self, epsilon: f32) -> bool { + self.as_ref().abs_diff_eq(other.as_ref(), epsilon) + } +} + +#[cfg(feature = "approx")] +impl approx::RelativeEq for Direction3d { + fn default_max_relative() -> f32 { + f32::EPSILON + } + fn relative_eq(&self, other: &Self, epsilon: f32, max_relative: f32) -> bool { + self.as_ref() + .relative_eq(other.as_ref(), epsilon, max_relative) + } +} + +#[cfg(feature = "approx")] +impl approx::UlpsEq for Direction3d { + fn default_max_ulps() -> u32 { + 4 + } + fn ulps_eq(&self, other: &Self, epsilon: f32, max_ulps: u32) -> bool { + self.as_ref().ulps_eq(other.as_ref(), epsilon, max_ulps) + } +} + +/// A normalized SIMD vector pointing in a direction in 3D space. +/// +/// This type stores a 16 byte aligned [`Vec3A`]. +/// This may or may not be faster than [`Direction3d`]: make sure to benchmark! +#[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] +pub struct Direction3dA(Vec3A); +impl Primitive3d for Direction3dA {} + +impl Direction3dA { + /// A unit vector pointing along the positive X axis. + pub const X: Self = Self(Vec3A::X); + /// A unit vector pointing along the positive Y axis. + pub const Y: Self = Self(Vec3A::Y); + /// A unit vector pointing along the positive Z axis. + pub const Z: Self = Self(Vec3A::Z); + /// A unit vector pointing along the negative X axis. + pub const NEG_X: Self = Self(Vec3A::NEG_X); + /// A unit vector pointing along the negative Y axis. + pub const NEG_Y: Self = Self(Vec3A::NEG_Y); + /// A unit vector pointing along the negative Z axis. + pub const NEG_Z: Self = Self(Vec3A::NEG_Z); + + /// Create a direction from a finite, nonzero [`Vec3A`]. + /// + /// 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: Vec3A) -> Result { + Self::new_and_length(value).map(|(dir, _)| dir) + } + + /// Create a [`Direction3dA`] from a [`Vec3A`] that is already normalized. + /// + /// # Warning + /// + /// `value` must be normalized, i.e it's length must be `1.0`. + pub fn new_unchecked(value: Vec3A) -> Self { + debug_assert!(value.is_normalized()); + + Self(value) + } + + /// Create a direction from a finite, nonzero [`Vec3A`], also returning its original length. + /// + /// Returns [`Err(InvalidDirectionError)`](InvalidDirectionError) if the length + /// of the given vector is zero (or very close to zero), infinite, or `NaN`. + pub fn new_and_length(value: Vec3A) -> Result<(Self, f32), InvalidDirectionError> { + let length = value.length(); + let direction = (length.is_finite() && length > 0.0).then_some(value / length); + + direction + .map(|dir| (Self(dir), length)) + .ok_or(InvalidDirectionError::from_length(length)) + } + + /// Create a direction from its `x`, `y`, and `z` components. + /// + /// Returns [`Err(InvalidDirectionError)`](InvalidDirectionError) if the length + /// of the vector formed by the components is zero (or very close to zero), infinite, or `NaN`. + pub fn from_xyz(x: f32, y: f32, z: f32) -> Result { + Self::new(Vec3A::new(x, y, z)) + } +} + +impl TryFrom for Direction3dA { + type Error = InvalidDirectionError; + + fn try_from(value: Vec3A) -> Result { + Self::new(value) + } +} + +impl From for Vec3A { + fn from(value: Direction3dA) -> Self { + value.0 + } +} + +impl std::ops::Deref for Direction3dA { + type Target = Vec3A; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl std::ops::Neg for Direction3dA { + type Output = Self; + fn neg(self) -> Self::Output { + Self(-self.0) + } +} + +impl std::ops::Mul for Direction3dA { + type Output = Vec3A; + fn mul(self, rhs: f32) -> Self::Output { + self.0 * rhs + } +} + +impl std::ops::Mul for Quat { + type Output = Direction3dA; + + /// Rotates the [`Direction3dA`] using a [`Quat`]. + fn mul(self, direction: Direction3dA) -> Self::Output { + let rotated = self * *direction; + + // Make sure the result is normalized. + // This can fail for non-unit quaternions. + debug_assert!(rotated.is_normalized()); + + Direction3dA::new_unchecked(rotated) + } +} + +#[cfg(feature = "approx")] +impl approx::AbsDiffEq for Direction3dA { + type Epsilon = f32; + fn default_epsilon() -> f32 { + f32::EPSILON + } + fn abs_diff_eq(&self, other: &Self, epsilon: f32) -> bool { + self.as_ref().abs_diff_eq(other.as_ref(), epsilon) + } +} + +#[cfg(feature = "approx")] +impl approx::RelativeEq for Direction3dA { + fn default_max_relative() -> f32 { + f32::EPSILON + } + fn relative_eq(&self, other: &Self, epsilon: f32, max_relative: f32) -> bool { + self.as_ref() + .relative_eq(other.as_ref(), epsilon, max_relative) + } +} + +#[cfg(feature = "approx")] +impl approx::UlpsEq for Direction3dA { + fn default_max_ulps() -> u32 { + 4 + } + fn ulps_eq(&self, other: &Self, epsilon: f32, max_ulps: u32) -> bool { + self.as_ref().ulps_eq(other.as_ref(), epsilon, max_ulps) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::InvalidDirectionError; + + #[test] + fn dir2_creation() { + assert_eq!(Direction2d::new(Vec2::X * 12.5), Ok(Direction2d::X)); + assert_eq!( + Direction2d::new(Vec2::new(0.0, 0.0)), + Err(InvalidDirectionError::Zero) + ); + assert_eq!( + Direction2d::new(Vec2::new(f32::INFINITY, 0.0)), + Err(InvalidDirectionError::Infinite) + ); + assert_eq!( + Direction2d::new(Vec2::new(f32::NEG_INFINITY, 0.0)), + Err(InvalidDirectionError::Infinite) + ); + assert_eq!( + Direction2d::new(Vec2::new(f32::NAN, 0.0)), + Err(InvalidDirectionError::NaN) + ); + assert_eq!( + Direction2d::new_and_length(Vec2::X * 6.5), + Ok((Direction2d::X, 6.5)) + ); + } + + #[test] + fn dir3_creation() { + assert_eq!(Direction3d::new(Vec3::X * 12.5), Ok(Direction3d::X)); + assert_eq!( + Direction3d::new(Vec3::new(0.0, 0.0, 0.0)), + Err(InvalidDirectionError::Zero) + ); + assert_eq!( + Direction3d::new(Vec3::new(f32::INFINITY, 0.0, 0.0)), + Err(InvalidDirectionError::Infinite) + ); + assert_eq!( + Direction3d::new(Vec3::new(f32::NEG_INFINITY, 0.0, 0.0)), + Err(InvalidDirectionError::Infinite) + ); + assert_eq!( + Direction3d::new(Vec3::new(f32::NAN, 0.0, 0.0)), + Err(InvalidDirectionError::NaN) + ); + assert_eq!( + Direction3d::new_and_length(Vec3::X * 6.5), + Ok((Direction3d::X, 6.5)) + ); + + // Test rotation + assert!( + (Quat::from_rotation_z(std::f32::consts::FRAC_PI_2) * Direction3d::X) + .abs_diff_eq(Vec3::Y, 10e-6) + ); + } + + #[test] + fn dir3a_creation() { + assert_eq!(Direction3dA::new(Vec3A::X * 12.5), Ok(Direction3dA::X)); + assert_eq!( + Direction3dA::new(Vec3A::new(0.0, 0.0, 0.0)), + Err(InvalidDirectionError::Zero) + ); + assert_eq!( + Direction3dA::new(Vec3A::new(f32::INFINITY, 0.0, 0.0)), + Err(InvalidDirectionError::Infinite) + ); + assert_eq!( + Direction3dA::new(Vec3A::new(f32::NEG_INFINITY, 0.0, 0.0)), + Err(InvalidDirectionError::Infinite) + ); + assert_eq!( + Direction3dA::new(Vec3A::new(f32::NAN, 0.0, 0.0)), + Err(InvalidDirectionError::NaN) + ); + assert_eq!( + Direction3dA::new_and_length(Vec3A::X * 6.5), + Ok((Direction3dA::X, 6.5)) + ); + + // Test rotation + assert!( + (Quat::from_rotation_z(std::f32::consts::FRAC_PI_2) * Direction3dA::X) + .abs_diff_eq(Vec3A::Y, 10e-6) + ); + } +} diff --git a/crates/bevy_math/src/lib.rs b/crates/bevy_math/src/lib.rs index d4ac8b6158..451d2a2648 100644 --- a/crates/bevy_math/src/lib.rs +++ b/crates/bevy_math/src/lib.rs @@ -8,12 +8,14 @@ mod affine3; mod aspect_ratio; pub mod bounding; pub mod cubic_splines; +mod direction; pub mod primitives; mod ray; mod rects; pub use affine3::*; pub use aspect_ratio::AspectRatio; +pub use direction::*; pub use ray::{Ray2d, Ray3d}; pub use rects::*; @@ -25,6 +27,7 @@ pub mod prelude { CubicBSpline, CubicBezier, CubicCardinalSpline, CubicGenerator, CubicHermite, CubicSegment, }, + direction::{Direction2d, Direction3d, Direction3dA}, primitives::*, BVec2, BVec3, BVec4, EulerRot, FloatExt, IRect, IVec2, IVec3, IVec4, Mat2, Mat3, Mat4, Quat, Ray2d, Ray3d, Rect, URect, UVec2, UVec3, UVec4, Vec2, Vec2Swizzles, Vec3, diff --git a/crates/bevy_math/src/primitives/dim2.rs b/crates/bevy_math/src/primitives/dim2.rs index 09eb970200..3ec6c126f5 100644 --- a/crates/bevy_math/src/primitives/dim2.rs +++ b/crates/bevy_math/src/primitives/dim2.rs @@ -1,118 +1,7 @@ use std::f32::consts::PI; -use super::{InvalidDirectionError, Primitive2d, WindingOrder}; -use crate::Vec2; - -/// A normalized vector pointing in a direction in 2D space -#[derive(Clone, Copy, Debug, PartialEq)] -#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] -pub struct Direction2d(Vec2); -impl Primitive2d for Direction2d {} - -impl Direction2d { - /// A unit vector pointing along the positive X axis. - pub const X: Self = Self(Vec2::X); - /// A unit vector pointing along the positive Y axis. - pub const Y: Self = Self(Vec2::Y); - /// A unit vector pointing along the negative X axis. - pub const NEG_X: Self = Self(Vec2::NEG_X); - /// A unit vector pointing along the negative Y axis. - pub const NEG_Y: Self = Self(Vec2::NEG_Y); - - /// Create a direction from a finite, nonzero [`Vec2`]. - /// - /// 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::new_and_length(value).map(|(dir, _)| dir) - } - - /// Create a [`Direction2d`] from a [`Vec2`] that is already normalized. - /// - /// # Warning - /// - /// `value` must be normalized, i.e it's length must be `1.0`. - pub fn new_unchecked(value: Vec2) -> Self { - debug_assert!(value.is_normalized()); - - Self(value) - } - - /// Create a direction from a finite, nonzero [`Vec2`], also returning its original length. - /// - /// Returns [`Err(InvalidDirectionError)`](InvalidDirectionError) if the length - /// of the given vector is zero (or very close to zero), infinite, or `NaN`. - pub fn new_and_length(value: Vec2) -> Result<(Self, f32), InvalidDirectionError> { - let length = value.length(); - let direction = (length.is_finite() && length > 0.0).then_some(value / length); - - direction - .map(|dir| (Self(dir), length)) - .ok_or(InvalidDirectionError::from_length(length)) - } - - /// Create a direction from its `x` and `y` components. - /// - /// Returns [`Err(InvalidDirectionError)`](InvalidDirectionError) if the length - /// of the vector formed by the components is zero (or very close to zero), infinite, or `NaN`. - pub fn from_xy(x: f32, y: f32) -> Result { - Self::new(Vec2::new(x, y)) - } -} - -impl TryFrom for Direction2d { - type Error = InvalidDirectionError; - - fn try_from(value: Vec2) -> Result { - Self::new(value) - } -} - -impl std::ops::Deref for Direction2d { - type Target = Vec2; - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl std::ops::Neg for Direction2d { - type Output = Self; - fn neg(self) -> Self::Output { - Self(-self.0) - } -} - -#[cfg(feature = "approx")] -impl approx::AbsDiffEq for Direction2d { - type Epsilon = f32; - fn default_epsilon() -> f32 { - f32::EPSILON - } - fn abs_diff_eq(&self, other: &Self, epsilon: f32) -> bool { - self.as_ref().abs_diff_eq(other.as_ref(), epsilon) - } -} - -#[cfg(feature = "approx")] -impl approx::RelativeEq for Direction2d { - fn default_max_relative() -> f32 { - f32::EPSILON - } - fn relative_eq(&self, other: &Self, epsilon: f32, max_relative: f32) -> bool { - self.as_ref() - .relative_eq(other.as_ref(), epsilon, max_relative) - } -} - -#[cfg(feature = "approx")] -impl approx::UlpsEq for Direction2d { - fn default_max_ulps() -> u32 { - 4 - } - fn ulps_eq(&self, other: &Self, epsilon: f32, max_ulps: u32) -> bool { - self.as_ref().ulps_eq(other.as_ref(), epsilon, max_ulps) - } -} +use super::{Primitive2d, WindingOrder}; +use crate::{Direction2d, Vec2}; /// A circle primitive #[derive(Clone, Copy, Debug, PartialEq)] @@ -806,31 +695,6 @@ mod tests { use super::*; use approx::assert_relative_eq; - #[test] - fn direction_creation() { - assert_eq!(Direction2d::new(Vec2::X * 12.5), Ok(Direction2d::X)); - assert_eq!( - Direction2d::new(Vec2::new(0.0, 0.0)), - Err(InvalidDirectionError::Zero) - ); - assert_eq!( - Direction2d::new(Vec2::new(f32::INFINITY, 0.0)), - Err(InvalidDirectionError::Infinite) - ); - assert_eq!( - Direction2d::new(Vec2::new(f32::NEG_INFINITY, 0.0)), - Err(InvalidDirectionError::Infinite) - ); - assert_eq!( - Direction2d::new(Vec2::new(f32::NAN, 0.0)), - Err(InvalidDirectionError::NaN) - ); - assert_eq!( - Direction2d::new_and_length(Vec2::X * 6.5), - Ok((Direction2d::X, 6.5)) - ); - } - #[test] fn rectangle_closest_point() { let rectangle = Rectangle::new(2.0, 2.0); diff --git a/crates/bevy_math/src/primitives/dim3.rs b/crates/bevy_math/src/primitives/dim3.rs index 05b682f096..a8d2946f85 100644 --- a/crates/bevy_math/src/primitives/dim3.rs +++ b/crates/bevy_math/src/primitives/dim3.rs @@ -1,150 +1,7 @@ use std::f32::consts::{FRAC_PI_3, PI}; -use super::{Circle, InvalidDirectionError, Primitive3d}; -use crate::{Quat, Vec3}; - -/// A normalized vector pointing in a direction in 3D space -#[derive(Clone, Copy, Debug, PartialEq)] -#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] -pub struct Direction3d(Vec3); -impl Primitive3d for Direction3d {} - -impl Direction3d { - /// A unit vector pointing along the positive X axis. - pub const X: Self = Self(Vec3::X); - /// A unit vector pointing along the positive Y axis. - pub const Y: Self = Self(Vec3::Y); - /// A unit vector pointing along the positive Z axis. - pub const Z: Self = Self(Vec3::Z); - /// A unit vector pointing along the negative X axis. - pub const NEG_X: Self = Self(Vec3::NEG_X); - /// A unit vector pointing along the negative Y axis. - pub const NEG_Y: Self = Self(Vec3::NEG_Y); - /// A unit vector pointing along the negative Z axis. - pub const NEG_Z: Self = Self(Vec3::NEG_Z); - - /// Create a direction from a finite, nonzero [`Vec3`]. - /// - /// 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::new_and_length(value).map(|(dir, _)| dir) - } - - /// Create a [`Direction3d`] from a [`Vec3`] that is already normalized. - /// - /// # Warning - /// - /// `value` must be normalized, i.e it's length must be `1.0`. - pub fn new_unchecked(value: Vec3) -> Self { - debug_assert!(value.is_normalized()); - - Self(value) - } - - /// Create a direction from a finite, nonzero [`Vec3`], also returning its original length. - /// - /// Returns [`Err(InvalidDirectionError)`](InvalidDirectionError) if the length - /// of the given vector is zero (or very close to zero), infinite, or `NaN`. - pub fn new_and_length(value: Vec3) -> Result<(Self, f32), InvalidDirectionError> { - let length = value.length(); - let direction = (length.is_finite() && length > 0.0).then_some(value / length); - - direction - .map(|dir| (Self(dir), length)) - .ok_or(InvalidDirectionError::from_length(length)) - } - - /// Create a direction from its `x`, `y`, and `z` components. - /// - /// Returns [`Err(InvalidDirectionError)`](InvalidDirectionError) if the length - /// of the vector formed by the components is zero (or very close to zero), infinite, or `NaN`. - pub fn from_xyz(x: f32, y: f32, z: f32) -> Result { - Self::new(Vec3::new(x, y, z)) - } -} - -impl TryFrom for Direction3d { - type Error = InvalidDirectionError; - - fn try_from(value: Vec3) -> Result { - Self::new(value) - } -} - -impl From for Vec3 { - fn from(value: Direction3d) -> Self { - value.0 - } -} - -impl std::ops::Deref for Direction3d { - type Target = Vec3; - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl std::ops::Neg for Direction3d { - type Output = Self; - fn neg(self) -> Self::Output { - Self(-self.0) - } -} - -impl std::ops::Mul for Direction3d { - type Output = Vec3; - fn mul(self, rhs: f32) -> Self::Output { - self.0 * rhs - } -} - -impl std::ops::Mul for Quat { - type Output = Direction3d; - - /// Rotates the [`Direction3d`] using a [`Quat`]. - fn mul(self, direction: Direction3d) -> Self::Output { - let rotated = self * *direction; - - // Make sure the result is normalized. - // This can fail for non-unit quaternions. - debug_assert!(rotated.is_normalized()); - - Direction3d::new_unchecked(rotated) - } -} - -#[cfg(feature = "approx")] -impl approx::AbsDiffEq for Direction3d { - type Epsilon = f32; - fn default_epsilon() -> f32 { - f32::EPSILON - } - fn abs_diff_eq(&self, other: &Self, epsilon: f32) -> bool { - self.as_ref().abs_diff_eq(other.as_ref(), epsilon) - } -} - -#[cfg(feature = "approx")] -impl approx::RelativeEq for Direction3d { - fn default_max_relative() -> f32 { - f32::EPSILON - } - fn relative_eq(&self, other: &Self, epsilon: f32, max_relative: f32) -> bool { - self.as_ref() - .relative_eq(other.as_ref(), epsilon, max_relative) - } -} - -#[cfg(feature = "approx")] -impl approx::UlpsEq for Direction3d { - fn default_max_ulps() -> u32 { - 4 - } - fn ulps_eq(&self, other: &Self, epsilon: f32, max_ulps: u32) -> bool { - self.as_ref().ulps_eq(other.as_ref(), epsilon, max_ulps) - } -} +use super::{Circle, Primitive3d}; +use crate::{Direction3d, Vec3}; /// A sphere primitive #[derive(Clone, Copy, Debug, PartialEq)] @@ -779,6 +636,7 @@ mod tests { // Reference values were computed by hand and/or with external tools use super::*; + use crate::{InvalidDirectionError, Quat}; use approx::assert_relative_eq; #[test] diff --git a/crates/bevy_math/src/primitives/mod.rs b/crates/bevy_math/src/primitives/mod.rs index db5f380897..34754ad89b 100644 --- a/crates/bevy_math/src/primitives/mod.rs +++ b/crates/bevy_math/src/primitives/mod.rs @@ -15,41 +15,6 @@ 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 InvalidDirectionError { - /// Creates an [`InvalidDirectionError`] from the length of an invalid direction vector. - pub fn from_length(length: f32) -> Self { - if length.is_nan() { - InvalidDirectionError::NaN - } else if !length.is_finite() { - // If the direction is non-finite but also not NaN, it must be infinite - InvalidDirectionError::Infinite - } else { - // If the direction is invalid but neither NaN nor infinite, it must be zero - InvalidDirectionError::Zero - } - } -} - -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 { diff --git a/crates/bevy_math/src/ray.rs b/crates/bevy_math/src/ray.rs index bc9c45fa66..2aebad7933 100644 --- a/crates/bevy_math/src/ray.rs +++ b/crates/bevy_math/src/ray.rs @@ -1,6 +1,6 @@ use crate::{ - primitives::{Direction2d, Direction3d, Plane2d, Plane3d}, - Vec2, Vec3, + primitives::{Plane2d, Plane3d}, + Direction2d, Direction3d, Vec2, Vec3, }; /// An infinite half-line starting at `origin` and going in `direction` in 2D space. diff --git a/crates/bevy_reflect/src/impls/math/direction.rs b/crates/bevy_reflect/src/impls/math/direction.rs new file mode 100644 index 0000000000..2a22b98a41 --- /dev/null +++ b/crates/bevy_reflect/src/impls/math/direction.rs @@ -0,0 +1,22 @@ +use crate as bevy_reflect; +use crate::{ReflectDeserialize, ReflectSerialize}; +use bevy_reflect_derive::impl_reflect_value; + +impl_reflect_value!(::bevy_math::Direction2d( + Debug, + PartialEq, + Serialize, + Deserialize +)); +impl_reflect_value!(::bevy_math::Direction3d( + Debug, + PartialEq, + Serialize, + Deserialize +)); +impl_reflect_value!(::bevy_math::Direction3dA( + Debug, + PartialEq, + Serialize, + Deserialize +)); diff --git a/crates/bevy_reflect/src/impls/math/primitives2d.rs b/crates/bevy_reflect/src/impls/math/primitives2d.rs index 29f93eb561..09333240ff 100644 --- a/crates/bevy_reflect/src/impls/math/primitives2d.rs +++ b/crates/bevy_reflect/src/impls/math/primitives2d.rs @@ -1,14 +1,7 @@ use crate as bevy_reflect; use crate::{ReflectDeserialize, ReflectSerialize}; -use bevy_math::{primitives::*, Vec2}; -use bevy_reflect_derive::{impl_reflect, impl_reflect_value}; - -impl_reflect_value!(::bevy_math::primitives::Direction2d( - Debug, - PartialEq, - Serialize, - Deserialize -)); +use bevy_math::{primitives::*, Direction2d, Vec2}; +use bevy_reflect_derive::impl_reflect; impl_reflect!( #[reflect(Debug, PartialEq, Serialize, Deserialize)] diff --git a/crates/bevy_reflect/src/impls/math/primitives3d.rs b/crates/bevy_reflect/src/impls/math/primitives3d.rs index 3c986fc1a6..d3408070db 100644 --- a/crates/bevy_reflect/src/impls/math/primitives3d.rs +++ b/crates/bevy_reflect/src/impls/math/primitives3d.rs @@ -1,14 +1,7 @@ use crate as bevy_reflect; use crate::{ReflectDeserialize, ReflectSerialize}; -use bevy_math::{primitives::*, Vec3}; -use bevy_reflect_derive::{impl_reflect, impl_reflect_value}; - -impl_reflect_value!(::bevy_math::primitives::Direction3d( - Debug, - PartialEq, - Serialize, - Deserialize -)); +use bevy_math::{primitives::*, Direction3d, Vec3}; +use bevy_reflect_derive::impl_reflect; impl_reflect!( #[reflect(Debug, PartialEq, Serialize, Deserialize)] diff --git a/crates/bevy_reflect/src/lib.rs b/crates/bevy_reflect/src/lib.rs index 0c909ae093..ab7b2d0a0a 100644 --- a/crates/bevy_reflect/src/lib.rs +++ b/crates/bevy_reflect/src/lib.rs @@ -486,6 +486,7 @@ mod impls { mod glam; #[cfg(feature = "bevy_math")] mod math { + mod direction; mod primitives2d; mod primitives3d; mod rect; diff --git a/crates/bevy_render/src/camera/camera.rs b/crates/bevy_render/src/camera/camera.rs index c2e25686b1..f12faea44e 100644 --- a/crates/bevy_render/src/camera/camera.rs +++ b/crates/bevy_render/src/camera/camera.rs @@ -21,9 +21,7 @@ use bevy_ecs::{ system::{Commands, Query, Res, ResMut, Resource}, }; use bevy_log::warn; -use bevy_math::{ - primitives::Direction3d, vec2, Mat4, Ray3d, Rect, URect, UVec2, UVec4, Vec2, Vec3, -}; +use bevy_math::{vec2, Direction3d, Mat4, Ray3d, Rect, URect, UVec2, UVec4, Vec2, Vec3}; use bevy_reflect::prelude::*; use bevy_render_macros::ExtractComponent; use bevy_transform::components::GlobalTransform; diff --git a/crates/bevy_render/src/mesh/primitives/dim3/plane.rs b/crates/bevy_render/src/mesh/primitives/dim3/plane.rs index 02043acf48..ac795cfec3 100644 --- a/crates/bevy_render/src/mesh/primitives/dim3/plane.rs +++ b/crates/bevy_render/src/mesh/primitives/dim3/plane.rs @@ -1,7 +1,4 @@ -use bevy_math::{ - primitives::{Direction3d, Plane3d}, - Quat, Vec2, Vec3, -}; +use bevy_math::{primitives::Plane3d, Direction3d, Quat, Vec2, Vec3}; use wgpu::PrimitiveTopology; use crate::{ diff --git a/crates/bevy_transform/src/components/transform.rs b/crates/bevy_transform/src/components/transform.rs index 46dcceb68e..1d1b52d40d 100644 --- a/crates/bevy_transform/src/components/transform.rs +++ b/crates/bevy_transform/src/components/transform.rs @@ -1,7 +1,6 @@ use super::GlobalTransform; use bevy_ecs::{component::Component, reflect::ReflectComponent}; -use bevy_math::primitives::Direction3d; -use bevy_math::{Affine3A, Mat3, Mat4, Quat, Vec3}; +use bevy_math::{Affine3A, Direction3d, Mat3, Mat4, Quat, Vec3}; use bevy_reflect::prelude::*; use bevy_reflect::Reflect; use std::ops::Mul; diff --git a/examples/3d/3d_viewport_to_world.rs b/examples/3d/3d_viewport_to_world.rs index ac13169de0..22aef131b0 100644 --- a/examples/3d/3d_viewport_to_world.rs +++ b/examples/3d/3d_viewport_to_world.rs @@ -1,6 +1,6 @@ //! This example demonstrates how to use the `Camera::viewport_to_world` method. -use bevy::math::primitives::Direction3d; +use bevy::math::Direction3d; use bevy::prelude::*; fn main() {