Skip to main content

bevy_curve/
lib.rs

1#![forbid(unsafe_code)]
2#![cfg_attr(
3    any(docsrs, docsrs_dep),
4    expect(
5        internal_features,
6        reason = "rustdoc_internals is needed for fake_variadic"
7    )
8)]
9#![cfg_attr(any(docsrs, docsrs_dep), feature(rustdoc_internals))]
10#![cfg_attr(docsrs, feature(doc_cfg))]
11#![doc(
12    html_logo_url = "https://bevy.org/assets/icon.png",
13    html_favicon_url = "https://bevy.org/assets/icon.png"
14)]
15#![no_std]
16
17//! The [`Curve`] trait, providing a domain-agnostic description of curves.
18//!
19//! ## Overview
20//!
21//! At a high level, [`Curve`] is a trait that abstracts away the implementation details of curves,
22//! which comprise any kind of data parametrized by a single continuous variable. For example, that
23//! variable could represent time, in which case a curve would represent a value that changes over
24//! time, as in animation; on the other hand, it could represent something like displacement or
25//! distance, as in graphs, gradients, and curves in space.
26//!
27//! The trait itself has two fundamental components: a curve must have a [domain], which is a nonempty
28//! range of `f32` values, and it must be able to be [sampled] on every one of those values, producing
29//! output of some fixed type.
30//!
31//! A primary goal of the trait is to allow interfaces to simply accept `impl Curve<T>` as input
32//! rather than requiring for input curves to be defined in data in any particular way. This is
33//! supported by a number of interface methods which allow [changing parametrizations], [mapping output],
34//! and [rasterization].
35//!
36//! ## Analogy with `Iterator`
37//!
38//! The `Curve` API behaves, in many ways, like a continuous counterpart to [`Iterator`]. The analogy
39//! looks something like this with some of the common methods:
40//!
41//! |    Iterators     |     Curves      |
42//! | :--------------- | :-------------- |
43//! | `map`            | `map`           |
44//! | `skip`/`step_by` | `reparametrize` |
45//! | `enumerate`      | `graph`         |
46//! | `chain`          | `chain`         |
47//! | `zip`            | `zip`           |
48//! | `rev`            | `reverse`       |
49//! | `by_ref`         | `by_ref`        |
50//!
51//! Of course, there are very important differences, as well. For instance, the continuous nature of
52//! curves means that many iterator methods make little sense in the context of curves, or at least
53//! require numerical techniques. For example, the analogue of `sum` would be an integral, approximated
54//! by something like Riemann summation.
55//!
56//! Furthermore, the two also differ greatly in their orientation to borrowing and mutation:
57//! iterators are mutated by being iterated, and by contrast, all curve methods are immutable. More
58//! information on the implications of this can be found [below](self#Ownership-and-borrowing).
59//!
60//! ## Defining curves
61//!
62//! Curves may be defined in a number of ways. The following are common:
63//! - using [functions];
64//! - using [sample interpolation];
65//! - using [splines];
66//! - using [easings].
67//!
68//! Among these, the first is the most versatile[^footnote]: the domain and the sampling output are just
69//! specified directly in the construction. For this reason, function curves are a reliable go-to for
70//! simple one-off constructions and procedural uses, where flexibility is desirable. For example:
71//! ```rust
72//! # use bevy_math::vec3;
73//! # use bevy_curve::prelude::*;
74//! // A sinusoid:
75//! let sine_curve = FunctionCurve::new(Interval::EVERYWHERE, f32::sin);
76//!
77//! // A sawtooth wave:
78//! let sawtooth_curve = FunctionCurve::new(Interval::EVERYWHERE, |t| t % 1.0);
79//!
80//! // A helix:
81//! let helix_curve = FunctionCurve::new(Interval::EVERYWHERE, |theta| vec3(theta.sin(), theta, theta.cos()));
82//! ```
83//!
84//! Sample-interpolated curves commonly arises in both rasterization and in animation, and this library
85//! has support for producing them in both fashions. See [below](self#Resampling-and-rasterization) for
86//! more information about rasterization. Here is what an explicit sample-interpolated curve might look like:
87//! ```rust
88//! # use bevy_math::prelude::*;
89//! # use bevy_curve::prelude::*;
90//! # use std::f32::consts::FRAC_PI_2;
91//! // A list of angles that we want to traverse:
92//! let angles = [
93//!     0.0,
94//!     -FRAC_PI_2,
95//!     0.0,
96//!     FRAC_PI_2,
97//!     0.0
98//! ];
99//!
100//! // Make each angle into a rotation by that angle:
101//! let rotations = angles.map(|angle| Rot2::radians(angle));
102//!
103//! // Interpolate these rotations with a `Rot2`-valued curve:
104//! let rotation_curve = SampleAutoCurve::new(interval(0.0, 4.0).unwrap(), rotations).unwrap();
105//! ```
106//!
107//! For more information on [spline curves] and [easing curves], see their respective modules.
108//!
109//! And, of course, you are also free to define curve types yourself, implementing the trait directly.
110//! For custom sample-interpolated curves, the [`cores`] submodule provides machinery to avoid having to
111//! reimplement interpolation logic yourself. In many other cases, implementing the trait directly is
112//! often quite straightforward:
113//! ```rust
114//! # use bevy_math::prelude::*;
115//! # use bevy_curve::prelude::*;
116//! struct ExponentialCurve {
117//!     exponent: f32,
118//! }
119//!
120//! impl Curve<f32> for ExponentialCurve {
121//!     fn domain(&self) -> Interval {
122//!         Interval::EVERYWHERE
123//!     }
124//!
125//!     fn sample_unchecked(&self, t: f32) -> f32 {
126//!         f32::exp(self.exponent * t)
127//!     }
128//!
129//!     // All other trait methods can be inferred from these.
130//! }
131//! ```
132//!
133//! ## Transforming curves
134//!
135//! The API provides a few key ways of transforming one curve into another. These are often useful when
136//! you would like to make use of an interface that requires a curve that bears some logical relationship
137//! to one that you already have access to, but with different requirements or expectations. For example,
138//! the output type of the curves may differ, or the domain may be expected to be different. The `map`
139//! and `reparametrize` methods can help address this.
140//!
141//! As a simple example of the kind of thing that arises in practice, let's imagine that we have a
142//! `Curve<Vec2>` that we want to use to describe the motion of some object over time, but the interface
143//! for animation expects a `Curve<Vec3>`, since the object will move in three dimensions:
144//! ```rust
145//! # use bevy_math::{vec2, prelude::*};
146//! # use bevy_curve::prelude::*;
147//! # use std::f32::consts::TAU;
148//! // Our original curve, which may look something like this:
149//! let ellipse_curve = FunctionCurve::new(
150//!     interval(0.0, TAU).unwrap(),
151//!     |t| vec2(t.cos(), t.sin() * 2.0)
152//! );
153//!
154//! // Use `map` to situate this in 3D as a Curve<Vec3>; in this case, it will be in the xy-plane:
155//! let ellipse_motion_curve = ellipse_curve.map(|pos| pos.extend(0.0));
156//! ```
157//!
158//! We might imagine further still that the interface expects the curve to have domain `[0, 1]`. The
159//! `reparametrize` methods can address this:
160//! ```rust
161//! # use bevy_math::{vec2, prelude::*};
162//! # use bevy_curve::prelude::*;
163//! # use std::f32::consts::TAU;
164//! # let ellipse_curve = FunctionCurve::new(interval(0.0, TAU).unwrap(), |t| vec2(t.cos(), t.sin() * 2.0));
165//! # let ellipse_motion_curve = ellipse_curve.map(|pos| pos.extend(0.0));
166//! // Change the domain to `[0, 1]` instead of `[0, TAU]`:
167//! let final_curve = ellipse_motion_curve.reparametrize_linear(Interval::UNIT).unwrap();
168//! ```
169//!
170//! Of course, there are many other ways of using these methods. In general, `map` is used for transforming
171//! the output and using it to drive something else, while `reparametrize` preserves the curve's shape but
172//! changes the speed and direction in which it is traversed. For instance:
173//! ```rust
174//! # use bevy_math::{vec2, prelude::*};
175//! # use bevy_curve::prelude::*;
176//! // A line segment curve connecting two points in the plane:
177//! let start = vec2(-1.0, 1.0);
178//! let end = vec2(1.0, 1.0);
179//! let segment = FunctionCurve::new(Interval::UNIT, |t| start.lerp(end, t));
180//!
181//! // Let's make a curve that goes back and forth along this line segment forever.
182//! //
183//! // Start by stretching the line segment in parameter space so that it travels along its length
184//! // from `-1` to `1` instead of `0` to `1`:
185//! let stretched_segment = segment.reparametrize_linear(interval(-1.0, 1.0).unwrap()).unwrap();
186//!
187//! // Now, the *output* of `f32::sin` in `[-1, 1]` corresponds to the *input* interval of
188//! // `stretched_segment`; the sinusoid output is mapped to the input parameter and controls how
189//! // far along the segment we are:
190//! let back_and_forth_curve = stretched_segment.reparametrize(Interval::EVERYWHERE, f32::sin);
191//! ```
192//!
193//! ## Combining curves
194//!
195//! Curves become more expressive when used together. For example, maybe you want to combine two
196//! curves end-to-end:
197//! ```rust
198//! # use bevy_math::{vec2, prelude::*};
199//! # use bevy_curve::prelude::*;
200//! # use std::f32::consts::PI;
201//! // A line segment connecting `(-1, 0)` to `(0, 0)`:
202//! let line_curve = FunctionCurve::new(
203//!     Interval::UNIT,
204//!     |t| vec2(-1.0, 0.0).lerp(vec2(0.0, 0.0), t)
205//! );
206//!
207//! // A half-circle curve starting at `(0, 0)`:
208//! let half_circle_curve = FunctionCurve::new(
209//!     interval(0.0, PI).unwrap(),
210//!     |t| vec2(t.cos() * -1.0 + 1.0, t.sin())
211//! );
212//!
213//! // A curve that traverses `line_curve` and then `half_circle_curve` over the interval
214//! // from `0` to `PI + 1`:
215//! let combined_curve = line_curve.chain(half_circle_curve).unwrap();
216//! ```
217//!
218//! Or, instead, maybe you want to combine two curves the *other* way, producing a single curve
219//! that combines their output in a tuple:
220//! ```rust
221//! # use bevy_math::{vec2, prelude::*};
222//! # use bevy_curve::prelude::*;
223//! // Some entity's position in 2D:
224//! let position_curve = FunctionCurve::new(Interval::UNIT, |t| vec2(t.cos(), t.sin()));
225//!
226//! // The same entity's orientation, described as a rotation. (In this case it will be spinning.)
227//! let orientation_curve = FunctionCurve::new(Interval::UNIT, |t| Rot2::radians(5.0 * t));
228//!
229//! // Both in one curve with `(Vec2, Rot2)` output:
230//! let position_and_orientation = position_curve.zip(orientation_curve).unwrap();
231//! ```
232//!
233//! See the documentation on [`chain`] and [`zip`] for more details on how these methods work.
234//!
235//! ## <a name="Resampling-and-rasterization"></a>Resampling and rasterization
236//!
237//! Sometimes, for reasons of portability, performance, or otherwise, it can be useful to ensure that
238//! curves of various provenance all actually share the same concrete type. This is the purpose of the
239//! [`resample`] family of functions: they allow a curve to be replaced by an approximate version of
240//! itself defined by interpolation over samples from the original curve.
241//!
242//! In effect, this allows very different curves to be rasterized and treated uniformly. For example:
243//! ```rust
244//! # use bevy_math::{vec2, prelude::*};
245//! # use bevy_curve::prelude::*;
246//! // A curve that is not easily transported because it relies on evaluating a function:
247//! let interesting_curve = FunctionCurve::new(Interval::UNIT, |t| vec2(t * 3.0, t.exp()));
248//!
249//! // A rasterized form of the preceding curve which is just a `SampleAutoCurve`. Inside, this
250//! // just stores an `Interval` along with a buffer of sample data, so it's easy to serialize
251//! // and deserialize:
252//! let resampled_curve = interesting_curve.resample_auto(100).unwrap();
253//!
254//! // The rasterized form can be seamlessly used as a curve itself:
255//! let some_value = resampled_curve.sample(0.5).unwrap();
256//! ```
257//!
258//! ## <a name="Ownership-and-borrowing"></a>Ownership and borrowing
259//!
260//! It can be easy to get tripped up by how curves specifically interact with Rust's ownership semantics.
261//! First of all, it's worth noting that the API never uses `&mut self` — every method either takes
262//! ownership of the original curve or uses a shared reference.
263//!
264//! Because of the methods that take ownership, it is useful to be aware of the following:
265//! - If `curve` is a curve, then `&curve` is also a curve with the same output. For convenience,
266//!   `&curve` can be written as `curve.by_ref()` for use in method chaining.
267//! - However, `&curve` cannot outlive `curve`. In general, it is not `'static`.
268//!
269//! In other words, `&curve` can be used to perform temporary operations without consuming `curve` (for
270//! example, to effectively pass `curve` into an API which expects an `impl Curve<T>`), but it *cannot*
271//! be used in situations where persistence is necessary (e.g. when the curve itself must be stored
272//! for later use).
273//!
274//! Here is a demonstration:
275//! ```rust
276//! # use bevy_math::prelude::*;
277//! # use bevy_curve::prelude::*;
278//! # let some_magic_constructor = || EasingCurve::new(0.0, 1.0, EaseFunction::ElasticInOut).graph();
279//! //`my_curve` is obtained somehow. It is a `Curve<(f32, f32)>`.
280//! let my_curve = some_magic_constructor();
281//!
282//! // Now, we want to sample a mapped version of `my_curve`.
283//!
284//! // let samples: Vec<f32> = my_curve.map(|(x, y)| y).samples(50).unwrap().collect();
285//! // ^ This would work, but it would also invalidate `my_curve`, since `map` takes ownership.
286//!
287//! // Instead, we pass a borrowed version of `my_curve` to `map`. It lives long enough that we
288//! // can extract samples:
289//! let samples: Vec<f32> = my_curve.by_ref().map(|(x, y)| y).samples(50).unwrap().collect();
290//!
291//! // This way, we retain the ability to use `my_curve` later:
292//! let new_curve = my_curve.map(|(x,y)| x + y);
293//! ```
294//!
295//! [domain]: Curve::domain
296//! [sampled]: Curve::sample
297//! [changing parametrizations]: CurveExt::reparametrize
298//! [mapping output]: CurveExt::map
299//! [rasterization]: CurveResampleExt::resample
300//! [functions]: FunctionCurve
301//! [sample interpolation]: SampleCurve
302//! [splines]: crate::cubic_splines
303//! [easings]: easing
304//! [spline curves]: crate::cubic_splines
305//! [easing curves]: easing
306//! [`chain`]: CurveExt::chain
307//! [`zip`]: CurveExt::zip
308//! [`resample`]: CurveResampleExt::resample
309//!
310//! [^footnote]: In fact, universal as well, in some sense: if `curve` is any curve, then `FunctionCurve::new
311//! (curve.domain(), |t| curve.sample_unchecked(t))` is an equivalent function curve.
312
313#[cfg(feature = "std")]
314extern crate std;
315
316#[cfg(feature = "alloc")]
317extern crate alloc;
318
319pub mod adaptors;
320pub mod cores;
321pub mod cubic_splines;
322pub mod derivatives;
323pub mod easing;
324pub mod interval;
325pub mod iterable;
326
327#[cfg(feature = "alloc")]
328pub mod sample_curves;
329
330// bevy_curve re-exports all commonly-needed curve-related items.
331pub use adaptors::*;
332#[cfg(feature = "alloc")]
333pub use cubic_splines::*;
334pub use easing::*;
335pub use interval::{interval, Interval};
336
337#[cfg(feature = "alloc")]
338pub use {
339    cores::{EvenCore, UnevenCore},
340    sample_curves::*,
341};
342
343mod base {
344    use super::*;
345    use crate::interval::InvalidIntervalError;
346    use bevy_math::VectorSpace;
347    use core::{marker::PhantomData, ops::Deref};
348    use thiserror::Error;
349
350    #[cfg(feature = "alloc")]
351    use {bevy_math::StableInterpolate, itertools::Itertools};
352
353    /// A trait for a type that can represent values of type `T` parametrized over a fixed interval.
354    ///
355    /// Typical examples of this are actual geometric curves where `T: VectorSpace`, but other kinds
356    /// of output data can be represented as well. See the [module-level documentation] for details.
357    ///
358    /// [module-level documentation]: super
359    pub trait Curve<T> {
360        /// The interval over which this curve is parametrized.
361        ///
362        /// This is the range of values of `t` where we can sample the curve and receive valid output.
363        fn domain(&self) -> Interval;
364
365        /// Sample a point on this curve at the parameter value `t`, extracting the associated value.
366        /// This is the unchecked version of sampling, which should only be used if the sample time `t`
367        /// is already known to lie within the curve's domain.
368        ///
369        /// Values sampled from outside of a curve's domain are generally considered invalid; data which
370        /// is nonsensical or otherwise useless may be returned in such a circumstance, and extrapolation
371        /// beyond a curve's domain should not be relied upon.
372        fn sample_unchecked(&self, t: f32) -> T;
373
374        /// Sample a point on this curve at the parameter value `t`, returning `None` if the point is
375        /// outside of the curve's domain.
376        fn sample(&self, t: f32) -> Option<T> {
377            match self.domain().contains(t) {
378                true => Some(self.sample_unchecked(t)),
379                false => None,
380            }
381        }
382
383        /// Sample a point on this curve at the parameter value `t`, clamping `t` to lie inside the
384        /// domain of the curve.
385        fn sample_clamped(&self, t: f32) -> T {
386            let t = self.domain().clamp(t);
387            self.sample_unchecked(t)
388        }
389    }
390
391    impl<T, C, D> Curve<T> for D
392    where
393        C: Curve<T> + ?Sized,
394        D: Deref<Target = C>,
395    {
396        fn domain(&self) -> Interval {
397            <C as Curve<T>>::domain(self)
398        }
399
400        fn sample_unchecked(&self, t: f32) -> T {
401            <C as Curve<T>>::sample_unchecked(self, t)
402        }
403    }
404
405    /// Extension trait implemented by [curves], allowing access to a number of adaptors and
406    /// convenience methods.
407    ///
408    /// This trait is automatically implemented for all curves that are `Sized`. In particular,
409    /// it is implemented for types like `Box<dyn Curve<T>>`. `CurveExt` is not dyn-compatible
410    /// itself.
411    ///
412    /// For more information, see the [module-level documentation].
413    ///
414    /// [curves]: Curve
415    /// [module-level documentation]: super
416    pub trait CurveExt<T>: Curve<T> + Sized {
417        /// Sample a collection of `n >= 0` points on this curve at the parameter values `t_n`,
418        /// returning `None` if the point is outside of the curve's domain.
419        ///
420        /// The samples are returned in the same order as the parameter values `t_n` were provided and
421        /// will include all results. This leaves the responsibility for things like filtering and
422        /// sorting to the user for maximum flexibility.
423        fn sample_iter(
424            &self,
425            iter: impl IntoIterator<Item = f32>,
426        ) -> impl Iterator<Item = Option<T>> {
427            iter.into_iter().map(|t| self.sample(t))
428        }
429
430        /// Sample a collection of `n >= 0` points on this curve at the parameter values `t_n`,
431        /// extracting the associated values. This is the unchecked version of sampling, which should
432        /// only be used if the sample times `t_n` are already known to lie within the curve's domain.
433        ///
434        /// Values sampled from outside of a curve's domain are generally considered invalid; data
435        /// which is nonsensical or otherwise useless may be returned in such a circumstance, and
436        /// extrapolation beyond a curve's domain should not be relied upon.
437        ///
438        /// The samples are returned in the same order as the parameter values `t_n` were provided and
439        /// will include all results. This leaves the responsibility for things like filtering and
440        /// sorting to the user for maximum flexibility.
441        fn sample_iter_unchecked(
442            &self,
443            iter: impl IntoIterator<Item = f32>,
444        ) -> impl Iterator<Item = T> {
445            iter.into_iter().map(|t| self.sample_unchecked(t))
446        }
447
448        /// Sample a collection of `n >= 0` points on this curve at the parameter values `t_n`,
449        /// clamping `t_n` to lie inside the domain of the curve.
450        ///
451        /// The samples are returned in the same order as the parameter values `t_n` were provided and
452        /// will include all results. This leaves the responsibility for things like filtering and
453        /// sorting to the user for maximum flexibility.
454        fn sample_iter_clamped(
455            &self,
456            iter: impl IntoIterator<Item = f32>,
457        ) -> impl Iterator<Item = T> {
458            iter.into_iter().map(|t| self.sample_clamped(t))
459        }
460
461        /// Create a new curve by mapping the values of this curve via a function `f`; i.e., if the
462        /// sample at time `t` for this curve is `x`, the value at time `t` on the new curve will be
463        /// `f(x)`.
464        #[must_use]
465        fn map<S, F>(self, f: F) -> MapCurve<T, S, Self, F>
466        where
467            F: Fn(T) -> S,
468        {
469            MapCurve {
470                preimage: self,
471                f,
472                _phantom: PhantomData,
473            }
474        }
475
476        /// Create a new [`Curve`] whose parameter space is related to the parameter space of this curve
477        /// by `f`. For each time `t`, the sample from the new curve at time `t` is the sample from
478        /// this curve at time `f(t)`. The given `domain` will be the domain of the new curve. The
479        /// function `f` is expected to take `domain` into `self.domain()`.
480        ///
481        /// Note that this is the opposite of what one might expect intuitively; for example, if this
482        /// curve has a parameter domain of `[0, 1]`, then stretching the parameter domain to
483        /// `[0, 2]` would be performed as follows, dividing by what might be perceived as the scaling
484        /// factor rather than multiplying:
485        /// ```
486        /// # use bevy_curve::prelude::*;
487        /// let my_curve = ConstantCurve::new(Interval::UNIT, 1.0);
488        /// let scaled_curve = my_curve.reparametrize(interval(0.0, 2.0).unwrap(), |t| t / 2.0);
489        /// ```
490        /// This kind of linear remapping is provided by the convenience method
491        /// [`CurveExt::reparametrize_linear`], which requires only the desired domain for the new curve.
492        ///
493        /// # Examples
494        /// ```
495        /// // Reverse a curve:
496        /// # use bevy_curve::prelude::*;
497        /// # use bevy_math::vec2;
498        /// let my_curve = ConstantCurve::new(Interval::UNIT, 1.0);
499        /// let domain = my_curve.domain();
500        /// let reversed_curve = my_curve.reparametrize(domain, |t| domain.end() - (t - domain.start()));
501        ///
502        /// // Take a segment of a curve:
503        /// # let my_curve = ConstantCurve::new(Interval::UNIT, 1.0);
504        /// let curve_segment = my_curve.reparametrize(interval(0.0, 0.5).unwrap(), |t| 0.5 + t);
505        /// ```
506        #[must_use]
507        fn reparametrize<F>(self, domain: Interval, f: F) -> ReparamCurve<T, Self, F>
508        where
509            F: Fn(f32) -> f32,
510        {
511            ReparamCurve {
512                domain,
513                base: self,
514                f,
515                _phantom: PhantomData,
516            }
517        }
518
519        /// Linearly reparametrize this [`Curve`], producing a new curve whose domain is the given
520        /// `domain` instead of the current one. This operation is only valid for curves with bounded
521        /// domains.
522        ///
523        /// # Errors
524        ///
525        /// If either this curve's domain or the given `domain` is unbounded, an error is returned.
526        fn reparametrize_linear(
527            self,
528            domain: Interval,
529        ) -> Result<LinearReparamCurve<T, Self>, LinearReparamError> {
530            if !self.domain().is_bounded() {
531                return Err(LinearReparamError::SourceCurveUnbounded);
532            }
533
534            if !domain.is_bounded() {
535                return Err(LinearReparamError::TargetIntervalUnbounded);
536            }
537
538            Ok(LinearReparamCurve {
539                base: self,
540                new_domain: domain,
541                _phantom: PhantomData,
542            })
543        }
544
545        /// Reparametrize this [`Curve`] by sampling from another curve.
546        ///
547        /// The resulting curve samples at time `t` by first sampling `other` at time `t`, which produces
548        /// another sample time `s` which is then used to sample this curve. The domain of the resulting
549        /// curve is the domain of `other`.
550        #[must_use]
551        fn reparametrize_by_curve<C>(self, other: C) -> CurveReparamCurve<T, Self, C>
552        where
553            C: Curve<f32>,
554        {
555            CurveReparamCurve {
556                base: self,
557                reparam_curve: other,
558                _phantom: PhantomData,
559            }
560        }
561
562        /// Create a new [`Curve`] which is the graph of this one; that is, its output echoes the sample
563        /// time as part of a tuple.
564        ///
565        /// For example, if this curve outputs `x` at time `t`, then the produced curve will produce
566        /// `(t, x)` at time `t`. In particular, if this curve is a `Curve<T>`, the output of this method
567        /// is a `Curve<(f32, T)>`.
568        #[must_use]
569        fn graph(self) -> GraphCurve<T, Self> {
570            GraphCurve {
571                base: self,
572                _phantom: PhantomData,
573            }
574        }
575
576        /// Create a new [`Curve`] by zipping this curve together with another.
577        ///
578        /// The sample at time `t` in the new curve is `(x, y)`, where `x` is the sample of `self` at
579        /// time `t` and `y` is the sample of `other` at time `t`. The domain of the new curve is the
580        /// intersection of the domains of its constituents.
581        ///
582        /// # Errors
583        ///
584        /// If the domain intersection would be empty, an error is returned instead.
585        fn zip<S, C>(self, other: C) -> Result<ZipCurve<T, S, Self, C>, InvalidIntervalError>
586        where
587            C: Curve<S> + Sized,
588        {
589            let domain = self.domain().intersect(other.domain())?;
590            Ok(ZipCurve {
591                domain,
592                first: self,
593                second: other,
594                _phantom: PhantomData,
595            })
596        }
597
598        /// Create a new [`Curve`] by composing this curve end-to-start with another, producing another curve
599        /// with outputs of the same type. The domain of the other curve is translated so that its start
600        /// coincides with where this curve ends.
601        ///
602        /// # Errors
603        ///
604        /// A [`ChainError`] is returned if this curve's domain doesn't have a finite end or if
605        /// `other`'s domain doesn't have a finite start.
606        fn chain<C>(self, other: C) -> Result<ChainCurve<T, Self, C>, ChainError>
607        where
608            C: Curve<T>,
609        {
610            if !self.domain().has_finite_end() {
611                return Err(ChainError::FirstEndInfinite);
612            }
613            if !other.domain().has_finite_start() {
614                return Err(ChainError::SecondStartInfinite);
615            }
616            Ok(ChainCurve {
617                first: self,
618                second: other,
619                _phantom: PhantomData,
620            })
621        }
622
623        /// Create a new [`Curve`] inverting this curve on the x-axis, producing another curve with
624        /// outputs of the same type, effectively playing backwards starting at `self.domain().end()`
625        /// and transitioning over to `self.domain().start()`. The domain of the new curve is still the
626        /// same.
627        ///
628        /// # Errors
629        ///
630        /// A [`ReverseError`] is returned if this curve's domain isn't bounded.
631        fn reverse(self) -> Result<ReverseCurve<T, Self>, ReverseError> {
632            self.domain()
633                .is_bounded()
634                .then(|| ReverseCurve {
635                    curve: self,
636                    _phantom: PhantomData,
637                })
638                .ok_or(ReverseError::SourceDomainEndInfinite)
639        }
640
641        /// Create a new [`Curve`] repeating this curve `N` times, producing another curve with outputs
642        /// of the same type. The domain of the new curve will be bigger by a factor of `n + 1`.
643        ///
644        /// # Notes
645        ///
646        /// - this doesn't guarantee a smooth transition from one occurrence of the curve to its next
647        ///   iteration. The curve will make a jump if `self.domain().start() != self.domain().end()`!
648        /// - for `count == 0` the output of this adaptor is basically identical to the previous curve
649        /// - the value at the transitioning points (`domain.end() * n` for `n >= 1`) in the results is the
650        ///   value at `domain.end()` in the original curve
651        ///
652        /// # Errors
653        ///
654        /// A [`RepeatError`] is returned if this curve's domain isn't bounded.
655        fn repeat(self, count: usize) -> Result<RepeatCurve<T, Self>, RepeatError> {
656            self.domain()
657                .is_bounded()
658                .then(|| {
659                    // This unwrap always succeeds because `curve` has a valid Interval as its domain and the
660                    // length of `curve` cannot be NAN. It's still fine if it's infinity.
661                    let domain = Interval::new(
662                        self.domain().start(),
663                        self.domain().end() + self.domain().length() * count as f32,
664                    )
665                    .unwrap();
666                    RepeatCurve {
667                        domain,
668                        curve: self,
669                        _phantom: PhantomData,
670                    }
671                })
672                .ok_or(RepeatError::SourceDomainUnbounded)
673        }
674
675        /// Create a new [`Curve`] repeating this curve forever, producing another curve with
676        /// outputs of the same type. The domain of the new curve will be unbounded.
677        ///
678        /// # Notes
679        ///
680        /// - this doesn't guarantee a smooth transition from one occurrence of the curve to its next
681        ///   iteration. The curve will make a jump if `self.domain().start() != self.domain().end()`!
682        /// - the value at the transitioning points (`domain.end() * n` for `n >= 1`) in the results is the
683        ///   value at `domain.end()` in the original curve
684        ///
685        /// # Errors
686        ///
687        /// A [`RepeatError`] is returned if this curve's domain isn't bounded.
688        fn forever(self) -> Result<ForeverCurve<T, Self>, RepeatError> {
689            self.domain()
690                .is_bounded()
691                .then(|| ForeverCurve {
692                    curve: self,
693                    _phantom: PhantomData,
694                })
695                .ok_or(RepeatError::SourceDomainUnbounded)
696        }
697
698        /// Create a new [`Curve`] chaining the original curve with its inverse, producing
699        /// another curve with outputs of the same type. The domain of the new curve will be twice as
700        /// long. The transition point is guaranteed to not make any jumps.
701        ///
702        /// # Errors
703        ///
704        /// A [`PingPongError`] is returned if this curve's domain isn't right-finite.
705        fn ping_pong(self) -> Result<PingPongCurve<T, Self>, PingPongError> {
706            self.domain()
707                .has_finite_end()
708                .then(|| PingPongCurve {
709                    curve: self,
710                    _phantom: PhantomData,
711                })
712                .ok_or(PingPongError::SourceDomainEndInfinite)
713        }
714
715        /// Create a new [`Curve`] by composing this curve end-to-start with another, producing another
716        /// curve with outputs of the same type. The domain of the other curve is translated so that
717        /// its start coincides with where this curve ends.
718        ///
719        ///
720        /// Additionally the transition of the samples is guaranteed to make no sudden jumps. This is
721        /// useful if you really just know about the shapes of your curves and don't want to deal with
722        /// stitching them together properly when it would just introduce useless complexity. It is
723        /// realized by translating the other curve so that its start sample point coincides with the
724        /// current curves' end sample point.
725        ///
726        /// # Errors
727        ///
728        /// A [`ChainError`] is returned if this curve's domain doesn't have a finite end or if
729        /// `other`'s domain doesn't have a finite start.
730        fn chain_continue<C>(self, other: C) -> Result<ContinuationCurve<T, Self, C>, ChainError>
731        where
732            T: VectorSpace,
733            C: Curve<T>,
734        {
735            if !self.domain().has_finite_end() {
736                return Err(ChainError::FirstEndInfinite);
737            }
738            if !other.domain().has_finite_start() {
739                return Err(ChainError::SecondStartInfinite);
740            }
741
742            let offset = self.sample_unchecked(self.domain().end())
743                - other.sample_unchecked(self.domain().start());
744
745            Ok(ContinuationCurve {
746                first: self,
747                second: other,
748                offset,
749                _phantom: PhantomData,
750            })
751        }
752
753        /// Extract an iterator over evenly-spaced samples from this curve.
754        ///
755        /// # Errors
756        ///
757        /// If `samples` is less than 2 or if this curve has unbounded domain, a [`ResamplingError`]
758        /// is returned.
759        fn samples(&self, samples: usize) -> Result<impl Iterator<Item = T>, ResamplingError> {
760            if samples < 2 {
761                return Err(ResamplingError::NotEnoughSamples(samples));
762            }
763            if !self.domain().is_bounded() {
764                return Err(ResamplingError::UnboundedDomain);
765            }
766
767            // Unwrap on `spaced_points` always succeeds because its error conditions are handled
768            // above.
769            Ok(self
770                .domain()
771                .spaced_points(samples)
772                .unwrap()
773                .map(|t| self.sample_unchecked(t)))
774        }
775
776        /// Borrow this curve rather than taking ownership of it. This is essentially an alias for a
777        /// prefix `&`; the point is that intermediate operations can be performed while retaining
778        /// access to the original curve.
779        ///
780        /// # Example
781        /// ```
782        /// # use bevy_curve::prelude::*;
783        /// let my_curve = FunctionCurve::new(Interval::UNIT, |t| t * t + 1.0);
784        ///
785        /// // Borrow `my_curve` long enough to resample a mapped version. Note that `map` takes
786        /// // ownership of its input.
787        /// let samples = my_curve.by_ref().map(|x| x * 2.0).resample_auto(100).unwrap();
788        ///
789        /// // Do something else with `my_curve` since we retained ownership:
790        /// let new_curve = my_curve.reparametrize_linear(interval(-1.0, 1.0).unwrap()).unwrap();
791        /// ```
792        fn by_ref(&self) -> &Self {
793            self
794        }
795
796        /// Flip this curve so that its tuple output is arranged the other way.
797        #[must_use]
798        fn flip<U, V>(self) -> impl Curve<(V, U)>
799        where
800            Self: CurveExt<(U, V)>,
801        {
802            self.map(|(u, v)| (v, u))
803        }
804    }
805
806    impl<C, T> CurveExt<T> for C where C: Curve<T> {}
807
808    /// Extension trait implemented by [curves], allowing access to generic resampling methods as
809    /// well as those based on [stable interpolation].
810    ///
811    /// This trait is automatically implemented for all curves.
812    ///
813    /// For more information, see the [module-level documentation].
814    ///
815    /// [curves]: Curve
816    /// [stable interpolation]: bevy_math::StableInterpolate
817    /// [module-level documentation]: super
818    #[cfg(feature = "alloc")]
819    pub trait CurveResampleExt<T>: Curve<T> {
820        /// Resample this [`Curve`] to produce a new one that is defined by interpolation over equally
821        /// spaced sample values, using the provided `interpolation` to interpolate between adjacent samples.
822        /// The curve is interpolated on `segments` segments between samples. For example, if `segments` is 1,
823        /// only the start and end points of the curve are used as samples; if `segments` is 2, a sample at
824        /// the midpoint is taken as well, and so on.
825        ///
826        /// The interpolation takes two values by reference together with a scalar parameter and
827        /// produces an owned value. The expectation is that `interpolation(&x, &y, 0.0)` and
828        /// `interpolation(&x, &y, 1.0)` are equivalent to `x` and `y` respectively.
829        ///
830        /// # Errors
831        ///
832        /// If `segments` is zero or if this curve has unbounded domain, then a [`ResamplingError`] is
833        /// returned.
834        ///
835        /// # Example
836        /// ```
837        /// # use bevy_math::*;
838        /// # use bevy_curve::prelude::*;
839        /// let quarter_rotation = FunctionCurve::new(interval(0.0, 90.0).unwrap(), |t| Rot2::degrees(t));
840        /// // A curve which only stores three data points and uses `nlerp` to interpolate them:
841        /// let resampled_rotation = quarter_rotation.resample(3, |x, y, t| x.nlerp(*y, t));
842        /// ```
843        fn resample<I>(
844            &self,
845            segments: usize,
846            interpolation: I,
847        ) -> Result<SampleCurve<T, I>, ResamplingError>
848        where
849            I: Fn(&T, &T, f32) -> T,
850        {
851            let samples = self.samples(segments + 1)?.collect_vec();
852            Ok(SampleCurve {
853                core: EvenCore {
854                    domain: self.domain(),
855                    samples,
856                },
857                interpolation,
858            })
859        }
860
861        /// Resample this [`Curve`] to produce a new one that is defined by interpolation over equally
862        /// spaced sample values, using [automatic interpolation] to interpolate between adjacent samples.
863        /// The curve is interpolated on `segments` segments between samples. For example, if `segments` is 1,
864        /// only the start and end points of the curve are used as samples; if `segments` is 2, a sample at
865        /// the midpoint is taken as well, and so on.
866        ///
867        /// # Errors
868        ///
869        /// If `segments` is zero or if this curve has unbounded domain, a [`ResamplingError`] is returned.
870        ///
871        /// [automatic interpolation]: bevy_math::common_traits::StableInterpolate
872        fn resample_auto(&self, segments: usize) -> Result<SampleAutoCurve<T>, ResamplingError>
873        where
874            T: StableInterpolate,
875        {
876            let samples = self.samples(segments + 1)?.collect_vec();
877            Ok(SampleAutoCurve {
878                core: EvenCore {
879                    domain: self.domain(),
880                    samples,
881                },
882            })
883        }
884
885        /// Resample this [`Curve`] to produce a new one that is defined by interpolation over samples
886        /// taken at a given set of times. The given `interpolation` is used to interpolate adjacent
887        /// samples, and the `sample_times` are expected to contain at least two valid times within the
888        /// curve's domain interval.
889        ///
890        /// Redundant sample times, non-finite sample times, and sample times outside of the domain
891        /// are filtered out. With an insufficient quantity of data, a [`ResamplingError`] is
892        /// returned.
893        ///
894        /// The domain of the produced curve stretches between the first and last sample times of the
895        /// iterator.
896        ///
897        /// The interpolation takes two values by reference together with a scalar parameter and
898        /// produces an owned value. The expectation is that `interpolation(&x, &y, 0.0)` and
899        /// `interpolation(&x, &y, 1.0)` are equivalent to `x` and `y` respectively.
900        ///
901        /// # Errors
902        ///
903        /// If `sample_times` doesn't contain at least two distinct times after filtering, a
904        /// [`ResamplingError`] is returned.
905        fn resample_uneven<I>(
906            &self,
907            sample_times: impl IntoIterator<Item = f32>,
908            interpolation: I,
909        ) -> Result<UnevenSampleCurve<T, I>, ResamplingError>
910        where
911            I: Fn(&T, &T, f32) -> T,
912        {
913            let domain = self.domain();
914            let mut times = sample_times
915                .into_iter()
916                .filter(|t| t.is_finite() && domain.contains(*t))
917                .collect_vec();
918            times.sort_unstable_by(f32::total_cmp);
919            times.dedup();
920            if times.len() < 2 {
921                return Err(ResamplingError::NotEnoughSamples(times.len()));
922            }
923            let samples = times.iter().map(|t| self.sample_unchecked(*t)).collect();
924            Ok(UnevenSampleCurve {
925                core: UnevenCore { times, samples },
926                interpolation,
927            })
928        }
929
930        /// Resample this [`Curve`] to produce a new one that is defined by [automatic interpolation] over
931        /// samples taken at the given set of times. The given `sample_times` are expected to contain at least
932        /// two valid times within the curve's domain interval.
933        ///
934        /// Redundant sample times, non-finite sample times, and sample times outside of the domain
935        /// are simply filtered out. With an insufficient quantity of data, a [`ResamplingError`] is
936        /// returned.
937        ///
938        /// The domain of the produced [`UnevenSampleAutoCurve`] stretches between the first and last
939        /// sample times of the iterator.
940        ///
941        /// # Errors
942        ///
943        /// If `sample_times` doesn't contain at least two distinct times after filtering, a
944        /// [`ResamplingError`] is returned.
945        ///
946        /// [automatic interpolation]: bevy_math::common_traits::StableInterpolate
947        fn resample_uneven_auto(
948            &self,
949            sample_times: impl IntoIterator<Item = f32>,
950        ) -> Result<UnevenSampleAutoCurve<T>, ResamplingError>
951        where
952            T: StableInterpolate,
953        {
954            let domain = self.domain();
955            let mut times = sample_times
956                .into_iter()
957                .filter(|t| t.is_finite() && domain.contains(*t))
958                .collect_vec();
959            times.sort_unstable_by(f32::total_cmp);
960            times.dedup();
961            if times.len() < 2 {
962                return Err(ResamplingError::NotEnoughSamples(times.len()));
963            }
964            let samples = times.iter().map(|t| self.sample_unchecked(*t)).collect();
965            Ok(UnevenSampleAutoCurve {
966                core: UnevenCore { times, samples },
967            })
968        }
969    }
970
971    #[cfg(feature = "alloc")]
972    impl<C, T> CurveResampleExt<T> for C where C: Curve<T> + ?Sized {}
973
974    /// An error indicating that a linear reparameterization couldn't be performed because of
975    /// malformed inputs.
976    #[derive(Debug, Error)]
977    #[error("Could not build a linear function to reparametrize this curve")]
978    pub enum LinearReparamError {
979        /// The source curve that was to be reparametrized had unbounded domain.
980        #[error("This curve has unbounded domain")]
981        SourceCurveUnbounded,
982
983        /// The target interval for reparameterization was unbounded.
984        #[error("The target interval for reparameterization is unbounded")]
985        TargetIntervalUnbounded,
986    }
987
988    /// An error indicating that a reversion of a curve couldn't be performed because of
989    /// malformed inputs.
990    #[derive(Debug, Error)]
991    #[error("Could not reverse this curve")]
992    pub enum ReverseError {
993        /// The source curve that was to be reversed had unbounded domain end.
994        #[error("This curve has an unbounded domain end")]
995        SourceDomainEndInfinite,
996    }
997
998    /// An error indicating that a repetition of a curve couldn't be performed because of malformed
999    /// inputs.
1000    #[derive(Debug, Error)]
1001    #[error("Could not repeat this curve")]
1002    pub enum RepeatError {
1003        /// The source curve that was to be repeated had unbounded domain.
1004        #[error("This curve has an unbounded domain")]
1005        SourceDomainUnbounded,
1006    }
1007
1008    /// An error indicating that a ping ponging of a curve couldn't be performed because of
1009    /// malformed inputs.
1010    #[derive(Debug, Error)]
1011    #[error("Could not ping pong this curve")]
1012    pub enum PingPongError {
1013        /// The source curve that was to be ping ponged had unbounded domain end.
1014        #[error("This curve has an unbounded domain end")]
1015        SourceDomainEndInfinite,
1016    }
1017
1018    /// An error indicating that an end-to-end composition couldn't be performed because of
1019    /// malformed inputs.
1020    #[derive(Debug, Error)]
1021    #[error("Could not compose these curves together")]
1022    pub enum ChainError {
1023        /// The right endpoint of the first curve was infinite.
1024        #[error("The first curve's domain has an infinite end")]
1025        FirstEndInfinite,
1026
1027        /// The left endpoint of the second curve was infinite.
1028        #[error("The second curve's domain has an infinite start")]
1029        SecondStartInfinite,
1030    }
1031
1032    /// An error indicating that a resampling operation could not be performed because of
1033    /// malformed inputs.
1034    #[derive(Debug, Error)]
1035    #[error("Could not resample from this curve because of bad inputs")]
1036    pub enum ResamplingError {
1037        /// This resampling operation was not provided with enough samples to have well-formed output.
1038        #[error("Not enough unique samples to construct resampled curve")]
1039        NotEnoughSamples(usize),
1040
1041        /// This resampling operation failed because of an unbounded interval.
1042        #[error("Could not resample because this curve has unbounded domain")]
1043        UnboundedDomain,
1044    }
1045
1046    #[cfg(test)]
1047    mod tests {
1048        use super::*;
1049        use alloc::vec::Vec;
1050        use approx::{assert_abs_diff_eq, AbsDiffEq};
1051        use bevy_math::{ops, Quat, Vec2};
1052        use core::f32::consts::TAU;
1053
1054        #[test]
1055        fn curve_can_be_made_into_an_object() {
1056            let curve = ConstantCurve::new(Interval::UNIT, 42.0);
1057            let curve: &dyn Curve<f64> = &curve;
1058
1059            assert_eq!(curve.sample(1.0), Some(42.0));
1060            assert_eq!(curve.sample(2.0), None);
1061        }
1062
1063        #[test]
1064        fn constant_curves() {
1065            let curve = ConstantCurve::new(Interval::EVERYWHERE, 5.0);
1066            assert!(curve.sample_unchecked(-35.0) == 5.0);
1067
1068            let curve = ConstantCurve::new(Interval::UNIT, true);
1069            assert!(curve.sample_unchecked(2.0));
1070            assert!(curve.sample(2.0).is_none());
1071        }
1072
1073        #[test]
1074        fn function_curves() {
1075            let curve = FunctionCurve::new(Interval::EVERYWHERE, |t| t * t);
1076            assert!(curve.sample_unchecked(2.0).abs_diff_eq(&4.0, f32::EPSILON));
1077            assert!(curve.sample_unchecked(-3.0).abs_diff_eq(&9.0, f32::EPSILON));
1078
1079            let curve = FunctionCurve::new(interval(0.0, f32::INFINITY).unwrap(), ops::log2);
1080            assert_eq!(curve.sample_unchecked(3.5), ops::log2(3.5));
1081            assert!(curve.sample_unchecked(-1.0).is_nan());
1082            assert!(curve.sample(-1.0).is_none());
1083        }
1084
1085        #[test]
1086        fn linear_curve() {
1087            let start = Vec2::ZERO;
1088            let end = Vec2::new(1.0, 2.0);
1089            let curve = EasingCurve::new(start, end, EaseFunction::Linear);
1090
1091            let mid = (start + end) / 2.0;
1092
1093            [(0.0, start), (0.5, mid), (1.0, end)]
1094                .into_iter()
1095                .for_each(|(t, x)| {
1096                    assert!(curve.sample_unchecked(t).abs_diff_eq(x, f32::EPSILON));
1097                });
1098        }
1099
1100        #[test]
1101        fn easing_curves_step() {
1102            let start = Vec2::ZERO;
1103            let end = Vec2::new(1.0, 2.0);
1104
1105            let curve = EasingCurve::new(start, end, EaseFunction::Steps(4, JumpAt::End));
1106            [
1107                (0.0, start),
1108                (0.249, start),
1109                (0.250, Vec2::new(0.25, 0.5)),
1110                (0.499, Vec2::new(0.25, 0.5)),
1111                (0.500, Vec2::new(0.5, 1.0)),
1112                (0.749, Vec2::new(0.5, 1.0)),
1113                (0.750, Vec2::new(0.75, 1.5)),
1114                (1.0, end),
1115            ]
1116            .into_iter()
1117            .for_each(|(t, x)| {
1118                assert!(curve.sample_unchecked(t).abs_diff_eq(x, f32::EPSILON));
1119            });
1120        }
1121
1122        #[test]
1123        fn easing_curves_quadratic() {
1124            let start = Vec2::ZERO;
1125            let end = Vec2::new(1.0, 2.0);
1126
1127            let curve = EasingCurve::new(start, end, EaseFunction::QuadraticIn);
1128            [
1129                (0.0, start),
1130                (0.25, Vec2::new(0.0625, 0.125)),
1131                (0.5, Vec2::new(0.25, 0.5)),
1132                (1.0, end),
1133            ]
1134            .into_iter()
1135            .for_each(|(t, x)| {
1136                assert!(curve.sample_unchecked(t).abs_diff_eq(x, f32::EPSILON),);
1137            });
1138        }
1139
1140        #[expect(
1141            clippy::neg_multiply,
1142            reason = "Clippy doesn't like this, but it's correct"
1143        )]
1144        #[test]
1145        fn mapping() {
1146            let curve = FunctionCurve::new(Interval::EVERYWHERE, |t| t * 3.0 + 1.0);
1147            let mapped_curve = curve.map(|x| x / 7.0);
1148            assert_eq!(mapped_curve.sample_unchecked(3.5), (3.5 * 3.0 + 1.0) / 7.0);
1149            assert_eq!(
1150                mapped_curve.sample_unchecked(-1.0),
1151                (-1.0 * 3.0 + 1.0) / 7.0
1152            );
1153            assert_eq!(mapped_curve.domain(), Interval::EVERYWHERE);
1154
1155            let curve = FunctionCurve::new(Interval::UNIT, |t| t * TAU);
1156            let mapped_curve = curve.map(Quat::from_rotation_z);
1157            assert_eq!(mapped_curve.sample_unchecked(0.0), Quat::IDENTITY);
1158            assert!(mapped_curve.sample_unchecked(1.0).is_near_identity());
1159            assert_eq!(mapped_curve.domain(), Interval::UNIT);
1160        }
1161
1162        #[test]
1163        fn reverse() {
1164            let curve = FunctionCurve::new(Interval::new(0.0, 1.0).unwrap(), |t| t * 3.0 + 1.0);
1165            let rev_curve = curve.reverse().unwrap();
1166            assert_eq!(rev_curve.sample(-0.1), None);
1167            assert_eq!(rev_curve.sample(0.0), Some(1.0 * 3.0 + 1.0));
1168            assert_eq!(rev_curve.sample(0.5), Some(0.5 * 3.0 + 1.0));
1169            assert_eq!(rev_curve.sample(1.0), Some(0.0 * 3.0 + 1.0));
1170            assert_eq!(rev_curve.sample(1.1), None);
1171
1172            let curve = FunctionCurve::new(Interval::new(-2.0, 1.0).unwrap(), |t| t * 3.0 + 1.0);
1173            let rev_curve = curve.reverse().unwrap();
1174            assert_eq!(rev_curve.sample(-2.1), None);
1175            assert_eq!(rev_curve.sample(-2.0), Some(1.0 * 3.0 + 1.0));
1176            assert_eq!(rev_curve.sample(-0.5), Some(-0.5 * 3.0 + 1.0));
1177            assert_eq!(rev_curve.sample(1.0), Some(-2.0 * 3.0 + 1.0));
1178            assert_eq!(rev_curve.sample(1.1), None);
1179        }
1180
1181        #[test]
1182        fn repeat() {
1183            let curve = FunctionCurve::new(Interval::new(0.0, 1.0).unwrap(), |t| t * 3.0 + 1.0);
1184            let repeat_curve = curve.by_ref().repeat(1).unwrap();
1185            assert_eq!(repeat_curve.sample(-0.1), None);
1186            assert_eq!(repeat_curve.sample(0.0), Some(0.0 * 3.0 + 1.0));
1187            assert_eq!(repeat_curve.sample(0.5), Some(0.5 * 3.0 + 1.0));
1188            assert_eq!(repeat_curve.sample(0.99), Some(0.99 * 3.0 + 1.0));
1189            assert_eq!(repeat_curve.sample(1.0), Some(1.0 * 3.0 + 1.0));
1190            assert_eq!(repeat_curve.sample(1.01), Some(0.01 * 3.0 + 1.0));
1191            assert_eq!(repeat_curve.sample(1.5), Some(0.5 * 3.0 + 1.0));
1192            assert_eq!(repeat_curve.sample(1.99), Some(0.99 * 3.0 + 1.0));
1193            assert_eq!(repeat_curve.sample(2.0), Some(1.0 * 3.0 + 1.0));
1194            assert_eq!(repeat_curve.sample(2.01), None);
1195
1196            let repeat_curve = curve.by_ref().repeat(3).unwrap();
1197            assert_eq!(repeat_curve.sample(2.0), Some(1.0 * 3.0 + 1.0));
1198            assert_eq!(repeat_curve.sample(3.0), Some(1.0 * 3.0 + 1.0));
1199            assert_eq!(repeat_curve.sample(4.0), Some(1.0 * 3.0 + 1.0));
1200            assert_eq!(repeat_curve.sample(5.0), None);
1201
1202            let repeat_curve = curve.by_ref().forever().unwrap();
1203            assert_eq!(repeat_curve.sample(-1.0), Some(1.0 * 3.0 + 1.0));
1204            assert_eq!(repeat_curve.sample(2.0), Some(1.0 * 3.0 + 1.0));
1205            assert_eq!(repeat_curve.sample(3.0), Some(1.0 * 3.0 + 1.0));
1206            assert_eq!(repeat_curve.sample(4.0), Some(1.0 * 3.0 + 1.0));
1207            assert_eq!(repeat_curve.sample(5.0), Some(1.0 * 3.0 + 1.0));
1208        }
1209
1210        #[test]
1211        fn ping_pong() {
1212            let curve = FunctionCurve::new(Interval::new(0.0, 1.0).unwrap(), |t| t * 3.0 + 1.0);
1213            let ping_pong_curve = curve.ping_pong().unwrap();
1214            assert_eq!(ping_pong_curve.sample(-0.1), None);
1215            assert_eq!(ping_pong_curve.sample(0.0), Some(0.0 * 3.0 + 1.0));
1216            assert_eq!(ping_pong_curve.sample(0.5), Some(0.5 * 3.0 + 1.0));
1217            assert_eq!(ping_pong_curve.sample(1.0), Some(1.0 * 3.0 + 1.0));
1218            assert_eq!(ping_pong_curve.sample(1.5), Some(0.5 * 3.0 + 1.0));
1219            assert_eq!(ping_pong_curve.sample(2.0), Some(0.0 * 3.0 + 1.0));
1220            assert_eq!(ping_pong_curve.sample(2.1), None);
1221
1222            let curve = FunctionCurve::new(Interval::new(-2.0, 2.0).unwrap(), |t| t * 3.0 + 1.0);
1223            let ping_pong_curve = curve.ping_pong().unwrap();
1224            assert_eq!(ping_pong_curve.sample(-2.1), None);
1225            assert_eq!(ping_pong_curve.sample(-2.0), Some(-2.0 * 3.0 + 1.0));
1226            assert_eq!(ping_pong_curve.sample(-0.5), Some(-0.5 * 3.0 + 1.0));
1227            assert_eq!(ping_pong_curve.sample(2.0), Some(2.0 * 3.0 + 1.0));
1228            assert_eq!(ping_pong_curve.sample(4.5), Some(-0.5 * 3.0 + 1.0));
1229            assert_eq!(ping_pong_curve.sample(6.0), Some(-2.0 * 3.0 + 1.0));
1230            assert_eq!(ping_pong_curve.sample(6.1), None);
1231        }
1232
1233        #[test]
1234        fn continue_chain() {
1235            let first = FunctionCurve::new(Interval::new(0.0, 1.0).unwrap(), |t| t * 3.0 + 1.0);
1236            let second = FunctionCurve::new(Interval::new(0.0, 1.0).unwrap(), |t| t * t);
1237            let c0_chain_curve = first.chain_continue(second).unwrap();
1238            assert_eq!(c0_chain_curve.sample(-0.1), None);
1239            assert_eq!(c0_chain_curve.sample(0.0), Some(0.0 * 3.0 + 1.0));
1240            assert_eq!(c0_chain_curve.sample(0.5), Some(0.5 * 3.0 + 1.0));
1241            assert_eq!(c0_chain_curve.sample(1.0), Some(1.0 * 3.0 + 1.0));
1242            assert_eq!(c0_chain_curve.sample(1.5), Some(1.0 * 3.0 + 1.0 + 0.25));
1243            assert_eq!(c0_chain_curve.sample(2.0), Some(1.0 * 3.0 + 1.0 + 1.0));
1244            assert_eq!(c0_chain_curve.sample(2.1), None);
1245        }
1246
1247        #[test]
1248        fn reparameterization() {
1249            let curve = FunctionCurve::new(interval(1.0, f32::INFINITY).unwrap(), ops::log2);
1250            let reparametrized_curve = curve
1251                .by_ref()
1252                .reparametrize(interval(0.0, f32::INFINITY).unwrap(), ops::exp2);
1253            assert_abs_diff_eq!(reparametrized_curve.sample_unchecked(3.5), 3.5);
1254            assert_abs_diff_eq!(reparametrized_curve.sample_unchecked(100.0), 100.0);
1255            assert_eq!(
1256                reparametrized_curve.domain(),
1257                interval(0.0, f32::INFINITY).unwrap()
1258            );
1259
1260            let reparametrized_curve = curve.by_ref().reparametrize(Interval::UNIT, |t| t + 1.0);
1261            assert_abs_diff_eq!(reparametrized_curve.sample_unchecked(0.0), 0.0);
1262            assert_abs_diff_eq!(reparametrized_curve.sample_unchecked(1.0), 1.0);
1263            assert_eq!(reparametrized_curve.domain(), Interval::UNIT);
1264        }
1265
1266        #[test]
1267        fn multiple_maps() {
1268            // Make sure these actually happen in the right order.
1269            let curve = FunctionCurve::new(Interval::UNIT, ops::exp2);
1270            let first_mapped = curve.map(ops::log2);
1271            let second_mapped = first_mapped.map(|x| x * -2.0);
1272            assert_abs_diff_eq!(second_mapped.sample_unchecked(0.0), 0.0);
1273            assert_abs_diff_eq!(second_mapped.sample_unchecked(0.5), -1.0);
1274            assert_abs_diff_eq!(second_mapped.sample_unchecked(1.0), -2.0);
1275        }
1276
1277        #[test]
1278        fn multiple_reparams() {
1279            // Make sure these happen in the right order too.
1280            let curve = FunctionCurve::new(Interval::UNIT, ops::exp2);
1281            let first_reparam = curve.reparametrize(interval(1.0, 2.0).unwrap(), ops::log2);
1282            let second_reparam = first_reparam.reparametrize(Interval::UNIT, |t| t + 1.0);
1283            assert_abs_diff_eq!(second_reparam.sample_unchecked(0.0), 1.0);
1284            assert_abs_diff_eq!(second_reparam.sample_unchecked(0.5), 1.5);
1285            assert_abs_diff_eq!(second_reparam.sample_unchecked(1.0), 2.0);
1286        }
1287
1288        #[test]
1289        fn resampling() {
1290            let curve = FunctionCurve::new(interval(1.0, 4.0).unwrap(), ops::log2);
1291
1292            // Need at least one segment to sample.
1293            let nice_try = curve.by_ref().resample_auto(0);
1294            assert!(nice_try.is_err());
1295
1296            // The values of a resampled curve should be very close at the sample points.
1297            // Because of denominators, it's not literally equal.
1298            // (This is a tradeoff against O(1) sampling.)
1299            let resampled_curve = curve.by_ref().resample_auto(100).unwrap();
1300            for test_pt in curve.domain().spaced_points(101).unwrap() {
1301                let expected = curve.sample_unchecked(test_pt);
1302                assert_abs_diff_eq!(
1303                    resampled_curve.sample_unchecked(test_pt),
1304                    expected,
1305                    epsilon = 1e-6
1306                );
1307            }
1308
1309            // Another example.
1310            let curve = FunctionCurve::new(interval(0.0, TAU).unwrap(), ops::cos);
1311            let resampled_curve = curve.by_ref().resample_auto(1000).unwrap();
1312            for test_pt in curve.domain().spaced_points(1001).unwrap() {
1313                let expected = curve.sample_unchecked(test_pt);
1314                assert_abs_diff_eq!(
1315                    resampled_curve.sample_unchecked(test_pt),
1316                    expected,
1317                    epsilon = 1e-6
1318                );
1319            }
1320        }
1321
1322        #[test]
1323        fn uneven_resampling() {
1324            let curve = FunctionCurve::new(interval(0.0, f32::INFINITY).unwrap(), ops::exp);
1325
1326            // Need at least two points to resample.
1327            let nice_try = curve.by_ref().resample_uneven_auto([1.0; 1]);
1328            assert!(nice_try.is_err());
1329
1330            // Uneven sampling should produce literal equality at the sample points.
1331            // (This is part of what you get in exchange for O(log(n)) sampling.)
1332            let sample_points = (0..100).map(|idx| idx as f32 * 0.1);
1333            let resampled_curve = curve.by_ref().resample_uneven_auto(sample_points).unwrap();
1334            for idx in 0..100 {
1335                let test_pt = idx as f32 * 0.1;
1336                let expected = curve.sample_unchecked(test_pt);
1337                assert_eq!(resampled_curve.sample_unchecked(test_pt), expected);
1338            }
1339            assert_abs_diff_eq!(resampled_curve.domain().start(), 0.0);
1340            assert_abs_diff_eq!(resampled_curve.domain().end(), 9.9, epsilon = 1e-6);
1341
1342            // Another example.
1343            let curve = FunctionCurve::new(interval(1.0, f32::INFINITY).unwrap(), ops::log2);
1344            let sample_points = (0..10).map(|idx| ops::exp2(idx as f32));
1345            let resampled_curve = curve.by_ref().resample_uneven_auto(sample_points).unwrap();
1346            for idx in 0..10 {
1347                let test_pt = ops::exp2(idx as f32);
1348                let expected = curve.sample_unchecked(test_pt);
1349                assert_eq!(resampled_curve.sample_unchecked(test_pt), expected);
1350            }
1351            assert_abs_diff_eq!(resampled_curve.domain().start(), 1.0);
1352            assert_abs_diff_eq!(resampled_curve.domain().end(), 512.0);
1353        }
1354
1355        #[test]
1356        fn sample_iterators() {
1357            let times = [-0.5, 0.0, 0.5, 1.0, 1.5];
1358
1359            let curve = FunctionCurve::new(Interval::EVERYWHERE, |t| t * 3.0 + 1.0);
1360            let samples = curve.sample_iter_unchecked(times).collect::<Vec<_>>();
1361            let [y0, y1, y2, y3, y4] = samples.try_into().unwrap();
1362
1363            assert_eq!(y0, -0.5 * 3.0 + 1.0);
1364            assert_eq!(y1, 0.0 * 3.0 + 1.0);
1365            assert_eq!(y2, 0.5 * 3.0 + 1.0);
1366            assert_eq!(y3, 1.0 * 3.0 + 1.0);
1367            assert_eq!(y4, 1.5 * 3.0 + 1.0);
1368
1369            let finite_curve =
1370                FunctionCurve::new(Interval::new(0.0, 1.0).unwrap(), |t| t * 3.0 + 1.0);
1371            let samples = finite_curve.sample_iter(times).collect::<Vec<_>>();
1372            let [y0, y1, y2, y3, y4] = samples.try_into().unwrap();
1373
1374            assert_eq!(y0, None);
1375            assert_eq!(y1, Some(0.0 * 3.0 + 1.0));
1376            assert_eq!(y2, Some(0.5 * 3.0 + 1.0));
1377            assert_eq!(y3, Some(1.0 * 3.0 + 1.0));
1378            assert_eq!(y4, None);
1379
1380            let samples = finite_curve.sample_iter_clamped(times).collect::<Vec<_>>();
1381            let [y0, y1, y2, y3, y4] = samples.try_into().unwrap();
1382
1383            assert_eq!(y0, 0.0 * 3.0 + 1.0);
1384            assert_eq!(y1, 0.0 * 3.0 + 1.0);
1385            assert_eq!(y2, 0.5 * 3.0 + 1.0);
1386            assert_eq!(y3, 1.0 * 3.0 + 1.0);
1387            assert_eq!(y4, 1.0 * 3.0 + 1.0);
1388        }
1389    }
1390}
1391pub use base::*;
1392
1393/// The curve prelude.
1394///
1395/// This includes the most common types in this crate, re-exported for your convenience.
1396pub mod prelude {
1397    #[doc(hidden)]
1398    pub use crate::adaptors::*;
1399
1400    #[doc(hidden)]
1401    pub use crate::base::*;
1402
1403    #[doc(hidden)]
1404    #[cfg(feature = "alloc")]
1405    pub use crate::cubic_splines::*;
1406
1407    #[doc(hidden)]
1408    pub use crate::easing::*;
1409
1410    #[doc(hidden)]
1411    pub use crate::interval::{interval, Interval};
1412
1413    #[doc(hidden)]
1414    #[cfg(feature = "alloc")]
1415    pub use crate::sample_curves::*;
1416
1417    #[doc(hidden)]
1418    #[cfg(feature = "alloc")]
1419    pub use crate::cores::{EvenCore, UnevenCore};
1420}