oxihuman_morph/
neck_flexion_control.rs1#![allow(dead_code)]
4
5use std::f32::consts::FRAC_PI_2;
8
9#[allow(dead_code)]
11#[derive(Debug, Clone)]
12pub struct NeckFlexionConfig {
13 pub max_flexion: f32,
14 pub max_lateral: f32,
15}
16
17#[allow(dead_code)]
19#[derive(Debug, Clone)]
20pub struct NeckFlexionState {
21 pub forward_flexion: f32,
22 pub backward_extension: f32,
23 pub lateral_left: f32,
24 pub lateral_right: f32,
25}
26
27#[allow(dead_code)]
28pub fn default_neck_flexion_config() -> NeckFlexionConfig {
29 NeckFlexionConfig {
30 max_flexion: 1.0,
31 max_lateral: 1.0,
32 }
33}
34
35#[allow(dead_code)]
36pub fn new_neck_flexion_state() -> NeckFlexionState {
37 NeckFlexionState {
38 forward_flexion: 0.0,
39 backward_extension: 0.0,
40 lateral_left: 0.0,
41 lateral_right: 0.0,
42 }
43}
44
45#[allow(dead_code)]
46pub fn nf_set_forward(state: &mut NeckFlexionState, cfg: &NeckFlexionConfig, v: f32) {
47 state.forward_flexion = v.clamp(0.0, cfg.max_flexion);
48 state.backward_extension = 0.0;
49}
50
51#[allow(dead_code)]
52pub fn nf_set_backward(state: &mut NeckFlexionState, cfg: &NeckFlexionConfig, v: f32) {
53 state.backward_extension = v.clamp(0.0, cfg.max_flexion);
54 state.forward_flexion = 0.0;
55}
56
57#[allow(dead_code)]
58pub fn nf_set_lateral(
59 state: &mut NeckFlexionState,
60 cfg: &NeckFlexionConfig,
61 left: f32,
62 right: f32,
63) {
64 state.lateral_left = left.clamp(0.0, cfg.max_lateral);
65 state.lateral_right = right.clamp(0.0, cfg.max_lateral);
66}
67
68#[allow(dead_code)]
69pub fn nf_reset(state: &mut NeckFlexionState) {
70 *state = new_neck_flexion_state();
71}
72
73#[allow(dead_code)]
74pub fn nf_is_neutral(state: &NeckFlexionState) -> bool {
75 let vals = [
76 state.forward_flexion,
77 state.backward_extension,
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 nf_sagittal_angle_rad(state: &NeckFlexionState) -> f32 {
86 (state.forward_flexion - state.backward_extension) * FRAC_PI_2 * 0.5
87}
88
89#[allow(dead_code)]
90pub fn nf_lateral_angle_rad(state: &NeckFlexionState) -> f32 {
91 (state.lateral_left - state.lateral_right) * FRAC_PI_2 * 0.3
92}
93
94#[allow(dead_code)]
95pub fn nf_blend(a: &NeckFlexionState, b: &NeckFlexionState, t: f32) -> NeckFlexionState {
96 let t = t.clamp(0.0, 1.0);
97 NeckFlexionState {
98 forward_flexion: a.forward_flexion + (b.forward_flexion - a.forward_flexion) * t,
99 backward_extension: a.backward_extension
100 + (b.backward_extension - a.backward_extension) * t,
101 lateral_left: a.lateral_left + (b.lateral_left - a.lateral_left) * t,
102 lateral_right: a.lateral_right + (b.lateral_right - a.lateral_right) * t,
103 }
104}
105
106#[allow(dead_code)]
107pub fn nf_to_weights(state: &NeckFlexionState) -> Vec<(String, f32)> {
108 vec![
109 ("neck_flexion_fwd".to_string(), state.forward_flexion),
110 ("neck_extension_bwd".to_string(), state.backward_extension),
111 ("neck_lateral_l".to_string(), state.lateral_left),
112 ("neck_lateral_r".to_string(), state.lateral_right),
113 ]
114}
115
116#[allow(dead_code)]
117pub fn nf_to_json(state: &NeckFlexionState) -> String {
118 format!(
119 r#"{{"forward_flexion":{:.4},"backward_extension":{:.4},"lateral_left":{:.4},"lateral_right":{:.4}}}"#,
120 state.forward_flexion, state.backward_extension, state.lateral_left, state.lateral_right
121 )
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127
128 #[test]
129 fn default_config() {
130 let cfg = default_neck_flexion_config();
131 assert!((cfg.max_flexion - 1.0).abs() < 1e-6);
132 }
133
134 #[test]
135 fn new_state_neutral() {
136 let s = new_neck_flexion_state();
137 assert!(nf_is_neutral(&s));
138 }
139
140 #[test]
141 fn set_forward_clears_backward() {
142 let cfg = default_neck_flexion_config();
143 let mut s = new_neck_flexion_state();
144 nf_set_backward(&mut s, &cfg, 0.5);
145 nf_set_forward(&mut s, &cfg, 0.3);
146 assert_eq!(s.backward_extension, 0.0);
147 assert!((s.forward_flexion - 0.3).abs() < 1e-6);
148 }
149
150 #[test]
151 fn set_backward_clears_forward() {
152 let cfg = default_neck_flexion_config();
153 let mut s = new_neck_flexion_state();
154 nf_set_forward(&mut s, &cfg, 0.5);
155 nf_set_backward(&mut s, &cfg, 0.4);
156 assert_eq!(s.forward_flexion, 0.0);
157 assert!((s.backward_extension - 0.4).abs() < 1e-6);
158 }
159
160 #[test]
161 fn set_lateral() {
162 let cfg = default_neck_flexion_config();
163 let mut s = new_neck_flexion_state();
164 nf_set_lateral(&mut s, &cfg, 0.4, 0.6);
165 assert!((s.lateral_left - 0.4).abs() < 1e-6);
166 assert!((s.lateral_right - 0.6).abs() < 1e-6);
167 }
168
169 #[test]
170 fn sagittal_angle_forward_positive() {
171 let cfg = default_neck_flexion_config();
172 let mut s = new_neck_flexion_state();
173 nf_set_forward(&mut s, &cfg, 1.0);
174 assert!(nf_sagittal_angle_rad(&s) > 0.0);
175 }
176
177 #[test]
178 fn reset_clears() {
179 let cfg = default_neck_flexion_config();
180 let mut s = new_neck_flexion_state();
181 nf_set_forward(&mut s, &cfg, 0.5);
182 nf_reset(&mut s);
183 assert!(nf_is_neutral(&s));
184 }
185
186 #[test]
187 fn blend_midpoint() {
188 let a = new_neck_flexion_state();
189 let cfg = default_neck_flexion_config();
190 let mut b = new_neck_flexion_state();
191 nf_set_forward(&mut b, &cfg, 1.0);
192 let mid = nf_blend(&a, &b, 0.5);
193 assert!((mid.forward_flexion - 0.5).abs() < 1e-6);
194 }
195
196 #[test]
197 fn to_weights_count() {
198 let s = new_neck_flexion_state();
199 assert_eq!(nf_to_weights(&s).len(), 4);
200 }
201
202 #[test]
203 fn to_json_fields() {
204 let s = new_neck_flexion_state();
205 let j = nf_to_json(&s);
206 assert!(j.contains("forward_flexion"));
207 assert!(j.contains("lateral_right"));
208 }
209}