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 });
1051 }
1052 }
1053
1054 None
1055}
1056
1057pub fn find_matching_faces(
1067 block1: &Block,
1068 block2: &Block,
1069 tol: Float,
1070) -> Option<(&'static str, &'static str, (bool, bool))> {
1071 for f1 in BlockFaceKind::all() {
1072 let face1 = f1.structured_face(block1);
1073 for f2 in BlockFaceKind::all() {
1074 let face2 = f2.structured_face(block2);
1075 let (matched, flips) = faces_match(&face1, &face2, tol);
1076 if matched {
1077 return flips.map(|flip| (f1.name(), f2.name(), flip));
1078 }
1079 }
1080 }
1081 None
1082}
1083
1084pub fn get_outer_faces(block: &Block) -> (Vec<Face>, Vec<(Face, Face)>) {
1092 let mut faces = Vec::with_capacity(6);
1093 for kind in BlockFaceKind::all() {
1094 let mut face = Face::new();
1095 match kind {
1096 BlockFaceKind::IMin | BlockFaceKind::IMax => {
1097 let i = if matches!(kind, BlockFaceKind::IMin) {
1098 0
1099 } else {
1100 block.imax - 1
1101 };
1102 for j in [0, block.jmax - 1] {
1103 for k in [0, block.kmax - 1] {
1104 let (x, y, z) = block.xyz(i, j, k);
1105 face.add_vertex(x, y, z, i, j, k);
1106 }
1107 }
1108 }
1109 BlockFaceKind::JMin | BlockFaceKind::JMax => {
1110 let j = if matches!(kind, BlockFaceKind::JMin) {
1111 0
1112 } else {
1113 block.jmax - 1
1114 };
1115 for i in [0, block.imax - 1] {
1116 for k in [0, block.kmax - 1] {
1117 let (x, y, z) = block.xyz(i, j, k);
1118 face.add_vertex(x, y, z, i, j, k);
1119 }
1120 }
1121 }
1122 BlockFaceKind::KMin | BlockFaceKind::KMax => {
1123 let k = if matches!(kind, BlockFaceKind::KMin) {
1124 0
1125 } else {
1126 block.kmax - 1
1127 };
1128 for i in [0, block.imax - 1] {
1129 for j in [0, block.jmax - 1] {
1130 let (x, y, z) = block.xyz(i, j, k);
1131 face.add_vertex(x, y, z, i, j, k);
1132 }
1133 }
1134 }
1135 }
1136 faces.push(face);
1137 }
1138
1139 let mut matching_pairs = Vec::new();
1140 let mut non_matching = Vec::new();
1141 for i in 0..faces.len() {
1142 let mut matched = false;
1143 for j in 0..faces.len() {
1144 if i == j {
1145 continue;
1146 }
1147 if faces[i].vertices_equals(&faces[j], DEFAULT_TOL) {
1148 matching_pairs.push((i, j));
1149 matched = true;
1150 }
1151 }
1152 if !matched {
1153 non_matching.push(faces[i].clone());
1154 }
1155 }
1156
1157 let pairs = unique_pairs(&matching_pairs)
1158 .into_iter()
1159 .map(|(a, b)| (faces[a].clone(), faces[b].clone()))
1160 .collect();
1161
1162 (non_matching, pairs)
1163}
1164
1165pub fn create_face_from_diagonals(
1175 block: &Block,
1176 imin: usize,
1177 jmin: usize,
1178 kmin: usize,
1179 imax: usize,
1180 jmax: usize,
1181 kmax: usize,
1182) -> Face {
1183 let mut face = Face::new();
1184 if imin == imax {
1185 let i = imin;
1186 for j in [jmin, jmax] {
1187 for k in [kmin, kmax] {
1188 let (x, y, z) = block.xyz(i, j, k);
1189 face.add_vertex(x, y, z, i, j, k);
1190 }
1191 }
1192 } else if jmin == jmax {
1193 let j = jmin;
1194 for i in [imin, imax] {
1195 for k in [kmin, kmax] {
1196 let (x, y, z) = block.xyz(i, j, k);
1197 face.add_vertex(x, y, z, i, j, k);
1198 }
1199 }
1200 } else if kmin == kmax {
1201 let k = kmin;
1202 for i in [imin, imax] {
1203 for j in [jmin, jmax] {
1204 let (x, y, z) = block.xyz(i, j, k);
1205 face.add_vertex(x, y, z, i, j, k);
1206 }
1207 }
1208 }
1209 face
1210}
1211
1212pub fn outer_face_records_to_list(
1222 blocks: &[Block],
1223 outer_faces: &[FaceRecord],
1224 gcd: usize,
1225) -> Vec<Face> {
1226 let mut faces = Vec::new();
1227 for record in outer_faces {
1228 let block_idx = record.block_index;
1229 if block_idx >= blocks.len() {
1230 continue;
1231 }
1232 let block = &blocks[block_idx];
1233 let scale = gcd.max(1);
1234 let (si, sj, sk) = (
1235 record.i_lo() / scale,
1236 record.j_lo() / scale,
1237 record.k_lo() / scale,
1238 );
1239 let (ei, ej, ek) = (
1240 record.i_hi() / scale,
1241 record.j_hi() / scale,
1242 record.k_hi() / scale,
1243 );
1244 if ei >= block.imax || ej >= block.jmax || ek >= block.kmax {
1246 continue;
1247 }
1248 let mut face = create_face_from_diagonals(block, si, sj, sk, ei, ej, ek);
1249 face.set_block_index(block_idx);
1250 if let Some(id) = record.id {
1251 face.set_id(id);
1252 }
1253 faces.push(face);
1254 }
1255 faces
1256}
1257
1258pub fn match_faces_to_list(blocks: &[Block], matched_faces: &[FaceMatch], gcd: usize) -> Vec<Face> {
1268 let mut out = Vec::new();
1269 for record in matched_faces {
1270 let f1 = outer_face_records_to_list(blocks, std::slice::from_ref(&record.block1), gcd)
1271 .into_iter()
1272 .next();
1273 let f2 = outer_face_records_to_list(blocks, std::slice::from_ref(&record.block2), gcd)
1274 .into_iter()
1275 .next();
1276 if let Some(face) = f1 {
1277 out.push(face);
1278 }
1279 if let Some(face) = f2 {
1280 out.push(face);
1281 }
1282 }
1283 out
1284}
1285
1286#[allow(clippy::too_many_arguments)]
1297pub fn split_face(
1298 face_to_split: &Face,
1299 block: &Block,
1300 imin: usize,
1301 jmin: usize,
1302 kmin: usize,
1303 imax: usize,
1304 jmax: usize,
1305 kmax: usize,
1306) -> Vec<Face> {
1307 let center = create_face_from_diagonals(block, imin, jmin, kmin, imax, jmax, kmax);
1308 let mut faces = Vec::new();
1309
1310 if kmin == kmax {
1311 faces.push(create_face_from_diagonals(
1312 block,
1313 imin,
1314 jmax,
1315 kmin,
1316 imax,
1317 face_to_split.jmax(),
1318 kmax,
1319 ));
1320 faces.push(create_face_from_diagonals(
1321 block,
1322 imin,
1323 face_to_split.jmin(),
1324 kmin,
1325 imax,
1326 jmin,
1327 kmax,
1328 ));
1329 faces.push(create_face_from_diagonals(
1330 block,
1331 face_to_split.imin(),
1332 face_to_split.jmin(),
1333 kmin,
1334 imin,
1335 face_to_split.jmax(),
1336 kmax,
1337 ));
1338 faces.push(create_face_from_diagonals(
1339 block,
1340 imax,
1341 face_to_split.jmin(),
1342 kmin,
1343 face_to_split.imax(),
1344 face_to_split.jmax(),
1345 kmax,
1346 ));
1347 } else if imin == imax {
1348 faces.push(create_face_from_diagonals(
1349 block,
1350 imin,
1351 jmin,
1352 kmax,
1353 imax,
1354 jmax,
1355 face_to_split.kmax(),
1356 ));
1357 faces.push(create_face_from_diagonals(
1358 block,
1359 imin,
1360 jmin,
1361 face_to_split.kmin(),
1362 imax,
1363 jmax,
1364 kmin,
1365 ));
1366 faces.push(create_face_from_diagonals(
1367 block,
1368 imin,
1369 face_to_split.jmin(),
1370 face_to_split.kmin(),
1371 imax,
1372 jmin,
1373 face_to_split.kmax(),
1374 ));
1375 faces.push(create_face_from_diagonals(
1376 block,
1377 imin,
1378 jmax,
1379 face_to_split.kmin(),
1380 imax,
1381 face_to_split.jmax(),
1382 face_to_split.kmax(),
1383 ));
1384 } else if jmin == jmax {
1385 faces.push(create_face_from_diagonals(
1386 block,
1387 imin,
1388 jmin,
1389 kmax,
1390 imax,
1391 jmax,
1392 face_to_split.kmax(),
1393 ));
1394 faces.push(create_face_from_diagonals(
1395 block,
1396 imin,
1397 jmin,
1398 face_to_split.kmin(),
1399 imax,
1400 jmax,
1401 kmin,
1402 ));
1403 faces.push(create_face_from_diagonals(
1404 block,
1405 face_to_split.imin(),
1406 jmin,
1407 face_to_split.kmin(),
1408 imin,
1409 jmax,
1410 face_to_split.kmax(),
1411 ));
1412 faces.push(create_face_from_diagonals(
1413 block,
1414 imax,
1415 jmin,
1416 face_to_split.kmin(),
1417 face_to_split.imax(),
1418 jmax,
1419 face_to_split.kmax(),
1420 ));
1421 }
1422
1423 faces
1424 .into_iter()
1425 .filter_map(|mut face| {
1426 if face.is_edge() || face.index_equals(¢er) {
1427 None
1428 } else {
1429 if let Some(idx) = face_to_split.block_index() {
1430 face.set_block_index(idx);
1431 }
1432 Some(face)
1433 }
1434 })
1435 .collect()
1436}
1437
1438pub fn find_face_nearest_point(faces: &[Face], point: [Float; 3]) -> Option<usize> {
1447 faces
1448 .iter()
1449 .enumerate()
1450 .min_by(|(_, a), (_, b)| {
1451 distance(a.centroid(), point)
1452 .partial_cmp(&distance(b.centroid(), point))
1453 .unwrap_or(std::cmp::Ordering::Equal)
1454 })
1455 .map(|(idx, _)| idx)
1456}
1457
1458pub fn reduce_blocks(blocks: &[Block], factor: usize) -> Vec<Block> {
1467 if factor <= 1 {
1468 return blocks.to_vec();
1469 }
1470
1471 fn sampled_indices(max: usize, stride: usize) -> Vec<usize> {
1472 if max == 0 {
1473 return Vec::new();
1474 }
1475 let mut indices: Vec<usize> = (0..max).step_by(stride).collect();
1476 if let Some(&last) = indices.last() {
1477 if last != max - 1 {
1478 indices.push(max - 1);
1479 }
1480 } else {
1481 indices.push(max - 1);
1482 }
1483 indices
1484 }
1485
1486 blocks
1487 .iter()
1488 .map(|block| {
1489 let i_idx = sampled_indices(block.imax, factor);
1490 let j_idx = sampled_indices(block.jmax, factor);
1491 let k_idx = sampled_indices(block.kmax, factor);
1492
1493 let si = i_idx.len();
1494 let sj = j_idx.len();
1495 let sk = k_idx.len();
1496
1497 let mut x = Vec::with_capacity(si * sj * sk);
1498 let mut y = Vec::with_capacity(si * sj * sk);
1499 let mut z = Vec::with_capacity(si * sj * sk);
1500
1501 for &k in &k_idx {
1502 for &j in &j_idx {
1503 for &i in &i_idx {
1504 let (px, py, pz) = block.xyz(i, j, k);
1505 x.push(px);
1506 y.push(py);
1507 z.push(pz);
1508 }
1509 }
1510 }
1511
1512 Block::new(si, sj, sk, x, y, z)
1513 })
1514 .collect()
1515}
1516
1517pub fn rotate_block(block: &Block, rotation: [[Float; 3]; 3]) -> Block {
1526 let mut x = Vec::with_capacity(block.npoints());
1527 let mut y = Vec::with_capacity(block.npoints());
1528 let mut z = Vec::with_capacity(block.npoints());
1529 for k in 0..block.kmax {
1530 for j in 0..block.jmax {
1531 for i in 0..block.imax {
1532 let (px, py, pz) = block.xyz(i, j, k);
1533 x.push(rotation[0][0] * px + rotation[0][1] * py + rotation[0][2] * pz);
1534 y.push(rotation[1][0] * px + rotation[1][1] * py + rotation[1][2] * pz);
1535 z.push(rotation[2][0] * px + rotation[2][1] * py + rotation[2][2] * pz);
1536 }
1537 }
1538 }
1539 Block::new(block.imax, block.jmax, block.kmax, x, y, z)
1540}
1541
1542