use crate::class::Eps;
use crate::easing::Easing;
use crate::lanes::Lanes;
use crate::spring::{MAX_SETTLE, Spring, finite_f32, sane_state};
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Curve {
Spring(Spring),
Tween {
duration: f32,
easing: Easing,
},
Decay {
k: f32,
},
Instant,
}
impl Default for Curve {
fn default() -> Self {
Curve::DEFAULT
}
}
impl From<Spring> for Curve {
fn from(s: Spring) -> Self {
Curve::Spring(s)
}
}
const DEFAULT_TWEEN: f32 = 0.3;
const MAX_TWEEN: f32 = 60.0;
const DEFAULT_DECAY: f32 = 2.0;
impl Curve {
pub const DEFAULT: Curve = Curve::Spring(Spring::DEFAULT);
pub const fn tween(duration: f32, easing: Easing) -> Curve {
Curve::Tween { duration, easing }.sanitized()
}
pub fn decay(rate_per_ms: f32) -> Curve {
Curve::Decay { k: crate::gesture::decay_k(rate_per_ms) }
}
pub const fn sanitized(self) -> Curve {
match self {
Curve::Spring(s) => {
let s = Spring::new(s.duration(), s.bounce());
if s.is_none() { Curve::Instant } else { Curve::Spring(s) }
}
Curve::Tween { duration, easing } => {
let d = if duration.is_nan() { DEFAULT_TWEEN } else { duration };
if d <= 0.0 {
Curve::Instant
} else {
let [x1, y1, x2, y2] = easing.points();
Curve::Tween { duration: d.min(MAX_TWEEN), easing: Easing::cubic_bezier(x1, y1, x2, y2) }
}
}
Curve::Decay { k } => Curve::Decay { k: if k.is_nan() { DEFAULT_DECAY } else { k.clamp(0.05, 1000.0) } },
Curve::Instant => Curve::Instant,
}
}
pub const fn is_instant(self) -> bool {
matches!(self.sanitized(), Curve::Instant)
}
pub fn rest(self, x0: f32, v0: f32, target: f32) -> f32 {
let (x0, v0, target) = sane_state(x0, v0, target);
match self.sanitized() {
Curve::Decay { k } => finite_f32(f64::from(x0) + f64::from(v0) / f64::from(k), x0),
_ => target,
}
}
pub fn sample(self, x0: f32, v0: f32, target: f32, t: f32) -> (f32, f32) {
let (x0, v0, target) = sane_state(x0, v0, target);
if t.is_nan() || t <= 0.0 {
return (x0, v0);
}
match self.sanitized() {
Curve::Spring(s) => s.sample(x0, v0, target, t),
Curve::Tween { duration, easing } => tween(duration, easing, x0, v0, target, t),
Curve::Decay { k } => {
let (k, v) = (f64::from(k), f64::from(v0));
let e = (-k * f64::from(t)).exp();
(finite_f32(f64::from(x0) + v / k * (1.0 - e), x0), finite_f32(v * e, 0.0))
}
Curve::Instant => (target, 0.0),
}
}
pub fn settle_time(self, x0: f32, v0: f32, target: f32, eps: Eps) -> f32 {
self.settle_time_lanes([(x0, v0, target)], eps)
}
pub fn settle_time_lanes(self, lanes: impl IntoIterator<Item = (f32, f32, f32)>, eps: Eps) -> f32 {
match self.sanitized() {
Curve::Spring(s) => s.settle_time_lanes(lanes, eps),
Curve::Tween { duration, .. } => duration,
Curve::Decay { k } => {
let eps = eps.sanitized();
let v = lanes.into_iter().map(|(x0, v0, target)| sane_state(x0, v0, target).1.abs()).fold(0.0_f32, f32::max);
let (k, v) = (f64::from(k), f64::from(v));
let t = (v / (k * f64::from(eps.pos))).ln().max((v / f64::from(eps.vel)).ln()).max(0.0) / k;
let t = t * (1.0 + 1e-4) + 1e-4;
if t.is_finite() { (t as f32).min(MAX_SETTLE) } else { 0.0 }
}
Curve::Instant => 0.0,
}
}
pub(crate) fn sample_lanes(self, from: &Lanes, vel: &Lanes, target: &Lanes, n: usize, t: f32) -> (Lanes, Lanes) {
let mut x = *target;
let mut v = [0.0; crate::lanes::MAX_LANES];
let lanes = x.iter_mut().zip(v.iter_mut()).zip(from.iter().zip(vel.iter()));
for ((x, v), (&from, &vel)) in lanes.take(n) {
let (px, pv) = self.sample(from, vel, *x, t);
*x = px;
*v = pv;
}
(x, v)
}
}
fn tween(duration: f32, easing: Easing, x0: f32, v0: f32, target: f32, t: f32) -> (f32, f32) {
if t >= duration {
return (target, 0.0);
}
let d = f64::from(duration);
let t = f64::from(t);
let u = t / d;
let delta = f64::from(target) - f64::from(x0);
let w = if v0 == 0.0 { 0.0 } else { f64::from(v0) - delta * f64::from(easing.slope(0.0)) / d };
let uf = u as f32;
let x = f64::from(x0) + delta * f64::from(easing.ease(uf)) + w * t * (1.0 - u) * (1.0 - u);
let v = delta * f64::from(easing.slope(uf)) / d + w * (1.0 - u) * (1.0 - 3.0 * u);
(finite_f32(x, target), finite_f32(v, 0.0))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn tween_keeps_start_velocity_and_lands() {
for e in [Easing::LINEAR, Easing::EASE, Easing::EASE_OUT] {
let c = Curve::tween(0.5, e);
let (x, v) = c.sample(0.0, 7.0, 10.0, 1e-6);
assert!(x.abs() < 1e-3);
assert!((v - 7.0).abs() < 5e-2, "{e:?} {v}");
assert_eq!(c.sample(0.0, 7.0, 10.0, 0.5), (10.0, 0.0));
let (x, _) = c.sample(0.0, 7.0, 10.0, 0.4999);
assert!((x - 10.0).abs() < 1e-2, "{e:?} {x}");
}
}
#[test]
fn decay_rests_at_projection() {
let c = Curve::Decay { k: 4.0 };
assert!((c.rest(1.0, 8.0, 99.0) - 3.0).abs() < 1e-6);
let t = c.settle_time(1.0, 8.0, 99.0, Eps::new(1e-3, 1e-2));
let (x, v) = c.sample(1.0, 8.0, 99.0, t);
assert!((x - 3.0).abs() <= 1e-3 && v.abs() <= 1e-2);
}
#[test]
fn sanitize() {
assert!(Curve::tween(0.0, Easing::EASE).is_instant());
assert!(Curve::Spring(Spring::NONE).is_instant());
assert_eq!(Curve::Decay { k: f32::NAN }.sanitized(), Curve::Decay { k: 2.0 });
assert_eq!(Curve::tween(f32::INFINITY, Easing::EASE), Curve::Tween { duration: 60.0, easing: Easing::EASE });
}
}