1use crate::class::Eps;
12use core::f64::consts::TAU;
13
14pub const MAX_SETTLE: f32 = 120.0;
16
17#[derive(Clone, Copy, Debug, PartialEq)]
22pub struct Spring {
23 duration: f32,
24 bounce: f32,
25}
26
27impl Default for Spring {
28 fn default() -> Self {
29 Spring::DEFAULT
30 }
31}
32
33#[derive(Clone, Copy, Debug)]
35struct Params {
36 alpha: f64,
38 w0: f64,
40 kind: Kind,
41}
42
43#[derive(Clone, Copy, Debug)]
44enum Kind {
45 Under { wd: f64 },
47 Critical,
49 Over { s: f64 },
51}
52
53impl Spring {
54 pub const MIN_DURATION: f32 = 0.01;
56 pub const MAX_DURATION: f32 = 60.0;
58 pub const MAX_BOUNCE: f32 = 0.95;
60 pub const DEFAULT: Spring = Spring { duration: 0.3, bounce: 0.0 };
62 pub const NONE: Spring = Spring { duration: 0.0, bounce: 0.0 };
64
65 pub const fn new(duration: f32, bounce: f32) -> Spring {
69 let duration = if duration.is_nan() {
70 Self::DEFAULT.duration
71 } else if duration <= 0.0 {
72 0.0
73 } else {
74 duration.clamp(Self::MIN_DURATION, Self::MAX_DURATION)
75 };
76 let bounce = if bounce.is_nan() { 0.0 } else { bounce.clamp(-Self::MAX_BOUNCE, Self::MAX_BOUNCE) };
77 Spring { duration, bounce }
78 }
79
80 pub const fn from_response(response: f32, damping_ratio: f32) -> Spring {
83 let zeta = if damping_ratio.is_nan() { 1.0 } else { damping_ratio };
84 let bounce = if zeta <= 0.0 {
85 Self::MAX_BOUNCE
86 } else if zeta <= 1.0 {
87 1.0 - zeta
88 } else {
89 1.0 / zeta - 1.0
90 };
91 Spring::new(response, bounce)
92 }
93
94 pub const fn duration(self) -> f32 {
96 self.duration
97 }
98
99 pub const fn bounce(self) -> f32 {
101 self.bounce
102 }
103
104 pub const fn is_none(self) -> bool {
106 self.duration <= 0.0
107 }
108
109 pub fn damping_ratio(self) -> f32 {
111 zeta(self.bounce) as f32
112 }
113
114 pub fn stiffness(self) -> f32 {
116 self.params().map_or(0.0, |p| (p.w0 * p.w0) as f32)
117 }
118
119 pub fn damping(self) -> f32 {
121 self.params().map_or(0.0, |p| (2.0 * p.alpha) as f32)
122 }
123
124 fn params(self) -> Option<Params> {
125 if self.is_none() {
126 return None;
127 }
128 let w0 = TAU / f64::from(self.duration);
129 let zeta = zeta(self.bounce);
130 let alpha = zeta * w0;
131 let kind = if zeta < 1.0 {
132 let wd = w0 * (1.0 - zeta * zeta).sqrt();
133 if wd > 0.0 { Kind::Under { wd } } else { Kind::Critical }
134 } else if zeta > 1.0 {
135 let s = w0 * (zeta * zeta - 1.0).sqrt();
136 if s > 0.0 { Kind::Over { s } } else { Kind::Critical }
137 } else {
138 Kind::Critical
139 };
140 Some(Params { alpha, w0, kind })
141 }
142
143 pub fn sample(self, x0: f32, v0: f32, target: f32, t: f32) -> (f32, f32) {
148 let (x0, v0, target) = sane_state(x0, v0, target);
149 if t.is_nan() || t <= 0.0 {
150 return (x0, v0);
151 }
152 let Some(p) = self.params() else { return (target, 0.0) };
153 let (x, v) = p.sample(f64::from(x0), f64::from(v0), f64::from(target), f64::from(t));
154 (finite_f32(x, target), finite_f32(v, 0.0))
155 }
156
157 pub fn settle_time(self, from: f32, v0: f32, target: f32, eps: Eps) -> f32 {
161 self.settle_time_lanes([(from, v0, target)], eps)
162 }
163
164 pub fn settle_time_lanes(self, lanes: impl IntoIterator<Item = (f32, f32, f32)>, eps: Eps) -> f32 {
168 let Some(p) = self.params() else { return 0.0 };
169 let mut bounds = Bounds::default();
170 for (from, v0, target) in lanes {
171 let (from, v0, target) = sane_state(from, v0, target);
172 bounds = bounds.max(p.bounds(f64::from(from) - f64::from(target), f64::from(v0)));
173 }
174 let t = p.solve(&bounds, eps.sanitized());
175 let t = t * (1.0 + 1e-4) + 1e-4;
177 if t.is_finite() { (t as f32).clamp(0.0, MAX_SETTLE) } else { MAX_SETTLE }
178 }
179}
180
181#[derive(Clone, Copy, Debug, Default)]
184struct Bounds {
185 y_a: f64,
187 y_b: f64,
188 v_a: f64,
189 v_b: f64,
190 y_e: f64,
193 y_f: f64,
194 v_e: f64,
195 v_f: f64,
196}
197
198impl Bounds {
199 fn max(self, o: Bounds) -> Bounds {
200 Bounds {
201 y_a: self.y_a.max(o.y_a),
202 y_b: self.y_b.max(o.y_b),
203 v_a: self.v_a.max(o.v_a),
204 v_b: self.v_b.max(o.v_b),
205 y_e: self.y_e.max(o.y_e),
206 y_f: self.y_f.max(o.y_f),
207 v_e: self.v_e.max(o.v_e),
208 v_f: self.v_f.max(o.v_f),
209 }
210 }
211}
212
213impl Params {
214 fn decayed_c_s(&self, t: f64) -> (f64, f64) {
218 match self.kind {
219 Kind::Under { wd } => {
220 let e = (-self.alpha * t).exp();
221 let (sin, cos) = (wd * t).sin_cos();
222 (e * cos, e * sin / wd)
223 }
224 Kind::Critical => {
225 let e = (-self.w0 * t).exp();
226 (e, e * t)
227 }
228 Kind::Over { s } => {
229 let e1 = ((s - self.alpha) * t).exp();
230 let e2 = (-(s + self.alpha) * t).exp();
231 let x = s * t;
232 let es = if x < 1e-3 {
233 (-self.alpha * t).exp() * t * (1.0 + x * x / 6.0)
235 } else {
236 (e1 - e2) / (2.0 * s)
237 };
238 ((e1 + e2) * 0.5, es)
239 }
240 }
241 }
242
243 fn sample(&self, x0: f64, v0: f64, target: f64, t: f64) -> (f64, f64) {
244 let y0 = x0 - target;
245 let (ec, es) = self.decayed_c_s(t);
246 let y = y0 * ec + (v0 + self.alpha * y0) * es;
247 let v = v0 * ec - (self.alpha * v0 + self.w0 * self.w0 * y0) * es;
248 (target + y, v)
249 }
250
251 fn bounds(&self, y0: f64, v0: f64) -> Bounds {
254 let w2 = self.w0 * self.w0;
255 let by = v0 + self.alpha * y0;
257 let bv = self.alpha * v0 + w2 * y0;
258 let (y_e, y_f, v_e, v_f) = match self.kind {
259 Kind::Under { wd } => ((y0 * y0 + (by / wd) * (by / wd)).sqrt(), 0.0, (v0 * v0 + (bv / wd) * (bv / wd)).sqrt(), 0.0),
261 Kind::Critical => (0.0, 0.0, 0.0, 0.0),
262 Kind::Over { s } => {
264 let (r1, r2) = (s - self.alpha, -s - self.alpha);
265 let c2 = (v0 - r1 * y0) / (r2 - r1);
266 let c1 = y0 - c2;
267 (c1.abs(), c2.abs(), (r1 * c1).abs(), (r2 * c2).abs())
268 }
269 };
270 Bounds { y_a: y0.abs(), y_b: by.abs(), v_a: v0.abs(), v_b: bv.abs(), y_e, y_f, v_e, v_f }
271 }
272
273 fn solve(&self, b: &Bounds, eps: Eps) -> f64 {
276 let (ep, ev) = (f64::from(eps.pos), f64::from(eps.vel));
277 let (ty, tv) = match self.kind {
278 Kind::Under { .. } => (
280 linear_exp_root(b.y_a, b.y_b, self.alpha, ep).min(exp_root(b.y_e, self.alpha, ep)),
281 linear_exp_root(b.v_a, b.v_b, self.alpha, ev).min(exp_root(b.v_e, self.alpha, ev)),
282 ),
283 Kind::Critical => (linear_exp_root(b.y_a, b.y_b, self.w0, ep), linear_exp_root(b.v_a, b.v_b, self.w0, ev)),
284 Kind::Over { s } => {
286 let (slow, fast) = (self.alpha - s, self.alpha + s);
287 (
288 linear_exp_root(b.y_a, b.y_b, slow, ep).min(two_exp_root(b.y_e, slow, b.y_f, fast, ep)),
289 linear_exp_root(b.v_a, b.v_b, slow, ev).min(two_exp_root(b.v_e, slow, b.v_f, fast, ev)),
290 )
291 }
292 };
293 let t = ty.max(tv);
294 if t.is_nan() { f64::from(MAX_SETTLE) } else { t.max(0.0) }
295 }
296}
297
298fn zeta(bounce: f32) -> f64 {
299 let b = f64::from(bounce);
300 if b >= 0.0 { 1.0 - b } else { 1.0 / (1.0 + b) }
301}
302
303fn exp_root(a: f64, r: f64, eps: f64) -> f64 {
305 if a.is_nan() || a <= eps {
306 return 0.0;
307 }
308 if r.is_nan() || r <= 0.0 || !a.is_finite() {
309 return f64::INFINITY;
310 }
311 (a / eps).ln() / r
312}
313
314fn two_exp_root(a: f64, ra: f64, b: f64, rb: f64, eps: f64) -> f64 {
318 let f = |t: f64| a * (-ra * t).exp() + b * (-rb * t).exp() - eps;
319 let (mut lo, mut hi) = (exp_root(a, ra, eps), exp_root(a + b, ra, eps));
321 if !hi.is_finite() || hi <= lo {
322 return hi;
323 }
324 let (mut flo, mut fhi) = (f(lo), f(hi));
325 if flo <= 0.0 {
326 return lo;
328 }
329 let mut side = 0i8;
330 for _ in 0..24 {
331 if flo.is_nan() || fhi.is_nan() || fhi > 0.0 || hi - lo < 1e-6 * (1.0 + hi) {
332 break;
333 }
334 let t = (lo * fhi - hi * flo) / (fhi - flo);
335 let t = if t > lo && t < hi { t } else { (lo + hi) * 0.5 };
336 let ft = f(t);
337 if ft > 0.0 {
338 lo = t;
339 flo = ft;
340 if side == -1 {
341 fhi *= 0.5;
342 }
343 side = -1;
344 } else {
345 hi = t;
346 fhi = ft;
347 if side == 1 {
348 flo *= 0.5;
349 }
350 side = 1;
351 }
352 }
353 hi
354}
355
356fn linear_exp_root(a: f64, b: f64, r: f64, eps: f64) -> f64 {
360 if b.is_nan() || b <= 0.0 {
361 return exp_root(a, r, eps);
362 }
363 if r.is_nan() || r <= 0.0 || !a.is_finite() || !b.is_finite() {
364 return f64::INFINITY;
365 }
366 let big = a + 2.0 * b / r;
368 if big.is_nan() || big <= eps {
369 return 0.0;
370 }
371 let mut t = 2.0 * (big / eps).ln() / r;
372 let target = eps.ln();
373 for _ in 0..16 {
374 let h = (a + b * t).ln() - r * t - target;
375 let dh = b / (a + b * t) - r;
376 if dh.is_nan() || dh >= 0.0 {
377 return if a <= eps { 0.0 } else { t };
379 }
380 let next = t - h / dh;
381 if !next.is_finite() || next >= t {
382 break;
383 }
384 let step = t - next;
385 t = next;
386 if step < 1e-9 * (1.0 + t) {
387 break;
388 }
389 }
390 t.max(0.0)
391}
392
393pub(crate) fn sane_state(x0: f32, v0: f32, target: f32) -> (f32, f32, f32) {
396 let target = if target.is_finite() {
397 target
398 } else if x0.is_finite() {
399 x0
400 } else {
401 0.0
402 };
403 let x0 = if x0.is_finite() { x0 } else { target };
404 let v0 = if v0.is_finite() { v0 } else { 0.0 };
405 (x0, v0, target)
406}
407
408pub(crate) fn finite_f32(x: f64, fallback: f32) -> f32 {
410 let y = x as f32;
411 if y.is_finite() { y } else { fallback }
412}
413
414#[cfg(test)]
415mod tests {
416 use super::*;
417
418 #[test]
419 fn constructor_clamps() {
420 assert_eq!(Spring::new(f32::NAN, f32::NAN), Spring::DEFAULT);
421 assert!(Spring::new(-1.0, 0.0).is_none());
422 assert!(Spring::new(f32::NEG_INFINITY, 0.0).is_none());
423 assert_eq!(Spring::new(f32::INFINITY, 2.0).duration(), Spring::MAX_DURATION);
424 assert_eq!(Spring::new(1e-9, -2.0).duration(), Spring::MIN_DURATION);
425 assert_eq!(Spring::new(1.0, -2.0).bounce(), -Spring::MAX_BOUNCE);
426 assert_eq!(Spring::from_response(0.5, 1.0), Spring::new(0.5, 0.0));
427 assert!((Spring::from_response(0.5, 0.7).bounce() - 0.3).abs() < 1e-6);
428 assert!((Spring::from_response(0.5, 2.0).damping_ratio() - 2.0).abs() < 1e-5);
429 }
430
431 #[test]
432 fn critical_has_no_overshoot() {
433 let s = Spring::new(0.4, 0.0);
434 let mut t = 0.0;
435 while t < 2.0 {
436 let (x, _) = s.sample(0.0, 0.0, 1.0, t);
437 assert!(x <= 1.0 + 1e-6, "{x} at {t}");
438 t += 0.001;
439 }
440 }
441
442 #[test]
443 fn linear_exp_root_is_last_crossing() {
444 for &(a, b, r) in &[(1.0, 10.0, 3.0), (0.0, 5.0, 1.0), (2.0, 0.0, 2.0), (1e-6, 1.0, 50.0)] {
445 let eps = 1e-3;
446 let t = linear_exp_root(a, b, r, eps);
447 let f = |t: f64| (a + b * t) * (-r * t).exp();
448 assert!(f(t) <= eps * (1.0 + 1e-9), "{a} {b} {r}");
449 let mut u = t;
450 while u < t + 20.0 {
451 assert!(f(u) <= eps * (1.0 + 1e-9));
452 u += 0.01;
453 }
454 assert!(f(t - 1e-3) > eps * 0.99 || t < 1e-3);
455 }
456 }
457}