axiolid_reference/
triangle_triangle.rs1use axiolid_contracts::Sign;
7use axiolid_core::{Point2, Point3};
8
9use crate::{orient2d, segment_triangle_relation, SegmentTriangleRelation};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum TriangleTriangleRelation {
14 Disjoint,
16 Proper,
18 Touching,
20 Coplanar,
22 DegenerateTriangle,
24}
25
26#[must_use]
32pub fn triangle_triangle_relation(
33 first: [Point3; 3],
34 second: [Point3; 3],
35) -> TriangleTriangleRelation {
36 if degenerate(first) || degenerate(second) {
37 return TriangleTriangleRelation::DegenerateTriangle;
38 }
39
40 let second_in_first_plane =
41 second.map(|point| sign(crate::orient3d(first[0], first[1], first[2], point)));
42 if second_in_first_plane.iter().all(|&side| side == Sign::Zero) {
43 return TriangleTriangleRelation::Coplanar;
44 }
45
46 let mut touching = false;
47 for triangle in [first, second] {
48 let other = if triangle == first { second } else { first };
49 for edge in [
50 [triangle[0], triangle[1]],
51 [triangle[1], triangle[2]],
52 [triangle[2], triangle[0]],
53 ] {
54 match segment_triangle_relation(edge[0], edge[1], other) {
55 SegmentTriangleRelation::Proper => return TriangleTriangleRelation::Proper,
56 SegmentTriangleRelation::Touching => touching = true,
57 SegmentTriangleRelation::Coplanar => {
58 touching |= coplanar_segment_touches_triangle(edge, other);
59 }
60 SegmentTriangleRelation::Disjoint => {}
61 SegmentTriangleRelation::DegenerateSegment
62 | SegmentTriangleRelation::DegenerateTriangle => {
63 unreachable!("validated triangles have non-degenerate edges")
64 }
65 }
66 }
67 }
68
69 if touching {
70 TriangleTriangleRelation::Touching
71 } else {
72 TriangleTriangleRelation::Disjoint
73 }
74}
75
76fn degenerate([a, b, c]: [Point3; 3]) -> bool {
77 (b - a).cross(c - a).length_squared() == 0.0
78}
79
80fn coplanar_segment_touches_triangle(segment: [Point3; 2], triangle: [Point3; 3]) -> bool {
81 let normal = (triangle[1] - triangle[0]).cross(triangle[2] - triangle[0]);
82 let axis = dominant_axis(normal);
83 let [start, end] = segment.map(|point| project(point, axis));
84 let projected = triangle.map(|point| project(point, axis));
85
86 point_in_triangle(start, projected)
87 || point_in_triangle(end, projected)
88 || [
89 [projected[0], projected[1]],
90 [projected[1], projected[2]],
91 [projected[2], projected[0]],
92 ]
93 .into_iter()
94 .any(|edge| segments_touch([start, end], edge))
95}
96
97fn point_in_triangle(point: Point2, [a, b, c]: [Point2; 3]) -> bool {
98 let signs = [
99 sign(orient2d(a, b, point)),
100 sign(orient2d(b, c, point)),
101 sign(orient2d(c, a, point)),
102 ];
103 signs.iter().all(|&side| side != Sign::Negative)
104 || signs.iter().all(|&side| side != Sign::Positive)
105}
106
107fn segments_touch([a, b]: [Point2; 2], [c, d]: [Point2; 2]) -> bool {
108 let ab_c = sign(orient2d(a, b, c));
109 let ab_d = sign(orient2d(a, b, d));
110 let cd_a = sign(orient2d(c, d, a));
111 let cd_b = sign(orient2d(c, d, b));
112 straddles(ab_c, ab_d) && straddles(cd_a, cd_b)
113}
114
115fn straddles(first: Sign, second: Sign) -> bool {
116 first == Sign::Zero || second == Sign::Zero || first != second
117}
118
119fn dominant_axis(vector: Point3) -> usize {
120 let absolute = vector.abs();
121 if absolute.x >= absolute.y && absolute.x >= absolute.z {
122 0
123 } else if absolute.y >= absolute.z {
124 1
125 } else {
126 2
127 }
128}
129
130fn project(point: Point3, axis: usize) -> Point2 {
131 match axis {
132 0 => Point2::new(point.y, point.z),
133 1 => Point2::new(point.x, point.z),
134 _ => Point2::new(point.x, point.y),
135 }
136}
137
138fn sign(value: axiolid_contracts::Certified) -> Sign {
139 value.sign().expect("certified predicates are total")
140}