Vendor dependencies for 0.3.0 release

This commit is contained in:
2025-09-27 10:29:08 -05:00
parent 0c8d39d483
commit 82ab7f317b
26803 changed files with 16134934 additions and 0 deletions

815
vendor/bevy_math/src/curve/adaptors.rs vendored Normal file
View File

@@ -0,0 +1,815 @@
//! Adaptors used by the Curve API for transforming and combining curves together.
use super::interval::*;
use super::Curve;
use crate::ops;
use crate::VectorSpace;
use core::any::type_name;
use core::fmt::{self, Debug};
use core::marker::PhantomData;
#[cfg(feature = "bevy_reflect")]
use {
alloc::format,
bevy_reflect::{utility::GenericTypePathCell, FromReflect, Reflect, TypePath},
};
#[cfg(feature = "bevy_reflect")]
mod paths {
pub(super) const THIS_MODULE: &str = "bevy_math::curve::adaptors";
pub(super) const THIS_CRATE: &str = "bevy_math";
}
#[expect(unused, reason = "imported just for doc links")]
use super::CurveExt;
// NOTE ON REFLECTION:
//
// Function members of structs pose an obstacle for reflection, because they don't implement
// reflection traits themselves. Some of these are more problematic than others; for example,
// `FromReflect` is basically hopeless for function members regardless, so function-containing
// adaptors will just never be `FromReflect` (at least until function item types implement
// Default, if that ever happens). Similarly, they do not implement `TypePath`, and as a result,
// those adaptors also need custom `TypePath` adaptors which use `type_name` instead.
//
// The sum total weirdness of the `Reflect` implementations amounts to this; those adaptors:
// - are currently never `FromReflect`;
// - have custom `TypePath` implementations which are not fully stable;
// - have custom `Debug` implementations which display the function only by type name.
/// A curve with a constant value over its domain.
///
/// This is a curve that holds an inner value and always produces a clone of that value when sampled.
#[derive(Clone, Copy, Debug)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
pub struct ConstantCurve<T> {
pub(crate) domain: Interval,
pub(crate) value: T,
}
impl<T> ConstantCurve<T>
where
T: Clone,
{
/// Create a constant curve, which has the given `domain` and always produces the given `value`
/// when sampled.
pub fn new(domain: Interval, value: T) -> Self {
Self { domain, value }
}
}
impl<T> Curve<T> for ConstantCurve<T>
where
T: Clone,
{
#[inline]
fn domain(&self) -> Interval {
self.domain
}
#[inline]
fn sample_unchecked(&self, _t: f32) -> T {
self.value.clone()
}
}
/// A curve defined by a function together with a fixed domain.
///
/// This is a curve that holds an inner function `f` which takes numbers (`f32`) as input and produces
/// output of type `T`. The value of this curve when sampled at time `t` is just `f(t)`.
#[derive(Clone)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(where T: TypePath),
reflect(from_reflect = false, type_path = false),
)]
pub struct FunctionCurve<T, F> {
pub(crate) domain: Interval,
#[cfg_attr(feature = "bevy_reflect", reflect(ignore))]
pub(crate) f: F,
#[cfg_attr(feature = "bevy_reflect", reflect(ignore, clone))]
pub(crate) _phantom: PhantomData<fn() -> T>,
}
impl<T, F> Debug for FunctionCurve<T, F> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("FunctionCurve")
.field("domain", &self.domain)
.field("f", &type_name::<F>())
.finish()
}
}
/// Note: This is not a fully stable implementation of `TypePath` due to usage of `type_name`
/// for function members.
#[cfg(feature = "bevy_reflect")]
impl<T, F> TypePath for FunctionCurve<T, F>
where
T: TypePath,
F: 'static,
{
fn type_path() -> &'static str {
static CELL: GenericTypePathCell = GenericTypePathCell::new();
CELL.get_or_insert::<Self, _>(|| {
format!(
"{}::FunctionCurve<{},{}>",
paths::THIS_MODULE,
T::type_path(),
type_name::<F>()
)
})
}
fn short_type_path() -> &'static str {
static CELL: GenericTypePathCell = GenericTypePathCell::new();
CELL.get_or_insert::<Self, _>(|| {
format!(
"FunctionCurve<{},{}>",
T::short_type_path(),
type_name::<F>()
)
})
}
fn type_ident() -> Option<&'static str> {
Some("FunctionCurve")
}
fn crate_name() -> Option<&'static str> {
Some(paths::THIS_CRATE)
}
fn module_path() -> Option<&'static str> {
Some(paths::THIS_MODULE)
}
}
impl<T, F> FunctionCurve<T, F>
where
F: Fn(f32) -> T,
{
/// Create a new curve with the given `domain` from the given `function`. When sampled, the
/// `function` is evaluated at the sample time to compute the output.
pub fn new(domain: Interval, function: F) -> Self {
FunctionCurve {
domain,
f: function,
_phantom: PhantomData,
}
}
}
impl<T, F> Curve<T> for FunctionCurve<T, F>
where
F: Fn(f32) -> T,
{
#[inline]
fn domain(&self) -> Interval {
self.domain
}
#[inline]
fn sample_unchecked(&self, t: f32) -> T {
(self.f)(t)
}
}
/// A curve whose samples are defined by mapping samples from another curve through a
/// given function. Curves of this type are produced by [`CurveExt::map`].
#[derive(Clone)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(where S: TypePath, T: TypePath, C: TypePath),
reflect(from_reflect = false, type_path = false),
)]
pub struct MapCurve<S, T, C, F> {
pub(crate) preimage: C,
#[cfg_attr(feature = "bevy_reflect", reflect(ignore))]
pub(crate) f: F,
#[cfg_attr(feature = "bevy_reflect", reflect(ignore, clone))]
pub(crate) _phantom: PhantomData<(fn() -> S, fn(S) -> T)>,
}
impl<S, T, C, F> Debug for MapCurve<S, T, C, F>
where
C: Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("MapCurve")
.field("preimage", &self.preimage)
.field("f", &type_name::<F>())
.finish()
}
}
/// Note: This is not a fully stable implementation of `TypePath` due to usage of `type_name`
/// for function members.
#[cfg(feature = "bevy_reflect")]
impl<S, T, C, F> TypePath for MapCurve<S, T, C, F>
where
S: TypePath,
T: TypePath,
C: TypePath,
F: 'static,
{
fn type_path() -> &'static str {
static CELL: GenericTypePathCell = GenericTypePathCell::new();
CELL.get_or_insert::<Self, _>(|| {
format!(
"{}::MapCurve<{},{},{},{}>",
paths::THIS_MODULE,
S::type_path(),
T::type_path(),
C::type_path(),
type_name::<F>()
)
})
}
fn short_type_path() -> &'static str {
static CELL: GenericTypePathCell = GenericTypePathCell::new();
CELL.get_or_insert::<Self, _>(|| {
format!(
"MapCurve<{},{},{},{}>",
S::type_path(),
T::type_path(),
C::type_path(),
type_name::<F>()
)
})
}
fn type_ident() -> Option<&'static str> {
Some("MapCurve")
}
fn crate_name() -> Option<&'static str> {
Some(paths::THIS_CRATE)
}
fn module_path() -> Option<&'static str> {
Some(paths::THIS_MODULE)
}
}
impl<S, T, C, F> Curve<T> for MapCurve<S, T, C, F>
where
C: Curve<S>,
F: Fn(S) -> T,
{
#[inline]
fn domain(&self) -> Interval {
self.preimage.domain()
}
#[inline]
fn sample_unchecked(&self, t: f32) -> T {
(self.f)(self.preimage.sample_unchecked(t))
}
}
/// A curve whose sample space is mapped onto that of some base curve's before sampling.
/// Curves of this type are produced by [`CurveExt::reparametrize`].
#[derive(Clone)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(where T: TypePath, C: TypePath),
reflect(from_reflect = false, type_path = false),
)]
pub struct ReparamCurve<T, C, F> {
pub(crate) domain: Interval,
pub(crate) base: C,
#[cfg_attr(feature = "bevy_reflect", reflect(ignore))]
pub(crate) f: F,
#[cfg_attr(feature = "bevy_reflect", reflect(ignore, clone))]
pub(crate) _phantom: PhantomData<fn() -> T>,
}
impl<T, C, F> Debug for ReparamCurve<T, C, F>
where
C: Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ReparamCurve")
.field("domain", &self.domain)
.field("base", &self.base)
.field("f", &type_name::<F>())
.finish()
}
}
/// Note: This is not a fully stable implementation of `TypePath` due to usage of `type_name`
/// for function members.
#[cfg(feature = "bevy_reflect")]
impl<T, C, F> TypePath for ReparamCurve<T, C, F>
where
T: TypePath,
C: TypePath,
F: 'static,
{
fn type_path() -> &'static str {
static CELL: GenericTypePathCell = GenericTypePathCell::new();
CELL.get_or_insert::<Self, _>(|| {
format!(
"{}::ReparamCurve<{},{},{}>",
paths::THIS_MODULE,
T::type_path(),
C::type_path(),
type_name::<F>()
)
})
}
fn short_type_path() -> &'static str {
static CELL: GenericTypePathCell = GenericTypePathCell::new();
CELL.get_or_insert::<Self, _>(|| {
format!(
"ReparamCurve<{},{},{}>",
T::type_path(),
C::type_path(),
type_name::<F>()
)
})
}
fn type_ident() -> Option<&'static str> {
Some("ReparamCurve")
}
fn crate_name() -> Option<&'static str> {
Some(paths::THIS_CRATE)
}
fn module_path() -> Option<&'static str> {
Some(paths::THIS_MODULE)
}
}
impl<T, C, F> Curve<T> for ReparamCurve<T, C, F>
where
C: Curve<T>,
F: Fn(f32) -> f32,
{
#[inline]
fn domain(&self) -> Interval {
self.domain
}
#[inline]
fn sample_unchecked(&self, t: f32) -> T {
self.base.sample_unchecked((self.f)(t))
}
}
/// A curve that has had its domain changed by a linear reparameterization (stretching and scaling).
/// Curves of this type are produced by [`CurveExt::reparametrize_linear`].
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect, FromReflect),
reflect(from_reflect = false)
)]
pub struct LinearReparamCurve<T, C> {
/// Invariants: The domain of this curve must always be bounded.
pub(crate) base: C,
/// Invariants: This interval must always be bounded.
pub(crate) new_domain: Interval,
#[cfg_attr(feature = "bevy_reflect", reflect(ignore, clone))]
pub(crate) _phantom: PhantomData<fn() -> T>,
}
impl<T, C> Curve<T> for LinearReparamCurve<T, C>
where
C: Curve<T>,
{
#[inline]
fn domain(&self) -> Interval {
self.new_domain
}
#[inline]
fn sample_unchecked(&self, t: f32) -> T {
// The invariants imply this unwrap always succeeds.
let f = self.new_domain.linear_map_to(self.base.domain()).unwrap();
self.base.sample_unchecked(f(t))
}
}
/// A curve that has been reparametrized by another curve, using that curve to transform the
/// sample times before sampling. Curves of this type are produced by [`CurveExt::reparametrize_by_curve`].
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect, FromReflect),
reflect(from_reflect = false)
)]
pub struct CurveReparamCurve<T, C, D> {
pub(crate) base: C,
pub(crate) reparam_curve: D,
#[cfg_attr(feature = "bevy_reflect", reflect(ignore, clone))]
pub(crate) _phantom: PhantomData<fn() -> T>,
}
impl<T, C, D> Curve<T> for CurveReparamCurve<T, C, D>
where
C: Curve<T>,
D: Curve<f32>,
{
#[inline]
fn domain(&self) -> Interval {
self.reparam_curve.domain()
}
#[inline]
fn sample_unchecked(&self, t: f32) -> T {
let sample_time = self.reparam_curve.sample_unchecked(t);
self.base.sample_unchecked(sample_time)
}
}
/// A curve that is the graph of another curve over its parameter space. Curves of this type are
/// produced by [`CurveExt::graph`].
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect, FromReflect),
reflect(from_reflect = false)
)]
pub struct GraphCurve<T, C> {
pub(crate) base: C,
#[cfg_attr(feature = "bevy_reflect", reflect(ignore, clone))]
pub(crate) _phantom: PhantomData<fn() -> T>,
}
impl<T, C> Curve<(f32, T)> for GraphCurve<T, C>
where
C: Curve<T>,
{
#[inline]
fn domain(&self) -> Interval {
self.base.domain()
}
#[inline]
fn sample_unchecked(&self, t: f32) -> (f32, T) {
(t, self.base.sample_unchecked(t))
}
}
/// A curve that combines the output data from two constituent curves into a tuple output. Curves
/// of this type are produced by [`CurveExt::zip`].
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect, FromReflect),
reflect(from_reflect = false)
)]
pub struct ZipCurve<S, T, C, D> {
pub(crate) domain: Interval,
pub(crate) first: C,
pub(crate) second: D,
#[cfg_attr(feature = "bevy_reflect", reflect(ignore, clone))]
pub(crate) _phantom: PhantomData<fn() -> (S, T)>,
}
impl<S, T, C, D> Curve<(S, T)> for ZipCurve<S, T, C, D>
where
C: Curve<S>,
D: Curve<T>,
{
#[inline]
fn domain(&self) -> Interval {
self.domain
}
#[inline]
fn sample_unchecked(&self, t: f32) -> (S, T) {
(
self.first.sample_unchecked(t),
self.second.sample_unchecked(t),
)
}
}
/// The curve that results from chaining one curve with another. The second curve is
/// effectively reparametrized so that its start is at the end of the first.
///
/// For this to be well-formed, the first curve's domain must be right-finite and the second's
/// must be left-finite.
///
/// Curves of this type are produced by [`CurveExt::chain`].
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect, FromReflect),
reflect(from_reflect = false)
)]
pub struct ChainCurve<T, C, D> {
pub(crate) first: C,
pub(crate) second: D,
#[cfg_attr(feature = "bevy_reflect", reflect(ignore, clone))]
pub(crate) _phantom: PhantomData<fn() -> T>,
}
impl<T, C, D> Curve<T> for ChainCurve<T, C, D>
where
C: Curve<T>,
D: Curve<T>,
{
#[inline]
fn domain(&self) -> Interval {
// This unwrap always succeeds because `first` has a valid Interval as its domain and the
// length of `second` cannot be NAN. It's still fine if it's infinity.
Interval::new(
self.first.domain().start(),
self.first.domain().end() + self.second.domain().length(),
)
.unwrap()
}
#[inline]
fn sample_unchecked(&self, t: f32) -> T {
if t > self.first.domain().end() {
self.second.sample_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_unchecked(t)
}
}
}
/// The curve that results from reversing another.
///
/// Curves of this type are produced by [`CurveExt::reverse`].
///
/// # Domain
///
/// The original curve's domain must be bounded to get a valid [`ReverseCurve`].
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect, FromReflect),
reflect(from_reflect = false)
)]
pub struct ReverseCurve<T, C> {
pub(crate) curve: C,
#[cfg_attr(feature = "bevy_reflect", reflect(ignore, clone))]
pub(crate) _phantom: PhantomData<fn() -> T>,
}
impl<T, C> Curve<T> for ReverseCurve<T, C>
where
C: Curve<T>,
{
#[inline]
fn domain(&self) -> Interval {
self.curve.domain()
}
#[inline]
fn sample_unchecked(&self, t: f32) -> T {
self.curve
.sample_unchecked(self.domain().end() - (t - self.domain().start()))
}
}
/// The curve that results from repeating a curve `N` times.
///
/// # Notes
///
/// - the value at the transitioning points (`domain.end() * n` for `n >= 1`) in the results is the
/// value at `domain.end()` in the original curve
///
/// Curves of this type are produced by [`CurveExt::repeat`].
///
/// # Domain
///
/// The original curve's domain must be bounded to get a valid [`RepeatCurve`].
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect, FromReflect),
reflect(from_reflect = false)
)]
pub struct RepeatCurve<T, C> {
pub(crate) domain: Interval,
pub(crate) curve: C,
#[cfg_attr(feature = "bevy_reflect", reflect(ignore, clone))]
pub(crate) _phantom: PhantomData<fn() -> T>,
}
impl<T, C> Curve<T> for RepeatCurve<T, C>
where
C: Curve<T>,
{
#[inline]
fn domain(&self) -> Interval {
self.domain
}
#[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());
if t != d.start() && cyclic_t == 0.0 {
d.end()
} else {
d.start() + cyclic_t
}
}
}
/// The curve that results from repeating a curve forever.
///
/// # Notes
///
/// - the value at the transitioning points (`domain.end() * n` for `n >= 1`) in the results is the
/// value at `domain.end()` in the original curve
///
/// Curves of this type are produced by [`CurveExt::forever`].
///
/// # Domain
///
/// The original curve's domain must be bounded to get a valid [`ForeverCurve`].
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect, FromReflect),
reflect(from_reflect = false)
)]
pub struct ForeverCurve<T, C> {
pub(crate) curve: C,
#[cfg_attr(feature = "bevy_reflect", reflect(ignore, clone))]
pub(crate) _phantom: PhantomData<fn() -> T>,
}
impl<T, C> Curve<T> for ForeverCurve<T, C>
where
C: Curve<T>,
{
#[inline]
fn domain(&self) -> Interval {
Interval::EVERYWHERE
}
#[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());
if t != d.start() && cyclic_t == 0.0 {
d.end()
} else {
d.start() + cyclic_t
}
}
}
/// The curve that results from chaining a curve with its reversed version. The transition point
/// is guaranteed to make no jump.
///
/// Curves of this type are produced by [`CurveExt::ping_pong`].
///
/// # Domain
///
/// The original curve's domain must be right-finite to get a valid [`PingPongCurve`].
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect, FromReflect),
reflect(from_reflect = false)
)]
pub struct PingPongCurve<T, C> {
pub(crate) curve: C,
#[cfg_attr(feature = "bevy_reflect", reflect(ignore, clone))]
pub(crate) _phantom: PhantomData<fn() -> T>,
}
impl<T, C> Curve<T> for PingPongCurve<T, C>
where
C: Curve<T>,
{
#[inline]
fn domain(&self) -> Interval {
// This unwrap always succeeds because `curve` has a valid Interval as its domain and the
// length of `curve` cannot be NAN. It's still fine if it's infinity.
Interval::new(
self.curve.domain().start(),
self.curve.domain().end() + self.curve.domain().length(),
)
.unwrap()
}
#[inline]
fn sample_unchecked(&self, t: f32) -> T {
// the domain is bounded by construction
let final_t = if t > self.curve.domain().end() {
self.curve.domain().end() * 2.0 - t
} else {
t
};
self.curve.sample_unchecked(final_t)
}
}
/// The curve that results from chaining two curves.
///
/// Additionally the transition of the samples is guaranteed to not make sudden jumps. This is
/// useful if you really just know about the shapes of your curves and don't want to deal with
/// stitching them together properly when it would just introduce useless complexity. It is
/// realized by translating the second curve so that its start sample point coincides with the
/// first curves' end sample point.
///
/// Curves of this type are produced by [`CurveExt::chain_continue`].
///
/// # Domain
///
/// The first curve's domain must be right-finite and the second's must be left-finite to get a
/// valid [`ContinuationCurve`].
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect, FromReflect),
reflect(from_reflect = false)
)]
pub struct ContinuationCurve<T, C, D> {
pub(crate) first: C,
pub(crate) second: D,
// cache the offset in the curve directly to prevent triple sampling for every sample we make
pub(crate) offset: T,
#[cfg_attr(feature = "bevy_reflect", reflect(ignore, clone))]
pub(crate) _phantom: PhantomData<fn() -> T>,
}
impl<T, C, D> Curve<T> for ContinuationCurve<T, C, D>
where
T: VectorSpace,
C: Curve<T>,
D: Curve<T>,
{
#[inline]
fn domain(&self) -> Interval {
// This unwrap always succeeds because `curve` has a valid Interval as its domain and the
// length of `curve` cannot be NAN. It's still fine if it's infinity.
Interval::new(
self.first.domain().start(),
self.first.domain().end() + self.second.domain().length(),
)
.unwrap()
}
#[inline]
fn sample_unchecked(&self, t: f32) -> T {
if t > self.first.domain().end() {
self.second.sample_unchecked(
// `t - first.domain.end` computes the offset into the domain of the second.
t - self.first.domain().end() + self.second.domain().start(),
) + self.offset
} else {
self.first.sample_unchecked(t)
}
}
}

