fj_kernel/algorithms/intersect/
ray_face.rs

1//! Intersection between a ray and a face, in 3D
2
3use fj_math::{Plane, Point, Scalar};
4
5use crate::{
6    algorithms::intersect::face_point::FacePointIntersection,
7    geometry::curve::GlobalPath,
8    objects::{Face, HalfEdge},
9    storage::Handle,
10};
11
12use super::{HorizontalRayToTheRight, Intersect};
13
14impl Intersect for (&HorizontalRayToTheRight<3>, &Face) {
15    type Intersection = RayFaceIntersection;
16
17    fn intersect(self) -> Option<Self::Intersection> {
18        let (ray, face) = self;
19
20        let plane = match face.surface().geometry().u {
21            GlobalPath::Circle(_) => todo!(
22                "Casting a ray against a swept circle is not supported yet"
23            ),
24            GlobalPath::Line(line) => Plane::from_parametric(
25                line.origin(),
26                line.direction(),
27                face.surface().geometry().v,
28            ),
29        };
30
31        if plane.is_parallel_to_vector(&ray.direction()) {
32            let a = plane.origin();
33            let b = plane.origin() + plane.u();
34            let c = plane.origin() + plane.v();
35            let d = ray.origin;
36
37            let [a, b, c, d] = [a, b, c, d]
38                .map(|point| [point.x, point.y, point.z])
39                .map(|point| point.map(Scalar::into_f64))
40                .map(|[x, y, z]| robust::Coord3D { x, y, z });
41
42            if robust::orient3d(a, b, c, d) == 0. {
43                return Some(RayFaceIntersection::RayHitsFaceAndAreParallel);
44            } else {
45                return None;
46            }
47        }
48
49        // The pattern in this assertion resembles `ax*by = ay*bx`, which holds
50        // true if the vectors `a = (ax, ay)` and `b = (bx, by)` are parallel.
51        //
52        // We're looking at the plane's direction vectors here, but we're
53        // ignoring their x-components. By doing that, we're essentially
54        // projecting those vectors into the yz-plane.
55        //
56        // This means that the following assertion verifies that the projections
57        // of the plane's direction vectors into the yz-plane are not parallel.
58        // If they were, then the plane could only be parallel to the x-axis,
59        // and thus our ray.
60        //
61        // We already handled the case of the ray and plane being parallel
62        // above. The following assertion should thus never be triggered.
63        assert_ne!(
64            plane.u().y * plane.v().z,
65            plane.u().z * plane.v().y,
66            "Plane and ray are parallel; should have been ruled out previously"
67        );
68
69        // Let's figure out the intersection between the ray and the plane.
70        let (t, u, v) = {
71            // The following math would get *very* unwieldy with those
72            // full-length variable names. Let's define some short-hands.
73            let orx = ray.origin.x;
74            let ory = ray.origin.y;
75            let orz = ray.origin.z;
76            let opx = plane.origin().x;
77            let opy = plane.origin().y;
78            let opz = plane.origin().z;
79            let d1x = plane.u().x;
80            let d1y = plane.u().y;
81            let d1z = plane.u().z;
82            let d2x = plane.v().x;
83            let d2y = plane.v().y;
84            let d2z = plane.v().z;
85
86            // Let's figure out where the intersection between the ray and the
87            // plane is. By equating the parametric equations of the ray and the
88            // plane, we get a vector equation, which in turn gives us a system
89            // of three equations with three unknowns: `t` (for the ray) and
90            // `u`/`v` (for the plane).
91            //
92            // Since the ray's direction vector is `(1, 0, 0)`, it works out
93            // such that `t` is not in the equations for y and z, meaning we can
94            // solve those equations for `u` and `v` independently.
95            //
96            // By doing some math, we get the following solutions:
97            let v = (d1y * (orz - opz) + (opy - ory) * d1z)
98                / (d1y * d2z - d2y * d1z);
99            let u = (ory - opy - d2y * v) / d1y;
100            let t = opx - orx + d1x * u + d2x * v;
101
102            (t, u, v)
103        };
104
105        if t < Scalar::ZERO {
106            // Ray points away from plane.
107            return None;
108        }
109
110        let point = Point::from([u, v]);
111        let intersection = match (face, &point).intersect()? {
112            FacePointIntersection::PointIsInsideFace => {
113                RayFaceIntersection::RayHitsFace
114            }
115            FacePointIntersection::PointIsOnEdge(edge) => {
116                RayFaceIntersection::RayHitsEdge(edge)
117            }
118            FacePointIntersection::PointIsOnVertex(vertex) => {
119                RayFaceIntersection::RayHitsVertex(vertex)
120            }
121        };
122
123        Some(intersection)
124    }
125}
126
127/// A hit between a ray and a face
128#[derive(Clone, Debug, Eq, PartialEq)]
129pub enum RayFaceIntersection {
130    /// The ray hits the face itself
131    RayHitsFace,
132
133    /// The ray is parallel to the face
134    RayHitsFaceAndAreParallel,
135
136    /// The ray hits an edge
137    RayHitsEdge(Handle<HalfEdge>),
138
139    /// The ray hits a vertex
140    RayHitsVertex(Point<2>),
141}
142
143#[cfg(test)]
144mod tests {
145    use fj_math::Point;
146
147    use crate::{
148        algorithms::{
149            intersect::{
150                ray_face::RayFaceIntersection, HorizontalRayToTheRight,
151                Intersect,
152            },
153            transform::TransformObject,
154        },
155        objects::{Cycle, Face},
156        operations::{BuildCycle, BuildFace, Insert, UpdateFace},
157        services::Services,
158    };
159
160    #[test]
161    fn ray_misses_whole_surface() {
162        let mut services = Services::new();
163
164        let ray = HorizontalRayToTheRight::from([0., 0., 0.]);
165
166        let face =
167            Face::unbound(services.objects.surfaces.yz_plane(), &mut services)
168                .update_exterior(|_| {
169                    Cycle::polygon(
170                        [[-1., -1.], [1., -1.], [1., 1.], [-1., 1.]],
171                        &mut services,
172                    )
173                    .insert(&mut services)
174                });
175        let face = face.translate([-1., 0., 0.], &mut services);
176
177        assert_eq!((&ray, &face).intersect(), None);
178
179        services.only_validate(face);
180    }
181
182    #[test]
183    fn ray_hits_face() {
184        let mut services = Services::new();
185
186        let ray = HorizontalRayToTheRight::from([0., 0., 0.]);
187
188        let face =
189            Face::unbound(services.objects.surfaces.yz_plane(), &mut services)
190                .update_exterior(|_| {
191                    Cycle::polygon(
192                        [[-1., -1.], [1., -1.], [1., 1.], [-1., 1.]],
193                        &mut services,
194                    )
195                    .insert(&mut services)
196                });
197        let face = face.translate([1., 0., 0.], &mut services);
198
199        assert_eq!(
200            (&ray, &face).intersect(),
201            Some(RayFaceIntersection::RayHitsFace)
202        );
203
204        services.only_validate(face);
205    }
206
207    #[test]
208    fn ray_hits_surface_but_misses_face() {
209        let mut services = Services::new();
210
211        let ray = HorizontalRayToTheRight::from([0., 0., 0.]);
212
213        let face =
214            Face::unbound(services.objects.surfaces.yz_plane(), &mut services)
215                .update_exterior(|_| {
216                    Cycle::polygon(
217                        [[-1., -1.], [1., -1.], [1., 1.], [-1., 1.]],
218                        &mut services,
219                    )
220                    .insert(&mut services)
221                });
222        let face = face.translate([0., 0., 2.], &mut services);
223
224        assert_eq!((&ray, &face).intersect(), None);
225
226        services.only_validate(face);
227    }
228
229    #[test]
230    fn ray_hits_edge() {
231        let mut services = Services::new();
232
233        let ray = HorizontalRayToTheRight::from([0., 0., 0.]);
234
235        let face =
236            Face::unbound(services.objects.surfaces.yz_plane(), &mut services)
237                .update_exterior(|_| {
238                    Cycle::polygon(
239                        [[-1., -1.], [1., -1.], [1., 1.], [-1., 1.]],
240                        &mut services,
241                    )
242                    .insert(&mut services)
243                });
244        let face = face.translate([1., 1., 0.], &mut services);
245
246        let edge = face
247            .exterior()
248            .half_edges()
249            .find(|edge| edge.start_position() == Point::from([-1., 1.]))
250            .unwrap();
251        assert_eq!(
252            (&ray, &face).intersect(),
253            Some(RayFaceIntersection::RayHitsEdge(edge.clone()))
254        );
255
256        services.only_validate(face);
257    }
258
259    #[test]
260    fn ray_hits_vertex() {
261        let mut services = Services::new();
262
263        let ray = HorizontalRayToTheRight::from([0., 0., 0.]);
264
265        let face =
266            Face::unbound(services.objects.surfaces.yz_plane(), &mut services)
267                .update_exterior(|_| {
268                    Cycle::polygon(
269                        [[-1., -1.], [1., -1.], [1., 1.], [-1., 1.]],
270                        &mut services,
271                    )
272                    .insert(&mut services)
273                });
274        let face = face.translate([1., 1., 1.], &mut services);
275
276        let vertex = face
277            .exterior()
278            .half_edges()
279            .find(|half_edge| {
280                half_edge.start_position() == Point::from([-1., -1.])
281            })
282            .map(|half_edge| half_edge.start_position())
283            .unwrap();
284        assert_eq!(
285            (&ray, &face).intersect(),
286            Some(RayFaceIntersection::RayHitsVertex(vertex))
287        );
288
289        services.only_validate(face);
290    }
291
292    #[test]
293    fn ray_is_parallel_to_surface_and_hits() {
294        let mut services = Services::new();
295
296        let ray = HorizontalRayToTheRight::from([0., 0., 0.]);
297
298        let face =
299            Face::unbound(services.objects.surfaces.xy_plane(), &mut services)
300                .update_exterior(|_| {
301                    Cycle::polygon(
302                        [[-1., -1.], [1., -1.], [1., 1.], [-1., 1.]],
303                        &mut services,
304                    )
305                    .insert(&mut services)
306                });
307
308        assert_eq!(
309            (&ray, &face).intersect(),
310            Some(RayFaceIntersection::RayHitsFaceAndAreParallel)
311        );
312
313        services.only_validate(face);
314    }
315
316    #[test]
317    fn ray_is_parallel_to_surface_and_misses() {
318        let mut services = Services::new();
319
320        let ray = HorizontalRayToTheRight::from([0., 0., 0.]);
321
322        let face =
323            Face::unbound(services.objects.surfaces.xy_plane(), &mut services)
324                .update_exterior(|_| {
325                    Cycle::polygon(
326                        [[-1., -1.], [1., -1.], [1., 1.], [-1., 1.]],
327                        &mut services,
328                    )
329                    .insert(&mut services)
330                });
331        let face = face.translate([0., 0., 1.], &mut services);
332
333        assert_eq!((&ray, &face).intersect(), None);
334
335        services.only_validate(face);
336    }
337}