mirror of
https://github.com/bevyengine/bevy
synced 2024-11-24 21:53:07 +00:00
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<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>
This commit is contained in:
parent
760c645de1
commit
f924b4d9ef
11 changed files with 327 additions and 81 deletions
|
@ -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.
|
||||
|
|
|
@ -164,7 +164,7 @@ where
|
|||
{
|
||||
}
|
||||
|
||||
macro_rules! impl_componentwise_point {
|
||||
macro_rules! impl_componentwise_vector_space {
|
||||
($ty: ident, [$($element: ident),+]) => {
|
||||
impl std::ops::Add<Self> 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<Self> 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;
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
180
crates/bevy_math/src/common_traits.rs
Normal file
180
crates/bevy_math/src/common_traits.rs
Normal file
|
@ -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<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
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
|
@ -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<f32, Output = Self>
|
||||
+ Div<f32, Output = Self>
|
||||
+ Add<Self, Output = Self>
|
||||
+ Sub<Self, Output = Self>
|
||||
+ 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<P: Point> {
|
||||
pub struct CubicBezier<P: VectorSpace> {
|
||||
control_points: Vec<[P; 4]>,
|
||||
}
|
||||
|
||||
impl<P: Point> CubicBezier<P> {
|
||||
impl<P: VectorSpace> CubicBezier<P> {
|
||||
/// Create a new cubic Bezier curve from sets of control points.
|
||||
pub fn new(control_points: impl Into<Vec<[P; 4]>>) -> Self {
|
||||
Self {
|
||||
|
@ -76,7 +52,7 @@ impl<P: Point> CubicBezier<P> {
|
|||
}
|
||||
}
|
||||
}
|
||||
impl<P: Point> CubicGenerator<P> for CubicBezier<P> {
|
||||
impl<P: VectorSpace> CubicGenerator<P> for CubicBezier<P> {
|
||||
#[inline]
|
||||
fn to_curve(&self) -> CubicCurve<P> {
|
||||
// A derivation for this matrix can be found in "General Matrix Representations for B-splines" by Kaihuai Qin.
|
||||
|
@ -135,10 +111,10 @@ impl<P: Point> CubicGenerator<P> for CubicBezier<P> {
|
|||
/// let hermite = CubicHermite::new(points, tangents).to_curve();
|
||||
/// let positions: Vec<_> = hermite.iter_positions(100).collect();
|
||||
/// ```
|
||||
pub struct CubicHermite<P: Point> {
|
||||
pub struct CubicHermite<P: VectorSpace> {
|
||||
control_points: Vec<(P, P)>,
|
||||
}
|
||||
impl<P: Point> CubicHermite<P> {
|
||||
impl<P: VectorSpace> CubicHermite<P> {
|
||||
/// Create a new Hermite curve from sets of control points.
|
||||
pub fn new(
|
||||
control_points: impl IntoIterator<Item = P>,
|
||||
|
@ -149,7 +125,7 @@ impl<P: Point> CubicHermite<P> {
|
|||
}
|
||||
}
|
||||
}
|
||||
impl<P: Point> CubicGenerator<P> for CubicHermite<P> {
|
||||
impl<P: VectorSpace> CubicGenerator<P> for CubicHermite<P> {
|
||||
#[inline]
|
||||
fn to_curve(&self) -> CubicCurve<P> {
|
||||
let char_matrix = [
|
||||
|
@ -201,12 +177,12 @@ impl<P: Point> CubicGenerator<P> for CubicHermite<P> {
|
|||
/// let cardinal = CubicCardinalSpline::new(0.3, points).to_curve();
|
||||
/// let positions: Vec<_> = cardinal.iter_positions(100).collect();
|
||||
/// ```
|
||||
pub struct CubicCardinalSpline<P: Point> {
|
||||
pub struct CubicCardinalSpline<P: VectorSpace> {
|
||||
tension: f32,
|
||||
control_points: Vec<P>,
|
||||
}
|
||||
|
||||
impl<P: Point> CubicCardinalSpline<P> {
|
||||
impl<P: VectorSpace> CubicCardinalSpline<P> {
|
||||
/// Build a new Cardinal spline.
|
||||
pub fn new(tension: f32, control_points: impl Into<Vec<P>>) -> Self {
|
||||
Self {
|
||||
|
@ -223,7 +199,7 @@ impl<P: Point> CubicCardinalSpline<P> {
|
|||
}
|
||||
}
|
||||
}
|
||||
impl<P: Point> CubicGenerator<P> for CubicCardinalSpline<P> {
|
||||
impl<P: VectorSpace> CubicGenerator<P> for CubicCardinalSpline<P> {
|
||||
#[inline]
|
||||
fn to_curve(&self) -> CubicCurve<P> {
|
||||
let s = self.tension;
|
||||
|
@ -288,10 +264,10 @@ impl<P: Point> CubicGenerator<P> for CubicCardinalSpline<P> {
|
|||
/// let b_spline = CubicBSpline::new(points).to_curve();
|
||||
/// let positions: Vec<_> = b_spline.iter_positions(100).collect();
|
||||
/// ```
|
||||
pub struct CubicBSpline<P: Point> {
|
||||
pub struct CubicBSpline<P: VectorSpace> {
|
||||
control_points: Vec<P>,
|
||||
}
|
||||
impl<P: Point> CubicBSpline<P> {
|
||||
impl<P: VectorSpace> CubicBSpline<P> {
|
||||
/// Build a new B-Spline.
|
||||
pub fn new(control_points: impl Into<Vec<P>>) -> Self {
|
||||
Self {
|
||||
|
@ -299,7 +275,7 @@ impl<P: Point> CubicBSpline<P> {
|
|||
}
|
||||
}
|
||||
}
|
||||
impl<P: Point> CubicGenerator<P> for CubicBSpline<P> {
|
||||
impl<P: VectorSpace> CubicGenerator<P> for CubicBSpline<P> {
|
||||
#[inline]
|
||||
fn to_curve(&self) -> CubicCurve<P> {
|
||||
// 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<P: Point> {
|
||||
pub struct CubicNurbs<P: VectorSpace> {
|
||||
control_points: Vec<P>,
|
||||
weights: Vec<f32>,
|
||||
knots: Vec<f32>,
|
||||
}
|
||||
impl<P: Point> CubicNurbs<P> {
|
||||
impl<P: VectorSpace> CubicNurbs<P> {
|
||||
/// 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<P: Point> CubicNurbs<P> {
|
|||
]
|
||||
}
|
||||
}
|
||||
impl<P: Point> RationalGenerator<P> for CubicNurbs<P> {
|
||||
impl<P: VectorSpace> RationalGenerator<P> for CubicNurbs<P> {
|
||||
#[inline]
|
||||
fn to_curve(&self) -> RationalCurve<P> {
|
||||
let segments = self
|
||||
|
@ -609,10 +585,10 @@ impl<P: Point> RationalGenerator<P> for CubicNurbs<P> {
|
|||
///
|
||||
/// ### Continuity
|
||||
/// The curve is C0 continuous, meaning it has no holes or jumps.
|
||||
pub struct LinearSpline<P: Point> {
|
||||
pub struct LinearSpline<P: VectorSpace> {
|
||||
points: Vec<P>,
|
||||
}
|
||||
impl<P: Point> LinearSpline<P> {
|
||||
impl<P: VectorSpace> LinearSpline<P> {
|
||||
/// Create a new linear spline
|
||||
pub fn new(points: impl Into<Vec<P>>) -> Self {
|
||||
Self {
|
||||
|
@ -620,7 +596,7 @@ impl<P: Point> LinearSpline<P> {
|
|||
}
|
||||
}
|
||||
}
|
||||
impl<P: Point> CubicGenerator<P> for LinearSpline<P> {
|
||||
impl<P: VectorSpace> CubicGenerator<P> for LinearSpline<P> {
|
||||
#[inline]
|
||||
fn to_curve(&self) -> CubicCurve<P> {
|
||||
let segments = self
|
||||
|
@ -639,7 +615,7 @@ impl<P: Point> CubicGenerator<P> for LinearSpline<P> {
|
|||
}
|
||||
|
||||
/// Implement this on cubic splines that can generate a cubic curve from their spline parameters.
|
||||
pub trait CubicGenerator<P: Point> {
|
||||
pub trait CubicGenerator<P: VectorSpace> {
|
||||
/// Build a [`CubicCurve`] by computing the interpolation coefficients for each curve segment.
|
||||
fn to_curve(&self) -> CubicCurve<P>;
|
||||
}
|
||||
|
@ -649,11 +625,11 @@ pub trait CubicGenerator<P: Point> {
|
|||
///
|
||||
/// Segments can be chained together to form a longer compound curve.
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
pub struct CubicSegment<P: Point> {
|
||||
pub struct CubicSegment<P: VectorSpace> {
|
||||
coeff: [P; 4],
|
||||
}
|
||||
|
||||
impl<P: Point> CubicSegment<P> {
|
||||
impl<P: VectorSpace> CubicSegment<P> {
|
||||
/// Instantaneous position of a point at parametric value `t`.
|
||||
#[inline]
|
||||
pub fn position(&self, t: f32) -> P {
|
||||
|
@ -807,11 +783,11 @@ impl CubicSegment<Vec2> {
|
|||
/// Use any struct that implements the [`CubicGenerator`] trait to create a new curve, such as
|
||||
/// [`CubicBezier`].
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct CubicCurve<P: Point> {
|
||||
pub struct CubicCurve<P: VectorSpace> {
|
||||
segments: Vec<CubicSegment<P>>,
|
||||
}
|
||||
|
||||
impl<P: Point> CubicCurve<P> {
|
||||
impl<P: VectorSpace> CubicCurve<P> {
|
||||
/// 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<P: Point> CubicCurve<P> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<P: Point> Extend<CubicSegment<P>> for CubicCurve<P> {
|
||||
impl<P: VectorSpace> Extend<CubicSegment<P>> for CubicCurve<P> {
|
||||
fn extend<T: IntoIterator<Item = CubicSegment<P>>>(&mut self, iter: T) {
|
||||
self.segments.extend(iter);
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Point> IntoIterator for CubicCurve<P> {
|
||||
impl<P: VectorSpace> IntoIterator for CubicCurve<P> {
|
||||
type IntoIter = <Vec<CubicSegment<P>> as IntoIterator>::IntoIter;
|
||||
|
||||
type Item = CubicSegment<P>;
|
||||
|
@ -929,7 +905,7 @@ impl<P: Point> IntoIterator for CubicCurve<P> {
|
|||
}
|
||||
|
||||
/// Implement this on cubic splines that can generate a rational cubic curve from their spline parameters.
|
||||
pub trait RationalGenerator<P: Point> {
|
||||
pub trait RationalGenerator<P: VectorSpace> {
|
||||
/// Build a [`RationalCurve`] by computing the interpolation coefficients for each curve segment.
|
||||
fn to_curve(&self) -> RationalCurve<P>;
|
||||
}
|
||||
|
@ -939,7 +915,7 @@ pub trait RationalGenerator<P: Point> {
|
|||
///
|
||||
/// Segments can be chained together to form a longer compound curve.
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
pub struct RationalSegment<P: Point> {
|
||||
pub struct RationalSegment<P: VectorSpace> {
|
||||
/// The coefficients matrix of the cubic curve.
|
||||
coeff: [P; 4],
|
||||
/// The homogeneous weight coefficients.
|
||||
|
@ -948,7 +924,7 @@ pub struct RationalSegment<P: Point> {
|
|||
knot_span: f32,
|
||||
}
|
||||
|
||||
impl<P: Point> RationalSegment<P> {
|
||||
impl<P: VectorSpace> RationalSegment<P> {
|
||||
/// 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<P: Point> RationalSegment<P> {
|
|||
/// 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<P: Point> {
|
||||
pub struct RationalCurve<P: VectorSpace> {
|
||||
segments: Vec<RationalSegment<P>>,
|
||||
}
|
||||
|
||||
impl<P: Point> RationalCurve<P> {
|
||||
impl<P: VectorSpace> RationalCurve<P> {
|
||||
/// 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<P: Point> RationalCurve<P> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<P: Point> Extend<RationalSegment<P>> for RationalCurve<P> {
|
||||
impl<P: VectorSpace> Extend<RationalSegment<P>> for RationalCurve<P> {
|
||||
fn extend<T: IntoIterator<Item = RationalSegment<P>>>(&mut self, iter: T) {
|
||||
self.segments.extend(iter);
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Point> IntoIterator for RationalCurve<P> {
|
||||
impl<P: VectorSpace> IntoIterator for RationalCurve<P> {
|
||||
type IntoIter = <Vec<RationalSegment<P>> as IntoIterator>::IntoIter;
|
||||
|
||||
type Item = RationalSegment<P>;
|
||||
|
@ -1206,7 +1182,7 @@ impl<P: Point> IntoIterator for RationalCurve<P> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<P: Point> From<CubicSegment<P>> for RationalSegment<P> {
|
||||
impl<P: VectorSpace> From<CubicSegment<P>> for RationalSegment<P> {
|
||||
fn from(value: CubicSegment<P>) -> Self {
|
||||
Self {
|
||||
coeff: value.coeff,
|
||||
|
@ -1216,7 +1192,7 @@ impl<P: Point> From<CubicSegment<P>> for RationalSegment<P> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<P: Point> From<CubicCurve<P>> for RationalCurve<P> {
|
||||
impl<P: VectorSpace> From<CubicCurve<P>> for RationalCurve<P> {
|
||||
fn from(value: CubicCurve<P>) -> Self {
|
||||
Self {
|
||||
segments: value.segments.into_iter().map(Into::into).collect(),
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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<P: NormedVectorSpace, R: Rng + ?Sized>(
|
||||
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<P: NormedVectorSpace, R: Rng + ?Sized>(
|
||||
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<R: Rng + ?Sized>(&self, rng: &mut R) -> Self::Output {
|
||||
sample_triangle_interior(self.vertices, rng)
|
||||
}
|
||||
|
||||
fn sample_boundary<R: Rng + ?Sized>(&self, rng: &mut R) -> Self::Output {
|
||||
sample_triangle_boundary(self.vertices, rng)
|
||||
}
|
||||
}
|
||||
|
||||
impl ShapeSample for Triangle3d {
|
||||
type Output = Vec3;
|
||||
|
||||
fn sample_interior<R: Rng + ?Sized>(&self, rng: &mut R) -> Self::Output {
|
||||
sample_triangle_interior(self.vertices, rng)
|
||||
}
|
||||
|
||||
fn sample_boundary<R: Rng + ?Sized>(&self, rng: &mut R) -> Self::Output {
|
||||
sample_triangle_boundary(self.vertices, rng)
|
||||
}
|
||||
}
|
||||
|
||||
impl ShapeSample for Cylinder {
|
||||
type Output = Vec3;
|
||||
|
||||
|
|
|
@ -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<Color> + Send + Sync + 'static {}
|
||||
impl<T: Point + Into<Color> + Send + Sync + 'static> CurveColor for T {}
|
||||
trait CurveColor: VectorSpace + Into<Color> + Send + Sync + 'static {}
|
||||
impl<T: VectorSpace + Into<Color> + 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<Color> + Send + Sync + 'static {}
|
||||
|
|
Loading…
Reference in a new issue