1#![allow(dead_code)]
5
6use std::collections::HashMap;
7
8#[derive(Clone, Debug)]
14pub struct RigJoint {
15 pub name: String,
16 pub parent: Option<String>,
17 pub position: [f32; 3],
19 pub rotation: [f32; 4],
21 pub scale: [f32; 3],
23}
24
25impl RigJoint {
26 pub fn new(name: impl Into<String>) -> Self {
28 RigJoint {
29 name: name.into(),
30 parent: None,
31 position: [0.0, 0.0, 0.0],
32 rotation: [0.0, 1.0, 0.0, 0.0],
33 scale: [1.0, 1.0, 1.0],
34 }
35 }
36
37 pub fn with_parent(mut self, parent: impl Into<String>) -> Self {
39 self.parent = Some(parent.into());
40 self
41 }
42
43 pub fn with_position(mut self, pos: [f32; 3]) -> Self {
45 self.position = pos;
46 self
47 }
48
49 pub fn children_of<'a>(joints: &'a [RigJoint], parent: &str) -> Vec<&'a RigJoint> {
51 joints
52 .iter()
53 .filter(|j| j.parent.as_deref() == Some(parent))
54 .collect()
55 }
56}
57
58#[derive(Clone, Debug)]
64pub struct MorphBinding {
65 pub morph_name: String,
66 pub param_name: String,
68 pub input_min: f32,
70 pub input_max: f32,
71 pub joint_driver: Option<String>,
73}
74
75impl MorphBinding {
76 pub fn new(morph: impl Into<String>, param: impl Into<String>) -> Self {
78 MorphBinding {
79 morph_name: morph.into(),
80 param_name: param.into(),
81 input_min: 0.0,
82 input_max: 1.0,
83 joint_driver: None,
84 }
85 }
86
87 pub fn compute_weight(&self, param_value: f32) -> f32 {
89 ((param_value - self.input_min) / (self.input_max - self.input_min)).clamp(0.0, 1.0)
90 }
91}
92
93#[derive(Clone, Debug)]
99pub struct RigBodyRegion {
100 pub name: String,
101 pub joints: Vec<String>,
103 pub morph_targets: Vec<String>,
105 pub vertex_group: Option<String>,
107}
108
109pub struct CharacterRig {
115 pub name: String,
116 pub joints: Vec<RigJoint>,
117 pub morph_bindings: Vec<MorphBinding>,
118 pub regions: Vec<RigBodyRegion>,
119 pub metadata: HashMap<String, String>,
120}
121
122impl CharacterRig {
123 pub fn new(name: impl Into<String>) -> Self {
125 CharacterRig {
126 name: name.into(),
127 joints: Vec::new(),
128 morph_bindings: Vec::new(),
129 regions: Vec::new(),
130 metadata: HashMap::new(),
131 }
132 }
133
134 pub fn add_joint(&mut self, joint: RigJoint) {
136 self.joints.push(joint);
137 }
138
139 pub fn add_morph_binding(&mut self, binding: MorphBinding) {
141 self.morph_bindings.push(binding);
142 }
143
144 pub fn add_region(&mut self, region: RigBodyRegion) {
146 self.regions.push(region);
147 }
148
149 pub fn joint_count(&self) -> usize {
151 self.joints.len()
152 }
153
154 pub fn morph_binding_count(&self) -> usize {
156 self.morph_bindings.len()
157 }
158
159 pub fn get_joint(&self, name: &str) -> Option<&RigJoint> {
161 self.joints.iter().find(|j| j.name == name)
162 }
163
164 pub fn root_joints(&self) -> Vec<&RigJoint> {
166 self.joints.iter().filter(|j| j.parent.is_none()).collect()
167 }
168
169 pub fn children_of(&self, parent: &str) -> Vec<&RigJoint> {
171 RigJoint::children_of(&self.joints, parent)
172 }
173
174 pub fn joint_depth(&self, name: &str) -> usize {
176 let mut depth = 0;
177 let mut current = name.to_string();
178 loop {
179 match self.get_joint(¤t) {
180 None => break,
181 Some(j) => match &j.parent {
182 None => break,
183 Some(p) => {
184 depth += 1;
185 current = p.clone();
186 }
187 },
188 }
189 }
190 depth
191 }
192
193 pub fn bindings_for_param(&self, param: &str) -> Vec<&MorphBinding> {
195 self.morph_bindings
196 .iter()
197 .filter(|b| b.param_name == param)
198 .collect()
199 }
200
201 pub fn bindings_for_joint(&self, joint: &str) -> Vec<&MorphBinding> {
203 self.morph_bindings
204 .iter()
205 .filter(|b| b.joint_driver.as_deref() == Some(joint))
206 .collect()
207 }
208
209 pub fn evaluate_morphs(&self, params: &HashMap<String, f32>) -> HashMap<String, f32> {
211 let mut result = HashMap::new();
212 for binding in &self.morph_bindings {
213 let param_value = params.get(&binding.param_name).copied().unwrap_or(0.0);
214 let weight = binding.compute_weight(param_value);
215 result.insert(binding.morph_name.clone(), weight);
216 }
217 result
218 }
219
220 pub fn to_json(&self) -> String {
222 let joints_json: Vec<serde_json::Value> = self
223 .joints
224 .iter()
225 .map(|j| {
226 serde_json::json!({
227 "name": j.name,
228 "parent": j.parent,
229 "position": j.position,
230 "rotation": j.rotation,
231 "scale": j.scale,
232 })
233 })
234 .collect();
235
236 let bindings_json: Vec<serde_json::Value> = self
237 .morph_bindings
238 .iter()
239 .map(|b| {
240 serde_json::json!({
241 "morph_name": b.morph_name,
242 "param_name": b.param_name,
243 "input_min": b.input_min,
244 "input_max": b.input_max,
245 "joint_driver": b.joint_driver,
246 })
247 })
248 .collect();
249
250 let regions_json: Vec<serde_json::Value> = self
251 .regions
252 .iter()
253 .map(|r| {
254 serde_json::json!({
255 "name": r.name,
256 "joints": r.joints,
257 "morph_targets": r.morph_targets,
258 "vertex_group": r.vertex_group,
259 })
260 })
261 .collect();
262
263 let obj = serde_json::json!({
264 "name": self.name,
265 "joints": joints_json,
266 "morph_bindings": bindings_json,
267 "regions": regions_json,
268 "metadata": self.metadata,
269 });
270
271 obj.to_string()
272 }
273}
274
275pub fn standard_human_rig() -> CharacterRig {
281 let mut rig = CharacterRig::new("standard_human");
282 rig.metadata
283 .insert("source".to_string(), "hm08_approximation".to_string());
284 rig.metadata
285 .insert("version".to_string(), "1.0".to_string());
286
287 rig.add_joint(RigJoint::new("hips").with_position([0.0, 1.0, 0.0]));
289
290 rig.add_joint(
292 RigJoint::new("spine1")
293 .with_parent("hips")
294 .with_position([0.0, 1.1, 0.0]),
295 );
296 rig.add_joint(
297 RigJoint::new("spine2")
298 .with_parent("spine1")
299 .with_position([0.0, 1.3, 0.0]),
300 );
301 rig.add_joint(
302 RigJoint::new("spine3")
303 .with_parent("spine2")
304 .with_position([0.0, 1.5, 0.0]),
305 );
306
307 rig.add_joint(
309 RigJoint::new("neck")
310 .with_parent("spine3")
311 .with_position([0.0, 1.6, 0.0]),
312 );
313 rig.add_joint(
314 RigJoint::new("head")
315 .with_parent("neck")
316 .with_position([0.0, 1.75, 0.0]),
317 );
318
319 rig.add_joint(
321 RigJoint::new("l_shoulder")
322 .with_parent("spine3")
323 .with_position([-0.2, 1.5, 0.0]),
324 );
325 rig.add_joint(
326 RigJoint::new("l_elbow")
327 .with_parent("l_shoulder")
328 .with_position([-0.45, 1.2, 0.0]),
329 );
330 rig.add_joint(
331 RigJoint::new("l_wrist")
332 .with_parent("l_elbow")
333 .with_position([-0.65, 0.95, 0.0]),
334 );
335
336 rig.add_joint(
338 RigJoint::new("r_shoulder")
339 .with_parent("spine3")
340 .with_position([0.2, 1.5, 0.0]),
341 );
342 rig.add_joint(
343 RigJoint::new("r_elbow")
344 .with_parent("r_shoulder")
345 .with_position([0.45, 1.2, 0.0]),
346 );
347 rig.add_joint(
348 RigJoint::new("r_wrist")
349 .with_parent("r_elbow")
350 .with_position([0.65, 0.95, 0.0]),
351 );
352
353 rig.add_joint(
355 RigJoint::new("l_hip")
356 .with_parent("hips")
357 .with_position([-0.1, 0.9, 0.0]),
358 );
359 rig.add_joint(
360 RigJoint::new("l_knee")
361 .with_parent("l_hip")
362 .with_position([-0.1, 0.55, 0.0]),
363 );
364 rig.add_joint(
365 RigJoint::new("l_ankle")
366 .with_parent("l_knee")
367 .with_position([-0.1, 0.1, 0.0]),
368 );
369
370 rig.add_joint(
372 RigJoint::new("r_hip")
373 .with_parent("hips")
374 .with_position([0.1, 0.9, 0.0]),
375 );
376 rig.add_joint(
377 RigJoint::new("r_knee")
378 .with_parent("r_hip")
379 .with_position([0.1, 0.55, 0.0]),
380 );
381 rig.add_joint(
382 RigJoint::new("r_ankle")
383 .with_parent("r_knee")
384 .with_position([0.1, 0.1, 0.0]),
385 );
386
387 rig.add_joint(
389 RigJoint::new("l_hand")
390 .with_parent("l_wrist")
391 .with_position([-0.7, 0.85, 0.0]),
392 );
393 rig.add_joint(
395 RigJoint::new("r_hand")
396 .with_parent("r_wrist")
397 .with_position([0.7, 0.85, 0.0]),
398 );
399
400 rig.add_joint(
402 RigJoint::new("l_foot")
403 .with_parent("l_ankle")
404 .with_position([-0.1, 0.0, 0.1]),
405 );
406 rig.add_joint(
408 RigJoint::new("r_foot")
409 .with_parent("r_ankle")
410 .with_position([0.1, 0.0, 0.1]),
411 );
412
413 rig.add_morph_binding(MorphBinding::new("head_size", "head_scale"));
415 rig.add_morph_binding(MorphBinding::new("body_weight", "weight"));
416 rig.add_morph_binding(MorphBinding::new("muscle_tone", "muscle"));
417
418 rig.add_region(RigBodyRegion {
420 name: "head_region".to_string(),
421 joints: vec!["neck".to_string(), "head".to_string()],
422 morph_targets: vec!["head_size".to_string()],
423 vertex_group: Some("head_vg".to_string()),
424 });
425 rig.add_region(RigBodyRegion {
426 name: "torso_region".to_string(),
427 joints: vec![
428 "spine1".to_string(),
429 "spine2".to_string(),
430 "spine3".to_string(),
431 ],
432 morph_targets: vec!["body_weight".to_string(), "muscle_tone".to_string()],
433 vertex_group: Some("torso_vg".to_string()),
434 });
435
436 rig
437}
438
439pub fn minimal_human_rig() -> CharacterRig {
445 let mut rig = CharacterRig::new("minimal_human");
446 rig.metadata
447 .insert("source".to_string(), "minimal_16".to_string());
448
449 rig.add_joint(RigJoint::new("pelvis").with_position([0.0, 1.0, 0.0]));
451
452 rig.add_joint(
454 RigJoint::new("spine")
455 .with_parent("pelvis")
456 .with_position([0.0, 1.2, 0.0]),
457 );
458 rig.add_joint(
459 RigJoint::new("chest")
460 .with_parent("spine")
461 .with_position([0.0, 1.45, 0.0]),
462 );
463 rig.add_joint(
464 RigJoint::new("head")
465 .with_parent("chest")
466 .with_position([0.0, 1.75, 0.0]),
467 );
468
469 rig.add_joint(
471 RigJoint::new("l_upper_arm")
472 .with_parent("chest")
473 .with_position([-0.2, 1.45, 0.0]),
474 );
475 rig.add_joint(
476 RigJoint::new("l_forearm")
477 .with_parent("l_upper_arm")
478 .with_position([-0.4, 1.2, 0.0]),
479 );
480 rig.add_joint(
481 RigJoint::new("l_hand")
482 .with_parent("l_forearm")
483 .with_position([-0.6, 0.95, 0.0]),
484 );
485
486 rig.add_joint(
488 RigJoint::new("r_upper_arm")
489 .with_parent("chest")
490 .with_position([0.2, 1.45, 0.0]),
491 );
492 rig.add_joint(
493 RigJoint::new("r_forearm")
494 .with_parent("r_upper_arm")
495 .with_position([0.4, 1.2, 0.0]),
496 );
497 rig.add_joint(
498 RigJoint::new("r_hand")
499 .with_parent("r_forearm")
500 .with_position([0.6, 0.95, 0.0]),
501 );
502
503 rig.add_joint(
505 RigJoint::new("l_thigh")
506 .with_parent("pelvis")
507 .with_position([-0.1, 0.9, 0.0]),
508 );
509 rig.add_joint(
510 RigJoint::new("l_shin")
511 .with_parent("l_thigh")
512 .with_position([-0.1, 0.5, 0.0]),
513 );
514 rig.add_joint(
515 RigJoint::new("l_foot")
516 .with_parent("l_shin")
517 .with_position([-0.1, 0.05, 0.0]),
518 );
519
520 rig.add_joint(
522 RigJoint::new("r_thigh")
523 .with_parent("pelvis")
524 .with_position([0.1, 0.9, 0.0]),
525 );
526 rig.add_joint(
527 RigJoint::new("r_shin")
528 .with_parent("r_thigh")
529 .with_position([0.1, 0.5, 0.0]),
530 );
531 rig.add_joint(
532 RigJoint::new("r_foot")
533 .with_parent("r_shin")
534 .with_position([0.1, 0.05, 0.0]),
535 );
536
537 rig.add_morph_binding(MorphBinding::new("head_size", "head_scale"));
539 rig.add_morph_binding(MorphBinding::new("body_weight", "weight"));
540
541 rig
542}
543
544#[cfg(test)]
549mod tests {
550 use super::*;
551
552 #[test]
553 fn test_rig_joint_new() {
554 let j = RigJoint::new("hips");
555 assert_eq!(j.name, "hips");
556 assert!(j.parent.is_none());
557 assert_eq!(j.position, [0.0, 0.0, 0.0]);
558 assert_eq!(j.rotation, [0.0, 1.0, 0.0, 0.0]);
559 assert_eq!(j.scale, [1.0, 1.0, 1.0]);
560 }
561
562 #[test]
563 fn test_rig_joint_with_parent() {
564 let j = RigJoint::new("spine").with_parent("hips");
565 assert_eq!(j.parent.as_deref(), Some("hips"));
566 }
567
568 #[test]
569 fn test_morph_binding_compute_weight() {
570 let b = MorphBinding::new("fat_belly", "weight");
571 let w = b.compute_weight(0.5);
573 assert!((w - 0.5).abs() < 1e-6);
574 assert!((b.compute_weight(1.0) - 1.0).abs() < 1e-6);
576 assert!((b.compute_weight(0.0) - 0.0).abs() < 1e-6);
578 }
579
580 #[test]
581 fn test_morph_binding_clamped() {
582 let b = MorphBinding::new("fat_belly", "weight");
583 assert!((b.compute_weight(2.0) - 1.0).abs() < 1e-6);
585 assert!((b.compute_weight(-1.0) - 0.0).abs() < 1e-6);
587 }
588
589 #[test]
590 fn test_character_rig_new() {
591 let rig = CharacterRig::new("test_rig");
592 assert_eq!(rig.name, "test_rig");
593 assert_eq!(rig.joint_count(), 0);
594 assert_eq!(rig.morph_binding_count(), 0);
595 assert!(rig.regions.is_empty());
596 assert!(rig.metadata.is_empty());
597 }
598
599 #[test]
600 fn test_add_joint() {
601 let mut rig = CharacterRig::new("rig");
602 rig.add_joint(RigJoint::new("hips"));
603 rig.add_joint(RigJoint::new("spine").with_parent("hips"));
604 assert_eq!(rig.joint_count(), 2);
605 }
606
607 #[test]
608 fn test_get_joint() {
609 let mut rig = CharacterRig::new("rig");
610 rig.add_joint(RigJoint::new("hips"));
611 assert!(rig.get_joint("hips").is_some());
612 assert!(rig.get_joint("nonexistent").is_none());
613 }
614
615 #[test]
616 fn test_root_joints() {
617 let mut rig = CharacterRig::new("rig");
618 rig.add_joint(RigJoint::new("hips"));
619 rig.add_joint(RigJoint::new("spine").with_parent("hips"));
620 rig.add_joint(RigJoint::new("chest").with_parent("spine"));
621 let roots = rig.root_joints();
622 assert_eq!(roots.len(), 1);
623 assert_eq!(roots[0].name, "hips");
624 }
625
626 #[test]
627 fn test_children_of() {
628 let mut rig = CharacterRig::new("rig");
629 rig.add_joint(RigJoint::new("hips"));
630 rig.add_joint(RigJoint::new("l_hip").with_parent("hips"));
631 rig.add_joint(RigJoint::new("r_hip").with_parent("hips"));
632 rig.add_joint(RigJoint::new("spine").with_parent("hips"));
633 let children = rig.children_of("hips");
634 assert_eq!(children.len(), 3);
635 }
636
637 #[test]
638 fn test_joint_depth() {
639 let mut rig = CharacterRig::new("rig");
640 rig.add_joint(RigJoint::new("hips"));
641 rig.add_joint(RigJoint::new("spine").with_parent("hips"));
642 rig.add_joint(RigJoint::new("chest").with_parent("spine"));
643 rig.add_joint(RigJoint::new("neck").with_parent("chest"));
644 rig.add_joint(RigJoint::new("head").with_parent("neck"));
645
646 assert_eq!(rig.joint_depth("hips"), 0);
647 assert_eq!(rig.joint_depth("spine"), 1);
648 assert_eq!(rig.joint_depth("chest"), 2);
649 assert_eq!(rig.joint_depth("neck"), 3);
650 assert_eq!(rig.joint_depth("head"), 4);
651 }
652
653 #[test]
654 fn test_bindings_for_param() {
655 let mut rig = CharacterRig::new("rig");
656 rig.add_morph_binding(MorphBinding::new("fat_belly", "weight"));
657 rig.add_morph_binding(MorphBinding::new("fat_arms", "weight"));
658 rig.add_morph_binding(MorphBinding::new("muscle", "muscle"));
659 let by_weight = rig.bindings_for_param("weight");
660 assert_eq!(by_weight.len(), 2);
661 let by_muscle = rig.bindings_for_param("muscle");
662 assert_eq!(by_muscle.len(), 1);
663 }
664
665 #[test]
666 fn test_evaluate_morphs() {
667 let mut rig = CharacterRig::new("rig");
668 rig.add_morph_binding(MorphBinding::new("fat_belly", "weight"));
669 rig.add_morph_binding(MorphBinding::new("muscle", "muscle"));
670
671 let mut params = HashMap::new();
672 params.insert("weight".to_string(), 0.75_f32);
673 let weights = rig.evaluate_morphs(¶ms);
676 assert!((weights["fat_belly"] - 0.75).abs() < 1e-6);
677 assert!((weights["muscle"] - 0.0).abs() < 1e-6);
678 }
679
680 #[test]
681 fn test_to_json() {
682 let mut rig = CharacterRig::new("test_rig");
683 rig.add_joint(RigJoint::new("hips"));
684 rig.add_morph_binding(MorphBinding::new("fat_belly", "weight"));
685 let json = rig.to_json();
686 assert!(json.contains("test_rig"));
687 assert!(json.contains("hips"));
688 assert!(json.contains("fat_belly"));
689 assert!(json.contains("weight"));
690 }
691
692 #[test]
693 fn test_standard_human_rig() {
694 let rig = standard_human_rig();
695 assert_eq!(rig.name, "standard_human");
696 assert!(rig.joint_count() >= 20);
698 assert!(rig.morph_binding_count() > 0);
699 let roots = rig.root_joints();
701 assert_eq!(roots.len(), 1);
702 assert_eq!(roots[0].name, "hips");
703 assert!(rig.joint_depth("head") >= 2);
705 }
706
707 #[test]
708 fn test_minimal_human_rig() {
709 let rig = minimal_human_rig();
710 assert_eq!(rig.name, "minimal_human");
711 assert_eq!(rig.joint_count(), 16);
712 let roots = rig.root_joints();
714 assert_eq!(roots.len(), 1);
715 assert_eq!(roots[0].name, "pelvis");
716 assert_eq!(rig.joint_depth("head"), 3);
718 assert!(rig.morph_binding_count() >= 2);
720 }
721}