1#[allow(dead_code)]
4pub struct MuscleLine {
5 pub name: String,
6 pub origin: [f32; 3],
7 pub insertion: [f32; 3],
8 pub max_bulge: f32,
9 pub falloff_radius: f32,
10 pub contraction: f32,
11}
12
13#[allow(dead_code)]
14pub struct MuscleDeformation {
15 pub vertex_deltas: Vec<[f32; 3]>,
16 pub influence_weights: Vec<f32>,
17}
18
19#[allow(dead_code)]
20pub struct MuscleGroup {
21 pub name: String,
22 pub muscles: Vec<MuscleLine>,
23}
24
25#[allow(dead_code)]
26pub fn new_muscle_line(
27 name: &str,
28 origin: [f32; 3],
29 insertion: [f32; 3],
30 max_bulge: f32,
31 falloff: f32,
32) -> MuscleLine {
33 MuscleLine {
34 name: name.to_string(),
35 origin,
36 insertion,
37 max_bulge,
38 falloff_radius: falloff,
39 contraction: 0.0,
40 }
41}
42
43#[allow(dead_code)]
44pub fn muscle_direction(muscle: &MuscleLine) -> [f32; 3] {
45 let dx = muscle.insertion[0] - muscle.origin[0];
46 let dy = muscle.insertion[1] - muscle.origin[1];
47 let dz = muscle.insertion[2] - muscle.origin[2];
48 let len = (dx * dx + dy * dy + dz * dz).sqrt();
49 if len > 1e-6 {
50 [dx / len, dy / len, dz / len]
51 } else {
52 [0.0, 1.0, 0.0]
53 }
54}
55
56#[allow(dead_code)]
57pub fn muscle_length(muscle: &MuscleLine) -> f32 {
58 let dx = muscle.insertion[0] - muscle.origin[0];
59 let dy = muscle.insertion[1] - muscle.origin[1];
60 let dz = muscle.insertion[2] - muscle.origin[2];
61 (dx * dx + dy * dy + dz * dz).sqrt()
62}
63
64#[allow(dead_code)]
65pub fn point_to_line_distance(point: [f32; 3], line_start: [f32; 3], line_end: [f32; 3]) -> f32 {
66 let dx = line_end[0] - line_start[0];
67 let dy = line_end[1] - line_start[1];
68 let dz = line_end[2] - line_start[2];
69 let len_sq = dx * dx + dy * dy + dz * dz;
70
71 if len_sq < 1e-12 {
72 let ex = point[0] - line_start[0];
73 let ey = point[1] - line_start[1];
74 let ez = point[2] - line_start[2];
75 return (ex * ex + ey * ey + ez * ez).sqrt();
76 }
77
78 let t = ((point[0] - line_start[0]) * dx
79 + (point[1] - line_start[1]) * dy
80 + (point[2] - line_start[2]) * dz)
81 / len_sq;
82 let t = t.clamp(0.0, 1.0);
83
84 let proj_x = line_start[0] + t * dx - point[0];
85 let proj_y = line_start[1] + t * dy - point[1];
86 let proj_z = line_start[2] + t * dz - point[2];
87 (proj_x * proj_x + proj_y * proj_y + proj_z * proj_z).sqrt()
88}
89
90#[allow(dead_code)]
91pub fn muscle_influence_weight(muscle: &MuscleLine, pos: [f32; 3]) -> f32 {
92 let dist = point_to_line_distance(pos, muscle.origin, muscle.insertion);
93 if muscle.falloff_radius < 1e-6 {
94 return 0.0;
95 }
96 let normalized = (dist / muscle.falloff_radius).clamp(0.0, 1.0);
97 (1.0 - normalized).max(0.0)
98}
99
100#[allow(dead_code)]
101pub fn compute_muscle_deformation(
102 muscle: &MuscleLine,
103 positions: &[[f32; 3]],
104 normals: &[[f32; 3]],
105) -> MuscleDeformation {
106 let n = positions.len().min(normals.len());
107 let dir = muscle_direction(muscle);
108 let mut vertex_deltas = Vec::with_capacity(n);
109 let mut influence_weights = Vec::with_capacity(n);
110
111 for i in 0..n {
112 let pos = positions[i];
113 let weight = muscle_influence_weight(muscle, pos);
114 let influence = weight * muscle.contraction * muscle.max_bulge;
115
116 let to_point = [
118 pos[0] - muscle.origin[0],
119 pos[1] - muscle.origin[1],
120 pos[2] - muscle.origin[2],
121 ];
122 let dot = to_point[0] * dir[0] + to_point[1] * dir[1] + to_point[2] * dir[2];
123 let along = [dir[0] * dot, dir[1] * dot, dir[2] * dot];
124 let radial = [
125 to_point[0] - along[0],
126 to_point[1] - along[1],
127 to_point[2] - along[2],
128 ];
129 let radial_len =
130 (radial[0] * radial[0] + radial[1] * radial[1] + radial[2] * radial[2]).sqrt();
131
132 let delta = if radial_len > 1e-6 {
133 [
134 radial[0] / radial_len * influence,
135 radial[1] / radial_len * influence,
136 radial[2] / radial_len * influence,
137 ]
138 } else {
139 let nrm = normals[i];
140 [nrm[0] * influence, nrm[1] * influence, nrm[2] * influence]
141 };
142
143 vertex_deltas.push(delta);
144 influence_weights.push(weight);
145 }
146
147 MuscleDeformation {
148 vertex_deltas,
149 influence_weights,
150 }
151}
152
153#[allow(dead_code)]
154pub fn apply_muscle_deformation(
155 positions: &mut [[f32; 3]],
156 deform: &MuscleDeformation,
157 weight: f32,
158) {
159 let n = positions.len().min(deform.vertex_deltas.len());
160 for (pos, delta) in positions[..n]
161 .iter_mut()
162 .zip(deform.vertex_deltas[..n].iter())
163 {
164 pos[0] += delta[0] * weight;
165 pos[1] += delta[1] * weight;
166 pos[2] += delta[2] * weight;
167 }
168}
169
170#[allow(dead_code)]
171pub fn contract_muscle(muscle: &mut MuscleLine, amount: f32) {
172 muscle.contraction = amount.clamp(0.0, 1.0);
173}
174
175#[allow(dead_code)]
176pub fn relax_muscle(muscle: &mut MuscleLine) {
177 muscle.contraction = 0.0;
178}
179
180#[allow(dead_code)]
181pub fn muscle_group_deformation(
182 group: &MuscleGroup,
183 positions: &[[f32; 3]],
184 normals: &[[f32; 3]],
185) -> Vec<MuscleDeformation> {
186 group
187 .muscles
188 .iter()
189 .map(|m| compute_muscle_deformation(m, positions, normals))
190 .collect()
191}
192
193#[allow(dead_code)]
194pub fn add_muscle_to_group(group: &mut MuscleGroup, muscle: MuscleLine) {
195 group.muscles.push(muscle);
196}
197
198#[allow(dead_code)]
199pub fn new_muscle_group(name: &str) -> MuscleGroup {
200 MuscleGroup {
201 name: name.to_string(),
202 muscles: Vec::new(),
203 }
204}
205
206#[allow(dead_code)]
207pub fn default_arm_muscles() -> MuscleGroup {
208 let mut group = new_muscle_group("arm");
209
210 let mut bicep = new_muscle_line("bicep", [0.15, 0.4, 0.0], [0.15, -0.1, 0.05], 0.02, 0.08);
212 bicep.contraction = 0.0;
213
214 let mut tricep = new_muscle_line(
216 "tricep",
217 [0.15, 0.35, -0.02],
218 [0.15, -0.1, -0.04],
219 0.018,
220 0.07,
221 );
222 tricep.contraction = 0.0;
223
224 let mut deltoid = new_muscle_line("deltoid", [0.08, 0.42, 0.0], [0.18, 0.25, 0.0], 0.025, 0.1);
226 deltoid.contraction = 0.0;
227
228 group.muscles.push(bicep);
229 group.muscles.push(tricep);
230 group.muscles.push(deltoid);
231
232 group
233}
234
235#[cfg(test)]
236mod tests {
237 use super::*;
238
239 #[test]
240 fn test_muscle_direction_unit_length() {
241 let m = new_muscle_line("test", [0.0, 0.0, 0.0], [3.0, 4.0, 0.0], 0.01, 0.1);
242 let dir = muscle_direction(&m);
243 let len = (dir[0] * dir[0] + dir[1] * dir[1] + dir[2] * dir[2]).sqrt();
244 assert!((len - 1.0).abs() < 1e-5);
245 }
246
247 #[test]
248 fn test_muscle_direction_components() {
249 let m = new_muscle_line("test", [0.0, 0.0, 0.0], [1.0, 0.0, 0.0], 0.01, 0.1);
250 let dir = muscle_direction(&m);
251 assert!((dir[0] - 1.0).abs() < 1e-5);
252 assert!(dir[1].abs() < 1e-5);
253 assert!(dir[2].abs() < 1e-5);
254 }
255
256 #[test]
257 fn test_muscle_length() {
258 let m = new_muscle_line("test", [0.0, 0.0, 0.0], [3.0, 4.0, 0.0], 0.01, 0.1);
259 assert!((muscle_length(&m) - 5.0).abs() < 1e-5);
260 }
261
262 #[test]
263 fn test_muscle_length_zero() {
264 let m = new_muscle_line("test", [1.0, 2.0, 3.0], [1.0, 2.0, 3.0], 0.01, 0.1);
265 assert!(muscle_length(&m) < 1e-5);
266 }
267
268 #[test]
269 fn test_point_to_line_distance_on_line() {
270 let dist = point_to_line_distance([0.5, 0.0, 0.0], [0.0, 0.0, 0.0], [1.0, 0.0, 0.0]);
272 assert!(dist < 1e-5);
273 }
274
275 #[test]
276 fn test_point_to_line_distance_perpendicular() {
277 let dist = point_to_line_distance([0.5, 1.0, 0.0], [0.0, 0.0, 0.0], [1.0, 0.0, 0.0]);
279 assert!((dist - 1.0).abs() < 1e-5);
280 }
281
282 #[test]
283 fn test_point_to_line_distance_clamped() {
284 let dist = point_to_line_distance([2.0, 1.0, 0.0], [0.0, 0.0, 0.0], [1.0, 0.0, 0.0]);
286 assert!((dist - 2.0f32.sqrt()).abs() < 1e-4);
288 }
289
290 #[test]
291 fn test_muscle_influence_weight_on_axis() {
292 let m = new_muscle_line("test", [0.0, 0.0, 0.0], [1.0, 0.0, 0.0], 0.01, 0.5);
293 let w = muscle_influence_weight(&m, [0.5, 0.0, 0.0]);
295 assert!((w - 1.0).abs() < 1e-5);
296 }
297
298 #[test]
299 fn test_muscle_influence_weight_far() {
300 let m = new_muscle_line("test", [0.0, 0.0, 0.0], [1.0, 0.0, 0.0], 0.01, 0.5);
301 let w = muscle_influence_weight(&m, [0.5, 10.0, 0.0]);
303 assert!(w < 1e-5);
304 }
305
306 #[test]
307 fn test_compute_muscle_deformation() {
308 let mut m = new_muscle_line("test", [0.0, 0.0, 0.0], [0.0, 1.0, 0.0], 0.05, 0.2);
309 contract_muscle(&mut m, 1.0);
310 let positions = vec![[0.1, 0.5, 0.0], [5.0, 5.0, 5.0]];
311 let normals = vec![[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
312 let deform = compute_muscle_deformation(&m, &positions, &normals);
313 assert_eq!(deform.vertex_deltas.len(), 2);
314 assert_eq!(deform.influence_weights.len(), 2);
315 assert!(deform.influence_weights[0] > 0.0);
317 }
318
319 #[test]
320 fn test_contract_relax() {
321 let mut m = new_muscle_line("test", [0.0; 3], [1.0, 0.0, 0.0], 0.01, 0.1);
322 contract_muscle(&mut m, 0.7);
323 assert!((m.contraction - 0.7).abs() < 1e-5);
324 relax_muscle(&mut m);
325 assert!(m.contraction < 1e-5);
326 }
327
328 #[test]
329 fn test_contract_clamp() {
330 let mut m = new_muscle_line("test", [0.0; 3], [1.0, 0.0, 0.0], 0.01, 0.1);
331 contract_muscle(&mut m, 2.0);
332 assert!((m.contraction - 1.0).abs() < 1e-5);
333 contract_muscle(&mut m, -1.0);
334 assert!(m.contraction < 1e-5);
335 }
336
337 #[test]
338 fn test_muscle_group() {
339 let mut group = new_muscle_group("legs");
340 assert!(group.muscles.is_empty());
341 let m = new_muscle_line("quad", [0.0; 3], [0.0, -0.5, 0.0], 0.02, 0.1);
342 add_muscle_to_group(&mut group, m);
343 assert_eq!(group.muscles.len(), 1);
344 }
345
346 #[test]
347 fn test_default_arm_muscles_has_three() {
348 let group = default_arm_muscles();
349 assert_eq!(group.muscles.len(), 3);
350 }
351
352 #[test]
353 fn test_default_arm_muscles_names() {
354 let group = default_arm_muscles();
355 let names: Vec<&str> = group.muscles.iter().map(|m| m.name.as_str()).collect();
356 assert!(names.contains(&"bicep"));
357 assert!(names.contains(&"tricep"));
358 assert!(names.contains(&"deltoid"));
359 }
360
361 #[test]
362 fn test_muscle_group_deformation() {
363 let group = default_arm_muscles();
364 let positions = vec![[0.15f32, 0.3, 0.0]];
365 let normals = vec![[0.0f32, 0.0, 1.0]];
366 let deforms = muscle_group_deformation(&group, &positions, &normals);
367 assert_eq!(deforms.len(), 3);
368 }
369
370 #[test]
371 fn test_apply_muscle_deformation() {
372 let mut m = new_muscle_line("test", [0.0; 3], [0.0, 1.0, 0.0], 0.1, 0.5);
373 contract_muscle(&mut m, 1.0);
374 let positions_orig = vec![[0.1f32, 0.5, 0.0]];
375 let normals = vec![[1.0f32, 0.0, 0.0]];
376 let deform = compute_muscle_deformation(&m, &positions_orig, &normals);
377 let mut positions = positions_orig.clone();
378 apply_muscle_deformation(&mut positions, &deform, 1.0);
379 assert_eq!(positions.len(), 1);
380 }
381}