oxihuman_export/
dynamic_bone_export.rs1#![allow(dead_code)]
4
5#[allow(dead_code)]
7pub struct DynamicBone {
8 pub name: String,
9 pub root_bone: String,
10 pub stiffness: f32,
11 pub damping: f32,
12 pub gravity: [f32; 3],
13 pub radius: f32,
14 pub end_length: f32,
15 pub colliders: Vec<String>,
16}
17
18#[allow(dead_code)]
19pub struct DynamicBoneExport {
20 pub chains: Vec<DynamicBone>,
21}
22
23#[allow(dead_code)]
24pub fn new_dynamic_bone_export() -> DynamicBoneExport {
25 DynamicBoneExport { chains: vec![] }
26}
27
28#[allow(dead_code)]
29pub fn add_dynamic_bone(export: &mut DynamicBoneExport, bone: DynamicBone) {
30 export.chains.push(bone);
31}
32
33#[allow(dead_code)]
34pub fn dynamic_bone_count(export: &DynamicBoneExport) -> usize {
35 export.chains.len()
36}
37
38#[allow(dead_code)]
39pub fn find_dynamic_bone<'a>(export: &'a DynamicBoneExport, name: &str) -> Option<&'a DynamicBone> {
40 export.chains.iter().find(|b| b.name == name)
41}
42
43#[allow(dead_code)]
44pub fn default_dynamic_bone(name: &str, root: &str) -> DynamicBone {
45 DynamicBone {
46 name: name.to_string(),
47 root_bone: root.to_string(),
48 stiffness: 0.2,
49 damping: 0.2,
50 gravity: [0.0, -9.81, 0.0],
51 radius: 0.02,
52 end_length: 0.0,
53 colliders: vec![],
54 }
55}
56
57#[allow(dead_code)]
58pub fn validate_dynamic_bone(bone: &DynamicBone) -> bool {
59 !bone.name.is_empty()
60 && !bone.root_bone.is_empty()
61 && (0.0..=1.0).contains(&bone.stiffness)
62 && (0.0..=1.0).contains(&bone.damping)
63 && bone.radius >= 0.0
64}
65
66#[allow(dead_code)]
67pub fn total_colliders(export: &DynamicBoneExport) -> usize {
68 export.chains.iter().map(|b| b.colliders.len()).sum()
69}
70
71#[allow(dead_code)]
72pub fn dynamic_bone_to_json(bone: &DynamicBone) -> String {
73 format!(
74 "{{\"name\":\"{}\",\"root\":\"{}\",\"stiffness\":{},\"damping\":{}}}",
75 bone.name, bone.root_bone, bone.stiffness, bone.damping
76 )
77}
78
79#[allow(dead_code)]
80pub fn dynamic_bone_export_to_json(export: &DynamicBoneExport) -> String {
81 format!("{{\"chain_count\":{}}}", export.chains.len())
82}
83
84#[allow(dead_code)]
85pub fn simulate_gravity_offset(bone: &DynamicBone, dt: f32) -> [f32; 3] {
86 let g = bone.gravity;
87 let stiffness = bone.stiffness.clamp(0.0, 1.0);
88 let influence = (1.0 - stiffness) * dt;
89 [g[0] * influence, g[1] * influence, g[2] * influence]
90}
91
92#[allow(dead_code)]
93pub fn bones_with_colliders(export: &DynamicBoneExport) -> Vec<&DynamicBone> {
94 export
95 .chains
96 .iter()
97 .filter(|b| !b.colliders.is_empty())
98 .collect()
99}
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104
105 fn hair_bone() -> DynamicBone {
106 default_dynamic_bone("hair_chain", "hair_root")
107 }
108
109 #[test]
110 fn test_add_dynamic_bone() {
111 let mut e = new_dynamic_bone_export();
112 add_dynamic_bone(&mut e, hair_bone());
113 assert_eq!(dynamic_bone_count(&e), 1);
114 }
115
116 #[test]
117 fn test_find_dynamic_bone() {
118 let mut e = new_dynamic_bone_export();
119 add_dynamic_bone(&mut e, hair_bone());
120 assert!(find_dynamic_bone(&e, "hair_chain").is_some());
121 }
122
123 #[test]
124 fn test_validate_default() {
125 let b = hair_bone();
126 assert!(validate_dynamic_bone(&b));
127 }
128
129 #[test]
130 fn test_validate_bad_stiffness() {
131 let mut b = hair_bone();
132 b.stiffness = 2.0;
133 assert!(!validate_dynamic_bone(&b));
134 }
135
136 #[test]
137 fn test_gravity_offset_nonzero() {
138 let b = hair_bone();
139 let off = simulate_gravity_offset(&b, 0.016);
140 assert!(off[1] < 0.0);
141 }
142
143 #[test]
144 fn test_bones_with_colliders_empty() {
145 let mut e = new_dynamic_bone_export();
146 add_dynamic_bone(&mut e, hair_bone());
147 assert_eq!(bones_with_colliders(&e).len(), 0);
148 }
149
150 #[test]
151 fn test_bones_with_colliders_found() {
152 let mut e = new_dynamic_bone_export();
153 let mut b = hair_bone();
154 b.colliders.push("head_collider".to_string());
155 add_dynamic_bone(&mut e, b);
156 assert_eq!(bones_with_colliders(&e).len(), 1);
157 }
158
159 #[test]
160 fn test_to_json() {
161 let b = hair_bone();
162 let j = dynamic_bone_to_json(&b);
163 assert!(j.contains("hair_chain"));
164 }
165
166 #[test]
167 fn test_export_to_json() {
168 let mut e = new_dynamic_bone_export();
169 add_dynamic_bone(&mut e, hair_bone());
170 let j = dynamic_bone_export_to_json(&e);
171 assert!(j.contains("chain_count"));
172 }
173
174 #[test]
175 fn test_total_colliders() {
176 let mut e = new_dynamic_bone_export();
177 let mut b = hair_bone();
178 b.colliders = vec!["c1".to_string(), "c2".to_string()];
179 add_dynamic_bone(&mut e, b);
180 assert_eq!(total_colliders(&e), 2);
181 }
182}