1use crate::error::{SpatialError, SpatialResult};
49use scirs2_core::ndarray::{Array2, ArrayView2};
50use std::cmp::Ordering;
51use std::collections::HashMap;
52
53#[derive(Debug, Clone, Copy, PartialEq)]
55struct Point2D {
56 x: f64,
57 y: f64,
58}
59
60impl Point2D {
61 fn new(x: f64, y: f64) -> Self {
62 Self { x, y }
63 }
64
65 #[allow(dead_code)]
66 fn distance_to(&self, other: &Point2D) -> f64 {
67 ((self.x - other.x).powi(2) + (self.y - other.y).powi(2)).sqrt()
68 }
69
70 fn cross_product(&self, other: &Point2D) -> f64 {
71 self.x * other.y - self.y * other.x
72 }
73
74 #[allow(dead_code)]
75 fn dot_product(&self, other: &Point2D) -> f64 {
76 self.x * other.x + self.y * other.y
77 }
78}
79
80#[derive(Debug, Clone)]
82struct Edge {
83 start: Point2D,
84 end: Point2D,
85 polygon_id: usize, intersectionpoints: Vec<IntersectionPoint>,
87}
88
89#[derive(Debug, Clone)]
91#[allow(dead_code)]
92struct IntersectionPoint {
93 point: Point2D,
94 t: f64, other_edge_id: usize,
96}
97
98#[derive(Debug, Clone)]
100struct LabeledPolygon {
101 vertices: Vec<Point2D>,
102 edges: Vec<Edge>,
103 #[allow(dead_code)]
104 is_hole: bool,
105}
106
107impl LabeledPolygon {
108 fn from_array(vertices: &ArrayView2<'_, f64>) -> SpatialResult<Self> {
109 if vertices.ncols() != 2 {
110 return Err(SpatialError::ValueError(
111 "Polygon vertices must be 2D".to_string(),
112 ));
113 }
114
115 let points: Vec<Point2D> = vertices
116 .outer_iter()
117 .map(|row| Point2D::new(row[0], row[1]))
118 .collect();
119
120 if points.len() < 3 {
121 return Err(SpatialError::ValueError(
122 "Polygon must have at least 3 vertices".to_string(),
123 ));
124 }
125
126 let mut edges = Vec::new();
127 for i in 0..points.len() {
128 let start = points[i];
129 let end = points[(i + 1) % points.len()];
130 edges.push(Edge {
131 start,
132 end,
133 polygon_id: 0,
134 intersectionpoints: Vec::new(),
135 });
136 }
137
138 Ok(LabeledPolygon {
139 vertices: points,
140 edges,
141 is_hole: false,
142 })
143 }
144
145 fn to_array(&self) -> Array2<f64> {
146 let mut data = Vec::with_capacity(self.vertices.len() * 2);
147 for vertex in &self.vertices {
148 data.push(vertex.x);
149 data.push(vertex.y);
150 }
151 Array2::from_shape_vec((self.vertices.len(), 2), data).unwrap()
152 }
153
154 fn ispoint_inside(&self, point: &Point2D) -> bool {
155 let mut inside = false;
156 let n = self.vertices.len();
157
158 for i in 0..n {
159 let j = (i + 1) % n;
160 let vi = &self.vertices[i];
161 let vj = &self.vertices[j];
162
163 if ((vi.y > point.y) != (vj.y > point.y))
164 && (point.x < (vj.x - vi.x) * (point.y - vi.y) / (vj.y - vi.y) + vi.x)
165 {
166 inside = !inside;
167 }
168 }
169
170 inside
171 }
172
173 fn compute_area(&self) -> f64 {
174 let mut area = 0.0;
175 let n = self.vertices.len();
176
177 for i in 0..n {
178 let j = (i + 1) % n;
179 let vi = &self.vertices[i];
180 let vj = &self.vertices[j];
181 area += vi.x * vj.y - vj.x * vi.y;
182 }
183
184 area.abs() / 2.0
185 }
186
187 #[allow(dead_code)]
188 fn is_clockwise(&self) -> bool {
189 let mut sum = 0.0;
190 let n = self.vertices.len();
191
192 for i in 0..n {
193 let j = (i + 1) % n;
194 let vi = &self.vertices[i];
195 let vj = &self.vertices[j];
196 sum += (vj.x - vi.x) * (vj.y + vi.y);
197 }
198
199 sum > 0.0
200 }
201
202 #[allow(dead_code)]
203 fn reverse(&mut self) {
204 self.vertices.reverse();
205 let mut edges = Vec::new();
207 for i in 0..self.vertices.len() {
208 let start = self.vertices[i];
209 let end = self.vertices[(i + 1) % self.vertices.len()];
210 edges.push(Edge {
211 start,
212 end,
213 polygon_id: self.edges[0].polygon_id,
214 intersectionpoints: Vec::new(),
215 });
216 }
217 self.edges = edges;
218 }
219}
220
221#[allow(dead_code)]
244pub fn polygon_union(
245 poly1: &ArrayView2<'_, f64>,
246 poly2: &ArrayView2<'_, f64>,
247) -> SpatialResult<Array2<f64>> {
248 let mut p1 = LabeledPolygon::from_array(poly1)?;
249 let mut p2 = LabeledPolygon::from_array(poly2)?;
250
251 for edge in &mut p1.edges {
253 edge.polygon_id = 0;
254 }
255 for edge in &mut p2.edges {
256 edge.polygon_id = 1;
257 }
258
259 find_intersections(&mut p1, &mut p2)?;
261
262 let result_polygons = weiler_atherton_union(&p1, &p2)?;
264
265 if result_polygons.is_empty() {
266 if p1.compute_area() >= p2.compute_area() {
268 Ok(p1.to_array())
269 } else {
270 Ok(p2.to_array())
271 }
272 } else {
273 Ok(result_polygons[0].to_array())
275 }
276}
277
278#[allow(dead_code)]
289pub fn polygon_intersection(
290 poly1: &ArrayView2<'_, f64>,
291 poly2: &ArrayView2<'_, f64>,
292) -> SpatialResult<Array2<f64>> {
293 let mut p1 = LabeledPolygon::from_array(poly1)?;
294 let mut p2 = LabeledPolygon::from_array(poly2)?;
295
296 for edge in &mut p1.edges {
298 edge.polygon_id = 0;
299 }
300 for edge in &mut p2.edges {
301 edge.polygon_id = 1;
302 }
303
304 find_intersections(&mut p1, &mut p2)?;
306
307 let result = sutherland_hodgman_clip(&p1, &p2)?;
309
310 Ok(result.to_array())
311}
312
313#[allow(dead_code)]
324pub fn polygon_difference(
325 poly1: &ArrayView2<'_, f64>,
326 poly2: &ArrayView2<'_, f64>,
327) -> SpatialResult<Array2<f64>> {
328 let mut p1 = LabeledPolygon::from_array(poly1)?;
329 let mut p2 = LabeledPolygon::from_array(poly2)?;
330
331 for edge in &mut p1.edges {
333 edge.polygon_id = 0;
334 }
335 for edge in &mut p2.edges {
336 edge.polygon_id = 1;
337 }
338
339 find_intersections(&mut p1, &mut p2)?;
341
342 let result = weiler_atherton_difference(&p1, &p2)?;
344
345 if result.is_empty() {
346 Ok(p1.to_array())
348 } else {
349 Ok(result[0].to_array())
350 }
351}
352
353#[allow(dead_code)]
364pub fn polygon_symmetric_difference(
365 poly1: &ArrayView2<'_, f64>,
366 poly2: &ArrayView2<'_, f64>,
367) -> SpatialResult<Array2<f64>> {
368 let diff1 = polygon_difference(poly1, poly2)?;
370 let diff2 = polygon_difference(poly2, poly1)?;
371
372 polygon_union(&diff1.view(), &diff2.view())
374}
375
376#[allow(dead_code)]
378fn find_intersections(poly1: &mut LabeledPolygon, poly2: &mut LabeledPolygon) -> SpatialResult<()> {
379 for (i, edge1) in poly1.edges.iter_mut().enumerate() {
380 for (j, edge2) in poly2.edges.iter_mut().enumerate() {
381 if let Some((intersectionpoint, t1, t2)) =
382 line_segment_intersection(&edge1.start, &edge1.end, &edge2.start, &edge2.end)
383 {
384 edge1.intersectionpoints.push(IntersectionPoint {
386 point: intersectionpoint,
387 t: t1,
388 other_edge_id: j,
389 });
390
391 edge2.intersectionpoints.push(IntersectionPoint {
392 point: intersectionpoint,
393 t: t2,
394 other_edge_id: i,
395 });
396 }
397 }
398 }
399
400 for edge in &mut poly1.edges {
402 edge.intersectionpoints
403 .sort_by(|a, b| a.t.partial_cmp(&b.t).unwrap_or(Ordering::Equal));
404 }
405 for edge in &mut poly2.edges {
406 edge.intersectionpoints
407 .sort_by(|a, b| a.t.partial_cmp(&b.t).unwrap_or(Ordering::Equal));
408 }
409
410 Ok(())
411}
412
413#[allow(dead_code)]
415fn line_segment_intersection(
416 p1: &Point2D,
417 p2: &Point2D,
418 p3: &Point2D,
419 p4: &Point2D,
420) -> Option<(Point2D, f64, f64)> {
421 let x1 = p1.x;
422 let y1 = p1.y;
423 let x2 = p2.x;
424 let y2 = p2.y;
425 let x3 = p3.x;
426 let y3 = p3.y;
427 let x4 = p4.x;
428 let y4 = p4.y;
429
430 let denom = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
431
432 if denom.abs() < 1e-10 {
433 return None;
435 }
436
437 let t = ((x1 - x3) * (y3 - y4) - (y1 - y3) * (x3 - x4)) / denom;
438 let u = -((x1 - x2) * (y1 - y3) - (y1 - y2) * (x1 - x3)) / denom;
439
440 if (0.0..=1.0).contains(&t) && (0.0..=1.0).contains(&u) {
442 let ix = x1 + t * (x2 - x1);
443 let iy = y1 + t * (y2 - y1);
444 let intersection = Point2D::new(ix, iy);
445 Some((intersection, t, u))
446 } else {
447 None
448 }
449}
450
451#[allow(dead_code)]
453fn sutherland_hodgman_clip(
454 subject: &LabeledPolygon,
455 clip: &LabeledPolygon,
456) -> SpatialResult<LabeledPolygon> {
457 let mut outputvertices = subject.vertices.clone();
458
459 for i in 0..clip.vertices.len() {
460 if outputvertices.is_empty() {
461 break;
462 }
463
464 let clip_edge_start = clip.vertices[i];
465 let clip_edge_end = clip.vertices[(i + 1) % clip.vertices.len()];
466
467 let inputvertices = outputvertices.clone();
468 outputvertices.clear();
469
470 if !inputvertices.is_empty() {
471 let mut s = inputvertices[inputvertices.len() - 1];
472
473 for vertex in inputvertices {
474 if is_inside(&vertex, &clip_edge_start, &clip_edge_end) {
475 if !is_inside(&s, &clip_edge_start, &clip_edge_end) {
476 if let Some((intersection__, _, _)) =
478 line_segment_intersection(&s, &vertex, &clip_edge_start, &clip_edge_end)
479 {
480 outputvertices.push(intersection__);
481 }
482 }
483 outputvertices.push(vertex);
484 } else if is_inside(&s, &clip_edge_start, &clip_edge_end) {
485 if let Some((intersection__, _, _)) =
487 line_segment_intersection(&s, &vertex, &clip_edge_start, &clip_edge_end)
488 {
489 outputvertices.push(intersection__);
490 }
491 }
492 s = vertex;
493 }
494 }
495 }
496
497 if outputvertices.len() < 3 {
499 outputvertices.clear();
501 }
502
503 let mut edges = Vec::new();
504 for i in 0..outputvertices.len() {
505 let start = outputvertices[i];
506 let end = outputvertices[(i + 1) % outputvertices.len()];
507 edges.push(Edge {
508 start,
509 end,
510 polygon_id: 0,
511 intersectionpoints: Vec::new(),
512 });
513 }
514
515 Ok(LabeledPolygon {
516 vertices: outputvertices,
517 edges,
518 is_hole: false,
519 })
520}
521
522#[allow(dead_code)]
524fn is_inside(point: &Point2D, edge_start: &Point2D, edgeend: &Point2D) -> bool {
525 let edge_vector = Point2D::new(edgeend.x - edge_start.x, edgeend.y - edge_start.y);
526 let point_vector = Point2D::new(point.x - edge_start.x, point.y - edge_start.y);
527 edge_vector.cross_product(&point_vector) >= 0.0
528}
529
530#[allow(dead_code)]
532fn weiler_atherton_union(
533 poly1: &LabeledPolygon,
534 poly2: &LabeledPolygon,
535) -> SpatialResult<Vec<LabeledPolygon>> {
536 if !polygons_intersect(poly1, poly2) {
538 return Ok(vec![poly1.clone(), poly2.clone()]);
540 }
541
542 let intersection_graph = build_intersection_graph(poly1, poly2)?;
544
545 let result_polygons = trace_union_boundary(&intersection_graph, poly1, poly2)?;
547
548 Ok(result_polygons)
549}
550
551#[allow(dead_code)]
553fn weiler_atherton_difference(
554 poly1: &LabeledPolygon,
555 poly2: &LabeledPolygon,
556) -> SpatialResult<Vec<LabeledPolygon>> {
557 if !polygons_intersect(poly1, poly2) {
559 return Ok(vec![poly1.clone()]);
561 }
562
563 let intersection_graph = build_intersection_graph(poly1, poly2)?;
565
566 let result_polygons = trace_difference_boundary(&intersection_graph, poly1, poly2)?;
568
569 Ok(result_polygons)
570}
571
572#[allow(dead_code)]
574fn polygons_intersect(poly1: &LabeledPolygon, poly2: &LabeledPolygon) -> bool {
575 for vertex in &poly1.vertices {
577 if poly2.ispoint_inside(vertex) {
578 return true;
579 }
580 }
581
582 for vertex in &poly2.vertices {
583 if poly1.ispoint_inside(vertex) {
584 return true;
585 }
586 }
587
588 for edge1 in &poly1.edges {
590 for edge2 in &poly2.edges {
591 if line_segment_intersection(&edge1.start, &edge1.end, &edge2.start, &edge2.end)
592 .is_some()
593 {
594 return true;
595 }
596 }
597 }
598
599 false
600}
601
602#[allow(dead_code)]
604fn build_intersection_graph(
605 poly1: &LabeledPolygon,
606 _poly2: &LabeledPolygon,
607) -> SpatialResult<HashMap<String, Vec<Point2D>>> {
608 Ok(HashMap::new())
612}
613
614#[allow(dead_code)]
616fn trace_union_boundary(
617 _graph: &HashMap<String, Vec<Point2D>>,
618 poly1: &LabeledPolygon,
619 poly2: &LabeledPolygon,
620) -> SpatialResult<Vec<LabeledPolygon>> {
621 if poly1.compute_area() >= poly2.compute_area() {
624 Ok(vec![poly1.clone()])
625 } else {
626 Ok(vec![poly2.clone()])
627 }
628}
629
630#[allow(dead_code)]
632fn trace_difference_boundary(
633 _graph: &HashMap<String, Vec<Point2D>>,
634 poly1: &LabeledPolygon,
635 _poly2: &LabeledPolygon,
636) -> SpatialResult<Vec<LabeledPolygon>> {
637 Ok(vec![poly1.clone()])
640}
641
642#[allow(dead_code)]
644pub fn is_convex_polygon(vertices: &ArrayView2<'_, f64>) -> SpatialResult<bool> {
645 if vertices.ncols() != 2 {
646 return Err(SpatialError::ValueError("Vertices must be 2D".to_string()));
647 }
648
649 let n = vertices.nrows();
650 if n < 3 {
651 return Ok(false);
652 }
653
654 let mut sign = 0i32;
655
656 for i in 0..n {
657 let p1 = Point2D::new(vertices[[i, 0]], vertices[[i, 1]]);
658 let p2 = Point2D::new(vertices[[(i + 1) % n, 0]], vertices[[(i + 1) % n, 1]]);
659 let p3 = Point2D::new(vertices[[(i + 2) % n, 0]], vertices[[(i + 2) % n, 1]]);
660
661 let v1 = Point2D::new(p2.x - p1.x, p2.y - p1.y);
662 let v2 = Point2D::new(p3.x - p2.x, p3.y - p2.y);
663
664 let cross = v1.cross_product(&v2);
665
666 if cross.abs() > 1e-10 {
667 let current_sign = if cross > 0.0 { 1 } else { -1 };
668
669 if sign == 0 {
670 sign = current_sign;
671 } else if sign != current_sign {
672 return Ok(false);
673 }
674 }
675 }
676
677 Ok(true)
678}
679
680#[allow(dead_code)]
682pub fn compute_polygon_area(vertices: &ArrayView2<'_, f64>) -> SpatialResult<f64> {
683 let polygon = LabeledPolygon::from_array(vertices)?;
684 Ok(polygon.compute_area())
685}
686
687#[allow(dead_code)]
689pub fn is_self_intersecting(vertices: &ArrayView2<'_, f64>) -> SpatialResult<bool> {
690 let polygon = LabeledPolygon::from_array(vertices)?;
691 let n = polygon.vertices.len();
692
693 for i in 0..n {
694 let edge1_start = polygon.vertices[i];
695 let edge1_end = polygon.vertices[(i + 1) % n];
696
697 for j in (i + 2)..n {
698 if j == (i + n - 1) % n {
700 continue;
701 }
702
703 let edge2_start = polygon.vertices[j];
704 let edge2_end = polygon.vertices[(j + 1) % n];
705
706 if line_segment_intersection(&edge1_start, &edge1_end, &edge2_start, &edge2_end)
707 .is_some()
708 {
709 return Ok(true);
710 }
711 }
712 }
713
714 Ok(false)
715}
716
717#[cfg(test)]
718mod tests {
719 use super::*;
720 use approx::assert_relative_eq;
721 use scirs2_core::ndarray::arr2;
722
723 #[test]
724 fn testpoint2d_operations() {
725 let p1 = Point2D::new(0.0, 0.0);
726 let p2 = Point2D::new(1.0, 1.0);
727
728 assert_relative_eq!(p1.distance_to(&p2), 2.0_f64.sqrt(), epsilon = 1e-10);
729
730 let v1 = Point2D::new(1.0, 0.0);
731 let v2 = Point2D::new(0.0, 1.0);
732 assert_relative_eq!(v1.cross_product(&v2), 1.0, epsilon = 1e-10);
733 assert_relative_eq!(v1.dot_product(&v2), 0.0, epsilon = 1e-10);
734 }
735
736 #[test]
737 fn test_polygon_creation() {
738 let vertices = arr2(&[[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]]);
739 let polygon = LabeledPolygon::from_array(&vertices.view()).unwrap();
740
741 assert_eq!(polygon.vertices.len(), 4);
742 assert_eq!(polygon.edges.len(), 4);
743 assert_relative_eq!(polygon.compute_area(), 1.0, epsilon = 1e-10);
744 }
745
746 #[test]
747 fn testpoint_inside_polygon() {
748 let vertices = arr2(&[[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]]);
749 let polygon = LabeledPolygon::from_array(&vertices.view()).unwrap();
750
751 let insidepoint = Point2D::new(0.5, 0.5);
752 let outsidepoint = Point2D::new(1.5, 1.5);
753
754 assert!(polygon.ispoint_inside(&insidepoint));
755 assert!(!polygon.ispoint_inside(&outsidepoint));
756 }
757
758 #[test]
759 fn test_line_intersection() {
760 let p1 = Point2D::new(0.0, 0.0);
761 let p2 = Point2D::new(1.0, 1.0);
762 let p3 = Point2D::new(0.0, 1.0);
763 let p4 = Point2D::new(1.0, 0.0);
764
765 let result = line_segment_intersection(&p1, &p2, &p3, &p4);
766 assert!(result.is_some());
767
768 let (intersection, t1, t2) = result.unwrap();
769 assert_relative_eq!(intersection.x, 0.5, epsilon = 1e-10);
770 assert_relative_eq!(intersection.y, 0.5, epsilon = 1e-10);
771 assert_relative_eq!(t1, 0.5, epsilon = 1e-10);
772 assert_relative_eq!(t2, 0.5, epsilon = 1e-10);
773 }
774
775 #[test]
776 fn test_is_convex_polygon() {
777 let convex = arr2(&[[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]]);
779 assert!(is_convex_polygon(&convex.view()).unwrap());
780
781 let non_convex = arr2(&[
783 [0.0, 0.0],
784 [1.0, 0.0],
785 [1.0, 0.5],
786 [0.5, 0.5],
787 [0.5, 1.0],
788 [0.0, 1.0],
789 ]);
790 assert!(!is_convex_polygon(&non_convex.view()).unwrap());
791 }
792
793 #[test]
794 fn test_polygon_area() {
795 let square = arr2(&[[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]]);
796 let area = compute_polygon_area(&square.view()).unwrap();
797 assert_relative_eq!(area, 1.0, epsilon = 1e-10);
798
799 let triangle = arr2(&[[0.0, 0.0], [1.0, 0.0], [0.5, 1.0]]);
800 let area = compute_polygon_area(&triangle.view()).unwrap();
801 assert_relative_eq!(area, 0.5, epsilon = 1e-10);
802 }
803
804 #[test]
805 fn test_intersection() {
806 let square = arr2(&[[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]]);
808 assert!(!is_self_intersecting(&square.view()).unwrap());
809
810 let bowtie = arr2(&[[0.0, 0.0], [1.0, 1.0], [1.0, 0.0], [0.0, 1.0]]);
812 assert!(is_self_intersecting(&bowtie.view()).unwrap());
813 }
814
815 #[test]
816 fn test_polygon_union_basic() {
817 let poly1 = arr2(&[[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]]);
819 let poly2 = arr2(&[[2.0, 0.0], [3.0, 0.0], [3.0, 1.0], [2.0, 1.0]]);
820
821 let union = polygon_union(&poly1.view(), &poly2.view()).unwrap();
822 assert!(union.nrows() >= 4); }
824
825 #[test]
826 fn test_polygon_intersection_basic() {
827 let poly1 = arr2(&[[0.0, 0.0], [2.0, 0.0], [2.0, 2.0], [0.0, 2.0]]);
829 let poly2 = arr2(&[[1.0, 1.0], [3.0, 1.0], [3.0, 3.0], [1.0, 3.0]]);
830
831 let intersection = polygon_intersection(&poly1.view(), &poly2.view()).unwrap();
832
833 if intersection.nrows() > 0 {
835 let area = compute_polygon_area(&intersection.view()).unwrap();
836 assert!(area > 0.0); }
838 }
839
840 #[test]
841 fn test_polygon_difference_basic() {
842 let poly1 = arr2(&[[0.0, 0.0], [2.0, 0.0], [2.0, 2.0], [0.0, 2.0]]);
843 let poly2 = arr2(&[[1.0, 1.0], [3.0, 1.0], [3.0, 3.0], [1.0, 3.0]]);
844
845 let difference = polygon_difference(&poly1.view(), &poly2.view()).unwrap();
846 assert!(difference.nrows() >= 3); }
848
849 #[test]
850 fn test_sutherland_hodgman_clip() {
851 let subjectvertices = vec![
852 Point2D::new(0.0, 0.0),
853 Point2D::new(2.0, 0.0),
854 Point2D::new(2.0, 2.0),
855 Point2D::new(0.0, 2.0),
856 ];
857
858 let clipvertices = vec![
859 Point2D::new(1.0, 1.0),
860 Point2D::new(3.0, 1.0),
861 Point2D::new(3.0, 3.0),
862 Point2D::new(1.0, 3.0),
863 ];
864
865 let subject = LabeledPolygon {
866 vertices: subjectvertices,
867 edges: Vec::new(),
868 is_hole: false,
869 };
870
871 let clip = LabeledPolygon {
872 vertices: clipvertices,
873 edges: Vec::new(),
874 is_hole: false,
875 };
876
877 let result = sutherland_hodgman_clip(&subject, &clip).unwrap();
878
879 assert!(result.vertices.len() >= 3);
881 }
882}