Skip to main content

oxihuman_morph/
clavicle_control.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan) / SPDX-License-Identifier: Apache-2.0
2#![allow(dead_code)]
3
4//! Clavicle (collarbone) shape and position morph controls.
5
6#[allow(dead_code)]
7#[derive(Debug, Clone)]
8pub struct ClavicleConfig {
9    pub prominence: f32,
10    pub length_ratio: f32,
11    pub angle_range: f32,
12}
13
14#[allow(dead_code)]
15#[derive(Debug, Clone)]
16pub struct ClavicleState {
17    pub prominence: f32,
18    pub angle: f32,
19    pub length: f32,
20    pub left_offset: f32,
21    pub right_offset: f32,
22}
23
24#[allow(dead_code)]
25#[derive(Debug, Clone)]
26pub struct ClavicleMorphWeights {
27    pub prominent: f32,
28    pub flat: f32,
29    pub angled_up: f32,
30    pub angled_down: f32,
31    pub wide: f32,
32}
33
34#[allow(dead_code)]
35pub fn default_clavicle_config() -> ClavicleConfig {
36    ClavicleConfig {
37        prominence: 0.5,
38        length_ratio: 0.5,
39        angle_range: 0.3,
40    }
41}
42
43#[allow(dead_code)]
44pub fn new_clavicle_state() -> ClavicleState {
45    ClavicleState {
46        prominence: 0.5,
47        angle: 0.5,
48        length: 0.5,
49        left_offset: 0.0,
50        right_offset: 0.0,
51    }
52}
53
54#[allow(dead_code)]
55pub fn set_clavicle_prominence(state: &mut ClavicleState, value: f32) {
56    state.prominence = value.clamp(0.0, 1.0);
57}
58
59#[allow(dead_code)]
60pub fn set_clavicle_angle(state: &mut ClavicleState, value: f32) {
61    state.angle = value.clamp(0.0, 1.0);
62}
63
64#[allow(dead_code)]
65pub fn set_clavicle_length(state: &mut ClavicleState, value: f32) {
66    state.length = value.clamp(0.0, 1.0);
67}
68
69#[allow(dead_code)]
70pub fn set_clavicle_offset(state: &mut ClavicleState, left: f32, right: f32) {
71    state.left_offset = left.clamp(-1.0, 1.0);
72    state.right_offset = right.clamp(-1.0, 1.0);
73}
74
75#[allow(dead_code)]
76pub fn compute_clavicle_weights(
77    state: &ClavicleState,
78    cfg: &ClavicleConfig,
79) -> ClavicleMorphWeights {
80    let p = state.prominence * cfg.prominence;
81    let prominent = p.clamp(0.0, 1.0);
82    let flat = (1.0 - p).clamp(0.0, 1.0);
83    let angle_bias = (state.angle - 0.5) * 2.0 * cfg.angle_range;
84    let angled_up = angle_bias.max(0.0).clamp(0.0, 1.0);
85    let angled_down = (-angle_bias).max(0.0).clamp(0.0, 1.0);
86    let wide = (state.length * cfg.length_ratio).clamp(0.0, 1.0);
87    ClavicleMorphWeights {
88        prominent,
89        flat,
90        angled_up,
91        angled_down,
92        wide,
93    }
94}
95
96#[allow(dead_code)]
97pub fn clavicle_to_json(state: &ClavicleState) -> String {
98    format!(
99        r#"{{"prominence":{},"angle":{},"length":{},"left_offset":{},"right_offset":{}}}"#,
100        state.prominence, state.angle, state.length, state.left_offset, state.right_offset
101    )
102}
103
104#[allow(dead_code)]
105pub fn blend_clavicle_states(a: &ClavicleState, b: &ClavicleState, t: f32) -> ClavicleState {
106    let t = t.clamp(0.0, 1.0);
107    ClavicleState {
108        prominence: a.prominence + (b.prominence - a.prominence) * t,
109        angle: a.angle + (b.angle - a.angle) * t,
110        length: a.length + (b.length - a.length) * t,
111        left_offset: a.left_offset + (b.left_offset - a.left_offset) * t,
112        right_offset: a.right_offset + (b.right_offset - a.right_offset) * t,
113    }
114}
115
116#[cfg(test)]
117mod tests {
118    use super::*;
119
120    #[test]
121    fn test_default_config() {
122        let c = default_clavicle_config();
123        assert!((0.0..=1.0).contains(&c.prominence));
124    }
125
126    #[test]
127    fn test_new_state() {
128        let s = new_clavicle_state();
129        assert!((s.prominence - 0.5).abs() < 1e-6);
130    }
131
132    #[test]
133    fn test_set_prominence() {
134        let mut s = new_clavicle_state();
135        set_clavicle_prominence(&mut s, 0.9);
136        assert!((s.prominence - 0.9).abs() < 1e-6);
137    }
138
139    #[test]
140    fn test_set_angle_clamp() {
141        let mut s = new_clavicle_state();
142        set_clavicle_angle(&mut s, 1.5);
143        assert!((s.angle - 1.0).abs() < 1e-6);
144    }
145
146    #[test]
147    fn test_set_length() {
148        let mut s = new_clavicle_state();
149        set_clavicle_length(&mut s, 0.7);
150        assert!((s.length - 0.7).abs() < 1e-6);
151    }
152
153    #[test]
154    fn test_offset() {
155        let mut s = new_clavicle_state();
156        set_clavicle_offset(&mut s, 0.3, -0.2);
157        assert!((s.left_offset - 0.3).abs() < 1e-6);
158        assert!((s.right_offset - (-0.2)).abs() < 1e-6);
159    }
160
161    #[test]
162    fn test_compute_weights() {
163        let s = new_clavicle_state();
164        let cfg = default_clavicle_config();
165        let w = compute_clavicle_weights(&s, &cfg);
166        assert!((0.0..=1.0).contains(&w.prominent));
167        assert!((0.0..=1.0).contains(&w.flat));
168    }
169
170    #[test]
171    fn test_to_json() {
172        let s = new_clavicle_state();
173        let j = clavicle_to_json(&s);
174        assert!(j.contains("prominence"));
175    }
176
177    #[test]
178    fn test_blend() {
179        let a = new_clavicle_state();
180        let mut b = new_clavicle_state();
181        b.prominence = 1.0;
182        let mid = blend_clavicle_states(&a, &b, 0.5);
183        assert!((mid.prominence - 0.75).abs() < 1e-6);
184    }
185
186    #[test]
187    fn test_blend_identity() {
188        let a = new_clavicle_state();
189        let r = blend_clavicle_states(&a, &a, 0.3);
190        assert!((r.angle - a.angle).abs() < 1e-6);
191    }
192}