eazy_core/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}