mirage_engine/animation/states.rs
1use core::fmt::Debug;
2use core::time::Duration;
3
4use crate::animation::{Motion, Progress};
5use crate::mesh::Clip;
6
7/// The states a game poses one mesh through, and what each of them plays.
8///
9/// Required if you want a machine to pose a draw: write the states as a
10/// fieldless enum, state what each of them plays and where it goes from
11/// there, and keep an [`Animator`](crate::Animator) of them beside every
12/// mesh the game poses.
13pub trait AnimationStates: Copy + Eq + Debug + Sized {
14 /// The clip vocabulary of the mesh these states pose.
15 type Clip: Clip;
16
17 /// What the states read to decide, the game's own to write; a machine
18 /// reads the default until `animate` first runs it.
19 type Input: Default;
20
21 /// The state a machine starts in.
22 fn entry() -> Self;
23
24 /// What this state plays, read every tick.
25 fn motion(&self, input: &Self::Input) -> Motion<Self::Clip>;
26
27 /// Where the machine goes from this state, read every tick; `at` is how
28 /// far along what it plays is, and nothing at all keeps it where it is.
29 ///
30 /// While a fade runs this is the state being faded into, so this state
31 /// and nothing both leave that fade running, and any other interrupts
32 /// it.
33 fn next(&self, input: &Self::Input, at: Progress) -> Option<Transition<Self>>;
34
35 /// Into this state over `over`; see [`Transition::fade`].
36 fn fade(self, over: Duration) -> Transition<Self> {
37 Transition::from(self).fade(over)
38 }
39
40 /// Into this state with no fade; see [`Transition::at_once`].
41 fn at_once(self) -> Transition<Self> {
42 Transition::from(self).at_once()
43 }
44
45 /// Into this state, entering `fraction` of the way in; see
46 /// [`Transition::entering_at`].
47 fn entering_at(self, fraction: f32) -> Transition<Self> {
48 Transition::from(self).entering_at(fraction)
49 }
50
51 /// Into this state from the start of what it plays again; see
52 /// [`Transition::restarted`].
53 fn restarted(self) -> Transition<Self> {
54 Transition::from(self).restarted()
55 }
56}
57
58/// Where a machine goes next, and how it gets there.
59///
60/// A state on its own reads as one that takes the default fade, so a
61/// transition that states no knob is `Some(State::Idle.into())`.
62#[must_use = "a transition only moves a machine once next returns it"]
63#[derive(Clone, Debug)]
64pub struct Transition<S> {
65 pub(crate) into: S,
66 pub(crate) fade: Duration,
67 pub(crate) entering_at: f32,
68 pub(crate) restarted: bool,
69}
70
71impl<S> Transition<S> {
72 /// The fade a transition takes to blend out of the state it leaves when
73 /// no call sets one: `150` milliseconds.
74 pub const DEFAULT_FADE: Duration = Duration::from_millis(150);
75
76 /// Takes `over` to blend out of the state it leaves;
77 /// [`Transition::DEFAULT_FADE`] until set.
78 ///
79 /// The state it leaves plays on under the fade, and a fade longer than
80 /// what the state it enters plays holds that motion's last key.
81 pub fn fade(mut self, over: Duration) -> Self {
82 self.fade = over;
83 self
84 }
85
86 /// Enters with no fade, which drops the state it leaves at the instant
87 /// it enters.
88 pub fn at_once(mut self) -> Self {
89 self.fade = Duration::ZERO;
90 self
91 }
92
93 /// Enters `fraction` of the way into what the state it enters plays, a
94 /// fraction held inside `0.0..=1.0`; the start of it until set.
95 ///
96 /// Required if you want to skip the first part of a clip a state enters
97 /// part way through.
98 pub fn entering_at(mut self, fraction: f32) -> Self {
99 self.entering_at = match fraction.is_nan() {
100 true => 0.0,
101 false => fraction.clamp(0.0, 1.0),
102 };
103 self
104 }
105
106 /// Plays what the state it enters plays from the start again, which is
107 /// what moves a machine to the state it is already in.
108 pub fn restarted(mut self) -> Self {
109 self.restarted = true;
110 self
111 }
112}
113
114impl<S> From<S> for Transition<S> {
115 /// Into `into` over [`Transition::DEFAULT_FADE`], from the start of
116 /// what it plays.
117 fn from(into: S) -> Self {
118 Self {
119 into,
120 fade: Self::DEFAULT_FADE,
121 entering_at: 0.0,
122 restarted: false,
123 }
124 }
125}