Skip to main content

oxihuman_morph/
pose_space_deform.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Pose space deformation (PSD) stub.
6
7/// A single PSD example pose.
8#[derive(Debug, Clone)]
9pub struct PsdExample {
10    pub pose: Vec<f32>,
11    pub deltas: Vec<[f32; 3]>,
12    pub weight: f32,
13}
14
15/// Pose space deformer.
16#[derive(Debug, Clone)]
17pub struct PoseSpaceDeform {
18    pub examples: Vec<PsdExample>,
19    pub current_deltas: Vec<[f32; 3]>,
20}
21
22impl PoseSpaceDeform {
23    pub fn new(vertex_count: usize) -> Self {
24        PoseSpaceDeform {
25            examples: Vec::new(),
26            current_deltas: vec![[0.0; 3]; vertex_count],
27        }
28    }
29}
30
31/// Create a new PSD deformer.
32pub fn new_psd(vertex_count: usize) -> PoseSpaceDeform {
33    PoseSpaceDeform::new(vertex_count)
34}
35
36/// Add a PSD example.
37pub fn psd_add_example(psd: &mut PoseSpaceDeform, pose: Vec<f32>, deltas: Vec<[f32; 3]>) {
38    psd.examples.push(PsdExample {
39        pose,
40        deltas,
41        weight: 0.0,
42    });
43}
44
45/// Return example count.
46pub fn psd_example_count(psd: &PoseSpaceDeform) -> usize {
47    psd.examples.len()
48}
49
50/// Evaluate PSD given current pose (stub: uses nearest pose by Euclidean distance).
51pub fn psd_evaluate<'a>(psd: &'a mut PoseSpaceDeform, current_pose: &[f32]) -> &'a [[f32; 3]] {
52    for ex in &mut psd.examples {
53        let n = ex.pose.len().min(current_pose.len());
54        let dist: f32 = (0..n)
55            .map(|i| (ex.pose[i] - current_pose[i]).powi(2))
56            .sum::<f32>()
57            .sqrt();
58        ex.weight = if dist < 1e-6 { 1.0 } else { 1.0 / (1.0 + dist) };
59    }
60    /* Blend all examples by weight (stub: use highest-weight example's deltas) */
61    if let Some(best) = psd.examples.iter().max_by(|a, b| {
62        a.weight
63            .partial_cmp(&b.weight)
64            .unwrap_or(std::cmp::Ordering::Equal)
65    }) {
66        let deltas = best.deltas.clone();
67        let n = psd.current_deltas.len().min(deltas.len());
68        psd.current_deltas[..n].copy_from_slice(&deltas[..n]);
69    }
70    &psd.current_deltas
71}
72
73/// Reset all current deltas to zero.
74pub fn psd_reset(psd: &mut PoseSpaceDeform) {
75    for d in &mut psd.current_deltas {
76        *d = [0.0; 3];
77    }
78}
79
80/// Return a JSON-like string.
81pub fn psd_to_json(psd: &PoseSpaceDeform) -> String {
82    format!(
83        r#"{{"examples":{},"vertices":{}}}"#,
84        psd.examples.len(),
85        psd.current_deltas.len()
86    )
87}
88
89/// Return vertex count.
90pub fn psd_vertex_count(psd: &PoseSpaceDeform) -> usize {
91    psd.current_deltas.len()
92}
93
94#[cfg(test)]
95mod tests {
96    use super::*;
97
98    #[test]
99    fn test_new_psd_vertex_count() {
100        let p = new_psd(12);
101        assert_eq!(psd_vertex_count(&p), 12 /* vertex count must match */,);
102    }
103
104    #[test]
105    fn test_initial_no_examples() {
106        let p = new_psd(5);
107        assert_eq!(
108            psd_example_count(&p),
109            0, /* should start with no examples */
110        );
111    }
112
113    #[test]
114    fn test_add_example_increases_count() {
115        let mut p = new_psd(5);
116        psd_add_example(&mut p, vec![0.0; 4], vec![[0.0; 3]; 5]);
117        assert_eq!(psd_example_count(&p), 1 /* count should increase */,);
118    }
119
120    #[test]
121    fn test_evaluate_exact_pose_sets_deltas() {
122        let mut p = new_psd(3);
123        psd_add_example(&mut p, vec![1.0, 0.0], vec![[0.5, 0.0, 0.0]; 3]);
124        psd_evaluate(&mut p, &[1.0, 0.0]);
125        assert!(p.current_deltas[0][0] > 0.0, /* deltas should be set for exact pose */);
126    }
127
128    #[test]
129    fn test_reset_zeroes_deltas() {
130        let mut p = new_psd(3);
131        psd_add_example(&mut p, vec![0.0; 2], vec![[1.0; 3]; 3]);
132        psd_evaluate(&mut p, &[0.0; 2]);
133        psd_reset(&mut p);
134        for d in &p.current_deltas {
135            assert!((d[0]).abs() < 1e-6 /* reset should zero deltas */,);
136        }
137    }
138
139    #[test]
140    fn test_to_json_contains_examples() {
141        let p = new_psd(4);
142        let j = psd_to_json(&p);
143        assert!(j.contains("examples") /* JSON must contain examples */,);
144    }
145
146    #[test]
147    fn test_to_json_contains_vertices() {
148        let p = new_psd(7);
149        let j = psd_to_json(&p);
150        assert!(j.contains("7") /* JSON should contain vertex count */,);
151    }
152
153    #[test]
154    fn test_initial_deltas_zero() {
155        let p = new_psd(6);
156        for d in &p.current_deltas {
157            assert!((d[0]).abs() < 1e-6 /* initial deltas should be 0 */,);
158        }
159    }
160
161    #[test]
162    fn test_multiple_examples() {
163        let mut p = new_psd(2);
164        psd_add_example(&mut p, vec![0.0], vec![[0.0; 3]; 2]);
165        psd_add_example(&mut p, vec![1.0], vec![[1.0; 3]; 2]);
166        assert_eq!(
167            psd_example_count(&p),
168            2, /* two examples should be stored */
169        );
170    }
171
172    #[test]
173    fn test_example_weights_initially_zero() {
174        let mut p = new_psd(2);
175        psd_add_example(&mut p, vec![0.0], vec![[0.0; 3]; 2]);
176        assert!((p.examples[0].weight).abs() < 1e-6, /* initial example weight is 0 */);
177    }
178
179    #[test]
180    fn test_evaluate_no_examples_keeps_zero() {
181        let mut p = new_psd(3);
182        psd_evaluate(&mut p, &[0.5]);
183        for d in &p.current_deltas {
184            assert!((d[0]).abs() < 1e-6 /* no examples means zero deltas */,);
185        }
186    }
187}