1use oxihuman_core::parser::target::Delta;
7
8#[allow(dead_code)]
10pub struct AuthoringConfig {
11 pub threshold: f32,
13 pub smooth_iterations: u32,
15 pub normalize: bool,
17}
18
19impl Default for AuthoringConfig {
20 fn default() -> Self {
21 Self {
22 threshold: 1e-5,
23 smooth_iterations: 0,
24 normalize: false,
25 }
26 }
27}
28
29#[allow(dead_code)]
31pub struct AuthoredTarget {
32 pub name: String,
33 pub deltas: Vec<Delta>,
34 pub nonzero_count: usize,
35 pub max_magnitude: f32,
36 pub bounds: [[f32; 3]; 2],
38}
39
40#[inline]
43fn magnitude(v: [f32; 3]) -> f32 {
44 (v[0] * v[0] + v[1] * v[1] + v[2] * v[2]).sqrt()
45}
46
47fn build_authored(name: &str, raw: Vec<[f32; 3]>, cfg: &AuthoringConfig) -> AuthoredTarget {
48 let mut field = raw;
50 if cfg.smooth_iterations > 0 {
51 for _ in 0..cfg.smooth_iterations {
55 let len = field.len();
56 if len < 2 {
57 break;
58 }
59 let mut smoothed = field.clone();
60 for i in 0..len {
61 let prev = if i == 0 { len - 1 } else { i - 1 };
62 let next = (i + 1) % len;
63 smoothed[i] = [
64 (field[prev][0] + field[i][0] + field[next][0]) / 3.0,
65 (field[prev][1] + field[i][1] + field[next][1]) / 3.0,
66 (field[prev][2] + field[i][2] + field[next][2]) / 3.0,
67 ];
68 }
69 field = smoothed;
70 }
71 }
72
73 if cfg.normalize {
75 let max_m = field.iter().map(|v| magnitude(*v)).fold(0.0_f32, f32::max);
76 if max_m > 1e-12 {
77 for v in &mut field {
78 v[0] /= max_m;
79 v[1] /= max_m;
80 v[2] /= max_m;
81 }
82 }
83 }
84
85 let mut deltas: Vec<Delta> = Vec::new();
87 for (vid, v) in field.iter().enumerate() {
88 if magnitude(*v) >= cfg.threshold {
89 deltas.push(Delta {
90 vid: vid as u32,
91 dx: v[0],
92 dy: v[1],
93 dz: v[2],
94 });
95 }
96 }
97
98 let nonzero_count = deltas.len();
99 let max_magnitude = deltas
100 .iter()
101 .map(|d| magnitude([d.dx, d.dy, d.dz]))
102 .fold(0.0_f32, f32::max);
103 let bounds = target_delta_bounds(&deltas);
104
105 AuthoredTarget {
106 name: name.to_owned(),
107 deltas,
108 nonzero_count,
109 max_magnitude,
110 bounds,
111 }
112}
113
114#[allow(dead_code)]
119pub fn create_target_from_mesh_pair(
120 name: &str,
121 base: &[[f32; 3]],
122 deformed: &[[f32; 3]],
123 cfg: &AuthoringConfig,
124) -> AuthoredTarget {
125 let len = base.len().min(deformed.len());
126 let raw: Vec<[f32; 3]> = (0..len)
127 .map(|i| {
128 [
129 deformed[i][0] - base[i][0],
130 deformed[i][1] - base[i][1],
131 deformed[i][2] - base[i][2],
132 ]
133 })
134 .collect();
135 build_authored(name, raw, cfg)
136}
137
138#[allow(dead_code)]
140pub fn create_target_from_delta_field(
141 name: &str,
142 deltas: &[[f32; 3]],
143 cfg: &AuthoringConfig,
144) -> AuthoredTarget {
145 build_authored(name, deltas.to_vec(), cfg)
146}
147
148#[allow(dead_code)]
150pub fn merge_targets(a: &AuthoredTarget, b: &AuthoredTarget, blend: f32) -> AuthoredTarget {
151 let blend = blend.clamp(0.0, 1.0);
152 let max_vid_a = a.deltas.iter().map(|d| d.vid).max().unwrap_or(0);
154 let max_vid_b = b.deltas.iter().map(|d| d.vid).max().unwrap_or(0);
155 let n = (max_vid_a.max(max_vid_b) as usize) + 1;
156
157 let mut field = vec![[0.0_f32; 3]; n];
158 for d in &a.deltas {
159 field[d.vid as usize] = [
160 d.dx * (1.0 - blend),
161 d.dy * (1.0 - blend),
162 d.dz * (1.0 - blend),
163 ];
164 }
165 for d in &b.deltas {
166 let v = &mut field[d.vid as usize];
167 v[0] += d.dx * blend;
168 v[1] += d.dy * blend;
169 v[2] += d.dz * blend;
170 }
171
172 let cfg = AuthoringConfig {
173 threshold: 0.0,
174 ..AuthoringConfig::default()
175 };
176 let mut result = build_authored(&a.name, field, &cfg);
177 result.name = format!("{}_merge_{}", a.name, b.name);
178 result
179}
180
181#[allow(dead_code)]
183pub fn scale_target(t: &AuthoredTarget, scale: f32) -> AuthoredTarget {
184 let field: Vec<[f32; 3]> = {
185 let max_vid = t.deltas.iter().map(|d| d.vid).max().unwrap_or(0) as usize;
186 let mut v = vec![[0.0_f32; 3]; max_vid + 1];
187 for d in &t.deltas {
188 v[d.vid as usize] = [d.dx * scale, d.dy * scale, d.dz * scale];
189 }
190 v
191 };
192 let cfg = AuthoringConfig {
193 threshold: 0.0,
194 ..AuthoringConfig::default()
195 };
196 let mut result = build_authored(&t.name, field, &cfg);
197 result.name = t.name.clone();
198 result
199}
200
201#[allow(dead_code)]
203pub fn invert_target(t: &AuthoredTarget) -> AuthoredTarget {
204 scale_target(t, -1.0)
205}
206
207#[allow(dead_code)]
209pub fn mirror_target_x(t: &AuthoredTarget, sym_pairs: &[(usize, usize)]) -> AuthoredTarget {
210 let max_vid = t.deltas.iter().map(|d| d.vid).max().unwrap_or(0) as usize;
211 let max_sym_vid = sym_pairs
213 .iter()
214 .flat_map(|(a, b)| [*a, *b])
215 .max()
216 .unwrap_or(0);
217 let field_len = max_vid.max(max_sym_vid) + 1;
218 let mut field = vec![[0.0_f32; 3]; field_len];
219 for d in &t.deltas {
220 field[d.vid as usize] = [d.dx, d.dy, d.dz];
221 }
222
223 let mut mirrored = field.clone();
224 for (l, r) in sym_pairs {
225 if *l < field.len() && *r < field.len() {
226 mirrored[*r] = [-field[*l][0], field[*l][1], field[*l][2]];
228 mirrored[*l] = [-field[*r][0], field[*r][1], field[*r][2]];
229 }
230 }
231
232 let cfg = AuthoringConfig {
233 threshold: 0.0,
234 ..AuthoringConfig::default()
235 };
236 let mut result = build_authored(&t.name, mirrored, &cfg);
237 result.name = format!("{}_mirrored", t.name);
238 result
239}
240
241#[allow(dead_code)]
243pub fn target_delta_bounds(deltas: &[Delta]) -> [[f32; 3]; 2] {
244 if deltas.is_empty() {
245 return [[0.0; 3], [0.0; 3]];
246 }
247 let mut mn = [f32::MAX; 3];
248 let mut mx = [f32::MIN; 3];
249 for d in deltas {
250 let v = [d.dx, d.dy, d.dz];
251 for i in 0..3 {
252 mn[i] = mn[i].min(v[i]);
253 mx[i] = mx[i].max(v[i]);
254 }
255 }
256 [mn, mx]
257}
258
259#[allow(dead_code)]
262pub fn smooth_target_deltas(deltas: &mut [[f32; 3]], indices: &[u32], iterations: u32) {
263 let n = deltas.len();
264 if n == 0 || iterations == 0 {
265 return;
266 }
267
268 let mut adj: Vec<Vec<usize>> = vec![Vec::new(); n];
270 for tri in indices.chunks_exact(3) {
271 let (a, b, c) = (tri[0] as usize, tri[1] as usize, tri[2] as usize);
272 if a < n && b < n && c < n {
273 if !adj[a].contains(&b) {
274 adj[a].push(b);
275 }
276 if !adj[a].contains(&c) {
277 adj[a].push(c);
278 }
279 if !adj[b].contains(&a) {
280 adj[b].push(a);
281 }
282 if !adj[b].contains(&c) {
283 adj[b].push(c);
284 }
285 if !adj[c].contains(&a) {
286 adj[c].push(a);
287 }
288 if !adj[c].contains(&b) {
289 adj[c].push(b);
290 }
291 }
292 }
293
294 for _ in 0..iterations {
295 let prev = deltas.to_vec();
296 for i in 0..n {
297 if adj[i].is_empty() {
298 continue;
299 }
300 let mut sum = [0.0_f32; 3];
301 for &nb in &adj[i] {
302 sum[0] += prev[nb][0];
303 sum[1] += prev[nb][1];
304 sum[2] += prev[nb][2];
305 }
306 let cnt = adj[i].len() as f32;
307 deltas[i] = [sum[0] / cnt, sum[1] / cnt, sum[2] / cnt];
308 }
309 }
310}
311
312#[allow(dead_code)]
314pub fn authored_target_stats(t: &AuthoredTarget) -> String {
315 format!(
316 "Target '{}': {} nonzero deltas, max_magnitude={:.6}, bounds=min{:?} max{:?}",
317 t.name, t.nonzero_count, t.max_magnitude, t.bounds[0], t.bounds[1]
318 )
319}
320
321#[cfg(test)]
324mod tests {
325 use super::*;
326
327 fn default_cfg() -> AuthoringConfig {
328 AuthoringConfig::default()
329 }
330
331 #[test]
333 fn test_mesh_pair_delta_count() {
334 let base = vec![[0.0, 0.0, 0.0]; 10];
335 let mut deformed = base.clone();
336 deformed[3] = [1.0, 0.0, 0.0];
337 deformed[7] = [0.0, 2.0, 0.0];
338 let t = create_target_from_mesh_pair("t", &base, &deformed, &default_cfg());
339 assert_eq!(t.nonzero_count, 2);
340 }
341
342 #[test]
344 fn test_threshold_filters() {
345 let base = vec![[0.0, 0.0, 0.0]; 5];
346 let deformed = vec![
347 [1e-6, 0.0, 0.0],
348 [1.0, 0.0, 0.0],
349 [0.0, 0.0, 0.0],
350 [0.0, 0.0, 0.0],
351 [0.0, 0.0, 0.0],
352 ];
353 let cfg = AuthoringConfig {
354 threshold: 1e-5,
355 ..Default::default()
356 };
357 let t = create_target_from_mesh_pair("t", &base, &deformed, &cfg);
358 assert_eq!(t.nonzero_count, 1);
360 }
361
362 #[test]
364 fn test_delta_field_nonzero_count() {
365 let deltas = vec![[1.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
366 let t = create_target_from_delta_field("d", &deltas, &default_cfg());
367 assert_eq!(t.nonzero_count, 2);
368 }
369
370 #[test]
372 fn test_merge_blend0_is_a() {
373 let base = vec![[0.0; 3]; 4];
374 let def_a = vec![
375 [1.0, 0.0, 0.0],
376 [0.0, 0.0, 0.0],
377 [0.0, 0.0, 0.0],
378 [0.0, 0.0, 0.0],
379 ];
380 let def_b = vec![
381 [0.0, 0.0, 0.0],
382 [0.0, 2.0, 0.0],
383 [0.0, 0.0, 0.0],
384 [0.0, 0.0, 0.0],
385 ];
386 let a = create_target_from_mesh_pair("a", &base, &def_a, &default_cfg());
387 let b = create_target_from_mesh_pair("b", &base, &def_b, &default_cfg());
388 let m = merge_targets(&a, &b, 0.0);
389 let d0 = m
391 .deltas
392 .iter()
393 .find(|d| d.vid == 0)
394 .expect("should succeed");
395 assert!((d0.dx - 1.0).abs() < 1e-5);
396 assert!(m.deltas.iter().all(|d| d.vid != 1 || d.dy.abs() < 1e-5));
398 }
399
400 #[test]
402 fn test_merge_blend1_is_b() {
403 let base = vec![[0.0; 3]; 4];
404 let def_a = vec![
405 [1.0, 0.0, 0.0],
406 [0.0, 0.0, 0.0],
407 [0.0, 0.0, 0.0],
408 [0.0, 0.0, 0.0],
409 ];
410 let def_b = vec![
411 [0.0, 0.0, 0.0],
412 [0.0, 2.0, 0.0],
413 [0.0, 0.0, 0.0],
414 [0.0, 0.0, 0.0],
415 ];
416 let a = create_target_from_mesh_pair("a", &base, &def_a, &default_cfg());
417 let b = create_target_from_mesh_pair("b", &base, &def_b, &default_cfg());
418 let m = merge_targets(&a, &b, 1.0);
419 let d1 = m
421 .deltas
422 .iter()
423 .find(|d| d.vid == 1)
424 .expect("should succeed");
425 assert!((d1.dy - 2.0).abs() < 1e-5);
426 assert!(m.deltas.iter().all(|d| d.vid != 0 || d.dx.abs() < 1e-5));
427 }
428
429 #[test]
431 fn test_scale_target_doubles() {
432 let deltas = vec![[1.0, 2.0, 3.0]];
433 let t = create_target_from_delta_field("s", &deltas, &default_cfg());
434 let scaled = scale_target(&t, 2.0);
435 let d = &scaled.deltas[0];
436 assert!((d.dx - 2.0).abs() < 1e-5);
437 assert!((d.dy - 4.0).abs() < 1e-5);
438 assert!((d.dz - 6.0).abs() < 1e-5);
439 }
440
441 #[test]
443 fn test_invert_target_negates() {
444 let deltas = vec![[1.0, -2.0, 3.0]];
445 let t = create_target_from_delta_field("i", &deltas, &default_cfg());
446 let inv = invert_target(&t);
447 let d = &inv.deltas[0];
448 assert!((d.dx + 1.0).abs() < 1e-5);
449 assert!((d.dy - 2.0).abs() < 1e-5);
450 assert!((d.dz + 3.0).abs() < 1e-5);
451 }
452
453 #[test]
455 fn test_invert_twice_identity() {
456 let deltas = vec![[1.0, -2.0, 3.0], [0.5, 0.5, 0.5]];
457 let t = create_target_from_delta_field("i2", &deltas, &default_cfg());
458 let inv2 = invert_target(&invert_target(&t));
459 for (orig, inv) in t.deltas.iter().zip(inv2.deltas.iter()) {
460 assert!((orig.dx - inv.dx).abs() < 1e-4);
461 assert!((orig.dy - inv.dy).abs() < 1e-4);
462 assert!((orig.dz - inv.dz).abs() < 1e-4);
463 }
464 }
465
466 #[test]
468 fn test_bounds_min_le_max() {
469 let deltas = vec![[1.0, -2.0, 3.0], [-1.0, 4.0, 0.5]];
470 let t = create_target_from_delta_field("b", &deltas, &default_cfg());
471 let b = &t.bounds;
472 for (i, (mn, mx)) in b[0].iter().zip(b[1].iter()).enumerate() {
473 assert!(mn <= mx, "min > max at axis {i}");
474 }
475 }
476
477 #[test]
479 fn test_bounds_values() {
480 let d = vec![
481 Delta {
482 vid: 0,
483 dx: 1.0,
484 dy: -2.0,
485 dz: 3.0,
486 },
487 Delta {
488 vid: 1,
489 dx: -1.0,
490 dy: 4.0,
491 dz: 0.5,
492 },
493 ];
494 let b = target_delta_bounds(&d);
495 assert!((b[0][0] - (-1.0)).abs() < 1e-6);
496 assert!((b[1][0] - 1.0).abs() < 1e-6);
497 assert!((b[0][1] - (-2.0)).abs() < 1e-6);
498 assert!((b[1][1] - 4.0).abs() < 1e-6);
499 }
500
501 #[test]
503 fn test_smooth_reduces_magnitude() {
504 let indices = vec![0u32, 1, 2];
506 let mut deltas = vec![[10.0_f32, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0]];
507 let before = deltas[0][0];
508 smooth_target_deltas(&mut deltas, &indices, 1);
509 let after = deltas[0][0];
510 assert!(after < before, "smoothing should reduce peak magnitude");
511 }
512
513 #[test]
515 fn test_authored_target_stats_non_empty() {
516 let deltas = vec![[1.0, 0.0, 0.0]];
517 let t = create_target_from_delta_field("stats", &deltas, &default_cfg());
518 let s = authored_target_stats(&t);
519 assert!(!s.is_empty());
520 assert!(s.contains("stats"));
521 }
522
523 #[test]
525 fn test_mirror_target_x_swaps() {
526 let deltas = vec![[1.0, 2.0, 3.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0]];
527 let t = create_target_from_delta_field("mx", &deltas, &default_cfg());
528 let mirrored = mirror_target_x(&t, &[(0, 1)]);
529 let d1 = mirrored.deltas.iter().find(|d| d.vid == 1);
531 assert!(d1.is_some(), "mirrored vertex 1 should appear");
532 let d1 = d1.expect("should succeed");
533 assert!(
534 (d1.dx - (-1.0)).abs() < 1e-5,
535 "X should be negated: got {}",
536 d1.dx
537 );
538 }
539
540 #[test]
542 fn test_normalize_flag() {
543 let deltas = vec![[0.0, 0.0, 5.0], [0.0, 0.0, 3.0]];
544 let cfg = AuthoringConfig {
545 normalize: true,
546 ..Default::default()
547 };
548 let t = create_target_from_delta_field("norm", &deltas, &cfg);
549 assert!(
550 (t.max_magnitude - 1.0).abs() < 1e-5,
551 "max_magnitude should be 1.0 after normalize, got {}",
552 t.max_magnitude
553 );
554 }
555
556 #[test]
558 fn test_bounds_empty() {
559 let b = target_delta_bounds(&[]);
560 assert_eq!(b, [[0.0; 3], [0.0; 3]]);
561 }
562}