Skip to main content

fyrox_animation/machine/node/
blendspace.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#![allow(missing_docs)] // TODO
22
23use crate::{
24    core::{
25        algebra::Vector2,
26        math::{self, TriangleDefinition},
27        pool::{Handle, Pool},
28        reflect::prelude::*,
29        visitor::prelude::*,
30    },
31    machine::{
32        node::AnimationEventCollectionStrategy, node::BasePoseNode, AnimationPoseSource, Parameter,
33        ParameterContainer, PoseNode,
34    },
35    Animation, AnimationContainer, AnimationEvent, AnimationPose, EntityId,
36};
37use fyrox_core::uuid::{uuid, Uuid};
38use fyrox_core::TypeUuidProvider;
39use spade::{DelaunayTriangulation, Point2, Triangulation};
40use std::cmp::Ordering;
41use std::{
42    cell::{Ref, RefCell},
43    ops::{Deref, DerefMut},
44};
45
46#[derive(Debug, Visit, Clone, Reflect, PartialEq, Default)]
47pub struct BlendSpacePoint<T: EntityId> {
48    pub position: Vector2<f32>,
49    pub pose_source: Handle<PoseNode<T>>,
50}
51
52impl<T: EntityId> TypeUuidProvider for BlendSpacePoint<T> {
53    fn type_uuid() -> Uuid {
54        uuid!("d163b4b9-aed6-447f-bb93-b7e539099417")
55    }
56}
57
58#[derive(Debug, Visit, Clone, Reflect, PartialEq)]
59pub struct BlendSpace<T: EntityId> {
60    base: BasePoseNode<T>,
61
62    #[reflect(hidden)]
63    points: Vec<BlendSpacePoint<T>>,
64
65    #[reflect(hidden)]
66    triangles: Vec<TriangleDefinition>,
67
68    #[reflect(setter = "set_x_axis_name")]
69    x_axis_name: String,
70
71    #[reflect(setter = "set_y_axis_name")]
72    y_axis_name: String,
73
74    #[reflect(setter = "set_min_values")]
75    min_values: Vector2<f32>,
76
77    #[reflect(setter = "set_max_values")]
78    max_values: Vector2<f32>,
79
80    #[reflect(setter = "set_snap_step")]
81    snap_step: Vector2<f32>,
82
83    #[reflect(setter = "set_sampling_parameter")]
84    sampling_parameter: String,
85
86    #[reflect(hidden)]
87    #[visit(skip)]
88    pose: RefCell<AnimationPose<T>>,
89}
90
91impl<T: EntityId> Default for BlendSpace<T> {
92    fn default() -> Self {
93        Self {
94            base: Default::default(),
95            points: vec![],
96            triangles: Default::default(),
97            x_axis_name: "X".to_string(),
98            y_axis_name: "Y".to_string(),
99            min_values: Default::default(),
100            max_values: Vector2::new(1.0, 1.0),
101            snap_step: Vector2::new(0.1, 0.1),
102            sampling_parameter: Default::default(),
103            pose: Default::default(),
104        }
105    }
106}
107
108impl<T: EntityId> Deref for BlendSpace<T> {
109    type Target = BasePoseNode<T>;
110
111    fn deref(&self) -> &Self::Target {
112        &self.base
113    }
114}
115
116impl<T: EntityId> DerefMut for BlendSpace<T> {
117    fn deref_mut(&mut self) -> &mut Self::Target {
118        &mut self.base
119    }
120}
121
122impl<T: EntityId> AnimationPoseSource<T> for BlendSpace<T> {
123    fn eval_pose(
124        &self,
125        nodes: &Pool<PoseNode<T>>,
126        params: &ParameterContainer,
127        animations: &AnimationContainer<T>,
128        dt: f32,
129    ) -> Ref<AnimationPose<T>> {
130        let mut pose = self.pose.borrow_mut();
131
132        pose.reset();
133
134        if let Some(Parameter::SamplingPoint(sampling_point)) = params.get(&self.sampling_parameter)
135        {
136            if let Some(weights) = self.fetch_weights(*sampling_point) {
137                let (ia, wa) = weights[0];
138                let (ib, wb) = weights[1];
139                let (ic, wc) = weights[2];
140
141                if let (Ok(pose_a), Ok(pose_b), Ok(pose_c)) = (
142                    nodes.try_borrow(self.points[ia].pose_source),
143                    nodes.try_borrow(self.points[ib].pose_source),
144                    nodes.try_borrow(self.points[ic].pose_source),
145                ) {
146                    pose.blend_with(&pose_a.eval_pose(nodes, params, animations, dt), wa);
147                    pose.blend_with(&pose_b.eval_pose(nodes, params, animations, dt), wb);
148                    pose.blend_with(&pose_c.eval_pose(nodes, params, animations, dt), wc);
149                }
150            }
151        }
152
153        drop(pose);
154
155        self.pose.borrow()
156    }
157
158    fn pose(&self) -> Ref<AnimationPose<T>> {
159        self.pose.borrow()
160    }
161
162    fn collect_animation_events(
163        &self,
164        nodes: &Pool<PoseNode<T>>,
165        params: &ParameterContainer,
166        animations: &AnimationContainer<T>,
167        strategy: AnimationEventCollectionStrategy,
168    ) -> Vec<(Handle<Animation<T>>, AnimationEvent)> {
169        if let Some(Parameter::SamplingPoint(sampling_point)) = params.get(&self.sampling_parameter)
170        {
171            if let Some(weights) = self.fetch_weights(*sampling_point) {
172                let (ia, wa) = weights[0];
173                let (ib, wb) = weights[1];
174                let (ic, wc) = weights[2];
175
176                if let (Ok(pose_a), Ok(pose_b), Ok(pose_c)) = (
177                    nodes.try_borrow(self.points[ia].pose_source),
178                    nodes.try_borrow(self.points[ib].pose_source),
179                    nodes.try_borrow(self.points[ic].pose_source),
180                ) {
181                    match strategy {
182                        AnimationEventCollectionStrategy::All => {
183                            let mut events = Vec::new();
184                            for pose in [pose_a, pose_b, pose_c] {
185                                events.extend(
186                                    pose.collect_animation_events(
187                                        nodes, params, animations, strategy,
188                                    ),
189                                );
190                            }
191                            return events;
192                        }
193                        AnimationEventCollectionStrategy::MaxWeight => {
194                            if let Some((max_weight_pose, _)) =
195                                [(pose_a, wa), (pose_b, wb), (pose_c, wc)].iter().max_by(
196                                    |(_, w1), (_, w2)| {
197                                        w1.partial_cmp(w2).unwrap_or(Ordering::Equal)
198                                    },
199                                )
200                            {
201                                return max_weight_pose
202                                    .collect_animation_events(nodes, params, animations, strategy);
203                            }
204                        }
205                        AnimationEventCollectionStrategy::MinWeight => {
206                            if let Some((min_weight_pose, _)) =
207                                [(pose_a, wa), (pose_b, wb), (pose_c, wc)].iter().min_by(
208                                    |(_, w1), (_, w2)| {
209                                        w1.partial_cmp(w2).unwrap_or(Ordering::Equal)
210                                    },
211                                )
212                            {
213                                return min_weight_pose
214                                    .collect_animation_events(nodes, params, animations, strategy);
215                            }
216                        }
217                    }
218                }
219            }
220        }
221
222        Default::default()
223    }
224}
225
226pub struct PointsMut<'a, T: EntityId> {
227    blend_space: &'a mut BlendSpace<T>,
228}
229
230impl<T: EntityId> Deref for PointsMut<'_, T> {
231    type Target = Vec<BlendSpacePoint<T>>;
232
233    fn deref(&self) -> &Self::Target {
234        &self.blend_space.points
235    }
236}
237
238impl<T: EntityId> DerefMut for PointsMut<'_, T> {
239    fn deref_mut(&mut self) -> &mut Self::Target {
240        &mut self.blend_space.points
241    }
242}
243
244impl<T: EntityId> Drop for PointsMut<'_, T> {
245    fn drop(&mut self) {
246        self.blend_space.triangulate();
247    }
248}
249
250impl<T: EntityId> BlendSpace<T> {
251    pub fn add_point(&mut self, point: BlendSpacePoint<T>) -> bool {
252        self.points.push(point);
253        self.triangulate()
254    }
255
256    /// Sets new points to the blend space.
257    pub fn set_points(&mut self, points: Vec<BlendSpacePoint<T>>) -> bool {
258        self.points = points;
259        self.triangulate()
260    }
261
262    pub fn clear_points(&mut self) {
263        self.points.clear();
264        self.triangles.clear();
265    }
266
267    pub fn points(&self) -> &[BlendSpacePoint<T>] {
268        &self.points
269    }
270
271    pub fn points_mut(&mut self) -> PointsMut<T> {
272        PointsMut { blend_space: self }
273    }
274
275    pub fn triangles(&self) -> &[TriangleDefinition] {
276        &self.triangles
277    }
278
279    pub fn children(&self) -> Vec<Handle<PoseNode<T>>> {
280        self.points.iter().map(|p| p.pose_source).collect()
281    }
282
283    pub fn set_min_values(&mut self, min_values: Vector2<f32>) {
284        self.min_values = min_values;
285        self.max_values = self.max_values.sup(&self.min_values);
286    }
287
288    pub fn min_values(&self) -> Vector2<f32> {
289        self.min_values
290    }
291
292    pub fn set_max_values(&mut self, max_values: Vector2<f32>) {
293        self.max_values = max_values;
294        self.min_values = self.min_values.inf(&self.max_values);
295    }
296
297    pub fn max_values(&self) -> Vector2<f32> {
298        self.max_values
299    }
300
301    pub fn set_snap_step(&mut self, step: Vector2<f32>) {
302        self.snap_step = step;
303    }
304
305    pub fn snap_step(&self) -> Vector2<f32> {
306        self.snap_step
307    }
308
309    pub fn set_sampling_parameter(&mut self, parameter: String) {
310        self.sampling_parameter = parameter;
311    }
312
313    pub fn sampling_parameter(&self) -> &str {
314        &self.sampling_parameter
315    }
316
317    pub fn set_x_axis_name(&mut self, name: String) -> String {
318        std::mem::replace(&mut self.x_axis_name, name)
319    }
320
321    pub fn x_axis_name(&self) -> &str {
322        &self.x_axis_name
323    }
324
325    pub fn set_y_axis_name(&mut self, name: String) -> String {
326        std::mem::replace(&mut self.y_axis_name, name)
327    }
328
329    pub fn y_axis_name(&self) -> &str {
330        &self.y_axis_name
331    }
332
333    pub fn try_snap_points(&mut self) {
334        for point in self.points.iter_mut() {
335            let x = math::round_to_step(point.position.x, self.snap_step.x)
336                .clamp(self.min_values.x, self.max_values.x);
337            let y = math::round_to_step(point.position.y, self.snap_step.y)
338                .clamp(self.min_values.y, self.max_values.y);
339            point.position = Vector2::new(x, y);
340        }
341    }
342
343    pub fn fetch_weights(&self, sampling_point: Vector2<f32>) -> Option<[(usize, f32); 3]> {
344        if self.points.is_empty() {
345            return None;
346        }
347
348        // Single point blend space.
349        if self.points.len() == 1 {
350            return Some([(0, 1.0), (0, 0.0), (0, 0.0)]);
351        }
352
353        // Check if there's an edge that contains a projection of the sampling point.
354        if self.points.len() == 2 {
355            let edge = self.points[1].position - self.points[0].position;
356            let to_point = sampling_point - self.points[0].position;
357            let t = to_point.dot(&edge) / edge.dot(&edge);
358            if (0.0..=1.0).contains(&t) {
359                return Some([(0, (1.0 - t)), (1, t), (0, 0.0)]);
360            }
361        }
362
363        let triangles = &self.triangles;
364
365        // Try to find a triangle that contains the sampling point.
366        for triangle in triangles.iter() {
367            let ia = triangle[0] as usize;
368            let ib = triangle[1] as usize;
369            let ic = triangle[2] as usize;
370
371            let a = &self.points[ia];
372            let b = &self.points[ib];
373            let c = &self.points[ic];
374
375            let barycentric_coordinates =
376                math::get_barycentric_coords_2d(sampling_point, a.position, b.position, c.position);
377
378            if math::barycentric_is_inside(barycentric_coordinates) {
379                let (u, v, w) = barycentric_coordinates;
380
381                return Some([(ia, u), (ib, v), (ic, w)]);
382            }
383        }
384
385        // If none of the triangles contains the sampling point, then try to find a closest edge of a
386        // triangle and calculate weights.
387        let mut min_distance = f32::MAX;
388        let mut weights = None;
389
390        for triangle in triangles.iter() {
391            for (a, b) in [
392                (triangle[0] as usize, triangle[1] as usize),
393                (triangle[1] as usize, triangle[2] as usize),
394                (triangle[2] as usize, triangle[0] as usize),
395            ] {
396                let pt_a = self.points[a].position;
397                let pt_b = self.points[b].position;
398
399                let edge = pt_b - pt_a;
400                let to_point = sampling_point - pt_a;
401
402                let t = to_point.dot(&edge) / edge.dot(&edge);
403
404                if (0.0..=1.0).contains(&t) {
405                    let projection = pt_a + edge.scale(t);
406
407                    let distance = sampling_point.metric_distance(&projection);
408
409                    if distance < min_distance {
410                        min_distance = distance;
411
412                        weights = Some([(a, (1.0 - t)), (b, t), (b, 0.0)]);
413                    }
414                }
415            }
416        }
417
418        weights
419    }
420
421    fn triangulate(&mut self) -> bool {
422        self.triangles.clear();
423
424        if self.points.len() < 3 {
425            return false;
426        }
427
428        let mut triangulation: DelaunayTriangulation<_> = DelaunayTriangulation::new();
429
430        for point in self.points.iter() {
431            if triangulation
432                .insert(Point2::new(point.position.x, point.position.y))
433                .is_err()
434            {
435                return false;
436            }
437        }
438
439        for face in triangulation.inner_faces() {
440            let edges = face.adjacent_edges();
441            self.triangles.push(TriangleDefinition([
442                edges[0].from().index() as u32,
443                edges[1].from().index() as u32,
444                edges[2].from().index() as u32,
445            ]))
446        }
447
448        true
449    }
450}
451
452#[cfg(test)]
453mod test {
454    use crate::{
455        core::{algebra::Vector2, math::TriangleDefinition},
456        machine::node::blendspace::{BlendSpace, BlendSpacePoint},
457    };
458    use fyrox_core::pool::ErasedHandle;
459
460    #[test]
461    fn test_blend_space_triangulation() {
462        let mut blend_space = BlendSpace::<ErasedHandle>::default();
463
464        blend_space.set_points(vec![
465            BlendSpacePoint {
466                position: Vector2::new(0.0, 0.0),
467                pose_source: Default::default(),
468            },
469            BlendSpacePoint {
470                position: Vector2::new(1.0, 0.0),
471                pose_source: Default::default(),
472            },
473            BlendSpacePoint {
474                position: Vector2::new(1.0, 1.0),
475                pose_source: Default::default(),
476            },
477            BlendSpacePoint {
478                position: Vector2::new(0.0, 1.0),
479                pose_source: Default::default(),
480            },
481        ]);
482
483        blend_space.fetch_weights(Default::default());
484
485        assert_eq!(
486            blend_space.triangles,
487            vec![TriangleDefinition([2, 0, 1]), TriangleDefinition([3, 0, 2])]
488        )
489    }
490
491    #[test]
492    fn test_empty_blend_space_sampling() {
493        assert!(BlendSpace::<ErasedHandle>::default()
494            .fetch_weights(Default::default())
495            .is_none())
496    }
497
498    #[test]
499    fn test_single_point_blend_space_sampling() {
500        let mut blend_space = BlendSpace::<ErasedHandle>::default();
501
502        blend_space.set_points(vec![BlendSpacePoint {
503            position: Vector2::new(0.0, 0.0),
504            pose_source: Default::default(),
505        }]);
506
507        assert_eq!(
508            blend_space.fetch_weights(Default::default()),
509            Some([(0, 1.0), (0, 0.0), (0, 0.0)])
510        );
511    }
512
513    #[test]
514    fn test_two_points_blend_space_sampling() {
515        let mut blend_space = BlendSpace::<ErasedHandle>::default();
516
517        blend_space.set_points(vec![
518            BlendSpacePoint {
519                position: Vector2::new(0.0, 0.0),
520                pose_source: Default::default(),
521            },
522            BlendSpacePoint {
523                position: Vector2::new(1.0, 0.0),
524                pose_source: Default::default(),
525            },
526        ]);
527
528        assert_eq!(
529            blend_space.fetch_weights(Vector2::new(0.0, 0.0)),
530            Some([(0, 1.0), (1, 0.0), (0, 0.0)])
531        );
532
533        assert_eq!(
534            blend_space.fetch_weights(Vector2::new(0.5, 0.0)),
535            Some([(0, 0.5), (1, 0.5), (0, 0.0)])
536        );
537
538        assert_eq!(
539            blend_space.fetch_weights(Vector2::new(1.0, 0.0)),
540            Some([(0, 0.0), (1, 1.0), (0, 0.0)])
541        );
542    }
543}