use core::ops::{Add, Mul, RangeInclusive, Sub}; #[cfg(any(feature = "std", feature = "libm"))] use num_traits::Float; use num_traits::{float::FloatCore, Num, One, Zero}; use crate::{constant, fun, id}; /// A `Fun` represents anything that maps from some type `T` to another /// type `V`. /// /// `T` usually stands for time and `V` for some value that is parameterized by /// time. /// /// ## Implementation details /// The only reason that we define this trait instead of just using `Fn(T) -> V` /// is so that the library works in stable rust. Having this type allows us to /// implement e.g. `std::ops::Add` for [`Anim`](struct.Anim.html) where /// `F: Fun`. Without this trait, it becomes difficult (impossible?) to provide /// a name for `Add::Output`, unless you have the unstable feature /// `type_alias_impl_trait` or `fn_traits`. /// /// In contrast to `std::ops::FnOnce`, both input _and_ output are associated /// types of `Fun`. The main reason is that this makes types smaller for the /// user of the library. I have not observed any downsides to this yet. pub trait Fun { /// The function's input type. Usually time. type T; /// The function's output type. type V; /// Evaluate the function at time `t`. fn eval(&self, t: Self::T) -> Self::V; } impl<'a, F> Fun for &'a F where F: Fun, { type T = F::T; type V = F::V; fn eval(&self, t: Self::T) -> Self::V { (*self).eval(t) } } /// `Anim` is the main type provided by pareen. It is a wrapper around any type /// implementing [`Fun`](trait.Fun.html). /// /// `Anim` provides methods that transform or compose animations, allowing /// complex animations to be created out of simple pieces. #[derive(Clone, Debug)] pub struct Anim(pub F); impl Anim where F: Fun, { /// Evaluate the animation at time `t`. pub fn eval(&self, t: F::T) -> F::V { self.0.eval(t) } /// Transform an animation so that it applies a given function to its /// values. /// /// # Example /// /// Turn `(2.0 * t)` into `(2.0 * t).sqrt() + 2.0 * t`: /// ``` /// # use assert_approx_eq::assert_approx_eq; /// let anim = pareen::prop(2.0f32).map(|value| value.sqrt() + value); /// /// assert_approx_eq!(anim.eval(1.0), 2.0f32.sqrt() + 2.0); /// ``` pub fn map(self, f: impl Fn(F::V) -> W) -> Anim> { self.map_anim(fun(f)) } /// Transform an animation so that it modifies time according to the given /// function before evaluating the animation. /// /// # Example /// Run an animation two times slower: /// ``` /// let anim = pareen::cubic(&[1.0, 1.0, 1.0, 1.0]); /// let slower_anim = anim.map_time(|t: f32| t / 2.0); /// ``` pub fn map_time(self, f: impl Fn(S) -> F::T) -> Anim> { fun(f).map_anim(self) } /// Converts from `Anim` to `Anim<&F>`. pub fn as_ref(&self) -> Anim<&F> { Anim(&self.0) } pub fn map_anim(self, anim: A) -> Anim> where G: Fun, A: Into>, { // Nested closures result in exponential compilation time increase, and we // expect map_anim to be used often. Thus, we avoid using `pareen::fun` here. // For reference: https://github.com/rust-lang/rust/issues/72408 Anim(MapClosure(self.0, anim.into().0)) } pub fn map_time_anim(self, anim: A) -> Anim> where G: Fun, A: Into>, { anim.into().map_anim(self) } pub fn into_fn(self) -> impl Fn(F::T) -> F::V { move |t| self.eval(t) } } impl Anim where F: Fun, F::T: Clone + Mul, { pub fn scale_time(self, t_scale: F::T) -> Anim> { self.map_time(move |t| t * t_scale.clone()) } } impl Fun for Option where F: Fun, { type T = F::T; type V = Option; fn eval(&self, t: F::T) -> Option { self.as_ref().map(|f| f.eval(t)) } } #[doc(hidden)] #[derive(Debug, Clone)] pub struct MapClosure(F, G); impl Fun for MapClosure where F: Fun, G: Fun, { type T = F::T; type V = G::V; fn eval(&self, t: F::T) -> G::V { self.1.eval(self.0.eval(t)) } } impl Anim where F: Fun, { pub fn fst(self) -> Anim> { self.map(|(x, _)| x) } pub fn snd(self) -> Anim> { self.map(|(_, y)| y) } } impl Anim where F: Fun, F::T: Clone, { /// Combine two animations into one, yielding an animation having pairs as /// the values. pub fn zip(self, other: A) -> Anim> where G: Fun, A: Into>, { // Nested closures result in exponential compilation time increase, and we // expect zip to be used frequently. Thus, we avoid using `pareen::fun` here. // For reference: https://github.com/rust-lang/rust/issues/72408 Anim(ZipClosure(self.0, other.into().0)) } pub fn bind(self, f: impl Fn(F::V) -> Anim) -> Anim> where G: Fun, { fun(move |t: F::T| f(self.eval(t.clone())).eval(t)) } } impl<'a, T, V, F> Anim where V: 'a + Copy, F: Fun + 'a, { pub fn copied(self) -> Anim + 'a> { self.map(|x| *x) } } impl<'a, T, V, F> Anim where V: 'a + Clone, F: Fun + 'a, { pub fn cloned(self) -> Anim + 'a> { self.map(|x| x.clone()) } } #[doc(hidden)] #[derive(Debug, Clone)] pub struct ZipClosure(F, G); impl Fun for ZipClosure where F: Fun, F::T: Clone, G: Fun, { type T = F::T; type V = (F::V, G::V); fn eval(&self, t: F::T) -> Self::V { (self.0.eval(t.clone()), self.1.eval(t)) } } impl Anim where F: Fun, F::T: Copy + Sub, { /// Shift an animation in time, so that it is moved to the right by `t_delay`. pub fn shift_time(self, t_delay: F::T) -> Anim> { (id::() - t_delay).map_anim(self) } } impl Anim where F: Fun, F::T: Clone + PartialOrd, { /// Concatenate `self` with another animation in time, using `self` until /// time `self_end` (non-inclusive), and then switching to `next`. /// /// # Examples /// Switch from one constant value to another: /// ``` /// # use assert_approx_eq::assert_approx_eq; /// let anim = pareen::constant(1.0f32).switch(0.5f32, 2.0); /// /// assert_approx_eq!(anim.eval(0.0), 1.0); /// assert_approx_eq!(anim.eval(0.5), 2.0); /// assert_approx_eq!(anim.eval(42.0), 2.0); /// ``` /// /// Piecewise combinations of functions: /// ``` /// let cubic_1 = pareen::cubic(&[4.4034, 0.0, -4.5455e-2, 0.0]); /// let cubic_2 = pareen::cubic(&[-1.2642e1, 2.0455e1, -8.1364, 1.0909]); /// let cubic_3 = pareen::cubic(&[1.6477e1, -4.9432e1, 4.7773e1, -1.3818e1]); /// /// // Use cubic_1 for [0.0, 0.4), cubic_2 for [0.4, 0.8) and /// // cubic_3 for [0.8, ..). /// let anim = cubic_1.switch(0.4, cubic_2).switch(0.8, cubic_3); /// ``` pub fn switch(self, self_end: F::T, next: A) -> Anim> where G: Fun, A: Into>, { // Nested closures result in exponential compilation time increase, and we // expect switch to be used frequently. Thus, we avoid using `pareen::fun` here. // For reference: https://github.com/rust-lang/rust/issues/72408 cond(switch_cond(self_end), self, next) } /// Play `self` in time range `range`, and `surround` outside of the time range. /// /// # Examples /// ``` /// # use assert_approx_eq::assert_approx_eq; /// let anim = pareen::constant(10.0f32).surround(2.0..=5.0, 20.0); /// /// assert_approx_eq!(anim.eval(0.0), 20.0); /// assert_approx_eq!(anim.eval(2.0), 10.0); /// assert_approx_eq!(anim.eval(4.0), 10.0); /// assert_approx_eq!(anim.eval(5.0), 10.0); /// assert_approx_eq!(anim.eval(6.0), 20.0); /// ``` pub fn surround( self, range: RangeInclusive, surround: A, ) -> Anim> where G: Fun, A: Into>, { // Nested closures result in exponential compilation time increase, and we // expect surround to be used frequently. Thus, we avoid using `pareen::fun` here. // For reference: https://github.com/rust-lang/rust/issues/72408 cond(surround_cond(range), self, surround) } } fn switch_cond(self_end: T) -> Anim> { fun(move |t| t < self_end) } fn surround_cond(range: RangeInclusive) -> Anim> { fun(move |t| range.contains(&t)) } impl Anim where F: Fun, F::T: Clone + PartialOrd, F::V: Clone, { /// Play `self` until time `self_end`, then always return the value of /// `self` at time `self_end`. pub fn hold(self, self_end: F::T) -> Anim> { let end_value = self.eval(self_end.clone()); self.switch(self_end, constant(end_value)) } } impl Anim where F: Fun, F::T: Copy + PartialOrd + Sub, { /// Play two animations in sequence, first playing `self` until time /// `self_end` (non-inclusive), and then switching to `next`. Note that /// `next` will see time starting at zero once it plays. /// /// # Example /// Stay at value `5.0` for ten seconds, then increase value proportionally: /// ``` /// # use assert_approx_eq::assert_approx_eq; /// let anim_1 = pareen::constant(5.0f32); /// let anim_2 = pareen::prop(2.0f32) + 5.0; /// let anim = anim_1.seq(10.0, anim_2); /// /// assert_approx_eq!(anim.eval(0.0), 5.0); /// assert_approx_eq!(anim.eval(10.0), 5.0); /// assert_approx_eq!(anim.eval(11.0), 7.0); /// ``` pub fn seq(self, self_end: F::T, next: A) -> Anim> where G: Fun, A: Into>, { self.switch(self_end.clone(), next.into().shift_time(self_end)) } pub fn seq_continue( self, self_end: F::T, next_fn: H, ) -> Anim> where G: Fun, A: Into>, H: Fn(F::V) -> A, { let next = next_fn(self.eval(self_end.clone())).into(); self.seq(self_end, next) } } impl Anim where F: Fun, F::T: Clone + Sub, { /// Play an animation backwards, starting at time `end`. /// /// # Example /// /// ``` /// # use assert_approx_eq::assert_approx_eq; /// let anim = pareen::prop(2.0f32).backwards(1.0); /// /// assert_approx_eq!(anim.eval(0.0f32), 2.0); /// assert_approx_eq!(anim.eval(1.0f32), 0.0); /// ``` pub fn backwards(self, end: F::T) -> Anim> { (constant(end) - id()).map_anim(self) } } impl Anim where F: Fun, F::T: Copy, F::V: Copy + Num, { /// Given animation values in `[0.0 .. 1.0]`, this function transforms the /// values so that they are in `[min .. max]`. /// /// # Example /// ``` /// # use assert_approx_eq::assert_approx_eq; /// let min = -3.0f32; /// let max = 10.0; /// let anim = pareen::id().scale_min_max(min, max); /// /// assert_approx_eq!(anim.eval(0.0f32), min); /// assert_approx_eq!(anim.eval(1.0f32), max); /// ``` pub fn scale_min_max(self, min: F::V, max: F::V) -> Anim> { self * (max.clone() - min) + min } } #[cfg(any(feature = "std", feature = "libm"))] impl Anim where F: Fun, F::V: Float, { /// Apply `Float::sin` to the animation values. pub fn sin(self) -> Anim> { self.map(Float::sin) } /// Apply `Float::cos` to the animation values. pub fn cos(self) -> Anim> { self.map(Float::cos) } /// Apply `Float::powf` to the animation values. pub fn powf(self, e: F::V) -> Anim> { self.map(move |v| v.powf(e)) } } impl Anim where F: Fun, F::V: FloatCore, { /// Apply `FloatCore::abs` to the animation values. pub fn abs(self) -> Anim> { self.map(FloatCore::abs) } /// Apply `FloatCore::powi` to the animation values. pub fn powi(self, n: i32) -> Anim> { self.map(move |v| v.powi(n)) } } impl Anim where F: Fun, F::T: Copy + FloatCore, { /// Transform an animation in time, so that its time `[0 .. 1]` is shifted /// and scaled into the given `range`. /// /// In other words, this function can both delay and speed up or slow down a /// given animation. /// /// # Example /// /// Go from zero to 2π in half a second: /// ``` /// # use assert_approx_eq::assert_approx_eq; /// // From zero to 2π in one second /// let angle = pareen::circle::(); /// /// // From zero to 2π in time range [0.5 .. 1.0] /// let anim = angle.squeeze(0.5..=1.0); /// /// assert_approx_eq!(anim.eval(0.5), 0.0); /// assert_approx_eq!(anim.eval(1.0), std::f32::consts::PI * 2.0); /// ``` pub fn squeeze(self, range: RangeInclusive) -> Anim> { let time_shift = *range.start(); let time_scale = F::T::one() / (*range.end() - *range.start()); self.map_time(move |t| (t - time_shift) * time_scale) } /// Transform an animation in time, so that its time `[0 .. 1]` is shifted /// and scaled into the given `range`. /// /// In other words, this function can both delay and speed up or slow down a /// given animation. /// /// For time outside of the given `range`, the `surround` animation is used /// instead. /// /// # Example /// /// Go from zero to 2π in half a second: /// ``` /// # use assert_approx_eq::assert_approx_eq; /// // From zero to 2π in one second /// let angle = pareen::circle(); /// /// // From zero to 2π in time range [0.5 .. 1.0] /// let anim = angle.squeeze_and_surround(0.5..=1.0, 42.0); /// /// assert_approx_eq!(anim.eval(0.0f32), 42.0f32); /// assert_approx_eq!(anim.eval(0.5), 0.0); /// assert_approx_eq!(anim.eval(1.0), std::f32::consts::PI * 2.0); /// assert_approx_eq!(anim.eval(1.1), 42.0); /// ``` pub fn squeeze_and_surround( self, range: RangeInclusive, surround: A, ) -> Anim> where G: Fun, A: Into>, { self.squeeze(range.clone()).surround(range, surround) } /// Play two animations in sequence, first playing `self` until time /// `self_end` (non-inclusive), and then switching to `next`. The animations /// are squeezed in time so that they fit into `[0 .. 1]` together. /// /// `self` is played in time `[0 .. self_end)`, and then `next` is played /// in time [self_end .. 1]`. pub fn seq_squeeze(self, self_end: F::T, next: A) -> Anim> where G: Fun, A: Into>, { let first = self.squeeze(Zero::zero()..=self_end); let second = next.into().squeeze(self_end..=One::one()); first.switch(self_end, second) } /// Repeat an animation forever. pub fn repeat(self, period: F::T) -> Anim> { self.map_time(move |t: F::T| (t * period.recip()).fract() * period) } } impl Anim where F: Fun, F::T: Copy + Mul, F::V: Copy + Add + Sub, { /// Linearly interpolate between two animations, starting at time zero and /// finishing at time one. /// /// # Examples /// /// Linearly interpolate between two constant values: /// ``` /// # use assert_approx_eq::assert_approx_eq; /// let anim = pareen::constant(5.0f32).lerp(10.0); /// /// assert_approx_eq!(anim.eval(0.0f32), 5.0); /// assert_approx_eq!(anim.eval(0.5), 7.5); /// assert_approx_eq!(anim.eval(1.0), 10.0); /// assert_approx_eq!(anim.eval(2.0), 15.0); /// ``` #[cfg_attr(any(feature = "std", feature = "libm"), doc = r##" It is also possible to linearly interpolate between two non-constant animations: ``` let anim = pareen::circle().sin().lerp(pareen::circle().cos()); let value: f32 = anim.eval(0.5f32); ``` "##)] pub fn lerp(self, other: A) -> Anim> where G: Fun, A: Into>, { let other = other.into(); fun(move |t| { let a = self.eval(t); let b = other.eval(t); let delta = b - a; a + t * delta }) } } impl Anim where F: Fun>, F::T: Clone, { /// Unwrap an animation of optional values. /// /// At any time, returns the animation value if it is not `None`, or the /// given `default` value otherwise. /// /// # Examples /// /// ``` /// let anim1 = pareen::constant(Some(42)).unwrap_or(-1); /// assert_eq!(anim1.eval(2), 42); /// assert_eq!(anim1.eval(3), 42); /// ``` /// /// ``` /// let cond = pareen::fun(|t| t % 2 == 0); /// let anim1 = pareen::cond(cond, Some(42), None).unwrap_or(-1); /// assert_eq!(anim1.eval(2), 42); /// assert_eq!(anim1.eval(3), -1); /// ``` pub fn unwrap_or(self, default: A) -> Anim> where G: Fun, A: Into>, { self.zip(default.into()) .map(|(v, default)| v.unwrap_or(default)) } /// Applies a function to the contained value (if any), or returns the /// provided default (if not). /// /// Note that the function `f` itself returns an animation. /// /// # Example /// /// Animate a player's position offset if it is moving: /// ``` /// # use assert_approx_eq::assert_approx_eq; /// fn my_offset_anim( /// move_dir: Option, /// ) -> pareen::Anim> { /// let move_speed = 2.0f32; /// /// pareen::constant(move_dir).map_or( /// 0.0, /// move |move_dir| pareen::prop(move_dir) * move_speed, /// ) /// } /// /// let move_anim = my_offset_anim(Some(1.0)); /// let stay_anim = my_offset_anim(None); /// /// assert_approx_eq!(move_anim.eval(0.5), 1.0); /// assert_approx_eq!(stay_anim.eval(0.5), 0.0); /// ``` pub fn map_or( self, default: A, f: impl Fn(V) -> Anim, ) -> Anim> where G: Fun, H: Fun, A: Into>, { let default = default.into(); //self.bind(move |v| v.map_or(default, f)) fun(move |t: F::T| { self.eval(t.clone()) .map_or_else(|| default.eval(t.clone()), |v| f(v).eval(t.clone())) }) } } /// Return the value of one of two animations depending on a condition. /// /// This allows returning animations of different types conditionally. /// /// Note that the condition `cond` may either be a value `true` and `false`, or /// it may itself be a dynamic animation of type `bool`. /// /// For dynamic conditions, in many cases it suffices to use either /// [`Anim::switch`](struct.Anim.html#method.switch) or /// [`Anim::seq`](struct.Anim.html#method.seq) instead of this function. /// /// # Examples /// ## Constant conditions /// /// The following example does _not_ compile, because the branches have /// different types: /// ```compile_fail /// let cond = true; /// let anim = if cond { pareen::constant(1) } else { pareen::id() }; /// ``` /// /// However, this does compile: /// ``` /// let cond = true; /// let anim = pareen::cond(cond, 1, pareen::id()); /// /// assert_eq!(anim.eval(2), 1); /// ``` /// /// ## Dynamic conditions /// /// ``` /// let cond = pareen::fun(|t| t * t <= 4); /// let anim_1 = 1; /// let anim_2 = pareen::id(); /// let anim = pareen::cond(cond, anim_1, anim_2); /// /// assert_eq!(anim.eval(1), 1); // 1 * 1 <= 4 /// assert_eq!(anim.eval(2), 1); // 2 * 2 <= 4 /// assert_eq!(anim.eval(3), 3); // 3 * 3 > 4 /// ``` pub fn cond(cond: Cond, a: A, b: B) -> Anim> where F::T: Clone, F: Fun, G: Fun, H: Fun, Cond: Into>, A: Into>, B: Into>, { // Nested closures result in exponential compilation time increase, and we // expect cond to be used often. Thus, we avoid using `pareen::fun` here. // For reference: https://github.com/rust-lang/rust/issues/72408 Anim(CondClosure(cond.into().0, a.into().0, b.into().0)) } #[doc(hidden)] #[derive(Debug, Clone)] pub struct CondClosure(F, G, H); impl Fun for CondClosure where F::T: Clone, F: Fun, G: Fun, H: Fun, { type T = F::T; type V = G::V; fn eval(&self, t: F::T) -> G::V { if self.0.eval(t.clone()) { self.1.eval(t) } else { self.2.eval(t) } } } /// Linearly interpolate between two animations, starting at time zero and /// finishing at time one. /// /// This is a wrapper around [`Anim::lerp`](struct.Anim.html#method.lerp) for /// convenience, allowing automatic conversion into [`Anim`](struct.Anim.html) /// for both `a` and `b`. /// /// # Example /// /// Linearly interpolate between two constant values: /// /// ``` /// # use assert_approx_eq::assert_approx_eq; /// let anim = pareen::lerp(5.0f32, 10.0); /// /// assert_approx_eq!(anim.eval(0.0f32), 5.0); /// assert_approx_eq!(anim.eval(0.5), 7.5); /// assert_approx_eq!(anim.eval(1.0), 10.0); /// assert_approx_eq!(anim.eval(2.0), 15.0); /// ``` pub fn lerp(a: A, b: B) -> Anim> where T: Copy + Mul, V: Copy + Add + Sub, F: Fun, G: Fun, A: Into>, B: Into>, { a.into().lerp(b.into()) } /// Build an animation that depends on matching some expression. /// /// Importantly, this macro allows returning animations of a different type in /// each match arm, which is not possible with a normal `match` expression. /// /// # Example /// ``` /// # use assert_approx_eq::assert_approx_eq; /// enum MyPlayerState { /// Standing, /// Running, /// Jumping, /// } /// /// fn my_anim(state: MyPlayerState) -> pareen::Anim> { /// pareen::anim_match!(state; /// MyPlayerState::Standing => pareen::constant(0.0), /// MyPlayerState::Running => pareen::prop(1.0), /// MyPlayerState::Jumping => pareen::id().powi(2), /// ) /// } /// /// assert_approx_eq!(my_anim(MyPlayerState::Standing).eval(2.0), 0.0); /// assert_approx_eq!(my_anim(MyPlayerState::Running).eval(2.0), 2.0); /// assert_approx_eq!(my_anim(MyPlayerState::Jumping).eval(2.0), 4.0); /// ``` #[macro_export] macro_rules! anim_match { ( $expr:expr; $($pat:pat => $value:expr $(,)?)* ) => { $crate::fun(move |t| match $expr { $( $pat => ($crate::Anim::from($value)).eval(t), )* }) } }