oxihuman_morph/
hand_grip_control.rs1#![allow(dead_code)]
4
5#[allow(dead_code)]
9#[derive(Debug, Clone)]
10pub struct HandGripConfig {
11 pub max_curl: f32,
12}
13
14#[allow(dead_code)]
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum HandGripSide {
18 Left,
19 Right,
20}
21
22#[allow(dead_code)]
24#[derive(Debug, Clone)]
25pub struct HandGripState {
26 pub left_curl: f32,
27 pub right_curl: f32,
28 pub palm_compression: f32,
29}
30
31#[allow(dead_code)]
32pub fn default_hand_grip_config() -> HandGripConfig {
33 HandGripConfig { max_curl: 1.0 }
34}
35
36#[allow(dead_code)]
37pub fn new_hand_grip_state() -> HandGripState {
38 HandGripState {
39 left_curl: 0.0,
40 right_curl: 0.0,
41 palm_compression: 0.0,
42 }
43}
44
45#[allow(dead_code)]
46pub fn hg_set_curl(state: &mut HandGripState, cfg: &HandGripConfig, side: HandGripSide, v: f32) {
47 let clamped = v.clamp(0.0, cfg.max_curl);
48 match side {
49 HandGripSide::Left => state.left_curl = clamped,
50 HandGripSide::Right => state.right_curl = clamped,
51 }
52}
53
54#[allow(dead_code)]
55pub fn hg_set_both(state: &mut HandGripState, cfg: &HandGripConfig, v: f32) {
56 let clamped = v.clamp(0.0, cfg.max_curl);
57 state.left_curl = clamped;
58 state.right_curl = clamped;
59}
60
61#[allow(dead_code)]
62pub fn hg_set_palm_compression(state: &mut HandGripState, v: f32) {
63 state.palm_compression = v.clamp(0.0, 1.0);
64}
65
66#[allow(dead_code)]
67pub fn hg_reset(state: &mut HandGripState) {
68 *state = new_hand_grip_state();
69}
70
71#[allow(dead_code)]
72pub fn hg_is_neutral(state: &HandGripState) -> bool {
73 state.left_curl.abs() < 1e-6
74 && state.right_curl.abs() < 1e-6
75 && state.palm_compression.abs() < 1e-6
76}
77
78#[allow(dead_code)]
79pub fn hg_average_curl(state: &HandGripState) -> f32 {
80 (state.left_curl + state.right_curl) * 0.5
81}
82
83#[allow(dead_code)]
84pub fn hg_symmetry(state: &HandGripState) -> f32 {
85 (state.left_curl - state.right_curl).abs()
86}
87
88#[allow(dead_code)]
89pub fn hg_blend(a: &HandGripState, b: &HandGripState, t: f32) -> HandGripState {
90 let t = t.clamp(0.0, 1.0);
91 HandGripState {
92 left_curl: a.left_curl + (b.left_curl - a.left_curl) * t,
93 right_curl: a.right_curl + (b.right_curl - a.right_curl) * t,
94 palm_compression: a.palm_compression + (b.palm_compression - a.palm_compression) * t,
95 }
96}
97
98#[allow(dead_code)]
99pub fn hg_to_weights(state: &HandGripState) -> Vec<(String, f32)> {
100 vec![
101 ("hand_grip_curl_l".to_string(), state.left_curl),
102 ("hand_grip_curl_r".to_string(), state.right_curl),
103 ("palm_compression".to_string(), state.palm_compression),
104 ]
105}
106
107#[allow(dead_code)]
108pub fn hg_to_json(state: &HandGripState) -> String {
109 format!(
110 r#"{{"left_curl":{:.4},"right_curl":{:.4},"palm_compression":{:.4}}}"#,
111 state.left_curl, state.right_curl, state.palm_compression
112 )
113}
114
115#[cfg(test)]
116mod tests {
117 use super::*;
118
119 #[test]
120 fn default_config() {
121 let cfg = default_hand_grip_config();
122 assert!((cfg.max_curl - 1.0).abs() < 1e-6);
123 }
124
125 #[test]
126 fn new_state_neutral() {
127 let s = new_hand_grip_state();
128 assert!(hg_is_neutral(&s));
129 }
130
131 #[test]
132 fn set_curl_left() {
133 let cfg = default_hand_grip_config();
134 let mut s = new_hand_grip_state();
135 hg_set_curl(&mut s, &cfg, HandGripSide::Left, 0.7);
136 assert!((s.left_curl - 0.7).abs() < 1e-6);
137 }
138
139 #[test]
140 fn set_curl_clamps() {
141 let cfg = default_hand_grip_config();
142 let mut s = new_hand_grip_state();
143 hg_set_curl(&mut s, &cfg, HandGripSide::Right, 5.0);
144 assert!((s.right_curl - 1.0).abs() < 1e-6);
145 }
146
147 #[test]
148 fn set_both_symmetric() {
149 let cfg = default_hand_grip_config();
150 let mut s = new_hand_grip_state();
151 hg_set_both(&mut s, &cfg, 0.5);
152 assert!(hg_symmetry(&s) < 1e-6);
153 }
154
155 #[test]
156 fn set_palm_compression() {
157 let mut s = new_hand_grip_state();
158 hg_set_palm_compression(&mut s, 0.6);
159 assert!((s.palm_compression - 0.6).abs() < 1e-6);
160 }
161
162 #[test]
163 fn reset_clears() {
164 let cfg = default_hand_grip_config();
165 let mut s = new_hand_grip_state();
166 hg_set_both(&mut s, &cfg, 0.8);
167 hg_reset(&mut s);
168 assert!(hg_is_neutral(&s));
169 }
170
171 #[test]
172 fn blend_midpoint() {
173 let a = new_hand_grip_state();
174 let cfg = default_hand_grip_config();
175 let mut b = new_hand_grip_state();
176 hg_set_both(&mut b, &cfg, 1.0);
177 let m = hg_blend(&a, &b, 0.5);
178 assert!((m.left_curl - 0.5).abs() < 1e-6);
179 }
180
181 #[test]
182 fn to_weights_count() {
183 let s = new_hand_grip_state();
184 assert_eq!(hg_to_weights(&s).len(), 3);
185 }
186
187 #[test]
188 fn to_json_fields() {
189 let s = new_hand_grip_state();
190 let j = hg_to_json(&s);
191 assert!(j.contains("left_curl"));
192 }
193}