#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct CorrectiveDriverConfig {
pub joint_name: String,
pub shape_name: String,
pub smooth_falloff: bool,
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
struct DriverKey {
angle_deg: f32,
weight: f32,
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct CorrectiveDriver {
config: CorrectiveDriverConfig,
keys: Vec<DriverKey>,
last_weight: f32,
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct DriverEvalResult {
pub weight: f32,
pub active: bool,
pub shape_name: String,
}
#[allow(dead_code)]
pub fn default_corrective_driver_config() -> CorrectiveDriverConfig {
CorrectiveDriverConfig {
joint_name: "elbow_l".into(),
shape_name: "elbow_corrective_l".into(),
smooth_falloff: true,
}
}
#[allow(dead_code)]
pub fn new_corrective_driver(config: CorrectiveDriverConfig) -> CorrectiveDriver {
CorrectiveDriver {
config,
keys: Vec::new(),
last_weight: 0.0,
}
}
#[allow(dead_code)]
pub fn driver_add_key(driver: &mut CorrectiveDriver, angle_deg: f32, weight: f32) {
let weight = weight.clamp(0.0, 1.0);
driver.keys.push(DriverKey { angle_deg, weight });
driver
.keys
.sort_by(|a, b| a.angle_deg.partial_cmp(&b.angle_deg).unwrap_or(std::cmp::Ordering::Equal));
}
#[allow(dead_code)]
pub fn driver_evaluate(driver: &mut CorrectiveDriver, angle_deg: f32) -> DriverEvalResult {
let w = if driver.keys.is_empty() {
0.0f32
} else if driver.keys.len() == 1 {
driver.keys[0].weight
} else {
let n = driver.keys.len();
if angle_deg <= driver.keys[0].angle_deg {
driver.keys[0].weight
} else if angle_deg >= driver.keys[n - 1].angle_deg {
driver.keys[n - 1].weight
} else {
let mut lo = 0usize;
for i in 0..n - 1 {
if driver.keys[i].angle_deg <= angle_deg
&& angle_deg < driver.keys[i + 1].angle_deg
{
lo = i;
break;
}
}
let k0 = &driver.keys[lo];
let k1 = &driver.keys[lo + 1];
let range = k1.angle_deg - k0.angle_deg;
let t = if range.abs() < 1e-9 {
0.0
} else {
(angle_deg - k0.angle_deg) / range
};
let t = t.clamp(0.0, 1.0);
if driver.config.smooth_falloff {
let t2 = t * t;
let t3 = t2 * t;
let s = 3.0 * t2 - 2.0 * t3;
k0.weight + s * (k1.weight - k0.weight)
} else {
k0.weight + t * (k1.weight - k0.weight)
}
}
};
driver.last_weight = w;
DriverEvalResult {
weight: w,
active: w > 1e-6,
shape_name: driver.config.shape_name.clone(),
}
}
#[allow(dead_code)]
pub fn driver_key_count(driver: &CorrectiveDriver) -> usize {
driver.keys.len()
}
#[allow(dead_code)]
pub fn driver_is_active(driver: &CorrectiveDriver) -> bool {
driver.last_weight > 1e-6
}
#[allow(dead_code)]
pub fn driver_peak_weight(driver: &CorrectiveDriver) -> f32 {
driver
.keys
.iter()
.map(|k| k.weight)
.fold(0.0f32, f32::max)
}
#[allow(dead_code)]
pub fn driver_clear_keys(driver: &mut CorrectiveDriver) {
driver.keys.clear();
}
#[allow(dead_code)]
pub fn driver_to_json(driver: &CorrectiveDriver) -> String {
let keys_json: Vec<String> = driver
.keys
.iter()
.map(|k| format!(r#"{{"angle":{:.2},"weight":{:.4}}}"#, k.angle_deg, k.weight))
.collect();
format!(
r#"{{"joint":"{}","shape":"{}","key_count":{},"last_weight":{:.6},"keys":[{}]}}"#,
driver.config.joint_name,
driver.config.shape_name,
driver.keys.len(),
driver.last_weight,
keys_json.join(",")
)
}
#[allow(dead_code)]
pub fn driver_reset(driver: &mut CorrectiveDriver) {
driver.last_weight = 0.0;
}
#[cfg(test)]
mod tests {
use super::*;
fn make_driver() -> CorrectiveDriver {
let mut d = new_corrective_driver(default_corrective_driver_config());
driver_add_key(&mut d, 0.0, 0.0);
driver_add_key(&mut d, 90.0, 1.0);
driver_add_key(&mut d, 180.0, 0.0);
d
}
#[test]
fn test_default_config() {
let cfg = default_corrective_driver_config();
assert!(!cfg.joint_name.is_empty());
assert!(!cfg.shape_name.is_empty());
}
#[test]
fn test_key_count() {
let d = make_driver();
assert_eq!(driver_key_count(&d), 3);
}
#[test]
fn test_evaluate_at_peak() {
let mut d = make_driver();
let res = driver_evaluate(&mut d, 90.0);
assert!((res.weight - 1.0).abs() < 1e-5);
assert!(res.active);
}
#[test]
fn test_evaluate_at_zero() {
let mut d = make_driver();
let res = driver_evaluate(&mut d, 0.0);
assert!(res.weight < 1e-6);
assert!(!res.active);
}
#[test]
fn test_evaluate_midpoint_linear() {
let cfg = CorrectiveDriverConfig {
smooth_falloff: false,
..default_corrective_driver_config()
};
let mut d = new_corrective_driver(cfg);
driver_add_key(&mut d, 0.0, 0.0);
driver_add_key(&mut d, 100.0, 1.0);
let res = driver_evaluate(&mut d, 50.0);
assert!((res.weight - 0.5).abs() < 1e-5);
}
#[test]
fn test_peak_weight() {
let d = make_driver();
assert!((driver_peak_weight(&d) - 1.0).abs() < 1e-6);
}
#[test]
fn test_clear_keys() {
let mut d = make_driver();
driver_clear_keys(&mut d);
assert_eq!(driver_key_count(&d), 0);
}
#[test]
fn test_is_active_after_evaluate() {
let mut d = make_driver();
driver_evaluate(&mut d, 90.0);
assert!(driver_is_active(&d));
}
#[test]
fn test_reset_clears_last_weight() {
let mut d = make_driver();
driver_evaluate(&mut d, 90.0);
driver_reset(&mut d);
assert!(!driver_is_active(&d));
}
#[test]
fn test_to_json() {
let d = make_driver();
let json = driver_to_json(&d);
assert!(json.contains("key_count"));
assert!(json.contains("elbow_corrective_l"));
}
#[test]
fn test_keys_sorted_on_insert() {
let mut d = new_corrective_driver(default_corrective_driver_config());
driver_add_key(&mut d, 180.0, 0.0);
driver_add_key(&mut d, 0.0, 0.0);
driver_add_key(&mut d, 90.0, 1.0);
assert_eq!(driver_key_count(&d), 3);
let mut d2 = new_corrective_driver(default_corrective_driver_config());
driver_add_key(&mut d2, 0.0, 0.0);
driver_add_key(&mut d2, 90.0, 1.0);
driver_add_key(&mut d2, 180.0, 0.0);
let r1 = driver_evaluate(&mut d, 45.0);
let r2 = driver_evaluate(&mut d2, 45.0);
assert!((r1.weight - r2.weight).abs() < 1e-5);
}
}