use std::time::Duration;
use std::sync::Arc;
pub mod particles;
pub use particles::*;
#[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 struct ActiveAnimation {
pub animation: Animation,
pub elapsed: Duration,
pub is_finished: bool,
pub current_value: f32,
solver: Option<SleipnirSolver>,
child_states: Vec<ActiveAnimation>,
current_index: usize,
}
impl ActiveAnimation {
pub fn new(animation: Animation) -> Self {
Self {
animation,
elapsed: Duration::ZERO,
is_finished: false,
current_value: 0.0,
solver: None,
child_states: Vec::new(),
current_index: 0,
}
}
pub fn update(&mut self, dt: Duration, start_val: f32, end_val: f32) -> f32 {
if self.is_finished { return end_val; }
self.elapsed += dt;
let t = self.elapsed.as_secs_f32();
match &self.animation {
Animation::Ginnungagap => {
self.is_finished = true;
self.current_value = end_val;
}
Animation::Linear { duration } => {
let d = duration.as_secs_f32();
if t >= d {
self.is_finished = true;
self.current_value = end_val;
} else {
self.current_value = start_val + (end_val - start_val) * (t / d);
}
}
Animation::Sleipnir(params) => {
let solver = self.solver.get_or_insert_with(|| SleipnirSolver::new(*params, end_val, start_val));
self.current_value = solver.tick(dt.as_secs_f32());
if solver.is_settled() {
self.is_finished = true;
}
}
Animation::Sequence(anims) => {
if self.current_index >= anims.len() {
self.is_finished = true;
self.current_value = end_val;
} else {
if self.child_states.is_empty() {
self.child_states = anims.iter().map(|a| ActiveAnimation::new(a.clone())).collect();
}
let child = &mut self.child_states[self.current_index];
self.current_value = child.update(dt, start_val, end_val);
if child.is_finished {
self.current_index += 1;
if self.current_index >= anims.len() {
self.is_finished = true;
}
}
}
}
Animation::Parallel(anims) => {
if self.child_states.is_empty() {
self.child_states = anims.iter().map(|a| ActiveAnimation::new(a.clone())).collect();
}
let mut all_finished = true;
let mut sum_val = 0.0;
for child in &mut self.child_states {
sum_val += child.update(dt, start_val, end_val);
if !child.is_finished {
all_finished = false;
}
}
self.current_value = if !anims.is_empty() { sum_val / anims.len() as f32 } else { end_val };
if all_finished {
self.is_finished = true;
}
}
_ => {
self.is_finished = true;
self.current_value = end_val;
}
}
self.current_value
}
}
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);
}
#[test]
fn test_animation_sequence_execution() {
let anims = vec![
Animation::Linear { duration: Duration::from_millis(100) },
Animation::Linear { duration: Duration::from_millis(100) },
];
let mut active = ActiveAnimation::new(Animation::Sequence(anims));
active.update(Duration::from_millis(50), 0.0, 100.0);
assert!(!active.is_finished);
assert_eq!(active.current_index, 0);
active.update(Duration::from_millis(60), 0.0, 100.0);
assert!(!active.is_finished);
assert_eq!(active.current_index, 1);
active.update(Duration::from_millis(100), 0.0, 100.0);
assert!(active.is_finished);
}
}