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")]
127#[reflect(derived_type = "UiNode")]
128pub struct AnimationBlendingStateMachine {
129    widget: Widget,
130    #[component(include)]
131    machine: InheritableVariable<Machine>,
132    #[component(include)]
133    animation_player: InheritableVariable<Handle<UiNode>>,
134}
135
136impl ConstructorProvider<UiNode, UserInterface> for AnimationBlendingStateMachine {
137    fn constructor() -> GraphNodeConstructor<UiNode, UserInterface> {
138        GraphNodeConstructor::new::<Self>()
139            .with_variant("Animation Blending State Machine", |ui| {
140                AnimationBlendingStateMachineBuilder::new(
141                    WidgetBuilder::new().with_name("Animation Blending State Machine"),
142                )
143                .build(&mut ui.build_ctx())
144                .into()
145            })
146            .with_group("Animation")
147    }
148}
149
150impl AnimationBlendingStateMachine {
151    /// Sets new state machine to the node.
152    pub fn set_machine(&mut self, machine: Machine) {
153        self.machine.set_value_and_mark_modified(machine);
154    }
155
156    /// Returns a reference to the state machine used by the node.
157    pub fn machine(&self) -> &InheritableVariable<Machine> {
158        &self.machine
159    }
160
161    /// Returns a mutable reference to the state machine used by the node.
162    pub fn machine_mut(&mut self) -> &mut InheritableVariable<Machine> {
163        &mut self.machine
164    }
165
166    /// Sets new animation player of the node. The animation player is a source of animations for blending, the state
167    /// machine node must have the animation player specified, otherwise it won't have any effect.
168    pub fn set_animation_player(&mut self, animation_player: Handle<UiNode>) {
169        self.animation_player
170            .set_value_and_mark_modified(animation_player);
171    }
172
173    /// Returns an animation player used by the node.
174    pub fn animation_player(&self) -> Handle<UiNode> {
175        *self.animation_player
176    }
177}
178
179define_widget_deref!(AnimationBlendingStateMachine);
180
181impl Control for AnimationBlendingStateMachine {
182    fn update(&mut self, dt: f32, ui: &mut UserInterface) {
183        if let Some(animation_player) = ui
184            .nodes
185            .try_borrow_mut(*self.animation_player)
186            .and_then(|n| n.component_mut::<AnimationPlayer>())
187        {
188            // Prevent animation player to apply animation to scene nodes. The animation will
189            // do than instead.
190            animation_player.set_auto_apply(false);
191
192            let pose = self
193                .machine
194                .get_value_mut_silent()
195                .evaluate_pose(animation_player.animations.get_value_mut_silent(), dt);
196
197            pose.apply(ui);
198        }
199    }
200
201    fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
202        self.widget.handle_routed_message(ui, message)
203    }
204}
205
206/// Animation blending state machine builder allows you to create state machines in declarative manner.
207pub struct AnimationBlendingStateMachineBuilder {
208    widget_builder: WidgetBuilder,
209    machine: Machine,
210    animation_player: Handle<UiNode>,
211}
212
213impl AnimationBlendingStateMachineBuilder {
214    /// Creates new builder instance.
215    pub fn new(widget_builder: WidgetBuilder) -> Self {
216        Self {
217            widget_builder,
218            machine: Default::default(),
219            animation_player: Default::default(),
220        }
221    }
222
223    /// Sets the desired state machine.
224    pub fn with_machine(mut self, machine: Machine) -> Self {
225        self.machine = machine;
226        self
227    }
228
229    /// Sets the animation player as a source of animations.
230    pub fn with_animation_player(mut self, animation_player: Handle<UiNode>) -> Self {
231        self.animation_player = animation_player;
232        self
233    }
234
235    /// Creates new node.
236    pub fn build_node(self, ctx: &BuildContext) -> UiNode {
237        UiNode::new(AnimationBlendingStateMachine {
238            widget: self.widget_builder.with_need_update(true).build(ctx),
239            machine: self.machine.into(),
240            animation_player: self.animation_player.into(),
241        })
242    }
243
244    /// Creates new node and adds it to the user interface.
245    pub fn build(self, ctx: &mut BuildContext) -> Handle<UiNode> {
246        ctx.add_node(self.build_node(ctx))
247    }
248}
249
250#[derive(
251    Visit,
252    Reflect,
253    Clone,
254    Debug,
255    Default,
256    PartialEq,
257    TypeUuidProvider,
258    AsRefStr,
259    EnumString,
260    VariantNames,
261)]
262#[type_uuid(id = "291e8734-47df-408e-8b2c-57bfb941d8ec")]
263pub enum EventKind {
264    #[default]
265    MouseEnter,
266    MouseLeave,
267    MouseMove,
268    MouseDown(MouseButton),
269    MouseUp(MouseButton),
270    MouseWheel,
271    KeyDown(KeyCode),
272    KeyUp(KeyCode),
273    Focus,
274    Unfocus,
275    TouchStarted,
276    TouchEnded,
277    TouchMoved,
278    TouchCancelled,
279    DoubleTap,
280}
281
282#[derive(Visit, Reflect, Clone, Debug, Default, PartialEq, TypeUuidProvider)]
283#[type_uuid(id = "15f306b8-3bb8-4b35-87bd-6e9e5d748454")]
284pub struct EventAction {
285    kind: EventKind,
286    parameter_name: String,
287    parameter_value: Parameter,
288}
289
290/// A widget that listens for particular events and sets parameters in an ABSM accordingly.
291#[derive(Visit, Reflect, Clone, Debug, Default, ComponentProvider, TypeUuidProvider)]
292#[type_uuid(id = "15f306b8-3bb8-4b35-87bd-6e9e5d748455")]
293#[reflect(derived_type = "UiNode")]
294pub struct AbsmEventProvider {
295    widget: Widget,
296    actions: InheritableVariable<Vec<EventAction>>,
297    absm: InheritableVariable<Handle<UiNode>>,
298}
299
300impl ConstructorProvider<UiNode, UserInterface> for AbsmEventProvider {
301    fn constructor() -> GraphNodeConstructor<UiNode, UserInterface> {
302        GraphNodeConstructor::new::<Self>()
303            .with_variant("Absm Event Provider", |ui| {
304                AbsmEventProviderBuilder::new(WidgetBuilder::new().with_name("Absm Event Provider"))
305                    .build(&mut ui.build_ctx())
306                    .into()
307            })
308            .with_group("Animation")
309    }
310}
311
312define_widget_deref!(AbsmEventProvider);
313
314impl AbsmEventProvider {
315    fn on_event(&self, ui: &mut UserInterface, kind: EventKind) {
316        let Some(action) = self.actions.iter().find(|a| a.kind == kind) else {
317            return;
318        };
319
320        let Some(absm) = ui.try_get_mut_of_type::<AnimationBlendingStateMachine>(*self.absm) else {
321            return;
322        };
323
324        absm.machine_mut()
325            .set_parameter(&action.parameter_name, action.parameter_value);
326    }
327}
328
329impl Control for AbsmEventProvider {
330    fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
331        self.widget.handle_routed_message(ui, message);
332
333        let Some(msg) = message.data::<WidgetMessage>() else {
334            return;
335        };
336
337        use WidgetMessage as Msg;
338        match msg {
339            Msg::MouseDown { button, .. } => self.on_event(ui, EventKind::MouseDown(*button)),
340            Msg::MouseUp { button, .. } => self.on_event(ui, EventKind::MouseUp(*button)),
341            Msg::MouseMove { .. } => self.on_event(ui, EventKind::MouseMove),
342            Msg::MouseWheel { .. } => self.on_event(ui, EventKind::MouseWheel),
343            Msg::MouseLeave => self.on_event(ui, EventKind::MouseLeave),
344            Msg::MouseEnter => self.on_event(ui, EventKind::MouseEnter),
345            Msg::Focus => self.on_event(ui, EventKind::Focus),
346            Msg::Unfocus => self.on_event(ui, EventKind::Unfocus),
347            Msg::TouchStarted { .. } => self.on_event(ui, EventKind::TouchStarted),
348            Msg::TouchEnded { .. } => self.on_event(ui, EventKind::TouchEnded),
349            Msg::TouchMoved { .. } => self.on_event(ui, EventKind::TouchMoved),
350            Msg::TouchCancelled { .. } => self.on_event(ui, EventKind::TouchCancelled),
351            Msg::DoubleTap { .. } => self.on_event(ui, EventKind::DoubleTap),
352            Msg::KeyUp(key) => self.on_event(ui, EventKind::KeyUp(*key)),
353            Msg::KeyDown(key) => self.on_event(ui, EventKind::KeyDown(*key)),
354            _ => (),
355        }
356    }
357}
358
359pub struct AbsmEventProviderBuilder {
360    widget_builder: WidgetBuilder,
361    actions: Vec<EventAction>,
362    absm: Handle<UiNode>,
363}
364
365impl AbsmEventProviderBuilder {
366    pub fn new(widget_builder: WidgetBuilder) -> Self {
367        Self {
368            widget_builder,
369            actions: Default::default(),
370            absm: Default::default(),
371        }
372    }
373
374    pub fn with_actions(mut self, actions: Vec<EventAction>) -> Self {
375        self.actions = actions;
376        self
377    }
378
379    pub fn with_absm(mut self, absm: Handle<UiNode>) -> Self {
380        self.absm = absm;
381        self
382    }
383
384    pub fn build(self, ctx: &mut BuildContext) -> Handle<UiNode> {
385        let provider = AbsmEventProvider {
386            widget: self.widget_builder.build(ctx),
387            actions: self.actions.into(),
388            absm: self.absm.into(),
389        };
390
391        ctx.add_node(UiNode::new(provider))
392    }
393}
394
395#[cfg(test)]
396mod test {
397    use crate::absm::AnimationBlendingStateMachineBuilder;
398    use crate::{test::test_widget_deletion, widget::WidgetBuilder};
399
400    #[test]
401    fn test_deletion() {
402        test_widget_deletion(|ctx| {
403            AnimationBlendingStateMachineBuilder::new(WidgetBuilder::new()).build(ctx)
404        });
405    }
406}