rgeometry/algorithms/visibility/
naive.rs1use std::cmp::Ordering;
2
3use crate::data::{
4 Cursor, DirectedEdge, Direction, HalfLineSoS, IHalfLineLineSegmentSoS, Line, Point, Polygon,
5};
6use crate::{Intersects, Orientation, PolygonScalar};
7
8pub fn get_visibility_polygon<T>(point: &Point<T>, polygon: &Polygon<T>) -> Option<Polygon<T>>
93where
94 T: PolygonScalar,
95{
96 let mut vertices: Vec<Cursor<'_, T>> = polygon.iter_boundary().collect();
98 vertices.sort_by(|a, b| point.ccw_cmp_around(a, b));
99
100 let mut polygon_points = Vec::new();
101 for vertex in vertices {
102 let ray_sos = HalfLineSoS::new_through(point, vertex.point());
103 let mut right_intersection = NearestIntersection::new(point);
104 let mut left_intersection = NearestIntersection::new(point);
105
106 for edge in polygon.iter_boundary_edges() {
108 use IHalfLineLineSegmentSoS::*;
109 use Orientation::*;
110 match ray_sos.intersect(edge) {
111 None => (),
112 Some(Crossing(CoLinear)) => {
114 let isect = get_intersection(ray_sos, edge);
115 left_intersection.push(isect.clone());
116 right_intersection.push(isect);
117 }
118 Some(Crossing(CounterClockWise)) => {
120 left_intersection.push(get_intersection_colinear(ray_sos, edge));
121 }
122 Some(Crossing(ClockWise)) => {
124 right_intersection.push(get_intersection_colinear(ray_sos, edge));
125 }
126 }
127 }
128
129 match right_intersection.take() {
130 Some(interesection) => {
131 if point.cmp_distance_to(&interesection, &vertex) != Ordering::Less {
132 polygon_points.push(interesection);
133 }
134 }
135 None => return Option::None,
136 };
137 match left_intersection.take() {
138 Some(intersection) => {
139 if point.cmp_distance_to(&intersection, &vertex) != Ordering::Less {
140 polygon_points.push(intersection);
141 }
142 }
143 None => return Option::None,
144 };
145 }
146 polygon_points.dedup();
147
148 Some(Polygon::new(polygon_points).expect("Polygon Creation failed"))
149}
150
151fn get_intersection_colinear<T>(sos_line: HalfLineSoS<T>, edge: DirectedEdge<'_, T>) -> Point<T>
152where
153 T: PolygonScalar,
154{
155 let line: Line<T> = sos_line.into();
156 match Point::orient_along_direction(line.origin, line.direction, edge.src) {
157 Orientation::CoLinear => edge.src.clone(),
158 _ => edge.dst.clone(),
160 }
161}
162
163fn get_intersection<T>(sos_line: HalfLineSoS<T>, edge: DirectedEdge<'_, T>) -> Point<T>
164where
165 T: PolygonScalar,
166{
167 let segment_line = Line {
168 origin: edge.src,
169 direction: Direction::Through(edge.dst),
170 };
171 Line::from(sos_line)
172 .intersection_point(&segment_line)
173 .expect("LinesMustIntersect")
174}
175
176struct NearestIntersection<'a, T> {
178 origin: &'a Point<T>,
179 nearest_intersection: Option<Point<T>>,
180}
181
182impl<'a, T> NearestIntersection<'a, T>
183where
184 T: PolygonScalar,
185{
186 fn new(origin: &'a Point<T>) -> NearestIntersection<'a, T> {
187 NearestIntersection {
188 origin,
189 nearest_intersection: None,
190 }
191 }
192
193 fn push(&mut self, mut intersection: Point<T>) {
194 match self.nearest_intersection.as_mut() {
195 None => self.nearest_intersection = Some(intersection),
196 Some(previous) => {
197 if self.origin.cmp_distance_to(&intersection, previous) == Ordering::Less {
198 std::mem::swap(previous, &mut intersection);
199 }
200 }
201 }
202 }
203
204 fn take(self) -> Option<Point<T>> {
205 self.nearest_intersection
206 }
207}
208
209#[cfg(test)]
210mod naive_testing {
211 use super::*;
212
213 #[test]
219 fn point_outside_polygon() {
220 let point = Point::new([2, 8]);
221 let input_polygon = Polygon::new(vec![
222 Point::new([0, 0]),
223 Point::new([8, 0]),
224 Point::new([8, 6]),
225 Point::new([0, 6]),
226 ])
227 .unwrap();
228 let out_polygon = get_visibility_polygon(&point, &input_polygon);
229
230 assert!(out_polygon.is_none())
231 }
232 #[test]
238 fn case_1() {
239 let point = Point::new([2, 3]);
240 let input_polygon = Polygon::new(vec![
241 Point::new([0, 0]),
242 Point::new([8, 0]),
243 Point::new([8, 3]),
244 Point::new([10, 3]),
245 Point::new([10, 6]),
246 Point::new([6, 6]),
247 Point::new([6, 3]),
248 Point::new([4, 3]),
249 Point::new([4, 6]),
250 Point::new([0, 6]),
251 ])
252 .unwrap();
253 let out_test_points = [
254 Point::new([0, 0]),
255 Point::new([8, 0]),
256 Point::new([8, 3]),
257 Point::new([4, 3]),
258 Point::new([4, 6]),
259 Point::new([0, 6]),
260 ];
261 let out_polygon = get_visibility_polygon(&point, &input_polygon);
262 match out_polygon {
263 Some(polygon) => {
264 assert_eq!(out_test_points.len(), polygon.points.len())
266 }
267 None => panic!(),
268 }
269 }
270
271 #[test]
278 fn case_2() {
279 let point = Point::new([2, 3]);
280 let input_polygon = Polygon::new(vec![
281 Point::new([0, 0]),
282 Point::new([10, 0]),
283 Point::new([10, 6]),
284 Point::new([6, 6]),
285 Point::new([6, 3]),
286 Point::new([4, 3]),
287 Point::new([4, 6]),
288 Point::new([0, 6]),
289 ])
290 .unwrap();
291 let out_test_points = [
292 Point::new([0, 0]),
293 Point::new([10, 0]),
294 Point::new([10, 3]),
295 Point::new([4, 3]),
296 Point::new([4, 6]),
297 Point::new([0, 6]),
298 ];
299 let out_polygon = get_visibility_polygon(&point, &input_polygon);
300 match out_polygon {
301 Some(polygon) => {
302 assert_eq!(out_test_points.len(), polygon.points.len())
303 }
304 None => panic!(),
305 }
306 }
307
308 #[test]
310 fn test_rotating_square() {
311 use std::f64::consts::{FRAC_PI_2, PI};
312
313 fn get_point(theta: f64) -> Point<f64> {
314 Point::new([theta.sin(), theta.cos()])
315 }
316
317 for i in 0..100 {
318 let theta = PI / 2.0 * i as f64 / 100.0;
319 let points = vec![
320 get_point(theta),
321 get_point(theta - FRAC_PI_2),
322 get_point(theta - FRAC_PI_2 * 2.0),
323 get_point(theta - FRAC_PI_2 * 3.0),
324 ];
325
326 let point = Point::new([0.0, 0.0]);
327 let polygon = Polygon::new(points.clone()).unwrap();
328 let out_polygon = get_visibility_polygon(&point, &polygon).expect("get_visibility_polygon");
329 assert_eq!(points, out_polygon.points);
330 }
331 }
332}