1#![allow(dead_code)]
5
6use crate::heat_map::{scalars_to_colors_range, ColorRamp};
7use crate::mesh::MeshBuffers;
8use crate::normals::compute_normals;
9
10pub struct DisplacementField {
12 pub deltas: Vec<[f32; 3]>,
14 pub magnitudes: Vec<f32>,
16 pub max_magnitude: f32,
18 pub mean_magnitude: f32,
20 pub rms: f32,
22}
23
24impl DisplacementField {
25 pub fn compute(base: &MeshBuffers, deformed: &MeshBuffers) -> anyhow::Result<Self> {
29 compute_displacement(base, deformed)
30 }
31
32 pub fn vertex_count(&self) -> usize {
34 self.deltas.len()
35 }
36
37 pub fn is_zero(&self) -> bool {
39 self.max_magnitude < f32::EPSILON
40 }
41
42 pub fn top_displaced(&self, n: usize) -> Vec<usize> {
45 let mut indices: Vec<usize> = (0..self.magnitudes.len()).collect();
46 indices.sort_unstable_by(|&a, &b| {
47 self.magnitudes[b]
48 .partial_cmp(&self.magnitudes[a])
49 .unwrap_or(std::cmp::Ordering::Equal)
50 });
51 indices.truncate(n);
52 indices
53 }
54
55 pub fn scale(&self, factor: f32) -> Self {
57 let deltas: Vec<[f32; 3]> = self
58 .deltas
59 .iter()
60 .map(|d| [d[0] * factor, d[1] * factor, d[2] * factor])
61 .collect();
62 let magnitudes: Vec<f32> = deltas
63 .iter()
64 .map(|d| (d[0] * d[0] + d[1] * d[1] + d[2] * d[2]).sqrt())
65 .collect();
66 let max_magnitude = magnitudes.iter().cloned().fold(0.0f32, f32::max);
67 let mean_magnitude = if magnitudes.is_empty() {
68 0.0
69 } else {
70 magnitudes.iter().sum::<f32>() / magnitudes.len() as f32
71 };
72 let rms = if magnitudes.is_empty() {
73 0.0
74 } else {
75 let mean_sq = magnitudes.iter().map(|m| m * m).sum::<f32>() / magnitudes.len() as f32;
76 mean_sq.sqrt()
77 };
78 DisplacementField {
79 deltas,
80 magnitudes,
81 max_magnitude,
82 mean_magnitude,
83 rms,
84 }
85 }
86
87 pub fn apply_to(&self, mesh: &MeshBuffers) -> MeshBuffers {
90 let count = mesh.positions.len().min(self.deltas.len());
91 let mut new_positions = mesh.positions.clone();
92 for (p, d) in new_positions.iter_mut().zip(self.deltas.iter()).take(count) {
93 p[0] += d[0];
94 p[1] += d[1];
95 p[2] += d[2];
96 }
97 let mut result = MeshBuffers {
98 positions: new_positions,
99 normals: mesh.normals.clone(),
100 tangents: mesh.tangents.clone(),
101 uvs: mesh.uvs.clone(),
102 indices: mesh.indices.clone(),
103 colors: mesh.colors.clone(),
104 has_suit: mesh.has_suit,
105 };
106 compute_normals(&mut result);
107 result
108 }
109
110 pub fn threshold(&self, min_magnitude: f32) -> Self {
113 let deltas: Vec<[f32; 3]> = self
114 .deltas
115 .iter()
116 .zip(self.magnitudes.iter())
117 .map(|(d, &mag)| {
118 if mag < min_magnitude {
119 [0.0, 0.0, 0.0]
120 } else {
121 *d
122 }
123 })
124 .collect();
125 let magnitudes: Vec<f32> = deltas
126 .iter()
127 .map(|d| (d[0] * d[0] + d[1] * d[1] + d[2] * d[2]).sqrt())
128 .collect();
129 let max_magnitude = magnitudes.iter().cloned().fold(0.0f32, f32::max);
130 let mean_magnitude = if magnitudes.is_empty() {
131 0.0
132 } else {
133 magnitudes.iter().sum::<f32>() / magnitudes.len() as f32
134 };
135 let rms = if magnitudes.is_empty() {
136 0.0
137 } else {
138 let mean_sq = magnitudes.iter().map(|m| m * m).sum::<f32>() / magnitudes.len() as f32;
139 mean_sq.sqrt()
140 };
141 DisplacementField {
142 deltas,
143 magnitudes,
144 max_magnitude,
145 mean_magnitude,
146 rms,
147 }
148 }
149}
150
151pub struct MeshDiffStats {
153 pub vertex_count: usize,
154 pub max_displacement: f32,
155 pub mean_displacement: f32,
156 pub rms_displacement: f32,
157 pub hausdorff_approx: f32,
159 pub percent_changed: f32,
161 pub epsilon: f32,
162}
163
164pub fn compute_displacement(
168 base: &MeshBuffers,
169 deformed: &MeshBuffers,
170) -> anyhow::Result<DisplacementField> {
171 if base.positions.len() != deformed.positions.len() {
172 anyhow::bail!(
173 "mesh_diff: vertex count mismatch ({} vs {})",
174 base.positions.len(),
175 deformed.positions.len()
176 );
177 }
178 let deltas: Vec<[f32; 3]> = base
179 .positions
180 .iter()
181 .zip(deformed.positions.iter())
182 .map(|(b, d)| [d[0] - b[0], d[1] - b[1], d[2] - b[2]])
183 .collect();
184 let magnitudes: Vec<f32> = deltas
185 .iter()
186 .map(|d| (d[0] * d[0] + d[1] * d[1] + d[2] * d[2]).sqrt())
187 .collect();
188 let max_magnitude = magnitudes.iter().cloned().fold(0.0f32, f32::max);
189 let mean_magnitude = if magnitudes.is_empty() {
190 0.0
191 } else {
192 magnitudes.iter().sum::<f32>() / magnitudes.len() as f32
193 };
194 let rms = if magnitudes.is_empty() {
195 0.0
196 } else {
197 let mean_sq = magnitudes.iter().map(|m| m * m).sum::<f32>() / magnitudes.len() as f32;
198 mean_sq.sqrt()
199 };
200 Ok(DisplacementField {
201 deltas,
202 magnitudes,
203 max_magnitude,
204 mean_magnitude,
205 rms,
206 })
207}
208
209pub fn mesh_diff_stats(
213 base: &MeshBuffers,
214 deformed: &MeshBuffers,
215 epsilon: f32,
216) -> anyhow::Result<MeshDiffStats> {
217 let field_ab = compute_displacement(base, deformed)?;
218 let hausdorff_approx = field_ab.mean_magnitude;
223
224 let n = field_ab.magnitudes.len();
225 let changed = field_ab.magnitudes.iter().filter(|&&m| m > epsilon).count();
226 let percent_changed = if n == 0 {
227 0.0
228 } else {
229 changed as f32 / n as f32 * 100.0
230 };
231
232 Ok(MeshDiffStats {
233 vertex_count: n,
234 max_displacement: field_ab.max_magnitude,
235 mean_displacement: field_ab.mean_magnitude,
236 rms_displacement: field_ab.rms,
237 hausdorff_approx,
238 percent_changed,
239 epsilon,
240 })
241}
242
243pub fn meshes_approx_equal(a: &MeshBuffers, b: &MeshBuffers, epsilon: f32) -> bool {
246 if a.positions.len() != b.positions.len() {
247 return false;
248 }
249 a.positions.iter().zip(b.positions.iter()).all(|(pa, pb)| {
250 let dx = pa[0] - pb[0];
251 let dy = pa[1] - pb[1];
252 let dz = pa[2] - pb[2];
253 (dx * dx + dy * dy + dz * dz).sqrt() <= epsilon
254 })
255}
256
257pub fn blend_meshes(
261 base: &MeshBuffers,
262 target: &MeshBuffers,
263 t: f32,
264) -> anyhow::Result<MeshBuffers> {
265 if base.positions.len() != target.positions.len() {
266 anyhow::bail!(
267 "blend_meshes: vertex count mismatch ({} vs {})",
268 base.positions.len(),
269 target.positions.len()
270 );
271 }
272 let t = t.clamp(0.0, 1.0);
273 let positions: Vec<[f32; 3]> = base
274 .positions
275 .iter()
276 .zip(target.positions.iter())
277 .map(|(b, tgt)| {
278 [
279 b[0] + (tgt[0] - b[0]) * t,
280 b[1] + (tgt[1] - b[1]) * t,
281 b[2] + (tgt[2] - b[2]) * t,
282 ]
283 })
284 .collect();
285 let mut result = MeshBuffers {
286 positions,
287 normals: base.normals.clone(),
288 tangents: base.tangents.clone(),
289 uvs: base.uvs.clone(),
290 indices: base.indices.clone(),
291 colors: base.colors.clone(),
292 has_suit: base.has_suit,
293 };
294 compute_normals(&mut result);
295 Ok(result)
296}
297
298pub fn interpolate_mesh_sequence(frames: &[MeshBuffers], t: f32) -> anyhow::Result<MeshBuffers> {
303 if frames.is_empty() {
304 anyhow::bail!("interpolate_mesh_sequence: frames slice is empty");
305 }
306 if frames.len() == 1 {
307 return Ok(frames[0].clone());
308 }
309 let t = t.clamp(0.0, 1.0);
310 let last = (frames.len() - 1) as f32;
311 let pos = t * last;
312 let lo = (pos.floor() as usize).min(frames.len() - 2);
313 let hi = lo + 1;
314 let frac = pos - lo as f32;
315 blend_meshes(&frames[lo], &frames[hi], frac)
316}
317
318pub fn displacement_to_heat_mesh(base: &MeshBuffers, field: &DisplacementField) -> MeshBuffers {
321 let ramp = ColorRamp::Rainbow;
322 let max_mag = field.max_magnitude;
323 let min_mag = 0.0f32;
324 let rgb_colors = scalars_to_colors_range(&field.magnitudes, &ramp, min_mag, max_mag);
325 let rgba_colors: Vec<[f32; 4]> = rgb_colors
326 .into_iter()
327 .map(|c| [c[0], c[1], c[2], 1.0])
328 .collect();
329 MeshBuffers {
330 positions: base.positions.clone(),
331 normals: base.normals.clone(),
332 tangents: base.tangents.clone(),
333 uvs: base.uvs.clone(),
334 indices: base.indices.clone(),
335 colors: Some(rgba_colors),
336 has_suit: base.has_suit,
337 }
338}
339
340#[cfg(test)]
343mod tests {
344 use super::*;
345 use oxihuman_morph::engine::MeshBuffers as MB;
346
347 fn make_mesh(positions: Vec<[f32; 3]>) -> MeshBuffers {
348 let n = positions.len();
349 let normals = vec![[0.0f32, 0.0, 1.0]; n];
350 let uvs = vec![[0.0f32, 0.0]; n];
351 let indices: Vec<u32> = if n >= 3 {
353 (0..((n as u32) - 2))
354 .flat_map(|i| [0, i + 1, i + 2])
355 .collect()
356 } else {
357 vec![]
358 };
359 MeshBuffers::from_morph(MB {
360 positions,
361 normals,
362 uvs,
363 indices,
364 has_suit: false,
365 })
366 }
367
368 fn base_triangle() -> MeshBuffers {
369 make_mesh(vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]])
370 }
371
372 fn deformed_triangle() -> MeshBuffers {
373 make_mesh(vec![
374 [0.0, 0.0, 1.0], [1.0, 0.0, 1.0],
376 [0.0, 1.0, 1.0],
377 ])
378 }
379
380 #[test]
383 fn test_displacement_field_zero() {
384 let base = base_triangle();
385 let field = DisplacementField::compute(&base, &base).expect("should succeed");
386 assert_eq!(field.vertex_count(), 3);
387 assert!(field.is_zero());
388 assert!(field.max_magnitude < 1e-6);
389 }
390
391 #[test]
392 fn test_displacement_field_nonzero() {
393 let base = base_triangle();
394 let deformed = deformed_triangle();
395 let field = DisplacementField::compute(&base, &deformed).expect("should succeed");
396 assert!(!field.is_zero());
397 assert!((field.max_magnitude - 1.0).abs() < 1e-5);
398 }
399
400 #[test]
401 fn test_displacement_magnitudes() {
402 let base = base_triangle();
403 let deformed = deformed_triangle();
404 let field = DisplacementField::compute(&base, &deformed).expect("should succeed");
405 assert_eq!(field.magnitudes.len(), 3);
406 for &m in &field.magnitudes {
407 assert!(
408 (m - 1.0).abs() < 1e-5,
409 "each magnitude should be 1.0, got {m}"
410 );
411 }
412 assert!((field.mean_magnitude - 1.0).abs() < 1e-5);
413 assert!((field.rms - 1.0).abs() < 1e-5);
414 }
415
416 #[test]
417 fn test_displacement_top_displaced() {
418 let base = make_mesh(vec![
419 [0.0, 0.0, 0.0],
420 [0.0, 0.0, 0.0],
421 [0.0, 0.0, 0.0],
422 [0.0, 0.0, 0.0],
423 ]);
424 let deformed = make_mesh(vec![
425 [0.0, 0.0, 3.0],
426 [0.0, 0.0, 1.0],
427 [0.0, 0.0, 4.0],
428 [0.0, 0.0, 2.0],
429 ]);
430 let field = DisplacementField::compute(&base, &deformed).expect("should succeed");
431 let top2 = field.top_displaced(2);
432 assert_eq!(top2.len(), 2);
433 assert_eq!(top2[0], 2);
435 assert_eq!(top2[1], 0);
436 }
437
438 #[test]
439 fn test_displacement_scale() {
440 let base = base_triangle();
441 let deformed = deformed_triangle();
442 let field = DisplacementField::compute(&base, &deformed).expect("should succeed");
443 let scaled = field.scale(2.0);
444 assert!((scaled.max_magnitude - 2.0).abs() < 1e-5);
445 assert!((scaled.mean_magnitude - 2.0).abs() < 1e-5);
446 for d in &scaled.deltas {
447 assert!((d[2] - 2.0).abs() < 1e-5);
448 }
449 }
450
451 #[test]
452 fn test_displacement_threshold() {
453 let base = make_mesh(vec![[0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0]]);
454 let deformed = make_mesh(vec![
455 [0.0, 0.0, 0.5], [0.0, 0.0, 1.0], [0.0, 0.0, 0.3], ]);
459 let field = DisplacementField::compute(&base, &deformed).expect("should succeed");
460 let thresholded = field.threshold(0.8);
461 assert!(thresholded.magnitudes[0] < 1e-6);
463 assert!(thresholded.magnitudes[2] < 1e-6);
464 assert!((thresholded.magnitudes[1] - 1.0).abs() < 1e-5);
465 }
466
467 #[test]
468 fn test_displacement_apply_to() {
469 let base = base_triangle();
470 let deformed = deformed_triangle();
471 let field = DisplacementField::compute(&base, &deformed).expect("should succeed");
472 let result = field.apply_to(&base);
473 for (r, d) in result.positions.iter().zip(deformed.positions.iter()) {
475 assert!((r[0] - d[0]).abs() < 1e-5);
476 assert!((r[1] - d[1]).abs() < 1e-5);
477 assert!((r[2] - d[2]).abs() < 1e-5);
478 }
479 }
480
481 #[test]
484 fn test_meshes_approx_equal_true() {
485 let a = base_triangle();
486 let b = base_triangle();
487 assert!(meshes_approx_equal(&a, &b, 1e-6));
488 }
489
490 #[test]
491 fn test_meshes_approx_equal_false() {
492 let a = base_triangle();
493 let b = deformed_triangle();
494 assert!(!meshes_approx_equal(&a, &b, 1e-6));
495 assert!(meshes_approx_equal(&a, &b, 2.0));
497 }
498
499 #[test]
502 fn test_blend_meshes_zero() {
503 let base = base_triangle();
504 let target = deformed_triangle();
505 let blended = blend_meshes(&base, &target, 0.0).expect("should succeed");
506 assert!(meshes_approx_equal(&blended, &base, 1e-5));
507 }
508
509 #[test]
510 fn test_blend_meshes_one() {
511 let base = base_triangle();
512 let target = deformed_triangle();
513 let blended = blend_meshes(&base, &target, 1.0).expect("should succeed");
514 assert!(meshes_approx_equal(&blended, &target, 1e-5));
515 }
516
517 #[test]
518 fn test_blend_meshes_half() {
519 let base = base_triangle();
520 let target = deformed_triangle();
521 let blended = blend_meshes(&base, &target, 0.5).expect("should succeed");
522 for p in &blended.positions {
524 assert!((p[2] - 0.5).abs() < 1e-5, "expected Z=0.5, got {}", p[2]);
525 }
526 }
527
528 #[test]
531 fn test_mesh_diff_stats() {
532 let base = base_triangle();
533 let deformed = deformed_triangle();
534 let stats = mesh_diff_stats(&base, &deformed, 0.5).expect("should succeed");
535 assert_eq!(stats.vertex_count, 3);
536 assert!((stats.max_displacement - 1.0).abs() < 1e-5);
537 assert!((stats.mean_displacement - 1.0).abs() < 1e-5);
538 assert!((stats.rms_displacement - 1.0).abs() < 1e-5);
539 assert!((stats.percent_changed - 100.0).abs() < 1e-3);
541 }
542
543 #[test]
546 fn test_interpolate_sequence() {
547 let f0 = base_triangle();
548 let f1 = deformed_triangle();
549 let frames = vec![f0.clone(), f1.clone()];
550
551 let r0 = interpolate_mesh_sequence(&frames, 0.0).expect("should succeed");
553 assert!(meshes_approx_equal(&r0, &f0, 1e-5));
554
555 let r1 = interpolate_mesh_sequence(&frames, 1.0).expect("should succeed");
557 assert!(meshes_approx_equal(&r1, &f1, 1e-5));
558
559 let r_half = interpolate_mesh_sequence(&frames, 0.5).expect("should succeed");
561 for p in &r_half.positions {
562 assert!((p[2] - 0.5).abs() < 1e-5);
563 }
564
565 let empty: Vec<MeshBuffers> = vec![];
567 assert!(interpolate_mesh_sequence(&empty, 0.5).is_err());
568 }
569}