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