1use crate::registration::{RegistrationParams, RigidTransform, align_meshes};
39use crate::{Mesh, MeshError, MeshResult};
40use nalgebra::{Point3, UnitQuaternion, Vector3};
41use std::collections::HashMap;
42
43#[derive(Debug)]
45pub struct MultiAlignmentResult {
46 pub aligned_scans: Vec<Mesh>,
48
49 pub transforms: Vec<RigidTransform>,
51
52 pub pairwise_errors: Vec<(usize, usize, f64)>,
54
55 pub global_error: f64,
57
58 pub reference_index: usize,
60
61 pub globally_optimized: bool,
63}
64
65#[derive(Debug, Clone)]
67pub struct MultiAlignmentParams {
68 pub registration_params: RegistrationParams,
70
71 pub reference_index: Option<usize>,
73
74 pub global_optimization: bool,
76
77 pub global_iterations: usize,
79
80 pub min_overlap_ratio: f64,
82}
83
84impl Default for MultiAlignmentParams {
85 fn default() -> Self {
86 Self {
87 registration_params: RegistrationParams::icp(),
88 reference_index: None,
89 global_optimization: true,
90 global_iterations: 3,
91 min_overlap_ratio: 0.1,
92 }
93 }
94}
95
96impl MultiAlignmentParams {
97 pub fn for_body_scans() -> Self {
99 Self {
100 registration_params: RegistrationParams::icp(),
101 reference_index: None,
102 global_optimization: true,
103 global_iterations: 5,
104 min_overlap_ratio: 0.15,
105 }
106 }
107
108 pub fn for_prealigned_scans() -> Self {
110 Self {
111 registration_params: RegistrationParams::icp(),
112 reference_index: Some(0),
113 global_optimization: false,
114 global_iterations: 0,
115 min_overlap_ratio: 0.05,
116 }
117 }
118}
119
120#[derive(Debug, Clone)]
122pub struct MergeParams {
123 pub overlap_handling: OverlapHandling,
125
126 pub duplicate_threshold: f64,
128
129 pub remove_duplicates: bool,
131
132 pub blend_normals: bool,
134
135 pub fill_gaps: bool,
137
138 pub max_gap_size: f64,
140}
141
142impl Default for MergeParams {
143 fn default() -> Self {
144 Self {
145 overlap_handling: OverlapHandling::Average,
146 duplicate_threshold: 0.5, remove_duplicates: true,
148 blend_normals: true,
149 fill_gaps: true,
150 max_gap_size: 5.0, }
152 }
153}
154
155impl MergeParams {
156 pub fn conservative() -> Self {
158 Self {
159 overlap_handling: OverlapHandling::KeepBoth,
160 duplicate_threshold: 0.1,
161 remove_duplicates: false,
162 blend_normals: false,
163 fill_gaps: false,
164 max_gap_size: 0.0,
165 }
166 }
167
168 pub fn aggressive() -> Self {
170 Self {
171 overlap_handling: OverlapHandling::SelectBest,
172 duplicate_threshold: 1.0,
173 remove_duplicates: true,
174 blend_normals: true,
175 fill_gaps: true,
176 max_gap_size: 10.0,
177 }
178 }
179}
180
181#[derive(Debug, Clone, Copy, PartialEq, Eq)]
183pub enum OverlapHandling {
184 KeepBoth,
186 Average,
188 SelectBest,
190 KeepFirst,
192}
193
194#[derive(Debug)]
196pub struct MergeResult {
197 pub mesh: Mesh,
199
200 pub scans_merged: usize,
202
203 pub duplicates_removed: usize,
205
206 pub gap_faces_added: usize,
208
209 pub overlap_regions: Vec<OverlapRegion>,
211}
212
213#[derive(Debug, Clone)]
215pub struct OverlapRegion {
216 pub scan_indices: (usize, usize),
218 pub center: Point3<f64>,
220 pub radius: f64,
222 pub vertex_count: usize,
224}
225
226pub fn align_multiple_scans(scans: &[&Mesh]) -> MeshResult<MultiAlignmentResult> {
231 align_multiple_scans_with_params(scans, &MultiAlignmentParams::default())
232}
233
234pub fn align_multiple_scans_with_params(
236 scans: &[&Mesh],
237 params: &MultiAlignmentParams,
238) -> MeshResult<MultiAlignmentResult> {
239 if scans.is_empty() {
240 return Err(MeshError::InvalidTopology {
241 details: "No scans provided for alignment".into(),
242 });
243 }
244
245 if scans.len() == 1 {
246 return Ok(MultiAlignmentResult {
247 aligned_scans: vec![scans[0].clone()],
248 transforms: vec![RigidTransform::identity()],
249 pairwise_errors: Vec::new(),
250 global_error: 0.0,
251 reference_index: 0,
252 globally_optimized: false,
253 });
254 }
255
256 let reference_idx = params.reference_index.unwrap_or_else(|| {
258 scans
260 .iter()
261 .enumerate()
262 .max_by_key(|(_, s)| s.vertices.len())
263 .map(|(i, _)| i)
264 .unwrap_or(0)
265 });
266
267 let mut transforms: Vec<RigidTransform> = vec![RigidTransform::identity(); scans.len()];
269 let mut pairwise_errors = Vec::new();
270
271 for (i, scan) in scans.iter().enumerate() {
273 if i == reference_idx {
274 continue;
275 }
276
277 match align_meshes(scan, scans[reference_idx], ¶ms.registration_params) {
278 Ok(result) => {
279 transforms[i] = result.transformation.clone();
280 pairwise_errors.push((i, reference_idx, result.rms_error));
281 }
282 Err(_) => {
283 pairwise_errors.push((i, reference_idx, f64::INFINITY));
285 }
286 }
287 }
288
289 let aligned_scans: Vec<Mesh> = scans
291 .iter()
292 .enumerate()
293 .map(|(i, scan)| apply_transform(scan, &transforms[i]))
294 .collect();
295
296 if params.global_optimization && scans.len() > 2 {
299 for _ in 0..params.global_iterations {
300 let (_new_transforms, _) =
301 refine_global_alignment(&aligned_scans, reference_idx, params);
302 }
303 }
304
305 let global_error = if pairwise_errors.is_empty() {
307 0.0
308 } else {
309 let valid_errors: Vec<f64> = pairwise_errors
310 .iter()
311 .filter(|(_, _, e)| e.is_finite())
312 .map(|(_, _, e)| *e)
313 .collect();
314 if valid_errors.is_empty() {
315 f64::INFINITY
316 } else {
317 valid_errors.iter().sum::<f64>() / valid_errors.len() as f64
318 }
319 };
320
321 Ok(MultiAlignmentResult {
322 aligned_scans,
323 transforms,
324 pairwise_errors,
325 global_error,
326 reference_index: reference_idx,
327 globally_optimized: params.global_optimization && scans.len() > 2,
328 })
329}
330
331pub fn merge_scans(scans: &[Mesh], params: &MergeParams) -> MergeResult {
333 if scans.is_empty() {
334 return MergeResult {
335 mesh: Mesh::new(),
336 scans_merged: 0,
337 duplicates_removed: 0,
338 gap_faces_added: 0,
339 overlap_regions: Vec::new(),
340 };
341 }
342
343 if scans.len() == 1 {
344 return MergeResult {
345 mesh: scans[0].clone(),
346 scans_merged: 1,
347 duplicates_removed: 0,
348 gap_faces_added: 0,
349 overlap_regions: Vec::new(),
350 };
351 }
352
353 let mut merged = Mesh::new();
354 let mut vertex_offsets: Vec<usize> = Vec::new();
355 let mut overlap_regions = Vec::new();
356
357 for scan in scans {
359 vertex_offsets.push(merged.vertices.len());
360 merged.vertices.extend(scan.vertices.iter().cloned());
361
362 let offset = *vertex_offsets.last().unwrap() as u32;
363 for face in &scan.faces {
364 merged
365 .faces
366 .push([face[0] + offset, face[1] + offset, face[2] + offset]);
367 }
368 }
369
370 let mut duplicates_removed = 0;
371
372 if params.remove_duplicates
374 || matches!(
375 params.overlap_handling,
376 OverlapHandling::Average | OverlapHandling::SelectBest
377 )
378 {
379 let (deduped, removed, regions) = handle_overlaps(&merged, scans, &vertex_offsets, params);
380 merged = deduped;
381 duplicates_removed = removed;
382 overlap_regions = regions;
383 }
384
385 let gap_faces_added = if params.fill_gaps {
387 fill_scan_gaps(&mut merged, params.max_gap_size)
388 } else {
389 0
390 };
391
392 MergeResult {
393 mesh: merged,
394 scans_merged: scans.len(),
395 duplicates_removed,
396 gap_faces_added,
397 overlap_regions,
398 }
399}
400
401fn apply_transform(mesh: &Mesh, transform: &RigidTransform) -> Mesh {
407 let mut result = mesh.clone();
408 for vertex in &mut result.vertices {
409 let rotated = transform.rotation * vertex.position.coords;
411 let scaled = rotated * transform.scale;
412 vertex.position = Point3::from(scaled + transform.translation);
413
414 if let Some(ref mut normal) = vertex.normal {
415 *normal = transform.rotation * *normal;
416 }
417 }
418 result
419}
420
421fn refine_global_alignment(
423 scans: &[Mesh],
424 reference_idx: usize,
425 params: &MultiAlignmentParams,
426) -> (Vec<RigidTransform>, f64) {
427 let mut transforms: Vec<RigidTransform> = vec![RigidTransform::identity(); scans.len()];
428 let mut total_error = 0.0;
429 let mut pair_count = 0;
430
431 for i in 0..scans.len() {
433 if i == reference_idx {
434 continue;
435 }
436
437 let mut accumulated_translation = Vector3::zeros();
439 let mut accumulated_rotation = UnitQuaternion::identity();
440 let mut weight_sum = 0.0;
441
442 for j in 0..scans.len() {
443 if i == j {
444 continue;
445 }
446
447 let overlap = estimate_overlap(&scans[i], &scans[j]);
449 if overlap < params.min_overlap_ratio {
450 continue;
451 }
452
453 if let Ok(result) = align_meshes(&scans[i], &scans[j], ¶ms.registration_params) {
455 let weight = overlap;
456 accumulated_translation += result.transformation.translation * weight;
457 accumulated_rotation = accumulated_rotation.slerp(
459 &result.transformation.rotation,
460 weight / (weight_sum + weight),
461 );
462 weight_sum += weight;
463 total_error += result.rms_error;
464 pair_count += 1;
465 }
466 }
467
468 if weight_sum > 0.0 {
469 transforms[i] = RigidTransform {
470 rotation: accumulated_rotation,
471 translation: accumulated_translation / weight_sum,
472 scale: 1.0,
473 };
474 }
475 }
476
477 let avg_error = if pair_count > 0 {
478 total_error / pair_count as f64
479 } else {
480 0.0
481 };
482
483 (transforms, avg_error)
484}
485
486fn estimate_overlap(mesh_a: &Mesh, mesh_b: &Mesh) -> f64 {
488 if mesh_a.vertices.is_empty() || mesh_b.vertices.is_empty() {
489 return 0.0;
490 }
491
492 let threshold_sq = 5.0 * 5.0; let mut overlap_count = 0;
496 for va in &mesh_a.vertices {
497 for vb in &mesh_b.vertices {
498 let dist_sq = (va.position - vb.position).norm_squared();
499 if dist_sq < threshold_sq {
500 overlap_count += 1;
501 break;
502 }
503 }
504 }
505
506 overlap_count as f64 / mesh_a.vertices.len() as f64
507}
508
509fn handle_overlaps(
511 merged: &Mesh,
512 scans: &[Mesh],
513 vertex_offsets: &[usize],
514 params: &MergeParams,
515) -> (Mesh, usize, Vec<OverlapRegion>) {
516 let threshold_sq = params.duplicate_threshold * params.duplicate_threshold;
517 let mut result = Mesh::new();
518 let mut vertex_map: HashMap<usize, usize> = HashMap::new();
519 let mut duplicates_removed = 0;
520 let mut overlap_regions = Vec::new();
521
522 let mut is_duplicate: Vec<bool> = vec![false; merged.vertices.len()];
527 let mut duplicate_of: Vec<Option<usize>> = vec![None; merged.vertices.len()];
528
529 for (scan_i, (offset_i, scan_a)) in vertex_offsets.iter().zip(scans.iter()).enumerate() {
531 let end_i = *offset_i + scan_a.vertices.len();
532
533 for (scan_j, (offset_j, scan_b)) in vertex_offsets.iter().zip(scans.iter()).enumerate() {
534 if scan_j <= scan_i {
535 continue; }
537
538 let end_j = *offset_j + scan_b.vertices.len();
539 let mut overlap_vertices = Vec::new();
540
541 for vi in *offset_i..end_i {
542 if is_duplicate[vi] {
543 continue;
544 }
545
546 for vj in *offset_j..end_j {
547 if is_duplicate[vj] {
548 continue;
549 }
550
551 let dist_sq = (merged.vertices[vi].position - merged.vertices[vj].position)
552 .norm_squared();
553
554 if dist_sq < threshold_sq {
555 match params.overlap_handling {
556 OverlapHandling::KeepBoth => {
557 overlap_vertices.push(vi);
559 }
560 OverlapHandling::Average => {
561 is_duplicate[vj] = true;
563 duplicate_of[vj] = Some(vi);
564 duplicates_removed += 1;
565 overlap_vertices.push(vi);
566 }
567 OverlapHandling::SelectBest | OverlapHandling::KeepFirst => {
568 is_duplicate[vj] = true;
570 duplicate_of[vj] = Some(vi);
571 duplicates_removed += 1;
572 overlap_vertices.push(vi);
573 }
574 }
575 break;
576 }
577 }
578 }
579
580 if !overlap_vertices.is_empty() {
582 let center = Point3::from(
583 overlap_vertices
584 .iter()
585 .map(|&i| merged.vertices[i].position.coords)
586 .fold(Vector3::zeros(), |a, b| a + b)
587 / overlap_vertices.len() as f64,
588 );
589 let radius = overlap_vertices
590 .iter()
591 .map(|&i| (merged.vertices[i].position - center).norm())
592 .fold(0.0_f64, |a, b| a.max(b));
593
594 overlap_regions.push(OverlapRegion {
595 scan_indices: (scan_i, scan_j),
596 center,
597 radius,
598 vertex_count: overlap_vertices.len(),
599 });
600 }
601 }
602 }
603
604 for (old_idx, vertex) in merged.vertices.iter().enumerate() {
606 if is_duplicate[old_idx] {
607 if let Some(original_idx) = duplicate_of[old_idx]
609 && let Some(&new_idx) = vertex_map.get(&original_idx)
610 {
611 vertex_map.insert(old_idx, new_idx);
612 }
613 } else {
614 let new_idx = result.vertices.len();
615 vertex_map.insert(old_idx, new_idx);
616
617 if matches!(params.overlap_handling, OverlapHandling::Average) {
619 let mut avg_pos = vertex.position.coords;
620 let mut count = 1;
621
622 for (dup_idx, dup_of) in duplicate_of.iter().enumerate() {
623 if *dup_of == Some(old_idx) {
624 avg_pos += merged.vertices[dup_idx].position.coords;
625 count += 1;
626 }
627 }
628
629 let mut new_vertex = vertex.clone();
630 new_vertex.position = Point3::from(avg_pos / count as f64);
631 result.vertices.push(new_vertex);
632 } else {
633 result.vertices.push(vertex.clone());
634 }
635 }
636 }
637
638 for face in &merged.faces {
640 if let (Some(&i0), Some(&i1), Some(&i2)) = (
641 vertex_map.get(&(face[0] as usize)),
642 vertex_map.get(&(face[1] as usize)),
643 vertex_map.get(&(face[2] as usize)),
644 ) {
645 if i0 != i1 && i1 != i2 && i0 != i2 {
647 result.faces.push([i0 as u32, i1 as u32, i2 as u32]);
648 }
649 }
650 }
651
652 (result, duplicates_removed, overlap_regions)
653}
654
655fn fill_scan_gaps(mesh: &mut Mesh, max_gap_size: f64) -> usize {
657 let mut edge_face_count: HashMap<(u32, u32), usize> = HashMap::new();
659
660 for face in &mesh.faces {
661 for i in 0..3 {
662 let v0 = face[i];
663 let v1 = face[(i + 1) % 3];
664 let edge = if v0 < v1 { (v0, v1) } else { (v1, v0) };
665 *edge_face_count.entry(edge).or_insert(0) += 1;
666 }
667 }
668
669 let boundary_edges: Vec<(u32, u32)> = edge_face_count
670 .into_iter()
671 .filter(|(_, count)| *count == 1)
672 .map(|(edge, _)| edge)
673 .collect();
674
675 if boundary_edges.is_empty() {
676 return 0;
677 }
678
679 let max_gap_sq = max_gap_size * max_gap_size;
681 let mut new_faces = Vec::new();
682
683 for (i, &(e1_v0, e1_v1)) in boundary_edges.iter().enumerate() {
685 for &(e2_v0, e2_v1) in boundary_edges.iter().skip(i + 1) {
686 if e1_v0 == e2_v0 || e1_v0 == e2_v1 || e1_v1 == e2_v0 || e1_v1 == e2_v1 {
688 continue;
689 }
690
691 let p1_0 = mesh.vertices[e1_v0 as usize].position;
693 let p1_1 = mesh.vertices[e1_v1 as usize].position;
694 let p2_0 = mesh.vertices[e2_v0 as usize].position;
695 let p2_1 = mesh.vertices[e2_v1 as usize].position;
696
697 let d00 = (p1_0 - p2_0).norm_squared();
699 let d01 = (p1_0 - p2_1).norm_squared();
700 let d10 = (p1_1 - p2_0).norm_squared();
701 let d11 = (p1_1 - p2_1).norm_squared();
702
703 let min_dist = d00.min(d01).min(d10).min(d11);
704
705 if min_dist < max_gap_sq {
706 if (d00 + d11) < (d01 + d10) {
709 if d00 < max_gap_sq && d10 < max_gap_sq {
711 new_faces.push([e1_v0, e1_v1, e2_v0]);
712 }
713 if d11 < max_gap_sq && d01 < max_gap_sq {
714 new_faces.push([e1_v1, e2_v1, e2_v0]);
715 }
716 } else {
717 if d01 < max_gap_sq && d11 < max_gap_sq {
719 new_faces.push([e1_v0, e1_v1, e2_v1]);
720 }
721 if d10 < max_gap_sq && d00 < max_gap_sq {
722 new_faces.push([e1_v1, e2_v0, e2_v1]);
723 }
724 }
725 }
726 }
727 }
728
729 let count = new_faces.len();
730 mesh.faces.extend(new_faces);
731 count
732}
733
734impl Mesh {
739 pub fn align_with_scans(&self, other_scans: &[&Mesh]) -> MeshResult<MultiAlignmentResult> {
741 let mut all_scans: Vec<&Mesh> = vec![self];
742 all_scans.extend(other_scans);
743 align_multiple_scans(&all_scans)
744 }
745
746 pub fn merge_with_scans(&self, other_scans: &[Mesh]) -> MergeResult {
748 let mut all_scans = vec![self.clone()];
749 all_scans.extend(other_scans.iter().cloned());
750 merge_scans(&all_scans, &MergeParams::default())
751 }
752
753 pub fn merge_with_scans_params(
755 &self,
756 other_scans: &[Mesh],
757 params: &MergeParams,
758 ) -> MergeResult {
759 let mut all_scans = vec![self.clone()];
760 all_scans.extend(other_scans.iter().cloned());
761 merge_scans(&all_scans, params)
762 }
763}
764
765#[cfg(test)]
766mod tests {
767 use super::*;
768 use crate::Vertex;
769
770 fn create_test_triangle(offset_x: f64, offset_y: f64) -> Mesh {
771 let mut mesh = Mesh::new();
772 mesh.vertices
773 .push(Vertex::from_coords(0.0 + offset_x, 0.0 + offset_y, 0.0));
774 mesh.vertices
775 .push(Vertex::from_coords(10.0 + offset_x, 0.0 + offset_y, 0.0));
776 mesh.vertices
777 .push(Vertex::from_coords(5.0 + offset_x, 10.0 + offset_y, 0.0));
778 mesh.faces.push([0, 1, 2]);
779 mesh
780 }
781
782 #[test]
783 fn test_multi_alignment_params_default() {
784 let params = MultiAlignmentParams::default();
785 assert!(params.global_optimization);
786 assert!(params.min_overlap_ratio > 0.0);
787 }
788
789 #[test]
790 fn test_merge_params_default() {
791 let params = MergeParams::default();
792 assert!(params.remove_duplicates);
793 assert!(params.duplicate_threshold > 0.0);
794 }
795
796 #[test]
797 fn test_align_single_scan() {
798 let mesh = create_test_triangle(0.0, 0.0);
799 let result = align_multiple_scans(&[&mesh]).unwrap();
800
801 assert_eq!(result.aligned_scans.len(), 1);
802 assert_eq!(result.transforms.len(), 1);
803 assert_eq!(result.global_error, 0.0);
804 }
805
806 #[test]
807 fn test_align_two_scans() {
808 let scan1 = create_test_triangle(0.0, 0.0);
809 let scan2 = create_test_triangle(5.0, 0.0); let result = align_multiple_scans(&[&scan1, &scan2]).unwrap();
812
813 assert_eq!(result.aligned_scans.len(), 2);
814 assert_eq!(result.transforms.len(), 2);
815 }
816
817 #[test]
818 fn test_merge_single_scan() {
819 let mesh = create_test_triangle(0.0, 0.0);
820 let result = merge_scans(std::slice::from_ref(&mesh), &MergeParams::default());
821
822 assert_eq!(result.scans_merged, 1);
823 assert_eq!(result.mesh.vertices.len(), 3);
824 assert_eq!(result.mesh.faces.len(), 1);
825 }
826
827 #[test]
828 fn test_merge_two_scans_no_overlap() {
829 let scan1 = create_test_triangle(0.0, 0.0);
830 let scan2 = create_test_triangle(100.0, 0.0); let result = merge_scans(&[scan1, scan2], &MergeParams::default());
833
834 assert_eq!(result.scans_merged, 2);
835 assert_eq!(result.mesh.vertices.len(), 6);
836 assert_eq!(result.mesh.faces.len(), 2);
837 assert_eq!(result.duplicates_removed, 0);
838 }
839
840 #[test]
841 fn test_merge_overlapping_scans() {
842 let scan1 = create_test_triangle(0.0, 0.0);
843 let mut scan2 = Mesh::new();
844 scan2.vertices.push(Vertex::from_coords(10.0, 0.0, 0.0)); scan2.vertices.push(Vertex::from_coords(20.0, 0.0, 0.0));
847 scan2.vertices.push(Vertex::from_coords(15.0, 10.0, 0.0));
848 scan2.faces.push([0, 1, 2]);
849
850 let result = merge_scans(&[scan1, scan2], &MergeParams::default());
851
852 assert_eq!(result.scans_merged, 2);
853 assert!(result.duplicates_removed >= 1 || result.mesh.vertices.len() <= 5);
855 }
856
857 #[test]
858 fn test_overlap_handling_keep_both() {
859 let scan1 = create_test_triangle(0.0, 0.0);
860 let scan2 = create_test_triangle(0.1, 0.0); let params = MergeParams {
863 overlap_handling: OverlapHandling::KeepBoth,
864 duplicate_threshold: 1.0,
865 ..Default::default()
866 };
867
868 let result = merge_scans(&[scan1, scan2], ¶ms);
869 assert_eq!(result.mesh.vertices.len(), 6);
871 }
872
873 #[test]
874 fn test_estimate_overlap() {
875 let mesh1 = create_test_triangle(0.0, 0.0);
876 let mesh2 = create_test_triangle(0.0, 0.0); let overlap = estimate_overlap(&mesh1, &mesh2);
879 assert!(overlap > 0.9); let mesh3 = create_test_triangle(1000.0, 0.0); let overlap2 = estimate_overlap(&mesh1, &mesh3);
883 assert!(overlap2 < 0.1); }
885
886 #[test]
887 fn test_mesh_merge_method() {
888 let mesh1 = create_test_triangle(0.0, 0.0);
889 let mesh2 = create_test_triangle(100.0, 0.0);
890
891 let result = mesh1.merge_with_scans(&[mesh2]);
892
893 assert_eq!(result.scans_merged, 2);
894 assert!(!result.mesh.vertices.is_empty());
895 }
896}