#![allow(dead_code)]
#[derive(Debug, Clone)]
pub struct PsdExample {
pub pose: Vec<f32>,
pub deltas: Vec<[f32; 3]>,
pub weight: f32,
}
#[derive(Debug, Clone)]
pub struct PoseSpaceDeform {
pub examples: Vec<PsdExample>,
pub current_deltas: Vec<[f32; 3]>,
}
impl PoseSpaceDeform {
pub fn new(vertex_count: usize) -> Self {
PoseSpaceDeform {
examples: Vec::new(),
current_deltas: vec![[0.0; 3]; vertex_count],
}
}
}
pub fn new_psd(vertex_count: usize) -> PoseSpaceDeform {
PoseSpaceDeform::new(vertex_count)
}
pub fn psd_add_example(psd: &mut PoseSpaceDeform, pose: Vec<f32>, deltas: Vec<[f32; 3]>) {
psd.examples.push(PsdExample {
pose,
deltas,
weight: 0.0,
});
}
pub fn psd_example_count(psd: &PoseSpaceDeform) -> usize {
psd.examples.len()
}
pub fn psd_evaluate<'a>(psd: &'a mut PoseSpaceDeform, current_pose: &[f32]) -> &'a [[f32; 3]] {
for ex in &mut psd.examples {
let n = ex.pose.len().min(current_pose.len());
let dist: f32 = (0..n)
.map(|i| (ex.pose[i] - current_pose[i]).powi(2))
.sum::<f32>()
.sqrt();
ex.weight = if dist < 1e-6 {
f32::INFINITY
} else {
1.0 / (1.0 + dist)
};
}
let exact_idx = psd.examples.iter().position(|ex| ex.weight.is_infinite());
if let Some(idx) = exact_idx {
let deltas = psd.examples[idx].deltas.clone();
let nv = psd.current_deltas.len().min(deltas.len());
for d in &mut psd.current_deltas {
*d = [0.0; 3];
}
psd.current_deltas[..nv].copy_from_slice(&deltas[..nv]);
return &psd.current_deltas;
}
let weight_sum: f32 = psd.examples.iter().map(|ex| ex.weight).sum();
for d in &mut psd.current_deltas {
*d = [0.0; 3];
}
if weight_sum < 1e-12 {
return &psd.current_deltas;
}
let nv = psd.current_deltas.len();
for ex in psd.examples.iter() {
let w = ex.weight / weight_sum;
let copy_len = nv.min(ex.deltas.len());
for v in 0..copy_len {
psd.current_deltas[v][0] += w * ex.deltas[v][0];
psd.current_deltas[v][1] += w * ex.deltas[v][1];
psd.current_deltas[v][2] += w * ex.deltas[v][2];
}
}
&psd.current_deltas
}
pub fn psd_reset(psd: &mut PoseSpaceDeform) {
for d in &mut psd.current_deltas {
*d = [0.0; 3];
}
}
pub fn psd_to_json(psd: &PoseSpaceDeform) -> String {
format!(
r#"{{"examples":{},"vertices":{}}}"#,
psd.examples.len(),
psd.current_deltas.len()
)
}
pub fn psd_vertex_count(psd: &PoseSpaceDeform) -> usize {
psd.current_deltas.len()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_psd_vertex_count() {
let p = new_psd(12);
assert_eq!(psd_vertex_count(&p), 12 ,);
}
#[test]
fn test_initial_no_examples() {
let p = new_psd(5);
assert_eq!(
psd_example_count(&p),
0,
);
}
#[test]
fn test_add_example_increases_count() {
let mut p = new_psd(5);
psd_add_example(&mut p, vec![0.0; 4], vec![[0.0; 3]; 5]);
assert_eq!(psd_example_count(&p), 1 ,);
}
#[test]
fn test_evaluate_exact_pose_sets_deltas() {
let mut p = new_psd(3);
psd_add_example(&mut p, vec![1.0, 0.0], vec![[0.5, 0.0, 0.0]; 3]);
psd_evaluate(&mut p, &[1.0, 0.0]);
assert!(p.current_deltas[0][0] > 0.0, );
}
#[test]
fn test_reset_zeroes_deltas() {
let mut p = new_psd(3);
psd_add_example(&mut p, vec![0.0; 2], vec![[1.0; 3]; 3]);
psd_evaluate(&mut p, &[0.0; 2]);
psd_reset(&mut p);
for d in &p.current_deltas {
assert!((d[0]).abs() < 1e-6 ,);
}
}
#[test]
fn test_to_json_contains_examples() {
let p = new_psd(4);
let j = psd_to_json(&p);
assert!(j.contains("examples") ,);
}
#[test]
fn test_to_json_contains_vertices() {
let p = new_psd(7);
let j = psd_to_json(&p);
assert!(j.contains("7") ,);
}
#[test]
fn test_initial_deltas_zero() {
let p = new_psd(6);
for d in &p.current_deltas {
assert!((d[0]).abs() < 1e-6 ,);
}
}
#[test]
fn test_multiple_examples() {
let mut p = new_psd(2);
psd_add_example(&mut p, vec![0.0], vec![[0.0; 3]; 2]);
psd_add_example(&mut p, vec![1.0], vec![[1.0; 3]; 2]);
assert_eq!(
psd_example_count(&p),
2,
);
}
#[test]
fn test_example_weights_initially_zero() {
let mut p = new_psd(2);
psd_add_example(&mut p, vec![0.0], vec![[0.0; 3]; 2]);
assert!((p.examples[0].weight).abs() < 1e-6, );
}
#[test]
fn test_evaluate_no_examples_keeps_zero() {
let mut p = new_psd(3);
psd_evaluate(&mut p, &[0.5]);
for d in &p.current_deltas {
assert!((d[0]).abs() < 1e-6 ,);
}
}
#[test]
fn test_blend_two_equidistant_examples_is_average() {
let mut p = new_psd(2);
psd_add_example(&mut p, vec![0.0], vec![[2.0f32, 0.0, 0.0]; 2]);
psd_add_example(&mut p, vec![1.0], vec![[0.0f32, 0.0, 0.0]; 2]);
psd_evaluate(&mut p, &[0.5]);
let expected_x = 1.0f32;
for (i, d) in p.current_deltas.iter().enumerate() {
assert!(
(d[0] - expected_x).abs() < 1e-4,
"vertex[{i}][0]: expected {expected_x}, got {}",
d[0]
);
assert!(
(d[1]).abs() < 1e-6,
"vertex[{i}][1]: expected 0, got {}",
d[1]
);
}
}
#[test]
fn test_exact_pose_match_returns_that_example_directly() {
let mut p = new_psd(2);
psd_add_example(&mut p, vec![1.0, 0.0], vec![[5.0f32, 6.0, 7.0]; 2]);
psd_add_example(&mut p, vec![0.0, 1.0], vec![[0.0f32; 3]; 2]);
psd_evaluate(&mut p, &[1.0, 0.0]);
assert!(
(p.current_deltas[0][0] - 5.0).abs() < 1e-5,
"exact match must copy A deltas"
);
assert!(
(p.current_deltas[0][1] - 6.0).abs() < 1e-5,
"exact match must copy A deltas"
);
}
#[test]
fn test_weighted_blend_asymmetric() {
let mut p = new_psd(1);
psd_add_example(&mut p, vec![0.0f32], vec![[1.0f32, 0.0, 0.0]]);
psd_add_example(&mut p, vec![0.0f32], vec![[0.0f32, 0.0, 0.0]]);
let mut p2 = new_psd(1);
psd_add_example(&mut p2, vec![0.0f32], vec![[1.0f32, 0.0, 0.0]]);
psd_add_example(&mut p2, vec![5.0f32], vec![[0.0f32, 0.0, 0.0]]);
psd_evaluate(&mut p2, &[1.0]);
let expected = 0.5f32 / (0.5 + 0.2); assert!(
(p2.current_deltas[0][0] - expected).abs() < 1e-3,
"asymmetric blend: expected ≈{expected:.4}, got {}",
p2.current_deltas[0][0]
);
}
}