bevy_math/curve/
sample_curves.rs

1//! Sample-interpolated curves constructed using the [`Curve`] API.
2
3use super::cores::{EvenCore, EvenCoreError, UnevenCore, UnevenCoreError};
4use super::{Curve, Interval};
5
6use crate::StableInterpolate;
7use core::any::type_name;
8use core::fmt::{self, Debug};
9
10#[cfg(feature = "bevy_reflect")]
11use bevy_reflect::{utility::GenericTypePathCell, Reflect, TypePath};
12
13#[cfg(feature = "bevy_reflect")]
14mod paths {
15    pub(super) const THIS_MODULE: &str = "bevy_math::curve::sample_curves";
16    pub(super) const THIS_CRATE: &str = "bevy_math";
17}
18
19/// A curve that is defined by explicit neighbor interpolation over a set of evenly-spaced samples.
20#[derive(Clone)]
21#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
22#[cfg_attr(
23    feature = "bevy_reflect",
24    derive(Reflect),
25    reflect(where T: TypePath),
26    reflect(from_reflect = false, type_path = false),
27)]
28pub struct SampleCurve<T, I> {
29    pub(crate) core: EvenCore<T>,
30    #[cfg_attr(feature = "bevy_reflect", reflect(ignore))]
31    pub(crate) interpolation: I,
32}
33
34impl<T, I> Debug for SampleCurve<T, I>
35where
36    EvenCore<T>: Debug,
37{
38    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39        f.debug_struct("SampleCurve")
40            .field("core", &self.core)
41            .field("interpolation", &type_name::<I>())
42            .finish()
43    }
44}
45
46/// Note: This is not a fully stable implementation of `TypePath` due to usage of `type_name`
47/// for function members.
48#[cfg(feature = "bevy_reflect")]
49impl<T, I> TypePath for SampleCurve<T, I>
50where
51    T: TypePath,
52    I: 'static,
53{
54    fn type_path() -> &'static str {
55        static CELL: GenericTypePathCell = GenericTypePathCell::new();
56        CELL.get_or_insert::<Self, _>(|| {
57            format!(
58                "{}::SampleCurve<{},{}>",
59                paths::THIS_MODULE,
60                T::type_path(),
61                type_name::<I>()
62            )
63        })
64    }
65
66    fn short_type_path() -> &'static str {
67        static CELL: GenericTypePathCell = GenericTypePathCell::new();
68        CELL.get_or_insert::<Self, _>(|| {
69            format!("SampleCurve<{},{}>", T::type_path(), type_name::<I>())
70        })
71    }
72
73    fn type_ident() -> Option<&'static str> {
74        Some("SampleCurve")
75    }
76
77    fn crate_name() -> Option<&'static str> {
78        Some(paths::THIS_CRATE)
79    }
80
81    fn module_path() -> Option<&'static str> {
82        Some(paths::THIS_MODULE)
83    }
84}
85
86impl<T, I> Curve<T> for SampleCurve<T, I>
87where
88    T: Clone,
89    I: Fn(&T, &T, f32) -> T,
90{
91    #[inline]
92    fn domain(&self) -> Interval {
93        self.core.domain()
94    }
95
96    #[inline]
97    fn sample_clamped(&self, t: f32) -> T {
98        // `EvenCore::sample_with` is implicitly clamped.
99        self.core.sample_with(t, &self.interpolation)
100    }
101
102    #[inline]
103    fn sample_unchecked(&self, t: f32) -> T {
104        self.sample_clamped(t)
105    }
106}
107
108impl<T, I> SampleCurve<T, I> {
109    /// Create a new [`SampleCurve`] using the specified `interpolation` to interpolate between
110    /// the given `samples`. An error is returned if there are not at least 2 samples or if the
111    /// given `domain` is unbounded.
112    ///
113    /// The interpolation takes two values by reference together with a scalar parameter and
114    /// produces an owned value. The expectation is that `interpolation(&x, &y, 0.0)` and
115    /// `interpolation(&x, &y, 1.0)` are equivalent to `x` and `y` respectively.
116    pub fn new(
117        domain: Interval,
118        samples: impl IntoIterator<Item = T>,
119        interpolation: I,
120    ) -> Result<Self, EvenCoreError>
121    where
122        I: Fn(&T, &T, f32) -> T,
123    {
124        Ok(Self {
125            core: EvenCore::new(domain, samples)?,
126            interpolation,
127        })
128    }
129}
130
131/// A curve that is defined by neighbor interpolation over a set of evenly-spaced samples,
132/// interpolated automatically using [a particularly well-behaved interpolation].
133///
134/// [a particularly well-behaved interpolation]: StableInterpolate
135#[derive(Clone, Debug)]
136#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
137#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
138pub struct SampleAutoCurve<T> {
139    pub(crate) core: EvenCore<T>,
140}
141
142impl<T> Curve<T> for SampleAutoCurve<T>
143where
144    T: StableInterpolate,
145{
146    #[inline]
147    fn domain(&self) -> Interval {
148        self.core.domain()
149    }
150
151    #[inline]
152    fn sample_clamped(&self, t: f32) -> T {
153        // `EvenCore::sample_with` is implicitly clamped.
154        self.core
155            .sample_with(t, <T as StableInterpolate>::interpolate_stable)
156    }
157
158    #[inline]
159    fn sample_unchecked(&self, t: f32) -> T {
160        self.sample_clamped(t)
161    }
162}
163
164impl<T> SampleAutoCurve<T> {
165    /// Create a new [`SampleCurve`] using type-inferred interpolation to interpolate between
166    /// the given `samples`. An error is returned if there are not at least 2 samples or if the
167    /// given `domain` is unbounded.
168    pub fn new(
169        domain: Interval,
170        samples: impl IntoIterator<Item = T>,
171    ) -> Result<Self, EvenCoreError> {
172        Ok(Self {
173            core: EvenCore::new(domain, samples)?,
174        })
175    }
176}
177
178/// A curve that is defined by interpolation over unevenly spaced samples with explicit
179/// interpolation.
180#[derive(Clone)]
181#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
182#[cfg_attr(
183    feature = "bevy_reflect",
184    derive(Reflect),
185    reflect(where T: TypePath),
186    reflect(from_reflect = false, type_path = false),
187)]
188pub struct UnevenSampleCurve<T, I> {
189    pub(crate) core: UnevenCore<T>,
190    #[cfg_attr(feature = "bevy_reflect", reflect(ignore))]
191    pub(crate) interpolation: I,
192}
193
194impl<T, I> Debug for UnevenSampleCurve<T, I>
195where
196    UnevenCore<T>: Debug,
197{
198    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
199        f.debug_struct("SampleCurve")
200            .field("core", &self.core)
201            .field("interpolation", &type_name::<I>())
202            .finish()
203    }
204}
205
206/// Note: This is not a fully stable implementation of `TypePath` due to usage of `type_name`
207/// for function members.
208#[cfg(feature = "bevy_reflect")]
209impl<T, I> TypePath for UnevenSampleCurve<T, I>
210where
211    T: TypePath,
212    I: 'static,
213{
214    fn type_path() -> &'static str {
215        static CELL: GenericTypePathCell = GenericTypePathCell::new();
216        CELL.get_or_insert::<Self, _>(|| {
217            format!(
218                "{}::UnevenSampleCurve<{},{}>",
219                paths::THIS_MODULE,
220                T::type_path(),
221                type_name::<I>()
222            )
223        })
224    }
225
226    fn short_type_path() -> &'static str {
227        static CELL: GenericTypePathCell = GenericTypePathCell::new();
228        CELL.get_or_insert::<Self, _>(|| {
229            format!("UnevenSampleCurve<{},{}>", T::type_path(), type_name::<I>())
230        })
231    }
232
233    fn type_ident() -> Option<&'static str> {
234        Some("UnevenSampleCurve")
235    }
236
237    fn crate_name() -> Option<&'static str> {
238        Some(paths::THIS_CRATE)
239    }
240
241    fn module_path() -> Option<&'static str> {
242        Some(paths::THIS_MODULE)
243    }
244}
245
246impl<T, I> Curve<T> for UnevenSampleCurve<T, I>
247where
248    T: Clone,
249    I: Fn(&T, &T, f32) -> T,
250{
251    #[inline]
252    fn domain(&self) -> Interval {
253        self.core.domain()
254    }
255
256    #[inline]
257    fn sample_clamped(&self, t: f32) -> T {
258        // `UnevenCore::sample_with` is implicitly clamped.
259        self.core.sample_with(t, &self.interpolation)
260    }
261
262    #[inline]
263    fn sample_unchecked(&self, t: f32) -> T {
264        self.sample_clamped(t)
265    }
266}
267
268impl<T, I> UnevenSampleCurve<T, I> {
269    /// Create a new [`UnevenSampleCurve`] using the provided `interpolation` to interpolate
270    /// between adjacent `timed_samples`. The given samples are filtered to finite times and
271    /// sorted internally; if there are not at least 2 valid timed samples, an error will be
272    /// returned.
273    ///
274    /// The interpolation takes two values by reference together with a scalar parameter and
275    /// produces an owned value. The expectation is that `interpolation(&x, &y, 0.0)` and
276    /// `interpolation(&x, &y, 1.0)` are equivalent to `x` and `y` respectively.
277    pub fn new(
278        timed_samples: impl IntoIterator<Item = (f32, T)>,
279        interpolation: I,
280    ) -> Result<Self, UnevenCoreError> {
281        Ok(Self {
282            core: UnevenCore::new(timed_samples)?,
283            interpolation,
284        })
285    }
286
287    /// This [`UnevenSampleAutoCurve`], but with the sample times moved by the map `f`.
288    /// In principle, when `f` is monotone, this is equivalent to [`Curve::reparametrize`],
289    /// but the function inputs to each are inverses of one another.
290    ///
291    /// The samples are re-sorted by time after mapping and deduplicated by output time, so
292    /// the function `f` should generally be injective over the sample times of the curve.
293    pub fn map_sample_times(self, f: impl Fn(f32) -> f32) -> UnevenSampleCurve<T, I> {
294        Self {
295            core: self.core.map_sample_times(f),
296            interpolation: self.interpolation,
297        }
298    }
299}
300
301/// A curve that is defined by interpolation over unevenly spaced samples,
302/// interpolated automatically using [a particularly well-behaved interpolation].
303///
304/// [a particularly well-behaved interpolation]: StableInterpolate
305#[derive(Clone, Debug)]
306#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
307#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
308pub struct UnevenSampleAutoCurve<T> {
309    pub(crate) core: UnevenCore<T>,
310}
311
312impl<T> Curve<T> for UnevenSampleAutoCurve<T>
313where
314    T: StableInterpolate,
315{
316    #[inline]
317    fn domain(&self) -> Interval {
318        self.core.domain()
319    }
320
321    #[inline]
322    fn sample_clamped(&self, t: f32) -> T {
323        // `UnevenCore::sample_with` is implicitly clamped.
324        self.core
325            .sample_with(t, <T as StableInterpolate>::interpolate_stable)
326    }
327
328    #[inline]
329    fn sample_unchecked(&self, t: f32) -> T {
330        self.sample_clamped(t)
331    }
332}
333
334impl<T> UnevenSampleAutoCurve<T> {
335    /// Create a new [`UnevenSampleAutoCurve`] from a given set of timed samples.
336    ///
337    /// The samples are filtered to finite times and sorted internally; if there are not
338    /// at least 2 valid timed samples, an error will be returned.
339    pub fn new(timed_samples: impl IntoIterator<Item = (f32, T)>) -> Result<Self, UnevenCoreError> {
340        Ok(Self {
341            core: UnevenCore::new(timed_samples)?,
342        })
343    }
344
345    /// This [`UnevenSampleAutoCurve`], but with the sample times moved by the map `f`.
346    /// In principle, when `f` is monotone, this is equivalent to [`Curve::reparametrize`],
347    /// but the function inputs to each are inverses of one another.
348    ///
349    /// The samples are re-sorted by time after mapping and deduplicated by output time, so
350    /// the function `f` should generally be injective over the sample times of the curve.
351    pub fn map_sample_times(self, f: impl Fn(f32) -> f32) -> UnevenSampleAutoCurve<T> {
352        Self {
353            core: self.core.map_sample_times(f),
354        }
355    }
356}
357
358#[cfg(test)]
359mod tests {
360    //! These tests should guarantee (by even compiling) that `SampleCurve` and `UnevenSampleCurve`
361    //! can be `Reflect` under reasonable circumstances where their interpolation is defined by:
362    //! - function items
363    //! - 'static closures
364    //! - function pointers
365    use super::{SampleCurve, UnevenSampleCurve};
366    use crate::{curve::Interval, VectorSpace};
367    use bevy_reflect::Reflect;
368
369    #[test]
370    fn reflect_sample_curve() {
371        fn foo(x: &f32, y: &f32, t: f32) -> f32 {
372            x.lerp(*y, t)
373        }
374        let bar = |x: &f32, y: &f32, t: f32| x.lerp(*y, t);
375        let baz: fn(&f32, &f32, f32) -> f32 = bar;
376
377        let samples = [0.0, 1.0, 2.0];
378
379        let _: Box<dyn Reflect> = Box::new(SampleCurve::new(Interval::UNIT, samples, foo).unwrap());
380        let _: Box<dyn Reflect> = Box::new(SampleCurve::new(Interval::UNIT, samples, bar).unwrap());
381        let _: Box<dyn Reflect> = Box::new(SampleCurve::new(Interval::UNIT, samples, baz).unwrap());
382    }
383
384    #[test]
385    fn reflect_uneven_sample_curve() {
386        fn foo(x: &f32, y: &f32, t: f32) -> f32 {
387            x.lerp(*y, t)
388        }
389        let bar = |x: &f32, y: &f32, t: f32| x.lerp(*y, t);
390        let baz: fn(&f32, &f32, f32) -> f32 = bar;
391
392        let keyframes = [(0.0, 1.0), (1.0, 0.0), (2.0, -1.0)];
393
394        let _: Box<dyn Reflect> = Box::new(UnevenSampleCurve::new(keyframes, foo).unwrap());
395        let _: Box<dyn Reflect> = Box::new(UnevenSampleCurve::new(keyframes, bar).unwrap());
396        let _: Box<dyn Reflect> = Box::new(UnevenSampleCurve::new(keyframes, baz).unwrap());
397    }
398}