fj_kernel/algorithms/intersect/
face_point.rs

1//! Intersection between faces and points in 2D
2
3use fj_math::Point;
4use itertools::Itertools;
5
6use crate::{
7    objects::{Face, HalfEdge},
8    storage::Handle,
9};
10
11use super::{
12    ray_segment::RaySegmentIntersection, HorizontalRayToTheRight, Intersect,
13};
14
15impl Intersect for (&Face, &Point<2>) {
16    type Intersection = FacePointIntersection;
17
18    fn intersect(self) -> Option<Self::Intersection> {
19        let (face, point) = self;
20
21        let ray = HorizontalRayToTheRight { origin: *point };
22
23        let mut num_hits = 0;
24
25        for cycle in face.all_cycles() {
26            // We need to properly detect the ray passing the boundary at the
27            // "seam" of the polygon, i.e. the vertex between the last and the
28            // first segment. The logic in the loop properly takes care of that,
29            // as long as we initialize the `previous_hit` variable with the
30            // result of the last segment.
31            let mut previous_hit = cycle
32                .half_edges()
33                .last()
34                .cloned()
35                .and_then(|edge| (&ray, &edge).intersect());
36
37            for (half_edge, next_half_edge) in
38                cycle.half_edges().circular_tuple_windows::<(_, _)>()
39            {
40                let hit = (&ray, half_edge).intersect();
41
42                let count_hit = match (hit, previous_hit) {
43                    (
44                        Some(RaySegmentIntersection::RayStartsOnSegment),
45                        _,
46                    ) => {
47                        // If the ray starts on the boundary of the face,
48                        // there's nothing to else check.
49                        return Some(FacePointIntersection::PointIsOnEdge(
50                            half_edge.clone()
51                        ));
52                    }
53                    (Some(RaySegmentIntersection::RayStartsOnOnFirstVertex), _) => {
54                        let vertex = half_edge.start_position();
55                        return Some(
56                            FacePointIntersection::PointIsOnVertex(vertex)
57                        );
58                    }
59                    (Some(RaySegmentIntersection::RayStartsOnSecondVertex), _) => {
60                        let vertex = next_half_edge.start_position();
61                        return Some(
62                            FacePointIntersection::PointIsOnVertex(vertex)
63                        );
64                    }
65                    (Some(RaySegmentIntersection::RayHitsSegment), _) => {
66                        // We're hitting a segment right-on. Clear case.
67                        true
68                    }
69                    (
70                        Some(RaySegmentIntersection::RayHitsUpperVertex),
71                        Some(RaySegmentIntersection::RayHitsLowerVertex),
72                    )
73                    | (
74                        Some(RaySegmentIntersection::RayHitsLowerVertex),
75                        Some(RaySegmentIntersection::RayHitsUpperVertex),
76                    ) => {
77                        // If we're hitting a vertex, only count it if we've hit
78                        // the other kind of vertex right before.
79                        //
80                        // That means, we're passing through the polygon
81                        // boundary at where two edges touch. Depending on the
82                        // order in which edges are checked, we're seeing this
83                        // as a hit to one edge's lower/upper vertex, then the
84                        // other edge's opposite vertex.
85                        //
86                        // If we're seeing two of the same vertices in a row,
87                        // we're not actually passing through the polygon
88                        // boundary. Then we're just touching a vertex without
89                        // passing through anything.
90                        true
91                    }
92                    (Some(RaySegmentIntersection::RayHitsSegmentAndAreParallel), _) => {
93                        // A parallel edge must be completely ignored. Its
94                        // presence won't change anything, so we can treat it as
95                        // if it wasn't there, and its neighbors were connected
96                        // to each other.
97                        continue;
98                    }
99                    _ => {
100                        // Any other case is not a valid hit.
101                        false
102                    }
103                };
104
105                if count_hit {
106                    num_hits += 1;
107                }
108
109                previous_hit = hit;
110            }
111        }
112
113        if num_hits % 2 == 1 {
114            Some(FacePointIntersection::PointIsInsideFace)
115        } else {
116            None
117        }
118    }
119}
120
121/// The intersection between a face and a point
122#[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
123pub enum FacePointIntersection {
124    /// The point is inside of the face
125    PointIsInsideFace,
126
127    /// The point is coincident with an edge
128    PointIsOnEdge(Handle<HalfEdge>),
129
130    /// The point is coincident with a vertex
131    PointIsOnVertex(Point<2>),
132}
133
134#[cfg(test)]
135mod tests {
136    use fj_math::Point;
137    use pretty_assertions::assert_eq;
138
139    use crate::{
140        algorithms::intersect::{face_point::FacePointIntersection, Intersect},
141        objects::{Cycle, Face},
142        operations::{BuildCycle, BuildFace, Insert, UpdateFace},
143        services::Services,
144    };
145
146    #[test]
147    fn point_is_outside_face() {
148        let mut services = Services::new();
149
150        let face =
151            Face::unbound(services.objects.surfaces.xy_plane(), &mut services)
152                .update_exterior(|_| {
153                    Cycle::polygon(
154                        [[0., 0.], [1., 1.], [0., 2.]],
155                        &mut services,
156                    )
157                    .insert(&mut services)
158                });
159        let point = Point::from([2., 1.]);
160
161        let intersection = (&face, &point).intersect();
162        assert_eq!(intersection, None);
163
164        services.only_validate(face);
165    }
166
167    #[test]
168    fn ray_hits_vertex_while_passing_outside() {
169        let mut services = Services::new();
170
171        let face =
172            Face::unbound(services.objects.surfaces.xy_plane(), &mut services)
173                .update_exterior(|_| {
174                    Cycle::polygon(
175                        [[0., 0.], [2., 1.], [0., 2.]],
176                        &mut services,
177                    )
178                    .insert(&mut services)
179                });
180        let point = Point::from([1., 1.]);
181
182        let intersection = (&face, &point).intersect();
183        assert_eq!(
184            intersection,
185            Some(FacePointIntersection::PointIsInsideFace)
186        );
187
188        services.only_validate(face);
189    }
190
191    #[test]
192    fn ray_hits_vertex_at_cycle_seam() {
193        let mut services = Services::new();
194
195        let face =
196            Face::unbound(services.objects.surfaces.xy_plane(), &mut services)
197                .update_exterior(|_| {
198                    Cycle::polygon(
199                        [[4., 2.], [0., 4.], [0., 0.]],
200                        &mut services,
201                    )
202                    .insert(&mut services)
203                });
204        let point = Point::from([1., 2.]);
205
206        let intersection = (&face, &point).intersect();
207        assert_eq!(
208            intersection,
209            Some(FacePointIntersection::PointIsInsideFace)
210        );
211
212        services.only_validate(face);
213    }
214
215    #[test]
216    fn ray_hits_vertex_while_staying_inside() {
217        let mut services = Services::new();
218
219        let face =
220            Face::unbound(services.objects.surfaces.xy_plane(), &mut services)
221                .update_exterior(|_| {
222                    Cycle::polygon(
223                        [[0., 0.], [2., 1.], [3., 0.], [3., 4.]],
224                        &mut services,
225                    )
226                    .insert(&mut services)
227                });
228        let point = Point::from([1., 1.]);
229
230        let intersection = (&face, &point).intersect();
231        assert_eq!(
232            intersection,
233            Some(FacePointIntersection::PointIsInsideFace)
234        );
235
236        services.only_validate(face);
237    }
238
239    #[test]
240    fn ray_hits_parallel_edge_and_leaves_face_at_vertex() {
241        let mut services = Services::new();
242
243        let face =
244            Face::unbound(services.objects.surfaces.xy_plane(), &mut services)
245                .update_exterior(|_| {
246                    Cycle::polygon(
247                        [[0., 0.], [2., 1.], [3., 1.], [0., 2.]],
248                        &mut services,
249                    )
250                    .insert(&mut services)
251                });
252        let point = Point::from([1., 1.]);
253
254        let intersection = (&face, &point).intersect();
255        assert_eq!(
256            intersection,
257            Some(FacePointIntersection::PointIsInsideFace)
258        );
259
260        services.only_validate(face);
261    }
262
263    #[test]
264    fn ray_hits_parallel_edge_and_does_not_leave_face_there() {
265        let mut services = Services::new();
266
267        let face =
268            Face::unbound(services.objects.surfaces.xy_plane(), &mut services)
269                .update_exterior(|_| {
270                    Cycle::polygon(
271                        [[0., 0.], [2., 1.], [3., 1.], [4., 0.], [4., 5.]],
272                        &mut services,
273                    )
274                    .insert(&mut services)
275                });
276        let point = Point::from([1., 1.]);
277
278        let intersection = (&face, &point).intersect();
279        assert_eq!(
280            intersection,
281            Some(FacePointIntersection::PointIsInsideFace)
282        );
283
284        services.only_validate(face);
285    }
286
287    #[test]
288    fn point_is_coincident_with_edge() {
289        let mut services = Services::new();
290
291        let face =
292            Face::unbound(services.objects.surfaces.xy_plane(), &mut services)
293                .update_exterior(|_| {
294                    Cycle::polygon(
295                        [[0., 0.], [2., 0.], [0., 1.]],
296                        &mut services,
297                    )
298                    .insert(&mut services)
299                });
300        let point = Point::from([1., 0.]);
301
302        let intersection = (&face, &point).intersect();
303
304        let edge = face
305            .exterior()
306            .half_edges()
307            .find(|edge| edge.start_position() == Point::from([0., 0.]))
308            .unwrap();
309        assert_eq!(
310            intersection,
311            Some(FacePointIntersection::PointIsOnEdge(edge.clone()))
312        );
313
314        services.only_validate(face);
315    }
316
317    #[test]
318    fn point_is_coincident_with_vertex() {
319        let mut services = Services::new();
320
321        let face =
322            Face::unbound(services.objects.surfaces.xy_plane(), &mut services)
323                .update_exterior(|_| {
324                    Cycle::polygon(
325                        [[0., 0.], [1., 0.], [0., 1.]],
326                        &mut services,
327                    )
328                    .insert(&mut services)
329                });
330        let point = Point::from([1., 0.]);
331
332        let intersection = (&face, &point).intersect();
333
334        let vertex = face
335            .exterior()
336            .half_edges()
337            .find(|half_edge| {
338                half_edge.start_position() == Point::from([1., 0.])
339            })
340            .map(|half_edge| half_edge.start_position())
341            .unwrap();
342        assert_eq!(
343            intersection,
344            Some(FacePointIntersection::PointIsOnVertex(vertex))
345        );
346
347        services.only_validate(face);
348    }
349}