1use super::primitives::{orientation_2, Orientation, Point2};
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub enum KirkpatrickError {
27 EmptyTriangulation,
29 TooFewVertices,
31 OutsideBoundingTriangle,
33}
34
35impl core::fmt::Display for KirkpatrickError {
36 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
37 match self {
38 Self::EmptyTriangulation => write!(f, "kirkpatrick: empty triangulation"),
39 Self::TooFewVertices => write!(f, "kirkpatrick: need at least 3 vertices"),
40 Self::OutsideBoundingTriangle => {
41 write!(f, "kirkpatrick: query outside bounding triangle")
42 }
43 }
44 }
45}
46
47impl std::error::Error for KirkpatrickError {}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq)]
57struct Tri {
58 v: [usize; 3],
59 face: usize,
62 level: usize,
64}
65
66#[derive(Debug, Clone)]
68struct Level {
69 triangles: Vec<Tri>,
70 children: Vec<Vec<usize>>,
74}
75
76pub struct KirkpatrickHierarchy {
85 levels: Vec<Level>,
87 vertices: Vec<Point2>,
89 bbox_tri: [usize; 3],
91}
92
93impl std::fmt::Debug for KirkpatrickHierarchy {
94 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
95 f.debug_struct("KirkpatrickHierarchy")
96 .field("num_levels", &self.levels.len())
97 .field("num_vertices", &self.vertices.len())
98 .field("bbox_tri", &self.bbox_tri)
99 .finish()
100 }
101}
102
103fn point_in_tri(p: Point2, a: Point2, b: Point2, c: Point2) -> bool {
106 let o1 = orientation_2(a, b, p);
107 let o2 = orientation_2(b, c, p);
108 let o3 = orientation_2(c, a, p);
109 o1 != Orientation::Clockwise && o2 != Orientation::Clockwise && o3 != Orientation::Clockwise
110}
111
112fn bounding_triangle(points: &[Point2]) -> [Point2; 3] {
114 let mut min_x = f64::INFINITY;
115 let mut min_y = f64::INFINITY;
116 let mut max_x = f64::NEG_INFINITY;
117 let mut max_y = f64::NEG_INFINITY;
118 for &p in points {
119 min_x = min_x.min(p.x);
120 min_y = min_y.min(p.y);
121 max_x = max_x.max(p.x);
122 max_y = max_y.max(p.y);
123 }
124 let dx = max_x - min_x;
125 let dy = max_y - min_y;
126 let d = dx.max(dy).max(1.0) * 10.0;
127 let cx = (min_x + max_x) * 0.5;
128 let cy = (min_y + max_y) * 0.5;
129 [
131 Point2::new(cx, cy + d),
132 Point2::new(cx - d, cy - d * 0.5),
133 Point2::new(cx + d, cy - d * 0.5),
134 ]
135}
136
137impl KirkpatrickHierarchy {
138 pub fn build(
148 vertices: &[Point2],
149 triangles: &[[usize; 3]],
150 face_labels: &[usize],
151 ) -> Result<Self, KirkpatrickError> {
152 if vertices.len() < 3 {
153 return Err(KirkpatrickError::TooFewVertices);
154 }
155 if triangles.is_empty() {
156 return Err(KirkpatrickError::EmptyTriangulation);
157 }
158
159 let bbox = bounding_triangle(vertices);
161 let n = vertices.len();
162 let mut all_vertices = vertices.to_vec();
163 all_vertices.push(bbox[0]);
164 all_vertices.push(bbox[1]);
165 all_vertices.push(bbox[2]);
166 let bv = [n, n + 1, n + 2];
167
168 let mut edge_count: std::collections::HashMap<(usize, usize), usize> =
201 std::collections::HashMap::new();
202 for &t in triangles {
203 for i in 0..3 {
204 let a = t[i];
205 let b = t[(i + 1) % 3];
206 let key = if a < b { (a, b) } else { (b, a) };
207 *edge_count.entry(key).or_insert(0) += 1;
208 }
209 }
210
211 let mut finest_tris: Vec<Tri> = Vec::with_capacity(triangles.len() + 20);
212 for (i, &t) in triangles.iter().enumerate() {
213 finest_tris.push(Tri {
214 v: t,
215 face: face_labels[i],
216 level: 0,
217 });
218 }
219
220 let mut boundary_edges: Vec<(usize, usize)> = Vec::new();
223 for &t in triangles {
224 for i in 0..3 {
225 let a = t[i];
226 let b = t[(i + 1) % 3];
227 let key = if a < b { (a, b) } else { (b, a) };
228 if edge_count[&key] == 1 {
229 for &bv in &bv {
234 let o = orientation_2(all_vertices[a], all_vertices[b], all_vertices[bv]);
235 if o == Orientation::CounterClockwise {
236 boundary_edges.push((a, b));
237 finest_tris.push(Tri {
238 v: [a, bv, b],
239 face: usize::MAX, level: 0,
241 });
242 break;
243 }
244 }
245 }
246 }
247 }
248
249 finest_tris.push(Tri {
251 v: [bv[0], bv[1], bv[2]],
252 face: usize::MAX,
253 level: 0,
254 });
255
256 let level0 = Level {
259 triangles: finest_tris,
260 children: Vec::new(),
261 };
262
263 let mut levels: Vec<Level> = Vec::new();
265 levels.push(level0.clone());
266
267 const MAX_DEGREE: usize = 12;
268 const MIN_TRIANGLES: usize = 4;
269
270 loop {
271 let current = levels.last().unwrap();
272 if current.triangles.len() <= MIN_TRIANGLES {
273 break;
274 }
275
276 let next = build_next_level(current, &all_vertices, MAX_DEGREE);
277 if next.triangles.len() >= current.triangles.len() {
278 break;
280 }
281 levels.push(next);
282 }
283
284 for li in (1..levels.len()).rev() {
288 let finer = &levels[li - 1].triangles.clone();
289 let coarse = &levels[li].triangles;
290 let mut children = vec![Vec::new(); coarse.len()];
291
292 for (fi, ft) in finer.iter().enumerate() {
293 let cx =
295 (all_vertices[ft.v[0]].x + all_vertices[ft.v[1]].x + all_vertices[ft.v[2]].x)
296 / 3.0;
297 let cy =
298 (all_vertices[ft.v[0]].y + all_vertices[ft.v[1]].y + all_vertices[ft.v[2]].y)
299 / 3.0;
300 let centroid = Point2::new(cx, cy);
301
302 for (ci, ct) in coarse.iter().enumerate() {
303 if point_in_tri(
304 centroid,
305 all_vertices[ct.v[0]],
306 all_vertices[ct.v[1]],
307 all_vertices[ct.v[2]],
308 ) {
309 children[ci].push(fi);
310 break;
311 }
312 }
313 }
314
315 levels[li].children = children;
316 }
317
318 levels[0].children = vec![Vec::new(); levels[0].triangles.len()];
320
321 Ok(KirkpatrickHierarchy {
322 levels,
323 vertices: all_vertices,
324 bbox_tri: bv,
325 })
326 }
327
328 pub fn locate(&self, query: Point2) -> Result<Option<usize>, KirkpatrickError> {
334 let bv = self.bbox_tri;
336 if !point_in_tri(
337 query,
338 self.vertices[bv[0]],
339 self.vertices[bv[1]],
340 self.vertices[bv[2]],
341 ) {
342 return Err(KirkpatrickError::OutsideBoundingTriangle);
343 }
344
345 let coarsest = self.levels.len() - 1;
347 let coarse_tris = &self.levels[coarsest].triangles;
348
349 let mut current_tri = 0usize;
351 for (i, t) in coarse_tris.iter().enumerate() {
352 if point_in_tri(
353 query,
354 self.vertices[t.v[0]],
355 self.vertices[t.v[1]],
356 self.vertices[t.v[2]],
357 ) {
358 current_tri = i;
359 break;
360 }
361 }
362
363 for li in (0..coarsest).rev() {
365 let children = &self.levels[li + 1].children[current_tri];
366 if children.is_empty() {
367 let tri = &self.levels[li + 1].triangles[current_tri];
370 return Ok(if tri.face == usize::MAX {
371 None
372 } else {
373 Some(tri.face)
374 });
375 }
376
377 let finer_tris = &self.levels[li].triangles;
379 let mut found = false;
380 for &ci in children {
381 let t = &finer_tris[ci];
382 if point_in_tri(
383 query,
384 self.vertices[t.v[0]],
385 self.vertices[t.v[1]],
386 self.vertices[t.v[2]],
387 ) {
388 current_tri = ci;
389 found = true;
390 break;
391 }
392 }
393 if !found {
394 return Ok(None);
396 }
397 }
398
399 let tri = &self.levels[0].triangles[current_tri];
401 Ok(if tri.face == usize::MAX {
402 None
403 } else {
404 Some(tri.face)
405 })
406 }
407
408 pub fn num_levels(&self) -> usize {
410 self.levels.len()
411 }
412
413 pub fn num_finest_triangles(&self) -> usize {
415 self.levels[0].triangles.len()
416 }
417
418 pub fn num_coarsest_triangles(&self) -> usize {
420 self.levels.last().unwrap().triangles.len()
421 }
422
423 pub fn locate_brute_force(&self, query: Point2) -> Option<usize> {
426 for t in &self.levels[0].triangles {
427 if point_in_tri(
428 query,
429 self.vertices[t.v[0]],
430 self.vertices[t.v[1]],
431 self.vertices[t.v[2]],
432 ) {
433 return if t.face == usize::MAX {
434 None
435 } else {
436 Some(t.face)
437 };
438 }
439 }
440 None
441 }
442}
443
444fn build_next_level(current: &Level, vertices: &[Point2], max_degree: usize) -> Level {
447 let mut vert_tris: Vec<Vec<usize>> = vec![Vec::new(); vertices.len()];
449 for (ti, t) in current.triangles.iter().enumerate() {
450 for &v in &t.v {
451 vert_tris[v].push(ti);
452 }
453 }
454
455 let n_orig = vertices.len() - 3;
463 let mut removed = vec![false; vertices.len()];
464 let mut to_remove: Vec<usize> = Vec::new();
465
466 for v in 0..n_orig {
467 if removed[v] {
468 continue;
469 }
470 if vert_tris[v].len() > max_degree {
471 continue;
472 }
473 let is_interior = vert_tris[v]
475 .iter()
476 .all(|&ti| current.triangles[ti].v.iter().all(|&vv| vv < n_orig));
477 if !is_interior {
478 continue;
479 }
480 to_remove.push(v);
481 removed[v] = true;
482 for &ti in &vert_tris[v] {
484 for &nv in ¤t.triangles[ti].v {
485 if nv != v && nv < n_orig {
486 removed[nv] = true;
487 }
488 }
489 }
490 }
491
492 if to_remove.is_empty() {
493 return Level {
495 triangles: current.triangles.clone(),
496 children: Vec::new(),
497 };
498 }
499
500 let mut new_tris: Vec<Tri> = Vec::new();
504 let mut dead: Vec<bool> = vec![false; current.triangles.len()];
505
506 for &v in &to_remove {
508 for &ti in &vert_tris[v] {
509 dead[ti] = true;
510 }
511 }
512
513 for (ti, t) in current.triangles.iter().enumerate() {
515 if !dead[ti] {
516 new_tris.push(Tri {
517 v: t.v,
518 face: t.face,
519 level: t.level + 1,
520 });
521 }
522 }
523
524 for &v in &to_remove {
528 let star = &vert_tris[v];
530 if star.is_empty() {
531 continue;
532 }
533
534 let boundary = trace_hole_boundary(star, v, ¤t.triangles);
536
537 if boundary.len() < 3 {
538 continue;
539 }
540
541 for i in 1..boundary.len() - 1 {
543 let tri = Tri {
544 v: [boundary[0], boundary[i], boundary[i + 1]],
545 face: usize::MAX,
546 level: current.triangles[0].level + 1,
547 };
548 let o = orientation_2(vertices[tri.v[0]], vertices[tri.v[1]], vertices[tri.v[2]]);
550 if o == Orientation::Clockwise {
551 new_tris.push(Tri {
552 v: [tri.v[0], tri.v[2], tri.v[1]],
553 face: usize::MAX,
554 level: tri.level,
555 });
556 } else {
557 new_tris.push(tri);
558 }
559 }
560 }
561
562 Level {
563 triangles: new_tris,
564 children: Vec::new(),
565 }
566}
567
568fn trace_hole_boundary(star: &[usize], v: usize, triangles: &[Tri]) -> Vec<usize> {
571 let mut edge_map: std::collections::HashMap<(usize, usize), usize> =
575 std::collections::HashMap::new();
576
577 for &ti in star {
578 let t = &triangles[ti];
579 for i in 0..3 {
580 let a = t.v[i];
581 let b = t.v[(i + 1) % 3];
582 if a != v && b != v {
583 let key = if a < b { (a, b) } else { (b, a) };
584 *edge_map.entry(key).or_insert(0) += 1;
585 }
586 }
587 }
588
589 let mut boundary_edges: Vec<(usize, usize)> = Vec::new();
591 for &ti in star {
592 let t = &triangles[ti];
593 for i in 0..3 {
594 let a = t.v[i];
595 let b = t.v[(i + 1) % 3];
596 if a != v && b != v {
597 let key = if a < b { (a, b) } else { (b, a) };
598 if edge_map[&key] == 1 {
599 boundary_edges.push((a, b));
601 }
602 }
603 }
604 }
605
606 let mut boundary: Vec<usize> = Vec::new();
608 if boundary_edges.is_empty() {
609 return boundary;
610 }
611
612 let mut used = vec![false; boundary_edges.len()];
613 let mut current = boundary_edges[0];
614 boundary.push(current.0);
615 used[0] = true;
616
617 loop {
618 boundary.push(current.1);
619 let mut found = false;
621 for (i, &(a, b)) in boundary_edges.iter().enumerate() {
622 if !used[i] && a == current.1 {
623 used[i] = true;
624 current = (a, b);
625 found = true;
626 break;
627 }
628 }
629 if !found {
630 break;
631 }
632 }
633
634 if boundary.len() > 1 && boundary.last() == boundary.first() {
636 boundary.pop();
637 }
638
639 boundary
640}
641
642#[cfg(test)]
647mod tests {
648 use super::*;
649
650 fn pt(x: f64, y: f64) -> Point2 {
651 Point2::new(x, y)
652 }
653
654 #[test]
657 fn single_triangle_builds() {
658 let verts = vec![pt(0.0, 0.0), pt(4.0, 0.0), pt(2.0, 4.0)];
659 let tris = vec![[0, 1, 2]];
660 let faces = vec![0];
661 let h = KirkpatrickHierarchy::build(&verts, &tris, &faces).unwrap();
662 assert!(h.num_levels() >= 1);
663 assert!(h.num_finest_triangles() >= 1);
664 }
665
666 #[test]
667 fn two_triangles_builds() {
668 let verts = vec![pt(0.0, 0.0), pt(2.0, 0.0), pt(4.0, 0.0), pt(2.0, 4.0)];
669 let tris = vec![[0, 1, 3], [1, 2, 3]];
670 let faces = vec![0, 1];
671 let h = KirkpatrickHierarchy::build(&verts, &tris, &faces).unwrap();
672 assert!(h.num_levels() >= 1);
673 }
674
675 #[test]
676 fn empty_triangulation_errors() {
677 let verts = vec![pt(0.0, 0.0), pt(1.0, 0.0), pt(0.0, 1.0)];
678 let result = KirkpatrickHierarchy::build(&verts, &[], &[]);
679 assert!(matches!(result, Err(KirkpatrickError::EmptyTriangulation)));
680 }
681
682 #[test]
683 fn too_few_vertices_errors() {
684 let verts = vec![pt(0.0, 0.0), pt(1.0, 0.0)];
685 let result = KirkpatrickHierarchy::build(&verts, &[[0, 0, 0]], &[0]);
686 assert!(matches!(result, Err(KirkpatrickError::TooFewVertices)));
687 }
688
689 #[test]
692 fn locate_in_single_triangle() {
693 let verts = vec![pt(0.0, 0.0), pt(4.0, 0.0), pt(2.0, 4.0)];
694 let tris = vec![[0, 1, 2]];
695 let faces = vec![0];
696 let h = KirkpatrickHierarchy::build(&verts, &tris, &faces).unwrap();
697 let result = h.locate(pt(2.0, 1.0)).unwrap();
698 assert_eq!(result, Some(0));
699 }
700
701 #[test]
702 fn locate_in_two_triangles() {
703 let verts = vec![pt(0.0, 0.0), pt(2.0, 0.0), pt(4.0, 0.0), pt(2.0, 4.0)];
704 let tris = vec![[0, 1, 3], [1, 2, 3]];
705 let faces = vec![0, 1];
706 let h = KirkpatrickHierarchy::build(&verts, &tris, &faces).unwrap();
707 assert_eq!(h.locate(pt(1.0, 1.0)).unwrap(), Some(0));
709 assert_eq!(h.locate(pt(3.0, 1.0)).unwrap(), Some(1));
711 }
712
713 #[test]
714 fn locate_outside_bbox_errors() {
715 let verts = vec![pt(0.0, 0.0), pt(4.0, 0.0), pt(2.0, 4.0)];
716 let tris = vec![[0, 1, 2]];
717 let faces = vec![0];
718 let h = KirkpatrickHierarchy::build(&verts, &tris, &faces).unwrap();
719 assert!(matches!(
720 h.locate(pt(1000.0, 1000.0)),
721 Err(KirkpatrickError::OutsideBoundingTriangle)
722 ));
723 }
724
725 fn build_grid_triangulation(
728 nx: usize,
729 ny: usize,
730 ) -> (Vec<Point2>, Vec<[usize; 3]>, Vec<usize>) {
731 let mut verts = Vec::new();
732 for j in 0..=ny {
733 for i in 0..=nx {
734 verts.push(pt(i as f64, j as f64));
735 }
736 }
737 let idx = |i: usize, j: usize| j * (nx + 1) + i;
738
739 let mut tris = Vec::new();
740 let mut faces = Vec::new();
741 let mut fi = 0;
742 for j in 0..ny {
743 for i in 0..nx {
744 let a = idx(i, j);
745 let b = idx(i + 1, j);
746 let c = idx(i + 1, j + 1);
747 let d = idx(i, j + 1);
748 tris.push([a, b, c]);
749 faces.push(fi);
750 fi += 1;
751 tris.push([a, c, d]);
752 faces.push(fi);
753 fi += 1;
754 }
755 }
756 (verts, tris, faces)
757 }
758
759 #[test]
760 fn grid_2x2_locate_all_faces() {
761 let (verts, tris, faces) = build_grid_triangulation(2, 2);
762 let h = KirkpatrickHierarchy::build(&verts, &tris, &faces).unwrap();
763
764 for j in 0..2 {
766 for i in 0..2 {
767 let qx = i as f64 + 0.25;
768 let qy = j as f64 + 0.25;
769 let result = h.locate(pt(qx, qy)).unwrap();
770 assert!(
771 result.is_some(),
772 "point ({}, {}) should be in a face",
773 qx,
774 qy
775 );
776 }
777 }
778 }
779
780 #[test]
781 fn grid_4x4_locate_matches_brute_force() {
782 let (verts, tris, faces) = build_grid_triangulation(4, 4);
783 let h = KirkpatrickHierarchy::build(&verts, &tris, &faces).unwrap();
784
785 for j in 0..4 {
786 for i in 0..4 {
787 let qx = i as f64 + 0.3;
788 let qy = j as f64 + 0.3;
789 let p = pt(qx, qy);
790 let dag = h.locate(p).unwrap();
791 let bf = h.locate_brute_force(p);
792 assert_eq!(
794 dag.is_some(),
795 bf.is_some(),
796 "mismatch at ({}, {}): dag={:?}, bf={:?}",
797 qx,
798 qy,
799 dag,
800 bf
801 );
802 }
803 }
804 }
805
806 #[test]
807 fn grid_4x4_locate_edge_centers() {
808 let (verts, tris, faces) = build_grid_triangulation(4, 4);
809 let h = KirkpatrickHierarchy::build(&verts, &tris, &faces).unwrap();
810
811 for i in 0..4 {
813 let p = pt(i as f64 + 0.5, 2.0);
814 let result = h.locate(p);
815 assert!(
816 result.is_ok(),
817 "edge point ({}, 2) should be locatable",
818 p.x
819 );
820 }
821 }
822
823 #[test]
826 fn hierarchy_coarsens() {
827 let (verts, tris, faces) = build_grid_triangulation(4, 4);
828 let h = KirkpatrickHierarchy::build(&verts, &tris, &faces).unwrap();
829 assert!(h.num_coarsest_triangles() < h.num_finest_triangles());
831 assert!(h.num_coarsest_triangles() >= 1);
833 assert!(h.num_levels() >= 2);
835 }
836
837 #[test]
838 fn larger_grid_builds_and_locates() {
839 let (verts, tris, faces) = build_grid_triangulation(8, 8);
840 let h = KirkpatrickHierarchy::build(&verts, &tris, &faces).unwrap();
841 assert!(h.num_levels() >= 2);
842
843 for j in 0..8 {
845 for i in 0..8 {
846 let qx = i as f64 + 0.5;
847 let qy = j as f64 + 0.5;
848 let result = h.locate(pt(qx, qy)).unwrap();
849 assert!(
850 result.is_some(),
851 "point ({}, {}) should be in a face",
852 qx,
853 qy
854 );
855 }
856 }
857 }
858
859 #[test]
862 fn error_display() {
863 assert!(KirkpatrickError::EmptyTriangulation
864 .to_string()
865 .contains("empty"));
866 assert!(KirkpatrickError::TooFewVertices
867 .to_string()
868 .contains("at least 3"));
869 assert!(KirkpatrickError::OutsideBoundingTriangle
870 .to_string()
871 .contains("bounding triangle"));
872 }
873
874 #[test]
877 fn same_input_produces_same_hierarchy() {
878 let (verts, tris, faces) = build_grid_triangulation(4, 4);
879 let h1 = KirkpatrickHierarchy::build(&verts, &tris, &faces).unwrap();
880 let h2 = KirkpatrickHierarchy::build(&verts, &tris, &faces).unwrap();
881
882 assert_eq!(h1.num_levels(), h2.num_levels());
883 assert_eq!(h1.num_finest_triangles(), h2.num_finest_triangles());
884
885 for j in 0..4 {
886 for i in 0..4 {
887 let p = pt(i as f64 + 0.3, j as f64 + 0.3);
888 assert_eq!(h1.locate(p), h2.locate(p), "mismatch at ({}, {})", p.x, p.y);
889 }
890 }
891 }
892
893 #[test]
896 fn exterior_point_returns_none() {
897 let (verts, tris, faces) = build_grid_triangulation(2, 2);
898 let h = KirkpatrickHierarchy::build(&verts, &tris, &faces).unwrap();
899
900 let result = h.locate(pt(-0.5, -0.5));
904 assert!(result.is_ok());
906 assert_eq!(result.unwrap(), None);
907 }
908}