use std::collections::HashMap;
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct ExpressionComposerLayer {
pub name: String,
pub weight: f32,
pub morphs: HashMap<String, f32>,
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct ComposedExpression {
pub weights: HashMap<String, f32>,
pub layer_count: usize,
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct ExpressionComposerConfig {
pub auto_normalize: bool,
pub max_layers: usize,
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct ExpressionComposer {
pub config: ExpressionComposerConfig,
pub layers: Vec<ExpressionComposerLayer>,
}
#[allow(dead_code)]
pub fn default_composer_config() -> ExpressionComposerConfig {
ExpressionComposerConfig {
auto_normalize: false,
max_layers: 32,
}
}
#[allow(dead_code)]
pub fn new_composed_expression() -> ComposedExpression {
ComposedExpression {
weights: HashMap::new(),
layer_count: 0,
}
}
#[allow(dead_code)]
pub fn new_expression_composer(config: ExpressionComposerConfig) -> ExpressionComposer {
ExpressionComposer {
config,
layers: Vec::new(),
}
}
#[allow(dead_code)]
pub fn add_layer(
composer: &mut ExpressionComposer,
name: &str,
morphs: HashMap<String, f32>,
) -> bool {
if composer.layers.len() >= composer.config.max_layers {
return false;
}
composer.layers.push(ExpressionComposerLayer {
name: name.to_string(),
weight: 1.0,
morphs,
});
true
}
#[allow(dead_code)]
pub fn remove_layer(composer: &mut ExpressionComposer, name: &str) -> bool {
let before = composer.layers.len();
composer.layers.retain(|l| l.name != name);
composer.layers.len() < before
}
#[allow(dead_code)]
pub fn blend_layers(composer: &ExpressionComposer) -> ComposedExpression {
let mut weights: HashMap<String, f32> = HashMap::new();
let weight_sum: f32 = composer.layers.iter().map(|l| l.weight.abs()).sum();
for layer in &composer.layers {
let layer_w = if weight_sum > 0.0 {
layer.weight / weight_sum
} else {
0.0
};
for (morph, &mv) in &layer.morphs {
let entry = weights.entry(morph.clone()).or_insert(0.0);
*entry += mv * layer_w;
}
}
ComposedExpression {
layer_count: composer.layers.len(),
weights,
}
}
#[allow(dead_code)]
pub fn evaluate_expression(composer: &ExpressionComposer) -> ComposedExpression {
let mut expr = blend_layers(composer);
if composer.config.auto_normalize {
normalize_expression(&mut expr);
}
expr
}
#[allow(dead_code)]
pub fn layer_count(composer: &ExpressionComposer) -> usize {
composer.layers.len()
}
#[allow(dead_code)]
pub fn set_layer_weight(composer: &mut ExpressionComposer, name: &str, weight: f32) -> bool {
for layer in &mut composer.layers {
if layer.name == name {
layer.weight = weight.clamp(0.0, 1.0);
return true;
}
}
false
}
#[allow(dead_code)]
pub fn get_layer_weight(composer: &ExpressionComposer, name: &str) -> Option<f32> {
composer
.layers
.iter()
.find(|l| l.name == name)
.map(|l| l.weight)
}
#[allow(dead_code)]
pub fn expression_to_json(expr: &ComposedExpression) -> String {
let mut pairs: Vec<String> = expr
.weights
.iter()
.map(|(k, v)| format!(" \"{k}\": {v:.4}"))
.collect();
pairs.sort();
format!("{{\n{}\n}}", pairs.join(",\n"))
}
#[allow(dead_code)]
pub fn reset_expression(composer: &mut ExpressionComposer) {
composer.layers.clear();
}
#[allow(dead_code)]
pub fn add_preset_layer(
composer: &mut ExpressionComposer,
name: &str,
presets: &[(&str, f32)],
) -> bool {
let morphs: HashMap<String, f32> = presets.iter().map(|(k, v)| (k.to_string(), *v)).collect();
add_layer(composer, name, morphs)
}
#[allow(dead_code)]
pub fn expression_energy(expr: &ComposedExpression) -> f32 {
expr.weights.values().map(|v| v.abs()).sum()
}
#[allow(dead_code)]
pub fn normalize_expression(expr: &mut ComposedExpression) {
let max = expr.weights.values().cloned().fold(0.0_f32, f32::max);
if max > 0.0 {
for v in expr.weights.values_mut() {
*v /= max;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn simple_morphs(v: f32) -> HashMap<String, f32> {
let mut m = HashMap::new();
m.insert("smile".to_string(), v);
m.insert("frown".to_string(), v * 0.5);
m
}
fn make_composer() -> ExpressionComposer {
new_expression_composer(default_composer_config())
}
#[test]
fn test_default_config() {
let cfg = default_composer_config();
assert!(!cfg.auto_normalize);
assert!(cfg.max_layers > 0);
}
#[test]
fn test_new_composed_expression_empty() {
let expr = new_composed_expression();
assert!(expr.weights.is_empty());
assert_eq!(expr.layer_count, 0);
}
#[test]
fn test_add_layer_increases_count() {
let mut c = make_composer();
add_layer(&mut c, "happy", simple_morphs(1.0));
assert_eq!(layer_count(&c), 1);
}
#[test]
fn test_remove_layer() {
let mut c = make_composer();
add_layer(&mut c, "happy", simple_morphs(1.0));
let removed = remove_layer(&mut c, "happy");
assert!(removed);
assert_eq!(layer_count(&c), 0);
}
#[test]
fn test_remove_layer_not_found() {
let mut c = make_composer();
let removed = remove_layer(&mut c, "missing");
assert!(!removed);
}
#[test]
fn test_blend_layers_single_layer() {
let mut c = make_composer();
add_layer(&mut c, "happy", simple_morphs(0.8));
let expr = blend_layers(&c);
assert!(!expr.weights.is_empty());
assert_eq!(expr.layer_count, 1);
}
#[test]
fn test_blend_layers_empty() {
let c = make_composer();
let expr = blend_layers(&c);
assert!(expr.weights.is_empty());
}
#[test]
fn test_evaluate_expression_returns_weights() {
let mut c = make_composer();
add_layer(&mut c, "sad", simple_morphs(0.5));
let expr = evaluate_expression(&c);
assert!(expr.weights.contains_key("smile"));
}
#[test]
fn test_set_layer_weight() {
let mut c = make_composer();
add_layer(&mut c, "angry", simple_morphs(1.0));
let ok = set_layer_weight(&mut c, "angry", 0.3);
assert!(ok);
assert!((get_layer_weight(&c, "angry").expect("should succeed") - 0.3).abs() < 1e-6);
}
#[test]
fn test_set_layer_weight_not_found() {
let mut c = make_composer();
let ok = set_layer_weight(&mut c, "ghost", 0.5);
assert!(!ok);
}
#[test]
fn test_get_layer_weight_none() {
let c = make_composer();
assert!(get_layer_weight(&c, "nonexistent").is_none());
}
#[test]
fn test_expression_to_json_contains_keys() {
let mut c = make_composer();
add_layer(&mut c, "test", simple_morphs(1.0));
let expr = evaluate_expression(&c);
let json = expression_to_json(&expr);
assert!(json.contains("smile"));
}
#[test]
fn test_reset_expression() {
let mut c = make_composer();
add_layer(&mut c, "layer1", simple_morphs(1.0));
reset_expression(&mut c);
assert_eq!(layer_count(&c), 0);
}
#[test]
fn test_add_preset_layer() {
let mut c = make_composer();
let ok = add_preset_layer(&mut c, "joy", &[("smile", 0.9), ("brow_raise", 0.4)]);
assert!(ok);
assert_eq!(layer_count(&c), 1);
}
#[test]
fn test_expression_energy() {
let mut c = make_composer();
add_layer(&mut c, "full", simple_morphs(1.0));
let expr = evaluate_expression(&c);
let energy = expression_energy(&expr);
assert!(energy > 0.0);
}
#[test]
fn test_normalize_expression() {
let mut expr = new_composed_expression();
expr.weights.insert("a".to_string(), 2.0);
expr.weights.insert("b".to_string(), 1.0);
normalize_expression(&mut expr);
assert!((expr.weights["a"] - 1.0).abs() < 1e-6);
assert!((expr.weights["b"] - 0.5).abs() < 1e-6);
}
#[test]
fn test_normalize_expression_zero_noop() {
let mut expr = new_composed_expression();
expr.weights.insert("x".to_string(), 0.0);
normalize_expression(&mut expr);
assert_eq!(expr.weights["x"], 0.0);
}
#[test]
fn test_layer_count_limit() {
let mut c = new_expression_composer(ExpressionComposerConfig {
auto_normalize: false,
max_layers: 2,
});
assert!(add_layer(&mut c, "l1", simple_morphs(1.0)));
assert!(add_layer(&mut c, "l2", simple_morphs(1.0)));
assert!(!add_layer(&mut c, "l3", simple_morphs(1.0)));
assert_eq!(layer_count(&c), 2);
}
#[test]
fn test_two_layer_blend() {
let mut c = make_composer();
let mut m1 = HashMap::new();
m1.insert("x".to_string(), 1.0_f32);
let mut m2 = HashMap::new();
m2.insert("x".to_string(), 0.0_f32);
add_layer(&mut c, "a", m1);
add_layer(&mut c, "b", m2);
let expr = blend_layers(&c);
assert!((expr.weights["x"] - 0.5).abs() < 1e-6);
}
}