mirror of
https://github.com/bevyengine/bevy
synced 2024-12-21 02:23:08 +00:00
Derivative access patterns for curves (#16503)
# Objective - For curves that also include derivatives, make accessing derivative information via the `Curve` API ergonomic: that is, provide access to a curve that also samples derivative information. - Implement this functionality for cubic spline curves provided by `bevy_math`. Ultimately, this is to serve the purpose of doing more geometric operations on curves, like reparametrization by arclength and the construction of moving frames. ## Solution This has several parts, some of which may seem redundant. However, care has been put into this to satisfy the following constraints: - Accessing a `Curve` that samples derivative information should be not just possible but easy and non-error-prone. For example, given a differentiable `Curve<Vec2>`, one should be able to access something like a `Curve<(Vec2, Vec2)>` ergonomically, and not just sample the derivatives piecemeal from point to point. - Derivative access should not step on the toes of ordinary curve usage. In particular, in the above scenario, we want to avoid simply making the same curve both a `Curve<Vec2>` and a `Curve<(Vec2, Vec2)>` because this requires manual disambiguation when the API is used. - Derivative access must work gracefully in both owned and borrowed contexts. ### `HasTangent` We introduce a trait `HasTangent` that provides an associated `Tangent` type for types that have tangent spaces: ```rust pub trait HasTangent { /// The tangent type. type Tangent: VectorSpace; } ``` (Mathematically speaking, it would be more precise to say that these are types that represent spaces which are canonically [parallelized](https://en.wikipedia.org/wiki/Parallelizable_manifold). ) The idea here is that a point moving through a `HasTangent` type may have a derivative valued in the associated `Tangent` type at each time in its journey. We reify this with a `WithDerivative<T>` type that uses `HasTangent` to include derivative information: ```rust pub struct WithDerivative<T> where T: HasTangent, { /// The underlying value. pub value: T, /// The derivative at `value`. pub derivative: T::Tangent, } ``` And we can play the same game with second derivatives as well, since every `VectorSpace` type is `HasTangent` where `Tangent` is itself (we may want to be more restrictive with this in practice, but this holds mathematically). ```rust pub struct WithTwoDerivatives<T> where T: HasTangent, { /// The underlying value. pub value: T, /// The derivative at `value`. pub derivative: T::Tangent, /// The second derivative at `value`. pub second_derivative: <T::Tangent as HasTangent>::Tangent, } ``` In this PR, `HasTangent` is only implemented for `VectorSpace` types, but it would be valuable to have this implementation for types like `Rot2` and `Quat` as well. We could also do it for the isometry types and, potentially, transforms as well. (This is in decreasing order of value in my opinion.) ### `CurveWithDerivative` This is a trait for a `Curve<T>` which allows the construction of a `Curve<WithDerivative<T>>` when derivative information is known intrinsically. It looks like this: ```rust /// Trait for curves that have a well-defined notion of derivative, allowing for /// derivatives to be extracted along with values. pub trait CurveWithDerivative<T> where T: HasTangent, { /// This curve, but with its first derivative included in sampling. fn with_derivative(self) -> impl Curve<WithDerivative<T>>; } ``` The idea here is to provide patterns like this: ```rust let value_and_derivative = my_curve.with_derivative().sample_clamped(t); ``` One of the main points here is that `Curve<WithDerivative<T>>` is useful as an output because it can be used durably. For example, in a dynamic context, something that needs curves with derivatives can store something like a `Box<dyn Curve<WithDerivative<T>>>`. Note that `CurveWithDerivative` is not dyn-compatible. ### `SampleDerivative` Many curves "know" how to sample their derivatives instrinsically, but implementing `CurveWithDerivative` as given would be onerous or require an annoying amount of boilerplate. There are also hurdles to overcome that involve references to curves: for the `Curve` API, the expectation is that curve transformations like `with_derivative` take things by value, with the contract that they can still be used by reference through deref-magic by including `by_ref` in a method chain. These problems are solved simultaneously by a trait `SampleDerivative` which, when implemented, automatically derives `CurveWithDerivative` for a type and all types that dereference to it. It just looks like this: ```rust pub trait SampleDerivative<T>: Curve<T> where T: HasTangent, { fn sample_with_derivative_unchecked(&self, t: f32) -> WithDerivative<T>; // ... other sampling variants as default methods } ``` The point is that the output of `with_derivative` is a `Curve<WithDerivative<T>>` that uses the `SampleDerivative` implementation. On a `SampleDerivative` type, you can also just call `my_curve.sample_with_derivative(t)` instead of something like `my_curve.by_ref().with_derivative().sample(t)`, which is more verbose and less accessible. In practice, `CurveWithDerivative<T>` is actually a "sealed" extension trait of `SampleDerivative<T>`. ## Adaptors `SampleDerivative` has automatic implementations on all curve adaptors except for `FunctionCurve`, `MapCurve`, and `ReparamCurve` (because we do not have a notion of differentiable Rust functions). For example, `CurveReparamCurve` (the reparametrization of a curve by another curve) can compute derivatives using the chain rule in the case both its constituents have them. ## Testing Tests for derivatives on the curve adaptors are included. --- ## Showcase This development allows derivative information to be included with and extracted from curves using the `Curve` API. ```rust let points = [ vec2(-1.0, -20.0), vec2(3.0, 2.0), vec2(5.0, 3.0), vec2(9.0, 8.0), ]; // A cubic spline curve that goes through `points`. let curve = CubicCardinalSpline::new(0.3, points).to_curve().unwrap(); // Calling `with_derivative` causes derivative output to be included in the output of the curve API. let curve_with_derivative = curve.with_derivative(); // A `Curve<f32>` that outputs the speed of the original. let speed_curve = curve_with_derivative.map(|x| x.derivative.norm()); ``` --- ## Questions - ~~Maybe we should seal `WithDerivative` or make it require `SampleDerivative` (i.e. make it unimplementable except through `SampleDerivative`).~~ I decided this is a good idea. - ~~Unclear whether `VectorSpace: HasTangent` blanket implementation is really appropriate. For colors, for example, I'm not sure that the derivative values can really be interpreted as a color. In any case, it should still remain the case that `VectorSpace` types are `HasTangent` and that `HasTangent::Tangent: HasTangent`.~~ I think this is fine. - Infinity bikeshed on names of traits and things. ## Future - Faster implementations of `SampleDerivative` for cubic spline curves. - Improve ergonomics for accessing only derivatives (and other kinds of transformations on derivative curves). - Implement `HasTangent` for: - `Rot2`/`Quat` - `Isometry` types - `Transform`, maybe - Implement derivatives for easing curves. - Marker traits for continuous/differentiable curves. (It's actually unclear to me how much value this has in practice, but we have discussed it in the past.) --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
This commit is contained in:
parent
711246aa34
commit
c60dcea231
8 changed files with 1243 additions and 96 deletions
|
@ -30,7 +30,7 @@ pub trait VectorSpace:
|
|||
+ Div<f32, Output = Self>
|
||||
+ Add<Self, Output = Self>
|
||||
+ Sub<Self, Output = Self>
|
||||
+ Neg
|
||||
+ Neg<Output = Self>
|
||||
+ Default
|
||||
+ Debug
|
||||
+ Clone
|
||||
|
@ -71,6 +71,89 @@ impl VectorSpace for f32 {
|
|||
const ZERO: Self = 0.0;
|
||||
}
|
||||
|
||||
/// A type consisting of formal sums of elements from `V` and `W`. That is,
|
||||
/// each value `Sum(v, w)` is thought of as `v + w`, with no available
|
||||
/// simplification. In particular, if `V` and `W` are [vector spaces], then
|
||||
/// `Sum<V, W>` is a vector space whose dimension is the sum of those of `V`
|
||||
/// and `W`, and the field accessors `.0` and `.1` are vector space projections.
|
||||
///
|
||||
/// [vector spaces]: VectorSpace
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Sum<V, W>(pub V, pub W);
|
||||
|
||||
impl<V, W> Mul<f32> for Sum<V, W>
|
||||
where
|
||||
V: VectorSpace,
|
||||
W: VectorSpace,
|
||||
{
|
||||
type Output = Self;
|
||||
fn mul(self, rhs: f32) -> Self::Output {
|
||||
Sum(self.0 * rhs, self.1 * rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl<V, W> Div<f32> for Sum<V, W>
|
||||
where
|
||||
V: VectorSpace,
|
||||
W: VectorSpace,
|
||||
{
|
||||
type Output = Self;
|
||||
fn div(self, rhs: f32) -> Self::Output {
|
||||
Sum(self.0 / rhs, self.1 / rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl<V, W> Add<Self> for Sum<V, W>
|
||||
where
|
||||
V: VectorSpace,
|
||||
W: VectorSpace,
|
||||
{
|
||||
type Output = Self;
|
||||
fn add(self, other: Self) -> Self::Output {
|
||||
Sum(self.0 + other.0, self.1 + other.1)
|
||||
}
|
||||
}
|
||||
|
||||
impl<V, W> Sub<Self> for Sum<V, W>
|
||||
where
|
||||
V: VectorSpace,
|
||||
W: VectorSpace,
|
||||
{
|
||||
type Output = Self;
|
||||
fn sub(self, other: Self) -> Self::Output {
|
||||
Sum(self.0 - other.0, self.1 - other.1)
|
||||
}
|
||||
}
|
||||
|
||||
impl<V, W> Neg for Sum<V, W>
|
||||
where
|
||||
V: VectorSpace,
|
||||
W: VectorSpace,
|
||||
{
|
||||
type Output = Self;
|
||||
fn neg(self) -> Self::Output {
|
||||
Sum(-self.0, -self.1)
|
||||
}
|
||||
}
|
||||
|
||||
impl<V, W> Default for Sum<V, W>
|
||||
where
|
||||
V: VectorSpace,
|
||||
W: VectorSpace,
|
||||
{
|
||||
fn default() -> Self {
|
||||
Sum(V::default(), W::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<V, W> VectorSpace for Sum<V, W>
|
||||
where
|
||||
V: VectorSpace,
|
||||
W: VectorSpace,
|
||||
{
|
||||
const ZERO: Self = Sum(V::ZERO, W::ZERO);
|
||||
}
|
||||
|
||||
/// 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:
|
||||
|
@ -410,3 +493,48 @@ impl_stable_interpolate_tuple!(
|
|||
(T9, 9),
|
||||
(T10, 10)
|
||||
);
|
||||
|
||||
/// A type that has tangents.
|
||||
pub trait HasTangent {
|
||||
/// The tangent type.
|
||||
type Tangent: VectorSpace;
|
||||
}
|
||||
|
||||
/// A value with its derivative.
|
||||
pub struct WithDerivative<T>
|
||||
where
|
||||
T: HasTangent,
|
||||
{
|
||||
/// The underlying value.
|
||||
pub value: T,
|
||||
|
||||
/// The derivative at `value`.
|
||||
pub derivative: T::Tangent,
|
||||
}
|
||||
|
||||
/// A value together with its first and second derivatives.
|
||||
pub struct WithTwoDerivatives<T>
|
||||
where
|
||||
T: HasTangent,
|
||||
{
|
||||
/// The underlying value.
|
||||
pub value: T,
|
||||
|
||||
/// The derivative at `value`.
|
||||
pub derivative: T::Tangent,
|
||||
|
||||
/// The second derivative at `value`.
|
||||
pub second_derivative: <T::Tangent as HasTangent>::Tangent,
|
||||
}
|
||||
|
||||
impl<V: VectorSpace> HasTangent for V {
|
||||
type Tangent = V;
|
||||
}
|
||||
|
||||
impl<M, N> HasTangent for (M, N)
|
||||
where
|
||||
M: HasTangent,
|
||||
N: HasTangent,
|
||||
{
|
||||
type Tangent = Sum<M::Tangent, N::Tangent>;
|
||||
}
|
||||
|
|
159
crates/bevy_math/src/cubic_splines/curve_impls.rs
Normal file
159
crates/bevy_math/src/cubic_splines/curve_impls.rs
Normal file
|
@ -0,0 +1,159 @@
|
|||
use super::{CubicSegment, RationalSegment};
|
||||
use crate::common_traits::{VectorSpace, WithDerivative, WithTwoDerivatives};
|
||||
use crate::curve::{
|
||||
derivatives::{SampleDerivative, SampleTwoDerivatives},
|
||||
Curve, Interval,
|
||||
};
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
use super::{CubicCurve, RationalCurve};
|
||||
|
||||
// -- CubicSegment
|
||||
|
||||
impl<P: VectorSpace> Curve<P> for CubicSegment<P> {
|
||||
#[inline]
|
||||
fn domain(&self) -> Interval {
|
||||
Interval::UNIT
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sample_unchecked(&self, t: f32) -> P {
|
||||
self.position(t)
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: VectorSpace> SampleDerivative<P> for CubicSegment<P> {
|
||||
#[inline]
|
||||
fn sample_with_derivative_unchecked(&self, t: f32) -> WithDerivative<P> {
|
||||
WithDerivative {
|
||||
value: self.position(t),
|
||||
derivative: self.velocity(t),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: VectorSpace> SampleTwoDerivatives<P> for CubicSegment<P> {
|
||||
#[inline]
|
||||
fn sample_with_two_derivatives_unchecked(&self, t: f32) -> WithTwoDerivatives<P> {
|
||||
WithTwoDerivatives {
|
||||
value: self.position(t),
|
||||
derivative: self.velocity(t),
|
||||
second_derivative: self.acceleration(t),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -- CubicCurve
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
impl<P: VectorSpace> Curve<P> for CubicCurve<P> {
|
||||
#[inline]
|
||||
fn domain(&self) -> Interval {
|
||||
// The non-emptiness invariant guarantees that this succeeds.
|
||||
Interval::new(0.0, self.segments.len() as f32)
|
||||
.expect("CubicCurve is invalid because it has no segments")
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sample_unchecked(&self, t: f32) -> P {
|
||||
self.position(t)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
impl<P: VectorSpace> SampleDerivative<P> for CubicCurve<P> {
|
||||
#[inline]
|
||||
fn sample_with_derivative_unchecked(&self, t: f32) -> WithDerivative<P> {
|
||||
WithDerivative {
|
||||
value: self.position(t),
|
||||
derivative: self.velocity(t),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
impl<P: VectorSpace> SampleTwoDerivatives<P> for CubicCurve<P> {
|
||||
#[inline]
|
||||
fn sample_with_two_derivatives_unchecked(&self, t: f32) -> WithTwoDerivatives<P> {
|
||||
WithTwoDerivatives {
|
||||
value: self.position(t),
|
||||
derivative: self.velocity(t),
|
||||
second_derivative: self.acceleration(t),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -- RationalSegment
|
||||
|
||||
impl<P: VectorSpace> Curve<P> for RationalSegment<P> {
|
||||
#[inline]
|
||||
fn domain(&self) -> Interval {
|
||||
Interval::UNIT
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sample_unchecked(&self, t: f32) -> P {
|
||||
self.position(t)
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: VectorSpace> SampleDerivative<P> for RationalSegment<P> {
|
||||
#[inline]
|
||||
fn sample_with_derivative_unchecked(&self, t: f32) -> WithDerivative<P> {
|
||||
WithDerivative {
|
||||
value: self.position(t),
|
||||
derivative: self.velocity(t),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: VectorSpace> SampleTwoDerivatives<P> for RationalSegment<P> {
|
||||
#[inline]
|
||||
fn sample_with_two_derivatives_unchecked(&self, t: f32) -> WithTwoDerivatives<P> {
|
||||
WithTwoDerivatives {
|
||||
value: self.position(t),
|
||||
derivative: self.velocity(t),
|
||||
second_derivative: self.acceleration(t),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -- RationalCurve
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
impl<P: VectorSpace> Curve<P> for RationalCurve<P> {
|
||||
#[inline]
|
||||
fn domain(&self) -> Interval {
|
||||
// The non-emptiness invariant guarantees the success of this.
|
||||
Interval::new(0.0, self.length())
|
||||
.expect("RationalCurve is invalid because it has zero length")
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sample_unchecked(&self, t: f32) -> P {
|
||||
self.position(t)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
impl<P: VectorSpace> SampleDerivative<P> for RationalCurve<P> {
|
||||
#[inline]
|
||||
fn sample_with_derivative_unchecked(&self, t: f32) -> WithDerivative<P> {
|
||||
WithDerivative {
|
||||
value: self.position(t),
|
||||
derivative: self.velocity(t),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
impl<P: VectorSpace> SampleTwoDerivatives<P> for RationalCurve<P> {
|
||||
#[inline]
|
||||
fn sample_with_two_derivatives_unchecked(&self, t: f32) -> WithTwoDerivatives<P> {
|
||||
WithTwoDerivatives {
|
||||
value: self.position(t),
|
||||
derivative: self.velocity(t),
|
||||
second_derivative: self.acceleration(t),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,22 +1,16 @@
|
|||
//! Provides types for building cubic splines for rendering curves and use with animation easing.
|
||||
|
||||
use core::fmt::Debug;
|
||||
|
||||
#[cfg(feature = "curve")]
|
||||
mod curve_impls;
|
||||
use crate::{
|
||||
ops::{self, FloatPow},
|
||||
Vec2, VectorSpace,
|
||||
};
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
use {alloc::vec, alloc::vec::Vec, core::iter::once, itertools::Itertools};
|
||||
|
||||
#[cfg(feature = "curve")]
|
||||
use crate::curve::{Curve, Interval};
|
||||
|
||||
#[cfg(feature = "bevy_reflect")]
|
||||
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
|
||||
use thiserror::Error;
|
||||
#[cfg(feature = "alloc")]
|
||||
use {alloc::vec, alloc::vec::Vec, core::iter::once, itertools::Itertools};
|
||||
|
||||
/// A spline composed of a single cubic Bezier curve.
|
||||
///
|
||||
|
@ -24,15 +18,18 @@ use bevy_reflect::{std_traits::ReflectDefault, Reflect};
|
|||
/// [`CubicSegment::new_bezier`] for use in easing.
|
||||
///
|
||||
/// ### Interpolation
|
||||
///
|
||||
/// The curve only passes through the first and last control point in each set of four points. The curve
|
||||
/// is divided into "segments" by every fourth control point.
|
||||
///
|
||||
/// ### Tangency
|
||||
///
|
||||
/// Tangents are manually defined by the two intermediate control points within each set of four points.
|
||||
/// You can think of the control points the curve passes through as "anchors", and as the intermediate
|
||||
/// control points as the anchors displaced along their tangent vectors
|
||||
///
|
||||
/// ### Continuity
|
||||
///
|
||||
/// A Bezier curve is at minimum C0 continuous, meaning it has no holes or jumps. Each curve segment is
|
||||
/// C2, meaning the tangent vector changes smoothly between each set of four control points, but this
|
||||
/// doesn't hold at the control points between segments. Making the whole curve C1 or C2 requires moving
|
||||
|
@ -52,8 +49,8 @@ use bevy_reflect::{std_traits::ReflectDefault, Reflect};
|
|||
/// let bezier = CubicBezier::new(points).to_curve().unwrap();
|
||||
/// let positions: Vec<_> = bezier.iter_positions(100).collect();
|
||||
/// ```
|
||||
#[cfg(feature = "alloc")]
|
||||
#[derive(Clone, Debug)]
|
||||
#[cfg(feature = "alloc")]
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug))]
|
||||
pub struct CubicBezier<P: VectorSpace> {
|
||||
/// The control points of the Bezier curve.
|
||||
|
@ -99,7 +96,6 @@ impl<P: VectorSpace> CubicGenerator<P> for CubicBezier<P> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An error returned during cubic curve generation for cubic Bezier curves indicating that a
|
||||
/// segment of control points was not present.
|
||||
#[derive(Clone, Debug, Error)]
|
||||
|
@ -114,16 +110,20 @@ pub struct CubicBezierError;
|
|||
/// such as network prediction.
|
||||
///
|
||||
/// ### Interpolation
|
||||
///
|
||||
/// The curve passes through every control point.
|
||||
///
|
||||
/// ### Tangency
|
||||
///
|
||||
/// Tangents are explicitly defined at each control point.
|
||||
///
|
||||
/// ### Continuity
|
||||
///
|
||||
/// The curve is at minimum C1 continuous, meaning that it has no holes or jumps and the tangent vector also
|
||||
/// has no sudden jumps.
|
||||
///
|
||||
/// ### Parametrization
|
||||
///
|
||||
/// The first segment of the curve connects the first two control points, the second connects the second and
|
||||
/// third, and so on. This remains true when a cyclic curve is formed with [`to_curve_cyclic`], in which case
|
||||
/// the final curve segment connects the last control point to the first.
|
||||
|
@ -149,8 +149,8 @@ pub struct CubicBezierError;
|
|||
/// ```
|
||||
///
|
||||
/// [`to_curve_cyclic`]: CyclicCubicGenerator::to_curve_cyclic
|
||||
#[cfg(feature = "alloc")]
|
||||
#[derive(Clone, Debug)]
|
||||
#[cfg(feature = "alloc")]
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug))]
|
||||
pub struct CubicHermite<P: VectorSpace> {
|
||||
/// The control points of the Hermite curve.
|
||||
|
@ -245,16 +245,20 @@ impl<P: VectorSpace> CyclicCubicGenerator<P> for CubicHermite<P> {
|
|||
/// **Note** the Catmull-Rom spline is a special case of Cardinal spline where the tension is 0.5.
|
||||
///
|
||||
/// ### Interpolation
|
||||
///
|
||||
/// The curve passes through every control point.
|
||||
///
|
||||
/// ### Tangency
|
||||
///
|
||||
/// Tangents are automatically computed based on the positions of control points.
|
||||
///
|
||||
/// ### Continuity
|
||||
///
|
||||
/// The curve is at minimum C1, meaning that it is continuous (it has no holes or jumps), and its tangent
|
||||
/// vector is also well-defined everywhere, without sudden jumps.
|
||||
///
|
||||
/// ### Parametrization
|
||||
///
|
||||
/// The first segment of the curve connects the first two control points, the second connects the second and
|
||||
/// third, and so on. This remains true when a cyclic curve is formed with [`to_curve_cyclic`], in which case
|
||||
/// the final curve segment connects the last control point to the first.
|
||||
|
@ -274,8 +278,8 @@ impl<P: VectorSpace> CyclicCubicGenerator<P> for CubicHermite<P> {
|
|||
/// ```
|
||||
///
|
||||
/// [`to_curve_cyclic`]: CyclicCubicGenerator::to_curve_cyclic
|
||||
#[cfg(feature = "alloc")]
|
||||
#[derive(Clone, Debug)]
|
||||
#[cfg(feature = "alloc")]
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug))]
|
||||
pub struct CubicCardinalSpline<P: VectorSpace> {
|
||||
/// Tension
|
||||
|
@ -404,17 +408,20 @@ impl<P: VectorSpace> CyclicCubicGenerator<P> for CubicCardinalSpline<P> {
|
|||
/// necessarily pass through any of the control points.
|
||||
///
|
||||
/// ### Interpolation
|
||||
///
|
||||
/// The curve does not necessarily pass through its control points.
|
||||
///
|
||||
/// ### Tangency
|
||||
/// Tangents are automatically computed based on the positions of control points.
|
||||
///
|
||||
/// ### Continuity
|
||||
///
|
||||
/// The curve is C2 continuous, meaning it has no holes or jumps, the tangent vector changes smoothly along
|
||||
/// the entire curve, and the acceleration also varies continuously. The acceleration continuity of this
|
||||
/// spline makes it useful for camera paths.
|
||||
///
|
||||
/// ### Parametrization
|
||||
///
|
||||
/// Each curve segment is defined by a window of four control points taken in sequence. When [`to_curve_cyclic`]
|
||||
/// is used to form a cyclic curve, the three additional segments used to close the curve come last.
|
||||
///
|
||||
|
@ -433,14 +440,13 @@ impl<P: VectorSpace> CyclicCubicGenerator<P> for CubicCardinalSpline<P> {
|
|||
/// ```
|
||||
///
|
||||
/// [`to_curve_cyclic`]: CyclicCubicGenerator::to_curve_cyclic
|
||||
#[cfg(feature = "alloc")]
|
||||
#[derive(Clone, Debug)]
|
||||
#[cfg(feature = "alloc")]
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug))]
|
||||
pub struct CubicBSpline<P: VectorSpace> {
|
||||
/// The control points of the spline
|
||||
pub control_points: Vec<P>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
impl<P: VectorSpace> CubicBSpline<P> {
|
||||
/// Build a new B-Spline.
|
||||
|
@ -564,6 +570,7 @@ pub enum CubicNurbsError {
|
|||
/// represent a much more diverse class of curves (like perfect circles and ellipses).
|
||||
///
|
||||
/// ### Non-uniformity
|
||||
///
|
||||
/// The 'NU' part of NURBS stands for "Non-Uniform". This has to do with a parameter called 'knots'.
|
||||
/// The knots are a non-decreasing sequence of floating point numbers. The first and last three pairs of
|
||||
/// knots control the behavior of the curve as it approaches its endpoints. The intermediate pairs
|
||||
|
@ -572,16 +579,20 @@ pub enum CubicNurbsError {
|
|||
/// and can create sharp corners.
|
||||
///
|
||||
/// ### Rationality
|
||||
///
|
||||
/// The 'R' part of NURBS stands for "Rational". This has to do with NURBS allowing each control point to
|
||||
/// be assigned a weighting, which controls how much it affects the curve compared to the other points.
|
||||
///
|
||||
/// ### Interpolation
|
||||
///
|
||||
/// The curve will not pass through the control points except where a knot has multiplicity four.
|
||||
///
|
||||
/// ### Tangency
|
||||
///
|
||||
/// Tangents are automatically computed based on the position of control points.
|
||||
///
|
||||
/// ### Continuity
|
||||
///
|
||||
/// When there is no knot multiplicity, the curve is C2 continuous, meaning it has no holes or jumps and the
|
||||
/// tangent vector changes smoothly along the entire curve length. Like the [`CubicBSpline`], the acceleration
|
||||
/// continuity makes it useful for camera paths. Knot multiplicity of 2 in intermediate knots reduces the
|
||||
|
@ -606,8 +617,8 @@ pub enum CubicNurbsError {
|
|||
/// .unwrap();
|
||||
/// let positions: Vec<_> = nurbs.iter_positions(100).collect();
|
||||
/// ```
|
||||
#[cfg(feature = "alloc")]
|
||||
#[derive(Clone, Debug)]
|
||||
#[cfg(feature = "alloc")]
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug))]
|
||||
pub struct CubicNurbs<P: VectorSpace> {
|
||||
/// The control points of the NURBS
|
||||
|
@ -822,21 +833,25 @@ impl<P: VectorSpace> RationalGenerator<P> for CubicNurbs<P> {
|
|||
/// A spline interpolated linearly between the nearest 2 points.
|
||||
///
|
||||
/// ### Interpolation
|
||||
///
|
||||
/// The curve passes through every control point.
|
||||
///
|
||||
/// ### Tangency
|
||||
///
|
||||
/// The curve is not generally differentiable at control points.
|
||||
///
|
||||
/// ### Continuity
|
||||
///
|
||||
/// The curve is C0 continuous, meaning it has no holes or jumps.
|
||||
///
|
||||
/// ### Parametrization
|
||||
///
|
||||
/// Each curve segment connects two adjacent control points in sequence. When a cyclic curve is
|
||||
/// formed with [`to_curve_cyclic`], the final segment connects the last control point with the first.
|
||||
///
|
||||
/// [`to_curve_cyclic`]: CyclicCubicGenerator::to_curve_cyclic
|
||||
#[cfg(feature = "alloc")]
|
||||
#[derive(Clone, Debug)]
|
||||
#[cfg(feature = "alloc")]
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug))]
|
||||
pub struct LinearSpline<P: VectorSpace> {
|
||||
/// The control points of the linear spline.
|
||||
|
@ -945,6 +960,7 @@ pub trait CyclicCubicGenerator<P: VectorSpace> {
|
|||
/// Segments can be chained together to form a longer [compound curve].
|
||||
///
|
||||
/// [compound curve]: CubicCurve
|
||||
/// [`Curve`]: crate::curve::Curve
|
||||
#[derive(Copy, Clone, Debug, Default, PartialEq)]
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, Default))]
|
||||
|
@ -1108,26 +1124,15 @@ impl CubicSegment<Vec2> {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "curve")]
|
||||
impl<P: VectorSpace> Curve<P> for CubicSegment<P> {
|
||||
#[inline]
|
||||
fn domain(&self) -> Interval {
|
||||
Interval::UNIT
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sample_unchecked(&self, t: f32) -> P {
|
||||
self.position(t)
|
||||
}
|
||||
}
|
||||
|
||||
/// A collection of [`CubicSegment`]s chained into a single parametric curve. It is a [`Curve`]
|
||||
/// with domain `[0, N]`, where `N` is its number of segments.
|
||||
///
|
||||
/// Use any struct that implements the [`CubicGenerator`] trait to create a new curve, such as
|
||||
/// [`CubicBezier`].
|
||||
#[cfg(feature = "alloc")]
|
||||
///
|
||||
/// [`Curve`]: crate::curve::Curve
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[cfg(feature = "alloc")]
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug))]
|
||||
pub struct CubicCurve<P: VectorSpace> {
|
||||
|
@ -1248,21 +1253,6 @@ impl<P: VectorSpace> CubicCurve<P> {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "curve", feature = "alloc"))]
|
||||
impl<P: VectorSpace> Curve<P> for CubicCurve<P> {
|
||||
#[inline]
|
||||
fn domain(&self) -> Interval {
|
||||
// The non-emptiness invariant guarantees the success of this.
|
||||
Interval::new(0.0, self.segments.len() as f32)
|
||||
.expect("CubicCurve is invalid because it has no segments")
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sample_unchecked(&self, t: f32) -> P {
|
||||
self.position(t)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
impl<P: VectorSpace> Extend<CubicSegment<P>> for CubicCurve<P> {
|
||||
fn extend<T: IntoIterator<Item = CubicSegment<P>>>(&mut self, iter: T) {
|
||||
|
@ -1298,6 +1288,7 @@ pub trait RationalGenerator<P: VectorSpace> {
|
|||
/// together.
|
||||
///
|
||||
/// [compound curves]: RationalCurve
|
||||
/// [`Curve`]: crate::curve::Curve
|
||||
#[derive(Copy, Clone, Debug, Default, PartialEq)]
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, Default))]
|
||||
|
@ -1309,7 +1300,6 @@ pub struct RationalSegment<P: VectorSpace> {
|
|||
/// The width of the domain of this segment.
|
||||
pub knot_span: f32,
|
||||
}
|
||||
|
||||
impl<P: VectorSpace> RationalSegment<P> {
|
||||
/// Instantaneous position of a point at parametric value `t` in `[0, 1]`.
|
||||
#[inline]
|
||||
|
@ -1424,26 +1414,15 @@ impl<P: VectorSpace> RationalSegment<P> {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "curve")]
|
||||
impl<P: VectorSpace> Curve<P> for RationalSegment<P> {
|
||||
#[inline]
|
||||
fn domain(&self) -> Interval {
|
||||
Interval::UNIT
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sample_unchecked(&self, t: f32) -> P {
|
||||
self.position(t)
|
||||
}
|
||||
}
|
||||
|
||||
/// A collection of [`RationalSegment`]s chained into a single parametric curve. It is a [`Curve`]
|
||||
/// with domain `[0, N]`, where `N` is the number of segments.
|
||||
///
|
||||
/// Use any struct that implements the [`RationalGenerator`] trait to create a new curve, such as
|
||||
/// [`CubicNurbs`], or convert [`CubicCurve`] using `into/from`.
|
||||
#[cfg(feature = "alloc")]
|
||||
///
|
||||
/// [`Curve`]: crate::curve::Curve
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[cfg(feature = "alloc")]
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug))]
|
||||
pub struct RationalCurve<P: VectorSpace> {
|
||||
|
@ -1583,21 +1562,6 @@ impl<P: VectorSpace> RationalCurve<P> {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "curve", feature = "alloc"))]
|
||||
impl<P: VectorSpace> Curve<P> for RationalCurve<P> {
|
||||
#[inline]
|
||||
fn domain(&self) -> Interval {
|
||||
// The non-emptiness invariant guarantees the success of this.
|
||||
Interval::new(0.0, self.length())
|
||||
.expect("RationalCurve is invalid because it has zero length")
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sample_unchecked(&self, t: f32) -> P {
|
||||
self.position(t)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
impl<P: VectorSpace> Extend<RationalSegment<P>> for RationalCurve<P> {
|
||||
fn extend<T: IntoIterator<Item = RationalSegment<P>>>(&mut self, iter: T) {
|
|
@ -620,15 +620,25 @@ where
|
|||
|
||||
#[inline]
|
||||
fn sample_unchecked(&self, t: f32) -> T {
|
||||
let t = self.base_curve_sample_time(t);
|
||||
self.curve.sample_unchecked(t)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, C> RepeatCurve<T, C>
|
||||
where
|
||||
C: Curve<T>,
|
||||
{
|
||||
#[inline]
|
||||
pub(crate) fn base_curve_sample_time(&self, t: f32) -> f32 {
|
||||
// the domain is bounded by construction
|
||||
let d = self.curve.domain();
|
||||
let cyclic_t = ops::rem_euclid(t - d.start(), d.length());
|
||||
let t = if t != d.start() && cyclic_t == 0.0 {
|
||||
if t != d.start() && cyclic_t == 0.0 {
|
||||
d.end()
|
||||
} else {
|
||||
d.start() + cyclic_t
|
||||
};
|
||||
self.curve.sample_unchecked(t)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -668,15 +678,25 @@ where
|
|||
|
||||
#[inline]
|
||||
fn sample_unchecked(&self, t: f32) -> T {
|
||||
let t = self.base_curve_sample_time(t);
|
||||
self.curve.sample_unchecked(t)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, C> ForeverCurve<T, C>
|
||||
where
|
||||
C: Curve<T>,
|
||||
{
|
||||
#[inline]
|
||||
pub(crate) fn base_curve_sample_time(&self, t: f32) -> f32 {
|
||||
// the domain is bounded by construction
|
||||
let d = self.curve.domain();
|
||||
let cyclic_t = ops::rem_euclid(t - d.start(), d.length());
|
||||
let t = if t != d.start() && cyclic_t == 0.0 {
|
||||
if t != d.start() && cyclic_t == 0.0 {
|
||||
d.end()
|
||||
} else {
|
||||
d.start() + cyclic_t
|
||||
};
|
||||
self.curve.sample_unchecked(t)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
648
crates/bevy_math/src/curve/derivatives/adaptor_impls.rs
Normal file
648
crates/bevy_math/src/curve/derivatives/adaptor_impls.rs
Normal file
|
@ -0,0 +1,648 @@
|
|||
//! Implementations of derivatives on curve adaptors. These allow
|
||||
//! compositionality for derivatives.
|
||||
|
||||
use super::{SampleDerivative, SampleTwoDerivatives};
|
||||
use crate::common_traits::{HasTangent, Sum, VectorSpace, WithDerivative, WithTwoDerivatives};
|
||||
use crate::curve::{
|
||||
adaptors::{
|
||||
ChainCurve, ConstantCurve, ContinuationCurve, CurveReparamCurve, ForeverCurve, GraphCurve,
|
||||
LinearReparamCurve, PingPongCurve, RepeatCurve, ReverseCurve, ZipCurve,
|
||||
},
|
||||
Curve,
|
||||
};
|
||||
|
||||
// -- ConstantCurve
|
||||
|
||||
impl<T> SampleDerivative<T> for ConstantCurve<T>
|
||||
where
|
||||
T: HasTangent + Clone,
|
||||
{
|
||||
fn sample_with_derivative_unchecked(&self, _t: f32) -> WithDerivative<T> {
|
||||
WithDerivative {
|
||||
value: self.value.clone(),
|
||||
derivative: VectorSpace::ZERO,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> SampleTwoDerivatives<T> for ConstantCurve<T>
|
||||
where
|
||||
T: HasTangent + Clone,
|
||||
{
|
||||
fn sample_with_two_derivatives_unchecked(&self, _t: f32) -> WithTwoDerivatives<T> {
|
||||
WithTwoDerivatives {
|
||||
value: self.value.clone(),
|
||||
derivative: VectorSpace::ZERO,
|
||||
second_derivative: VectorSpace::ZERO,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -- ChainCurve
|
||||
|
||||
impl<T, C, D> SampleDerivative<T> for ChainCurve<T, C, D>
|
||||
where
|
||||
T: HasTangent,
|
||||
C: SampleDerivative<T>,
|
||||
D: SampleDerivative<T>,
|
||||
{
|
||||
fn sample_with_derivative_unchecked(&self, t: f32) -> WithDerivative<T> {
|
||||
if t > self.first.domain().end() {
|
||||
self.second.sample_with_derivative_unchecked(
|
||||
// `t - first.domain.end` computes the offset into the domain of the second.
|
||||
t - self.first.domain().end() + self.second.domain().start(),
|
||||
)
|
||||
} else {
|
||||
self.first.sample_with_derivative_unchecked(t)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, C, D> SampleTwoDerivatives<T> for ChainCurve<T, C, D>
|
||||
where
|
||||
T: HasTangent,
|
||||
C: SampleTwoDerivatives<T>,
|
||||
D: SampleTwoDerivatives<T>,
|
||||
{
|
||||
fn sample_with_two_derivatives_unchecked(&self, t: f32) -> WithTwoDerivatives<T> {
|
||||
if t > self.first.domain().end() {
|
||||
self.second.sample_with_two_derivatives_unchecked(
|
||||
// `t - first.domain.end` computes the offset into the domain of the second.
|
||||
t - self.first.domain().end() + self.second.domain().start(),
|
||||
)
|
||||
} else {
|
||||
self.first.sample_with_two_derivatives_unchecked(t)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -- ContinuationCurve
|
||||
|
||||
impl<T, C, D> SampleDerivative<T> for ContinuationCurve<T, C, D>
|
||||
where
|
||||
T: VectorSpace,
|
||||
C: SampleDerivative<T>,
|
||||
D: SampleDerivative<T>,
|
||||
{
|
||||
fn sample_with_derivative_unchecked(&self, t: f32) -> WithDerivative<T> {
|
||||
if t > self.first.domain().end() {
|
||||
let mut output = self.second.sample_with_derivative_unchecked(
|
||||
// `t - first.domain.end` computes the offset into the domain of the second.
|
||||
t - self.first.domain().end() + self.second.domain().start(),
|
||||
);
|
||||
output.value = output.value + self.offset;
|
||||
output
|
||||
} else {
|
||||
self.first.sample_with_derivative_unchecked(t)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, C, D> SampleTwoDerivatives<T> for ContinuationCurve<T, C, D>
|
||||
where
|
||||
T: VectorSpace,
|
||||
C: SampleTwoDerivatives<T>,
|
||||
D: SampleTwoDerivatives<T>,
|
||||
{
|
||||
fn sample_with_two_derivatives_unchecked(&self, t: f32) -> WithTwoDerivatives<T> {
|
||||
if t > self.first.domain().end() {
|
||||
let mut output = self.second.sample_with_two_derivatives_unchecked(
|
||||
// `t - first.domain.end` computes the offset into the domain of the second.
|
||||
t - self.first.domain().end() + self.second.domain().start(),
|
||||
);
|
||||
output.value = output.value + self.offset;
|
||||
output
|
||||
} else {
|
||||
self.first.sample_with_two_derivatives_unchecked(t)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -- RepeatCurve
|
||||
|
||||
impl<T, C> SampleDerivative<T> for RepeatCurve<T, C>
|
||||
where
|
||||
T: HasTangent,
|
||||
C: SampleDerivative<T>,
|
||||
{
|
||||
fn sample_with_derivative_unchecked(&self, t: f32) -> WithDerivative<T> {
|
||||
let t = self.base_curve_sample_time(t);
|
||||
self.curve.sample_with_derivative_unchecked(t)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, C> SampleTwoDerivatives<T> for RepeatCurve<T, C>
|
||||
where
|
||||
T: HasTangent,
|
||||
C: SampleTwoDerivatives<T>,
|
||||
{
|
||||
fn sample_with_two_derivatives_unchecked(&self, t: f32) -> WithTwoDerivatives<T> {
|
||||
let t = self.base_curve_sample_time(t);
|
||||
self.curve.sample_with_two_derivatives_unchecked(t)
|
||||
}
|
||||
}
|
||||
|
||||
// -- ForeverCurve
|
||||
|
||||
impl<T, C> SampleDerivative<T> for ForeverCurve<T, C>
|
||||
where
|
||||
T: HasTangent,
|
||||
C: SampleDerivative<T>,
|
||||
{
|
||||
fn sample_with_derivative_unchecked(&self, t: f32) -> WithDerivative<T> {
|
||||
let t = self.base_curve_sample_time(t);
|
||||
self.curve.sample_with_derivative_unchecked(t)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, C> SampleTwoDerivatives<T> for ForeverCurve<T, C>
|
||||
where
|
||||
T: HasTangent,
|
||||
C: SampleTwoDerivatives<T>,
|
||||
{
|
||||
fn sample_with_two_derivatives_unchecked(&self, t: f32) -> WithTwoDerivatives<T> {
|
||||
let t = self.base_curve_sample_time(t);
|
||||
self.curve.sample_with_two_derivatives_unchecked(t)
|
||||
}
|
||||
}
|
||||
|
||||
// -- PingPongCurve
|
||||
|
||||
impl<T, C> SampleDerivative<T> for PingPongCurve<T, C>
|
||||
where
|
||||
T: HasTangent,
|
||||
C: SampleDerivative<T>,
|
||||
{
|
||||
fn sample_with_derivative_unchecked(&self, t: f32) -> WithDerivative<T> {
|
||||
if t > self.curve.domain().end() {
|
||||
let t = self.curve.domain().end() * 2.0 - t;
|
||||
// The derivative of the preceding expression is -1, so the chain
|
||||
// rule implies the derivative should be negated.
|
||||
let mut output = self.curve.sample_with_derivative_unchecked(t);
|
||||
output.derivative = -output.derivative;
|
||||
output
|
||||
} else {
|
||||
self.curve.sample_with_derivative_unchecked(t)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, C> SampleTwoDerivatives<T> for PingPongCurve<T, C>
|
||||
where
|
||||
T: HasTangent,
|
||||
C: SampleTwoDerivatives<T>,
|
||||
{
|
||||
fn sample_with_two_derivatives_unchecked(&self, t: f32) -> WithTwoDerivatives<T> {
|
||||
if t > self.curve.domain().end() {
|
||||
let t = self.curve.domain().end() * 2.0 - t;
|
||||
// See the implementation on `ReverseCurve` for an explanation of
|
||||
// why this is correct.
|
||||
let mut output = self.curve.sample_with_two_derivatives_unchecked(t);
|
||||
output.derivative = -output.derivative;
|
||||
output
|
||||
} else {
|
||||
self.curve.sample_with_two_derivatives_unchecked(t)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -- ZipCurve
|
||||
|
||||
impl<S, T, C, D> SampleDerivative<(S, T)> for ZipCurve<S, T, C, D>
|
||||
where
|
||||
S: HasTangent,
|
||||
T: HasTangent,
|
||||
C: SampleDerivative<S>,
|
||||
D: SampleDerivative<T>,
|
||||
{
|
||||
fn sample_with_derivative_unchecked(&self, t: f32) -> WithDerivative<(S, T)> {
|
||||
let first_output = self.first.sample_with_derivative_unchecked(t);
|
||||
let second_output = self.second.sample_with_derivative_unchecked(t);
|
||||
WithDerivative {
|
||||
value: (first_output.value, second_output.value),
|
||||
derivative: Sum(first_output.derivative, second_output.derivative),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, T, C, D> SampleTwoDerivatives<(S, T)> for ZipCurve<S, T, C, D>
|
||||
where
|
||||
S: HasTangent,
|
||||
T: HasTangent,
|
||||
C: SampleTwoDerivatives<S>,
|
||||
D: SampleTwoDerivatives<T>,
|
||||
{
|
||||
fn sample_with_two_derivatives_unchecked(&self, t: f32) -> WithTwoDerivatives<(S, T)> {
|
||||
let first_output = self.first.sample_with_two_derivatives_unchecked(t);
|
||||
let second_output = self.second.sample_with_two_derivatives_unchecked(t);
|
||||
WithTwoDerivatives {
|
||||
value: (first_output.value, second_output.value),
|
||||
derivative: Sum(first_output.derivative, second_output.derivative),
|
||||
second_derivative: Sum(
|
||||
first_output.second_derivative,
|
||||
second_output.second_derivative,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -- GraphCurve
|
||||
|
||||
impl<T, C> SampleDerivative<(f32, T)> for GraphCurve<T, C>
|
||||
where
|
||||
T: HasTangent,
|
||||
C: SampleDerivative<T>,
|
||||
{
|
||||
fn sample_with_derivative_unchecked(&self, t: f32) -> WithDerivative<(f32, T)> {
|
||||
let output = self.base.sample_with_derivative_unchecked(t);
|
||||
WithDerivative {
|
||||
value: (t, output.value),
|
||||
derivative: Sum(1.0, output.derivative),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, C> SampleTwoDerivatives<(f32, T)> for GraphCurve<T, C>
|
||||
where
|
||||
T: HasTangent,
|
||||
C: SampleTwoDerivatives<T>,
|
||||
{
|
||||
fn sample_with_two_derivatives_unchecked(&self, t: f32) -> WithTwoDerivatives<(f32, T)> {
|
||||
let output = self.base.sample_with_two_derivatives_unchecked(t);
|
||||
WithTwoDerivatives {
|
||||
value: (t, output.value),
|
||||
derivative: Sum(1.0, output.derivative),
|
||||
second_derivative: Sum(0.0, output.second_derivative),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -- ReverseCurve
|
||||
|
||||
impl<T, C> SampleDerivative<T> for ReverseCurve<T, C>
|
||||
where
|
||||
T: HasTangent,
|
||||
C: SampleDerivative<T>,
|
||||
{
|
||||
fn sample_with_derivative_unchecked(&self, t: f32) -> WithDerivative<T> {
|
||||
// This gets almost the correct value, but we haven't accounted for the
|
||||
// reversal of orientation yet.
|
||||
let mut output = self
|
||||
.curve
|
||||
.sample_with_derivative_unchecked(self.domain().end() - (t - self.domain().start()));
|
||||
|
||||
output.derivative = -output.derivative;
|
||||
|
||||
output
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, C> SampleTwoDerivatives<T> for ReverseCurve<T, C>
|
||||
where
|
||||
T: HasTangent,
|
||||
C: SampleTwoDerivatives<T>,
|
||||
{
|
||||
fn sample_with_two_derivatives_unchecked(&self, t: f32) -> WithTwoDerivatives<T> {
|
||||
// This gets almost the correct value, but we haven't accounted for the
|
||||
// reversal of orientation yet.
|
||||
let mut output = self.curve.sample_with_two_derivatives_unchecked(
|
||||
self.domain().end() - (t - self.domain().start()),
|
||||
);
|
||||
|
||||
output.derivative = -output.derivative;
|
||||
|
||||
// (Note that the reparametrization that reverses the curve satisfies
|
||||
// g'(t)^2 = 1 and g''(t) = 0, so the second derivative is already
|
||||
// correct.)
|
||||
|
||||
output
|
||||
}
|
||||
}
|
||||
|
||||
// -- CurveReparamCurve
|
||||
|
||||
impl<T, C, D> SampleDerivative<T> for CurveReparamCurve<T, C, D>
|
||||
where
|
||||
T: HasTangent,
|
||||
C: SampleDerivative<T>,
|
||||
D: SampleDerivative<f32>,
|
||||
{
|
||||
fn sample_with_derivative_unchecked(&self, t: f32) -> WithDerivative<T> {
|
||||
// This curve is r(t) = f(g(t)), where f(t) is `self.base` and g(t)
|
||||
// is `self.reparam_curve`.
|
||||
|
||||
// Start by computing g(t) and g'(t).
|
||||
let reparam_output = self.reparam_curve.sample_with_derivative_unchecked(t);
|
||||
|
||||
// Compute:
|
||||
// - value: f(g(t))
|
||||
// - derivative: f'(g(t))
|
||||
let mut output = self
|
||||
.base
|
||||
.sample_with_derivative_unchecked(reparam_output.value);
|
||||
|
||||
// Do the multiplication part of the chain rule.
|
||||
output.derivative = output.derivative * reparam_output.derivative;
|
||||
|
||||
// value: f(g(t)), derivative: f'(g(t)) g'(t)
|
||||
output
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, C, D> SampleTwoDerivatives<T> for CurveReparamCurve<T, C, D>
|
||||
where
|
||||
T: HasTangent,
|
||||
C: SampleTwoDerivatives<T>,
|
||||
D: SampleTwoDerivatives<f32>,
|
||||
{
|
||||
fn sample_with_two_derivatives_unchecked(&self, t: f32) -> WithTwoDerivatives<T> {
|
||||
// This curve is r(t) = f(g(t)), where f(t) is `self.base` and g(t)
|
||||
// is `self.reparam_curve`.
|
||||
|
||||
// Start by computing g(t), g'(t), g''(t).
|
||||
let reparam_output = self.reparam_curve.sample_with_two_derivatives_unchecked(t);
|
||||
|
||||
// Compute:
|
||||
// - value: f(g(t))
|
||||
// - derivative: f'(g(t))
|
||||
// - second derivative: f''(g(t))
|
||||
let mut output = self
|
||||
.base
|
||||
.sample_with_two_derivatives_unchecked(reparam_output.value);
|
||||
|
||||
// Set the second derivative according to the chain and product rules
|
||||
// r''(t) = f''(g(t)) g'(t)^2 + f'(g(t)) g''(t)
|
||||
output.second_derivative = (output.second_derivative
|
||||
* (reparam_output.derivative * reparam_output.derivative))
|
||||
+ (output.derivative * reparam_output.second_derivative);
|
||||
|
||||
// Set the first derivative according to the chain rule
|
||||
// r'(t) = f'(g(t)) g'(t)
|
||||
output.derivative = output.derivative * reparam_output.derivative;
|
||||
|
||||
output
|
||||
}
|
||||
}
|
||||
|
||||
// -- LinearReparamCurve
|
||||
|
||||
impl<T, C> SampleDerivative<T> for LinearReparamCurve<T, C>
|
||||
where
|
||||
T: HasTangent,
|
||||
C: SampleDerivative<T>,
|
||||
{
|
||||
fn sample_with_derivative_unchecked(&self, t: f32) -> WithDerivative<T> {
|
||||
// This curve is r(t) = f(g(t)), where f(t) is `self.base` and g(t) is
|
||||
// the linear map bijecting `self.new_domain` onto `self.base.domain()`.
|
||||
|
||||
// The invariants imply this unwrap always succeeds.
|
||||
let g = self.new_domain.linear_map_to(self.base.domain()).unwrap();
|
||||
|
||||
// Compute g'(t) from the domain lengths.
|
||||
let g_derivative = self.base.domain().length() / self.new_domain.length();
|
||||
|
||||
// Compute:
|
||||
// - value: f(g(t))
|
||||
// - derivative: f'(g(t))
|
||||
let mut output = self.base.sample_with_derivative_unchecked(g(t));
|
||||
|
||||
// Adjust the derivative according to the chain rule.
|
||||
output.derivative = output.derivative * g_derivative;
|
||||
|
||||
output
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, C> SampleTwoDerivatives<T> for LinearReparamCurve<T, C>
|
||||
where
|
||||
T: HasTangent,
|
||||
C: SampleTwoDerivatives<T>,
|
||||
{
|
||||
fn sample_with_two_derivatives_unchecked(&self, t: f32) -> WithTwoDerivatives<T> {
|
||||
// This curve is r(t) = f(g(t)), where f(t) is `self.base` and g(t) is
|
||||
// the linear map bijecting `self.new_domain` onto `self.base.domain()`.
|
||||
|
||||
// The invariants imply this unwrap always succeeds.
|
||||
let g = self.new_domain.linear_map_to(self.base.domain()).unwrap();
|
||||
|
||||
// Compute g'(t) from the domain lengths.
|
||||
let g_derivative = self.base.domain().length() / self.new_domain.length();
|
||||
|
||||
// Compute:
|
||||
// - value: f(g(t))
|
||||
// - derivative: f'(g(t))
|
||||
// - second derivative: f''(g(t))
|
||||
let mut output = self.base.sample_with_two_derivatives_unchecked(g(t));
|
||||
|
||||
// Set the second derivative according to the chain and product rules
|
||||
// r''(t) = f''(g(t)) g'(t)^2 (g''(t) = 0)
|
||||
output.second_derivative = output.second_derivative * (g_derivative * g_derivative);
|
||||
|
||||
// Set the first derivative according to the chain rule
|
||||
// r'(t) = f'(g(t)) g'(t)
|
||||
output.derivative = output.derivative * g_derivative;
|
||||
|
||||
output
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use approx::assert_abs_diff_eq;
|
||||
|
||||
use super::*;
|
||||
use crate::cubic_splines::{CubicBezier, CubicCardinalSpline, CubicCurve, CubicGenerator};
|
||||
use crate::curve::{Curve, Interval};
|
||||
use crate::{vec2, Vec2, Vec3};
|
||||
|
||||
fn test_curve() -> CubicCurve<Vec2> {
|
||||
let control_pts = [[
|
||||
vec2(0.0, 0.0),
|
||||
vec2(1.0, 0.0),
|
||||
vec2(0.0, 1.0),
|
||||
vec2(1.0, 1.0),
|
||||
]];
|
||||
|
||||
CubicBezier::new(control_pts).to_curve().unwrap()
|
||||
}
|
||||
|
||||
fn other_test_curve() -> CubicCurve<Vec2> {
|
||||
let control_pts = [
|
||||
vec2(1.0, 1.0),
|
||||
vec2(2.0, 1.0),
|
||||
vec2(2.0, 0.0),
|
||||
vec2(1.0, 0.0),
|
||||
];
|
||||
|
||||
CubicCardinalSpline::new(0.5, control_pts)
|
||||
.to_curve()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn reparam_curve() -> CubicCurve<f32> {
|
||||
let control_pts = [[0.0, 0.25, 0.75, 1.0]];
|
||||
|
||||
CubicBezier::new(control_pts).to_curve().unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn constant_curve() {
|
||||
let curve = ConstantCurve::new(Interval::UNIT, Vec3::new(0.2, 1.5, -2.6));
|
||||
let jet = curve.sample_with_derivative(0.5).unwrap();
|
||||
assert_abs_diff_eq!(jet.derivative, Vec3::ZERO);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn chain_curve() {
|
||||
let curve1 = test_curve();
|
||||
let curve2 = other_test_curve();
|
||||
let curve = curve1.by_ref().chain(&curve2).unwrap();
|
||||
|
||||
let jet = curve.sample_with_derivative(0.65).unwrap();
|
||||
let true_jet = curve1.sample_with_derivative(0.65).unwrap();
|
||||
assert_abs_diff_eq!(jet.value, true_jet.value);
|
||||
assert_abs_diff_eq!(jet.derivative, true_jet.derivative);
|
||||
|
||||
let jet = curve.sample_with_derivative(1.1).unwrap();
|
||||
let true_jet = curve2.sample_with_derivative(0.1).unwrap();
|
||||
assert_abs_diff_eq!(jet.value, true_jet.value);
|
||||
assert_abs_diff_eq!(jet.derivative, true_jet.derivative);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn continuation_curve() {
|
||||
let curve1 = test_curve();
|
||||
let curve2 = other_test_curve();
|
||||
let curve = curve1.by_ref().chain_continue(&curve2).unwrap();
|
||||
|
||||
let jet = curve.sample_with_derivative(0.99).unwrap();
|
||||
let true_jet = curve1.sample_with_derivative(0.99).unwrap();
|
||||
assert_abs_diff_eq!(jet.value, true_jet.value);
|
||||
assert_abs_diff_eq!(jet.derivative, true_jet.derivative);
|
||||
|
||||
let jet = curve.sample_with_derivative(1.3).unwrap();
|
||||
let true_jet = curve2.sample_with_derivative(0.3).unwrap();
|
||||
assert_abs_diff_eq!(jet.value, true_jet.value);
|
||||
assert_abs_diff_eq!(jet.derivative, true_jet.derivative);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn repeat_curve() {
|
||||
let curve1 = test_curve();
|
||||
let curve = curve1.by_ref().repeat(3).unwrap();
|
||||
|
||||
let jet = curve.sample_with_derivative(0.73).unwrap();
|
||||
let true_jet = curve1.sample_with_derivative(0.73).unwrap();
|
||||
assert_abs_diff_eq!(jet.value, true_jet.value);
|
||||
assert_abs_diff_eq!(jet.derivative, true_jet.derivative);
|
||||
|
||||
let jet = curve.sample_with_derivative(3.5).unwrap();
|
||||
let true_jet = curve1.sample_with_derivative(0.5).unwrap();
|
||||
assert_abs_diff_eq!(jet.value, true_jet.value);
|
||||
assert_abs_diff_eq!(jet.derivative, true_jet.derivative);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn forever_curve() {
|
||||
let curve1 = test_curve();
|
||||
let curve = curve1.by_ref().forever().unwrap();
|
||||
|
||||
let jet = curve.sample_with_derivative(0.12).unwrap();
|
||||
let true_jet = curve1.sample_with_derivative(0.12).unwrap();
|
||||
assert_abs_diff_eq!(jet.value, true_jet.value);
|
||||
assert_abs_diff_eq!(jet.derivative, true_jet.derivative);
|
||||
|
||||
let jet = curve.sample_with_derivative(500.5).unwrap();
|
||||
let true_jet = curve1.sample_with_derivative(0.5).unwrap();
|
||||
assert_abs_diff_eq!(jet.value, true_jet.value);
|
||||
assert_abs_diff_eq!(jet.derivative, true_jet.derivative);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ping_pong_curve() {
|
||||
let curve1 = test_curve();
|
||||
let curve = curve1.by_ref().ping_pong().unwrap();
|
||||
|
||||
let jet = curve.sample_with_derivative(0.99).unwrap();
|
||||
let comparison_jet = curve1.sample_with_derivative(0.99).unwrap();
|
||||
assert_abs_diff_eq!(jet.value, comparison_jet.value);
|
||||
assert_abs_diff_eq!(jet.derivative, comparison_jet.derivative);
|
||||
|
||||
let jet = curve.sample_with_derivative(1.3).unwrap();
|
||||
let comparison_jet = curve1.sample_with_derivative(0.7).unwrap();
|
||||
assert_abs_diff_eq!(jet.value, comparison_jet.value);
|
||||
assert_abs_diff_eq!(jet.derivative, -comparison_jet.derivative, epsilon = 1.0e-5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zip_curve() {
|
||||
let curve1 = test_curve();
|
||||
let curve2 = other_test_curve();
|
||||
let curve = curve1.by_ref().zip(&curve2).unwrap();
|
||||
|
||||
let jet = curve.sample_with_derivative(0.7).unwrap();
|
||||
let comparison_jet1 = curve1.sample_with_derivative(0.7).unwrap();
|
||||
let comparison_jet2 = curve2.sample_with_derivative(0.7).unwrap();
|
||||
assert_abs_diff_eq!(jet.value.0, comparison_jet1.value);
|
||||
assert_abs_diff_eq!(jet.value.1, comparison_jet2.value);
|
||||
let Sum(derivative1, derivative2) = jet.derivative;
|
||||
assert_abs_diff_eq!(derivative1, comparison_jet1.derivative);
|
||||
assert_abs_diff_eq!(derivative2, comparison_jet2.derivative);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn graph_curve() {
|
||||
let curve1 = test_curve();
|
||||
let curve = curve1.by_ref().graph();
|
||||
|
||||
let jet = curve.sample_with_derivative(0.25).unwrap();
|
||||
let comparison_jet = curve1.sample_with_derivative(0.25).unwrap();
|
||||
assert_abs_diff_eq!(jet.value.0, 0.25);
|
||||
assert_abs_diff_eq!(jet.value.1, comparison_jet.value);
|
||||
let Sum(one, derivative) = jet.derivative;
|
||||
assert_abs_diff_eq!(one, 1.0);
|
||||
assert_abs_diff_eq!(derivative, comparison_jet.derivative);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reverse_curve() {
|
||||
let curve1 = test_curve();
|
||||
let curve = curve1.by_ref().reverse().unwrap();
|
||||
|
||||
let jet = curve.sample_with_derivative(0.23).unwrap();
|
||||
let comparison_jet = curve1.sample_with_derivative(0.77).unwrap();
|
||||
assert_abs_diff_eq!(jet.value, comparison_jet.value);
|
||||
assert_abs_diff_eq!(jet.derivative, -comparison_jet.derivative);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn curve_reparam_curve() {
|
||||
let reparam_curve = reparam_curve();
|
||||
let reparam_jet = reparam_curve.sample_with_derivative(0.6).unwrap();
|
||||
|
||||
let curve1 = test_curve();
|
||||
let curve = curve1.by_ref().reparametrize_by_curve(&reparam_curve);
|
||||
|
||||
let jet = curve.sample_with_derivative(0.6).unwrap();
|
||||
let base_jet = curve1
|
||||
.sample_with_derivative(reparam_curve.sample(0.6).unwrap())
|
||||
.unwrap();
|
||||
assert_abs_diff_eq!(jet.value, base_jet.value);
|
||||
assert_abs_diff_eq!(jet.derivative, base_jet.derivative * reparam_jet.derivative);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn linear_reparam_curve() {
|
||||
let curve1 = test_curve();
|
||||
let curve = curve1
|
||||
.by_ref()
|
||||
.reparametrize_linear(Interval::new(0.0, 0.5).unwrap())
|
||||
.unwrap();
|
||||
|
||||
let jet = curve.sample_with_derivative(0.23).unwrap();
|
||||
let comparison_jet = curve1.sample_with_derivative(0.46).unwrap();
|
||||
assert_abs_diff_eq!(jet.value, comparison_jet.value);
|
||||
assert_abs_diff_eq!(jet.derivative, comparison_jet.derivative * 2.0);
|
||||
}
|
||||
}
|
226
crates/bevy_math/src/curve/derivatives/mod.rs
Normal file
226
crates/bevy_math/src/curve/derivatives/mod.rs
Normal file
|
@ -0,0 +1,226 @@
|
|||
//! This module holds traits related to extracting derivatives from curves. In
|
||||
//! applications, the derivatives of interest are chiefly the first and second;
|
||||
//! in this module, these are provided by the traits [`CurveWithDerivative`]
|
||||
//! and [`CurveWithTwoDerivatives`].
|
||||
//!
|
||||
//! These take ownership of the curve they are used on by default, so that
|
||||
//! the resulting output may be used in more durable contexts. For example,
|
||||
//! `CurveWithDerivative<T>` is not dyn-compatible, but `Curve<WithDerivative<T>>`
|
||||
//! is, so if such a curve needs to be stored in a dynamic context, calling
|
||||
//! [`with_derivative`] and then placing the result in a
|
||||
//! `Box<Curve<WithDerivative<T>>>` is sensible.
|
||||
//!
|
||||
//! On the other hand, in more transient contexts, consuming a value merely to
|
||||
//! sample derivatives is inconvenient, and in these cases, it is recommended
|
||||
//! to use [`by_ref`] when possible to create a referential curve first, retaining
|
||||
//! liveness of the original.
|
||||
//!
|
||||
//! This module also holds the [`SampleDerivative`] and [`SampleTwoDerivatives`]
|
||||
//! traits, which can be used to easily implement `CurveWithDerivative` and its
|
||||
//! counterpart.
|
||||
//!
|
||||
//! [`with_derivative`]: CurveWithDerivative::with_derivative
|
||||
//! [`by_ref`]: Curve::by_ref
|
||||
|
||||
pub mod adaptor_impls;
|
||||
|
||||
use crate::{
|
||||
common_traits::{HasTangent, WithDerivative, WithTwoDerivatives},
|
||||
curve::{Curve, Interval},
|
||||
};
|
||||
use core::ops::Deref;
|
||||
|
||||
#[cfg(feature = "bevy_reflect")]
|
||||
use bevy_reflect::{FromReflect, Reflect};
|
||||
|
||||
/// Trait for curves that have a well-defined notion of derivative, allowing for
|
||||
/// derivatives to be extracted along with values.
|
||||
///
|
||||
/// This is implemented by implementing [`SampleDerivative`].
|
||||
pub trait CurveWithDerivative<T>: SampleDerivative<T>
|
||||
where
|
||||
T: HasTangent,
|
||||
{
|
||||
/// This curve, but with its first derivative included in sampling.
|
||||
fn with_derivative(self) -> impl Curve<WithDerivative<T>>;
|
||||
}
|
||||
|
||||
/// Trait for curves that have a well-defined notion of second derivative,
|
||||
/// allowing for two derivatives to be extracted along with values.
|
||||
///
|
||||
/// This is implemented by implementing [`SampleTwoDerivatives`].
|
||||
pub trait CurveWithTwoDerivatives<T>: SampleTwoDerivatives<T>
|
||||
where
|
||||
T: HasTangent,
|
||||
{
|
||||
/// This curve, but with its first two derivatives included in sampling.
|
||||
fn with_two_derivatives(self) -> impl Curve<WithTwoDerivatives<T>>;
|
||||
}
|
||||
|
||||
/// A trait for curves that can sample derivatives in addition to values.
|
||||
///
|
||||
/// Types that implement this trait automatically implement [`CurveWithDerivative`];
|
||||
/// the curve produced by [`with_derivative`] uses the sampling defined in the trait
|
||||
/// implementation.
|
||||
///
|
||||
/// [`with_derivative`]: CurveWithDerivative::with_derivative
|
||||
pub trait SampleDerivative<T>: Curve<T>
|
||||
where
|
||||
T: HasTangent,
|
||||
{
|
||||
/// Sample this curve at the parameter value `t`, extracting the associated value
|
||||
/// in addition to its derivative. This is the unchecked version of sampling, which
|
||||
/// should only be used if the sample time `t` is already known to lie within the
|
||||
/// curve's domain.
|
||||
///
|
||||
/// See [`Curve::sample_unchecked`] for more information.
|
||||
fn sample_with_derivative_unchecked(&self, t: f32) -> WithDerivative<T>;
|
||||
|
||||
/// Sample this curve's value and derivative at the parameter value `t`, returning
|
||||
/// `None` if the point is outside of the curve's domain.
|
||||
fn sample_with_derivative(&self, t: f32) -> Option<WithDerivative<T>> {
|
||||
match self.domain().contains(t) {
|
||||
true => Some(self.sample_with_derivative_unchecked(t)),
|
||||
false => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Sample this curve's value and derivative at the parameter value `t`, clamping `t`
|
||||
/// to lie inside the domain of the curve.
|
||||
fn sample_with_derivative_clamped(&self, t: f32) -> WithDerivative<T> {
|
||||
let t = self.domain().clamp(t);
|
||||
self.sample_with_derivative_unchecked(t)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, C, D> SampleDerivative<T> for D
|
||||
where
|
||||
T: HasTangent,
|
||||
C: SampleDerivative<T> + ?Sized,
|
||||
D: Deref<Target = C>,
|
||||
{
|
||||
fn sample_with_derivative_unchecked(&self, t: f32) -> WithDerivative<T> {
|
||||
<C as SampleDerivative<T>>::sample_with_derivative_unchecked(self, t)
|
||||
}
|
||||
}
|
||||
|
||||
/// A trait for curves that can sample two derivatives in addition to values.
|
||||
///
|
||||
/// Types that implement this trait automatically implement [`CurveWithTwoDerivatives`];
|
||||
/// the curve produced by [`with_two_derivatives`] uses the sampling defined in the trait
|
||||
/// implementation.
|
||||
///
|
||||
/// [`with_two_derivatives`]: CurveWithTwoDerivatives::with_two_derivatives
|
||||
pub trait SampleTwoDerivatives<T>: Curve<T>
|
||||
where
|
||||
T: HasTangent,
|
||||
{
|
||||
/// Sample this curve at the parameter value `t`, extracting the associated value
|
||||
/// in addition to two derivatives. This is the unchecked version of sampling, which
|
||||
/// should only be used if the sample time `t` is already known to lie within the
|
||||
/// curve's domain.
|
||||
///
|
||||
/// See [`Curve::sample_unchecked`] for more information.
|
||||
fn sample_with_two_derivatives_unchecked(&self, t: f32) -> WithTwoDerivatives<T>;
|
||||
|
||||
/// Sample this curve's value and two derivatives at the parameter value `t`, returning
|
||||
/// `None` if the point is outside of the curve's domain.
|
||||
fn sample_with_two_derivatives(&self, t: f32) -> Option<WithTwoDerivatives<T>> {
|
||||
match self.domain().contains(t) {
|
||||
true => Some(self.sample_with_two_derivatives_unchecked(t)),
|
||||
false => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Sample this curve's value and two derivatives at the parameter value `t`, clamping `t`
|
||||
/// to lie inside the domain of the curve.
|
||||
fn sample_with_two_derivatives_clamped(&self, t: f32) -> WithTwoDerivatives<T> {
|
||||
let t = self.domain().clamp(t);
|
||||
self.sample_with_two_derivatives_unchecked(t)
|
||||
}
|
||||
}
|
||||
|
||||
/// A wrapper that uses a [`SampleDerivative<T>`] curve to produce a `Curve<WithDerivative<T>>`.
|
||||
#[derive(Copy, Clone, Debug, Default, PartialEq)]
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect, FromReflect),
|
||||
reflect(from_reflect = false)
|
||||
)]
|
||||
pub struct SampleDerivativeWrapper<C>(C);
|
||||
|
||||
impl<T, C> Curve<WithDerivative<T>> for SampleDerivativeWrapper<C>
|
||||
where
|
||||
T: HasTangent,
|
||||
C: SampleDerivative<T>,
|
||||
{
|
||||
fn domain(&self) -> Interval {
|
||||
self.0.domain()
|
||||
}
|
||||
|
||||
fn sample_unchecked(&self, t: f32) -> WithDerivative<T> {
|
||||
self.0.sample_with_derivative_unchecked(t)
|
||||
}
|
||||
|
||||
fn sample(&self, t: f32) -> Option<WithDerivative<T>> {
|
||||
self.0.sample_with_derivative(t)
|
||||
}
|
||||
|
||||
fn sample_clamped(&self, t: f32) -> WithDerivative<T> {
|
||||
self.0.sample_with_derivative_clamped(t)
|
||||
}
|
||||
}
|
||||
|
||||
/// A wrapper that uses a [`SampleTwoDerivatives<T>`] curve to produce a
|
||||
/// `Curve<WithTwoDerivatives<T>>`.
|
||||
#[derive(Copy, Clone, Debug, Default, PartialEq)]
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect, FromReflect),
|
||||
reflect(from_reflect = false)
|
||||
)]
|
||||
pub struct SampleTwoDerivativesWrapper<C>(C);
|
||||
|
||||
impl<T, C> Curve<WithTwoDerivatives<T>> for SampleTwoDerivativesWrapper<C>
|
||||
where
|
||||
T: HasTangent,
|
||||
C: SampleTwoDerivatives<T>,
|
||||
{
|
||||
fn domain(&self) -> Interval {
|
||||
self.0.domain()
|
||||
}
|
||||
|
||||
fn sample_unchecked(&self, t: f32) -> WithTwoDerivatives<T> {
|
||||
self.0.sample_with_two_derivatives_unchecked(t)
|
||||
}
|
||||
|
||||
fn sample(&self, t: f32) -> Option<WithTwoDerivatives<T>> {
|
||||
self.0.sample_with_two_derivatives(t)
|
||||
}
|
||||
|
||||
fn sample_clamped(&self, t: f32) -> WithTwoDerivatives<T> {
|
||||
self.0.sample_with_two_derivatives_clamped(t)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, C> CurveWithDerivative<T> for C
|
||||
where
|
||||
T: HasTangent,
|
||||
C: SampleDerivative<T>,
|
||||
{
|
||||
fn with_derivative(self) -> impl Curve<WithDerivative<T>> {
|
||||
SampleDerivativeWrapper(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, C> CurveWithTwoDerivatives<T> for C
|
||||
where
|
||||
T: HasTangent,
|
||||
C: SampleTwoDerivatives<T> + CurveWithDerivative<T>,
|
||||
{
|
||||
fn with_two_derivatives(self) -> impl Curve<WithTwoDerivatives<T>> {
|
||||
SampleTwoDerivativesWrapper(self)
|
||||
}
|
||||
}
|
|
@ -287,6 +287,7 @@
|
|||
|
||||
pub mod adaptors;
|
||||
pub mod cores;
|
||||
pub mod derivatives;
|
||||
pub mod easing;
|
||||
pub mod interval;
|
||||
pub mod iterable;
|
||||
|
|
|
@ -27,6 +27,7 @@ extend-ignore-identifiers-re = [
|
|||
"metalness", # Rendering term (metallicity)
|
||||
"inventario", # Inventory in Portuguese
|
||||
"[Rr]eparametrize", # Mathematical term in curve context (reparameterize)
|
||||
"[Rr]eparametrization",
|
||||
# Used in bevy_mikktspace
|
||||
"iFO",
|
||||
"vOt",
|
||||
|
|
Loading…
Reference in a new issue