#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct TongueCtrlConfig {
pub max_out: f32,
pub max_up: f32,
pub max_curl: f32,
pub max_width: f32,
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct TongueCtrlState {
pub config: TongueCtrlConfig,
pub out_amount: f32,
pub up_amount: f32,
pub curl_amount: f32,
pub width_amount: f32,
}
#[allow(dead_code)]
pub fn default_tongue_config() -> TongueCtrlConfig {
TongueCtrlConfig {
max_out: 1.0,
max_up: 1.0,
max_curl: 1.0,
max_width: 1.0,
}
}
#[allow(dead_code)]
pub fn new_tongue_state(cfg: &TongueCtrlConfig) -> TongueCtrlState {
TongueCtrlState {
config: cfg.clone(),
out_amount: 0.0,
up_amount: 0.0,
curl_amount: 0.0,
width_amount: 0.0,
}
}
#[allow(dead_code)]
pub fn set_tongue_out(state: &mut TongueCtrlState, amount: f32) {
state.out_amount = amount.clamp(0.0, state.config.max_out);
}
#[allow(dead_code)]
pub fn set_tongue_up(state: &mut TongueCtrlState, amount: f32) {
state.up_amount = amount.clamp(0.0, state.config.max_up);
}
#[allow(dead_code)]
pub fn set_tongue_curl(state: &mut TongueCtrlState, amount: f32) {
state.curl_amount = amount.clamp(0.0, state.config.max_curl);
}
#[allow(dead_code)]
pub fn set_tongue_width(state: &mut TongueCtrlState, amount: f32) {
state.width_amount = amount.clamp(0.0, state.config.max_width);
}
#[allow(dead_code)]
pub fn tongue_morph_weights(state: &TongueCtrlState) -> [f32; 4] {
[
state.out_amount,
state.up_amount,
state.curl_amount,
state.width_amount,
]
}
#[allow(dead_code)]
pub fn reset_tongue(state: &mut TongueCtrlState) {
state.out_amount = 0.0;
state.up_amount = 0.0;
state.curl_amount = 0.0;
state.width_amount = 0.0;
}
#[allow(dead_code)]
pub fn blend_tongue_states(a: &TongueCtrlState, b: &TongueCtrlState, t: f32) -> TongueCtrlState {
let t = t.clamp(0.0, 1.0);
let lerp = |x: f32, y: f32| x + (y - x) * t;
TongueCtrlState {
config: a.config.clone(),
out_amount: lerp(a.out_amount, b.out_amount),
up_amount: lerp(a.up_amount, b.up_amount),
curl_amount: lerp(a.curl_amount, b.curl_amount),
width_amount: lerp(a.width_amount, b.width_amount),
}
}
#[allow(dead_code)]
pub fn tongue_is_retracted(state: &TongueCtrlState) -> bool {
state.out_amount < 1e-4
&& state.up_amount < 1e-4
&& state.curl_amount < 1e-4
&& state.width_amount < 1e-4
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct TongueConfig {
pub max_extension: f32,
pub max_elevation: f32,
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct TongueState {
pub extension: f32,
pub elevation: f32,
pub curl: f32,
pub width: f32,
}
#[allow(dead_code)]
pub fn default_tongue_ext_config() -> TongueConfig {
TongueConfig { max_extension: 1.0, max_elevation: 1.0 }
}
#[allow(dead_code)]
pub fn new_tongue_ext_state(cfg: &TongueConfig) -> TongueState {
let _ = cfg;
TongueState { extension: 0.0, elevation: 0.0, curl: 0.0, width: 0.5 }
}
#[allow(dead_code)]
pub fn tongue_extend(state: &mut TongueState, cfg: &TongueConfig, value: f32) {
state.extension = value.clamp(0.0, cfg.max_extension);
}
#[allow(dead_code)]
pub fn tongue_elevate(state: &mut TongueState, cfg: &TongueConfig, value: f32) {
state.elevation = value.clamp(0.0, cfg.max_elevation);
}
#[allow(dead_code)]
pub fn tongue_curl(state: &mut TongueState, value: f32) {
state.curl = value.clamp(0.0, 1.0);
}
#[allow(dead_code)]
pub fn tongue_reset(state: &mut TongueState) {
state.extension = 0.0;
state.elevation = 0.0;
state.curl = 0.0;
state.width = 0.5;
}
#[allow(dead_code)]
pub fn tongue_to_weights(state: &TongueState) -> Vec<(String, f32)> {
vec![
("tongue_extension".to_string(), state.extension),
("tongue_elevation".to_string(), state.elevation),
("tongue_curl".to_string(), state.curl),
("tongue_width".to_string(), state.width),
]
}
#[allow(dead_code)]
pub fn tongue_to_json(state: &TongueState) -> String {
format!(
r#"{{"extension":{:.4},"elevation":{:.4},"curl":{:.4},"width":{:.4}}}"#,
state.extension, state.elevation, state.curl, state.width
)
}
#[allow(dead_code)]
pub fn tongue_clamp(state: &mut TongueState, cfg: &TongueConfig) {
state.extension = state.extension.clamp(0.0, cfg.max_extension);
state.elevation = state.elevation.clamp(0.0, cfg.max_elevation);
state.curl = state.curl.clamp(0.0, 1.0);
state.width = state.width.clamp(0.0, 1.0);
}
#[cfg(test)]
mod tests {
use super::*;
fn make_state() -> TongueCtrlState {
new_tongue_state(&default_tongue_config())
}
#[test]
fn test_default_config_max_one() {
let cfg = default_tongue_config();
assert_eq!(cfg.max_out, 1.0);
assert_eq!(cfg.max_up, 1.0);
assert_eq!(cfg.max_curl, 1.0);
assert_eq!(cfg.max_width, 1.0);
}
#[test]
fn test_new_state_all_zeros() {
let s = make_state();
assert_eq!(s.out_amount, 0.0);
assert_eq!(s.up_amount, 0.0);
assert_eq!(s.curl_amount, 0.0);
assert_eq!(s.width_amount, 0.0);
}
#[test]
fn test_is_retracted_initially() {
let s = make_state();
assert!(tongue_is_retracted(&s));
}
#[test]
fn test_set_tongue_out_clamps() {
let mut s = make_state();
set_tongue_out(&mut s, 2.0);
assert_eq!(s.out_amount, 1.0);
set_tongue_out(&mut s, -1.0);
assert_eq!(s.out_amount, 0.0);
}
#[test]
fn test_set_tongue_up_clamps() {
let mut s = make_state();
set_tongue_up(&mut s, 0.5);
assert!((s.up_amount - 0.5).abs() < 1e-6);
set_tongue_up(&mut s, 9.9);
assert_eq!(s.up_amount, 1.0);
}
#[test]
fn test_set_tongue_curl_clamps() {
let mut s = make_state();
set_tongue_curl(&mut s, 0.8);
assert!((s.curl_amount - 0.8).abs() < 1e-6);
}
#[test]
fn test_set_tongue_width_clamps() {
let mut s = make_state();
set_tongue_width(&mut s, 0.3);
assert!((s.width_amount - 0.3).abs() < 1e-6);
}
#[test]
fn test_morph_weights_returns_four() {
let mut s = make_state();
set_tongue_out(&mut s, 0.6);
set_tongue_up(&mut s, 0.4);
set_tongue_curl(&mut s, 0.2);
set_tongue_width(&mut s, 0.1);
let w = tongue_morph_weights(&s);
assert_eq!(w.len(), 4);
assert!((w[0] - 0.6).abs() < 1e-6);
assert!((w[1] - 0.4).abs() < 1e-6);
assert!((w[2] - 0.2).abs() < 1e-6);
assert!((w[3] - 0.1).abs() < 1e-6);
}
#[test]
fn test_reset_tongue() {
let mut s = make_state();
set_tongue_out(&mut s, 1.0);
set_tongue_up(&mut s, 1.0);
reset_tongue(&mut s);
assert!(tongue_is_retracted(&s));
}
#[test]
fn test_blend_tongue_states_midpoint() {
let mut a = make_state();
let mut b = make_state();
set_tongue_out(&mut a, 0.0);
set_tongue_out(&mut b, 1.0);
let mid = blend_tongue_states(&a, &b, 0.5);
assert!((mid.out_amount - 0.5).abs() < 1e-6);
}
#[test]
fn test_blend_tongue_states_t0_is_a() {
let mut a = make_state();
let b = make_state();
set_tongue_out(&mut a, 0.7);
let result = blend_tongue_states(&a, &b, 0.0);
assert!((result.out_amount - 0.7).abs() < 1e-6);
}
#[test]
fn test_blend_tongue_states_t1_is_b() {
let a = make_state();
let mut b = make_state();
set_tongue_out(&mut b, 0.9);
let result = blend_tongue_states(&a, &b, 1.0);
assert!((result.out_amount - 0.9).abs() < 1e-6);
}
#[test]
fn test_not_retracted_when_out_set() {
let mut s = make_state();
set_tongue_out(&mut s, 0.5);
assert!(!tongue_is_retracted(&s));
}
}