//! The [`Curve`] trait, providing a domain-agnostic description of curves.1//!2//! ## Overview3//!4//! At a high level, [`Curve`] is a trait that abstracts away the implementation details of curves,5//! which comprise any kind of data parametrized by a single continuous variable. For example, that6//! variable could represent time, in which case a curve would represent a value that changes over7//! time, as in animation; on the other hand, it could represent something like displacement or8//! distance, as in graphs, gradients, and curves in space.9//!10//! The trait itself has two fundamental components: a curve must have a [domain], which is a nonempty11//! range of `f32` values, and it must be able to be [sampled] on every one of those values, producing12//! output of some fixed type.13//!14//! A primary goal of the trait is to allow interfaces to simply accept `impl Curve<T>` as input15//! rather than requiring for input curves to be defined in data in any particular way. This is16//! supported by a number of interface methods which allow [changing parametrizations], [mapping output],17//! and [rasterization].18//!19//! ## Analogy with `Iterator`20//!21//! The `Curve` API behaves, in many ways, like a continuous counterpart to [`Iterator`]. The analogy22//! looks something like this with some of the common methods:23//!24//! | Iterators | Curves |25//! | :--------------- | :-------------- |26//! | `map` | `map` |27//! | `skip`/`step_by` | `reparametrize` |28//! | `enumerate` | `graph` |29//! | `chain` | `chain` |30//! | `zip` | `zip` |31//! | `rev` | `reverse` |32//! | `by_ref` | `by_ref` |33//!34//! Of course, there are very important differences, as well. For instance, the continuous nature of35//! curves means that many iterator methods make little sense in the context of curves, or at least36//! require numerical techniques. For example, the analogue of `sum` would be an integral, approximated37//! by something like Riemann summation.38//!39//! Furthermore, the two also differ greatly in their orientation to borrowing and mutation:40//! iterators are mutated by being iterated, and by contrast, all curve methods are immutable. More41//! information on the implications of this can be found [below](self#Ownership-and-borrowing).42//!43//! ## Defining curves44//!45//! Curves may be defined in a number of ways. The following are common:46//! - using [functions];47//! - using [sample interpolation];48//! - using [splines];49//! - using [easings].50//!51//! Among these, the first is the most versatile[^footnote]: the domain and the sampling output are just52//! specified directly in the construction. For this reason, function curves are a reliable go-to for53//! simple one-off constructions and procedural uses, where flexibility is desirable. For example:54//! ```rust55//! # use bevy_math::vec3;56//! # use bevy_math::curve::*;57//! // A sinusoid:58//! let sine_curve = FunctionCurve::new(Interval::EVERYWHERE, f32::sin);59//!60//! // A sawtooth wave:61//! let sawtooth_curve = FunctionCurve::new(Interval::EVERYWHERE, |t| t % 1.0);62//!63//! // A helix:64//! let helix_curve = FunctionCurve::new(Interval::EVERYWHERE, |theta| vec3(theta.sin(), theta, theta.cos()));65//! ```66//!67//! Sample-interpolated curves commonly arises in both rasterization and in animation, and this library68//! has support for producing them in both fashions. See [below](self#Resampling-and-rasterization) for69//! more information about rasterization. Here is what an explicit sample-interpolated curve might look like:70//! ```rust71//! # use bevy_math::prelude::*;72//! # use std::f32::consts::FRAC_PI_2;73//! // A list of angles that we want to traverse:74//! let angles = [75//! 0.0,76//! -FRAC_PI_2,77//! 0.0,78//! FRAC_PI_2,79//! 0.080//! ];81//!82//! // Make each angle into a rotation by that angle:83//! let rotations = angles.map(|angle| Rot2::radians(angle));84//!85//! // Interpolate these rotations with a `Rot2`-valued curve:86//! let rotation_curve = SampleAutoCurve::new(interval(0.0, 4.0).unwrap(), rotations).unwrap();87//! ```88//!89//! For more information on [spline curves] and [easing curves], see their respective modules.90//!91//! And, of course, you are also free to define curve types yourself, implementing the trait directly.92//! For custom sample-interpolated curves, the [`cores`] submodule provides machinery to avoid having to93//! reimplement interpolation logic yourself. In many other cases, implementing the trait directly is94//! often quite straightforward:95//! ```rust96//! # use bevy_math::prelude::*;97//! struct ExponentialCurve {98//! exponent: f32,99//! }100//!101//! impl Curve<f32> for ExponentialCurve {102//! fn domain(&self) -> Interval {103//! Interval::EVERYWHERE104//! }105//!106//! fn sample_unchecked(&self, t: f32) -> f32 {107//! f32::exp(self.exponent * t)108//! }109//!110//! // All other trait methods can be inferred from these.111//! }112//! ```113//!114//! ## Transforming curves115//!116//! The API provides a few key ways of transforming one curve into another. These are often useful when117//! you would like to make use of an interface that requires a curve that bears some logical relationship118//! to one that you already have access to, but with different requirements or expectations. For example,119//! the output type of the curves may differ, or the domain may be expected to be different. The `map`120//! and `reparametrize` methods can help address this.121//!122//! As a simple example of the kind of thing that arises in practice, let's imagine that we have a123//! `Curve<Vec2>` that we want to use to describe the motion of some object over time, but the interface124//! for animation expects a `Curve<Vec3>`, since the object will move in three dimensions:125//! ```rust126//! # use bevy_math::{vec2, prelude::*};127//! # use std::f32::consts::TAU;128//! // Our original curve, which may look something like this:129//! let ellipse_curve = FunctionCurve::new(130//! interval(0.0, TAU).unwrap(),131//! |t| vec2(t.cos(), t.sin() * 2.0)132//! );133//!134//! // Use `map` to situate this in 3D as a Curve<Vec3>; in this case, it will be in the xy-plane:135//! let ellipse_motion_curve = ellipse_curve.map(|pos| pos.extend(0.0));136//! ```137//!138//! We might imagine further still that the interface expects the curve to have domain `[0, 1]`. The139//! `reparametrize` methods can address this:140//! ```rust141//! # use bevy_math::{vec2, prelude::*};142//! # use std::f32::consts::TAU;143//! # let ellipse_curve = FunctionCurve::new(interval(0.0, TAU).unwrap(), |t| vec2(t.cos(), t.sin() * 2.0));144//! # let ellipse_motion_curve = ellipse_curve.map(|pos| pos.extend(0.0));145//! // Change the domain to `[0, 1]` instead of `[0, TAU]`:146//! let final_curve = ellipse_motion_curve.reparametrize_linear(Interval::UNIT).unwrap();147//! ```148//!149//! Of course, there are many other ways of using these methods. In general, `map` is used for transforming150//! the output and using it to drive something else, while `reparametrize` preserves the curve's shape but151//! changes the speed and direction in which it is traversed. For instance:152//! ```rust153//! # use bevy_math::{vec2, prelude::*};154//! // A line segment curve connecting two points in the plane:155//! let start = vec2(-1.0, 1.0);156//! let end = vec2(1.0, 1.0);157//! let segment = FunctionCurve::new(Interval::UNIT, |t| start.lerp(end, t));158//!159//! // Let's make a curve that goes back and forth along this line segment forever.160//! //161//! // Start by stretching the line segment in parameter space so that it travels along its length162//! // from `-1` to `1` instead of `0` to `1`:163//! let stretched_segment = segment.reparametrize_linear(interval(-1.0, 1.0).unwrap()).unwrap();164//!165//! // Now, the *output* of `f32::sin` in `[-1, 1]` corresponds to the *input* interval of166//! // `stretched_segment`; the sinusoid output is mapped to the input parameter and controls how167//! // far along the segment we are:168//! let back_and_forth_curve = stretched_segment.reparametrize(Interval::EVERYWHERE, f32::sin);169//! ```170//!171//! ## Combining curves172//!173//! Curves become more expressive when used together. For example, maybe you want to combine two174//! curves end-to-end:175//! ```rust176//! # use bevy_math::{vec2, prelude::*};177//! # use std::f32::consts::PI;178//! // A line segment connecting `(-1, 0)` to `(0, 0)`:179//! let line_curve = FunctionCurve::new(180//! Interval::UNIT,181//! |t| vec2(-1.0, 0.0).lerp(vec2(0.0, 0.0), t)182//! );183//!184//! // A half-circle curve starting at `(0, 0)`:185//! let half_circle_curve = FunctionCurve::new(186//! interval(0.0, PI).unwrap(),187//! |t| vec2(t.cos() * -1.0 + 1.0, t.sin())188//! );189//!190//! // A curve that traverses `line_curve` and then `half_circle_curve` over the interval191//! // from `0` to `PI + 1`:192//! let combined_curve = line_curve.chain(half_circle_curve).unwrap();193//! ```194//!195//! Or, instead, maybe you want to combine two curves the *other* way, producing a single curve196//! that combines their output in a tuple:197//! ```rust198//! # use bevy_math::{vec2, prelude::*};199//! // Some entity's position in 2D:200//! let position_curve = FunctionCurve::new(Interval::UNIT, |t| vec2(t.cos(), t.sin()));201//!202//! // The same entity's orientation, described as a rotation. (In this case it will be spinning.)203//! let orientation_curve = FunctionCurve::new(Interval::UNIT, |t| Rot2::radians(5.0 * t));204//!205//! // Both in one curve with `(Vec2, Rot2)` output:206//! let position_and_orientation = position_curve.zip(orientation_curve).unwrap();207//! ```208//!209//! See the documentation on [`chain`] and [`zip`] for more details on how these methods work.210//!211//! ## <a name="Resampling-and-rasterization"></a>Resampling and rasterization212//!213//! Sometimes, for reasons of portability, performance, or otherwise, it can be useful to ensure that214//! curves of various provenance all actually share the same concrete type. This is the purpose of the215//! [`resample`] family of functions: they allow a curve to be replaced by an approximate version of216//! itself defined by interpolation over samples from the original curve.217//!218//! In effect, this allows very different curves to be rasterized and treated uniformly. For example:219//! ```rust220//! # use bevy_math::{vec2, prelude::*};221//! // A curve that is not easily transported because it relies on evaluating a function:222//! let interesting_curve = FunctionCurve::new(Interval::UNIT, |t| vec2(t * 3.0, t.exp()));223//!224//! // A rasterized form of the preceding curve which is just a `SampleAutoCurve`. Inside, this225//! // just stores an `Interval` along with a buffer of sample data, so it's easy to serialize226//! // and deserialize:227//! let resampled_curve = interesting_curve.resample_auto(100).unwrap();228//!229//! // The rasterized form can be seamlessly used as a curve itself:230//! let some_value = resampled_curve.sample(0.5).unwrap();231//! ```232//!233//! ## <a name="Ownership-and-borrowing"></a>Ownership and borrowing234//!235//! It can be easy to get tripped up by how curves specifically interact with Rust's ownership semantics.236//! First of all, it's worth noting that the API never uses `&mut self` — every method either takes237//! ownership of the original curve or uses a shared reference.238//!239//! Because of the methods that take ownership, it is useful to be aware of the following:240//! - If `curve` is a curve, then `&curve` is also a curve with the same output. For convenience,241//! `&curve` can be written as `curve.by_ref()` for use in method chaining.242//! - However, `&curve` cannot outlive `curve`. In general, it is not `'static`.243//!244//! In other words, `&curve` can be used to perform temporary operations without consuming `curve` (for245//! example, to effectively pass `curve` into an API which expects an `impl Curve<T>`), but it *cannot*246//! be used in situations where persistence is necessary (e.g. when the curve itself must be stored247//! for later use).248//!249//! Here is a demonstration:250//! ```rust251//! # use bevy_math::prelude::*;252//! # let some_magic_constructor = || EasingCurve::new(0.0, 1.0, EaseFunction::ElasticInOut).graph();253//! //`my_curve` is obtained somehow. It is a `Curve<(f32, f32)>`.254//! let my_curve = some_magic_constructor();255//!256//! // Now, we want to sample a mapped version of `my_curve`.257//!258//! // let samples: Vec<f32> = my_curve.map(|(x, y)| y).samples(50).unwrap().collect();259//! // ^ This would work, but it would also invalidate `my_curve`, since `map` takes ownership.260//!261//! // Instead, we pass a borrowed version of `my_curve` to `map`. It lives long enough that we262//! // can extract samples:263//! let samples: Vec<f32> = my_curve.by_ref().map(|(x, y)| y).samples(50).unwrap().collect();264//!265//! // This way, we retain the ability to use `my_curve` later:266//! let new_curve = my_curve.map(|(x,y)| x + y);267//! ```268//!269//! [domain]: Curve::domain270//! [sampled]: Curve::sample271//! [changing parametrizations]: CurveExt::reparametrize272//! [mapping output]: CurveExt::map273//! [rasterization]: CurveResampleExt::resample274//! [functions]: FunctionCurve275//! [sample interpolation]: SampleCurve276//! [splines]: crate::cubic_splines277//! [easings]: easing278//! [spline curves]: crate::cubic_splines279//! [easing curves]: easing280//! [`chain`]: CurveExt::chain281//! [`zip`]: CurveExt::zip282//! [`resample`]: CurveResampleExt::resample283//!284//! [^footnote]: In fact, universal as well, in some sense: if `curve` is any curve, then `FunctionCurve::new285//! (curve.domain(), |t| curve.sample_unchecked(t))` is an equivalent function curve.286287pub mod adaptors;288pub mod cores;289pub mod derivatives;290pub mod easing;291pub mod interval;292pub mod iterable;293294#[cfg(feature = "alloc")]295pub mod sample_curves;296297// bevy_math::curve re-exports all commonly-needed curve-related items.298pub use adaptors::*;299pub use easing::*;300pub use interval::{interval, Interval};301302#[cfg(feature = "alloc")]303pub use {304cores::{EvenCore, UnevenCore},305sample_curves::*,306};307308use crate::VectorSpace;309use core::{marker::PhantomData, ops::Deref};310use interval::InvalidIntervalError;311use thiserror::Error;312313#[cfg(feature = "alloc")]314use {crate::StableInterpolate, itertools::Itertools};315316/// A trait for a type that can represent values of type `T` parametrized over a fixed interval.317///318/// Typical examples of this are actual geometric curves where `T: VectorSpace`, but other kinds319/// of output data can be represented as well. See the [module-level documentation] for details.320///321/// [module-level documentation]: self322pub trait Curve<T> {323/// The interval over which this curve is parametrized.324///325/// This is the range of values of `t` where we can sample the curve and receive valid output.326fn domain(&self) -> Interval;327328/// Sample a point on this curve at the parameter value `t`, extracting the associated value.329/// This is the unchecked version of sampling, which should only be used if the sample time `t`330/// is already known to lie within the curve's domain.331///332/// Values sampled from outside of a curve's domain are generally considered invalid; data which333/// is nonsensical or otherwise useless may be returned in such a circumstance, and extrapolation334/// beyond a curve's domain should not be relied upon.335fn sample_unchecked(&self, t: f32) -> T;336337/// Sample a point on this curve at the parameter value `t`, returning `None` if the point is338/// outside of the curve's domain.339fn sample(&self, t: f32) -> Option<T> {340match self.domain().contains(t) {341true => Some(self.sample_unchecked(t)),342false => None,343}344}345346/// Sample a point on this curve at the parameter value `t`, clamping `t` to lie inside the347/// domain of the curve.348fn sample_clamped(&self, t: f32) -> T {349let t = self.domain().clamp(t);350self.sample_unchecked(t)351}352}353354impl<T, C, D> Curve<T> for D355where356C: Curve<T> + ?Sized,357D: Deref<Target = C>,358{359fn domain(&self) -> Interval {360<C as Curve<T>>::domain(self)361}362363fn sample_unchecked(&self, t: f32) -> T {364<C as Curve<T>>::sample_unchecked(self, t)365}366}367368/// Extension trait implemented by [curves], allowing access to a number of adaptors and369/// convenience methods.370///371/// This trait is automatically implemented for all curves that are `Sized`. In particular,372/// it is implemented for types like `Box<dyn Curve<T>>`. `CurveExt` is not dyn-compatible373/// itself.374///375/// For more information, see the [module-level documentation].376///377/// [curves]: Curve378/// [module-level documentation]: self379pub trait CurveExt<T>: Curve<T> + Sized {380/// Sample a collection of `n >= 0` points on this curve at the parameter values `t_n`,381/// returning `None` if the point is outside of the curve's domain.382///383/// The samples are returned in the same order as the parameter values `t_n` were provided and384/// will include all results. This leaves the responsibility for things like filtering and385/// sorting to the user for maximum flexibility.386fn sample_iter(&self, iter: impl IntoIterator<Item = f32>) -> impl Iterator<Item = Option<T>> {387iter.into_iter().map(|t| self.sample(t))388}389390/// Sample a collection of `n >= 0` points on this curve at the parameter values `t_n`,391/// extracting the associated values. This is the unchecked version of sampling, which should392/// only be used if the sample times `t_n` are already known to lie within the curve's domain.393///394/// Values sampled from outside of a curve's domain are generally considered invalid; data395/// which is nonsensical or otherwise useless may be returned in such a circumstance, and396/// extrapolation beyond a curve's domain should not be relied upon.397///398/// The samples are returned in the same order as the parameter values `t_n` were provided and399/// will include all results. This leaves the responsibility for things like filtering and400/// sorting to the user for maximum flexibility.401fn sample_iter_unchecked(402&self,403iter: impl IntoIterator<Item = f32>,404) -> impl Iterator<Item = T> {405iter.into_iter().map(|t| self.sample_unchecked(t))406}407408/// Sample a collection of `n >= 0` points on this curve at the parameter values `t_n`,409/// clamping `t_n` to lie inside the domain of the curve.410///411/// The samples are returned in the same order as the parameter values `t_n` were provided and412/// will include all results. This leaves the responsibility for things like filtering and413/// sorting to the user for maximum flexibility.414fn sample_iter_clamped(&self, iter: impl IntoIterator<Item = f32>) -> impl Iterator<Item = T> {415iter.into_iter().map(|t| self.sample_clamped(t))416}417418/// Create a new curve by mapping the values of this curve via a function `f`; i.e., if the419/// sample at time `t` for this curve is `x`, the value at time `t` on the new curve will be420/// `f(x)`.421#[must_use]422fn map<S, F>(self, f: F) -> MapCurve<T, S, Self, F>423where424F: Fn(T) -> S,425{426MapCurve {427preimage: self,428f,429_phantom: PhantomData,430}431}432433/// Create a new [`Curve`] whose parameter space is related to the parameter space of this curve434/// by `f`. For each time `t`, the sample from the new curve at time `t` is the sample from435/// this curve at time `f(t)`. The given `domain` will be the domain of the new curve. The436/// function `f` is expected to take `domain` into `self.domain()`.437///438/// Note that this is the opposite of what one might expect intuitively; for example, if this439/// curve has a parameter domain of `[0, 1]`, then stretching the parameter domain to440/// `[0, 2]` would be performed as follows, dividing by what might be perceived as the scaling441/// factor rather than multiplying:442/// ```443/// # use bevy_math::curve::*;444/// let my_curve = ConstantCurve::new(Interval::UNIT, 1.0);445/// let scaled_curve = my_curve.reparametrize(interval(0.0, 2.0).unwrap(), |t| t / 2.0);446/// ```447/// This kind of linear remapping is provided by the convenience method448/// [`CurveExt::reparametrize_linear`], which requires only the desired domain for the new curve.449///450/// # Examples451/// ```452/// // Reverse a curve:453/// # use bevy_math::curve::*;454/// # use bevy_math::vec2;455/// let my_curve = ConstantCurve::new(Interval::UNIT, 1.0);456/// let domain = my_curve.domain();457/// let reversed_curve = my_curve.reparametrize(domain, |t| domain.end() - (t - domain.start()));458///459/// // Take a segment of a curve:460/// # let my_curve = ConstantCurve::new(Interval::UNIT, 1.0);461/// let curve_segment = my_curve.reparametrize(interval(0.0, 0.5).unwrap(), |t| 0.5 + t);462/// ```463#[must_use]464fn reparametrize<F>(self, domain: Interval, f: F) -> ReparamCurve<T, Self, F>465where466F: Fn(f32) -> f32,467{468ReparamCurve {469domain,470base: self,471f,472_phantom: PhantomData,473}474}475476/// Linearly reparametrize this [`Curve`], producing a new curve whose domain is the given477/// `domain` instead of the current one. This operation is only valid for curves with bounded478/// domains.479///480/// # Errors481///482/// If either this curve's domain or the given `domain` is unbounded, an error is returned.483fn reparametrize_linear(484self,485domain: Interval,486) -> Result<LinearReparamCurve<T, Self>, LinearReparamError> {487if !self.domain().is_bounded() {488return Err(LinearReparamError::SourceCurveUnbounded);489}490491if !domain.is_bounded() {492return Err(LinearReparamError::TargetIntervalUnbounded);493}494495Ok(LinearReparamCurve {496base: self,497new_domain: domain,498_phantom: PhantomData,499})500}501502/// Reparametrize this [`Curve`] by sampling from another curve.503///504/// The resulting curve samples at time `t` by first sampling `other` at time `t`, which produces505/// another sample time `s` which is then used to sample this curve. The domain of the resulting506/// curve is the domain of `other`.507#[must_use]508fn reparametrize_by_curve<C>(self, other: C) -> CurveReparamCurve<T, Self, C>509where510C: Curve<f32>,511{512CurveReparamCurve {513base: self,514reparam_curve: other,515_phantom: PhantomData,516}517}518519/// Create a new [`Curve`] which is the graph of this one; that is, its output echoes the sample520/// time as part of a tuple.521///522/// For example, if this curve outputs `x` at time `t`, then the produced curve will produce523/// `(t, x)` at time `t`. In particular, if this curve is a `Curve<T>`, the output of this method524/// is a `Curve<(f32, T)>`.525#[must_use]526fn graph(self) -> GraphCurve<T, Self> {527GraphCurve {528base: self,529_phantom: PhantomData,530}531}532533/// Create a new [`Curve`] by zipping this curve together with another.534///535/// The sample at time `t` in the new curve is `(x, y)`, where `x` is the sample of `self` at536/// time `t` and `y` is the sample of `other` at time `t`. The domain of the new curve is the537/// intersection of the domains of its constituents.538///539/// # Errors540///541/// If the domain intersection would be empty, an error is returned instead.542fn zip<S, C>(self, other: C) -> Result<ZipCurve<T, S, Self, C>, InvalidIntervalError>543where544C: Curve<S> + Sized,545{546let domain = self.domain().intersect(other.domain())?;547Ok(ZipCurve {548domain,549first: self,550second: other,551_phantom: PhantomData,552})553}554555/// Create a new [`Curve`] by composing this curve end-to-start with another, producing another curve556/// with outputs of the same type. The domain of the other curve is translated so that its start557/// coincides with where this curve ends.558///559/// # Errors560///561/// A [`ChainError`] is returned if this curve's domain doesn't have a finite end or if562/// `other`'s domain doesn't have a finite start.563fn chain<C>(self, other: C) -> Result<ChainCurve<T, Self, C>, ChainError>564where565C: Curve<T>,566{567if !self.domain().has_finite_end() {568return Err(ChainError::FirstEndInfinite);569}570if !other.domain().has_finite_start() {571return Err(ChainError::SecondStartInfinite);572}573Ok(ChainCurve {574first: self,575second: other,576_phantom: PhantomData,577})578}579580/// Create a new [`Curve`] inverting this curve on the x-axis, producing another curve with581/// outputs of the same type, effectively playing backwards starting at `self.domain().end()`582/// and transitioning over to `self.domain().start()`. The domain of the new curve is still the583/// same.584///585/// # Errors586///587/// A [`ReverseError`] is returned if this curve's domain isn't bounded.588fn reverse(self) -> Result<ReverseCurve<T, Self>, ReverseError> {589self.domain()590.is_bounded()591.then(|| ReverseCurve {592curve: self,593_phantom: PhantomData,594})595.ok_or(ReverseError::SourceDomainEndInfinite)596}597598/// Create a new [`Curve`] repeating this curve `N` times, producing another curve with outputs599/// of the same type. The domain of the new curve will be bigger by a factor of `n + 1`.600///601/// # Notes602///603/// - this doesn't guarantee a smooth transition from one occurrence of the curve to its next604/// iteration. The curve will make a jump if `self.domain().start() != self.domain().end()`!605/// - for `count == 0` the output of this adaptor is basically identical to the previous curve606/// - the value at the transitioning points (`domain.end() * n` for `n >= 1`) in the results is the607/// value at `domain.end()` in the original curve608///609/// # Errors610///611/// A [`RepeatError`] is returned if this curve's domain isn't bounded.612fn repeat(self, count: usize) -> Result<RepeatCurve<T, Self>, RepeatError> {613self.domain()614.is_bounded()615.then(|| {616// This unwrap always succeeds because `curve` has a valid Interval as its domain and the617// length of `curve` cannot be NAN. It's still fine if it's infinity.618let domain = Interval::new(619self.domain().start(),620self.domain().end() + self.domain().length() * count as f32,621)622.unwrap();623RepeatCurve {624domain,625curve: self,626_phantom: PhantomData,627}628})629.ok_or(RepeatError::SourceDomainUnbounded)630}631632/// Create a new [`Curve`] repeating this curve forever, producing another curve with633/// outputs of the same type. The domain of the new curve will be unbounded.634///635/// # Notes636///637/// - this doesn't guarantee a smooth transition from one occurrence of the curve to its next638/// iteration. The curve will make a jump if `self.domain().start() != self.domain().end()`!639/// - the value at the transitioning points (`domain.end() * n` for `n >= 1`) in the results is the640/// value at `domain.end()` in the original curve641///642/// # Errors643///644/// A [`RepeatError`] is returned if this curve's domain isn't bounded.645fn forever(self) -> Result<ForeverCurve<T, Self>, RepeatError> {646self.domain()647.is_bounded()648.then(|| ForeverCurve {649curve: self,650_phantom: PhantomData,651})652.ok_or(RepeatError::SourceDomainUnbounded)653}654655/// Create a new [`Curve`] chaining the original curve with its inverse, producing656/// another curve with outputs of the same type. The domain of the new curve will be twice as657/// long. The transition point is guaranteed to not make any jumps.658///659/// # Errors660///661/// A [`PingPongError`] is returned if this curve's domain isn't right-finite.662fn ping_pong(self) -> Result<PingPongCurve<T, Self>, PingPongError> {663self.domain()664.has_finite_end()665.then(|| PingPongCurve {666curve: self,667_phantom: PhantomData,668})669.ok_or(PingPongError::SourceDomainEndInfinite)670}671672/// Create a new [`Curve`] by composing this curve end-to-start with another, producing another673/// curve with outputs of the same type. The domain of the other curve is translated so that674/// its start coincides with where this curve ends.675///676///677/// Additionally the transition of the samples is guaranteed to make no sudden jumps. This is678/// useful if you really just know about the shapes of your curves and don't want to deal with679/// stitching them together properly when it would just introduce useless complexity. It is680/// realized by translating the other curve so that its start sample point coincides with the681/// current curves' end sample point.682///683/// # Errors684///685/// A [`ChainError`] is returned if this curve's domain doesn't have a finite end or if686/// `other`'s domain doesn't have a finite start.687fn chain_continue<C>(self, other: C) -> Result<ContinuationCurve<T, Self, C>, ChainError>688where689T: VectorSpace,690C: Curve<T>,691{692if !self.domain().has_finite_end() {693return Err(ChainError::FirstEndInfinite);694}695if !other.domain().has_finite_start() {696return Err(ChainError::SecondStartInfinite);697}698699let offset = self.sample_unchecked(self.domain().end())700- other.sample_unchecked(self.domain().start());701702Ok(ContinuationCurve {703first: self,704second: other,705offset,706_phantom: PhantomData,707})708}709710/// Extract an iterator over evenly-spaced samples from this curve.711///712/// # Errors713///714/// If `samples` is less than 2 or if this curve has unbounded domain, a [`ResamplingError`]715/// is returned.716fn samples(&self, samples: usize) -> Result<impl Iterator<Item = T>, ResamplingError> {717if samples < 2 {718return Err(ResamplingError::NotEnoughSamples(samples));719}720if !self.domain().is_bounded() {721return Err(ResamplingError::UnboundedDomain);722}723724// Unwrap on `spaced_points` always succeeds because its error conditions are handled725// above.726Ok(self727.domain()728.spaced_points(samples)729.unwrap()730.map(|t| self.sample_unchecked(t)))731}732733/// Borrow this curve rather than taking ownership of it. This is essentially an alias for a734/// prefix `&`; the point is that intermediate operations can be performed while retaining735/// access to the original curve.736///737/// # Example738/// ```739/// # use bevy_math::curve::*;740/// let my_curve = FunctionCurve::new(Interval::UNIT, |t| t * t + 1.0);741///742/// // Borrow `my_curve` long enough to resample a mapped version. Note that `map` takes743/// // ownership of its input.744/// let samples = my_curve.by_ref().map(|x| x * 2.0).resample_auto(100).unwrap();745///746/// // Do something else with `my_curve` since we retained ownership:747/// let new_curve = my_curve.reparametrize_linear(interval(-1.0, 1.0).unwrap()).unwrap();748/// ```749fn by_ref(&self) -> &Self {750self751}752753/// Flip this curve so that its tuple output is arranged the other way.754#[must_use]755fn flip<U, V>(self) -> impl Curve<(V, U)>756where757Self: CurveExt<(U, V)>,758{759self.map(|(u, v)| (v, u))760}761}762763impl<C, T> CurveExt<T> for C where C: Curve<T> {}764765/// Extension trait implemented by [curves], allowing access to generic resampling methods as766/// well as those based on [stable interpolation].767///768/// This trait is automatically implemented for all curves.769///770/// For more information, see the [module-level documentation].771///772/// [curves]: Curve773/// [stable interpolation]: crate::StableInterpolate774/// [module-level documentation]: self775#[cfg(feature = "alloc")]776pub trait CurveResampleExt<T>: Curve<T> {777/// Resample this [`Curve`] to produce a new one that is defined by interpolation over equally778/// spaced sample values, using the provided `interpolation` to interpolate between adjacent samples.779/// The curve is interpolated on `segments` segments between samples. For example, if `segments` is 1,780/// only the start and end points of the curve are used as samples; if `segments` is 2, a sample at781/// the midpoint is taken as well, and so on.782///783/// The interpolation takes two values by reference together with a scalar parameter and784/// produces an owned value. The expectation is that `interpolation(&x, &y, 0.0)` and785/// `interpolation(&x, &y, 1.0)` are equivalent to `x` and `y` respectively.786///787/// # Errors788///789/// If `segments` is zero or if this curve has unbounded domain, then a [`ResamplingError`] is790/// returned.791///792/// # Example793/// ```794/// # use bevy_math::*;795/// # use bevy_math::curve::*;796/// let quarter_rotation = FunctionCurve::new(interval(0.0, 90.0).unwrap(), |t| Rot2::degrees(t));797/// // A curve which only stores three data points and uses `nlerp` to interpolate them:798/// let resampled_rotation = quarter_rotation.resample(3, |x, y, t| x.nlerp(*y, t));799/// ```800fn resample<I>(801&self,802segments: usize,803interpolation: I,804) -> Result<SampleCurve<T, I>, ResamplingError>805where806I: Fn(&T, &T, f32) -> T,807{808let samples = self.samples(segments + 1)?.collect_vec();809Ok(SampleCurve {810core: EvenCore {811domain: self.domain(),812samples,813},814interpolation,815})816}817818/// Resample this [`Curve`] to produce a new one that is defined by interpolation over equally819/// spaced sample values, using [automatic interpolation] to interpolate between adjacent samples.820/// The curve is interpolated on `segments` segments between samples. For example, if `segments` is 1,821/// only the start and end points of the curve are used as samples; if `segments` is 2, a sample at822/// the midpoint is taken as well, and so on.823///824/// # Errors825///826/// If `segments` is zero or if this curve has unbounded domain, a [`ResamplingError`] is returned.827///828/// [automatic interpolation]: crate::common_traits::StableInterpolate829fn resample_auto(&self, segments: usize) -> Result<SampleAutoCurve<T>, ResamplingError>830where831T: StableInterpolate,832{833let samples = self.samples(segments + 1)?.collect_vec();834Ok(SampleAutoCurve {835core: EvenCore {836domain: self.domain(),837samples,838},839})840}841842/// Resample this [`Curve`] to produce a new one that is defined by interpolation over samples843/// taken at a given set of times. The given `interpolation` is used to interpolate adjacent844/// samples, and the `sample_times` are expected to contain at least two valid times within the845/// curve's domain interval.846///847/// Redundant sample times, non-finite sample times, and sample times outside of the domain848/// are filtered out. With an insufficient quantity of data, a [`ResamplingError`] is849/// returned.850///851/// The domain of the produced curve stretches between the first and last sample times of the852/// iterator.853///854/// The interpolation takes two values by reference together with a scalar parameter and855/// produces an owned value. The expectation is that `interpolation(&x, &y, 0.0)` and856/// `interpolation(&x, &y, 1.0)` are equivalent to `x` and `y` respectively.857///858/// # Errors859///860/// If `sample_times` doesn't contain at least two distinct times after filtering, a861/// [`ResamplingError`] is returned.862fn resample_uneven<I>(863&self,864sample_times: impl IntoIterator<Item = f32>,865interpolation: I,866) -> Result<UnevenSampleCurve<T, I>, ResamplingError>867where868I: Fn(&T, &T, f32) -> T,869{870let domain = self.domain();871let mut times = sample_times872.into_iter()873.filter(|t| t.is_finite() && domain.contains(*t))874.collect_vec();875times.sort_by(f32::total_cmp);876times.dedup();877if times.len() < 2 {878return Err(ResamplingError::NotEnoughSamples(times.len()));879}880let samples = times.iter().map(|t| self.sample_unchecked(*t)).collect();881Ok(UnevenSampleCurve {882core: UnevenCore { times, samples },883interpolation,884})885}886887/// Resample this [`Curve`] to produce a new one that is defined by [automatic interpolation] over888/// samples taken at the given set of times. The given `sample_times` are expected to contain at least889/// two valid times within the curve's domain interval.890///891/// Redundant sample times, non-finite sample times, and sample times outside of the domain892/// are simply filtered out. With an insufficient quantity of data, a [`ResamplingError`] is893/// returned.894///895/// The domain of the produced [`UnevenSampleAutoCurve`] stretches between the first and last896/// sample times of the iterator.897///898/// # Errors899///900/// If `sample_times` doesn't contain at least two distinct times after filtering, a901/// [`ResamplingError`] is returned.902///903/// [automatic interpolation]: crate::common_traits::StableInterpolate904fn resample_uneven_auto(905&self,906sample_times: impl IntoIterator<Item = f32>,907) -> Result<UnevenSampleAutoCurve<T>, ResamplingError>908where909T: StableInterpolate,910{911let domain = self.domain();912let mut times = sample_times913.into_iter()914.filter(|t| t.is_finite() && domain.contains(*t))915.collect_vec();916times.sort_by(f32::total_cmp);917times.dedup();918if times.len() < 2 {919return Err(ResamplingError::NotEnoughSamples(times.len()));920}921let samples = times.iter().map(|t| self.sample_unchecked(*t)).collect();922Ok(UnevenSampleAutoCurve {923core: UnevenCore { times, samples },924})925}926}927928#[cfg(feature = "alloc")]929impl<C, T> CurveResampleExt<T> for C where C: Curve<T> + ?Sized {}930931/// An error indicating that a linear reparameterization couldn't be performed because of932/// malformed inputs.933#[derive(Debug, Error)]934#[error("Could not build a linear function to reparametrize this curve")]935pub enum LinearReparamError {936/// The source curve that was to be reparametrized had unbounded domain.937#[error("This curve has unbounded domain")]938SourceCurveUnbounded,939940/// The target interval for reparameterization was unbounded.941#[error("The target interval for reparameterization is unbounded")]942TargetIntervalUnbounded,943}944945/// An error indicating that a reversion of a curve couldn't be performed because of946/// malformed inputs.947#[derive(Debug, Error)]948#[error("Could not reverse this curve")]949pub enum ReverseError {950/// The source curve that was to be reversed had unbounded domain end.951#[error("This curve has an unbounded domain end")]952SourceDomainEndInfinite,953}954955/// An error indicating that a repetition of a curve couldn't be performed because of malformed956/// inputs.957#[derive(Debug, Error)]958#[error("Could not repeat this curve")]959pub enum RepeatError {960/// The source curve that was to be repeated had unbounded domain.961#[error("This curve has an unbounded domain")]962SourceDomainUnbounded,963}964965/// An error indicating that a ping ponging of a curve couldn't be performed because of966/// malformed inputs.967#[derive(Debug, Error)]968#[error("Could not ping pong this curve")]969pub enum PingPongError {970/// The source curve that was to be ping ponged had unbounded domain end.971#[error("This curve has an unbounded domain end")]972SourceDomainEndInfinite,973}974975/// An error indicating that an end-to-end composition couldn't be performed because of976/// malformed inputs.977#[derive(Debug, Error)]978#[error("Could not compose these curves together")]979pub enum ChainError {980/// The right endpoint of the first curve was infinite.981#[error("The first curve's domain has an infinite end")]982FirstEndInfinite,983984/// The left endpoint of the second curve was infinite.985#[error("The second curve's domain has an infinite start")]986SecondStartInfinite,987}988989/// An error indicating that a resampling operation could not be performed because of990/// malformed inputs.991#[derive(Debug, Error)]992#[error("Could not resample from this curve because of bad inputs")]993pub enum ResamplingError {994/// This resampling operation was not provided with enough samples to have well-formed output.995#[error("Not enough unique samples to construct resampled curve")]996NotEnoughSamples(usize),997998/// This resampling operation failed because of an unbounded interval.999#[error("Could not resample because this curve has unbounded domain")]1000UnboundedDomain,1001}10021003#[cfg(test)]1004mod tests {1005use super::*;1006use crate::{ops, Quat};1007use alloc::vec::Vec;1008use approx::{assert_abs_diff_eq, AbsDiffEq};1009use core::f32::consts::TAU;1010use glam::*;10111012#[test]1013fn curve_can_be_made_into_an_object() {1014let curve = ConstantCurve::new(Interval::UNIT, 42.0);1015let curve: &dyn Curve<f64> = &curve;10161017assert_eq!(curve.sample(1.0), Some(42.0));1018assert_eq!(curve.sample(2.0), None);1019}10201021#[test]1022fn constant_curves() {1023let curve = ConstantCurve::new(Interval::EVERYWHERE, 5.0);1024assert!(curve.sample_unchecked(-35.0) == 5.0);10251026let curve = ConstantCurve::new(Interval::UNIT, true);1027assert!(curve.sample_unchecked(2.0));1028assert!(curve.sample(2.0).is_none());1029}10301031#[test]1032fn function_curves() {1033let curve = FunctionCurve::new(Interval::EVERYWHERE, |t| t * t);1034assert!(curve.sample_unchecked(2.0).abs_diff_eq(&4.0, f32::EPSILON));1035assert!(curve.sample_unchecked(-3.0).abs_diff_eq(&9.0, f32::EPSILON));10361037let curve = FunctionCurve::new(interval(0.0, f32::INFINITY).unwrap(), ops::log2);1038assert_eq!(curve.sample_unchecked(3.5), ops::log2(3.5));1039assert!(curve.sample_unchecked(-1.0).is_nan());1040assert!(curve.sample(-1.0).is_none());1041}10421043#[test]1044fn linear_curve() {1045let start = Vec2::ZERO;1046let end = Vec2::new(1.0, 2.0);1047let curve = EasingCurve::new(start, end, EaseFunction::Linear);10481049let mid = (start + end) / 2.0;10501051[(0.0, start), (0.5, mid), (1.0, end)]1052.into_iter()1053.for_each(|(t, x)| {1054assert!(curve.sample_unchecked(t).abs_diff_eq(x, f32::EPSILON));1055});1056}10571058#[test]1059fn easing_curves_step() {1060let start = Vec2::ZERO;1061let end = Vec2::new(1.0, 2.0);10621063let curve = EasingCurve::new(start, end, EaseFunction::Steps(4, JumpAt::End));1064[1065(0.0, start),1066(0.249, start),1067(0.250, Vec2::new(0.25, 0.5)),1068(0.499, Vec2::new(0.25, 0.5)),1069(0.500, Vec2::new(0.5, 1.0)),1070(0.749, Vec2::new(0.5, 1.0)),1071(0.750, Vec2::new(0.75, 1.5)),1072(1.0, end),1073]1074.into_iter()1075.for_each(|(t, x)| {1076assert!(curve.sample_unchecked(t).abs_diff_eq(x, f32::EPSILON));1077});1078}10791080#[test]1081fn easing_curves_quadratic() {1082let start = Vec2::ZERO;1083let end = Vec2::new(1.0, 2.0);10841085let curve = EasingCurve::new(start, end, EaseFunction::QuadraticIn);1086[1087(0.0, start),1088(0.25, Vec2::new(0.0625, 0.125)),1089(0.5, Vec2::new(0.25, 0.5)),1090(1.0, end),1091]1092.into_iter()1093.for_each(|(t, x)| {1094assert!(curve.sample_unchecked(t).abs_diff_eq(x, f32::EPSILON),);1095});1096}10971098#[expect(1099clippy::neg_multiply,1100reason = "Clippy doesn't like this, but it's correct"1101)]1102#[test]1103fn mapping() {1104let curve = FunctionCurve::new(Interval::EVERYWHERE, |t| t * 3.0 + 1.0);1105let mapped_curve = curve.map(|x| x / 7.0);1106assert_eq!(mapped_curve.sample_unchecked(3.5), (3.5 * 3.0 + 1.0) / 7.0);1107assert_eq!(1108mapped_curve.sample_unchecked(-1.0),1109(-1.0 * 3.0 + 1.0) / 7.01110);1111assert_eq!(mapped_curve.domain(), Interval::EVERYWHERE);11121113let curve = FunctionCurve::new(Interval::UNIT, |t| t * TAU);1114let mapped_curve = curve.map(Quat::from_rotation_z);1115assert_eq!(mapped_curve.sample_unchecked(0.0), Quat::IDENTITY);1116assert!(mapped_curve.sample_unchecked(1.0).is_near_identity());1117assert_eq!(mapped_curve.domain(), Interval::UNIT);1118}11191120#[test]1121fn reverse() {1122let curve = FunctionCurve::new(Interval::new(0.0, 1.0).unwrap(), |t| t * 3.0 + 1.0);1123let rev_curve = curve.reverse().unwrap();1124assert_eq!(rev_curve.sample(-0.1), None);1125assert_eq!(rev_curve.sample(0.0), Some(1.0 * 3.0 + 1.0));1126assert_eq!(rev_curve.sample(0.5), Some(0.5 * 3.0 + 1.0));1127assert_eq!(rev_curve.sample(1.0), Some(0.0 * 3.0 + 1.0));1128assert_eq!(rev_curve.sample(1.1), None);11291130let curve = FunctionCurve::new(Interval::new(-2.0, 1.0).unwrap(), |t| t * 3.0 + 1.0);1131let rev_curve = curve.reverse().unwrap();1132assert_eq!(rev_curve.sample(-2.1), None);1133assert_eq!(rev_curve.sample(-2.0), Some(1.0 * 3.0 + 1.0));1134assert_eq!(rev_curve.sample(-0.5), Some(-0.5 * 3.0 + 1.0));1135assert_eq!(rev_curve.sample(1.0), Some(-2.0 * 3.0 + 1.0));1136assert_eq!(rev_curve.sample(1.1), None);1137}11381139#[test]1140fn repeat() {1141let curve = FunctionCurve::new(Interval::new(0.0, 1.0).unwrap(), |t| t * 3.0 + 1.0);1142let repeat_curve = curve.by_ref().repeat(1).unwrap();1143assert_eq!(repeat_curve.sample(-0.1), None);1144assert_eq!(repeat_curve.sample(0.0), Some(0.0 * 3.0 + 1.0));1145assert_eq!(repeat_curve.sample(0.5), Some(0.5 * 3.0 + 1.0));1146assert_eq!(repeat_curve.sample(0.99), Some(0.99 * 3.0 + 1.0));1147assert_eq!(repeat_curve.sample(1.0), Some(1.0 * 3.0 + 1.0));1148assert_eq!(repeat_curve.sample(1.01), Some(0.01 * 3.0 + 1.0));1149assert_eq!(repeat_curve.sample(1.5), Some(0.5 * 3.0 + 1.0));1150assert_eq!(repeat_curve.sample(1.99), Some(0.99 * 3.0 + 1.0));1151assert_eq!(repeat_curve.sample(2.0), Some(1.0 * 3.0 + 1.0));1152assert_eq!(repeat_curve.sample(2.01), None);11531154let repeat_curve = curve.by_ref().repeat(3).unwrap();1155assert_eq!(repeat_curve.sample(2.0), Some(1.0 * 3.0 + 1.0));1156assert_eq!(repeat_curve.sample(3.0), Some(1.0 * 3.0 + 1.0));1157assert_eq!(repeat_curve.sample(4.0), Some(1.0 * 3.0 + 1.0));1158assert_eq!(repeat_curve.sample(5.0), None);11591160let repeat_curve = curve.by_ref().forever().unwrap();1161assert_eq!(repeat_curve.sample(-1.0), Some(1.0 * 3.0 + 1.0));1162assert_eq!(repeat_curve.sample(2.0), Some(1.0 * 3.0 + 1.0));1163assert_eq!(repeat_curve.sample(3.0), Some(1.0 * 3.0 + 1.0));1164assert_eq!(repeat_curve.sample(4.0), Some(1.0 * 3.0 + 1.0));1165assert_eq!(repeat_curve.sample(5.0), Some(1.0 * 3.0 + 1.0));1166}11671168#[test]1169fn ping_pong() {1170let curve = FunctionCurve::new(Interval::new(0.0, 1.0).unwrap(), |t| t * 3.0 + 1.0);1171let ping_pong_curve = curve.ping_pong().unwrap();1172assert_eq!(ping_pong_curve.sample(-0.1), None);1173assert_eq!(ping_pong_curve.sample(0.0), Some(0.0 * 3.0 + 1.0));1174assert_eq!(ping_pong_curve.sample(0.5), Some(0.5 * 3.0 + 1.0));1175assert_eq!(ping_pong_curve.sample(1.0), Some(1.0 * 3.0 + 1.0));1176assert_eq!(ping_pong_curve.sample(1.5), Some(0.5 * 3.0 + 1.0));1177assert_eq!(ping_pong_curve.sample(2.0), Some(0.0 * 3.0 + 1.0));1178assert_eq!(ping_pong_curve.sample(2.1), None);11791180let curve = FunctionCurve::new(Interval::new(-2.0, 2.0).unwrap(), |t| t * 3.0 + 1.0);1181let ping_pong_curve = curve.ping_pong().unwrap();1182assert_eq!(ping_pong_curve.sample(-2.1), None);1183assert_eq!(ping_pong_curve.sample(-2.0), Some(-2.0 * 3.0 + 1.0));1184assert_eq!(ping_pong_curve.sample(-0.5), Some(-0.5 * 3.0 + 1.0));1185assert_eq!(ping_pong_curve.sample(2.0), Some(2.0 * 3.0 + 1.0));1186assert_eq!(ping_pong_curve.sample(4.5), Some(-0.5 * 3.0 + 1.0));1187assert_eq!(ping_pong_curve.sample(6.0), Some(-2.0 * 3.0 + 1.0));1188assert_eq!(ping_pong_curve.sample(6.1), None);1189}11901191#[test]1192fn continue_chain() {1193let first = FunctionCurve::new(Interval::new(0.0, 1.0).unwrap(), |t| t * 3.0 + 1.0);1194let second = FunctionCurve::new(Interval::new(0.0, 1.0).unwrap(), |t| t * t);1195let c0_chain_curve = first.chain_continue(second).unwrap();1196assert_eq!(c0_chain_curve.sample(-0.1), None);1197assert_eq!(c0_chain_curve.sample(0.0), Some(0.0 * 3.0 + 1.0));1198assert_eq!(c0_chain_curve.sample(0.5), Some(0.5 * 3.0 + 1.0));1199assert_eq!(c0_chain_curve.sample(1.0), Some(1.0 * 3.0 + 1.0));1200assert_eq!(c0_chain_curve.sample(1.5), Some(1.0 * 3.0 + 1.0 + 0.25));1201assert_eq!(c0_chain_curve.sample(2.0), Some(1.0 * 3.0 + 1.0 + 1.0));1202assert_eq!(c0_chain_curve.sample(2.1), None);1203}12041205#[test]1206fn reparameterization() {1207let curve = FunctionCurve::new(interval(1.0, f32::INFINITY).unwrap(), ops::log2);1208let reparametrized_curve = curve1209.by_ref()1210.reparametrize(interval(0.0, f32::INFINITY).unwrap(), ops::exp2);1211assert_abs_diff_eq!(reparametrized_curve.sample_unchecked(3.5), 3.5);1212assert_abs_diff_eq!(reparametrized_curve.sample_unchecked(100.0), 100.0);1213assert_eq!(1214reparametrized_curve.domain(),1215interval(0.0, f32::INFINITY).unwrap()1216);12171218let reparametrized_curve = curve.by_ref().reparametrize(Interval::UNIT, |t| t + 1.0);1219assert_abs_diff_eq!(reparametrized_curve.sample_unchecked(0.0), 0.0);1220assert_abs_diff_eq!(reparametrized_curve.sample_unchecked(1.0), 1.0);1221assert_eq!(reparametrized_curve.domain(), Interval::UNIT);1222}12231224#[test]1225fn multiple_maps() {1226// Make sure these actually happen in the right order.1227let curve = FunctionCurve::new(Interval::UNIT, ops::exp2);1228let first_mapped = curve.map(ops::log2);1229let second_mapped = first_mapped.map(|x| x * -2.0);1230assert_abs_diff_eq!(second_mapped.sample_unchecked(0.0), 0.0);1231assert_abs_diff_eq!(second_mapped.sample_unchecked(0.5), -1.0);1232assert_abs_diff_eq!(second_mapped.sample_unchecked(1.0), -2.0);1233}12341235#[test]1236fn multiple_reparams() {1237// Make sure these happen in the right order too.1238let curve = FunctionCurve::new(Interval::UNIT, ops::exp2);1239let first_reparam = curve.reparametrize(interval(1.0, 2.0).unwrap(), ops::log2);1240let second_reparam = first_reparam.reparametrize(Interval::UNIT, |t| t + 1.0);1241assert_abs_diff_eq!(second_reparam.sample_unchecked(0.0), 1.0);1242assert_abs_diff_eq!(second_reparam.sample_unchecked(0.5), 1.5);1243assert_abs_diff_eq!(second_reparam.sample_unchecked(1.0), 2.0);1244}12451246#[test]1247fn resampling() {1248let curve = FunctionCurve::new(interval(1.0, 4.0).unwrap(), ops::log2);12491250// Need at least one segment to sample.1251let nice_try = curve.by_ref().resample_auto(0);1252assert!(nice_try.is_err());12531254// The values of a resampled curve should be very close at the sample points.1255// Because of denominators, it's not literally equal.1256// (This is a tradeoff against O(1) sampling.)1257let resampled_curve = curve.by_ref().resample_auto(100).unwrap();1258for test_pt in curve.domain().spaced_points(101).unwrap() {1259let expected = curve.sample_unchecked(test_pt);1260assert_abs_diff_eq!(1261resampled_curve.sample_unchecked(test_pt),1262expected,1263epsilon = 1e-61264);1265}12661267// Another example.1268let curve = FunctionCurve::new(interval(0.0, TAU).unwrap(), ops::cos);1269let resampled_curve = curve.by_ref().resample_auto(1000).unwrap();1270for test_pt in curve.domain().spaced_points(1001).unwrap() {1271let expected = curve.sample_unchecked(test_pt);1272assert_abs_diff_eq!(1273resampled_curve.sample_unchecked(test_pt),1274expected,1275epsilon = 1e-61276);1277}1278}12791280#[test]1281fn uneven_resampling() {1282let curve = FunctionCurve::new(interval(0.0, f32::INFINITY).unwrap(), ops::exp);12831284// Need at least two points to resample.1285let nice_try = curve.by_ref().resample_uneven_auto([1.0; 1]);1286assert!(nice_try.is_err());12871288// Uneven sampling should produce literal equality at the sample points.1289// (This is part of what you get in exchange for O(log(n)) sampling.)1290let sample_points = (0..100).map(|idx| idx as f32 * 0.1);1291let resampled_curve = curve.by_ref().resample_uneven_auto(sample_points).unwrap();1292for idx in 0..100 {1293let test_pt = idx as f32 * 0.1;1294let expected = curve.sample_unchecked(test_pt);1295assert_eq!(resampled_curve.sample_unchecked(test_pt), expected);1296}1297assert_abs_diff_eq!(resampled_curve.domain().start(), 0.0);1298assert_abs_diff_eq!(resampled_curve.domain().end(), 9.9, epsilon = 1e-6);12991300// Another example.1301let curve = FunctionCurve::new(interval(1.0, f32::INFINITY).unwrap(), ops::log2);1302let sample_points = (0..10).map(|idx| ops::exp2(idx as f32));1303let resampled_curve = curve.by_ref().resample_uneven_auto(sample_points).unwrap();1304for idx in 0..10 {1305let test_pt = ops::exp2(idx as f32);1306let expected = curve.sample_unchecked(test_pt);1307assert_eq!(resampled_curve.sample_unchecked(test_pt), expected);1308}1309assert_abs_diff_eq!(resampled_curve.domain().start(), 1.0);1310assert_abs_diff_eq!(resampled_curve.domain().end(), 512.0);1311}13121313#[test]1314fn sample_iterators() {1315let times = [-0.5, 0.0, 0.5, 1.0, 1.5];13161317let curve = FunctionCurve::new(Interval::EVERYWHERE, |t| t * 3.0 + 1.0);1318let samples = curve.sample_iter_unchecked(times).collect::<Vec<_>>();1319let [y0, y1, y2, y3, y4] = samples.try_into().unwrap();13201321assert_eq!(y0, -0.5 * 3.0 + 1.0);1322assert_eq!(y1, 0.0 * 3.0 + 1.0);1323assert_eq!(y2, 0.5 * 3.0 + 1.0);1324assert_eq!(y3, 1.0 * 3.0 + 1.0);1325assert_eq!(y4, 1.5 * 3.0 + 1.0);13261327let finite_curve = FunctionCurve::new(Interval::new(0.0, 1.0).unwrap(), |t| t * 3.0 + 1.0);1328let samples = finite_curve.sample_iter(times).collect::<Vec<_>>();1329let [y0, y1, y2, y3, y4] = samples.try_into().unwrap();13301331assert_eq!(y0, None);1332assert_eq!(y1, Some(0.0 * 3.0 + 1.0));1333assert_eq!(y2, Some(0.5 * 3.0 + 1.0));1334assert_eq!(y3, Some(1.0 * 3.0 + 1.0));1335assert_eq!(y4, None);13361337let samples = finite_curve.sample_iter_clamped(times).collect::<Vec<_>>();1338let [y0, y1, y2, y3, y4] = samples.try_into().unwrap();13391340assert_eq!(y0, 0.0 * 3.0 + 1.0);1341assert_eq!(y1, 0.0 * 3.0 + 1.0);1342assert_eq!(y2, 0.5 * 3.0 + 1.0);1343assert_eq!(y3, 1.0 * 3.0 + 1.0);1344assert_eq!(y4, 1.0 * 3.0 + 1.0);1345}1346}134713481349