Skip to main content

oxihuman_morph/
calf_control.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3
4//! Calf muscle shape morph control.
5
6#![allow(dead_code)]
7
8#[allow(dead_code)]
9#[derive(Debug, Clone)]
10pub struct CalfConfig {
11    pub max_muscle_size: f32,
12}
13
14#[allow(dead_code)]
15#[derive(Debug, Clone)]
16pub struct CalfState {
17    pub muscle_l: f32,
18    pub muscle_r: f32,
19    pub definition_l: f32,
20    pub definition_r: f32,
21}
22
23#[allow(dead_code)]
24pub fn default_calf_config() -> CalfConfig {
25    CalfConfig {
26        max_muscle_size: 1.0,
27    }
28}
29
30#[allow(dead_code)]
31pub fn new_calf_state() -> CalfState {
32    CalfState {
33        muscle_l: 0.0,
34        muscle_r: 0.0,
35        definition_l: 0.0,
36        definition_r: 0.0,
37    }
38}
39
40#[allow(dead_code)]
41pub fn calf_set_muscle(state: &mut CalfState, cfg: &CalfConfig, left: f32, right: f32) {
42    state.muscle_l = left.clamp(0.0, cfg.max_muscle_size);
43    state.muscle_r = right.clamp(0.0, cfg.max_muscle_size);
44}
45
46#[allow(dead_code)]
47pub fn calf_set_definition(state: &mut CalfState, left: f32, right: f32) {
48    state.definition_l = left.clamp(0.0, 1.0);
49    state.definition_r = right.clamp(0.0, 1.0);
50}
51
52#[allow(dead_code)]
53pub fn calf_mirror(state: &mut CalfState) {
54    let avg_m = (state.muscle_l + state.muscle_r) * 0.5;
55    let avg_d = (state.definition_l + state.definition_r) * 0.5;
56    state.muscle_l = avg_m;
57    state.muscle_r = avg_m;
58    state.definition_l = avg_d;
59    state.definition_r = avg_d;
60}
61
62#[allow(dead_code)]
63pub fn calf_reset(state: &mut CalfState) {
64    *state = new_calf_state();
65}
66
67#[allow(dead_code)]
68pub fn calf_to_weights(state: &CalfState) -> Vec<(String, f32)> {
69    vec![
70        ("calf_muscle_l".to_string(), state.muscle_l),
71        ("calf_muscle_r".to_string(), state.muscle_r),
72        ("calf_definition_l".to_string(), state.definition_l),
73        ("calf_definition_r".to_string(), state.definition_r),
74    ]
75}
76
77#[allow(dead_code)]
78pub fn calf_to_json(state: &CalfState) -> String {
79    format!(
80        r#"{{"muscle_l":{:.4},"muscle_r":{:.4},"definition_l":{:.4},"definition_r":{:.4}}}"#,
81        state.muscle_l, state.muscle_r, state.definition_l, state.definition_r
82    )
83}
84
85// ── New canonical structs/functions required by lib.rs re-export ──────────────
86
87/// Canonical calf control struct.
88#[allow(dead_code)]
89#[derive(Debug, Clone)]
90pub struct CalfControl {
91    pub width: f32,
92    pub length: f32,
93    pub muscle_tone: f32,
94}
95
96/// Returns a default `CalfControl`.
97#[allow(dead_code)]
98pub fn default_calf_control() -> CalfControl {
99    CalfControl {
100        width: 0.5,
101        length: 0.5,
102        muscle_tone: 0.0,
103    }
104}
105
106/// Applies calf control values to a weight slice.
107#[allow(dead_code)]
108pub fn apply_calf_control(weights: &mut [f32], cc: &CalfControl) {
109    if !weights.is_empty() {
110        weights[0] = cc.width;
111    }
112    if weights.len() > 1 {
113        weights[1] = cc.length;
114    }
115    if weights.len() > 2 {
116        weights[2] = cc.muscle_tone;
117    }
118}
119
120/// Linearly blends two `CalfControl` values by `t` in [0, 1].
121#[allow(dead_code)]
122pub fn calf_control_blend(a: &CalfControl, b: &CalfControl, t: f32) -> CalfControl {
123    let t = t.clamp(0.0, 1.0);
124    CalfControl {
125        width: a.width + (b.width - a.width) * t,
126        length: a.length + (b.length - a.length) * t,
127        muscle_tone: a.muscle_tone + (b.muscle_tone - a.muscle_tone) * t,
128    }
129}
130
131#[cfg(test)]
132mod tests {
133    use super::*;
134
135    #[test]
136    fn test_default_config() {
137        let cfg = default_calf_config();
138        assert_eq!(cfg.max_muscle_size, 1.0);
139    }
140
141    #[test]
142    fn test_new_state_zeros() {
143        let s = new_calf_state();
144        assert_eq!(s.muscle_l, 0.0);
145        assert_eq!(s.definition_r, 0.0);
146    }
147
148    #[test]
149    fn test_set_muscle_clamps() {
150        let cfg = default_calf_config();
151        let mut s = new_calf_state();
152        calf_set_muscle(&mut s, &cfg, 2.0, -0.5);
153        assert_eq!(s.muscle_l, 1.0);
154        assert_eq!(s.muscle_r, 0.0);
155    }
156
157    #[test]
158    fn test_set_definition_clamps() {
159        let mut s = new_calf_state();
160        calf_set_definition(&mut s, 0.6, 1.5);
161        assert!((s.definition_l - 0.6).abs() < 1e-6);
162        assert_eq!(s.definition_r, 1.0);
163    }
164
165    #[test]
166    fn test_mirror_averages() {
167        let mut s = new_calf_state();
168        s.muscle_l = 0.3;
169        s.muscle_r = 0.7;
170        calf_mirror(&mut s);
171        assert!((s.muscle_l - 0.5).abs() < 1e-6);
172        assert!((s.muscle_r - 0.5).abs() < 1e-6);
173    }
174
175    #[test]
176    fn test_reset() {
177        let cfg = default_calf_config();
178        let mut s = new_calf_state();
179        calf_set_muscle(&mut s, &cfg, 0.8, 0.8);
180        calf_reset(&mut s);
181        assert_eq!(s.muscle_l, 0.0);
182    }
183
184    #[test]
185    fn test_to_weights_count() {
186        let s = new_calf_state();
187        assert_eq!(calf_to_weights(&s).len(), 4);
188    }
189
190    #[test]
191    fn test_to_json_has_keys() {
192        let s = new_calf_state();
193        let j = calf_to_json(&s);
194        assert!(j.contains("muscle_l"));
195        assert!(j.contains("definition_r"));
196    }
197
198    #[test]
199    fn test_set_muscle_valid() {
200        let cfg = default_calf_config();
201        let mut s = new_calf_state();
202        calf_set_muscle(&mut s, &cfg, 0.4, 0.6);
203        assert!((s.muscle_l - 0.4).abs() < 1e-6);
204        assert!((s.muscle_r - 0.6).abs() < 1e-6);
205    }
206}