1use std::collections::HashMap;
7
8#[allow(dead_code)]
10#[derive(Debug, Clone)]
11pub struct ExpressionComposerLayer {
12 pub name: String,
13 pub weight: f32,
14 pub morphs: HashMap<String, f32>,
16}
17
18#[allow(dead_code)]
20#[derive(Debug, Clone)]
21pub struct ComposedExpression {
22 pub weights: HashMap<String, f32>,
24 pub layer_count: usize,
26}
27
28#[allow(dead_code)]
30#[derive(Debug, Clone)]
31pub struct ExpressionComposerConfig {
32 pub auto_normalize: bool,
34 pub max_layers: usize,
36}
37
38#[allow(dead_code)]
40#[derive(Debug, Clone)]
41pub struct ExpressionComposer {
42 pub config: ExpressionComposerConfig,
43 pub layers: Vec<ExpressionComposerLayer>,
44}
45
46#[allow(dead_code)]
52pub fn default_composer_config() -> ExpressionComposerConfig {
53 ExpressionComposerConfig {
54 auto_normalize: false,
55 max_layers: 32,
56 }
57}
58
59#[allow(dead_code)]
61pub fn new_composed_expression() -> ComposedExpression {
62 ComposedExpression {
63 weights: HashMap::new(),
64 layer_count: 0,
65 }
66}
67
68#[allow(dead_code)]
70pub fn new_expression_composer(config: ExpressionComposerConfig) -> ExpressionComposer {
71 ExpressionComposer {
72 config,
73 layers: Vec::new(),
74 }
75}
76
77#[allow(dead_code)]
80pub fn add_layer(
81 composer: &mut ExpressionComposer,
82 name: &str,
83 morphs: HashMap<String, f32>,
84) -> bool {
85 if composer.layers.len() >= composer.config.max_layers {
86 return false;
87 }
88 composer.layers.push(ExpressionComposerLayer {
89 name: name.to_string(),
90 weight: 1.0,
91 morphs,
92 });
93 true
94}
95
96#[allow(dead_code)]
98pub fn remove_layer(composer: &mut ExpressionComposer, name: &str) -> bool {
99 let before = composer.layers.len();
100 composer.layers.retain(|l| l.name != name);
101 composer.layers.len() < before
102}
103
104#[allow(dead_code)]
106pub fn blend_layers(composer: &ExpressionComposer) -> ComposedExpression {
107 let mut weights: HashMap<String, f32> = HashMap::new();
108 let weight_sum: f32 = composer.layers.iter().map(|l| l.weight.abs()).sum();
109 for layer in &composer.layers {
110 let layer_w = if weight_sum > 0.0 {
111 layer.weight / weight_sum
112 } else {
113 0.0
114 };
115 for (morph, &mv) in &layer.morphs {
116 let entry = weights.entry(morph.clone()).or_insert(0.0);
117 *entry += mv * layer_w;
118 }
119 }
120 ComposedExpression {
121 layer_count: composer.layers.len(),
122 weights,
123 }
124}
125
126#[allow(dead_code)]
128pub fn evaluate_expression(composer: &ExpressionComposer) -> ComposedExpression {
129 let mut expr = blend_layers(composer);
130 if composer.config.auto_normalize {
131 normalize_expression(&mut expr);
132 }
133 expr
134}
135
136#[allow(dead_code)]
138pub fn layer_count(composer: &ExpressionComposer) -> usize {
139 composer.layers.len()
140}
141
142#[allow(dead_code)]
144pub fn set_layer_weight(composer: &mut ExpressionComposer, name: &str, weight: f32) -> bool {
145 for layer in &mut composer.layers {
146 if layer.name == name {
147 layer.weight = weight.clamp(0.0, 1.0);
148 return true;
149 }
150 }
151 false
152}
153
154#[allow(dead_code)]
156pub fn get_layer_weight(composer: &ExpressionComposer, name: &str) -> Option<f32> {
157 composer
158 .layers
159 .iter()
160 .find(|l| l.name == name)
161 .map(|l| l.weight)
162}
163
164#[allow(dead_code)]
166pub fn expression_to_json(expr: &ComposedExpression) -> String {
167 let mut pairs: Vec<String> = expr
168 .weights
169 .iter()
170 .map(|(k, v)| format!(" \"{k}\": {v:.4}"))
171 .collect();
172 pairs.sort();
173 format!("{{\n{}\n}}", pairs.join(",\n"))
174}
175
176#[allow(dead_code)]
178pub fn reset_expression(composer: &mut ExpressionComposer) {
179 composer.layers.clear();
180}
181
182#[allow(dead_code)]
184pub fn add_preset_layer(
185 composer: &mut ExpressionComposer,
186 name: &str,
187 presets: &[(&str, f32)],
188) -> bool {
189 let morphs: HashMap<String, f32> = presets.iter().map(|(k, v)| (k.to_string(), *v)).collect();
190 add_layer(composer, name, morphs)
191}
192
193#[allow(dead_code)]
195pub fn expression_energy(expr: &ComposedExpression) -> f32 {
196 expr.weights.values().map(|v| v.abs()).sum()
197}
198
199#[allow(dead_code)]
202pub fn normalize_expression(expr: &mut ComposedExpression) {
203 let max = expr.weights.values().cloned().fold(0.0_f32, f32::max);
204 if max > 0.0 {
205 for v in expr.weights.values_mut() {
206 *v /= max;
207 }
208 }
209}
210
211#[cfg(test)]
216mod tests {
217 use super::*;
218
219 fn simple_morphs(v: f32) -> HashMap<String, f32> {
220 let mut m = HashMap::new();
221 m.insert("smile".to_string(), v);
222 m.insert("frown".to_string(), v * 0.5);
223 m
224 }
225
226 fn make_composer() -> ExpressionComposer {
227 new_expression_composer(default_composer_config())
228 }
229
230 #[test]
231 fn test_default_config() {
232 let cfg = default_composer_config();
233 assert!(!cfg.auto_normalize);
234 assert!(cfg.max_layers > 0);
235 }
236
237 #[test]
238 fn test_new_composed_expression_empty() {
239 let expr = new_composed_expression();
240 assert!(expr.weights.is_empty());
241 assert_eq!(expr.layer_count, 0);
242 }
243
244 #[test]
245 fn test_add_layer_increases_count() {
246 let mut c = make_composer();
247 add_layer(&mut c, "happy", simple_morphs(1.0));
248 assert_eq!(layer_count(&c), 1);
249 }
250
251 #[test]
252 fn test_remove_layer() {
253 let mut c = make_composer();
254 add_layer(&mut c, "happy", simple_morphs(1.0));
255 let removed = remove_layer(&mut c, "happy");
256 assert!(removed);
257 assert_eq!(layer_count(&c), 0);
258 }
259
260 #[test]
261 fn test_remove_layer_not_found() {
262 let mut c = make_composer();
263 let removed = remove_layer(&mut c, "missing");
264 assert!(!removed);
265 }
266
267 #[test]
268 fn test_blend_layers_single_layer() {
269 let mut c = make_composer();
270 add_layer(&mut c, "happy", simple_morphs(0.8));
271 let expr = blend_layers(&c);
272 assert!(!expr.weights.is_empty());
273 assert_eq!(expr.layer_count, 1);
274 }
275
276 #[test]
277 fn test_blend_layers_empty() {
278 let c = make_composer();
279 let expr = blend_layers(&c);
280 assert!(expr.weights.is_empty());
281 }
282
283 #[test]
284 fn test_evaluate_expression_returns_weights() {
285 let mut c = make_composer();
286 add_layer(&mut c, "sad", simple_morphs(0.5));
287 let expr = evaluate_expression(&c);
288 assert!(expr.weights.contains_key("smile"));
289 }
290
291 #[test]
292 fn test_set_layer_weight() {
293 let mut c = make_composer();
294 add_layer(&mut c, "angry", simple_morphs(1.0));
295 let ok = set_layer_weight(&mut c, "angry", 0.3);
296 assert!(ok);
297 assert!((get_layer_weight(&c, "angry").expect("should succeed") - 0.3).abs() < 1e-6);
298 }
299
300 #[test]
301 fn test_set_layer_weight_not_found() {
302 let mut c = make_composer();
303 let ok = set_layer_weight(&mut c, "ghost", 0.5);
304 assert!(!ok);
305 }
306
307 #[test]
308 fn test_get_layer_weight_none() {
309 let c = make_composer();
310 assert!(get_layer_weight(&c, "nonexistent").is_none());
311 }
312
313 #[test]
314 fn test_expression_to_json_contains_keys() {
315 let mut c = make_composer();
316 add_layer(&mut c, "test", simple_morphs(1.0));
317 let expr = evaluate_expression(&c);
318 let json = expression_to_json(&expr);
319 assert!(json.contains("smile"));
320 }
321
322 #[test]
323 fn test_reset_expression() {
324 let mut c = make_composer();
325 add_layer(&mut c, "layer1", simple_morphs(1.0));
326 reset_expression(&mut c);
327 assert_eq!(layer_count(&c), 0);
328 }
329
330 #[test]
331 fn test_add_preset_layer() {
332 let mut c = make_composer();
333 let ok = add_preset_layer(&mut c, "joy", &[("smile", 0.9), ("brow_raise", 0.4)]);
334 assert!(ok);
335 assert_eq!(layer_count(&c), 1);
336 }
337
338 #[test]
339 fn test_expression_energy() {
340 let mut c = make_composer();
341 add_layer(&mut c, "full", simple_morphs(1.0));
342 let expr = evaluate_expression(&c);
343 let energy = expression_energy(&expr);
344 assert!(energy > 0.0);
345 }
346
347 #[test]
348 fn test_normalize_expression() {
349 let mut expr = new_composed_expression();
350 expr.weights.insert("a".to_string(), 2.0);
351 expr.weights.insert("b".to_string(), 1.0);
352 normalize_expression(&mut expr);
353 assert!((expr.weights["a"] - 1.0).abs() < 1e-6);
354 assert!((expr.weights["b"] - 0.5).abs() < 1e-6);
355 }
356
357 #[test]
358 fn test_normalize_expression_zero_noop() {
359 let mut expr = new_composed_expression();
360 expr.weights.insert("x".to_string(), 0.0);
361 normalize_expression(&mut expr);
362 assert_eq!(expr.weights["x"], 0.0);
363 }
364
365 #[test]
366 fn test_layer_count_limit() {
367 let mut c = new_expression_composer(ExpressionComposerConfig {
368 auto_normalize: false,
369 max_layers: 2,
370 });
371 assert!(add_layer(&mut c, "l1", simple_morphs(1.0)));
372 assert!(add_layer(&mut c, "l2", simple_morphs(1.0)));
373 assert!(!add_layer(&mut c, "l3", simple_morphs(1.0)));
374 assert_eq!(layer_count(&c), 2);
375 }
376
377 #[test]
378 fn test_two_layer_blend() {
379 let mut c = make_composer();
380 let mut m1 = HashMap::new();
381 m1.insert("x".to_string(), 1.0_f32);
382 let mut m2 = HashMap::new();
383 m2.insert("x".to_string(), 0.0_f32);
384 add_layer(&mut c, "a", m1);
385 add_layer(&mut c, "b", m2);
386 let expr = blend_layers(&c);
387 assert!((expr.weights["x"] - 0.5).abs() < 1e-6);
389 }
390}