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