oxihuman_morph/
foot_instep_control.rs1#![allow(dead_code)]
3
4use std::f32::consts::FRAC_PI_4;
7
8#[allow(dead_code)]
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum FootInstepSide {
12 Left,
13 Right,
14}
15
16#[allow(dead_code)]
18#[derive(Debug, Clone, PartialEq)]
19pub struct FootInstepConfig {
20 pub max_arch_m: f32,
21}
22
23impl Default for FootInstepConfig {
24 fn default() -> Self {
25 Self { max_arch_m: 0.014 }
26 }
27}
28
29#[allow(dead_code)]
31#[derive(Debug, Clone, Default)]
32pub struct FootInstepState {
33 pub left_arch: f32,
34 pub right_arch: f32,
35}
36
37#[allow(dead_code)]
38pub fn new_foot_instep_state() -> FootInstepState {
39 FootInstepState::default()
40}
41
42#[allow(dead_code)]
43pub fn default_foot_instep_config() -> FootInstepConfig {
44 FootInstepConfig::default()
45}
46
47#[allow(dead_code)]
48pub fn fi_set_arch(state: &mut FootInstepState, side: FootInstepSide, v: f32) {
49 let v = v.clamp(0.0, 1.0);
50 match side {
51 FootInstepSide::Left => state.left_arch = v,
52 FootInstepSide::Right => state.right_arch = v,
53 }
54}
55
56#[allow(dead_code)]
57pub fn fi_set_both(state: &mut FootInstepState, v: f32) {
58 let v = v.clamp(0.0, 1.0);
59 state.left_arch = v;
60 state.right_arch = v;
61}
62
63#[allow(dead_code)]
64pub fn fi_reset(state: &mut FootInstepState) {
65 *state = FootInstepState::default();
66}
67
68#[allow(dead_code)]
69pub fn fi_is_neutral(state: &FootInstepState) -> bool {
70 state.left_arch < 1e-4 && state.right_arch < 1e-4
71}
72
73#[allow(dead_code)]
74pub fn fi_average_arch(state: &FootInstepState) -> f32 {
75 (state.left_arch + state.right_arch) * 0.5
76}
77
78#[allow(dead_code)]
80pub fn fi_arch_angle_rad(state: &FootInstepState, side: FootInstepSide) -> f32 {
81 let v = match side {
82 FootInstepSide::Left => state.left_arch,
83 FootInstepSide::Right => state.right_arch,
84 };
85 v * FRAC_PI_4 * 0.5
86}
87
88#[allow(dead_code)]
89pub fn fi_to_weights(state: &FootInstepState, cfg: &FootInstepConfig) -> [f32; 2] {
90 [
91 state.left_arch * cfg.max_arch_m,
92 state.right_arch * cfg.max_arch_m,
93 ]
94}
95
96#[allow(dead_code)]
97pub fn fi_blend(a: &FootInstepState, b: &FootInstepState, t: f32) -> FootInstepState {
98 let t = t.clamp(0.0, 1.0);
99 let inv = 1.0 - t;
100 FootInstepState {
101 left_arch: a.left_arch * inv + b.left_arch * t,
102 right_arch: a.right_arch * inv + b.right_arch * t,
103 }
104}
105
106#[allow(dead_code)]
107pub fn fi_to_json(state: &FootInstepState) -> String {
108 format!(
109 "{{\"left_arch\":{:.4},\"right_arch\":{:.4}}}",
110 state.left_arch, state.right_arch
111 )
112}
113
114#[cfg(test)]
115mod tests {
116 use super::*;
117
118 #[test]
119 fn default_neutral() {
120 assert!(fi_is_neutral(&new_foot_instep_state()));
121 }
122
123 #[test]
124 fn set_clamps_high() {
125 let mut s = new_foot_instep_state();
126 fi_set_arch(&mut s, FootInstepSide::Left, 10.0);
127 assert!((s.left_arch - 1.0).abs() < 1e-6);
128 }
129
130 #[test]
131 fn set_clamps_low() {
132 let mut s = new_foot_instep_state();
133 fi_set_arch(&mut s, FootInstepSide::Right, -5.0);
134 assert!(s.right_arch < 1e-6);
135 }
136
137 #[test]
138 fn reset_clears() {
139 let mut s = new_foot_instep_state();
140 fi_set_both(&mut s, 0.7);
141 fi_reset(&mut s);
142 assert!(fi_is_neutral(&s));
143 }
144
145 #[test]
146 fn average_arch_correct() {
147 let mut s = new_foot_instep_state();
148 fi_set_arch(&mut s, FootInstepSide::Left, 0.4);
149 fi_set_arch(&mut s, FootInstepSide::Right, 0.6);
150 assert!((fi_average_arch(&s) - 0.5).abs() < 1e-5);
151 }
152
153 #[test]
154 fn arch_angle_positive() {
155 let mut s = new_foot_instep_state();
156 fi_set_arch(&mut s, FootInstepSide::Left, 1.0);
157 assert!(fi_arch_angle_rad(&s, FootInstepSide::Left) > 0.0);
158 }
159
160 #[test]
161 fn weights_scale_correctly() {
162 let cfg = default_foot_instep_config();
163 let mut s = new_foot_instep_state();
164 fi_set_both(&mut s, 1.0);
165 let w = fi_to_weights(&s, &cfg);
166 assert!((w[0] - cfg.max_arch_m).abs() < 1e-6);
167 }
168
169 #[test]
170 fn blend_midpoint() {
171 let mut b = new_foot_instep_state();
172 fi_set_both(&mut b, 1.0);
173 let r = fi_blend(&new_foot_instep_state(), &b, 0.5);
174 assert!((r.left_arch - 0.5).abs() < 1e-5);
175 }
176
177 #[test]
178 fn json_has_keys() {
179 let j = fi_to_json(&new_foot_instep_state());
180 assert!(j.contains("left_arch") && j.contains("right_arch"));
181 }
182
183 #[test]
184 fn set_both_equal() {
185 let mut s = new_foot_instep_state();
186 fi_set_both(&mut s, 0.3);
187 assert!((s.left_arch - s.right_arch).abs() < 1e-6);
188 }
189}