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