#![allow(dead_code)]
#[allow(dead_code)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum BrowTailSide {
Left,
Right,
Both,
}
#[allow(dead_code)]
#[derive(Clone, Debug)]
pub struct BrowTailState {
pub raise_left: f32,
pub raise_right: f32,
pub angle_left: f32,
pub angle_right: f32,
}
#[allow(dead_code)]
#[derive(Clone, Debug)]
pub struct BrowTailConfig {
pub max_raise: f32,
pub max_lower: f32,
pub max_angle_deg: f32,
}
impl Default for BrowTailConfig {
fn default() -> Self {
Self {
max_raise: 1.0,
max_lower: 1.0,
max_angle_deg: 30.0,
}
}
}
impl Default for BrowTailState {
fn default() -> Self {
Self {
raise_left: 0.0,
raise_right: 0.0,
angle_left: 0.0,
angle_right: 0.0,
}
}
}
#[allow(dead_code)]
pub fn new_brow_tail_state() -> BrowTailState {
BrowTailState::default()
}
#[allow(dead_code)]
pub fn default_brow_tail_config() -> BrowTailConfig {
BrowTailConfig::default()
}
#[allow(dead_code)]
pub fn bt_set_raise(state: &mut BrowTailState, cfg: &BrowTailConfig, side: BrowTailSide, v: f32) {
let v = v.clamp(-cfg.max_lower, cfg.max_raise);
match side {
BrowTailSide::Left => state.raise_left = v,
BrowTailSide::Right => state.raise_right = v,
BrowTailSide::Both => {
state.raise_left = v;
state.raise_right = v;
}
}
}
#[allow(dead_code)]
pub fn bt_set_angle(state: &mut BrowTailState, cfg: &BrowTailConfig, side: BrowTailSide, deg: f32) {
let d = deg.clamp(-cfg.max_angle_deg, cfg.max_angle_deg);
match side {
BrowTailSide::Left => state.angle_left = d,
BrowTailSide::Right => state.angle_right = d,
BrowTailSide::Both => {
state.angle_left = d;
state.angle_right = d;
}
}
}
#[allow(dead_code)]
pub fn bt_reset(state: &mut BrowTailState) {
*state = BrowTailState::default();
}
#[allow(dead_code)]
pub fn bt_symmetry(state: &BrowTailState) -> f32 {
1.0 - (state.raise_left - state.raise_right).abs().min(1.0)
}
#[allow(dead_code)]
pub fn bt_blend(a: &BrowTailState, b: &BrowTailState, t: f32) -> BrowTailState {
let t = t.clamp(0.0, 1.0);
BrowTailState {
raise_left: a.raise_left + (b.raise_left - a.raise_left) * t,
raise_right: a.raise_right + (b.raise_right - a.raise_right) * t,
angle_left: a.angle_left + (b.angle_left - a.angle_left) * t,
angle_right: a.angle_right + (b.angle_right - a.angle_right) * t,
}
}
#[allow(dead_code)]
pub fn bt_to_morph_weights(state: &BrowTailState) -> [f32; 4] {
[
state.raise_left,
state.raise_right,
state.angle_left / 30.0,
state.angle_right / 30.0,
]
}
#[allow(dead_code)]
pub fn bt_is_neutral(state: &BrowTailState) -> bool {
state.raise_left.abs() < 1e-4
&& state.raise_right.abs() < 1e-4
&& state.angle_left.abs() < 1e-4
&& state.angle_right.abs() < 1e-4
}
#[allow(dead_code)]
pub fn bt_to_json(state: &BrowTailState) -> String {
format!(
"{{\"raise_left\":{:.4},\"raise_right\":{:.4},\"angle_left\":{:.4},\"angle_right\":{:.4}}}",
state.raise_left, state.raise_right, state.angle_left, state.angle_right
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_is_neutral() {
assert!(bt_is_neutral(&new_brow_tail_state()));
}
#[test]
fn set_raise_clamps_max() {
let mut s = new_brow_tail_state();
let cfg = default_brow_tail_config();
bt_set_raise(&mut s, &cfg, BrowTailSide::Left, 2.0);
assert!(s.raise_left <= cfg.max_raise);
}
#[test]
fn set_raise_clamps_min() {
let mut s = new_brow_tail_state();
let cfg = default_brow_tail_config();
bt_set_raise(&mut s, &cfg, BrowTailSide::Right, -5.0);
assert!(s.raise_right >= -cfg.max_lower);
}
#[test]
fn both_side_sets_both() {
let mut s = new_brow_tail_state();
let cfg = default_brow_tail_config();
bt_set_raise(&mut s, &cfg, BrowTailSide::Both, 0.5);
assert!((s.raise_left - 0.5).abs() < 1e-5);
assert!((s.raise_right - 0.5).abs() < 1e-5);
}
#[test]
fn reset_clears_state() {
let mut s = new_brow_tail_state();
let cfg = default_brow_tail_config();
bt_set_raise(&mut s, &cfg, BrowTailSide::Both, 0.8);
bt_reset(&mut s);
assert!(bt_is_neutral(&s));
}
#[test]
fn blend_midpoint() {
let mut a = new_brow_tail_state();
let mut b = new_brow_tail_state();
let cfg = default_brow_tail_config();
bt_set_raise(&mut a, &cfg, BrowTailSide::Left, 0.0);
bt_set_raise(&mut b, &cfg, BrowTailSide::Left, 1.0);
let mid = bt_blend(&a, &b, 0.5);
assert!((mid.raise_left - 0.5).abs() < 1e-4);
}
#[test]
fn symmetry_one_when_equal() {
let s = new_brow_tail_state();
assert!((bt_symmetry(&s) - 1.0).abs() < 1e-5);
}
#[test]
fn morph_weights_len() {
let s = new_brow_tail_state();
assert_eq!(bt_to_morph_weights(&s).len(), 4);
}
#[test]
fn angle_clamp() {
let mut s = new_brow_tail_state();
let cfg = default_brow_tail_config();
bt_set_angle(&mut s, &cfg, BrowTailSide::Left, 999.0);
assert!(s.angle_left <= cfg.max_angle_deg);
}
#[test]
fn json_output_not_empty() {
let s = new_brow_tail_state();
assert!(!bt_to_json(&s).is_empty());
}
}