oxihuman_morph/
foot_width_control.rs1#![allow(dead_code)]
4
5#[allow(dead_code)]
9#[derive(Clone, Copy, Debug, PartialEq, Eq)]
10pub enum FootSide {
11 Left,
12 Right,
13 Both,
14}
15
16#[allow(dead_code)]
18#[derive(Clone, Debug)]
19pub struct FootWidthState {
20 pub forefoot_left: f32,
21 pub forefoot_right: f32,
22 pub heel_left: f32,
23 pub heel_right: f32,
24 pub arch_height: f32,
25}
26
27#[allow(dead_code)]
29#[derive(Clone, Debug)]
30pub struct FootWidthConfig {
31 pub max_width: f32,
32 pub max_arch: f32,
33}
34
35impl Default for FootWidthConfig {
36 fn default() -> Self {
37 Self {
38 max_width: 1.0,
39 max_arch: 1.0,
40 }
41 }
42}
43impl Default for FootWidthState {
44 fn default() -> Self {
45 Self {
46 forefoot_left: 0.5,
47 forefoot_right: 0.5,
48 heel_left: 0.5,
49 heel_right: 0.5,
50 arch_height: 0.0,
51 }
52 }
53}
54
55#[allow(dead_code)]
56pub fn new_foot_width_state() -> FootWidthState {
57 FootWidthState::default()
58}
59
60#[allow(dead_code)]
61pub fn default_foot_width_config() -> FootWidthConfig {
62 FootWidthConfig::default()
63}
64
65#[allow(dead_code)]
66pub fn fw_set_forefoot(state: &mut FootWidthState, cfg: &FootWidthConfig, side: FootSide, v: f32) {
67 let v = v.clamp(0.0, cfg.max_width);
68 match side {
69 FootSide::Left => state.forefoot_left = v,
70 FootSide::Right => state.forefoot_right = v,
71 FootSide::Both => {
72 state.forefoot_left = v;
73 state.forefoot_right = v;
74 }
75 }
76}
77
78#[allow(dead_code)]
79pub fn fw_set_heel(state: &mut FootWidthState, cfg: &FootWidthConfig, side: FootSide, v: f32) {
80 let v = v.clamp(0.0, cfg.max_width);
81 match side {
82 FootSide::Left => state.heel_left = v,
83 FootSide::Right => state.heel_right = v,
84 FootSide::Both => {
85 state.heel_left = v;
86 state.heel_right = v;
87 }
88 }
89}
90
91#[allow(dead_code)]
92pub fn fw_set_arch(state: &mut FootWidthState, cfg: &FootWidthConfig, v: f32) {
93 state.arch_height = v.clamp(0.0, cfg.max_arch);
94}
95
96#[allow(dead_code)]
97pub fn fw_reset(state: &mut FootWidthState) {
98 *state = FootWidthState::default();
99}
100
101#[allow(dead_code)]
102pub fn fw_blend(a: &FootWidthState, b: &FootWidthState, t: f32) -> FootWidthState {
103 let t = t.clamp(0.0, 1.0);
104 FootWidthState {
105 forefoot_left: a.forefoot_left + (b.forefoot_left - a.forefoot_left) * t,
106 forefoot_right: a.forefoot_right + (b.forefoot_right - a.forefoot_right) * t,
107 heel_left: a.heel_left + (b.heel_left - a.heel_left) * t,
108 heel_right: a.heel_right + (b.heel_right - a.heel_right) * t,
109 arch_height: a.arch_height + (b.arch_height - a.arch_height) * t,
110 }
111}
112
113#[allow(dead_code)]
114pub fn fw_symmetry(state: &FootWidthState) -> f32 {
115 1.0 - (state.forefoot_left - state.forefoot_right).abs().min(1.0)
116}
117
118#[allow(dead_code)]
119pub fn fw_to_weights(state: &FootWidthState) -> [f32; 5] {
120 [
121 state.forefoot_left,
122 state.forefoot_right,
123 state.heel_left,
124 state.heel_right,
125 state.arch_height,
126 ]
127}
128
129#[allow(dead_code)]
130pub fn fw_to_json(state: &FootWidthState) -> String {
131 format!(
132 "{{\"ff_l\":{:.4},\"ff_r\":{:.4},\"heel_l\":{:.4},\"heel_r\":{:.4},\"arch\":{:.4}}}",
133 state.forefoot_left,
134 state.forefoot_right,
135 state.heel_left,
136 state.heel_right,
137 state.arch_height
138 )
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144
145 #[test]
146 fn default_half_width() {
147 let s = new_foot_width_state();
148 assert!((s.forefoot_left - 0.5).abs() < 1e-5);
149 }
150
151 #[test]
152 fn clamp_max() {
153 let mut s = new_foot_width_state();
154 let cfg = default_foot_width_config();
155 fw_set_forefoot(&mut s, &cfg, FootSide::Left, 5.0);
156 assert!(s.forefoot_left <= cfg.max_width);
157 }
158
159 #[test]
160 fn clamp_min() {
161 let mut s = new_foot_width_state();
162 let cfg = default_foot_width_config();
163 fw_set_heel(&mut s, &cfg, FootSide::Right, -1.0);
164 assert!(s.heel_right >= 0.0);
165 }
166
167 #[test]
168 fn both_sides_set() {
169 let mut s = new_foot_width_state();
170 let cfg = default_foot_width_config();
171 fw_set_forefoot(&mut s, &cfg, FootSide::Both, 0.8);
172 assert!((s.forefoot_left - s.forefoot_right).abs() < 1e-5);
173 }
174
175 #[test]
176 fn reset_default() {
177 let mut s = new_foot_width_state();
178 let cfg = default_foot_width_config();
179 fw_set_arch(&mut s, &cfg, 0.9);
180 fw_reset(&mut s);
181 assert!((s.arch_height).abs() < 1e-5);
182 }
183
184 #[test]
185 fn blend_half() {
186 let cfg = default_foot_width_config();
187 let mut a = new_foot_width_state();
188 let mut b = new_foot_width_state();
189 fw_set_forefoot(&mut a, &cfg, FootSide::Left, 0.0);
190 fw_set_forefoot(&mut b, &cfg, FootSide::Left, 1.0);
191 let m = fw_blend(&a, &b, 0.5);
192 assert!((m.forefoot_left - 0.5).abs() < 1e-4);
193 }
194
195 #[test]
196 fn symmetry_equal() {
197 let s = new_foot_width_state();
198 assert!((fw_symmetry(&s) - 1.0).abs() < 1e-5);
199 }
200
201 #[test]
202 fn weights_len() {
203 assert_eq!(fw_to_weights(&new_foot_width_state()).len(), 5);
204 }
205
206 #[test]
207 fn json_has_arch() {
208 assert!(fw_to_json(&new_foot_width_state()).contains("arch"));
209 }
210
211 #[test]
212 fn arch_clamp() {
213 let mut s = new_foot_width_state();
214 let cfg = default_foot_width_config();
215 fw_set_arch(&mut s, &cfg, -5.0);
216 assert!(s.arch_height >= 0.0);
217 }
218}