1pub const RATE: f32 = 240.0;
2pub const STEP: f32 = 1.0 / 240.0;
3
4#[derive(Clone, Copy, Debug, PartialEq)]
5pub struct Rest {
6 pub position: f32,
7 pub velocity: f32,
8}
9
10impl Rest {
11 pub const COARSE: Self = Self {
12 position: 1e-4,
13 velocity: 1e-3,
14 };
15 pub const FINE: Self = Self {
16 position: 1e-5,
17 velocity: 1e-4,
18 };
19}
20
21impl Default for Rest {
22 fn default() -> Self {
23 Self::COARSE
24 }
25}
26
27pub trait Sample: Copy + Default {
28 fn integrate(
29 &mut self,
30 velocity: &mut Self,
31 target: Self,
32 stiffness: f32,
33 damping: f32,
34 h: f32,
35 );
36 fn settled(self, target: Self, velocity: Self, rest: Rest) -> bool;
37 fn lerp(self, to: Self, t: f32) -> Self;
38}
39
40impl Sample for f32 {
41 fn integrate(
42 &mut self,
43 velocity: &mut Self,
44 target: Self,
45 stiffness: f32,
46 damping: f32,
47 h: f32,
48 ) {
49 let force = -stiffness * (*self - target) - damping * *velocity;
50 *velocity += force * h;
51 *self += *velocity * h;
52 }
53
54 fn settled(self, target: Self, velocity: Self, rest: Rest) -> bool {
55 (self - target).abs() < rest.position && velocity.abs() < rest.velocity
56 }
57
58 fn lerp(self, to: Self, t: f32) -> Self {
59 self * (1.0 - t) + to * t
60 }
61}
62
63impl Sample for [f32; 3] {
64 fn integrate(
65 &mut self,
66 velocity: &mut Self,
67 target: Self,
68 stiffness: f32,
69 damping: f32,
70 h: f32,
71 ) {
72 self[0].integrate(&mut velocity[0], target[0], stiffness, damping, h);
73 self[1].integrate(&mut velocity[1], target[1], stiffness, damping, h);
74 self[2].integrate(&mut velocity[2], target[2], stiffness, damping, h);
75 }
76
77 fn settled(self, target: Self, velocity: Self, rest: Rest) -> bool {
78 self[0].settled(target[0], velocity[0], rest)
79 && self[1].settled(target[1], velocity[1], rest)
80 && self[2].settled(target[2], velocity[2], rest)
81 }
82
83 fn lerp(self, to: Self, t: f32) -> Self {
84 [
85 self[0].lerp(to[0], t),
86 self[1].lerp(to[1], t),
87 self[2].lerp(to[2], t),
88 ]
89 }
90}
91
92#[derive(Clone, Copy, Debug, PartialEq)]
93pub struct Spring<T: Sample> {
94 pub value: T,
95 pub velocity: T,
96 pub target: T,
97 pub stiffness: f32,
98 pub damping_ratio: f32,
99 pub rest: Rest,
100}
101
102impl<T: Sample> Spring<T> {
103 pub fn new(stiffness: f32) -> Self {
104 Self {
105 value: T::default(),
106 velocity: T::default(),
107 target: T::default(),
108 stiffness,
109 damping_ratio: 1.0,
110 rest: Rest::default(),
111 }
112 }
113
114 pub fn with_rest(mut self, rest: Rest) -> Self {
115 self.rest = rest;
116 self
117 }
118
119 pub fn at(stiffness: f32, value: T) -> Self {
120 let mut spring = Self::new(stiffness);
121 spring.value = value;
122 spring.target = value;
123 spring
124 }
125
126 pub fn step(&mut self, dt: f32) {
127 let steps = (dt / STEP).ceil().max(1.0) as u32;
128 let h = dt / steps as f32;
129 let damping = 2.0 * self.stiffness.sqrt() * self.damping_ratio;
130 for _ in 0..steps {
131 self.value
132 .integrate(&mut self.velocity, self.target, self.stiffness, damping, h);
133 }
134 if self.settled() {
135 self.value = self.target;
136 self.velocity = T::default();
137 }
138 }
139
140 pub fn settled(&self) -> bool {
141 self.value.settled(self.target, self.velocity, self.rest)
142 }
143}
144
145#[derive(Clone, Copy, Debug, PartialEq)]
146pub struct Cascade<T: Sample> {
147 pub lead: Spring<T>,
148 pub follow: Spring<T>,
149}
150
151impl<T: Sample> Cascade<T> {
152 pub fn new(stiffness: f32, value: T) -> Self {
153 Self {
154 lead: Spring::at(stiffness, value),
155 follow: Spring::at(stiffness, value),
156 }
157 }
158
159 pub fn with_rest(mut self, rest: Rest) -> Self {
160 self.lead.rest = rest;
161 self.follow.rest = rest;
162 self
163 }
164
165 pub fn step(&mut self, dt: f32) {
166 self.lead.step(dt);
167 self.follow.target = self.lead.value;
168 self.follow.step(dt);
169 }
170
171 pub fn value(&self) -> T {
172 self.follow.value
173 }
174
175 pub fn settled(&self) -> bool {
176 self.lead.settled() && self.follow.settled()
177 }
178}
179
180#[derive(Clone, Copy, Debug, PartialEq)]
181pub struct Stepper {
182 time: f64,
183 ticks: u64,
184}
185
186impl Default for Stepper {
187 fn default() -> Self {
188 Self::new()
189 }
190}
191
192impl Stepper {
193 pub fn new() -> Self {
194 Self {
195 time: 0.0,
196 ticks: 0,
197 }
198 }
199
200 pub fn time(&self) -> f64 {
201 self.time
202 }
203
204 pub fn advance(&mut self, dt: f32) -> u32 {
205 if !dt.is_finite() || dt <= 0.0 {
206 return 0;
207 }
208 self.advance_f64(self.time + f64::from(dt))
209 }
210
211 pub fn advance_to(&mut self, to: f32) -> u32 {
212 self.advance_f64(f64::from(to))
213 }
214
215 fn advance_f64(&mut self, to: f64) -> u32 {
216 if !to.is_finite() || to < self.time {
217 return 0;
218 }
219 let due = (to * f64::from(RATE)).floor().max(0.0) as u64;
220 let n = due.saturating_sub(self.ticks);
221 self.ticks = due;
222 self.time = to;
223 u32::try_from(n).unwrap_or(u32::MAX)
224 }
225}
226
227pub fn smoothstep(t: f32) -> f32 {
228 let t = t.clamp(0.0, 1.0);
229 t * t * (3.0 - 2.0 * t)
230}
231
232pub fn smootherstep(t: f32) -> f32 {
233 let t = t.clamp(0.0, 1.0);
234 let u = if t <= 0.5 { t } else { 1.0 - t };
235 let y = u * u * u * (u * (u * 6.0 - 15.0) + 10.0);
236 if t <= 0.5 { y } else { 1.0 - y }
237}
238
239pub fn quad_in(t: f32) -> f32 {
240 let t = t.clamp(0.0, 1.0);
241 t * t
242}
243
244pub fn quad_out(t: f32) -> f32 {
245 let t = t.clamp(0.0, 1.0);
246 let u = 1.0 - t;
247 1.0 - u * u
248}
249
250pub fn cubic_in(t: f32) -> f32 {
251 let t = t.clamp(0.0, 1.0);
252 t * t * t
253}
254
255pub fn cubic_out(t: f32) -> f32 {
256 let t = t.clamp(0.0, 1.0);
257 let u = 1.0 - t;
258 1.0 - u * u * u
259}
260
261pub fn cubic_in_out(t: f32) -> f32 {
262 let t = t.clamp(0.0, 1.0);
263 if t < 0.5 {
264 4.0 * t * t * t
265 } else {
266 let u = -2.0 * t + 2.0;
267 1.0 - u * u * u / 2.0
268 }
269}
270
271#[cfg(test)]
272mod tests {
273 use super::*;
274
275 fn fine_reference(
276 mut value: f32,
277 mut velocity: f32,
278 target: f32,
279 stiffness: f32,
280 dt: f32,
281 ) -> (f32, f32) {
282 let steps = (dt / (1.0 / 240.0)).ceil().max(1.0) as u32;
283 let h = dt / steps as f32;
284 let damping = 2.0 * stiffness.sqrt();
285 for _ in 0..steps {
286 let force = -stiffness * (value - target) - damping * velocity;
287 velocity += force * h;
288 value += velocity * h;
289 }
290 if (value - target).abs() < 1e-5 && velocity.abs() < 1e-4 {
291 value = target;
292 velocity = 0.0;
293 }
294 (value, velocity)
295 }
296
297 #[test]
298 fn a_fine_spring_follows_small_moves_that_a_coarse_one_snaps() {
299 let mut fine = Spring::at(40.0, 0.0f32).with_rest(Rest::FINE);
300 let mut coarse = Spring::at(40.0, 0.0f32);
301 fine.target = 5e-5;
302 coarse.target = 5e-5;
303 fine.step(1.0 / 240.0);
304 coarse.step(1.0 / 240.0);
305 assert_eq!(coarse.value, 5e-5);
306 assert!(fine.value < 5e-5);
307 assert!(!fine.settled());
308 }
309
310 #[test]
311 fn a_fine_spring_matches_its_reference_step_for_step() {
312 let mut spring = Spring::at(37.0, 0.0f32).with_rest(Rest::FINE);
313 let (mut value, mut velocity) = (0.0f32, 0.0f32);
314 let frames = [1.0 / 60.0, 1.0 / 144.0, 0.02, 1.0 / 240.0, 0.05, 1.0 / 30.0];
315 for (index, target) in [0.004f32, -0.0003, 0.00002, 0.0].iter().enumerate() {
316 spring.target = *target;
317 for dt in frames.iter().cycle().skip(index).take(200) {
318 spring.step(*dt);
319 (value, velocity) = fine_reference(value, velocity, *target, 37.0, *dt);
320 assert_eq!(spring.value.to_bits(), value.to_bits());
321 assert_eq!(spring.velocity.to_bits(), velocity.to_bits());
322 }
323 }
324 }
325
326 #[test]
327 fn a_cascade_carries_its_rest_to_both_springs() {
328 let cascade = Cascade::new(10.0, 1.0f32).with_rest(Rest::FINE);
329 assert_eq!(cascade.lead.rest, Rest::FINE);
330 assert_eq!(cascade.follow.rest, Rest::FINE);
331 assert_eq!(Cascade::new(10.0, 1.0f32).lead.rest, Rest::COARSE);
332 }
333
334 fn coarse_reference(
335 mut value: f32,
336 mut velocity: f32,
337 target: f32,
338 stiffness: f32,
339 ratio: f32,
340 dt: f32,
341 ) -> (f32, f32) {
342 let steps = (dt / (1.0 / 240.0)).ceil().max(1.0) as u32;
343 let h = dt / steps as f32;
344 let damping = 2.0 * stiffness.sqrt() * ratio;
345 for _ in 0..steps {
346 let force = -stiffness * (value - target) - damping * velocity;
347 velocity += force * h;
348 value += velocity * h;
349 }
350 if (value - target).abs() < 1e-4 && velocity.abs() < 1e-3 {
351 value = target;
352 velocity = 0.0;
353 }
354 (value, velocity)
355 }
356
357 fn drive(spring: &mut Spring<f32>, frames: &[f32]) -> u32 {
358 let mut clock = Stepper::new();
359 let mut ticks = 0;
360 for &dt in frames {
361 let n = clock.advance(dt);
362 ticks += n;
363 for _ in 0..n {
364 spring.step(STEP);
365 }
366 }
367 ticks
368 }
369
370 #[test]
371 fn settling_time() {
372 let mut spring = Spring::new(100.0);
373 spring.target = 1.0;
374 let dt = 1.0 / 60.0;
375 let mut time = 0.0;
376 let mut previous = 0.0;
377 while !spring.settled() {
378 spring.step(dt);
379 assert!(spring.value >= previous);
380 assert!(spring.value <= 1.0);
381 previous = spring.value;
382 time += dt;
383 assert!(time < 3.0, "still moving at {time}");
384 }
385 assert!(time > 1.0 && time < 1.5, "settled at {time}");
386 assert_eq!(spring.value, 1.0);
387 assert_eq!(spring.velocity, 0.0);
388 spring.step(dt);
389 assert_eq!(spring.value, 1.0);
390 assert_eq!(spring.velocity, 0.0);
391 }
392
393 #[test]
394 fn frame_patterns_agree() {
395 let patterns: [Vec<f32>; 4] = [
396 vec![1.0 / 60.0; 30],
397 vec![1.0 / 240.0; 120],
398 vec![1.0 / 120.0; 60],
399 vec![1.0 / 30.0; 15],
400 ];
401 let mut states = Vec::new();
402 for frames in &patterns {
403 let mut spring = Spring::new(44.0);
404 spring.target = 1.0;
405 let ticks = drive(&mut spring, frames);
406 assert_eq!(ticks, 120);
407 states.push(spring);
408 }
409 let mut jumped = Spring::new(44.0);
410 jumped.target = 1.0;
411 let mut clock = Stepper::new();
412 let ticks = clock.advance_to(0.5);
413 assert_eq!(ticks, 120);
414 for _ in 0..ticks {
415 jumped.step(STEP);
416 }
417 for spring in states {
418 assert!((spring.value - jumped.value).abs() < 1e-6);
419 assert!((spring.velocity - jumped.velocity).abs() < 1e-6);
420 }
421
422 let mut odd = Spring::new(44.0);
423 odd.target = 1.0;
424 let left = drive(&mut odd, &[1.0 / 144.0; 72]);
425 let mut even = Spring::new(44.0);
426 even.target = 1.0;
427 let right = drive(&mut even, &[1.0 / 60.0; 30]);
428 assert_eq!(left, right);
429 assert!((odd.value - even.value).abs() < 1e-6);
430 assert!((odd.velocity - even.velocity).abs() < 1e-6);
431
432 let mut once = Spring::new(44.0);
433 once.target = [0.2, -0.4, 0.8];
434 let mut other = once;
435 let mut a = Stepper::new();
436 let mut b = Stepper::new();
437 for _ in 0..40 {
438 for _ in 0..a.advance(1.0 / 60.0) {
439 once.step(STEP);
440 }
441 for _ in 0..4 {
442 for _ in 0..b.advance(1.0 / 240.0) {
443 other.step(STEP);
444 }
445 }
446 }
447 for ((left, left_v), (right, right_v)) in once
448 .value
449 .into_iter()
450 .zip(once.velocity)
451 .zip(other.value.into_iter().zip(other.velocity))
452 {
453 assert!((left - right).abs() < 1e-6);
454 assert!((left_v - right_v).abs() < 1e-6);
455 }
456 }
457
458 #[test]
459 fn easing_endpoints_and_monotonicity() {
460 let curves: [fn(f32) -> f32; 7] = [
461 smoothstep,
462 smootherstep,
463 quad_in,
464 quad_out,
465 cubic_in,
466 cubic_out,
467 cubic_in_out,
468 ];
469 for curve in curves {
470 assert_eq!(curve(0.0), 0.0);
471 assert_eq!(curve(1.0), 1.0);
472 assert_eq!(curve(-0.4), 0.0);
473 assert_eq!(curve(1.7), 1.0);
474 let mut previous = 0.0;
475 for i in 0..=4000 {
476 let y = curve(i as f32 / 4000.0);
477 assert!((0.0..=1.0).contains(&y));
478 assert!(y >= previous);
479 previous = y;
480 }
481 }
482 assert_eq!(smoothstep(0.5), 0.5);
483 assert_eq!(smootherstep(0.5), 0.5);
484 assert_eq!(quad_in(0.5), 0.25);
485 assert_eq!(quad_out(0.5), 0.75);
486 assert_eq!(cubic_in(0.5), 0.125);
487 assert_eq!(cubic_out(0.5), 0.875);
488 assert_eq!(cubic_in_out(0.5), 0.5);
489 assert_eq!(smoothstep(0.25), 0.15625);
490 }
491
492 #[test]
493 fn a_coarse_spring_matches_its_reference_step_for_step() {
494 let mut spring = Spring::new(900.0);
495 spring.target = 1.0;
496 for _ in 0..8 {
497 spring.step(1.0 / 60.0);
498 }
499 assert_eq!(spring.value.to_bits(), 0x3f68_168c);
500 assert_eq!(spring.velocity.to_bits(), 0x4007_7e40);
501
502 let mut turn = Spring::new(220.0);
503 turn.target = 1.0;
504 for _ in 0..12 {
505 turn.step(1.0 / 60.0);
506 }
507 assert_eq!(turn.value.to_bits(), 0x3f4c_e683);
508 assert_eq!(turn.velocity.to_bits(), 0x400c_876c);
509
510 let stiffness = [5.0, 11.0, 44.0, 100.0, 220.0, 900.0];
511 let dts = [STEP, 1.0 / 60.0, 1.0 / 144.0, 0.007, 0.02];
512 let ratios = [1.0, 0.5, 1.4];
513 for &k in &stiffness {
514 for &dt in &dts {
515 for &ratio in &ratios {
516 let mut value = 0.2;
517 let mut velocity = -0.3;
518 let mut spring = Spring::at(k, value);
519 spring.velocity = velocity;
520 spring.target = 0.7;
521 spring.damping_ratio = ratio;
522 for _ in 0..25 {
523 let (next, speed) = coarse_reference(value, velocity, 0.7, k, ratio, dt);
524 spring.step(dt);
525 assert_eq!(spring.value.to_bits(), next.to_bits());
526 assert_eq!(spring.velocity.to_bits(), speed.to_bits());
527 value = next;
528 velocity = speed;
529 }
530 }
531 }
532 }
533
534 let mut scalar = [Spring::new(132.0), Spring::new(132.0), Spring::new(132.0)];
535 let targets = [0.4, -1.2, 2.5];
536 for (spring, target) in scalar.iter_mut().zip(targets) {
537 spring.target = target;
538 }
539 let mut vector = Spring::new(132.0);
540 vector.target = targets;
541 for _ in 0..17 {
542 for spring in &mut scalar {
543 spring.step(0.007);
544 }
545 vector.step(0.007);
546 }
547 for (lane, (value, velocity)) in scalar
548 .iter()
549 .zip(vector.value.into_iter().zip(vector.velocity))
550 {
551 assert_eq!(value.to_bits(), lane.value.to_bits());
552 assert_eq!(velocity.to_bits(), lane.velocity.to_bits());
553 }
554 }
555
556 #[test]
557 fn cascade_lags_its_leader() {
558 let mut cascade = Cascade::new(44.0, 0.0);
559 cascade.lead.target = 1.0;
560 cascade.step(1.0 / 60.0);
561 assert!(cascade.follow.value > 0.0);
562 assert!(cascade.follow.value < cascade.lead.value);
563 assert!(cascade.lead.value < 1.0);
564 for _ in 0..24 {
565 cascade.step(1.0 / 60.0);
566 assert!(cascade.follow.value <= cascade.lead.value);
567 }
568 assert!(cascade.lead.value - cascade.follow.value > 0.05);
569 assert!(!cascade.settled());
570
571 let mut spatial = Cascade::new(44.0, [0.0; 3]);
572 spatial.lead.target = [1.0, -2.0, 0.5];
573 for _ in 0..8 {
574 spatial.step(1.0 / 60.0);
575 }
576 for (lead, follow) in spatial.lead.value.into_iter().zip(spatial.follow.value) {
577 assert!(follow.abs() < lead.abs());
578 }
579 }
580
581 #[test]
582 fn a_lower_damping_ratio_overshoots() {
583 let mut spring = Spring::new(100.0);
584 spring.damping_ratio = 0.4;
585 spring.target = 1.0;
586 let mut peak = 0.0f32;
587 for _ in 0..180 {
588 spring.step(1.0 / 60.0);
589 peak = peak.max(spring.value);
590 }
591 assert!(peak > 1.2);
592 }
593}