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}