Skip to main content

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;
38use strum_macros::{AsRefStr, EnumString, VariantNames};
39
40/// UI-specific root motion settings.
41pub type RootMotionSettings = crate::generic_animation::RootMotionSettings<Handle<UiNode>>;
42/// UI-specific animation pose node.
43pub type PoseNode = crate::generic_animation::machine::PoseNode<Handle<UiNode>>;
44/// UI-specific animation pose node.
45pub type PlayAnimation =
46    crate::generic_animation::machine::node::play::PlayAnimation<Handle<UiNode>>;
47/// UI-specific animation blending state machine BlendAnimations node.
48pub type BlendAnimations =
49    crate::generic_animation::machine::node::blend::BlendAnimations<Handle<UiNode>>;
50/// UI-specific animation blending state machine BlendAnimationsByIndex node.
51pub type BlendAnimationsByIndex =
52    crate::generic_animation::machine::node::blend::BlendAnimationsByIndex<Handle<UiNode>>;
53/// UI-specific animation blending state machine BlendPose node.
54pub type BlendPose = crate::generic_animation::machine::node::blend::BlendPose<Handle<UiNode>>;
55/// UI-specific animation blending state machine IndexedBlendInput node.
56pub type IndexedBlendInput =
57    crate::generic_animation::machine::node::blend::IndexedBlendInput<Handle<UiNode>>;
58/// UI-specific animation blending state machine BlendSpace node.
59pub type BlendSpace =
60    crate::generic_animation::machine::node::blendspace::BlendSpace<Handle<UiNode>>;
61/// UI-specific animation blending state machine blend space point.
62pub type BlendSpacePoint =
63    crate::generic_animation::machine::node::blendspace::BlendSpacePoint<Handle<UiNode>>;
64/// UI-specific animation blending state machine layer mask.
65pub type LayerMask = crate::generic_animation::machine::mask::LayerMask<Handle<UiNode>>;
66/// UI-specific animation blending state machine layer mask.
67pub type Event = crate::generic_animation::machine::event::Event<Handle<UiNode>>;
68/// UI-specific animation blending state machine.
69pub type Machine = crate::generic_animation::machine::Machine<Handle<UiNode>>;
70/// UI-specific animation blending state machine layer.
71pub type MachineLayer = crate::generic_animation::machine::MachineLayer<Handle<UiNode>>;
72/// UI-specific animation blending state machine transition.
73pub type Transition = crate::generic_animation::machine::transition::Transition<Handle<UiNode>>;
74/// UI-specific animation blending state machine state.
75pub type State = crate::generic_animation::machine::state::State<Handle<UiNode>>;
76/// UI-specific animation blending state machine base pose node.
77pub type BasePoseNode = crate::generic_animation::machine::node::BasePoseNode<Handle<UiNode>>;
78/// UI-specific animation blending state machine state action.
79pub type StateAction = crate::generic_animation::machine::state::StateAction<Handle<UiNode>>;
80/// UI-specific animation blending state machine state action wrapper.
81pub type StateActionWrapper =
82    crate::generic_animation::machine::state::StateActionWrapper<Handle<UiNode>>;
83/// UI-specific animation blending state machine logic node.
84pub type LogicNode = crate::generic_animation::machine::transition::LogicNode<Handle<UiNode>>;
85/// UI-specific animation blending state machine And logic node.
86pub type AndNode = crate::generic_animation::machine::transition::AndNode<Handle<UiNode>>;
87/// UI-specific animation blending state machine Xor logic nde.
88pub type XorNode = crate::generic_animation::machine::transition::XorNode<Handle<UiNode>>;
89/// UI-specific animation blending state machine Or logic node.
90pub type OrNode = crate::generic_animation::machine::transition::OrNode<Handle<UiNode>>;
91/// UI-specific animation blending state machine Not logic node.
92pub type NotNode = crate::generic_animation::machine::transition::NotNode<Handle<UiNode>>;
93/// UI-specific animation blending state machine layer animation events collection.
94pub type LayerAnimationEventsCollection =
95    crate::generic_animation::machine::layer::LayerAnimationEventsCollection<Handle<UiNode>>;
96/// UI-specific animation blending state machine animation events source.
97pub type AnimationEventsSource =
98    crate::generic_animation::machine::layer::AnimationEventsSource<Handle<UiNode>>;
99
100/// Standard prelude for animation blending state machine, that contains all the most commonly used types and traits.
101pub mod prelude {
102    pub use super::{
103        AndNode, AnimationBlendingStateMachine, AnimationBlendingStateMachineBuilder,
104        AnimationEventsSource, BasePoseNode, BlendAnimations, BlendAnimationsByIndex, BlendPose,
105        BlendSpace, BlendSpacePoint, Event, IndexedBlendInput, LayerAnimationEventsCollection,
106        LayerMask, LogicNode, Machine, MachineLayer, NotNode, OrNode, PlayAnimation, PoseNode,
107        RootMotionSettings, State, StateAction, StateActionWrapper, Transition, XorNode,
108    };
109    pub use crate::generic_animation::machine::{
110        node::AnimationEventCollectionStrategy,
111        parameter::{Parameter, ParameterContainer, ParameterDefinition, PoseWeight},
112    };
113}
114
115/// Animation blending state machine (ABSM) is a node that takes multiple animations from an animation player and
116/// mixes them in arbitrary way into one animation. Usually, ABSMs are used to animate humanoid characters in games,
117/// by blending multiple states with one or more animations. More info about state machines can be found in
118/// [`Machine`] docs.
119///
120/// # Important notes
121///
122/// The node does **not** contain any animations, instead it just takes animations from an animation
123/// player node and mixes them.
124#[derive(Visit, Reflect, Clone, Debug, Default, ComponentProvider, TypeUuidProvider)]
125#[type_uuid(id = "4b08c753-2a10-41e3-8fb2-4fd0517e86bc")]
126#[reflect(derived_type = "UiNode")]
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                .to_base()
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 Ok(animation_player) = ui
184            .nodes
185            .try_get_component_of_type_mut::<AnimationPlayer>(*self.animation_player)
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 a 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_absm(self, ctx: &BuildContext) -> AnimationBlendingStateMachine {
236        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.
244    pub fn build_node(self, ctx: &BuildContext) -> UiNode {
245        UiNode::new(self.build_absm(ctx))
246    }
247
248    /// Creates a new node and adds it to the user interface.
249    pub fn build(self, ctx: &mut BuildContext) -> Handle<AnimationBlendingStateMachine> {
250        ctx.add(self.build_absm(ctx))
251    }
252}
253
254#[derive(
255    Visit,
256    Reflect,
257    Clone,
258    Debug,
259    Default,
260    PartialEq,
261    TypeUuidProvider,
262    AsRefStr,
263    EnumString,
264    VariantNames,
265)]
266#[type_uuid(id = "291e8734-47df-408e-8b2c-57bfb941d8ec")]
267pub enum EventKind {
268    #[default]
269    MouseEnter,
270    MouseLeave,
271    MouseMove,
272    MouseDown(MouseButton),
273    MouseUp(MouseButton),
274    MouseWheel,
275    KeyDown(KeyCode),
276    KeyUp(KeyCode),
277    Focus,
278    Unfocus,
279    TouchStarted,
280    TouchEnded,
281    TouchMoved,
282    TouchCancelled,
283    DoubleTap,
284}
285
286#[derive(Visit, Reflect, Clone, Debug, Default, PartialEq, TypeUuidProvider)]
287#[type_uuid(id = "15f306b8-3bb8-4b35-87bd-6e9e5d748454")]
288pub struct EventAction {
289    kind: EventKind,
290    parameter_name: String,
291    parameter_value: Parameter,
292}
293
294/// A widget that listens for particular events and sets parameters in an ABSM accordingly.
295#[derive(Visit, Reflect, Clone, Debug, Default, ComponentProvider, TypeUuidProvider)]
296#[type_uuid(id = "15f306b8-3bb8-4b35-87bd-6e9e5d748455")]
297#[reflect(derived_type = "UiNode")]
298pub struct AbsmEventProvider {
299    widget: Widget,
300    actions: InheritableVariable<Vec<EventAction>>,
301    absm: InheritableVariable<Handle<UiNode>>,
302}
303
304impl ConstructorProvider<UiNode, UserInterface> for AbsmEventProvider {
305    fn constructor() -> GraphNodeConstructor<UiNode, UserInterface> {
306        GraphNodeConstructor::new::<Self>()
307            .with_variant("Absm Event Provider", |ui| {
308                AbsmEventProviderBuilder::new(WidgetBuilder::new().with_name("Absm Event Provider"))
309                    .build(&mut ui.build_ctx())
310                    .to_base()
311                    .into()
312            })
313            .with_group("Animation")
314    }
315}
316
317define_widget_deref!(AbsmEventProvider);
318
319impl AbsmEventProvider {
320    fn on_event(&self, ui: &mut UserInterface, kind: EventKind) {
321        let Some(action) = self.actions.iter().find(|a| a.kind == kind) else {
322            return;
323        };
324
325        let Ok(absm) = ui.try_get_mut_of_type::<AnimationBlendingStateMachine>(*self.absm) else {
326            return;
327        };
328
329        absm.machine_mut()
330            .set_parameter(&action.parameter_name, action.parameter_value);
331    }
332}
333
334impl Control for AbsmEventProvider {
335    fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
336        self.widget.handle_routed_message(ui, message);
337
338        let Some(msg) = message.data::<WidgetMessage>() else {
339            return;
340        };
341
342        use WidgetMessage as Msg;
343        match msg {
344            Msg::MouseDown { button, .. } => self.on_event(ui, EventKind::MouseDown(*button)),
345            Msg::MouseUp { button, .. } => self.on_event(ui, EventKind::MouseUp(*button)),
346            Msg::MouseMove { .. } => self.on_event(ui, EventKind::MouseMove),
347            Msg::MouseWheel { .. } => self.on_event(ui, EventKind::MouseWheel),
348            Msg::MouseLeave => self.on_event(ui, EventKind::MouseLeave),
349            Msg::MouseEnter => self.on_event(ui, EventKind::MouseEnter),
350            Msg::Focus => self.on_event(ui, EventKind::Focus),
351            Msg::Unfocus => self.on_event(ui, EventKind::Unfocus),
352            Msg::TouchStarted { .. } => self.on_event(ui, EventKind::TouchStarted),
353            Msg::TouchEnded { .. } => self.on_event(ui, EventKind::TouchEnded),
354            Msg::TouchMoved { .. } => self.on_event(ui, EventKind::TouchMoved),
355            Msg::TouchCancelled { .. } => self.on_event(ui, EventKind::TouchCancelled),
356            Msg::DoubleTap { .. } => self.on_event(ui, EventKind::DoubleTap),
357            Msg::KeyUp(key) => self.on_event(ui, EventKind::KeyUp(*key)),
358            Msg::KeyDown(key) => self.on_event(ui, EventKind::KeyDown(*key)),
359            _ => (),
360        }
361    }
362}
363
364pub struct AbsmEventProviderBuilder {
365    widget_builder: WidgetBuilder,
366    actions: Vec<EventAction>,
367    absm: Handle<UiNode>,
368}
369
370impl AbsmEventProviderBuilder {
371    pub fn new(widget_builder: WidgetBuilder) -> Self {
372        Self {
373            widget_builder,
374            actions: Default::default(),
375            absm: Default::default(),
376        }
377    }
378
379    pub fn with_actions(mut self, actions: Vec<EventAction>) -> Self {
380        self.actions = actions;
381        self
382    }
383
384    pub fn with_absm(mut self, absm: Handle<UiNode>) -> Self {
385        self.absm = absm;
386        self
387    }
388
389    pub fn build(self, ctx: &mut BuildContext) -> Handle<AbsmEventProvider> {
390        let provider = AbsmEventProvider {
391            widget: self.widget_builder.build(ctx),
392            actions: self.actions.into(),
393            absm: self.absm.into(),
394        };
395
396        ctx.add(provider)
397    }
398}
399
400#[cfg(test)]
401mod test {
402    use crate::absm::AnimationBlendingStateMachineBuilder;
403    use crate::{test::test_widget_deletion, widget::WidgetBuilder};
404
405    #[test]
406    fn test_deletion() {
407        test_widget_deletion(|ctx| {
408            AnimationBlendingStateMachineBuilder::new(WidgetBuilder::new()).build(ctx)
409        });
410    }
411}