Skip to main content

pfx_load/
rig.rs

1use pfx_core::anim::{Channel, Clip, Joint, MAX_JOINTS, Rig, Skeleton, Trs};
2
3use crate::mesh::Mesh;
4
5#[derive(Clone, Debug, PartialEq)]
6pub struct SkinRig {
7    pub rig: Rig,
8    pub remap: Vec<u16>,
9}
10
11impl SkinRig {
12    pub fn joints(&self, raw: &[[u16; 4]], weights: &[[f32; 4]]) -> Result<Vec<[u16; 4]>, String> {
13        raw.iter()
14            .zip(weights)
15            .map(|(joints, weights)| {
16                let mut out = [0u16; 4];
17                for slot in 0..4 {
18                    if weights[slot] == 0.0 {
19                        continue;
20                    }
21                    out[slot] = *self.remap.get(joints[slot] as usize).ok_or_else(|| {
22                        format!(
23                            "a vertex names joint {} of a skin of {}",
24                            joints[slot],
25                            self.remap.len()
26                        )
27                    })?;
28                }
29                Ok(out)
30            })
31            .collect()
32    }
33}
34
35impl Mesh {
36    pub fn rig(&self, skin: u32) -> Result<SkinRig, String> {
37        let found = self
38            .skins
39            .get(skin as usize)
40            .ok_or_else(|| format!("mesh: no skin {skin}"))?;
41        if found.joints.is_empty() {
42            return Err(format!("mesh: skin {skin} has no joints"));
43        }
44        if found.joints.len() > MAX_JOINTS {
45            return Err(format!(
46                "mesh: skin {skin} has {} joints, over {MAX_JOINTS}",
47                found.joints.len()
48            ));
49        }
50        let depth = |node: u32| {
51            let mut depth = 0usize;
52            let mut at = self.nodes[node as usize].parent;
53            while let Some(parent) = at {
54                depth += 1;
55                at = self.nodes[parent as usize].parent;
56                if depth > self.nodes.len() {
57                    break;
58                }
59            }
60            depth
61        };
62        let mut order: Vec<usize> = (0..found.joints.len()).collect();
63        order.sort_by_key(|&slot| (depth(found.joints[slot]), slot));
64        let mut remap = vec![0u16; found.joints.len()];
65        for (new, &old) in order.iter().enumerate() {
66            remap[old] = new as u16;
67        }
68        let index_of = |node: u32| found.joints.iter().position(|&joint| joint == node);
69        let mut root_parent: Option<Option<u32>> = None;
70        let mut joints = Vec::with_capacity(order.len());
71        let mut binds = Vec::with_capacity(order.len());
72        for &old in &order {
73            let node_index = found.joints[old];
74            let node = &self.nodes[node_index as usize];
75            let mut parent = None;
76            let mut at = node.parent;
77            while let Some(up) = at {
78                if let Some(slot) = index_of(up) {
79                    parent = Some(remap[slot] as usize);
80                    break;
81                }
82                at = self.nodes[up as usize].parent;
83            }
84            if parent.is_none() {
85                match root_parent {
86                    None => root_parent = Some(node.parent),
87                    Some(other) if other != node.parent => {
88                        return Err(format!(
89                            "mesh: skin {skin} has root joints under different nodes"
90                        ));
91                    }
92                    Some(_) => {}
93                }
94            }
95            joints.push(Joint {
96                name: if node.name.is_empty() {
97                    format!("joint{node_index}")
98                } else {
99                    node.name.clone()
100                },
101                parent,
102                rest: node.trs,
103            });
104            binds.push(found.inverse_binds[old]);
105        }
106        let root = root_parent
107            .flatten()
108            .and_then(|parent| self.world(parent))
109            .unwrap_or(pfx_core::anim::math::IDENTITY);
110        let skeleton = Skeleton::rooted(joints, binds, root)
111            .map_err(|error| format!("mesh: skin {skin}: {error}"))?;
112        let clips = self.clips(|node| index_of(node).map(|slot| remap[slot] as usize))?;
113        let rig = Rig::new(skeleton, clips).map_err(|error| format!("mesh: {error}"))?;
114        Ok(SkinRig { rig, remap })
115    }
116}
117
118impl Mesh {
119    fn clips(&self, joint_of: impl Fn(u32) -> Option<usize>) -> Result<Vec<Clip>, String> {
120        let mut clips = Vec::new();
121        for animation in &self.animations {
122            let channels: Vec<Channel> = animation
123                .channels
124                .iter()
125                .filter_map(|channel| {
126                    Some(Channel {
127                        joint: joint_of(channel.node)?,
128                        property: channel.property,
129                        interpolation: channel.interpolation,
130                        times: channel.times.clone(),
131                        values: channel.values.clone(),
132                    })
133                })
134                .collect();
135            if channels.is_empty() {
136                continue;
137            }
138            clips.push(
139                Clip::new(animation.name.clone(), channels)
140                    .map_err(|error| format!("mesh: {error}"))?,
141            );
142        }
143        Ok(clips)
144    }
145}
146
147impl Mesh {
148    pub fn node_rig(&self, walked: &[(u32, bool)]) -> Result<Option<Rig>, String> {
149        if self.animations.is_empty() || walked.is_empty() {
150            return Ok(None);
151        }
152        if walked.len() > MAX_JOINTS {
153            return Err(format!(
154                "mesh: {} animated nodes, over {MAX_JOINTS}",
155                walked.len()
156            ));
157        }
158        let joint_of = |node: u32| {
159            walked
160                .iter()
161                .position(|(walked, own)| *walked == node && *own)
162        };
163        let joints: Vec<Joint> = walked
164            .iter()
165            .enumerate()
166            .map(|(index, &(node, own))| {
167                let found = &self.nodes[node as usize];
168                let parent = if own {
169                    found
170                        .parent
171                        .and_then(|parent| walked[..index].iter().position(|(n, _)| *n == parent))
172                } else {
173                    None
174                };
175                Joint {
176                    name: if found.name.is_empty() {
177                        format!("node{node}")
178                    } else {
179                        found.name.clone()
180                    },
181                    parent,
182                    rest: if own { found.trs } else { Trs::IDENTITY },
183                }
184            })
185            .collect();
186        let binds = vec![pfx_core::anim::math::IDENTITY; joints.len()];
187        let skeleton = Skeleton::new(joints, binds).map_err(|error| format!("mesh: {error}"))?;
188        let clips = self.clips(joint_of)?;
189        if clips.is_empty() {
190            return Ok(None);
191        }
192        Rig::new(skeleton, clips)
193            .map(Some)
194            .map_err(|error| format!("mesh: {error}"))
195    }
196}