826
vendor/bevy_math/src/curve/cores.rs vendored Normal file
View File

@@ -0,0 +1,826 @@
//! Core data structures to be used internally in Curve implementations, encapsulating storage
//! and access patterns for reuse.
//!
//! The `Core` types here expose their fields publicly so that it is easier to manipulate and
//! extend them, but in doing so, you must maintain the invariants of those fields yourself. The
//! provided methods all maintain the invariants, so this is only a concern if you manually mutate
//! the fields.
use crate::ops;
use super::interval::Interval;
use core::fmt::Debug;
use thiserror::Error;
#[cfg(feature = "alloc")]
use {alloc::vec::Vec, itertools::Itertools};
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::Reflect;
/// This type expresses the relationship of a value to a fixed collection of values. It is a kind
/// of summary used intermediately by sampling operations.
#[derive(Debug, Copy, Clone, PartialEq)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
pub enum InterpolationDatum<T> {
/// This value lies exactly on a value in the family.
Exact(T),
/// This value is off the left tail of the family; the inner value is the family's leftmost.
LeftTail(T),
/// This value is off the right tail of the family; the inner value is the family's rightmost.
RightTail(T),
/// This value lies on the interior, in between two points, with a third parameter expressing
/// the interpolation factor between the two.
Between(T, T, f32),
}
impl<T> InterpolationDatum<T> {
/// Map all values using a given function `f`, leaving the interpolation parameters in any
/// [`Between`] variants unchanged.
///
/// [`Between`]: `InterpolationDatum::Between`
#[must_use]
pub fn map<S>(self, f: impl Fn(T) -> S) -> InterpolationDatum<S> {
match self {
InterpolationDatum::Exact(v) => InterpolationDatum::Exact(f(v)),
InterpolationDatum::LeftTail(v) => InterpolationDatum::LeftTail(f(v)),
InterpolationDatum::RightTail(v) => InterpolationDatum::RightTail(f(v)),
InterpolationDatum::Between(u, v, s) => InterpolationDatum::Between(f(u), f(v), s),
}
}
}
/// The data core of a curve derived from evenly-spaced samples. The intention is to use this
/// in addition to explicit or inferred interpolation information in user-space in order to
/// implement curves using [`domain`] and [`sample_with`].
///
/// The internals are made transparent to give curve authors freedom, but [the provided constructor]
/// enforces the required invariants, and the methods maintain those invariants.
///
/// [the provided constructor]: EvenCore::new
/// [`domain`]: EvenCore::domain
/// [`sample_with`]: EvenCore::sample_with
///
/// # Example
/// ```rust
/// # use bevy_math::curve::*;
/// # use bevy_math::curve::cores::*;
/// // Let's make a curve that interpolates evenly spaced samples using either linear interpolation
/// // or step "interpolation" — i.e. just using the most recent sample as the source of truth.
/// enum InterpolationMode {
/// Linear,
/// Step,
/// }
///
/// // Linear interpolation mode is driven by a trait.
/// trait LinearInterpolate {
/// fn lerp(&self, other: &Self, t: f32) -> Self;
/// }
///
/// // Step interpolation just uses an explicit function.
/// fn step<T: Clone>(first: &T, second: &T, t: f32) -> T {
/// if t >= 1.0 {
/// second.clone()
/// } else {
/// first.clone()
/// }
/// }
///
/// // Omitted: Implementing `LinearInterpolate` on relevant types; e.g. `f32`, `Vec3`, and so on.
///
/// // The curve itself uses `EvenCore` to hold the evenly-spaced samples, and the `sample_with`
/// // function will do all the work of interpolating once given a function to do it with.
/// struct MyCurve<T> {
/// core: EvenCore<T>,
/// interpolation_mode: InterpolationMode,
/// }
///
/// impl<T> Curve<T> for MyCurve<T>
/// where
/// T: LinearInterpolate + Clone,
/// {
/// fn domain(&self) -> Interval {
/// self.core.domain()
/// }
///
/// fn sample_unchecked(&self, t: f32) -> T {
/// // To sample this curve, check the interpolation mode and dispatch accordingly.
/// match self.interpolation_mode {
/// InterpolationMode::Linear => self.core.sample_with(t, <T as LinearInterpolate>::lerp),
/// InterpolationMode::Step => self.core.sample_with(t, step),
/// }
/// }
/// }
/// ```
#[cfg(feature = "alloc")]
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
pub struct EvenCore<T> {
/// The domain over which the samples are taken, which corresponds to the domain of the curve
/// formed by interpolating them.
///
/// # Invariants
/// This must always be a bounded interval; i.e. its endpoints must be finite.
pub domain: Interval,
/// The samples that are interpolated to extract values.
///
/// # Invariants
/// This must always have a length of at least 2.
pub samples: Vec<T>,
}
/// An error indicating that an [`EvenCore`] could not be constructed.
#[derive(Debug, Error)]
#[error("Could not construct an EvenCore")]
pub enum EvenCoreError {
/// Not enough samples were provided.
#[error("Need at least two samples to create an EvenCore, but {samples} were provided")]
NotEnoughSamples {
/// The number of samples that were provided.
samples: usize,
},
/// Unbounded domains are not compatible with `EvenCore`.
#[error("Cannot create an EvenCore over an unbounded domain")]
UnboundedDomain,
}
#[cfg(feature = "alloc")]
impl<T> EvenCore<T> {
/// Create a new [`EvenCore`] from the specified `domain` and `samples`. The samples are
/// regarded to be evenly spaced within the given domain interval, so that the outermost
/// samples form the boundary of that interval. An error is returned if there are not at
/// least 2 samples or if the given domain is unbounded.
#[inline]
pub fn new(
domain: Interval,
samples: impl IntoIterator<Item = T>,
) -> Result<Self, EvenCoreError> {
let samples: Vec<T> = samples.into_iter().collect();
if samples.len() < 2 {
return Err(EvenCoreError::NotEnoughSamples {
samples: samples.len(),
});
}
if !domain.is_bounded() {
return Err(EvenCoreError::UnboundedDomain);
}
Ok(EvenCore { domain, samples })
}
/// The domain of the curve derived from this core.
#[inline]
pub const fn domain(&self) -> Interval {
self.domain
}
/// Obtain a value from the held samples using the given `interpolation` to interpolate
/// between adjacent samples.
///
/// The interpolation takes two values by reference together with a scalar parameter and
/// produces an owned value. The expectation is that `interpolation(&x, &y, 0.0)` and
/// `interpolation(&x, &y, 1.0)` are equivalent to `x` and `y` respectively.
#[inline]
pub fn sample_with<I>(&self, t: f32, interpolation: I) -> T
where
T: Clone,
I: Fn(&T, &T, f32) -> T,
{
match even_interp(self.domain, self.samples.len(), t) {
InterpolationDatum::Exact(idx)
| InterpolationDatum::LeftTail(idx)
| InterpolationDatum::RightTail(idx) => self.samples[idx].clone(),
InterpolationDatum::Between(lower_idx, upper_idx, s) => {
interpolation(&self.samples[lower_idx], &self.samples[upper_idx], s)
}
}
}
/// Given a time `t`, obtain a [`InterpolationDatum`] which governs how interpolation might recover
/// a sample at time `t`. For example, when a [`Between`] value is returned, its contents can
/// be used to interpolate between the two contained values with the given parameter. The other
/// variants give additional context about where the value is relative to the family of samples.
///
/// [`Between`]: `InterpolationDatum::Between`
pub fn sample_interp(&self, t: f32) -> InterpolationDatum<&T> {
even_interp(self.domain, self.samples.len(), t).map(|idx| &self.samples[idx])
}
/// Like [`sample_interp`], but the returned values include the sample times. This can be
/// useful when sample interpolation is not scale-invariant.
///
/// [`sample_interp`]: EvenCore::sample_interp
pub fn sample_interp_timed(&self, t: f32) -> InterpolationDatum<(f32, &T)> {
let segment_len = self.domain.length() / (self.samples.len() - 1) as f32;
even_interp(self.domain, self.samples.len(), t).map(|idx| {
(
self.domain.start() + segment_len * idx as f32,
&self.samples[idx],
)
})
}
}
/// Given a domain and a number of samples taken over that interval, return an [`InterpolationDatum`]
/// that governs how samples are extracted relative to the stored data.
///
/// `domain` must be a bounded interval (i.e. `domain.is_bounded() == true`).
///
/// `samples` must be at least 2.
///
/// This function will never panic, but it may return invalid indices if its assumptions are violated.
pub fn even_interp(domain: Interval, samples: usize, t: f32) -> InterpolationDatum<usize> {
let subdivs = samples - 1;
let step = domain.length() / subdivs as f32;
let t_shifted = t - domain.start();
let steps_taken = t_shifted / step;
if steps_taken <= 0.0 {
// To the left side of all the samples.
InterpolationDatum::LeftTail(0)
} else if steps_taken >= subdivs as f32 {
// To the right side of all the samples
InterpolationDatum::RightTail(samples - 1)
} else {
let lower_index = ops::floor(steps_taken) as usize;
// This upper index is always valid because `steps_taken` is a finite value
// strictly less than `samples - 1`, so its floor is at most `samples - 2`
let upper_index = lower_index + 1;
let s = ops::fract(steps_taken);
InterpolationDatum::Between(lower_index, upper_index, s)
}
}
/// The data core of a curve defined by unevenly-spaced samples or keyframes. The intention is to
/// use this in concert with implicitly or explicitly-defined interpolation in user-space in
/// order to implement the curve interface using [`domain`] and [`sample_with`].
///
/// The internals are made transparent to give curve authors freedom, but [the provided constructor]
/// enforces the required invariants, and the methods maintain those invariants.
///
/// # Example
/// ```rust
/// # use bevy_math::curve::*;
/// # use bevy_math::curve::cores::*;
/// // Let's make a curve formed by interpolating rotations.
/// // We'll support two common modes of interpolation:
/// // - Normalized linear: First do linear interpolation, then normalize to get a valid rotation.
/// // - Spherical linear: Interpolate through valid rotations with constant angular velocity.
/// enum InterpolationMode {
/// NormalizedLinear,
/// SphericalLinear,
/// }
///
/// // Our interpolation modes will be driven by traits.
/// trait NormalizedLinearInterpolate {
/// fn nlerp(&self, other: &Self, t: f32) -> Self;
/// }
///
/// trait SphericalLinearInterpolate {
/// fn slerp(&self, other: &Self, t: f32) -> Self;
/// }
///
/// // Omitted: These traits would be implemented for `Rot2`, `Quat`, and other rotation representations.
///
/// // The curve itself just needs to use the curve core for keyframes, `UnevenCore`, which handles
/// // everything except for the explicit interpolation used.
/// struct RotationCurve<T> {
/// core: UnevenCore<T>,
/// interpolation_mode: InterpolationMode,
/// }
///
/// impl<T> Curve<T> for RotationCurve<T>
/// where
/// T: NormalizedLinearInterpolate + SphericalLinearInterpolate + Clone,
/// {
/// fn domain(&self) -> Interval {
/// self.core.domain()
/// }
///
/// fn sample_unchecked(&self, t: f32) -> T {
/// // To sample the curve, we just look at the interpolation mode and
/// // dispatch accordingly.
/// match self.interpolation_mode {
/// InterpolationMode::NormalizedLinear =>
/// self.core.sample_with(t, <T as NormalizedLinearInterpolate>::nlerp),
/// InterpolationMode::SphericalLinear =>
/// self.core.sample_with(t, <T as SphericalLinearInterpolate>::slerp),
/// }
/// }
/// }
/// ```
///
/// [`domain`]: UnevenCore::domain
/// [`sample_with`]: UnevenCore::sample_with
/// [the provided constructor]: UnevenCore::new
#[cfg(feature = "alloc")]
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
pub struct UnevenCore<T> {
/// The times for the samples of this curve.
///
/// # Invariants
/// This must always have a length of at least 2, be sorted, and have no
/// duplicated or non-finite times.
pub times: Vec<f32>,
/// The samples corresponding to the times for this curve.
///
/// # Invariants
/// This must always have the same length as `times`.
pub samples: Vec<T>,
}
/// An error indicating that an [`UnevenCore`] could not be constructed.
#[derive(Debug, Error)]
#[error("Could not construct an UnevenCore")]
pub enum UnevenCoreError {
/// Not enough samples were provided.
#[error(
"Need at least two unique samples to create an UnevenCore, but {samples} were provided"
)]
NotEnoughSamples {
/// The number of samples that were provided.
samples: usize,
},
}
#[cfg(feature = "alloc")]
impl<T> UnevenCore<T> {
/// Create a new [`UnevenCore`]. The given samples are filtered to finite times and
/// sorted internally; if there are not at least 2 valid timed samples, an error will be
/// returned.
pub fn new(timed_samples: impl IntoIterator<Item = (f32, T)>) -> Result<Self, UnevenCoreError> {
// Filter out non-finite sample times first so they don't interfere with sorting/deduplication.
let mut timed_samples = timed_samples
.into_iter()
.filter(|(t, _)| t.is_finite())
.collect_vec();
timed_samples
// Using `total_cmp` is fine because no NANs remain and because deduplication uses
// `PartialEq` anyway (so -0.0 and 0.0 will be considered equal later regardless).
.sort_by(|(t0, _), (t1, _)| t0.total_cmp(t1));
timed_samples.dedup_by_key(|(t, _)| *t);
if timed_samples.len() < 2 {
return Err(UnevenCoreError::NotEnoughSamples {
samples: timed_samples.len(),
});
}
let (times, samples): (Vec<f32>, Vec<T>) = timed_samples.into_iter().unzip();
Ok(UnevenCore { times, samples })
}
/// The domain of the curve derived from this core.
///
/// # Panics
/// This method may panic if the type's invariants aren't satisfied.
#[inline]
pub fn domain(&self) -> Interval {
let start = self.times.first().unwrap();
let end = self.times.last().unwrap();
Interval::new(*start, *end).unwrap()
}
/// Obtain a value from the held samples using the given `interpolation` to interpolate
/// between adjacent samples.
///
/// The interpolation takes two values by reference together with a scalar parameter and
/// produces an owned value. The expectation is that `interpolation(&x, &y, 0.0)` and
/// `interpolation(&x, &y, 1.0)` are equivalent to `x` and `y` respectively.
#[inline]
pub fn sample_with<I>(&self, t: f32, interpolation: I) -> T
where
T: Clone,
I: Fn(&T, &T, f32) -> T,
{
match uneven_interp(&self.times, t) {
InterpolationDatum::Exact(idx)
| InterpolationDatum::LeftTail(idx)
| InterpolationDatum::RightTail(idx) => self.samples[idx].clone(),
InterpolationDatum::Between(lower_idx, upper_idx, s) => {
interpolation(&self.samples[lower_idx], &self.samples[upper_idx], s)
}
}
}
/// Given a time `t`, obtain a [`InterpolationDatum`] which governs how interpolation might recover
/// a sample at time `t`. For example, when a [`Between`] value is returned, its contents can
/// be used to interpolate between the two contained values with the given parameter. The other
/// variants give additional context about where the value is relative to the family of samples.
///
/// [`Between`]: `InterpolationDatum::Between`
pub fn sample_interp(&self, t: f32) -> InterpolationDatum<&T> {
uneven_interp(&self.times, t).map(|idx| &self.samples[idx])
}
/// Like [`sample_interp`], but the returned values include the sample times. This can be
/// useful when sample interpolation is not scale-invariant.
///
/// [`sample_interp`]: UnevenCore::sample_interp
pub fn sample_interp_timed(&self, t: f32) -> InterpolationDatum<(f32, &T)> {
uneven_interp(&self.times, t).map(|idx| (self.times[idx], &self.samples[idx]))
}
/// This core, but with the sample times moved by the map `f`.
/// In principle, when `f` is monotone, this is equivalent to [`CurveExt::reparametrize`],
/// but the function inputs to each are inverses of one another.
///
/// The samples are re-sorted by time after mapping and deduplicated by output time, so
/// the function `f` should generally be injective over the set of sample times, otherwise
/// data will be deleted.
///
/// [`CurveExt::reparametrize`]: crate::curve::CurveExt::reparametrize
#[must_use]
pub fn map_sample_times(mut self, f: impl Fn(f32) -> f32) -> UnevenCore<T> {
let mut timed_samples = self
.times
.into_iter()
.map(f)
.zip(self.samples)
.collect_vec();
timed_samples.sort_by(|(t1, _), (t2, _)| t1.total_cmp(t2));
timed_samples.dedup_by_key(|(t, _)| *t);
(self.times, self.samples) = timed_samples.into_iter().unzip();
self
}
}
/// The data core of a curve using uneven samples (i.e. keyframes), where each sample time
/// yields some fixed number of values — the [sampling width]. This may serve as storage for
/// curves that yield vectors or iterators, and in some cases, it may be useful for cache locality
/// if the sample type can effectively be encoded as a fixed-length slice of values.
///
/// [sampling width]: ChunkedUnevenCore::width
#[cfg(feature = "alloc")]
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
pub struct ChunkedUnevenCore<T> {
/// The times, one for each sample.
///
/// # Invariants
/// This must always have a length of at least 2, be sorted, and have no duplicated or
/// non-finite times.
pub times: Vec<f32>,
/// The values that are used in sampling. Each width-worth of these correspond to a single sample.
///
/// # Invariants
/// The length of this vector must always be some fixed integer multiple of that of `times`.
pub values: Vec<T>,
}
/// An error that indicates that a [`ChunkedUnevenCore`] could not be formed.
#[derive(Debug, Error)]
#[error("Could not create a ChunkedUnevenCore")]
pub enum ChunkedUnevenCoreError {
/// The width of a `ChunkedUnevenCore` cannot be zero.
#[error("Chunk width must be at least 1")]
ZeroWidth,
/// At least two sample times are necessary to interpolate in `ChunkedUnevenCore`.
#[error(
"Need at least two unique samples to create a ChunkedUnevenCore, but {samples} were provided"
)]
NotEnoughSamples {
/// The number of samples that were provided.
samples: usize,
},
/// The length of the value buffer is supposed to be the `width` times the number of samples.
#[error("Expected {expected} total values based on width, but {actual} were provided")]
MismatchedLengths {
/// The expected length of the value buffer.
expected: usize,
/// The actual length of the value buffer.
actual: usize,
},
/// Tried to infer the width, but the ratio of lengths wasn't an integer, so no such length exists.
#[error("The length of the list of values ({values_len}) was not divisible by that of the list of times ({times_len})")]
NonDivisibleLengths {
/// The length of the value buffer.
values_len: usize,
/// The length of the time buffer.
times_len: usize,
},
}
#[cfg(feature = "alloc")]
impl<T> ChunkedUnevenCore<T> {
/// Create a new [`ChunkedUnevenCore`]. The given `times` are sorted, filtered to finite times,
/// and deduplicated. See the [type-level documentation] for more information about this type.
///
/// Produces an error in any of the following circumstances:
/// - `width` is zero.
/// - `times` has less than `2` unique valid entries.
/// - `values` has the incorrect length relative to `times`.
///
/// [type-level documentation]: ChunkedUnevenCore
pub fn new(
times: impl IntoIterator<Item = f32>,
values: impl IntoIterator<Item = T>,
width: usize,
) -> Result<Self, ChunkedUnevenCoreError> {
let times = times.into_iter().collect_vec();
let values = values.into_iter().collect_vec();
if width == 0 {
return Err(ChunkedUnevenCoreError::ZeroWidth);
}
let times = filter_sort_dedup_times(times);
if times.len() < 2 {
return Err(ChunkedUnevenCoreError::NotEnoughSamples {
samples: times.len(),
});
}
if values.len() != times.len() * width {
return Err(ChunkedUnevenCoreError::MismatchedLengths {
expected: times.len() * width,
actual: values.len(),
});
}
Ok(Self { times, values })
}
/// Create a new [`ChunkedUnevenCore`], inferring the width from the sizes of the inputs.
/// The given `times` are sorted, filtered to finite times, and deduplicated. See the
/// [type-level documentation] for more information about this type. Prefer using [`new`]
/// if possible, since that constructor has richer error checking.
///
/// Produces an error in any of the following circumstances:
/// - `values` has length zero.
/// - `times` has less than `2` unique valid entries.
/// - The length of `values` is not divisible by that of `times` (once sorted, filtered,
/// and deduplicated).
///
/// The [width] is implicitly taken to be the length of `values` divided by that of `times`
/// (once sorted, filtered, and deduplicated).
///
/// [type-level documentation]: ChunkedUnevenCore
/// [`new`]: ChunkedUnevenCore::new
/// [width]: ChunkedUnevenCore::width
pub fn new_width_inferred(
times: impl IntoIterator<Item = f32>,
values: impl IntoIterator<Item = T>,
) -> Result<Self, ChunkedUnevenCoreError> {
let times = times.into_iter().collect_vec();
let values = values.into_iter().collect_vec();
let times = filter_sort_dedup_times(times);
if times.len() < 2 {
return Err(ChunkedUnevenCoreError::NotEnoughSamples {
samples: times.len(),
});
}
if values.len() % times.len() != 0 {
return Err(ChunkedUnevenCoreError::NonDivisibleLengths {
values_len: values.len(),
times_len: times.len(),
});
}
if values.is_empty() {
return Err(ChunkedUnevenCoreError::ZeroWidth);
}
Ok(Self { times, values })
}
/// The domain of the curve derived from this core.
///
/// # Panics
/// This may panic if this type's invariants aren't met.
#[inline]
pub fn domain(&self) -> Interval {
let start = self.times.first().unwrap();
let end = self.times.last().unwrap();
Interval::new(*start, *end).unwrap()
}
/// The sample width: the number of values that are contained in each sample.
#[inline]
pub fn width(&self) -> usize {
self.values.len() / self.times.len()
}
/// Given a time `t`, obtain a [`InterpolationDatum`] which governs how interpolation might recover
/// a sample at time `t`. For example, when a [`Between`] value is returned, its contents can
/// be used to interpolate between the two contained values with the given parameter. The other
/// variants give additional context about where the value is relative to the family of samples.
///
/// [`Between`]: `InterpolationDatum::Between`
#[inline]
pub fn sample_interp(&self, t: f32) -> InterpolationDatum<&[T]> {
uneven_interp(&self.times, t).map(|idx| self.time_index_to_slice(idx))
}
/// Like [`sample_interp`], but the returned values include the sample times. This can be
/// useful when sample interpolation is not scale-invariant.
///
/// [`sample_interp`]: ChunkedUnevenCore::sample_interp
pub fn sample_interp_timed(&self, t: f32) -> InterpolationDatum<(f32, &[T])> {
uneven_interp(&self.times, t).map(|idx| (self.times[idx], self.time_index_to_slice(idx)))
}
/// Given an index in [times], returns the slice of [values] that correspond to the sample at
/// that time.
///
/// [times]: ChunkedUnevenCore::times
/// [values]: ChunkedUnevenCore::values
#[inline]
fn time_index_to_slice(&self, idx: usize) -> &[T] {
let width = self.width();
let lower_idx = width * idx;
let upper_idx = lower_idx + width;
&self.values[lower_idx..upper_idx]
}
}
/// Sort the given times, deduplicate them, and filter them to only finite times.
#[cfg(feature = "alloc")]
fn filter_sort_dedup_times(times: impl IntoIterator<Item = f32>) -> Vec<f32> {
// Filter before sorting/deduplication so that NAN doesn't interfere with them.
let mut times = times.into_iter().filter(|t| t.is_finite()).collect_vec();
times.sort_by(f32::total_cmp);
times.dedup();
times
}
/// Given a list of `times` and a target value, get the interpolation relationship for the
/// target value in terms of the indices of the starting list. In a sense, this encapsulates the
/// heart of uneven/keyframe sampling.
///
/// `times` is assumed to be sorted, deduplicated, and consisting only of finite values. It is also
/// assumed to contain at least two values.
///
/// # Panics
/// This function will panic if `times` contains NAN.
pub fn uneven_interp(times: &[f32], t: f32) -> InterpolationDatum<usize> {
match times.binary_search_by(|pt| pt.partial_cmp(&t).unwrap()) {
Ok(index) => InterpolationDatum::Exact(index),
Err(index) => {
if index == 0 {
// This is before the first keyframe.
InterpolationDatum::LeftTail(0)
} else if index >= times.len() {
// This is after the last keyframe.
InterpolationDatum::RightTail(times.len() - 1)
} else {
// This is actually in the middle somewhere.
let t_lower = times[index - 1];
let t_upper = times[index];
let s = (t - t_lower) / (t_upper - t_lower);
InterpolationDatum::Between(index - 1, index, s)
}
}
}
}
#[cfg(all(test, feature = "alloc"))]
mod tests {
use super::{ChunkedUnevenCore, EvenCore, UnevenCore};
use crate::curve::{cores::InterpolationDatum, interval};
use alloc::vec;
use approx::{assert_abs_diff_eq, AbsDiffEq};
fn approx_between<T>(datum: InterpolationDatum<T>, start: T, end: T, p: f32) -> bool
where
T: PartialEq,
{
if let InterpolationDatum::Between(m_start, m_end, m_p) = datum {
m_start == start && m_end == end && m_p.abs_diff_eq(&p, 1e-6)
} else {
false
}
}
fn is_left_tail<T>(datum: InterpolationDatum<T>) -> bool {
matches!(datum, InterpolationDatum::LeftTail(_))
}
fn is_right_tail<T>(datum: InterpolationDatum<T>) -> bool {
matches!(datum, InterpolationDatum::RightTail(_))
}
fn is_exact<T>(datum: InterpolationDatum<T>, target: T) -> bool
where
T: PartialEq,
{
if let InterpolationDatum::Exact(v) = datum {
v == target
} else {
false
}
}
#[test]
fn even_sample_interp() {
let even_core = EvenCore::<f32>::new(
interval(0.0, 1.0).unwrap(),
// 11 entries -> 10 segments
vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0],
)
.expect("Failed to construct test core");
let datum = even_core.sample_interp(-1.0);
assert!(is_left_tail(datum));
let datum = even_core.sample_interp(0.0);
assert!(is_left_tail(datum));
let datum = even_core.sample_interp(1.0);
assert!(is_right_tail(datum));
let datum = even_core.sample_interp(2.0);
assert!(is_right_tail(datum));
let datum = even_core.sample_interp(0.05);
let InterpolationDatum::Between(0.0, 1.0, p) = datum else {
panic!("Sample did not lie in the correct subinterval")
};
assert_abs_diff_eq!(p, 0.5);
let datum = even_core.sample_interp(0.05);
assert!(approx_between(datum, &0.0, &1.0, 0.5));
let datum = even_core.sample_interp(0.33);
assert!(approx_between(datum, &3.0, &4.0, 0.3));
let datum = even_core.sample_interp(0.78);
assert!(approx_between(datum, &7.0, &8.0, 0.8));
let datum = even_core.sample_interp(0.5);
assert!(approx_between(datum, &4.0, &5.0, 1.0) || approx_between(datum, &5.0, &6.0, 0.0));
let datum = even_core.sample_interp(0.7);
assert!(approx_between(datum, &6.0, &7.0, 1.0) || approx_between(datum, &7.0, &8.0, 0.0));
}
#[test]
fn uneven_sample_interp() {
let uneven_core = UnevenCore::<f32>::new(vec![
(0.0, 0.0),
(1.0, 3.0),
(2.0, 9.0),
(4.0, 10.0),
(8.0, -5.0),
])
.expect("Failed to construct test core");
let datum = uneven_core.sample_interp(-1.0);
assert!(is_left_tail(datum));
let datum = uneven_core.sample_interp(0.0);
assert!(is_exact(datum, &0.0));
let datum = uneven_core.sample_interp(8.0);
assert!(is_exact(datum, &(-5.0)));
let datum = uneven_core.sample_interp(9.0);
assert!(is_right_tail(datum));
let datum = uneven_core.sample_interp(0.5);
assert!(approx_between(datum, &0.0, &3.0, 0.5));
let datum = uneven_core.sample_interp(2.5);
assert!(approx_between(datum, &9.0, &10.0, 0.25));
let datum = uneven_core.sample_interp(7.0);
assert!(approx_between(datum, &10.0, &(-5.0), 0.75));
let datum = uneven_core.sample_interp(2.0);
assert!(is_exact(datum, &9.0));
let datum = uneven_core.sample_interp(4.0);
assert!(is_exact(datum, &10.0));
}
#[test]
fn chunked_uneven_sample_interp() {
let core =
ChunkedUnevenCore::new(vec![0.0, 2.0, 8.0], vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0], 2)
.expect("Failed to construct test core");
let datum = core.sample_interp(-1.0);
assert!(is_left_tail(datum));
let datum = core.sample_interp(0.0);
assert!(is_exact(datum, &[0.0, 1.0]));
let datum = core.sample_interp(8.0);
assert!(is_exact(datum, &[4.0, 5.0]));
let datum = core.sample_interp(10.0);
assert!(is_right_tail(datum));
let datum = core.sample_interp(1.0);
assert!(approx_between(datum, &[0.0, 1.0], &[2.0, 3.0], 0.5));
let datum = core.sample_interp(3.0);
assert!(approx_between(datum, &[2.0, 3.0], &[4.0, 5.0], 1.0 / 6.0));
let datum = core.sample_interp(2.0);
assert!(is_exact(datum, &[2.0, 3.0]));
}
}

