oxihuman_export/
blend_tree_node_export.rs1#![allow(dead_code)]
4
5#[allow(dead_code)]
9#[derive(Debug, Clone, PartialEq, Eq)]
10pub enum BlendNodeType {
11 Clip,
12 Blend1D,
13 Blend2D,
14 StateMachine,
15}
16
17#[allow(dead_code)]
19#[derive(Debug, Clone)]
20pub struct BlendTreeNode {
21 pub id: u32,
22 pub name: String,
23 pub node_type: BlendNodeType,
24 pub children: Vec<u32>,
25 pub weight: f32,
26}
27
28#[allow(dead_code)]
30#[derive(Debug, Clone)]
31pub struct BlendTreeExport {
32 pub nodes: Vec<BlendTreeNode>,
33 pub root_id: Option<u32>,
34}
35
36#[allow(dead_code)]
38pub fn new_blend_tree_export() -> BlendTreeExport {
39 BlendTreeExport {
40 nodes: Vec::new(),
41 root_id: None,
42 }
43}
44
45#[allow(dead_code)]
47pub fn add_blend_node(exp: &mut BlendTreeExport, node: BlendTreeNode) {
48 exp.nodes.push(node);
49}
50
51#[allow(dead_code)]
53pub fn set_blend_root(exp: &mut BlendTreeExport, id: u32) {
54 exp.root_id = Some(id);
55}
56
57#[allow(dead_code)]
59pub fn blend_node_count(exp: &BlendTreeExport) -> usize {
60 exp.nodes.len()
61}
62
63#[allow(dead_code)]
65pub fn find_blend_node(exp: &BlendTreeExport, id: u32) -> Option<&BlendTreeNode> {
66 exp.nodes.iter().find(|n| n.id == id)
67}
68
69#[allow(dead_code)]
71pub fn total_connections(exp: &BlendTreeExport) -> usize {
72 exp.nodes.iter().map(|n| n.children.len()).sum()
73}
74
75#[allow(dead_code)]
77pub fn nodes_of_type<'a>(exp: &'a BlendTreeExport, t: &BlendNodeType) -> Vec<&'a BlendTreeNode> {
78 exp.nodes.iter().filter(|n| &n.node_type == t).collect()
79}
80
81#[allow(dead_code)]
83pub fn blend_tree_to_json(exp: &BlendTreeExport) -> String {
84 format!(
85 "{{\"node_count\":{},\"root_id\":{}}}",
86 blend_node_count(exp),
87 exp.root_id.map_or("null".to_string(), |id| id.to_string())
88 )
89}
90
91#[allow(dead_code)]
93pub fn validate_blend_tree(exp: &BlendTreeExport) -> bool {
94 let ids: std::collections::HashSet<u32> = exp.nodes.iter().map(|n| n.id).collect();
95 exp.nodes
96 .iter()
97 .all(|n| n.children.iter().all(|&c| ids.contains(&c)))
98}
99
100#[cfg(test)]
101mod tests {
102 use super::*;
103
104 fn clip_node(id: u32, name: &str) -> BlendTreeNode {
105 BlendTreeNode {
106 id,
107 name: name.to_string(),
108 node_type: BlendNodeType::Clip,
109 children: Vec::new(),
110 weight: 1.0,
111 }
112 }
113
114 #[test]
115 fn new_export_empty() {
116 let exp = new_blend_tree_export();
117 assert_eq!(blend_node_count(&exp), 0);
118 }
119
120 #[test]
121 fn add_node_increments() {
122 let mut exp = new_blend_tree_export();
123 add_blend_node(&mut exp, clip_node(0, "idle"));
124 assert_eq!(blend_node_count(&exp), 1);
125 }
126
127 #[test]
128 fn find_existing() {
129 let mut exp = new_blend_tree_export();
130 add_blend_node(&mut exp, clip_node(5, "run"));
131 assert!(find_blend_node(&exp, 5).is_some());
132 }
133
134 #[test]
135 fn find_missing_none() {
136 let exp = new_blend_tree_export();
137 assert!(find_blend_node(&exp, 99).is_none());
138 }
139
140 #[test]
141 fn set_root() {
142 let mut exp = new_blend_tree_export();
143 add_blend_node(&mut exp, clip_node(0, "root"));
144 set_blend_root(&mut exp, 0);
145 assert!(exp.root_id.is_some_and(|id| id == 0));
146 }
147
148 #[test]
149 fn total_connections_none() {
150 let mut exp = new_blend_tree_export();
151 add_blend_node(&mut exp, clip_node(0, "a"));
152 assert_eq!(total_connections(&exp), 0);
153 }
154
155 #[test]
156 fn nodes_of_type_filter() {
157 let mut exp = new_blend_tree_export();
158 add_blend_node(&mut exp, clip_node(0, "a"));
159 add_blend_node(
160 &mut exp,
161 BlendTreeNode {
162 id: 1,
163 name: "b1d".to_string(),
164 node_type: BlendNodeType::Blend1D,
165 children: vec![0],
166 weight: 0.5,
167 },
168 );
169 assert_eq!(nodes_of_type(&exp, &BlendNodeType::Clip).len(), 1);
170 }
171
172 #[test]
173 fn validate_no_dangling_children() {
174 let mut exp = new_blend_tree_export();
175 add_blend_node(&mut exp, clip_node(0, "a"));
176 add_blend_node(&mut exp, clip_node(1, "b"));
177 assert!(validate_blend_tree(&exp));
178 }
179
180 #[test]
181 fn json_contains_node_count() {
182 let exp = new_blend_tree_export();
183 let j = blend_tree_to_json(&exp);
184 assert!(j.contains("node_count"));
185 }
186
187 #[test]
188 fn weight_in_range() {
189 let n = clip_node(0, "t");
190 assert!((0.0..=1.0).contains(&n.weight));
191 }
192}