Skip to main content

myth_animation/
binder.rs

1use crate::binding::{ClipBinding, Rig, TrackBinding};
2use crate::clip::AnimationClip;
3use crate::target::AnimationTarget;
4use myth_core::NodeHandle;
5
6/// Resolves animation clip track targets to logical bone indices via a [`Rig`].
7///
8/// Two main workflows:
9///
10/// 1. **Rig construction** — [`build_rig`](Self::build_rig) walks the scene
11///    subtree to produce a [`Rig`] with ordered bone handles and paths.
12/// 2. **Clip binding** — [`build_clip_binding`](Self::build_clip_binding) maps
13///    each track's path to a bone index, lazily recording rest poses for
14///    newly animated nodes.
15pub struct Binder;
16
17impl Binder {
18    /// Builds a [`Rig`] by walking the subtree rooted at `root_node`.
19    ///
20    /// Every named descendant (and the root itself) becomes a bone entry.
21    /// Bone paths are recorded relative to `root_node` so that the same
22    /// rig topology can be matched against multiple clips.
23    pub fn build_rig(target: &dyn AnimationTarget, root_node: NodeHandle) -> Rig {
24        let mut bones = Vec::new();
25        let mut bone_paths = Vec::new();
26
27        // Depth-first walk collecting (handle, path) pairs.
28        let mut stack: Vec<(NodeHandle, Vec<String>)> = Vec::new();
29
30        // Seed with root's direct children.
31        if let Some(children) = target.node_children(root_node) {
32            for child in children {
33                if let Some(name) = target.node_name(child) {
34                    stack.push((child, vec![name]));
35                }
36            }
37        }
38
39        // Also include root itself if it has a name.
40        if let Some(name) = target.node_name(root_node) {
41            bones.push(root_node);
42            bone_paths.push(vec![name]);
43        }
44
45        while let Some((handle, path)) = stack.pop() {
46            // Only register named nodes (unnamed internal nodes are skipped).
47            if target.node_name(handle).is_some() {
48                bones.push(handle);
49                bone_paths.push(path.clone());
50            }
51
52            if let Some(children) = target.node_children(handle) {
53                for child in children {
54                    if let Some(child_name) = target.node_name(child) {
55                        let mut child_path = path.clone();
56                        child_path.push(child_name);
57                        stack.push((child, child_path));
58                    }
59                }
60            }
61        }
62
63        Rig { bones, bone_paths }
64    }
65
66    /// Builds a [`ClipBinding`] mapping clip tracks to rig bone indices.
67    ///
68    /// For every track whose [`crate::clip::TrackMeta::path`] matches a bone path in the
69    /// rig, a [`TrackBinding`] is emitted. Nodes receiving their first
70    /// animation binding get their current transform lazily recorded as
71    /// the rest pose.
72    pub fn build_clip_binding(
73        target: &mut dyn AnimationTarget,
74        rig: &Rig,
75        clip: &AnimationClip,
76    ) -> ClipBinding {
77        let mut bindings = Vec::with_capacity(clip.tracks.len());
78
79        for (track_idx, track) in clip.tracks.iter().enumerate() {
80            let t = track.meta.target;
81
82            // Match track path against rig bone paths.
83            let bone_index = rig.bone_paths.iter().position(|p| *p == track.meta.path);
84
85            if let Some(bone_index) = bone_index {
86                let node_handle = rig.bones[bone_index];
87
88                // Lazy record rest pose on first encounter.
89                if !target.has_rest_transform(node_handle)
90                    && let Some(transform) = target.node_transform(node_handle)
91                {
92                    target.store_rest_transform(node_handle, transform);
93                }
94
95                bindings.push(TrackBinding {
96                    track_index: track_idx,
97                    bone_index,
98                    target: t,
99                });
100            }
101        }
102
103        ClipBinding { bindings }
104    }
105
106    /// Convenience: builds a rig *and* a clip binding in one call.
107    ///
108    /// Equivalent to calling [`build_rig`](Self::build_rig) followed by
109    /// [`build_clip_binding`](Self::build_clip_binding).
110    pub fn bind(
111        target: &mut dyn AnimationTarget,
112        root_node: NodeHandle,
113        clip: &AnimationClip,
114    ) -> (Rig, ClipBinding) {
115        let rig = Self::build_rig(target, root_node);
116        let binding = Self::build_clip_binding(target, &rig, clip);
117        (rig, binding)
118    }
119}