oxihuman_morph/
shoulder_pad_control.rs1#![allow(dead_code)]
4
5#[allow(dead_code)]
9#[derive(Debug, Clone)]
10pub struct ShoulderPadConfig {
11 pub max_bulk: f32,
12}
13
14#[allow(dead_code)]
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum ShoulderPadSide {
18 Left,
19 Right,
20}
21
22#[allow(dead_code)]
24#[derive(Debug, Clone)]
25pub struct ShoulderPadState {
26 pub left_bulk: f32,
27 pub right_bulk: f32,
28 pub acromion_prominence: f32,
29}
30
31#[allow(dead_code)]
32pub fn default_shoulder_pad_config() -> ShoulderPadConfig {
33 ShoulderPadConfig { max_bulk: 1.0 }
34}
35
36#[allow(dead_code)]
37pub fn new_shoulder_pad_state() -> ShoulderPadState {
38 ShoulderPadState {
39 left_bulk: 0.0,
40 right_bulk: 0.0,
41 acromion_prominence: 0.0,
42 }
43}
44
45#[allow(dead_code)]
46pub fn spad_set_bulk(
47 state: &mut ShoulderPadState,
48 cfg: &ShoulderPadConfig,
49 side: ShoulderPadSide,
50 v: f32,
51) {
52 let clamped = v.clamp(0.0, cfg.max_bulk);
53 match side {
54 ShoulderPadSide::Left => state.left_bulk = clamped,
55 ShoulderPadSide::Right => state.right_bulk = clamped,
56 }
57}
58
59#[allow(dead_code)]
60pub fn spad_set_both(state: &mut ShoulderPadState, cfg: &ShoulderPadConfig, v: f32) {
61 let clamped = v.clamp(0.0, cfg.max_bulk);
62 state.left_bulk = clamped;
63 state.right_bulk = clamped;
64}
65
66#[allow(dead_code)]
67pub fn spad_set_acromion(state: &mut ShoulderPadState, v: f32) {
68 state.acromion_prominence = v.clamp(0.0, 1.0);
69}
70
71#[allow(dead_code)]
72pub fn spad_reset(state: &mut ShoulderPadState) {
73 *state = new_shoulder_pad_state();
74}
75
76#[allow(dead_code)]
77pub fn spad_is_neutral(state: &ShoulderPadState) -> bool {
78 state.left_bulk.abs() < 1e-6
79 && state.right_bulk.abs() < 1e-6
80 && state.acromion_prominence.abs() < 1e-6
81}
82
83#[allow(dead_code)]
84pub fn spad_average_bulk(state: &ShoulderPadState) -> f32 {
85 (state.left_bulk + state.right_bulk) * 0.5
86}
87
88#[allow(dead_code)]
89pub fn spad_symmetry(state: &ShoulderPadState) -> f32 {
90 (state.left_bulk - state.right_bulk).abs()
91}
92
93#[allow(dead_code)]
94pub fn spad_blend(a: &ShoulderPadState, b: &ShoulderPadState, t: f32) -> ShoulderPadState {
95 let t = t.clamp(0.0, 1.0);
96 ShoulderPadState {
97 left_bulk: a.left_bulk + (b.left_bulk - a.left_bulk) * t,
98 right_bulk: a.right_bulk + (b.right_bulk - a.right_bulk) * t,
99 acromion_prominence: a.acromion_prominence
100 + (b.acromion_prominence - a.acromion_prominence) * t,
101 }
102}
103
104#[allow(dead_code)]
105pub fn spad_to_weights(state: &ShoulderPadState) -> Vec<(String, f32)> {
106 vec![
107 ("shoulder_pad_bulk_l".to_string(), state.left_bulk),
108 ("shoulder_pad_bulk_r".to_string(), state.right_bulk),
109 ("acromion_prominence".to_string(), state.acromion_prominence),
110 ]
111}
112
113#[allow(dead_code)]
114pub fn spad_to_json(state: &ShoulderPadState) -> String {
115 format!(
116 r#"{{"left_bulk":{:.4},"right_bulk":{:.4},"acromion":{:.4}}}"#,
117 state.left_bulk, state.right_bulk, state.acromion_prominence
118 )
119}
120
121#[cfg(test)]
122mod tests {
123 use super::*;
124
125 #[test]
126 fn default_config() {
127 let cfg = default_shoulder_pad_config();
128 assert!((cfg.max_bulk - 1.0).abs() < 1e-6);
129 }
130
131 #[test]
132 fn new_state_neutral() {
133 let s = new_shoulder_pad_state();
134 assert!(spad_is_neutral(&s));
135 }
136
137 #[test]
138 fn set_bulk_left() {
139 let cfg = default_shoulder_pad_config();
140 let mut s = new_shoulder_pad_state();
141 spad_set_bulk(&mut s, &cfg, ShoulderPadSide::Left, 0.6);
142 assert!((s.left_bulk - 0.6).abs() < 1e-6);
143 }
144
145 #[test]
146 fn set_bulk_clamps() {
147 let cfg = default_shoulder_pad_config();
148 let mut s = new_shoulder_pad_state();
149 spad_set_bulk(&mut s, &cfg, ShoulderPadSide::Right, 5.0);
150 assert!((s.right_bulk - 1.0).abs() < 1e-6);
151 }
152
153 #[test]
154 fn set_both_symmetric() {
155 let cfg = default_shoulder_pad_config();
156 let mut s = new_shoulder_pad_state();
157 spad_set_both(&mut s, &cfg, 0.4);
158 assert!(spad_symmetry(&s) < 1e-6);
159 }
160
161 #[test]
162 fn set_acromion() {
163 let mut s = new_shoulder_pad_state();
164 spad_set_acromion(&mut s, 0.7);
165 assert!((s.acromion_prominence - 0.7).abs() < 1e-6);
166 }
167
168 #[test]
169 fn reset_clears() {
170 let cfg = default_shoulder_pad_config();
171 let mut s = new_shoulder_pad_state();
172 spad_set_both(&mut s, &cfg, 0.8);
173 spad_reset(&mut s);
174 assert!(spad_is_neutral(&s));
175 }
176
177 #[test]
178 fn blend_midpoint() {
179 let a = new_shoulder_pad_state();
180 let cfg = default_shoulder_pad_config();
181 let mut b = new_shoulder_pad_state();
182 spad_set_both(&mut b, &cfg, 1.0);
183 let m = spad_blend(&a, &b, 0.5);
184 assert!((m.left_bulk - 0.5).abs() < 1e-6);
185 }
186
187 #[test]
188 fn to_weights_count() {
189 let s = new_shoulder_pad_state();
190 assert_eq!(spad_to_weights(&s).len(), 3);
191 }
192
193 #[test]
194 fn to_json_fields() {
195 let s = new_shoulder_pad_state();
196 let j = spad_to_json(&s);
197 assert!(j.contains("left_bulk"));
198 }
199}