#![allow(dead_code)]
use std::f32::consts::PI;
#[allow(dead_code)]
#[derive(Debug, Clone, PartialEq)]
pub struct DeltoidParams {
pub size: f32,
pub anterior: f32,
pub lateral: f32,
pub posterior: f32,
pub abduction_angle: f32,
pub head_definition: f32,
}
impl Default for DeltoidParams {
fn default() -> Self {
Self {
size: 0.5,
anterior: 0.33,
lateral: 0.34,
posterior: 0.33,
abduction_angle: 0.0,
head_definition: 0.2,
}
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct DeltoidResult {
pub displacements: Vec<(usize, f32)>,
pub abduction_corrective: f32,
pub total_volume: f32,
}
#[allow(dead_code)]
pub fn height_profile(t: f32) -> f32 {
let t = t.clamp(0.0, 1.0);
let peak = 0.3;
let sigma = 0.25;
(-(t - peak).powi(2) / (2.0 * sigma * sigma)).exp()
}
#[allow(dead_code)]
pub fn head_angular_profile(theta: f32, head_centre: f32, spread: f32) -> f32 {
if spread <= 0.0 {
return 0.0;
}
let diff = (theta - head_centre).abs();
let diff = if diff > PI { 2.0 * PI - diff } else { diff };
let t = (diff / spread).clamp(0.0, 1.0);
0.5 * (1.0 + (PI * t).cos())
}
#[allow(dead_code)]
pub fn abduction_corrective(angle: f32) -> f32 {
let t = (angle / (PI / 2.0)).clamp(0.0, 1.0);
t * t * (3.0 - 2.0 * t)
}
#[allow(dead_code)]
pub fn separation_groove(theta: f32, definition: f32) -> f32 {
let d = definition.clamp(0.0, 1.0);
let boundaries = [0.0, 2.0 * PI / 3.0, 4.0 * PI / 3.0];
let mut min_dist = f32::MAX;
for &b in &boundaries {
let diff = (theta - b).abs();
let diff = if diff > PI { 2.0 * PI - diff } else { diff };
min_dist = min_dist.min(diff);
}
if min_dist < 0.15 {
-d * 0.003 * (1.0 - min_dist / 0.15)
} else {
0.0
}
}
#[allow(dead_code)]
pub fn evaluate_deltoid(
shoulder_coords: &[(f32, f32, f32)],
params: &DeltoidParams,
) -> DeltoidResult {
let abd_w = abduction_corrective(params.abduction_angle);
let head_centres = [0.0_f32, 2.0 * PI / 3.0, 4.0 * PI / 3.0];
let head_weights = [params.anterior, params.lateral, params.posterior];
let spread = PI / 3.0;
let mut displacements = Vec::with_capacity(shoulder_coords.len());
let mut total_vol = 0.0_f32;
for (i, &(ht, theta, _radius)) in shoulder_coords.iter().enumerate() {
let hp = height_profile(ht);
let mut angular = 0.0_f32;
for (j, ¢re) in head_centres.iter().enumerate() {
angular += head_weights[j] * head_angular_profile(theta, centre, spread);
}
let groove = separation_groove(theta, params.head_definition);
let disp = params.size * hp * angular * 0.02 + groove;
if disp.abs() > 1e-7 {
displacements.push((i, disp));
total_vol += disp.abs();
}
}
DeltoidResult {
displacements,
abduction_corrective: abd_w,
total_volume: total_vol * 0.001,
}
}
#[allow(dead_code)]
pub fn blend_deltoid_params(a: &DeltoidParams, b: &DeltoidParams, t: f32) -> DeltoidParams {
let t = t.clamp(0.0, 1.0);
let inv = 1.0 - t;
DeltoidParams {
size: a.size * inv + b.size * t,
anterior: a.anterior * inv + b.anterior * t,
lateral: a.lateral * inv + b.lateral * t,
posterior: a.posterior * inv + b.posterior * t,
abduction_angle: a.abduction_angle * inv + b.abduction_angle * t,
head_definition: a.head_definition * inv + b.head_definition * t,
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::f32::consts::PI;
#[test]
fn test_default_params() {
let p = DeltoidParams::default();
let sum = p.anterior + p.lateral + p.posterior;
assert!((sum - 1.0).abs() < 0.01);
}
#[test]
fn test_height_profile_peak() {
let v = height_profile(0.3);
assert!(v > 0.95);
}
#[test]
fn test_height_profile_edges() {
let h0 = height_profile(0.0);
let h1 = height_profile(1.0);
let hp = height_profile(0.3);
assert!(hp > h0);
assert!(hp > h1);
}
#[test]
fn test_head_angular_centre() {
let v = head_angular_profile(0.5, 0.5, 1.0);
assert!((v - 1.0).abs() < 1e-5);
}
#[test]
fn test_head_angular_zero_spread() {
assert_eq!(head_angular_profile(0.0, 0.0, 0.0), 0.0);
}
#[test]
fn test_abduction_corrective_bounds() {
let w0 = abduction_corrective(0.0);
let w1 = abduction_corrective(PI / 2.0);
assert!(w0.abs() < 1e-5);
assert!((w1 - 1.0).abs() < 1e-5);
}
#[test]
fn test_separation_groove_far() {
let g = separation_groove(PI / 3.0, 1.0);
assert!(g.abs() < 1e-6);
}
#[test]
fn test_evaluate_empty() {
let r = evaluate_deltoid(&[], &DeltoidParams::default());
assert!(r.displacements.is_empty());
}
#[test]
fn test_blend_deltoid_midpoint() {
let a = DeltoidParams { size: 0.0, ..Default::default() };
let b = DeltoidParams { size: 1.0, ..Default::default() };
let r = blend_deltoid_params(&a, &b, 0.5);
assert!((r.size - 0.5).abs() < 1e-6);
}
#[test]
fn test_evaluate_produces_output() {
let coords = vec![(0.3, 0.0, 0.05), (0.3, 2.0, 0.05), (0.3, 4.0, 0.05)];
let params = DeltoidParams { size: 1.0, ..Default::default() };
let r = evaluate_deltoid(&coords, ¶ms);
assert!(!r.displacements.is_empty());
}
}