bevy_tween/
interpolation.rs

1//! Module containing ease functions and related systems.
2//!
3//! # [`Interpolation`]
4//!
5//! **Built-in interpolations**:
6//! - [`EaseKind`]
7//! - [`EaseClosure`]
8//!
9//! **Systems**:
10//! - [`sample_interpolations_system`]
11
12use bevy::prelude::*;
13
14use crate::{tween::TweenInterpolationValue, TweenSystemSet};
15use bevy_time_runner::TimeSpanProgress;
16#[cfg(feature = "serde")]
17use serde::{Deserialize, Serialize};
18
19#[cfg(feature = "bevy_lookup_curve")]
20pub mod bevy_lookup_curve;
21
22/// A trait for implementing interpolation algorithms.
23///
24/// Currently only used for registering [`sample_interpolations_system`].
25pub trait Interpolation {
26    /// Sample a value from this algorithm.
27    /// Input should be between 0–1 and returns value that should be
28    /// between 0–1
29    fn sample(&self, v: f32) -> f32;
30}
31
32/// Plugin for [`EaseKind`]
33pub struct EaseKindPlugin;
34
35impl Plugin for EaseKindPlugin {
36    /// # Panics
37    ///
38    /// Panics if [`TweenAppResource`] does not exist in world.
39    ///
40    /// [`TweenAppResource`]: crate::TweenAppResource
41    fn build(&self, app: &mut App) {
42        let app_resource = app
43            .world()
44            .get_resource::<crate::TweenAppResource>()
45            .expect("`TweenAppResource` to be is inserted to world");
46        app.add_systems(
47            app_resource.schedule,
48            sample_interpolations_system::<EaseKind>
49                .in_set(TweenSystemSet::UpdateInterpolationValue),
50        )
51        .register_type::<EaseKind>();
52    }
53}
54
55/// Curve functions over the [unit interval], commonly used for easing transitions.
56///
57/// # Note
58/// This enum is copied directly from [`EaseFunction`] and will be deprecated in future version.
59///
60/// [unit interval]: `Interval::UNIT`
61#[derive(Debug, Copy, Clone, PartialEq, Component, Reflect)]
62#[reflect(Component)]
63#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
64pub enum EaseKind {
65    /// `f(t) = t`
66    Linear,
67
68    /// `f(t) = t²`
69    QuadraticIn,
70    /// `f(t) = -(t * (t - 2.0))`
71    QuadraticOut,
72    /// Behaves as `EaseFunction::QuadraticIn` for t < 0.5 and as `EaseFunction::QuadraticOut` for t >= 0.5
73    QuadraticInOut,
74
75    /// `f(t) = t³`
76    CubicIn,
77    /// `f(t) = (t - 1.0)³ + 1.0`
78    CubicOut,
79    /// Behaves as `EaseFunction::CubicIn` for t < 0.5 and as `EaseFunction::CubicOut` for t >= 0.5
80    CubicInOut,
81
82    /// `f(t) = t⁴`
83    QuarticIn,
84    /// `f(t) = (t - 1.0)³ * (1.0 - t) + 1.0`
85    QuarticOut,
86    /// Behaves as `EaseFunction::QuarticIn` for t < 0.5 and as `EaseFunction::QuarticOut` for t >= 0.5
87    QuarticInOut,
88
89    /// `f(t) = t⁵`
90    QuinticIn,
91    /// `f(t) = (t - 1.0)⁵ + 1.0`
92    QuinticOut,
93    /// Behaves as `EaseFunction::QuinticIn` for t < 0.5 and as `EaseFunction::QuinticOut` for t >= 0.5
94    QuinticInOut,
95
96    /// `f(t) = 1.0 - cos(t * π / 2.0)`
97    SineIn,
98    /// `f(t) = sin(t * π / 2.0)`
99    SineOut,
100    /// Behaves as `EaseFunction::SineIn` for t < 0.5 and as `EaseFunction::SineOut` for t >= 0.5
101    SineInOut,
102
103    /// `f(t) = 1.0 - sqrt(1.0 - t²)`
104    CircularIn,
105    /// `f(t) = sqrt((2.0 - t) * t)`
106    CircularOut,
107    /// Behaves as `EaseFunction::CircularIn` for t < 0.5 and as `EaseFunction::CircularOut` for t >= 0.5
108    CircularInOut,
109
110    /// `f(t) = 2.0^(10.0 * (t - 1.0))`
111    ExponentialIn,
112    /// `f(t) = 1.0 - 2.0^(-10.0 * t)`
113    ExponentialOut,
114    /// Behaves as `EaseFunction::ExponentialIn` for t < 0.5 and as `EaseFunction::ExponentialOut` for t >= 0.5
115    ExponentialInOut,
116
117    /// `f(t) = -2.0^(10.0 * t - 10.0) * sin((t * 10.0 - 10.75) * 2.0 * π / 3.0)`
118    ElasticIn,
119    /// `f(t) = 2.0^(-10.0 * t) * sin((t * 10.0 - 0.75) * 2.0 * π / 3.0) + 1.0`
120    ElasticOut,
121    /// Behaves as `EaseFunction::ElasticIn` for t < 0.5 and as `EaseFunction::ElasticOut` for t >= 0.5
122    ElasticInOut,
123
124    /// `f(t) = 2.70158 * t³ - 1.70158 * t²`
125    BackIn,
126    /// `f(t) = 1.0 +  2.70158 * (t - 1.0)³ - 1.70158 * (t - 1.0)²`
127    BackOut,
128    /// Behaves as `EaseFunction::BackIn` for t < 0.5 and as `EaseFunction::BackOut` for t >= 0.5
129    BackInOut,
130
131    /// bouncy at the start!
132    BounceIn,
133    /// bouncy at the end!
134    BounceOut,
135    /// Behaves as `EaseFunction::BounceIn` for t < 0.5 and as `EaseFunction::BounceOut` for t >= 0.5
136    BounceInOut,
137
138    /// `n` steps connecting the start and the end
139    Steps(usize),
140
141    /// `f(omega,t) = 1 - (1 - t)²(2sin(omega * t) / omega + cos(omega * t))`, parametrized by `omega`
142    Elastic(f32),
143}
144
145impl EaseKind {
146    /// Sample a value from this ease function.
147    pub fn sample(&self, t: f32) -> f32 {
148        match self {
149            EaseKind::Linear => easing_functions::linear(t),
150            EaseKind::QuadraticIn => easing_functions::quadratic_in(t),
151            EaseKind::QuadraticOut => easing_functions::quadratic_out(t),
152            EaseKind::QuadraticInOut => easing_functions::quadratic_in_out(t),
153            EaseKind::CubicIn => easing_functions::cubic_in(t),
154            EaseKind::CubicOut => easing_functions::cubic_out(t),
155            EaseKind::CubicInOut => easing_functions::cubic_in_out(t),
156            EaseKind::QuarticIn => easing_functions::quartic_in(t),
157            EaseKind::QuarticOut => easing_functions::quartic_out(t),
158            EaseKind::QuarticInOut => easing_functions::quartic_in_out(t),
159            EaseKind::QuinticIn => easing_functions::quintic_in(t),
160            EaseKind::QuinticOut => easing_functions::quintic_out(t),
161            EaseKind::QuinticInOut => easing_functions::quintic_in_out(t),
162            EaseKind::SineIn => easing_functions::sine_in(t),
163            EaseKind::SineOut => easing_functions::sine_out(t),
164            EaseKind::SineInOut => easing_functions::sine_in_out(t),
165            EaseKind::CircularIn => easing_functions::circular_in(t),
166            EaseKind::CircularOut => easing_functions::circular_out(t),
167            EaseKind::CircularInOut => easing_functions::circular_in_out(t),
168            EaseKind::ExponentialIn => easing_functions::exponential_in(t),
169            EaseKind::ExponentialOut => easing_functions::exponential_out(t),
170            EaseKind::ExponentialInOut => {
171                easing_functions::exponential_in_out(t)
172            }
173            EaseKind::ElasticIn => easing_functions::elastic_in(t),
174            EaseKind::ElasticOut => easing_functions::elastic_out(t),
175            EaseKind::ElasticInOut => easing_functions::elastic_in_out(t),
176            EaseKind::BackIn => easing_functions::back_in(t),
177            EaseKind::BackOut => easing_functions::back_out(t),
178            EaseKind::BackInOut => easing_functions::back_in_out(t),
179            EaseKind::BounceIn => easing_functions::bounce_in(t),
180            EaseKind::BounceOut => easing_functions::bounce_out(t),
181            EaseKind::BounceInOut => easing_functions::bounce_in_out(t),
182            EaseKind::Steps(num_steps) => {
183                easing_functions::steps(*num_steps, t)
184            }
185            EaseKind::Elastic(omega) => easing_functions::elastic(*omega, t),
186        }
187    }
188}
189
190impl Interpolation for EaseKind {
191    fn sample(&self, v: f32) -> f32 {
192        self.sample(v)
193    }
194}
195
196impl From<EaseFunction> for EaseKind {
197    fn from(x: EaseFunction) -> Self {
198        match x {
199            EaseFunction::Linear => EaseKind::Linear,
200            EaseFunction::QuadraticIn => EaseKind::QuadraticIn,
201            EaseFunction::QuadraticOut => EaseKind::QuadraticOut,
202            EaseFunction::QuadraticInOut => EaseKind::QuadraticInOut,
203            EaseFunction::CubicIn => EaseKind::CubicIn,
204            EaseFunction::CubicOut => EaseKind::CubicOut,
205            EaseFunction::CubicInOut => EaseKind::CubicInOut,
206            EaseFunction::QuarticIn => EaseKind::QuarticIn,
207            EaseFunction::QuarticOut => EaseKind::QuarticOut,
208            EaseFunction::QuarticInOut => EaseKind::QuarticInOut,
209            EaseFunction::QuinticIn => EaseKind::QuinticIn,
210            EaseFunction::QuinticOut => EaseKind::QuinticOut,
211            EaseFunction::QuinticInOut => EaseKind::QuinticInOut,
212            EaseFunction::SineIn => EaseKind::SineIn,
213            EaseFunction::SineOut => EaseKind::SineOut,
214            EaseFunction::SineInOut => EaseKind::SineInOut,
215            EaseFunction::CircularIn => EaseKind::CircularIn,
216            EaseFunction::CircularOut => EaseKind::CircularOut,
217            EaseFunction::CircularInOut => EaseKind::CircularInOut,
218            EaseFunction::ExponentialIn => EaseKind::ExponentialIn,
219            EaseFunction::ExponentialOut => EaseKind::ExponentialOut,
220            EaseFunction::ExponentialInOut => EaseKind::ExponentialInOut,
221            EaseFunction::ElasticIn => EaseKind::ElasticIn,
222            EaseFunction::ElasticOut => EaseKind::ElasticOut,
223            EaseFunction::ElasticInOut => EaseKind::ElasticInOut,
224            EaseFunction::BackIn => EaseKind::BackIn,
225            EaseFunction::BackOut => EaseKind::BackOut,
226            EaseFunction::BackInOut => EaseKind::BackInOut,
227            EaseFunction::BounceIn => EaseKind::BounceIn,
228            EaseFunction::BounceOut => EaseKind::BounceOut,
229            EaseFunction::BounceInOut => EaseKind::BounceInOut,
230            EaseFunction::Steps(x) => EaseKind::Steps(x),
231            EaseFunction::Elastic(x) => EaseKind::Elastic(x),
232        }
233    }
234}
235
236/// Plugin for [`EaseClosure`]. In case you want to use custom an ease
237/// function. Since most people likely wouldn't use this type, this plugin is
238/// not with [`DefaultTweenPlugins`] to reduce unused system.
239///
240/// [`DefaultTweenPlugins`]: crate::DefaultTweenPlugins
241pub struct EaseClosurePlugin;
242impl Plugin for EaseClosurePlugin {
243    /// # Panics
244    ///
245    /// Panics if [`TweenAppResource`] does not exist in world.
246    ///
247    /// [`TweenAppResource`]: crate::TweenAppResource
248    fn build(&self, app: &mut App) {
249        let app_resource = app
250            .world()
251            .get_resource::<crate::TweenAppResource>()
252            .expect("`TweenAppResource` to be is inserted to world");
253        app.add_systems(
254            app_resource.schedule,
255            sample_interpolations_system::<EaseClosure>
256                .in_set(TweenSystemSet::UpdateInterpolationValue),
257        );
258    }
259}
260
261/// Use a custom easing function via a closure.
262///
263/// See [`EaseKind`].
264#[derive(Component)]
265pub struct EaseClosure(pub Box<dyn Fn(f32) -> f32 + Send + Sync + 'static>);
266
267impl EaseClosure {
268    /// Create new [`EaseClosure`]
269    pub fn new<F: Fn(f32) -> f32 + Send + Sync + 'static>(f: F) -> EaseClosure {
270        EaseClosure(Box::new(f))
271    }
272}
273
274impl Default for EaseClosure {
275    fn default() -> Self {
276        EaseClosure::new(easing_functions::linear)
277    }
278}
279
280impl Interpolation for EaseClosure {
281    fn sample(&self, v: f32) -> f32 {
282        self.0(v)
283    }
284}
285
286/// This system will automatically sample in each entities with a
287/// [`TimeSpanProgress`] component then insert [`TweenInterpolationValue`].
288/// Remove [`TweenInterpolationValue`] if [`TimeSpanProgress`] is removed.
289#[allow(clippy::type_complexity)]
290pub fn sample_interpolations_system<I>(
291    mut commands: Commands,
292    query: Query<
293        (Entity, &I, &TimeSpanProgress),
294        Or<(Changed<I>, Changed<TimeSpanProgress>)>,
295    >,
296    mut removed: RemovedComponents<TimeSpanProgress>,
297) where
298    I: Interpolation + Component,
299{
300    query.iter().for_each(|(entity, interpolator, progress)| {
301        if progress.now_percentage.is_nan() {
302            return;
303        }
304        let value = interpolator.sample(progress.now_percentage.clamp(0., 1.));
305
306        commands
307            .entity(entity)
308            .insert(TweenInterpolationValue(value));
309    });
310    removed.read().for_each(|entity| {
311        if let Some(mut entity) = commands.get_entity(entity) {
312            entity.remove::<TweenInterpolationValue>();
313        }
314    });
315}
316
317mod easing_functions {
318    use bevy::math::prelude::*;
319    use core::f32::consts::{FRAC_PI_2, FRAC_PI_3, PI};
320    use ops::FloatPow;
321
322    #[inline]
323    pub(crate) fn linear(t: f32) -> f32 {
324        t
325    }
326
327    #[inline]
328    pub(crate) fn quadratic_in(t: f32) -> f32 {
329        t.squared()
330    }
331    #[inline]
332    pub(crate) fn quadratic_out(t: f32) -> f32 {
333        1.0 - (1.0 - t).squared()
334    }
335    #[inline]
336    pub(crate) fn quadratic_in_out(t: f32) -> f32 {
337        if t < 0.5 {
338            2.0 * t.squared()
339        } else {
340            1.0 - (-2.0 * t + 2.0).squared() / 2.0
341        }
342    }
343
344    #[inline]
345    pub(crate) fn cubic_in(t: f32) -> f32 {
346        t.cubed()
347    }
348    #[inline]
349    pub(crate) fn cubic_out(t: f32) -> f32 {
350        1.0 - (1.0 - t).cubed()
351    }
352    #[inline]
353    pub(crate) fn cubic_in_out(t: f32) -> f32 {
354        if t < 0.5 {
355            4.0 * t.cubed()
356        } else {
357            1.0 - (-2.0 * t + 2.0).cubed() / 2.0
358        }
359    }
360
361    #[inline]
362    pub(crate) fn quartic_in(t: f32) -> f32 {
363        t * t * t * t
364    }
365    #[inline]
366    pub(crate) fn quartic_out(t: f32) -> f32 {
367        1.0 - (1.0 - t) * (1.0 - t) * (1.0 - t) * (1.0 - t)
368    }
369    #[inline]
370    pub(crate) fn quartic_in_out(t: f32) -> f32 {
371        if t < 0.5 {
372            8.0 * t * t * t * t
373        } else {
374            1.0 - (-2.0 * t + 2.0)
375                * (-2.0 * t + 2.0)
376                * (-2.0 * t + 2.0)
377                * (-2.0 * t + 2.0)
378                / 2.0
379        }
380    }
381
382    #[inline]
383    pub(crate) fn quintic_in(t: f32) -> f32 {
384        t * t * t * t * t
385    }
386    #[inline]
387    pub(crate) fn quintic_out(t: f32) -> f32 {
388        1.0 - (1.0 - t) * (1.0 - t) * (1.0 - t) * (1.0 - t) * (1.0 - t)
389    }
390    #[inline]
391    pub(crate) fn quintic_in_out(t: f32) -> f32 {
392        if t < 0.5 {
393            16.0 * t * t * t * t * t
394        } else {
395            1.0 - (-2.0 * t + 2.0)
396                * (-2.0 * t + 2.0)
397                * (-2.0 * t + 2.0)
398                * (-2.0 * t + 2.0)
399                * (-2.0 * t + 2.0)
400                / 2.0
401        }
402    }
403
404    #[inline]
405    pub(crate) fn sine_in(t: f32) -> f32 {
406        1.0 - ops::cos(t * FRAC_PI_2)
407    }
408    #[inline]
409    pub(crate) fn sine_out(t: f32) -> f32 {
410        ops::sin(t * FRAC_PI_2)
411    }
412    #[inline]
413    pub(crate) fn sine_in_out(t: f32) -> f32 {
414        -(ops::cos(PI * t) - 1.0) / 2.0
415    }
416
417    #[inline]
418    pub(crate) fn circular_in(t: f32) -> f32 {
419        1.0 - (1.0 - t.squared()).sqrt()
420    }
421    #[inline]
422    pub(crate) fn circular_out(t: f32) -> f32 {
423        (1.0 - (t - 1.0).squared()).sqrt()
424    }
425    #[inline]
426    pub(crate) fn circular_in_out(t: f32) -> f32 {
427        if t < 0.5 {
428            (1.0 - (1.0 - (2.0 * t).squared()).sqrt()) / 2.0
429        } else {
430            ((1.0 - (-2.0 * t + 2.0).squared()).sqrt() + 1.0) / 2.0
431        }
432    }
433
434    #[inline]
435    pub(crate) fn exponential_in(t: f32) -> f32 {
436        ops::powf(2.0, 10.0 * t - 10.0)
437    }
438    #[inline]
439    pub(crate) fn exponential_out(t: f32) -> f32 {
440        1.0 - ops::powf(2.0, -10.0 * t)
441    }
442    #[inline]
443    pub(crate) fn exponential_in_out(t: f32) -> f32 {
444        if t < 0.5 {
445            ops::powf(2.0, 20.0 * t - 10.0) / 2.0
446        } else {
447            (2.0 - ops::powf(2.0, -20.0 * t + 10.0)) / 2.0
448        }
449    }
450
451    #[inline]
452    pub(crate) fn back_in(t: f32) -> f32 {
453        let c = 1.70158;
454
455        (c + 1.0) * t.cubed() - c * t.squared()
456    }
457    #[inline]
458    pub(crate) fn back_out(t: f32) -> f32 {
459        let c = 1.70158;
460
461        1.0 + (c + 1.0) * (t - 1.0).cubed() + c * (t - 1.0).squared()
462    }
463    #[inline]
464    pub(crate) fn back_in_out(t: f32) -> f32 {
465        let c1 = 1.70158;
466        let c2 = c1 + 1.525;
467
468        if t < 0.5 {
469            (2.0 * t).squared() * ((c2 + 1.0) * 2.0 * t - c2) / 2.0
470        } else {
471            ((2.0 * t - 2.0).squared() * ((c2 + 1.0) * (2.0 * t - 2.0) + c2)
472                + 2.0)
473                / 2.0
474        }
475    }
476
477    #[inline]
478    pub(crate) fn elastic_in(t: f32) -> f32 {
479        -ops::powf(2.0, 10.0 * t - 10.0)
480            * ops::sin((t * 10.0 - 10.75) * 2.0 * FRAC_PI_3)
481    }
482    #[inline]
483    pub(crate) fn elastic_out(t: f32) -> f32 {
484        ops::powf(2.0, -10.0 * t)
485            * ops::sin((t * 10.0 - 0.75) * 2.0 * FRAC_PI_3)
486            + 1.0
487    }
488    #[inline]
489    pub(crate) fn elastic_in_out(t: f32) -> f32 {
490        let c = (2.0 * PI) / 4.5;
491
492        if t < 0.5 {
493            -ops::powf(2.0, 20.0 * t - 10.0) * ops::sin((t * 20.0 - 11.125) * c)
494                / 2.0
495        } else {
496            ops::powf(2.0, -20.0 * t + 10.0) * ops::sin((t * 20.0 - 11.125) * c)
497                / 2.0
498                + 1.0
499        }
500    }
501
502    #[inline]
503    pub(crate) fn bounce_in(t: f32) -> f32 {
504        1.0 - bounce_out(1.0 - t)
505    }
506    #[inline]
507    pub(crate) fn bounce_out(t: f32) -> f32 {
508        if t < 4.0 / 11.0 {
509            (121.0 * t.squared()) / 16.0
510        } else if t < 8.0 / 11.0 {
511            (363.0 / 40.0 * t.squared()) - (99.0 / 10.0 * t) + 17.0 / 5.0
512        } else if t < 9.0 / 10.0 {
513            (4356.0 / 361.0 * t.squared()) - (35442.0 / 1805.0 * t)
514                + 16061.0 / 1805.0
515        } else {
516            (54.0 / 5.0 * t.squared()) - (513.0 / 25.0 * t) + 268.0 / 25.0
517        }
518    }
519    #[inline]
520    pub(crate) fn bounce_in_out(t: f32) -> f32 {
521        if t < 0.5 {
522            (1.0 - bounce_out(1.0 - 2.0 * t)) / 2.0
523        } else {
524            (1.0 + bounce_out(2.0 * t - 1.0)) / 2.0
525        }
526    }
527
528    #[inline]
529    pub(crate) fn steps(num_steps: usize, t: f32) -> f32 {
530        (t * num_steps as f32).round() / num_steps.max(1) as f32
531    }
532
533    #[inline]
534    pub(crate) fn elastic(omega: f32, t: f32) -> f32 {
535        1.0 - (1.0 - t).squared()
536            * (2.0 * ops::sin(omega * t) / omega + ops::cos(omega * t))
537    }
538}