1use 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
22pub trait Interpolation {
26 fn sample(&self, v: f32) -> f32;
30}
31
32pub struct EaseKindPlugin;
34
35impl Plugin for EaseKindPlugin {
36 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#[derive(Debug, Copy, Clone, PartialEq, Component, Reflect)]
62#[reflect(Component)]
63#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
64pub enum EaseKind {
65 Linear,
67
68 QuadraticIn,
70 QuadraticOut,
72 QuadraticInOut,
74
75 CubicIn,
77 CubicOut,
79 CubicInOut,
81
82 QuarticIn,
84 QuarticOut,
86 QuarticInOut,
88
89 QuinticIn,
91 QuinticOut,
93 QuinticInOut,
95
96 SineIn,
98 SineOut,
100 SineInOut,
102
103 CircularIn,
105 CircularOut,
107 CircularInOut,
109
110 ExponentialIn,
112 ExponentialOut,
114 ExponentialInOut,
116
117 ElasticIn,
119 ElasticOut,
121 ElasticInOut,
123
124 BackIn,
126 BackOut,
128 BackInOut,
130
131 BounceIn,
133 BounceOut,
135 BounceInOut,
137
138 Steps(usize),
140
141 Elastic(f32),
143}
144
145impl EaseKind {
146 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
236pub struct EaseClosurePlugin;
242impl Plugin for EaseClosurePlugin {
243 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#[derive(Component)]
265pub struct EaseClosure(pub Box<dyn Fn(f32) -> f32 + Send + Sync + 'static>);
266
267impl EaseClosure {
268 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#[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}