oxihuman_morph/
muscle_group_driver.rs1#![allow(dead_code)]
3
4#[allow(dead_code)]
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
9pub enum MuscleGroup {
10 Pectoralis,
11 Deltoid,
12 Biceps,
13 Triceps,
14 Quadriceps,
15 Hamstrings,
16 Gluteus,
17 Gastrocnemius,
18 Abdominals,
19 Trapezius,
20}
21
22#[allow(dead_code)]
24#[derive(Debug, Clone, Default)]
25pub struct MuscleActivation {
26 pub group: Option<MuscleGroup>,
27 pub level: f32,
29 pub contraction: f32,
31}
32
33#[allow(dead_code)]
35#[derive(Debug, Clone, Default)]
36pub struct MuscleGroupDriver {
37 activations: Vec<MuscleActivation>,
38}
39
40#[allow(dead_code)]
41pub fn new_muscle_group_driver() -> MuscleGroupDriver {
42 MuscleGroupDriver::default()
43}
44
45#[allow(dead_code)]
46pub fn mgd_set(driver: &mut MuscleGroupDriver, group: MuscleGroup, level: f32, contraction: f32) {
47 let level = level.clamp(0.0, 1.0);
48 let contraction = contraction.clamp(0.0, 1.0);
49 if let Some(a) = driver
50 .activations
51 .iter_mut()
52 .find(|a| a.group == Some(group))
53 {
54 a.level = level;
55 a.contraction = contraction;
56 } else {
57 driver.activations.push(MuscleActivation {
58 group: Some(group),
59 level,
60 contraction,
61 });
62 }
63}
64
65#[allow(dead_code)]
66pub fn mgd_get(driver: &MuscleGroupDriver, group: MuscleGroup) -> Option<&MuscleActivation> {
67 driver.activations.iter().find(|a| a.group == Some(group))
68}
69
70#[allow(dead_code)]
71pub fn mgd_reset(driver: &mut MuscleGroupDriver) {
72 driver.activations.clear();
73}
74
75#[allow(dead_code)]
76pub fn mgd_active_count(driver: &MuscleGroupDriver) -> usize {
77 driver.activations.iter().filter(|a| a.level > 1e-4).count()
78}
79
80#[allow(dead_code)]
81pub fn mgd_total_activation(driver: &MuscleGroupDriver) -> f32 {
82 driver.activations.iter().map(|a| a.level).sum()
83}
84
85#[allow(dead_code)]
87pub fn mgd_bulge_weight(driver: &MuscleGroupDriver, group: MuscleGroup) -> f32 {
88 mgd_get(driver, group).map_or(0.0, |a| a.level * a.contraction)
89}
90
91#[allow(dead_code)]
93pub fn mgd_blend(a: &MuscleGroupDriver, b: &MuscleGroupDriver, t: f32) -> MuscleGroupDriver {
94 let t = t.clamp(0.0, 1.0);
95 let inv = 1.0 - t;
96 let mut result = MuscleGroupDriver::default();
97 for act_a in &a.activations {
98 if let Some(group) = act_a.group {
99 let level_b = b
100 .activations
101 .iter()
102 .find(|x| x.group == Some(group))
103 .map_or(0.0, |x| x.level);
104 let cont_b = b
105 .activations
106 .iter()
107 .find(|x| x.group == Some(group))
108 .map_or(0.0, |x| x.contraction);
109 result.activations.push(MuscleActivation {
110 group: Some(group),
111 level: act_a.level * inv + level_b * t,
112 contraction: act_a.contraction * inv + cont_b * t,
113 });
114 }
115 }
116 result
117}
118
119#[allow(dead_code)]
120pub fn mgd_to_json(driver: &MuscleGroupDriver) -> String {
121 format!(
122 "{{\"active_count\":{},\"total_activation\":{:.4}}}",
123 mgd_active_count(driver),
124 mgd_total_activation(driver)
125 )
126}
127
128#[cfg(test)]
129mod tests {
130 use super::*;
131
132 #[test]
133 fn empty_driver_has_zero_active() {
134 assert_eq!(mgd_active_count(&new_muscle_group_driver()), 0);
135 }
136
137 #[test]
138 fn set_and_get() {
139 let mut d = new_muscle_group_driver();
140 mgd_set(&mut d, MuscleGroup::Biceps, 0.8, 0.6);
141 let a = mgd_get(&d, MuscleGroup::Biceps).expect("should succeed");
142 assert!((a.level - 0.8).abs() < 1e-6);
143 }
144
145 #[test]
146 fn clamps_level() {
147 let mut d = new_muscle_group_driver();
148 mgd_set(&mut d, MuscleGroup::Triceps, 5.0, 0.5);
149 let a = mgd_get(&d, MuscleGroup::Triceps).expect("should succeed");
150 assert!((a.level - 1.0).abs() < 1e-6);
151 }
152
153 #[test]
154 fn reset_clears() {
155 let mut d = new_muscle_group_driver();
156 mgd_set(&mut d, MuscleGroup::Deltoid, 1.0, 1.0);
157 mgd_reset(&mut d);
158 assert_eq!(mgd_active_count(&d), 0);
159 }
160
161 #[test]
162 fn total_activation_sums() {
163 let mut d = new_muscle_group_driver();
164 mgd_set(&mut d, MuscleGroup::Biceps, 0.5, 0.5);
165 mgd_set(&mut d, MuscleGroup::Triceps, 0.5, 0.5);
166 assert!((mgd_total_activation(&d) - 1.0).abs() < 1e-5);
167 }
168
169 #[test]
170 fn bulge_weight_product() {
171 let mut d = new_muscle_group_driver();
172 mgd_set(&mut d, MuscleGroup::Pectoralis, 0.8, 0.5);
173 assert!((mgd_bulge_weight(&d, MuscleGroup::Pectoralis) - 0.4).abs() < 1e-5);
174 }
175
176 #[test]
177 fn missing_group_bulge_zero() {
178 let d = new_muscle_group_driver();
179 assert!(mgd_bulge_weight(&d, MuscleGroup::Quadriceps) < 1e-8);
180 }
181
182 #[test]
183 fn update_existing_entry() {
184 let mut d = new_muscle_group_driver();
185 mgd_set(&mut d, MuscleGroup::Abdominals, 0.3, 0.3);
186 mgd_set(&mut d, MuscleGroup::Abdominals, 0.9, 0.9);
187 assert_eq!(mgd_active_count(&d), 1);
188 assert!(
189 (mgd_get(&d, MuscleGroup::Abdominals)
190 .expect("should succeed")
191 .level
192 - 0.9)
193 .abs()
194 < 1e-6
195 );
196 }
197
198 #[test]
199 fn blend_midpoint() {
200 let mut a = new_muscle_group_driver();
201 mgd_set(&mut a, MuscleGroup::Gluteus, 1.0, 1.0);
202 let b = new_muscle_group_driver();
203 let r = mgd_blend(&a, &b, 0.5);
204 assert!(
205 (mgd_get(&r, MuscleGroup::Gluteus)
206 .expect("should succeed")
207 .level
208 - 0.5)
209 .abs()
210 < 1e-5
211 );
212 }
213
214 #[test]
215 fn json_has_keys() {
216 let d = new_muscle_group_driver();
217 let j = mgd_to_json(&d);
218 assert!(j.contains("active_count") && j.contains("total_activation"));
219 }
220}