#![allow(dead_code)]
use std::f32::consts::PI;
#[allow(dead_code)]
#[derive(Debug, Clone, PartialEq)]
pub struct WristParams {
pub thickness: f32,
pub ulnar_prominence: f32,
pub radial_prominence: f32,
pub tendon_visibility: f32,
pub flexion: f32,
pub deviation: f32,
}
impl Default for WristParams {
fn default() -> Self {
Self {
thickness: 1.0,
ulnar_prominence: 0.4,
radial_prominence: 0.3,
tendon_visibility: 0.2,
flexion: 0.0,
deviation: 0.0,
}
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct WristResult {
pub displacements: Vec<(usize, f32)>,
pub flexion_corrective: f32,
pub deviation_corrective: f32,
}
#[allow(dead_code)]
pub fn flexion_weight(angle: f32) -> f32 {
let t = (angle.abs() / (PI / 3.0)).clamp(0.0, 1.0);
t * t * (3.0 - 2.0 * t)
}
#[allow(dead_code)]
pub fn deviation_weight(angle: f32) -> f32 {
let t = (angle.abs() / (PI / 6.0)).clamp(0.0, 1.0);
t * t * (3.0 - 2.0 * t)
}
#[allow(dead_code)]
pub fn styloid_bump(angle: f32, centre: f32, prominence: f32) -> f32 {
let diff = (angle - centre).abs();
let diff = if diff > PI { 2.0 * PI - diff } else { diff };
if diff < 0.4 {
prominence * 0.006 * (1.0 - diff / 0.4)
} else {
0.0
}
}
#[allow(dead_code)]
pub fn tendon_ridge(angle: f32, along_t: f32, visibility: f32) -> f32 {
if visibility < 0.05 {
return 0.0;
}
let dorsal = 0.5 * (1.0 + (angle - PI).cos());
if dorsal < 0.3 {
return 0.0;
}
let ridges = ((along_t * 6.0) * PI).cos().abs();
visibility * 0.002 * dorsal * ridges
}
#[allow(dead_code)]
pub fn evaluate_wrist(wrist_coords: &[(f32, f32, f32)], params: &WristParams) -> WristResult {
let flex_w = flexion_weight(params.flexion);
let dev_w = deviation_weight(params.deviation);
let mut displacements = Vec::with_capacity(wrist_coords.len());
for (i, &(along, angle, _r)) in wrist_coords.iter().enumerate() {
let thick = (params.thickness - 1.0) * 0.005;
let ulnar = styloid_bump(angle, PI / 2.0, params.ulnar_prominence);
let radial = styloid_bump(angle, -PI / 2.0, params.radial_prominence);
let tendon = tendon_ridge(angle, along, params.tendon_visibility);
let height_falloff = (-((along - 0.5) / 0.3).powi(2)).exp();
let disp = (thick + ulnar + radial + tendon) * height_falloff;
if disp.abs() > 1e-7 {
displacements.push((i, disp));
}
}
WristResult {
displacements,
flexion_corrective: flex_w,
deviation_corrective: dev_w,
}
}
#[allow(dead_code)]
pub fn blend_wrist_params(a: &WristParams, b: &WristParams, t: f32) -> WristParams {
let t = t.clamp(0.0, 1.0);
let inv = 1.0 - t;
WristParams {
thickness: a.thickness * inv + b.thickness * t,
ulnar_prominence: a.ulnar_prominence * inv + b.ulnar_prominence * t,
radial_prominence: a.radial_prominence * inv + b.radial_prominence * t,
tendon_visibility: a.tendon_visibility * inv + b.tendon_visibility * t,
flexion: a.flexion * inv + b.flexion * t,
deviation: a.deviation * inv + b.deviation * t,
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::f32::consts::PI;
#[test]
fn test_default_params() {
let p = WristParams::default();
assert!((p.thickness - 1.0).abs() < 1e-6);
}
#[test]
fn test_flexion_weight_zero() {
assert!(flexion_weight(0.0).abs() < 1e-5);
}
#[test]
fn test_flexion_weight_max() {
let w = flexion_weight(PI / 3.0);
assert!((w - 1.0).abs() < 1e-5);
}
#[test]
fn test_deviation_weight_zero() {
assert!(deviation_weight(0.0).abs() < 1e-5);
}
#[test]
fn test_styloid_bump_at_centre() {
let b = styloid_bump(PI / 2.0, PI / 2.0, 1.0);
assert!(b > 0.0);
}
#[test]
fn test_styloid_bump_far_away() {
let b = styloid_bump(0.0, PI, 1.0);
assert!(b.abs() < 1e-6);
}
#[test]
fn test_tendon_ridge_zero_vis() {
let t = tendon_ridge(PI, 0.5, 0.0);
assert!(t.abs() < 1e-6);
}
#[test]
fn test_evaluate_empty() {
let r = evaluate_wrist(&[], &WristParams::default());
assert!(r.displacements.is_empty());
}
#[test]
fn test_blend_wrist_midpoint() {
let a = WristParams {
thickness: 0.8,
..Default::default()
};
let b = WristParams {
thickness: 1.2,
..Default::default()
};
let r = blend_wrist_params(&a, &b, 0.5);
assert!((r.thickness - 1.0).abs() < 1e-5);
}
#[test]
fn test_evaluate_produces_output() {
let coords = vec![(0.5, PI, 0.02), (0.5, PI / 2.0, 0.02)];
let params = WristParams {
ulnar_prominence: 1.0,
..Default::default()
};
let r = evaluate_wrist(&coords, ¶ms);
assert!(!r.displacements.is_empty());
}
}