From f924b4d9efc8bb885cfed43aeb3b2da32357ed87 Mon Sep 17 00:00:00 2001 From: Matty Date: Thu, 28 Mar 2024 09:40:26 -0400 Subject: [PATCH] Move `Point` out of cubic splines module and expand it (#12747) # Objective Previously, the `Point` trait, which abstracts all of the operations of a real vector space, was sitting in the submodule of `bevy_math` for cubic splines. However, the trait has broader applications than merely cubic splines, and we should use it when possible to avoid code duplication when performing vector operations. ## Solution `Point` has been moved into a new submodule in `bevy_math` named `common_traits`. Furthermore, it has been renamed to `VectorSpace`, which is more descriptive, and an additional trait `NormedVectorSpace` has been introduced to expand the API to cover situations involving geometry in addition to algebra. Additionally, `VectorSpace` itself now requires a `ZERO` constant and `Neg`. It also supports a `lerp` function as an automatic trait method. Here is what that looks like: ```rust /// A type that supports the mathematical operations of a real vector space, irrespective of dimension. /// In particular, this means that the implementing type supports: /// - Scalar multiplication and division on the right by elements of `f32` /// - Negation /// - Addition and subtraction /// - Zero /// /// Within the limitations of floating point arithmetic, all the following are required to hold: /// - (Associativity of addition) For all `u, v, w: Self`, `(u + v) + w == u + (v + w)`. /// - (Commutativity of addition) For all `u, v: Self`, `u + v == v + u`. /// - (Additive identity) For all `v: Self`, `v + Self::ZERO == v`. /// - (Additive inverse) For all `v: Self`, `v - v == v + (-v) == Self::ZERO`. /// - (Compatibility of multiplication) For all `a, b: f32`, `v: Self`, `v * (a * b) == (v * a) * b`. /// - (Multiplicative identity) For all `v: Self`, `v * 1.0 == v`. /// - (Distributivity for vector addition) For all `a: f32`, `u, v: Self`, `(u + v) * a == u * a + v * a`. /// - (Distributivity for scalar addition) For all `a, b: f32`, `v: Self`, `v * (a + b) == v * a + v * b`. /// /// Note that, because implementing types use floating point arithmetic, they are not required to actually /// implement `PartialEq` or `Eq`. pub trait VectorSpace: Mul + Div + Add + Sub + Neg + Default + Debug + Clone + Copy { /// The zero vector, which is the identity of addition for the vector space type. const ZERO: Self; /// Perform vector space linear interpolation between this element and another, based /// on the parameter `t`. When `t` is `0`, `self` is recovered. When `t` is `1`, `rhs` /// is recovered. /// /// Note that the value of `t` is not clamped by this function, so interpolating outside /// of the interval `[0,1]` is allowed. #[inline] fn lerp(&self, rhs: Self, t: f32) -> Self { *self * (1. - t) + rhs * t } } ``` ```rust /// A type that supports the operations of a normed vector space; i.e. a norm operation in addition /// to those of [`VectorSpace`]. Specifically, the implementor must guarantee that the following /// relationships hold, within the limitations of floating point arithmetic: /// - (Nonnegativity) For all `v: Self`, `v.norm() >= 0.0`. /// - (Positive definiteness) For all `v: Self`, `v.norm() == 0.0` implies `v == Self::ZERO`. /// - (Absolute homogeneity) For all `c: f32`, `v: Self`, `(v * c).norm() == v.norm() * c.abs()`. /// - (Triangle inequality) For all `v, w: Self`, `(v + w).norm() <= v.norm() + w.norm()`. /// /// Note that, because implementing types use floating point arithmetic, they are not required to actually /// implement `PartialEq` or `Eq`. pub trait NormedVectorSpace: VectorSpace { /// The size of this element. The return value should always be nonnegative. fn norm(self) -> f32; /// The squared norm of this element. Computing this is often faster than computing /// [`NormedVectorSpace::norm`]. #[inline] fn norm_squared(self) -> f32 { self.norm() * self.norm() } /// The distance between this element and another, as determined by the norm. #[inline] fn distance(self, rhs: Self) -> f32 { (rhs - self).norm() } /// The squared distance between this element and another, as determined by the norm. Note that /// this is often faster to compute in practice than [`NormedVectorSpace::distance`]. #[inline] fn distance_squared(self, rhs: Self) -> f32 { (rhs - self).norm_squared() } } ``` Furthermore, this PR also demonstrates the use of the `NormedVectorSpace` combined API to implement `ShapeSample` for `Triangle2d` and `Triangle3d` simultaneously. Such deduplication is one of the drivers for developing these APIs. --- ## Changelog - `Point` from `cubic_splines` becomes `VectorSpace`, exported as `bevy::math::VectorSpace`. - `VectorSpace` requires `Neg` and `VectorSpace::ZERO` in addition to its existing prerequisites. - Introduced public traits `bevy::math::NormedVectorSpace` for generic geometry tasks involving vectors. - Implemented `ShapeSample` for `Triangle2d` and `Triangle3d`. ## Migration Guide Since `Point` no longer exists, any projects using it must switch to `bevy::math::VectorSpace`. Additionally, third-party implementations of this trait now require the `Neg` trait; the constant `VectorSpace::ZERO` must be provided as well. --- ## Discussion ### Design considerations Originally, the `NormedVectorSpace::norm` method was part of a separate trait `Normed`. However, I think that was probably too broad and, more importantly, the semantics of having it in `NormedVectorSpace` are much clearer. As it currently stands, the API exposed here is pretty minimal, and there is definitely a lot more that we could do, but there are more questions to answer along the way. As a silly example, we could implement `NormedVectorSpace::length` as an alias for `NormedVectorSpace::norm`, but this overlaps with methods in all of the glam types, so we would want to make sure that the implementations are effectively identical (for what it's worth, I think they are already). ### Future directions One example of something that could belong in the `NormedVectorSpace` API is normalization. Actually, such a thing previously existed on this branch before I decided to shelve it because of concerns with namespace collision. It looked like this: ```rust /// This element, but normalized to norm 1 if possible. Returns an error when the reciprocal of /// the element's norm is not finite. #[inline] #[must_use] fn normalize(&self) -> Result { let reciprocal = 1.0 / self.norm(); if reciprocal.is_finite() { Ok(*self * reciprocal) } else { Err(NonNormalizableError { reciprocal }) } } /// An error indicating that an element of a [`NormedVectorSpace`] was non-normalizable due to having /// non-finite norm-reciprocal. #[derive(Debug, Error)] #[error("Element with norm reciprocal {reciprocal} cannot be normalized")] pub struct NonNormalizableError { reciprocal: f32 } ``` With this kind of thing in hand, it might be worth considering eventually making the passage from vectors to directions fully generic by employing a wrapper type. (Of course, for our concrete types, we would leave the existing names in place as aliases.) That is, something like: ```rust pub struct NormOne where T: NormedVectorSpace { //... } ``` Utterly separately, the reason that I implemented `ShapeSample` for `Triangle2d`/`Triangle3d` was to prototype uniform sampling of abstract meshes, so that's also a future direction. --------- Co-authored-by: Zachary Harrold --- crates/bevy_color/src/laba.rs | 6 +- crates/bevy_color/src/lib.rs | 20 ++- crates/bevy_color/src/linear_rgba.rs | 6 +- crates/bevy_color/src/oklaba.rs | 6 +- crates/bevy_color/src/srgba.rs | 5 +- crates/bevy_color/src/xyza.rs | 4 +- crates/bevy_math/src/common_traits.rs | 180 +++++++++++++++++++++++++ crates/bevy_math/src/cubic_splines.rs | 98 +++++--------- crates/bevy_math/src/lib.rs | 2 + crates/bevy_math/src/shape_sampling.rs | 75 ++++++++++- examples/animation/color_animation.rs | 6 +- 11 files changed, 327 insertions(+), 81 deletions(-) create mode 100644 crates/bevy_math/src/common_traits.rs diff --git a/crates/bevy_color/src/laba.rs b/crates/bevy_color/src/laba.rs index cec3019a31..5c95472932 100644 --- a/crates/bevy_color/src/laba.rs +++ b/crates/bevy_color/src/laba.rs @@ -1,6 +1,6 @@ use crate::{ - impl_componentwise_point, Alpha, ClampColor, Hsla, Hsva, Hwba, LinearRgba, Luminance, Mix, - Oklaba, Srgba, StandardColor, Xyza, + impl_componentwise_vector_space, Alpha, ClampColor, Hsla, Hsva, Hwba, LinearRgba, Luminance, + Mix, Oklaba, Srgba, StandardColor, Xyza, }; use bevy_reflect::prelude::*; @@ -29,7 +29,7 @@ pub struct Laba { impl StandardColor for Laba {} -impl_componentwise_point!(Laba, [lightness, a, b, alpha]); +impl_componentwise_vector_space!(Laba, [lightness, a, b, alpha]); impl Laba { /// Construct a new [`Laba`] color from components. diff --git a/crates/bevy_color/src/lib.rs b/crates/bevy_color/src/lib.rs index c9c6b49381..923a603c0c 100644 --- a/crates/bevy_color/src/lib.rs +++ b/crates/bevy_color/src/lib.rs @@ -164,7 +164,7 @@ where { } -macro_rules! impl_componentwise_point { +macro_rules! impl_componentwise_vector_space { ($ty: ident, [$($element: ident),+]) => { impl std::ops::Add for $ty { type Output = Self; @@ -182,6 +182,16 @@ macro_rules! impl_componentwise_point { } } + impl std::ops::Neg for $ty { + type Output = Self; + + fn neg(self) -> Self::Output { + Self::Output { + $($element: -self.$element,)+ + } + } + } + impl std::ops::Sub for $ty { type Output = Self; @@ -240,8 +250,12 @@ macro_rules! impl_componentwise_point { } } - impl bevy_math::cubic_splines::Point for $ty {} + impl bevy_math::VectorSpace for $ty { + const ZERO: Self = Self { + $($element: 0.0,)+ + }; + } }; } -pub(crate) use impl_componentwise_point; +pub(crate) use impl_componentwise_vector_space; diff --git a/crates/bevy_color/src/linear_rgba.rs b/crates/bevy_color/src/linear_rgba.rs index ef5fa40b5b..946e461694 100644 --- a/crates/bevy_color/src/linear_rgba.rs +++ b/crates/bevy_color/src/linear_rgba.rs @@ -1,6 +1,6 @@ use crate::{ - color_difference::EuclideanDistance, impl_componentwise_point, Alpha, ClampColor, Luminance, - Mix, StandardColor, + color_difference::EuclideanDistance, impl_componentwise_vector_space, Alpha, ClampColor, + Luminance, Mix, StandardColor, }; use bevy_math::Vec4; use bevy_reflect::prelude::*; @@ -32,7 +32,7 @@ pub struct LinearRgba { impl StandardColor for LinearRgba {} -impl_componentwise_point!(LinearRgba, [red, green, blue, alpha]); +impl_componentwise_vector_space!(LinearRgba, [red, green, blue, alpha]); impl LinearRgba { /// A fully black color with full alpha. diff --git a/crates/bevy_color/src/oklaba.rs b/crates/bevy_color/src/oklaba.rs index 06a2d67753..65cd88f775 100644 --- a/crates/bevy_color/src/oklaba.rs +++ b/crates/bevy_color/src/oklaba.rs @@ -1,6 +1,6 @@ use crate::{ - color_difference::EuclideanDistance, impl_componentwise_point, Alpha, ClampColor, Hsla, Hsva, - Hwba, Lcha, LinearRgba, Luminance, Mix, Srgba, StandardColor, Xyza, + color_difference::EuclideanDistance, impl_componentwise_vector_space, Alpha, ClampColor, Hsla, + Hsva, Hwba, Lcha, LinearRgba, Luminance, Mix, Srgba, StandardColor, Xyza, }; use bevy_reflect::prelude::*; @@ -29,7 +29,7 @@ pub struct Oklaba { impl StandardColor for Oklaba {} -impl_componentwise_point!(Oklaba, [lightness, a, b, alpha]); +impl_componentwise_vector_space!(Oklaba, [lightness, a, b, alpha]); impl Oklaba { /// Construct a new [`Oklaba`] color from components. diff --git a/crates/bevy_color/src/srgba.rs b/crates/bevy_color/src/srgba.rs index 31fafd0802..43c82fda25 100644 --- a/crates/bevy_color/src/srgba.rs +++ b/crates/bevy_color/src/srgba.rs @@ -1,6 +1,7 @@ use crate::color_difference::EuclideanDistance; use crate::{ - impl_componentwise_point, Alpha, ClampColor, LinearRgba, Luminance, Mix, StandardColor, Xyza, + impl_componentwise_vector_space, Alpha, ClampColor, LinearRgba, Luminance, Mix, StandardColor, + Xyza, }; use bevy_math::Vec4; use bevy_reflect::prelude::*; @@ -31,7 +32,7 @@ pub struct Srgba { impl StandardColor for Srgba {} -impl_componentwise_point!(Srgba, [red, green, blue, alpha]); +impl_componentwise_vector_space!(Srgba, [red, green, blue, alpha]); impl Srgba { // The standard VGA colors, with alpha set to 1.0. diff --git a/crates/bevy_color/src/xyza.rs b/crates/bevy_color/src/xyza.rs index bc0c50e29b..d3baf464f4 100644 --- a/crates/bevy_color/src/xyza.rs +++ b/crates/bevy_color/src/xyza.rs @@ -1,5 +1,5 @@ use crate::{ - impl_componentwise_point, Alpha, ClampColor, LinearRgba, Luminance, Mix, StandardColor, + impl_componentwise_vector_space, Alpha, ClampColor, LinearRgba, Luminance, Mix, StandardColor, }; use bevy_reflect::prelude::*; @@ -28,7 +28,7 @@ pub struct Xyza { impl StandardColor for Xyza {} -impl_componentwise_point!(Xyza, [x, y, z, alpha]); +impl_componentwise_vector_space!(Xyza, [x, y, z, alpha]); impl Xyza { /// Construct a new [`Xyza`] color from components. diff --git a/crates/bevy_math/src/common_traits.rs b/crates/bevy_math/src/common_traits.rs new file mode 100644 index 0000000000..3c8220335a --- /dev/null +++ b/crates/bevy_math/src/common_traits.rs @@ -0,0 +1,180 @@ +use glam::{Quat, Vec2, Vec3, Vec3A, Vec4}; +use std::fmt::Debug; +use std::ops::{Add, Div, Mul, Neg, Sub}; + +/// A type that supports the mathematical operations of a real vector space, irrespective of dimension. +/// In particular, this means that the implementing type supports: +/// - Scalar multiplication and division on the right by elements of `f32` +/// - Negation +/// - Addition and subtraction +/// - Zero +/// +/// Within the limitations of floating point arithmetic, all the following are required to hold: +/// - (Associativity of addition) For all `u, v, w: Self`, `(u + v) + w == u + (v + w)`. +/// - (Commutativity of addition) For all `u, v: Self`, `u + v == v + u`. +/// - (Additive identity) For all `v: Self`, `v + Self::ZERO == v`. +/// - (Additive inverse) For all `v: Self`, `v - v == v + (-v) == Self::ZERO`. +/// - (Compatibility of multiplication) For all `a, b: f32`, `v: Self`, `v * (a * b) == (v * a) * b`. +/// - (Multiplicative identity) For all `v: Self`, `v * 1.0 == v`. +/// - (Distributivity for vector addition) For all `a: f32`, `u, v: Self`, `(u + v) * a == u * a + v * a`. +/// - (Distributivity for scalar addition) For all `a, b: f32`, `v: Self`, `v * (a + b) == v * a + v * b`. +/// +/// Note that, because implementing types use floating point arithmetic, they are not required to actually +/// implement `PartialEq` or `Eq`. +pub trait VectorSpace: + Mul + + Div + + Add + + Sub + + Neg + + Default + + Debug + + Clone + + Copy +{ + /// The zero vector, which is the identity of addition for the vector space type. + const ZERO: Self; + + /// Perform vector space linear interpolation between this element and another, based + /// on the parameter `t`. When `t` is `0`, `self` is recovered. When `t` is `1`, `rhs` + /// is recovered. + /// + /// Note that the value of `t` is not clamped by this function, so interpolating outside + /// of the interval `[0,1]` is allowed. + #[inline] + fn lerp(&self, rhs: Self, t: f32) -> Self { + *self * (1. - t) + rhs * t + } +} + +// This is cursed and we should probably remove Quat from these. +impl VectorSpace for Quat { + const ZERO: Self = Quat::from_xyzw(0., 0., 0., 0.); +} + +impl VectorSpace for Vec4 { + const ZERO: Self = Vec4::ZERO; +} + +impl VectorSpace for Vec3 { + const ZERO: Self = Vec3::ZERO; +} + +impl VectorSpace for Vec3A { + const ZERO: Self = Vec3A::ZERO; +} + +impl VectorSpace for Vec2 { + const ZERO: Self = Vec2::ZERO; +} + +impl VectorSpace for f32 { + const ZERO: Self = 0.0; +} + +/// A type that supports the operations of a normed vector space; i.e. a norm operation in addition +/// to those of [`VectorSpace`]. Specifically, the implementor must guarantee that the following +/// relationships hold, within the limitations of floating point arithmetic: +/// - (Nonnegativity) For all `v: Self`, `v.norm() >= 0.0`. +/// - (Positive definiteness) For all `v: Self`, `v.norm() == 0.0` implies `v == Self::ZERO`. +/// - (Absolute homogeneity) For all `c: f32`, `v: Self`, `(v * c).norm() == v.norm() * c.abs()`. +/// - (Triangle inequality) For all `v, w: Self`, `(v + w).norm() <= v.norm() + w.norm()`. +/// +/// Note that, because implementing types use floating point arithmetic, they are not required to actually +/// implement `PartialEq` or `Eq`. +pub trait NormedVectorSpace: VectorSpace { + /// The size of this element. The return value should always be nonnegative. + fn norm(self) -> f32; + + /// The squared norm of this element. Computing this is often faster than computing + /// [`NormedVectorSpace::norm`]. + #[inline] + fn norm_squared(self) -> f32 { + self.norm() * self.norm() + } + + /// The distance between this element and another, as determined by the norm. + #[inline] + fn distance(self, rhs: Self) -> f32 { + (rhs - self).norm() + } + + /// The squared distance between this element and another, as determined by the norm. Note that + /// this is often faster to compute in practice than [`NormedVectorSpace::distance`]. + #[inline] + fn distance_squared(self, rhs: Self) -> f32 { + (rhs - self).norm_squared() + } +} + +impl NormedVectorSpace for Quat { + #[inline] + fn norm(self) -> f32 { + self.length() + } + + #[inline] + fn norm_squared(self) -> f32 { + self.length_squared() + } +} + +impl NormedVectorSpace for Vec4 { + #[inline] + fn norm(self) -> f32 { + self.length() + } + + #[inline] + fn norm_squared(self) -> f32 { + self.length_squared() + } +} + +impl NormedVectorSpace for Vec3 { + #[inline] + fn norm(self) -> f32 { + self.length() + } + + #[inline] + fn norm_squared(self) -> f32 { + self.length_squared() + } +} + +impl NormedVectorSpace for Vec3A { + #[inline] + fn norm(self) -> f32 { + self.length() + } + + #[inline] + fn norm_squared(self) -> f32 { + self.length_squared() + } +} + +impl NormedVectorSpace for Vec2 { + #[inline] + fn norm(self) -> f32 { + self.length() + } + + #[inline] + fn norm_squared(self) -> f32 { + self.length_squared() + } +} + +impl NormedVectorSpace for f32 { + #[inline] + fn norm(self) -> f32 { + self.abs() + } + + #[inline] + fn norm_squared(self) -> f32 { + self * self + } +} diff --git a/crates/bevy_math/src/cubic_splines.rs b/crates/bevy_math/src/cubic_splines.rs index b7796b68f0..c1da3184f7 100644 --- a/crates/bevy_math/src/cubic_splines.rs +++ b/crates/bevy_math/src/cubic_splines.rs @@ -1,35 +1,11 @@ //! Provides types for building cubic splines for rendering curves and use with animation easing. -use std::{ - fmt::Debug, - iter::once, - ops::{Add, Div, Mul, Sub}, -}; +use std::{fmt::Debug, iter::once}; + +use crate::{Vec2, VectorSpace}; -use glam::{Quat, Vec2, Vec3, Vec3A, Vec4}; use thiserror::Error; -/// A point in space of any dimension that supports the math ops needed for cubic spline -/// interpolation. -pub trait Point: - Mul - + Div - + Add - + Sub - + Default - + Debug - + Clone - + Copy -{ -} - -impl Point for Quat {} -impl Point for Vec4 {} -impl Point for Vec3 {} -impl Point for Vec3A {} -impl Point for Vec2 {} -impl Point for f32 {} - /// A spline composed of a single cubic Bezier curve. /// /// Useful for user-drawn curves with local control, or animation easing. See @@ -64,11 +40,11 @@ impl Point for f32 {} /// let bezier = CubicBezier::new(points).to_curve(); /// let positions: Vec<_> = bezier.iter_positions(100).collect(); /// ``` -pub struct CubicBezier { +pub struct CubicBezier { control_points: Vec<[P; 4]>, } -impl CubicBezier

{ +impl CubicBezier

{ /// Create a new cubic Bezier curve from sets of control points. pub fn new(control_points: impl Into>) -> Self { Self { @@ -76,7 +52,7 @@ impl CubicBezier

{ } } } -impl CubicGenerator

for CubicBezier

{ +impl CubicGenerator

for CubicBezier

{ #[inline] fn to_curve(&self) -> CubicCurve

{ // A derivation for this matrix can be found in "General Matrix Representations for B-splines" by Kaihuai Qin. @@ -135,10 +111,10 @@ impl CubicGenerator

for CubicBezier

{ /// let hermite = CubicHermite::new(points, tangents).to_curve(); /// let positions: Vec<_> = hermite.iter_positions(100).collect(); /// ``` -pub struct CubicHermite { +pub struct CubicHermite { control_points: Vec<(P, P)>, } -impl CubicHermite

{ +impl CubicHermite

{ /// Create a new Hermite curve from sets of control points. pub fn new( control_points: impl IntoIterator, @@ -149,7 +125,7 @@ impl CubicHermite

{ } } } -impl CubicGenerator

for CubicHermite

{ +impl CubicGenerator

for CubicHermite

{ #[inline] fn to_curve(&self) -> CubicCurve

{ let char_matrix = [ @@ -201,12 +177,12 @@ impl CubicGenerator

for CubicHermite

{ /// let cardinal = CubicCardinalSpline::new(0.3, points).to_curve(); /// let positions: Vec<_> = cardinal.iter_positions(100).collect(); /// ``` -pub struct CubicCardinalSpline { +pub struct CubicCardinalSpline { tension: f32, control_points: Vec

, } -impl CubicCardinalSpline

{ +impl CubicCardinalSpline

{ /// Build a new Cardinal spline. pub fn new(tension: f32, control_points: impl Into>) -> Self { Self { @@ -223,7 +199,7 @@ impl CubicCardinalSpline

{ } } } -impl CubicGenerator

for CubicCardinalSpline

{ +impl CubicGenerator

for CubicCardinalSpline

{ #[inline] fn to_curve(&self) -> CubicCurve

{ let s = self.tension; @@ -288,10 +264,10 @@ impl CubicGenerator

for CubicCardinalSpline

{ /// let b_spline = CubicBSpline::new(points).to_curve(); /// let positions: Vec<_> = b_spline.iter_positions(100).collect(); /// ``` -pub struct CubicBSpline { +pub struct CubicBSpline { control_points: Vec

, } -impl CubicBSpline

{ +impl CubicBSpline

{ /// Build a new B-Spline. pub fn new(control_points: impl Into>) -> Self { Self { @@ -299,7 +275,7 @@ impl CubicBSpline

{ } } } -impl CubicGenerator

for CubicBSpline

{ +impl CubicGenerator

for CubicBSpline

{ #[inline] fn to_curve(&self) -> CubicCurve

{ // A derivation for this matrix can be found in "General Matrix Representations for B-splines" by Kaihuai Qin. @@ -405,12 +381,12 @@ pub enum CubicNurbsError { /// .to_curve(); /// let positions: Vec<_> = nurbs.iter_positions(100).collect(); /// ``` -pub struct CubicNurbs { +pub struct CubicNurbs { control_points: Vec

, weights: Vec, knots: Vec, } -impl CubicNurbs

{ +impl CubicNurbs

{ /// Build a Non-Uniform Rational B-Spline. /// /// If provided, weights must be the same length as the control points. Defaults to equal weights. @@ -570,7 +546,7 @@ impl CubicNurbs

{ ] } } -impl RationalGenerator

for CubicNurbs

{ +impl RationalGenerator

for CubicNurbs

{ #[inline] fn to_curve(&self) -> RationalCurve

{ let segments = self @@ -609,10 +585,10 @@ impl RationalGenerator

for CubicNurbs

{ /// /// ### Continuity /// The curve is C0 continuous, meaning it has no holes or jumps. -pub struct LinearSpline { +pub struct LinearSpline { points: Vec

, } -impl LinearSpline

{ +impl LinearSpline

{ /// Create a new linear spline pub fn new(points: impl Into>) -> Self { Self { @@ -620,7 +596,7 @@ impl LinearSpline

{ } } } -impl CubicGenerator

for LinearSpline

{ +impl CubicGenerator

for LinearSpline

{ #[inline] fn to_curve(&self) -> CubicCurve

{ let segments = self @@ -639,7 +615,7 @@ impl CubicGenerator

for LinearSpline

{ } /// Implement this on cubic splines that can generate a cubic curve from their spline parameters. -pub trait CubicGenerator { +pub trait CubicGenerator { /// Build a [`CubicCurve`] by computing the interpolation coefficients for each curve segment. fn to_curve(&self) -> CubicCurve

; } @@ -649,11 +625,11 @@ pub trait CubicGenerator { /// /// Segments can be chained together to form a longer compound curve. #[derive(Clone, Debug, Default, PartialEq)] -pub struct CubicSegment { +pub struct CubicSegment { coeff: [P; 4], } -impl CubicSegment

{ +impl CubicSegment

{ /// Instantaneous position of a point at parametric value `t`. #[inline] pub fn position(&self, t: f32) -> P { @@ -807,11 +783,11 @@ impl CubicSegment { /// Use any struct that implements the [`CubicGenerator`] trait to create a new curve, such as /// [`CubicBezier`]. #[derive(Clone, Debug, PartialEq)] -pub struct CubicCurve { +pub struct CubicCurve { segments: Vec>, } -impl CubicCurve

{ +impl CubicCurve

{ /// Compute the position of a point on the cubic curve at the parametric value `t`. /// /// Note that `t` varies from `0..=(n_points - 3)`. @@ -912,13 +888,13 @@ impl CubicCurve

{ } } -impl Extend> for CubicCurve

{ +impl Extend> for CubicCurve

{ fn extend>>(&mut self, iter: T) { self.segments.extend(iter); } } -impl IntoIterator for CubicCurve

{ +impl IntoIterator for CubicCurve

{ type IntoIter = > as IntoIterator>::IntoIter; type Item = CubicSegment

; @@ -929,7 +905,7 @@ impl IntoIterator for CubicCurve

{ } /// Implement this on cubic splines that can generate a rational cubic curve from their spline parameters. -pub trait RationalGenerator { +pub trait RationalGenerator { /// Build a [`RationalCurve`] by computing the interpolation coefficients for each curve segment. fn to_curve(&self) -> RationalCurve

; } @@ -939,7 +915,7 @@ pub trait RationalGenerator { /// /// Segments can be chained together to form a longer compound curve. #[derive(Clone, Debug, Default, PartialEq)] -pub struct RationalSegment { +pub struct RationalSegment { /// The coefficients matrix of the cubic curve. coeff: [P; 4], /// The homogeneous weight coefficients. @@ -948,7 +924,7 @@ pub struct RationalSegment { knot_span: f32, } -impl RationalSegment

{ +impl RationalSegment

{ /// Instantaneous position of a point at parametric value `t` in `[0, knot_span)`. #[inline] pub fn position(&self, t: f32) -> P { @@ -1066,11 +1042,11 @@ impl RationalSegment

{ /// Use any struct that implements the [`RationalGenerator`] trait to create a new curve, such as /// [`CubicNurbs`], or convert [`CubicCurve`] using `into/from`. #[derive(Clone, Debug, PartialEq)] -pub struct RationalCurve { +pub struct RationalCurve { segments: Vec>, } -impl RationalCurve

{ +impl RationalCurve

{ /// Compute the position of a point on the curve at the parametric value `t`. /// /// Note that `t` varies from `0..=(n_points - 3)`. @@ -1190,13 +1166,13 @@ impl RationalCurve

{ } } -impl Extend> for RationalCurve

{ +impl Extend> for RationalCurve

{ fn extend>>(&mut self, iter: T) { self.segments.extend(iter); } } -impl IntoIterator for RationalCurve

{ +impl IntoIterator for RationalCurve

{ type IntoIter = > as IntoIterator>::IntoIter; type Item = RationalSegment

; @@ -1206,7 +1182,7 @@ impl IntoIterator for RationalCurve

{ } } -impl From> for RationalSegment

{ +impl From> for RationalSegment

{ fn from(value: CubicSegment

) -> Self { Self { coeff: value.coeff, @@ -1216,7 +1192,7 @@ impl From> for RationalSegment

{ } } -impl From> for RationalCurve

{ +impl From> for RationalCurve

{ fn from(value: CubicCurve

) -> Self { Self { segments: value.segments.into_iter().map(Into::into).collect(), diff --git a/crates/bevy_math/src/lib.rs b/crates/bevy_math/src/lib.rs index 8b4b8cef14..3075d5be3d 100644 --- a/crates/bevy_math/src/lib.rs +++ b/crates/bevy_math/src/lib.rs @@ -14,6 +14,7 @@ mod affine3; mod aspect_ratio; pub mod bounding; +mod common_traits; pub mod cubic_splines; mod direction; mod float_ord; @@ -26,6 +27,7 @@ mod shape_sampling; pub use affine3::*; pub use aspect_ratio::AspectRatio; +pub use common_traits::*; pub use direction::*; pub use float_ord::*; pub use ray::{Ray2d, Ray3d}; diff --git a/crates/bevy_math/src/shape_sampling.rs b/crates/bevy_math/src/shape_sampling.rs index eff4a0898f..1479aa7209 100644 --- a/crates/bevy_math/src/shape_sampling.rs +++ b/crates/bevy_math/src/shape_sampling.rs @@ -1,6 +1,6 @@ use std::f32::consts::{PI, TAU}; -use crate::{primitives::*, Vec2, Vec3}; +use crate::{primitives::*, NormedVectorSpace, Vec2, Vec3}; use rand::{ distributions::{Distribution, WeightedIndex}, Rng, @@ -142,6 +142,79 @@ impl ShapeSample for Cuboid { } } +/// Interior sampling for triangles which doesn't depend on the ambient dimension. +fn sample_triangle_interior( + vertices: [P; 3], + rng: &mut R, +) -> P { + let [a, b, c] = vertices; + let ab = b - a; + let ac = c - a; + + // Generate random points on a parallelipiped and reflect so that + // we can use the points that lie outside the triangle + let u = rng.gen_range(0.0..=1.0); + let v = rng.gen_range(0.0..=1.0); + + if u + v > 1. { + let u1 = 1. - v; + let v1 = 1. - u; + ab * u1 + ac * v1 + } else { + ab * u + ac * v + } +} + +/// Boundary sampling for triangles which doesn't depend on the ambient dimension. +fn sample_triangle_boundary( + vertices: [P; 3], + rng: &mut R, +) -> P { + let [a, b, c] = vertices; + let ab = b - a; + let ac = c - a; + let bc = c - b; + + let t = rng.gen_range(0.0..=1.0); + + if let Ok(dist) = WeightedIndex::new([ab.norm(), ac.norm(), bc.norm()]) { + match dist.sample(rng) { + 0 => a.lerp(b, t), + 1 => a.lerp(c, t), + 2 => b.lerp(c, t), + _ => unreachable!(), + } + } else { + // This should only occur when the triangle is 0-dimensional degenerate + // so this is actually the correct result. + a + } +} + +impl ShapeSample for Triangle2d { + type Output = Vec2; + + fn sample_interior(&self, rng: &mut R) -> Self::Output { + sample_triangle_interior(self.vertices, rng) + } + + fn sample_boundary(&self, rng: &mut R) -> Self::Output { + sample_triangle_boundary(self.vertices, rng) + } +} + +impl ShapeSample for Triangle3d { + type Output = Vec3; + + fn sample_interior(&self, rng: &mut R) -> Self::Output { + sample_triangle_interior(self.vertices, rng) + } + + fn sample_boundary(&self, rng: &mut R) -> Self::Output { + sample_triangle_boundary(self.vertices, rng) + } +} + impl ShapeSample for Cylinder { type Output = Vec3; diff --git a/examples/animation/color_animation.rs b/examples/animation/color_animation.rs index 605e8f395e..48c5ecc25f 100644 --- a/examples/animation/color_animation.rs +++ b/examples/animation/color_animation.rs @@ -1,10 +1,10 @@ //! Demonstrates how to animate colors in different color spaces using mixing and splines. -use bevy::{math::cubic_splines::Point, prelude::*}; +use bevy::{math::VectorSpace, prelude::*}; // We define this trait so we can reuse the same code for multiple color types that may be implemented using curves. -trait CurveColor: Point + Into + Send + Sync + 'static {} -impl + Send + Sync + 'static> CurveColor for T {} +trait CurveColor: VectorSpace + Into + Send + Sync + 'static {} +impl + Send + Sync + 'static> CurveColor for T {} // We define this trait so we can reuse the same code for multiple color types that may be implemented using mixing. trait MixedColor: Mix + Into + Send + Sync + 'static {}