Use normal constructors for EasingCurve, FunctionCurve, ConstantCurve (#16367)

# Objective

We currently use special "floating" constructors for `EasingCurve`,
`FunctionCurve`, and `ConstantCurve` (ex: `easing_curve`). This erases
the type being created (and in general "what is happening"
structurally), for very minimal ergonomics improvements. With rare
exceptions, we prefer normal `X::new()` constructors over floating `x()`
constructors in Bevy. I don't think this use case merits special casing
here.

## Solution

Add `EasingCurve::new()`, use normal constructors everywhere, and remove
the floating constructors.

I think this should land in 0.15 in the interest of not breaking people
later.
This commit is contained in:
Carter Anderson 2024-11-13 07:30:05 -08:00 committed by GitHub
parent 7b935c424b
commit 7477928f13
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 90 additions and 106 deletions

View file

@ -7,9 +7,9 @@
//! `Curve<Vec3>` that we want to use to animate something. That could be defined in //! `Curve<Vec3>` that we want to use to animate something. That could be defined in
//! a number of different ways, but let's imagine that we've defined it [using a function]: //! a number of different ways, but let's imagine that we've defined it [using a function]:
//! //!
//! # use bevy_math::curve::{Curve, Interval, function_curve}; //! # use bevy_math::curve::{Curve, Interval, FunctionCurve};
//! # use bevy_math::vec3; //! # use bevy_math::vec3;
//! let wobble_curve = function_curve( //! let wobble_curve = FunctionCurve::new(
//! Interval::UNIT, //! Interval::UNIT,
//! |t| { vec3(t.cos(), 0.0, 0.0) }, //! |t| { vec3(t.cos(), 0.0, 0.0) },
//! ); //! );
@ -25,10 +25,10 @@
//! the adaptor [`TranslationCurve`], which wraps any `Curve<Vec3>` and turns it into an //! the adaptor [`TranslationCurve`], which wraps any `Curve<Vec3>` and turns it into an
//! [`AnimationCurve`] that will use the given curve to animate the entity's translation: //! [`AnimationCurve`] that will use the given curve to animate the entity's translation:
//! //!
//! # use bevy_math::curve::{Curve, Interval, function_curve}; //! # use bevy_math::curve::{Curve, Interval, FunctionCurve};
//! # use bevy_math::vec3; //! # use bevy_math::vec3;
//! # use bevy_animation::animation_curves::*; //! # use bevy_animation::animation_curves::*;
//! # let wobble_curve = function_curve( //! # let wobble_curve = FunctionCurve::new(
//! # Interval::UNIT, //! # Interval::UNIT,
//! # |t| vec3(t.cos(), 0.0, 0.0) //! # |t| vec3(t.cos(), 0.0, 0.0)
//! # ); //! # );
@ -37,11 +37,11 @@
//! And finally, this `AnimationCurve` needs to be added to an [`AnimationClip`] in order to //! And finally, this `AnimationCurve` needs to be added to an [`AnimationClip`] in order to
//! actually animate something. This is what that looks like: //! actually animate something. This is what that looks like:
//! //!
//! # use bevy_math::curve::{Curve, Interval, function_curve}; //! # use bevy_math::curve::{Curve, Interval, FunctionCurve};
//! # use bevy_animation::{AnimationClip, AnimationTargetId, animation_curves::*}; //! # use bevy_animation::{AnimationClip, AnimationTargetId, animation_curves::*};
//! # use bevy_core::Name; //! # use bevy_core::Name;
//! # use bevy_math::vec3; //! # use bevy_math::vec3;
//! # let wobble_curve = function_curve( //! # let wobble_curve = FunctionCurve::new(
//! # Interval::UNIT, //! # Interval::UNIT,
//! # |t| { vec3(t.cos(), 0.0, 0.0) }, //! # |t| { vec3(t.cos(), 0.0, 0.0) },
//! # ); //! # );
@ -71,7 +71,7 @@
//! Animation of arbitrary components can be accomplished using [`AnimatableProperty`] in //! Animation of arbitrary components can be accomplished using [`AnimatableProperty`] in
//! conjunction with [`AnimatableCurve`]. See the documentation [there] for details. //! conjunction with [`AnimatableCurve`]. See the documentation [there] for details.
//! //!
//! [using a function]: bevy_math::curve::function_curve //! [using a function]: bevy_math::curve::FunctionCurve
//! [translation component of a `Transform`]: bevy_transform::prelude::Transform::translation //! [translation component of a `Transform`]: bevy_transform::prelude::Transform::translation
//! [`AnimationClip`]: crate::AnimationClip //! [`AnimationClip`]: crate::AnimationClip
//! [there]: AnimatableProperty //! [there]: AnimatableProperty

View file

@ -33,7 +33,7 @@ where
/// # use bevy_color::palettes::basic::{RED}; /// # use bevy_color::palettes::basic::{RED};
/// fn system(mut gizmos: Gizmos) { /// fn system(mut gizmos: Gizmos) {
/// let domain = Interval::UNIT; /// let domain = Interval::UNIT;
/// let curve = function_curve(domain, |t| Vec2::from(t.sin_cos())); /// let curve = FunctionCurve::new(domain, |t| Vec2::from(t.sin_cos()));
/// gizmos.curve_2d(curve, (0..=100).map(|n| n as f32 / 100.0), RED); /// gizmos.curve_2d(curve, (0..=100).map(|n| n as f32 / 100.0), RED);
/// } /// }
/// # bevy_ecs::system::assert_is_system(system); /// # bevy_ecs::system::assert_is_system(system);
@ -67,7 +67,7 @@ where
/// # use bevy_color::palettes::basic::{RED}; /// # use bevy_color::palettes::basic::{RED};
/// fn system(mut gizmos: Gizmos) { /// fn system(mut gizmos: Gizmos) {
/// let domain = Interval::UNIT; /// let domain = Interval::UNIT;
/// let curve = function_curve(domain, |t| { /// let curve = FunctionCurve::new(domain, |t| {
/// let (x,y) = t.sin_cos(); /// let (x,y) = t.sin_cos();
/// Vec3::new(x, y, t) /// Vec3::new(x, y, t)
/// }); /// });
@ -104,7 +104,7 @@ where
/// # use bevy_color::{Mix, palettes::basic::{GREEN, RED}}; /// # use bevy_color::{Mix, palettes::basic::{GREEN, RED}};
/// fn system(mut gizmos: Gizmos) { /// fn system(mut gizmos: Gizmos) {
/// let domain = Interval::UNIT; /// let domain = Interval::UNIT;
/// let curve = function_curve(domain, |t| Vec2::from(t.sin_cos())); /// let curve = FunctionCurve::new(domain, |t| Vec2::from(t.sin_cos()));
/// gizmos.curve_gradient_2d( /// gizmos.curve_gradient_2d(
/// curve, /// curve,
/// (0..=100).map(|n| n as f32 / 100.0) /// (0..=100).map(|n| n as f32 / 100.0)
@ -147,7 +147,7 @@ where
/// # use bevy_color::{Mix, palettes::basic::{GREEN, RED}}; /// # use bevy_color::{Mix, palettes::basic::{GREEN, RED}};
/// fn system(mut gizmos: Gizmos) { /// fn system(mut gizmos: Gizmos) {
/// let domain = Interval::UNIT; /// let domain = Interval::UNIT;
/// let curve = function_curve(domain, |t| { /// let curve = FunctionCurve::new(domain, |t| {
/// let (x,y) = t.sin_cos(); /// let (x,y) = t.sin_cos();
/// Vec3::new(x, y, t) /// Vec3::new(x, y, t)
/// }); /// });

View file

@ -275,7 +275,7 @@ async fn load_gltf<'a, 'b, 'c>(
#[cfg(feature = "bevy_animation")] #[cfg(feature = "bevy_animation")]
let (animations, named_animations, animation_roots) = { let (animations, named_animations, animation_roots) = {
use bevy_animation::{animation_curves::*, gltf_curves::*, VariableCurve}; use bevy_animation::{animation_curves::*, gltf_curves::*, VariableCurve};
use bevy_math::curve::{constant_curve, Interval, UnevenSampleAutoCurve}; use bevy_math::curve::{ConstantCurve, Interval, UnevenSampleAutoCurve};
use bevy_math::{Quat, Vec4}; use bevy_math::{Quat, Vec4};
use gltf::animation::util::ReadOutputs; use gltf::animation::util::ReadOutputs;
let mut animations = vec![]; let mut animations = vec![];
@ -313,7 +313,7 @@ async fn load_gltf<'a, 'b, 'c>(
let translations: Vec<Vec3> = tr.map(Vec3::from).collect(); let translations: Vec<Vec3> = tr.map(Vec3::from).collect();
if keyframe_timestamps.len() == 1 { if keyframe_timestamps.len() == 1 {
#[allow(clippy::unnecessary_map_on_constructor)] #[allow(clippy::unnecessary_map_on_constructor)]
Some(constant_curve(Interval::EVERYWHERE, translations[0])) Some(ConstantCurve::new(Interval::EVERYWHERE, translations[0]))
.map(TranslationCurve) .map(TranslationCurve)
.map(VariableCurve::new) .map(VariableCurve::new)
} else { } else {
@ -348,7 +348,7 @@ async fn load_gltf<'a, 'b, 'c>(
rots.into_f32().map(Quat::from_array).collect(); rots.into_f32().map(Quat::from_array).collect();
if keyframe_timestamps.len() == 1 { if keyframe_timestamps.len() == 1 {
#[allow(clippy::unnecessary_map_on_constructor)] #[allow(clippy::unnecessary_map_on_constructor)]
Some(constant_curve(Interval::EVERYWHERE, rotations[0])) Some(ConstantCurve::new(Interval::EVERYWHERE, rotations[0]))
.map(RotationCurve) .map(RotationCurve)
.map(VariableCurve::new) .map(VariableCurve::new)
} else { } else {
@ -385,7 +385,7 @@ async fn load_gltf<'a, 'b, 'c>(
let scales: Vec<Vec3> = scale.map(Vec3::from).collect(); let scales: Vec<Vec3> = scale.map(Vec3::from).collect();
if keyframe_timestamps.len() == 1 { if keyframe_timestamps.len() == 1 {
#[allow(clippy::unnecessary_map_on_constructor)] #[allow(clippy::unnecessary_map_on_constructor)]
Some(constant_curve(Interval::EVERYWHERE, scales[0])) Some(ConstantCurve::new(Interval::EVERYWHERE, scales[0]))
.map(ScaleCurve) .map(ScaleCurve)
.map(VariableCurve::new) .map(VariableCurve::new)
} else { } else {
@ -419,7 +419,7 @@ async fn load_gltf<'a, 'b, 'c>(
let weights: Vec<f32> = weights.into_f32().collect(); let weights: Vec<f32> = weights.into_f32().collect();
if keyframe_timestamps.len() == 1 { if keyframe_timestamps.len() == 1 {
#[allow(clippy::unnecessary_map_on_constructor)] #[allow(clippy::unnecessary_map_on_constructor)]
Some(constant_curve(Interval::EVERYWHERE, weights)) Some(ConstantCurve::new(Interval::EVERYWHERE, weights))
.map(WeightsCurve) .map(WeightsCurve)
.map(VariableCurve::new) .map(VariableCurve::new)
} else { } else {

View file

@ -3,9 +3,10 @@
//! //!
//! [easing functions]: EaseFunction //! [easing functions]: EaseFunction
use crate::{Dir2, Dir3, Dir3A, Quat, Rot2, VectorSpace}; use crate::{
curve::{FunctionCurve, Interval},
use super::{function_curve, Curve, Interval}; Curve, Dir2, Dir3, Dir3A, Quat, Rot2, VectorSpace,
};
// TODO: Think about merging `Ease` with `StableInterpolate` // TODO: Think about merging `Ease` with `StableInterpolate`
@ -28,13 +29,13 @@ pub trait Ease: Sized {
impl<V: VectorSpace> Ease for V { impl<V: VectorSpace> Ease for V {
fn interpolating_curve_unbounded(start: Self, end: Self) -> impl Curve<Self> { fn interpolating_curve_unbounded(start: Self, end: Self) -> impl Curve<Self> {
function_curve(Interval::EVERYWHERE, move |t| V::lerp(start, end, t)) FunctionCurve::new(Interval::EVERYWHERE, move |t| V::lerp(start, end, t))
} }
} }
impl Ease for Rot2 { impl Ease for Rot2 {
fn interpolating_curve_unbounded(start: Self, end: Self) -> impl Curve<Self> { fn interpolating_curve_unbounded(start: Self, end: Self) -> impl Curve<Self> {
function_curve(Interval::EVERYWHERE, move |t| Rot2::slerp(start, end, t)) FunctionCurve::new(Interval::EVERYWHERE, move |t| Rot2::slerp(start, end, t))
} }
} }
@ -44,7 +45,7 @@ impl Ease for Quat {
let end_adjusted = if dot < 0.0 { -end } else { end }; let end_adjusted = if dot < 0.0 { -end } else { end };
let difference = end_adjusted * start.inverse(); let difference = end_adjusted * start.inverse();
let (axis, angle) = difference.to_axis_angle(); let (axis, angle) = difference.to_axis_angle();
function_curve(Interval::EVERYWHERE, move |s| { FunctionCurve::new(Interval::EVERYWHERE, move |s| {
Quat::from_axis_angle(axis, angle * s) * start Quat::from_axis_angle(axis, angle * s) * start
}) })
} }
@ -52,7 +53,7 @@ impl Ease for Quat {
impl Ease for Dir2 { impl Ease for Dir2 {
fn interpolating_curve_unbounded(start: Self, end: Self) -> impl Curve<Self> { fn interpolating_curve_unbounded(start: Self, end: Self) -> impl Curve<Self> {
function_curve(Interval::EVERYWHERE, move |t| Dir2::slerp(start, end, t)) FunctionCurve::new(Interval::EVERYWHERE, move |t| Dir2::slerp(start, end, t))
} }
} }
@ -71,20 +72,6 @@ impl Ease for Dir3A {
} }
} }
/// Given a `start` and `end` value, create a curve parametrized over [the unit interval]
/// that connects them, using the given [ease function] to determine the form of the
/// curve in between.
///
/// [the unit interval]: Interval::UNIT
/// [ease function]: EaseFunction
pub fn easing_curve<T: Ease + Clone>(start: T, end: T, ease_fn: EaseFunction) -> EasingCurve<T> {
EasingCurve {
start,
end,
ease_fn,
}
}
/// A [`Curve`] that is defined by /// A [`Curve`] that is defined by
/// ///
/// - an initial `start` sample value at `t = 0` /// - an initial `start` sample value at `t = 0`
@ -104,6 +91,22 @@ pub struct EasingCurve<T> {
ease_fn: EaseFunction, ease_fn: EaseFunction,
} }
impl<T> EasingCurve<T> {
/// Given a `start` and `end` value, create a curve parametrized over [the unit interval]
/// that connects them, using the given [ease function] to determine the form of the
/// curve in between.
///
/// [the unit interval]: Interval::UNIT
/// [ease function]: EaseFunction
pub fn new(start: T, end: T, ease_fn: EaseFunction) -> Self {
Self {
start,
end,
ease_fn,
}
}
}
impl<T> Curve<T> for EasingCurve<T> impl<T> Curve<T> for EasingCurve<T>
where where
T: Ease + Clone, T: Ease + Clone,

View file

@ -56,13 +56,13 @@
//! # use bevy_math::vec3; //! # use bevy_math::vec3;
//! # use bevy_math::curve::*; //! # use bevy_math::curve::*;
//! // A sinusoid: //! // A sinusoid:
//! let sine_curve = function_curve(Interval::EVERYWHERE, f32::sin); //! let sine_curve = FunctionCurve::new(Interval::EVERYWHERE, f32::sin);
//! //!
//! // A sawtooth wave: //! // A sawtooth wave:
//! let sawtooth_curve = function_curve(Interval::EVERYWHERE, |t| t % 1.0); //! let sawtooth_curve = FunctionCurve::new(Interval::EVERYWHERE, |t| t % 1.0);
//! //!
//! // A helix: //! // A helix:
//! let helix_curve = function_curve(Interval::EVERYWHERE, |theta| vec3(theta.sin(), theta, theta.cos())); //! let helix_curve = FunctionCurve::new(Interval::EVERYWHERE, |theta| vec3(theta.sin(), theta, theta.cos()));
//! ``` //! ```
//! //!
//! Sample-interpolated curves commonly arises in both rasterization and in animation, and this library //! Sample-interpolated curves commonly arises in both rasterization and in animation, and this library
@ -127,7 +127,7 @@
//! # use bevy_math::{vec2, prelude::*}; //! # use bevy_math::{vec2, prelude::*};
//! # use std::f32::consts::TAU; //! # use std::f32::consts::TAU;
//! // Our original curve, which may look something like this: //! // Our original curve, which may look something like this:
//! let ellipse_curve = function_curve( //! let ellipse_curve = FunctionCurve::new(
//! interval(0.0, TAU).unwrap(), //! interval(0.0, TAU).unwrap(),
//! |t| vec2(t.cos(), t.sin() * 2.0) //! |t| vec2(t.cos(), t.sin() * 2.0)
//! ); //! );
@ -141,7 +141,7 @@
//! ```rust //! ```rust
//! # use bevy_math::{vec2, prelude::*}; //! # use bevy_math::{vec2, prelude::*};
//! # use std::f32::consts::TAU; //! # use std::f32::consts::TAU;
//! # let ellipse_curve = function_curve(interval(0.0, TAU).unwrap(), |t| vec2(t.cos(), t.sin() * 2.0)); //! # let ellipse_curve = FunctionCurve::new(interval(0.0, TAU).unwrap(), |t| vec2(t.cos(), t.sin() * 2.0));
//! # let ellipse_motion_curve = ellipse_curve.map(|pos| pos.extend(0.0)); //! # let ellipse_motion_curve = ellipse_curve.map(|pos| pos.extend(0.0));
//! // Change the domain to `[0, 1]` instead of `[0, TAU]`: //! // Change the domain to `[0, 1]` instead of `[0, TAU]`:
//! let final_curve = ellipse_motion_curve.reparametrize_linear(Interval::UNIT).unwrap(); //! let final_curve = ellipse_motion_curve.reparametrize_linear(Interval::UNIT).unwrap();
@ -155,7 +155,7 @@
//! // A line segment curve connecting two points in the plane: //! // A line segment curve connecting two points in the plane:
//! let start = vec2(-1.0, 1.0); //! let start = vec2(-1.0, 1.0);
//! let end = vec2(1.0, 1.0); //! let end = vec2(1.0, 1.0);
//! let segment = function_curve(Interval::UNIT, |t| start.lerp(end, t)); //! let segment = FunctionCurve::new(Interval::UNIT, |t| start.lerp(end, t));
//! //!
//! // Let's make a curve that goes back and forth along this line segment forever. //! // Let's make a curve that goes back and forth along this line segment forever.
//! // //! //
@ -177,13 +177,13 @@
//! # use bevy_math::{vec2, prelude::*}; //! # use bevy_math::{vec2, prelude::*};
//! # use std::f32::consts::PI; //! # use std::f32::consts::PI;
//! // A line segment connecting `(-1, 0)` to `(0, 0)`: //! // A line segment connecting `(-1, 0)` to `(0, 0)`:
//! let line_curve = function_curve( //! let line_curve = FunctionCurve::new(
//! Interval::UNIT, //! Interval::UNIT,
//! |t| vec2(-1.0, 0.0).lerp(vec2(0.0, 0.0), t) //! |t| vec2(-1.0, 0.0).lerp(vec2(0.0, 0.0), t)
//! ); //! );
//! //!
//! // A half-circle curve starting at `(0, 0)`: //! // A half-circle curve starting at `(0, 0)`:
//! let half_circle_curve = function_curve( //! let half_circle_curve = FunctionCurve::new(
//! interval(0.0, PI).unwrap(), //! interval(0.0, PI).unwrap(),
//! |t| vec2(t.cos() * -1.0 + 1.0, t.sin()) //! |t| vec2(t.cos() * -1.0 + 1.0, t.sin())
//! ); //! );
@ -198,10 +198,10 @@
//! ```rust //! ```rust
//! # use bevy_math::{vec2, prelude::*}; //! # use bevy_math::{vec2, prelude::*};
//! // Some entity's position in 2D: //! // Some entity's position in 2D:
//! let position_curve = function_curve(Interval::UNIT, |t| vec2(t.cos(), t.sin())); //! let position_curve = FunctionCurve::new(Interval::UNIT, |t| vec2(t.cos(), t.sin()));
//! //!
//! // The same entity's orientation, described as a rotation. (In this case it will be spinning.) //! // The same entity's orientation, described as a rotation. (In this case it will be spinning.)
//! let orientation_curve = function_curve(Interval::UNIT, |t| Rot2::radians(5.0 * t)); //! let orientation_curve = FunctionCurve::new(Interval::UNIT, |t| Rot2::radians(5.0 * t));
//! //!
//! // Both in one curve with `(Vec2, Rot2)` output: //! // Both in one curve with `(Vec2, Rot2)` output:
//! let position_and_orientation = position_curve.zip(orientation_curve).unwrap(); //! let position_and_orientation = position_curve.zip(orientation_curve).unwrap();
@ -220,7 +220,7 @@
//! ```rust //! ```rust
//! # use bevy_math::{vec2, prelude::*}; //! # use bevy_math::{vec2, prelude::*};
//! // A curve that is not easily transported because it relies on evaluating a function: //! // A curve that is not easily transported because it relies on evaluating a function:
//! let interesting_curve = function_curve(Interval::UNIT, |t| vec2(t * 3.0, t.exp())); //! let interesting_curve = FunctionCurve::new(Interval::UNIT, |t| vec2(t * 3.0, t.exp()));
//! //!
//! // A rasterized form of the preceding curve which is just a `SampleAutoCurve`. Inside, this //! // A rasterized form of the preceding curve which is just a `SampleAutoCurve`. Inside, this
//! // just stores an `Interval` along with a buffer of sample data, so it's easy to serialize //! // just stores an `Interval` along with a buffer of sample data, so it's easy to serialize
@ -250,7 +250,7 @@
//! Here is a demonstration: //! Here is a demonstration:
//! ```rust //! ```rust
//! # use bevy_math::prelude::*; //! # use bevy_math::prelude::*;
//! # let some_magic_constructor = || easing_curve(0.0, 1.0, EaseFunction::ElasticInOut).graph(); //! # let some_magic_constructor = || EasingCurve::new(0.0, 1.0, EaseFunction::ElasticInOut).graph();
//! //`my_curve` is obtained somehow. It is a `Curve<(f32, f32)>`. //! //`my_curve` is obtained somehow. It is a `Curve<(f32, f32)>`.
//! let my_curve = some_magic_constructor(); //! let my_curve = some_magic_constructor();
//! //!
@ -272,7 +272,7 @@
//! [changing parametrizations]: Curve::reparametrize //! [changing parametrizations]: Curve::reparametrize
//! [mapping output]: Curve::map //! [mapping output]: Curve::map
//! [rasterization]: Curve::resample //! [rasterization]: Curve::resample
//! [functions]: function_curve //! [functions]: FunctionCurve
//! [sample interpolation]: SampleCurve //! [sample interpolation]: SampleCurve
//! [splines]: crate::cubic_splines //! [splines]: crate::cubic_splines
//! [easings]: easing //! [easings]: easing
@ -282,7 +282,7 @@
//! [`zip`]: Curve::zip //! [`zip`]: Curve::zip
//! [`resample`]: Curve::resample //! [`resample`]: Curve::resample
//! //!
//! [^footnote]: In fact, universal as well, in some sense: if `curve` is any curve, then `function_curve //! [^footnote]: In fact, universal as well, in some sense: if `curve` is any curve, then `FunctionCurve::new
//! (curve.domain(), |t| curve.sample_unchecked(t))` is an equivalent function curve. //! (curve.domain(), |t| curve.sample_unchecked(t))` is an equivalent function curve.
pub mod adaptors; pub mod adaptors;
@ -414,7 +414,7 @@ pub trait Curve<T> {
/// factor rather than multiplying: /// factor rather than multiplying:
/// ``` /// ```
/// # use bevy_math::curve::*; /// # use bevy_math::curve::*;
/// let my_curve = constant_curve(Interval::UNIT, 1.0); /// let my_curve = ConstantCurve::new(Interval::UNIT, 1.0);
/// let scaled_curve = my_curve.reparametrize(interval(0.0, 2.0).unwrap(), |t| t / 2.0); /// let scaled_curve = my_curve.reparametrize(interval(0.0, 2.0).unwrap(), |t| t / 2.0);
/// ``` /// ```
/// This kind of linear remapping is provided by the convenience method /// This kind of linear remapping is provided by the convenience method
@ -425,12 +425,12 @@ pub trait Curve<T> {
/// // Reverse a curve: /// // Reverse a curve:
/// # use bevy_math::curve::*; /// # use bevy_math::curve::*;
/// # use bevy_math::vec2; /// # use bevy_math::vec2;
/// let my_curve = constant_curve(Interval::UNIT, 1.0); /// let my_curve = ConstantCurve::new(Interval::UNIT, 1.0);
/// let domain = my_curve.domain(); /// let domain = my_curve.domain();
/// let reversed_curve = my_curve.reparametrize(domain, |t| domain.end() - (t - domain.start())); /// let reversed_curve = my_curve.reparametrize(domain, |t| domain.end() - (t - domain.start()));
/// ///
/// // Take a segment of a curve: /// // Take a segment of a curve:
/// # let my_curve = constant_curve(Interval::UNIT, 1.0); /// # let my_curve = ConstantCurve::new(Interval::UNIT, 1.0);
/// let curve_segment = my_curve.reparametrize(interval(0.0, 0.5).unwrap(), |t| 0.5 + t); /// let curve_segment = my_curve.reparametrize(interval(0.0, 0.5).unwrap(), |t| 0.5 + t);
/// ``` /// ```
#[must_use] #[must_use]
@ -712,7 +712,7 @@ pub trait Curve<T> {
/// ``` /// ```
/// # use bevy_math::*; /// # use bevy_math::*;
/// # use bevy_math::curve::*; /// # use bevy_math::curve::*;
/// let quarter_rotation = function_curve(interval(0.0, 90.0).unwrap(), |t| Rot2::degrees(t)); /// let quarter_rotation = FunctionCurve::new(interval(0.0, 90.0).unwrap(), |t| Rot2::degrees(t));
/// // A curve which only stores three data points and uses `nlerp` to interpolate them: /// // A curve which only stores three data points and uses `nlerp` to interpolate them:
/// let resampled_rotation = quarter_rotation.resample(3, |x, y, t| x.nlerp(*y, t)); /// let resampled_rotation = quarter_rotation.resample(3, |x, y, t| x.nlerp(*y, t));
/// ``` /// ```
@ -863,7 +863,7 @@ pub trait Curve<T> {
/// # Example /// # Example
/// ``` /// ```
/// # use bevy_math::curve::*; /// # use bevy_math::curve::*;
/// let my_curve = function_curve(Interval::UNIT, |t| t * t + 1.0); /// let my_curve = FunctionCurve::new(Interval::UNIT, |t| t * t + 1.0);
/// ///
/// // Borrow `my_curve` long enough to resample a mapped version. Note that `map` takes /// // Borrow `my_curve` long enough to resample a mapped version. Note that `map` takes
/// // ownership of its input. /// // ownership of its input.
@ -976,27 +976,8 @@ pub enum ResamplingError {
UnboundedDomain, UnboundedDomain,
} }
/// Create a [`Curve`] that constantly takes the given `value` over the given `domain`.
pub fn constant_curve<T: Clone>(domain: Interval, value: T) -> ConstantCurve<T> {
ConstantCurve { domain, value }
}
/// Convert the given function `f` into a [`Curve`] with the given `domain`, sampled by
/// evaluating the function.
pub fn function_curve<T, F>(domain: Interval, f: F) -> FunctionCurve<T, F>
where
F: Fn(f32) -> T,
{
FunctionCurve {
domain,
f,
_phantom: PhantomData,
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::easing::*;
use super::*; use super::*;
use crate::{ops, Quat}; use crate::{ops, Quat};
use approx::{assert_abs_diff_eq, AbsDiffEq}; use approx::{assert_abs_diff_eq, AbsDiffEq};
@ -1005,7 +986,7 @@ mod tests {
#[test] #[test]
fn curve_can_be_made_into_an_object() { fn curve_can_be_made_into_an_object() {
let curve = constant_curve(Interval::UNIT, 42.0); let curve = ConstantCurve::new(Interval::UNIT, 42.0);
let curve: &dyn Curve<f64> = &curve; let curve: &dyn Curve<f64> = &curve;
assert_eq!(curve.sample(1.0), Some(42.0)); assert_eq!(curve.sample(1.0), Some(42.0));
@ -1014,21 +995,21 @@ mod tests {
#[test] #[test]
fn constant_curves() { fn constant_curves() {
let curve = constant_curve(Interval::EVERYWHERE, 5.0); let curve = ConstantCurve::new(Interval::EVERYWHERE, 5.0);
assert!(curve.sample_unchecked(-35.0) == 5.0); assert!(curve.sample_unchecked(-35.0) == 5.0);
let curve = constant_curve(Interval::UNIT, true); let curve = ConstantCurve::new(Interval::UNIT, true);
assert!(curve.sample_unchecked(2.0)); assert!(curve.sample_unchecked(2.0));
assert!(curve.sample(2.0).is_none()); assert!(curve.sample(2.0).is_none());
} }
#[test] #[test]
fn function_curves() { fn function_curves() {
let curve = function_curve(Interval::EVERYWHERE, |t| t * t); let curve = FunctionCurve::new(Interval::EVERYWHERE, |t| t * t);
assert!(curve.sample_unchecked(2.0).abs_diff_eq(&4.0, f32::EPSILON)); assert!(curve.sample_unchecked(2.0).abs_diff_eq(&4.0, f32::EPSILON));
assert!(curve.sample_unchecked(-3.0).abs_diff_eq(&9.0, f32::EPSILON)); assert!(curve.sample_unchecked(-3.0).abs_diff_eq(&9.0, f32::EPSILON));
let curve = function_curve(interval(0.0, f32::INFINITY).unwrap(), ops::log2); let curve = FunctionCurve::new(interval(0.0, f32::INFINITY).unwrap(), ops::log2);
assert_eq!(curve.sample_unchecked(3.5), ops::log2(3.5)); assert_eq!(curve.sample_unchecked(3.5), ops::log2(3.5));
assert!(curve.sample_unchecked(-1.0).is_nan()); assert!(curve.sample_unchecked(-1.0).is_nan());
assert!(curve.sample(-1.0).is_none()); assert!(curve.sample(-1.0).is_none());
@ -1038,7 +1019,7 @@ mod tests {
fn linear_curve() { fn linear_curve() {
let start = Vec2::ZERO; let start = Vec2::ZERO;
let end = Vec2::new(1.0, 2.0); let end = Vec2::new(1.0, 2.0);
let curve = easing_curve(start, end, EaseFunction::Linear); let curve = EasingCurve::new(start, end, EaseFunction::Linear);
let mid = (start + end) / 2.0; let mid = (start + end) / 2.0;
@ -1054,7 +1035,7 @@ mod tests {
let start = Vec2::ZERO; let start = Vec2::ZERO;
let end = Vec2::new(1.0, 2.0); let end = Vec2::new(1.0, 2.0);
let curve = easing_curve(start, end, EaseFunction::Steps(4)); let curve = EasingCurve::new(start, end, EaseFunction::Steps(4));
[ [
(0.0, start), (0.0, start),
(0.124, start), (0.124, start),
@ -1078,7 +1059,7 @@ mod tests {
let start = Vec2::ZERO; let start = Vec2::ZERO;
let end = Vec2::new(1.0, 2.0); let end = Vec2::new(1.0, 2.0);
let curve = easing_curve(start, end, EaseFunction::QuadraticIn); let curve = EasingCurve::new(start, end, EaseFunction::QuadraticIn);
[ [
(0.0, start), (0.0, start),
(0.25, Vec2::new(0.0625, 0.125)), (0.25, Vec2::new(0.0625, 0.125)),
@ -1093,7 +1074,7 @@ mod tests {
#[test] #[test]
fn mapping() { fn mapping() {
let curve = function_curve(Interval::EVERYWHERE, |t| t * 3.0 + 1.0); let curve = FunctionCurve::new(Interval::EVERYWHERE, |t| t * 3.0 + 1.0);
let mapped_curve = curve.map(|x| x / 7.0); let mapped_curve = curve.map(|x| x / 7.0);
assert_eq!(mapped_curve.sample_unchecked(3.5), (3.5 * 3.0 + 1.0) / 7.0); assert_eq!(mapped_curve.sample_unchecked(3.5), (3.5 * 3.0 + 1.0) / 7.0);
assert_eq!( assert_eq!(
@ -1102,7 +1083,7 @@ mod tests {
); );
assert_eq!(mapped_curve.domain(), Interval::EVERYWHERE); assert_eq!(mapped_curve.domain(), Interval::EVERYWHERE);
let curve = function_curve(Interval::UNIT, |t| t * TAU); let curve = FunctionCurve::new(Interval::UNIT, |t| t * TAU);
let mapped_curve = curve.map(Quat::from_rotation_z); let mapped_curve = curve.map(Quat::from_rotation_z);
assert_eq!(mapped_curve.sample_unchecked(0.0), Quat::IDENTITY); assert_eq!(mapped_curve.sample_unchecked(0.0), Quat::IDENTITY);
assert!(mapped_curve.sample_unchecked(1.0).is_near_identity()); assert!(mapped_curve.sample_unchecked(1.0).is_near_identity());
@ -1111,7 +1092,7 @@ mod tests {
#[test] #[test]
fn reverse() { fn reverse() {
let curve = function_curve(Interval::new(0.0, 1.0).unwrap(), |t| t * 3.0 + 1.0); let curve = FunctionCurve::new(Interval::new(0.0, 1.0).unwrap(), |t| t * 3.0 + 1.0);
let rev_curve = curve.reverse().unwrap(); let rev_curve = curve.reverse().unwrap();
assert_eq!(rev_curve.sample(-0.1), None); assert_eq!(rev_curve.sample(-0.1), None);
assert_eq!(rev_curve.sample(0.0), Some(1.0 * 3.0 + 1.0)); assert_eq!(rev_curve.sample(0.0), Some(1.0 * 3.0 + 1.0));
@ -1119,7 +1100,7 @@ mod tests {
assert_eq!(rev_curve.sample(1.0), Some(0.0 * 3.0 + 1.0)); assert_eq!(rev_curve.sample(1.0), Some(0.0 * 3.0 + 1.0));
assert_eq!(rev_curve.sample(1.1), None); assert_eq!(rev_curve.sample(1.1), None);
let curve = function_curve(Interval::new(-2.0, 1.0).unwrap(), |t| t * 3.0 + 1.0); let curve = FunctionCurve::new(Interval::new(-2.0, 1.0).unwrap(), |t| t * 3.0 + 1.0);
let rev_curve = curve.reverse().unwrap(); let rev_curve = curve.reverse().unwrap();
assert_eq!(rev_curve.sample(-2.1), None); assert_eq!(rev_curve.sample(-2.1), None);
assert_eq!(rev_curve.sample(-2.0), Some(1.0 * 3.0 + 1.0)); assert_eq!(rev_curve.sample(-2.0), Some(1.0 * 3.0 + 1.0));
@ -1130,7 +1111,7 @@ mod tests {
#[test] #[test]
fn repeat() { fn repeat() {
let curve = function_curve(Interval::new(0.0, 1.0).unwrap(), |t| t * 3.0 + 1.0); let curve = FunctionCurve::new(Interval::new(0.0, 1.0).unwrap(), |t| t * 3.0 + 1.0);
let repeat_curve = curve.by_ref().repeat(1).unwrap(); let repeat_curve = curve.by_ref().repeat(1).unwrap();
assert_eq!(repeat_curve.sample(-0.1), None); assert_eq!(repeat_curve.sample(-0.1), None);
assert_eq!(repeat_curve.sample(0.0), Some(0.0 * 3.0 + 1.0)); assert_eq!(repeat_curve.sample(0.0), Some(0.0 * 3.0 + 1.0));
@ -1159,7 +1140,7 @@ mod tests {
#[test] #[test]
fn ping_pong() { fn ping_pong() {
let curve = function_curve(Interval::new(0.0, 1.0).unwrap(), |t| t * 3.0 + 1.0); let curve = FunctionCurve::new(Interval::new(0.0, 1.0).unwrap(), |t| t * 3.0 + 1.0);
let ping_pong_curve = curve.ping_pong().unwrap(); let ping_pong_curve = curve.ping_pong().unwrap();
assert_eq!(ping_pong_curve.sample(-0.1), None); assert_eq!(ping_pong_curve.sample(-0.1), None);
assert_eq!(ping_pong_curve.sample(0.0), Some(0.0 * 3.0 + 1.0)); assert_eq!(ping_pong_curve.sample(0.0), Some(0.0 * 3.0 + 1.0));
@ -1169,7 +1150,7 @@ mod tests {
assert_eq!(ping_pong_curve.sample(2.0), Some(0.0 * 3.0 + 1.0)); assert_eq!(ping_pong_curve.sample(2.0), Some(0.0 * 3.0 + 1.0));
assert_eq!(ping_pong_curve.sample(2.1), None); assert_eq!(ping_pong_curve.sample(2.1), None);
let curve = function_curve(Interval::new(-2.0, 2.0).unwrap(), |t| t * 3.0 + 1.0); let curve = FunctionCurve::new(Interval::new(-2.0, 2.0).unwrap(), |t| t * 3.0 + 1.0);
let ping_pong_curve = curve.ping_pong().unwrap(); let ping_pong_curve = curve.ping_pong().unwrap();
assert_eq!(ping_pong_curve.sample(-2.1), None); assert_eq!(ping_pong_curve.sample(-2.1), None);
assert_eq!(ping_pong_curve.sample(-2.0), Some(-2.0 * 3.0 + 1.0)); assert_eq!(ping_pong_curve.sample(-2.0), Some(-2.0 * 3.0 + 1.0));
@ -1182,8 +1163,8 @@ mod tests {
#[test] #[test]
fn continue_chain() { fn continue_chain() {
let first = function_curve(Interval::new(0.0, 1.0).unwrap(), |t| t * 3.0 + 1.0); let first = FunctionCurve::new(Interval::new(0.0, 1.0).unwrap(), |t| t * 3.0 + 1.0);
let second = function_curve(Interval::new(0.0, 1.0).unwrap(), |t| t * t); let second = FunctionCurve::new(Interval::new(0.0, 1.0).unwrap(), |t| t * t);
let c0_chain_curve = first.chain_continue(second).unwrap(); let c0_chain_curve = first.chain_continue(second).unwrap();
assert_eq!(c0_chain_curve.sample(-0.1), None); assert_eq!(c0_chain_curve.sample(-0.1), None);
assert_eq!(c0_chain_curve.sample(0.0), Some(0.0 * 3.0 + 1.0)); assert_eq!(c0_chain_curve.sample(0.0), Some(0.0 * 3.0 + 1.0));
@ -1196,7 +1177,7 @@ mod tests {
#[test] #[test]
fn reparameterization() { fn reparameterization() {
let curve = function_curve(interval(1.0, f32::INFINITY).unwrap(), ops::log2); let curve = FunctionCurve::new(interval(1.0, f32::INFINITY).unwrap(), ops::log2);
let reparametrized_curve = curve let reparametrized_curve = curve
.by_ref() .by_ref()
.reparametrize(interval(0.0, f32::INFINITY).unwrap(), ops::exp2); .reparametrize(interval(0.0, f32::INFINITY).unwrap(), ops::exp2);
@ -1216,7 +1197,7 @@ mod tests {
#[test] #[test]
fn multiple_maps() { fn multiple_maps() {
// Make sure these actually happen in the right order. // Make sure these actually happen in the right order.
let curve = function_curve(Interval::UNIT, ops::exp2); let curve = FunctionCurve::new(Interval::UNIT, ops::exp2);
let first_mapped = curve.map(ops::log2); let first_mapped = curve.map(ops::log2);
let second_mapped = first_mapped.map(|x| x * -2.0); let second_mapped = first_mapped.map(|x| x * -2.0);
assert_abs_diff_eq!(second_mapped.sample_unchecked(0.0), 0.0); assert_abs_diff_eq!(second_mapped.sample_unchecked(0.0), 0.0);
@ -1227,7 +1208,7 @@ mod tests {
#[test] #[test]
fn multiple_reparams() { fn multiple_reparams() {
// Make sure these happen in the right order too. // Make sure these happen in the right order too.
let curve = function_curve(Interval::UNIT, ops::exp2); let curve = FunctionCurve::new(Interval::UNIT, ops::exp2);
let first_reparam = curve.reparametrize(interval(1.0, 2.0).unwrap(), ops::log2); let first_reparam = curve.reparametrize(interval(1.0, 2.0).unwrap(), ops::log2);
let second_reparam = first_reparam.reparametrize(Interval::UNIT, |t| t + 1.0); let second_reparam = first_reparam.reparametrize(Interval::UNIT, |t| t + 1.0);
assert_abs_diff_eq!(second_reparam.sample_unchecked(0.0), 1.0); assert_abs_diff_eq!(second_reparam.sample_unchecked(0.0), 1.0);
@ -1237,7 +1218,7 @@ mod tests {
#[test] #[test]
fn resampling() { fn resampling() {
let curve = function_curve(interval(1.0, 4.0).unwrap(), ops::log2); let curve = FunctionCurve::new(interval(1.0, 4.0).unwrap(), ops::log2);
// Need at least one segment to sample. // Need at least one segment to sample.
let nice_try = curve.by_ref().resample_auto(0); let nice_try = curve.by_ref().resample_auto(0);
@ -1257,7 +1238,7 @@ mod tests {
} }
// Another example. // Another example.
let curve = function_curve(interval(0.0, TAU).unwrap(), ops::cos); let curve = FunctionCurve::new(interval(0.0, TAU).unwrap(), ops::cos);
let resampled_curve = curve.by_ref().resample_auto(1000).unwrap(); let resampled_curve = curve.by_ref().resample_auto(1000).unwrap();
for test_pt in curve.domain().spaced_points(1001).unwrap() { for test_pt in curve.domain().spaced_points(1001).unwrap() {
let expected = curve.sample_unchecked(test_pt); let expected = curve.sample_unchecked(test_pt);
@ -1271,7 +1252,7 @@ mod tests {
#[test] #[test]
fn uneven_resampling() { fn uneven_resampling() {
let curve = function_curve(interval(0.0, f32::INFINITY).unwrap(), ops::exp); let curve = FunctionCurve::new(interval(0.0, f32::INFINITY).unwrap(), ops::exp);
// Need at least two points to resample. // Need at least two points to resample.
let nice_try = curve.by_ref().resample_uneven_auto([1.0; 1]); let nice_try = curve.by_ref().resample_uneven_auto([1.0; 1]);
@ -1290,7 +1271,7 @@ mod tests {
assert_abs_diff_eq!(resampled_curve.domain().end(), 9.9, epsilon = 1e-6); assert_abs_diff_eq!(resampled_curve.domain().end(), 9.9, epsilon = 1e-6);
// Another example. // Another example.
let curve = function_curve(interval(1.0, f32::INFINITY).unwrap(), ops::log2); let curve = FunctionCurve::new(interval(1.0, f32::INFINITY).unwrap(), ops::log2);
let sample_points = (0..10).map(|idx| ops::exp2(idx as f32)); let sample_points = (0..10).map(|idx| ops::exp2(idx as f32));
let resampled_curve = curve.by_ref().resample_uneven_auto(sample_points).unwrap(); let resampled_curve = curve.by_ref().resample_uneven_auto(sample_points).unwrap();
for idx in 0..10 { for idx in 0..10 {
@ -1306,7 +1287,7 @@ mod tests {
fn sample_iterators() { fn sample_iterators() {
let times = [-0.5, 0.0, 0.5, 1.0, 1.5]; let times = [-0.5, 0.0, 0.5, 1.0, 1.5];
let curve = function_curve(Interval::EVERYWHERE, |t| t * 3.0 + 1.0); let curve = FunctionCurve::new(Interval::EVERYWHERE, |t| t * 3.0 + 1.0);
let samples = curve.sample_iter_unchecked(times).collect::<Vec<_>>(); let samples = curve.sample_iter_unchecked(times).collect::<Vec<_>>();
let [y0, y1, y2, y3, y4] = samples.try_into().unwrap(); let [y0, y1, y2, y3, y4] = samples.try_into().unwrap();
@ -1316,7 +1297,7 @@ mod tests {
assert_eq!(y3, 1.0 * 3.0 + 1.0); assert_eq!(y3, 1.0 * 3.0 + 1.0);
assert_eq!(y4, 1.5 * 3.0 + 1.0); assert_eq!(y4, 1.5 * 3.0 + 1.0);
let finite_curve = function_curve(Interval::new(0.0, 1.0).unwrap(), |t| t * 3.0 + 1.0); let finite_curve = FunctionCurve::new(Interval::new(0.0, 1.0).unwrap(), |t| t * 3.0 + 1.0);
let samples = finite_curve.sample_iter(times).collect::<Vec<_>>(); let samples = finite_curve.sample_iter(times).collect::<Vec<_>>();
let [y0, y1, y2, y3, y4] = samples.try_into().unwrap(); let [y0, y1, y2, y3, y4] = samples.try_into().unwrap();

View file

@ -106,7 +106,7 @@ impl AnimationInfo {
// The easing curve is parametrized over [0, 1], so we reparametrize it and // The easing curve is parametrized over [0, 1], so we reparametrize it and
// then ping-pong, which makes it spend another 3 seconds on the return journey. // then ping-pong, which makes it spend another 3 seconds on the return journey.
let translation_curve = easing_curve( let translation_curve = EasingCurve::new(
vec3(-6., 2., 0.), vec3(-6., 2., 0.),
vec3(6., 2., 0.), vec3(6., 2., 0.),
EaseFunction::CubicInOut, EaseFunction::CubicInOut,
@ -119,7 +119,7 @@ impl AnimationInfo {
// Something similar for rotation. The repetition here is an illusion caused // Something similar for rotation. The repetition here is an illusion caused
// by the symmetry of the cube; it rotates on the forward journey and never // by the symmetry of the cube; it rotates on the forward journey and never
// rotates back. // rotates back.
let rotation_curve = easing_curve( let rotation_curve = EasingCurve::new(
Quat::IDENTITY, Quat::IDENTITY,
Quat::from_rotation_y(FRAC_PI_2), Quat::from_rotation_y(FRAC_PI_2),
EaseFunction::ElasticInOut, EaseFunction::ElasticInOut,

View file

@ -138,7 +138,7 @@ fn display_curves(
); );
// Draw the curve // Draw the curve
let f = easing_curve(0.0, 1.0, *function); let f = EasingCurve::new(0.0, 1.0, *function);
let drawn_curve = f.by_ref().graph().map(|(x, y)| { let drawn_curve = f.by_ref().graph().map(|(x, y)| {
Vec2::new( Vec2::new(
x * SIZE_PER_FUNCTION + transform.translation.x, x * SIZE_PER_FUNCTION + transform.translation.x,

View file

@ -69,7 +69,7 @@ fn draw_example_collection(
gizmos.cross_2d(Vec2::new(-160., 120.), 12., FUCHSIA); gizmos.cross_2d(Vec2::new(-160., 120.), 12., FUCHSIA);
let domain = Interval::EVERYWHERE; let domain = Interval::EVERYWHERE;
let curve = function_curve(domain, |t| Vec2::new(t, ops::sin(t / 25.0) * 100.0)); let curve = FunctionCurve::new(domain, |t| Vec2::new(t, ops::sin(t / 25.0) * 100.0));
let resolution = ((ops::sin(time.elapsed_secs()) + 1.0) * 50.0) as usize; let resolution = ((ops::sin(time.elapsed_secs()) + 1.0) * 50.0) as usize;
let times_and_colors = (0..=resolution) let times_and_colors = (0..=resolution)
.map(|n| n as f32 / resolution as f32) .map(|n| n as f32 / resolution as f32)

View file

@ -122,7 +122,7 @@ fn draw_example_collection(
gizmos.cross(Vec3::new(-1., 1., 1.), 0.5, FUCHSIA); gizmos.cross(Vec3::new(-1., 1., 1.), 0.5, FUCHSIA);
let domain = Interval::EVERYWHERE; let domain = Interval::EVERYWHERE;
let curve = function_curve(domain, |t| { let curve = FunctionCurve::new(domain, |t| {
(Vec2::from(ops::sin_cos(t * 10.0))).extend(t - 6.0) (Vec2::from(ops::sin_cos(t * 10.0))).extend(t - 6.0)
}); });
let resolution = ((ops::sin(time.elapsed_secs()) + 1.0) * 100.0) as usize; let resolution = ((ops::sin(time.elapsed_secs()) + 1.0) * 100.0) as usize;