Skip to main content

pfx_core/
tween.rs

1use crate::ease::Ease;
2use crate::motion::Sample;
3
4const SNAP: f64 = 1e-6;
5
6#[derive(Clone, Copy, Debug, PartialEq, Eq)]
7pub enum Repeat {
8    Count(u32),
9    Forever,
10}
11
12impl Default for Repeat {
13    fn default() -> Self {
14        Repeat::Count(1)
15    }
16}
17
18#[derive(Clone, Copy, Debug, PartialEq)]
19pub struct Tween<T: Sample> {
20    pub from: T,
21    pub to: T,
22    pub duration: f32,
23    pub ease: Ease,
24    pub delay: f32,
25    pub repeat: Repeat,
26    pub yoyo: bool,
27    time: f64,
28}
29
30impl<T: Sample> Tween<T> {
31    pub fn new(from: T, to: T, duration: f32) -> Self {
32        Self {
33            from,
34            to,
35            duration,
36            ease: Ease::Linear,
37            delay: 0.0,
38            repeat: Repeat::default(),
39            yoyo: false,
40            time: 0.0,
41        }
42    }
43
44    pub fn with_ease(mut self, ease: Ease) -> Self {
45        self.ease = ease;
46        self
47    }
48
49    pub fn with_delay(mut self, delay: f32) -> Self {
50        self.delay = delay;
51        self
52    }
53
54    pub fn with_repeat(mut self, repeat: Repeat) -> Self {
55        self.repeat = repeat;
56        self
57    }
58
59    pub fn with_yoyo(mut self) -> Self {
60        self.yoyo = true;
61        self
62    }
63
64    pub fn step(&mut self, dt: f32) {
65        if !dt.is_finite() || dt <= 0.0 {
66            return;
67        }
68        self.advance(self.time + f64::from(dt));
69    }
70
71    pub fn seek(&mut self, seconds: f32) {
72        if seconds.is_finite() {
73            self.advance(f64::from(seconds).max(0.0));
74        }
75    }
76
77    pub fn reset(&mut self) {
78        self.time = 0.0;
79    }
80
81    pub fn time(&self) -> f64 {
82        self.time
83    }
84
85    pub fn value(&self) -> T {
86        self.sample(self.time)
87    }
88
89    pub fn at(&self, seconds: f32) -> T {
90        self.sample(f64::from(seconds))
91    }
92
93    pub fn finished(&self) -> bool {
94        self.length().is_some_and(|total| self.time >= total)
95    }
96
97    pub fn length(&self) -> Option<f64> {
98        match self.repeat {
99            Repeat::Count(count) => {
100                Some(self.delay_seconds() + f64::from(count.max(1)) * self.duration_seconds())
101            }
102            Repeat::Forever => None,
103        }
104    }
105
106    fn delay_seconds(&self) -> f64 {
107        f64::from(self.delay).max(0.0)
108    }
109
110    fn duration_seconds(&self) -> f64 {
111        f64::from(self.duration).max(0.0)
112    }
113
114    fn advance(&mut self, to: f64) {
115        self.time = match self.length() {
116            Some(total) if to + SNAP >= total => total,
117            _ => to,
118        };
119    }
120
121    fn sample(&self, seconds: f64) -> T {
122        let local = seconds - self.delay_seconds();
123        let duration = self.duration_seconds();
124        let cycles = match self.repeat {
125            Repeat::Count(count) => Some(u64::from(count.max(1))),
126            Repeat::Forever => None,
127        };
128        let done = match self.length() {
129            Some(total) => seconds >= total,
130            None => duration <= 0.0 && local > 0.0,
131        };
132        if !done && (local.is_nan() || local <= 0.0) {
133            return self.from;
134        }
135        let (cycle, fraction) = if done {
136            (cycles.map_or(0, |count| count - 1), 1.0)
137        } else {
138            let progress = local / duration;
139            let whole = progress.floor();
140            (whole as u64, progress - whole)
141        };
142        let mut x = fraction as f32;
143        if self.yoyo && cycle % 2 == 1 {
144            x = 1.0 - x;
145        }
146        self.from.lerp(self.to, self.ease.at(x))
147    }
148}
149
150#[derive(Clone, Debug, PartialEq)]
151pub struct Sequence<T: Sample> {
152    tweens: Vec<Tween<T>>,
153    time: f64,
154}
155
156impl<T: Sample> Default for Sequence<T> {
157    fn default() -> Self {
158        Self::new()
159    }
160}
161
162impl<T: Sample> Sequence<T> {
163    pub fn new() -> Self {
164        Self {
165            tweens: Vec::new(),
166            time: 0.0,
167        }
168    }
169
170    pub fn then(mut self, tween: Tween<T>) -> Self {
171        self.tweens.push(tween);
172        self
173    }
174
175    pub fn len(&self) -> usize {
176        self.tweens.len()
177    }
178
179    pub fn is_empty(&self) -> bool {
180        self.tweens.is_empty()
181    }
182
183    pub fn step(&mut self, dt: f32) {
184        if !dt.is_finite() || dt <= 0.0 {
185            return;
186        }
187        self.advance(self.time + f64::from(dt));
188    }
189
190    pub fn seek(&mut self, seconds: f32) {
191        if seconds.is_finite() {
192            self.advance(f64::from(seconds).max(0.0));
193        }
194    }
195
196    pub fn reset(&mut self) {
197        self.time = 0.0;
198    }
199
200    pub fn time(&self) -> f64 {
201        self.time
202    }
203
204    pub fn value(&self) -> T {
205        self.sample(self.time)
206    }
207
208    pub fn at(&self, seconds: f32) -> T {
209        self.sample(f64::from(seconds))
210    }
211
212    pub fn finished(&self) -> bool {
213        self.length().is_some_and(|total| self.time >= total)
214    }
215
216    pub fn length(&self) -> Option<f64> {
217        let mut total = 0.0;
218        for tween in &self.tweens {
219            total += tween.length()?;
220        }
221        Some(total)
222    }
223
224    pub fn current(&self) -> Option<usize> {
225        let mut start = 0.0;
226        for (index, tween) in self.tweens.iter().enumerate() {
227            match tween.length() {
228                Some(len) if self.time >= start + len => start += len,
229                _ => return Some(index),
230            }
231        }
232        None
233    }
234
235    fn advance(&mut self, to: f64) {
236        self.time = match self.length() {
237            Some(total) if to + SNAP >= total => total,
238            _ => to,
239        };
240    }
241
242    fn sample(&self, seconds: f64) -> T {
243        let mut start = 0.0;
244        let mut last = T::default();
245        for tween in &self.tweens {
246            match tween.length() {
247                Some(len) => {
248                    let end = start + len;
249                    if seconds >= end {
250                        last = tween.sample(len);
251                        start = end;
252                    } else {
253                        return tween.sample(seconds - start);
254                    }
255                }
256                None => return tween.sample(seconds - start),
257            }
258        }
259        last
260    }
261}
262
263#[cfg(test)]
264mod tests {
265    use super::*;
266
267    const DT: f32 = 1.0 / 60.0;
268
269    fn close(a: f32, b: f32) -> bool {
270        (a - b).abs() < 1e-5
271    }
272
273    #[test]
274    fn sixty_steps_of_a_sixtieth_land_exactly_on_the_end() {
275        for ease in Ease::ALL {
276            let mut tween = Tween::new(2.0f32, 5.0, 1.0).with_ease(ease);
277            for _ in 0..59 {
278                tween.step(DT);
279                assert!(!tween.finished(), "{}", ease.name());
280            }
281            tween.step(DT);
282            assert!(tween.finished(), "{}", ease.name());
283            assert_eq!(tween.value().to_bits(), tween.at(1.0).to_bits());
284            assert_eq!(tween.value(), 5.0);
285            tween.step(DT);
286            assert_eq!(tween.value(), 5.0);
287        }
288    }
289
290    #[test]
291    fn stepping_agrees_with_random_access_all_the_way() {
292        for ease in Ease::ALL {
293            let mut tween = Tween::new(-1.0f32, 3.0, 1.0)
294                .with_ease(ease)
295                .with_delay(0.25);
296            let mut clock = 0.0f64;
297            for _ in 0..90 {
298                tween.step(DT);
299                clock += f64::from(DT);
300                assert!(
301                    close(tween.value(), tween.sample(clock)),
302                    "{} at {clock}",
303                    ease.name()
304                );
305            }
306            assert_eq!(tween.value(), 3.0);
307        }
308    }
309
310    #[test]
311    fn other_frame_rates_land_on_the_same_end() {
312        for frames in [24u32, 30, 48, 60, 90, 120, 144, 240] {
313            let dt = 1.0 / frames as f32;
314            let mut tween = Tween::new(0.0f32, 1.0, 1.0).with_ease(Ease::CubicInOut);
315            for _ in 0..frames {
316                tween.step(dt);
317            }
318            assert!(tween.finished(), "{frames}");
319            assert_eq!(tween.value(), 1.0, "{frames}");
320        }
321        let mut fine = Tween::new(0.0f32, 1.0, 0.7);
322        let mut coarse = fine;
323        for _ in 0..70 {
324            fine.step(0.01);
325        }
326        for _ in 0..7 {
327            coarse.step(0.1);
328        }
329        assert!(fine.finished() && coarse.finished());
330        assert_eq!(fine.value(), coarse.value());
331    }
332
333    #[test]
334    fn a_tween_holds_its_start_through_the_delay() {
335        let tween = Tween::new(10.0f32, 20.0, 2.0).with_delay(1.0);
336        assert_eq!(tween.at(-5.0), 10.0);
337        assert_eq!(tween.at(0.0), 10.0);
338        assert_eq!(tween.at(1.0), 10.0);
339        assert_eq!(tween.at(2.0), 15.0);
340        assert_eq!(tween.at(3.0), 20.0);
341        assert_eq!(tween.at(99.0), 20.0);
342        assert_eq!(tween.length(), Some(3.0));
343        let mut stepped = tween;
344        stepped.step(0.5);
345        assert_eq!(stepped.value(), 10.0);
346        assert!(!stepped.finished());
347    }
348
349    #[test]
350    fn a_repeat_count_replays_from_the_start() {
351        let tween = Tween::new(0.0f32, 1.0, 2.0).with_repeat(Repeat::Count(3));
352        assert_eq!(tween.length(), Some(6.0));
353        assert_eq!(tween.at(1.0), 0.5);
354        assert_eq!(tween.at(2.0), 0.0);
355        assert_eq!(tween.at(3.0), 0.5);
356        assert_eq!(tween.at(5.0), 0.5);
357        assert_eq!(tween.at(6.0), 1.0);
358        assert_eq!(tween.at(60.0), 1.0);
359
360        let mut stepped = tween;
361        let mut previous = 0.0f32;
362        let mut drops = 0;
363        for _ in 0..360 {
364            stepped.step(DT);
365            let value = stepped.value();
366            if value < previous {
367                drops += 1;
368            }
369            previous = value;
370        }
371        assert_eq!(drops, 2);
372        assert!(stepped.finished());
373        assert_eq!(stepped.value(), 1.0);
374
375        let once = Tween::new(0.0f32, 1.0, 2.0).with_repeat(Repeat::Count(0));
376        assert_eq!(once.length(), Some(2.0));
377        assert_eq!(once.at(2.0), 1.0);
378    }
379
380    #[test]
381    fn yoyo_runs_every_other_cycle_backward() {
382        let tween = Tween::new(0.0f32, 4.0, 1.0)
383            .with_repeat(Repeat::Count(4))
384            .with_yoyo();
385        assert_eq!(tween.at(0.25), 1.0);
386        assert_eq!(tween.at(1.0), 4.0);
387        assert_eq!(tween.at(1.25), 3.0);
388        assert_eq!(tween.at(2.0), 0.0);
389        assert_eq!(tween.at(2.25), 1.0);
390        assert_eq!(tween.at(3.5), 2.0);
391        assert_eq!(tween.at(4.0), 0.0);
392        assert_eq!(tween.at(40.0), 0.0);
393
394        let odd = Tween::new(0.0f32, 4.0, 1.0)
395            .with_repeat(Repeat::Count(3))
396            .with_yoyo();
397        assert_eq!(odd.at(3.0), 4.0);
398
399        let mut stepped = tween;
400        for _ in 0..240 {
401            stepped.step(DT);
402        }
403        assert!(stepped.finished());
404        assert_eq!(stepped.value(), 0.0);
405
406        let single = Tween::new(0.0f32, 4.0, 1.0).with_yoyo();
407        assert_eq!(single.at(1.0), 4.0);
408    }
409
410    #[test]
411    fn yoyo_mirrors_an_eased_curve_in_time() {
412        let tween = Tween::new(0.0f32, 1.0, 1.0)
413            .with_ease(Ease::BackOut)
414            .with_repeat(Repeat::Count(2))
415            .with_yoyo();
416        for i in 1..100 {
417            let t = i as f32 / 100.0;
418            let eased = Ease::BackOut.at(t);
419            assert!(close(tween.at(t), eased), "{t}");
420            assert!(close(tween.at(1.0 + t), Ease::BackOut.at(1.0 - t)), "{t}");
421            assert!(close(tween.at(2.0 - t), eased), "{t}");
422        }
423    }
424
425    #[test]
426    fn forever_never_finishes() {
427        let tween = Tween::new(0.0f32, 1.0, 1.0)
428            .with_repeat(Repeat::Forever)
429            .with_yoyo();
430        assert_eq!(tween.length(), None);
431        assert_eq!(tween.at(0.5), 0.5);
432        assert_eq!(tween.at(1.5), 0.5);
433        assert_eq!(tween.at(1001.25), 0.75);
434        assert_eq!(tween.at(1000.25), 0.25);
435        let mut stepped = tween;
436        for _ in 0..6000 {
437            stepped.step(DT);
438        }
439        assert!(!stepped.finished());
440        assert_eq!(stepped.value(), stepped.sample(stepped.time()));
441
442        let plain = Tween::new(0.0f32, 8.0, 2.0).with_repeat(Repeat::Forever);
443        assert_eq!(plain.at(3.0), 4.0);
444        assert_eq!(plain.at(201.0), 4.0);
445    }
446
447    #[test]
448    fn a_zero_length_tween_jumps_to_its_end() {
449        let tween = Tween::new(1.0f32, 2.0, 0.0);
450        assert!(tween.finished());
451        assert_eq!(tween.at(-0.1), 1.0);
452        assert_eq!(tween.at(0.0), 2.0);
453        assert_eq!(tween.value(), 2.0);
454        let delayed = Tween::new(1.0f32, 2.0, 0.0).with_delay(1.0);
455        assert_eq!(delayed.at(0.5), 1.0);
456        assert_eq!(delayed.at(1.0), 2.0);
457        assert_eq!(delayed.at(1.1), 2.0);
458        let forever = Tween::new(1.0f32, 2.0, 0.0).with_repeat(Repeat::Forever);
459        assert_eq!(forever.at(5.0), 2.0);
460        assert_eq!(forever.at(0.0), 1.0);
461    }
462
463    #[test]
464    fn bad_time_is_ignored() {
465        let mut tween = Tween::new(0.0f32, 1.0, 1.0);
466        tween.step(f32::NAN);
467        tween.step(-1.0);
468        tween.step(f32::INFINITY);
469        tween.step(0.0);
470        assert_eq!(tween.time(), 0.0);
471        tween.seek(f32::NAN);
472        assert_eq!(tween.time(), 0.0);
473        tween.seek(0.25);
474        assert_eq!(tween.value(), 0.25);
475        tween.seek(-3.0);
476        assert_eq!(tween.value(), 0.0);
477        tween.seek(0.5);
478        tween.reset();
479        assert_eq!(tween.time(), 0.0);
480        assert_eq!(tween.value(), 0.0);
481    }
482
483    #[test]
484    fn a_tween_moves_a_vector_lane_by_lane() {
485        let tween = Tween::new([0.0f32, 10.0, -4.0], [4.0, 0.0, 4.0], 2.0).with_ease(Ease::QuadIn);
486        assert_eq!(tween.at(1.0), [1.0, 7.5, -2.0]);
487        assert_eq!(tween.at(2.0), [4.0, 0.0, 4.0]);
488        let mut stepped = tween;
489        for _ in 0..120 {
490            stepped.step(DT);
491        }
492        assert_eq!(stepped.value(), [4.0, 0.0, 4.0]);
493    }
494
495    #[test]
496    fn a_sequence_chains_tweens_end_to_start() {
497        let sequence = Sequence::new()
498            .then(Tween::new(0.0f32, 1.0, 1.0))
499            .then(Tween::new(5.0f32, 7.0, 2.0).with_delay(1.0))
500            .then(Tween::new(-1.0f32, -2.0, 1.0).with_repeat(Repeat::Count(2)));
501        assert_eq!(sequence.len(), 3);
502        assert!(!sequence.is_empty());
503        assert_eq!(sequence.length(), Some(6.0));
504        assert_eq!(sequence.at(-1.0), 0.0);
505        assert_eq!(sequence.at(0.5), 0.5);
506        assert_eq!(sequence.at(1.0), 5.0);
507        assert_eq!(sequence.at(1.5), 5.0);
508        assert_eq!(sequence.at(2.0), 5.0);
509        assert_eq!(sequence.at(3.0), 6.0);
510        assert_eq!(sequence.at(4.0), -1.0);
511        assert_eq!(sequence.at(4.5), -1.5);
512        assert_eq!(sequence.at(5.0), -1.0);
513        assert_eq!(sequence.at(6.0), -2.0);
514        assert_eq!(sequence.at(100.0), -2.0);
515    }
516
517    #[test]
518    fn a_sequence_steps_like_it_is_read_at_random() {
519        let sequence = Sequence::new()
520            .then(Tween::new(0.0f32, 1.0, 0.5).with_ease(Ease::ElasticOut))
521            .then(Tween::new(1.0f32, 0.0, 0.5).with_ease(Ease::BounceOut))
522            .then(
523                Tween::new(0.0f32, 3.0, 0.25)
524                    .with_repeat(Repeat::Count(2))
525                    .with_yoyo(),
526            );
527        let mut stepped = sequence.clone();
528        let mut clock = 0.0f64;
529        for _ in 0..90 {
530            stepped.step(DT);
531            clock += f64::from(DT);
532            assert!(close(stepped.value(), sequence.sample(clock)), "{clock}");
533        }
534        assert!(stepped.finished());
535        assert_eq!(stepped.value().to_bits(), sequence.at(1.5).to_bits());
536        assert_eq!(stepped.value(), 0.0);
537    }
538
539    #[test]
540    fn sixty_steps_land_on_a_two_part_sequence() {
541        let mut sequence = Sequence::new()
542            .then(Tween::new(0.0f32, 1.0, 0.5).with_ease(Ease::SineInOut))
543            .then(Tween::new(1.0f32, 9.0, 0.5).with_ease(Ease::CircOut));
544        for _ in 0..59 {
545            sequence.step(DT);
546        }
547        assert!(!sequence.finished());
548        sequence.step(DT);
549        assert!(sequence.finished());
550        assert_eq!(sequence.value().to_bits(), sequence.at(1.0).to_bits());
551        assert_eq!(sequence.value(), 9.0);
552    }
553
554    #[test]
555    fn a_sequence_reports_where_it_is() {
556        let mut sequence = Sequence::new()
557            .then(Tween::new(0.0f32, 1.0, 1.0))
558            .then(Tween::new(1.0f32, 2.0, 1.0));
559        assert_eq!(sequence.current(), Some(0));
560        sequence.seek(1.0);
561        assert_eq!(sequence.current(), Some(1));
562        sequence.seek(2.0);
563        assert_eq!(sequence.current(), None);
564        assert!(sequence.finished());
565        sequence.reset();
566        assert_eq!(sequence.current(), Some(0));
567        assert_eq!(sequence.value(), 0.0);
568    }
569
570    #[test]
571    fn a_forever_tween_ends_its_sequence() {
572        let sequence = Sequence::new()
573            .then(Tween::new(0.0f32, 1.0, 1.0))
574            .then(Tween::new(0.0f32, 2.0, 1.0).with_repeat(Repeat::Forever))
575            .then(Tween::new(9.0f32, 9.5, 1.0));
576        assert_eq!(sequence.length(), None);
577        assert_eq!(sequence.at(1.5), 1.0);
578        assert_eq!(sequence.at(500.5), 1.0);
579        let mut stepped = sequence.clone();
580        for _ in 0..600 {
581            stepped.step(DT);
582        }
583        assert!(!stepped.finished());
584        assert_eq!(stepped.current(), Some(1));
585    }
586
587    #[test]
588    fn an_empty_sequence_rests_at_the_default() {
589        let mut sequence = Sequence::<f32>::new();
590        assert!(sequence.is_empty());
591        assert_eq!(sequence.length(), Some(0.0));
592        assert!(sequence.finished());
593        sequence.step(DT);
594        assert_eq!(sequence.value(), 0.0);
595        assert_eq!(sequence.current(), None);
596    }
597}