fyrox_animation/machine/node/
blend.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
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
//! Various animation blending nodes.

use crate::{
    core::{
        pool::{Handle, Pool},
        reflect::prelude::*,
        visitor::{Visit, VisitResult, Visitor},
    },
    machine::{
        node::AnimationEventCollectionStrategy, node::BasePoseNode, AnimationPoseSource, Parameter,
        ParameterContainer, PoseNode, PoseWeight,
    },
    Animation, AnimationContainer, AnimationEvent, AnimationPose, EntityId,
};
use fyrox_core::uuid::{uuid, Uuid};
use fyrox_core::TypeUuidProvider;
use std::cmp::Ordering;
use std::{
    cell::{Cell, Ref, RefCell},
    ops::{Deref, DerefMut},
};

/// Weighted proxy for animation pose. It has an input pose source and a weight, that tells in which proportion
/// the pose should be blended into final pose.
#[derive(Default, Debug, Visit, Clone, Reflect, PartialEq)]
pub struct BlendPose<T: EntityId> {
    /// Weight of the pose.
    pub weight: PoseWeight,

    /// A source of animation pose.
    #[reflect(hidden)]
    pub pose_source: Handle<PoseNode<T>>,
}

impl<T: EntityId> TypeUuidProvider for BlendPose<T> {
    fn type_uuid() -> Uuid {
        uuid!("b01d7639-7b39-4eaf-87e6-29fd5221951b")
    }
}

impl<T: EntityId> BlendPose<T> {
    /// Creates new instance of blend pose with given weight and animation pose.
    pub fn new(weight: PoseWeight, pose_source: Handle<PoseNode<T>>) -> Self {
        Self {
            weight,
            pose_source,
        }
    }

    /// Specialized constructor that creates blend pose with constant weight.
    /// `weight` should be positive.
    pub fn with_constant_weight(weight: f32, pose_source: Handle<PoseNode<T>>) -> Self {
        Self {
            weight: PoseWeight::Constant(weight),
            pose_source,
        }
    }

    /// Specialized constructor that creates blend pose with parametrized weight.
    /// `param_id` must be name of Weight parameter in machine.
    pub fn with_param_weight(param_id: &str, pose_source: Handle<PoseNode<T>>) -> Self {
        Self {
            weight: PoseWeight::Parameter(param_id.to_owned()),
            pose_source,
        }
    }
}

/// Animation blend node. It takes multiple input poses and mixes them together into single pose with specified
/// weights. Could be used to mix hit and run animations for example - once your character got hit, you set some
/// significant weight for hit animation (0.8 for example) and lower weight for run animation (0.2) and it will
/// look like your character got wounded while it still running (probably you should decrease speed here too).
/// Weights can be parametrized, which means that you can dynamically change them in runtime. In our example we
/// can decrease weight of hit animation over time and increase weight of run animation, so character will recover
/// from his wounds.
#[derive(Default, Debug, Visit, Clone, Reflect, PartialEq)]
pub struct BlendAnimations<T: EntityId> {
    /// Base node.
    pub base: BasePoseNode<T>,

    /// A list of pose sources. See [`BlendPose`] docs for more info.
    pub pose_sources: Vec<BlendPose<T>>,

    /// Output pose of the node, contains final result of blending all input poses.
    #[visit(skip)]
    #[reflect(hidden)]
    pub output_pose: RefCell<AnimationPose<T>>,
}

impl<T: EntityId> Deref for BlendAnimations<T> {
    type Target = BasePoseNode<T>;

    fn deref(&self) -> &Self::Target {
        &self.base
    }
}

impl<T: EntityId> DerefMut for BlendAnimations<T> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.base
    }
}

impl<T: EntityId> BlendAnimations<T> {
    /// Creates new animation blend node with given poses.
    pub fn new(poses: Vec<BlendPose<T>>) -> Self {
        Self {
            base: Default::default(),
            pose_sources: poses,
            output_pose: Default::default(),
        }
    }

    /// Returns a set of handles to children pose nodes.
    pub fn children(&self) -> Vec<Handle<PoseNode<T>>> {
        self.pose_sources.iter().map(|s| s.pose_source).collect()
    }
}

impl<T: EntityId> AnimationPoseSource<T> for BlendAnimations<T> {
    fn eval_pose(
        &self,
        nodes: &Pool<PoseNode<T>>,
        params: &ParameterContainer,
        animations: &AnimationContainer<T>,
        dt: f32,
    ) -> Ref<AnimationPose<T>> {
        self.output_pose.borrow_mut().reset();
        for blend_pose in self.pose_sources.iter() {
            let weight = match blend_pose.weight {
                PoseWeight::Constant(value) => value,
                PoseWeight::Parameter(ref param_id) => {
                    if let Some(Parameter::Weight(weight)) = params.get(param_id) {
                        *weight
                    } else {
                        0.0
                    }
                }
            };

            if let Some(pose_source) = nodes
                .try_borrow(blend_pose.pose_source)
                .map(|pose_source| pose_source.eval_pose(nodes, params, animations, dt))
            {
                self.output_pose
                    .borrow_mut()
                    .blend_with(&pose_source, weight);
            }
        }
        self.output_pose.borrow()
    }

