Skip to main content

oxihuman_morph/
emotion_blend_tree.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Emotion node blend tree stub.
6
7/// Blend operation for combining child nodes.
8#[derive(Debug, Clone, Copy, PartialEq)]
9pub enum BlendOp {
10    Add,
11    Multiply,
12    Override,
13}
14
15/// A node in the emotion blend tree.
16#[derive(Debug, Clone)]
17pub struct EmotionNode {
18    pub name: String,
19    pub weight: f32,
20    pub children: Vec<usize>,
21    pub blend_op: BlendOp,
22}
23
24/// Emotion blend tree.
25#[derive(Debug, Clone)]
26pub struct EmotionBlendTree {
27    pub nodes: Vec<EmotionNode>,
28    pub root: Option<usize>,
29    pub output_dim: usize,
30    pub enabled: bool,
31}
32
33impl EmotionBlendTree {
34    pub fn new(output_dim: usize) -> Self {
35        EmotionBlendTree {
36            nodes: Vec::new(),
37            root: None,
38            output_dim,
39            enabled: true,
40        }
41    }
42}
43
44/// Create a new emotion blend tree.
45pub fn new_emotion_blend_tree(output_dim: usize) -> EmotionBlendTree {
46    EmotionBlendTree::new(output_dim)
47}
48
49/// Add a node and return its index.
50pub fn ebt_add_node(tree: &mut EmotionBlendTree, node: EmotionNode) -> usize {
51    let idx = tree.nodes.len();
52    tree.nodes.push(node);
53    idx
54}
55
56/// Set the root node index.
57pub fn ebt_set_root(tree: &mut EmotionBlendTree, root: usize) {
58    tree.root = Some(root);
59}
60
61/// Evaluate the tree to produce output weights (stub: zeroed).
62pub fn ebt_evaluate(tree: &EmotionBlendTree) -> Vec<f32> {
63    /* Stub: returns zeroed output */
64    vec![0.0; tree.output_dim]
65}
66
67/// Return node count.
68pub fn ebt_node_count(tree: &EmotionBlendTree) -> usize {
69    tree.nodes.len()
70}
71
72/// Enable or disable the tree.
73pub fn ebt_set_enabled(tree: &mut EmotionBlendTree, enabled: bool) {
74    tree.enabled = enabled;
75}
76
77/// Serialize to JSON-like string.
78pub fn ebt_to_json(tree: &EmotionBlendTree) -> String {
79    format!(
80        r#"{{"node_count":{},"output_dim":{},"has_root":{},"enabled":{}}}"#,
81        tree.nodes.len(),
82        tree.output_dim,
83        tree.root.is_some(),
84        tree.enabled
85    )
86}
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91
92    #[test]
93    fn test_new_output_dim() {
94        let t = new_emotion_blend_tree(10);
95        assert_eq!(t.output_dim, 10 /* output_dim must match */,);
96    }
97
98    #[test]
99    fn test_no_nodes_initially() {
100        let t = new_emotion_blend_tree(5);
101        assert_eq!(ebt_node_count(&t), 0 /* no nodes initially */,);
102    }
103
104    #[test]
105    fn test_add_node_returns_index() {
106        let mut t = new_emotion_blend_tree(5);
107        let idx = ebt_add_node(
108            &mut t,
109            EmotionNode {
110                name: "joy".into(),
111                weight: 1.0,
112                children: vec![],
113                blend_op: BlendOp::Add,
114            },
115        );
116        assert_eq!(idx, 0 /* first node must have index 0 */,);
117    }
118
119    #[test]
120    fn test_set_root() {
121        let mut t = new_emotion_blend_tree(5);
122        let idx = ebt_add_node(
123            &mut t,
124            EmotionNode {
125                name: "root".into(),
126                weight: 1.0,
127                children: vec![],
128                blend_op: BlendOp::Add,
129            },
130        );
131        ebt_set_root(&mut t, idx);
132        assert_eq!(t.root, Some(0) /* root must be set */,);
133    }
134
135    #[test]
136    fn test_evaluate_output_length() {
137        let t = new_emotion_blend_tree(8);
138        let out = ebt_evaluate(&t);
139        assert_eq!(out.len(), 8 /* output length must match output_dim */,);
140    }
141
142    #[test]
143    fn test_evaluate_zeroed() {
144        let t = new_emotion_blend_tree(3);
145        let out = ebt_evaluate(&t);
146        assert!(out.iter().all(|&v| v.abs() < 1e-6), /* stub must return zeros */);
147    }
148
149    #[test]
150    fn test_set_enabled() {
151        let mut t = new_emotion_blend_tree(3);
152        ebt_set_enabled(&mut t, false);
153        assert!(!t.enabled /* must be disabled */,);
154    }
155
156    #[test]
157    fn test_to_json_contains_node_count() {
158        let t = new_emotion_blend_tree(4);
159        let j = ebt_to_json(&t);
160        assert!(j.contains("\"node_count\""), /* json must contain node_count */);
161    }
162
163    #[test]
164    fn test_enabled_default() {
165        let t = new_emotion_blend_tree(1);
166        assert!(t.enabled /* must be enabled by default */,);
167    }
168
169    #[test]
170    fn test_no_root_initially() {
171        let t = new_emotion_blend_tree(2);
172        assert!(t.root.is_none() /* root must be None initially */,);
173    }
174}