fyrox_animation/machine/
mod.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
//! Animation blending state machine.
//!
//! Machine is used to blend multiple animation as well as perform automatic "smooth transition
//! between states. See [`Machine`] docs for more info and examples.

#![warn(missing_docs)]

use crate::{
    core::{
        reflect::prelude::*,
        visitor::{Visit, VisitResult, Visitor},
    },
    AnimationContainer, AnimationPose, EntityId,
};

pub use event::Event;
use fyrox_core::{find_by_name_mut, find_by_name_ref};
pub use layer::MachineLayer;
pub use mask::LayerMask;
pub use node::{
    blend::{BlendAnimations, BlendAnimationsByIndex, BlendPose, IndexedBlendInput},
    play::PlayAnimation,
    AnimationPoseSource, PoseNode,
};
pub use parameter::{Parameter, ParameterContainer, PoseWeight};
pub use state::State;
pub use transition::Transition;

pub mod event;
pub mod layer;
pub mod mask;
pub mod node;
pub mod parameter;
pub mod state;
pub mod transition;

/// Animation blending state machine is used to blend multiple animation as well as perform automatic smooth transitions
/// between states.
///
/// # Terminology
///
/// `Node` - is a part of sub-graph that backs _states_ with animations. Typical nodes are `PlayAnimation`, `BlendAnimations`,
/// `BlendAnimationsByIndex`, etc. Nodes can be connected forming a tree, some node could be marked as output - its animation
/// will be used in parent state.
/// `State` - is a final source of animation for blending. There could be any number of states, for example typical
/// states are: `run`, `idle`, `jump` etc. A state could be marked as _entry_ state - it will be active at the first frame
/// when using the machine. There is always one state active.
/// `Transition` - is a connection between states that has transition time, a link to a parameter that defines whether the
/// transition should be performed or not. Transition is directional; there could be any number of transitions between any
/// number of states (loops are allowed).
/// `Parameter` - is a named variable of a fixed type (see `Parameters` section for more info).
/// `Layer` - is a separate state graph, there could be any number of layers - each with its own mask.
/// `Mask` - a set of handles to nodes which will be excluded from animation on a layer.
/// `Pose` - a final result of blending multiple animation into one.
///
/// Summarizing everything of this, we can describe animation blending state machine as a state graph, where each state has its
/// own sub-graph (tree) that provides animation for blending. States can be connected via transitions.
///
/// # Parameters
///
/// Parameter is a named variable of a fixed type. Parameters are used as a data source in various places in the animation
/// blending state machines. There are three main types of parameters:
///
/// `Rule` - boolean value that used as a trigger for transitions. When transition is using some rule, it checks the value
/// of the parameter and if it is `true` transition starts.
/// `Weight` - real number (`f32`) that is used a weight when you blending multiple animations into one.
/// `Index` - natural number (`i32`) that is used as an animation selector.
///
/// Each parameter has a name, it could be pretty much any string.
///
/// # Layers
///
/// Layer is a separate state graph. Layers mainly used to animate different parts of humanoid (but not only) characters. For
/// example there could a layer for upper body and a layer for lower body. Upper body layer could contain animations for aiming,
/// melee attacks while lower body layer could contain animations for standing, running, crouching, etc. This gives you an
/// ability to have running character that could aim or melee attack, or crouching and aiming, and so on with any combination.
/// Both layers use the same set of parameters, so a change in a parameter will affect all layers that use it.
///
/// # Examples
///
/// Let have a quick look at simple state machine graph with a single layer:
///
/// ```text
///                                                  +-------------+
///                                                  |  Idle Anim  |
///                                                  +------+------+
///                                                         |
///           Walk Weight                                   |
/// +-----------+      +-------+           Walk->Idle Rule  |
/// | Walk Anim +------+       |                            |
/// +-----------+      |       |      +-------+         +---+---+
///                    | Blend |      |       +-------->+       |
///                    |       +------+ Walk  |         |  Idle |
/// +-----------+      |       |      |       +<--------+       |
/// | Aim Anim  +------+       |      +--+----+         +---+---+
/// +-----------+      +-------+         |                  ^
///           Aim Weight                 | Idle->Walk Rule  |
///                                      |                  |
///                       Walk->Run Rule |    +---------+   | Run->Idle Rule
///                                      |    |         |   |
///                                      +--->+   Run   +---+
///                                           |         |
///                                           +----+----+
///                                                |
///                                                |
///                                         +------+------+
///                                         |  Run Anim   |
///                                         +-------------+
/// ```
///
/// Here we have `Walk`, `Idle`, `Run` _states_ which uses different sources of poses:
///
/// - `Run` and `Idle` both directly uses respective animations as a pose source.
/// - `Walk` - is the most complex here - it uses result of blending between `Aim` and `Walk` animations with different
/// weights. This is useful if your character can only walk or can walk *and* aim at the same time. Desired pose
/// determined by `Walk Weight` and `Aim Weight` parameters combination (see `Parameters` section for more info).
/// **Note:** Such blending is almost never used on practice, instead you should use multiple animation layers. This
/// serves only as an example that the machine can blend animations.
///
/// There are four transitions between three states each with its own _rule_. Rule is just Rule parameter which can
/// have boolean value that indicates that transition should be activated. The machine on the image above can be created
/// using code like so:
///
/// ```no_run
/// use fyrox_animation::{
///     machine::{
///         Machine, State, Transition, PoseNode,
///         Parameter, PlayAnimation, PoseWeight, BlendAnimations, BlendPose
///     },
///     core::pool::Handle
/// };
/// use fyrox_core::pool::ErasedHandle;
///
/// // Assume that these are correct handles.
/// let idle_animation = Handle::default();
/// let walk_animation = Handle::default();
/// let aim_animation = Handle::default();
///
/// let mut machine = Machine::<ErasedHandle>::new();
///
/// let root_layer = &mut machine.layers_mut()[0];
///
/// let aim = root_layer.add_node(PoseNode::PlayAnimation(PlayAnimation::new(aim_animation)));
/// let walk = root_layer.add_node(PoseNode::PlayAnimation(PlayAnimation::new(walk_animation)));
///
/// // Blend two animations together
/// let blend_aim_walk = root_layer.add_node(PoseNode::BlendAnimations(
///     BlendAnimations::new(vec![
///         BlendPose::new(PoseWeight::Constant(0.75), aim),
///         BlendPose::new(PoseWeight::Constant(0.25), walk)
///     ])
/// ));
///
/// let walk_state = root_layer.add_state(State::new("Walk", blend_aim_walk));
///
/// let idle = root_layer.add_node(PoseNode::PlayAnimation(PlayAnimation::new(idle_animation)));
/// let idle_state = root_layer.add_state(State::new("Idle", idle));
///
/// root_layer.add_transition(Transition::new("Walk->Idle", walk_state, idle_state, 1.0, "WalkToIdle"));
/// root_layer.add_transition(Transition::new("Idle->Walk", idle_state, walk_state, 1.0, "IdleToWalk"));
///
/// ```
///
/// This creates a machine with a single animation layer, fills it with some states that are backed by animation
/// sources (either simple animation playback or animation blending). You can use multiple layers to animate a single
/// model - for example one layer could be used for upper body of a character and other is lower body. This means that
/// locomotion machine will take control over lower body and combat machine will control upper body.
///
/// Complex state machines quite hard to create from code, you should use ABSM editor instead whenever possible.
#[derive(Default, Debug, Visit, Reflect, Clone, PartialEq)]
pub struct Machine<T: EntityId> {
    parameters: ParameterContainer,

