oxihuman_morph/
body_center_control.rs1#![allow(dead_code)]
3
4use std::f32::consts::FRAC_PI_4;
7
8#[allow(dead_code)]
10#[derive(Debug, Clone, PartialEq)]
11pub struct BodyCenterConfig {
12 pub max_anterior: f32,
14 pub max_posterior: f32,
16 pub max_lateral: f32,
18}
19
20impl Default for BodyCenterConfig {
21 fn default() -> Self {
22 Self {
23 max_anterior: 0.08,
24 max_posterior: 0.06,
25 max_lateral: 0.05,
26 }
27 }
28}
29
30#[allow(dead_code)]
32#[derive(Debug, Clone, Default)]
33pub struct BodyCenterState {
34 pub ap_shift: f32,
36 pub lateral_shift: f32,
38}
39
40#[allow(dead_code)]
42pub fn new_body_center_state() -> BodyCenterState {
43 BodyCenterState::default()
44}
45
46#[allow(dead_code)]
48pub fn default_body_center_config() -> BodyCenterConfig {
49 BodyCenterConfig::default()
50}
51
52#[allow(dead_code)]
54pub fn bcc_set_ap(state: &mut BodyCenterState, v: f32) {
55 state.ap_shift = v.clamp(-1.0, 1.0);
56}
57
58#[allow(dead_code)]
60pub fn bcc_set_lateral(state: &mut BodyCenterState, v: f32) {
61 state.lateral_shift = v.clamp(-1.0, 1.0);
62}
63
64#[allow(dead_code)]
66pub fn bcc_reset(state: &mut BodyCenterState) {
67 *state = BodyCenterState::default();
68}
69
70#[allow(dead_code)]
72pub fn bcc_is_neutral(state: &BodyCenterState) -> bool {
73 state.ap_shift.abs() < 1e-4 && state.lateral_shift.abs() < 1e-4
74}
75
76#[allow(dead_code)]
78pub fn bcc_displacement(state: &BodyCenterState) -> f32 {
79 (state.ap_shift.powi(2) + state.lateral_shift.powi(2))
80 .sqrt()
81 .min(1.0)
82}
83
84#[allow(dead_code)]
86pub fn bcc_to_weights(state: &BodyCenterState, cfg: &BodyCenterConfig) -> [f32; 2] {
87 let ap = if state.ap_shift >= 0.0 {
88 state.ap_shift * cfg.max_anterior
89 } else {
90 state.ap_shift * cfg.max_posterior
91 };
92 let lat = state.lateral_shift * cfg.max_lateral;
93 [ap, lat]
94}
95
96#[allow(dead_code)]
98pub fn bcc_lean_angle_rad(state: &BodyCenterState) -> f32 {
99 state.ap_shift * FRAC_PI_4 * 0.25
100}
101
102#[allow(dead_code)]
104pub fn bcc_blend(a: &BodyCenterState, b: &BodyCenterState, t: f32) -> BodyCenterState {
105 let t = t.clamp(0.0, 1.0);
106 let inv = 1.0 - t;
107 BodyCenterState {
108 ap_shift: a.ap_shift * inv + b.ap_shift * t,
109 lateral_shift: a.lateral_shift * inv + b.lateral_shift * t,
110 }
111}
112
113#[allow(dead_code)]
115pub fn bcc_to_json(state: &BodyCenterState) -> String {
116 format!(
117 "{{\"ap_shift\":{:.4},\"lateral_shift\":{:.4}}}",
118 state.ap_shift, state.lateral_shift
119 )
120}
121
122#[cfg(test)]
123mod tests {
124 use super::*;
125
126 #[test]
127 fn default_is_neutral() {
128 let s = new_body_center_state();
129 assert!(bcc_is_neutral(&s));
130 }
131
132 #[test]
133 fn set_ap_clamps() {
134 let mut s = new_body_center_state();
135 bcc_set_ap(&mut s, 5.0);
136 assert!((s.ap_shift - 1.0).abs() < 1e-6);
137 bcc_set_ap(&mut s, -5.0);
138 assert!((s.ap_shift + 1.0).abs() < 1e-6);
139 }
140
141 #[test]
142 fn set_lateral_clamps() {
143 let mut s = new_body_center_state();
144 bcc_set_lateral(&mut s, -3.0);
145 assert!((s.lateral_shift + 1.0).abs() < 1e-6);
146 }
147
148 #[test]
149 fn reset_clears() {
150 let mut s = new_body_center_state();
151 bcc_set_ap(&mut s, 0.5);
152 bcc_set_lateral(&mut s, 0.3);
153 bcc_reset(&mut s);
154 assert!(bcc_is_neutral(&s));
155 }
156
157 #[test]
158 fn displacement_zero_at_neutral() {
159 let s = new_body_center_state();
160 assert!(bcc_displacement(&s) < 1e-6);
161 }
162
163 #[test]
164 fn displacement_positive_when_shifted() {
165 let mut s = new_body_center_state();
166 bcc_set_ap(&mut s, 0.6);
167 bcc_set_lateral(&mut s, 0.4);
168 assert!(bcc_displacement(&s) > 0.5);
169 }
170
171 #[test]
172 fn weights_sign_matches_direction() {
173 let cfg = default_body_center_config();
174 let mut s = new_body_center_state();
175 bcc_set_ap(&mut s, 1.0);
176 let w = bcc_to_weights(&s, &cfg);
177 assert!(w[0] > 0.0);
178 }
179
180 #[test]
181 fn lean_angle_sign() {
182 let mut s = new_body_center_state();
183 bcc_set_ap(&mut s, 1.0);
184 assert!(bcc_lean_angle_rad(&s) > 0.0);
185 bcc_set_ap(&mut s, -1.0);
186 assert!(bcc_lean_angle_rad(&s) < 0.0);
187 }
188
189 #[test]
190 fn blend_midpoint() {
191 let mut a = new_body_center_state();
192 let mut b = new_body_center_state();
193 bcc_set_ap(&mut a, 0.0);
194 bcc_set_ap(&mut b, 1.0);
195 let r = bcc_blend(&a, &b, 0.5);
196 assert!((r.ap_shift - 0.5).abs() < 1e-5);
197 }
198
199 #[test]
200 fn json_contains_ap_key() {
201 let s = new_body_center_state();
202 let j = bcc_to_json(&s);
203 assert!(j.contains("ap_shift"));
204 }
205}