View 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, CurveExt, 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);
}
}

View File

@@ -0,0 +1,230 @@
//! 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`]: crate::curve::CurveExt::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> + Sized
where
T: HasTangent,
{
/// This curve, but with its first derivative included in sampling.
///
/// Notably, the output type is a `Curve<WithDerivative<T>>`.
fn with_derivative(self) -> SampleDerivativeWrapper<Self>;
}
/// 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> + Sized
where
T: HasTangent,
{
/// This curve, but with its first two derivatives included in sampling.
///
/// Notably, the output type is a `Curve<WithTwoDerivatives<T>>`.
fn with_two_derivatives(self) -> SampleTwoDerivativesWrapper<Self>;
}
/// 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) -> SampleDerivativeWrapper<Self> {
SampleDerivativeWrapper(self)
}
}
impl<T, C> CurveWithTwoDerivatives<T> for C
where
T: HasTangent,
C: SampleTwoDerivatives<T> + CurveWithDerivative<T>,
{
fn with_two_derivatives(self) -> SampleTwoDerivativesWrapper<Self> {
SampleTwoDerivativesWrapper(self)
}
}

1202
vendor/bevy_math/src/curve/easing.rs vendored Normal file

File diff suppressed because it is too large Load Diff

382
vendor/bevy_math/src/curve/interval.rs vendored Normal file
View File

