Skip to main content

oxihuman_morph/
scapula_morph.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Scapula shape morph — controls scapular size, winging, and spine prominence.
6
7/// Scapula morph configuration.
8#[derive(Debug, Clone)]
9pub struct ScapulaMorph {
10    pub size: f32,
11    pub winging: f32,
12    pub spine_prominence: f32,
13    pub rotation: f32,
14}
15
16impl ScapulaMorph {
17    pub fn new() -> Self {
18        Self {
19            size: 0.5,
20            winging: 0.0,
21            spine_prominence: 0.5,
22            rotation: 0.0,
23        }
24    }
25}
26
27impl Default for ScapulaMorph {
28    fn default() -> Self {
29        Self::new()
30    }
31}
32
33/// Create a new scapula morph.
34pub fn new_scapula_morph() -> ScapulaMorph {
35    ScapulaMorph::new()
36}
37
38/// Set scapula overall size.
39pub fn scap_set_size(m: &mut ScapulaMorph, v: f32) {
40    m.size = v.clamp(0.0, 1.0);
41}
42
43/// Set scapular winging (0 = flat against thorax, 1 = fully winged).
44pub fn scap_set_winging(m: &mut ScapulaMorph, v: f32) {
45    m.winging = v.clamp(0.0, 1.0);
46}
47
48/// Set spine of scapula prominence.
49pub fn scap_set_spine_prominence(m: &mut ScapulaMorph, v: f32) {
50    m.spine_prominence = v.clamp(0.0, 1.0);
51}
52
53/// Set upward/downward rotation bias (-1 = downward, 0 = neutral, 1 = upward).
54pub fn scap_set_rotation(m: &mut ScapulaMorph, v: f32) {
55    m.rotation = v.clamp(-1.0, 1.0);
56}
57
58/// Surface visibility score (winging makes scapula more visible dorsally).
59pub fn scap_visibility(m: &ScapulaMorph) -> f32 {
60    (m.spine_prominence * 0.6 + m.winging * 0.4).clamp(0.0, 1.0)
61}
62
63/// Serialize to JSON-like string.
64pub fn scapula_morph_to_json(m: &ScapulaMorph) -> String {
65    format!(
66        r#"{{"size":{:.4},"winging":{:.4},"spine_prominence":{:.4},"rotation":{:.4}}}"#,
67        m.size, m.winging, m.spine_prominence, m.rotation
68    )
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74
75    #[test]
76    fn test_defaults() {
77        let m = new_scapula_morph();
78        assert_eq!(m.winging, 0.0);
79        assert!((m.size - 0.5).abs() < 1e-6);
80    }
81
82    #[test]
83    fn test_size_clamp() {
84        let mut m = new_scapula_morph();
85        scap_set_size(&mut m, 5.0);
86        assert_eq!(m.size, 1.0);
87    }
88
89    #[test]
90    fn test_winging_set() {
91        let mut m = new_scapula_morph();
92        scap_set_winging(&mut m, 0.6);
93        assert!((m.winging - 0.6).abs() < 1e-6);
94    }
95
96    #[test]
97    fn test_spine_prominence_set() {
98        let mut m = new_scapula_morph();
99        scap_set_spine_prominence(&mut m, 0.9);
100        assert!((m.spine_prominence - 0.9).abs() < 1e-6);
101    }
102
103    #[test]
104    fn test_rotation_clamp() {
105        let mut m = new_scapula_morph();
106        scap_set_rotation(&mut m, 3.0);
107        assert_eq!(m.rotation, 1.0);
108    }
109
110    #[test]
111    fn test_visibility_range() {
112        let m = new_scapula_morph();
113        let v = scap_visibility(&m);
114        assert!((0.0..=1.0).contains(&v));
115    }
116
117    #[test]
118    fn test_visibility_increases_with_winging() {
119        let mut m = new_scapula_morph();
120        let v0 = scap_visibility(&m);
121        scap_set_winging(&mut m, 1.0);
122        let v1 = scap_visibility(&m);
123        assert!(v1 > v0);
124    }
125
126    #[test]
127    fn test_json_keys() {
128        let m = new_scapula_morph();
129        let s = scapula_morph_to_json(&m);
130        assert!(s.contains("spine_prominence"));
131    }
132
133    #[test]
134    fn test_clone() {
135        let m = new_scapula_morph();
136        let m2 = m.clone();
137        assert!((m2.size - m.size).abs() < 1e-6);
138    }
139}