parry3d/query/point/
point_tetrahedron.rs

1use crate::math::{Real, Vector};
2use crate::query::{PointProjection, PointQuery, PointQueryWithLocation};
3use crate::shape::{FeatureId, Tetrahedron, TetrahedronPointLocation};
4
5impl PointQuery for Tetrahedron {
6    #[inline]
7    fn project_local_point(&self, pt: Vector, solid: bool) -> PointProjection {
8        self.project_local_point_and_get_location(pt, solid).0
9    }
10
11    #[inline]
12    fn project_local_point_and_get_feature(&self, pt: Vector) -> (PointProjection, FeatureId) {
13        let (proj, loc) = self.project_local_point_and_get_location(pt, false);
14        let feature = match loc {
15            TetrahedronPointLocation::OnVertex(i) => FeatureId::Vertex(i),
16            TetrahedronPointLocation::OnEdge(i, _) => FeatureId::Edge(i),
17            TetrahedronPointLocation::OnFace(i, _) => FeatureId::Face(i),
18            TetrahedronPointLocation::OnSolid => unreachable!(),
19        };
20
21        (proj, feature)
22    }
23}
24
25impl PointQueryWithLocation for Tetrahedron {
26    type Location = TetrahedronPointLocation;
27
28    #[inline]
29    fn project_local_point_and_get_location(
30        &self,
31        pt: Vector,
32        solid: bool,
33    ) -> (PointProjection, Self::Location) {
34        let ab = self.b - self.a;
35        let ac = self.c - self.a;
36        let ad = self.d - self.a;
37        let ap = pt - self.a;
38
39        /*
40         * Voronoï regions of vertices.
41         */
42        let ap_ab = ap.dot(ab);
43        let ap_ac = ap.dot(ac);
44        let ap_ad = ap.dot(ad);
45
46        if ap_ab <= 0.0 && ap_ac <= 0.0 && ap_ad <= 0.0 {
47            // Voronoï region of `a`.
48            let proj = PointProjection::new(false, self.a);
49            return (proj, TetrahedronPointLocation::OnVertex(0));
50        }
51
52        let bc = self.c - self.b;
53        let bd = self.d - self.b;
54        let bp = pt - self.b;
55
56        let bp_bc = bp.dot(bc);
57        let bp_bd = bp.dot(bd);
58        let bp_ab = bp.dot(ab);
59
60        if bp_bc <= 0.0 && bp_bd <= 0.0 && bp_ab >= 0.0 {
61            // Voronoï region of `b`.
62            let proj = PointProjection::new(false, self.b);
63            return (proj, TetrahedronPointLocation::OnVertex(1));
64        }
65
66        let cd = self.d - self.c;
67        let cp = pt - self.c;
68
69        let cp_ac = cp.dot(ac);
70        let cp_bc = cp.dot(bc);
71        let cp_cd = cp.dot(cd);
72
73        if cp_cd <= 0.0 && cp_bc >= 0.0 && cp_ac >= 0.0 {
74            // Voronoï region of `c`.
75            let proj = PointProjection::new(false, self.c);
76            return (proj, TetrahedronPointLocation::OnVertex(2));
77        }
78
79        let dp = pt - self.d;
80
81        let dp_cd = dp.dot(cd);
82        let dp_bd = dp.dot(bd);
83        let dp_ad = dp.dot(ad);
84
85        if dp_ad >= 0.0 && dp_bd >= 0.0 && dp_cd >= 0.0 {
86            // Voronoï region of `d`.
87            let proj = PointProjection::new(false, self.d);
88            return (proj, TetrahedronPointLocation::OnVertex(3));
89        }
90
91        /*
92         * Voronoï regions of edges.
93         */
94        #[inline(always)]
95        fn check_edge(
96            i: usize,
97            a: Vector,
98            _: Vector,
99            nabc: Vector,
100            nabd: Vector,
101            ap: Vector,
102            ab: Vector,
103            ap_ab: Real, /*ap_ac: Real, ap_ad: Real,*/
104            bp_ab: Real, /*bp_ac: Real, bp_ad: Real*/
105        ) -> (
106            Real,
107            Real,
108            Option<(PointProjection, TetrahedronPointLocation)>,
109        ) {
110            let ab_ab = ap_ab - bp_ab;
111
112            // NOTE: The following avoids the subsequent cross and dot products but are not
113            // numerically stable.
114            //
115            // let dabc  = ap_ab * (ap_ac - bp_ac) - ap_ac * ab_ab;
116            // let dabd  = ap_ab * (ap_ad - bp_ad) - ap_ad * ab_ab;
117
118            let ap_x_ab = ap.cross(ab);
119            let dabc = ap_x_ab.dot(nabc);
120            let dabd = ap_x_ab.dot(nabd);
121
122            // TODO: the case where ab_ab == 0.0 is not well defined.
123            if ab_ab != 0.0 && dabc >= 0.0 && dabd >= 0.0 && ap_ab >= 0.0 && ap_ab <= ab_ab {
124                // Voronoi region of `ab`.
125                let u = ap_ab / ab_ab;
126                let bcoords = [1.0 - u, u];
127                let res = a + ab * u;
128                let proj = PointProjection::new(false, res);
129                (
130                    dabc,
131                    dabd,
132                    Some((proj, TetrahedronPointLocation::OnEdge(i as u32, bcoords))),
133                )
134            } else {
135                (dabc, dabd, None)
136            }
137        }
138
139        // Voronoï region of ab.
140        //            let bp_ad = bp_bd + bp_ab;
141        //            let bp_ac = bp_bc + bp_ab;
142        let nabc = ab.cross(ac);
143        let nabd = ab.cross(ad);
144        let (dabc, dabd, res) = check_edge(
145            0, self.a, self.b, nabc, nabd, ap, ab, ap_ab,
146            /*ap_ac, ap_ad,*/ bp_ab, /*, bp_ac, bp_ad*/
147        );
148        if let Some(res) = res {
149            return res;
150        }
151
152        // Voronoï region of ac.
153        // Substitutions (wrt. ab):
154        //   b -> c
155        //   c -> d
156        //   d -> b
157        //            let cp_ab = cp_ac - cp_bc;
158        //            let cp_ad = cp_cd + cp_ac;
159        let nacd = ac.cross(ad);
160        let (dacd, dacb, res) = check_edge(
161            1, self.a, self.c, nacd, -nabc, ap, ac, ap_ac,
162            /*ap_ad, ap_ab,*/ cp_ac, /*, cp_ad, cp_ab*/
163        );
164        if let Some(res) = res {
165            return res;
166        }
167
168        // Voronoï region of ad.
169        // Substitutions (wrt. ab):
170        //   b -> d
171        //   c -> b
172        //   d -> c
173        //            let dp_ac = dp_ad - dp_cd;
174        //            let dp_ab = dp_ad - dp_bd;
175        let (dadb, dadc, res) = check_edge(
176            2, self.a, self.d, -nabd, -nacd, ap, ad, ap_ad,
177            /*ap_ab, ap_ac,*/ dp_ad, /*, dp_ab, dp_ac*/
178        );
179        if let Some(res) = res {
180            return res;
181        }
182
183        // Voronoï region of bc.
184        // Substitutions (wrt. ab):
185        //   a -> b
186        //   b -> c
187        //   c -> a
188        //            let cp_bd = cp_cd + cp_bc;
189        let nbcd = bc.cross(bd);
190        // NOTE: nabc = nbcd
191        let (dbca, dbcd, res) = check_edge(
192            3, self.b, self.c, nabc, nbcd, bp, bc, bp_bc,
193            /*-bp_ab, bp_bd,*/ cp_bc, /*, -cp_ab, cp_bd*/
194        );
195        if let Some(res) = res {
196            return res;
197        }
198
199        // Voronoï region of bd.
200        // Substitutions (wrt. ab):
201        //   a -> b
202        //   b -> d
203        //   d -> a
204
205        //            let dp_bc = dp_bd - dp_cd;
206        // NOTE: nbdc = -nbcd
207        // NOTE: nbda = nabd
208        let (dbdc, dbda, res) = check_edge(
209            4, self.b, self.d, -nbcd, nabd, bp, bd, bp_bd,
210            /*bp_bc, -bp_ab,*/ dp_bd, /*, dp_bc, -dp_ab*/
211        );
212        if let Some(res) = res {
213            return res;
214        }
215
216        // Voronoï region of cd.
217        // Substitutions (wrt. ab):
218        //   a -> c
219        //   b -> d
220        //   c -> a
221        //   d -> b
222        // NOTE: ncda = nacd
223        // NOTE: ncdb = nbcd
224        let (dcda, dcdb, res) = check_edge(
225            5, self.c, self.d, nacd, nbcd, cp, cd, cp_cd,
226            /*-cp_ac, -cp_bc,*/ dp_cd, /*, -dp_ac, -dp_bc*/
227        );
228        if let Some(res) = res {
229            return res;
230        }
231
232        /*
233         * Voronoï regions of faces.
234         */
235        #[inline(always)]
236        fn check_face(
237            i: usize,
238            a: Vector,
239            b: Vector,
240            c: Vector,
241            ap: Vector,
242            bp: Vector,
243            cp: Vector,
244            ab: Vector,
245            ac: Vector,
246            ad: Vector,
247            dabc: Real,
248            dbca: Real,
249            dacb: Real,
250            /* ap_ab: Real, bp_ab: Real, cp_ab: Real,
251            ap_ac: Real, bp_ac: Real, cp_ac: Real, */
252        ) -> Option<(PointProjection, TetrahedronPointLocation)> {
253            if dabc < 0.0 && dbca < 0.0 && dacb < 0.0 {
254                let n = ab.cross(ac); // TODO: is is possible to avoid this cross product?
255                if n.dot(ad) * n.dot(ap) < 0.0 {
256                    // Voronoï region of the face.
257
258                    // NOTE:
259                    // The following avoids expansive computations but are not very
260                    // numerically stable.
261                    //
262                    // let va = bp_ab * cp_ac - cp_ab * bp_ac;
263                    // let vb = cp_ab * ap_ac - ap_ab * cp_ac;
264                    // let vc = ap_ab * bp_ac - bp_ab * ap_ac;
265
266                    // NOTE: the normalization may fail even if the dot products
267                    // above were < 0. This happens, e.g., when we use fixed-point
268                    // numbers and there are not enough decimal bits to perform
269                    // the normalization.
270                    let normal = n.try_normalize()?;
271                    let vc = normal.dot(ap.cross(bp));
272                    let va = normal.dot(bp.cross(cp));
273                    let vb = normal.dot(cp.cross(ap));
274
275                    let denom = va + vb + vc;
276                    assert!(denom != 0.0);
277                    let inv_denom = 1.0 / denom;
278
279                    let bcoords = [va * inv_denom, vb * inv_denom, vc * inv_denom];
280                    let res = a * bcoords[0] + b * bcoords[1] + c * bcoords[2];
281                    let proj = PointProjection::new(false, res);
282
283                    return Some((proj, TetrahedronPointLocation::OnFace(i as u32, bcoords)));
284                }
285            }
286            None
287        }
288
289        // Face abc.
290        if let Some(res) = check_face(
291            0, self.a, self.b, self.c, ap, bp, cp, ab, ac, ad, dabc, dbca,
292            dacb,
293            /*ap_ab, bp_ab, cp_ab,
294            ap_ac, bp_ac, cp_ac*/
295        ) {
296            return res;
297        }
298
299        // Face abd.
300        if let Some(res) = check_face(
301            1, self.a, self.b, self.d, ap, bp, dp, ab, ad, ac, dadb, dabd,
302            dbda,
303            /*ap_ab, bp_ab, dp_ab,
304            ap_ad, bp_ad, dp_ad*/
305        ) {
306            return res;
307        }
308        // Face acd.
309        if let Some(res) = check_face(
310            2, self.a, self.c, self.d, ap, cp, dp, ac, ad, ab, dacd, dcda,
311            dadc,
312            /*ap_ac, cp_ac, dp_ac,
313            ap_ad, cp_ad, dp_ad*/
314        ) {
315            return res;
316        }
317        // Face bcd.
318        if let Some(res) = check_face(
319            3, self.b, self.c, self.d, bp, cp, dp, bc, bd, -ab, dbcd, dcdb,
320            dbdc,
321            /*bp_bc, cp_bc, dp_bc,
322            bp_bd, cp_bd, dp_bd*/
323        ) {
324            return res;
325        }
326
327        if !solid {
328            // XXX: implement the non-solid projection.
329            unimplemented!(
330                "Non-solid ray-cast/point projection on a tetrahedron is not yet implemented."
331            )
332        }
333
334        let proj = PointProjection::new(true, pt);
335        (proj, TetrahedronPointLocation::OnSolid)
336    }
337}