1#[allow(dead_code)]
4pub struct FacialBone {
5 pub name: String,
6 pub position: [f32; 3],
7 pub rotation: [f32; 4], pub parent: Option<usize>,
9 pub children: Vec<usize>,
10 pub weight: f32,
11 pub influence_radius: f32,
12}
13
14#[allow(dead_code)]
15pub struct CorrectiveShape {
16 pub name: String,
17 pub trigger_bone: String,
18 pub trigger_angle: f32, pub deltas: Vec<[f32; 3]>,
20 pub weight: f32,
21}
22
23#[allow(dead_code)]
24pub struct FacialRig {
25 pub bones: Vec<FacialBone>,
26 pub correctives: Vec<CorrectiveShape>,
27 pub version: u32,
28}
29
30#[allow(dead_code)]
31pub struct FacialPose {
32 pub bone_rotations: Vec<[f32; 4]>, pub blend_weights: Vec<f32>, }
35
36#[allow(dead_code)]
37pub fn new_facial_rig() -> FacialRig {
38 FacialRig {
39 bones: Vec::new(),
40 correctives: Vec::new(),
41 version: 1,
42 }
43}
44
45#[allow(dead_code)]
46pub fn add_bone(rig: &mut FacialRig, name: &str, pos: [f32; 3], parent: Option<usize>) -> usize {
47 let idx = rig.bones.len();
48 if let Some(p) = parent {
49 if p < rig.bones.len() {
50 rig.bones[p].children.push(idx);
51 }
52 }
53 rig.bones.push(FacialBone {
54 name: name.to_string(),
55 position: pos,
56 rotation: [0.0, 0.0, 0.0, 1.0],
57 parent,
58 children: Vec::new(),
59 weight: 1.0,
60 influence_radius: 0.1,
61 });
62 idx
63}
64
65#[allow(dead_code)]
66pub fn add_corrective(rig: &mut FacialRig, c: CorrectiveShape) {
67 rig.correctives.push(c);
68}
69
70#[allow(dead_code)]
71pub fn default_facial_rig() -> FacialRig {
72 let mut rig = new_facial_rig();
73 let jaw = add_bone(&mut rig, "jaw", [0.0, -0.05, 0.0], None);
75 let _eye_l = add_bone(&mut rig, "eye_l", [-0.03, 0.02, 0.04], None);
77 let _eye_r = add_bone(&mut rig, "eye_r", [0.03, 0.02, 0.04], None);
78 let _brow_l = add_bone(&mut rig, "brow_l", [-0.03, 0.05, 0.03], None);
80 let _brow_l_inner = add_bone(&mut rig, "brow_l_inner", [-0.015, 0.05, 0.03], None);
81 let _brow_r = add_bone(&mut rig, "brow_r", [0.03, 0.05, 0.03], None);
82 let _brow_r_inner = add_bone(&mut rig, "brow_r_inner", [0.015, 0.05, 0.03], None);
83 let _cheek_l = add_bone(&mut rig, "cheek_l", [-0.04, 0.0, 0.03], None);
85 let _cheek_r = add_bone(&mut rig, "cheek_r", [0.04, 0.0, 0.03], None);
86 let _lip_upper = add_bone(&mut rig, "lip_upper", [0.0, -0.01, 0.05], Some(jaw));
88 let _lip_lower = add_bone(&mut rig, "lip_lower", [0.0, -0.03, 0.05], Some(jaw));
89 let _lip_corner_l = add_bone(&mut rig, "lip_corner_l", [-0.02, -0.02, 0.05], Some(jaw));
90 let _lip_corner_r = add_bone(&mut rig, "lip_corner_r", [0.02, -0.02, 0.05], Some(jaw));
91 let _nose = add_bone(&mut rig, "nose", [0.0, 0.01, 0.06], None);
93 rig
94}
95
96#[allow(dead_code)]
97pub fn get_bone<'a>(rig: &'a FacialRig, name: &str) -> Option<&'a FacialBone> {
98 rig.bones.iter().find(|b| b.name == name)
99}
100
101#[allow(dead_code)]
102pub fn set_bone_rotation(rig: &mut FacialRig, name: &str, rot: [f32; 4]) -> bool {
103 if let Some(bone) = rig.bones.iter_mut().find(|b| b.name == name) {
104 bone.rotation = rot;
105 true
106 } else {
107 false
108 }
109}
110
111#[allow(dead_code)]
113pub fn evaluate_correctives(rig: &FacialRig) -> Vec<f32> {
114 rig.correctives
115 .iter()
116 .map(|c| {
117 if let Some(bone) = rig.bones.iter().find(|b| b.name == c.trigger_bone) {
119 let angle = quat_angle_from_identity(bone.rotation);
120 let diff = (angle - c.trigger_angle).abs();
121 let w = (1.0 - diff / std::f32::consts::PI).max(0.0);
122 w * c.weight
123 } else {
124 0.0
125 }
126 })
127 .collect()
128}
129
130fn quat_angle_from_identity(q: [f32; 4]) -> f32 {
131 let w = q[3].clamp(-1.0, 1.0);
133 2.0 * w.acos()
134}
135
136#[allow(dead_code)]
137pub fn apply_facial_pose(
138 rig: &FacialRig,
139 pose: &FacialPose,
140 base_positions: &[[f32; 3]],
141) -> Vec<[f32; 3]> {
142 let mut result: Vec<[f32; 3]> = base_positions.to_vec();
143
144 for (i, &w) in pose.blend_weights.iter().enumerate() {
146 if i >= rig.correctives.len() {
147 break;
148 }
149 if w.abs() < 1e-6 {
150 continue;
151 }
152 let corr = &rig.correctives[i];
153 for (j, pos) in result.iter_mut().enumerate() {
154 if j < corr.deltas.len() {
155 pos[0] += corr.deltas[j][0] * w;
156 pos[1] += corr.deltas[j][1] * w;
157 pos[2] += corr.deltas[j][2] * w;
158 }
159 }
160 }
161 result
162}
163
164#[allow(dead_code)]
165pub fn bone_count(rig: &FacialRig) -> usize {
166 rig.bones.len()
167}
168
169#[allow(dead_code)]
170pub fn corrective_count(rig: &FacialRig) -> usize {
171 rig.correctives.len()
172}
173
174#[allow(dead_code)]
175pub fn facial_rig_to_json(rig: &FacialRig) -> String {
176 let mut s = String::from("{");
177 s.push_str(&format!(
178 r#""version":{},"bone_count":{},"corrective_count":{}"#,
179 rig.version,
180 rig.bones.len(),
181 rig.correctives.len()
182 ));
183 s.push('}');
184 s
185}
186
187#[allow(dead_code)]
188pub fn identity_pose(rig: &FacialRig) -> FacialPose {
189 FacialPose {
190 bone_rotations: vec![[0.0, 0.0, 0.0, 1.0]; rig.bones.len()],
191 blend_weights: vec![0.0; rig.correctives.len()],
192 }
193}
194
195#[allow(dead_code)]
196pub fn blend_facial_poses(a: &FacialPose, b: &FacialPose, t: f32) -> FacialPose {
197 let len = a.bone_rotations.len().min(b.bone_rotations.len());
198 let bone_rotations = (0..len)
199 .map(|i| {
200 let qa = a.bone_rotations[i];
201 let qb = b.bone_rotations[i];
202 slerp_quat(qa, qb, t)
203 })
204 .collect();
205
206 let wlen = a.blend_weights.len().min(b.blend_weights.len());
207 let blend_weights = (0..wlen)
208 .map(|i| a.blend_weights[i] * (1.0 - t) + b.blend_weights[i] * t)
209 .collect();
210
211 FacialPose {
212 bone_rotations,
213 blend_weights,
214 }
215}
216
217fn slerp_quat(a: [f32; 4], b: [f32; 4], t: f32) -> [f32; 4] {
218 let mut dot = a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3];
219 let bq = if dot < 0.0 {
220 dot = -dot;
221 [-b[0], -b[1], -b[2], -b[3]]
222 } else {
223 b
224 };
225
226 if dot > 0.9995 {
227 let r = [
228 a[0] + t * (bq[0] - a[0]),
229 a[1] + t * (bq[1] - a[1]),
230 a[2] + t * (bq[2] - a[2]),
231 a[3] + t * (bq[3] - a[3]),
232 ];
233 normalize_q(r)
234 } else {
235 let theta_0 = dot.acos();
236 let theta = theta_0 * t;
237 let sin_theta = theta.sin();
238 let sin_theta_0 = theta_0.sin();
239 let s0 = (theta_0 * (1.0 - t)).sin() / sin_theta_0;
240 let s1 = sin_theta / sin_theta_0;
241 [
242 s0 * a[0] + s1 * bq[0],
243 s0 * a[1] + s1 * bq[1],
244 s0 * a[2] + s1 * bq[2],
245 s0 * a[3] + s1 * bq[3],
246 ]
247 }
248}
249
250fn normalize_q(q: [f32; 4]) -> [f32; 4] {
251 let len = (q[0] * q[0] + q[1] * q[1] + q[2] * q[2] + q[3] * q[3]).sqrt();
252 if len < 1e-9 {
253 [0.0, 0.0, 0.0, 1.0]
254 } else {
255 [q[0] / len, q[1] / len, q[2] / len, q[3] / len]
256 }
257}
258
259#[allow(dead_code)]
260pub fn quat_angle_between(q1: [f32; 4], q2: [f32; 4]) -> f32 {
261 let dot = (q1[0] * q2[0] + q1[1] * q2[1] + q1[2] * q2[2] + q1[3] * q2[3])
263 .abs()
264 .clamp(0.0, 1.0);
265 2.0 * dot.acos()
266}
267
268#[cfg(test)]
269mod tests {
270 use super::*;
271 use std::f32::consts::PI;
272
273 #[test]
274 fn test_new_facial_rig_empty() {
275 let rig = new_facial_rig();
276 assert_eq!(rig.bones.len(), 0);
277 assert_eq!(rig.correctives.len(), 0);
278 assert_eq!(rig.version, 1);
279 }
280
281 #[test]
282 fn test_add_bone_returns_index() {
283 let mut rig = new_facial_rig();
284 let idx = add_bone(&mut rig, "jaw", [0.0, 0.0, 0.0], None);
285 assert_eq!(idx, 0);
286 assert_eq!(rig.bones.len(), 1);
287 assert_eq!(rig.bones[0].name, "jaw");
288 }
289
290 #[test]
291 fn test_add_bone_parent_child_link() {
292 let mut rig = new_facial_rig();
293 let p = add_bone(&mut rig, "root", [0.0, 0.0, 0.0], None);
294 let c = add_bone(&mut rig, "child", [1.0, 0.0, 0.0], Some(p));
295 assert!(rig.bones[p].children.contains(&c));
296 assert_eq!(rig.bones[c].parent, Some(p));
297 }
298
299 #[test]
300 fn test_add_corrective() {
301 let mut rig = new_facial_rig();
302 add_bone(&mut rig, "jaw", [0.0, 0.0, 0.0], None);
303 let cs = CorrectiveShape {
304 name: "jaw_open".to_string(),
305 trigger_bone: "jaw".to_string(),
306 trigger_angle: PI / 4.0,
307 deltas: vec![[0.0, -0.01, 0.0]; 3],
308 weight: 1.0,
309 };
310 add_corrective(&mut rig, cs);
311 assert_eq!(rig.correctives.len(), 1);
312 }
313
314 #[test]
315 fn test_default_facial_rig_non_empty() {
316 let rig = default_facial_rig();
317 assert!(rig.bones.len() >= 14);
318 }
319
320 #[test]
321 fn test_get_bone_found() {
322 let rig = default_facial_rig();
323 let bone = get_bone(&rig, "jaw");
324 assert!(bone.is_some());
325 assert_eq!(bone.expect("should succeed").name, "jaw");
326 }
327
328 #[test]
329 fn test_get_bone_not_found() {
330 let rig = default_facial_rig();
331 assert!(get_bone(&rig, "nonexistent").is_none());
332 }
333
334 #[test]
335 fn test_bone_count() {
336 let rig = default_facial_rig();
337 assert_eq!(bone_count(&rig), rig.bones.len());
338 }
339
340 #[test]
341 fn test_corrective_count_zero() {
342 let rig = default_facial_rig();
343 assert_eq!(corrective_count(&rig), 0);
344 }
345
346 #[test]
347 fn test_set_bone_rotation_success() {
348 let mut rig = default_facial_rig();
349 let rot = [0.0, 0.0, 0.707, 0.707];
350 let ok = set_bone_rotation(&mut rig, "jaw", rot);
351 assert!(ok);
352 let bone = get_bone(&rig, "jaw").expect("should succeed");
353 assert_eq!(bone.rotation, rot);
354 }
355
356 #[test]
357 fn test_set_bone_rotation_fail() {
358 let mut rig = default_facial_rig();
359 let ok = set_bone_rotation(&mut rig, "ghost_bone", [0.0, 0.0, 0.0, 1.0]);
360 assert!(!ok);
361 }
362
363 #[test]
364 fn test_identity_pose() {
365 let rig = default_facial_rig();
366 let pose = identity_pose(&rig);
367 assert_eq!(pose.bone_rotations.len(), rig.bones.len());
368 for rot in &pose.bone_rotations {
369 assert_eq!(*rot, [0.0, 0.0, 0.0, 1.0]);
370 }
371 for w in &pose.blend_weights {
372 assert_eq!(*w, 0.0);
373 }
374 }
375
376 #[test]
377 fn test_blend_facial_poses() {
378 let rig = default_facial_rig();
379 let a = identity_pose(&rig);
380 let mut b = identity_pose(&rig);
381 if !b.bone_rotations.is_empty() {
382 b.bone_rotations[0] = [0.0, 0.0, 0.707, 0.707];
383 }
384 let blended = blend_facial_poses(&a, &b, 0.5);
385 assert_eq!(blended.bone_rotations.len(), a.bone_rotations.len());
386 }
387
388 #[test]
389 fn test_evaluate_correctives_empty() {
390 let rig = default_facial_rig();
391 let weights = evaluate_correctives(&rig);
392 assert!(weights.is_empty());
393 }
394
395 #[test]
396 fn test_apply_facial_pose_no_correctives() {
397 let rig = default_facial_rig();
398 let pose = identity_pose(&rig);
399 let base = vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
400 let result = apply_facial_pose(&rig, &pose, &base);
401 assert_eq!(result.len(), base.len());
402 for (r, b) in result.iter().zip(base.iter()) {
403 assert!((r[0] - b[0]).abs() < 1e-6);
404 }
405 }
406
407 #[test]
408 fn test_quat_angle_between_identity() {
409 let q = [0.0, 0.0, 0.0, 1.0];
410 let angle = quat_angle_between(q, q);
411 assert!(angle < 1e-5);
412 }
413
414 #[test]
415 fn test_facial_rig_to_json_contains_version() {
416 let rig = default_facial_rig();
417 let json = facial_rig_to_json(&rig);
418 assert!(json.contains("version"));
419 assert!(json.contains("bone_count"));
420 }
421}