ezing/
lib.rs

1//! Simple easing functions
2//!
3//! All functions have the signature `fn<F: Float>(F) -> F` (letting you use `f32`, `f64`, or any other type that implements [`num_traits`'s `Float`](https://docs.rs/num-traits/0.2.0/num_traits/float/trait.Float.html)). Input should range from `0.0` to `1.0`, and output is generally in the `0.0` to `1.0` range (except for `elastic` and `back`, which return values slightly outside). `0.0` always maps to `0.0`, and `1.0` always maps to `1.0`.
4
5#![cfg_attr(test, feature(concat_idents))]
6
7extern crate num_traits;
8use num_traits::Float;
9
10use std::f64::consts::{PI,FRAC_PI_2};
11
12fn lit<F: Float>(f: f64) -> F {
13  F::from(f).unwrap()
14}
15
16// Linear
17
18#[inline]
19pub fn linear<F: Float>(t: F) -> F {
20  t
21}
22
23// Quadratic
24
25#[inline]
26pub fn quad_in<F: Float>(t: F) -> F {
27  t * t
28}
29
30#[inline]
31pub fn quad_out<F: Float>(t: F) -> F {
32  -t * (t - lit::<F>(2.0))
33}
34
35#[inline]
36pub fn quad_inout<F: Float>(t: F) -> F {
37  if t < lit::<F>(0.5) {
38    lit::<F>(2.0) * t * t
39  } else {
40    (lit::<F>(-2.0) * t * t) + (lit::<F>(4.0) * t) - lit::<F>(1.0)
41  }
42}
43
44// Cubic
45
46#[inline]
47pub fn cubic_in<F: Float>(t: F) -> F {
48  t * t * t
49}
50
51#[inline]
52pub fn cubic_out<F: Float>(t: F) -> F {
53  let f = t - lit::<F>(1.0);
54  f * f * f + lit::<F>(1.0)
55}
56
57#[inline]
58pub fn cubic_inout<F: Float>(t: F) -> F {
59  if t < lit::<F>(0.5) {
60    lit::<F>(4.0) * t * t * t
61  } else {
62    let f = (lit::<F>(2.0) * t) - lit::<F>(2.0);
63    lit::<F>(0.5) * f * f * f + lit::<F>(1.0)
64  }
65}
66
67// Quartic
68
69#[inline]
70pub fn quart_in<F: Float>(t: F) -> F {
71  t * t * t * t
72}
73
74#[inline]
75pub fn quart_out<F: Float>(t: F) -> F {
76  let f = t - lit::<F>(1.0);
77  f * f * f * (lit::<F>(1.0) - t) + lit::<F>(1.0)
78}
79
80#[inline]
81pub fn quart_inout<F: Float>(t: F) -> F {
82  if t < lit::<F>(0.5) {
83    lit::<F>(8.0) * t * t * t * t
84  } else {
85    let f = t - lit::<F>(1.0);
86    lit::<F>(-8.0) * f * f * f * f + lit::<F>(1.0)
87  }
88}
89
90// Quintic
91
92#[inline]
93pub fn quint_in<F: Float>(t: F) -> F {
94  t * t * t * t * t
95}
96
97#[inline]
98pub fn quint_out<F: Float>(t: F) -> F {
99  let f = t - lit::<F>(1.0);
100  f * f * f * f * f + lit::<F>(1.0)
101}
102
103#[inline]
104pub fn quint_inout<F: Float>(t: F) -> F {
105  if t < lit::<F>(0.5) {
106    lit::<F>(16.0) * t * t * t * t * t
107  } else {
108    let f = (lit::<F>(2.0) * t) - lit::<F>(2.0);
109    lit::<F>(0.5) * f * f * f * f * f + lit::<F>(1.0)
110  }
111}
112
113// Sine
114
115#[inline]
116pub fn sine_in<F: Float>(t: F) -> F {
117  ((t - lit::<F>(1.0)) * lit::<F>(FRAC_PI_2)).sin() + lit::<F>(1.0)
118}
119
120#[inline]
121pub fn sine_out<F: Float>(t: F) -> F {
122  (t * lit::<F>(FRAC_PI_2)).sin()
123}
124
125#[inline]
126pub fn sine_inout<F: Float>(t: F) -> F {
127  lit::<F>(0.5) * (lit::<F>(1.0) - (t * lit::<F>(PI)).cos())
128}
129
130// Circular
131
132#[inline]
133pub fn circ_in<F: Float>(t: F) -> F {
134  lit::<F>(1.0) - (lit::<F>(1.0) - t * t).sqrt()
135}
136
137#[inline]
138pub fn circ_out<F: Float>(t: F) -> F {
139  ((lit::<F>(2.0) - t) * t).sqrt()
140}
141
142#[inline]
143pub fn circ_inout<F: Float>(t: F) -> F {
144  if t < lit::<F>(0.5) {
145    lit::<F>(0.5) * (lit::<F>(1.0) - (lit::<F>(1.0) - lit::<F>(4.0) * t * t).sqrt())
146  } else {
147    lit::<F>(0.5) * ((-(lit::<F>(2.0) * t - lit::<F>(3.0)) * (lit::<F>(2.0) * t - lit::<F>(1.0))).sqrt() + lit::<F>(1.0))
148  }
149}
150
151// Exponential
152
153#[inline]
154pub fn expo_in<F: Float>(t: F) -> F {
155  if t == lit::<F>(0.0) {
156    lit::<F>(0.0)
157  } else {
158    lit::<F>(2.0).powf(lit::<F>(10.0) * (t - lit::<F>(1.0)))
159  }
160}
161
162
163#[cfg_attr(feature = "cargo-clippy", allow(float_cmp))]
164#[inline]
165pub fn expo_out<F: Float>(t: F) -> F {
166  if t == lit::<F>(1.0) {
167    lit::<F>(1.0)
168  } else {
169    lit::<F>(1.0) - lit::<F>(2.0).powf(lit::<F>(-10.0) * t)
170  }
171}
172
173#[cfg_attr(feature = "cargo-clippy", allow(float_cmp))]
174#[inline]
175pub fn expo_inout<F: Float>(t: F) -> F {
176  if t == lit::<F>(0.0) {
177    lit::<F>(0.0)
178  } else if t == lit::<F>(1.0) {
179    lit::<F>(1.0)
180  } else if t < lit::<F>(0.5) {
181    lit::<F>(0.5) * lit::<F>(2.0).powf(lit::<F>(20.0) * t - lit::<F>(10.0))
182  } else {
183    lit::<F>(-0.5) * lit::<F>(2.0).powf(lit::<F>(-20.0) * t + lit::<F>(10.0)) + lit::<F>(1.0)
184  }
185}
186
187// Elastic
188
189#[inline]
190pub fn elastic_in<F: Float>(t: F) -> F {
191  (lit::<F>(13.0) * lit::<F>(FRAC_PI_2) * t).sin() * lit::<F>(2.0).powf(lit::<F>(10.0) * (t - lit::<F>(1.0)))
192}
193
194#[inline]
195pub fn elastic_out<F: Float>(t: F) -> F {
196  (lit::<F>(-13.0) * lit::<F>(FRAC_PI_2) * (t + lit::<F>(1.0))).sin() * lit::<F>(2.0).powf(lit::<F>(-10.0) * t) + lit::<F>(1.0)
197}
198
199#[inline]
200pub fn elastic_inout<F: Float>(t: F) -> F {
201  if t < lit::<F>(0.5) {
202    lit::<F>(0.5) * (lit::<F>(13.0) * lit::<F>(FRAC_PI_2) * lit::<F>(2.0) * t).sin() * lit::<F>(2.0).powf(lit::<F>(10.0) * (lit::<F>(2.0) * t - lit::<F>(1.0)))
203  } else {
204    lit::<F>(0.5) * ((lit::<F>(-13.0) * lit::<F>(FRAC_PI_2) * lit::<F>(2.0) * t).sin() * (lit::<F>(2.0)).powf(lit::<F>(-10.0) * (lit::<F>(2.0) * t - lit::<F>(1.0))) + lit::<F>(2.0))
205  }
206}
207
208// Back
209
210#[inline]
211pub fn back_in<F: Float>(t: F) -> F {
212  t * t * t - t * (t * lit::<F>(PI)).sin()
213}
214
215#[inline]
216pub fn back_out<F: Float>(t: F) -> F {
217  let f = lit::<F>(1.0) - t;
218  lit::<F>(1.0) - f * f * f + f * (f * lit::<F>(PI)).sin()
219}
220
221#[inline]
222pub fn back_inout<F: Float>(t: F) -> F {
223  if t < lit::<F>(0.5) {
224    let f = lit::<F>(2.0) * t;
225    lit::<F>(0.5) * (f * f * f - f * (f * lit::<F>(PI)).sin())
226  } else {
227    let f = lit::<F>(2.0) - lit::<F>(2.0) * t;
228    lit::<F>(0.5) * (lit::<F>(1.0) - (f * f * f - f * (f * lit::<F>(PI)).sin())) + lit::<F>(0.5)
229  }
230}
231
232// Bounce
233
234#[inline]
235pub fn bounce_in<F: Float>(t: F) -> F {
236  lit::<F>(1.0) - bounce_out(lit::<F>(1.0) - t)
237}
238
239#[inline]
240pub fn bounce_out<F: Float>(t: F) -> F {
241  if t < lit::<F>(4.0 / 11.0) {
242    lit::<F>(121.0 / 16.0) * t * t
243  } else if t < lit::<F>(8.0 / 11.0) {
244    lit::<F>(363.0 / 40.0) * t * t - lit::<F>(99.0 / 10.0) * t + lit::<F>(17.0 / 5.0)
245  } else if t < lit::<F>(9.0 / 10.0) {
246    lit::<F>(4356.0 / 361.0) * t * t - lit::<F>(35442.0 / 1805.0) * t + lit::<F>(16061.0 / 1805.0)
247  } else {
248    lit::<F>(54.0 / 5.0) * t * t - lit::<F>(513.0 / 25.0) * t + lit::<F>(268.0 / 25.0)
249  }
250}
251
252#[inline]
253pub fn bounce_inout<F: Float>(t: F) -> F {
254  if t < lit::<F>(0.5) {
255    lit::<F>(0.5) * bounce_in(t * lit::<F>(2.0))
256  } else {
257    lit::<F>(0.5) * bounce_out(t * lit::<F>(2.0) - lit::<F>(1.0)) + lit::<F>(0.5)
258  }
259}
260
261
262#[cfg(test)]
263mod tests {
264  use num_traits::Float;
265  use super::lit;
266
267  fn assert_float_eq<A: Float, B: Float>(a: A, b: B) {
268    let a = a.to_f64().unwrap();
269    let b = b.to_f64().unwrap();
270
271    assert!(a - b < 0.00001, "a = {}, b = {}", a, b);
272  }
273
274  macro_rules! tests {
275    ($name:ident) => {
276      mod $name{
277        use super::*;
278        use super::super::*;
279
280        #[test] fn in32() { test::<f32, _>(concat_idents!($name, _in)); }
281        #[test] fn in64() { test::<f64, _>(concat_idents!($name, _in)); }
282        #[test] fn out32() { test::<f32, _>(concat_idents!($name, _out)); }
283        #[test] fn out64() { test::<f64, _>(concat_idents!($name, _out)); }
284        #[test] fn inout32() { test::<f32, _>(concat_idents!($name, _inout)); }
285        #[test] fn inout64() { test::<f64, _>(concat_idents!($name, _inout)); }
286        #[test] fn trio32() { test_trio::<f32, _, _, _>(concat_idents!($name, _in),
287                                                        concat_idents!($name, _out),
288                                                        concat_idents!($name, _inout)); }
289        #[test] fn trio64() { test_trio::<f64, _, _, _>(concat_idents!($name, _in),
290                                                        concat_idents!($name, _out),
291                                                        concat_idents!($name, _inout)); }
292      }
293    }
294  }
295
296  fn test<T: Float, F>(f: F)
297    where F: Fn(T) -> T {
298    assert_float_eq(f(lit::<T>(0.0)), 0.0);
299    assert_float_eq(f(lit::<T>(1.0)), 1.0);
300  }
301
302  fn test_trio<T: Float, FIN, FOUT, FINOUT>(fin: FIN, fout: FOUT, finout: FINOUT)
303    where FIN: Fn(T) -> T,
304          FOUT: Fn(T) -> T,
305          FINOUT: Fn(T) -> T {
306
307    let n = 99;
308
309    for i in 0..n+1 {
310      let t = T::from(i).unwrap() / T::from(n).unwrap();
311      println!("{}", t.to_f32().unwrap());
312
313      assert_float_eq::<T, T>(fin(t), lit::<T>(1.0) - (fout(lit::<T>(1.0) - t)));
314
315      if t < lit(0.5) {
316        assert_float_eq(finout(t), fin(t*lit::<T>(2.0)) / lit::<T>(2.0));
317      } else {
318        assert_float_eq(finout(t), fout((t-lit::<T>(0.5))*lit::<T>(2.0)) / lit::<T>(2.0) + lit::<T>(0.5));
319      }
320    }
321  }
322
323  tests!(quad);
324  tests!(cubic);
325  tests!(quart);
326  tests!(quint);
327  tests!(sine);
328  tests!(circ);
329  tests!(expo);
330  tests!(elastic);
331  tests!(back);
332  tests!(bounce);
333  #[test]
334  fn test_linear() {
335    test::<f32, _>(super::linear);
336    test::<f64, _>(super::linear);
337  }
338}