oxihuman_morph/
foot_heel_control.rs1#![allow(dead_code)]
4
5#[allow(dead_code)]
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum FootSide {
11 Left,
12 Right,
13}
14
15#[allow(dead_code)]
17#[derive(Debug, Clone)]
18pub struct FootHeelConfig {
19 pub max_pad: f32,
20}
21
22impl Default for FootHeelConfig {
23 fn default() -> Self {
24 FootHeelConfig { max_pad: 1.0 }
25 }
26}
27
28#[allow(dead_code)]
30#[derive(Debug, Clone)]
31pub struct FootHeelEntry {
32 pub pad: f32,
33 pub calcaneus: f32,
34}
35
36#[allow(dead_code)]
38#[derive(Debug, Clone)]
39pub struct FootHeelState {
40 left: FootHeelEntry,
41 right: FootHeelEntry,
42 config: FootHeelConfig,
43}
44
45pub fn default_foot_heel_config() -> FootHeelConfig {
47 FootHeelConfig::default()
48}
49
50pub fn new_foot_heel_state(config: FootHeelConfig) -> FootHeelState {
52 FootHeelState {
53 left: FootHeelEntry {
54 pad: 0.5,
55 calcaneus: 0.0,
56 },
57 right: FootHeelEntry {
58 pad: 0.5,
59 calcaneus: 0.0,
60 },
61 config,
62 }
63}
64
65fn entry_mut(state: &mut FootHeelState, side: FootSide) -> &mut FootHeelEntry {
66 match side {
67 FootSide::Left => &mut state.left,
68 FootSide::Right => &mut state.right,
69 }
70}
71
72fn entry_ref(state: &FootHeelState, side: FootSide) -> &FootHeelEntry {
73 match side {
74 FootSide::Left => &state.left,
75 FootSide::Right => &state.right,
76 }
77}
78
79pub fn fhc_set_pad(state: &mut FootHeelState, side: FootSide, v: f32) {
81 entry_mut(state, side).pad = v.clamp(0.0, 1.0);
82}
83
84pub fn fhc_set_calcaneus(state: &mut FootHeelState, side: FootSide, v: f32) {
86 entry_mut(state, side).calcaneus = v.clamp(0.0, 1.0);
87}
88
89pub fn fhc_set_both_pad(state: &mut FootHeelState, v: f32) {
91 let v = v.clamp(0.0, 1.0);
92 state.left.pad = v;
93 state.right.pad = v;
94}
95
96pub fn fhc_reset(state: &mut FootHeelState) {
98 state.left = FootHeelEntry {
99 pad: 0.5,
100 calcaneus: 0.0,
101 };
102 state.right = FootHeelEntry {
103 pad: 0.5,
104 calcaneus: 0.0,
105 };
106}
107
108pub fn fhc_is_neutral(state: &FootHeelState) -> bool {
110 (state.left.pad - 0.5).abs() < 1e-5
111 && (state.right.pad - 0.5).abs() < 1e-5
112 && state.left.calcaneus < 1e-5
113 && state.right.calcaneus < 1e-5
114}
115
116pub fn fhc_pad(state: &FootHeelState, side: FootSide) -> f32 {
118 entry_ref(state, side).pad
119}
120
121pub fn fhc_pad_asymmetry(state: &FootHeelState) -> f32 {
123 (state.left.pad - state.right.pad).abs()
124}
125
126pub fn fhc_to_weights(state: &FootHeelState) -> [f32; 4] {
128 [
129 state.left.pad,
130 state.right.pad,
131 state.left.calcaneus,
132 state.right.calcaneus,
133 ]
134}
135
136pub fn fhc_to_json(state: &FootHeelState) -> String {
138 format!(
139 r#"{{"left_pad":{:.4},"right_pad":{:.4},"left_cal":{:.4},"right_cal":{:.4}}}"#,
140 state.left.pad, state.right.pad, state.left.calcaneus, state.right.calcaneus
141 )
142}
143
144#[cfg(test)]
148mod tests {
149 use super::*;
150
151 fn make() -> FootHeelState {
152 new_foot_heel_state(default_foot_heel_config())
153 }
154
155 #[test]
156 fn neutral_on_creation() {
157 assert!(fhc_is_neutral(&make()));
158 }
159
160 #[test]
161 fn set_pad_clamps_high() {
162 let mut s = make();
163 fhc_set_pad(&mut s, FootSide::Left, 10.0);
164 assert!((s.left.pad - 1.0).abs() < 1e-5);
165 }
166
167 #[test]
168 fn set_both_pads_equal() {
169 let mut s = make();
170 fhc_set_both_pad(&mut s, 0.7);
171 assert!((s.left.pad - s.right.pad).abs() < 1e-5);
172 }
173
174 #[test]
175 fn reset_restores_neutral() {
176 let mut s = make();
177 fhc_set_pad(&mut s, FootSide::Left, 1.0);
178 fhc_reset(&mut s);
179 assert!(fhc_is_neutral(&s));
180 }
181
182 #[test]
183 fn pad_asymmetry_zero_when_equal() {
184 let mut s = make();
185 fhc_set_both_pad(&mut s, 0.6);
186 assert!(fhc_pad_asymmetry(&s) < 1e-5);
187 }
188
189 #[test]
190 fn weights_in_range() {
191 let s = make();
192 for v in fhc_to_weights(&s) {
193 assert!((0.0..=1.0).contains(&v));
194 }
195 }
196
197 #[test]
198 fn json_has_left_pad() {
199 assert!(fhc_to_json(&make()).contains("left_pad"));
200 }
201
202 #[test]
203 fn calcaneus_clamped() {
204 let mut s = make();
205 fhc_set_calcaneus(&mut s, FootSide::Right, -1.0);
206 assert!(s.right.calcaneus >= 0.0);
207 }
208
209 #[test]
210 fn pad_returns_correct_side() {
211 let mut s = make();
212 fhc_set_pad(&mut s, FootSide::Right, 0.3);
213 assert!((fhc_pad(&s, FootSide::Right) - 0.3).abs() < 1e-5);
214 }
215}