@@ -0,0 +1,382 @@
//! The [`Interval`] type for nonempty intervals used by the [`Curve`](super::Curve) trait.
use core::{
cmp::{max_by, min_by},
ops::RangeInclusive,
};
use itertools::Either;
use thiserror::Error;
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::Reflect;
#[cfg(all(feature = "serialize", feature = "bevy_reflect"))]
use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
/// A nonempty closed interval, possibly unbounded in either direction.
///
/// In other words, the interval may stretch all the way to positive or negative infinity, but it
/// will always have some nonempty interior.
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Clone)
)]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
pub struct Interval {
start: f32,
end: f32,
}
/// An error that indicates that an operation would have returned an invalid [`Interval`].
#[derive(Debug, Error)]
#[error("The resulting interval would be invalid (empty or with a NaN endpoint)")]
pub struct InvalidIntervalError;
/// An error indicating that spaced points could not be extracted from an unbounded interval.
#[derive(Debug, Error)]
#[error("Cannot extract spaced points from an unbounded interval")]
pub struct SpacedPointsError;
/// An error indicating that a linear map between intervals could not be constructed because of
/// unboundedness.
#[derive(Debug, Error)]
#[error("Could not construct linear function to map between intervals")]
pub(super) enum LinearMapError {
/// The source interval being mapped out of was unbounded.
#[error("The source interval is unbounded")]
SourceUnbounded,
/// The target interval being mapped into was unbounded.
#[error("The target interval is unbounded")]
TargetUnbounded,
}
impl Interval {
/// Create a new [`Interval`] with the specified `start` and `end`. The interval can be unbounded
/// but cannot be empty (so `start` must be less than `end`) and neither endpoint can be NaN; invalid
/// parameters will result in an error.
#[inline]
pub fn new(start: f32, end: f32) -> Result<Self, InvalidIntervalError> {
if start >= end || start.is_nan() || end.is_nan() {
Err(InvalidIntervalError)
} else {
Ok(Self { start, end })
}
}
/// An interval of length 1.0, starting at 0.0 and ending at 1.0.
pub const UNIT: Self = Self {
start: 0.0,
end: 1.0,
};
/// An interval which stretches across the entire real line from negative infinity to infinity.
pub const EVERYWHERE: Self = Self {
start: f32::NEG_INFINITY,
end: f32::INFINITY,
};
/// Get the start of this interval.
#[inline]
pub const fn start(self) -> f32 {
self.start
}
/// Get the end of this interval.
#[inline]
pub const fn end(self) -> f32 {
self.end
}
/// Create an [`Interval`] by intersecting this interval with another. Returns an error if the
/// intersection would be empty (hence an invalid interval).
pub fn intersect(self, other: Interval) -> Result<Interval, InvalidIntervalError> {
let lower = max_by(self.start, other.start, f32::total_cmp);
let upper = min_by(self.end, other.end, f32::total_cmp);
Self::new(lower, upper)
}
/// Get the length of this interval. Note that the result may be infinite (`f32::INFINITY`).
#[inline]
pub fn length(self) -> f32 {
self.end - self.start
}
/// Returns `true` if this interval is bounded — that is, if both its start and end are finite.
///
/// Equivalently, an interval is bounded if its length is finite.
#[inline]
pub fn is_bounded(self) -> bool {
self.length().is_finite()
}
/// Returns `true` if this interval has a finite start.
#[inline]
pub fn has_finite_start(self) -> bool {
self.start.is_finite()
}
/// Returns `true` if this interval has a finite end.
#[inline]
pub fn has_finite_end(self) -> bool {
self.end.is_finite()
}
/// Returns `true` if `item` is contained in this interval.
#[inline]
pub fn contains(self, item: f32) -> bool {
(self.start..=self.end).contains(&item)
}
/// Returns `true` if the other interval is contained in this interval.
///
/// This is non-strict: each interval will contain itself.
#[inline]
pub fn contains_interval(self, other: Self) -> bool {
self.start <= other.start && self.end >= other.end
}
/// Clamp the given `value` to lie within this interval.
#[inline]
pub fn clamp(self, value: f32) -> f32 {
value.clamp(self.start, self.end)
}
/// Get an iterator over equally-spaced points from this interval in increasing order.
/// If `points` is 1, the start of this interval is returned. If `points` is 0, an empty
/// iterator is returned. An error is returned if the interval is unbounded.
#[inline]
pub fn spaced_points(
self,
points: usize,
) -> Result<impl Iterator<Item = f32>, SpacedPointsError> {
if !self.is_bounded() {
return Err(SpacedPointsError);
}
if points < 2 {
// If `points` is 1, this is `Some(self.start)` as an iterator, and if `points` is 0,
// then this is `None` as an iterator. This is written this way to avoid having to
// introduce a ternary disjunction of iterators.
let iter = (points == 1).then_some(self.start).into_iter();
return Ok(Either::Left(iter));
}
let step = self.length() / (points - 1) as f32;
let iter = (0..points).map(move |x| self.start + x as f32 * step);
Ok(Either::Right(iter))
}
/// Get the linear function which maps this interval onto the `other` one. Returns an error if either
/// interval is unbounded.
#[inline]
pub(super) fn linear_map_to(self, other: Self) -> Result<impl Fn(f32) -> f32, LinearMapError> {
if !self.is_bounded() {
return Err(LinearMapError::SourceUnbounded);
}
if !other.is_bounded() {
return Err(LinearMapError::TargetUnbounded);
}
let scale = other.length() / self.length();
Ok(move |x| (x - self.start) * scale + other.start)
}
}
impl TryFrom<RangeInclusive<f32>> for Interval {
type Error = InvalidIntervalError;
fn try_from(range: RangeInclusive<f32>) -> Result<Self, Self::Error> {
Interval::new(*range.start(), *range.end())
}
}
/// Create an [`Interval`] with a given `start` and `end`. Alias of [`Interval::new`].
#[inline]
pub fn interval(start: f32, end: f32) -> Result<Interval, InvalidIntervalError> {
Interval::new(start, end)
}
#[cfg(test)]
mod tests {
use crate::ops;
use super::*;
use alloc::vec::Vec;
use approx::{assert_abs_diff_eq, AbsDiffEq};
#[test]
fn make_intervals() {
let ivl = Interval::new(2.0, -1.0);
assert!(ivl.is_err());
let ivl = Interval::new(-0.0, 0.0);
assert!(ivl.is_err());
let ivl = Interval::new(f32::NEG_INFINITY, 15.5);
assert!(ivl.is_ok());
let ivl = Interval::new(-2.0, f32::INFINITY);
assert!(ivl.is_ok());
let ivl = Interval::new(f32::NEG_INFINITY, f32::INFINITY);
assert!(ivl.is_ok());
let ivl = Interval::new(f32::INFINITY, f32::NEG_INFINITY);
assert!(ivl.is_err());
let ivl = Interval::new(-1.0, f32::NAN);
assert!(ivl.is_err());
let ivl = Interval::new(f32::NAN, -42.0);
assert!(ivl.is_err());
let ivl = Interval::new(f32::NAN, f32::NAN);
assert!(ivl.is_err());
let ivl = Interval::new(0.0, 1.0);
assert!(ivl.is_ok());
}
#[test]
fn lengths() {
let ivl = interval(-5.0, 10.0).unwrap();
assert!(ops::abs(ivl.length() - 15.0) <= f32::EPSILON);
let ivl = interval(5.0, 100.0).unwrap();
assert!(ops::abs(ivl.length() - 95.0) <= f32::EPSILON);
let ivl = interval(0.0, f32::INFINITY).unwrap();
assert_eq!(ivl.length(), f32::INFINITY);
let ivl = interval(f32::NEG_INFINITY, 0.0).unwrap();
assert_eq!(ivl.length(), f32::INFINITY);
let ivl = Interval::EVERYWHERE;
assert_eq!(ivl.length(), f32::INFINITY);
}
#[test]
fn intersections() {
let ivl1 = interval(-1.0, 1.0).unwrap();
let ivl2 = interval(0.0, 2.0).unwrap();
let ivl3 = interval(-3.0, 0.0).unwrap();
let ivl4 = interval(0.0, f32::INFINITY).unwrap();
let ivl5 = interval(f32::NEG_INFINITY, 0.0).unwrap();
let ivl6 = Interval::EVERYWHERE;
assert!(ivl1.intersect(ivl2).is_ok_and(|ivl| ivl == Interval::UNIT));
assert!(ivl1
.intersect(ivl3)
.is_ok_and(|ivl| ivl == interval(-1.0, 0.0).unwrap()));
assert!(ivl2.intersect(ivl3).is_err());
assert!(ivl1.intersect(ivl4).is_ok_and(|ivl| ivl == Interval::UNIT));
assert!(ivl1
.intersect(ivl5)
.is_ok_and(|ivl| ivl == interval(-1.0, 0.0).unwrap()));
assert!(ivl4.intersect(ivl5).is_err());
assert_eq!(ivl1.intersect(ivl6).unwrap(), ivl1);
assert_eq!(ivl4.intersect(ivl6).unwrap(), ivl4);
assert_eq!(ivl5.intersect(ivl6).unwrap(), ivl5);
}
#[test]
fn containment() {
let ivl = Interval::UNIT;
assert!(ivl.contains(0.0));
assert!(ivl.contains(1.0));
assert!(ivl.contains(0.5));
assert!(!ivl.contains(-0.1));
assert!(!ivl.contains(1.1));
assert!(!ivl.contains(f32::NAN));
let ivl = interval(3.0, f32::INFINITY).unwrap();
assert!(ivl.contains(3.0));
assert!(ivl.contains(2.0e5));
assert!(ivl.contains(3.5e6));
assert!(!ivl.contains(2.5));
assert!(!ivl.contains(-1e5));
assert!(!ivl.contains(f32::NAN));
}
#[test]
fn interval_containment() {
let ivl = Interval::UNIT;
assert!(ivl.contains_interval(interval(-0.0, 0.5).unwrap()));
assert!(ivl.contains_interval(interval(0.5, 1.0).unwrap()));
assert!(ivl.contains_interval(interval(0.25, 0.75).unwrap()));
assert!(!ivl.contains_interval(interval(-0.25, 0.5).unwrap()));
assert!(!ivl.contains_interval(interval(0.5, 1.25).unwrap()));
assert!(!ivl.contains_interval(interval(0.25, f32::INFINITY).unwrap()));
assert!(!ivl.contains_interval(interval(f32::NEG_INFINITY, 0.75).unwrap()));
let big_ivl = interval(0.0, f32::INFINITY).unwrap();
assert!(big_ivl.contains_interval(interval(0.0, 5.0).unwrap()));
assert!(big_ivl.contains_interval(interval(0.0, f32::INFINITY).unwrap()));
assert!(big_ivl.contains_interval(interval(1.0, 5.0).unwrap()));
assert!(!big_ivl.contains_interval(interval(-1.0, f32::INFINITY).unwrap()));
assert!(!big_ivl.contains_interval(interval(-2.0, 5.0).unwrap()));
}
#[test]
fn boundedness() {
assert!(!Interval::EVERYWHERE.is_bounded());
assert!(interval(0.0, 3.5e5).unwrap().is_bounded());
assert!(!interval(-2.0, f32::INFINITY).unwrap().is_bounded());
assert!(!interval(f32::NEG_INFINITY, 5.0).unwrap().is_bounded());
}
#[test]
fn linear_maps() {
let ivl1 = interval(-3.0, 5.0).unwrap();
let ivl2 = Interval::UNIT;
let map = ivl1.linear_map_to(ivl2);
assert!(map.is_ok_and(|f| f(-3.0).abs_diff_eq(&0.0, f32::EPSILON)
&& f(5.0).abs_diff_eq(&1.0, f32::EPSILON)
&& f(1.0).abs_diff_eq(&0.5, f32::EPSILON)));
let ivl1 = Interval::UNIT;
let ivl2 = Interval::EVERYWHERE;
assert!(ivl1.linear_map_to(ivl2).is_err());
let ivl1 = interval(f32::NEG_INFINITY, -4.0).unwrap();
let ivl2 = Interval::UNIT;
assert!(ivl1.linear_map_to(ivl2).is_err());
}
#[test]
fn spaced_points() {
let ivl = interval(0.0, 50.0).unwrap();
let points_iter: Vec<f32> = ivl.spaced_points(1).unwrap().collect();
assert_abs_diff_eq!(points_iter[0], 0.0);
assert_eq!(points_iter.len(), 1);
let points_iter: Vec<f32> = ivl.spaced_points(2).unwrap().collect();
assert_abs_diff_eq!(points_iter[0], 0.0);
assert_abs_diff_eq!(points_iter[1], 50.0);
let points_iter = ivl.spaced_points(21).unwrap();
let step = ivl.length() / 20.0;
for (index, point) in points_iter.enumerate() {
let expected = ivl.start() + step * index as f32;
assert_abs_diff_eq!(point, expected);
}
let ivl = interval(-21.0, 79.0).unwrap();
let points_iter = ivl.spaced_points(10000).unwrap();
let step = ivl.length() / 9999.0;
for (index, point) in points_iter.enumerate() {
let expected = ivl.start() + step * index as f32;
assert_abs_diff_eq!(point, expected);
}
let ivl = interval(-1.0, f32::INFINITY).unwrap();
let points_iter = ivl.spaced_points(25);
assert!(points_iter.is_err());
let ivl = interval(f32::NEG_INFINITY, -25.0).unwrap();
let points_iter = ivl.spaced_points(9);
assert!(points_iter.is_err());
}
}

