Skip to main content

oxihuman_morph/
forearm_control.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan) / SPDX-License-Identifier: Apache-2.0
2#![allow(dead_code)]
3
4//! Forearm shape morph controls: muscle mass, pronation, wrist taper.
5
6#[allow(dead_code)]
7#[derive(Debug, Clone)]
8pub struct ForearmConfig {
9    pub muscle_range: f32,
10    pub taper_range: f32,
11    pub rotation_range: f32,
12}
13
14#[allow(dead_code)]
15#[derive(Debug, Clone)]
16pub struct ForearmState {
17    pub muscle_mass: f32,
18    pub taper: f32,
19    pub pronation: f32,
20    pub vein_visibility: f32,
21    pub length_ratio: f32,
22}
23
24#[allow(dead_code)]
25#[derive(Debug, Clone)]
26pub struct ForearmMorphWeights {
27    pub muscular: f32,
28    pub thin: f32,
29    pub tapered: f32,
30    pub pronated: f32,
31    pub supinated: f32,
32}
33
34#[allow(dead_code)]
35pub fn default_forearm_config() -> ForearmConfig {
36    ForearmConfig {
37        muscle_range: 0.8,
38        taper_range: 0.5,
39        rotation_range: 0.6,
40    }
41}
42
43#[allow(dead_code)]
44pub fn new_forearm_state() -> ForearmState {
45    ForearmState {
46        muscle_mass: 0.5,
47        taper: 0.5,
48        pronation: 0.5,
49        vein_visibility: 0.0,
50        length_ratio: 0.5,
51    }
52}
53
54#[allow(dead_code)]
55pub fn set_forearm_muscle(state: &mut ForearmState, value: f32) {
56    state.muscle_mass = value.clamp(0.0, 1.0);
57}
58
59#[allow(dead_code)]
60pub fn set_forearm_taper(state: &mut ForearmState, value: f32) {
61    state.taper = value.clamp(0.0, 1.0);
62}
63
64#[allow(dead_code)]
65pub fn set_forearm_pronation(state: &mut ForearmState, value: f32) {
66    state.pronation = value.clamp(0.0, 1.0);
67}
68
69#[allow(dead_code)]
70pub fn set_vein_visibility(state: &mut ForearmState, value: f32) {
71    state.vein_visibility = value.clamp(0.0, 1.0);
72}
73
74#[allow(dead_code)]
75pub fn compute_forearm_weights(state: &ForearmState, cfg: &ForearmConfig) -> ForearmMorphWeights {
76    let m = state.muscle_mass * cfg.muscle_range;
77    let muscular = m.clamp(0.0, 1.0);
78    let thin = (1.0 - m).clamp(0.0, 1.0);
79    let tapered = (state.taper * cfg.taper_range).clamp(0.0, 1.0);
80    let rot = (state.pronation - 0.5) * 2.0 * cfg.rotation_range;
81    let pronated = rot.max(0.0).clamp(0.0, 1.0);
82    let supinated = (-rot).max(0.0).clamp(0.0, 1.0);
83    ForearmMorphWeights {
84        muscular,
85        thin,
86        tapered,
87        pronated,
88        supinated,
89    }
90}
91
92#[allow(dead_code)]
93pub fn forearm_to_json(state: &ForearmState) -> String {
94    format!(
95        r#"{{"muscle_mass":{},"taper":{},"pronation":{},"veins":{},"length":{}}}"#,
96        state.muscle_mass, state.taper, state.pronation, state.vein_visibility, state.length_ratio
97    )
98}
99
100#[allow(dead_code)]
101pub fn blend_forearm_states(a: &ForearmState, b: &ForearmState, t: f32) -> ForearmState {
102    let t = t.clamp(0.0, 1.0);
103    ForearmState {
104        muscle_mass: a.muscle_mass + (b.muscle_mass - a.muscle_mass) * t,
105        taper: a.taper + (b.taper - a.taper) * t,
106        pronation: a.pronation + (b.pronation - a.pronation) * t,
107        vein_visibility: a.vein_visibility + (b.vein_visibility - a.vein_visibility) * t,
108        length_ratio: a.length_ratio + (b.length_ratio - a.length_ratio) * t,
109    }
110}
111
112#[cfg(test)]
113mod tests {
114    use super::*;
115
116    #[test]
117    fn test_default_config() {
118        let c = default_forearm_config();
119        assert!((0.0..=1.0).contains(&c.muscle_range));
120    }
121
122    #[test]
123    fn test_new_state() {
124        let s = new_forearm_state();
125        assert!((s.muscle_mass - 0.5).abs() < 1e-6);
126    }
127
128    #[test]
129    fn test_set_muscle() {
130        let mut s = new_forearm_state();
131        set_forearm_muscle(&mut s, 0.9);
132        assert!((s.muscle_mass - 0.9).abs() < 1e-6);
133    }
134
135    #[test]
136    fn test_set_taper() {
137        let mut s = new_forearm_state();
138        set_forearm_taper(&mut s, 0.3);
139        assert!((s.taper - 0.3).abs() < 1e-6);
140    }
141
142    #[test]
143    fn test_set_pronation_clamp() {
144        let mut s = new_forearm_state();
145        set_forearm_pronation(&mut s, 5.0);
146        assert!((s.pronation - 1.0).abs() < 1e-6);
147    }
148
149    #[test]
150    fn test_compute_weights_range() {
151        let s = new_forearm_state();
152        let cfg = default_forearm_config();
153        let w = compute_forearm_weights(&s, &cfg);
154        assert!((0.0..=1.0).contains(&w.muscular));
155        assert!((0.0..=1.0).contains(&w.thin));
156    }
157
158    #[test]
159    fn test_to_json() {
160        let s = new_forearm_state();
161        let j = forearm_to_json(&s);
162        assert!(j.contains("muscle_mass"));
163    }
164
165    #[test]
166    fn test_blend() {
167        let a = new_forearm_state();
168        let mut b = new_forearm_state();
169        b.muscle_mass = 1.0;
170        let mid = blend_forearm_states(&a, &b, 0.5);
171        assert!((mid.muscle_mass - 0.75).abs() < 1e-6);
172    }
173
174    #[test]
175    fn test_set_veins() {
176        let mut s = new_forearm_state();
177        set_vein_visibility(&mut s, 0.6);
178        assert!((s.vein_visibility - 0.6).abs() < 1e-6);
179    }
180
181    #[test]
182    fn test_blend_identity() {
183        let a = new_forearm_state();
184        let r = blend_forearm_states(&a, &a, 0.5);
185        assert!((r.taper - a.taper).abs() < 1e-6);
186    }
187}