parry2d/query/point/
point_triangle.rs

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