    #[visit(optional)]
    layers: Vec<MachineLayer<T>>,

    #[visit(skip)]
    #[reflect(hidden)]
    final_pose: AnimationPose<T>,
}

impl<T: EntityId> Machine<T> {
    /// Creates a new animation blending state machine with a single animation layer.
    #[inline]
    pub fn new() -> Self {
        Self {
            parameters: Default::default(),
            layers: vec![MachineLayer::new()],
            final_pose: Default::default(),
        }
    }

    /// Sets a value for existing parameter with given id or registers new parameter with given id and provided value.
    /// The method returns a reference to the machine, so the calls could be chained:
    ///
    /// ```rust
    /// use fyrox_animation::machine::{Machine, Parameter};
    /// use fyrox_core::pool::ErasedHandle;
    ///
    /// let mut machine = Machine::<ErasedHandle>::new();
    ///
    /// machine
    ///     .set_parameter("Run", Parameter::Rule(true))
    ///     .set_parameter("Jump", Parameter::Rule(false));
    /// ```
    #[inline]
    pub fn set_parameter(&mut self, id: &str, new_value: Parameter) -> &mut Self {
        match self.parameters.get_mut(id) {
            Some(parameter) => {
                *parameter = new_value;
            }
            None => {
                self.parameters.add(id, new_value);
            }
        }

        self
    }

