Skip to main content

avatar_anim/
skeleton.rs

1use crate::{AnimError, Result};
2use glam::Vec3;
3use std::collections::BTreeMap;
4use std::fs::File;
5use std::io::{BufReader, Read};
6use std::path::Path;
7use xml::reader::{EventReader, XmlEvent};
8
9#[derive(Clone, Debug, PartialEq)]
10pub struct SkeletonBone {
11    pub name: String,
12    pub pos: Vec3,
13    pub parent: Option<String>,
14    pub attributes: BTreeMap<String, String>,
15}
16
17#[derive(Clone, Debug, Default, PartialEq)]
18pub struct SkeletonDefinition {
19    pub bones: Vec<SkeletonBone>,
20}
21
22impl SkeletonDefinition {
23    pub fn from_xml_file<P: AsRef<Path>>(path: P) -> Result<Self> {
24        let file = File::open(path).map_err(AnimError::Io)?;
25        Self::from_xml_reader(BufReader::new(file))
26    }
27
28    pub fn from_xml_reader<R: Read>(reader: R) -> Result<Self> {
29        let parser = EventReader::new(reader);
30        let mut bones = Vec::new();
31        let mut parent_stack = Vec::<Option<String>>::new();
32
33        for event in parser {
34            let event = event.map_err(|e| {
35                AnimError::InvalidStructure(format!("Skeleton XML parse error: {e}"))
36            })?;
37            match event {
38                XmlEvent::StartElement {
39                    name, attributes, ..
40                } if name.local_name == "bone" => {
41                    let parent = parent_stack.iter().rev().find_map(Clone::clone);
42                    let mut attrs = BTreeMap::new();
43                    let mut bone_name = None;
44                    let mut pos = None;
45                    for attr in attributes {
46                        let attr_name = attr.name.local_name;
47                        if attr_name == "name" {
48                            bone_name = Some(attr.value.clone());
49                        } else if attr_name == "pos" || attr_name == "position" {
50                            pos = Some(parse_vec3(&attr.value)?);
51                        }
52                        attrs.insert(attr_name, attr.value);
53                    }
54
55                    parent_stack.push(bone_name.clone());
56                    if let Some(name) = bone_name
57                        && let Some(pos) = pos
58                    {
59                        bones.push(SkeletonBone {
60                            name,
61                            pos,
62                            parent,
63                            attributes: attrs,
64                        });
65                    }
66                }
67                XmlEvent::EndElement { name } if name.local_name == "bone" => {
68                    parent_stack.pop();
69                }
70                _ => {}
71            }
72        }
73
74        Ok(Self { bones })
75    }
76
77    pub fn bone(&self, name: &str) -> Option<&SkeletonBone> {
78        self.bones.iter().find(|bone| bone.name == name)
79    }
80
81    pub fn position(&self, name: &str) -> Option<Vec3> {
82        self.bone(name).map(|bone| bone.pos)
83    }
84
85    pub fn bones_with_prefix(&self, prefix: &str) -> Vec<&SkeletonBone> {
86        self.bones
87            .iter()
88            .filter(|bone| bone.name.starts_with(prefix))
89            .collect()
90    }
91
92    pub fn bones_in_group(&self, group: &str) -> Vec<&SkeletonBone> {
93        self.bones
94            .iter()
95            .filter(|bone| bone.attributes.get("group").is_some_and(|g| g == group))
96            .collect()
97    }
98}
99
100fn parse_vec3(value: &str) -> Result<Vec3> {
101    let mut parts = value.split_whitespace();
102    let x = parse_component(parts.next(), value)?;
103    let y = parse_component(parts.next(), value)?;
104    let z = parse_component(parts.next(), value)?;
105    if parts.next().is_some() {
106        return Err(AnimError::InvalidStructure(format!(
107            "Expected 3-vector, got '{value}'"
108        )));
109    }
110    Ok(Vec3::new(x, y, z))
111}
112
113fn parse_component(component: Option<&str>, original: &str) -> Result<f32> {
114    component
115        .ok_or_else(|| AnimError::InvalidStructure(format!("Expected 3-vector, got '{original}'")))?
116        .parse::<f32>()
117        .map_err(|_| {
118            AnimError::InvalidStructure(format!("Invalid vector component in '{original}'"))
119        })
120}