bevy_math/curve/
adaptors.rs

1//! Adaptors used by the Curve API for transforming and combining curves together.
2
3use super::interval::*;
4use super::Curve;
5
6use crate::ops;
7use crate::VectorSpace;
8use core::any::type_name;
9use core::fmt::{self, Debug};
10use core::marker::PhantomData;
11
12#[cfg(feature = "bevy_reflect")]
13use {
14    alloc::format,
15    bevy_reflect::{utility::GenericTypePathCell, FromReflect, Reflect, TypePath},
16};
17
18#[cfg(feature = "bevy_reflect")]
19mod paths {
20    pub(super) const THIS_MODULE: &str = "bevy_math::curve::adaptors";
21    pub(super) const THIS_CRATE: &str = "bevy_math";
22}
23
24#[expect(unused, reason = "imported just for doc links")]
25use super::CurveExt;
26
27// NOTE ON REFLECTION:
28//
29// Function members of structs pose an obstacle for reflection, because they don't implement
30// reflection traits themselves. Some of these are more problematic than others; for example,
31// `FromReflect` is basically hopeless for function members regardless, so function-containing
32// adaptors will just never be `FromReflect` (at least until function item types implement
33// Default, if that ever happens). Similarly, they do not implement `TypePath`, and as a result,
34// those adaptors also need custom `TypePath` adaptors which use `type_name` instead.
35//
36// The sum total weirdness of the `Reflect` implementations amounts to this; those adaptors:
37// - are currently never `FromReflect`;
38// - have custom `TypePath` implementations which are not fully stable;
39// - have custom `Debug` implementations which display the function only by type name.
40
41/// A curve with a constant value over its domain.
42///
43/// This is a curve that holds an inner value and always produces a clone of that value when sampled.
44#[derive(Clone, Copy, Debug)]
45#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
46#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
47pub struct ConstantCurve<T> {
48    pub(crate) domain: Interval,
49    pub(crate) value: T,
50}
51
52impl<T> ConstantCurve<T>
53where
54    T: Clone,
55{
56    /// Create a constant curve, which has the given `domain` and always produces the given `value`
57    /// when sampled.
58    pub fn new(domain: Interval, value: T) -> Self {
59        Self { domain, value }
60    }
61}
62
63impl<T> Curve<T> for ConstantCurve<T>
64where
65    T: Clone,
66{
67    #[inline]
68    fn domain(&self) -> Interval {
69        self.domain
70    }
71
72    #[inline]
73    fn sample_unchecked(&self, _t: f32) -> T {
74        self.value.clone()
75    }
76}
77
78/// A curve defined by a function together with a fixed domain.
79///
80/// This is a curve that holds an inner function `f` which takes numbers (`f32`) as input and produces
81/// output of type `T`. The value of this curve when sampled at time `t` is just `f(t)`.
82#[derive(Clone)]
83#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
84#[cfg_attr(
85    feature = "bevy_reflect",
86    derive(Reflect),
87    reflect(where T: TypePath),
88    reflect(from_reflect = false, type_path = false),
89)]
90pub struct FunctionCurve<T, F> {
91    pub(crate) domain: Interval,
92    #[cfg_attr(feature = "bevy_reflect", reflect(ignore))]
93    pub(crate) f: F,
94    #[cfg_attr(feature = "serialize", serde(skip))]
95    #[cfg_attr(feature = "bevy_reflect", reflect(ignore, clone))]
96    pub(crate) _phantom: PhantomData<fn() -> T>,
97}
98
99impl<T, F> Debug for FunctionCurve<T, F> {
100    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
101        f.debug_struct("FunctionCurve")
102            .field("domain", &self.domain)
103            .field("f", &type_name::<F>())
104            .finish()
105    }
106}
107
108/// Note: This is not a fully stable implementation of `TypePath` due to usage of `type_name`
109/// for function members.
110#[cfg(feature = "bevy_reflect")]
111impl<T, F> TypePath for FunctionCurve<T, F>
112where
113    T: TypePath,
114    F: 'static,
115{
116    fn type_path() -> &'static str {
117        static CELL: GenericTypePathCell = GenericTypePathCell::new();
118        CELL.get_or_insert::<Self, _>(|| {
119            format!(
120                "{}::FunctionCurve<{},{}>",
121                paths::THIS_MODULE,
122                T::type_path(),
123                type_name::<F>()
124            )
125        })
126    }
127
128    fn short_type_path() -> &'static str {
129        static CELL: GenericTypePathCell = GenericTypePathCell::new();
130        CELL.get_or_insert::<Self, _>(|| {
131            format!(
132                "FunctionCurve<{},{}>",
133                T::short_type_path(),
134                type_name::<F>()
135            )
136        })
137    }
138
139    fn type_ident() -> Option<&'static str> {
140        Some("FunctionCurve")
141    }
142
143    fn crate_name() -> Option<&'static str> {
144        Some(paths::THIS_CRATE)
145    }
146
147    fn module_path() -> Option<&'static str> {
148        Some(paths::THIS_MODULE)
149    }
150}
151
152impl<T, F> FunctionCurve<T, F>
153where
154    F: Fn(f32) -> T,
155{
156    /// Create a new curve with the given `domain` from the given `function`. When sampled, the
157    /// `function` is evaluated at the sample time to compute the output.
158    pub fn new(domain: Interval, function: F) -> Self {
159        FunctionCurve {
160            domain,
161            f: function,
162            _phantom: PhantomData,
163        }
164    }
165}
166
167impl<T, F> Curve<T> for FunctionCurve<T, F>
168where
169    F: Fn(f32) -> T,
170{
171    #[inline]
172    fn domain(&self) -> Interval {
173        self.domain
174    }
175
176    #[inline]
177    fn sample_unchecked(&self, t: f32) -> T {
178        (self.f)(t)
179    }
180}
181
182/// A curve whose samples are defined by mapping samples from another curve through a
183/// given function. Curves of this type are produced by [`CurveExt::map`].
184#[derive(Clone)]
185#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
186#[cfg_attr(
187    feature = "bevy_reflect",
188    derive(Reflect),
189    reflect(where S: TypePath, T: TypePath, C: TypePath),
190    reflect(from_reflect = false, type_path = false),
191)]
192pub struct MapCurve<S, T, C, F> {
193    pub(crate) preimage: C,
194    #[cfg_attr(feature = "bevy_reflect", reflect(ignore))]
195    pub(crate) f: F,
196    #[cfg_attr(feature = "serialize", serde(skip))]
197    #[cfg_attr(feature = "bevy_reflect", reflect(ignore, clone))]
198    pub(crate) _phantom: PhantomData<(fn() -> S, fn(S) -> T)>,
199}
200
201impl<S, T, C, F> Debug for MapCurve<S, T, C, F>
202where
203    C: Debug,
204{
205    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
206        f.debug_struct("MapCurve")
207            .field("preimage", &self.preimage)
208            .field("f", &type_name::<F>())
209            .finish()
210    }
211}
212
213/// Note: This is not a fully stable implementation of `TypePath` due to usage of `type_name`
214/// for function members.
215#[cfg(feature = "bevy_reflect")]
216impl<S, T, C, F> TypePath for MapCurve<S, T, C, F>
217where
218    S: TypePath,
219    T: TypePath,
220    C: TypePath,
221    F: 'static,
222{
223    fn type_path() -> &'static str {
224        static CELL: GenericTypePathCell = GenericTypePathCell::new();
225        CELL.get_or_insert::<Self, _>(|| {
226            format!(
227                "{}::MapCurve<{},{},{},{}>",
228                paths::THIS_MODULE,
229                S::type_path(),
230                T::type_path(),
231                C::type_path(),
232                type_name::<F>()
233            )
234        })
235    }
236
237    fn short_type_path() -> &'static str {
238        static CELL: GenericTypePathCell = GenericTypePathCell::new();
239        CELL.get_or_insert::<Self, _>(|| {
240            format!(
241                "MapCurve<{},{},{},{}>",
242                S::type_path(),
243                T::type_path(),
244                C::type_path(),
245                type_name::<F>()
246            )
247        })
248    }
249
250    fn type_ident() -> Option<&'static str> {
251        Some("MapCurve")
252    }
253
254    fn crate_name() -> Option<&'static str> {
255        Some(paths::THIS_CRATE)
256    }
257
258    fn module_path() -> Option<&'static str> {
259        Some(paths::THIS_MODULE)
260    }
261}
262
263impl<S, T, C, F> Curve<T> for MapCurve<S, T, C, F>
264where
265    C: Curve<S>,
266    F: Fn(S) -> T,
267{
268    #[inline]
269    fn domain(&self) -> Interval {
270        self.preimage.domain()
271    }
272
273    #[inline]
274    fn sample_unchecked(&self, t: f32) -> T {
275        (self.f)(self.preimage.sample_unchecked(t))
276    }
277}
278
279/// A curve whose sample space is mapped onto that of some base curve's before sampling.
280/// Curves of this type are produced by [`CurveExt::reparametrize`].
281#[derive(Clone)]
282#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
283#[cfg_attr(
284    feature = "bevy_reflect",
285    derive(Reflect),
286    reflect(where T: TypePath, C: TypePath),
287    reflect(from_reflect = false, type_path = false),
288)]
289pub struct ReparamCurve<T, C, F> {
290    pub(crate) domain: Interval,
291    pub(crate) base: C,
292    #[cfg_attr(feature = "bevy_reflect", reflect(ignore))]
293    pub(crate) f: F,
294    #[cfg_attr(feature = "serialize", serde(skip))]
295    #[cfg_attr(feature = "bevy_reflect", reflect(ignore, clone))]
296    pub(crate) _phantom: PhantomData<fn() -> T>,
297}
298
299impl<T, C, F> Debug for ReparamCurve<T, C, F>
300where
301    C: Debug,
302{
303    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
304        f.debug_struct("ReparamCurve")
305            .field("domain", &self.domain)
306            .field("base", &self.base)
307            .field("f", &type_name::<F>())
308            .finish()
309    }
310}
311
312/// Note: This is not a fully stable implementation of `TypePath` due to usage of `type_name`
313/// for function members.
314#[cfg(feature = "bevy_reflect")]
315impl<T, C, F> TypePath for ReparamCurve<T, C, F>
316where
317    T: TypePath,
318    C: TypePath,
319    F: 'static,
320{
321    fn type_path() -> &'static str {
322        static CELL: GenericTypePathCell = GenericTypePathCell::new();
323        CELL.get_or_insert::<Self, _>(|| {
324            format!(
325                "{}::ReparamCurve<{},{},{}>",
326                paths::THIS_MODULE,
327                T::type_path(),
328                C::type_path(),
329                type_name::<F>()
330            )
331        })
332    }
333
334    fn short_type_path() -> &'static str {
335        static CELL: GenericTypePathCell = GenericTypePathCell::new();
336        CELL.get_or_insert::<Self, _>(|| {
337            format!(
338                "ReparamCurve<{},{},{}>",
339                T::type_path(),
340                C::type_path(),
341                type_name::<F>()
342            )
343        })
344    }
345
346    fn type_ident() -> Option<&'static str> {
347        Some("ReparamCurve")
348    }
349
350    fn crate_name() -> Option<&'static str> {
351        Some(paths::THIS_CRATE)
352    }
353
354    fn module_path() -> Option<&'static str> {
355        Some(paths::THIS_MODULE)
356    }
357}
358
359impl<T, C, F> Curve<T> for ReparamCurve<T, C, F>
360where
361    C: Curve<T>,
362    F: Fn(f32) -> f32,
363{
364    #[inline]
365    fn domain(&self) -> Interval {
366        self.domain
367    }
368
369    #[inline]
370    fn sample_unchecked(&self, t: f32) -> T {
371        self.base.sample_unchecked((self.f)(t))
372    }
373}
374
375/// A curve that has had its domain changed by a linear reparameterization (stretching and scaling).
376/// Curves of this type are produced by [`CurveExt::reparametrize_linear`].
377#[derive(Clone, Debug)]
378#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
379#[cfg_attr(
380    feature = "bevy_reflect",
381    derive(Reflect, FromReflect),
382    reflect(from_reflect = false)
383)]
384pub struct LinearReparamCurve<T, C> {
385    /// Invariants: The domain of this curve must always be bounded.
386    pub(crate) base: C,
387    /// Invariants: This interval must always be bounded.
388    pub(crate) new_domain: Interval,
389    #[cfg_attr(feature = "serialize", serde(skip))]
390    #[cfg_attr(feature = "bevy_reflect", reflect(ignore, clone))]
391    pub(crate) _phantom: PhantomData<fn() -> T>,
392}
393
394impl<T, C> Curve<T> for LinearReparamCurve<T, C>
395where
396    C: Curve<T>,
397{
398    #[inline]
399    fn domain(&self) -> Interval {
400        self.new_domain
401    }
402
403    #[inline]
404    fn sample_unchecked(&self, t: f32) -> T {
405        // The invariants imply this unwrap always succeeds.
406        let f = self.new_domain.linear_map_to(self.base.domain()).unwrap();
407        self.base.sample_unchecked(f(t))
408    }
409}
410
411/// A curve that has been reparametrized by another curve, using that curve to transform the
412/// sample times before sampling. Curves of this type are produced by [`CurveExt::reparametrize_by_curve`].
413#[derive(Clone, Debug)]
414#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
415#[cfg_attr(
416    feature = "bevy_reflect",
417    derive(Reflect, FromReflect),
418    reflect(from_reflect = false)
419)]
420pub struct CurveReparamCurve<T, C, D> {
421    pub(crate) base: C,
422    pub(crate) reparam_curve: D,
423    #[cfg_attr(feature = "serialize", serde(skip))]
424    #[cfg_attr(feature = "bevy_reflect", reflect(ignore, clone))]
425    pub(crate) _phantom: PhantomData<fn() -> T>,
426}
427
428impl<T, C, D> Curve<T> for CurveReparamCurve<T, C, D>
429where
430    C: Curve<T>,
431    D: Curve<f32>,
432{
433    #[inline]
434    fn domain(&self) -> Interval {
435        self.reparam_curve.domain()
436    }
437
438    #[inline]
439    fn sample_unchecked(&self, t: f32) -> T {
440        let sample_time = self.reparam_curve.sample_unchecked(t);
441        self.base.sample_unchecked(sample_time)
442    }
443}
444
445/// A curve that is the graph of another curve over its parameter space. Curves of this type are
446/// produced by [`CurveExt::graph`].
447#[derive(Clone, Debug)]
448#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
449#[cfg_attr(
450    feature = "bevy_reflect",
451    derive(Reflect, FromReflect),
452    reflect(from_reflect = false)
453)]
454pub struct GraphCurve<T, C> {
455    pub(crate) base: C,
456    #[cfg_attr(feature = "serialize", serde(skip))]
457    #[cfg_attr(feature = "bevy_reflect", reflect(ignore, clone))]
458    pub(crate) _phantom: PhantomData<fn() -> T>,
459}
460
461impl<T, C> Curve<(f32, T)> for GraphCurve<T, C>
462where
463    C: Curve<T>,
464{
465    #[inline]
466    fn domain(&self) -> Interval {
467        self.base.domain()
468    }
469
470    #[inline]
471    fn sample_unchecked(&self, t: f32) -> (f32, T) {
472        (t, self.base.sample_unchecked(t))
473    }
474}
475
476/// A curve that combines the output data from two constituent curves into a tuple output. Curves
477/// of this type are produced by [`CurveExt::zip`].
478#[derive(Clone, Debug)]
479#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
480#[cfg_attr(
481    feature = "bevy_reflect",
482    derive(Reflect, FromReflect),
483    reflect(from_reflect = false)
484)]
485pub struct ZipCurve<S, T, C, D> {
486    pub(crate) domain: Interval,
487    pub(crate) first: C,
488    pub(crate) second: D,
489    #[cfg_attr(feature = "serialize", serde(skip))]
490    #[cfg_attr(feature = "bevy_reflect", reflect(ignore, clone))]
491    pub(crate) _phantom: PhantomData<fn() -> (S, T)>,
492}
493
494impl<S, T, C, D> Curve<(S, T)> for ZipCurve<S, T, C, D>
495where
496    C: Curve<S>,
497    D: Curve<T>,
498{
499    #[inline]
500    fn domain(&self) -> Interval {
501        self.domain
502    }
503
504    #[inline]
505    fn sample_unchecked(&self, t: f32) -> (S, T) {
506        (
507            self.first.sample_unchecked(t),
508            self.second.sample_unchecked(t),
509        )
510    }
511}
512
513/// The curve that results from chaining one curve with another. The second curve is
514/// effectively reparametrized so that its start is at the end of the first.
515///
516/// For this to be well-formed, the first curve's domain must be right-finite and the second's
517/// must be left-finite.
518///
519/// Curves of this type are produced by [`CurveExt::chain`].
520#[derive(Clone, Debug)]
521#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
522#[cfg_attr(
523    feature = "bevy_reflect",
524    derive(Reflect, FromReflect),
525    reflect(from_reflect = false)
526)]
527pub struct ChainCurve<T, C, D> {
528    pub(crate) first: C,
529    pub(crate) second: D,
530    #[cfg_attr(feature = "serialize", serde(skip))]
531    #[cfg_attr(feature = "bevy_reflect", reflect(ignore, clone))]
532    pub(crate) _phantom: PhantomData<fn() -> T>,
533}
534
535impl<T, C, D> Curve<T> for ChainCurve<T, C, D>
536where
537    C: Curve<T>,
538    D: Curve<T>,
539{
540    #[inline]
541    fn domain(&self) -> Interval {
542        // This unwrap always succeeds because `first` has a valid Interval as its domain and the
543        // length of `second` cannot be NAN. It's still fine if it's infinity.
544        Interval::new(
545            self.first.domain().start(),
546            self.first.domain().end() + self.second.domain().length(),
547        )
548        .unwrap()
549    }
550
551    #[inline]
552    fn sample_unchecked(&self, t: f32) -> T {
553        if t > self.first.domain().end() {
554            self.second.sample_unchecked(
555                // `t - first.domain.end` computes the offset into the domain of the second.
556                t - self.first.domain().end() + self.second.domain().start(),
557            )
558        } else {
559            self.first.sample_unchecked(t)
560        }
561    }
562}
563
564/// The curve that results from reversing another.
565///
566/// Curves of this type are produced by [`CurveExt::reverse`].
567///
568/// # Domain
569///
570/// The original curve's domain must be bounded to get a valid [`ReverseCurve`].
571#[derive(Clone, Debug)]
572#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
573#[cfg_attr(
574    feature = "bevy_reflect",
575    derive(Reflect, FromReflect),
576    reflect(from_reflect = false)
577)]
578pub struct ReverseCurve<T, C> {
579    pub(crate) curve: C,
580    #[cfg_attr(feature = "serialize", serde(skip))]
581    #[cfg_attr(feature = "bevy_reflect", reflect(ignore, clone))]
582    pub(crate) _phantom: PhantomData<fn() -> T>,
583}
584
585impl<T, C> Curve<T> for ReverseCurve<T, C>
586where
587    C: Curve<T>,
588{
589    #[inline]
590    fn domain(&self) -> Interval {
591        self.curve.domain()
592    }
593
594    #[inline]
595    fn sample_unchecked(&self, t: f32) -> T {
596        self.curve
597            .sample_unchecked(self.domain().end() - (t - self.domain().start()))
598    }
599}
600
601/// The curve that results from repeating a curve `N` times.
602///
603/// # Notes
604///
605/// - the value at the transitioning points (`domain.end() * n` for `n >= 1`) in the results is the
606///   value at `domain.end()` in the original curve
607///
608/// Curves of this type are produced by [`CurveExt::repeat`].
609///
610/// # Domain
611///
612/// The original curve's domain must be bounded to get a valid [`RepeatCurve`].
613#[derive(Clone, Debug)]
614#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
615#[cfg_attr(
616    feature = "bevy_reflect",
617    derive(Reflect, FromReflect),
618    reflect(from_reflect = false)
619)]
620pub struct RepeatCurve<T, C> {
621    pub(crate) domain: Interval,
622    pub(crate) curve: C,
623    #[cfg_attr(feature = "serialize", serde(skip))]
624    #[cfg_attr(feature = "bevy_reflect", reflect(ignore, clone))]
625    pub(crate) _phantom: PhantomData<fn() -> T>,
626}
627
628impl<T, C> Curve<T> for RepeatCurve<T, C>
629where
630    C: Curve<T>,
631{
632    #[inline]
633    fn domain(&self) -> Interval {
634        self.domain
635    }
636
637    #[inline]
638    fn sample_unchecked(&self, t: f32) -> T {
639        let t = self.base_curve_sample_time(t);
640        self.curve.sample_unchecked(t)
641    }
642}
643
644impl<T, C> RepeatCurve<T, C>
645where
646    C: Curve<T>,
647{
648    #[inline]
649    pub(crate) fn base_curve_sample_time(&self, t: f32) -> f32 {
650        // the domain is bounded by construction
651        let d = self.curve.domain();
652        let cyclic_t = ops::rem_euclid(t - d.start(), d.length());
653        if t != d.start() && cyclic_t == 0.0 {
654            d.end()
655        } else {
656            d.start() + cyclic_t
657        }
658    }
659}
660
661/// The curve that results from repeating a curve forever.
662///
663/// # Notes
664///
665/// - the value at the transitioning points (`domain.end() * n` for `n >= 1`) in the results is the
666///   value at `domain.end()` in the original curve
667///
668/// Curves of this type are produced by [`CurveExt::forever`].
669///
670/// # Domain
671///
672/// The original curve's domain must be bounded to get a valid [`ForeverCurve`].
673#[derive(Clone, Debug)]
674#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
675#[cfg_attr(
676    feature = "bevy_reflect",
677    derive(Reflect, FromReflect),
678    reflect(from_reflect = false)
679)]
680pub struct ForeverCurve<T, C> {
681    pub(crate) curve: C,
682    #[cfg_attr(feature = "serialize", serde(skip))]
683    #[cfg_attr(feature = "bevy_reflect", reflect(ignore, clone))]
684    pub(crate) _phantom: PhantomData<fn() -> T>,
685}
686
687impl<T, C> Curve<T> for ForeverCurve<T, C>
688where
689    C: Curve<T>,
690{
691    #[inline]
692    fn domain(&self) -> Interval {
693        Interval::EVERYWHERE
694    }
695
696    #[inline]
697    fn sample_unchecked(&self, t: f32) -> T {
698        let t = self.base_curve_sample_time(t);
699        self.curve.sample_unchecked(t)
700    }
701}
702
703impl<T, C> ForeverCurve<T, C>
704where
705    C: Curve<T>,
706{
707    #[inline]
708    pub(crate) fn base_curve_sample_time(&self, t: f32) -> f32 {
709        // the domain is bounded by construction
710        let d = self.curve.domain();
711        let cyclic_t = ops::rem_euclid(t - d.start(), d.length());
712        if t != d.start() && cyclic_t == 0.0 {
713            d.end()
714        } else {
715            d.start() + cyclic_t
716        }
717    }
718}
719
720/// The curve that results from chaining a curve with its reversed version. The transition point
721/// is guaranteed to make no jump.
722///
723/// Curves of this type are produced by [`CurveExt::ping_pong`].
724///
725/// # Domain
726///
727/// The original curve's domain must be right-finite to get a valid [`PingPongCurve`].
728#[derive(Clone, Debug)]
729#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
730#[cfg_attr(
731    feature = "bevy_reflect",
732    derive(Reflect, FromReflect),
733    reflect(from_reflect = false)
734)]
735pub struct PingPongCurve<T, C> {
736    pub(crate) curve: C,
737    #[cfg_attr(feature = "serialize", serde(skip))]
738    #[cfg_attr(feature = "bevy_reflect", reflect(ignore, clone))]
739    pub(crate) _phantom: PhantomData<fn() -> T>,
740}
741
742impl<T, C> Curve<T> for PingPongCurve<T, C>
743where
744    C: Curve<T>,
745{
746    #[inline]
747    fn domain(&self) -> Interval {
748        // This unwrap always succeeds because `curve` has a valid Interval as its domain and the
749        // length of `curve` cannot be NAN. It's still fine if it's infinity.
750        Interval::new(
751            self.curve.domain().start(),
752            self.curve.domain().end() + self.curve.domain().length(),
753        )
754        .unwrap()
755    }
756
757    #[inline]
758    fn sample_unchecked(&self, t: f32) -> T {
759        // the domain is bounded by construction
760        let final_t = if t > self.curve.domain().end() {
761            self.curve.domain().end() * 2.0 - t
762        } else {
763            t
764        };
765        self.curve.sample_unchecked(final_t)
766    }
767}
768
769/// The curve that results from chaining two curves.
770///
771/// Additionally the transition of the samples is guaranteed to not make sudden jumps. This is
772/// useful if you really just know about the shapes of your curves and don't want to deal with
773/// stitching them together properly when it would just introduce useless complexity. It is
774/// realized by translating the second curve so that its start sample point coincides with the
775/// first curves' end sample point.
776///
777/// Curves of this type are produced by [`CurveExt::chain_continue`].
778///
779/// # Domain
780///
781/// The first curve's domain must be right-finite and the second's must be left-finite to get a
782/// valid [`ContinuationCurve`].
783#[derive(Clone, Debug)]
784#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
785#[cfg_attr(
786    feature = "bevy_reflect",
787    derive(Reflect, FromReflect),
788    reflect(from_reflect = false)
789)]
790pub struct ContinuationCurve<T, C, D> {
791    pub(crate) first: C,
792    pub(crate) second: D,
793    // cache the offset in the curve directly to prevent triple sampling for every sample we make
794    pub(crate) offset: T,
795    #[cfg_attr(feature = "serialize", serde(skip))]
796    #[cfg_attr(feature = "bevy_reflect", reflect(ignore, clone))]
797    pub(crate) _phantom: PhantomData<fn() -> T>,
798}
799
800impl<T, C, D> Curve<T> for ContinuationCurve<T, C, D>
801where
802    T: VectorSpace,
803    C: Curve<T>,
804    D: Curve<T>,
805{
806    #[inline]
807    fn domain(&self) -> Interval {
808        // This unwrap always succeeds because `curve` has a valid Interval as its domain and the
809        // length of `curve` cannot be NAN. It's still fine if it's infinity.
810        Interval::new(
811            self.first.domain().start(),
812            self.first.domain().end() + self.second.domain().length(),
813        )
814        .unwrap()
815    }
816
817    #[inline]
818    fn sample_unchecked(&self, t: f32) -> T {
819        if t > self.first.domain().end() {
820            self.second.sample_unchecked(
821                // `t - first.domain.end` computes the offset into the domain of the second.
822                t - self.first.domain().end() + self.second.domain().start(),
823            ) + self.offset
824        } else {
825            self.first.sample_unchecked(t)
826        }
827    }
828}