mirror of
https://github.com/bevyengine/bevy
synced 2024-11-26 22:50:19 +00:00
f924b4d9ef
# 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<f32, Output = Self> + Div<f32, Output = Self> + Add<Self, Output = Self> + Sub<Self, Output = Self> + 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<Self, NonNormalizableError> { 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<T> 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 <zac@harrold.com.au> |
||
---|---|---|
.. | ||
crates/gen_tests | ||
docs | ||
src | ||
Cargo.toml |