1#![allow(dead_code)]
4
5use std::f32::consts::PI;
8
9#[allow(dead_code)]
11#[derive(Clone, Copy, Debug, PartialEq, Eq)]
12pub enum BodySegment {
13 Head,
14 Torso,
15 UpperArm,
16 LowerArm,
17 UpperLeg,
18 LowerLeg,
19}
20
21#[allow(dead_code)]
23#[derive(Clone, Debug)]
24pub struct SegmentScale {
25 pub segment: BodySegment,
26 pub scale: f32,
28 pub weight: f32,
30}
31
32#[allow(dead_code)]
34#[derive(Clone, Debug, Default)]
35pub struct BodySegmentState {
36 pub overrides: Vec<SegmentScale>,
37}
38
39#[allow(dead_code)]
41#[derive(Clone, Debug)]
42pub struct BodySegmentConfig {
43 pub min_scale: f32,
44 pub max_scale: f32,
45}
46
47impl Default for BodySegmentConfig {
48 fn default() -> Self {
49 Self {
50 min_scale: 0.5,
51 max_scale: 2.0,
52 }
53 }
54}
55
56#[allow(dead_code)]
57pub fn new_body_segment_state() -> BodySegmentState {
58 BodySegmentState::default()
59}
60
61#[allow(dead_code)]
62pub fn default_body_segment_config() -> BodySegmentConfig {
63 BodySegmentConfig::default()
64}
65
66#[allow(dead_code)]
67pub fn set_segment_scale(
68 state: &mut BodySegmentState,
69 cfg: &BodySegmentConfig,
70 segment: BodySegment,
71 scale: f32,
72 weight: f32,
73) {
74 let scale = scale.clamp(cfg.min_scale, cfg.max_scale);
75 let weight = weight.clamp(0.0, 1.0);
76 if let Some(entry) = state.overrides.iter_mut().find(|e| e.segment == segment) {
77 entry.scale = scale;
78 entry.weight = weight;
79 } else {
80 state.overrides.push(SegmentScale {
81 segment,
82 scale,
83 weight,
84 });
85 }
86}
87
88#[allow(dead_code)]
89pub fn get_segment_scale(state: &BodySegmentState, segment: BodySegment) -> f32 {
90 state
91 .overrides
92 .iter()
93 .find(|e| e.segment == segment)
94 .map(|e| e.scale)
95 .unwrap_or(1.0)
96}
97
98#[allow(dead_code)]
99pub fn reset_segment(state: &mut BodySegmentState, segment: BodySegment) {
100 state.overrides.retain(|e| e.segment != segment);
101}
102
103#[allow(dead_code)]
104pub fn reset_all_segments(state: &mut BodySegmentState) {
105 state.overrides.clear();
106}
107
108#[allow(dead_code)]
109pub fn blend_segment_states(
110 a: &BodySegmentState,
111 b: &BodySegmentState,
112 t: f32,
113) -> BodySegmentState {
114 let t = t.clamp(0.0, 1.0);
115 let all_segs = [
116 BodySegment::Head,
117 BodySegment::Torso,
118 BodySegment::UpperArm,
119 BodySegment::LowerArm,
120 BodySegment::UpperLeg,
121 BodySegment::LowerLeg,
122 ];
123 let overrides = all_segs
124 .iter()
125 .map(|&seg| {
126 let sa = a
127 .overrides
128 .iter()
129 .find(|e| e.segment == seg)
130 .map(|e| e.scale)
131 .unwrap_or(1.0);
132 let sb = b
133 .overrides
134 .iter()
135 .find(|e| e.segment == seg)
136 .map(|e| e.scale)
137 .unwrap_or(1.0);
138 let wa = a
139 .overrides
140 .iter()
141 .find(|e| e.segment == seg)
142 .map(|e| e.weight)
143 .unwrap_or(0.0);
144 let wb = b
145 .overrides
146 .iter()
147 .find(|e| e.segment == seg)
148 .map(|e| e.weight)
149 .unwrap_or(0.0);
150 SegmentScale {
151 segment: seg,
152 scale: sa + (sb - sa) * t,
153 weight: wa + (wb - wa) * t,
154 }
155 })
156 .collect();
157 BodySegmentState { overrides }
158}
159
160#[allow(dead_code)]
161pub fn segment_name(seg: BodySegment) -> &'static str {
162 match seg {
163 BodySegment::Head => "head",
164 BodySegment::Torso => "torso",
165 BodySegment::UpperArm => "upper_arm",
166 BodySegment::LowerArm => "lower_arm",
167 BodySegment::UpperLeg => "upper_leg",
168 BodySegment::LowerLeg => "lower_leg",
169 }
170}
171
172#[allow(dead_code)]
173pub fn total_limb_scale(state: &BodySegmentState) -> f32 {
174 let ua = get_segment_scale(state, BodySegment::UpperArm);
175 let la = get_segment_scale(state, BodySegment::LowerArm);
176 let ul = get_segment_scale(state, BodySegment::UpperLeg);
177 let ll = get_segment_scale(state, BodySegment::LowerLeg);
178 (ua + la + ul + ll) / 4.0
179}
180
181#[allow(dead_code)]
183pub fn rhythm_scale(base: f32, amplitude: f32, phase_rad: f32) -> f32 {
184 base + amplitude * phase_rad.sin()
185}
186
187#[allow(dead_code)]
189pub fn limb_length_m(reference_m: f32, scale: f32) -> f32 {
190 reference_m * scale
191}
192
193#[allow(dead_code)]
195pub fn segment_angle_contribution(scale: f32) -> f32 {
196 (scale - 1.0) * PI * 0.1
197}
198
199#[allow(dead_code)]
200pub fn state_to_json(state: &BodySegmentState) -> String {
201 let entries: Vec<String> = state
202 .overrides
203 .iter()
204 .map(|e| {
205 format!(
206 "{{\"segment\":\"{}\",\"scale\":{:.4},\"weight\":{:.4}}}",
207 segment_name(e.segment),
208 e.scale,
209 e.weight
210 )
211 })
212 .collect();
213 format!("[{}]", entries.join(","))
214}
215
216#[cfg(test)]
217mod tests {
218 use super::*;
219
220 #[test]
221 fn default_state_is_empty() {
222 let s = new_body_segment_state();
223 assert!(s.overrides.is_empty());
224 }
225
226 #[test]
227 fn set_and_get_scale() {
228 let mut s = new_body_segment_state();
229 let cfg = default_body_segment_config();
230 set_segment_scale(&mut s, &cfg, BodySegment::Torso, 1.2, 1.0);
231 assert!((get_segment_scale(&s, BodySegment::Torso) - 1.2).abs() < 1e-5);
232 }
233
234 #[test]
235 fn unknown_segment_returns_neutral() {
236 let s = new_body_segment_state();
237 assert!((get_segment_scale(&s, BodySegment::Head) - 1.0).abs() < 1e-5);
238 }
239
240 #[test]
241 fn clamps_min_scale() {
242 let mut s = new_body_segment_state();
243 let cfg = default_body_segment_config();
244 set_segment_scale(&mut s, &cfg, BodySegment::Head, 0.1, 1.0);
245 assert!(get_segment_scale(&s, BodySegment::Head) >= cfg.min_scale);
246 }
247
248 #[test]
249 fn clamps_max_scale() {
250 let mut s = new_body_segment_state();
251 let cfg = default_body_segment_config();
252 set_segment_scale(&mut s, &cfg, BodySegment::LowerLeg, 5.0, 1.0);
253 assert!(get_segment_scale(&s, BodySegment::LowerLeg) <= cfg.max_scale);
254 }
255
256 #[test]
257 fn reset_segment_removes_entry() {
258 let mut s = new_body_segment_state();
259 let cfg = default_body_segment_config();
260 set_segment_scale(&mut s, &cfg, BodySegment::UpperArm, 1.5, 1.0);
261 reset_segment(&mut s, BodySegment::UpperArm);
262 assert!(s.overrides.is_empty());
263 }
264
265 #[test]
266 fn blend_midpoint() {
267 let mut a = new_body_segment_state();
268 let mut b = new_body_segment_state();
269 let cfg = default_body_segment_config();
270 set_segment_scale(&mut a, &cfg, BodySegment::Torso, 1.0, 1.0);
271 set_segment_scale(&mut b, &cfg, BodySegment::Torso, 2.0, 1.0);
272 let mid = blend_segment_states(&a, &b, 0.5);
273 let s = get_segment_scale(&mid, BodySegment::Torso);
274 assert!((s - 1.5).abs() < 1e-4);
275 }
276
277 #[test]
278 fn segment_name_all_variants() {
279 assert_eq!(segment_name(BodySegment::Head), "head");
280 assert_eq!(segment_name(BodySegment::LowerLeg), "lower_leg");
281 }
282
283 #[test]
284 fn rhythm_scale_at_zero_phase() {
285 let v = rhythm_scale(1.0, 0.1, 0.0);
286 assert!((v - 1.0).abs() < 1e-5);
287 }
288
289 #[test]
290 fn json_contains_torso() {
291 let mut s = new_body_segment_state();
292 let cfg = default_body_segment_config();
293 set_segment_scale(&mut s, &cfg, BodySegment::Torso, 1.1, 0.8);
294 let j = state_to_json(&s);
295 assert!(j.contains("torso"));
296 }
297}