use fj_math::Point;
use crate::objects::{Face, HalfEdge, Vertex};
use super::{
ray_segment::RaySegmentIntersection, HorizontalRayToTheRight, Intersect,
};
impl Intersect for (&Face, &Point<2>) {
type Intersection = FacePointIntersection;
fn intersect(self) -> Option<Self::Intersection> {
let (face, point) = self;
let ray = HorizontalRayToTheRight { origin: *point };
let mut num_hits = 0;
for cycle in face.all_cycles() {
let mut previous_hit = cycle
.half_edges()
.last()
.cloned()
.and_then(|edge| (&ray, &edge).intersect());
for half_edge in cycle.half_edges() {
let hit = (&ray, half_edge).intersect();
let count_hit = match (hit, previous_hit) {
(
Some(RaySegmentIntersection::RayStartsOnSegment),
_,
) => {
return Some(FacePointIntersection::PointIsOnEdge(
half_edge.clone()
));
}
(Some(RaySegmentIntersection::RayStartsOnOnFirstVertex), _) => {
let vertex = half_edge.vertices()[0].clone();
return Some(
FacePointIntersection::PointIsOnVertex(vertex)
);
}
(Some(RaySegmentIntersection::RayStartsOnSecondVertex), _) => {
let vertex = half_edge.vertices()[1].clone();
return Some(
FacePointIntersection::PointIsOnVertex(vertex)
);
}
(Some(RaySegmentIntersection::RayHitsSegment), _) => {
true
}
(
Some(RaySegmentIntersection::RayHitsUpperVertex),
Some(RaySegmentIntersection::RayHitsLowerVertex),
)
| (
Some(RaySegmentIntersection::RayHitsLowerVertex),
Some(RaySegmentIntersection::RayHitsUpperVertex),
) => {
true
}
(Some(RaySegmentIntersection::RayHitsSegmentAndAreParallel), _) => {
continue;
}
_ => {
false
}
};
if count_hit {
num_hits += 1;
}
previous_hit = hit;
}
}
if num_hits % 2 == 1 {
Some(FacePointIntersection::PointIsInsideFace)
} else {
None
}
}
}
#[allow(clippy::large_enum_variant)]
#[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub enum FacePointIntersection {
PointIsInsideFace,
PointIsOnEdge(HalfEdge),
PointIsOnVertex(Vertex),
}
#[cfg(test)]
mod tests {
use fj_math::Point;
use pretty_assertions::assert_eq;
use crate::{
algorithms::intersect::{face_point::FacePointIntersection, Intersect},
iter::ObjectIters,
objects::{Face, Surface},
stores::Stores,
};
#[test]
fn point_is_outside_face() {
let stores = Stores::new();
let surface = stores.surfaces.insert(Surface::xy_plane());
let face = Face::builder(&stores, surface)
.with_exterior_polygon_from_points([[0., 0.], [1., 1.], [0., 2.]])
.build();
let point = Point::from([2., 1.]);
let intersection = (&face, &point).intersect();
assert_eq!(intersection, None);
}
#[test]
fn ray_hits_vertex_while_passing_outside() {
let stores = Stores::new();
let surface = stores.surfaces.insert(Surface::xy_plane());
let face = Face::builder(&stores, surface)
.with_exterior_polygon_from_points([[0., 0.], [2., 1.], [0., 2.]])
.build();
let point = Point::from([1., 1.]);
let intersection = (&face, &point).intersect();
assert_eq!(
intersection,
Some(FacePointIntersection::PointIsInsideFace)
);
}
#[test]
fn ray_hits_vertex_at_cycle_seam() {
let stores = Stores::new();
let surface = stores.surfaces.insert(Surface::xy_plane());
let face = Face::builder(&stores, surface)
.with_exterior_polygon_from_points([[4., 2.], [0., 4.], [0., 0.]])
.build();
let point = Point::from([1., 2.]);
let intersection = (&face, &point).intersect();
assert_eq!(
intersection,
Some(FacePointIntersection::PointIsInsideFace)
);
}
#[test]
fn ray_hits_vertex_while_staying_inside() {
let stores = Stores::new();
let surface = stores.surfaces.insert(Surface::xy_plane());
let face = Face::builder(&stores, surface)
.with_exterior_polygon_from_points([
[0., 0.],
[2., 1.],
[3., 0.],
[3., 4.],
])
.build();
let point = Point::from([1., 1.]);
let intersection = (&face, &point).intersect();
assert_eq!(
intersection,
Some(FacePointIntersection::PointIsInsideFace)
);
}
#[test]
fn ray_hits_parallel_edge_and_leaves_face_at_vertex() {
let stores = Stores::new();
let surface = stores.surfaces.insert(Surface::xy_plane());
let face = Face::builder(&stores, surface)
.with_exterior_polygon_from_points([
[0., 0.],
[2., 1.],
[3., 1.],
[0., 2.],
])
.build();
let point = Point::from([1., 1.]);
let intersection = (&face, &point).intersect();
assert_eq!(
intersection,
Some(FacePointIntersection::PointIsInsideFace)
);
}
#[test]
fn ray_hits_parallel_edge_and_does_not_leave_face_there() {
let stores = Stores::new();
let surface = stores.surfaces.insert(Surface::xy_plane());
let face = Face::builder(&stores, surface)
.with_exterior_polygon_from_points([
[0., 0.],
[2., 1.],
[3., 1.],
[4., 0.],
[4., 5.],
])
.build();
let point = Point::from([1., 1.]);
let intersection = (&face, &point).intersect();
assert_eq!(
intersection,
Some(FacePointIntersection::PointIsInsideFace)
);
}
#[test]
fn point_is_coincident_with_edge() {
let stores = Stores::new();
let surface = stores.surfaces.insert(Surface::xy_plane());
let face = Face::builder(&stores, surface)
.with_exterior_polygon_from_points([[0., 0.], [2., 0.], [0., 1.]])
.build();
let point = Point::from([1., 0.]);
let intersection = (&face, &point).intersect();
let edge = face
.half_edge_iter()
.find(|edge| {
let [a, b] = edge.vertices();
a.global_form().position() == Point::from([0., 0., 0.])
&& b.global_form().position() == Point::from([2., 0., 0.])
})
.unwrap();
assert_eq!(
intersection,
Some(FacePointIntersection::PointIsOnEdge(edge.clone()))
);
}
#[test]
fn point_is_coincident_with_vertex() {
let stores = Stores::new();
let surface = stores.surfaces.insert(Surface::xy_plane());
let face = Face::builder(&stores, surface)
.with_exterior_polygon_from_points([[0., 0.], [1., 0.], [0., 1.]])
.build();
let point = Point::from([1., 0.]);
let intersection = (&face, &point).intersect();
let vertex = face
.vertex_iter()
.find(|vertex| {
vertex.global_form().position() == Point::from([1., 0., 0.])
})
.unwrap();
assert_eq!(
intersection,
Some(FacePointIntersection::PointIsOnVertex(vertex.clone()))
);
}
}