oxihuman_morph/
body_lean_control.rs1#![allow(dead_code)]
4
5use std::f32::consts::FRAC_PI_2;
8
9#[allow(dead_code)]
11#[derive(Debug, Clone)]
12pub struct BodyLeanConfig {
13 pub max_forward: f32,
14 pub max_lateral: f32,
15}
16
17#[allow(dead_code)]
19#[derive(Debug, Clone)]
20pub struct BodyLeanState {
21 pub forward: f32,
22 pub backward: f32,
23 pub lateral_left: f32,
24 pub lateral_right: f32,
25}
26
27#[allow(dead_code)]
28pub fn default_body_lean_config() -> BodyLeanConfig {
29 BodyLeanConfig {
30 max_forward: 1.0,
31 max_lateral: 1.0,
32 }
33}
34
35#[allow(dead_code)]
36pub fn new_body_lean_state() -> BodyLeanState {
37 BodyLeanState {
38 forward: 0.0,
39 backward: 0.0,
40 lateral_left: 0.0,
41 lateral_right: 0.0,
42 }
43}
44
45#[allow(dead_code)]
46pub fn bl_set_forward(state: &mut BodyLeanState, cfg: &BodyLeanConfig, v: f32) {
47 state.forward = v.clamp(0.0, cfg.max_forward);
48 state.backward = 0.0;
49}
50
51#[allow(dead_code)]
52pub fn bl_set_backward(state: &mut BodyLeanState, cfg: &BodyLeanConfig, v: f32) {
53 state.backward = v.clamp(0.0, cfg.max_forward);
54 state.forward = 0.0;
55}
56
57#[allow(dead_code)]
58pub fn bl_set_lateral(state: &mut BodyLeanState, cfg: &BodyLeanConfig, left: f32, right: f32) {
59 state.lateral_left = left.clamp(0.0, cfg.max_lateral);
60 state.lateral_right = right.clamp(0.0, cfg.max_lateral);
61}
62
63#[allow(dead_code)]
64pub fn bl_reset(state: &mut BodyLeanState) {
65 *state = new_body_lean_state();
66}
67
68#[allow(dead_code)]
69pub fn bl_sagittal_angle_rad(state: &BodyLeanState) -> f32 {
70 (state.forward - state.backward) * FRAC_PI_2 * 0.5
71}
72
73#[allow(dead_code)]
74pub fn bl_is_neutral(state: &BodyLeanState) -> bool {
75 let vals = [
76 state.forward,
77 state.backward,
78 state.lateral_left,
79 state.lateral_right,
80 ];
81 !vals.is_empty() && vals.iter().all(|v| v.abs() < 1e-6)
82}
83
84#[allow(dead_code)]
85pub fn bl_blend(a: &BodyLeanState, b: &BodyLeanState, t: f32) -> BodyLeanState {
86 let t = t.clamp(0.0, 1.0);
87 BodyLeanState {
88 forward: a.forward + (b.forward - a.forward) * t,
89 backward: a.backward + (b.backward - a.backward) * t,
90 lateral_left: a.lateral_left + (b.lateral_left - a.lateral_left) * t,
91 lateral_right: a.lateral_right + (b.lateral_right - a.lateral_right) * t,
92 }
93}
94
95#[allow(dead_code)]
96pub fn bl_to_weights(state: &BodyLeanState) -> Vec<(String, f32)> {
97 vec![
98 ("body_lean_forward".to_string(), state.forward),
99 ("body_lean_backward".to_string(), state.backward),
100 ("body_lean_left".to_string(), state.lateral_left),
101 ("body_lean_right".to_string(), state.lateral_right),
102 ]
103}
104
105#[allow(dead_code)]
106pub fn bl_to_json(state: &BodyLeanState) -> String {
107 format!(
108 r#"{{"forward":{:.4},"backward":{:.4},"lateral_left":{:.4},"lateral_right":{:.4}}}"#,
109 state.forward, state.backward, state.lateral_left, state.lateral_right
110 )
111}
112
113#[cfg(test)]
114mod tests {
115 use super::*;
116
117 #[test]
118 fn default_config_sane() {
119 let cfg = default_body_lean_config();
120 assert!((cfg.max_forward - 1.0).abs() < 1e-6);
121 assert!((cfg.max_lateral - 1.0).abs() < 1e-6);
122 }
123
124 #[test]
125 fn new_state_is_neutral() {
126 let s = new_body_lean_state();
127 assert!(bl_is_neutral(&s));
128 }
129
130 #[test]
131 fn set_forward_clamps() {
132 let cfg = default_body_lean_config();
133 let mut s = new_body_lean_state();
134 bl_set_forward(&mut s, &cfg, 5.0);
135 assert!((s.forward - 1.0).abs() < 1e-6);
136 assert_eq!(s.backward, 0.0);
137 }
138
139 #[test]
140 fn set_backward_clears_forward() {
141 let cfg = default_body_lean_config();
142 let mut s = new_body_lean_state();
143 bl_set_forward(&mut s, &cfg, 0.5);
144 bl_set_backward(&mut s, &cfg, 0.3);
145 assert_eq!(s.forward, 0.0);
146 assert!((s.backward - 0.3).abs() < 1e-6);
147 }
148
149 #[test]
150 fn set_lateral() {
151 let cfg = default_body_lean_config();
152 let mut s = new_body_lean_state();
153 bl_set_lateral(&mut s, &cfg, 0.4, 0.6);
154 assert!((s.lateral_left - 0.4).abs() < 1e-6);
155 assert!((s.lateral_right - 0.6).abs() < 1e-6);
156 }
157
158 #[test]
159 fn reset_clears_all() {
160 let cfg = default_body_lean_config();
161 let mut s = new_body_lean_state();
162 bl_set_forward(&mut s, &cfg, 0.5);
163 bl_reset(&mut s);
164 assert!(bl_is_neutral(&s));
165 }
166
167 #[test]
168 fn sagittal_angle_forward() {
169 let cfg = default_body_lean_config();
170 let mut s = new_body_lean_state();
171 bl_set_forward(&mut s, &cfg, 1.0);
172 assert!(bl_sagittal_angle_rad(&s) > 0.0);
173 }
174
175 #[test]
176 fn blend_midpoint() {
177 let a = new_body_lean_state();
178 let cfg = default_body_lean_config();
179 let mut b = new_body_lean_state();
180 bl_set_forward(&mut b, &cfg, 1.0);
181 let mid = bl_blend(&a, &b, 0.5);
182 assert!((mid.forward - 0.5).abs() < 1e-6);
183 }
184
185 #[test]
186 fn to_weights_count() {
187 let s = new_body_lean_state();
188 assert_eq!(bl_to_weights(&s).len(), 4);
189 }
190
191 #[test]
192 fn to_json_contains_fields() {
193 let s = new_body_lean_state();
194 let j = bl_to_json(&s);
195 assert!(j.contains("forward"));
196 assert!(j.contains("lateral_right"));
197 }
198}