eazy_data/easing/oscillatory/
spring.rs

1//! # The Spring Curve.
2//!
3//! Spring-like easing with exponential decay and oscillation.
4//! Creates a bouncy, overshoot effect that settles at the target.
5//!
6//! #### formula.
7//!
8//! `1 + e^(-decay * t) * cos(frequency * t)`
9//!
10//! Default parameters: decay=6.0, frequency=10.0
11
12use crate::easing::Curve;
13
14use libm::{cosf, expf};
15
16/// ### The [`Spring`] Easing Function.
17///
18/// Spring-like easing that overshoots then settles.
19/// Uses exponential decay with cosine oscillation.
20///
21/// #### examples.
22///
23/// ```
24/// use eazy::Curve;
25/// use eazy::oscillatory::spring::Spring;
26///
27/// let p = Spring.y(1.0);
28/// assert!((p - 1.0).abs() < 0.01);
29/// ```
30#[derive(Debug)]
31pub struct Spring;
32
33impl Curve for Spring {
34  #[inline(always)]
35  fn y(&self, p: f32) -> f32 {
36    let p = p.clamp(0.0, 1.0);
37    // Spring formula: 1 + e^(-decay*t) * cos(frequency*t)
38    // decay=6.0, frequency=10.0 gives nice springy feel
39    1.0 + expf(-6.0 * p) * cosf(10.0 * p)
40  }
41}
42
43#[test]
44fn test_spring() {
45  // Spring(0) = 1 + e^0 * cos(0) = 1 + 1*1 = 2 (overshoots initially)
46  assert!((Spring.y(0.0) - 2.0).abs() < 0.0001);
47  // Spring(1) ≈ 1 (settles at target)
48  assert!((Spring.y(1.0) - 1.0).abs() < 0.01);
49}
50
51/// ### The [`SpringConfigurable`] Easing Function.
52///
53/// Spring easing with configurable decay and frequency parameters.
54///
55/// #### examples.
56///
57/// ```
58/// use eazy::Curve;
59/// use eazy::oscillatory::spring::SpringConfigurable;
60///
61/// // Stiffer spring with faster decay
62/// let spring = SpringConfigurable::new(8.0, 12.0);
63/// let p = spring.y(0.5);
64/// ```
65#[derive(Debug, Clone, Copy)]
66pub struct SpringConfigurable {
67  decay: f32,
68  frequency: f32,
69}
70
71impl SpringConfigurable {
72  /// Create a new configurable spring.
73  ///
74  /// #### params.
75  ///
76  /// |             |                                           |
77  /// |:------------|:------------------------------------------|
78  /// | `decay`     | How quickly oscillations fade (higher=faster) |
79  /// | `frequency` | How fast it oscillates (higher=more bounces)  |
80  #[inline(always)]
81  pub const fn new(decay: f32, frequency: f32) -> Self {
82    Self { decay, frequency }
83  }
84}
85
86impl Curve for SpringConfigurable {
87  #[inline(always)]
88  fn y(&self, p: f32) -> f32 {
89    let p = p.clamp(0.0, 1.0);
90    1.0 + expf(-self.decay * p) * cosf(self.frequency * p)
91  }
92}
93
94#[test]
95fn test_spring_configurable() {
96  let spring = SpringConfigurable::new(6.0, 10.0);
97  // Should behave like default Spring
98  assert!((spring.y(0.0) - 2.0).abs() < 0.0001);
99  assert!((spring.y(1.0) - 1.0).abs() < 0.01);
100}