1use std::collections::HashSet;
2
3use crate::{
4 block::Block,
5 face_record::{FaceKey, FaceMatch, FaceRecord},
6 geometry::{
7 clip_sutherland_hodgman, distance, dominant_projection_axis, poly_area_2d,
8 project_drop_axis, quad_normal_from_verts, quantize_point, to_array, vertex_aabb,
9 },
10 utils::{cross3, dot3, sub3, vec_norm3},
11 Float,
12};
13
14const DEFAULT_TOL: Float = 1e-8;
15
16const VERTEX_MATCH_TOL: Float = 1e-6;
18
19#[derive(Copy, Clone, Debug, Eq, PartialEq)]
21pub enum FaceAxis {
22 I,
24 J,
26 K,
28}
29
30#[derive(Clone, Debug)]
32pub struct Face {
33 vertices: Vec<[Float; 3]>,
34 indices: Vec<[usize; 3]>,
35 centroid: [Float; 3],
36 block_index: Option<usize>,
37 id: Option<usize>,
38}
39
40fn corner_index(face: &Face, i: usize, j: usize, k: usize) -> usize {
44 face.indices
45 .iter()
46 .enumerate()
47 .find_map(|(idx, ijk)| {
48 if ijk[0] == i && ijk[1] == j && ijk[2] == k {
49 Some(idx)
50 } else {
51 None
52 }
53 })
54 .unwrap_or_else(|| {
55 panic!(
56 "corner_index: vertex ({}, {}, {}) not found in face with {} vertices",
57 i,
58 j,
59 k,
60 face.indices.len()
61 )
62 })
63}
64
65impl Default for Face {
66 fn default() -> Self {
67 Self::new()
68 }
69}
70
71impl Face {
72 pub fn new() -> Self {
74 Self {
75 vertices: Vec::with_capacity(4),
76 indices: Vec::with_capacity(4),
77 centroid: [0.0; 3],
78 block_index: None,
79 id: None,
80 }
81 }
82
83 pub fn add_vertex(&mut self, x: Float, y: Float, z: Float, i: usize, j: usize, k: usize) {
88 self.vertices.push([x, y, z]);
89 self.indices.push([i, j, k]);
90 let n = self.vertices.len() as Float;
91 let mut cx = 0.0;
92 let mut cy = 0.0;
93 let mut cz = 0.0;
94 for v in &self.vertices {
95 cx += v[0];
96 cy += v[1];
97 cz += v[2];
98 }
99 self.centroid = [cx / n, cy / n, cz / n];
100 }
101
102 pub fn set_block_index(&mut self, idx: usize) {
104 self.block_index = Some(idx);
105 }
106
107 pub fn set_id(&mut self, id: usize) {
109 self.id = Some(id);
110 }
111
112 pub fn id(&self) -> Option<usize> {
114 self.id
115 }
116
117 pub fn centroid(&self) -> [Float; 3] {
119 self.centroid
120 }
121
122 pub fn block_index(&self) -> Option<usize> {
124 self.block_index
125 }
126
127 pub fn indices(&self) -> &[[usize; 3]] {
129 &self.indices
130 }
131
132 pub fn i_values(&self) -> impl Iterator<Item = usize> + '_ {
134 self.indices.iter().map(|ijk| ijk[0])
135 }
136
137 pub fn j_values(&self) -> impl Iterator<Item = usize> + '_ {
139 self.indices.iter().map(|ijk| ijk[1])
140 }
141
142 pub fn k_values(&self) -> impl Iterator<Item = usize> + '_ {
144 self.indices.iter().map(|ijk| ijk[2])
145 }
146
147 fn min_max(dim: usize, indices: &[[usize; 3]]) -> (usize, usize) {
148 let mut min_v = usize::MAX;
149 let mut max_v = 0usize;
150 for idx in indices {
151 min_v = min_v.min(idx[dim]);
152 max_v = max_v.max(idx[dim]);
153 }
154 (min_v, max_v)
155 }
156
157 pub fn imin(&self) -> usize {
159 Self::min_max(0, &self.indices).0
160 }
161 pub fn imax(&self) -> usize {
163 Self::min_max(0, &self.indices).1
164 }
165 pub fn jmin(&self) -> usize {
167 Self::min_max(1, &self.indices).0
168 }
169 pub fn jmax(&self) -> usize {
171 Self::min_max(1, &self.indices).1
172 }
173 pub fn kmin(&self) -> usize {
175 Self::min_max(2, &self.indices).0
176 }
177 pub fn kmax(&self) -> usize {
179 Self::min_max(2, &self.indices).1
180 }
181
182 pub fn const_axis(&self) -> Option<FaceAxis> {
184 let i_same = self.imin() == self.imax();
185 let j_same = self.jmin() == self.jmax();
186 let k_same = self.kmin() == self.kmax();
187 match (i_same, j_same, k_same) {
188 (true, false, false) => Some(FaceAxis::I),
189 (false, true, false) => Some(FaceAxis::J),
190 (false, false, true) => Some(FaceAxis::K),
191 _ => None,
192 }
193 }
194
195 pub fn const_type(&self) -> i8 {
198 match self.const_axis() {
199 Some(FaceAxis::I) => 0,
200 Some(FaceAxis::J) => 1,
201 Some(FaceAxis::K) => 2,
202 None => -1,
203 }
204 }
205
206 pub fn is_edge(&self) -> bool {
208 let eq = [
209 self.imin() == self.imax(),
210 self.jmin() == self.jmax(),
211 self.kmin() == self.kmax(),
212 ];
213 eq.iter().filter(|&&b| b).count() > 1
214 }
215
216 pub fn index_equals(&self, other: &Face) -> bool {
218 self.imin() == other.imin()
219 && self.imax() == other.imax()
220 && self.jmin() == other.jmin()
221 && self.jmax() == other.jmax()
222 && self.kmin() == other.kmin()
223 && self.kmax() == other.kmax()
224 }
225
226 pub fn vertices(&self) -> &[[Float; 3]] {
228 &self.vertices
229 }
230
231 pub fn get_corners(&self) -> Option<([Float; 3], [Float; 3])> {
238 if self.vertices.is_empty() {
239 return None;
240 }
241 let min_idx = corner_index(self, self.imin(), self.jmin(), self.kmin());
242 let max_idx = corner_index(self, self.imax(), self.jmax(), self.kmax());
243 Some((self.vertices[min_idx], self.vertices[max_idx]))
244 }
245
246 pub fn get_all_corners(&self) -> Option<[[Float; 3]; 4]> {
255 if self.vertices.len() < 4 {
256 return None;
257 }
258 let axis = self.const_axis()?;
259 let (imin, imax) = (self.imin(), self.imax());
260 let (jmin, jmax) = (self.jmin(), self.jmax());
261 let (kmin, kmax) = (self.kmin(), self.kmax());
262
263 let find = |ti: usize, tj: usize, tk: usize| -> Option<[Float; 3]> {
264 self.indices.iter().enumerate().find_map(|(idx, ijk)| {
265 if ijk[0] == ti && ijk[1] == tj && ijk[2] == tk {
266 Some(self.vertices[idx])
267 } else {
268 None
269 }
270 })
271 };
272
273 match axis {
274 FaceAxis::I => {
275 let ic = imin; Some([
277 find(ic, jmin, kmin)?,
278 find(ic, jmin, kmax)?,
279 find(ic, jmax, kmin)?,
280 find(ic, jmax, kmax)?,
281 ])
282 }
283 FaceAxis::J => {
284 let jc = jmin; Some([
286 find(imin, jc, kmin)?,
287 find(imin, jc, kmax)?,
288 find(imax, jc, kmin)?,
289 find(imax, jc, kmax)?,
290 ])
291 }
292 FaceAxis::K => {
293 let kc = kmin; Some([
295 find(imin, jmin, kc)?,
296 find(imin, jmax, kc)?,
297 find(imax, jmin, kc)?,
298 find(imax, jmax, kc)?,
299 ])
300 }
301 }
302 }
303
304 pub fn diagonal_length(&self) -> Float {
306 let min_idx = corner_index(self, self.imin(), self.jmin(), self.kmin());
307 let max_idx = corner_index(self, self.imax(), self.jmax(), self.kmax());
308 let p0 = self.vertices[min_idx];
309 let p1 = self.vertices[max_idx];
310 distance(p0, p1)
311 }
312
313 pub fn median_edge_spacing(&self, block: &Block) -> Float {
318 let axis = match self.const_axis() {
319 Some(a) => a,
320 None => return 1.0,
321 };
322 let mut spacings = Vec::new();
323 match axis {
324 FaceAxis::I => {
325 let ic = self.imin();
326 for j in self.jmin()..self.jmax() {
327 for k in self.kmin()..=self.kmax() {
328 if j < self.jmax()
329 && ic < block.imax
330 && j + 1 < block.jmax
331 && k < block.kmax
332 {
333 let (x0, y0, z0) = block.xyz(ic, j, k);
334 let (x1, y1, z1) = block.xyz(ic, j + 1, k);
335 spacings.push(
336 ((x1 - x0).powi(2) + (y1 - y0).powi(2) + (z1 - z0).powi(2)).sqrt(),
337 );
338 }
339 }
340 }
341 for k in self.kmin()..self.kmax() {
342 for j in self.jmin()..=self.jmax() {
343 if k < self.kmax()
344 && ic < block.imax
345 && j < block.jmax
346 && k + 1 < block.kmax
347 {
348 let (x0, y0, z0) = block.xyz(ic, j, k);
349 let (x1, y1, z1) = block.xyz(ic, j, k + 1);
350 spacings.push(
351 ((x1 - x0).powi(2) + (y1 - y0).powi(2) + (z1 - z0).powi(2)).sqrt(),
352 );
353 }
354 }
355 }
356 }
357 FaceAxis::J => {
358 let jc = self.jmin();
359 for i in self.imin()..self.imax() {
360 for k in self.kmin()..=self.kmax() {
361 if i < self.imax()
362 && i + 1 < block.imax
363 && jc < block.jmax
364 && k < block.kmax
365 {
366 let (x0, y0, z0) = block.xyz(i, jc, k);
367 let (x1, y1, z1) = block.xyz(i + 1, jc, k);
368 spacings.push(
369 ((x1 - x0).powi(2) + (y1 - y0).powi(2) + (z1 - z0).powi(2)).sqrt(),
370 );
371 }
372 }
373 }
374 for k in self.kmin()..self.kmax() {
375 for i in self.imin()..=self.imax() {
376 if k < self.kmax()
377 && i < block.imax
378 && jc < block.jmax
379 && k + 1 < block.kmax
380 {
381 let (x0, y0, z0) = block.xyz(i, jc, k);
382 let (x1, y1, z1) = block.xyz(i, jc, k + 1);
383 spacings.push(
384 ((x1 - x0).powi(2) + (y1 - y0).powi(2) + (z1 - z0).powi(2)).sqrt(),
385 );
386 }
387 }
388 }
389 }
390 FaceAxis::K => {
391 let kc = self.kmin();
392 for i in self.imin()..self.imax() {
393 for j in self.jmin()..=self.jmax() {
394 if i < self.imax()
395 && i + 1 < block.imax
396 && j < block.jmax
397 && kc < block.kmax
398 {
399 let (x0, y0, z0) = block.xyz(i, j, kc);
400 let (x1, y1, z1) = block.xyz(i + 1, j, kc);
401 spacings.push(
402 ((x1 - x0).powi(2) + (y1 - y0).powi(2) + (z1 - z0).powi(2)).sqrt(),
403 );
404 }
405 }
406 }
407 for j in self.jmin()..self.jmax() {
408 for i in self.imin()..=self.imax() {
409 if j < self.jmax()
410 && i < block.imax
411 && j + 1 < block.jmax
412 && kc < block.kmax
413 {
414 let (x0, y0, z0) = block.xyz(i, j, kc);
415 let (x1, y1, z1) = block.xyz(i, j + 1, kc);
416 spacings.push(
417 ((x1 - x0).powi(2) + (y1 - y0).powi(2) + (z1 - z0).powi(2)).sqrt(),
418 );
419 }
420 }
421 }
422 }
423 }
424 if spacings.is_empty() {
425 return 1.0;
426 }
427 spacings.sort_by(|a, b| a.partial_cmp(b).unwrap());
428 spacings[spacings.len() / 2]
429 }
430
431 pub fn vertices_equals(&self, other: &Face, tol: Float) -> bool {
433 if self.vertices.len() != other.vertices.len() {
434 return false;
435 }
436 let mut matched = vec![false; other.vertices.len()];
437 for v in &self.vertices {
438 let mut found = false;
439 for (idx, o) in other.vertices.iter().enumerate() {
440 if matched[idx] {
441 continue;
442 }
443 if distance(*v, *o) <= tol {
445 matched[idx] = true;
446 found = true;
447 break;
448 }
449 }
450 if !found {
451 return false;
452 }
453 }
454 true
455 }
456
457 pub fn grid_points(&self, block: &Block, stride_u: usize, stride_v: usize) -> Vec<[Float; 3]> {
462 let Some(axis) = self.const_axis() else {
463 return self.vertices.clone();
464 };
465 let su = stride_u.max(1);
466 let sv = stride_v.max(1);
467 let mut pts = Vec::new();
468 match axis {
469 FaceAxis::I => {
470 let i = self.imin();
471 for j in (self.jmin()..=self.jmax()).step_by(su) {
472 for k in (self.kmin()..=self.kmax()).step_by(sv) {
473 pts.push(to_array(block.xyz(i, j, k)));
474 }
475 }
476 }
477 FaceAxis::J => {
478 let j = self.jmin();
479 for i in (self.imin()..=self.imax()).step_by(su) {
480 for k in (self.kmin()..=self.kmax()).step_by(sv) {
481 pts.push(to_array(block.xyz(i, j, k)));
482 }
483 }
484 }
485 FaceAxis::K => {
486 let k = self.kmin();
487 for i in (self.imin()..=self.imax()).step_by(su) {
488 for j in (self.jmin()..=self.jmax()).step_by(sv) {
489 pts.push(to_array(block.xyz(i, j, k)));
490 }
491 }
492 }
493 }
494 pts
495 }
496
497 #[allow(clippy::too_many_arguments)]
506 pub fn touches_by_nodes(
507 &self,
508 other: &Face,
509 block_self: &Block,
510 block_other: &Block,
511 tol_xyz: Float,
512 min_shared_frac: Float,
513 min_shared_abs: usize,
514 stride_u: usize,
515 stride_v: usize,
516 ) -> bool {
517 let pts_self = self.grid_points(block_self, stride_u, stride_v);
518 let pts_other = other.grid_points(block_other, stride_u, stride_v);
519 if pts_self.is_empty() || pts_other.is_empty() {
520 return false;
521 }
522
523 let q_self: HashSet<_> = pts_self
524 .iter()
525 .map(|p| quantize_point(*p, tol_xyz))
526 .collect();
527 let q_other: HashSet<_> = pts_other
528 .iter()
529 .map(|p| quantize_point(*p, tol_xyz))
530 .collect();
531
532 let shared = q_self.intersection(&q_other).count();
533 if shared < min_shared_abs {
534 return false;
535 }
536
537 let denom = pts_self.len().min(pts_other.len()) as Float;
538 (shared as Float) / denom >= min_shared_frac
539 }
540
541 pub fn to_record(&self) -> FaceRecord {
543 FaceRecord {
544 block_index: self.block_index.unwrap_or(usize::MAX),
545 il: self.imin(),
546 jl: self.jmin(),
547 kl: self.kmin(),
548 ih: self.imax(),
549 jh: self.jmax(),
550 kh: self.kmax(),
551 id: self.id,
552 u_physical: None,
553 v_physical: None,
554 }
555 }
556
557 pub fn index_key(&self) -> FaceKey {
558 (
559 self.block_index.unwrap_or(usize::MAX),
560 self.imin(),
561 self.jmin(),
562 self.kmin(),
563 self.imax(),
564 self.jmax(),
565 self.kmax(),
566 )
567 }
568
569 pub fn scale_indices(&mut self, factor: usize) {
571 if factor <= 1 {
572 return;
573 }
574 for idx in &mut self.indices {
575 idx[0] *= factor;
576 idx[1] *= factor;
577 idx[2] *= factor;
578 }
579 }
580
581 pub fn is_point(&self) -> bool {
583 self.imin() == self.imax() && self.jmin() == self.jmax() && self.kmin() == self.kmax()
584 }
585
586 pub fn face_size(&self) -> usize {
592 let di = self.imax().saturating_sub(self.imin()).max(1);
593 let dj = self.jmax().saturating_sub(self.jmin()).max(1);
594 let dk = self.kmax().saturating_sub(self.kmin()).max(1);
595 if self.imin() == self.imax() {
596 dj * dk
597 } else if self.jmin() == self.jmax() {
598 di * dk
599 } else if self.kmin() == self.kmax() {
600 di * dj
601 } else {
602 di * dj * dk
603 }
604 }
605
606 pub fn normal(&self, block: &Block) -> [Float; 3] {
609 let axis = match self.const_axis() {
610 Some(a) => a,
611 None => return [0.0, 0.0, 0.0],
612 };
613
614 let (p1, p2, p3) = match axis {
615 FaceAxis::I => {
616 let ic = self.imin();
617 (
618 to_array(block.xyz(ic, self.jmin(), self.kmin())),
619 to_array(block.xyz(ic, self.jmax(), self.kmin())),
620 to_array(block.xyz(ic, self.jmin(), self.kmax())),
621 )
622 }
623 FaceAxis::J => {
624 let jc = self.jmin();
625 (
626 to_array(block.xyz(self.imin(), jc, self.kmin())),
627 to_array(block.xyz(self.imax(), jc, self.kmin())),
628 to_array(block.xyz(self.imin(), jc, self.kmax())),
629 )
630 }
631 FaceAxis::K => {
632 let kc = self.kmin();
633 (
634 to_array(block.xyz(self.imin(), self.jmin(), kc)),
635 to_array(block.xyz(self.imax(), self.jmin(), kc)),
636 to_array(block.xyz(self.imin(), self.jmax(), kc)),
637 )
638 }
639 };
640
641 cross3(sub3(p2, p1), sub3(p3, p1))
642 }
643
644 pub fn shift(&mut self, dx: Float, dy: Float, dz: Float) {
646 for v in &mut self.vertices {
647 v[0] += dx;
648 v[1] += dy;
649 v[2] += dz;
650 }
651 self.centroid[0] += dx;
652 self.centroid[1] += dy;
653 self.centroid[2] += dz;
654 }
655
656 pub fn match_indices(&self, other: &Face) -> Vec<[usize; 2]> {
661 let tol = VERTEX_MATCH_TOL;
662 let mut matched_other = vec![false; other.vertices.len()];
663 let mut result = Vec::new();
664 for (i, v_self) in self.vertices.iter().enumerate() {
665 for (j, v_other) in other.vertices.iter().enumerate() {
666 if matched_other[j] {
667 continue;
668 }
669 if (v_self[0] - v_other[0]).abs() < tol
670 && (v_self[1] - v_other[1]).abs() < tol
671 && (v_self[2] - v_other[2]).abs() < tol
672 {
673 result.push([i, j]);
674 matched_other[j] = true;
675 break;
676 }
677 }
678 }
679 result
680 }
681
682 pub fn overlap_fraction(
689 &self,
690 other: &Face,
691 tol_angle_deg: Float,
692 tol_plane_dist: Float,
693 ) -> Float {
694 if self.vertices.len() < 3 || other.vertices.len() < 3 {
695 return 0.0;
696 }
697
698 let (s_min, s_max) = vertex_aabb(&self.vertices);
700 let (o_min, o_max) = vertex_aabb(&other.vertices);
701 let diag = self
702 .diagonal_length()
703 .max(other.diagonal_length())
704 .max(1e-12);
705 let aabb_tol = tol_plane_dist * diag;
706 if s_max[0] + aabb_tol < o_min[0]
707 || o_max[0] + aabb_tol < s_min[0]
708 || s_max[1] + aabb_tol < o_min[1]
709 || o_max[1] + aabb_tol < s_min[1]
710 || s_max[2] + aabb_tol < o_min[2]
711 || o_max[2] + aabb_tol < s_min[2]
712 {
713 return 0.0;
714 }
715
716 let n1 = quad_normal_from_verts(&self.vertices);
717 let n2 = quad_normal_from_verts(&other.vertices);
718 let len1 = vec_norm3(n1);
719 let len2 = vec_norm3(n2);
720 if len1 < 1e-15 || len2 < 1e-15 {
721 return 0.0;
722 }
723
724 let cos_angle = dot3(n1, n2) / (len1 * len2);
726 let angle_deg = cos_angle.abs().min(1.0).acos().to_degrees();
727 if angle_deg > tol_angle_deg {
728 return 0.0;
729 }
730
731 let p0 = self.vertices[0];
733 let n_hat = [n1[0] / len1, n1[1] / len1, n1[2] / len1];
734 let diag = self
736 .diagonal_length()
737 .max(other.diagonal_length())
738 .max(1e-12);
739 let plane_tol = tol_plane_dist * diag;
740 for v in &other.vertices {
741 let d = dot3(sub3(*v, p0), n_hat).abs();
742 if d > plane_tol {
743 return 0.0;
744 }
745 }
746
747 let drop = dominant_projection_axis(n_hat);
749 let poly_self = project_drop_axis(&self.vertices, drop);
750 let poly_other = project_drop_axis(&other.vertices, drop);
751
752 let area_self = poly_area_2d(&poly_self).abs();
753 let area_other = poly_area_2d(&poly_other).abs();
754 if area_self < 1e-30 || area_other < 1e-30 {
755 return 0.0;
756 }
757
758 let clipped = clip_sutherland_hodgman(&poly_other, &poly_self);
759 if clipped.len() < 3 {
760 return 0.0;
761 }
762
763 let area_inter = poly_area_2d(&clipped).abs();
764 area_inter / area_self.min(area_other)
765 }
766
767 pub fn touches(
769 &self,
770 other: &Face,
771 tol_angle_deg: Float,
772 tol_plane_dist: Float,
773 min_overlap_frac: Float,
774 ) -> bool {
775 self.overlap_fraction(other, tol_angle_deg, tol_plane_dist) >= min_overlap_frac
776 }
777
778 pub fn shared_point_fraction(
782 &self,
783 other: &Face,
784 block_self: &Block,
785 block_other: &Block,
786 tol_xyz: Float,
787 stride_u: usize,
788 stride_v: usize,
789 ) -> Float {
790 let pts_self = self.grid_points(block_self, stride_u, stride_v);
791 let pts_other = other.grid_points(block_other, stride_u, stride_v);
792 if pts_self.is_empty() || pts_other.is_empty() {
793 return 0.0;
794 }
795
796 let q_self: HashSet<_> = pts_self
797 .iter()
798 .map(|p| quantize_point(*p, tol_xyz))
799 .collect();
800 let q_other: HashSet<_> = pts_other
801 .iter()
802 .map(|p| quantize_point(*p, tol_xyz))
803 .collect();
804
805 let shared = q_self.intersection(&q_other).count();
806 let denom = pts_self.len().min(pts_other.len()) as Float;
807 if denom == 0.0 {
808 return 0.0;
809 }
810 (shared as Float) / denom
811 }
812}
813
814#[derive(Clone, Debug)]
817pub struct StructuredFace {
818 pub dims: (usize, usize),
820 pub coords: Vec<[Float; 3]>,
822}
823
824impl StructuredFace {
825 fn idx(&self, u: usize, v: usize) -> [Float; 3] {
826 self.coords[v * self.dims.0 + u]
827 }
828}
829
830#[derive(Copy, Clone, Debug)]
831enum BlockFaceKind {
832 IMin,
833 IMax,
834 JMin,
835 JMax,
836 KMin,
837 KMax,
838}
839
840impl BlockFaceKind {
841 fn all() -> [Self; 6] {
842 [
843 Self::IMin,
844 Self::IMax,
845 Self::JMin,
846 Self::JMax,
847 Self::KMin,
848 Self::KMax,
849 ]
850 }
851
852 fn name(self) -> &'static str {
853 match self {
854 Self::IMin => "imin",
855 Self::IMax => "imax",
856 Self::JMin => "jmin",
857 Self::JMax => "jmax",
858 Self::KMin => "kmin",
859 Self::KMax => "kmax",
860 }
861 }
862
863 fn dims(self, block: &Block) -> (usize, usize) {
864 match self {
865 Self::IMin | Self::IMax => (block.jmax, block.kmax),
866 Self::JMin | Self::JMax => (block.imax, block.kmax),
867 Self::KMin | Self::KMax => (block.imax, block.jmax),
868 }
869 }
870
871 fn sample(self, block: &Block, u: usize, v: usize) -> [Float; 3] {
872 match self {
873 Self::IMin => to_array(block.xyz(0, u, v)),
874 Self::IMax => to_array(block.xyz(block.imax - 1, u, v)),
875 Self::JMin => to_array(block.xyz(u, 0, v)),
876 Self::JMax => to_array(block.xyz(u, block.jmax - 1, v)),
877 Self::KMin => to_array(block.xyz(u, v, 0)),
878 Self::KMax => to_array(block.xyz(u, v, block.kmax - 1)),
879 }
880 }
881
882 fn structured_face(self, block: &Block) -> StructuredFace {
883 let dims = self.dims(block);
884 let mut coords = Vec::with_capacity(dims.0 * dims.1);
885 for v in 0..dims.1 {
886 for u in 0..dims.0 {
887 coords.push(self.sample(block, u, v));
888 }
889 }
890 StructuredFace { dims, coords }
891 }
892}
893
894pub fn unique_pairs(pairs: &[(usize, usize)]) -> Vec<(usize, usize)> {
906 let mut seen = HashSet::new();
907 let mut out = Vec::new();
908 for &(a, b) in pairs {
909 if a == b {
910 continue;
911 }
912 let key = if a < b { (a, b) } else { (b, a) };
913 if seen.insert(key) {
914 out.push((a, b));
915 }
916 }
917 out
918}
919
920pub fn faces_match(
930 face1: &StructuredFace,
931 face2: &StructuredFace,
932 tol: Float,
933) -> (bool, Option<(bool, bool)>) {
934 if face1.dims != face2.dims {
935 return (false, None);
936 }
937 let (ni, nj) = face1.dims;
938
939 let corners = |f: &StructuredFace, flip_ud: bool, flip_lr: bool| -> [[Float; 3]; 4] {
940 let map = |u: usize, v: usize| {
941 let uu = if flip_ud { ni - 1 - u } else { u };
942 let vv = if flip_lr { nj - 1 - v } else { v };
943 f.idx(uu, vv)
944 };
945 [
946 map(0, 0),
947 map(0, nj - 1),
948 map(ni - 1, 0),
949 map(ni - 1, nj - 1),
950 ]
951 };
952
953 let c1 = corners(face1, false, false);
954 for flip_ud in [false, true] {
955 for flip_lr in [false, true] {
956 let c2 = corners(face2, flip_ud, flip_lr);
957 if c1.iter().zip(&c2).all(|(a, b)| distance(*a, *b) <= tol) {
958 return (true, Some((flip_ud, flip_lr)));
959 }
960 }
961 }
962 (false, None)
963}
964
965pub fn full_face_match(
975 face_a: &Face,
976 face_b: &Face,
977 tol: Float,
978) -> Option<crate::face_record::Orientation> {
979 let corners_a = face_a.get_all_corners()?;
980 let corners_b = face_b.get_all_corners()?;
981 try_corner_permutations(&corners_a, &corners_b, tol)
982}
983
984pub fn full_face_match_transformed<F>(
990 face_a: &Face,
991 face_b: &Face,
992 transform: F,
993 tol: Float,
994) -> Option<crate::face_record::Orientation>
995where
996 F: Fn([Float; 3]) -> [Float; 3],
997{
998 let raw = face_a.get_all_corners()?;
999 let corners_a = [
1000 transform(raw[0]),
1001 transform(raw[1]),
1002 transform(raw[2]),
1003 transform(raw[3]),
1004 ];
1005 let corners_b = face_b.get_all_corners()?;
1006 try_corner_permutations(&corners_a, &corners_b, tol)
1007}
1008
1009fn try_corner_permutations(
1017 ca: &[[Float; 3]; 4],
1018 cb: &[[Float; 3]; 4],
1019 tol: Float,
1020) -> Option<crate::face_record::Orientation> {
1021 use crate::face_record::{Orientation, OrientationPlane};
1022
1023 const CORNER_PERMS: [[usize; 4]; 8] = [
1027 [0, 1, 2, 3], [2, 3, 0, 1], [1, 0, 3, 2], [3, 2, 1, 0], [0, 2, 1, 3], [2, 0, 3, 1], [1, 3, 0, 2], [3, 1, 2, 0], ];
1036
1037 for (idx, perm) in CORNER_PERMS.iter().enumerate() {
1038 let all_match = perm
1039 .iter()
1040 .enumerate()
1041 .all(|(i, &j)| distance(ca[i], cb[j]) <= tol);
1042 if all_match {
1043 return Some(Orientation {
1044 permutation_index: idx as u8,
1045 plane: if idx >= 4 {
1046 OrientationPlane::CrossPlane
1047 } else {
1048 OrientationPlane::InPlane
1049 },
1050 permutation_matrix: None,
1051 });
1052 }
1053 }
1054
1055 None
1056}
1057
1058pub fn find_matching_faces(
1068 block1: &Block,
1069 block2: &Block,
1070 tol: Float,
1071) -> Option<(&'static str, &'static str, (bool, bool))> {
1072 for f1 in BlockFaceKind::all() {
1073 let face1 = f1.structured_face(block1);
1074 for f2 in BlockFaceKind::all() {
1075 let face2 = f2.structured_face(block2);
1076 let (matched, flips) = faces_match(&face1, &face2, tol);
1077 if matched {
1078 return flips.map(|flip| (f1.name(), f2.name(), flip));
1079 }
1080 }
1081 }
1082 None
1083}
1084
1085pub fn get_outer_faces(block: &Block) -> (Vec<Face>, Vec<(Face, Face)>) {
1093 let mut faces = Vec::with_capacity(6);
1094 for kind in BlockFaceKind::all() {
1095 let mut face = Face::new();
1096 match kind {
1097 BlockFaceKind::IMin | BlockFaceKind::IMax => {
1098 let i = if matches!(kind, BlockFaceKind::IMin) {
1099 0
1100 } else {
1101 block.imax - 1
1102 };
1103 for j in [0, block.jmax - 1] {
1104 for k in [0, block.kmax - 1] {
1105 let (x, y, z) = block.xyz(i, j, k);
1106 face.add_vertex(x, y, z, i, j, k);
1107 }
1108 }
1109 }
1110 BlockFaceKind::JMin | BlockFaceKind::JMax => {
1111 let j = if matches!(kind, BlockFaceKind::JMin) {
1112 0
1113 } else {
1114 block.jmax - 1
1115 };
1116 for i in [0, block.imax - 1] {
1117 for k in [0, block.kmax - 1] {
1118 let (x, y, z) = block.xyz(i, j, k);
1119 face.add_vertex(x, y, z, i, j, k);
1120 }
1121 }
1122 }
1123 BlockFaceKind::KMin | BlockFaceKind::KMax => {
1124 let k = if matches!(kind, BlockFaceKind::KMin) {
1125 0
1126 } else {
1127 block.kmax - 1
1128 };
1129 for i in [0, block.imax - 1] {
1130 for j in [0, block.jmax - 1] {
1131 let (x, y, z) = block.xyz(i, j, k);
1132 face.add_vertex(x, y, z, i, j, k);
1133 }
1134 }
1135 }
1136 }
1137 faces.push(face);
1138 }
1139
1140 let mut matching_pairs = Vec::new();
1141 let mut non_matching = Vec::new();
1142 for i in 0..faces.len() {
1143 let mut matched = false;
1144 for j in 0..faces.len() {
1145 if i == j {
1146 continue;
1147 }
1148 if faces[i].vertices_equals(&faces[j], DEFAULT_TOL) {
1149 matching_pairs.push((i, j));
1150 matched = true;
1151 }
1152 }
1153 if !matched {
1154 non_matching.push(faces[i].clone());
1155 }
1156 }
1157
1158 let pairs = unique_pairs(&matching_pairs)
1159 .into_iter()
1160 .map(|(a, b)| (faces[a].clone(), faces[b].clone()))
1161 .collect();
1162
1163 (non_matching, pairs)
1164}
1165
1166pub fn create_face_from_diagonals(
1176 block: &Block,
1177 imin: usize,
1178 jmin: usize,
1179 kmin: usize,
1180 imax: usize,
1181 jmax: usize,
1182 kmax: usize,
1183) -> Face {
1184 let mut face = Face::new();
1185 if imin == imax {
1186 let i = imin;
1187 for j in [jmin, jmax] {
1188 for k in [kmin, kmax] {
1189 let (x, y, z) = block.xyz(i, j, k);
1190 face.add_vertex(x, y, z, i, j, k);
1191 }
1192 }
1193 } else if jmin == jmax {
1194 let j = jmin;
1195 for i in [imin, imax] {
1196 for k in [kmin, kmax] {
1197 let (x, y, z) = block.xyz(i, j, k);
1198 face.add_vertex(x, y, z, i, j, k);
1199 }
1200 }
1201 } else if kmin == kmax {
1202 let k = kmin;
1203 for i in [imin, imax] {
1204 for j in [jmin, jmax] {
1205 let (x, y, z) = block.xyz(i, j, k);
1206 face.add_vertex(x, y, z, i, j, k);
1207 }
1208 }
1209 }
1210 face
1211}
1212
1213pub fn outer_face_records_to_list(
1223 blocks: &[Block],
1224 outer_faces: &[FaceRecord],
1225 gcd: usize,
1226) -> Vec<Face> {
1227 let mut faces = Vec::new();
1228 for record in outer_faces {
1229 let block_idx = record.block_index;
1230 if block_idx >= blocks.len() {
1231 continue;
1232 }
1233 let block = &blocks[block_idx];
1234 let scale = gcd.max(1);
1235 let (si, sj, sk) = (
1236 record.i_lo() / scale,
1237 record.j_lo() / scale,
1238 record.k_lo() / scale,
1239 );
1240 let (ei, ej, ek) = (
1241 record.i_hi() / scale,
1242 record.j_hi() / scale,
1243 record.k_hi() / scale,
1244 );
1245 if ei >= block.imax || ej >= block.jmax || ek >= block.kmax {
1247 continue;
1248 }
1249 let mut face = create_face_from_diagonals(block, si, sj, sk, ei, ej, ek);
1250 face.set_block_index(block_idx);
1251 if let Some(id) = record.id {
1252 face.set_id(id);
1253 }
1254 faces.push(face);
1255 }
1256 faces
1257}
1258
1259pub fn match_faces_to_list(blocks: &[Block], matched_faces: &[FaceMatch], gcd: usize) -> Vec<Face> {
1269 let mut out = Vec::new();
1270 for record in matched_faces {
1271 let f1 = outer_face_records_to_list(blocks, std::slice::from_ref(&record.block1), gcd)
1272 .into_iter()
1273 .next();
1274 let f2 = outer_face_records_to_list(blocks, std::slice::from_ref(&record.block2), gcd)
1275 .into_iter()
1276 .next();
1277 if let Some(face) = f1 {
1278 out.push(face);
1279 }
1280 if let Some(face) = f2 {
1281 out.push(face);
1282 }
1283 }
1284 out
1285}
1286
1287#[allow(clippy::too_many_arguments)]
1298pub fn split_face(
1299 face_to_split: &Face,
1300 block: &Block,
1301 imin: usize,
1302 jmin: usize,
1303 kmin: usize,
1304 imax: usize,
1305 jmax: usize,
1306 kmax: usize,
1307) -> Vec<Face> {
1308 let center = create_face_from_diagonals(block, imin, jmin, kmin, imax, jmax, kmax);
1309 let mut faces = Vec::new();
1310
1311 if kmin == kmax {
1312 faces.push(create_face_from_diagonals(
1313 block,
1314 imin,
1315 jmax,
1316 kmin,
1317 imax,
1318 face_to_split.jmax(),
1319 kmax,
1320 ));
1321 faces.push(create_face_from_diagonals(
1322 block,
1323 imin,
1324 face_to_split.jmin(),
1325 kmin,
1326 imax,
1327 jmin,
1328 kmax,
1329 ));
1330 faces.push(create_face_from_diagonals(
1331 block,
1332 face_to_split.imin(),
1333 face_to_split.jmin(),
1334 kmin,
1335 imin,
1336 face_to_split.jmax(),
1337 kmax,
1338 ));
1339 faces.push(create_face_from_diagonals(
1340 block,
1341 imax,
1342 face_to_split.jmin(),
1343 kmin,
1344 face_to_split.imax(),
1345 face_to_split.jmax(),
1346 kmax,
1347 ));
1348 } else if imin == imax {
1349 faces.push(create_face_from_diagonals(
1350 block,
1351 imin,
1352 jmin,
1353 kmax,
1354 imax,
1355 jmax,
1356 face_to_split.kmax(),
1357 ));
1358 faces.push(create_face_from_diagonals(
1359 block,
1360 imin,
1361 jmin,
1362 face_to_split.kmin(),
1363 imax,
1364 jmax,
1365 kmin,
1366 ));
1367 faces.push(create_face_from_diagonals(
1368 block,
1369 imin,
1370 face_to_split.jmin(),
1371 face_to_split.kmin(),
1372 imax,
1373 jmin,
1374 face_to_split.kmax(),
1375 ));
1376 faces.push(create_face_from_diagonals(
1377 block,
1378 imin,
1379 jmax,
1380 face_to_split.kmin(),
1381 imax,
1382 face_to_split.jmax(),
1383 face_to_split.kmax(),
1384 ));
1385 } else if jmin == jmax {
1386 faces.push(create_face_from_diagonals(
1387 block,
1388 imin,
1389 jmin,
1390 kmax,
1391 imax,
1392 jmax,
1393 face_to_split.kmax(),
1394 ));
1395 faces.push(create_face_from_diagonals(
1396 block,
1397 imin,
1398 jmin,
1399 face_to_split.kmin(),
1400 imax,
1401 jmax,
1402 kmin,
1403 ));
1404 faces.push(create_face_from_diagonals(
1405 block,
1406 face_to_split.imin(),
1407 jmin,
1408 face_to_split.kmin(),
1409 imin,
1410 jmax,
1411 face_to_split.kmax(),
1412 ));
1413 faces.push(create_face_from_diagonals(
1414 block,
1415 imax,
1416 jmin,
1417 face_to_split.kmin(),
1418 face_to_split.imax(),
1419 jmax,
1420 face_to_split.kmax(),
1421 ));
1422 }
1423
1424 faces
1425 .into_iter()
1426 .filter_map(|mut face| {
1427 if face.is_edge() || face.index_equals(¢er) {
1428 None
1429 } else {
1430 if let Some(idx) = face_to_split.block_index() {
1431 face.set_block_index(idx);
1432 }
1433 Some(face)
1434 }
1435 })
1436 .collect()
1437}
1438
1439pub fn find_face_nearest_point(faces: &[Face], point: [Float; 3]) -> Option<usize> {
1448 faces
1449 .iter()
1450 .enumerate()
1451 .min_by(|(_, a), (_, b)| {
1452 distance(a.centroid(), point)
1453 .partial_cmp(&distance(b.centroid(), point))
1454 .unwrap_or(std::cmp::Ordering::Equal)
1455 })
1456 .map(|(idx, _)| idx)
1457}
1458
1459pub fn reduce_blocks(blocks: &[Block], factor: usize) -> Vec<Block> {
1468 if factor <= 1 {
1469 return blocks.to_vec();
1470 }
1471
1472 fn sampled_indices(max: usize, stride: usize) -> Vec<usize> {
1473 if max == 0 {
1474 return Vec::new();
1475 }
1476 let mut indices: Vec<usize> = (0..max).step_by(stride).collect();
1477 if let Some(&last) = indices.last() {
1478 if last != max - 1 {
1479 indices.push(max - 1);
1480 }
1481 } else {
1482 indices.push(max - 1);
1483 }
1484 indices
1485 }
1486
1487 blocks
1488 .iter()
1489 .map(|block| {
1490 let i_idx = sampled_indices(block.imax, factor);
1491 let j_idx = sampled_indices(block.jmax, factor);
1492 let k_idx = sampled_indices(block.kmax, factor);
1493
1494 let si = i_idx.len();
1495 let sj = j_idx.len();
1496 let sk = k_idx.len();
1497
1498 let mut x = Vec::with_capacity(si * sj * sk);
1499 let mut y = Vec::with_capacity(si * sj * sk);
1500 let mut z = Vec::with_capacity(si * sj * sk);
1501
1502 for &k in &k_idx {
1503 for &j in &j_idx {
1504 for &i in &i_idx {
1505 let (px, py, pz) = block.xyz(i, j, k);
1506 x.push(px);
1507 y.push(py);
1508 z.push(pz);
1509 }
1510 }
1511 }
1512
1513 Block::new(si, sj, sk, x, y, z)
1514 })
1515 .collect()
1516}
1517
1518pub fn rotate_block(block: &Block, rotation: [[Float; 3]; 3]) -> Block {
1527 let mut x = Vec::with_capacity(block.npoints());
1528 let mut y = Vec::with_capacity(block.npoints());
1529 let mut z = Vec::with_capacity(block.npoints());
1530 for k in 0..block.kmax {
1531 for j in 0..block.jmax {
1532 for i in 0..block.imax {
1533 let (px, py, pz) = block.xyz(i, j, k);
1534 x.push(rotation[0][0] * px + rotation[0][1] * py + rotation[0][2] * pz);
1535 y.push(rotation[1][0] * px + rotation[1][1] * py + rotation[1][2] * pz);
1536 z.push(rotation[2][0] * px + rotation[2][1] * py + rotation[2][2] * pz);
1537 }
1538 }
1539 }
1540 Block::new(block.imax, block.jmax, block.kmax, x, y, z)
1541}
1542
1543