57
vendor/bevy_math/src/curve/iterable.rs vendored Normal file
View File

@@ -0,0 +1,57 @@
//! Iterable curves, which sample in the form of an iterator in order to support `Vec`-like
//! output whose length cannot be known statically.
use super::Interval;
#[cfg(feature = "alloc")]
use {super::ConstantCurve, alloc::vec::Vec};
/// A curve which provides samples in the form of [`Iterator`]s.
///
/// This is an abstraction that provides an interface for curves which look like `Curve<Vec<T>>`
/// but side-stepping issues with allocation on sampling. This happens when the size of an output
/// array cannot be known statically.
pub trait IterableCurve<T> {
/// The interval over which this curve is parametrized.
fn domain(&self) -> Interval;
/// Sample a point on this curve at the parameter value `t`, producing an iterator over values.
/// 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.
///
/// Values sampled from outside of a curve's domain are generally considered invalid; data which
/// is nonsensical or otherwise useless may be returned in such a circumstance, and extrapolation
/// beyond a curve's domain should not be relied upon.
fn sample_iter_unchecked(&self, t: f32) -> impl Iterator<Item = T>;
/// Sample this curve at a specified time `t`, producing an iterator over sampled values.
/// The parameter `t` is clamped to the domain of the curve.
fn sample_iter_clamped(&self, t: f32) -> impl Iterator<Item = T> {
let t_clamped = self.domain().clamp(t);
self.sample_iter_unchecked(t_clamped)
}
/// Sample this curve at a specified time `t`, producing an iterator over sampled values.
/// If the parameter `t` does not lie in the curve's domain, `None` is returned.
fn sample_iter(&self, t: f32) -> Option<impl Iterator<Item = T>> {
if self.domain().contains(t) {
Some(self.sample_iter_unchecked(t))
} else {
None
}
}
}
#[cfg(feature = "alloc")]
impl<T> IterableCurve<T> for ConstantCurve<Vec<T>>
where
T: Clone,
{
fn domain(&self) -> Interval {
self.domain
}
fn sample_iter_unchecked(&self, _t: f32) -> impl Iterator<Item = T> {
self.value.iter().cloned()
}
}

