1#![allow(missing_docs)] use 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 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 if self.points.len() == 1 {
350 return Some([(0, 1.0), (0, 0.0), (0, 0.0)]);
351 }
352
353 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 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 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}