animato_spring/config.rs
1//! [`SpringConfig`] — parameters that control spring behaviour.
2
3/// Configuration for a damped harmonic oscillator spring.
4///
5/// Use one of the named presets for common feels, or tune the parameters directly.
6///
7/// # Presets
8///
9/// | Preset | Stiffness | Damping | Feel |
10/// |--------|-----------|---------|------|
11/// | `gentle()` | 60 | 14 | Slow, soft |
12/// | `wobbly()` | 180 | 12 | Bouncy, playful |
13/// | `stiff()` | 210 | 20 | Fast, firm |
14/// | `slow()` | 37 | 14 | Very slow, lazy |
15/// | `snappy()` | 300 | 30 | Near-instant |
16///
17/// # Example
18///
19/// ```rust
20/// use animato_spring::SpringConfig;
21///
22/// let cfg = SpringConfig::wobbly();
23/// assert_eq!(cfg.stiffness, 180.0);
24/// ```
25#[derive(Clone, Debug)]
26#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
27pub struct SpringConfig {
28 /// Restoring force. Higher = snappier. Default: `100.0`.
29 pub stiffness: f32,
30 /// Resistance to motion. Higher = less bouncy. Default: `10.0`.
31 pub damping: f32,
32 /// Mass of the simulated object. Default: `1.0`.
33 pub mass: f32,
34 /// Settle threshold — spring is considered at rest when both
35 /// `|position - target| < epsilon` and `|velocity| < epsilon`. Default: `0.001`.
36 pub epsilon: f32,
37}
38
39impl Default for SpringConfig {
40 fn default() -> Self {
41 Self {
42 stiffness: 100.0,
43 damping: 10.0,
44 mass: 1.0,
45 epsilon: 0.001,
46 }
47 }
48}
49
50impl SpringConfig {
51 /// Create a critically damped spring for the given stiffness.
52 ///
53 /// Critical damping uses `damping = 2 * sqrt(stiffness * mass)` with
54 /// `mass = 1.0`, producing fast movement without intentional overshoot.
55 pub fn critically_damped(stiffness: f32) -> Self {
56 let stiffness = stiffness.max(0.0);
57 let mass = 1.0;
58 Self {
59 stiffness,
60 damping: 2.0 * animato_core::math::sqrt(stiffness * mass),
61 mass,
62 epsilon: 0.001,
63 }
64 }
65
66 /// Create an overdamped spring with damping ratio above `1.0`.
67 ///
68 /// Ratios at or below `1.0` are clamped to a small overdamped margin.
69 pub fn overdamped(stiffness: f32, ratio: f32) -> Self {
70 let mut config = Self::critically_damped(stiffness);
71 config.damping *= ratio.max(1.0001);
72 config
73 }
74
75 /// Create an underdamped spring with damping ratio below `1.0`.
76 ///
77 /// Ratios are clamped to `[0.0, 1.0]`; lower values produce more bounce.
78 pub fn underdamped(stiffness: f32, ratio: f32) -> Self {
79 let mut config = Self::critically_damped(stiffness);
80 config.damping *= ratio.clamp(0.0, 1.0);
81 config
82 }
83
84 /// Slow, soft spring — good for subtle UI elements.
85 pub fn gentle() -> Self {
86 Self {
87 stiffness: 60.0,
88 damping: 14.0,
89 mass: 1.0,
90 epsilon: 0.001,
91 }
92 }
93
94 /// Bouncy, playful spring — great for icons and interactive elements.
95 pub fn wobbly() -> Self {
96 Self {
97 stiffness: 180.0,
98 damping: 12.0,
99 mass: 1.0,
100 epsilon: 0.001,
101 }
102 }
103
104 /// Fast, firm spring — good for panels and drawers.
105 pub fn stiff() -> Self {
106 Self {
107 stiffness: 210.0,
108 damping: 20.0,
109 mass: 1.0,
110 epsilon: 0.001,
111 }
112 }
113
114 /// Very slow, lazy spring — good for background animations.
115 pub fn slow() -> Self {
116 Self {
117 stiffness: 37.0,
118 damping: 14.0,
119 mass: 1.0,
120 epsilon: 0.001,
121 }
122 }
123
124 /// Near-instant response — for time-critical feedback.
125 pub fn snappy() -> Self {
126 Self {
127 stiffness: 300.0,
128 damping: 30.0,
129 mass: 1.0,
130 epsilon: 0.001,
131 }
132 }
133}