1343
vendor/bevy_math/src/curve/mod.rs vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,406 @@
//! Sample-interpolated curves constructed using the [`Curve`] API.
use super::cores::{EvenCore, EvenCoreError, UnevenCore, UnevenCoreError};
use super::{Curve, Interval};
use crate::StableInterpolate;
#[cfg(feature = "bevy_reflect")]
use alloc::format;
use core::any::type_name;
use core::fmt::{self, Debug};
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::{utility::GenericTypePathCell, Reflect, TypePath};
#[cfg(feature = "bevy_reflect")]
mod paths {
pub(super) const THIS_MODULE: &str = "bevy_math::curve::sample_curves";
pub(super) const THIS_CRATE: &str = "bevy_math";
}
/// A curve that is defined by explicit neighbor interpolation over a set of evenly-spaced samples.
#[derive(Clone)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(where T: TypePath),
reflect(from_reflect = false, type_path = false),
)]
pub struct SampleCurve<T, I> {
pub(crate) core: EvenCore<T>,
#[cfg_attr(feature = "bevy_reflect", reflect(ignore))]
pub(crate) interpolation: I,
}
impl<T, I> Debug for SampleCurve<T, I>
where
EvenCore<T>: Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SampleCurve")
.field("core", &self.core)
.field("interpolation", &type_name::<I>())
.finish()
}
}
/// Note: This is not a fully stable implementation of `TypePath` due to usage of `type_name`
/// for function members.
#[cfg(feature = "bevy_reflect")]
impl<T, I> TypePath for SampleCurve<T, I>
where
T: TypePath,
I: 'static,
{
fn type_path() -> &'static str {
static CELL: GenericTypePathCell = GenericTypePathCell::new();
CELL.get_or_insert::<Self, _>(|| {
format!(
"{}::SampleCurve<{},{}>",
paths::THIS_MODULE,
T::type_path(),
type_name::<I>()
)
})
}
fn short_type_path() -> &'static str {
static CELL: GenericTypePathCell = GenericTypePathCell::new();
CELL.get_or_insert::<Self, _>(|| {
format!("SampleCurve<{},{}>", T::type_path(), type_name::<I>())
})
}
fn type_ident() -> Option<&'static str> {
Some("SampleCurve")
}
fn crate_name() -> Option<&'static str> {
Some(paths::THIS_CRATE)
}
fn module_path() -> Option<&'static str> {
Some(paths::THIS_MODULE)
}
}
impl<T, I> Curve<T> for SampleCurve<T, I>
where
T: Clone,
I: Fn(&T, &T, f32) -> T,
{
#[inline]
fn domain(&self) -> Interval {
self.core.domain()
}
#[inline]
fn sample_clamped(&self, t: f32) -> T {
// `EvenCore::sample_with` is implicitly clamped.
self.core.sample_with(t, &self.interpolation)
}
#[inline]
fn sample_unchecked(&self, t: f32) -> T {
self.sample_clamped(t)
}
}
impl<T, I> SampleCurve<T, I> {
/// Create a new [`SampleCurve`] using the specified `interpolation` to interpolate between
/// the given `samples`. An error is returned if there are not at least 2 samples or if the
/// given `domain` is unbounded.
///
/// The interpolation takes two values by reference together with a scalar parameter and
/// produces an owned value. The expectation is that `interpolation(&x, &y, 0.0)` and
/// `interpolation(&x, &y, 1.0)` are equivalent to `x` and `y` respectively.
pub fn new(
domain: Interval,
samples: impl IntoIterator<Item = T>,
interpolation: I,
) -> Result<Self, EvenCoreError>
where
I: Fn(&T, &T, f32) -> T,
{
Ok(Self {
core: EvenCore::new(domain, samples)?,
interpolation,
})
}
}
/// A curve that is defined by neighbor interpolation over a set of evenly-spaced samples,
/// interpolated automatically using [a particularly well-behaved interpolation].
///
/// [a particularly well-behaved interpolation]: StableInterpolate
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
pub struct SampleAutoCurve<T> {
pub(crate) core: EvenCore<T>,
}
impl<T> Curve<T> for SampleAutoCurve<T>
where
T: StableInterpolate,
{
#[inline]
fn domain(&self) -> Interval {
self.core.domain()
}
#[inline]
fn sample_clamped(&self, t: f32) -> T {
// `EvenCore::sample_with` is implicitly clamped.
self.core
.sample_with(t, <T as StableInterpolate>::interpolate_stable)
}
#[inline]
fn sample_unchecked(&self, t: f32) -> T {
self.sample_clamped(t)
}
}
impl<T> SampleAutoCurve<T> {
/// Create a new [`SampleCurve`] using type-inferred interpolation to interpolate between
/// the given `samples`. An error is returned if there are not at least 2 samples or if the
/// given `domain` is unbounded.
pub fn new(
domain: Interval,
samples: impl IntoIterator<Item = T>,
) -> Result<Self, EvenCoreError> {
Ok(Self {
core: EvenCore::new(domain, samples)?,
})
}
}
/// A curve that is defined by interpolation over unevenly spaced samples with explicit
/// interpolation.
#[derive(Clone)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(where T: TypePath),
reflect(from_reflect = false, type_path = false),
)]
pub struct UnevenSampleCurve<T, I> {
pub(crate) core: UnevenCore<T>,
#[cfg_attr(feature = "bevy_reflect", reflect(ignore))]
pub(crate) interpolation: I,
}
impl<T, I> Debug for UnevenSampleCurve<T, I>
where
UnevenCore<T>: Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SampleCurve")
.field("core", &self.core)
.field("interpolation", &type_name::<I>())
.finish()
}
}
/// Note: This is not a fully stable implementation of `TypePath` due to usage of `type_name`
/// for function members.
#[cfg(feature = "bevy_reflect")]
impl<T, I> TypePath for UnevenSampleCurve<T, I>
where
T: TypePath,
I: 'static,
{
fn type_path() -> &'static str {
static CELL: GenericTypePathCell = GenericTypePathCell::new();
CELL.get_or_insert::<Self, _>(|| {
format!(
"{}::UnevenSampleCurve<{},{}>",
paths::THIS_MODULE,
T::type_path(),
type_name::<I>()
)
})
}
fn short_type_path() -> &'static str {
static CELL: GenericTypePathCell = GenericTypePathCell::new();
CELL.get_or_insert::<Self, _>(|| {
format!("UnevenSampleCurve<{},{}>", T::type_path(), type_name::<I>())
})
}
fn type_ident() -> Option<&'static str> {
Some("UnevenSampleCurve")
}
fn crate_name() -> Option<&'static str> {
Some(paths::THIS_CRATE)
}
fn module_path() -> Option<&'static str> {
Some(paths::THIS_MODULE)
}
}
impl<T, I> Curve<T> for UnevenSampleCurve<T, I>
where
T: Clone,
I: Fn(&T, &T, f32) -> T,
{
#[inline]
fn domain(&self) -> Interval {
self.core.domain()
}
#[inline]
fn sample_clamped(&self, t: f32) -> T {
// `UnevenCore::sample_with` is implicitly clamped.
self.core.sample_with(t, &self.interpolation)
}
#[inline]
fn sample_unchecked(&self, t: f32) -> T {
self.sample_clamped(t)
}
}
impl<T, I> UnevenSampleCurve<T, I> {
/// Create a new [`UnevenSampleCurve`] using the provided `interpolation` to interpolate
/// between adjacent `timed_samples`. The given samples are filtered to finite times and
/// sorted internally; if there are not at least 2 valid timed samples, an error will be
/// returned.
///
/// The interpolation takes two values by reference together with a scalar parameter and
/// produces an owned value. The expectation is that `interpolation(&x, &y, 0.0)` and
/// `interpolation(&x, &y, 1.0)` are equivalent to `x` and `y` respectively.
pub fn new(
timed_samples: impl IntoIterator<Item = (f32, T)>,
interpolation: I,
) -> Result<Self, UnevenCoreError> {
Ok(Self {
core: UnevenCore::new(timed_samples)?,
interpolation,
})
}
/// This [`UnevenSampleAutoCurve`], but with the sample times moved by the map `f`.
/// In principle, when `f` is monotone, this is equivalent to [`CurveExt::reparametrize`],
/// but the function inputs to each are inverses of one another.
///
/// The samples are re-sorted by time after mapping and deduplicated by output time, so
/// the function `f` should generally be injective over the sample times of the curve.
///
/// [`CurveExt::reparametrize`]: super::CurveExt::reparametrize
pub fn map_sample_times(self, f: impl Fn(f32) -> f32) -> UnevenSampleCurve<T, I> {
Self {
core: self.core.map_sample_times(f),
interpolation: self.interpolation,
}
}
}
/// A curve that is defined by interpolation over unevenly spaced samples,
/// interpolated automatically using [a particularly well-behaved interpolation].
///
/// [a particularly well-behaved interpolation]: StableInterpolate
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
pub struct UnevenSampleAutoCurve<T> {
pub(crate) core: UnevenCore<T>,
}
impl<T> Curve<T> for UnevenSampleAutoCurve<T>
where
T: StableInterpolate,
{
#[inline]
fn domain(&self) -> Interval {
self.core.domain()
}
#[inline]
fn sample_clamped(&self, t: f32) -> T {
// `UnevenCore::sample_with` is implicitly clamped.
self.core
.sample_with(t, <T as StableInterpolate>::interpolate_stable)
}
#[inline]
fn sample_unchecked(&self, t: f32) -> T {
self.sample_clamped(t)
}
}
impl<T> UnevenSampleAutoCurve<T> {
/// Create a new [`UnevenSampleAutoCurve`] from a given set of timed samples.
///
/// The samples are filtered to finite times and sorted internally; if there are not
/// at least 2 valid timed samples, an error will be returned.
pub fn new(timed_samples: impl IntoIterator<Item = (f32, T)>) -> Result<Self, UnevenCoreError> {
Ok(Self {
core: UnevenCore::new(timed_samples)?,
})
}
/// This [`UnevenSampleAutoCurve`], but with the sample times moved by the map `f`.
/// In principle, when `f` is monotone, this is equivalent to [`CurveExt::reparametrize`],
/// but the function inputs to each are inverses of one another.
///
/// The samples are re-sorted by time after mapping and deduplicated by output time, so
/// the function `f` should generally be injective over the sample times of the curve.
///
/// [`CurveExt::reparametrize`]: super::CurveExt::reparametrize
pub fn map_sample_times(self, f: impl Fn(f32) -> f32) -> UnevenSampleAutoCurve<T> {
Self {
core: self.core.map_sample_times(f),
}
}
}
#[cfg(test)]
#[cfg(feature = "bevy_reflect")]
mod tests {
//! These tests should guarantee (by even compiling) that `SampleCurve` and `UnevenSampleCurve`
//! can be `Reflect` under reasonable circumstances where their interpolation is defined by:
//! - function items
//! - 'static closures
//! - function pointers
use super::{SampleCurve, UnevenSampleCurve};
use crate::{curve::Interval, VectorSpace};
use alloc::boxed::Box;
use bevy_reflect::Reflect;
#[test]
fn reflect_sample_curve() {
fn foo(x: &f32, y: &f32, t: f32) -> f32 {
x.lerp(*y, t)
}
let bar = |x: &f32, y: &f32, t: f32| x.lerp(*y, t);
let baz: fn(&f32, &f32, f32) -> f32 = bar;
let samples = [0.0, 1.0, 2.0];
let _: Box<dyn Reflect> = Box::new(SampleCurve::new(Interval::UNIT, samples, foo).unwrap());
let _: Box<dyn Reflect> = Box::new(SampleCurve::new(Interval::UNIT, samples, bar).unwrap());
let _: Box<dyn Reflect> = Box::new(SampleCurve::new(Interval::UNIT, samples, baz).unwrap());
}
#[test]
fn reflect_uneven_sample_curve() {
fn foo(x: &f32, y: &f32, t: f32) -> f32 {
x.lerp(*y, t)
}
let bar = |x: &f32, y: &f32, t: f32| x.lerp(*y, t);
let baz: fn(&f32, &f32, f32) -> f32 = bar;
let keyframes = [(0.0, 1.0), (1.0, 0.0), (2.0, -1.0)];
let _: Box<dyn Reflect> = Box::new(UnevenSampleCurve::new(keyframes, foo).unwrap());
let _: Box<dyn Reflect> = Box::new(UnevenSampleCurve::new(keyframes, bar).unwrap());
let _: Box<dyn Reflect> = Box::new(UnevenSampleCurve::new(keyframes, baz).unwrap());
}
}