    /// Returns a shared reference to the container with all parameters used by the animation blending state machine.
    #[inline]
    pub fn parameters(&self) -> &ParameterContainer {
        &self.parameters
    }

    /// Returns a mutable reference to the container with all parameters used by the animation blending state machine.
    #[inline]
    pub fn parameters_mut(&mut self) -> &mut ParameterContainer {
        &mut self.parameters
    }

    /// Adds a new layer to the animation blending state machine.
    #[inline]
    pub fn add_layer(&mut self, layer: MachineLayer<T>) {
        self.layers.push(layer)
    }

    /// Removes a layer at given index. Panics if index is out-of-bounds.
    #[inline]
    pub fn remove_layer(&mut self, index: usize) -> MachineLayer<T> {
        self.layers.remove(index)
    }

    /// Inserts a layer at given position, panics in index is out-of-bounds.
    #[inline]
    pub fn insert_layer(&mut self, index: usize, layer: MachineLayer<T>) {
        self.layers.insert(index, layer)
    }

    /// Removes last layer from the list.
    #[inline]
    pub fn pop_layer(&mut self) -> Option<MachineLayer<T>> {
        self.layers.pop()
    }

    /// Returns a shared reference to the list of layers.
    #[inline]
    pub fn layers(&self) -> &[MachineLayer<T>] {
        &self.layers
    }

    /// Returns a mutable reference to the list of layers.
    #[inline]
    pub fn layers_mut(&mut self) -> &mut [MachineLayer<T>] {
        &mut self.layers
    }

    /// Tries to find a layer by its name. Returns index of the layer and its reference.
    #[inline]
    pub fn find_layer_by_name_ref<S: AsRef<str>>(
        &self,
        name: S,
    ) -> Option<(usize, &MachineLayer<T>)> {
        find_by_name_ref(self.layers.iter().enumerate(), name)
    }

    /// Tries to find a layer by its name. Returns index of the layer and its reference.
    #[inline]
    pub fn find_by_name_mut<S: AsRef<str>>(
        &mut self,
        name: S,
    ) -> Option<(usize, &mut MachineLayer<T>)> {
        find_by_name_mut(self.layers.iter_mut().enumerate(), name)
    }

    /// Returns final pose of the machine.
    #[inline]
    pub fn pose(&self) -> &AnimationPose<T> {
        &self.final_pose
    }

    /// Computes final animation pose that could be then applied to a set of entities graph.
    #[inline]
    pub fn evaluate_pose(
        &mut self,
        animations: &mut AnimationContainer<T>,
        dt: f32,
    ) -> &AnimationPose<T> {
        self.final_pose.reset();

        for layer in self.layers.iter_mut() {
            let weight = layer.weight();
            let pose = layer.evaluate_pose(animations, &self.parameters, dt);

            self.final_pose.blend_with(pose, weight);
        }

        &self.final_pose
    }
}