1use core::fmt::{self, Debug, Formatter};
2use core::marker::PhantomData;
3use core::time::Duration;
4
5use crate::animation::posed::{Fading, Posed};
6use crate::animation::{AnimationStates, Running, Timed, Transition};
7use crate::mesh::Animation;
8
9const WHOLE: f32 = 1.0;
11
12const CROSSINGS: u32 = 14;
16
17pub struct Animator<M, S: AnimationStates> {
25 state: S,
26 motion: Timed,
27 fading: Option<Fading>,
28 changed: Option<Change<S>>,
29 holding: Holding,
30 seen: Duration,
33 mesh: PhantomData<fn() -> M>,
34}
35
36impl<M, S: AnimationStates> Animator<M, S> {
37 pub fn new() -> Self {
40 let state = S::entry();
41 let motion = Timed::new(&state.motion(&S::Input::default()), None, 0.0);
42
43 Self {
44 state,
45 motion,
46 fading: None,
47 changed: None,
48 holding: Holding::Running,
49 seen: Duration::ZERO,
50 mesh: PhantomData,
51 }
52 }
53
54 pub fn state(&self) -> S {
57 self.state
58 }
59
60 pub fn entered(&self, state: S) -> bool {
63 self.changed.is_some_and(|changed| changed.entered == state)
64 }
65
66 pub fn left(&self, state: S) -> bool {
69 self.changed.is_some_and(|changed| changed.left == state)
70 }
71
72 pub fn transitioning(&self) -> bool {
75 self.fading.is_some()
76 }
77
78 pub fn hold(&mut self) {
84 self.holding = Holding::Held;
85 }
86
87 pub fn resume(&mut self) {
89 if self.holding == Holding::Held {
90 self.holding = Holding::Resumed;
91 }
92 }
93
94 pub(crate) fn animate(&mut self, input: &S::Input, now: Duration, clips: &[Animation]) {
97 self.changed = None;
98 let now = now.max(self.seen);
99 let held = now.saturating_sub(self.seen);
100 let mut since = self.seen;
101 self.seen = now;
102 if self.holding != Holding::Running {
103 self.shift(held);
104 if self.holding == Holding::Held {
105 return;
106 }
107 self.holding = Holding::Running;
108 since = now;
111 }
112 self.settle(now);
113 self.motion = self.motion.started_at(now);
114
115 let at = self.motion.progress(now, clips);
116 if let Some(transition) = self.state.next(input, at) {
117 let crossing = self.crossing(transition.into, input, since, now, clips);
118 self.enter(transition, crossing, input, clips);
119 }
120 let motion = self.state.motion(input);
121 self.motion = self.motion.kept(&motion, now, clips);
122 }
123
124 pub(crate) fn running(&self) -> Running {
127 Running {
128 posed: Posed::Playing(self.motion),
129 fading: self.fading,
130 held: self.holding.stands().then_some(self.seen),
131 }
132 }
133
134 fn enter(
141 &mut self,
142 transition: Transition<S>,
143 crossing: Duration,
144 input: &S::Input,
145 clips: &[Animation],
146 ) {
147 if transition.into == self.state && !transition.restarted {
148 return;
149 }
150 self.fading = match transition.fade.is_zero() {
151 true => None,
152 false => Some(Fading {
153 under: match self.fading {
154 Some(_) => Posed::Stopped(self.running().posing(crossing, clips)),
155 None => Posed::Playing(self.motion),
156 },
157 from: crossing,
158 over: transition.fade,
159 }),
160 };
161 self.motion = Timed::new(
162 &transition.into.motion(input),
163 Some(crossing),
164 transition.entering_at,
165 );
166 self.changed = Some(Change {
167 left: self.state,
168 entered: transition.into,
169 });
170 self.state = transition.into;
171 }
172
173 fn crossing(
189 &self,
190 into: S,
191 input: &S::Input,
192 since: Duration,
193 now: Duration,
194 clips: &[Animation],
195 ) -> Duration {
196 let moves = |at: Duration| {
197 self.state
198 .next(input, self.motion.progress(at, clips))
199 .is_some_and(|transition| transition.into == into)
200 };
201 if since >= now || moves(since) {
202 return now;
203 }
204
205 let mut before = since;
206 let mut after = now;
207 for _ in 0..CROSSINGS {
208 let between = before + (after - before) / 2;
209 match moves(between) {
210 true => after = between,
211 false => before = between,
212 }
213 }
214
215 after
216 }
217
218 fn settle(&mut self, now: Duration) {
220 if self
221 .fading
222 .is_some_and(|fading| fading.weight(now) >= WHOLE)
223 {
224 self.fading = None;
225 }
226 }
227
228 fn shift(&mut self, span: Duration) {
231 self.motion = self.motion.shifted(span);
232 self.fading = self.fading.map(|fading| fading.shifted(span));
233 }
234}
235
236impl<M, S: AnimationStates> Clone for Animator<M, S> {
237 fn clone(&self) -> Self {
238 Self {
239 state: self.state,
240 motion: self.motion,
241 fading: self.fading,
242 changed: self.changed,
243 holding: self.holding,
244 seen: self.seen,
245 mesh: PhantomData,
246 }
247 }
248}
249
250impl<M, S: AnimationStates> Debug for Animator<M, S> {
251 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
254 formatter
255 .debug_struct("Animator")
256 .field("state", &self.state)
257 .field("transitioning", &self.transitioning())
258 .finish()
259 }
260}
261
262impl<M, S: AnimationStates> Default for Animator<M, S> {
263 fn default() -> Self {
265 Self::new()
266 }
267}
268
269#[derive(Clone, Copy, Debug, Eq, PartialEq)]
272enum Holding {
273 Running,
274 Held,
275 Resumed,
276}
277
278impl Holding {
279 fn stands(self) -> bool {
282 self != Self::Running
283 }
284}
285
286#[derive(Clone, Copy, Debug)]
289struct Change<S> {
290 left: S,
291 entered: S,
292}
293
294#[cfg(test)]
295mod tests {
296 use super::*;
297 use crate::animation::{Motion, Progress};
298 use crate::math::Vec3;
299 use crate::mesh::{Clip, Keys, Moves, Posing, Track};
300
301 const FADE: Duration = Duration::from_millis(200);
303
304 const TICK: Duration = Duration::from_nanos(16_666_667);
307
308 const LONG: f32 = 0.458_333_34;
311
312 const SPANS: [f32; 4] = [1.0, 0.25, LONG, 0.01];
314
315 struct Puppet;
317
318 #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
320 enum Step {
321 Slow,
322 Quick,
323 Long,
324 Brief,
325 }
326
327 impl Clip for Step {
328 fn from_name(_name: &str) -> Option<Self> {
329 None
330 }
331
332 fn all() -> Vec<Self> {
333 vec![Self::Slow, Self::Quick, Self::Long, Self::Brief]
334 }
335
336 fn index(&self) -> u32 {
337 *self as u32
338 }
339 }
340
341 fn clips() -> Vec<Animation> {
344 SPANS
345 .iter()
346 .map(|&span| {
347 Animation::new(vec![Track {
348 joint: 0,
349 moves: Moves::Position(Keys::Linear(vec![(0.0, Vec3::ZERO), (span, Vec3::X)])),
350 }])
351 })
352 .collect()
353 }
354
355 #[derive(Clone, Copy, Debug)]
358 struct Told {
359 go: bool,
360 restart: bool,
361 entering_at: f32,
362 step: u32,
363 weight: f32,
364 pace: f32,
365 openness: f32,
366 }
367
368 impl Default for Told {
369 fn default() -> Self {
371 Self {
372 go: false,
373 restart: false,
374 entering_at: 0.0,
375 step: 0,
376 weight: 0.0,
377 pace: 1.0,
378 openness: 0.0,
379 }
380 }
381 }
382
383 fn go() -> Told {
385 Told {
386 go: true,
387 ..Told::default()
388 }
389 }
390
391 fn restart(at: f32) -> Told {
394 Told {
395 restart: true,
396 entering_at: at,
397 ..Told::default()
398 }
399 }
400
401 fn mix(weight: f32, pace: f32) -> Told {
403 Told {
404 weight,
405 pace,
406 ..Told::default()
407 }
408 }
409
410 fn open(openness: f32) -> Told {
412 Told {
413 openness,
414 ..Told::default()
415 }
416 }
417
418 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
421 enum Walk {
422 Still,
423 Going,
424 Done,
425 }
426
427 impl AnimationStates for Walk {
428 type Clip = Step;
429 type Input = Told;
430
431 fn entry() -> Self {
432 Self::Still
433 }
434
435 fn motion(&self, input: &Told) -> Motion<Step> {
436 match self {
437 Self::Still => Motion::looping(Step::Slow),
438 Self::Going => Motion::once(Step::Long),
439 Self::Done => Motion::scrubbed(Step::Slow, input.openness),
440 }
441 }
442
443 fn next(&self, input: &Told, at: Progress) -> Option<Transition<Self>> {
444 match self {
445 Self::Still if input.go => Some(Self::Going.fade(FADE)),
446 Self::Going if at.ended() => Some(Self::Done.fade(FADE)),
447 _ => None,
448 }
449 }
450 }
451
452 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
454 enum Round {
455 Turning,
456 Past,
457 }
458
459 impl AnimationStates for Round {
460 type Clip = Step;
461 type Input = Told;
462
463 fn entry() -> Self {
464 Self::Turning
465 }
466
467 fn motion(&self, _input: &Told) -> Motion<Step> {
468 match self {
469 Self::Turning => Motion::looping(Step::Brief),
470 Self::Past => Motion::looping(Step::Slow),
471 }
472 }
473
474 fn next(&self, _input: &Told, at: Progress) -> Option<Transition<Self>> {
475 match self {
476 Self::Turning if at.past(0.5) => Some(Self::Past.fade(FADE)),
477 _ => None,
478 }
479 }
480 }
481
482 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
485 enum Steps {
486 First,
487 Second,
488 Third,
489 }
490
491 impl AnimationStates for Steps {
492 type Clip = Step;
493 type Input = Told;
494
495 fn entry() -> Self {
496 Self::First
497 }
498
499 fn motion(&self, _input: &Told) -> Motion<Step> {
500 match self {
501 Self::First => Motion::looping(Step::Slow),
502 Self::Second => Motion::looping(Step::Quick),
503 Self::Third => Motion::looping(Step::Long),
504 }
505 }
506
507 fn next(&self, input: &Told, _at: Progress) -> Option<Transition<Self>> {
508 let wanted = match input.step {
509 0 => Self::First,
510 1 => Self::Second,
511 _ => Self::Third,
512 };
513
514 (wanted != *self).then(|| wanted.fade(FADE))
515 }
516 }
517
518 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
520 enum Only {
521 It,
522 }
523
524 impl AnimationStates for Only {
525 type Clip = Step;
526 type Input = Told;
527
528 fn entry() -> Self {
529 Self::It
530 }
531
532 fn motion(&self, _input: &Told) -> Motion<Step> {
533 Motion::looping(Step::Slow)
534 }
535
536 fn next(&self, input: &Told, _at: Progress) -> Option<Transition<Self>> {
537 match (input.restart, input.go) {
538 (true, _) => Some(Self::It.restarted().entering_at(input.entering_at)),
539 (_, true) => Some(Self::It.fade(FADE)),
540 _ => None,
541 }
542 }
543 }
544
545 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
547 enum Into {
548 One,
549 Pair,
550 }
551
552 impl AnimationStates for Into {
553 type Clip = Step;
554 type Input = Told;
555
556 fn entry() -> Self {
557 Self::One
558 }
559
560 fn motion(&self, _input: &Told) -> Motion<Step> {
561 match self {
562 Self::One => Motion::looping(Step::Slow),
563 Self::Pair => Motion::blend(Step::Quick, Step::Long, 0.5),
564 }
565 }
566
567 fn next(&self, input: &Told, _at: Progress) -> Option<Transition<Self>> {
568 match input.go {
569 true => Some(Self::Pair.fade(FADE)),
570 false => None,
571 }
572 }
573 }
574
575 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
577 enum Opening {
578 It,
579 }
580
581 impl AnimationStates for Opening {
582 type Clip = Step;
583 type Input = Told;
584
585 fn entry() -> Self {
586 Self::It
587 }
588
589 fn motion(&self, input: &Told) -> Motion<Step> {
590 Motion::scrubbed(Step::Slow, input.openness)
591 }
592
593 fn next(&self, _input: &Told, _at: Progress) -> Option<Transition<Self>> {
594 None
595 }
596 }
597
598 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
601 enum Mixing {
602 It,
603 }
604
605 impl AnimationStates for Mixing {
606 type Clip = Step;
607 type Input = Told;
608
609 fn entry() -> Self {
610 Self::It
611 }
612
613 fn motion(&self, input: &Told) -> Motion<Step> {
614 Motion::blend(Step::Slow, Step::Quick, input.weight).paced(input.pace)
615 }
616
617 fn next(&self, _input: &Told, _at: Progress) -> Option<Transition<Self>> {
618 None
619 }
620 }
621
622 fn machine<S: AnimationStates<Input = Told>>() -> Animator<Puppet, S> {
624 Animator::new()
625 }
626
627 fn animate<S: AnimationStates<Input = Told>>(
629 animator: &mut Animator<Puppet, S>,
630 input: Told,
631 at: f32,
632 ) {
633 animator.animate(&input, seconds(at), &clips());
634 }
635
636 fn posing<S: AnimationStates<Input = Told>>(animator: &Animator<Puppet, S>, at: f32) -> Posing {
638 animator.running().posing(seconds(at), &clips())
639 }
640
641 fn seconds(at: f32) -> Duration {
643 Duration::from_secs_f32(at)
644 }
645
646 fn started(posing: Posing) -> f32 {
648 posing.from.at
649 }
650
651 fn weight(posing: Posing) -> Option<f32> {
654 posing.over[0].map(|faded| faded.weight)
655 }
656
657 fn blended(posing: Posing) -> Option<f32> {
660 posing.over[0].map(|faded| faded.sampled.at)
661 }
662
663 #[test]
664 fn a_machine_starts_in_its_entry_state_at_the_start_of_what_that_state_plays() {
665 let animator = machine::<Walk>();
666
667 assert_eq!(animator.state(), Walk::Still);
668 assert!(!animator.transitioning());
669 assert_eq!(
670 started(posing(&animator, 3.0)),
671 0.0,
672 "no clock has reached it yet"
673 );
674 }
675
676 #[test]
677 fn a_transition_starts_a_fade_of_the_length_it_states_and_is_entered_for_one_tick() {
678 let mut animator = machine::<Walk>();
679 animate(&mut animator, Told::default(), 0.0);
680 assert!(!animator.entered(Walk::Going));
681
682 animate(&mut animator, go(), 0.1);
683 assert_eq!(animator.state(), Walk::Going);
684 assert!(animator.entered(Walk::Going) && animator.left(Walk::Still));
685 assert!(animator.transitioning());
686 assert_eq!(weight(posing(&animator, 0.1)), Some(0.0));
687 assert_eq!(weight(posing(&animator, 0.2)), Some(0.5), "half a fade in");
688 assert_eq!(
689 weight(posing(&animator, 0.3)),
690 Some(1.0),
691 "and whole at its end"
692 );
693
694 animate(&mut animator, go(), 0.15);
695 assert!(
696 !animator.entered(Walk::Going) && !animator.left(Walk::Still),
697 "which reads for the tick the transition started in alone"
698 );
699 }
700
701 #[test]
702 fn the_motion_a_fade_leaves_runs_on_under_it() {
703 let mut animator = machine::<Walk>();
704 animate(&mut animator, Told::default(), 0.0);
705 animate(&mut animator, go(), 0.1);
706
707 assert!(
708 (started(posing(&animator, 0.1)) - 0.1).abs() < 1e-6,
709 "the loop it left is read where it had reached"
710 );
711 assert!(
712 (started(posing(&animator, 0.25)) - 0.25).abs() < 1e-6,
713 "and runs on while the fade does"
714 );
715 assert_eq!(
716 blended(posing(&animator, 0.1)),
717 Some(0.0),
718 "as the one it entered starts"
719 );
720 }
721
722 #[test]
723 fn a_transition_that_enters_part_way_in_starts_what_it_plays_there() {
724 let mut animator = machine::<Only>();
725 animate(&mut animator, Told::default(), 0.0);
726 animate(&mut animator, restart(0.25), 0.5);
727
728 assert_eq!(
729 blended(posing(&animator, 0.5)),
730 Some(0.25),
731 "a quarter of the way along a clip a second long"
732 );
733 }
734
735 #[test]
736 fn a_transition_to_the_state_it_is_in_moves_nothing_unless_it_starts_again() {
737 let mut running = machine::<Only>();
738 animate(&mut running, Told::default(), 0.0);
739 animate(&mut running, go(), 0.5);
740
741 assert!(!running.transitioning(), "there is nothing to fade between");
742 assert!(
743 (started(posing(&running, 0.5)) - 0.5).abs() < 1e-6,
744 "and what it plays runs on"
745 );
746
747 let mut again = machine::<Only>();
748 animate(&mut again, Told::default(), 0.0);
749 animate(&mut again, restart(0.0), 0.5);
750
751 assert!(again.transitioning());
752 assert!(again.entered(Only::It) && again.left(Only::It));
753 assert_eq!(blended(posing(&again, 0.5)), Some(0.0), "from the start");
754 }
755
756 #[test]
757 fn a_motion_that_ends_fades_from_the_instant_it_ended_and_not_from_the_tick_that_read_it() {
758 let mut animator = machine::<Walk>();
759 animate(&mut animator, go(), 0.0);
760 let mut read = Duration::ZERO;
761 while !animator.entered(Walk::Done) && read.as_secs_f32() < 1.0 {
762 read += TICK;
763 animator.animate(&go(), read, &clips());
764 }
765 let read = read.as_secs_f32();
766
767 assert_eq!(animator.state(), Walk::Done);
768 assert!(read > LONG, "the tick that read the end landed past it");
769 let crossed = (read - LONG) / FADE.as_secs_f32();
770 let weight = weight(posing(&animator, read)).expect("the fade is running");
771 assert!(
772 (weight - crossed).abs() < 2e-5,
775 "the fade reads {weight} of the way in at the tick, not the {crossed} that a fade \
776 from the end of the clip reads"
777 );
778 }
779
780 #[test]
781 fn a_clip_shorter_than_a_tick_fades_from_the_first_crossing_inside_the_tick() {
782 let mut animator = machine::<Round>();
783 animate(&mut animator, Told::default(), 0.0);
784 animator.animate(&Told::default(), TICK, &clips());
785 let read = TICK.as_secs_f32();
786
787 assert_eq!(animator.state(), Round::Past);
788 let crossed = (read - 0.005) / FADE.as_secs_f32();
792 let weight = weight(posing(&animator, read)).expect("the fade is running");
793 assert!(
794 (weight - crossed).abs() < 2e-5,
797 "the fade reads {weight} of the way in, not the {crossed} of a fade from the first \
798 crossing"
799 );
800 }
801
802 #[test]
803 fn interrupting_a_fade_stops_what_it_drew_at_the_instant_it_was_interrupted() {
804 let mut animator = machine::<Steps>();
805 animate(&mut animator, Told::default(), 0.0);
806 animate(
807 &mut animator,
808 Told {
809 step: 1,
810 ..Told::default()
811 },
812 0.1,
813 );
814 animate(
815 &mut animator,
816 Told {
817 step: 2,
818 ..Told::default()
819 },
820 0.2,
821 );
822
823 let interrupted = posing(&animator, 0.2);
824 assert!(
825 (started(interrupted) - 0.2).abs() < 1e-6,
826 "the first clip is read where it stood at the interruption"
827 );
828 let over = interrupted.over[0].expect("the fade it interrupted is stopped under it");
829 assert!(
830 (over.sampled.at - 0.1).abs() < 1e-6 && (over.weight - 0.5).abs() < 1e-5,
831 "{over:?} is not the second clip at the weight the fade had reached"
832 );
833 assert_eq!(interrupted.over[1].map(|faded| faded.weight), Some(0.0));
834
835 let later = posing(&animator, 0.3);
836 assert_eq!(
837 (started(later), later.over[0]),
838 (started(interrupted), interrupted.over[0]),
839 "and what was stopped is read the same a fade later"
840 );
841 let entering = later.over[1].expect("the state it entered fades in over that");
842 assert!(
843 (entering.weight - 0.5).abs() < 1e-5,
844 "{entering:?} is not half of the way into the fade that interrupted"
845 );
846 }
847
848 #[test]
849 fn a_fade_into_a_blend_takes_two_places_of_the_pose_and_reads_as_one_fade() {
850 let mut animator = machine::<Into>();
851 animate(&mut animator, Told::default(), 0.0);
852 animate(&mut animator, go(), 0.0);
853
854 let half = posing(&animator, 0.1);
859 let over = |at: usize| half.over[at].map(|faded| faded.weight);
860
861 assert!(
862 over(0).is_some_and(|weight| (weight - 1.0 / 3.0).abs() < 1e-5),
863 "{half:?} does not read as half of what it left"
864 );
865 assert!(
866 over(1).is_some_and(|weight| (weight - 0.25).abs() < 1e-5),
867 "{half:?} does not read as a quarter of the second of the pair"
868 );
869
870 let whole = posing(&animator, 0.2);
871 assert_eq!(
872 (
873 whole.over[0].map(|faded| faded.weight),
874 whole.over[1].map(|faded| faded.weight)
875 ),
876 (Some(1.0), Some(0.5)),
877 "and at the end of the fade the clip it left is gone and the pair \
878 is read at its own weight"
879 );
880 }
881
882 #[test]
883 fn a_scrubbed_motion_follows_the_value_it_is_given_and_no_clock() {
884 let mut animator = machine::<Opening>();
885 animate(&mut animator, open(0.25), 0.0);
886
887 assert_eq!(started(posing(&animator, 0.0)), 0.25);
888 assert_eq!(
889 started(posing(&animator, 9.0)),
890 0.25,
891 "which no clock moves"
892 );
893
894 animate(&mut animator, open(1.0), 1.0);
895 assert_eq!(started(posing(&animator, 1.0)), 1.0);
896
897 animate(&mut animator, open(4.0), 2.0);
898 assert_eq!(
899 started(posing(&animator, 2.0)),
900 1.0,
901 "a fraction past the end is held inside the clip"
902 );
903 }
904
905 #[test]
906 fn a_blend_keeps_its_place_across_a_change_of_weight() {
907 let mut animator = machine::<Mixing>();
908 animate(&mut animator, mix(0.0, 1.0), 0.0);
909 let before = posing(&animator, 0.3);
910 animate(&mut animator, mix(1.0, 1.0), 0.3);
911 let after = posing(&animator, 0.3);
912
913 assert_eq!(
914 (started(before), blended(before)),
915 (started(after), blended(after)),
916 "both clips are read where they already were"
917 );
918 assert_eq!(weight(after), Some(1.0), "at the weight it was given");
919 assert!(
920 (started(posing(&animator, 0.55)) - 0.3).abs() < 1e-6,
921 "and a cycle of the clip it now reads runs on from there"
922 );
923 }
924
925 #[test]
926 fn a_paced_motion_keeps_its_position_across_a_change_of_pace() {
927 let mut animator = machine::<Mixing>();
928 animate(&mut animator, mix(0.0, 1.0), 0.0);
929 let before = started(posing(&animator, 0.5));
930 animate(&mut animator, mix(0.0, 2.0), 0.5);
931
932 assert_eq!(started(posing(&animator, 0.5)), before);
933 assert!(
934 (started(posing(&animator, 0.6)) - 0.7).abs() < 1e-6,
935 "and runs on at twice the pace"
936 );
937 }
938
939 #[test]
940 fn a_weight_or_a_pace_outside_what_a_motion_takes_is_held_inside_it() {
941 let mut animator = machine::<Mixing>();
942 animate(&mut animator, mix(4.0, 1.0), 0.0);
943 assert_eq!(weight(posing(&animator, 0.0)), Some(1.0));
944
945 animate(&mut animator, mix(f32::NAN, 1.0), 0.1);
946 assert_eq!(
947 weight(posing(&animator, 0.1)),
948 Some(0.0),
949 "and a weight that is no number reads as none of the second clip"
950 );
951
952 let mut held = machine::<Mixing>();
953 animate(&mut held, mix(0.0, -2.0), 0.0);
954 let standing = started(posing(&held, 0.0));
955 animate(&mut held, mix(0.0, -2.0), 1.0);
956
957 assert_eq!(
958 started(posing(&held, 1.0)),
959 standing,
960 "a pace of nothing or less holds a motion where it is"
961 );
962 }
963
964 #[test]
965 fn holding_a_machine_leaves_what_it_plays_where_it_is_until_it_resumes() {
966 let mut animator = machine::<Only>();
967 animate(&mut animator, Told::default(), 0.0);
968 animate(&mut animator, Told::default(), 0.5);
969 let held = started(posing(&animator, 0.5));
970
971 animator.hold();
972 animate(&mut animator, Told::default(), 2.0);
973 assert_eq!(
974 started(posing(&animator, 2.0)),
975 held,
976 "the span it was held over counts for nothing"
977 );
978
979 animator.resume();
980 animate(&mut animator, Told::default(), 2.25);
981 assert_eq!(
982 started(posing(&animator, 2.25)),
983 held,
984 "it plays on from there"
985 );
986
987 animate(&mut animator, Told::default(), 2.5);
988 assert!((started(posing(&animator, 2.5)) - 0.75).abs() < 1e-6);
989 }
990
991 #[test]
992 fn a_held_machine_is_drawn_at_the_instant_it_was_held_whatever_instant_the_frame_draws_at() {
993 let mut animator = machine::<Walk>();
994 animate(&mut animator, Told::default(), 0.0);
995 animate(&mut animator, go(), 0.1);
996 let before = posing(&animator, 0.1);
997 animator.hold();
998 animate(&mut animator, go(), 0.15);
999
1000 assert_eq!(
1001 posing(&animator, 0.15),
1002 before,
1003 "the tick that holds it leaves the pose where the tick before found it"
1004 );
1005 assert_eq!(
1006 posing(&animator, 0.158),
1007 before,
1008 "a frame half a tick past that tick reads the same pose"
1009 );
1010 assert_eq!(
1011 posing(&animator, 0.166),
1012 before,
1013 "and so does one a whole tick past it, mid-fade"
1014 );
1015 }
1016
1017 #[test]
1018 fn a_clock_that_goes_back_is_held_to_the_last_instant_the_machine_read() {
1019 let mut animator = machine::<Only>();
1020 animate(&mut animator, Told::default(), 0.0);
1021 animate(&mut animator, Told::default(), 0.75);
1022 animate(&mut animator, Told::default(), 0.25);
1023
1024 animator.hold();
1025 animate(&mut animator, Told::default(), 1.0);
1026 assert!(
1027 (started(posing(&animator, 1.0)) - 0.75).abs() < 1e-6,
1028 "the hold counted the span since the instant it had read, not one before it"
1029 );
1030 }
1031
1032 #[test]
1033 fn two_machines_run_alike_read_alike() {
1034 let mut one = machine::<Walk>();
1035 let mut two = machine::<Walk>();
1036
1037 for tick in 0..48 {
1038 let at = tick as f32 * TICK.as_secs_f32();
1039 let told = Told {
1040 go: tick > 3,
1041 openness: 0.3,
1042 ..Told::default()
1043 };
1044 animate(&mut one, told, at);
1045 animate(&mut two, told, at);
1046
1047 assert_eq!(one.state(), two.state());
1048 assert_eq!(posing(&one, at), posing(&two, at));
1049 assert_eq!(one.transitioning(), two.transitioning());
1050 }
1051 assert_eq!(
1052 one.state(),
1053 Walk::Done,
1054 "and both ran the whole way through"
1055 );
1056 }
1057}