    fn pose(&self) -> Ref<AnimationPose<T>> {
        self.output_pose.borrow()
    }

    fn collect_animation_events(
        &self,
        nodes: &Pool<PoseNode<T>>,
        params: &ParameterContainer,
        animations: &AnimationContainer<T>,
        strategy: AnimationEventCollectionStrategy,
    ) -> Vec<(Handle<Animation<T>>, AnimationEvent)> {
        match strategy {
            AnimationEventCollectionStrategy::All => {
                let mut events = Vec::new();
                for pose in self.pose_sources.iter() {
                    if let Some(source) = nodes.try_borrow(pose.pose_source) {
                        events.extend(
                            source.collect_animation_events(nodes, params, animations, strategy),
                        );
                    }
                }
                events
            }
            AnimationEventCollectionStrategy::MaxWeight => {
                if let Some((pose, _)) = self
                    .pose_sources
                    .iter()
                    .filter_map(|s| s.weight.value(params).map(|w| (s, w)))
                    .max_by(|(_, w1), (_, w2)| w1.partial_cmp(w2).unwrap_or(Ordering::Equal))
                {
                    if let Some(pose_source) = nodes.try_borrow(pose.pose_source) {
                        return pose_source
                            .collect_animation_events(nodes, params, animations, strategy);
                    }
                }

                Default::default()
            }
            AnimationEventCollectionStrategy::MinWeight => {
                if let Some((pose, _)) = self
                    .pose_sources
                    .iter()
                    .filter_map(|s| s.weight.value(params).map(|w| (s, w)))
                    .min_by(|(_, w1), (_, w2)| w1.partial_cmp(w2).unwrap_or(Ordering::Equal))
                {
                    if let Some(pose_source) = nodes.try_borrow(pose.pose_source) {
                        return pose_source
                            .collect_animation_events(nodes, params, animations, strategy);
                    }
                }

                Default::default()
            }
        }
    }
}

/// An animation pose with specific blend time. Blend time tells the engine how many time it should use to perform
/// blending to this pose.
#[derive(Default, Debug, Visit, Clone, Reflect, PartialEq)]
pub struct IndexedBlendInput<T: EntityId> {
    /// Blend time tells the engine how many time it should use to perform blending to this pose.
    pub blend_time: f32,

    /// A handle to pose node source.
    #[reflect(hidden)]
    pub pose_source: Handle<PoseNode<T>>,
}

impl<T: EntityId> TypeUuidProvider for IndexedBlendInput<T> {
    fn type_uuid() -> Uuid {
        uuid!("92fcc992-9a68-4152-8449-657546faa286")
    }
}

/// A node that switches between given animations using index and smoothly blends from one animation to another
/// while switching. It is very useful for situations when you need to switch between different animations. For
/// example you could have an `aim` state, it is suitable for any weapon (you don't need to create a ton of states
/// like `aim_rifle`, `aim_pistol`, etc), but actual weapon holding animation should be different based on actual
/// weapon a character is holding. In this case you create a BlendAnimationsByIndex node, add a few inputs where
/// each input uses different weapon holding animation and in your game all you need to do is to set an index
/// parameter in the machine parameters. The node will automatically perform smooth transition between different
/// animations.
#[derive(Default, Debug, Visit, Clone, Reflect, PartialEq)]
pub struct BlendAnimationsByIndex<T: EntityId> {
    /// Base node.
    pub base: BasePoseNode<T>,

    /// A name of index parameter that will be used to switch between input poses.
    pub index_parameter: String,

    /// A set of input poses.
    pub inputs: Vec<IndexedBlendInput<T>>,

    /// Index of a previously active input pose.
    #[reflect(hidden)]
    pub prev_index: Cell<Option<u32>>,

    /// Current blend time.
    #[reflect(hidden)]
    pub blend_time: Cell<f32>,

    /// Output pose of the node.
    #[visit(skip)]
    #[reflect(hidden)]
    pub output_pose: RefCell<AnimationPose<T>>,
}

impl<T: EntityId> Deref for BlendAnimationsByIndex<T> {
    type Target = BasePoseNode<T>;

    fn deref(&self) -> &Self::Target {
        &self.base
    }
}

impl<T: EntityId> DerefMut for BlendAnimationsByIndex<T> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.base
    }
}

