use fj_math::Point;
use itertools::Itertools;
use crate::{
objects::{Face, HalfEdge},
storage::Handle,
};
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, next_half_edge) in
cycle.half_edges().circular_tuple_windows::<(_, _)>()
{
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.start_position();
return Some(
FacePointIntersection::PointIsOnVertex(vertex)
);
}
(Some(RaySegmentIntersection::RayStartsOnSecondVertex), _) => {
let vertex = next_half_edge.start_position();
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
}
}
}
#[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub enum FacePointIntersection {
PointIsInsideFace,
PointIsOnEdge(Handle<HalfEdge>),
PointIsOnVertex(Point<2>),
}
#[cfg(test)]
mod tests {
use fj_math::Point;
use pretty_assertions::assert_eq;
use crate::{
algorithms::intersect::{face_point::FacePointIntersection, Intersect},
builder::{CycleBuilder, FaceBuilder},
services::Services,
};
#[test]
fn point_is_outside_face() {
let mut services = Services::new();
let face = FaceBuilder::new(services.objects.surfaces.xy_plane())
.with_exterior(CycleBuilder::polygon(
[[0., 0.], [1., 1.], [0., 2.]],
&mut services.objects,
))
.build(&mut services.objects);
let point = Point::from([2., 1.]);
let intersection = (&face, &point).intersect();
assert_eq!(intersection, None);
}
#[test]
fn ray_hits_vertex_while_passing_outside() {
let mut services = Services::new();
let face = FaceBuilder::new(services.objects.surfaces.xy_plane())
.with_exterior(CycleBuilder::polygon(
[[0., 0.], [2., 1.], [0., 2.]],
&mut services.objects,
))
.build(&mut services.objects);
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 mut services = Services::new();
let face = FaceBuilder::new(services.objects.surfaces.xy_plane())
.with_exterior(CycleBuilder::polygon(
[[4., 2.], [0., 4.], [0., 0.]],
&mut services.objects,
))
.build(&mut services.objects);
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 mut services = Services::new();
let face = FaceBuilder::new(services.objects.surfaces.xy_plane())
.with_exterior(CycleBuilder::polygon(
[[0., 0.], [2., 1.], [3., 0.], [3., 4.]],
&mut services.objects,
))
.build(&mut services.objects);
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 mut services = Services::new();
let face = FaceBuilder::new(services.objects.surfaces.xy_plane())
.with_exterior(CycleBuilder::polygon(
[[0., 0.], [2., 1.], [3., 1.], [0., 2.]],
&mut services.objects,
))
.build(&mut services.objects);
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 mut services = Services::new();
let face = FaceBuilder::new(services.objects.surfaces.xy_plane())
.with_exterior(CycleBuilder::polygon(
[[0., 0.], [2., 1.], [3., 1.], [4., 0.], [4., 5.]],
&mut services.objects,
))
.build(&mut services.objects);
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 mut services = Services::new();
let face = FaceBuilder::new(services.objects.surfaces.xy_plane())
.with_exterior(CycleBuilder::polygon(
[[0., 0.], [2., 0.], [0., 1.]],
&mut services.objects,
))
.build(&mut services.objects);
let point = Point::from([1., 0.]);
let intersection = (&face, &point).intersect();
let edge = face
.exterior()
.half_edges()
.find(|edge| edge.start_position() == Point::from([0., 0.]))
.unwrap();
assert_eq!(
intersection,
Some(FacePointIntersection::PointIsOnEdge(edge.clone()))
);
}
#[test]
fn point_is_coincident_with_vertex() {
let mut services = Services::new();
let face = FaceBuilder::new(services.objects.surfaces.xy_plane())
.with_exterior(CycleBuilder::polygon(
[[0., 0.], [1., 0.], [0., 1.]],
&mut services.objects,
))
.build(&mut services.objects);
let point = Point::from([1., 0.]);
let intersection = (&face, &point).intersect();
let vertex = face
.exterior()
.half_edges()
.find(|half_edge| {
half_edge.start_position() == Point::from([1., 0.])
})
.map(|half_edge| half_edge.start_position())
.unwrap();
assert_eq!(
intersection,
Some(FacePointIntersection::PointIsOnVertex(vertex))
);
}
}