1#![allow(dead_code)]
4
5#[derive(Debug, Clone)]
9pub struct PsdExample {
10 pub pose: Vec<f32>,
11 pub deltas: Vec<[f32; 3]>,
12 pub weight: f32,
13}
14
15#[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
31pub fn new_psd(vertex_count: usize) -> PoseSpaceDeform {
33 PoseSpaceDeform::new(vertex_count)
34}
35
36pub 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
45pub fn psd_example_count(psd: &PoseSpaceDeform) -> usize {
47 psd.examples.len()
48}
49
50pub fn psd_evaluate<'a>(psd: &'a mut PoseSpaceDeform, current_pose: &[f32]) -> &'a [[f32; 3]] {
57 for ex in &mut psd.examples {
59 let n = ex.pose.len().min(current_pose.len());
60 let dist: f32 = (0..n)
61 .map(|i| (ex.pose[i] - current_pose[i]).powi(2))
62 .sum::<f32>()
63 .sqrt();
64 ex.weight = if dist < 1e-6 {
65 f32::INFINITY
66 } else {
67 1.0 / (1.0 + dist)
68 };
69 }
70
71 let exact_idx = psd.examples.iter().position(|ex| ex.weight.is_infinite());
73
74 if let Some(idx) = exact_idx {
75 let deltas = psd.examples[idx].deltas.clone();
76 let nv = psd.current_deltas.len().min(deltas.len());
77 for d in &mut psd.current_deltas {
78 *d = [0.0; 3];
79 }
80 psd.current_deltas[..nv].copy_from_slice(&deltas[..nv]);
81 return &psd.current_deltas;
82 }
83
84 let weight_sum: f32 = psd.examples.iter().map(|ex| ex.weight).sum();
86
87 for d in &mut psd.current_deltas {
89 *d = [0.0; 3];
90 }
91
92 if weight_sum < 1e-12 {
93 return &psd.current_deltas;
94 }
95
96 let nv = psd.current_deltas.len();
97 for ex in psd.examples.iter() {
98 let w = ex.weight / weight_sum;
99 let copy_len = nv.min(ex.deltas.len());
100 for v in 0..copy_len {
101 psd.current_deltas[v][0] += w * ex.deltas[v][0];
102 psd.current_deltas[v][1] += w * ex.deltas[v][1];
103 psd.current_deltas[v][2] += w * ex.deltas[v][2];
104 }
105 }
106
107 &psd.current_deltas
108}
109
110pub fn psd_reset(psd: &mut PoseSpaceDeform) {
112 for d in &mut psd.current_deltas {
113 *d = [0.0; 3];
114 }
115}
116
117pub fn psd_to_json(psd: &PoseSpaceDeform) -> String {
119 format!(
120 r#"{{"examples":{},"vertices":{}}}"#,
121 psd.examples.len(),
122 psd.current_deltas.len()
123 )
124}
125
126pub fn psd_vertex_count(psd: &PoseSpaceDeform) -> usize {
128 psd.current_deltas.len()
129}
130
131#[cfg(test)]
132mod tests {
133 use super::*;
134
135 #[test]
136 fn test_new_psd_vertex_count() {
137 let p = new_psd(12);
138 assert_eq!(psd_vertex_count(&p), 12 ,);
139 }
140
141 #[test]
142 fn test_initial_no_examples() {
143 let p = new_psd(5);
144 assert_eq!(
145 psd_example_count(&p),
146 0, );
148 }
149
150 #[test]
151 fn test_add_example_increases_count() {
152 let mut p = new_psd(5);
153 psd_add_example(&mut p, vec![0.0; 4], vec![[0.0; 3]; 5]);
154 assert_eq!(psd_example_count(&p), 1 ,);
155 }
156
157 #[test]
158 fn test_evaluate_exact_pose_sets_deltas() {
159 let mut p = new_psd(3);
160 psd_add_example(&mut p, vec![1.0, 0.0], vec![[0.5, 0.0, 0.0]; 3]);
161 psd_evaluate(&mut p, &[1.0, 0.0]);
162 assert!(p.current_deltas[0][0] > 0.0, );
163 }
164
165 #[test]
166 fn test_reset_zeroes_deltas() {
167 let mut p = new_psd(3);
168 psd_add_example(&mut p, vec![0.0; 2], vec![[1.0; 3]; 3]);
169 psd_evaluate(&mut p, &[0.0; 2]);
170 psd_reset(&mut p);
171 for d in &p.current_deltas {
172 assert!((d[0]).abs() < 1e-6 ,);
173 }
174 }
175
176 #[test]
177 fn test_to_json_contains_examples() {
178 let p = new_psd(4);
179 let j = psd_to_json(&p);
180 assert!(j.contains("examples") ,);
181 }
182
183 #[test]
184 fn test_to_json_contains_vertices() {
185 let p = new_psd(7);
186 let j = psd_to_json(&p);
187 assert!(j.contains("7") ,);
188 }
189
190 #[test]
191 fn test_initial_deltas_zero() {
192 let p = new_psd(6);
193 for d in &p.current_deltas {
194 assert!((d[0]).abs() < 1e-6 ,);
195 }
196 }
197
198 #[test]
199 fn test_multiple_examples() {
200 let mut p = new_psd(2);
201 psd_add_example(&mut p, vec![0.0], vec![[0.0; 3]; 2]);
202 psd_add_example(&mut p, vec![1.0], vec![[1.0; 3]; 2]);
203 assert_eq!(
204 psd_example_count(&p),
205 2, );
207 }
208
209 #[test]
210 fn test_example_weights_initially_zero() {
211 let mut p = new_psd(2);
212 psd_add_example(&mut p, vec![0.0], vec![[0.0; 3]; 2]);
213 assert!((p.examples[0].weight).abs() < 1e-6, );
214 }
215
216 #[test]
217 fn test_evaluate_no_examples_keeps_zero() {
218 let mut p = new_psd(3);
219 psd_evaluate(&mut p, &[0.5]);
220 for d in &p.current_deltas {
221 assert!((d[0]).abs() < 1e-6 ,);
222 }
223 }
224
225 #[test]
226 fn test_blend_two_equidistant_examples_is_average() {
227 let mut p = new_psd(2);
233 psd_add_example(&mut p, vec![0.0], vec![[2.0f32, 0.0, 0.0]; 2]);
234 psd_add_example(&mut p, vec![1.0], vec![[0.0f32, 0.0, 0.0]; 2]);
235 psd_evaluate(&mut p, &[0.5]);
236 let expected_x = 1.0f32;
238 for (i, d) in p.current_deltas.iter().enumerate() {
239 assert!(
240 (d[0] - expected_x).abs() < 1e-4,
241 "vertex[{i}][0]: expected {expected_x}, got {}",
242 d[0]
243 );
244 assert!(
245 (d[1]).abs() < 1e-6,
246 "vertex[{i}][1]: expected 0, got {}",
247 d[1]
248 );
249 }
250 }
251
252 #[test]
253 fn test_exact_pose_match_returns_that_example_directly() {
254 let mut p = new_psd(2);
256 psd_add_example(&mut p, vec![1.0, 0.0], vec![[5.0f32, 6.0, 7.0]; 2]);
257 psd_add_example(&mut p, vec![0.0, 1.0], vec![[0.0f32; 3]; 2]);
258 psd_evaluate(&mut p, &[1.0, 0.0]);
259 assert!(
260 (p.current_deltas[0][0] - 5.0).abs() < 1e-5,
261 "exact match must copy A deltas"
262 );
263 assert!(
264 (p.current_deltas[0][1] - 6.0).abs() < 1e-5,
265 "exact match must copy A deltas"
266 );
267 }
268
269 #[test]
270 fn test_weighted_blend_asymmetric() {
271 let mut p = new_psd(1);
277 psd_add_example(&mut p, vec![0.0f32], vec![[1.0f32, 0.0, 0.0]]);
278 psd_add_example(&mut p, vec![0.0f32], vec![[0.0f32, 0.0, 0.0]]);
279 let mut p2 = new_psd(1);
284 psd_add_example(&mut p2, vec![0.0f32], vec![[1.0f32, 0.0, 0.0]]);
285 psd_add_example(&mut p2, vec![5.0f32], vec![[0.0f32, 0.0, 0.0]]);
286 psd_evaluate(&mut p2, &[1.0]);
287 let expected = 0.5f32 / (0.5 + 0.2); assert!(
289 (p2.current_deltas[0][0] - expected).abs() < 1e-3,
290 "asymmetric blend: expected ≈{expected:.4}, got {}",
291 p2.current_deltas[0][0]
292 );
293 }
294}