use palette::Srgba;
pub trait Interpolate: Clone {
fn interpolate(&self, target: &Self, t: f64) -> Self;
}
impl Interpolate for f64 {
fn interpolate(&self, target: &Self, t: f64) -> Self {
self + (target - self) * t
}
}
impl Interpolate for f32 {
fn interpolate(&self, target: &Self, t: f64) -> Self {
self + (target - self) * t as f32
}
}
impl Interpolate for i32 {
fn interpolate(&self, target: &Self, t: f64) -> Self {
(*self as f64 + (*target - *self) as f64 * t).round() as i32
}
}
impl Interpolate for u32 {
fn interpolate(&self, target: &Self, t: f64) -> Self {
let start = *self as f64;
let end = *target as f64;
(start + (end - start) * t).round().max(0.0) as u32
}
}
impl<T: Interpolate> Interpolate for Vec<T> {
fn interpolate(&self, target: &Self, t: f64) -> Self {
let len = self.len().min(target.len());
self.iter()
.zip(target.iter())
.take(len)
.map(|(a, b)| a.interpolate(b, t))
.collect()
}
}
impl<T: Interpolate, const N: usize> Interpolate for [T; N] {
fn interpolate(&self, target: &Self, t: f64) -> Self {
std::array::from_fn(|i| self[i].interpolate(&target[i], t))
}
}
impl Interpolate for Srgba {
fn interpolate(&self, target: &Self, t: f64) -> Self {
Srgba::new(
self.red.interpolate(&target.red, t),
self.green.interpolate(&target.green, t),
self.blue.interpolate(&target.blue, t),
self.alpha.interpolate(&target.alpha, t),
)
}
}
pub type EasingFn = fn(f64) -> f64;
pub mod easing {
use std::f64::consts::PI;
pub fn linear(t: f64) -> f64 {
t
}
pub fn ease_in_quad(t: f64) -> f64 {
t * t
}
pub fn ease_out_quad(t: f64) -> f64 {
1.0 - (1.0 - t).powi(2)
}
pub fn ease_in_out_quad(t: f64) -> f64 {
if t < 0.5 {
2.0 * t * t
} else {
1.0 - (-2.0 * t + 2.0).powi(2) / 2.0
}
}
pub fn ease_in_cubic(t: f64) -> f64 {
t * t * t
}
pub fn ease_out_cubic(t: f64) -> f64 {
1.0 - (1.0 - t).powi(3)
}
pub fn ease_in_out_cubic(t: f64) -> f64 {
if t < 0.5 {
4.0 * t * t * t
} else {
1.0 - (-2.0 * t + 2.0).powi(3) / 2.0
}
}
pub fn ease_in_quart(t: f64) -> f64 {
t * t * t * t
}
pub fn ease_out_quart(t: f64) -> f64 {
1.0 - (1.0 - t).powi(4)
}
pub fn ease_in_out_quart(t: f64) -> f64 {
if t < 0.5 {
8.0 * t.powi(4)
} else {
1.0 - (-2.0 * t + 2.0).powi(4) / 2.0
}
}
pub fn ease_in_sine(t: f64) -> f64 {
1.0 - (t * PI / 2.0).cos()
}
pub fn ease_out_sine(t: f64) -> f64 {
(t * PI / 2.0).sin()
}
pub fn ease_in_out_sine(t: f64) -> f64 {
-(((t * PI).cos() - 1.0) / 2.0)
}
pub fn ease_in_expo(t: f64) -> f64 {
if t == 0.0 {
0.0
} else {
2.0_f64.powf(10.0 * t - 10.0)
}
}
pub fn ease_out_expo(t: f64) -> f64 {
if t == 1.0 {
1.0
} else {
1.0 - 2.0_f64.powf(-10.0 * t)
}
}
pub fn ease_in_out_expo(t: f64) -> f64 {
if t == 0.0 {
0.0
} else if t == 1.0 {
1.0
} else if t < 0.5 {
2.0_f64.powf(20.0 * t - 10.0) / 2.0
} else {
(2.0 - 2.0_f64.powf(-20.0 * t + 10.0)) / 2.0
}
}
pub fn ease_in_elastic(t: f64) -> f64 {
if t == 0.0 {
0.0
} else if t == 1.0 {
1.0
} else {
let c4 = (2.0 * PI) / 3.0;
-(2.0_f64.powf(10.0 * t - 10.0)) * ((t * 10.0 - 10.75) * c4).sin()
}
}
pub fn ease_out_elastic(t: f64) -> f64 {
if t == 0.0 {
0.0
} else if t == 1.0 {
1.0
} else {
let c4 = (2.0 * PI) / 3.0;
2.0_f64.powf(-10.0 * t) * ((t * 10.0 - 0.75) * c4).sin() + 1.0
}
}
pub fn ease_in_out_elastic(t: f64) -> f64 {
if t == 0.0 {
0.0
} else if t == 1.0 {
1.0
} else {
let c5 = (2.0 * PI) / 4.5;
if t < 0.5 {
-(2.0_f64.powf(20.0 * t - 10.0) * ((20.0 * t - 11.125) * c5).sin()) / 2.0
} else {
(2.0_f64.powf(-20.0 * t + 10.0) * ((20.0 * t - 11.125) * c5).sin()) / 2.0 + 1.0
}
}
}
pub fn ease_out_bounce(t: f64) -> f64 {
let n1 = 7.5625;
let d1 = 2.75;
if t < 1.0 / d1 {
n1 * t * t
} else if t < 2.0 / d1 {
let t = t - 1.5 / d1;
n1 * t * t + 0.75
} else if t < 2.5 / d1 {
let t = t - 2.25 / d1;
n1 * t * t + 0.9375
} else {
let t = t - 2.625 / d1;
n1 * t * t + 0.984375
}
}
pub fn ease_in_bounce(t: f64) -> f64 {
1.0 - ease_out_bounce(1.0 - t)
}
pub fn ease_in_out_bounce(t: f64) -> f64 {
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
}
}
pub fn ease_in_back(t: f64) -> f64 {
let c1 = 1.70158;
let c3 = c1 + 1.0;
c3 * t * t * t - c1 * t * t
}
pub fn ease_out_back(t: f64) -> f64 {
let c1 = 1.70158;
let c3 = c1 + 1.0;
1.0 + c3 * (t - 1.0).powi(3) + c1 * (t - 1.0).powi(2)
}
pub fn ease_in_out_back(t: f64) -> f64 {
let c1 = 1.70158;
let c2 = c1 * 1.525;
if t < 0.5 {
((2.0 * t).powi(2) * ((c2 + 1.0) * 2.0 * t - c2)) / 2.0
} else {
((2.0 * t - 2.0).powi(2) * ((c2 + 1.0) * (t * 2.0 - 2.0) + c2) + 2.0) / 2.0
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_f64_interpolate() {
assert_eq!(0.0_f64.interpolate(&10.0, 0.0), 0.0);
assert_eq!(0.0_f64.interpolate(&10.0, 0.5), 5.0);
assert_eq!(0.0_f64.interpolate(&10.0, 1.0), 10.0);
}
#[test]
fn test_vec_interpolate() {
let a = vec![0.0, 0.0];
let b = vec![10.0, 20.0];
let result = a.interpolate(&b, 0.5);
assert_eq!(result, vec![5.0, 10.0]);
}
#[test]
fn test_array_interpolate() {
let a = [0.0, 0.0, 0.0];
let b = [10.0, 20.0, 30.0];
let result = a.interpolate(&b, 0.25);
assert_eq!(result, [2.5, 5.0, 7.5]);
}
#[test]
fn test_linear_easing() {
assert_eq!(easing::linear(0.0), 0.0);
assert_eq!(easing::linear(0.5), 0.5);
assert_eq!(easing::linear(1.0), 1.0);
}
#[test]
fn test_ease_in_quad() {
assert_eq!(easing::ease_in_quad(0.0), 0.0);
assert_eq!(easing::ease_in_quad(0.5), 0.25);
assert_eq!(easing::ease_in_quad(1.0), 1.0);
}
#[test]
fn test_ease_out_quad() {
assert_eq!(easing::ease_out_quad(0.0), 0.0);
assert_eq!(easing::ease_out_quad(0.5), 0.75);
assert_eq!(easing::ease_out_quad(1.0), 1.0);
}
#[test]
fn test_easing_boundary_values() {
type EasingEntry = (&'static str, fn(f64) -> f64);
let easings: Vec<EasingEntry> = vec![
("linear", easing::linear),
("ease_in_quad", easing::ease_in_quad),
("ease_out_quad", easing::ease_out_quad),
("ease_in_out_quad", easing::ease_in_out_quad),
("ease_in_cubic", easing::ease_in_cubic),
("ease_out_cubic", easing::ease_out_cubic),
("ease_in_out_cubic", easing::ease_in_out_cubic),
("ease_in_sine", easing::ease_in_sine),
("ease_out_sine", easing::ease_out_sine),
("ease_in_out_sine", easing::ease_in_out_sine),
("ease_in_expo", easing::ease_in_expo),
("ease_out_expo", easing::ease_out_expo),
("ease_in_out_expo", easing::ease_in_out_expo),
("ease_in_elastic", easing::ease_in_elastic),
("ease_out_elastic", easing::ease_out_elastic),
("ease_in_out_elastic", easing::ease_in_out_elastic),
("ease_in_bounce", easing::ease_in_bounce),
("ease_out_bounce", easing::ease_out_bounce),
("ease_in_out_bounce", easing::ease_in_out_bounce),
("ease_in_back", easing::ease_in_back),
("ease_out_back", easing::ease_out_back),
("ease_in_out_back", easing::ease_in_out_back),
];
for (name, f) in easings {
let at_0 = f(0.0);
let at_1 = f(1.0);
assert!(
(at_0 - 0.0).abs() < 1e-10,
"{} at 0.0 = {} (expected 0.0)",
name,
at_0
);
assert!(
(at_1 - 1.0).abs() < 1e-10,
"{} at 1.0 = {} (expected 1.0)",
name,
at_1
);
}
}
#[test]
fn test_ease_in_slower_than_linear() {
let t = 0.25;
assert!(easing::ease_in_quad(t) < t);
assert!(easing::ease_in_cubic(t) < t);
assert!(easing::ease_in_quart(t) < t);
}
#[test]
fn test_ease_out_faster_than_linear() {
let t = 0.25;
assert!(easing::ease_out_quad(t) > t);
assert!(easing::ease_out_cubic(t) > t);
assert!(easing::ease_out_quart(t) > t);
}
}