Skip to main content

oxihuman_morph/
humerus_morph.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Humerus length/shape morph — controls upper arm bone proportions.
6
7/// Humerus morph configuration.
8#[derive(Debug, Clone)]
9pub struct HumerusMorph {
10    pub length: f32,
11    pub head_size: f32,
12    pub shaft_curvature: f32,
13    pub epicondyle_width: f32,
14}
15
16impl HumerusMorph {
17    pub fn new() -> Self {
18        Self {
19            length: 0.5,
20            head_size: 0.5,
21            shaft_curvature: 0.0,
22            epicondyle_width: 0.5,
23        }
24    }
25}
26
27impl Default for HumerusMorph {
28    fn default() -> Self {
29        Self::new()
30    }
31}
32
33/// Create a new humerus morph.
34pub fn new_humerus_morph() -> HumerusMorph {
35    HumerusMorph::new()
36}
37
38/// Set humerus length (relative to total arm length).
39pub fn hum_set_length(m: &mut HumerusMorph, v: f32) {
40    m.length = v.clamp(0.0, 1.0);
41}
42
43/// Set humeral head size.
44pub fn hum_set_head_size(m: &mut HumerusMorph, v: f32) {
45    m.head_size = v.clamp(0.0, 1.0);
46}
47
48/// Set shaft anterior curvature (0 = straight, 1 = maximum bow).
49pub fn hum_set_shaft_curvature(m: &mut HumerusMorph, v: f32) {
50    m.shaft_curvature = v.clamp(0.0, 1.0);
51}
52
53/// Set epicondyle width (elbow breadth contribution).
54pub fn hum_set_epicondyle_width(m: &mut HumerusMorph, v: f32) {
55    m.epicondyle_width = v.clamp(0.0, 1.0);
56}
57
58/// Elbow breadth heuristic combining epicondyle and length.
59pub fn hum_elbow_breadth(m: &HumerusMorph) -> f32 {
60    m.epicondyle_width * (0.8 + m.length * 0.2)
61}
62
63/// Serialize to JSON-like string.
64pub fn humerus_morph_to_json(m: &HumerusMorph) -> String {
65    format!(
66        r#"{{"length":{:.4},"head_size":{:.4},"shaft_curvature":{:.4},"epicondyle_width":{:.4}}}"#,
67        m.length, m.head_size, m.shaft_curvature, m.epicondyle_width
68    )
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74
75    #[test]
76    fn test_defaults() {
77        let m = new_humerus_morph();
78        assert!((m.length - 0.5).abs() < 1e-6);
79        assert_eq!(m.shaft_curvature, 0.0);
80    }
81
82    #[test]
83    fn test_length_clamp() {
84        let mut m = new_humerus_morph();
85        hum_set_length(&mut m, 2.0);
86        assert_eq!(m.length, 1.0);
87    }
88
89    #[test]
90    fn test_head_size_set() {
91        let mut m = new_humerus_morph();
92        hum_set_head_size(&mut m, 0.8);
93        assert!((m.head_size - 0.8).abs() < 1e-6);
94    }
95
96    #[test]
97    fn test_curvature_clamp() {
98        let mut m = new_humerus_morph();
99        hum_set_shaft_curvature(&mut m, -1.0);
100        assert_eq!(m.shaft_curvature, 0.0);
101    }
102
103    #[test]
104    fn test_epicondyle_set() {
105        let mut m = new_humerus_morph();
106        hum_set_epicondyle_width(&mut m, 0.7);
107        assert!((m.epicondyle_width - 0.7).abs() < 1e-6);
108    }
109
110    #[test]
111    fn test_elbow_breadth_positive() {
112        let m = new_humerus_morph();
113        assert!(hum_elbow_breadth(&m) > 0.0);
114    }
115
116    #[test]
117    fn test_elbow_breadth_scales_with_epicondyle() {
118        let mut m = new_humerus_morph();
119        let b0 = hum_elbow_breadth(&m);
120        hum_set_epicondyle_width(&mut m, 1.0);
121        let b1 = hum_elbow_breadth(&m);
122        assert!(b1 > b0);
123    }
124
125    #[test]
126    fn test_json_keys() {
127        let m = new_humerus_morph();
128        let s = humerus_morph_to_json(&m);
129        assert!(s.contains("epicondyle_width"));
130    }
131
132    #[test]
133    fn test_clone() {
134        let m = new_humerus_morph();
135        let m2 = m.clone();
136        assert!((m2.head_size - m.head_size).abs() < 1e-6);
137    }
138}