#[allow(dead_code)]
pub struct ActionUnitEntry {
pub code: u8,
pub name: String,
pub weight: f32,
}
#[allow(dead_code)]
pub struct FacsConfig {
pub max_weight: f32,
pub min_weight: f32,
}
#[allow(dead_code)]
pub struct FacsCtrlState {
pub units: Vec<ActionUnitEntry>,
pub config: FacsConfig,
}
#[allow(dead_code)]
pub fn default_facs_config() -> FacsConfig {
FacsConfig {
max_weight: 1.0,
min_weight: 0.0,
}
}
#[allow(dead_code)]
pub fn new_facs_state(cfg: &FacsConfig) -> FacsCtrlState {
FacsCtrlState {
units: Vec::new(),
config: FacsConfig {
max_weight: cfg.max_weight,
min_weight: cfg.min_weight,
},
}
}
#[allow(dead_code)]
pub fn facs_register_au(state: &mut FacsCtrlState, code: u8, name: &str) {
if let Some(entry) = state.units.iter_mut().find(|u| u.code == code) {
entry.name = name.to_string();
} else {
state.units.push(ActionUnitEntry {
code,
name: name.to_string(),
weight: 0.0,
});
}
}
#[allow(dead_code)]
pub fn facs_set_au_weight(state: &mut FacsCtrlState, code: u8, weight: f32) {
let clamped = weight.clamp(state.config.min_weight, state.config.max_weight);
if let Some(entry) = state.units.iter_mut().find(|u| u.code == code) {
entry.weight = clamped;
}
}
#[allow(dead_code)]
pub fn facs_get_au_weight(state: &FacsCtrlState, code: u8) -> f32 {
state
.units
.iter()
.find(|u| u.code == code)
.map_or(0.0, |u| u.weight)
}
#[allow(dead_code)]
pub fn facs_active_units(state: &FacsCtrlState) -> Vec<u8> {
state
.units
.iter()
.filter(|u| u.weight > 0.0)
.map(|u| u.code)
.collect()
}
#[allow(dead_code)]
pub fn facs_unit_count(state: &FacsCtrlState) -> usize {
state.units.len()
}
#[allow(dead_code)]
pub fn facs_reset(state: &mut FacsCtrlState) {
for u in &mut state.units {
u.weight = 0.0;
}
}
#[allow(dead_code)]
pub fn facs_to_morph_vector(state: &FacsCtrlState) -> Vec<f32> {
state.units.iter().map(|u| u.weight).collect()
}
#[allow(dead_code)]
pub fn facs_au_name(state: &FacsCtrlState, code: u8) -> Option<&str> {
state
.units
.iter()
.find(|u| u.code == code)
.map(|u| u.name.as_str())
}
#[cfg(test)]
mod tests {
use super::*;
fn make_state() -> FacsCtrlState {
let cfg = default_facs_config();
let mut s = new_facs_state(&cfg);
facs_register_au(&mut s, 1, "Inner Brow Raise");
facs_register_au(&mut s, 2, "Outer Brow Raise");
facs_register_au(&mut s, 4, "Brow Lowerer");
s
}
#[test]
fn test_default_config() {
let cfg = default_facs_config();
assert_eq!(cfg.max_weight, 1.0);
assert_eq!(cfg.min_weight, 0.0);
}
#[test]
fn test_register_au() {
let s = make_state();
assert_eq!(facs_unit_count(&s), 3);
}
#[test]
fn test_set_get_weight() {
let mut s = make_state();
facs_set_au_weight(&mut s, 1, 0.75);
assert!((facs_get_au_weight(&s, 1) - 0.75).abs() < 1e-6);
}
#[test]
fn test_weight_clamped() {
let mut s = make_state();
facs_set_au_weight(&mut s, 1, 2.0);
assert_eq!(facs_get_au_weight(&s, 1), 1.0);
facs_set_au_weight(&mut s, 1, -0.5);
assert_eq!(facs_get_au_weight(&s, 1), 0.0);
}
#[test]
fn test_active_units() {
let mut s = make_state();
assert!(facs_active_units(&s).is_empty());
facs_set_au_weight(&mut s, 1, 0.5);
facs_set_au_weight(&mut s, 4, 1.0);
let active = facs_active_units(&s);
assert_eq!(active.len(), 2);
assert!(active.contains(&1));
assert!(active.contains(&4));
}
#[test]
fn test_facs_reset() {
let mut s = make_state();
facs_set_au_weight(&mut s, 1, 0.8);
facs_reset(&mut s);
assert_eq!(facs_get_au_weight(&s, 1), 0.0);
assert!(facs_active_units(&s).is_empty());
}
#[test]
fn test_morph_vector_length() {
let s = make_state();
assert_eq!(facs_to_morph_vector(&s).len(), 3);
}
#[test]
fn test_au_name() {
let s = make_state();
assert_eq!(facs_au_name(&s, 1), Some("Inner Brow Raise"));
assert!(facs_au_name(&s, 99).is_none());
}
#[test]
fn test_register_duplicate_updates_name() {
let mut s = make_state();
facs_register_au(&mut s, 1, "Updated Name");
assert_eq!(facs_unit_count(&s), 3); assert_eq!(facs_au_name(&s, 1), Some("Updated Name"));
}
#[test]
fn test_get_unknown_code_returns_zero() {
let s = make_state();
assert_eq!(facs_get_au_weight(&s, 42), 0.0);
}
}