oxihuman_morph/
hand_width_control.rs1#![allow(dead_code)]
4
5#[allow(dead_code)]
9#[derive(Debug, Clone)]
10pub struct HandWidthConfig {
11 pub max_width: f32,
12}
13
14impl Default for HandWidthConfig {
15 fn default() -> Self {
16 HandWidthConfig { max_width: 1.0 }
17 }
18}
19
20#[allow(dead_code)]
22#[derive(Debug, Clone)]
23pub struct HandWidthState {
24 left: f32,
25 right: f32,
26 finger_spread: f32,
28 config: HandWidthConfig,
29}
30
31pub fn default_hand_width_config() -> HandWidthConfig {
33 HandWidthConfig::default()
34}
35
36pub fn new_hand_width_state(config: HandWidthConfig) -> HandWidthState {
38 HandWidthState {
39 left: 0.5,
40 right: 0.5,
41 finger_spread: 0.0,
42 config,
43 }
44}
45
46pub fn hwc_set_left(state: &mut HandWidthState, v: f32) {
48 state.left = v.clamp(0.0, 1.0);
49}
50
51pub fn hwc_set_right(state: &mut HandWidthState, v: f32) {
53 state.right = v.clamp(0.0, 1.0);
54}
55
56pub fn hwc_set_both(state: &mut HandWidthState, v: f32) {
58 let v = v.clamp(0.0, 1.0);
59 state.left = v;
60 state.right = v;
61}
62
63pub fn hwc_set_finger_spread(state: &mut HandWidthState, v: f32) {
65 state.finger_spread = v.clamp(0.0, 1.0);
66}
67
68pub fn hwc_reset(state: &mut HandWidthState) {
70 state.left = 0.5;
71 state.right = 0.5;
72 state.finger_spread = 0.0;
73}
74
75pub fn hwc_is_neutral(state: &HandWidthState) -> bool {
77 (state.left - 0.5).abs() < 1e-5
78 && (state.right - 0.5).abs() < 1e-5
79 && state.finger_spread < 1e-5
80}
81
82pub fn hwc_asymmetry(state: &HandWidthState) -> f32 {
84 (state.left - state.right).abs()
85}
86
87pub fn hwc_effective_width(state: &HandWidthState) -> f32 {
89 let base = (state.left + state.right) * 0.5;
90 (base + state.finger_spread * 0.2).clamp(0.0, 1.0)
91}
92
93pub fn hwc_to_weights(state: &HandWidthState) -> [f32; 3] {
95 [state.left, state.right, state.finger_spread]
96}
97
98pub fn hwc_blend(a: &HandWidthState, b: &HandWidthState, t: f32) -> HandWidthState {
100 let t = t.clamp(0.0, 1.0);
101 HandWidthState {
102 left: a.left + (b.left - a.left) * t,
103 right: a.right + (b.right - a.right) * t,
104 finger_spread: a.finger_spread + (b.finger_spread - a.finger_spread) * t,
105 config: a.config.clone(),
106 }
107}
108
109pub fn hwc_to_json(state: &HandWidthState) -> String {
111 format!(
112 r#"{{"left":{:.4},"right":{:.4},"finger_spread":{:.4}}}"#,
113 state.left, state.right, state.finger_spread
114 )
115}
116
117#[cfg(test)]
121mod tests {
122 use super::*;
123
124 fn make() -> HandWidthState {
125 new_hand_width_state(default_hand_width_config())
126 }
127
128 #[test]
129 fn neutral_on_creation() {
130 assert!(hwc_is_neutral(&make()));
131 }
132
133 #[test]
134 fn set_both_equal() {
135 let mut s = make();
136 hwc_set_both(&mut s, 0.8);
137 assert!((s.left - s.right).abs() < 1e-5);
138 }
139
140 #[test]
141 fn reset_restores_neutral() {
142 let mut s = make();
143 hwc_set_both(&mut s, 0.1);
144 hwc_reset(&mut s);
145 assert!(hwc_is_neutral(&s));
146 }
147
148 #[test]
149 fn asymmetry_zero_equal() {
150 let mut s = make();
151 hwc_set_both(&mut s, 0.7);
152 assert!(hwc_asymmetry(&s) < 1e-5);
153 }
154
155 #[test]
156 fn effective_width_in_range() {
157 let s = make();
158 assert!((0.0..=1.0).contains(&hwc_effective_width(&s)));
159 }
160
161 #[test]
162 fn weights_in_range() {
163 let s = make();
164 for v in hwc_to_weights(&s) {
165 assert!((0.0..=1.0).contains(&v));
166 }
167 }
168
169 #[test]
170 fn blend_midpoint() {
171 let mut a = make();
172 let mut b = make();
173 hwc_set_both(&mut a, 0.0);
174 hwc_set_both(&mut b, 1.0);
175 let m = hwc_blend(&a, &b, 0.5);
176 assert!((m.left - 0.5).abs() < 1e-5);
177 }
178
179 #[test]
180 fn json_has_left() {
181 assert!(hwc_to_json(&make()).contains("left"));
182 }
183
184 #[test]
185 fn finger_spread_clamped() {
186 let mut s = make();
187 hwc_set_finger_spread(&mut s, 2.0);
188 assert!((s.finger_spread - 1.0).abs() < 1e-5);
189 }
190}