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