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
}
}