#![allow(dead_code)]
use std::f32::consts::PI;
pub type EasingFn = fn(f32) -> f32;
pub fn linear(t: f32) -> f32 {
t
}
pub mod quad {
pub fn ease_in(t: f32) -> f32 {
t * t
}
pub fn ease_out(t: f32) -> f32 {
1.0 - (1.0 - t) * (1.0 - t)
}
pub fn ease_in_out(t: f32) -> f32 {
if t < 0.5 {
2.0 * t * t
} else {
1.0 - (-2.0 * t + 2.0).powi(2) / 2.0
}
}
}
pub mod cubic {
pub fn ease_in(t: f32) -> f32 {
t * t * t
}
pub fn ease_out(t: f32) -> f32 {
1.0 - (1.0 - t).powi(3)
}
pub fn ease_in_out(t: f32) -> f32 {
if t < 0.5 {
4.0 * t * t * t
} else {
1.0 - (-2.0 * t + 2.0).powi(3) / 2.0
}
}
}
pub mod quart {
pub fn ease_in(t: f32) -> f32 {
t * t * t * t
}
pub fn ease_out(t: f32) -> f32 {
1.0 - (1.0 - t).powi(4)
}
pub fn ease_in_out(t: f32) -> f32 {
if t < 0.5 {
8.0 * t * t * t * t
} else {
1.0 - (-2.0 * t + 2.0).powi(4) / 2.0
}
}
}
pub mod sine {
use super::PI;
pub fn ease_in(t: f32) -> f32 {
1.0 - (t * PI / 2.0).cos()
}
pub fn ease_out(t: f32) -> f32 {
(t * PI / 2.0).sin()
}
pub fn ease_in_out(t: f32) -> f32 {
-(((t * PI).cos() - 1.0) / 2.0)
}
}
pub mod expo {
pub fn ease_in(t: f32) -> f32 {
if t == 0.0 {
0.0
} else {
2.0_f32.powf(10.0 * (t - 1.0))
}
}
pub fn ease_out(t: f32) -> f32 {
if t == 1.0 {
1.0
} else {
1.0 - 2.0_f32.powf(-10.0 * t)
}
}
pub fn ease_in_out(t: f32) -> f32 {
if t == 0.0 {
0.0
} else if t == 1.0 {
1.0
} else if t < 0.5 {
2.0_f32.powf(20.0 * t - 10.0) / 2.0
} else {
(2.0 - 2.0_f32.powf(-20.0 * t + 10.0)) / 2.0
}
}
}
pub mod circ {
pub fn ease_in(t: f32) -> f32 {
1.0 - (1.0 - t * t).sqrt()
}
pub fn ease_out(t: f32) -> f32 {
(1.0 - (t - 1.0) * (t - 1.0)).sqrt()
}
pub fn ease_in_out(t: f32) -> f32 {
if t < 0.5 {
(1.0 - (1.0 - (2.0 * t).powi(2)).sqrt()) / 2.0
} else {
((1.0 - (-2.0 * t + 2.0).powi(2)).sqrt() + 1.0) / 2.0
}
}
}
pub mod bounce {
pub fn ease_out(t: f32) -> f32 {
const N1: f32 = 7.5625;
const D1: f32 = 2.75;
if t < 1.0 / D1 {
N1 * t * t
} else if t < 2.0 / D1 {
N1 * (t - 1.5 / D1) * (t - 1.5 / D1) + 0.75
} else if t < 2.5 / D1 {
N1 * (t - 2.25 / D1) * (t - 2.25 / D1) + 0.9375
} else {
N1 * (t - 2.625 / D1) * (t - 2.625 / D1) + 0.984375
}
}
pub fn ease_in(t: f32) -> f32 {
1.0 - ease_out(1.0 - t)
}
pub fn ease_in_out(t: f32) -> f32 {
if t < 0.5 {
(1.0 - ease_out(1.0 - 2.0 * t)) / 2.0
} else {
(1.0 + ease_out(2.0 * t - 1.0)) / 2.0
}
}
}
pub mod elastic {
use super::PI;
const C4: f32 = (2.0 * PI) / 3.0;
const C5: f32 = (2.0 * PI) / 4.5;
pub fn ease_in(t: f32) -> f32 {
if t == 0.0 {
0.0
} else if t == 1.0 {
1.0
} else {
-2.0_f32.powf(10.0 * t - 10.0) * ((t * 10.0 - 10.75) * C4).sin()
}
}
pub fn ease_out(t: f32) -> f32 {
if t == 0.0 {
0.0
} else if t == 1.0 {
1.0
} else {
2.0_f32.powf(-10.0 * t) * ((t * 10.0 - 0.75) * C4).sin() + 1.0
}
}
pub fn ease_in_out(t: f32) -> f32 {
if t == 0.0 {
0.0
} else if t == 1.0 {
1.0
} else if t < 0.5 {
-(2.0_f32.powf(20.0 * t - 10.0) * ((20.0 * t - 11.125) * C5).sin()) / 2.0
} else {
(2.0_f32.powf(-20.0 * t + 10.0) * ((20.0 * t - 11.125) * C5).sin()) / 2.0 + 1.0
}
}
}
pub fn interpolate<T>(start: T, end: T, t: f32, easing: EasingFn) -> T
where
T: std::ops::Add<Output = T>
+ std::ops::Sub<Output = T>
+ std::ops::Mul<f32, Output = T>
+ Copy,
{
let eased_t = easing(t.clamp(0.0, 1.0));
start + (end - start) * eased_t
}
pub mod presets {
use super::*;
pub const EASE: EasingFn = cubic::ease_in_out;
pub const EASE_IN: EasingFn = cubic::ease_in;
pub const EASE_OUT: EasingFn = cubic::ease_out;
pub const EASE_IN_OUT: EasingFn = cubic::ease_in_out;
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_abs_diff_eq;
#[test]
fn test_linear_easing() {
assert_abs_diff_eq!(linear(0.0), 0.0, epsilon = 1e-6);
assert_abs_diff_eq!(linear(0.5), 0.5, epsilon = 1e-6);
assert_abs_diff_eq!(linear(1.0), 1.0, epsilon = 1e-6);
}
#[test]
fn test_quad_easing_boundaries() {
assert_abs_diff_eq!(quad::ease_in(0.0), 0.0, epsilon = 1e-6);
assert_abs_diff_eq!(quad::ease_in(1.0), 1.0, epsilon = 1e-6);
assert_abs_diff_eq!(quad::ease_out(0.0), 0.0, epsilon = 1e-6);
assert_abs_diff_eq!(quad::ease_out(1.0), 1.0, epsilon = 1e-6);
assert_abs_diff_eq!(quad::ease_in_out(0.0), 0.0, epsilon = 1e-6);
assert_abs_diff_eq!(quad::ease_in_out(1.0), 1.0, epsilon = 1e-6);
}
#[test]
fn test_cubic_easing_boundaries() {
assert_abs_diff_eq!(cubic::ease_in(0.0), 0.0, epsilon = 1e-6);
assert_abs_diff_eq!(cubic::ease_in(1.0), 1.0, epsilon = 1e-6);
assert_abs_diff_eq!(cubic::ease_out(0.0), 0.0, epsilon = 1e-6);
assert_abs_diff_eq!(cubic::ease_out(1.0), 1.0, epsilon = 1e-6);
assert_abs_diff_eq!(cubic::ease_in_out(0.0), 0.0, epsilon = 1e-6);
assert_abs_diff_eq!(cubic::ease_in_out(1.0), 1.0, epsilon = 1e-6);
}
#[test]
fn test_sine_easing_boundaries() {
assert_abs_diff_eq!(sine::ease_in(0.0), 0.0, epsilon = 1e-6);
assert_abs_diff_eq!(sine::ease_in(1.0), 1.0, epsilon = 1e-6);
assert_abs_diff_eq!(sine::ease_out(0.0), 0.0, epsilon = 1e-6);
assert_abs_diff_eq!(sine::ease_out(1.0), 1.0, epsilon = 1e-6);
assert_abs_diff_eq!(sine::ease_in_out(0.0), 0.0, epsilon = 1e-6);
assert_abs_diff_eq!(sine::ease_in_out(1.0), 1.0, epsilon = 1e-6);
}
#[test]
fn test_bounce_easing_boundaries() {
assert_abs_diff_eq!(bounce::ease_out(0.0), 0.0, epsilon = 1e-6);
assert_abs_diff_eq!(bounce::ease_out(1.0), 1.0, epsilon = 1e-6);
}
#[test]
fn test_interpolate_function() {
assert_abs_diff_eq!(interpolate(0.0, 10.0, 0.5, linear), 5.0, epsilon = 1e-6);
assert_abs_diff_eq!(interpolate(0.0, 10.0, 0.0, linear), 0.0, epsilon = 1e-6);
assert_abs_diff_eq!(interpolate(0.0, 10.0, 1.0, linear), 10.0, epsilon = 1e-6);
let result = interpolate(0.0, 10.0, 0.5, cubic::ease_in_out);
assert!((0.0..=10.0).contains(&result));
}
#[test]
fn test_easing_monotonicity() {
let values = [0.0, 0.25, 0.5, 0.75, 1.0];
for i in 0..values.len() - 1 {
assert!(quad::ease_in(values[i]) <= quad::ease_in(values[i + 1]));
assert!(cubic::ease_in(values[i]) <= cubic::ease_in(values[i + 1]));
assert!(sine::ease_in(values[i]) <= sine::ease_in(values[i + 1]));
}
}
#[test]
fn test_ease_in_out_symmetry() {
let test_values = [0.1, 0.2, 0.3, 0.4];
for &t in &test_values {
let quad_left = quad::ease_in_out(t);
let quad_right = 1.0 - quad::ease_in_out(1.0 - t);
assert_abs_diff_eq!(quad_left, quad_right, epsilon = 1e-5);
let cubic_left = cubic::ease_in_out(t);
let cubic_right = 1.0 - cubic::ease_in_out(1.0 - t);
assert_abs_diff_eq!(cubic_left, cubic_right, epsilon = 1e-5);
}
}
#[test]
fn test_bounce_characteristics() {
let bounce_values: Vec<f32> = (0..=20)
.map(|i| bounce::ease_out(i as f32 / 20.0))
.collect();
let mut local_maxima = 0;
for i in 1..bounce_values.len() - 1 {
if bounce_values[i] > bounce_values[i - 1] && bounce_values[i] > bounce_values[i + 1] {
local_maxima += 1;
}
}
assert!(local_maxima >= 2, "Bounce should have multiple peaks");
}
#[test]
fn test_elastic_characteristics() {
let elastic_values: Vec<f32> = (1..20)
.map(|i| elastic::ease_out(i as f32 / 20.0))
.collect();
let has_overshoot = elastic_values.iter().any(|&v| !(-0.01..=1.01).contains(&v));
assert!(has_overshoot, "Elastic should overshoot the target range");
}
mod property_tests {
use super::*;
use proptest::prelude::*;
proptest! {
#[test]
fn test_easing_functions_stay_finite(t in 0.0f32..=1.0f32) {
assert!(linear(t).is_finite());
assert!(quad::ease_in(t).is_finite());
assert!(quad::ease_out(t).is_finite());
assert!(quad::ease_in_out(t).is_finite());
assert!(cubic::ease_in(t).is_finite());
assert!(cubic::ease_out(t).is_finite());
assert!(cubic::ease_in_out(t).is_finite());
assert!(sine::ease_in(t).is_finite());
assert!(sine::ease_out(t).is_finite());
assert!(sine::ease_in_out(t).is_finite());
assert!(bounce::ease_out(t).is_finite());
}
#[test]
fn test_easing_functions_boundaries_property(t in 0.0f32..=1.0f32) {
if (t - 0.0).abs() < f32::EPSILON {
assert_abs_diff_eq!(quad::ease_in(t), 0.0, epsilon = 1e-6);
assert_abs_diff_eq!(cubic::ease_in(t), 0.0, epsilon = 1e-6);
}
if (t - 1.0).abs() < f32::EPSILON {
assert_abs_diff_eq!(quad::ease_in(t), 1.0, epsilon = 1e-6);
assert_abs_diff_eq!(cubic::ease_in(t), 1.0, epsilon = 1e-6);
}
}
}
}
}