Skip to main content

parry2d/query/point/
point_triangle.rs

1use crate::math::{Real, Vector, DIM};
2use crate::query::{PointProjection, PointQuery, PointQueryWithLocation};
3use crate::shape::{FeatureId, Segment, SegmentPointLocation, Triangle, TrianglePointLocation};
4#[cfg(feature = "dim3")]
5use crate::utils::relative_eq_vector;
6
7#[inline]
8fn compute_result(pt: Vector, proj: Vector) -> PointProjection {
9    #[cfg(feature = "dim2")]
10    {
11        PointProjection::new(pt == proj, proj)
12    }
13
14    #[cfg(feature = "dim3")]
15    {
16        // TODO: is this acceptable to assume the point is inside of the
17        // triangle if it is close enough?
18        let inside = relative_eq_vector(proj, pt);
19        PointProjection::new(inside, proj)
20    }
21}
22
23impl PointQuery for Triangle {
24    #[inline]
25    fn project_local_point(&self, pt: Vector, solid: bool) -> PointProjection {
26        self.project_local_point_and_get_location(pt, solid).0
27    }
28
29    #[inline]
30    fn project_local_point_and_get_feature(&self, pt: Vector) -> (PointProjection, FeatureId) {
31        let (proj, loc) = if DIM == 2 {
32            self.project_local_point_and_get_location(pt, false)
33        } else {
34            self.project_local_point_and_get_location(pt, true)
35        };
36
37        let feature = match loc {
38            TrianglePointLocation::OnVertex(i) => FeatureId::Vertex(i),
39            #[cfg(feature = "dim3")]
40            TrianglePointLocation::OnEdge(i, _) => FeatureId::Edge(i),
41            #[cfg(feature = "dim2")]
42            TrianglePointLocation::OnEdge(i, _) => FeatureId::Face(i),
43            TrianglePointLocation::OnFace(i, _) => FeatureId::Face(i),
44            TrianglePointLocation::OnSolid => FeatureId::Face(0),
45        };
46
47        (proj, feature)
48    }
49
50    // NOTE: the default implementation of `.distance_to_point(...)` will return the error that was
51    // eaten by the `::approx_eq(...)` on `project_point(...)`.
52}
53
54impl PointQueryWithLocation for Triangle {
55    type Location = TrianglePointLocation;
56
57    #[inline]
58    fn project_local_point_and_get_location(
59        &self,
60        pt: Vector,
61        solid: bool,
62    ) -> (PointProjection, Self::Location) {
63        // To understand the ideas, consider reading the slides below
64        // https://box2d.org/files/ErinCatto_GJK_GDC2010.pdf
65
66        let a = self.a;
67        let b = self.b;
68        let c = self.c;
69
70        let ab = b - a;
71        let ac = c - a;
72        let ap = pt - a;
73
74        let ab_ap = ab.dot(ap);
75        let ac_ap = ac.dot(ap);
76
77        if ab_ap <= 0.0 && ac_ap <= 0.0 {
78            // Voronoï region of `a`.
79            return (compute_result(pt, a), TrianglePointLocation::OnVertex(0));
80        }
81
82        let bp = pt - b;
83        let ab_bp = ab.dot(bp);
84        let ac_bp = ac.dot(bp);
85
86        if ab_bp >= 0.0 && ac_bp <= ab_bp {
87            // Voronoï region of `b`.
88            return (compute_result(pt, b), TrianglePointLocation::OnVertex(1));
89        }
90
91        let cp = pt - c;
92        let ab_cp = ab.dot(cp);
93        let ac_cp = ac.dot(cp);
94
95        if ac_cp >= 0.0 && ab_cp <= ac_cp {
96            // Voronoï region of `c`.
97            return (compute_result(pt, c), TrianglePointLocation::OnVertex(2));
98        }
99
100        enum ProjectionInfo {
101            OnAB,
102            OnAC,
103            OnBC,
104            // The usize indicates if we are on the CW side (0) or CCW side (1) of the face.
105            OnFace(usize, Real, Real, Real),
106        }
107
108        // Checks on which edge voronoï region the point is.
109        // For 2D and 3D, it uses explicit cross/perp products that are
110        // more numerically stable.
111        fn stable_check_edges_voronoi(
112            ab: Vector,
113            ac: Vector,
114            bc: Vector,
115            ap: Vector,
116            bp: Vector,
117            cp: Vector,
118            ab_ap: Real,
119            ab_bp: Real,
120            ac_ap: Real,
121            ac_cp: Real,
122            ac_bp: Real,
123            ab_cp: Real,
124        ) -> ProjectionInfo {
125            #[cfg(feature = "dim2")]
126            {
127                let n = ab.perp_dot(ac);
128                let vc = n * ab.perp_dot(ap);
129                if vc < 0.0 && ab_ap >= 0.0 && ab_bp <= 0.0 {
130                    return ProjectionInfo::OnAB;
131                }
132
133                let vb = -n * ac.perp_dot(cp);
134                if vb < 0.0 && ac_ap >= 0.0 && ac_cp <= 0.0 {
135                    return ProjectionInfo::OnAC;
136                }
137
138                let va = n * bc.perp_dot(bp);
139                if va < 0.0 && ac_bp - ab_bp >= 0.0 && ab_cp - ac_cp >= 0.0 {
140                    return ProjectionInfo::OnBC;
141                }
142
143                ProjectionInfo::OnFace(0, va, vb, vc)
144            }
145            #[cfg(feature = "dim3")]
146            {
147                let n;
148
149                #[cfg(feature = "improved_fixed_point_support")]
150                {
151                    let scaled_n = ab.cross(ac);
152                    n = scaled_n.try_normalize(0.0).unwrap_or(scaled_n);
153                }
154
155                #[cfg(not(feature = "improved_fixed_point_support"))]
156                {
157                    n = ab.cross(ac);
158                }
159
160                let vc = n.dot(ab.cross(ap));
161                if vc < 0.0 && ab_ap >= 0.0 && ab_bp <= 0.0 {
162                    return ProjectionInfo::OnAB;
163                }
164
165                let vb = -n.dot(ac.cross(cp));
166                if vb < 0.0 && ac_ap >= 0.0 && ac_cp <= 0.0 {
167                    return ProjectionInfo::OnAC;
168                }
169
170                let va = n.dot(bc.cross(bp));
171                if va < 0.0 && ac_bp - ab_bp >= 0.0 && ab_cp - ac_cp >= 0.0 {
172                    return ProjectionInfo::OnBC;
173                }
174
175                let clockwise = if n.dot(ap) >= 0.0 { 0 } else { 1 };
176
177                ProjectionInfo::OnFace(clockwise, va, vb, vc)
178            }
179        }
180
181        let bc = c - b;
182        match stable_check_edges_voronoi(
183            ab, ac, bc, ap, bp, cp, ab_ap, ab_bp, ac_ap, ac_cp, ac_bp, ab_cp,
184        ) {
185            ProjectionInfo::OnAB => {
186                // Voronoï region of `ab`.
187                let v = ab_ap / ab.length_squared();
188                let bcoords = [1.0 - v, v];
189
190                let res = a + ab * v;
191                return (
192                    compute_result(pt, res),
193                    TrianglePointLocation::OnEdge(0, bcoords),
194                );
195            }
196            ProjectionInfo::OnAC => {
197                // Voronoï region of `ac`.
198                let w = ac_ap / ac.length_squared();
199                let bcoords = [1.0 - w, w];
200
201                let res = a + ac * w;
202                return (
203                    compute_result(pt, res),
204                    TrianglePointLocation::OnEdge(2, bcoords),
205                );
206            }
207            ProjectionInfo::OnBC => {
208                // Voronoï region of `bc`.
209                let w = bc.dot(bp) / bc.length_squared();
210                let bcoords = [1.0 - w, w];
211
212                let res = b + bc * w;
213                return (
214                    compute_result(pt, res),
215                    TrianglePointLocation::OnEdge(1, bcoords),
216                );
217            }
218            ProjectionInfo::OnFace(face_side, va, vb, vc) => {
219                // Voronoï region of the face.
220                let denom = va + vb + vc;
221
222                if denom == 0.0 {
223                    // Collinear vertices: the zero face normal skipped every edge Voronoï
224                    // test, so projecting on the "face" would report the point as inside
225                    // (or produce NaNs). A degenerate triangle has no interior, so project
226                    // on its longest edge regardless of `solid`.
227                    let sq_ab = ab.length_squared();
228                    let sq_ac = ac.length_squared();
229                    let sq_bc = bc.length_squared();
230
231                    // (segment, edge id, triangle vertex ids of the segment endpoints)
232                    let (seg, eid, vids) = if sq_ab >= sq_ac && sq_ab >= sq_bc {
233                        (Segment::new(a, b), 0, [0, 1])
234                    } else if sq_ac >= sq_bc {
235                        (Segment::new(a, c), 2, [0, 2])
236                    } else {
237                        (Segment::new(b, c), 1, [1, 2])
238                    };
239
240                    let (proj, loc) = seg.project_local_point_and_get_location(pt, solid);
241                    let loc = match loc {
242                        SegmentPointLocation::OnVertex(i) => {
243                            TrianglePointLocation::OnVertex(vids[i as usize])
244                        }
245                        SegmentPointLocation::OnEdge(bcoords) => {
246                            TrianglePointLocation::OnEdge(eid, bcoords)
247                        }
248                    };
249
250                    return (proj, loc);
251                } else if DIM != 2 {
252                    // NOTE: divide instead of multiplying by `1.0 / denom`, whose
253                    // reciprocal overflows to infinity for subnormal `denom`.
254                    let v = vb / denom;
255                    let w = vc / denom;
256                    let bcoords = [1.0 - v - w, v, w];
257                    let res = a + ab * v + ac * w;
258
259                    return (
260                        compute_result(pt, res),
261                        TrianglePointLocation::OnFace(face_side as u32, bcoords),
262                    );
263                }
264            }
265        }
266
267        // Special treatment if we work in 2d because in this case we really are inside of the
268        // object.
269        if solid {
270            (
271                PointProjection::new(true, pt),
272                TrianglePointLocation::OnSolid,
273            )
274        } else {
275            // We have to project on the closest edge.
276
277            // TODO: this might be optimizable.
278            // TODO: be careful with numerical errors.
279            let v = ab_ap / (ab_ap - ab_bp); // proj on ab = a + ab * v
280            let w = ac_ap / (ac_ap - ac_cp); // proj on ac = a + ac * w
281            let u = (ac_bp - ab_bp) / (ac_bp - ab_bp + ab_cp - ac_cp); // proj on bc = b + bc * u
282
283            let bc = c - b;
284            let d_ab = ap.length_squared() - (ab.length_squared() * v * v);
285            let d_ac = ap.length_squared() - (ac.length_squared() * w * w);
286            let d_bc = bp.length_squared() - (bc.length_squared() * u * u);
287
288            let proj;
289            let loc;
290
291            if d_ab < d_ac {
292                if d_ab < d_bc {
293                    // ab
294                    let bcoords = [1.0 - v, v];
295                    proj = a + ab * v;
296                    loc = TrianglePointLocation::OnEdge(0, bcoords);
297                } else {
298                    // bc
299                    let bcoords = [1.0 - u, u];
300                    proj = b + bc * u;
301                    loc = TrianglePointLocation::OnEdge(1, bcoords);
302                }
303            } else if d_ac < d_bc {
304                // ac
305                let bcoords = [1.0 - w, w];
306                proj = a + ac * w;
307                loc = TrianglePointLocation::OnEdge(2, bcoords);
308            } else {
309                // bc
310                let bcoords = [1.0 - u, u];
311                proj = b + bc * u;
312                loc = TrianglePointLocation::OnEdge(1, bcoords);
313            }
314
315            (PointProjection::new(true, proj), loc)
316        }
317    }
318}