1#[allow(dead_code)]
5#[derive(Clone)]
6pub enum BlendOp {
7 Add,
8 Multiply,
9 Override,
10 Screen,
11}
12
13#[allow(dead_code)]
14#[derive(Clone)]
15pub struct BlendNode {
16 pub id: u32,
17 pub name: String,
18 pub weight: f32,
19 pub op: BlendOp,
20 pub inputs: Vec<u32>,
21 pub target_name: Option<String>,
22}
23
24#[allow(dead_code)]
25pub struct BlendGraph {
26 pub nodes: Vec<BlendNode>,
27 pub roots: Vec<u32>,
28 pub next_id: u32,
29}
30
31#[allow(dead_code)]
32pub struct EvalResult {
33 pub target_weights: Vec<(String, f32)>,
34}
35
36#[allow(dead_code)]
37pub fn new_blend_graph() -> BlendGraph {
38 BlendGraph {
39 nodes: Vec::new(),
40 roots: Vec::new(),
41 next_id: 0,
42 }
43}
44
45#[allow(dead_code)]
46pub fn add_blend_node(graph: &mut BlendGraph, name: &str, op: BlendOp, weight: f32) -> u32 {
47 let id = graph.next_id;
48 graph.next_id += 1;
49 graph.nodes.push(BlendNode {
50 id,
51 name: name.to_string(),
52 weight,
53 op,
54 inputs: Vec::new(),
55 target_name: None,
56 });
57 id
58}
59
60#[allow(dead_code)]
61pub fn add_leaf_node(graph: &mut BlendGraph, target: &str, weight: f32) -> u32 {
62 let id = graph.next_id;
63 graph.next_id += 1;
64 graph.nodes.push(BlendNode {
65 id,
66 name: target.to_string(),
67 weight,
68 op: BlendOp::Add,
69 inputs: Vec::new(),
70 target_name: Some(target.to_string()),
71 });
72 id
73}
74
75#[allow(dead_code)]
76pub fn connect_nodes(graph: &mut BlendGraph, parent: u32, child: u32) {
77 if let Some(node) = graph.nodes.iter_mut().find(|n| n.id == parent) {
78 if !node.inputs.contains(&child) {
79 node.inputs.push(child);
80 }
81 }
82}
83
84#[allow(dead_code)]
85pub fn add_root(graph: &mut BlendGraph, node_id: u32) {
86 if !graph.roots.contains(&node_id) {
87 graph.roots.push(node_id);
88 }
89}
90
91fn eval_node(
93 graph: &BlendGraph,
94 node_id: u32,
95 parent_weight: f32,
96 results: &mut Vec<(String, f32)>,
97 visited: &mut Vec<u32>,
98) {
99 if visited.contains(&node_id) {
100 return;
101 }
102 visited.push(node_id);
103
104 let node = match graph.nodes.iter().find(|n| n.id == node_id) {
105 Some(n) => n.clone(),
106 None => return,
107 };
108
109 let effective_weight = match node.op {
110 BlendOp::Add => parent_weight + node.weight,
111 BlendOp::Multiply => parent_weight * node.weight,
112 BlendOp::Override => node.weight,
113 BlendOp::Screen => 1.0 - (1.0 - parent_weight) * (1.0 - node.weight),
114 };
115
116 if let Some(ref target) = node.target_name {
117 if let Some(entry) = results.iter_mut().find(|(name, _)| name == target) {
119 entry.1 += effective_weight;
120 } else {
121 results.push((target.clone(), effective_weight));
122 }
123 }
124
125 for child_id in &node.inputs {
126 eval_node(graph, *child_id, effective_weight, results, visited);
127 }
128}
129
130#[allow(dead_code)]
132pub fn evaluate_graph(graph: &BlendGraph) -> EvalResult {
133 let mut results: Vec<(String, f32)> = Vec::new();
134 let mut visited = Vec::new();
135
136 for &root_id in &graph.roots {
137 eval_node(graph, root_id, 1.0, &mut results, &mut visited);
138 }
139
140 EvalResult {
141 target_weights: results,
142 }
143}
144
145#[allow(dead_code)]
146pub fn set_node_weight(graph: &mut BlendGraph, id: u32, weight: f32) {
147 if let Some(node) = graph.nodes.iter_mut().find(|n| n.id == id) {
148 node.weight = weight;
149 }
150}
151
152#[allow(dead_code)]
153pub fn get_node(graph: &BlendGraph, id: u32) -> Option<&BlendNode> {
154 graph.nodes.iter().find(|n| n.id == id)
155}
156
157#[allow(dead_code)]
158pub fn node_count(graph: &BlendGraph) -> usize {
159 graph.nodes.len()
160}
161
162#[allow(dead_code)]
163pub fn leaf_nodes(graph: &BlendGraph) -> Vec<&BlendNode> {
164 graph
165 .nodes
166 .iter()
167 .filter(|n| n.target_name.is_some())
168 .collect()
169}
170
171fn topo_visit(graph: &BlendGraph, node_id: u32, visited: &mut Vec<u32>, order: &mut Vec<u32>) {
173 if visited.contains(&node_id) {
174 return;
175 }
176 visited.push(node_id);
177 if let Some(node) = graph.nodes.iter().find(|n| n.id == node_id) {
178 for &child in &node.inputs {
179 topo_visit(graph, child, visited, order);
180 }
181 }
182 order.push(node_id);
183}
184
185#[allow(dead_code)]
187pub fn topological_sort_graph(graph: &BlendGraph) -> Vec<u32> {
188 let mut visited = Vec::new();
189 let mut order = Vec::new();
190
191 for &root in &graph.roots {
192 topo_visit(graph, root, &mut visited, &mut order);
193 }
194 for node in &graph.nodes {
196 if !visited.contains(&node.id) {
197 topo_visit(graph, node.id, &mut visited, &mut order);
198 }
199 }
200
201 order.reverse();
202 order
203}
204
205#[allow(dead_code)]
207pub fn prune_zero_weight(graph: &mut BlendGraph) {
208 graph
210 .nodes
211 .retain(|n| !n.inputs.is_empty() || n.weight.abs() >= 1e-6);
212}
213
214#[allow(dead_code)]
215pub fn blend_graph_to_json(graph: &BlendGraph) -> String {
216 let mut parts = Vec::new();
217 for node in &graph.nodes {
218 let op_str = match node.op {
219 BlendOp::Add => "Add",
220 BlendOp::Multiply => "Multiply",
221 BlendOp::Override => "Override",
222 BlendOp::Screen => "Screen",
223 };
224 let target_str = node
225 .target_name
226 .as_deref()
227 .map(|s| format!("\"{}\"", s))
228 .unwrap_or_else(|| "null".to_string());
229 let inputs_str = node
230 .inputs
231 .iter()
232 .map(|id| id.to_string())
233 .collect::<Vec<_>>()
234 .join(",");
235 parts.push(format!(
236 "{{\"id\":{},\"name\":\"{}\",\"weight\":{},\"op\":\"{}\",\"target\":{},\"inputs\":[{}]}}",
237 node.id, node.name, node.weight, op_str, target_str, inputs_str
238 ));
239 }
240 let roots_str = graph
241 .roots
242 .iter()
243 .map(|id| id.to_string())
244 .collect::<Vec<_>>()
245 .join(",");
246 format!(
247 "{{\"nodes\":[{}],\"roots\":[{}]}}",
248 parts.join(","),
249 roots_str
250 )
251}
252
253#[cfg(test)]
254mod tests {
255 use super::*;
256
257 #[test]
258 fn test_new_blend_graph() {
259 let g = new_blend_graph();
260 assert!(g.nodes.is_empty());
261 assert!(g.roots.is_empty());
262 assert_eq!(g.next_id, 0);
263 }
264
265 #[test]
266 fn test_add_blend_node() {
267 let mut g = new_blend_graph();
268 let id = add_blend_node(&mut g, "parent", BlendOp::Add, 0.8);
269 assert_eq!(id, 0);
270 assert_eq!(g.nodes.len(), 1);
271 assert_eq!(g.nodes[0].name, "parent");
272 }
273
274 #[test]
275 fn test_add_leaf_node() {
276 let mut g = new_blend_graph();
277 let id = add_leaf_node(&mut g, "smile", 0.5);
278 assert_eq!(id, 0);
279 assert!(g.nodes[0].target_name.is_some());
280 assert_eq!(g.nodes[0].target_name.as_deref(), Some("smile"));
281 }
282
283 #[test]
284 fn test_connect_nodes() {
285 let mut g = new_blend_graph();
286 let p = add_blend_node(&mut g, "parent", BlendOp::Add, 1.0);
287 let c = add_leaf_node(&mut g, "child_target", 0.5);
288 connect_nodes(&mut g, p, c);
289 assert!(g.nodes[0].inputs.contains(&c));
290 }
291
292 #[test]
293 fn test_add_root() {
294 let mut g = new_blend_graph();
295 let id = add_blend_node(&mut g, "root", BlendOp::Add, 1.0);
296 add_root(&mut g, id);
297 assert!(g.roots.contains(&id));
298 add_root(&mut g, id);
300 assert_eq!(g.roots.len(), 1);
301 }
302
303 #[test]
304 fn test_evaluate_single_leaf() {
305 let mut g = new_blend_graph();
306 let leaf = add_leaf_node(&mut g, "brow_raise", 0.7);
307 add_root(&mut g, leaf);
308 let result = evaluate_graph(&g);
309 assert_eq!(result.target_weights.len(), 1);
310 let (name, w) = &result.target_weights[0];
311 assert_eq!(name, "brow_raise");
312 assert!(*w > 0.0);
314 }
315
316 #[test]
317 fn test_evaluate_tree_add() {
318 let mut g = new_blend_graph();
319 let parent = add_blend_node(&mut g, "group", BlendOp::Multiply, 0.5);
320 let leaf = add_leaf_node(&mut g, "jaw_open", 1.0);
321 connect_nodes(&mut g, parent, leaf);
322 add_root(&mut g, parent);
323 let result = evaluate_graph(&g);
324 assert!(!result.target_weights.is_empty());
325 }
326
327 #[test]
328 fn test_set_node_weight() {
329 let mut g = new_blend_graph();
330 let id = add_blend_node(&mut g, "n", BlendOp::Add, 0.0);
331 set_node_weight(&mut g, id, 0.9);
332 assert!((get_node(&g, id).expect("should succeed").weight - 0.9).abs() < 1e-6);
333 }
334
335 #[test]
336 fn test_get_node() {
337 let mut g = new_blend_graph();
338 let id = add_leaf_node(&mut g, "target", 0.3);
339 let node = get_node(&g, id);
340 assert!(node.is_some());
341 assert_eq!(node.expect("should succeed").id, id);
342 }
343
344 #[test]
345 fn test_node_count() {
346 let mut g = new_blend_graph();
347 assert_eq!(node_count(&g), 0);
348 add_blend_node(&mut g, "a", BlendOp::Add, 1.0);
349 add_leaf_node(&mut g, "b", 0.5);
350 assert_eq!(node_count(&g), 2);
351 }
352
353 #[test]
354 fn test_leaf_nodes() {
355 let mut g = new_blend_graph();
356 add_blend_node(&mut g, "group", BlendOp::Add, 1.0);
357 add_leaf_node(&mut g, "target_a", 0.5);
358 add_leaf_node(&mut g, "target_b", 0.3);
359 let leaves = leaf_nodes(&g);
360 assert_eq!(leaves.len(), 2);
361 }
362
363 #[test]
364 fn test_topological_sort() {
365 let mut g = new_blend_graph();
366 let p = add_blend_node(&mut g, "root", BlendOp::Add, 1.0);
367 let c1 = add_leaf_node(&mut g, "t1", 0.5);
368 let c2 = add_leaf_node(&mut g, "t2", 0.5);
369 connect_nodes(&mut g, p, c1);
370 connect_nodes(&mut g, p, c2);
371 add_root(&mut g, p);
372 let order = topological_sort_graph(&g);
373 assert_eq!(order.len(), 3);
374 let root_pos = order
376 .iter()
377 .position(|&id| id == p)
378 .expect("should succeed");
379 let c1_pos = order
380 .iter()
381 .position(|&id| id == c1)
382 .expect("should succeed");
383 assert!(root_pos < c1_pos);
384 }
385
386 #[test]
387 fn test_prune_zero_weight() {
388 let mut g = new_blend_graph();
389 add_leaf_node(&mut g, "zero_target", 0.0);
390 let _keep = add_leaf_node(&mut g, "keep_target", 0.5);
391 prune_zero_weight(&mut g);
392 assert_eq!(g.nodes.len(), 1);
394 assert_eq!(g.nodes[0].name, "keep_target");
395 }
396
397 #[test]
398 fn test_blend_graph_to_json() {
399 let mut g = new_blend_graph();
400 let leaf = add_leaf_node(&mut g, "smile", 0.8);
401 add_root(&mut g, leaf);
402 let json = blend_graph_to_json(&g);
403 assert!(json.contains("smile"));
404 assert!(json.contains("nodes"));
405 assert!(json.contains("roots"));
406 }
407}