1use super::EPS;
2use crate::math::{Point, Real, Vector};
3use crate::query;
4use crate::query::PointQuery;
5use crate::shape::{FeatureId, Segment, Triangle};
6use crate::transformation::polygon_intersection::{
7 PolygonIntersectionTolerances, PolylinePointLocation,
8};
9use crate::utils::WBasis;
10use alloc::{vec, vec::Vec};
11use na::Point2;
12
13#[derive(Copy, Clone, Debug, Default)]
14pub struct TriangleTriangleIntersectionPoint {
15 pub p1: Point<Real>,
16}
17
18#[derive(Clone, Debug)]
19pub enum TriangleTriangleIntersection {
20 Segment {
21 a: TriangleTriangleIntersectionPoint,
22 b: TriangleTriangleIntersectionPoint,
23 },
24 Polygon(Vec<TriangleTriangleIntersectionPoint>),
25}
26
27impl Default for TriangleTriangleIntersection {
28 fn default() -> Self {
29 Self::Segment {
30 a: Default::default(),
31 b: Default::default(),
32 }
33 }
34}
35
36pub(crate) fn triangle_triangle_intersection(
37 tri1: &Triangle,
38 tri2: &Triangle,
39 collinearity_epsilon: Real,
40) -> Option<TriangleTriangleIntersection> {
41 let normal1 = tri1.robust_normal();
42 let normal2 = tri2.robust_normal();
43
44 if let Some(intersection_dir) = normal1.cross(&normal2).try_normalize(1.0e-6) {
45 let mut range1 = [
46 (Real::MAX, Point::origin(), FeatureId::Unknown),
47 (-Real::MAX, Point::origin(), FeatureId::Unknown),
48 ];
49 let mut range2 = [
50 (Real::MAX, Point::origin(), FeatureId::Unknown),
51 (-Real::MAX, Point::origin(), FeatureId::Unknown),
52 ];
53
54 let hits1 = [
55 segment_plane_intersection(&tri2.a, &normal2, &Segment::new(tri1.a, tri1.b), 0, (0, 1))
56 .map(|(p, feat)| (intersection_dir.dot(&p.coords), p, feat)),
57 segment_plane_intersection(&tri2.a, &normal2, &Segment::new(tri1.b, tri1.c), 1, (1, 2))
58 .map(|(p, feat)| (intersection_dir.dot(&p.coords), p, feat)),
59 segment_plane_intersection(&tri2.a, &normal2, &Segment::new(tri1.c, tri1.a), 2, (2, 0))
60 .map(|(p, feat)| (intersection_dir.dot(&p.coords), p, feat)),
61 ];
62
63 for hit1 in hits1.into_iter().flatten() {
64 if hit1.0 < range1[0].0 {
65 range1[0] = hit1;
66 }
67 if hit1.0 > range1[1].0 {
68 range1[1] = hit1;
69 }
70 }
71
72 if range1[0].0 >= range1[1].0 {
73 return None;
75 }
76
77 let hits2 = [
78 segment_plane_intersection(&tri1.a, &normal1, &Segment::new(tri2.a, tri2.b), 0, (0, 1))
79 .map(|(p, feat)| (intersection_dir.dot(&p.coords), p, feat)),
80 segment_plane_intersection(&tri1.a, &normal1, &Segment::new(tri2.b, tri2.c), 1, (1, 2))
81 .map(|(p, feat)| (intersection_dir.dot(&p.coords), p, feat)),
82 segment_plane_intersection(&tri1.a, &normal1, &Segment::new(tri2.c, tri2.a), 2, (2, 0))
83 .map(|(p, feat)| (intersection_dir.dot(&p.coords), p, feat)),
84 ];
85
86 for hit2 in hits2.into_iter().flatten() {
87 if hit2.0 < range2[0].0 {
88 range2[0] = hit2;
89 }
90 if hit2.0 > range2[1].0 {
91 range2[1] = hit2;
92 }
93 }
94
95 if range2[0].0 >= range2[1].0 {
96 return None;
98 }
99
100 if range1[1].0 <= range2[0].0 + EPS || range2[1].0 <= range1[0].0 + EPS {
101 return None;
103 }
104
105 let a = if range2[0].0 > range1[0].0 + EPS {
106 TriangleTriangleIntersectionPoint { p1: range2[0].1 }
107 } else {
108 TriangleTriangleIntersectionPoint { p1: range1[0].1 }
109 };
110
111 let b = if range2[1].0 < range1[1].0 - EPS {
112 TriangleTriangleIntersectionPoint { p1: range2[1].1 }
113 } else {
114 TriangleTriangleIntersectionPoint { p1: range1[1].1 }
115 };
116
117 Some(TriangleTriangleIntersection::Segment { a, b })
118 } else {
119 let unit_normal2 = normal2.normalize();
120 if (tri1.a - tri2.a).dot(&unit_normal2) < EPS {
121 let basis = unit_normal2.orthonormal_basis();
122 let proj = |vect: Vector<Real>| Point2::new(vect.dot(&basis[0]), vect.dot(&basis[1]));
123
124 let mut intersections = vec![];
125
126 let pts1 = tri1.vertices();
127 let pts2 = tri2.vertices();
128 let poly1 = [
129 proj(tri1.a - tri2.a),
130 proj(tri1.b - tri2.a),
131 proj(tri1.c - tri2.a),
132 ];
133 let poly2 = [
134 proj(Vector::zeros()), proj(tri2.b - tri2.a),
136 proj(tri2.c - tri2.a),
137 ];
138
139 let convert_loc = |loc, pts: &[Point<Real>; 3]| match loc {
140 PolylinePointLocation::OnVertex(vid) => (FeatureId::Vertex(vid as u32), pts[vid]),
141 PolylinePointLocation::OnEdge(vid1, vid2, bcoords) => (
142 match (vid1, vid2) {
143 (0, 1) | (1, 0) => FeatureId::Edge(0),
144 (1, 2) | (2, 1) => FeatureId::Edge(1),
145 (2, 0) | (0, 2) => FeatureId::Edge(2),
146 _ => unreachable!(),
147 },
148 pts[vid1] * bcoords[0] + pts[vid2].coords * bcoords[1],
149 ),
150 };
151
152 crate::transformation::convex_polygons_intersection_with_tolerances(
153 &poly1,
154 &poly2,
155 PolygonIntersectionTolerances {
156 collinearity_epsilon,
157 },
158 |pt1, pt2| {
159 let intersection = match (pt1, pt2) {
160 (Some(loc1), Some(loc2)) => {
161 let (_f1, p1) = convert_loc(loc1, pts1);
162 let (_f2, _p2) = convert_loc(loc2, pts2);
163 TriangleTriangleIntersectionPoint { p1 }
164 }
165 (Some(loc1), None) => {
166 let (_f1, p1) = convert_loc(loc1, pts1);
167 TriangleTriangleIntersectionPoint { p1 }
168 }
169 (None, Some(loc2)) => {
170 let (_f2, p2) = convert_loc(loc2, pts2);
171 TriangleTriangleIntersectionPoint { p1: p2 }
172 }
173 (None, None) => unreachable!(),
174 };
175 intersections.push(intersection);
176 },
177 );
178
179 #[cfg(feature = "std")]
180 {
181 const DEBUG_INTERSECTIONS: bool = false;
184 if DEBUG_INTERSECTIONS {
185 debug_check_intersections(tri1, tri2, &basis, &poly1, &poly2, &intersections);
186 }
187 }
188
189 Some(TriangleTriangleIntersection::Polygon(intersections))
190 } else {
191 None
192 }
193 }
194}
195
196fn segment_plane_intersection(
197 plane_center: &Point<Real>,
198 plane_normal: &Vector<Real>,
199 segment: &Segment,
200 eid: u32,
201 vids: (u32, u32),
202) -> Option<(Point<Real>, FeatureId)> {
203 let dir = segment.b - segment.a;
204 let dir_norm = dir.norm();
205
206 let time_of_impact =
207 query::details::line_toi_with_halfspace(plane_center, plane_normal, &segment.a, &dir)?;
208 let scaled_toi = time_of_impact * dir_norm;
209
210 if scaled_toi < -EPS || scaled_toi > dir_norm + EPS {
211 None
212 } else if scaled_toi <= EPS {
213 Some((segment.a, FeatureId::Vertex(vids.0)))
214 } else if scaled_toi >= dir_norm - EPS {
215 Some((segment.b, FeatureId::Vertex(vids.1)))
216 } else {
217 Some((segment.a + dir * time_of_impact, FeatureId::Edge(eid)))
218 }
219}
220
221#[cfg(feature = "std")]
229fn debug_check_intersections(
230 tri1: &Triangle,
231 tri2: &Triangle,
232 basis: &[na::Vector3<Real>; 2],
233 poly1: &[Point2<Real>], poly2: &[Point2<Real>], intersections: &[TriangleTriangleIntersectionPoint],
236) {
237 use std::{print, println};
238
239 let proj = |vect: Vector<Real>| Point2::new(vect.dot(&basis[0]), vect.dot(&basis[1]));
240 let mut incorrect = false;
241 for pt in intersections {
242 if !tri1
243 .project_local_point(&pt.p1, false)
244 .is_inside_eps(&pt.p1, 1.0e-5)
245 {
246 incorrect = true;
247 break;
248 }
249
250 if !tri2
251 .project_local_point(&pt.p1, false)
252 .is_inside_eps(&pt.p1, 1.0e-5)
253 {
254 incorrect = true;
255 break;
256 }
257 }
258
259 if incorrect {
260 let proj_inter: Vec<_> = intersections.iter().map(|p| proj(p.p1 - tri2.a)).collect();
261 println!("-------- (copy/paste the following on Desmos graphing)");
262 println!("A=({:.2},{:.2})", poly1[0].x, poly1[0].y);
263 println!("B=({:.2},{:.2})", poly1[1].x, poly1[1].y);
264 println!("C=({:.2},{:.2})", poly1[2].x, poly1[2].y);
265 println!("D=({:.2},{:.2})", poly2[0].x, poly2[0].y);
266 println!("E=({:.2},{:.2})", poly2[1].x, poly2[1].y);
267 println!("F=({:.2},{:.2})", poly2[2].x, poly2[2].y);
268
269 let lbls = ["G", "H", "I", "J", "K", "L", "M", "N", "O"];
270 for (i, inter) in proj_inter.iter().enumerate() {
271 println!("{}=({:.2},{:.2})", lbls[i], inter.x, inter.y);
272 }
273
274 println!("X=polygon(A,B,C)");
276 println!("Y=polygon(D,E,F)");
277 print!("Z=polygon({}", lbls[0]);
278 for lbl in lbls.iter().skip(1) {
279 print!(",{lbl}");
280 }
281 println!(")");
282
283 println!("~~~~~~~ (copy/paste the following input in the `intersect_triangle_common_vertex` test)");
284 println!("(Triangle::new(");
285 for pt1 in poly1 {
286 println!(" Point2::new({},{}),", pt1.x, pt1.y);
287 }
288 println!("),");
289 println!("Triangle::new(");
290 for pt2 in poly2 {
291 println!(" Point2::new({},{}),", pt2.x, pt2.y);
292 }
293 println!("),),");
294 }
295}