third_person/
third_person.rs

1//! Example showing how to construct, serialize, compile, instantiate and run graphs
2
3use 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    // Parameter lookups can be done ahead of time using the definition which each graph has a reference to.
28    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    // Parameters are specific to a definition
38    locomotion_speed.set(&mut locomotion, 2.0);
39
40    // Event has multiple states that micmics that of a state in a statemachine (entering, entered, exiting, exited)
41    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        // Multiple graphs can be concatenated where the output of the first is set
48        // as a reference to the next. It's ultimately up to the next graph if it decides
49        // to blend with the reference task at all.
50        context.run(&mut action);
51
52        // In this example the second graph looks for an emitted event from previous graphs
53        // to decided if it should blend or not.
54        context.run_and_append(&mut locomotion);
55
56        // The resulting blend tree is all the active animations that could be sampled
57        // even if they dont contribute to the final blend for things like animation events.
58        // The tree can be evaluated from the last sample to form a trimmed blend stack.
59
60        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    // The constructed data model can be serialized and reused
148    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    // The specific nodes allowed is decided by the compilation registry
153    let mut registry = NodeCompilationRegistry::default();
154    add_default_nodes(&mut registry);
155
156    // The resulting compilation contains additional debug information but only the builder is needed for the runtime
157    let locomotion_compilation = GraphDefinitionCompilation::compile(&locomotion_graph, &registry)?;
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    // The specific nodes instantiated at runtime is decided by the graph node registry
163    let mut graph_nodes = GraphNodeRegistry::default();
164    add_default_constructors(&mut graph_nodes);
165
166    // The builder validates the definition and instantiates the immutable graph nodes which processes the graph data
167    let locomotion_definition = locomotion_compilation.builder.build(&graph_nodes)?;
168
169    // Resources are currently application defined. SimpleResourceProvider and the implementation in this example is illustrative of possible use.
170    let default_locomotion_resources = Arc::new(SimpleResourceProvider::new_with_map(
171        &locomotion_definition,
172        RuntimeResource::Empty,
173        get_cached_resource,
174    )?);
175
176    // Lookup default skeleton to use since there are no actual resources to probe
177    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    // The constructed data model can be serialized and reused
198    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    // The specific nodes allowed is decided by the compilation registry
203    let mut registry = NodeCompilationRegistry::default();
204    add_default_nodes(&mut registry);
205
206    // The resulting compilation contains additional debug information but only the builder is needed for the runtime
207    let action_compilation = GraphDefinitionCompilation::compile(&action_graph, &registry)?;
208    let serialize_action_definition = serde_json::to_string_pretty(&action_compilation.builder)?;
209    std::fs::write("action.agc", serialize_action_definition)?;
210
211    // The specific nodes instantiated at runtime is decided by the graph node registry
212    let mut graph_nodes = GraphNodeRegistry::default();
213    add_default_constructors(&mut graph_nodes);
214
215    // The builder validates the definition and instantiates the immutable graph nodes which processes the graph data
216    let action_definition = action_compilation.builder.build(&graph_nodes)?;
217
218    // Resources are currently application defined. SimpleResourceProvider and the implementation in this example is illustrative of possible use.
219    let default_action_resources = Arc::new(SimpleResourceProvider::new_with_map(
220        &action_definition,
221        RuntimeResource::Empty,
222        get_cached_resource,
223    )?);
224
225    // Lookup default skeleton to use since there are no actual resources to probe
226    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}