impl<T: EntityId> BlendAnimationsByIndex<T> {
    /// Creates new [`BlendAnimationsByIndex`] node using given index parameter name and a set of inputs.
    pub fn new(index_parameter: String, inputs: Vec<IndexedBlendInput<T>>) -> Self {
        Self {
            base: Default::default(),
            index_parameter,
            inputs,
            output_pose: RefCell::new(Default::default()),
            prev_index: Cell::new(None),
            blend_time: Cell::new(0.0),
        }
    }

    /// Return a set of handle of children nodes.
    pub fn children(&self) -> Vec<Handle<PoseNode<T>>> {
        self.inputs.iter().map(|s| s.pose_source).collect()
    }
}

impl<T: EntityId> AnimationPoseSource<T> for BlendAnimationsByIndex<T> {
    fn eval_pose(
        &self,
        nodes: &Pool<PoseNode<T>>,
        params: &ParameterContainer,
        animations: &AnimationContainer<T>,
        dt: f32,
    ) -> Ref<AnimationPose<T>> {
        self.output_pose.borrow_mut().reset();

        if let Some(&Parameter::Index(current_index)) = params.get(&self.index_parameter) {
            let mut applied = false;

            if let Some(prev_index) = self.prev_index.get() {
                if prev_index != current_index {
                    if let (Some(prev_input), Some(current_input)) = (
                        self.inputs.get(prev_index as usize),
                        self.inputs.get(current_index as usize),
                    ) {
                        self.blend_time
                            .set((self.blend_time.get() + dt).min(current_input.blend_time));

                        let interpolator = self.blend_time.get() / current_input.blend_time;

                        self.output_pose.borrow_mut().blend_with(
                            &nodes[prev_input.pose_source].eval_pose(nodes, params, animations, dt),
                            1.0 - interpolator,
                        );
                        self.output_pose.borrow_mut().blend_with(
                            &nodes[current_input.pose_source]
                                .eval_pose(nodes, params, animations, dt),
                            interpolator,
                        );

                        if interpolator >= 1.0 {
                            self.prev_index.set(Some(current_index));
                            self.blend_time.set(0.0);
                        }

                        applied = true;
                    }
                }
            } else {
                self.prev_index.set(Some(current_index));
            }

            if !applied {
                // Immediately jump to target pose (if any).
                self.blend_time.set(0.0);

                if let Some(current_input) = self.inputs.get(current_index as usize) {
                    nodes[current_input.pose_source]
                        .eval_pose(nodes, params, animations, dt)
                        .clone_into(&mut self.output_pose.borrow_mut());
                }
            }
        }

        self.output_pose.borrow()
    }

    fn pose(&self) -> Ref<AnimationPose<T>> {
        self.output_pose.borrow()
    }

    fn collect_animation_events(
        &self,
        nodes: &Pool<PoseNode<T>>,
        params: &ParameterContainer,
        animations: &AnimationContainer<T>,
        strategy: AnimationEventCollectionStrategy,
    ) -> Vec<(Handle<Animation<T>>, AnimationEvent)> {
        if let Some(&Parameter::Index(current_index)) = params.get(&self.index_parameter) {
            if let Some(prev_index) = self.prev_index.get() {
                if prev_index != current_index {
                    if let (Some(prev_input), Some(current_input)) = (
                        self.inputs.get(prev_index as usize),
                        self.inputs.get(current_index as usize),
                    ) {
                        let interpolator = self.blend_time.get() / current_input.blend_time;

                        match strategy {
                            AnimationEventCollectionStrategy::All => {
                                let mut events = Vec::new();
                                for input in [prev_input, current_input] {
                                    if let Some(source) = nodes.try_borrow(input.pose_source) {
                                        events.extend(source.collect_animation_events(
                                            nodes, params, animations, strategy,
                                        ));
                                    }
                                }
                                return events;
                            }
                            AnimationEventCollectionStrategy::MaxWeight => {
                                let input = if interpolator < 0.5 {
                                    prev_input
                                } else {
                                    current_input
                                };

                                if let Some(pose_source) = nodes.try_borrow(input.pose_source) {
                                    return pose_source.collect_animation_events(
                                        nodes, params, animations, strategy,
                                    );
                                }
                            }
                            AnimationEventCollectionStrategy::MinWeight => {
                                let input = if interpolator < 0.5 {
                                    current_input
                                } else {
                                    prev_input
                                };

                                if let Some(pose_source) = nodes.try_borrow(input.pose_source) {
                                    return pose_source.collect_animation_events(
                                        nodes, params, animations, strategy,
                                    );
                                }
                            }
                        }
                    }
                } else {
                    // In case where the transition is done, all the strategies does the same - just collects events
                    // from active pose node.
                    if let Some(current_input) = self.inputs.get(current_index as usize) {
                        if let Some(pose_source) = nodes.try_borrow(current_input.pose_source) {
                            return pose_source
                                .collect_animation_events(nodes, params, animations, strategy);
                        }
                    }
                }
            }
        }

        Default::default()
    }
}