fyrox_ui/
absm.rs

1// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in all
11// copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19// SOFTWARE.
20
21//! Animation blending state machine is a node that takes multiple animations from an animation player and
22//! mixes them in arbitrary way into one animation. See [`AnimationBlendingStateMachine`] docs for more info.
23
24use crate::{
25    animation::{AnimationPlayer, AnimationPoseExt},
26    core::{
27        pool::Handle, reflect::prelude::*, type_traits::prelude::*, variable::InheritableVariable,
28        visitor::prelude::*,
29    },
30    define_widget_deref,
31    message::{KeyCode, MouseButton, UiMessage},
32    widget::{Widget, WidgetBuilder, WidgetMessage},
33    BuildContext, Control, UiNode, UserInterface,
34};
35use fyrox_animation::machine::Parameter;
36use fyrox_graph::constructor::{ConstructorProvider, GraphNodeConstructor};
37use fyrox_graph::{SceneGraph, SceneGraphNode};
38use std::ops::{Deref, DerefMut};
39use strum_macros::{AsRefStr, EnumString, VariantNames};
40
41/// UI-specific root motion settings.
42pub type RootMotionSettings = crate::generic_animation::RootMotionSettings<Handle<UiNode>>;
43/// UI-specific animation pose node.
44pub type PoseNode = crate::generic_animation::machine::PoseNode<Handle<UiNode>>;
45/// UI-specific animation pose node.
46pub type PlayAnimation =
47    crate::generic_animation::machine::node::play::PlayAnimation<Handle<UiNode>>;
48/// UI-specific animation blending state machine BlendAnimations node.
49pub type BlendAnimations =
50    crate::generic_animation::machine::node::blend::BlendAnimations<Handle<UiNode>>;
51/// UI-specific animation blending state machine BlendAnimationsByIndex node.
52pub type BlendAnimationsByIndex =
53    crate::generic_animation::machine::node::blend::BlendAnimationsByIndex<Handle<UiNode>>;
54/// UI-specific animation blending state machine BlendPose node.
55pub type BlendPose = crate::generic_animation::machine::node::blend::BlendPose<Handle<UiNode>>;
56/// UI-specific animation blending state machine IndexedBlendInput node.
57pub type IndexedBlendInput =
58    crate::generic_animation::machine::node::blend::IndexedBlendInput<Handle<UiNode>>;
59/// UI-specific animation blending state machine BlendSpace node.
60pub type BlendSpace =
61    crate::generic_animation::machine::node::blendspace::BlendSpace<Handle<UiNode>>;
62/// UI-specific animation blending state machine blend space point.
63pub type BlendSpacePoint =
64    crate::generic_animation::machine::node::blendspace::BlendSpacePoint<Handle<UiNode>>;
65/// UI-specific animation blending state machine layer mask.
66pub type LayerMask = crate::generic_animation::machine::mask::LayerMask<Handle<UiNode>>;
67/// UI-specific animation blending state machine layer mask.
68pub type Event = crate::generic_animation::machine::event::Event<Handle<UiNode>>;
69/// UI-specific animation blending state machine.
70pub type Machine = crate::generic_animation::machine::Machine<Handle<UiNode>>;
71/// UI-specific animation blending state machine layer.
72pub type MachineLayer = crate::generic_animation::machine::MachineLayer<Handle<UiNode>>;
73/// UI-specific animation blending state machine transition.
74pub type Transition = crate::generic_animation::machine::transition::Transition<Handle<UiNode>>;
75/// UI-specific animation blending state machine state.
76pub type State = crate::generic_animation::machine::state::State<Handle<UiNode>>;
77/// UI-specific animation blending state machine base pose node.
78pub type BasePoseNode = crate::generic_animation::machine::node::BasePoseNode<Handle<UiNode>>;
79/// UI-specific animation blending state machine state action.
80pub type StateAction = crate::generic_animation::machine::state::StateAction<Handle<UiNode>>;
81/// UI-specific animation blending state machine state action wrapper.
82pub type StateActionWrapper =
83    crate::generic_animation::machine::state::StateActionWrapper<Handle<UiNode>>;
84/// UI-specific animation blending state machine logic node.
85pub type LogicNode = crate::generic_animation::machine::transition::LogicNode<Handle<UiNode>>;
86/// UI-specific animation blending state machine And logic node.
87pub type AndNode = crate::generic_animation::machine::transition::AndNode<Handle<UiNode>>;
88/// UI-specific animation blending state machine Xor logic nde.
89pub type XorNode = crate::generic_animation::machine::transition::XorNode<Handle<UiNode>>;
90/// UI-specific animation blending state machine Or logic node.
91pub type OrNode = crate::generic_animation::machine::transition::OrNode<Handle<UiNode>>;
92/// UI-specific animation blending state machine Not logic node.
93pub type NotNode = crate::generic_animation::machine::transition::NotNode<Handle<UiNode>>;
94/// UI-specific animation blending state machine layer animation events collection.
95pub type LayerAnimationEventsCollection =
96    crate::generic_animation::machine::layer::LayerAnimationEventsCollection<Handle<UiNode>>;
97/// UI-specific animation blending state machine animation events source.
98pub type AnimationEventsSource =
99    crate::generic_animation::machine::layer::AnimationEventsSource<Handle<UiNode>>;
100
101/// Standard prelude for animation blending state machine, that contains all most commonly used types and traits.
102pub mod prelude {
103    pub use super::{
104        AndNode, AnimationBlendingStateMachine, AnimationBlendingStateMachineBuilder,
105        AnimationEventsSource, BasePoseNode, BlendAnimations, BlendAnimationsByIndex, BlendPose,
106        BlendSpace, BlendSpacePoint, Event, IndexedBlendInput, LayerAnimationEventsCollection,
107        LayerMask, LogicNode, Machine, MachineLayer, NotNode, OrNode, PlayAnimation, PoseNode,
108        RootMotionSettings, State, StateAction, StateActionWrapper, Transition, XorNode,
109    };
110    pub use crate::generic_animation::machine::{
111        node::AnimationEventCollectionStrategy,
112        parameter::{Parameter, ParameterContainer, ParameterDefinition, PoseWeight},
113    };
114}
115
116/// Animation blending state machine (ABSM) is a node that takes multiple animations from an animation player and
117/// mixes them in arbitrary way into one animation. Usually, ABSMs are used to animate humanoid characters in games,
118/// by blending multiple states with one or more animations. More info about state machines can be found in
119/// [`Machine`] docs.
120///
121/// # Important notes
122///
123/// The node does **not** contain any animations, instead it just takes animations from an animation
124/// player node and mixes them.
125#[derive(Visit, Reflect, Clone, Debug, Default, ComponentProvider, TypeUuidProvider)]
126#[type_uuid(id = "4b08c753-2a10-41e3-8fb2-4fd0517e86bc")]
127pub struct AnimationBlendingStateMachine {
128    widget: Widget,
129    #[component(include)]
130    machine: InheritableVariable<Machine>,
131    #[component(include)]
132    animation_player: InheritableVariable<Handle<UiNode>>,
133}
134
135impl ConstructorProvider<UiNode, UserInterface> for AnimationBlendingStateMachine {
136    fn constructor() -> GraphNodeConstructor<UiNode, UserInterface> {
137        GraphNodeConstructor::new::<Self>()
138            .with_variant("Animation Blending State Machine", |ui| {
139                AnimationBlendingStateMachineBuilder::new(
140                    WidgetBuilder::new().with_name("Animation Blending State Machine"),
141                )
142                .build(&mut ui.build_ctx())
143                .into()
144            })
145            .with_group("Animation")
146    }
147}
148
149impl AnimationBlendingStateMachine {
150    /// Sets new state machine to the node.
151    pub fn set_machine(&mut self, machine: Machine) {
152        self.machine.set_value_and_mark_modified(machine);
153    }
154
155    /// Returns a reference to the state machine used by the node.
156    pub fn machine(&self) -> &InheritableVariable<Machine> {
157        &self.machine
158    }
159
160    /// Returns a mutable reference to the state machine used by the node.
161    pub fn machine_mut(&mut self) -> &mut InheritableVariable<Machine> {
162        &mut self.machine
163    }
164
165    /// Sets new animation player of the node. The animation player is a source of animations for blending, the state
166    /// machine node must have the animation player specified, otherwise it won't have any effect.
167    pub fn set_animation_player(&mut self, animation_player: Handle<UiNode>) {
168        self.animation_player
169            .set_value_and_mark_modified(animation_player);
170    }
171
172    /// Returns an animation player used by the node.
173    pub fn animation_player(&self) -> Handle<UiNode> {
174        *self.animation_player
175    }
176}
177
178define_widget_deref!(AnimationBlendingStateMachine);
179
180impl Control for AnimationBlendingStateMachine {
181    fn update(&mut self, dt: f32, ui: &mut UserInterface) {
182        if let Some(animation_player) = ui
183            .nodes
184            .try_borrow_mut(*self.animation_player)
185            .and_then(|n| n.component_mut::<AnimationPlayer>())
186        {
187            // Prevent animation player to apply animation to scene nodes. The animation will
188            // do than instead.
189            animation_player.set_auto_apply(false);
190
191            let pose = self
192                .machine
193                .get_value_mut_silent()
194                .evaluate_pose(animation_player.animations.get_value_mut_silent(), dt);
195
196            pose.apply(ui);
197        }
198    }
199
200    fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
201        self.widget.handle_routed_message(ui, message)
202    }
203}
204
205/// Animation blending state machine builder allows you to create state machines in declarative manner.
206pub struct AnimationBlendingStateMachineBuilder {
207    widget_builder: WidgetBuilder,
208    machine: Machine,
209    animation_player: Handle<UiNode>,
210}
211
212impl AnimationBlendingStateMachineBuilder {
213    /// Creates new builder instance.
214    pub fn new(widget_builder: WidgetBuilder) -> Self {
215        Self {
216            widget_builder,
217            machine: Default::default(),
218            animation_player: Default::default(),
219        }
220    }
221
222    /// Sets the desired state machine.
223    pub fn with_machine(mut self, machine: Machine) -> Self {
224        self.machine = machine;
225        self
226    }
227
228    /// Sets the animation player as a source of animations.
229    pub fn with_animation_player(mut self, animation_player: Handle<UiNode>) -> Self {
230        self.animation_player = animation_player;
231        self
232    }
233
234    /// Creates new node.
235    pub fn build_node(self, ctx: &BuildContext) -> UiNode {
236        UiNode::new(AnimationBlendingStateMachine {
237            widget: self.widget_builder.with_need_update(true).build(ctx),
238            machine: self.machine.into(),
239            animation_player: self.animation_player.into(),
240        })
241    }
242
243    /// Creates new node and adds it to the user interface.
244    pub fn build(self, ctx: &mut BuildContext) -> Handle<UiNode> {
245        ctx.add_node(self.build_node(ctx))
246    }
247}
248
249#[derive(
250    Visit,
251    Reflect,
252    Clone,
253    Debug,
254    Default,
255    PartialEq,
256    TypeUuidProvider,
257    AsRefStr,
258    EnumString,
259    VariantNames,
260)]
261#[type_uuid(id = "291e8734-47df-408e-8b2c-57bfb941d8ec")]
262pub enum EventKind {
263    #[default]
264    MouseEnter,
265    MouseLeave,
266    MouseMove,
267    MouseDown(MouseButton),
268    MouseUp(MouseButton),
269    MouseWheel,
270    KeyDown(KeyCode),
271    KeyUp(KeyCode),
272    Focus,
273    Unfocus,
274    TouchStarted,
275    TouchEnded,
276    TouchMoved,
277    TouchCancelled,
278    DoubleTap,
279}
280
281#[derive(Visit, Reflect, Clone, Debug, Default, PartialEq, TypeUuidProvider)]
282#[type_uuid(id = "15f306b8-3bb8-4b35-87bd-6e9e5d748454")]
283pub struct EventAction {
284    kind: EventKind,
285    parameter_name: String,
286    parameter_value: Parameter,
287}
288
289/// A widget that listens for particular events and sets parameters in an ABSM accordingly.
290#[derive(Visit, Reflect, Clone, Debug, Default, ComponentProvider, TypeUuidProvider)]
291#[type_uuid(id = "15f306b8-3bb8-4b35-87bd-6e9e5d748455")]
292pub struct AbsmEventProvider {
293    widget: Widget,
294    actions: InheritableVariable<Vec<EventAction>>,
295    absm: InheritableVariable<Handle<UiNode>>,
296}
297
298impl ConstructorProvider<UiNode, UserInterface> for AbsmEventProvider {
299    fn constructor() -> GraphNodeConstructor<UiNode, UserInterface> {
300        GraphNodeConstructor::new::<Self>()
301            .with_variant("Absm Event Provider", |ui| {
302                AbsmEventProviderBuilder::new(WidgetBuilder::new().with_name("Absm Event Provider"))
303                    .build(&mut ui.build_ctx())
304                    .into()
305            })
306            .with_group("Animation")
307    }
308}
309
310define_widget_deref!(AbsmEventProvider);
311
312impl AbsmEventProvider {
313    fn on_event(&self, ui: &mut UserInterface, kind: EventKind) {
314        let Some(action) = self.actions.iter().find(|a| a.kind == kind) else {
315            return;
316        };
317
318        let Some(absm) = ui.try_get_mut_of_type::<AnimationBlendingStateMachine>(*self.absm) else {
319            return;
320        };
321
322        absm.machine_mut()
323            .set_parameter(&action.parameter_name, action.parameter_value);
324    }
325}
326
327impl Control for AbsmEventProvider {
328    fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
329        self.widget.handle_routed_message(ui, message);
330
331        let Some(msg) = message.data::<WidgetMessage>() else {
332            return;
333        };
334
335        use WidgetMessage as Msg;
336        match msg {
337            Msg::MouseDown { button, .. } => self.on_event(ui, EventKind::MouseDown(*button)),
338            Msg::MouseUp { button, .. } => self.on_event(ui, EventKind::MouseUp(*button)),
339            Msg::MouseMove { .. } => self.on_event(ui, EventKind::MouseMove),
340            Msg::MouseWheel { .. } => self.on_event(ui, EventKind::MouseWheel),
341            Msg::MouseLeave => self.on_event(ui, EventKind::MouseLeave),
342            Msg::MouseEnter => self.on_event(ui, EventKind::MouseEnter),
343            Msg::Focus => self.on_event(ui, EventKind::Focus),
344            Msg::Unfocus => self.on_event(ui, EventKind::Unfocus),
345            Msg::TouchStarted { .. } => self.on_event(ui, EventKind::TouchStarted),
346            Msg::TouchEnded { .. } => self.on_event(ui, EventKind::TouchEnded),
347            Msg::TouchMoved { .. } => self.on_event(ui, EventKind::TouchMoved),
348            Msg::TouchCancelled { .. } => self.on_event(ui, EventKind::TouchCancelled),
349            Msg::DoubleTap { .. } => self.on_event(ui, EventKind::DoubleTap),
350            Msg::KeyUp(key) => self.on_event(ui, EventKind::KeyUp(*key)),
351            Msg::KeyDown(key) => self.on_event(ui, EventKind::KeyDown(*key)),
352            _ => (),
353        }
354    }
355}
356
357pub struct AbsmEventProviderBuilder {
358    widget_builder: WidgetBuilder,
359    actions: Vec<EventAction>,
360    absm: Handle<UiNode>,
361}
362
363impl AbsmEventProviderBuilder {
364    pub fn new(widget_builder: WidgetBuilder) -> Self {
365        Self {
366            widget_builder,
367            actions: Default::default(),
368            absm: Default::default(),
369        }
370    }
371
372    pub fn with_actions(mut self, actions: Vec<EventAction>) -> Self {
373        self.actions = actions;
374        self
375    }
376
377    pub fn with_absm(mut self, absm: Handle<UiNode>) -> Self {
378        self.absm = absm;
379        self
380    }
381
382    pub fn build(self, ctx: &mut BuildContext) -> Handle<UiNode> {
383        let provider = AbsmEventProvider {
384            widget: self.widget_builder.build(ctx),
385            actions: self.actions.into(),
386            absm: self.absm.into(),
387        };
388
389        ctx.add_node(UiNode::new(provider))
390    }
391}
392
393#[cfg(test)]
394mod test {
395    use crate::absm::AnimationBlendingStateMachineBuilder;
396    use crate::{test::test_widget_deletion, widget::WidgetBuilder};
397
398    #[test]
399    fn test_deletion() {
400        test_widget_deletion(|ctx| {
401            AnimationBlendingStateMachineBuilder::new(WidgetBuilder::new()).build(ctx)
402        });
403    }
404}