Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_math/src/curve/easing.rs
6596 views
1
//! Module containing different easing functions.
2
//!
3
//! An easing function is a [`Curve`] that's used to transition between two
4
//! values. It takes a time parameter, where a time of zero means the start of
5
//! the transition and a time of one means the end.
6
//!
7
//! Easing functions come in a variety of shapes - one might [transition smoothly],
8
//! while another might have a [bouncing motion].
9
//!
10
//! There are several ways to use easing functions. The simplest option is a
11
//! struct thats represents a single easing function, like [`SmoothStepCurve`]
12
//! and [`StepsCurve`]. These structs can only transition from a value of zero
13
//! to a value of one.
14
//!
15
//! ```
16
//! # use bevy_math::prelude::*;
17
//! # let time = 0.0;
18
//! let smoothed_value = SmoothStepCurve.sample(time);
19
//! ```
20
//!
21
//! ```
22
//! # use bevy_math::prelude::*;
23
//! # let time = 0.0;
24
//! let stepped_value = StepsCurve(5, JumpAt::Start).sample(time);
25
//! ```
26
//!
27
//! Another option is [`EaseFunction`]. Unlike the single function structs,
28
//! which require you to choose a function at compile time, `EaseFunction` lets
29
//! you choose at runtime. It can also be serialized.
30
//!
31
//! ```
32
//! # use bevy_math::prelude::*;
33
//! # let time = 0.0;
34
//! # let make_it_smooth = false;
35
//! let mut curve = EaseFunction::Linear;
36
//!
37
//! if make_it_smooth {
38
//! curve = EaseFunction::SmoothStep;
39
//! }
40
//!
41
//! let value = curve.sample(time);
42
//! ```
43
//!
44
//! The final option is [`EasingCurve`]. This lets you transition between any
45
//! two values - not just zero to one. `EasingCurve` can use any value that
46
//! implements the [`Ease`] trait, including vectors and directions.
47
//!
48
//! ```
49
//! # use bevy_math::prelude::*;
50
//! # let time = 0.0;
51
//! // Make a curve that smoothly transitions between two positions.
52
//! let start_position = vec2(1.0, 2.0);
53
//! let end_position = vec2(5.0, 10.0);
54
//! let curve = EasingCurve::new(start_position, end_position, EaseFunction::SmoothStep);
55
//!
56
//! let smoothed_position = curve.sample(time);
57
//! ```
58
//!
59
//! Like `EaseFunction`, the values and easing function of `EasingCurve` can be
60
//! chosen at runtime and serialized.
61
//!
62
//! [transition smoothly]: `SmoothStepCurve`
63
//! [bouncing motion]: `BounceInCurve`
64
//! [`sample`]: `Curve::sample`
65
//! [`sample_clamped`]: `Curve::sample_clamped`
66
//! [`sample_unchecked`]: `Curve::sample_unchecked`
67
//!
68
69
use crate::{
70
curve::{Curve, CurveExt, FunctionCurve, Interval},
71
Dir2, Dir3, Dir3A, Isometry2d, Isometry3d, Quat, Rot2, VectorSpace,
72
};
73
74
#[cfg(feature = "bevy_reflect")]
75
use bevy_reflect::std_traits::ReflectDefault;
76
77
use variadics_please::all_tuples_enumerated;
78
79
// TODO: Think about merging `Ease` with `StableInterpolate`
80
81
/// A type whose values can be eased between.
82
///
83
/// This requires the construction of an interpolation curve that actually extends
84
/// beyond the curve segment that connects two values, because an easing curve may
85
/// extrapolate before the starting value and after the ending value. This is
86
/// especially common in easing functions that mimic elastic or springlike behavior.
87
pub trait Ease: Sized {
88
/// Given `start` and `end` values, produce a curve with [unlimited domain]
89
/// that:
90
/// - takes a value equivalent to `start` at `t = 0`
91
/// - takes a value equivalent to `end` at `t = 1`
92
/// - has constant speed everywhere, including outside of `[0, 1]`
93
///
94
/// [unlimited domain]: Interval::EVERYWHERE
95
fn interpolating_curve_unbounded(start: Self, end: Self) -> impl Curve<Self>;
96
}
97
98
impl<V: VectorSpace<Scalar = f32>> Ease for V {
99
fn interpolating_curve_unbounded(start: Self, end: Self) -> impl Curve<Self> {
100
FunctionCurve::new(Interval::EVERYWHERE, move |t| V::lerp(start, end, t))
101
}
102
}
103
104
impl Ease for Rot2 {
105
fn interpolating_curve_unbounded(start: Self, end: Self) -> impl Curve<Self> {
106
FunctionCurve::new(Interval::EVERYWHERE, move |t| Rot2::slerp(start, end, t))
107
}
108
}
109
110
impl Ease for Quat {
111
fn interpolating_curve_unbounded(start: Self, end: Self) -> impl Curve<Self> {
112
let dot = start.dot(end);
113
let end_adjusted = if dot < 0.0 { -end } else { end };
114
let difference = end_adjusted * start.inverse();
115
let (axis, angle) = difference.to_axis_angle();
116
FunctionCurve::new(Interval::EVERYWHERE, move |s| {
117
Quat::from_axis_angle(axis, angle * s) * start
118
})
119
}
120
}
121
122
impl Ease for Dir2 {
123
fn interpolating_curve_unbounded(start: Self, end: Self) -> impl Curve<Self> {
124
FunctionCurve::new(Interval::EVERYWHERE, move |t| Dir2::slerp(start, end, t))
125
}
126
}
127
128
impl Ease for Dir3 {
129
fn interpolating_curve_unbounded(start: Self, end: Self) -> impl Curve<Self> {
130
let difference_quat = Quat::from_rotation_arc(start.as_vec3(), end.as_vec3());
131
Quat::interpolating_curve_unbounded(Quat::IDENTITY, difference_quat).map(move |q| q * start)
132
}
133
}
134
135
impl Ease for Dir3A {
136
fn interpolating_curve_unbounded(start: Self, end: Self) -> impl Curve<Self> {
137
let difference_quat =
138
Quat::from_rotation_arc(start.as_vec3a().into(), end.as_vec3a().into());
139
Quat::interpolating_curve_unbounded(Quat::IDENTITY, difference_quat).map(move |q| q * start)
140
}
141
}
142
143
impl Ease for Isometry3d {
144
fn interpolating_curve_unbounded(start: Self, end: Self) -> impl Curve<Self> {
145
FunctionCurve::new(Interval::EVERYWHERE, move |t| {
146
// we can use sample_unchecked here, since both interpolating_curve_unbounded impls
147
// used are defined on the whole domain
148
Isometry3d {
149
rotation: Quat::interpolating_curve_unbounded(start.rotation, end.rotation)
150
.sample_unchecked(t),
151
translation: crate::Vec3A::interpolating_curve_unbounded(
152
start.translation,
153
end.translation,
154
)
155
.sample_unchecked(t),
156
}
157
})
158
}
159
}
160
161
impl Ease for Isometry2d {
162
fn interpolating_curve_unbounded(start: Self, end: Self) -> impl Curve<Self> {
163
FunctionCurve::new(Interval::EVERYWHERE, move |t| {
164
// we can use sample_unchecked here, since both interpolating_curve_unbounded impls
165
// used are defined on the whole domain
166
Isometry2d {
167
rotation: Rot2::interpolating_curve_unbounded(start.rotation, end.rotation)
168
.sample_unchecked(t),
169
translation: crate::Vec2::interpolating_curve_unbounded(
170
start.translation,
171
end.translation,
172
)
173
.sample_unchecked(t),
174
}
175
})
176
}
177
}
178
179
macro_rules! impl_ease_tuple {
180
($(#[$meta:meta])* $(($n:tt, $T:ident)),*) => {
181
$(#[$meta])*
182
impl<$($T: Ease),*> Ease for ($($T,)*) {
183
fn interpolating_curve_unbounded(start: Self, end: Self) -> impl Curve<Self> {
184
let curve_tuple =
185
(
186
$(
187
<$T as Ease>::interpolating_curve_unbounded(start.$n, end.$n),
188
)*
189
);
190
191
FunctionCurve::new(Interval::EVERYWHERE, move |t|
192
(
193
$(
194
curve_tuple.$n.sample_unchecked(t),
195
)*
196
)
197
)
198
}
199
}
200
};
201
}
202
203
all_tuples_enumerated!(
204
#[doc(fake_variadic)]
205
impl_ease_tuple,
206
1,
207
11,
208
T
209
);
210
211
/// A [`Curve`] that is defined by
212
///
213
/// - an initial `start` sample value at `t = 0`
214
/// - a final `end` sample value at `t = 1`
215
/// - an [easing function] to interpolate between the two values.
216
///
217
/// The resulting curve's domain is always [the unit interval].
218
///
219
/// # Example
220
///
221
/// Create a linear curve that interpolates between `2.0` and `4.0`.
222
///
223
/// ```
224
/// # use bevy_math::prelude::*;
225
/// let c = EasingCurve::new(2.0, 4.0, EaseFunction::Linear);
226
/// ```
227
///
228
/// [`sample`] the curve at various points. This will return `None` if the parameter
229
/// is outside the unit interval.
230
///
231
/// ```
232
/// # use bevy_math::prelude::*;
233
/// # let c = EasingCurve::new(2.0, 4.0, EaseFunction::Linear);
234
/// assert_eq!(c.sample(-1.0), None);
235
/// assert_eq!(c.sample(0.0), Some(2.0));
236
/// assert_eq!(c.sample(0.5), Some(3.0));
237
/// assert_eq!(c.sample(1.0), Some(4.0));
238
/// assert_eq!(c.sample(2.0), None);
239
/// ```
240
///
241
/// [`sample_clamped`] will clamp the parameter to the unit interval, so it
242
/// always returns a value.
243
///
244
/// ```
245
/// # use bevy_math::prelude::*;
246
/// # let c = EasingCurve::new(2.0, 4.0, EaseFunction::Linear);
247
/// assert_eq!(c.sample_clamped(-1.0), 2.0);
248
/// assert_eq!(c.sample_clamped(0.0), 2.0);
249
/// assert_eq!(c.sample_clamped(0.5), 3.0);
250
/// assert_eq!(c.sample_clamped(1.0), 4.0);
251
/// assert_eq!(c.sample_clamped(2.0), 4.0);
252
/// ```
253
///
254
/// `EasingCurve` can be used with any type that implements the [`Ease`] trait.
255
/// This includes many math types, like vectors and rotations.
256
///
257
/// ```
258
/// # use bevy_math::prelude::*;
259
/// let c = EasingCurve::new(
260
/// Vec2::new(0.0, 4.0),
261
/// Vec2::new(2.0, 8.0),
262
/// EaseFunction::Linear,
263
/// );
264
///
265
/// assert_eq!(c.sample_clamped(0.5), Vec2::new(1.0, 6.0));
266
/// ```
267
///
268
/// ```
269
/// # use bevy_math::prelude::*;
270
/// # use approx::assert_abs_diff_eq;
271
/// let c = EasingCurve::new(
272
/// Rot2::degrees(10.0),
273
/// Rot2::degrees(20.0),
274
/// EaseFunction::Linear,
275
/// );
276
///
277
/// assert_abs_diff_eq!(c.sample_clamped(0.5), Rot2::degrees(15.0));
278
/// ```
279
///
280
/// As a shortcut, an `EasingCurve` between `0.0` and `1.0` can be replaced by
281
/// [`EaseFunction`].
282
///
283
/// ```
284
/// # use bevy_math::prelude::*;
285
/// # let t = 0.5;
286
/// let f = EaseFunction::SineIn;
287
/// let c = EasingCurve::new(0.0, 1.0, EaseFunction::SineIn);
288
///
289
/// assert_eq!(f.sample(t), c.sample(t));
290
/// ```
291
///
292
/// [easing function]: EaseFunction
293
/// [the unit interval]: Interval::UNIT
294
/// [`sample`]: EasingCurve::sample
295
/// [`sample_clamped`]: EasingCurve::sample_clamped
296
#[derive(Clone, Debug)]
297
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
298
#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
299
pub struct EasingCurve<T> {
300
start: T,
301
end: T,
302
ease_fn: EaseFunction,
303
}
304
305
impl<T> EasingCurve<T> {
306
/// Given a `start` and `end` value, create a curve parametrized over [the unit interval]
307
/// that connects them, using the given [ease function] to determine the form of the
308
/// curve in between.
309
///
310
/// [the unit interval]: Interval::UNIT
311
/// [ease function]: EaseFunction
312
pub fn new(start: T, end: T, ease_fn: EaseFunction) -> Self {
313
Self {
314
start,
315
end,
316
ease_fn,
317
}
318
}
319
}
320
321
impl<T> Curve<T> for EasingCurve<T>
322
where
323
T: Ease + Clone,
324
{
325
#[inline]
326
fn domain(&self) -> Interval {
327
Interval::UNIT
328
}
329
330
#[inline]
331
fn sample_unchecked(&self, t: f32) -> T {
332
let remapped_t = self.ease_fn.eval(t);
333
T::interpolating_curve_unbounded(self.start.clone(), self.end.clone())
334
.sample_unchecked(remapped_t)
335
}
336
}
337
338
/// Configuration options for the [`EaseFunction::Steps`] curves. This closely replicates the
339
/// [CSS step function specification].
340
///
341
/// [CSS step function specification]: https://developer.mozilla.org/en-US/docs/Web/CSS/easing-function/steps#description
342
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
343
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
344
#[cfg_attr(
345
feature = "bevy_reflect",
346
derive(bevy_reflect::Reflect),
347
reflect(Clone, Default, PartialEq)
348
)]
349
pub enum JumpAt {
350
/// Indicates that the first step happens when the animation begins.
351
///
352
#[doc = include_str!("../../images/easefunction/StartSteps.svg")]
353
Start,
354
/// Indicates that the last step happens when the animation ends.
355
///
356
#[doc = include_str!("../../images/easefunction/EndSteps.svg")]
357
#[default]
358
End,
359
/// Indicates neither early nor late jumps happen.
360
///
361
#[doc = include_str!("../../images/easefunction/NoneSteps.svg")]
362
None,
363
/// Indicates both early and late jumps happen.
364
///
365
#[doc = include_str!("../../images/easefunction/BothSteps.svg")]
366
Both,
367
}
368
369
impl JumpAt {
370
#[inline]
371
pub(crate) fn eval(self, num_steps: usize, t: f32) -> f32 {
372
use crate::ops;
373
374
let (a, b) = match self {
375
JumpAt::Start => (1.0, 0),
376
JumpAt::End => (0.0, 0),
377
JumpAt::None => (0.0, -1),
378
JumpAt::Both => (1.0, 1),
379
};
380
381
let current_step = ops::floor(t * num_steps as f32) + a;
382
let step_size = (num_steps as isize + b).max(1) as f32;
383
384
(current_step / step_size).clamp(0.0, 1.0)
385
}
386
}
387
388
/// Curve functions over the [unit interval], commonly used for easing transitions.
389
///
390
/// `EaseFunction` can be used on its own to interpolate between `0.0` and `1.0`.
391
/// It can also be combined with [`EasingCurve`] to interpolate between other
392
/// intervals and types, including vectors and rotations.
393
///
394
/// # Example
395
///
396
/// [`sample`] the smoothstep function at various points. This will return `None`
397
/// if the parameter is outside the unit interval.
398
///
399
/// ```
400
/// # use bevy_math::prelude::*;
401
/// let f = EaseFunction::SmoothStep;
402
///
403
/// assert_eq!(f.sample(-1.0), None);
404
/// assert_eq!(f.sample(0.0), Some(0.0));
405
/// assert_eq!(f.sample(0.5), Some(0.5));
406
/// assert_eq!(f.sample(1.0), Some(1.0));
407
/// assert_eq!(f.sample(2.0), None);
408
/// ```
409
///
410
/// [`sample_clamped`] will clamp the parameter to the unit interval, so it
411
/// always returns a value.
412
///
413
/// ```
414
/// # use bevy_math::prelude::*;
415
/// # let f = EaseFunction::SmoothStep;
416
/// assert_eq!(f.sample_clamped(-1.0), 0.0);
417
/// assert_eq!(f.sample_clamped(0.0), 0.0);
418
/// assert_eq!(f.sample_clamped(0.5), 0.5);
419
/// assert_eq!(f.sample_clamped(1.0), 1.0);
420
/// assert_eq!(f.sample_clamped(2.0), 1.0);
421
/// ```
422
///
423
/// [`sample`]: EaseFunction::sample
424
/// [`sample_clamped`]: EaseFunction::sample_clamped
425
/// [unit interval]: `Interval::UNIT`
426
#[non_exhaustive]
427
#[derive(Debug, Copy, Clone, PartialEq)]
428
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
429
#[cfg_attr(
430
feature = "bevy_reflect",
431
derive(bevy_reflect::Reflect),
432
reflect(Clone, PartialEq)
433
)]
434
// Note: Graphs are auto-generated via `tools/build-easefunction-graphs`.
435
pub enum EaseFunction {
436
/// `f(t) = t`
437
///
438
#[doc = include_str!("../../images/easefunction/Linear.svg")]
439
Linear,
440
441
/// `f(t) = t²`
442
///
443
/// This is the Hermite interpolator for
444
/// - f(0) = 0
445
/// - f(1) = 1
446
/// - f′(0) = 0
447
///
448
#[doc = include_str!("../../images/easefunction/QuadraticIn.svg")]
449
QuadraticIn,
450
/// `f(t) = -(t * (t - 2.0))`
451
///
452
/// This is the Hermite interpolator for
453
/// - f(0) = 0
454
/// - f(1) = 1
455
/// - f′(1) = 0
456
///
457
#[doc = include_str!("../../images/easefunction/QuadraticOut.svg")]
458
QuadraticOut,
459
/// Behaves as `EaseFunction::QuadraticIn` for t < 0.5 and as `EaseFunction::QuadraticOut` for t >= 0.5
460
///
461
/// A quadratic has too low of a degree to be both an `InOut` and C²,
462
/// so consider using at least a cubic (such as [`EaseFunction::SmoothStep`])
463
/// if you want the acceleration to be continuous.
464
///
465
#[doc = include_str!("../../images/easefunction/QuadraticInOut.svg")]
466
QuadraticInOut,
467
468
/// `f(t) = t³`
469
///
470
/// This is the Hermite interpolator for
471
/// - f(0) = 0
472
/// - f(1) = 1
473
/// - f′(0) = 0
474
/// - f″(0) = 0
475
///
476
#[doc = include_str!("../../images/easefunction/CubicIn.svg")]
477
CubicIn,
478
/// `f(t) = (t - 1.0)³ + 1.0`
479
///
480
#[doc = include_str!("../../images/easefunction/CubicOut.svg")]
481
CubicOut,
482
/// Behaves as `EaseFunction::CubicIn` for t < 0.5 and as `EaseFunction::CubicOut` for t >= 0.5
483
///
484
/// Due to this piecewise definition, this is only C¹ despite being a cubic:
485
/// the acceleration jumps from +12 to -12 at t = ½.
486
///
487
/// Consider using [`EaseFunction::SmoothStep`] instead, which is also cubic,
488
/// or [`EaseFunction::SmootherStep`] if you picked this because you wanted
489
/// the acceleration at the endpoints to also be zero.
490
///
491
#[doc = include_str!("../../images/easefunction/CubicInOut.svg")]
492
CubicInOut,
493
494
/// `f(t) = t⁴`
495
///
496
#[doc = include_str!("../../images/easefunction/QuarticIn.svg")]
497
QuarticIn,
498
/// `f(t) = (t - 1.0)³ * (1.0 - t) + 1.0`
499
///
500
#[doc = include_str!("../../images/easefunction/QuarticOut.svg")]
501
QuarticOut,
502
/// Behaves as `EaseFunction::QuarticIn` for t < 0.5 and as `EaseFunction::QuarticOut` for t >= 0.5
503
///
504
#[doc = include_str!("../../images/easefunction/QuarticInOut.svg")]
505
QuarticInOut,
506
507
/// `f(t) = t⁵`
508
///
509
#[doc = include_str!("../../images/easefunction/QuinticIn.svg")]
510
QuinticIn,
511
/// `f(t) = (t - 1.0)⁵ + 1.0`
512
///
513
#[doc = include_str!("../../images/easefunction/QuinticOut.svg")]
514
QuinticOut,
515
/// Behaves as `EaseFunction::QuinticIn` for t < 0.5 and as `EaseFunction::QuinticOut` for t >= 0.5
516
///
517
/// Due to this piecewise definition, this is only C¹ despite being a quintic:
518
/// the acceleration jumps from +40 to -40 at t = ½.
519
///
520
/// Consider using [`EaseFunction::SmootherStep`] instead, which is also quintic.
521
///
522
#[doc = include_str!("../../images/easefunction/QuinticInOut.svg")]
523
QuinticInOut,
524
525
/// Behaves as the first half of [`EaseFunction::SmoothStep`].
526
///
527
/// This has f″(1) = 0, unlike [`EaseFunction::QuadraticIn`] which starts similarly.
528
///
529
#[doc = include_str!("../../images/easefunction/SmoothStepIn.svg")]
530
SmoothStepIn,
531
/// Behaves as the second half of [`EaseFunction::SmoothStep`].
532
///
533
/// This has f″(0) = 0, unlike [`EaseFunction::QuadraticOut`] which ends similarly.
534
///
535
#[doc = include_str!("../../images/easefunction/SmoothStepOut.svg")]
536
SmoothStepOut,
537
/// `f(t) = 3t² - 2t³`
538
///
539
/// This is the Hermite interpolator for
540
/// - f(0) = 0
541
/// - f(1) = 1
542
/// - f′(0) = 0
543
/// - f′(1) = 0
544
///
545
/// See also [`smoothstep` in GLSL][glss].
546
///
547
/// [glss]: https://registry.khronos.org/OpenGL-Refpages/gl4/html/smoothstep.xhtml
548
///
549
#[doc = include_str!("../../images/easefunction/SmoothStep.svg")]
550
SmoothStep,
551
552
/// Behaves as the first half of [`EaseFunction::SmootherStep`].
553
///
554
/// This has f″(1) = 0, unlike [`EaseFunction::CubicIn`] which starts similarly.
555
///
556
#[doc = include_str!("../../images/easefunction/SmootherStepIn.svg")]
557
SmootherStepIn,
558
/// Behaves as the second half of [`EaseFunction::SmootherStep`].
559
///
560
/// This has f″(0) = 0, unlike [`EaseFunction::CubicOut`] which ends similarly.
561
///
562
#[doc = include_str!("../../images/easefunction/SmootherStepOut.svg")]
563
SmootherStepOut,
564
/// `f(t) = 6t⁵ - 15t⁴ + 10t³`
565
///
566
/// This is the Hermite interpolator for
567
/// - f(0) = 0
568
/// - f(1) = 1
569
/// - f′(0) = 0
570
/// - f′(1) = 0
571
/// - f″(0) = 0
572
/// - f″(1) = 0
573
///
574
#[doc = include_str!("../../images/easefunction/SmootherStep.svg")]
575
SmootherStep,
576
577
/// `f(t) = 1.0 - cos(t * π / 2.0)`
578
///
579
#[doc = include_str!("../../images/easefunction/SineIn.svg")]
580
SineIn,
581
/// `f(t) = sin(t * π / 2.0)`
582
///
583
#[doc = include_str!("../../images/easefunction/SineOut.svg")]
584
SineOut,
585
/// Behaves as `EaseFunction::SineIn` for t < 0.5 and as `EaseFunction::SineOut` for t >= 0.5
586
///
587
#[doc = include_str!("../../images/easefunction/SineInOut.svg")]
588
SineInOut,
589
590
/// `f(t) = 1.0 - sqrt(1.0 - t²)`
591
///
592
#[doc = include_str!("../../images/easefunction/CircularIn.svg")]
593
CircularIn,
594
/// `f(t) = sqrt((2.0 - t) * t)`
595
///
596
#[doc = include_str!("../../images/easefunction/CircularOut.svg")]
597
CircularOut,
598
/// Behaves as `EaseFunction::CircularIn` for t < 0.5 and as `EaseFunction::CircularOut` for t >= 0.5
599
///
600
#[doc = include_str!("../../images/easefunction/CircularInOut.svg")]
601
CircularInOut,
602
603
/// `f(t) ≈ 2.0^(10.0 * (t - 1.0))`
604
///
605
/// The precise definition adjusts it slightly so it hits both `(0, 0)` and `(1, 1)`:
606
/// `f(t) = 2.0^(10.0 * t - A) - B`, where A = log₂(2¹⁰-1) and B = 1/(2¹⁰-1).
607
///
608
#[doc = include_str!("../../images/easefunction/ExponentialIn.svg")]
609
ExponentialIn,
610
/// `f(t) ≈ 1.0 - 2.0^(-10.0 * t)`
611
///
612
/// As with `EaseFunction::ExponentialIn`, the precise definition adjusts it slightly
613
// so it hits both `(0, 0)` and `(1, 1)`.
614
///
615
#[doc = include_str!("../../images/easefunction/ExponentialOut.svg")]
616
ExponentialOut,
617
/// Behaves as `EaseFunction::ExponentialIn` for t < 0.5 and as `EaseFunction::ExponentialOut` for t >= 0.5
618
///
619
#[doc = include_str!("../../images/easefunction/ExponentialInOut.svg")]
620
ExponentialInOut,
621
622
/// `f(t) = -2.0^(10.0 * t - 10.0) * sin((t * 10.0 - 10.75) * 2.0 * π / 3.0)`
623
///
624
#[doc = include_str!("../../images/easefunction/ElasticIn.svg")]
625
ElasticIn,
626
/// `f(t) = 2.0^(-10.0 * t) * sin((t * 10.0 - 0.75) * 2.0 * π / 3.0) + 1.0`
627
///
628
#[doc = include_str!("../../images/easefunction/ElasticOut.svg")]
629
ElasticOut,
630
/// Behaves as `EaseFunction::ElasticIn` for t < 0.5 and as `EaseFunction::ElasticOut` for t >= 0.5
631
///
632
#[doc = include_str!("../../images/easefunction/ElasticInOut.svg")]
633
ElasticInOut,
634
635
/// `f(t) = 2.70158 * t³ - 1.70158 * t²`
636
///
637
#[doc = include_str!("../../images/easefunction/BackIn.svg")]
638
BackIn,
639
/// `f(t) = 1.0 + 2.70158 * (t - 1.0)³ - 1.70158 * (t - 1.0)²`
640
///
641
#[doc = include_str!("../../images/easefunction/BackOut.svg")]
642
BackOut,
643
/// Behaves as `EaseFunction::BackIn` for t < 0.5 and as `EaseFunction::BackOut` for t >= 0.5
644
///
645
#[doc = include_str!("../../images/easefunction/BackInOut.svg")]
646
BackInOut,
647
648
/// bouncy at the start!
649
///
650
#[doc = include_str!("../../images/easefunction/BounceIn.svg")]
651
BounceIn,
652
/// bouncy at the end!
653
///
654
#[doc = include_str!("../../images/easefunction/BounceOut.svg")]
655
BounceOut,
656
/// Behaves as `EaseFunction::BounceIn` for t < 0.5 and as `EaseFunction::BounceOut` for t >= 0.5
657
///
658
#[doc = include_str!("../../images/easefunction/BounceInOut.svg")]
659
BounceInOut,
660
661
/// `n` steps connecting the start and the end. Jumping behavior is customizable via
662
/// [`JumpAt`]. See [`JumpAt`] for all the options and visual examples.
663
Steps(usize, JumpAt),
664
665
/// `f(omega,t) = 1 - (1 - t)²(2sin(omega * t) / omega + cos(omega * t))`, parametrized by `omega`
666
///
667
#[doc = include_str!("../../images/easefunction/Elastic.svg")]
668
Elastic(f32),
669
}
670
671
/// `f(t) = t`
672
///
673
#[doc = include_str!("../../images/easefunction/Linear.svg")]
674
#[derive(Copy, Clone)]
675
pub struct LinearCurve;
676
677
/// `f(t) = t²`
678
///
679
/// This is the Hermite interpolator for
680
/// - f(0) = 0
681
/// - f(1) = 1
682
/// - f′(0) = 0
683
///
684
#[doc = include_str!("../../images/easefunction/QuadraticIn.svg")]
685
#[derive(Copy, Clone)]
686
pub struct QuadraticInCurve;
687
688
/// `f(t) = -(t * (t - 2.0))`
689
///
690
/// This is the Hermite interpolator for
691
/// - f(0) = 0
692
/// - f(1) = 1
693
/// - f′(1) = 0
694
///
695
#[doc = include_str!("../../images/easefunction/QuadraticOut.svg")]
696
#[derive(Copy, Clone)]
697
pub struct QuadraticOutCurve;
698
699
/// Behaves as `QuadraticIn` for t < 0.5 and as `QuadraticOut` for t >= 0.5
700
///
701
/// A quadratic has too low of a degree to be both an `InOut` and C²,
702
/// so consider using at least a cubic (such as [`SmoothStepCurve`])
703
/// if you want the acceleration to be continuous.
704
///
705
#[doc = include_str!("../../images/easefunction/QuadraticInOut.svg")]
706
#[derive(Copy, Clone)]
707
pub struct QuadraticInOutCurve;
708
709
/// `f(t) = t³`
710
///
711
/// This is the Hermite interpolator for
712
/// - f(0) = 0
713
/// - f(1) = 1
714
/// - f′(0) = 0
715
/// - f″(0) = 0
716
///
717
#[doc = include_str!("../../images/easefunction/CubicIn.svg")]
718
#[derive(Copy, Clone)]
719
pub struct CubicInCurve;
720
721
/// `f(t) = (t - 1.0)³ + 1.0`
722
///
723
#[doc = include_str!("../../images/easefunction/CubicOut.svg")]
724
#[derive(Copy, Clone)]
725
pub struct CubicOutCurve;
726
727
/// Behaves as `CubicIn` for t < 0.5 and as `CubicOut` for t >= 0.5
728
///
729
/// Due to this piecewise definition, this is only C¹ despite being a cubic:
730
/// the acceleration jumps from +12 to -12 at t = ½.
731
///
732
/// Consider using [`SmoothStepCurve`] instead, which is also cubic,
733
/// or [`SmootherStepCurve`] if you picked this because you wanted
734
/// the acceleration at the endpoints to also be zero.
735
///
736
#[doc = include_str!("../../images/easefunction/CubicInOut.svg")]
737
#[derive(Copy, Clone)]
738
pub struct CubicInOutCurve;
739
740
/// `f(t) = t⁴`
741
///
742
#[doc = include_str!("../../images/easefunction/QuarticIn.svg")]
743
#[derive(Copy, Clone)]
744
pub struct QuarticInCurve;
745
746
/// `f(t) = (t - 1.0)³ * (1.0 - t) + 1.0`
747
///
748
#[doc = include_str!("../../images/easefunction/QuarticOut.svg")]
749
#[derive(Copy, Clone)]
750
pub struct QuarticOutCurve;
751
752
/// Behaves as `QuarticIn` for t < 0.5 and as `QuarticOut` for t >= 0.5
753
///
754
#[doc = include_str!("../../images/easefunction/QuarticInOut.svg")]
755
#[derive(Copy, Clone)]
756
pub struct QuarticInOutCurve;
757
758
/// `f(t) = t⁵`
759
///
760
#[doc = include_str!("../../images/easefunction/QuinticIn.svg")]
761
#[derive(Copy, Clone)]
762
pub struct QuinticInCurve;
763
764
/// `f(t) = (t - 1.0)⁵ + 1.0`
765
///
766
#[doc = include_str!("../../images/easefunction/QuinticOut.svg")]
767
#[derive(Copy, Clone)]
768
pub struct QuinticOutCurve;
769
770
/// Behaves as `QuinticIn` for t < 0.5 and as `QuinticOut` for t >= 0.5
771
///
772
/// Due to this piecewise definition, this is only C¹ despite being a quintic:
773
/// the acceleration jumps from +40 to -40 at t = ½.
774
///
775
/// Consider using [`SmootherStepCurve`] instead, which is also quintic.
776
///
777
#[doc = include_str!("../../images/easefunction/QuinticInOut.svg")]
778
#[derive(Copy, Clone)]
779
pub struct QuinticInOutCurve;
780
781
/// Behaves as the first half of [`SmoothStepCurve`].
782
///
783
/// This has f″(1) = 0, unlike [`QuadraticInCurve`] which starts similarly.
784
///
785
#[doc = include_str!("../../images/easefunction/SmoothStepIn.svg")]
786
#[derive(Copy, Clone)]
787
pub struct SmoothStepInCurve;
788
789
/// Behaves as the second half of [`SmoothStepCurve`].
790
///
791
/// This has f″(0) = 0, unlike [`QuadraticOutCurve`] which ends similarly.
792
///
793
#[doc = include_str!("../../images/easefunction/SmoothStepOut.svg")]
794
#[derive(Copy, Clone)]
795
pub struct SmoothStepOutCurve;
796
797
/// `f(t) = 3t² - 2t³`
798
///
799
/// This is the Hermite interpolator for
800
/// - f(0) = 0
801
/// - f(1) = 1
802
/// - f′(0) = 0
803
/// - f′(1) = 0
804
///
805
/// See also [`smoothstep` in GLSL][glss].
806
///
807
/// [glss]: https://registry.khronos.org/OpenGL-Refpages/gl4/html/smoothstep.xhtml
808
///
809
#[doc = include_str!("../../images/easefunction/SmoothStep.svg")]
810
#[derive(Copy, Clone)]
811
pub struct SmoothStepCurve;
812
813
/// Behaves as the first half of [`SmootherStepCurve`].
814
///
815
/// This has f″(1) = 0, unlike [`CubicInCurve`] which starts similarly.
816
///
817
#[doc = include_str!("../../images/easefunction/SmootherStepIn.svg")]
818
#[derive(Copy, Clone)]
819
pub struct SmootherStepInCurve;
820
821
/// Behaves as the second half of [`SmootherStepCurve`].
822
///
823
/// This has f″(0) = 0, unlike [`CubicOutCurve`] which ends similarly.
824
///
825
#[doc = include_str!("../../images/easefunction/SmootherStepOut.svg")]
826
#[derive(Copy, Clone)]
827
pub struct SmootherStepOutCurve;
828
829
/// `f(t) = 6t⁵ - 15t⁴ + 10t³`
830
///
831
/// This is the Hermite interpolator for
832
/// - f(0) = 0
833
/// - f(1) = 1
834
/// - f′(0) = 0
835
/// - f′(1) = 0
836
/// - f″(0) = 0
837
/// - f″(1) = 0
838
///
839
#[doc = include_str!("../../images/easefunction/SmootherStep.svg")]
840
#[derive(Copy, Clone)]
841
pub struct SmootherStepCurve;
842
843
/// `f(t) = 1.0 - cos(t * π / 2.0)`
844
///
845
#[doc = include_str!("../../images/easefunction/SineIn.svg")]
846
#[derive(Copy, Clone)]
847
pub struct SineInCurve;
848
849
/// `f(t) = sin(t * π / 2.0)`
850
///
851
#[doc = include_str!("../../images/easefunction/SineOut.svg")]
852
#[derive(Copy, Clone)]
853
pub struct SineOutCurve;
854
855
/// Behaves as `SineIn` for t < 0.5 and as `SineOut` for t >= 0.5
856
///
857
#[doc = include_str!("../../images/easefunction/SineInOut.svg")]
858
#[derive(Copy, Clone)]
859
pub struct SineInOutCurve;
860
861
/// `f(t) = 1.0 - sqrt(1.0 - t²)`
862
///
863
#[doc = include_str!("../../images/easefunction/CircularIn.svg")]
864
#[derive(Copy, Clone)]
865
pub struct CircularInCurve;
866
867
/// `f(t) = sqrt((2.0 - t) * t)`
868
///
869
#[doc = include_str!("../../images/easefunction/CircularOut.svg")]
870
#[derive(Copy, Clone)]
871
pub struct CircularOutCurve;
872
873
/// Behaves as `CircularIn` for t < 0.5 and as `CircularOut` for t >= 0.5
874
///
875
#[doc = include_str!("../../images/easefunction/CircularInOut.svg")]
876
#[derive(Copy, Clone)]
877
pub struct CircularInOutCurve;
878
879
/// `f(t) ≈ 2.0^(10.0 * (t - 1.0))`
880
///
881
/// The precise definition adjusts it slightly so it hits both `(0, 0)` and `(1, 1)`:
882
/// `f(t) = 2.0^(10.0 * t - A) - B`, where A = log₂(2¹⁰-1) and B = 1/(2¹⁰-1).
883
///
884
#[doc = include_str!("../../images/easefunction/ExponentialIn.svg")]
885
#[derive(Copy, Clone)]
886
pub struct ExponentialInCurve;
887
888
/// `f(t) ≈ 1.0 - 2.0^(-10.0 * t)`
889
///
890
/// As with `ExponentialIn`, the precise definition adjusts it slightly
891
// so it hits both `(0, 0)` and `(1, 1)`.
892
///
893
#[doc = include_str!("../../images/easefunction/ExponentialOut.svg")]
894
#[derive(Copy, Clone)]
895
pub struct ExponentialOutCurve;
896
897
/// Behaves as `ExponentialIn` for t < 0.5 and as `ExponentialOut` for t >= 0.5
898
///
899
#[doc = include_str!("../../images/easefunction/ExponentialInOut.svg")]
900
#[derive(Copy, Clone)]
901
pub struct ExponentialInOutCurve;
902
903
/// `f(t) = -2.0^(10.0 * t - 10.0) * sin((t * 10.0 - 10.75) * 2.0 * π / 3.0)`
904
///
905
#[doc = include_str!("../../images/easefunction/ElasticIn.svg")]
906
#[derive(Copy, Clone)]
907
pub struct ElasticInCurve;
908
909
/// `f(t) = 2.0^(-10.0 * t) * sin((t * 10.0 - 0.75) * 2.0 * π / 3.0) + 1.0`
910
///
911
#[doc = include_str!("../../images/easefunction/ElasticOut.svg")]
912
#[derive(Copy, Clone)]
913
pub struct ElasticOutCurve;
914
915
/// Behaves as `ElasticIn` for t < 0.5 and as `ElasticOut` for t >= 0.5
916
///
917
#[doc = include_str!("../../images/easefunction/ElasticInOut.svg")]
918
#[derive(Copy, Clone)]
919
pub struct ElasticInOutCurve;
920
921
/// `f(t) = 2.70158 * t³ - 1.70158 * t²`
922
///
923
#[doc = include_str!("../../images/easefunction/BackIn.svg")]
924
#[derive(Copy, Clone)]
925
pub struct BackInCurve;
926
927
/// `f(t) = 1.0 + 2.70158 * (t - 1.0)³ - 1.70158 * (t - 1.0)²`
928
///
929
#[doc = include_str!("../../images/easefunction/BackOut.svg")]
930
#[derive(Copy, Clone)]
931
pub struct BackOutCurve;
932
933
/// Behaves as `BackIn` for t < 0.5 and as `BackOut` for t >= 0.5
934
///
935
#[doc = include_str!("../../images/easefunction/BackInOut.svg")]
936
#[derive(Copy, Clone)]
937
pub struct BackInOutCurve;
938
939
/// bouncy at the start!
940
///
941
#[doc = include_str!("../../images/easefunction/BounceIn.svg")]
942
#[derive(Copy, Clone)]
943
pub struct BounceInCurve;
944
945
/// bouncy at the end!
946
///
947
#[doc = include_str!("../../images/easefunction/BounceOut.svg")]
948
#[derive(Copy, Clone)]
949
pub struct BounceOutCurve;
950
951
/// Behaves as `BounceIn` for t < 0.5 and as `BounceOut` for t >= 0.5
952
///
953
#[doc = include_str!("../../images/easefunction/BounceInOut.svg")]
954
#[derive(Copy, Clone)]
955
pub struct BounceInOutCurve;
956
957
/// `n` steps connecting the start and the end. Jumping behavior is customizable via
958
/// [`JumpAt`]. See [`JumpAt`] for all the options and visual examples.
959
#[derive(Copy, Clone)]
960
pub struct StepsCurve(pub usize, pub JumpAt);
961
962
/// `f(omega,t) = 1 - (1 - t)²(2sin(omega * t) / omega + cos(omega * t))`, parametrized by `omega`
963
///
964
#[doc = include_str!("../../images/easefunction/Elastic.svg")]
965
#[derive(Copy, Clone)]
966
pub struct ElasticCurve(pub f32);
967
968
/// Implements `Curve<f32>` for a unit struct using a function in `easing_functions`.
969
macro_rules! impl_ease_unit_struct {
970
($ty: ty, $fn: ident) => {
971
impl Curve<f32> for $ty {
972
#[inline]
973
fn domain(&self) -> Interval {
974
Interval::UNIT
975
}
976
977
#[inline]
978
fn sample_unchecked(&self, t: f32) -> f32 {
979
easing_functions::$fn(t)
980
}
981
}
982
};
983
}
984
985
impl_ease_unit_struct!(LinearCurve, linear);
986
impl_ease_unit_struct!(QuadraticInCurve, quadratic_in);
987
impl_ease_unit_struct!(QuadraticOutCurve, quadratic_out);
988
impl_ease_unit_struct!(QuadraticInOutCurve, quadratic_in_out);
989
impl_ease_unit_struct!(CubicInCurve, cubic_in);
990
impl_ease_unit_struct!(CubicOutCurve, cubic_out);
991
impl_ease_unit_struct!(CubicInOutCurve, cubic_in_out);
992
impl_ease_unit_struct!(QuarticInCurve, quartic_in);
993
impl_ease_unit_struct!(QuarticOutCurve, quartic_out);
994
impl_ease_unit_struct!(QuarticInOutCurve, quartic_in_out);
995
impl_ease_unit_struct!(QuinticInCurve, quintic_in);
996
impl_ease_unit_struct!(QuinticOutCurve, quintic_out);
997
impl_ease_unit_struct!(QuinticInOutCurve, quintic_in_out);
998
impl_ease_unit_struct!(SmoothStepInCurve, smoothstep_in);
999
impl_ease_unit_struct!(SmoothStepOutCurve, smoothstep_out);
1000
impl_ease_unit_struct!(SmoothStepCurve, smoothstep);
1001
impl_ease_unit_struct!(SmootherStepInCurve, smootherstep_in);
1002
impl_ease_unit_struct!(SmootherStepOutCurve, smootherstep_out);
1003
impl_ease_unit_struct!(SmootherStepCurve, smootherstep);
1004
impl_ease_unit_struct!(SineInCurve, sine_in);
1005
impl_ease_unit_struct!(SineOutCurve, sine_out);
1006
impl_ease_unit_struct!(SineInOutCurve, sine_in_out);
1007
impl_ease_unit_struct!(CircularInCurve, circular_in);
1008
impl_ease_unit_struct!(CircularOutCurve, circular_out);
1009
impl_ease_unit_struct!(CircularInOutCurve, circular_in_out);
1010
impl_ease_unit_struct!(ExponentialInCurve, exponential_in);
1011
impl_ease_unit_struct!(ExponentialOutCurve, exponential_out);
1012
impl_ease_unit_struct!(ExponentialInOutCurve, exponential_in_out);
1013
impl_ease_unit_struct!(ElasticInCurve, elastic_in);
1014
impl_ease_unit_struct!(ElasticOutCurve, elastic_out);
1015
impl_ease_unit_struct!(ElasticInOutCurve, elastic_in_out);
1016
impl_ease_unit_struct!(BackInCurve, back_in);
1017
impl_ease_unit_struct!(BackOutCurve, back_out);
1018
impl_ease_unit_struct!(BackInOutCurve, back_in_out);
1019
impl_ease_unit_struct!(BounceInCurve, bounce_in);
1020
impl_ease_unit_struct!(BounceOutCurve, bounce_out);
1021
impl_ease_unit_struct!(BounceInOutCurve, bounce_in_out);
1022
1023
impl Curve<f32> for StepsCurve {
1024
#[inline]
1025
fn domain(&self) -> Interval {
1026
Interval::UNIT
1027
}
1028
1029
#[inline]
1030
fn sample_unchecked(&self, t: f32) -> f32 {
1031
easing_functions::steps(self.0, self.1, t)
1032
}
1033
}
1034
1035
impl Curve<f32> for ElasticCurve {
1036
#[inline]
1037
fn domain(&self) -> Interval {
1038
Interval::UNIT
1039
}
1040
1041
#[inline]
1042
fn sample_unchecked(&self, t: f32) -> f32 {
1043
easing_functions::elastic(self.0, t)
1044
}
1045
}
1046
1047
mod easing_functions {
1048
use core::f32::consts::{FRAC_PI_2, FRAC_PI_3, PI};
1049
1050
use crate::{ops, FloatPow};
1051
1052
#[inline]
1053
pub(crate) fn linear(t: f32) -> f32 {
1054
t
1055
}
1056
1057
#[inline]
1058
pub(crate) fn quadratic_in(t: f32) -> f32 {
1059
t.squared()
1060
}
1061
#[inline]
1062
pub(crate) fn quadratic_out(t: f32) -> f32 {
1063
1.0 - (1.0 - t).squared()
1064
}
1065
#[inline]
1066
pub(crate) fn quadratic_in_out(t: f32) -> f32 {
1067
if t < 0.5 {
1068
2.0 * t.squared()
1069
} else {
1070
1.0 - (-2.0 * t + 2.0).squared() / 2.0
1071
}
1072
}
1073
1074
#[inline]
1075
pub(crate) fn cubic_in(t: f32) -> f32 {
1076
t.cubed()
1077
}
1078
#[inline]
1079
pub(crate) fn cubic_out(t: f32) -> f32 {
1080
1.0 - (1.0 - t).cubed()
1081
}
1082
#[inline]
1083
pub(crate) fn cubic_in_out(t: f32) -> f32 {
1084
if t < 0.5 {
1085
4.0 * t.cubed()
1086
} else {
1087
1.0 - (-2.0 * t + 2.0).cubed() / 2.0
1088
}
1089
}
1090
1091
#[inline]
1092
pub(crate) fn quartic_in(t: f32) -> f32 {
1093
t * t * t * t
1094
}
1095
#[inline]
1096
pub(crate) fn quartic_out(t: f32) -> f32 {
1097
1.0 - (1.0 - t) * (1.0 - t) * (1.0 - t) * (1.0 - t)
1098
}
1099
#[inline]
1100
pub(crate) fn quartic_in_out(t: f32) -> f32 {
1101
if t < 0.5 {
1102
8.0 * t * t * t * t
1103
} else {
1104
1.0 - (-2.0 * t + 2.0) * (-2.0 * t + 2.0) * (-2.0 * t + 2.0) * (-2.0 * t + 2.0) / 2.0
1105
}
1106
}
1107
1108
#[inline]
1109
pub(crate) fn quintic_in(t: f32) -> f32 {
1110
t * t * t * t * t
1111
}
1112
#[inline]
1113
pub(crate) fn quintic_out(t: f32) -> f32 {
1114
1.0 - (1.0 - t) * (1.0 - t) * (1.0 - t) * (1.0 - t) * (1.0 - t)
1115
}
1116
#[inline]
1117
pub(crate) fn quintic_in_out(t: f32) -> f32 {
1118
if t < 0.5 {
1119
16.0 * t * t * t * t * t
1120
} else {
1121
1.0 - (-2.0 * t + 2.0)
1122
* (-2.0 * t + 2.0)
1123
* (-2.0 * t + 2.0)
1124
* (-2.0 * t + 2.0)
1125
* (-2.0 * t + 2.0)
1126
/ 2.0
1127
}
1128
}
1129
1130
#[inline]
1131
pub(crate) fn smoothstep_in(t: f32) -> f32 {
1132
((1.5 - 0.5 * t) * t) * t
1133
}
1134
1135
#[inline]
1136
pub(crate) fn smoothstep_out(t: f32) -> f32 {
1137
(1.5 + (-0.5 * t) * t) * t
1138
}
1139
1140
#[inline]
1141
pub(crate) fn smoothstep(t: f32) -> f32 {
1142
((3.0 - 2.0 * t) * t) * t
1143
}
1144
1145
#[inline]
1146
pub(crate) fn smootherstep_in(t: f32) -> f32 {
1147
(((2.5 + (-1.875 + 0.375 * t) * t) * t) * t) * t
1148
}
1149
1150
#[inline]
1151
pub(crate) fn smootherstep_out(t: f32) -> f32 {
1152
(1.875 + ((-1.25 + (0.375 * t) * t) * t) * t) * t
1153
}
1154
1155
#[inline]
1156
pub(crate) fn smootherstep(t: f32) -> f32 {
1157
(((10.0 + (-15.0 + 6.0 * t) * t) * t) * t) * t
1158
}
1159
1160
#[inline]
1161
pub(crate) fn sine_in(t: f32) -> f32 {
1162
1.0 - ops::cos(t * FRAC_PI_2)
1163
}
1164
#[inline]
1165
pub(crate) fn sine_out(t: f32) -> f32 {
1166
ops::sin(t * FRAC_PI_2)
1167
}
1168
#[inline]
1169
pub(crate) fn sine_in_out(t: f32) -> f32 {
1170
-(ops::cos(PI * t) - 1.0) / 2.0
1171
}
1172
1173
#[inline]
1174
pub(crate) fn circular_in(t: f32) -> f32 {
1175
1.0 - ops::sqrt(1.0 - t.squared())
1176
}
1177
#[inline]
1178
pub(crate) fn circular_out(t: f32) -> f32 {
1179
ops::sqrt(1.0 - (t - 1.0).squared())
1180
}
1181
#[inline]
1182
pub(crate) fn circular_in_out(t: f32) -> f32 {
1183
if t < 0.5 {
1184
(1.0 - ops::sqrt(1.0 - (2.0 * t).squared())) / 2.0
1185
} else {
1186
(ops::sqrt(1.0 - (-2.0 * t + 2.0).squared()) + 1.0) / 2.0
1187
}
1188
}
1189
1190
// These are copied from a high precision calculator; I'd rather show them
1191
// with blatantly more digits than needed (since rust will round them to the
1192
// nearest representable value anyway) rather than make it seem like the
1193
// truncated value is somehow carefully chosen.
1194
#[expect(
1195
clippy::excessive_precision,
1196
reason = "This is deliberately more precise than an f32 will allow, as truncating the value might imply that the value is carefully chosen."
1197
)]
1198
const LOG2_1023: f32 = 9.998590429745328646459226;
1199
#[expect(
1200
clippy::excessive_precision,
1201
reason = "This is deliberately more precise than an f32 will allow, as truncating the value might imply that the value is carefully chosen."
1202
)]
1203
const FRAC_1_1023: f32 = 0.00097751710654936461388074291;
1204
#[inline]
1205
pub(crate) fn exponential_in(t: f32) -> f32 {
1206
// Derived from a rescaled exponential formula `(2^(10*t) - 1) / (2^10 - 1)`
1207
// See <https://www.wolframalpha.com/input?i=solve+over+the+reals%3A+pow%282%2C+10-A%29+-+pow%282%2C+-A%29%3D+1>
1208
ops::exp2(10.0 * t - LOG2_1023) - FRAC_1_1023
1209
}
1210
#[inline]
1211
pub(crate) fn exponential_out(t: f32) -> f32 {
1212
(FRAC_1_1023 + 1.0) - ops::exp2(-10.0 * t - (LOG2_1023 - 10.0))
1213
}
1214
#[inline]
1215
pub(crate) fn exponential_in_out(t: f32) -> f32 {
1216
if t < 0.5 {
1217
ops::exp2(20.0 * t - (LOG2_1023 + 1.0)) - (FRAC_1_1023 / 2.0)
1218
} else {
1219
(FRAC_1_1023 / 2.0 + 1.0) - ops::exp2(-20.0 * t - (LOG2_1023 - 19.0))
1220
}
1221
}
1222
1223
#[inline]
1224
pub(crate) fn back_in(t: f32) -> f32 {
1225
let c = 1.70158;
1226
1227
(c + 1.0) * t.cubed() - c * t.squared()
1228
}
1229
#[inline]
1230
pub(crate) fn back_out(t: f32) -> f32 {
1231
let c = 1.70158;
1232
1233
1.0 + (c + 1.0) * (t - 1.0).cubed() + c * (t - 1.0).squared()
1234
}
1235
#[inline]
1236
pub(crate) fn back_in_out(t: f32) -> f32 {
1237
let c1 = 1.70158;
1238
let c2 = c1 + 1.525;
1239
1240
if t < 0.5 {
1241
(2.0 * t).squared() * ((c2 + 1.0) * 2.0 * t - c2) / 2.0
1242
} else {
1243
((2.0 * t - 2.0).squared() * ((c2 + 1.0) * (2.0 * t - 2.0) + c2) + 2.0) / 2.0
1244
}
1245
}
1246
1247
#[inline]
1248
pub(crate) fn elastic_in(t: f32) -> f32 {
1249
-ops::powf(2.0, 10.0 * t - 10.0) * ops::sin((t * 10.0 - 10.75) * 2.0 * FRAC_PI_3)
1250
}
1251
#[inline]
1252
pub(crate) fn elastic_out(t: f32) -> f32 {
1253
ops::powf(2.0, -10.0 * t) * ops::sin((t * 10.0 - 0.75) * 2.0 * FRAC_PI_3) + 1.0
1254
}
1255
#[inline]
1256
pub(crate) fn elastic_in_out(t: f32) -> f32 {
1257
let c = (2.0 * PI) / 4.5;
1258
1259
if t < 0.5 {
1260
-ops::powf(2.0, 20.0 * t - 10.0) * ops::sin((t * 20.0 - 11.125) * c) / 2.0
1261
} else {
1262
ops::powf(2.0, -20.0 * t + 10.0) * ops::sin((t * 20.0 - 11.125) * c) / 2.0 + 1.0
1263
}
1264
}
1265
1266
#[inline]
1267
pub(crate) fn bounce_in(t: f32) -> f32 {
1268
1.0 - bounce_out(1.0 - t)
1269
}
1270
#[inline]
1271
pub(crate) fn bounce_out(t: f32) -> f32 {
1272
if t < 4.0 / 11.0 {
1273
(121.0 * t.squared()) / 16.0
1274
} else if t < 8.0 / 11.0 {
1275
(363.0 / 40.0 * t.squared()) - (99.0 / 10.0 * t) + 17.0 / 5.0
1276
} else if t < 9.0 / 10.0 {
1277
(4356.0 / 361.0 * t.squared()) - (35442.0 / 1805.0 * t) + 16061.0 / 1805.0
1278
} else {
1279
(54.0 / 5.0 * t.squared()) - (513.0 / 25.0 * t) + 268.0 / 25.0
1280
}
1281
}
1282
#[inline]
1283
pub(crate) fn bounce_in_out(t: f32) -> f32 {
1284
if t < 0.5 {
1285
(1.0 - bounce_out(1.0 - 2.0 * t)) / 2.0
1286
} else {
1287
(1.0 + bounce_out(2.0 * t - 1.0)) / 2.0
1288
}
1289
}
1290
1291
#[inline]
1292
pub(crate) fn steps(num_steps: usize, jump_at: super::JumpAt, t: f32) -> f32 {
1293
jump_at.eval(num_steps, t)
1294
}
1295
1296
#[inline]
1297
pub(crate) fn elastic(omega: f32, t: f32) -> f32 {
1298
1.0 - (1.0 - t).squared() * (2.0 * ops::sin(omega * t) / omega + ops::cos(omega * t))
1299
}
1300
}
1301
1302
impl EaseFunction {
1303
fn eval(&self, t: f32) -> f32 {
1304
match self {
1305
EaseFunction::Linear => easing_functions::linear(t),
1306
EaseFunction::QuadraticIn => easing_functions::quadratic_in(t),
1307
EaseFunction::QuadraticOut => easing_functions::quadratic_out(t),
1308
EaseFunction::QuadraticInOut => easing_functions::quadratic_in_out(t),
1309
EaseFunction::CubicIn => easing_functions::cubic_in(t),
1310
EaseFunction::CubicOut => easing_functions::cubic_out(t),
1311
EaseFunction::CubicInOut => easing_functions::cubic_in_out(t),
1312
EaseFunction::QuarticIn => easing_functions::quartic_in(t),
1313
EaseFunction::QuarticOut => easing_functions::quartic_out(t),
1314
EaseFunction::QuarticInOut => easing_functions::quartic_in_out(t),
1315
EaseFunction::QuinticIn => easing_functions::quintic_in(t),
1316
EaseFunction::QuinticOut => easing_functions::quintic_out(t),
1317
EaseFunction::QuinticInOut => easing_functions::quintic_in_out(t),
1318
EaseFunction::SmoothStepIn => easing_functions::smoothstep_in(t),
1319
EaseFunction::SmoothStepOut => easing_functions::smoothstep_out(t),
1320
EaseFunction::SmoothStep => easing_functions::smoothstep(t),
1321
EaseFunction::SmootherStepIn => easing_functions::smootherstep_in(t),
1322
EaseFunction::SmootherStepOut => easing_functions::smootherstep_out(t),
1323
EaseFunction::SmootherStep => easing_functions::smootherstep(t),
1324
EaseFunction::SineIn => easing_functions::sine_in(t),
1325
EaseFunction::SineOut => easing_functions::sine_out(t),
1326
EaseFunction::SineInOut => easing_functions::sine_in_out(t),
1327
EaseFunction::CircularIn => easing_functions::circular_in(t),
1328
EaseFunction::CircularOut => easing_functions::circular_out(t),
1329
EaseFunction::CircularInOut => easing_functions::circular_in_out(t),
1330
EaseFunction::ExponentialIn => easing_functions::exponential_in(t),
1331
EaseFunction::ExponentialOut => easing_functions::exponential_out(t),
1332
EaseFunction::ExponentialInOut => easing_functions::exponential_in_out(t),
1333
EaseFunction::ElasticIn => easing_functions::elastic_in(t),
1334
EaseFunction::ElasticOut => easing_functions::elastic_out(t),
1335
EaseFunction::ElasticInOut => easing_functions::elastic_in_out(t),
1336
EaseFunction::BackIn => easing_functions::back_in(t),
1337
EaseFunction::BackOut => easing_functions::back_out(t),
1338
EaseFunction::BackInOut => easing_functions::back_in_out(t),
1339
EaseFunction::BounceIn => easing_functions::bounce_in(t),
1340
EaseFunction::BounceOut => easing_functions::bounce_out(t),
1341
EaseFunction::BounceInOut => easing_functions::bounce_in_out(t),
1342
EaseFunction::Steps(num_steps, jump_at) => {
1343
easing_functions::steps(*num_steps, *jump_at, t)
1344
}
1345
EaseFunction::Elastic(omega) => easing_functions::elastic(*omega, t),
1346
}
1347
}
1348
}
1349
1350
impl Curve<f32> for EaseFunction {
1351
#[inline]
1352
fn domain(&self) -> Interval {
1353
Interval::UNIT
1354
}
1355
1356
#[inline]
1357
fn sample_unchecked(&self, t: f32) -> f32 {
1358
self.eval(t)
1359
}
1360
}
1361
1362
#[cfg(test)]
1363
#[cfg(feature = "approx")]
1364
mod tests {
1365
1366
use crate::{Vec2, Vec3, Vec3A};
1367
use approx::assert_abs_diff_eq;
1368
1369
use super::*;
1370
const MONOTONIC_IN_OUT_INOUT: &[[EaseFunction; 3]] = {
1371
use EaseFunction::*;
1372
&[
1373
[QuadraticIn, QuadraticOut, QuadraticInOut],
1374
[CubicIn, CubicOut, CubicInOut],
1375
[QuarticIn, QuarticOut, QuarticInOut],
1376
[QuinticIn, QuinticOut, QuinticInOut],
1377
[SmoothStepIn, SmoothStepOut, SmoothStep],
1378
[SmootherStepIn, SmootherStepOut, SmootherStep],
1379
[SineIn, SineOut, SineInOut],
1380
[CircularIn, CircularOut, CircularInOut],
1381
[ExponentialIn, ExponentialOut, ExponentialInOut],
1382
]
1383
};
1384
1385
// For easing function we don't care if eval(0) is super-tiny like 2.0e-28,
1386
// so add the same amount of error on both ends of the unit interval.
1387
const TOLERANCE: f32 = 1.0e-6;
1388
const _: () = const {
1389
assert!(1.0 - TOLERANCE != 1.0);
1390
};
1391
1392
#[test]
1393
fn ease_functions_zero_to_one() {
1394
for ef in MONOTONIC_IN_OUT_INOUT.iter().flatten() {
1395
let start = ef.eval(0.0);
1396
assert!(
1397
(0.0..=TOLERANCE).contains(&start),
1398
"EaseFunction.{ef:?}(0) was {start:?}",
1399
);
1400
1401
let finish = ef.eval(1.0);
1402
assert!(
1403
(1.0 - TOLERANCE..=1.0).contains(&finish),
1404
"EaseFunction.{ef:?}(1) was {start:?}",
1405
);
1406
}
1407
}
1408
1409
#[test]
1410
fn ease_function_inout_deciles() {
1411
// convexity gives the comparisons against the input built-in tolerances
1412
for [ef_in, ef_out, ef_inout] in MONOTONIC_IN_OUT_INOUT {
1413
for x in [0.1, 0.2, 0.3, 0.4] {
1414
let y = ef_inout.eval(x);
1415
assert!(y < x, "EaseFunction.{ef_inout:?}({x:?}) was {y:?}");
1416
1417
let iny = ef_in.eval(2.0 * x) / 2.0;
1418
assert!(
1419
(y - TOLERANCE..y + TOLERANCE).contains(&iny),
1420
"EaseFunction.{ef_inout:?}({x:?}) was {y:?}, but \
1421
EaseFunction.{ef_in:?}(2 * {x:?}) / 2 was {iny:?}",
1422
);
1423
}
1424
1425
for x in [0.6, 0.7, 0.8, 0.9] {
1426
let y = ef_inout.eval(x);
1427
assert!(y > x, "EaseFunction.{ef_inout:?}({x:?}) was {y:?}");
1428
1429
let outy = ef_out.eval(2.0 * x - 1.0) / 2.0 + 0.5;
1430
assert!(
1431
(y - TOLERANCE..y + TOLERANCE).contains(&outy),
1432
"EaseFunction.{ef_inout:?}({x:?}) was {y:?}, but \
1433
EaseFunction.{ef_out:?}(2 * {x:?} - 1) / 2 + ½ was {outy:?}",
1434
);
1435
}
1436
}
1437
}
1438
1439
#[test]
1440
fn ease_function_midpoints() {
1441
for [ef_in, ef_out, ef_inout] in MONOTONIC_IN_OUT_INOUT {
1442
let mid = ef_in.eval(0.5);
1443
assert!(
1444
mid < 0.5 - TOLERANCE,
1445
"EaseFunction.{ef_in:?}(½) was {mid:?}",
1446
);
1447
1448
let mid = ef_out.eval(0.5);
1449
assert!(
1450
mid > 0.5 + TOLERANCE,
1451
"EaseFunction.{ef_out:?}(½) was {mid:?}",
1452
);
1453
1454
let mid = ef_inout.eval(0.5);
1455
assert!(
1456
(0.5 - TOLERANCE..=0.5 + TOLERANCE).contains(&mid),
1457
"EaseFunction.{ef_inout:?}(½) was {mid:?}",
1458
);
1459
}
1460
}
1461
1462
#[test]
1463
fn ease_quats() {
1464
let quat_start = Quat::from_axis_angle(Vec3::Z, 0.0);
1465
let quat_end = Quat::from_axis_angle(Vec3::Z, 90.0_f32.to_radians());
1466
1467
let quat_curve = Quat::interpolating_curve_unbounded(quat_start, quat_end);
1468
1469
assert_abs_diff_eq!(
1470
quat_curve.sample(0.0).unwrap(),
1471
Quat::from_axis_angle(Vec3::Z, 0.0)
1472
);
1473
{
1474
let (before_mid_axis, before_mid_angle) =
1475
quat_curve.sample(0.25).unwrap().to_axis_angle();
1476
assert_abs_diff_eq!(before_mid_axis, Vec3::Z);
1477
assert_abs_diff_eq!(before_mid_angle, 22.5_f32.to_radians());
1478
}
1479
{
1480
let (mid_axis, mid_angle) = quat_curve.sample(0.5).unwrap().to_axis_angle();
1481
assert_abs_diff_eq!(mid_axis, Vec3::Z);
1482
assert_abs_diff_eq!(mid_angle, 45.0_f32.to_radians());
1483
}
1484
{
1485
let (after_mid_axis, after_mid_angle) =
1486
quat_curve.sample(0.75).unwrap().to_axis_angle();
1487
assert_abs_diff_eq!(after_mid_axis, Vec3::Z);
1488
assert_abs_diff_eq!(after_mid_angle, 67.5_f32.to_radians());
1489
}
1490
assert_abs_diff_eq!(
1491
quat_curve.sample(1.0).unwrap(),
1492
Quat::from_axis_angle(Vec3::Z, 90.0_f32.to_radians())
1493
);
1494
}
1495
1496
#[test]
1497
fn ease_isometries_2d() {
1498
let angle = 90.0;
1499
let iso_2d_start = Isometry2d::new(Vec2::ZERO, Rot2::degrees(0.0));
1500
let iso_2d_end = Isometry2d::new(Vec2::ONE, Rot2::degrees(angle));
1501
1502
let iso_2d_curve = Isometry2d::interpolating_curve_unbounded(iso_2d_start, iso_2d_end);
1503
1504
[-1.0, 0.0, 0.5, 1.0, 2.0].into_iter().for_each(|t| {
1505
assert_abs_diff_eq!(
1506
iso_2d_curve.sample(t).unwrap(),
1507
Isometry2d::new(Vec2::ONE * t, Rot2::degrees(angle * t))
1508
);
1509
});
1510
}
1511
1512
#[test]
1513
fn ease_isometries_3d() {
1514
let angle = 90.0_f32.to_radians();
1515
let iso_3d_start = Isometry3d::new(Vec3A::ZERO, Quat::from_axis_angle(Vec3::Z, 0.0));
1516
let iso_3d_end = Isometry3d::new(Vec3A::ONE, Quat::from_axis_angle(Vec3::Z, angle));
1517
1518
let iso_3d_curve = Isometry3d::interpolating_curve_unbounded(iso_3d_start, iso_3d_end);
1519
1520
[-1.0, 0.0, 0.5, 1.0, 2.0].into_iter().for_each(|t| {
1521
assert_abs_diff_eq!(
1522
iso_3d_curve.sample(t).unwrap(),
1523
Isometry3d::new(Vec3A::ONE * t, Quat::from_axis_angle(Vec3::Z, angle * t))
1524
);
1525
});
1526
}
1527
1528
#[test]
1529
fn jump_at_start() {
1530
let jump_at = JumpAt::Start;
1531
let num_steps = 4;
1532
1533
[
1534
(0.0, 0.25),
1535
(0.249, 0.25),
1536
(0.25, 0.5),
1537
(0.499, 0.5),
1538
(0.5, 0.75),
1539
(0.749, 0.75),
1540
(0.75, 1.0),
1541
(1.0, 1.0),
1542
]
1543
.into_iter()
1544
.for_each(|(t, expected)| {
1545
assert_abs_diff_eq!(jump_at.eval(num_steps, t), expected);
1546
});
1547
}
1548
1549
#[test]
1550
fn jump_at_end() {
1551
let jump_at = JumpAt::End;
1552
let num_steps = 4;
1553
1554
[
1555
(0.0, 0.0),
1556
(0.249, 0.0),
1557
(0.25, 0.25),
1558
(0.499, 0.25),
1559
(0.5, 0.5),
1560
(0.749, 0.5),
1561
(0.75, 0.75),
1562
(0.999, 0.75),
1563
(1.0, 1.0),
1564
]
1565
.into_iter()
1566
.for_each(|(t, expected)| {
1567
assert_abs_diff_eq!(jump_at.eval(num_steps, t), expected);
1568
});
1569
}
1570
1571
#[test]
1572
fn jump_at_none() {
1573
let jump_at = JumpAt::None;
1574
let num_steps = 5;
1575
1576
[
1577
(0.0, 0.0),
1578
(0.199, 0.0),
1579
(0.2, 0.25),
1580
(0.399, 0.25),
1581
(0.4, 0.5),
1582
(0.599, 0.5),
1583
(0.6, 0.75),
1584
(0.799, 0.75),
1585
(0.8, 1.0),
1586
(0.999, 1.0),
1587
(1.0, 1.0),
1588
]
1589
.into_iter()
1590
.for_each(|(t, expected)| {
1591
assert_abs_diff_eq!(jump_at.eval(num_steps, t), expected);
1592
});
1593
}
1594
1595
#[test]
1596
fn jump_at_both() {
1597
let jump_at = JumpAt::Both;
1598
let num_steps = 4;
1599
1600
[
1601
(0.0, 0.2),
1602
(0.249, 0.2),
1603
(0.25, 0.4),
1604
(0.499, 0.4),
1605
(0.5, 0.6),
1606
(0.749, 0.6),
1607
(0.75, 0.8),
1608
(0.999, 0.8),
1609
(1.0, 1.0),
1610
]
1611
.into_iter()
1612
.for_each(|(t, expected)| {
1613
assert_abs_diff_eq!(jump_at.eval(num_steps, t), expected);
1614
});
1615
}
1616
1617
#[test]
1618
fn ease_function_curve() {
1619
// Test that the various ways to build an ease function are all
1620
// equivalent.
1621
1622
let f0 = SmoothStepCurve;
1623
let f1 = EaseFunction::SmoothStep;
1624
let f2 = EasingCurve::new(0.0, 1.0, EaseFunction::SmoothStep);
1625
1626
assert_eq!(f0.domain(), f1.domain());
1627
assert_eq!(f0.domain(), f2.domain());
1628
1629
[
1630
-1.0,
1631
-f32::MIN_POSITIVE,
1632
0.0,
1633
0.5,
1634
1.0,
1635
1.0 + f32::EPSILON,
1636
2.0,
1637
]
1638
.into_iter()
1639
.for_each(|t| {
1640
assert_eq!(f0.sample(t), f1.sample(t));
1641
assert_eq!(f0.sample(t), f2.sample(t));
1642
1643
assert_eq!(f0.sample_clamped(t), f1.sample_clamped(t));
1644
assert_eq!(f0.sample_clamped(t), f2.sample_clamped(t));
1645
});
1646
}
1647
1648
#[test]
1649
fn unit_structs_match_function() {
1650
// Test that the unit structs and `EaseFunction` match each other and
1651
// implement `Curve<f32>`.
1652
1653
fn test(f1: impl Curve<f32>, f2: impl Curve<f32>, t: f32) {
1654
assert_eq!(f1.sample(t), f2.sample(t));
1655
}
1656
1657
for t in [-1.0, 0.0, 0.25, 0.5, 0.75, 1.0, 2.0] {
1658
test(LinearCurve, EaseFunction::Linear, t);
1659
test(QuadraticInCurve, EaseFunction::QuadraticIn, t);
1660
test(QuadraticOutCurve, EaseFunction::QuadraticOut, t);
1661
test(QuadraticInOutCurve, EaseFunction::QuadraticInOut, t);
1662
test(CubicInCurve, EaseFunction::CubicIn, t);
1663
test(CubicOutCurve, EaseFunction::CubicOut, t);
1664
test(CubicInOutCurve, EaseFunction::CubicInOut, t);
1665
test(QuarticInCurve, EaseFunction::QuarticIn, t);
1666
test(QuarticOutCurve, EaseFunction::QuarticOut, t);
1667
test(QuarticInOutCurve, EaseFunction::QuarticInOut, t);
1668
test(QuinticInCurve, EaseFunction::QuinticIn, t);
1669
test(QuinticOutCurve, EaseFunction::QuinticOut, t);
1670
test(QuinticInOutCurve, EaseFunction::QuinticInOut, t);
1671
test(SmoothStepInCurve, EaseFunction::SmoothStepIn, t);
1672
test(SmoothStepOutCurve, EaseFunction::SmoothStepOut, t);
1673
test(SmoothStepCurve, EaseFunction::SmoothStep, t);
1674
test(SmootherStepInCurve, EaseFunction::SmootherStepIn, t);
1675
test(SmootherStepOutCurve, EaseFunction::SmootherStepOut, t);
1676
test(SmootherStepCurve, EaseFunction::SmootherStep, t);
1677
test(SineInCurve, EaseFunction::SineIn, t);
1678
test(SineOutCurve, EaseFunction::SineOut, t);
1679
test(SineInOutCurve, EaseFunction::SineInOut, t);
1680
test(CircularInCurve, EaseFunction::CircularIn, t);
1681
test(CircularOutCurve, EaseFunction::CircularOut, t);
1682
test(CircularInOutCurve, EaseFunction::CircularInOut, t);
1683
test(ExponentialInCurve, EaseFunction::ExponentialIn, t);
1684
test(ExponentialOutCurve, EaseFunction::ExponentialOut, t);
1685
test(ExponentialInOutCurve, EaseFunction::ExponentialInOut, t);
1686
test(ElasticInCurve, EaseFunction::ElasticIn, t);
1687
test(ElasticOutCurve, EaseFunction::ElasticOut, t);
1688
test(ElasticInOutCurve, EaseFunction::ElasticInOut, t);
1689
test(BackInCurve, EaseFunction::BackIn, t);
1690
test(BackOutCurve, EaseFunction::BackOut, t);
1691
test(BackInOutCurve, EaseFunction::BackInOut, t);
1692
test(BounceInCurve, EaseFunction::BounceIn, t);
1693
test(BounceOutCurve, EaseFunction::BounceOut, t);
1694
test(BounceInOutCurve, EaseFunction::BounceInOut, t);
1695
1696
test(
1697
StepsCurve(4, JumpAt::Start),
1698
EaseFunction::Steps(4, JumpAt::Start),
1699
t,
1700
);
1701
1702
test(ElasticCurve(50.0), EaseFunction::Elastic(50.0), t);
1703
}
1704
}
1705
}
1706
1707