1use std::f32::consts::PI;
8
9#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
11pub enum Easing {
12 #[default]
14 Linear,
15
16 QuadIn,
17 QuadOut,
18 QuadInOut,
19
20 CubicIn,
21 CubicOut,
22 CubicInOut,
23
24 QuartIn,
25 QuartOut,
26 QuartInOut,
27
28 SineIn,
29 SineOut,
30 SineInOut,
31
32 ExpoIn,
33 ExpoOut,
34 ExpoInOut,
35
36 BackIn,
38 BackOut,
40 BackInOut,
41
42 BounceIn,
44 BounceOut,
46 BounceInOut,
47
48 ElasticIn,
50 ElasticOut,
52 ElasticInOut,
53}
54
55impl Easing {
56 pub fn apply(self, t: f32) -> f32 {
58 let t = t.clamp(0.0, 1.0);
59 match self {
60 Easing::Linear => t,
61
62 Easing::QuadIn => t * t,
63 Easing::QuadOut => 1.0 - (1.0 - t) * (1.0 - t),
64 Easing::QuadInOut => {
65 if t < 0.5 {
66 2.0 * t * t
67 } else {
68 1.0 - 2.0 * (1.0 - t) * (1.0 - t)
69 }
70 }
71
72 Easing::CubicIn => t * t * t,
73 Easing::CubicOut => {
74 let u = 1.0 - t;
75 1.0 - u * u * u
76 }
77 Easing::CubicInOut => {
78 if t < 0.5 {
79 4.0 * t * t * t
80 } else {
81 let u = -2.0 * t + 2.0;
82 1.0 - u * u * u / 2.0
83 }
84 }
85
86 Easing::QuartIn => t * t * t * t,
87 Easing::QuartOut => {
88 let u = 1.0 - t;
89 1.0 - u * u * u * u
90 }
91 Easing::QuartInOut => {
92 if t < 0.5 {
93 8.0 * t * t * t * t
94 } else {
95 let u = -2.0 * t + 2.0;
96 1.0 - u * u * u * u / 2.0
97 }
98 }
99
100 Easing::SineIn => 1.0 - (t * PI / 2.0).cos(),
101 Easing::SineOut => (t * PI / 2.0).sin(),
102 Easing::SineInOut => -((PI * t).cos() - 1.0) / 2.0,
103
104 Easing::ExpoIn => {
105 if t <= 0.0 {
106 0.0
107 } else {
108 2f32.powf(10.0 * t - 10.0)
109 }
110 }
111 Easing::ExpoOut => {
112 if t >= 1.0 {
113 1.0
114 } else {
115 1.0 - 2f32.powf(-10.0 * t)
116 }
117 }
118 Easing::ExpoInOut => {
119 if t <= 0.0 {
120 0.0
121 } else if t >= 1.0 {
122 1.0
123 } else if t < 0.5 {
124 2f32.powf(20.0 * t - 10.0) / 2.0
125 } else {
126 (2.0 - 2f32.powf(-20.0 * t + 10.0)) / 2.0
127 }
128 }
129
130 Easing::BackIn => {
131 let c1 = 1.70158;
132 let c3 = c1 + 1.0;
133 c3 * t * t * t - c1 * t * t
134 }
135 Easing::BackOut => {
136 let c1 = 1.70158;
137 let c3 = c1 + 1.0;
138 let u = t - 1.0;
139 1.0 + c3 * u * u * u + c1 * u * u
140 }
141 Easing::BackInOut => {
142 let c1 = 1.70158;
143 let c2 = c1 * 1.525;
144 if t < 0.5 {
145 let u = 2.0 * t;
146 (u * u * ((c2 + 1.0) * u - c2)) / 2.0
147 } else {
148 let u = 2.0 * t - 2.0;
149 (u * u * ((c2 + 1.0) * u + c2) + 2.0) / 2.0
150 }
151 }
152
153 Easing::BounceIn => 1.0 - bounce_out(1.0 - t),
154 Easing::BounceOut => bounce_out(t),
155 Easing::BounceInOut => {
156 if t < 0.5 {
157 (1.0 - bounce_out(1.0 - 2.0 * t)) / 2.0
158 } else {
159 (1.0 + bounce_out(2.0 * t - 1.0)) / 2.0
160 }
161 }
162
163 Easing::ElasticIn => {
164 if t <= 0.0 {
165 0.0
166 } else if t >= 1.0 {
167 1.0
168 } else {
169 let c4 = (2.0 * PI) / 3.0;
170 -(2f32.powf(10.0 * t - 10.0)) * ((10.0 * t - 10.75) * c4).sin()
171 }
172 }
173 Easing::ElasticOut => {
174 if t <= 0.0 {
175 0.0
176 } else if t >= 1.0 {
177 1.0
178 } else {
179 let c4 = (2.0 * PI) / 3.0;
180 2f32.powf(-10.0 * t) * ((10.0 * t - 0.75) * c4).sin() + 1.0
181 }
182 }
183 Easing::ElasticInOut => {
184 if t <= 0.0 {
185 0.0
186 } else if t >= 1.0 {
187 1.0
188 } else {
189 let c5 = (2.0 * PI) / 4.5;
190 if t < 0.5 {
191 -(2f32.powf(20.0 * t - 10.0) * ((20.0 * t - 11.125) * c5).sin()) / 2.0
192 } else {
193 (2f32.powf(-20.0 * t + 10.0) * ((20.0 * t - 11.125) * c5).sin()) / 2.0 + 1.0
194 }
195 }
196 }
197 }
198 }
199}
200
201fn bounce_out(t: f32) -> f32 {
202 let n1 = 7.5625;
203 let d1 = 2.75;
204 if t < 1.0 / d1 {
205 n1 * t * t
206 } else if t < 2.0 / d1 {
207 let t = t - 1.5 / d1;
208 n1 * t * t + 0.75
209 } else if t < 2.5 / d1 {
210 let t = t - 2.25 / d1;
211 n1 * t * t + 0.9375
212 } else {
213 let t = t - 2.625 / d1;
214 n1 * t * t + 0.984375
215 }
216}
217
218#[cfg(test)]
219mod tests {
220 use super::*;
221
222 #[test]
223 fn endpoints_are_anchored() {
224 for e in [
225 Easing::Linear,
226 Easing::QuadInOut,
227 Easing::CubicInOut,
228 Easing::SineInOut,
229 Easing::ExpoInOut,
230 Easing::BounceOut,
231 Easing::ElasticOut,
232 ] {
233 assert!((e.apply(0.0) - 0.0).abs() < 1e-3, "{e:?} at 0");
234 assert!((e.apply(1.0) - 1.0).abs() < 1e-3, "{e:?} at 1");
235 }
236 }
237
238 #[test]
239 fn linear_is_identity() {
240 assert!((Easing::Linear.apply(0.37) - 0.37).abs() < 1e-6);
241 }
242
243 #[test]
244 fn clamps_out_of_range() {
245 assert_eq!(Easing::Linear.apply(-1.0), 0.0);
246 assert_eq!(Easing::Linear.apply(2.0), 1.0);
247 }
248}