1use std::collections::{HashMap, HashSet};
59
60use indicatif::{ProgressBar, ProgressStyle};
61
62use crate::{
63 block::Block,
64 block_face_functions::{
65 create_face_from_diagonals, get_outer_faces, split_face, Face,
66 },
67 face_record::{match_point_bounds, FaceKey, FaceMatch, FaceRecord, MatchPoint, Orientation},
68 verification::{determine_plane, extract_canonical_grid, try_all_permutations},
69 Float,
70};
71
72const DEFAULT_TOL: Float = 1e-6;
73
74#[derive(Clone, Debug)]
76struct FaceNode {
77 i: usize,
78 j: usize,
79 k: usize,
80 coord: [Float; 3],
81}
82
83fn face_nodes(face: &Face, block: &Block) -> Vec<FaceNode> {
93 let mut nodes = Vec::new();
94 let i_vals: Vec<usize> = if face.imin() == face.imax() {
95 vec![face.imin()]
96 } else {
97 (face.imin()..=face.imax()).collect()
98 };
99 let j_vals: Vec<usize> = if face.jmin() == face.jmax() {
100 vec![face.jmin()]
101 } else {
102 (face.jmin()..=face.jmax()).collect()
103 };
104 let k_vals: Vec<usize> = if face.kmin() == face.kmax() {
105 vec![face.kmin()]
106 } else {
107 (face.kmin()..=face.kmax()).collect()
108 };
109 for &i in &i_vals {
110 for &j in &j_vals {
111 for &k in &k_vals {
112 if !(i < block.imax && j < block.jmax && k < block.kmax) {
113 continue;
114 }
115 let (x, y, z) = block.xyz(i, j, k);
116 nodes.push(FaceNode {
117 i,
118 j,
119 k,
120 coord: [x, y, z],
121 });
122 }
123 }
124 }
125 nodes
126}
127
128fn find_closest_node(
133 nodes: &[FaceNode],
134 target: [Float; 3],
135 tol: Float,
136) -> Option<&FaceNode> {
137 let mut best: Option<(&FaceNode, Float)> = None;
138 for node in nodes {
139 let dx = node.coord[0] - target[0];
140 let dy = node.coord[1] - target[1];
141 let dz = node.coord[2] - target[2];
142 let dist = (dx * dx + dy * dy + dz * dz).sqrt();
143 if dist <= tol {
144 match best {
145 Some((_, best_dist)) if dist >= best_dist => {}
146 _ => best = Some((node, dist)),
147 }
148 }
149 }
150 best.map(|(node, _)| node)
151}
152
153fn is_edge(points: &[MatchPoint]) -> bool {
155 if points.is_empty() {
156 return false;
157 }
158 let (i_lo, i_hi, j_lo, j_hi, k_lo, k_hi) = match_point_bounds(points, true);
159 let const_count =
160 usize::from(i_lo == i_hi) + usize::from(j_lo == j_hi) + usize::from(k_lo == k_hi);
161 const_count >= 2
162}
163
164fn filter_block_increasing(
172 points: &[MatchPoint],
173 key: fn(&MatchPoint) -> usize,
174) -> Vec<MatchPoint> {
175 if points.is_empty() {
176 return Vec::new();
177 }
178 let mut unique_vals: Vec<usize> = points.iter().map(key).collect();
179 unique_vals.sort_unstable();
180 unique_vals.dedup();
181 if unique_vals.len() <= 1 {
182 return Vec::new();
183 }
184 if unique_vals.len() == 2 {
186 return points.to_vec();
187 }
188 let mut keep: HashSet<usize> = HashSet::new();
189 for window in unique_vals.windows(2) {
190 if window[1] == window[0] + 1 {
191 keep.insert(window[0]);
192 keep.insert(window[1]);
193 }
194 }
195 points
196 .iter()
197 .filter(|p| keep.contains(&key(p)))
198 .cloned()
199 .collect()
200}
201
202fn apply_axis_filters(points: Vec<MatchPoint>, face1: &Face, face2: &Face) -> Vec<MatchPoint> {
204 let mut filtered = points;
205 match face1.const_axis() {
206 Some(crate::block_face_functions::FaceAxis::I) => {
207 filtered = filter_block_increasing(&filtered, |p| p.j1);
208 filtered = filter_block_increasing(&filtered, |p| p.k1);
209 }
210 Some(crate::block_face_functions::FaceAxis::J) => {
211 filtered = filter_block_increasing(&filtered, |p| p.i1);
212 filtered = filter_block_increasing(&filtered, |p| p.k1);
213 }
214 Some(crate::block_face_functions::FaceAxis::K) => {
215 filtered = filter_block_increasing(&filtered, |p| p.i1);
216 filtered = filter_block_increasing(&filtered, |p| p.j1);
217 }
218 None => {}
219 }
220 match face2.const_axis() {
221 Some(crate::block_face_functions::FaceAxis::I) => {
222 filtered = filter_block_increasing(&filtered, |p| p.j2);
223 filtered = filter_block_increasing(&filtered, |p| p.k2);
224 }
225 Some(crate::block_face_functions::FaceAxis::J) => {
226 filtered = filter_block_increasing(&filtered, |p| p.i2);
227 filtered = filter_block_increasing(&filtered, |p| p.k2);
228 }
229 Some(crate::block_face_functions::FaceAxis::K) => {
230 filtered = filter_block_increasing(&filtered, |p| p.i2);
231 filtered = filter_block_increasing(&filtered, |p| p.j2);
232 }
233 None => {}
234 }
235 filtered
236}
237
238fn create_split_faces(
240 face: &Face,
241 block: &Block,
242 points: &[MatchPoint],
243 use_block1: bool,
244) -> Vec<Face> {
245 if points.is_empty() {
246 return Vec::new();
247 }
248 let (i_lo, i_hi, j_lo, j_hi, k_lo, k_hi) = match_point_bounds(points, use_block1);
249 let degeneracy =
250 usize::from(i_lo == i_hi) + usize::from(j_lo == j_hi) + usize::from(k_lo == k_hi);
251 if degeneracy != 1 {
252 return Vec::new();
253 }
254 let mut split = split_face(face, block, i_lo, j_lo, k_lo, i_hi, j_hi, k_hi);
255 for f in &mut split {
256 if let Some(idx) = face.block_index() {
257 f.set_block_index(idx);
258 }
259 if let Some(id) = face.id() {
260 f.set_id(id);
261 }
262 }
263 split
264}
265
266pub fn get_face_intersection(
280 face1: &Face,
281 face2: &Face,
282 block1: &Block,
283 block2: &Block,
284 tol: Float,
285) -> (Vec<MatchPoint>, Vec<Face>, Vec<Face>) {
286 let nodes1 = face_nodes(face1, block1);
287 let nodes2 = face_nodes(face2, block2);
288 let mut matches = Vec::new();
289 for node1 in &nodes1 {
290 if let Some(node2) = find_closest_node(&nodes2, node1.coord, tol) {
291 matches.push(MatchPoint {
292 i1: node1.i,
293 j1: node1.j,
294 k1: node1.k,
295 i2: node2.i,
296 j2: node2.j,
297 k2: node2.k,
298 });
299 }
300 }
301 if matches.len() < 4 || is_edge(&matches) {
302 return (Vec::new(), Vec::new(), Vec::new());
303 }
304 let matches = apply_axis_filters(matches, face1, face2);
305 if matches.len() < 4 {
306 return (Vec::new(), Vec::new(), Vec::new());
307 }
308
309 let split_faces1 = create_split_faces(face1, block1, &matches, true);
310 let split_faces2 = create_split_faces(face2, block2, &matches, false);
311 (matches, split_faces1, split_faces2)
312}
313
314use crate::block_face_functions::FaceAxis;
319
320fn face_uv_ranges(
322 face: &Face,
323 axis: FaceAxis,
324) -> (
325 std::ops::RangeInclusive<usize>,
326 std::ops::RangeInclusive<usize>,
327) {
328 match axis {
329 FaceAxis::I => (face.jmin()..=face.jmax(), face.kmin()..=face.kmax()),
330 FaceAxis::J => (face.imin()..=face.imax(), face.kmin()..=face.kmax()),
331 FaceAxis::K => (face.imin()..=face.imax(), face.jmin()..=face.jmax()),
332 }
333}
334
335fn uv_to_ijk(u: usize, v: usize, axis: FaceAxis, face: &Face) -> (usize, usize, usize) {
337 match axis {
338 FaceAxis::I => (face.imin(), u, v), FaceAxis::J => (u, face.jmin(), v), FaceAxis::K => (u, v, face.kmin()), }
342}
343
344fn build_match_points_from_orientation(
349 face1: &Face,
350 face2: &Face,
351 orientation: &Orientation,
352) -> Vec<MatchPoint> {
353 let Some(axis1) = face1.const_axis() else {
354 return Vec::new();
355 };
356 let Some(axis2) = face2.const_axis() else {
357 return Vec::new();
358 };
359
360 let (u1_range, v1_range) = face_uv_ranges(face1, axis1);
361 let (u2_range, v2_range) = face_uv_ranges(face2, axis2);
362
363 let u1_vals: Vec<usize> = u1_range.collect();
364 let v1_vals: Vec<usize> = v1_range.collect();
365 let u2_vals: Vec<usize> = u2_range.collect();
366 let v2_vals: Vec<usize> = v2_range.collect();
367
368 let mut points = Vec::with_capacity(u1_vals.len() * v1_vals.len());
369
370 for (u_off, &u1) in u1_vals.iter().enumerate() {
371 for (v_off, &v1) in v1_vals.iter().enumerate() {
372 let (u2_off, v2_off) = if orientation.swapped() {
374 (v_off, u_off)
375 } else {
376 (u_off, v_off)
377 };
378
379 let u2_idx = if orientation.u_reversed() {
380 u2_vals.len().saturating_sub(1).saturating_sub(u2_off)
381 } else {
382 u2_off
383 };
384 let v2_idx = if orientation.v_reversed() {
385 v2_vals.len().saturating_sub(1).saturating_sub(v2_off)
386 } else {
387 v2_off
388 };
389
390 if u2_idx >= u2_vals.len() || v2_idx >= v2_vals.len() {
391 continue;
392 }
393
394 let (i1, j1, k1) = uv_to_ijk(u1, v1, axis1, face1);
395 let (i2, j2, k2) = uv_to_ijk(u2_vals[u2_idx], v2_vals[v2_idx], axis2, face2);
396
397 points.push(MatchPoint {
398 i1,
399 j1,
400 k1,
401 i2,
402 j2,
403 k2,
404 });
405 }
406 }
407 points
408}
409
410fn find_full_face_matches(
423 blocks: &[Block],
424 block_outer_faces: &[Vec<Face>],
425 candidate_pairs: &[(usize, usize)],
426 tol: Float,
427) -> (Vec<FaceMatch>, HashSet<FaceKey>) {
428 use crate::block_face_functions::full_face_match;
429 use crate::verification::{
430 determine_plane, extract_canonical_grid, try_all_permutations,
431 };
432
433 let mut face_matches = Vec::new();
434 let mut consumed: HashSet<FaceKey> = HashSet::new();
435
436 for &(i, j) in candidate_pairs {
437 for face_i in &block_outer_faces[i] {
438 if consumed.contains(&face_i.index_key()) {
439 continue;
440 }
441 for face_j in &block_outer_faces[j] {
442 if consumed.contains(&face_j.index_key()) {
443 continue;
444 }
445 if full_face_match(face_i, face_j, tol).is_none() {
447 continue;
448 }
449
450 let rec_a = FaceRecord::from_face(face_i);
452 let rec_b = FaceRecord::from_face(face_j);
453
454 let (pts_a, nu_a, nv_a) = match extract_canonical_grid(&blocks[i], &rec_a) {
456 Some(g) => g,
457 None => continue,
458 };
459 let (pts_b, nu_b, nv_b) = match extract_canonical_grid(&blocks[j], &rec_b) {
460 Some(g) => g,
461 None => continue,
462 };
463
464 if let Some(perm_idx) =
466 try_all_permutations(&pts_a, nu_a, nv_a, &pts_b, nu_b, nv_b, tol)
467 {
468 let plane = determine_plane(&rec_a, &rec_b);
469 let orientation = Orientation {
470 permutation_index: perm_idx,
471 plane,
472 };
473
474 let points =
476 build_match_points_from_orientation(face_i, face_j, &orientation);
477
478 consumed.insert(face_i.index_key());
479 consumed.insert(face_j.index_key());
480
481 face_matches.push(FaceMatch {
482 block1: rec_a,
483 block2: rec_b,
484 points,
485 orientation: Some(orientation),
486 });
487 break; }
489 }
490 }
491 }
492
493 (face_matches, consumed)
494}
495
496pub fn find_matching_blocks(
511 block1: &Block,
512 block2: &Block,
513 block1_outer: &mut Vec<Face>,
514 block2_outer: &mut Vec<Face>,
515 tol: Float,
516) -> Vec<Vec<MatchPoint>> {
517 let mut matches = Vec::new();
518 let mut i = 0;
519 'outer: while i < block1_outer.len() {
520 let mut j = 0;
521 while j < block2_outer.len() {
522 let face1 = block1_outer[i].clone();
523 let face2 = block2_outer[j].clone();
524 let (match_points, split1, split2) =
525 get_face_intersection(&face1, &face2, block1, block2, tol);
526 if !match_points.is_empty() {
527 matches.push(match_points.clone());
528
529 block1_outer.remove(i);
530 block2_outer.remove(j);
531 block1_outer.extend(split1);
532 block2_outer.extend(split2);
533 i = 0;
534 continue 'outer;
535 } else {
536 j += 1;
537 }
538 }
539 i += 1;
540 }
541 matches
542}
543
544fn candidate_neighbor_pairs(blocks: &[Block], tol: Float) -> Vec<(usize, usize)> {
559 use rayon::prelude::*;
560
561 let n = blocks.len();
562 let aabbs: Vec<[Float; 6]> = blocks
564 .par_iter()
565 .map(|b| {
566 let mut xmin = Float::INFINITY;
567 let mut xmax = Float::NEG_INFINITY;
568 let mut ymin = Float::INFINITY;
569 let mut ymax = Float::NEG_INFINITY;
570 let mut zmin = Float::INFINITY;
571 let mut zmax = Float::NEG_INFINITY;
572 for &x in &b.x {
573 xmin = xmin.min(x);
574 xmax = xmax.max(x);
575 }
576 for &y in &b.y {
577 ymin = ymin.min(y);
578 ymax = ymax.max(y);
579 }
580 for &z in &b.z {
581 zmin = zmin.min(z);
582 zmax = zmax.max(z);
583 }
584 [xmin, xmax, ymin, ymax, zmin, zmax]
585 })
586 .collect();
587
588 let pairs: Vec<(usize, usize)> = (0..n)
589 .into_par_iter()
590 .flat_map(|i| {
591 let aabbs = &aabbs;
592 ((i + 1)..n)
593 .filter_map(move |j| {
594 let a = &aabbs[i];
595 let b = &aabbs[j];
596 if a[1] + tol >= b[0]
597 && b[1] + tol >= a[0]
598 && a[3] + tol >= b[2]
599 && b[3] + tol >= a[2]
600 && a[5] + tol >= b[4]
601 && b[5] + tol >= a[4]
602 {
603 Some((i, j))
604 } else {
605 None
606 }
607 })
608 .collect::<Vec<_>>()
609 })
610 .collect();
611 pairs
612}
613
614pub fn connectivity_fast(blocks: &[Block]) -> (Vec<FaceMatch>, Vec<FaceRecord>) {
630 let gcd_to_use = crate::utils::compute_min_gcd(blocks);
631 let reduced_blocks = crate::block_face_functions::reduce_blocks(blocks, gcd_to_use);
632 let (mut matches, mut outer_faces) = connectivity(&reduced_blocks);
633 for face in &mut matches {
635 face.block1.scale_indices(gcd_to_use);
636 face.block2.scale_indices(gcd_to_use);
637 }
638 for face in &mut outer_faces {
639 face.scale_indices(gcd_to_use);
640 }
641 (matches, outer_faces)
642}
643
644pub fn connectivity(blocks: &[Block]) -> (Vec<FaceMatch>, Vec<FaceRecord>) {
653 use rayon::prelude::*;
654
655 let mut block_outer_faces: Vec<Vec<Face>> = blocks
662 .par_iter()
663 .enumerate()
664 .map(|(idx, block)| {
665 let (faces, _degenerate_pairs) = get_outer_faces(block);
666 faces
667 .into_iter()
668 .map(|mut f| {
669 f.set_block_index(idx);
670 f
671 })
672 .collect()
673 })
674 .collect();
675
676 let combos = candidate_neighbor_pairs(blocks, DEFAULT_TOL);
677
678 let (mut matches, consumed_keys) =
680 find_full_face_matches(blocks, &block_outer_faces, &combos, DEFAULT_TOL);
681
682 for faces in &mut block_outer_faces {
684 faces.retain(|f| !consumed_keys.contains(&f.index_key()));
685 }
686
687 let mut matches_to_remove: HashSet<FaceKey> = consumed_keys;
688
689 let mut phase2_round = 0;
694 let mut phase2_changed = true;
695 while phase2_changed {
696 phase2_changed = false;
697 phase2_round += 1;
698
699 let pb = ProgressBar::new(combos.len() as u64);
700 pb.set_style(
701 ProgressStyle::with_template(
702 "{msg} [{bar:40.cyan/blue}] {pos}/{len} pairs ({eta} remaining)",
703 )
704 .unwrap()
705 .progress_chars("=>-"),
706 );
707 pb.set_message(format!(
708 "Connectivity (partial matching, round {})",
709 phase2_round
710 ));
711
712 for &(i, j) in &combos {
713 pb.inc(1);
714 let (left, right) = block_outer_faces.split_at_mut(j);
716 let (left, right) = (&mut left[i], &mut right[0]);
717
718 if left.is_empty() || right.is_empty() {
720 continue;
721 }
722
723 let mut match_points =
724 find_matching_blocks(&blocks[i], &blocks[j], left, right, DEFAULT_TOL);
725 for points in match_points.drain(..) {
726 phase2_changed = true;
727 let (i1lo, i1hi, j1lo, j1hi, k1lo, k1hi) = match_point_bounds(&points, true);
728 let mut face1 = create_face_from_diagonals(
729 &blocks[i], i1lo, j1lo, k1lo, i1hi, j1hi, k1hi,
730 );
731 face1.set_block_index(i);
732 let (i2lo, i2hi, j2lo, j2hi, k2lo, k2hi) = match_point_bounds(&points, false);
733 let mut face2 = create_face_from_diagonals(
734 &blocks[j], i2lo, j2lo, k2lo, i2hi, j2hi, k2hi,
735 );
736 face2.set_block_index(j);
737 matches_to_remove.insert(face1.index_key());
738 matches_to_remove.insert(face2.index_key());
739
740 let corner1 = FaceRecord::from_match_points(i, &points, true).unwrap();
741 let corner2 = FaceRecord::from_match_points(j, &points, false).unwrap();
742 matches.push(FaceMatch {
743 block1: corner1,
744 block2: corner2,
745 points,
746 orientation: None,
747 });
748 }
749 }
750 pb.finish_with_message(format!(
751 "Connectivity round {} done (changed={})",
752 phase2_round, phase2_changed
753 ));
754 }
755
756 let mut outer_faces = Vec::new();
757 for faces in &block_outer_faces {
758 for face in faces {
759 outer_faces.push(face.clone());
760 }
761 }
762 drop(block_outer_faces);
764
765 let mut seen = HashSet::new();
766 outer_faces.retain(|face| seen.insert(face.index_key()));
767
768 outer_faces.retain(|face| !matches_to_remove.contains(&face.index_key()));
769 drop(matches_to_remove);
770
771 let mut outer_faces_to_remove = HashSet::new();
772 let mut by_block: HashMap<usize, Vec<&Face>> = HashMap::new();
773 for face in &outer_faces {
774 if let Some(idx) = face.block_index() {
775 by_block.entry(idx).or_default().push(face);
776 }
777 }
778 for faces in by_block.values() {
779 for (a_idx, face_a) in faces.iter().enumerate() {
780 let dims_a = [
781 face_a.imin(),
782 face_a.jmin(),
783 face_a.kmin(),
784 face_a.imax(),
785 face_a.jmax(),
786 face_a.kmax(),
787 ];
788 for (b_idx, face_b) in faces.iter().enumerate() {
789 if a_idx == b_idx {
790 continue;
791 }
792 let dims_b = [
793 face_b.imin(),
794 face_b.jmin(),
795 face_b.kmin(),
796 face_b.imax(),
797 face_b.jmax(),
798 face_b.kmax(),
799 ];
800 let equal_components = dims_a
801 .iter()
802 .zip(dims_b.iter())
803 .filter(|(a, b)| a == b)
804 .count();
805 if equal_components == 5 {
806 let remove_key = if face_b.diagonal_length() > face_a.diagonal_length() {
807 face_b.index_key()
808 } else {
809 face_a.index_key()
810 };
811 outer_faces_to_remove.insert(remove_key);
812 }
813 }
814 }
815 }
816
817 outer_faces.retain(|face| !outer_faces_to_remove.contains(&face.index_key()));
818
819 let mut self_match_keys: HashSet<FaceKey> = HashSet::new();
820 for (idx, block) in blocks.iter().enumerate() {
821 let (_, self_matches) = get_outer_faces(block);
822 for (face_a, face_b) in self_matches {
823 let mut corner1 = FaceRecord {
824 block_index: idx,
825 il: face_a.imin(),
826 jl: face_a.jmin(),
827 kl: face_a.kmin(),
828 ih: face_a.imax(),
829 jh: face_a.jmax(),
830 kh: face_a.kmax(),
831 id: face_a.id(),
832 u_physical: None,
833 v_physical: None,
834 };
835 let corner2 = FaceRecord {
836 block_index: idx,
837 il: face_b.imin(),
838 jl: face_b.jmin(),
839 kl: face_b.kmin(),
840 ih: face_b.imax(),
841 jh: face_b.jmax(),
842 kh: face_b.kmax(),
843 id: face_b.id(),
844 u_physical: None,
845 v_physical: None,
846 };
847 let mut fa = face_a.clone();
849 fa.set_block_index(idx);
850 let mut fb = face_b.clone();
851 fb.set_block_index(idx);
852 self_match_keys.insert(fa.index_key());
853 self_match_keys.insert(fb.index_key());
854
855 corner1.id = face_a.id();
856 matches.push(FaceMatch {
857 block1: corner1,
858 block2: corner2,
859 points: Vec::new(),
860 orientation: None,
861 });
862 }
863 }
864
865 outer_faces.retain(|face| !self_match_keys.contains(&face.index_key()));
867
868 {
873 let mut neighbors: Vec<Vec<usize>> = vec![Vec::new(); blocks.len()];
874 for &(i, j) in &combos {
875 neighbors[i].push(j);
876 neighbors[j].push(i);
877 }
878
879 let fresh_all: Vec<Vec<Face>> = blocks
884 .iter()
885 .map(|block| {
886 let (faces, _) = get_outer_faces(block);
887 faces
888 })
889 .collect();
890
891 let fresh_aabbs: Vec<Vec<[Float; 6]>> = blocks
893 .iter()
894 .zip(fresh_all.iter())
895 .map(|(block, faces)| {
896 faces
897 .iter()
898 .map(|f| {
899 let nodes = face_nodes(f, block);
900 let mut aabb = [
901 Float::INFINITY,
902 Float::NEG_INFINITY,
903 Float::INFINITY,
904 Float::NEG_INFINITY,
905 Float::INFINITY,
906 Float::NEG_INFINITY,
907 ];
908 for n in &nodes {
909 aabb[0] = aabb[0].min(n.coord[0]);
910 aabb[1] = aabb[1].max(n.coord[0]);
911 aabb[2] = aabb[2].min(n.coord[1]);
912 aabb[3] = aabb[3].max(n.coord[1]);
913 aabb[4] = aabb[4].min(n.coord[2]);
914 aabb[5] = aabb[5].max(n.coord[2]);
915 }
916 aabb
917 })
918 .collect()
919 })
920 .collect();
921
922 let pb = ProgressBar::new(outer_faces.len() as u64);
923 pb.set_style(
924 ProgressStyle::with_template(
925 "{msg} [{bar:40.cyan/blue}] {pos}/{len} ({eta} remaining)",
926 )
927 .unwrap()
928 .progress_chars("=>-"),
929 );
930 pb.set_message("Connectivity Phase 3 (fresh-face validation)");
931
932 let mut phase3_keys: HashSet<FaceKey> = HashSet::new();
933
934 for face in outer_faces.iter() {
935 pb.inc(1);
936 if phase3_keys.contains(&face.index_key()) {
937 continue;
938 }
939 let bi = match face.block_index() {
940 Some(v) => v,
941 None => continue,
942 };
943
944 let face_nodes_list = face_nodes(face, &blocks[bi]);
946 let mut fxn = Float::INFINITY;
947 let mut fxx = Float::NEG_INFINITY;
948 let mut fyn = Float::INFINITY;
949 let mut fyx = Float::NEG_INFINITY;
950 let mut fzn = Float::INFINITY;
951 let mut fzx = Float::NEG_INFINITY;
952 for n in &face_nodes_list {
953 fxn = fxn.min(n.coord[0]);
954 fxx = fxx.max(n.coord[0]);
955 fyn = fyn.min(n.coord[1]);
956 fyx = fyx.max(n.coord[1]);
957 fzn = fzn.min(n.coord[2]);
958 fzx = fzx.max(n.coord[2]);
959 }
960
961 for &bj in &neighbors[bi] {
962 for (fi, ff) in fresh_all[bj].iter().enumerate() {
963 let gaabb = &fresh_aabbs[bj][fi];
965 let tol_pre = 0.01;
966 if fxx + tol_pre < gaabb[0]
967 || gaabb[1] + tol_pre < fxn
968 || fyx + tol_pre < gaabb[2]
969 || gaabb[3] + tol_pre < fyn
970 || fzx + tol_pre < gaabb[4]
971 || gaabb[5] + tol_pre < fzn
972 {
973 continue;
974 }
975
976 let (pts, _, _) =
977 get_face_intersection(face, ff, &blocks[bi], &blocks[bj], DEFAULT_TOL);
978 if pts.is_empty() {
979 continue;
980 }
981 if let (Some(c1), Some(c2)) = (
982 FaceRecord::from_match_points(bi, &pts, true),
983 FaceRecord::from_match_points(bj, &pts, false),
984 ) {
985 matches.push(FaceMatch {
986 block1: c1,
987 block2: c2,
988 points: pts,
989 orientation: None,
990 });
991 }
992 phase3_keys.insert(face.index_key());
993 }
997 }
998 }
999
1000 let n3 = phase3_keys.len();
1001 pb.finish_with_message(format!("Phase 3 done ({n3} new matches)"));
1002
1003 outer_faces.retain(|f| !phase3_keys.contains(&f.index_key()));
1004 }
1005
1006 let mut formatted = Vec::new();
1007 let mut id_counter = 1;
1008 for face in outer_faces {
1009 formatted.push(FaceRecord {
1010 block_index: face.block_index().unwrap_or(usize::MAX),
1011 il: face.imin(),
1012 jl: face.jmin(),
1013 kl: face.kmin(),
1014 ih: face.imax(),
1015 jh: face.jmax(),
1016 kh: face.kmax(),
1017 id: Some(id_counter),
1018 u_physical: None,
1019 v_physical: None,
1020 });
1021 id_counter += 1;
1022 }
1023
1024 (matches, formatted)
1025}
1026
1027pub fn align_face_orientations(
1042 blocks: &[Block],
1043 face_matches: &[FaceMatch],
1044 tol: Float,
1045) -> (Vec<FaceMatch>, Vec<FaceMatch>) {
1046 let mut aligned = Vec::new();
1047 let mut rejected = Vec::new();
1048
1049 let pb = ProgressBar::new(face_matches.len() as u64);
1050 pb.set_style(
1051 ProgressStyle::with_template(
1052 "{msg} [{bar:40.cyan/blue}] {pos}/{len} matches ({eta} remaining)",
1053 )
1054 .unwrap()
1055 .progress_chars("=>-"),
1056 );
1057 pb.set_message("Align orientations");
1058
1059 for fm in face_matches {
1060 pb.inc(1);
1061 let b1 = &fm.block1;
1062 let b2 = &fm.block2;
1063
1064 if b1.block_index >= blocks.len() || b2.block_index >= blocks.len() {
1065 rejected.push(fm.clone());
1066 continue;
1067 }
1068
1069 let block1 = &blocks[b1.block_index];
1070 let block2 = &blocks[b2.block_index];
1071
1072 let grid_a = match extract_canonical_grid(block1, b1) {
1074 Some(g) => g,
1075 None => { rejected.push(fm.clone()); continue; }
1076 };
1077 let grid_b = match extract_canonical_grid(block2, b2) {
1078 Some(g) => g,
1079 None => { rejected.push(fm.clone()); continue; }
1080 };
1081
1082 let (pts_a, nu_a, nv_a) = grid_a;
1083 let (pts_b, nu_b, nv_b) = grid_b;
1084
1085 if let Some(perm_idx) = try_all_permutations(&pts_a, nu_a, nv_a, &pts_b, nu_b, nv_b, tol) {
1087 let mut fm_out = fm.clone();
1088 let plane = determine_plane(b1, b2);
1089 fm_out.orientation = Some(Orientation {
1090 permutation_index: perm_idx,
1091 plane,
1092 });
1093 aligned.push(fm_out);
1094 } else {
1095 eprintln!(
1096 " align: REJECTED block {}↔{} — no permutation matches",
1097 b1.block_index, b2.block_index
1098 );
1099 rejected.push(fm.clone());
1100 }
1101 }
1102
1103 pb.finish_with_message("Align orientations done");
1104 (aligned, rejected)
1105}
1106
1107fn derive_diagonal_from_match_points(
1112 fm: &FaceMatch,
1113 gcd: usize,
1114) -> Option<(FaceRecord, FaceRecord)> {
1115 let points = &fm.points;
1116 if points.is_empty() {
1117 return None;
1118 }
1119
1120 let first = &points[0];
1121 let last = &points[points.len() - 1];
1122
1123 let b1 = FaceRecord {
1124 block_index: fm.block1.block_index,
1125 il: first.i1 * gcd,
1126 jl: first.j1 * gcd,
1127 kl: first.k1 * gcd,
1128 ih: last.i1 * gcd,
1129 jh: last.j1 * gcd,
1130 kh: last.k1 * gcd,
1131 id: fm.block1.id,
1132 u_physical: None,
1133 v_physical: None,
1134 };
1135 let b2 = FaceRecord {
1136 block_index: fm.block2.block_index,
1137 il: first.i2 * gcd,
1138 jl: first.j2 * gcd,
1139 kl: first.k2 * gcd,
1140 ih: last.i2 * gcd,
1141 jh: last.j2 * gcd,
1142 kh: last.k2 * gcd,
1143 id: fm.block2.id,
1144 u_physical: None,
1145 v_physical: None,
1146 };
1147
1148 Some((b1, b2))
1149}
1150
1151pub fn face_matches_to_dict(blocks: &[Block], face_matches: &[FaceMatch]) -> Vec<FaceMatch> {
1164 let gcd = crate::utils::compute_min_gcd(blocks);
1167
1168 let mut matched_count = 0usize;
1169 let mut empty_count = 0usize;
1170 let mut spatial_count = 0usize;
1171
1172 let result: Vec<FaceMatch> = face_matches
1173 .iter()
1174 .filter_map(|fm| {
1175 let b1 = &fm.block1;
1176 let b2 = &fm.block2;
1177
1178 let block1 = blocks.get(b1.block_index)?;
1179 let block2 = blocks.get(b2.block_index)?;
1180
1181 let mut result = fm.clone();
1182
1183 if !fm.points.is_empty() {
1184 if let Some((b1_new, b2_new)) =
1186 derive_diagonal_from_match_points(fm, gcd)
1187 {
1188 result.block1 = b1_new;
1189 result.block2 = b2_new;
1190 matched_count += 1;
1191 } else {
1192 empty_count += 1;
1193 }
1194 } else {
1195 let (x1_l, y1_l, z1_l) = block1.xyz(b1.il, b1.jl, b1.kl);
1198
1199 let i_vals = [b2.i_lo(), b2.i_hi()];
1200 let j_vals = [b2.j_lo(), b2.j_hi()];
1201 let k_vals = [b2.k_lo(), b2.k_hi()];
1202
1203 let mut best_lower = (Float::MAX, b2.il, b2.jl, b2.kl);
1204 for &i in &i_vals {
1205 for &j in &j_vals {
1206 for &k in &k_vals {
1207 let (x2, y2, z2) = block2.xyz(i, j, k);
1208 let d = ((x2 - x1_l).powi(2)
1209 + (y2 - y1_l).powi(2)
1210 + (z2 - z1_l).powi(2))
1211 .sqrt();
1212 if d < best_lower.0 {
1213 best_lower = (d, i, j, k);
1214 }
1215 }
1216 }
1217 }
1218 result.block2.il = best_lower.1;
1219 result.block2.jl = best_lower.2;
1220 result.block2.kl = best_lower.3;
1221
1222 let (x1_u, y1_u, z1_u) = block1.xyz(b1.ih, b1.jh, b1.kh);
1223
1224 let mut best_upper = (Float::MAX, b2.ih, b2.jh, b2.kh);
1225 for &i in &i_vals {
1226 for &j in &j_vals {
1227 for &k in &k_vals {
1228 let (x2, y2, z2) = block2.xyz(i, j, k);
1229 let d = ((x2 - x1_u).powi(2)
1230 + (y2 - y1_u).powi(2)
1231 + (z2 - z1_u).powi(2))
1232 .sqrt();
1233 if d < best_upper.0 {
1234 best_upper = (d, i, j, k);
1235 }
1236 }
1237 }
1238 }
1239 result.block2.ih = best_upper.1;
1240 result.block2.jh = best_upper.2;
1241 result.block2.kh = best_upper.3;
1242
1243 spatial_count += 1;
1244 }
1245
1246 Some(result)
1247 })
1248 .collect();
1249
1250 eprintln!(
1251 " face_matches_to_dict: {} matched, {} empty, {} spatial",
1252 matched_count, empty_count, spatial_count
1253 );
1254 result
1255}