1use std::collections::HashMap;
7
8#[allow(dead_code)]
10#[derive(Clone, Debug)]
11pub struct FaceParam {
12 pub name: String,
13 pub value: f32,
15 pub symmetric: bool,
17}
18
19#[allow(dead_code)]
21#[derive(Clone, Debug, Default)]
22pub struct FaceModel {
23 pub params: HashMap<String, FaceParam>,
24}
25
26#[allow(dead_code)]
28#[derive(Clone, Debug)]
29pub struct FaceActionUnit {
30 pub au_id: u8,
32 pub name: String,
33 pub description: String,
34 pub morph_weights: HashMap<String, f32>,
36 pub bilateral: bool,
37}
38
39#[allow(dead_code)]
43pub fn standard_face_params() -> Vec<FaceParam> {
44 fn p(name: &str, sym: bool) -> FaceParam {
45 FaceParam {
46 name: name.to_owned(),
47 value: 0.0,
48 symmetric: sym,
49 }
50 }
51 vec![
52 p("brow_raise_l", false),
53 p("brow_raise_r", false),
54 p("brow_furrow", true),
55 p("eye_open_l", false),
56 p("eye_open_r", false),
57 p("eye_squint", true),
58 p("cheek_puff", true),
59 p("lip_corner_pull", true),
60 p("lip_pucker", true),
61 p("jaw_open", true),
62 p("chin_raise", true),
63 p("nose_wrinkle", true),
64 p("upper_lid_l", false),
65 p("upper_lid_r", false),
66 p("smile_l", false),
67 p("smile_r", false),
68 ]
69}
70
71#[allow(dead_code)]
73pub fn standard_face_action_units() -> Vec<FaceActionUnit> {
74 fn au(
75 id: u8,
76 name: &str,
77 desc: &str,
78 weights: &[(&str, f32)],
79 bilateral: bool,
80 ) -> FaceActionUnit {
81 FaceActionUnit {
82 au_id: id,
83 name: name.to_owned(),
84 description: desc.to_owned(),
85 morph_weights: weights.iter().map(|(k, v)| (k.to_string(), *v)).collect(),
86 bilateral,
87 }
88 }
89 vec![
90 au(
91 1,
92 "AU1",
93 "Inner brow raise",
94 &[("brow_raise_l", 0.5), ("brow_raise_r", 0.5)],
95 true,
96 ),
97 au(
98 2,
99 "AU2",
100 "Outer brow raise",
101 &[("brow_raise_l", 1.0), ("brow_raise_r", 1.0)],
102 true,
103 ),
104 au(4, "AU4", "Brow lowerer", &[("brow_furrow", 1.0)], true),
105 au(
106 5,
107 "AU5",
108 "Upper lid raiser",
109 &[("upper_lid_l", 1.0), ("upper_lid_r", 1.0)],
110 true,
111 ),
112 au(6, "AU6", "Cheek raiser", &[("cheek_puff", 0.8)], true),
113 au(7, "AU7", "Lid tightener", &[("eye_squint", 1.0)], true),
114 au(
115 10,
116 "AU10",
117 "Upper lip raiser",
118 &[("lip_corner_pull", 0.5)],
119 true,
120 ),
121 au(
122 12,
123 "AU12",
124 "Lip corner puller",
125 &[("smile_l", 1.0), ("smile_r", 1.0)],
126 true,
127 ),
128 au(17, "AU17", "Chin raiser", &[("chin_raise", 1.0)], true),
129 au(
130 25,
131 "AU25",
132 "Lips part",
133 &[("jaw_open", 0.4), ("lip_pucker", -0.2)],
134 true,
135 ),
136 ]
137}
138
139impl FaceModel {
142 #[allow(dead_code)]
144 pub fn new() -> Self {
145 Self {
146 params: HashMap::new(),
147 }
148 }
149
150 #[allow(dead_code)]
152 pub fn set_param(&mut self, name: &str, value: f32) {
153 self.params
154 .entry(name.to_owned())
155 .and_modify(|p| p.value = value)
156 .or_insert(FaceParam {
157 name: name.to_owned(),
158 value,
159 symmetric: false,
160 });
161 }
162
163 #[allow(dead_code)]
165 pub fn get_param(&self, name: &str) -> f32 {
166 self.params.get(name).map(|p| p.value).unwrap_or(0.0)
167 }
168
169 #[allow(dead_code)]
171 pub fn apply_action_unit(&mut self, au: &FaceActionUnit, intensity: f32) {
172 for (param, &weight) in &au.morph_weights {
173 let v = weight * intensity;
174 self.set_param(param, v);
175 }
176 }
177
178 #[allow(dead_code)]
180 pub fn compose_expression(&self) -> HashMap<String, f32> {
181 self.params
182 .iter()
183 .map(|(k, p)| (k.clone(), p.value))
184 .collect()
185 }
186
187 #[allow(dead_code)]
189 pub fn reset(&mut self) {
190 for p in self.params.values_mut() {
191 p.value = 0.0;
192 }
193 }
194}
195
196#[allow(dead_code)]
200pub fn blend_face_params(a: &FaceModel, b: &FaceModel, t: f32) -> FaceModel {
201 let t = t.clamp(0.0, 1.0);
202 let mut result = FaceModel::new();
203 let names: std::collections::HashSet<&String> =
205 a.params.keys().chain(b.params.keys()).collect();
206 for name in names {
207 let va = a.get_param(name);
208 let vb = b.get_param(name);
209 let sym = a
210 .params
211 .get(name)
212 .or_else(|| b.params.get(name))
213 .map(|p| p.symmetric)
214 .unwrap_or(false);
215 result.params.insert(
216 name.clone(),
217 FaceParam {
218 name: name.clone(),
219 value: va + (vb - va) * t,
220 symmetric: sym,
221 },
222 );
223 }
224 result
225}
226
227#[allow(dead_code)]
229pub fn expression_presets() -> HashMap<String, HashMap<String, f32>> {
230 let mut map: HashMap<String, HashMap<String, f32>> = HashMap::new();
231
232 map.insert("neutral".to_owned(), HashMap::new());
234
235 map.insert(
237 "happy".to_owned(),
238 [
239 ("smile_l", 0.9),
240 ("smile_r", 0.9),
241 ("cheek_puff", 0.3),
242 ("eye_squint", 0.2),
243 ("lip_corner_pull", 0.8),
244 ]
245 .iter()
246 .map(|(k, v)| (k.to_string(), *v))
247 .collect(),
248 );
249
250 map.insert(
252 "sad".to_owned(),
253 [
254 ("brow_furrow", 0.6),
255 ("brow_raise_l", -0.3),
256 ("brow_raise_r", -0.3),
257 ("lip_corner_pull", -0.5),
258 ("jaw_open", 0.1),
259 ]
260 .iter()
261 .map(|(k, v)| (k.to_string(), *v))
262 .collect(),
263 );
264
265 map.insert(
267 "surprised".to_owned(),
268 [
269 ("brow_raise_l", 1.0),
270 ("brow_raise_r", 1.0),
271 ("eye_open_l", 1.0),
272 ("eye_open_r", 1.0),
273 ("jaw_open", 0.6),
274 ]
275 .iter()
276 .map(|(k, v)| (k.to_string(), *v))
277 .collect(),
278 );
279
280 map.insert(
282 "angry".to_owned(),
283 [
284 ("brow_furrow", 1.0),
285 ("eye_squint", 0.5),
286 ("nose_wrinkle", 0.6),
287 ("lip_corner_pull", -0.4),
288 ]
289 .iter()
290 .map(|(k, v)| (k.to_string(), *v))
291 .collect(),
292 );
293
294 map.insert(
296 "disgusted".to_owned(),
297 [
298 ("nose_wrinkle", 1.0),
299 ("lip_pucker", 0.4),
300 ("lip_corner_pull", -0.5),
301 ("chin_raise", 0.3),
302 ]
303 .iter()
304 .map(|(k, v)| (k.to_string(), *v))
305 .collect(),
306 );
307
308 map.insert(
310 "fearful".to_owned(),
311 [
312 ("brow_raise_l", 0.7),
313 ("brow_raise_r", 0.7),
314 ("eye_open_l", 0.8),
315 ("eye_open_r", 0.8),
316 ("jaw_open", 0.3),
317 ("lip_corner_pull", -0.2),
318 ]
319 .iter()
320 .map(|(k, v)| (k.to_string(), *v))
321 .collect(),
322 );
323
324 map
325}
326
327#[allow(dead_code)]
329pub fn apply_expression_preset(model: &mut FaceModel, preset: &HashMap<String, f32>) {
330 for (name, &value) in preset {
331 model.set_param(name, value);
332 }
333}
334
335#[cfg(test)]
338mod tests {
339 use super::*;
340
341 #[test]
343 fn test_standard_face_params_count() {
344 assert!(standard_face_params().len() >= 15);
345 }
346
347 #[test]
349 fn test_standard_face_action_units_count() {
350 assert!(standard_face_action_units().len() >= 10);
351 }
352
353 #[test]
355 fn test_face_model_new_empty() {
356 let m = FaceModel::new();
357 assert!(m.params.is_empty());
358 }
359
360 #[test]
362 fn test_set_get_param() {
363 let mut m = FaceModel::new();
364 m.set_param("jaw_open", 0.7);
365 assert!((m.get_param("jaw_open") - 0.7).abs() < 1e-6);
366 }
367
368 #[test]
370 fn test_get_param_absent() {
371 let m = FaceModel::new();
372 assert!((m.get_param("nonexistent") - 0.0).abs() < 1e-6);
373 }
374
375 #[test]
377 fn test_apply_action_unit_scales() {
378 let aus = standard_face_action_units();
379 let au12 = aus
380 .iter()
381 .find(|au| au.au_id == 12)
382 .expect("should succeed");
383 let mut model = FaceModel::new();
384 model.apply_action_unit(au12, 0.5);
385 assert!((model.get_param("smile_l") - 0.5).abs() < 1e-5);
387 }
388
389 #[test]
391 fn test_compose_expression_contains_params() {
392 let mut m = FaceModel::new();
393 m.set_param("brow_furrow", 0.3);
394 m.set_param("jaw_open", 0.6);
395 let expr = m.compose_expression();
396 assert!(expr.contains_key("brow_furrow"));
397 assert!(expr.contains_key("jaw_open"));
398 }
399
400 #[test]
402 fn test_reset_zeros_all() {
403 let mut m = FaceModel::new();
404 m.set_param("smile_l", 0.8);
405 m.set_param("jaw_open", 0.4);
406 m.reset();
407 for p in m.params.values() {
408 assert!((p.value).abs() < 1e-6, "param {} should be 0", p.name);
409 }
410 }
411
412 #[test]
414 fn test_blend_t0_is_a() {
415 let mut a = FaceModel::new();
416 a.set_param("x", 1.0);
417 let mut b = FaceModel::new();
418 b.set_param("x", 0.0);
419 let result = blend_face_params(&a, &b, 0.0);
420 assert!((result.get_param("x") - 1.0).abs() < 1e-5);
421 }
422
423 #[test]
425 fn test_blend_t1_is_b() {
426 let mut a = FaceModel::new();
427 a.set_param("x", 0.0);
428 let mut b = FaceModel::new();
429 b.set_param("x", 1.0);
430 let result = blend_face_params(&a, &b, 1.0);
431 assert!((result.get_param("x") - 1.0).abs() < 1e-5);
432 }
433
434 #[test]
436 fn test_expression_presets_count() {
437 let presets = expression_presets();
438 assert!(
439 presets.len() >= 6,
440 "expected >= 6 presets, got {}",
441 presets.len()
442 );
443 }
444
445 #[test]
447 fn test_apply_expression_preset_applies() {
448 let presets = expression_presets();
449 let happy = &presets["happy"];
450 let mut model = FaceModel::new();
451 apply_expression_preset(&mut model, happy);
452 assert!(
454 model.get_param("smile_l") > 0.0,
455 "happy preset should set smile_l"
456 );
457 }
458
459 #[test]
461 fn test_neutral_preset_all_zero() {
462 let presets = expression_presets();
463 let neutral = &presets["neutral"];
464 for &v in neutral.values() {
465 assert!((v).abs() < 1e-6, "neutral preset should be all-zero");
466 }
467 }
468
469 #[test]
471 fn test_bilateral_param_symmetric_flag() {
472 let params = standard_face_params();
473 let jaw = params
474 .iter()
475 .find(|p| p.name == "jaw_open")
476 .expect("should succeed");
477 assert!(jaw.symmetric, "jaw_open should be symmetric");
478 let brow_l = params
479 .iter()
480 .find(|p| p.name == "brow_raise_l")
481 .expect("should succeed");
482 assert!(!brow_l.symmetric, "brow_raise_l should not be symmetric");
483 }
484
485 #[test]
487 fn test_action_unit_bilateral_flag() {
488 let aus = standard_face_action_units();
489 let au12 = aus
490 .iter()
491 .find(|au| au.au_id == 12)
492 .expect("should succeed");
493 assert!(au12.bilateral);
494 }
495
496 #[test]
498 fn test_blend_midpoint() {
499 let mut a = FaceModel::new();
500 a.set_param("x", 0.0);
501 let mut b = FaceModel::new();
502 b.set_param("x", 1.0);
503 let result = blend_face_params(&a, &b, 0.5);
504 assert!((result.get_param("x") - 0.5).abs() < 1e-5);
505 }
506}