1use animgraph::compiler::prelude::*;
4use serde_derive::{Deserialize, Serialize};
5use std::collections::BTreeMap;
6use std::sync::{Arc, Mutex};
7
8fn main() -> anyhow::Result<()> {
9 let (locomotion_definition, default_locomotion_skeleton, default_locomotion_resources) =
10 locomotion_graph_example()?;
11
12 let mut locomotion = create_instance(
13 locomotion_definition,
14 default_locomotion_skeleton,
15 default_locomotion_resources,
16 );
17
18 let (action_definition, default_action_skeleton, default_action_resources) =
19 action_graph_example()?;
20
21 let mut action = create_instance(
22 action_definition,
23 default_action_skeleton,
24 default_action_resources,
25 );
26
27 let locomotion_speed = locomotion
29 .definition()
30 .get_number_parameter::<f32>("locomotion_speed")
31 .expect("Valid parameter");
32 let action_sit = action
33 .definition()
34 .get_event_parameter("sit")
35 .expect("Valid parameter");
36
37 locomotion_speed.set(&mut locomotion, 2.0);
39
40 action_sit.set(&mut action, FlowState::Entered);
42
43 let delta_time = 0.1;
44 let mut context = DefaultRunContext::new(delta_time);
45 let resources = RESOURCES.lock().expect("Single threaded");
46 for frame in 1..=5 {
47 context.run(&mut action);
51
52 context.run_and_append(&mut locomotion);
55
56 println!("Frame #{frame}:");
61 println!("- Blend Tree:");
62 for (index, task) in context.tree.get().iter().enumerate() {
63 let n = index + 1;
64 match task {
65 BlendSample::Animation {
66 id,
67 normalized_time,
68 } => {
69 println!(
70 " #{n} Sample {} at t={normalized_time}",
71 &resources.animations[id.0 as usize].name
72 );
73 }
74 BlendSample::Blend(_, _, a, g) => {
75 if *g == BoneGroupId::All {
76 println!(" #{n} Blend a={a}");
77 } else {
78 println!(" #{n} Masked blend a={a}");
79 }
80 }
81 BlendSample::Interpolate(_, _, a) => {
82 println!(" #{n} Interpolate a={a}");
83 }
84 }
85 }
86
87 struct BlendStack<'a>(&'a GlobalResources);
88 impl<'a> BlendTreeVisitor for BlendStack<'a> {
89 fn visit(&mut self, tree: &BlendTree, sample: &BlendSample) {
90 match *sample {
91 BlendSample::Animation {
92 id,
93 normalized_time,
94 } => {
95 println!(
96 " Sample {} at t={normalized_time}",
97 &self.0.animations[id.0 as usize].name
98 );
99 }
100 BlendSample::Interpolate(x, y, a) => {
101 if a < 1.0 {
102 tree.visit(self, x);
103 }
104 if a > 0.0 {
105 tree.visit(self, y);
106 }
107
108 if a > 0.0 && a < 1.0 {
109 println!(" Interpolate a={a}");
110 }
111 }
112 BlendSample::Blend(x, y, a, g) => {
113 if g == BoneGroupId::All {
114 if a < 1.0 {
115 tree.visit(self, x);
116 }
117 if a > 0.0 {
118 tree.visit(self, y);
119 }
120
121 if a > 0.0 && a < 1.0 {
122 println!(" Interpolate a={a}");
123 }
124 } else {
125 tree.visit(self, x);
126 tree.visit(self, y);
127 println!(" Masked Blend a={a}");
128 }
129 }
130 }
131 }
132 }
133
134 println!("\n- Blend Stack:");
135 context.tree.visit_root(&mut BlendStack(&resources));
136 println!("");
137 }
138
139 Ok(())
140}
141
142fn locomotion_graph_example() -> anyhow::Result<(
143 Arc<GraphDefinition>,
144 Option<Arc<Skeleton>>,
145 Arc<dyn GraphResourceProvider>,
146)> {
147 let locomotion_graph = create_locomotion_graph();
149 let serialized_locmotion_graph = serde_json::to_string_pretty(&locomotion_graph)?;
150 std::fs::write("locomotion.ag", serialized_locmotion_graph)?;
151
152 let mut registry = NodeCompilationRegistry::default();
154 add_default_nodes(&mut registry);
155
156 let locomotion_compilation = GraphDefinitionCompilation::compile(&locomotion_graph, ®istry)?;
158 let serialize_locomotion_definition =
159 serde_json::to_string_pretty(&locomotion_compilation.builder)?;
160 std::fs::write("locomotion.agc", serialize_locomotion_definition)?;
161
162 let mut graph_nodes = GraphNodeRegistry::default();
164 add_default_constructors(&mut graph_nodes);
165
166 let locomotion_definition = locomotion_compilation.builder.build(&graph_nodes)?;
168
169 let default_locomotion_resources = Arc::new(SimpleResourceProvider::new_with_map(
171 &locomotion_definition,
172 RuntimeResource::Empty,
173 get_cached_resource,
174 )?);
175
176 let default_skeleton = default_locomotion_resources
178 .resources
179 .iter()
180 .find_map(|x| match x {
181 RuntimeResource::Skeleton(skeleton) => Some(skeleton.clone()),
182 _ => None,
183 });
184
185 Ok((
186 locomotion_definition,
187 default_skeleton,
188 default_locomotion_resources,
189 ))
190}
191
192fn action_graph_example() -> anyhow::Result<(
193 Arc<GraphDefinition>,
194 Option<Arc<Skeleton>>,
195 Arc<dyn GraphResourceProvider>,
196)> {
197 let action_graph = create_action_graph();
199 let serialized_locmotion_graph = serde_json::to_string_pretty(&action_graph)?;
200 std::fs::write("action.ag", serialized_locmotion_graph)?;
201
202 let mut registry = NodeCompilationRegistry::default();
204 add_default_nodes(&mut registry);
205
206 let action_compilation = GraphDefinitionCompilation::compile(&action_graph, ®istry)?;
208 let serialize_action_definition = serde_json::to_string_pretty(&action_compilation.builder)?;
209 std::fs::write("action.agc", serialize_action_definition)?;
210
211 let mut graph_nodes = GraphNodeRegistry::default();
213 add_default_constructors(&mut graph_nodes);
214
215 let action_definition = action_compilation.builder.build(&graph_nodes)?;
217
218 let default_action_resources = Arc::new(SimpleResourceProvider::new_with_map(
220 &action_definition,
221 RuntimeResource::Empty,
222 get_cached_resource,
223 )?);
224
225 let default_skeleton = default_action_resources
227 .resources
228 .iter()
229 .find_map(|x| match x {
230 RuntimeResource::Skeleton(skeleton) => Some(skeleton.clone()),
231 _ => None,
232 });
233
234 Ok((
235 action_definition,
236 default_skeleton,
237 default_action_resources,
238 ))
239}
240
241fn create_instance(
242 definition: Arc<GraphDefinition>,
243 skeleton: Option<Arc<Skeleton>>,
244 resources: Arc<dyn GraphResourceProvider>,
245) -> Graph {
246 if let Some(skeleton) = skeleton {
247 definition.build(resources.clone(), skeleton)
248 } else {
249 definition.build_with_empty_skeleton(resources.clone())
250 }
251}
252
253static RESOURCES: Mutex<GlobalResources> = Mutex::new(GlobalResources::new());
254
255fn get_cached_resource(_resource_type: &str, value: Value) -> anyhow::Result<RuntimeResource> {
256 let res = serde_json::from_value(value)?;
257 Ok(RESOURCES.lock().expect("Single threaded").get_cached(res))
258}
259
260enum RuntimeResource {
261 Empty,
262 AnimationClip(AnimationClip),
263 BoneGroup(Arc<BoneGroup>),
264 Skeleton(Arc<Skeleton>),
265}
266
267impl ResourceType for RuntimeResource {
268 fn get_resource(&self) -> &dyn std::any::Any {
269 match self {
270 RuntimeResource::AnimationClip(res) => res,
271 RuntimeResource::BoneGroup(res) => res,
272 RuntimeResource::Skeleton(res) => res,
273 RuntimeResource::Empty => &(),
274 }
275 }
276}
277
278#[derive(Serialize, Deserialize)]
279#[serde(rename_all = "snake_case")]
280enum SerializedResource {
281 AnimationClip(String),
282 BoneGroup(Vec<String>),
283 Skeleton(BTreeMap<String, String>),
284}
285
286struct Animation {
287 pub name: String,
288}
289
290struct GlobalResources {
291 pub animations: Vec<Animation>,
292 pub bone_groups: Vec<Arc<BoneGroup>>,
293 pub skeletons: Vec<Arc<Skeleton>>,
294}
295
296impl GlobalResources {
297 pub const fn new() -> Self {
298 Self {
299 animations: vec![],
300 bone_groups: vec![],
301 skeletons: vec![],
302 }
303 }
304
305 pub fn get_cached(&mut self, serialized: SerializedResource) -> RuntimeResource {
306 match serialized {
307 SerializedResource::AnimationClip(name) => {
308 let looping = name.contains("looping");
309 let animation =
310 if let Some(index) = self.animations.iter().position(|x| x.name == name) {
311 AnimationId(index as _)
312 } else {
313 let index = AnimationId(self.animations.len() as _);
314 self.animations.push(Animation { name });
315 index
316 };
317
318 RuntimeResource::AnimationClip(AnimationClip {
319 animation: animation,
320 bone_group: BoneGroupId::All,
321 looping,
322 start: Seconds(0.0),
323 duration: Seconds(1.0),
324 })
325 }
326 SerializedResource::BoneGroup(mut group) => {
327 group.sort();
328 let mut bones = BoneGroup::new(0, group.as_slice().iter());
329 let res = if let Some(res) =
330 self.bone_groups.iter().find(|x| x.weights == bones.weights)
331 {
332 res.clone()
333 } else {
334 bones.group = BoneGroupId::Reference(self.bone_groups.len() as _);
335 let res = Arc::new(bones);
336 self.bone_groups.push(res.clone());
337 res
338 };
339
340 RuntimeResource::BoneGroup(res)
341 }
342 SerializedResource::Skeleton(map) => {
343 let mut skeleton = Skeleton::from_parent_map(&map);
344 let res = if let Some(res) = self
345 .skeletons
346 .iter()
347 .find(|x| x.bones == skeleton.bones && x.parents == skeleton.parents)
348 {
349 res.clone()
350 } else {
351 skeleton.id = SkeletonId(self.skeletons.len() as _);
352 let res = Arc::new(skeleton);
353 self.skeletons.push(res.clone());
354 res
355 };
356 RuntimeResource::Skeleton(res)
357 }
358 }
359 }
360}
361
362fn serialize_upper_body_mask() -> Value {
363 serde_json::to_value(SerializedResource::BoneGroup(vec![
364 "LeftArm".to_owned(),
365 "LeftForeArm".to_owned(),
366 "LeftHand".to_owned(),
367 "LeftHandIndex1".to_owned(),
368 "LeftHandIndex2".to_owned(),
369 "LeftHandIndex3".to_owned(),
370 "LeftHandMiddle1".to_owned(),
371 "LeftHandMiddle2".to_owned(),
372 "LeftHandMiddle3".to_owned(),
373 "LeftHandPinky1".to_owned(),
374 "LeftHandPinky2".to_owned(),
375 "LeftHandPinky3".to_owned(),
376 "LeftHandRing1".to_owned(),
377 "LeftHandRing2".to_owned(),
378 "LeftHandRing3".to_owned(),
379 "LeftHandThumb1".to_owned(),
380 "LeftHandThumb2".to_owned(),
381 "LeftHandThumb3".to_owned(),
382 "LeftShoulder".to_owned(),
383 "Neck".to_owned(),
384 "RightArm".to_owned(),
385 "RightForeArm".to_owned(),
386 "RightHand".to_owned(),
387 "RightHandIndex1".to_owned(),
388 "RightHandIndex2".to_owned(),
389 "RightHandIndex3".to_owned(),
390 "RightHandMiddle1".to_owned(),
391 "RightHandMiddle2".to_owned(),
392 "RightHandMiddle3".to_owned(),
393 "RightHandPinky1".to_owned(),
394 "RightHandPinky2".to_owned(),
395 "RightHandPinky3".to_owned(),
396 "RightHandRing1".to_owned(),
397 "RightHandRing2".to_owned(),
398 "RightHandRing3".to_owned(),
399 "RightHandThumb1".to_owned(),
400 "RightHandThumb2".to_owned(),
401 "RightHandThumb3".to_owned(),
402 "RightShoulder".to_owned(),
403 "Spine".to_owned(),
404 "Spine1".to_owned(),
405 "Spine2".to_owned(),
406 ]))
407 .unwrap()
408}
409
410fn serialize_skeleton() -> Value {
411 let bones: BTreeMap<_, _> = [
412 ("Hips", ""),
413 ("Head", "Neck"),
414 ("LeftArm", "LeftShoulder"),
415 ("LeftFoot", "LeftLeg"),
416 ("LeftForeArm", "LeftArm"),
417 ("LeftHand", "LeftForeArm"),
418 ("LeftHandIndex1", "LeftHand"),
419 ("LeftHandIndex2", "LeftHandIndex1"),
420 ("LeftHandIndex3", "LeftHandIndex2"),
421 ("LeftHandMiddle1", "LeftHand"),
422 ("LeftHandMiddle2", "LeftHandMiddle1"),
423 ("LeftHandMiddle3", "LeftHandMiddle2"),
424 ("LeftHandPinky1", "LeftHand"),
425 ("LeftHandPinky2", "LeftHandPinky1"),
426 ("LeftHandPinky3", "LeftHandPinky2"),
427 ("LeftHandRing1", "LeftHand"),
428 ("LeftHandRing2", "LeftHandRing1"),
429 ("LeftHandRing3", "LeftHandRing2"),
430 ("LeftHandThumb1", "LeftHand"),
431 ("LeftHandThumb2", "LeftHandThumb1"),
432 ("LeftHandThumb3", "LeftHandThumb2"),
433 ("LeftLeg", "LeftUpLeg"),
434 ("LeftShoulder", "Spine2"),
435 ("LeftToeBase", "LeftFoot"),
436 ("LeftUpLeg", "Hips"),
437 ("Neck", "Spine2"),
438 ("RightArm", "RightShoulder"),
439 ("RightFoot", "RightLeg"),
440 ("RightForeArm", "RightArm"),
441 ("RightHand", "RightForeArm"),
442 ("RightHandIndex1", "RightHand"),
443 ("RightHandIndex2", "RightHandIndex1"),
444 ("RightHandIndex3", "RightHandIndex2"),
445 ("RightHandMiddle1", "RightHand"),
446 ("RightHandMiddle2", "RightHandMiddle1"),
447 ("RightHandMiddle3", "RightHandMiddle2"),
448 ("RightHandPinky1", "RightHand"),
449 ("RightHandPinky2", "RightHandPinky1"),
450 ("RightHandPinky3", "RightHandPinky2"),
451 ("RightHandRing1", "RightHand"),
452 ("RightHandRing2", "RightHandRing1"),
453 ("RightHandRing3", "RightHandRing2"),
454 ("RightHandThumb1", "RightHand"),
455 ("RightHandThumb2", "RightHandThumb1"),
456 ("RightHandThumb3", "RightHandThumb2"),
457 ("RightLeg", "RightUpLeg"),
458 ("RightShoulder", "Spine2"),
459 ("RightToeBase", "RightFoot"),
460 ("RightUpLeg", "Hips"),
461 ("Spine", "Hips"),
462 ("Spine1", "Spine"),
463 ("Spine2", "Spine1"),
464 ]
465 .into_iter()
466 .map(|(a, b)| (a.to_owned(), b.to_owned()))
467 .collect();
468
469 serde_json::to_value(&SerializedResource::Skeleton(bones)).unwrap()
470}
471
472fn serialize_animation(id: &str) -> Value {
473 serde_json::to_value(&SerializedResource::AnimationClip(id.to_owned())).unwrap()
474}
475
476fn init_res<'a>(list: impl IntoIterator<Item = &'a str>) -> Vec<ResourceContent> {
477 list.into_iter()
478 .map(|id| {
479 if id == SKELETON {
480 ResourceContent {
481 name: id.to_owned(),
482 resource_type: Skeleton::resource_type().to_owned(),
483 content: serialize_skeleton(),
484 ..Default::default()
485 }
486 } else if id == UPPER_BODY_MASK {
487 ResourceContent {
488 name: id.to_owned(),
489 resource_type: BoneGroup::resource_type().to_owned(),
490 content: serialize_upper_body_mask(),
491 ..Default::default()
492 }
493 } else {
494 ResourceContent {
495 name: id.to_owned(),
496 resource_type: AnimationClip::resource_type().to_owned(),
497 content: serialize_animation(id),
498 ..Default::default()
499 }
500 }
501 })
502 .collect()
503}
504
505fn init(name: &str, value: impl Into<InitialParameterValue>) -> Parameter {
506 Parameter {
507 name: name.to_owned(),
508 initial: value.into(),
509 ..Default::default()
510 }
511}
512
513const IDLE_CLIP: &str = "idle_looping";
514const WALKING_CLIP: &str = "walking_looping";
515const RUNNING_CLIP: &str = "running_looping";
516const JUMPING_CLIP: &str = "jumping_looping";
517const FALLING_CLIP: &str = "falling_looping";
518const DANCING_CLIP: &str = "dancing_looping";
519const DANCING_UPPER_CLIP: &str = "dancing_upper_looping";
520const SITTING_CLIP: &str = "sitting_looping";
521const POINTING_FORWARD_CLIP: &str = "pointing_forward";
522const TURN_AND_SIT_CLIP: &str = "turn_and_sit";
523const UPPER_BODY_MASK: &str = "upper_body_mask";
524const SKELETON: &str = "skeleton";
525const TRANSITION_DURATION: Seconds = Seconds(0.2);
526const FADE_OUT_DURATION: Seconds = Seconds(0.4);
527const ACTION_EVENT: &str = "action";
528const UPPER_BODY_ACTION_EVENT: &str = "upper_body_action";
529
530fn create_locomotion_graph() -> AnimGraph {
531 let resources = init_res([
532 SKELETON,
533 IDLE_CLIP,
534 WALKING_CLIP,
535 RUNNING_CLIP,
536 JUMPING_CLIP,
537 FALLING_CLIP,
538 DANCING_CLIP,
539 DANCING_UPPER_CLIP,
540 UPPER_BODY_MASK,
541 ]);
542
543 let parameters = vec![
544 init("grounded", true),
545 init("falling", true),
546 init("locomotion_speed", 0.0f32),
547 init("overdrive", 1.0f32),
548 init("moving", false),
549 init("dance", false),
550 ];
551
552 let locomotion_layer = {
553 let idle_node = alias("Idle", animation_pose(IDLE_CLIP));
554 let walking_node = alias("Walking", animation_pose(WALKING_CLIP));
555 let running_node = alias("Running", animation_pose(RUNNING_CLIP));
556
557 let locomotion_blend = endpoint(alias(
558 "locomotion",
559 blend_ranges(
560 bind_parameter("locomotion_speed"),
561 [(0.0, idle_node), (2.0, walking_node), (6.0, running_node)],
562 ),
563 ));
564
565 let speed_scaled_locomotion = preprocess(
566 speed_scale(bind_parameter("overdrive"), ALPHA_ONE),
567 locomotion_blend,
568 );
569
570 state_machine(
571 "Locomotion",
572 [state("Blend").with(speed_scaled_locomotion, [])],
573 )
574 };
575
576 let jump_layer = {
577 const STATE_FALLING: &str = "Falling";
578 const STATE_JUMPING: &str = "Jumping";
579 const STATE_OFF: &str = "Off";
580
581 let not_grounded = bind_parameter::<bool>("grounded").not();
582 let has_fallen = bind_parameter::<bool>("falling").and(not_grounded.clone());
583
584 let state_off = state(STATE_OFF)
585 .with_branch(endpoint(inactive_layer()))
586 .with_transitions([
587 has_fallen.transition(STATE_FALLING, TRANSITION_DURATION),
588 not_grounded.transition(STATE_JUMPING, TRANSITION_DURATION),
589 ]);
590
591 let is_grounded = bind_parameter::<bool>("grounded").as_expr();
592 let is_falling = bind_parameter::<bool>("falling").as_expr();
593 let state_jumping = state(STATE_JUMPING)
594 .with_branch(endpoint(tree([
595 animation_pose(JUMPING_CLIP),
596 state_event("jumped", true, EventEmit::Entry),
597 ])))
598 .with_transitions([
599 is_grounded
600 .clone()
601 .transition(STATE_OFF, TRANSITION_DURATION),
602 is_falling.transition(STATE_FALLING, TRANSITION_DURATION),
603 ]);
604
605 let state_falling = state(STATE_FALLING)
606 .with_branch(endpoint(animation_pose(FALLING_CLIP)))
607 .with_transitions([is_grounded.transition(STATE_OFF, TRANSITION_DURATION)]);
608
609 state_machine("Jump", [state_off, state_jumping, state_falling])
610 };
611
612 let dance_layer = {
613 const STATE_OFF: &str = "Off";
614 const STATE_FULL: &str = "Full Body";
615 const STATE_UPPER: &str = "Upper Body";
616
617 let is_dancing =
618 bind_parameter::<bool>("dance").and(state_is("::Dance::Off", QueryType::Entered));
619 let is_moving_and_dancing = bind_parameter::<bool>("moving")
620 .or(bind_route::<bool>("action_active"))
621 .and(is_dancing.clone());
622 let state_off = state(STATE_OFF)
623 .with_branch(endpoint(inactive_layer()))
624 .with_transitions([
625 is_moving_and_dancing.transition(STATE_UPPER, TRANSITION_DURATION),
626 is_dancing.transition(STATE_FULL, TRANSITION_DURATION),
627 ]);
628
629 let state_full = {
630 let dancing_pose = endpoint(animation_pose(DANCING_CLIP));
631 let not_dancing = bind_parameter::<bool>("dance").not();
632 let is_moving = bind_parameter::<bool>("moving");
633 state(STATE_FULL)
634 .with_branch(dancing_pose)
635 .with_transitions([
636 not_dancing.transition(STATE_OFF, FADE_OUT_DURATION),
637 is_moving.transition(STATE_UPPER, TRANSITION_DURATION),
638 ])
639 };
640
641 let state_upper = {
642 let dancing_upper = endpoint(tree([
643 animation_pose(DANCING_UPPER_CLIP),
644 pose_mask(UPPER_BODY_MASK),
645 ]));
646 let is_idle_or_not_dancing =
647 bind_parameter::<bool>("dance")
648 .not()
649 .or(bind_parameter::<bool>("moving")
650 .not()
651 .and(bind_route::<bool>("action_active").not()));
652
653 state(STATE_UPPER)
654 .with_branch(dancing_upper)
655 .with_transitions([is_idle_or_not_dancing.transition(STATE_OFF, FADE_OUT_DURATION)])
656 };
657
658 state_machine("Dance", [state_off, state_upper, state_full])
659 };
660
661 let action_layer = {
662 const STATE_OFF: &str = "Off";
663 const STATE_FULL: &str = "Full Body";
664 const STATE_UPPER: &str = "Upper Body";
665
666 state_machine(
667 "Action",
668 [
669 state(STATE_OFF)
670 .with_branch(endpoint(inactive_layer()))
671 .with_transitions([
672 bind_route::<bool>("action_active").transition(STATE_FULL, TRANSITION_DURATION),
673 bind_route::<bool>("upper_body_action_active")
674 .transition(STATE_UPPER, TRANSITION_DURATION),
675 ]),
676 state(STATE_FULL)
677 .with_branch(endpoint(reference_pose()))
678 .with_transitions([bind_route::<bool>("action_active")
679 .not()
680 .or(bind_parameter::<bool>("moving"))
681 .transition(STATE_OFF, TRANSITION_DURATION)]),
682 state(STATE_UPPER)
683 .with_branch(endpoint(tree([
684 reference_pose(),
685 pose_mask(UPPER_BODY_MASK),
686 ])))
687 .with_transitions([bind_route::<bool>("upper_body_action_active")
688 .not()
689 .or(bind_route::<bool>("action_active"))
690 .or(bind_parameter::<bool>("moving"))
691 .transition(STATE_OFF, FADE_OUT_DURATION * 2.0)]),
692 ],
693 )
694 };
695
696 let root = state_machine(
697 "Root",
698 [state("Layers").with_layers([
699 submachine("Locomotion"),
700 submachine("Jump"),
701 preprocess(
702 tree([
703 alias("action_active", event_emitted(ACTION_EVENT)),
704 alias(
705 "upper_body_action_active",
706 event_emitted(UPPER_BODY_ACTION_EVENT),
707 ),
708 ]),
709 submachine("Action"),
710 ),
711 submachine("Dance"),
712 ])],
713 );
714
715 AnimGraph {
716 resources,
717 parameters,
718 state_machines: vec![
719 root,
720 locomotion_layer,
721 jump_layer,
722 action_layer,
723 dance_layer,
724 ],
725 ..Default::default()
726 }
727}
728
729fn create_action_graph() -> AnimGraph {
730 let resources = init_res([
731 SKELETON,
732 SITTING_CLIP,
733 TURN_AND_SIT_CLIP,
734 POINTING_FORWARD_CLIP,
735 UPPER_BODY_MASK,
736 ]);
737
738 let parameters = vec![
739 init(
740 "fade_out",
741 InitialParameterValue::Event(FADE_OUT_EVENT.into()),
742 ),
743 init("turn_and_sit", false),
744 init("sit", InitialParameterValue::Event(SIT_EVENT.into())),
745 init("point_of_interest", InitialParameterValue::Vector([0.0; 3])),
746 init("enable_point_of_interest", false),
747 init(
748 "poi_cooldown",
749 InitialParameterValue::Event(COOLDOWN_EVENT.into()),
750 ),
751 ];
752
753 const COOLDOWN_EVENT: &str = "cooldown";
754 const FADE_OUT_EVENT: &str = "fade_out";
755 const SIT_EVENT: &str = "sit";
756
757 const POINTED_EVENT: &str = "pointed";
758
759 let pointing_cooldown = endpoint(tree([
760 reference_pose(),
761 state_event(COOLDOWN_EVENT, true, EventEmit::Entry),
762 alias("bounce_back", blend_in(ALPHA_ZERO, Seconds(5.0))),
763 ]));
764
765 let off_pose = endpoint(tree([
766 reference_pose(),
767 alias(
768 "poi_offset",
769 transform_offset("Hips", bind_parameter("point_of_interest")),
770 ),
771 ]));
772
773 const EMIT_ON_ENTER: EventEmit = EventEmit::Or(FlowState::Entering, FlowState::Entered);
774
775 let sitting_pose = endpoint(tree([
776 animation_pose(SITTING_CLIP),
777 state_event(ACTION_EVENT, true, EMIT_ON_ENTER),
778 ]));
779 let turn_and_sit_pose = endpoint(tree([
780 animation_pose(TURN_AND_SIT_CLIP),
781 state_event(ACTION_EVENT, true, EMIT_ON_ENTER),
782 ]));
783 let pointing_forward = endpoint(tree([
784 animation_pose(POINTING_FORWARD_CLIP),
785 remaining_event(
786 bind_route("animation_pose"),
787 POINTED_EVENT,
788 true,
789 TRANSITION_DURATION,
790 EventEmit::Never,
791 ),
792 state_event(UPPER_BODY_ACTION_EVENT, true, EMIT_ON_ENTER),
793 ]));
794 use QueryType::*;
795
796 const POINTING_STATE: &str = "Pointing";
797 const COOLDOWN_STATE: &str = "Cooldown";
798 let idling = state_machine(
799 "Idle",
800 [
801 state(OFF_STATE).with_branch(off_pose).with_transitions([
802 bind_route::<f32>("bounce_back")
803 .not_equal(1.0)
804 .immediate_transition(COOLDOWN_STATE),
805 bind_parameter::<bool>("enable_point_of_interest")
806 .and(contains_inclusive(
807 (0.4, 1.5),
808 bind_route::<[f32; 3]>("poi_offset").projection(Projection::Length),
809 ))
810 .and(
811 bind_route::<[f32; 3]>("poi_offset")
812 .projection(Projection::Back)
813 .gt(0.1),
814 )
815 .transition(POINTING_STATE, FADE_OUT_DURATION),
816 ]),
817 state(POINTING_STATE)
818 .with_branch(pointing_forward)
819 .with_transitions([bind_parameter::<bool>("enable_point_of_interest")
820 .not()
821 .or(event_is(POINTED_EVENT, QueryType::Active))
822 .transition(COOLDOWN_STATE, FADE_OUT_DURATION * 2.0)]),
823 state(COOLDOWN_STATE)
824 .with_branch(pointing_cooldown)
825 .with_transitions([bind_route::<f32>("bounce_back")
826 .equals(1.0)
827 .immediate_transition(OFF_STATE)]),
828 ],
829 );
830
831 const OFF_STATE: &str = "Off";
832 const TURN_AND_SIT_STATE: &str = "Turn and sit";
833 const SITTING_STATE: &str = "Sitting";
834
835 let root = state_machine(
836 "Root",
837 [
838 state(OFF_STATE)
839 .with_branch(submachine("Idle"))
840 .with_transitions([
841 event_is(FADE_OUT_EVENT, Exited)
842 .and(event_is(SIT_EVENT, Active))
843 .transition(SITTING_STATE, TRANSITION_DURATION),
844 event_is(FADE_OUT_EVENT, Exited)
845 .and(bind_parameter::<bool>("turn_and_sit"))
846 .transition(TURN_AND_SIT_STATE, TRANSITION_DURATION),
847 ]),
848 state(SITTING_STATE)
849 .with_branch(sitting_pose)
850 .with_global_condition(
851 event_is(FADE_OUT_EVENT, Exited).and(event_is(SIT_EVENT, Active)),
852 )
853 .with_transitions([event_is(SIT_EVENT, Exited)
854 .as_expr()
855 .transition(OFF_STATE, FADE_OUT_DURATION)]),
856 state(TURN_AND_SIT_STATE)
857 .with_branch(turn_and_sit_pose)
858 .with_global_condition(
859 event_is(FADE_OUT_EVENT, Exited).and(bind_parameter::<bool>("turn_and_sit")),
860 )
861 .with_transitions([
862 event_is(FADE_OUT_EVENT, Active)
863 .as_expr()
864 .transition(OFF_STATE, FADE_OUT_DURATION),
865 event_is(SIT_EVENT, Active)
866 .as_expr()
867 .transition(SITTING_STATE, TRANSITION_DURATION),
868 ]),
869 ],
870 );
871
872 AnimGraph {
873 resources,
874 parameters,
875 state_machines: vec![root, idling],
876 ..Default::default()
877 }
878}