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        Ok(Self {
284            core: UnevenCore::new(timed_samples)?,
285            interpolation,
286        })
287    }
288
289    /// This [`UnevenSampleAutoCurve`], but with the sample times moved by the map `f`.
290    /// In principle, when `f` is monotone, this is equivalent to [`CurveExt::reparametrize`],
291    /// but the function inputs to each are inverses of one another.
292    ///
293    /// The samples are re-sorted by time after mapping and deduplicated by output time, so
294    /// the function `f` should generally be injective over the sample times of the curve.
295    ///
296    /// [`CurveExt::reparametrize`]: super::CurveExt::reparametrize
297    pub fn map_sample_times(self, f: impl Fn(f32) -> f32) -> UnevenSampleCurve<T, I> {
298        Self {
299            core: self.core.map_sample_times(f),
300            interpolation: self.interpolation,
301        }
302    }
303}
304
305/// A curve that is defined by interpolation over unevenly spaced samples,
306/// interpolated automatically using [a particularly well-behaved interpolation].
307///
308/// [a particularly well-behaved interpolation]: StableInterpolate
309#[derive(Clone, Debug)]
310#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
311#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
312pub struct UnevenSampleAutoCurve<T> {
313    pub(crate) core: UnevenCore<T>,
314}
315
316impl<T> Curve<T> for UnevenSampleAutoCurve<T>
317where
318    T: StableInterpolate,
319{
320    #[inline]
321    fn domain(&self) -> Interval {
322        self.core.domain()
323    }
324
325    #[inline]
326    fn sample_clamped(&self, t: f32) -> T {
327        // `UnevenCore::sample_with` is implicitly clamped.
328        self.core
329            .sample_with(t, <T as StableInterpolate>::interpolate_stable)
330    }
331
332    #[inline]
333    fn sample_unchecked(&self, t: f32) -> T {
334        self.sample_clamped(t)
335    }
336}
337
338impl<T> UnevenSampleAutoCurve<T> {
339    /// Create a new [`UnevenSampleAutoCurve`] from a given set of timed samples.
340    ///
341    /// The samples are filtered to finite times and sorted internally; if there are not
342    /// at least 2 valid timed samples, an error will be returned.
343    pub fn new(timed_samples: impl IntoIterator<Item = (f32, T)>) -> Result<Self, UnevenCoreError> {
344        Ok(Self {
345            core: UnevenCore::new(timed_samples)?,
346        })
347    }
348
349    /// This [`UnevenSampleAutoCurve`], but with the sample times moved by the map `f`.
350    /// In principle, when `f` is monotone, this is equivalent to [`CurveExt::reparametrize`],
351    /// but the function inputs to each are inverses of one another.
352    ///
353    /// The samples are re-sorted by time after mapping and deduplicated by output time, so
354    /// the function `f` should generally be injective over the sample times of the curve.
355    ///
356    /// [`CurveExt::reparametrize`]: super::CurveExt::reparametrize
357    pub fn map_sample_times(self, f: impl Fn(f32) -> f32) -> UnevenSampleAutoCurve<T> {
358        Self {
359            core: self.core.map_sample_times(f),
360        }
361    }
362}
363
364#[cfg(test)]
365#[cfg(feature = "bevy_reflect")]
366mod tests {
367    //! These tests should guarantee (by even compiling) that `SampleCurve` and `UnevenSampleCurve`
368    //! can be `Reflect` under reasonable circumstances where their interpolation is defined by:
369    //! - function items
370    //! - 'static closures
371    //! - function pointers
372    use super::{SampleCurve, UnevenSampleCurve};
373    use crate::{curve::Interval, VectorSpace};
374    use alloc::boxed::Box;
375    use bevy_reflect::Reflect;
376
377    #[test]
378    fn reflect_sample_curve() {
379        fn foo(x: &f32, y: &f32, t: f32) -> f32 {
380            x.lerp(*y, t)
381        }
382        let bar = |x: &f32, y: &f32, t: f32| x.lerp(*y, t);
383        let baz: fn(&f32, &f32, f32) -> f32 = bar;
384
385        let samples = [0.0, 1.0, 2.0];
386
387        let _: Box<dyn Reflect> = Box::new(SampleCurve::new(Interval::UNIT, samples, foo).unwrap());
388        let _: Box<dyn Reflect> = Box::new(SampleCurve::new(Interval::UNIT, samples, bar).unwrap());
389        let _: Box<dyn Reflect> = Box::new(SampleCurve::new(Interval::UNIT, samples, baz).unwrap());
390    }
391
392    #[test]
393    fn reflect_uneven_sample_curve() {
394        fn foo(x: &f32, y: &f32, t: f32) -> f32 {
395            x.lerp(*y, t)
396        }
397        let bar = |x: &f32, y: &f32, t: f32| x.lerp(*y, t);
398        let baz: fn(&f32, &f32, f32) -> f32 = bar;
399
400        let keyframes = [(0.0, 1.0), (1.0, 0.0), (2.0, -1.0)];
401
402        let _: Box<dyn Reflect> = Box::new(UnevenSampleCurve::new(keyframes, foo).unwrap());
403        let _: Box<dyn Reflect> = Box::new(UnevenSampleCurve::new(keyframes, bar).unwrap());
404        let _: Box<dyn Reflect> = Box::new(UnevenSampleCurve::new(keyframes, baz).unwrap());
405    }
406}