#![allow(dead_code)]
use std::f32::consts::PI;
#[allow(dead_code)]
#[derive(Debug, Clone, PartialEq)]
pub struct KneeParams {
pub patella_prominence: f32,
pub patella_width: f32,
pub flexion: f32,
pub fat_pad: f32,
pub popliteal_depth: f32,
pub valgus: f32,
}
impl Default for KneeParams {
fn default() -> Self {
Self {
patella_prominence: 0.5,
patella_width: 1.0,
flexion: 0.0,
fat_pad: 0.2,
popliteal_depth: 0.3,
valgus: 0.0,
}
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct KneeResult {
pub displacements: Vec<(usize, [f32; 3])>,
pub flexion_corrective: f32,
pub skin_fold_weight: f32,
}
#[allow(dead_code)]
pub fn flexion_corrective(angle: f32) -> f32 {
let t = (angle / PI).clamp(0.0, 1.0);
t * t * (3.0 - 2.0 * t)
}
#[allow(dead_code)]
pub fn patella_profile(angle: f32, height_t: f32, prominence: f32, width: f32) -> f32 {
let angular = 0.5 * (1.0 + (angle - PI).cos());
if angular < 0.3 {
return 0.0;
}
let h_factor = (-((height_t - 0.5) / (0.15 * width)).powi(2)).exp();
prominence * 0.01 * angular * h_factor
}
#[allow(dead_code)]
pub fn popliteal_profile(angle: f32, height_t: f32, depth: f32) -> f32 {
let angular = 0.5 * (1.0 + angle.cos());
if angular < 0.3 {
return 0.0;
}
let h_factor = (-((height_t - 0.5) / 0.12).powi(2)).exp();
-depth * 0.005 * angular * h_factor
}
#[allow(dead_code)]
pub fn fat_pad_profile(angle: f32, height_t: f32, fat: f32) -> f32 {
let angular = 0.5 * (1.0 + (angle - PI).cos());
if angular < 0.3 {
return 0.0;
}
let h_factor = if (0.6..=0.8).contains(&height_t) {
1.0 - ((height_t - 0.7) / 0.1).abs()
} else {
0.0
};
fat * 0.004 * angular * h_factor
}
#[allow(dead_code)]
pub fn skin_fold_weight(flexion: f32) -> f32 {
let t = (flexion / (PI * 0.75)).clamp(0.0, 1.0);
t * t
}
#[allow(dead_code)]
pub fn evaluate_knee(knee_coords: &[(f32, f32, f32)], params: &KneeParams) -> KneeResult {
let flex_w = flexion_corrective(params.flexion);
let fold_w = skin_fold_weight(params.flexion);
let mut disps = Vec::with_capacity(knee_coords.len());
for (i, &(ht, angle, _r)) in knee_coords.iter().enumerate() {
let pat = patella_profile(angle, ht, params.patella_prominence, params.patella_width);
let pop = popliteal_profile(angle, ht, params.popliteal_depth);
let fp = fat_pad_profile(angle, ht, params.fat_pad);
let radial = pat + pop + fp;
if radial.abs() > 1e-7 {
let nx = angle.cos();
let nz = angle.sin();
disps.push((i, [nx * radial, 0.0, nz * radial]));
}
}
KneeResult {
displacements: disps,
flexion_corrective: flex_w,
skin_fold_weight: fold_w,
}
}
#[allow(dead_code)]
pub fn blend_knee_params(a: &KneeParams, b: &KneeParams, t: f32) -> KneeParams {
let t = t.clamp(0.0, 1.0);
let inv = 1.0 - t;
KneeParams {
patella_prominence: a.patella_prominence * inv + b.patella_prominence * t,
patella_width: a.patella_width * inv + b.patella_width * t,
flexion: a.flexion * inv + b.flexion * t,
fat_pad: a.fat_pad * inv + b.fat_pad * t,
popliteal_depth: a.popliteal_depth * inv + b.popliteal_depth * t,
valgus: a.valgus * inv + b.valgus * t,
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::f32::consts::PI;
#[test]
fn test_default_params() {
let p = KneeParams::default();
assert!((0.0..=1.0).contains(&p.patella_prominence));
}
#[test]
fn test_flexion_corrective_zero() {
assert!(flexion_corrective(0.0).abs() < 1e-5);
}
#[test]
fn test_flexion_corrective_full() {
let w = flexion_corrective(PI);
assert!((w - 1.0).abs() < 1e-5);
}
#[test]
fn test_patella_anterior() {
let p = patella_profile(PI, 0.5, 1.0, 1.0);
assert!(p > 0.0);
}
#[test]
fn test_patella_posterior() {
let p = patella_profile(0.0, 0.5, 1.0, 1.0);
assert!(p.abs() < 1e-6);
}
#[test]
fn test_popliteal_posterior() {
let p = popliteal_profile(0.0, 0.5, 1.0);
assert!(p < 0.0);
}
#[test]
fn test_fat_pad_below_patella() {
let f = fat_pad_profile(PI, 0.7, 1.0);
assert!(f > 0.0);
}
#[test]
fn test_skin_fold_weight_zero() {
assert!(skin_fold_weight(0.0).abs() < 1e-5);
}
#[test]
fn test_evaluate_empty() {
let r = evaluate_knee(&[], &KneeParams::default());
assert!(r.displacements.is_empty());
}
#[test]
fn test_blend_knee_midpoint() {
let a = KneeParams {
patella_prominence: 0.0,
..Default::default()
};
let b = KneeParams {
patella_prominence: 1.0,
..Default::default()
};
let r = blend_knee_params(&a, &b, 0.5);
assert!((r.patella_prominence - 0.5).abs() < 1e-6);
}
}