#[allow(dead_code)]
pub struct RetargetMap {
pub pairs: Vec<(String, String)>,
}
#[allow(dead_code)]
impl RetargetMap {
pub fn new() -> Self {
Self { pairs: Vec::new() }
}
pub fn add(&mut self, src: &str, tgt: &str) {
self.pairs.push((src.to_string(), tgt.to_string()));
}
pub fn identity(joint_names: &[&str]) -> Self {
let pairs = joint_names
.iter()
.map(|name| (name.to_string(), name.to_string()))
.collect();
Self { pairs }
}
pub fn resolve(&self, src_name: &str, target_names: &[String]) -> Option<usize> {
let tgt_name = self
.pairs
.iter()
.find(|(src, _)| src == src_name)
.map(|(_, tgt)| tgt.as_str())?;
target_names.iter().position(|n| n == tgt_name)
}
}
impl Default for RetargetMap {
fn default() -> Self {
Self::new()
}
}
#[allow(dead_code)]
pub fn retarget_pose(
src_quats: &[[f32; 4]],
src_names: &[String],
tgt_names: &[String],
map: &RetargetMap,
) -> Vec<[f32; 4]> {
const IDENTITY: [f32; 4] = [0.0, 0.0, 0.0, 1.0];
let mut result = vec![IDENTITY; tgt_names.len()];
for (src_idx, src_name) in src_names.iter().enumerate() {
if let Some(tgt_idx) = map.resolve(src_name, tgt_names) {
if let Some(quat) = src_quats.get(src_idx) {
result[tgt_idx] = *quat;
}
}
}
result
}
#[allow(dead_code)]
pub fn retarget_sequence(
src_sequence: &[Vec<[f32; 4]>],
src_names: &[String],
tgt_names: &[String],
map: &RetargetMap,
) -> Vec<Vec<[f32; 4]>> {
src_sequence
.iter()
.map(|pose| retarget_pose(pose, src_names, tgt_names, map))
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
fn names(ns: &[&str]) -> Vec<String> {
ns.iter().map(|s| s.to_string()).collect()
}
#[test]
fn retarget_map_new_is_empty() {
let map = RetargetMap::new();
assert!(map.pairs.is_empty());
}
#[test]
fn retarget_map_add_and_resolve() {
let mut map = RetargetMap::new();
map.add("hip", "pelvis");
let tgt = names(&["pelvis", "spine"]);
assert_eq!(map.resolve("hip", &tgt), Some(0));
}
#[test]
fn retarget_map_identity_resolves_same_name() {
let map = RetargetMap::identity(&["hip", "spine", "head"]);
let tgt = names(&["hip", "spine", "head"]);
assert_eq!(map.resolve("spine", &tgt), Some(1));
assert_eq!(map.resolve("head", &tgt), Some(2));
}
#[test]
fn retarget_map_resolve_missing_returns_none() {
let map = RetargetMap::identity(&["hip", "spine"]);
let tgt = names(&["hip", "spine"]);
assert_eq!(map.resolve("nonexistent", &tgt), None);
}
#[test]
fn retarget_pose_identity_map_preserves_quats() {
let map = RetargetMap::identity(&["hip", "spine"]);
let src_names = names(&["hip", "spine"]);
let tgt_names = names(&["hip", "spine"]);
let src_quats = vec![[0.1, 0.2, 0.3, 0.9], [0.0, 0.5, 0.5, 0.7]];
let result = retarget_pose(&src_quats, &src_names, &tgt_names, &map);
assert_eq!(result.len(), 2);
for i in 0..2 {
for j in 0..4 {
assert!((result[i][j] - src_quats[i][j]).abs() < 1e-6);
}
}
}
#[test]
fn retarget_pose_mapped_joint_transfers_correctly() {
let mut map = RetargetMap::new();
map.add("src_hip", "tgt_pelvis");
let src_names = names(&["src_hip", "src_spine"]);
let tgt_names = names(&["tgt_pelvis", "tgt_chest"]);
let src_quats = vec![[0.1, 0.2, 0.3, 0.9], [0.0, 0.0, 0.0, 1.0]];
let result = retarget_pose(&src_quats, &src_names, &tgt_names, &map);
assert!((result[0][0] - 0.1).abs() < 1e-6);
assert!((result[0][1] - 0.2).abs() < 1e-6);
assert!((result[0][2] - 0.3).abs() < 1e-6);
assert!((result[0][3] - 0.9).abs() < 1e-6);
}
#[test]
fn retarget_pose_unmapped_joint_is_identity_quat() {
let map = RetargetMap::new(); let src_names = names(&["hip"]);
let tgt_names = names(&["pelvis", "spine"]);
let src_quats = vec![[0.1, 0.2, 0.3, 0.9]];
let result = retarget_pose(&src_quats, &src_names, &tgt_names, &map);
assert_eq!(result[0], [0.0, 0.0, 0.0, 1.0]);
assert_eq!(result[1], [0.0, 0.0, 0.0, 1.0]);
}
#[test]
fn retarget_pose_length_equals_target_count() {
let map = RetargetMap::identity(&["a", "b", "c"]);
let src_names = names(&["a", "b", "c"]);
let tgt_names = names(&["a", "b", "c", "d", "e"]);
let src_quats = vec![[0.0, 0.0, 0.0, 1.0]; 3];
let result = retarget_pose(&src_quats, &src_names, &tgt_names, &map);
assert_eq!(result.len(), tgt_names.len());
}
#[test]
fn retarget_sequence_length_matches_input() {
let map = RetargetMap::identity(&["hip"]);
let src_names = names(&["hip"]);
let tgt_names = names(&["hip"]);
let src_sequence: Vec<Vec<[f32; 4]>> = vec![
vec![[0.0, 0.0, 0.0, 1.0]],
vec![[0.1, 0.0, 0.0, 1.0]],
vec![[0.2, 0.0, 0.0, 1.0]],
];
let result = retarget_sequence(&src_sequence, &src_names, &tgt_names, &map);
assert_eq!(result.len(), 3);
}
#[test]
fn retarget_sequence_each_pose_correct_length() {
let map = RetargetMap::identity(&["hip", "spine"]);
let src_names = names(&["hip", "spine"]);
let tgt_names = names(&["hip", "spine", "extra"]);
let src_sequence: Vec<Vec<[f32; 4]>> = vec![
vec![[0.0, 0.0, 0.0, 1.0], [0.0, 0.0, 0.0, 1.0]],
vec![[0.1, 0.0, 0.0, 1.0], [0.0, 0.1, 0.0, 1.0]],
];
let result = retarget_sequence(&src_sequence, &src_names, &tgt_names, &map);
for pose in &result {
assert_eq!(pose.len(), tgt_names.len());
}
}
}