use std::f64::consts::PI;
use super::{AdvancedEasing, Easable, split_and_calc};
const C1: f64 = 1.70158;
const C2: f64 = C1 * 1.525;
const C3: f64 = C1 + 1.0;
const C4: f64 = 2.0 * PI / 3.0;
const C5: f64 = 2.0 * PI / 4.5;
pub fn evaluate_easing<T, const N: usize>(ease: AdvancedEasing, from: T, to: T, t: T) -> T
where
T: Easable<N>,
{
split_and_calc::<T, N>(from, to, t, |from, to, t| {
if t < 0.0 {
from
} else if t > 1.0 {
to
} else {
let e = easing_value(ease, t);
from * (1.0 - e) + to * e
}
})
}
pub(crate) fn easing_value(ease: AdvancedEasing, t: f64) -> f64 {
match ease {
AdvancedEasing::Linear => t,
AdvancedEasing::InConst => {
if t >= 1.0 {
1.0
} else {
0.0
}
}
AdvancedEasing::OutConst => {
if t <= 0.0 {
0.0
} else {
1.0
}
}
AdvancedEasing::InOutConst => t.round(),
AdvancedEasing::InSine => 1.0 - f64::cos(t * PI / 2.0),
AdvancedEasing::OutSine => f64::sin(t * PI / 2.0),
AdvancedEasing::InOutSine => (1.0 - f64::cos(t * PI)) / 2.0,
AdvancedEasing::InQuad => t * t,
AdvancedEasing::OutQuad => 1.0 - (1.0 - t) * (1.0 - t),
AdvancedEasing::InOutQuad => {
if t < 0.5 {
2.0 * t * t
} else {
1.0 - f64::powi(-2.0 * t + 2.0, 2) / 2.0
}
}
AdvancedEasing::InCubic => t * t * t,
AdvancedEasing::OutCubic => 1.0 - f64::powi(1.0 - t, 3),
AdvancedEasing::InOutCubic => {
if t < 0.5 {
4.0 * t * t * t
} else {
1.0 - f64::powi(-2.0 * t + 2.0, 3) / 2.0
}
}
AdvancedEasing::InQuart => t * t * t * t,
AdvancedEasing::OutQuart => 1.0 - f64::powi(1.0 - t, 4),
AdvancedEasing::InOutQuart => {
if t < 0.5 {
8.0 * t * t * t * t
} else {
1.0 - f64::powi(-2.0 * t + 2.0, 4) / 2.0
}
}
AdvancedEasing::InQuint => t * t * t * t * t,
AdvancedEasing::OutQuint => 1.0 - f64::powi(1.0 - t, 5),
AdvancedEasing::InOutQuint => {
if t < 0.5 {
16.0 * t * t * t * t * t
} else {
1.0 - f64::powi(-2.0 * t + 2.0, 5) / 2.0
}
}
AdvancedEasing::InExpo => {
if t == 0.0 {
0.0
} else {
f64::powf(2.0, 10.0 * t - 10.0)
}
}
AdvancedEasing::OutExpo => {
if t == 1.0 {
1.0
} else {
1.0 - f64::powf(2.0, -10.0 * t)
}
}
AdvancedEasing::InOutExpo => {
if t == 0.0 {
0.0
} else if t == 1.0 {
1.0
} else if t < 0.5 {
f64::powf(2.0, 20.0 * t - 11.0)
} else {
1.0 - f64::powf(2.0, -20.0 * t + 9.0)
}
}
AdvancedEasing::InCirc => 1.0 - f64::sqrt(1.0 - t * t),
AdvancedEasing::OutCirc => f64::sqrt(1.0 - (t - 1.0) * (t - 1.0)),
AdvancedEasing::InOutCirc => {
if t < 0.5 {
(1.0 - f64::sqrt(1.0 - 4.0 * t * t)) / 2.0
} else {
f64::sqrt(1.0 - f64::powi(-2.0 * t + 2.0, 2)) / 2.0 + 0.5
}
}
AdvancedEasing::InBack => C3 * t * t * t - C1 * t * t,
AdvancedEasing::OutBack => 1.0 + C3 * f64::powi(t - 1.0, 3) + C1 * f64::powi(t - 1.0, 2),
AdvancedEasing::InOutBack => {
if t < 0.5 {
(4.0 * t * t * ((C2 + 1.0) * 2.0 * t - C2)) / 2.0
} else {
f64::powi(2.0 * t - 2.0, 2) * ((C2 + 1.0) * (2.0 * t - 2.0) + C2) / 2.0 + 1.0
}
}
AdvancedEasing::InElastic => {
if t == 0.0 {
0.0
} else if t == 1.0 {
1.0
} else {
f64::powf(2.0, 10.0 * t - 10.0) * f64::sin((t * 10.0 - 10.75) * C4)
}
}
AdvancedEasing::OutElastic => {
if t == 0.0 {
0.0
} else if t == 1.0 {
1.0
} else {
f64::powf(2.0, -10.0 * t) * f64::sin((t * 10.0 - 0.75) * C4) + 1.0
}
}
AdvancedEasing::InOutElastic => {
if t == 0.0 {
0.0
} else if t == 1.0 {
1.0
} else if t < 0.5 {
-f64::powf(2.0, 20.0 * t - 10.0) * f64::sin((20.0 * t - 11.125) * C5) / 2.0
} else {
f64::powf(2.0, -20.0 * t + 10.0) * f64::sin((20.0 * t - 11.125) * C5) / 2.0 + 1.0
}
}
AdvancedEasing::InBounce => 1.0 - ease_out_bounce(1.0 - t),
AdvancedEasing::OutBounce => ease_out_bounce(t),
AdvancedEasing::InOutBounce => {
if t < 0.5 {
(1.0 - ease_out_bounce(1.0 - 2.0 * t)) / 2.0
} else {
(1.0 + ease_out_bounce(2.0 * t - 1.0)) / 2.0
}
}
}
}
fn ease_out_bounce(x: f64) -> f64 {
let n1 = 7.5625;
let d1 = 2.75;
if x < 1.0 / d1 {
n1 * x * x
} else if x < 2.0 / d1 {
let x = x - 1.5 / d1;
n1 * x * x + 0.75
} else if x < 2.5 / d1 {
let x = x - 2.25 / d1;
n1 * x * x + 0.9375
} else {
let x = x - 2.625 / d1;
n1 * x * x + 0.984375
}
}
#[cfg(test)]
mod tests {
use super::*;
const FROM: f64 = 0.0;
const TO: f64 = 100.0;
const EPS: f64 = 1e-4;
const INOUT_EASINGS: &[AdvancedEasing] = &[
AdvancedEasing::InOutSine,
AdvancedEasing::InOutQuad,
AdvancedEasing::InOutCubic,
AdvancedEasing::InOutQuart,
AdvancedEasing::InOutQuint,
AdvancedEasing::InOutExpo,
AdvancedEasing::InOutCirc,
AdvancedEasing::InOutBack,
AdvancedEasing::InOutElastic,
AdvancedEasing::InOutBounce,
];
fn approx_eq(a: f64, b: f64) -> bool {
(a - b).abs() <= EPS
}
#[test]
fn t0_returns_from() {
let mut failures: Vec<(AdvancedEasing, f64)> = Vec::new();
for &ease in AdvancedEasing::ALL {
let result = evaluate_easing::<f64, 1>(ease, FROM, TO, 0.0);
if !approx_eq(result, FROM) {
failures.push((ease, result));
}
}
assert!(
failures.is_empty(),
"t=0 should return from ({}), but got: {}",
FROM,
failures
.iter()
.map(|(e, r)| format!("{e:?}={r}"))
.collect::<Vec<_>>()
.join(", ")
);
}
#[test]
fn t1_returns_to() {
let mut failures: Vec<(AdvancedEasing, f64)> = Vec::new();
for &ease in AdvancedEasing::ALL {
let result = evaluate_easing::<f64, 1>(ease, FROM, TO, 1.0);
if !approx_eq(result, TO) {
failures.push((ease, result));
}
}
assert!(
failures.is_empty(),
"t=1 should return to ({}), but got: {}",
TO,
failures
.iter()
.map(|(e, r)| format!("{e:?}={r}"))
.collect::<Vec<_>>()
.join(", ")
);
}
#[test]
fn t05_returns_midpoint_for_inout() {
let midpoint = (FROM + TO) / 2.0;
let mut failures: Vec<(AdvancedEasing, f64)> = Vec::new();
for &ease in INOUT_EASINGS {
if matches!(ease, AdvancedEasing::InOutConst) {
continue;
}
let result = evaluate_easing::<f64, 1>(ease, FROM, TO, 0.5);
if !approx_eq(result, midpoint) {
failures.push((ease, result));
}
}
assert!(
failures.is_empty(),
"t=0.5 should return midpoint ({}), but got: {}",
midpoint,
failures
.iter()
.map(|(e, r)| format!("{e:?}={r}"))
.collect::<Vec<_>>()
.join(", ")
);
}
}