use num_traits::{Float, NumCast};
use super::{Approximation, Curve};
#[derive(Copy, Clone, Debug)]
pub struct Spring<T>
where
T: Float,
{
pub from_value: T,
pub to_value: T,
pub stiffness: T,
pub damping: T,
pub mass: T,
pub initial_velocity: T,
pub allows_overdamping: bool,
pub overshoot_clamping: bool,
}
impl<T> Default for Spring<T>
where
T: Float,
{
fn default() -> Self {
Spring {
from_value: T::zero(),
to_value: T::zero(),
stiffness: <T as NumCast>::from(100.0).unwrap(),
damping: <T as NumCast>::from(10.0).unwrap(),
mass: <T as NumCast>::from(1.0).unwrap(),
initial_velocity: <T as NumCast>::from(1.0).unwrap(),
allows_overdamping: false,
overshoot_clamping: false,
}
}
}
impl<T> Curve for Spring<T>
where
T: Float,
{
type Value = T;
type Velocity = T;
fn approximate(&self, time: f32) -> Approximation<T> {
let time = <T as NumCast>::from(time * 1000.0).unwrap();
let c = self.damping;
let m = self.mass;
let k = self.stiffness;
let from_value = self.from_value;
let to_value = self.to_value;
let v0 = -self.initial_velocity;
assert!(m > T::zero(), "Mass value must be greater than 0.");
assert!(k > T::zero(), "Stiffness value must be greater than 0.");
assert!(c > T::zero(), "Damping value must be greater than 0.");
let mut zeta = c / (<T as NumCast>::from(2.0).unwrap() * (k * m).sqrt());
let omega0 = (k / m).sqrt() / <T as NumCast>::from(1000.0).unwrap();
let omega1 = omega0 * (T::one() - zeta * zeta).sqrt();
let omega2 = omega0 * (zeta * zeta - T::one()).sqrt();
let x0 = to_value - from_value;
if zeta > T::one() && !self.allows_overdamping {
zeta = T::one();
}
if zeta < T::one() {
let envelope = (-zeta * omega0 * time).exp();
let oscillation = to_value
- envelope
* ((v0 + zeta * omega0 * x0) / omega1 * (omega1 * time).sin()
+ x0 * (omega1 * time).cos());
let velocity = zeta
* omega0
* envelope
* ((omega1 * time).sin() * (v0 + zeta * omega0 * x0) / omega1
+ x0 * (omega1 * time).cos())
- envelope
* ((omega1 * time).cos() * (v0 + zeta * omega0 * x0)
- omega1 * x0 * (omega1 * time).sin());
Approximation {
value: oscillation,
velocity,
}
} else if zeta == T::one() {
let envelope = (-omega0 * time).exp();
let oscillation = to_value - envelope * (x0 + (v0 + omega0 * x0) * time);
let velocity =
envelope * (v0 * (time * omega0 - T::one()) + time * x0 * (omega0 * omega0));
Approximation {
value: oscillation,
velocity,
}
} else {
let envelope = (-zeta * omega0 * time).exp();
let oscillation = to_value
- envelope
* ((v0 + zeta * omega0 * x0) * (omega2 * time).sinh()
+ omega2 * x0 * (omega2 * time).cosh())
/ omega2;
let velocity = envelope
* zeta
* omega0
* ((omega2 * time).sinh() * (v0 + zeta * omega0 * x0)
+ x0 * omega2 * (omega2 * time).cosh())
/ omega2
- envelope
* (omega2 * (omega2 * time).cosh() * (v0 + zeta * omega0 * x0)
+ omega2 * omega2 * x0 * (omega2 * time).sinh())
/ omega2;
Approximation {
value: oscillation,
velocity,
}
}
}
fn target(&self) -> T {
self.to_value
}
}
#[cfg(test)]
mod tests {
use super::super::Sampler;
use super::*;
#[test]
fn test_spring() {
let spring = Spring {
from_value: 0.0,
to_value: 320.0,
stiffness: 100.0,
damping: 10.0,
mass: 1.0,
initial_velocity: 0.0,
overshoot_clamping: false,
allows_overdamping: false,
};
let y = Sampler::new(&spring, 20.0)
.map(|approx| approx.value)
.collect::<Vec<_>>();
println!("y: {:?}", y);
println!("y: {:?}", y.len());
}
#[test]
fn test_spring_ios() {
let spring = Spring {
from_value: 0.0,
to_value: 320.0,
stiffness: 1000.0,
damping: 500.0,
mass: 3.0,
initial_velocity: 0.0,
overshoot_clamping: false,
allows_overdamping: true,
};
let y = Sampler::new(&spring, 20.0)
.map(|approx| approx.value)
.collect::<Vec<_>>();
println!("y: {:?}", y);
println!("y: {:?}", y.len());
}
}