use std::cmp::Ordering;
use crate::data::{
Cursor, DirectedEdgeView, DirectionView, HalfLineSoS, IHalfLineLineSegmentSoS, LineView, Point,
Polygon,
};
use crate::{Intersects, Orientation, PolygonScalar};
pub fn get_visibility_polygon<T>(point: &Point<T>, polygon: &Polygon<T>) -> Option<Polygon<T>>
where
T: PolygonScalar,
{
assert!(
polygon.rings.len() == 1,
"visibility polygon algorithm does not support polygons with holes"
);
let mut vertices: Vec<Cursor<'_, T>> = polygon.iter_boundary().collect();
vertices.sort_by(|a, b| point.ccw_cmp_around(a, b));
let mut polygon_points = Vec::new();
for vertex in vertices {
let ray_sos = HalfLineSoS::new_through(point, vertex.point());
let mut right_intersection = NearestIntersection::new(point);
let mut left_intersection = NearestIntersection::new(point);
for edge in polygon.iter_boundary_edges() {
use IHalfLineLineSegmentSoS::*;
use Orientation::*;
match ray_sos.intersect(edge) {
None => (),
Some(Crossing(CoLinear)) => {
let intersection = get_intersection(ray_sos, edge);
left_intersection.push(intersection.clone());
right_intersection.push(intersection);
}
Some(Crossing(CounterClockWise)) => {
left_intersection.push(get_intersection_colinear(ray_sos, edge));
}
Some(Crossing(ClockWise)) => {
right_intersection.push(get_intersection_colinear(ray_sos, edge));
}
}
}
match right_intersection.take() {
Some(intersection) => {
if point.cmp_distance_to(&intersection, &vertex) != Ordering::Less {
polygon_points.push(intersection);
}
}
None => return None,
};
match left_intersection.take() {
Some(intersection) => {
if point.cmp_distance_to(&intersection, &vertex) != Ordering::Less {
polygon_points.push(intersection);
}
}
None => return None,
};
}
polygon_points.dedup();
Polygon::new(polygon_points).ok()
}
fn get_intersection_colinear<T>(sos_line: HalfLineSoS<T>, edge: DirectedEdgeView<'_, T>) -> Point<T>
where
T: PolygonScalar,
{
let line: LineView<T> = sos_line.into();
match Point::orient_along_direction(line.origin, line.direction, edge.src) {
Orientation::CoLinear => edge.src.clone(),
_ => edge.dst.clone(),
}
}
fn get_intersection<T>(sos_line: HalfLineSoS<T>, edge: DirectedEdgeView<'_, T>) -> Point<T>
where
T: PolygonScalar,
{
let segment_line = LineView {
origin: edge.src,
direction: DirectionView::Through(edge.dst),
};
LineView::from(sos_line)
.intersection_point(&segment_line)
.expect("LinesMustIntersect")
}
struct NearestIntersection<'a, T> {
origin: &'a Point<T>,
nearest_intersection: Option<Point<T>>,
}
impl<'a, T> NearestIntersection<'a, T>
where
T: PolygonScalar,
{
fn new(origin: &'a Point<T>) -> NearestIntersection<'a, T> {
NearestIntersection {
origin,
nearest_intersection: None,
}
}
fn push(&mut self, mut intersection: Point<T>) {
match self.nearest_intersection.as_mut() {
None => self.nearest_intersection = Some(intersection),
Some(previous) => {
if self.origin.cmp_distance_to(&intersection, previous) == Ordering::Less {
std::mem::swap(previous, &mut intersection);
}
}
}
}
fn take(self) -> Option<Point<T>> {
self.nearest_intersection
}
}
#[cfg(test)]
mod naive_testing {
use super::*;
#[test]
fn point_outside_polygon() {
let point = Point::new([2, 8]);
let input_polygon = Polygon::new(vec![
Point::new([0, 0]),
Point::new([8, 0]),
Point::new([8, 6]),
Point::new([0, 6]),
])
.unwrap();
let out_polygon = get_visibility_polygon(&point, &input_polygon);
assert!(out_polygon.is_none())
}
#[test]
fn case_1() {
let point = Point::new([2, 3]);
let input_polygon = Polygon::new(vec![
Point::new([0, 0]),
Point::new([8, 0]),
Point::new([8, 3]),
Point::new([10, 3]),
Point::new([10, 6]),
Point::new([6, 6]),
Point::new([6, 3]),
Point::new([4, 3]),
Point::new([4, 6]),
Point::new([0, 6]),
])
.unwrap();
let out_test_points = [
Point::new([0, 0]),
Point::new([8, 0]),
Point::new([8, 3]),
Point::new([4, 3]),
Point::new([4, 6]),
Point::new([0, 6]),
];
let out_polygon = get_visibility_polygon(&point, &input_polygon);
match out_polygon {
Some(polygon) => {
assert_eq!(out_test_points.len(), polygon.points.len())
}
None => panic!(),
}
}
#[test]
fn case_2() {
let point = Point::new([2, 3]);
let input_polygon = Polygon::new(vec![
Point::new([0, 0]),
Point::new([10, 0]),
Point::new([10, 6]),
Point::new([6, 6]),
Point::new([6, 3]),
Point::new([4, 3]),
Point::new([4, 6]),
Point::new([0, 6]),
])
.unwrap();
let out_test_points = [
Point::new([0, 0]),
Point::new([10, 0]),
Point::new([10, 3]),
Point::new([4, 3]),
Point::new([4, 6]),
Point::new([0, 6]),
];
let out_polygon = get_visibility_polygon(&point, &input_polygon);
match out_polygon {
Some(polygon) => {
assert_eq!(out_test_points.len(), polygon.points.len())
}
None => panic!(),
}
}
#[test]
fn test_rotating_square() {
use std::f64::consts::{FRAC_PI_2, PI};
fn get_point(theta: f64) -> Point<f64> {
Point::new([theta.sin(), theta.cos()])
}
for i in 0..100 {
let theta = PI / 2.0 * i as f64 / 100.0;
let points = vec![
get_point(theta),
get_point(theta - FRAC_PI_2),
get_point(theta - FRAC_PI_2 * 2.0),
get_point(theta - FRAC_PI_2 * 3.0),
];
let point = Point::new([0.0, 0.0]);
let polygon = Polygon::new(points.clone()).unwrap();
let out_polygon = get_visibility_polygon(&point, &polygon).expect("get_visibility_polygon");
assert_eq!(points, out_polygon.points);
}
}
#[test]
fn point_on_vertex_returns_none() {
let point = Point::new([0, 0]);
let input_polygon = Polygon::new(vec![
Point::new([0, 0]),
Point::new([8, 0]),
Point::new([8, 6]),
Point::new([0, 6]),
])
.unwrap();
let out_polygon = get_visibility_polygon(&point, &input_polygon);
assert!(out_polygon.is_none());
}
#[test]
fn point_on_edge_returns_none() {
let point = Point::new([4, 0]);
let input_polygon = Polygon::new(vec![
Point::new([0, 0]),
Point::new([8, 0]),
Point::new([8, 6]),
Point::new([0, 6]),
])
.unwrap();
let out_polygon = get_visibility_polygon(&point, &input_polygon);
assert!(out_polygon.is_none());
}
#[test]
fn point_at_center_of_convex_polygon() {
let point = Point::new([4, 3]);
let input_polygon = Polygon::new(vec![
Point::new([0, 0]),
Point::new([8, 0]),
Point::new([8, 6]),
Point::new([0, 6]),
])
.unwrap();
let out_polygon = get_visibility_polygon(&point, &input_polygon);
assert!(out_polygon.is_some());
if let Some(ref polygon) = out_polygon {
assert!(!polygon.points.is_empty());
}
}
#[test]
fn point_on_multiple_edges() {
let point = Point::new([2, 2]);
let input_polygon = Polygon::new(vec![
Point::new([0, 0]),
Point::new([4, 0]),
Point::new([4, 4]),
Point::new([0, 4]),
])
.unwrap();
let out_polygon = get_visibility_polygon(&point, &input_polygon);
assert!(out_polygon.is_some());
}
}