Path: blob/main/crates/bevy_math/src/curve/sample_curves.rs
6596 views
//! Sample-interpolated curves constructed using the [`Curve`] API.12use super::cores::{EvenCore, EvenCoreError, UnevenCore, UnevenCoreError};3use super::{Curve, Interval};45use crate::StableInterpolate;6#[cfg(feature = "bevy_reflect")]7use alloc::format;8use core::any::type_name;9use core::fmt::{self, Debug};1011#[cfg(feature = "bevy_reflect")]12use bevy_reflect::{utility::GenericTypePathCell, Reflect, TypePath};1314#[cfg(feature = "bevy_reflect")]15mod paths {16pub(super) const THIS_MODULE: &str = "bevy_math::curve::sample_curves";17pub(super) const THIS_CRATE: &str = "bevy_math";18}1920/// A curve that is defined by explicit neighbor interpolation over a set of evenly-spaced samples.21#[derive(Clone)]22#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]23#[cfg_attr(24feature = "bevy_reflect",25derive(Reflect),26reflect(where T: TypePath),27reflect(from_reflect = false, type_path = false),28)]29pub struct SampleCurve<T, I> {30pub(crate) core: EvenCore<T>,31#[cfg_attr(feature = "bevy_reflect", reflect(ignore))]32pub(crate) interpolation: I,33}3435impl<T, I> Debug for SampleCurve<T, I>36where37EvenCore<T>: Debug,38{39fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {40f.debug_struct("SampleCurve")41.field("core", &self.core)42.field("interpolation", &type_name::<I>())43.finish()44}45}4647/// Note: This is not a fully stable implementation of `TypePath` due to usage of `type_name`48/// for function members.49#[cfg(feature = "bevy_reflect")]50impl<T, I> TypePath for SampleCurve<T, I>51where52T: TypePath,53I: 'static,54{55fn type_path() -> &'static str {56static CELL: GenericTypePathCell = GenericTypePathCell::new();57CELL.get_or_insert::<Self, _>(|| {58format!(59"{}::SampleCurve<{},{}>",60paths::THIS_MODULE,61T::type_path(),62type_name::<I>()63)64})65}6667fn short_type_path() -> &'static str {68static CELL: GenericTypePathCell = GenericTypePathCell::new();69CELL.get_or_insert::<Self, _>(|| {70format!("SampleCurve<{},{}>", T::type_path(), type_name::<I>())71})72}7374fn type_ident() -> Option<&'static str> {75Some("SampleCurve")76}7778fn crate_name() -> Option<&'static str> {79Some(paths::THIS_CRATE)80}8182fn module_path() -> Option<&'static str> {83Some(paths::THIS_MODULE)84}85}8687impl<T, I> Curve<T> for SampleCurve<T, I>88where89T: Clone,90I: Fn(&T, &T, f32) -> T,91{92#[inline]93fn domain(&self) -> Interval {94self.core.domain()95}9697#[inline]98fn sample_clamped(&self, t: f32) -> T {99// `EvenCore::sample_with` is implicitly clamped.100self.core.sample_with(t, &self.interpolation)101}102103#[inline]104fn sample_unchecked(&self, t: f32) -> T {105self.sample_clamped(t)106}107}108109impl<T, I> SampleCurve<T, I> {110/// Create a new [`SampleCurve`] using the specified `interpolation` to interpolate between111/// the given `samples`. An error is returned if there are not at least 2 samples or if the112/// given `domain` is unbounded.113///114/// The interpolation takes two values by reference together with a scalar parameter and115/// produces an owned value. The expectation is that `interpolation(&x, &y, 0.0)` and116/// `interpolation(&x, &y, 1.0)` are equivalent to `x` and `y` respectively.117pub fn new(118domain: Interval,119samples: impl IntoIterator<Item = T>,120interpolation: I,121) -> Result<Self, EvenCoreError>122where123I: Fn(&T, &T, f32) -> T,124{125Ok(Self {126core: EvenCore::new(domain, samples)?,127interpolation,128})129}130}131132/// A curve that is defined by neighbor interpolation over a set of evenly-spaced samples,133/// interpolated automatically using [a particularly well-behaved interpolation].134///135/// [a particularly well-behaved interpolation]: StableInterpolate136#[derive(Clone, Debug)]137#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]138#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]139pub struct SampleAutoCurve<T> {140pub(crate) core: EvenCore<T>,141}142143impl<T> Curve<T> for SampleAutoCurve<T>144where145T: StableInterpolate,146{147#[inline]148fn domain(&self) -> Interval {149self.core.domain()150}151152#[inline]153fn sample_clamped(&self, t: f32) -> T {154// `EvenCore::sample_with` is implicitly clamped.155self.core156.sample_with(t, <T as StableInterpolate>::interpolate_stable)157}158159#[inline]160fn sample_unchecked(&self, t: f32) -> T {161self.sample_clamped(t)162}163}164165impl<T> SampleAutoCurve<T> {166/// Create a new [`SampleCurve`] using type-inferred interpolation to interpolate between167/// the given `samples`. An error is returned if there are not at least 2 samples or if the168/// given `domain` is unbounded.169pub fn new(170domain: Interval,171samples: impl IntoIterator<Item = T>,172) -> Result<Self, EvenCoreError> {173Ok(Self {174core: EvenCore::new(domain, samples)?,175})176}177}178179/// A curve that is defined by interpolation over unevenly spaced samples with explicit180/// interpolation.181#[derive(Clone)]182#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]183#[cfg_attr(184feature = "bevy_reflect",185derive(Reflect),186reflect(where T: TypePath),187reflect(from_reflect = false, type_path = false),188)]189pub struct UnevenSampleCurve<T, I> {190pub(crate) core: UnevenCore<T>,191#[cfg_attr(feature = "bevy_reflect", reflect(ignore))]192pub(crate) interpolation: I,193}194195impl<T, I> Debug for UnevenSampleCurve<T, I>196where197UnevenCore<T>: Debug,198{199fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {200f.debug_struct("SampleCurve")201.field("core", &self.core)202.field("interpolation", &type_name::<I>())203.finish()204}205}206207/// Note: This is not a fully stable implementation of `TypePath` due to usage of `type_name`208/// for function members.209#[cfg(feature = "bevy_reflect")]210impl<T, I> TypePath for UnevenSampleCurve<T, I>211where212T: TypePath,213I: 'static,214{215fn type_path() -> &'static str {216static CELL: GenericTypePathCell = GenericTypePathCell::new();217CELL.get_or_insert::<Self, _>(|| {218format!(219"{}::UnevenSampleCurve<{},{}>",220paths::THIS_MODULE,221T::type_path(),222type_name::<I>()223)224})225}226227fn short_type_path() -> &'static str {228static CELL: GenericTypePathCell = GenericTypePathCell::new();229CELL.get_or_insert::<Self, _>(|| {230format!("UnevenSampleCurve<{},{}>", T::type_path(), type_name::<I>())231})232}233234fn type_ident() -> Option<&'static str> {235Some("UnevenSampleCurve")236}237238fn crate_name() -> Option<&'static str> {239Some(paths::THIS_CRATE)240}241242fn module_path() -> Option<&'static str> {243Some(paths::THIS_MODULE)244}245}246247impl<T, I> Curve<T> for UnevenSampleCurve<T, I>248where249T: Clone,250I: Fn(&T, &T, f32) -> T,251{252#[inline]253fn domain(&self) -> Interval {254self.core.domain()255}256257#[inline]258fn sample_clamped(&self, t: f32) -> T {259// `UnevenCore::sample_with` is implicitly clamped.260self.core.sample_with(t, &self.interpolation)261}262263#[inline]264fn sample_unchecked(&self, t: f32) -> T {265self.sample_clamped(t)266}267}268269impl<T, I> UnevenSampleCurve<T, I> {270/// Create a new [`UnevenSampleCurve`] using the provided `interpolation` to interpolate271/// between adjacent `timed_samples`. The given samples are filtered to finite times and272/// sorted internally; if there are not at least 2 valid timed samples, an error will be273/// returned.274///275/// The interpolation takes two values by reference together with a scalar parameter and276/// produces an owned value. The expectation is that `interpolation(&x, &y, 0.0)` and277/// `interpolation(&x, &y, 1.0)` are equivalent to `x` and `y` respectively.278pub fn new(279timed_samples: impl IntoIterator<Item = (f32, T)>,280interpolation: I,281) -> Result<Self, UnevenCoreError>282where283I: Fn(&T, &T, f32) -> T,284{285Ok(Self {286core: UnevenCore::new(timed_samples)?,287interpolation,288})289}290291/// This [`UnevenSampleAutoCurve`], but with the sample times moved by the map `f`.292/// In principle, when `f` is monotone, this is equivalent to [`CurveExt::reparametrize`],293/// but the function inputs to each are inverses of one another.294///295/// The samples are re-sorted by time after mapping and deduplicated by output time, so296/// the function `f` should generally be injective over the sample times of the curve.297///298/// [`CurveExt::reparametrize`]: super::CurveExt::reparametrize299pub fn map_sample_times(self, f: impl Fn(f32) -> f32) -> UnevenSampleCurve<T, I> {300Self {301core: self.core.map_sample_times(f),302interpolation: self.interpolation,303}304}305}306307/// A curve that is defined by interpolation over unevenly spaced samples,308/// interpolated automatically using [a particularly well-behaved interpolation].309///310/// [a particularly well-behaved interpolation]: StableInterpolate311#[derive(Clone, Debug)]312#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]313#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]314pub struct UnevenSampleAutoCurve<T> {315pub(crate) core: UnevenCore<T>,316}317318impl<T> Curve<T> for UnevenSampleAutoCurve<T>319where320T: StableInterpolate,321{322#[inline]323fn domain(&self) -> Interval {324self.core.domain()325}326327#[inline]328fn sample_clamped(&self, t: f32) -> T {329// `UnevenCore::sample_with` is implicitly clamped.330self.core331.sample_with(t, <T as StableInterpolate>::interpolate_stable)332}333334#[inline]335fn sample_unchecked(&self, t: f32) -> T {336self.sample_clamped(t)337}338}339340impl<T> UnevenSampleAutoCurve<T> {341/// Create a new [`UnevenSampleAutoCurve`] from a given set of timed samples.342///343/// The samples are filtered to finite times and sorted internally; if there are not344/// at least 2 valid timed samples, an error will be returned.345pub fn new(timed_samples: impl IntoIterator<Item = (f32, T)>) -> Result<Self, UnevenCoreError> {346Ok(Self {347core: UnevenCore::new(timed_samples)?,348})349}350351/// This [`UnevenSampleAutoCurve`], but with the sample times moved by the map `f`.352/// In principle, when `f` is monotone, this is equivalent to [`CurveExt::reparametrize`],353/// but the function inputs to each are inverses of one another.354///355/// The samples are re-sorted by time after mapping and deduplicated by output time, so356/// the function `f` should generally be injective over the sample times of the curve.357///358/// [`CurveExt::reparametrize`]: super::CurveExt::reparametrize359pub fn map_sample_times(self, f: impl Fn(f32) -> f32) -> UnevenSampleAutoCurve<T> {360Self {361core: self.core.map_sample_times(f),362}363}364}365366#[cfg(test)]367#[cfg(feature = "bevy_reflect")]368mod tests {369//! These tests should guarantee (by even compiling) that `SampleCurve` and `UnevenSampleCurve`370//! can be `Reflect` under reasonable circumstances where their interpolation is defined by:371//! - function items372//! - 'static closures373//! - function pointers374use super::{SampleCurve, UnevenSampleCurve};375use crate::{curve::Interval, VectorSpace};376use alloc::boxed::Box;377use bevy_reflect::Reflect;378379#[test]380fn reflect_sample_curve() {381fn foo(x: &f32, y: &f32, t: f32) -> f32 {382x.lerp(*y, t)383}384let bar = |x: &f32, y: &f32, t: f32| x.lerp(*y, t);385let baz: fn(&f32, &f32, f32) -> f32 = bar;386387let samples = [0.0, 1.0, 2.0];388389let _: Box<dyn Reflect> = Box::new(SampleCurve::new(Interval::UNIT, samples, foo).unwrap());390let _: Box<dyn Reflect> = Box::new(SampleCurve::new(Interval::UNIT, samples, bar).unwrap());391let _: Box<dyn Reflect> = Box::new(SampleCurve::new(Interval::UNIT, samples, baz).unwrap());392}393394#[test]395fn reflect_uneven_sample_curve() {396fn foo(x: &f32, y: &f32, t: f32) -> f32 {397x.lerp(*y, t)398}399let bar = |x: &f32, y: &f32, t: f32| x.lerp(*y, t);400let baz: fn(&f32, &f32, f32) -> f32 = bar;401402let keyframes = [(0.0, 1.0), (1.0, 0.0), (2.0, -1.0)];403404let _: Box<dyn Reflect> = Box::new(UnevenSampleCurve::new(keyframes, foo).unwrap());405let _: Box<dyn Reflect> = Box::new(UnevenSampleCurve::new(keyframes, bar).unwrap());406let _: Box<dyn Reflect> = Box::new(UnevenSampleCurve::new(keyframes, baz).unwrap());407}408#[test]409fn test_infer_interp_arguments() {410// it should be possible to infer the x and y arguments of the interpolation function411// from the input samples. If that becomes impossible, this will fail to compile.412SampleCurve::new(Interval::UNIT, [0.0, 1.0], |x, y, t| x.lerp(*y, t)).ok();413UnevenSampleCurve::new([(0.1, 1.0), (1.0, 3.0)], |x, y, t| x.lerp(*y, t)).ok();414}415}416417418