1use crate::class::Eps;
4use crate::easing::Easing;
5use crate::lanes::Lanes;
6use crate::spring::{MAX_SETTLE, Spring, finite_f32, sane_state};
7
8#[derive(Clone, Copy, Debug, PartialEq)]
11pub enum Curve {
12 Spring(Spring),
14 Tween {
17 duration: f32,
19 easing: Easing,
21 },
22 Decay {
25 k: f32,
27 },
28 Instant,
30}
31
32impl Default for Curve {
33 fn default() -> Self {
34 Curve::DEFAULT
35 }
36}
37
38impl From<Spring> for Curve {
39 fn from(s: Spring) -> Self {
40 Curve::Spring(s)
41 }
42}
43
44const DEFAULT_TWEEN: f32 = 0.3;
45const MAX_TWEEN: f32 = 60.0;
46const DEFAULT_DECAY: f32 = 2.0;
47
48impl Curve {
49 pub const DEFAULT: Curve = Curve::Spring(Spring::DEFAULT);
51
52 pub const fn tween(duration: f32, easing: Easing) -> Curve {
54 Curve::Tween { duration, easing }.sanitized()
55 }
56
57 pub fn decay(rate_per_ms: f32) -> Curve {
60 Curve::Decay { k: crate::gesture::decay_k(rate_per_ms) }
61 }
62
63 pub const fn sanitized(self) -> Curve {
66 match self {
67 Curve::Spring(s) => {
68 let s = Spring::new(s.duration(), s.bounce());
70 if s.is_none() { Curve::Instant } else { Curve::Spring(s) }
71 }
72 Curve::Tween { duration, easing } => {
73 let d = if duration.is_nan() { DEFAULT_TWEEN } else { duration };
74 if d <= 0.0 {
75 Curve::Instant
76 } else {
77 let [x1, y1, x2, y2] = easing.points();
78 Curve::Tween { duration: d.min(MAX_TWEEN), easing: Easing::cubic_bezier(x1, y1, x2, y2) }
79 }
80 }
81 Curve::Decay { k } => Curve::Decay { k: if k.is_nan() { DEFAULT_DECAY } else { k.clamp(0.05, 1000.0) } },
82 Curve::Instant => Curve::Instant,
83 }
84 }
85
86 pub const fn is_instant(self) -> bool {
88 matches!(self.sanitized(), Curve::Instant)
89 }
90
91 pub fn rest(self, x0: f32, v0: f32, target: f32) -> f32 {
94 let (x0, v0, target) = sane_state(x0, v0, target);
95 match self.sanitized() {
96 Curve::Decay { k } => finite_f32(f64::from(x0) + f64::from(v0) / f64::from(k), x0),
97 _ => target,
98 }
99 }
100
101 pub fn sample(self, x0: f32, v0: f32, target: f32, t: f32) -> (f32, f32) {
104 let (x0, v0, target) = sane_state(x0, v0, target);
105 if t.is_nan() || t <= 0.0 {
106 return (x0, v0);
107 }
108 match self.sanitized() {
109 Curve::Spring(s) => s.sample(x0, v0, target, t),
110 Curve::Tween { duration, easing } => tween(duration, easing, x0, v0, target, t),
111 Curve::Decay { k } => {
112 let (k, v) = (f64::from(k), f64::from(v0));
113 let e = (-k * f64::from(t)).exp();
114 (finite_f32(f64::from(x0) + v / k * (1.0 - e), x0), finite_f32(v * e, 0.0))
115 }
116 Curve::Instant => (target, 0.0),
117 }
118 }
119
120 pub fn settle_time(self, x0: f32, v0: f32, target: f32, eps: Eps) -> f32 {
123 self.settle_time_lanes([(x0, v0, target)], eps)
124 }
125
126 pub fn settle_time_lanes(self, lanes: impl IntoIterator<Item = (f32, f32, f32)>, eps: Eps) -> f32 {
129 match self.sanitized() {
130 Curve::Spring(s) => s.settle_time_lanes(lanes, eps),
131 Curve::Tween { duration, .. } => duration,
132 Curve::Decay { k } => {
133 let eps = eps.sanitized();
134 let v = lanes.into_iter().map(|(x0, v0, target)| sane_state(x0, v0, target).1.abs()).fold(0.0_f32, f32::max);
135 let (k, v) = (f64::from(k), f64::from(v));
136 let t = (v / (k * f64::from(eps.pos))).ln().max((v / f64::from(eps.vel)).ln()).max(0.0) / k;
138 let t = t * (1.0 + 1e-4) + 1e-4;
139 if t.is_finite() { (t as f32).min(MAX_SETTLE) } else { 0.0 }
140 }
141 Curve::Instant => 0.0,
142 }
143 }
144
145 pub(crate) fn sample_lanes(self, from: &Lanes, vel: &Lanes, target: &Lanes, n: usize, t: f32) -> (Lanes, Lanes) {
148 let mut x = *target;
149 let mut v = [0.0; crate::lanes::MAX_LANES];
150 let lanes = x.iter_mut().zip(v.iter_mut()).zip(from.iter().zip(vel.iter()));
151 for ((x, v), (&from, &vel)) in lanes.take(n) {
152 let (px, pv) = self.sample(from, vel, *x, t);
153 *x = px;
154 *v = pv;
155 }
156 (x, v)
157 }
158}
159
160fn tween(duration: f32, easing: Easing, x0: f32, v0: f32, target: f32, t: f32) -> (f32, f32) {
165 if t >= duration {
166 return (target, 0.0);
167 }
168 let d = f64::from(duration);
169 let t = f64::from(t);
170 let u = t / d;
171 let delta = f64::from(target) - f64::from(x0);
172 let w = if v0 == 0.0 { 0.0 } else { f64::from(v0) - delta * f64::from(easing.slope(0.0)) / d };
173 let uf = u as f32;
174 let x = f64::from(x0) + delta * f64::from(easing.ease(uf)) + w * t * (1.0 - u) * (1.0 - u);
175 let v = delta * f64::from(easing.slope(uf)) / d + w * (1.0 - u) * (1.0 - 3.0 * u);
176 (finite_f32(x, target), finite_f32(v, 0.0))
177}
178
179#[cfg(test)]
180mod tests {
181 use super::*;
182
183 #[test]
184 fn tween_keeps_start_velocity_and_lands() {
185 for e in [Easing::LINEAR, Easing::EASE, Easing::EASE_OUT] {
186 let c = Curve::tween(0.5, e);
187 let (x, v) = c.sample(0.0, 7.0, 10.0, 1e-6);
188 assert!(x.abs() < 1e-3);
189 assert!((v - 7.0).abs() < 5e-2, "{e:?} {v}");
191 assert_eq!(c.sample(0.0, 7.0, 10.0, 0.5), (10.0, 0.0));
192 let (x, _) = c.sample(0.0, 7.0, 10.0, 0.4999);
193 assert!((x - 10.0).abs() < 1e-2, "{e:?} {x}");
194 }
195 }
196
197 #[test]
198 fn decay_rests_at_projection() {
199 let c = Curve::Decay { k: 4.0 };
200 assert!((c.rest(1.0, 8.0, 99.0) - 3.0).abs() < 1e-6);
201 let t = c.settle_time(1.0, 8.0, 99.0, Eps::new(1e-3, 1e-2));
202 let (x, v) = c.sample(1.0, 8.0, 99.0, t);
203 assert!((x - 3.0).abs() <= 1e-3 && v.abs() <= 1e-2);
204 }
205
206 #[test]
207 fn sanitize() {
208 assert!(Curve::tween(0.0, Easing::EASE).is_instant());
209 assert!(Curve::Spring(Spring::NONE).is_instant());
210 assert_eq!(Curve::Decay { k: f32::NAN }.sanitized(), Curve::Decay { k: 2.0 });
211 assert_eq!(Curve::tween(f32::INFINITY, Easing::EASE), Curve::Tween { duration: 60.0, easing: Easing::EASE });
212 }
213}