1#![allow(dead_code)]
5
6use std::collections::HashMap;
7
8pub type MorphWeights = HashMap<String, f32>;
10
11pub struct RetargetMap {
19 forward: HashMap<String, String>,
21 inverse: HashMap<String, String>,
23 prefix_rules: Vec<(String, String)>,
25}
26
27impl RetargetMap {
28 pub fn new() -> Self {
30 Self {
31 forward: HashMap::new(),
32 inverse: HashMap::new(),
33 prefix_rules: Vec::new(),
34 }
35 }
36
37 fn new_with_prefix_rules(rules: &[(&str, &str)]) -> Self {
39 Self {
40 forward: HashMap::new(),
41 inverse: HashMap::new(),
42 prefix_rules: rules
43 .iter()
44 .map(|&(s, t)| (s.to_owned(), t.to_owned()))
45 .collect(),
46 }
47 }
48
49 pub fn add(&mut self, source: impl Into<String>, target: impl Into<String>) {
51 let s = source.into();
52 let t = target.into();
53 self.forward.insert(s.clone(), t.clone());
54 self.inverse.insert(t, s);
55 }
56
57 pub fn forward(&self, source: &str) -> Option<&str> {
61 if let Some(t) = self.forward.get(source) {
62 return Some(t.as_str());
63 }
64 None
71 }
72
73 pub fn forward_owned(&self, source: &str) -> Option<String> {
75 if let Some(t) = self.forward.get(source) {
76 return Some(t.clone());
77 }
78 for (src_pfx, tgt_pfx) in &self.prefix_rules {
79 if let Some(suffix) = source.strip_prefix(src_pfx.as_str()) {
80 return Some(format!("{}{}", tgt_pfx, suffix));
81 }
82 }
83 None
84 }
85
86 pub fn inverse(&self, target: &str) -> Option<&str> {
88 self.inverse.get(target).map(|s| s.as_str())
89 }
90
91 pub fn retarget_weights(&self, source_weights: &MorphWeights) -> MorphWeights {
93 let mut out = MorphWeights::new();
94 for (k, &v) in source_weights {
95 if let Some(mapped) = self.forward_owned(k) {
96 out.insert(mapped, v);
97 }
98 }
99 out
100 }
101
102 pub fn inverse_retarget_weights(&self, target_weights: &MorphWeights) -> MorphWeights {
104 let mut out = MorphWeights::new();
105 for (k, &v) in target_weights {
106 if let Some(mapped) = self.inverse(k) {
107 out.insert(mapped.to_owned(), v);
108 }
109 }
110 out
111 }
112
113 pub fn len(&self) -> usize {
115 self.forward.len()
116 }
117
118 pub fn is_empty(&self) -> bool {
120 self.forward.is_empty() && self.prefix_rules.is_empty()
121 }
122}
123
124impl Default for RetargetMap {
125 fn default() -> Self {
126 Self::new()
127 }
128}
129
130pub enum UnmappedPolicy {
136 Drop,
138 PassThrough,
140 MapToPrefix,
142}
143
144pub struct RetargetConfig {
146 pub weight_scale: f32,
148 pub unmapped_policy: UnmappedPolicy,
150 pub prefix: String,
152 pub clamp_output: bool,
154}
155
156impl Default for RetargetConfig {
157 fn default() -> Self {
158 Self {
159 weight_scale: 1.0,
160 unmapped_policy: UnmappedPolicy::Drop,
161 prefix: String::new(),
162 clamp_output: false,
163 }
164 }
165}
166
167pub struct RetargetStats {
173 pub source_count: usize,
174 pub mapped_count: usize,
175 pub unmapped_count: usize,
176 pub mapping_rate: f32,
177}
178
179pub fn retarget_weights(
185 weights: &MorphWeights,
186 map: &RetargetMap,
187 config: &RetargetConfig,
188) -> MorphWeights {
189 let mut out = MorphWeights::new();
190 for (k, &v) in weights {
191 let mut val = v * config.weight_scale;
192 if config.clamp_output {
193 val = val.clamp(0.0, 1.0);
194 }
195 match map.forward_owned(k) {
196 Some(mapped) => {
197 out.insert(mapped, val);
198 }
199 None => match config.unmapped_policy {
200 UnmappedPolicy::Drop => {}
201 UnmappedPolicy::PassThrough => {
202 out.insert(k.clone(), val);
203 }
204 UnmappedPolicy::MapToPrefix => {
205 out.insert(format!("{}{}", config.prefix, k), val);
206 }
207 },
208 }
209 }
210 out
211}
212
213pub fn blend_retargeted(source: &MorphWeights, target: &MorphWeights, t: f32) -> MorphWeights {
217 let mut all_keys: Vec<String> = source.keys().cloned().collect();
218 for k in target.keys() {
219 if !source.contains_key(k.as_str()) {
220 all_keys.push(k.clone());
221 }
222 }
223 let mut out = MorphWeights::new();
224 for k in all_keys {
225 let a = source.get(k.as_str()).copied().unwrap_or(0.0);
226 let b = target.get(k.as_str()).copied().unwrap_or(0.0);
227 out.insert(k, a + (b - a) * t);
228 }
229 out
230}
231
232pub fn scale_retarget_weights(weights: &MorphWeights, scale: f32) -> MorphWeights {
234 weights
235 .iter()
236 .map(|(k, &v)| (k.clone(), v * scale))
237 .collect()
238}
239
240pub fn build_prefix_map(prefixes: &[(&str, &str)]) -> RetargetMap {
254 RetargetMap::new_with_prefix_rules(prefixes)
255}
256
257pub fn retarget_stats(
259 source: &MorphWeights,
260 _retargeted: &MorphWeights,
261 map: &RetargetMap,
262) -> RetargetStats {
263 let source_count = source.len();
264 let mapped_count = source
265 .keys()
266 .filter(|k| map.forward_owned(k).is_some())
267 .count();
268 let unmapped_count = source_count - mapped_count;
269 let mapping_rate = if source_count == 0 {
270 0.0
271 } else {
272 mapped_count as f32 / source_count as f32
273 };
274 RetargetStats {
275 source_count,
276 mapped_count,
277 unmapped_count,
278 mapping_rate,
279 }
280}
281
282pub fn makehuman_to_daz_map() -> RetargetMap {
288 let mut m = RetargetMap::new();
289 m.add("jaw_open", "mouth_open");
290 m.add("brow_raise_l", "brows_up_l");
291 m.add("brow_raise_r", "brows_up_r");
292 m.add("brow_lower_l", "brows_down_l");
293 m.add("brow_lower_r", "brows_down_r");
294 m.add("smile_l", "mouth_smile_l");
295 m.add("smile_r", "mouth_smile_r");
296 m.add("eye_blink_l", "eyes_closed_l");
297 m.add("eye_blink_r", "eyes_closed_r");
298 m.add("cheek_puff", "cheeks_puff");
299 m
300}
301
302pub fn identity_map(keys: &[&str]) -> RetargetMap {
304 let mut m = RetargetMap::new();
305 for &k in keys {
306 m.add(k, k);
307 }
308 m
309}
310
311#[cfg(test)]
316mod tests {
317 use super::*;
318
319 fn w(pairs: &[(&str, f32)]) -> MorphWeights {
320 pairs.iter().map(|&(k, v)| (k.to_owned(), v)).collect()
321 }
322
323 #[test]
325 fn test_retarget_map_new_empty() {
326 let m = RetargetMap::new();
327 assert!(m.is_empty());
328 assert_eq!(m.len(), 0);
329 }
330
331 #[test]
333 fn test_add_forward_inverse() {
334 let mut m = RetargetMap::new();
335 m.add("jaw_open", "mouth_open");
336 assert_eq!(m.forward("jaw_open"), Some("mouth_open"));
337 assert_eq!(m.inverse("mouth_open"), Some("jaw_open"));
338 assert_eq!(m.len(), 1);
339 }
340
341 #[test]
343 fn test_forward_unknown() {
344 let m = RetargetMap::new();
345 assert_eq!(m.forward("nonexistent"), None);
346 }
347
348 #[test]
350 fn test_inverse_unknown() {
351 let m = RetargetMap::new();
352 assert_eq!(m.inverse("nonexistent"), None);
353 }
354
355 #[test]
357 fn test_retarget_map_retarget_weights() {
358 let mut m = RetargetMap::new();
359 m.add("jaw_open", "mouth_open");
360 m.add("smile_l", "mouth_smile_l");
361 let src = w(&[("jaw_open", 0.8), ("smile_l", 0.5), ("unknown_key", 1.0)]);
362 let out = m.retarget_weights(&src);
363 assert!((out["mouth_open"] - 0.8).abs() < 1e-6);
364 assert!((out["mouth_smile_l"] - 0.5).abs() < 1e-6);
365 assert!(!out.contains_key("unknown_key"));
366 assert_eq!(out.len(), 2);
367 }
368
369 #[test]
371 fn test_inverse_retarget_weights() {
372 let mut m = RetargetMap::new();
373 m.add("jaw_open", "mouth_open");
374 let tgt = w(&[("mouth_open", 0.7)]);
375 let out = m.inverse_retarget_weights(&tgt);
376 assert!((out["jaw_open"] - 0.7).abs() < 1e-6);
377 }
378
379 #[test]
381 fn test_retarget_weights_drop() {
382 let mut m = RetargetMap::new();
383 m.add("jaw_open", "mouth_open");
384 let src = w(&[("jaw_open", 0.6), ("extra", 0.3)]);
385 let cfg = RetargetConfig {
386 unmapped_policy: UnmappedPolicy::Drop,
387 ..Default::default()
388 };
389 let out = retarget_weights(&src, &m, &cfg);
390 assert!(out.contains_key("mouth_open"));
391 assert!(!out.contains_key("extra"));
392 }
393
394 #[test]
396 fn test_retarget_weights_passthrough() {
397 let mut m = RetargetMap::new();
398 m.add("jaw_open", "mouth_open");
399 let src = w(&[("jaw_open", 0.6), ("extra", 0.3)]);
400 let cfg = RetargetConfig {
401 unmapped_policy: UnmappedPolicy::PassThrough,
402 ..Default::default()
403 };
404 let out = retarget_weights(&src, &m, &cfg);
405 assert!(out.contains_key("mouth_open"));
406 assert!(out.contains_key("extra"));
407 assert!((out["extra"] - 0.3).abs() < 1e-6);
408 }
409
410 #[test]
412 fn test_retarget_weights_map_to_prefix() {
413 let m = RetargetMap::new();
414 let src = w(&[("smile", 0.4)]);
415 let cfg = RetargetConfig {
416 unmapped_policy: UnmappedPolicy::MapToPrefix,
417 prefix: "pfx_".to_owned(),
418 ..Default::default()
419 };
420 let out = retarget_weights(&src, &m, &cfg);
421 assert!(out.contains_key("pfx_smile"));
422 assert!((out["pfx_smile"] - 0.4).abs() < 1e-6);
423 }
424
425 #[test]
427 fn test_retarget_weights_scale() {
428 let mut m = RetargetMap::new();
429 m.add("a", "b");
430 let src = w(&[("a", 0.5)]);
431 let cfg = RetargetConfig {
432 weight_scale: 2.0,
433 unmapped_policy: UnmappedPolicy::Drop,
434 ..Default::default()
435 };
436 let out = retarget_weights(&src, &m, &cfg);
437 assert!((out["b"] - 1.0).abs() < 1e-6);
438 }
439
440 #[test]
442 fn test_retarget_weights_clamp() {
443 let mut m = RetargetMap::new();
444 m.add("a", "b");
445 let src = w(&[("a", 2.0)]);
446 let cfg = RetargetConfig {
447 weight_scale: 1.0,
448 clamp_output: true,
449 unmapped_policy: UnmappedPolicy::Drop,
450 ..Default::default()
451 };
452 let out = retarget_weights(&src, &m, &cfg);
453 assert!((out["b"] - 1.0).abs() < 1e-6);
454 }
455
456 #[test]
458 fn test_blend_retargeted_t0() {
459 let s = w(&[("a", 0.3), ("b", 0.7)]);
460 let t = w(&[("a", 1.0), ("b", 0.0)]);
461 let out = blend_retargeted(&s, &t, 0.0);
462 assert!((out["a"] - 0.3).abs() < 1e-6);
463 assert!((out["b"] - 0.7).abs() < 1e-6);
464 }
465
466 #[test]
468 fn test_blend_retargeted_t1() {
469 let s = w(&[("a", 0.3), ("b", 0.7)]);
470 let t = w(&[("a", 1.0), ("b", 0.0)]);
471 let out = blend_retargeted(&s, &t, 1.0);
472 assert!((out["a"] - 1.0).abs() < 1e-6);
473 assert!((out["b"] - 0.0).abs() < 1e-6);
474 }
475
476 #[test]
478 fn test_blend_retargeted_union_keys() {
479 let s = w(&[("only_src", 0.5)]);
480 let t = w(&[("only_tgt", 0.8)]);
481 let out = blend_retargeted(&s, &t, 0.5);
482 assert!((out["only_src"] - 0.25).abs() < 1e-6);
483 assert!((out["only_tgt"] - 0.4).abs() < 1e-6);
484 }
485
486 #[test]
488 fn test_scale_retarget_weights() {
489 let src = w(&[("a", 0.4), ("b", 0.8)]);
490 let out = scale_retarget_weights(&src, 0.5);
491 assert!((out["a"] - 0.2).abs() < 1e-6);
492 assert!((out["b"] - 0.4).abs() < 1e-6);
493 }
494
495 #[test]
497 fn test_build_prefix_map() {
498 let map = build_prefix_map(&[("jaw_", "mouth_"), ("brow_", "brows_")]);
499 assert_eq!(map.forward_owned("jaw_open"), Some("mouth_open".to_owned()));
500 assert_eq!(
501 map.forward_owned("brow_raise_l"),
502 Some("brows_raise_l".to_owned())
503 );
504 assert_eq!(map.forward_owned("unknown"), None);
505 }
506
507 #[test]
509 fn test_prefix_map_with_retarget_weights() {
510 let map = build_prefix_map(&[("mh_", "daz_")]);
511 let src = w(&[("mh_smile", 0.6), ("other", 0.2)]);
512 let cfg = RetargetConfig {
513 unmapped_policy: UnmappedPolicy::Drop,
514 ..Default::default()
515 };
516 let out = retarget_weights(&src, &map, &cfg);
517 assert!(out.contains_key("daz_smile"));
518 assert!(!out.contains_key("other"));
519 }
520
521 #[test]
523 fn test_retarget_stats() {
524 let mut m = RetargetMap::new();
525 m.add("jaw_open", "mouth_open");
526 let src = w(&[("jaw_open", 0.8), ("unmapped", 0.2)]);
527 let retargeted = m.retarget_weights(&src);
528 let stats = retarget_stats(&src, &retargeted, &m);
529 assert_eq!(stats.source_count, 2);
530 assert_eq!(stats.mapped_count, 1);
531 assert_eq!(stats.unmapped_count, 1);
532 assert!((stats.mapping_rate - 0.5).abs() < 1e-6);
533 }
534
535 #[test]
537 fn test_retarget_stats_empty() {
538 let m = RetargetMap::new();
539 let src = w(&[]);
540 let retargeted = w(&[]);
541 let stats = retarget_stats(&src, &retargeted, &m);
542 assert_eq!(stats.source_count, 0);
543 assert_eq!(stats.mapping_rate, 0.0);
544 }
545
546 #[test]
548 fn test_makehuman_to_daz_map_count() {
549 let m = makehuman_to_daz_map();
550 assert_eq!(m.len(), 10);
551 }
552
553 #[test]
555 fn test_makehuman_to_daz_map_entries() {
556 let m = makehuman_to_daz_map();
557 assert_eq!(m.forward("jaw_open"), Some("mouth_open"));
558 assert_eq!(m.forward("brow_raise_l"), Some("brows_up_l"));
559 assert_eq!(m.forward("eye_blink_l"), Some("eyes_closed_l"));
560 assert_eq!(m.forward("cheek_puff"), Some("cheeks_puff"));
561 }
562
563 #[test]
565 fn test_identity_map() {
566 let keys = ["smile", "blink", "jaw_open"];
567 let m = identity_map(&keys);
568 assert_eq!(m.len(), 3);
569 assert_eq!(m.forward("smile"), Some("smile"));
570 assert_eq!(m.inverse("blink"), Some("blink"));
571 }
572
573 #[test]
575 fn test_identity_map_retarget_weights() {
576 let keys = ["a", "b"];
577 let m = identity_map(&keys);
578 let src = w(&[("a", 0.3), ("b", 0.7)]);
579 let out = m.retarget_weights(&src);
580 assert!((out["a"] - 0.3).abs() < 1e-6);
581 assert!((out["b"] - 0.7).abs() < 1e-6);
582 }
583}