use std::time::Duration;
use std::sync::Arc;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct SleipnirParams {
pub stiffness: f32,
pub damping: f32,
pub mass: f32,
}
impl SleipnirParams {
pub fn snappy() -> Self { Self { stiffness: 230.0, damping: 22.0, mass: 1.0 } }
pub fn fluid() -> Self { Self { stiffness: 170.0, damping: 26.0, mass: 1.0 } }
pub fn heavy() -> Self { Self { stiffness: 90.0, damping: 20.0, mass: 1.0 } }
pub fn bouncy() -> Self { Self { stiffness: 190.0, damping: 14.0, mass: 1.0 } }
}
impl Default for SleipnirParams {
fn default() -> Self { Self::fluid() }
}
#[derive(Debug, Clone, PartialEq)]
pub struct Keyframe {
pub value: f32,
pub time: Duration,
pub easing: Easing,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Easing {
Linear,
EaseIn,
EaseOut,
EaseInOut,
}
#[derive(Clone)]
pub enum Animation {
Ginnungagap,
Linear { duration: Duration },
Sleipnir(SleipnirParams),
Hybrid {
keyframes: Vec<Keyframe>,
settle: SleipnirParams,
},
Parallel(Vec<Animation>),
Sequence(Vec<Animation>),
Stagger {
animations: Vec<Animation>,
interval: Duration,
},
BifrostFade { duration: Duration },
MjolnirSlice { duration: Duration },
MjolnirShatter {
duration: Duration,
pieces: u32,
force: f32,
},
}
pub struct RubberBand {
pub min: f32,
pub max: f32,
pub constant: f32,
}
impl RubberBand {
pub fn new(min: f32, max: f32) -> Self {
Self { min, max, constant: 0.55 }
}
pub fn solve(&self, input: f32) -> f32 {
if input < self.min {
self.min - self.apply_resistance(self.min - input)
} else if input > self.max {
self.max + self.apply_resistance(input - self.max)
} else {
input
}
}
fn apply_resistance(&self, delta: f32) -> f32 {
(delta * self.constant).atan() * (1.0 / self.constant)
}
}
pub struct Motion {
pub animation: Animation,
pub on_start: Option<Arc<dyn Fn() + Send + Sync>>,
pub on_settle: Option<Arc<dyn Fn() + Send + Sync>>,
pub on_interrupt: Option<Arc<dyn Fn() + Send + Sync>>,
}
impl Motion {
pub fn new(animation: Animation) -> Self {
Self {
animation,
on_start: None,
on_settle: None,
on_interrupt: None,
}
}
}
pub struct SleipnirSolver {
params: SleipnirParams,
target: f32,
state: SolverState,
}
#[derive(Debug, Clone, Copy, PartialEq)]
struct SolverState {
x: f32,
v: f32,
}
impl SleipnirSolver {
pub fn new(params: SleipnirParams, target: f32, current: f32) -> Self {
Self {
params,
target,
state: SolverState { x: current, v: 0.0 },
}
}
pub fn tick(&mut self, dt: f32) -> f32 {
let a = self.evaluate(self.state, 0.0, SolverState { x: 0.0, v: 0.0 });
let b = self.evaluate(self.state, dt * 0.5, a);
let c = self.evaluate(self.state, dt * 0.5, b);
let d = self.evaluate(self.state, dt, c);
let dxdt = 1.0 / 6.0 * (a.x + 2.0 * (b.x + c.x) + d.x);
let dvdt = 1.0 / 6.0 * (a.v + 2.0 * (b.v + c.v) + d.v);
self.state.x += dxdt * dt;
self.state.v += dvdt * dt;
self.state.x
}
fn evaluate(&self, initial: SolverState, dt: f32, d: SolverState) -> SolverState {
let state = SolverState {
x: initial.x + d.x * dt,
v: initial.v + d.v * dt,
};
let force = -self.params.stiffness * (state.x - self.target) - self.params.damping * state.v;
let mass = self.params.mass.max(0.001);
SolverState { x: state.v, v: force / mass }
}
pub fn is_settled(&self) -> bool {
(self.state.x - self.target).abs() < 0.001 && self.state.v.abs() < 0.001
}
}
pub trait AnimationValue: Sized + Clone + PartialEq {
fn lerp(&self, other: &Self, t: f32) -> Self;
fn distance(&self, other: &Self) -> f32;
}
impl AnimationValue for f32 {
fn lerp(&self, other: &Self, t: f32) -> Self { self + (other - self) * t }
fn distance(&self, other: &Self) -> f32 { (self - other).abs() }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rubber_band_solving() {
let rb = RubberBand::new(0.0, 100.0);
assert_eq!(rb.solve(50.0), 50.0);
let over = rb.solve(150.0);
assert!(over > 100.0);
assert!(over < 150.0);
let under = rb.solve(-50.0);
assert!(under < 0.0);
assert!(under > -50.0); }
#[test]
fn test_sleipnir_solver_convergence() {
let params = SleipnirParams::snappy();
let mut solver = SleipnirSolver::new(params, 100.0, 0.0);
assert!(!solver.is_settled());
for _ in 0..100 {
solver.tick(0.016);
}
assert!(solver.is_settled());
assert!((solver.state.x - 100.0).abs() < 0.01);
}
}