ncollide3d/query/point/
point_triangle.rs

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