use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct CASpring {
pub response: f32,
pub damping_ratio: f32,
}
impl Default for CASpring {
fn default() -> Self {
Self {
response: 0.35,
damping_ratio: 0.85,
}
}
}
impl CASpring {
pub fn new(response: f32, damping_ratio: f32) -> Self {
Self {
response: response.max(0.0001),
damping_ratio: damping_ratio.max(0.0),
}
}
#[inline]
pub fn from_perceptual(response: f32, damping_ratio: f32) -> Self {
Self::new(response, damping_ratio)
}
pub fn from_frequency(omega_0: f32, damping_ratio: f32) -> Self {
let safe_omega = omega_0.max(0.0001);
let response = 2.0 * std::f32::consts::PI / safe_omega;
Self::new(response, damping_ratio)
}
pub fn from_physical(mass: f32, stiffness: f32, damping: f32) -> Self {
let m = mass.max(0.0001);
let k = stiffness.max(0.0001);
let omega_0 = (k / m).sqrt();
let damping_ratio = damping / (2.0 * (k * m).sqrt());
Self::from_frequency(omega_0, damping_ratio)
}
#[inline]
pub fn omega_0(&self) -> f32 {
2.0 * std::f32::consts::PI / self.response
}
#[inline]
pub fn omega_d(&self) -> f32 {
let zeta = self.damping_ratio;
self.omega_0() * (1.0 - zeta * zeta).abs().sqrt()
}
pub fn settling_duration(&self) -> f32 {
let zeta = self.damping_ratio;
if zeta <= 0.0 {
return f32::INFINITY;
}
let omega0 = self.omega_0();
if zeta <= 1.0 {
6.907755 / (zeta * omega0)
} else {
let alpha = self.omega_d();
let rate = (zeta * omega0 - alpha).max(0.0001);
6.907755 / rate
}
}
#[inline]
pub fn position_at(&self, t: f32) -> f32 {
self.evaluate(t)
}
pub fn overshoot_ratio(&self) -> f32 {
let zeta = self.damping_ratio;
if (0.0..1.0).contains(&zeta) {
(-std::f32::consts::PI * zeta / (1.0 - zeta * zeta).sqrt()).exp()
} else {
0.0
}
}
pub fn evaluate(&self, t: f32) -> f32 {
if t <= 0.0 {
return 0.0;
}
let omega0 = self.omega_0();
let zeta = self.damping_ratio;
if zeta < 1.0 {
let omega_d = self.omega_d();
let decay = (-zeta * omega0 * t).exp();
if decay == 0.0 {
1.0
} else {
let envelope =
decay * ((omega_d * t).cos() + (zeta * omega0 / omega_d) * (omega_d * t).sin());
1.0 - envelope
}
} else if (zeta - 1.0).abs() < 1e-5 {
let decay = (-omega0 * t).exp();
if decay == 0.0 {
1.0
} else {
1.0 - decay * (1.0 + omega0 * t)
}
} else {
let alpha = self.omega_d();
let rate_minus = (zeta * omega0 - alpha).max(0.0);
let rate_plus = zeta * omega0 + alpha;
let exp_minus = (-rate_minus * t).exp();
let exp_plus = (-rate_plus * t).exp();
let c = zeta * omega0 / alpha;
let term = 0.5 * ((1.0 + c) * exp_minus + (1.0 - c) * exp_plus);
1.0 - term
}
}
pub fn velocity(&self, t: f32) -> f32 {
if t <= 0.0 {
return 0.0;
}
let omega0 = self.omega_0();
let zeta = self.damping_ratio;
if zeta < 1.0 {
let omega_d = self.omega_d();
let decay = (-zeta * omega0 * t).exp();
if decay == 0.0 {
0.0
} else {
decay * (omega0 * omega0 / omega_d) * (omega_d * t).sin()
}
} else if (zeta - 1.0).abs() < 1e-5 {
let decay = (-omega0 * t).exp();
if decay == 0.0 {
0.0
} else {
omega0 * omega0 * t * decay
}
} else {
let alpha = self.omega_d();
let rate_minus = (zeta * omega0 - alpha).max(0.0);
let rate_plus = zeta * omega0 + alpha;
let exp_minus = (-rate_minus * t).exp();
let exp_plus = (-rate_plus * t).exp();
(omega0 * omega0 / (2.0 * alpha)) * (exp_minus - exp_plus)
}
}
}