Skip to content

Commit

Permalink
Implement Contains<Point<2>> for Face
Browse files Browse the repository at this point in the history
This duplicates code from `Polygon`. I hope that this new code can
eventually replace the old one, thereby re-consolidating the
duplication. I've run into some trouble trying to do that though, so for
now, duplication it is.
  • Loading branch information
hannobraun committed Aug 10, 2022
1 parent 767785e commit f4bf6f6
Show file tree
Hide file tree
Showing 2 changed files with 158 additions and 0 deletions.
156 changes: 156 additions & 0 deletions crates/fj-kernel/src/algorithms/contains/face_point.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
use fj_math::Point;

use crate::{
algorithms::cast_ray::{CastRay, HorizontalRayToTheRight, RaySegmentHit},
objects::Face,
};

use super::Contains;

impl Contains<Point<2>> for Face {
fn contains(&self, point: &Point<2>) -> bool {
let ray = HorizontalRayToTheRight { origin: *point };

let mut num_hits = 0;

for cycle in self.all_cycles() {
// We need to properly detect the ray passing the boundary at the
// "seam" of the polygon, i.e. the vertex between the last and the
// first segment. The logic in the loop properly takes care of that,
// as long as we initialize the `previous_hit` variable with the
// result of the last segment.
let mut previous_hit = cycle
.edges()
.last()
.copied()
.and_then(|edge| edge.cast_ray(ray));

for edge in cycle.edges() {
let hit = edge.cast_ray(ray);

let count_hit = match (hit, previous_hit) {
(Some(RaySegmentHit::Segment), _) => {
// We're hitting a segment right-on. Clear case.
true
}
(
Some(RaySegmentHit::UpperVertex),
Some(RaySegmentHit::LowerVertex),
)
| (
Some(RaySegmentHit::LowerVertex),
Some(RaySegmentHit::UpperVertex),
) => {
// If we're hitting a vertex, only count it if we've hit
// the other kind of vertex right before.
//
// That means, we're passing through the polygon
// boundary at where two edges touch. Depending on the
// order in which edges are checked, we're seeing this
// as a hit to one edge's lower/upper vertex, then the
// other edge's opposite vertex.
//
// If we're seeing two of the same vertices in a row,
// we're not actually passing through the polygon
// boundary. Then we're just touching a vertex without
// passing through anything.
true
}
(Some(RaySegmentHit::Parallel), _) => {
// A parallel edge must be completely ignored. Its
// presence won't change anything, so we can treat it as
// if it wasn't there, and its neighbors were connected
// to each other.
continue;
}
_ => {
// Any other case is not a valid hit.
false
}
};

if count_hit {
num_hits += 1;
}

previous_hit = hit;
}
}

num_hits % 2 == 1
}
}

#[cfg(test)]
mod tests {
use fj_math::Point;

use crate::{
algorithms::Contains,
objects::{Face, Surface},
};

#[test]
fn ray_hits_vertex_while_passing_outside() {
let face = Face::build(Surface::xy_plane()).polygon_from_points([
[0., 0.],
[2., 1.],
[0., 2.],
]);

assert_contains_point(face, [1., 1.]);
}

#[test]
fn ray_hits_vertex_at_cycle_seam() {
let face = Face::build(Surface::xy_plane())
.polygon_from_points([[4., 2.], [0., 4.], [0., 0.]])
.with_hole([[1., 1.], [2., 1.], [1., 3.]]);

assert_contains_point(face, [1., 2.]);
}

#[test]
fn ray_hits_vertex_while_staying_inside() {
let face = Face::build(Surface::xy_plane()).polygon_from_points([
[0., 0.],
[2., 1.],
[3., 0.],
[3., 4.],
]);

assert_contains_point(face, [1., 1.]);
}

#[test]
fn ray_hits_parallel_edge() {
// Ray passes face boundary at a vertex.
let face = Face::build(Surface::xy_plane()).polygon_from_points([
[0., 0.],
[2., 1.],
[3., 1.],
[0., 2.],
]);
assert_contains_point(face, [1., 1.]);

// Ray hits a vertex, but doesn't pass face boundary there.
let face = Face::build(Surface::xy_plane()).polygon_from_points([
[0., 0.],
[2., 1.],
[3., 1.],
[4., 0.],
[4., 5.],
]);
assert_contains_point(face, [1., 1.]);
}

fn assert_contains_point(
face: impl Into<Face>,
point: impl Into<Point<2>>,
) {
let face = face.into();
let point = point.into();

assert!(face.contains(&point));
}
}
2 changes: 2 additions & 0 deletions crates/fj-kernel/src/algorithms/contains/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
mod face_point;

/// Test whether an object or shape contains another
pub trait Contains<T> {
/// Test whether an object or shape contains another
Expand Down

0 comments on commit f4bf6f6

Please sign in to comment.