parry3d/query/point/
point_triangle.rs

1use crate::math::{Point, Real, Vector, DIM};
2use crate::query::{PointProjection, PointQuery, PointQueryWithLocation};
3use crate::shape::{FeatureId, Triangle, TrianglePointLocation};
4
5#[inline]
6fn compute_result(pt: &Point<Real>, proj: Point<Real>) -> 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: &Point<Real>, 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(
28        &self,
29        pt: &Point<Real>,
30    ) -> (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: &Point<Real>,
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<Real>,
113            ac: &Vector<Real>,
114            bc: &Vector<Real>,
115            ap: &Vector<Real>,
116            bp: &Vector<Real>,
117            cp: &Vector<Real>,
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(ac);
128                let vc = n * ab.perp(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(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(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.norm_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.norm_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.norm_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                if DIM != 2 {
221                    // NOTE: in some cases, numerical instability
222                    // may result in the denominator being zero
223                    // when the triangle is nearly degenerate.
224                    if va + vb + vc != 0.0 {
225                        let denom = 1.0 / (va + vb + vc);
226                        let v = vb * denom;
227                        let w = vc * denom;
228                        let bcoords = [1.0 - v - w, v, w];
229                        let res = a + ab * v + ac * w;
230
231                        return (
232                            compute_result(pt, res),
233                            TrianglePointLocation::OnFace(face_side as u32, bcoords),
234                        );
235                    }
236                }
237            }
238        }
239
240        // Special treatment if we work in 2d because in this case we really are inside of the
241        // object.
242        if solid {
243            (
244                PointProjection::new(true, *pt),
245                TrianglePointLocation::OnSolid,
246            )
247        } else {
248            // We have to project on the closest edge.
249
250            // TODO: this might be optimizable.
251            // TODO: be careful with numerical errors.
252            let v = ab_ap / (ab_ap - ab_bp); // proj on ab = a + ab * v
253            let w = ac_ap / (ac_ap - ac_cp); // proj on ac = a + ac * w
254            let u = (ac_bp - ab_bp) / (ac_bp - ab_bp + ab_cp - ac_cp); // proj on bc = b + bc * u
255
256            let bc = c - b;
257            let d_ab = ap.norm_squared() - (ab.norm_squared() * v * v);
258            let d_ac = ap.norm_squared() - (ac.norm_squared() * w * w);
259            let d_bc = bp.norm_squared() - (bc.norm_squared() * u * u);
260
261            let proj;
262            let loc;
263
264            if d_ab < d_ac {
265                if d_ab < d_bc {
266                    // ab
267                    let bcoords = [1.0 - v, v];
268                    proj = a + ab * v;
269                    loc = TrianglePointLocation::OnEdge(0, bcoords);
270                } else {
271                    // bc
272                    let bcoords = [1.0 - u, u];
273                    proj = b + bc * u;
274                    loc = TrianglePointLocation::OnEdge(1, bcoords);
275                }
276            } else if d_ac < d_bc {
277                // ac
278                let bcoords = [1.0 - w, w];
279                proj = a + ac * w;
280                loc = TrianglePointLocation::OnEdge(2, bcoords);
281            } else {
282                // bc
283                let bcoords = [1.0 - u, u];
284                proj = b + bc * u;
285                loc = TrianglePointLocation::OnEdge(1, bcoords);
286            }
287
288            (PointProjection::new(true, proj), loc)
289        }
290    }
291}