#[allow(dead_code)]
pub struct SkinTensionConfig {
pub global_sensitivity: f32,
pub default_threshold: f32,
pub max_weight_clamp: f32,
}
#[allow(dead_code)]
pub struct TensionZone {
pub joint_name: String,
pub threshold: f32,
pub max_weight: f32,
pub current_weight: f32,
}
#[allow(dead_code)]
pub struct SkinTensionState {
pub zones: Vec<TensionZone>,
pub sensitivity: f32,
}
#[allow(dead_code)]
pub fn default_skin_tension_config() -> SkinTensionConfig {
SkinTensionConfig {
global_sensitivity: 1.0,
default_threshold: 0.2,
max_weight_clamp: 1.0,
}
}
#[allow(dead_code)]
pub fn new_skin_tension_state(cfg: &SkinTensionConfig) -> SkinTensionState {
SkinTensionState {
zones: Vec::new(),
sensitivity: cfg.global_sensitivity,
}
}
#[allow(dead_code)]
pub fn add_tension_zone(
state: &mut SkinTensionState,
joint_name: &str,
threshold: f32,
max_weight: f32,
) {
if state.zones.iter().any(|z| z.joint_name == joint_name) {
return;
}
state.zones.push(TensionZone {
joint_name: joint_name.to_string(),
threshold: threshold.max(0.0),
max_weight: max_weight.clamp(0.0, 1.0),
current_weight: 0.0,
});
}
#[allow(dead_code)]
pub fn update_tension(state: &mut SkinTensionState, joint_name: &str, bend_angle_rad: f32) {
let sensitivity = state.sensitivity;
if let Some(zone) = state.zones.iter_mut().find(|z| z.joint_name == joint_name) {
let excess = (bend_angle_rad.abs() - zone.threshold).max(0.0);
let raw = excess * sensitivity;
zone.current_weight = raw.clamp(0.0, zone.max_weight);
}
}
#[allow(dead_code)]
pub fn tension_morph_weight(state: &SkinTensionState, joint_name: &str) -> f32 {
state
.zones
.iter()
.find(|z| z.joint_name == joint_name)
.map(|z| z.current_weight)
.unwrap_or(0.0)
}
#[allow(dead_code)]
pub fn tension_zone_count(state: &SkinTensionState) -> usize {
state.zones.len()
}
#[allow(dead_code)]
pub fn reset_tension(state: &mut SkinTensionState) {
for zone in &mut state.zones {
zone.current_weight = 0.0;
}
}
#[allow(dead_code)]
pub fn tension_zone_is_active(state: &SkinTensionState, joint_name: &str) -> bool {
state
.zones
.iter()
.find(|z| z.joint_name == joint_name)
.map(|z| z.current_weight > 0.001)
.unwrap_or(false)
}
#[allow(dead_code)]
pub fn all_tension_weights(state: &SkinTensionState) -> Vec<f32> {
state.zones.iter().map(|z| z.current_weight).collect()
}
#[allow(dead_code)]
pub fn set_tension_sensitivity(state: &mut SkinTensionState, sensitivity: f32) {
state.sensitivity = sensitivity.max(0.0);
for zone in &mut state.zones {
zone.current_weight = zone.current_weight.clamp(0.0, zone.max_weight);
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_state() -> SkinTensionState {
let cfg = default_skin_tension_config();
new_skin_tension_state(&cfg)
}
#[test]
fn test_default_config() {
let cfg = default_skin_tension_config();
assert!((cfg.global_sensitivity - 1.0).abs() < 1e-6);
assert!((cfg.max_weight_clamp - 1.0).abs() < 1e-6);
}
#[test]
fn test_new_state_empty() {
let state = make_state();
assert_eq!(tension_zone_count(&state), 0);
}
#[test]
fn test_add_tension_zone() {
let mut state = make_state();
add_tension_zone(&mut state, "elbow_l", 0.3, 0.8);
assert_eq!(tension_zone_count(&state), 1);
add_tension_zone(&mut state, "elbow_l", 0.1, 0.5);
assert_eq!(tension_zone_count(&state), 1);
}
#[test]
fn test_update_tension_no_activation_below_threshold() {
let mut state = make_state();
add_tension_zone(&mut state, "knee_r", 1.0, 1.0);
update_tension(&mut state, "knee_r", 0.5);
assert!((tension_morph_weight(&state, "knee_r")).abs() < 1e-6);
}
#[test]
fn test_update_tension_activates_above_threshold() {
let mut state = make_state();
add_tension_zone(&mut state, "knee_r", 0.5, 1.0);
update_tension(&mut state, "knee_r", 1.2);
let w = tension_morph_weight(&state, "knee_r");
assert!(w > 0.0);
assert!(w <= 1.0);
}
#[test]
fn test_tension_morph_weight_unknown_joint() {
let state = make_state();
assert!((tension_morph_weight(&state, "does_not_exist")).abs() < 1e-6);
}
#[test]
fn test_reset_tension() {
let mut state = make_state();
add_tension_zone(&mut state, "elbow_l", 0.1, 1.0);
update_tension(&mut state, "elbow_l", 1.5);
assert!(tension_zone_is_active(&state, "elbow_l"));
reset_tension(&mut state);
assert!(!tension_zone_is_active(&state, "elbow_l"));
}
#[test]
fn test_all_tension_weights_length() {
let mut state = make_state();
add_tension_zone(&mut state, "shoulder_l", 0.2, 0.9);
add_tension_zone(&mut state, "shoulder_r", 0.2, 0.9);
let weights = all_tension_weights(&state);
assert_eq!(weights.len(), 2);
}
#[test]
fn test_set_tension_sensitivity() {
let mut state = make_state();
add_tension_zone(&mut state, "wrist_l", 0.1, 1.0);
update_tension(&mut state, "wrist_l", 0.5);
let w1 = tension_morph_weight(&state, "wrist_l");
set_tension_sensitivity(&mut state, 2.0);
update_tension(&mut state, "wrist_l", 0.5);
let w2 = tension_morph_weight(&state, "wrist_l");
assert!(w2 > w1);
}
#[test]
fn test_tension_zone_is_active() {
let mut state = make_state();
add_tension_zone(&mut state, "hip_l", 0.3, 1.0);
assert!(!tension_zone_is_active(&state, "hip_l"));
update_tension(&mut state, "hip_l", 1.0);
assert!(tension_zone_is_active(&state, "hip_l"));
}
#[test]
fn test_max_weight_clamped() {
let mut state = make_state();
add_tension_zone(&mut state, "ankle_l", 0.1, 0.5);
update_tension(&mut state, "ankle_l", 100.0);
let w = tension_morph_weight(&state, "ankle_l");
assert!(w <= 0.5 + 1e-6);
}
}