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}