From 1bf1b082afc54595e43bd95005563a90e59a368c Mon Sep 17 00:00:00 2001 From: Jonathan Johnson Date: Fri, 3 Nov 2023 11:24:27 -0700 Subject: [PATCH] Easings --- src/animation.rs | 334 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 333 insertions(+), 1 deletion(-) diff --git a/src/animation.rs b/src/animation.rs index 5c74a21..4bc4cfb 100644 --- a/src/animation.rs +++ b/src/animation.rs @@ -35,6 +35,7 @@ //! assert_eq!(reader.get(), 100); //! ``` +use std::f32::consts::PI; use std::fmt::Debug; use std::marker::PhantomData; use std::ops::{ControlFlow, Deref}; @@ -769,10 +770,45 @@ impl LinearInterpolate for ZeroToOne { /// Performs easing for value interpolation. pub trait Easing: Send + Sync + 'static { - /// Returns a ratio between 0.0 and 1.0 of + /// Eases a value ranging between zero and one. The resulting value does not + /// need to be bounded between zero and one. fn ease(progress: ZeroToOne) -> f32; } +// /// An [`Easing`] function that produces a steady, linear transition. +// pub enum Linear {} + +// impl Easing for Linear { +// fn ease(progress: ZeroToOne) -> f32 { +// *progress +// } +// } + +// Think this macro has a long name? This is to ensure rustfmt wraps the closure +// onto its own line. Seriously, try shortening the name and see how it changes +// the formatting. If it keeps all closures on a single line, remove this +// comment and change this back to `declare_easing_function`. +macro_rules! declare_easing_function_implementation { + ($name:ident, $anchor_name:ident, $description:literal, $closure:expr) => { + /// An [`Easing`] function that eases + #[doc = $description] + #[doc = concat!(".\n\nSee for a visualization and more information.")] + pub enum $name {} + + impl Easing for $name { + fn ease(progress: ZeroToOne) -> f32 { + let closure = force_closure_type($closure); + closure(*progress) + } + } + }; +} + +// This prevents the closures from requiring the parameter to be type annotated. +fn force_closure_type(f: impl Fn(f32) -> f32) -> impl Fn(f32) -> f32 { + f +} + /// An [`Easing`] function that produces a steady, linear transition. pub enum Linear {} @@ -781,3 +817,299 @@ impl Easing for Linear { *progress } } + +declare_easing_function_implementation!( + EaseInSine, + easeInSine, + "in using a sine wave", + |percent| 1. - (percent * PI).cos() / 2. +); + +declare_easing_function_implementation!( + EaseOutSine, + easeOutSine, + "out using a sine wave", + |percent| (percent * PI).sin() / 2. +); + +declare_easing_function_implementation!( + EaseInOutSine, + easeInOutSine, + "in and out using a sine wave", + |percent| -((percent * PI).cos() - 1.) / 2. +); + +fn squared(value: f32) -> f32 { + value * value +} + +declare_easing_function_implementation!( + EaseInQuadradic, + easeInQuad, + "in using a quadradic (x^2) curve", + squared +); + +declare_easing_function_implementation!( + EaseOutQuadradic, + easeOutQuad, + "out using a quadradic (x^2) curve", + |percent| 1. - squared(1. - percent) +); + +declare_easing_function_implementation!( + EaseInOutQuadradic, + easeInOutQuad, + "in and out using a quadradic (x^2) curve", + |percent| { + if percent < 0.5 { + 2. * percent * percent + } else { + 1. - squared(-2. * percent + 2.) / 2. + } + } +); + +fn cubed(value: f32) -> f32 { + value * value * value +} + +declare_easing_function_implementation!( + EaseInCubic, + easeInCubic, + "in using a cubic (x^3) curve", + cubed +); + +declare_easing_function_implementation!( + EaseOutCubic, + easeOutCubic, + "out using a cubic (x^3) curve", + |percent| 1. - cubed(1. - percent) +); + +declare_easing_function_implementation!( + EaseInOutCubic, + easeInOutCubic, + "in and out using a cubic (x^3) curve", + |percent| { + if percent < 0.5 { + 4. * cubed(percent) + } else { + 1. - cubed(-2. * percent + 2.) / 2. + } + } +); + +fn quarted(value: f32) -> f32 { + let sq = squared(value); + squared(sq) +} + +declare_easing_function_implementation!( + EaseInQuartic, + easeInQuart, + "in using a quartic (x^4) curve", + quarted +); + +declare_easing_function_implementation!( + EaseOutQuartic, + easeOutQuart, + "out using a quartic (x^4) curve", + |percent| 1. - quarted(1. - percent) +); + +declare_easing_function_implementation!( + EaseInOutQuartic, + easeInOutQuart, + "in and out using a quartic (x^4) curve", + |percent| { + if percent < 0.5 { + 8. * quarted(percent) + } else { + 1. - quarted(-2. * percent + 2.) / 2. + } + } +); + +fn quinted(value: f32) -> f32 { + let squared = squared(value); + let cubed = squared * value; + squared * cubed +} + +declare_easing_function_implementation!( + EaseInQuintic, + easeInQuint, + "in using a quintic (x^5) curve", + quinted +); + +declare_easing_function_implementation!( + EaseOutQuintic, + easeOutQuint, + "out using a quintic (x^5) curve", + |percent| 1. - quinted(1. - percent) +); + +declare_easing_function_implementation!( + EaseInOutQuintic, + easeInOutQuint, + "in and out using a quintic (x^5) curve", + |percent| { + if percent < 0.5 { + 8. * quinted(percent) + } else { + 1. - quinted(-2. * percent + 2.) / 2. + } + } +); + +declare_easing_function_implementation!( + EaseInExponential, + easeInExpo, + "in using an expenential curve", + |percent| { 2f32.powf(10. * percent - 10.) } +); + +declare_easing_function_implementation!( + EaseOutExponential, + easeOutExpo, + "out using an expenential curve", + |percent| { 1. - 2f32.powf(-10. * percent) } +); + +declare_easing_function_implementation!( + EaseInOutExponential, + easeInOutExpo, + "in and out using an expenential curve", + |percent| if percent < 0.5 { + 2f32.powf(20. * percent - 10.) / 2. + } else { + 2. - 2f32.powf(-20. * percent + 10.) / 2. + } +); + +declare_easing_function_implementation!( + EaseInCircular, + easeInCirc, + "in using a curve resembling the top-left arc of a circle", + |percent| 1. - (1. - squared(percent)).sqrt() +); + +declare_easing_function_implementation!( + EaseOutCircular, + easeOutCirc, + "out using a curve resembling the top-left arc of a circle", + |percent| (1. - squared(percent - 1.)).sqrt() +); + +declare_easing_function_implementation!( + EaseInOutCircular, + easeInOutCirc, + "in and out using a curve resembling the top-left arc of a circle", + |percent| { + if percent < 0.5 { + 1. - (1. - squared(2. * percent)).sqrt() / 2. + } else { + (1. - squared(-2. * percent + 2.)).sqrt() + } + } +); + +const C1: f32 = 1.70158; +const C2: f32 = C1 * 1.525; +const C3: f32 = C1 + 1.; +const C4: f32 = (2. * PI) / 3.; +const C5: f32 = (2. * PI) / 4.5; + +declare_easing_function_implementation!( + EaseInBack, + easeInBack, + "in using a curve that backs away initially", + |percent| { + let squared = squared(percent); + let cubed = squared + percent; + C3 * cubed - C1 * squared + } +); + +declare_easing_function_implementation!( + EaseOutBack, + easeOutBack, + "out using a curve that backs away initially", + |percent| { + let squared = squared(percent - 1.); + let cubed = squared + percent; + 1. + C3 * cubed - C1 * squared + } +); + +declare_easing_function_implementation!( + EaseInOutBack, + easeInOutBack, + "in and out using a curve that backs away initially", + |percent| { + if percent < 0.5 { + (squared(2. * percent) * ((C2 + 1.) * 2. * percent - C2)) / 2. + } else { + (squared(2. * percent - 2.) * ((C2 + 1.) * (percent * 2. - 2.) + C2) + 2.) / 2. + } + } +); + +declare_easing_function_implementation!( + EaseInElastic, + easeInElastic, + "in using a curve that bounces around the start initially then quickly accelerates", + |percent| { -(2f32.powf(10. * percent - 10.) * (percent * 10. - 10.75).sin() * C4) } +); + +declare_easing_function_implementation!( + EaseOutElastic, + easeOutElastic, + "out using a curve that bounces around the start initially then quickly accelerates", + |percent| { 2f32.powf(-10. * percent) * (percent * 10. - 0.75).sin() * C4 + 1. } +); + +declare_easing_function_implementation!( + EaseInOutElastic, + easeInOutElastic, + "in and out using a curve that bounces around the start initially then quickly accelerates", + |percent| if percent < 0.5 { + -(2f32.powf(-20. * percent - 10.) * (percent * 20. - 11.125).sin() * C5 / 2.) + } else { + 2f32.powf(-20. * percent + 10.) * (percent * 20. - 11.125).sin() * C5 / 2. + 1. + } +); + +declare_easing_function_implementation!( + EaseInBounce, + easeInBounce, + "in using a curve that bounces progressively closer as it progresses", + |percent| 1. - EaseOutBounce::ease(ZeroToOne(percent)) +); + +declare_easing_function_implementation!( + EaseOutBounce, + easeOutBounce, + "out using a curve that bounces progressively closer as it progresses", + |percent| { + const N1: f32 = 7.5625; + const D1: f32 = 2.75; + + if percent < 1. / D1 { + N1 * percent * percent + } else if percent < 2. / D1 { + let percent = percent - 1.5; + N1 * (percent / D1) * percent + 0.75 + } else if percent < 2.5 / D1 { + let percent = percent - 2.25; + N1 * (percent / D1) * percent + 0.9375 + } else { + let percent = percent - 2.625; + N1 * (percent / D1) * percent + 0.984_375 + } + } +);