1use crate::{Classification, Plane3D, PlaneSide, Polygon, Rectangle, Triangle};
4
5pub trait Cuttable {
7 fn cut(&self, plane: &Plane3D) -> (Option<Polygon>, Option<Polygon>);
20}
21
22impl Cuttable for Polygon {
23 fn cut(&self, plane: &Plane3D) -> (Option<Polygon>, Option<Polygon>) {
24 match self.classify(plane) {
25 Classification::Front | Classification::Coplanar => {
26 (Some(self.clone()), None)
27 }
28 Classification::Back => {
29 (None, Some(self.clone()))
30 }
31 Classification::Spanning => {
32 split_polygon(self, plane)
33 }
34 }
35 }
36}
37
38fn split_polygon(polygon: &Polygon, plane: &Plane3D) -> (Option<Polygon>, Option<Polygon>) {
44 let vertices = polygon.vertices();
45 let n = vertices.len();
46
47 let mut front_verts = Vec::with_capacity(n + 1);
48 let mut back_verts = Vec::with_capacity(n + 1);
49
50 let sides: Vec<PlaneSide> = vertices
52 .iter()
53 .map(|v| plane.classify_point(*v))
54 .collect();
55
56 for i in 0..n {
57 let current = vertices[i];
58 let current_side = sides[i];
59 let next_idx = (i + 1) % n;
60 let next = vertices[next_idx];
61 let next_side = sides[next_idx];
62
63 match current_side {
65 PlaneSide::Front => front_verts.push(current),
66 PlaneSide::Back => back_verts.push(current),
67 PlaneSide::OnPlane => {
68 front_verts.push(current);
70 back_verts.push(current);
71 }
72 }
73
74 let crosses = matches!(
76 (current_side, next_side),
77 (PlaneSide::Front, PlaneSide::Back) | (PlaneSide::Back, PlaneSide::Front)
78 );
79
80 if crosses {
81 if let Some((_, intersection)) = plane.intersect_segment(current, next) {
83 front_verts.push(intersection);
84 back_verts.push(intersection);
85 }
86 }
87 }
88
89 let front = if front_verts.len() >= 3 {
91 Some(Polygon::new(front_verts))
92 } else {
93 None
94 };
95
96 let back = if back_verts.len() >= 3 {
97 Some(Polygon::new(back_verts))
98 } else {
99 None
100 };
101
102 (front, back)
103}
104
105impl Cuttable for Triangle {
106 fn cut(&self, plane: &Plane3D) -> (Option<Polygon>, Option<Polygon>) {
107 Polygon::from(self).cut(plane)
108 }
109}
110
111impl Cuttable for Rectangle {
112 fn cut(&self, plane: &Plane3D) -> (Option<Polygon>, Option<Polygon>) {
113 Polygon::from(self).cut(plane)
114 }
115}
116
117#[cfg(test)]
118mod tests {
119 use super::*;
120 use nalgebra::{Point3, Vector3};
121
122 fn horizontal_plane(height: f32) -> Plane3D {
128 Plane3D::from_point_and_normal(
129 Point3::new(0.0, height, 0.0),
130 Vector3::new(0.0, 1.0, 0.0),
131 )
132 }
133
134 fn vertical_plane_x(offset: f32) -> Plane3D {
136 Plane3D::from_point_and_normal(
137 Point3::new(offset, 0.0, 0.0),
138 Vector3::new(1.0, 0.0, 0.0),
139 )
140 }
141
142 fn vertical_plane_z(offset: f32) -> Plane3D {
144 Plane3D::from_point_and_normal(
145 Point3::new(0.0, 0.0, offset),
146 Vector3::new(0.0, 0.0, 1.0),
147 )
148 }
149
150 fn assert_all_vertices_on_side(polygon: &Polygon, plane: &Plane3D, expected: PlaneSide) {
152 for (i, v) in polygon.vertices().iter().enumerate() {
153 let side = plane.classify_point(*v);
154 assert!(
155 side == expected || side == PlaneSide::OnPlane,
156 "Vertex {i} at {v:?} is on {side:?}, expected {expected:?} or OnPlane"
157 );
158 }
159 }
160
161 fn assert_vertex_count(polygon: &Polygon, expected: usize) {
163 assert_eq!(
164 polygon.len(),
165 expected,
166 "Expected {expected} vertices, got {}",
167 polygon.len()
168 );
169 }
170
171 fn approx_eq(a: f32, b: f32, epsilon: f32) -> bool {
173 (a - b).abs() < epsilon
174 }
175
176 fn assert_point_on_plane(point: Point3<f32>, plane: &Plane3D) {
178 let dist = plane.signed_distance(point).abs();
179 assert!(
180 dist < 1e-4,
181 "Point {point:?} should be on plane, but distance is {dist}"
182 );
183 }
184
185 fn same_polygon_vertices(actual: &[Point3<f32>], expected: &[Point3<f32>]) -> bool {
190 if actual.len() != expected.len() {
191 return false;
192 }
193 let n = actual.len();
194 if n == 0 {
195 return true;
196 }
197
198 let Some(offset) = actual.iter().position(|v| v == &expected[0]) else {
200 return false;
201 };
202
203 for i in 0..n {
205 if actual[(offset + i) % n] != expected[i] {
206 return false;
207 }
208 }
209 true
210 }
211
212 #[test]
217 fn polygon_entirely_in_front() {
218 let polygon = Polygon::new(vec![
220 Point3::new(0.0, 1.0, 0.0),
221 Point3::new(1.0, 2.0, 0.0),
222 Point3::new(0.0, 1.5, 1.0),
223 ]);
224 let plane = horizontal_plane(0.0);
225
226 let (front, back) = polygon.cut(&plane);
227
228 assert!(front.is_some(), "Front should contain the polygon");
229 assert!(back.is_none(), "Back should be empty");
230 assert_eq!(front.unwrap().vertices(), polygon.vertices());
231 }
232
233 #[test]
234 fn polygon_entirely_behind() {
235 let polygon = Polygon::new(vec![
237 Point3::new(0.0, -1.0, 0.0),
238 Point3::new(1.0, -2.0, 0.0),
239 Point3::new(0.0, -1.5, 1.0),
240 ]);
241 let plane = horizontal_plane(0.0);
242
243 let (front, back) = polygon.cut(&plane);
244
245 assert!(front.is_none(), "Front should be empty");
246 assert!(back.is_some(), "Back should contain the polygon");
247 assert_eq!(back.unwrap().vertices(), polygon.vertices());
248 }
249
250 #[test]
251 fn polygon_coplanar_with_plane() {
252 let polygon = Polygon::new(vec![
254 Point3::new(0.0, 0.0, 0.0),
255 Point3::new(1.0, 0.0, 0.0),
256 Point3::new(0.5, 0.0, 1.0),
257 ]);
258 let plane = horizontal_plane(0.0);
259
260 let (front, back) = polygon.cut(&plane);
261
262 assert!(front.is_some(), "Coplanar should be treated as front");
263 assert!(back.is_none(), "Back should be empty for coplanar");
264 assert_eq!(front.unwrap().vertices(), polygon.vertices());
265 }
266
267 #[test]
272 fn polygon_split_triangle_one_vertex_front() {
273 let polygon = Polygon::new(vec![
278 Point3::new(0.0, 2.0, 0.0), Point3::new(-1.0, -1.0, 0.0), Point3::new(1.0, -1.0, 0.0), ]);
282 let plane = horizontal_plane(0.0);
283
284 let (front, back) = polygon.cut(&plane);
285
286 assert!(front.is_some(), "Should have front part");
287 assert!(back.is_some(), "Should have back part");
288
289 let front = front.unwrap();
290 let back = back.unwrap();
291
292 assert_vertex_count(&front, 3);
294 assert_vertex_count(&back, 4);
296
297 assert_all_vertices_on_side(&front, &plane, PlaneSide::Front);
299 assert_all_vertices_on_side(&back, &plane, PlaneSide::Back);
301 }
302
303 #[test]
304 fn polygon_split_triangle_two_vertices_front() {
305 let polygon = Polygon::new(vec![
307 Point3::new(-1.0, 1.0, 0.0), Point3::new(1.0, 1.0, 0.0), Point3::new(0.0, -2.0, 0.0), ]);
311 let plane = horizontal_plane(0.0);
312
313 let (front, back) = polygon.cut(&plane);
314
315 assert!(front.is_some(), "Should have front part");
316 assert!(back.is_some(), "Should have back part");
317
318 let front = front.unwrap();
319 let back = back.unwrap();
320
321 assert_vertex_count(&front, 4);
323 assert_vertex_count(&back, 3);
325
326 assert_all_vertices_on_side(&front, &plane, PlaneSide::Front);
327 assert_all_vertices_on_side(&back, &plane, PlaneSide::Back);
328 }
329
330 #[test]
331 fn polygon_split_quad_in_half() {
332 let polygon = Polygon::new(vec![
335 Point3::new(0.0, 1.0, 0.0), Point3::new(1.0, 1.0, 0.0), Point3::new(1.0, -1.0, 0.0), Point3::new(0.0, -1.0, 0.0), ]);
340 let plane = horizontal_plane(0.0);
341
342 let (front, back) = polygon.cut(&plane);
343
344 assert!(front.is_some());
345 assert!(back.is_some());
346
347 let front = front.unwrap();
348 let back = back.unwrap();
349
350 assert_vertex_count(&front, 4);
352 assert_vertex_count(&back, 4);
353
354 assert_all_vertices_on_side(&front, &plane, PlaneSide::Front);
355 assert_all_vertices_on_side(&back, &plane, PlaneSide::Back);
356 }
357
358 #[test]
359 fn polygon_split_pentagon() {
360 let polygon = Polygon::new(vec![
362 Point3::new(0.0, 2.0, 0.0), Point3::new(2.0, 1.0, 0.0), Point3::new(1.5, -1.0, 0.0), Point3::new(-1.5, -1.0, 0.0), Point3::new(-2.0, 1.0, 0.0), ]);
368 let plane = horizontal_plane(0.0);
369
370 let (front, back) = polygon.cut(&plane);
371
372 assert!(front.is_some());
373 assert!(back.is_some());
374
375 let front = front.unwrap();
376 let back = back.unwrap();
377
378 assert_vertex_count(&front, 5);
380 assert_vertex_count(&back, 4);
382
383 assert_all_vertices_on_side(&front, &plane, PlaneSide::Front);
384 assert_all_vertices_on_side(&back, &plane, PlaneSide::Back);
385 }
386
387 #[test]
388 fn polygon_split_preserves_intersection_points_on_plane() {
389 let polygon = Polygon::new(vec![
391 Point3::new(0.0, 3.0, 0.0),
392 Point3::new(2.0, -1.0, 1.0),
393 Point3::new(-2.0, -1.0, -1.0),
394 ]);
395 let plane = horizontal_plane(0.0);
396
397 let (front, back) = polygon.cut(&plane);
398
399 let front = front.unwrap();
400 let back = back.unwrap();
401
402 for fv in front.vertices() {
404 if plane.classify_point(*fv) == PlaneSide::OnPlane {
405 assert_point_on_plane(*fv, &plane);
406 }
407 }
408 for bv in back.vertices() {
409 if plane.classify_point(*bv) == PlaneSide::OnPlane {
410 assert_point_on_plane(*bv, &plane);
411 }
412 }
413 }
414
415 #[test]
420 fn polygon_one_vertex_exactly_on_plane() {
421 let polygon = Polygon::new(vec![
423 Point3::new(0.0, 0.0, 0.0), Point3::new(1.0, 1.0, 0.0), Point3::new(-1.0, 1.0, 0.0), ]);
427 let plane = horizontal_plane(0.0);
428
429 let (front, back) = polygon.cut(&plane);
430
431 assert!(front.is_some());
433 assert!(back.is_none());
434 }
435
436 #[test]
437 fn polygon_one_vertex_on_plane_spanning() {
438 let polygon = Polygon::new(vec![
440 Point3::new(0.0, 0.0, 0.0), Point3::new(1.0, 1.0, 0.0), Point3::new(1.0, -1.0, 0.0), ]);
444 let plane = horizontal_plane(0.0);
445
446 let (front, back) = polygon.cut(&plane);
447
448 assert!(front.is_some());
449 assert!(back.is_some());
450
451 let front = front.unwrap();
452 let back = back.unwrap();
453
454 let on_plane_point = Point3::new(0.0, 0.0, 0.0);
456 assert!(
457 front.vertices().contains(&on_plane_point),
458 "On-plane vertex should be in front polygon"
459 );
460 assert!(
461 back.vertices().contains(&on_plane_point),
462 "On-plane vertex should be in back polygon"
463 );
464 }
465
466 #[test]
467 fn polygon_edge_lies_on_plane() {
468 let polygon = Polygon::new(vec![
470 Point3::new(0.0, 0.0, 0.0), Point3::new(1.0, 0.0, 0.0), Point3::new(1.0, 1.0, 0.0), Point3::new(0.0, 1.0, 0.0), ]);
475 let plane = horizontal_plane(0.0);
476
477 let (front, back) = polygon.cut(&plane);
478
479 assert!(front.is_some());
481 assert!(back.is_none());
482 }
483
484 #[test]
485 fn polygon_edge_on_plane_with_back_vertices() {
486 let polygon = Polygon::new(vec![
488 Point3::new(0.0, 0.0, 0.0), Point3::new(1.0, 0.0, 0.0), Point3::new(1.0, -1.0, 0.0), Point3::new(0.0, -1.0, 0.0), ]);
493 let plane = horizontal_plane(0.0);
494
495 let (front, back) = polygon.cut(&plane);
496
497 assert!(front.is_none());
502 assert!(back.is_some());
503 }
504
505 #[test]
506 fn polygon_two_vertices_on_plane_spanning() {
507 let polygon = Polygon::new(vec![
510 Point3::new(0.0, 1.0, 0.0), Point3::new(1.0, 0.0, 0.0), Point3::new(1.0, -1.0, 0.0), Point3::new(0.0, -1.0, 0.0), Point3::new(-1.0, 0.0, 0.0), Point3::new(-1.0, 1.0, 0.0), ]);
517 let plane = horizontal_plane(0.0);
518
519 let (front, back) = polygon.cut(&plane);
520
521 assert!(front.is_some());
522 assert!(back.is_some());
523
524 let front = front.unwrap();
525 let back = back.unwrap();
526
527 let on_plane_1 = Point3::new(1.0, 0.0, 0.0);
529 let on_plane_2 = Point3::new(-1.0, 0.0, 0.0);
530
531 assert!(
532 front.vertices().contains(&on_plane_1),
533 "On-plane vertex 1 should be in front polygon"
534 );
535 assert!(
536 front.vertices().contains(&on_plane_2),
537 "On-plane vertex 2 should be in front polygon"
538 );
539 assert!(
540 back.vertices().contains(&on_plane_1),
541 "On-plane vertex 1 should be in back polygon"
542 );
543 assert!(
544 back.vertices().contains(&on_plane_2),
545 "On-plane vertex 2 should be in back polygon"
546 );
547
548 let expected_front = vec![
550 Point3::new(0.0, 1.0, 0.0),
551 Point3::new(1.0, 0.0, 0.0),
552 Point3::new(-1.0, 0.0, 0.0),
553 Point3::new(-1.0, 1.0, 0.0),
554 ];
555 let expected_back = vec![
556 Point3::new(1.0, 0.0, 0.0),
557 Point3::new(1.0, -1.0, 0.0),
558 Point3::new(0.0, -1.0, 0.0),
559 Point3::new(-1.0, 0.0, 0.0),
560 ];
561
562 assert!(
563 same_polygon_vertices(front.vertices(), &expected_front),
564 "Front polygon vertices mismatch.\nExpected: {expected_front:?}\nActual: {:?}",
565 front.vertices()
566 );
567 assert!(
568 same_polygon_vertices(back.vertices(), &expected_back),
569 "Back polygon vertices mismatch.\nExpected: {expected_back:?}\nActual: {:?}",
570 back.vertices()
571 );
572 }
573
574 #[test]
579 fn triangle_entirely_front() {
580 let triangle = Triangle::new(
581 Point3::new(0.0, 1.0, 0.0),
582 Point3::new(1.0, 2.0, 0.0),
583 Point3::new(0.5, 1.5, 1.0),
584 );
585 let plane = horizontal_plane(0.0);
586
587 let (front, back) = triangle.cut(&plane);
588
589 assert!(front.is_some());
590 assert!(back.is_none());
591 assert_vertex_count(&front.unwrap(), 3);
592 }
593
594 #[test]
595 fn triangle_entirely_back() {
596 let triangle = Triangle::new(
597 Point3::new(0.0, -1.0, 0.0),
598 Point3::new(1.0, -2.0, 0.0),
599 Point3::new(0.5, -1.5, 1.0),
600 );
601 let plane = horizontal_plane(0.0);
602
603 let (front, back) = triangle.cut(&plane);
604
605 assert!(front.is_none());
606 assert!(back.is_some());
607 assert_vertex_count(&back.unwrap(), 3);
608 }
609
610 #[test]
611 fn triangle_coplanar() {
612 let triangle = Triangle::new(
613 Point3::new(0.0, 0.0, 0.0),
614 Point3::new(1.0, 0.0, 0.0),
615 Point3::new(0.5, 0.0, 1.0),
616 );
617 let plane = horizontal_plane(0.0);
618
619 let (front, back) = triangle.cut(&plane);
620
621 assert!(front.is_some());
622 assert!(back.is_none());
623 }
624
625 #[test]
626 fn triangle_split_one_front_two_back() {
627 let triangle = Triangle::new(
628 Point3::new(0.0, 2.0, 0.0), Point3::new(-1.0, -1.0, 0.0), Point3::new(1.0, -1.0, 0.0), );
632 let plane = horizontal_plane(0.0);
633
634 let (front, back) = triangle.cut(&plane);
635
636 assert!(front.is_some());
637 assert!(back.is_some());
638
639 assert_vertex_count(&front.unwrap(), 3);
641 assert_vertex_count(&back.unwrap(), 4);
643 }
644
645 #[test]
646 fn triangle_split_two_front_one_back() {
647 let triangle = Triangle::new(
648 Point3::new(-1.0, 1.0, 0.0), Point3::new(1.0, 1.0, 0.0), Point3::new(0.0, -2.0, 0.0), );
652 let plane = horizontal_plane(0.0);
653
654 let (front, back) = triangle.cut(&plane);
655
656 assert!(front.is_some());
657 assert!(back.is_some());
658
659 assert_vertex_count(&front.unwrap(), 4);
661 assert_vertex_count(&back.unwrap(), 3);
663 }
664
665 #[test]
670 fn rectangle_entirely_front() {
671 let rect = Rectangle::new(
672 Point3::new(0.0, 1.0, 0.0),
673 Vector3::new(1.0, 0.0, 0.0),
674 Vector3::new(0.0, 1.0, 0.0),
675 );
676 let plane = horizontal_plane(0.0);
677
678 let (front, back) = rect.cut(&plane);
679
680 assert!(front.is_some());
681 assert!(back.is_none());
682 assert_vertex_count(&front.unwrap(), 4);
683 }
684
685 #[test]
686 fn rectangle_entirely_back() {
687 let rect = Rectangle::new(
688 Point3::new(0.0, -2.0, 0.0),
689 Vector3::new(1.0, 0.0, 0.0),
690 Vector3::new(0.0, 0.5, 0.0),
691 );
692 let plane = horizontal_plane(0.0);
693
694 let (front, back) = rect.cut(&plane);
695
696 assert!(front.is_none());
697 assert!(back.is_some());
698 assert_vertex_count(&back.unwrap(), 4);
699 }
700
701 #[test]
702 fn rectangle_coplanar() {
703 let rect = Rectangle::new(
704 Point3::new(0.0, 0.0, 0.0),
705 Vector3::new(1.0, 0.0, 0.0),
706 Vector3::new(0.0, 0.0, 1.0),
707 );
708 let plane = horizontal_plane(0.0);
709
710 let (front, back) = rect.cut(&plane);
711
712 assert!(front.is_some());
713 assert!(back.is_none());
714 }
715
716 #[test]
717 fn rectangle_split_horizontal() {
718 let rect = Rectangle::new(
720 Point3::new(0.0, -1.0, 0.0),
721 Vector3::new(2.0, 0.0, 0.0),
722 Vector3::new(0.0, 2.0, 0.0),
723 );
724 let plane = horizontal_plane(0.0);
725
726 let (front, back) = rect.cut(&plane);
727
728 assert!(front.is_some());
729 assert!(back.is_some());
730
731 assert_vertex_count(&front.unwrap(), 4);
733 assert_vertex_count(&back.unwrap(), 4);
734 }
735
736 #[test]
737 fn rectangle_split_diagonal() {
738 let rect = Rectangle::new(
740 Point3::new(0.0, 0.0, 0.0),
741 Vector3::new(2.0, 0.0, 0.0),
742 Vector3::new(0.0, 0.0, 2.0),
743 );
744 let plane = Plane3D::from_point_and_normal(
746 Point3::new(1.0, 0.0, 1.0),
747 Vector3::new(1.0, 0.0, 1.0),
748 );
749
750 let (front, back) = rect.cut(&plane);
751
752 assert!(front.is_some());
753 assert!(back.is_some());
754
755 assert_vertex_count(&front.unwrap(), 3);
757 assert_vertex_count(&back.unwrap(), 3);
758 }
759
760 #[test]
761 fn rectangle_cut_off_corner() {
762 let rect = Rectangle::new(
764 Point3::new(0.0, 0.0, 0.0),
765 Vector3::new(2.0, 0.0, 0.0),
766 Vector3::new(0.0, 0.0, 2.0),
767 );
768 let plane = Plane3D::from_point_and_normal(
770 Point3::new(1.5, 0.0, 1.5),
771 Vector3::new(1.0, 0.0, 1.0),
772 );
773
774 let (front, back) = rect.cut(&plane);
775
776 assert!(front.is_some());
777 assert!(back.is_some());
778
779 let front = front.unwrap();
780 let back = back.unwrap();
781
782 let (small, large) = if front.len() < back.len() {
785 (front, back)
786 } else {
787 (back, front)
788 };
789
790 assert_vertex_count(&small, 3);
791 assert_vertex_count(&large, 5);
792 }
793
794 #[test]
799 fn split_with_vertical_plane_x() {
800 let polygon = Polygon::new(vec![
802 Point3::new(-1.0, 0.0, 0.0), Point3::new(2.0, 0.0, 0.0), Point3::new(0.5, 1.0, 0.0), ]);
806 let plane = vertical_plane_x(0.0);
807
808 let (front, back) = polygon.cut(&plane);
809
810 assert!(front.is_some());
811 assert!(back.is_some());
812
813 assert_all_vertices_on_side(&front.unwrap(), &plane, PlaneSide::Front);
814 assert_all_vertices_on_side(&back.unwrap(), &plane, PlaneSide::Back);
815 }
816
817 #[test]
818 fn split_with_vertical_plane_z() {
819 let polygon = Polygon::new(vec![
821 Point3::new(0.0, 0.0, -1.0), Point3::new(0.0, 0.0, 2.0), Point3::new(0.0, 1.0, 0.5), ]);
825 let plane = vertical_plane_z(0.0);
826
827 let (front, back) = polygon.cut(&plane);
828
829 assert!(front.is_some());
830 assert!(back.is_some());
831
832 assert_all_vertices_on_side(&front.unwrap(), &plane, PlaneSide::Front);
833 assert_all_vertices_on_side(&back.unwrap(), &plane, PlaneSide::Back);
834 }
835
836 #[test]
837 fn split_with_tilted_plane() {
838 let polygon = Polygon::new(vec![
840 Point3::new(0.0, 0.0, 0.0),
841 Point3::new(2.0, 2.0, 0.0),
842 Point3::new(2.0, 0.0, 0.0),
843 ]);
844 let plane = Plane3D::from_point_and_normal(
846 Point3::new(0.5, 0.5, 0.0),
847 Vector3::new(1.0, 1.0, 0.0),
848 );
849
850 let (front, back) = polygon.cut(&plane);
851
852 assert!(front.is_some());
853 assert!(back.is_some());
854
855 assert_all_vertices_on_side(&front.unwrap(), &plane, PlaneSide::Front);
856 assert_all_vertices_on_side(&back.unwrap(), &plane, PlaneSide::Back);
857 }
858
859 #[test]
864 fn split_intersection_points_are_correct() {
865 let polygon = Polygon::new(vec![
868 Point3::new(0.0, -1.0, 0.0),
869 Point3::new(1.0, 1.0, 0.0),
870 Point3::new(-1.0, 1.0, 0.0),
871 ]);
872 let plane = horizontal_plane(0.0);
873
874 let (front, back) = polygon.cut(&plane);
875
876 let front = front.unwrap();
877 let _back = back.unwrap();
878
879 let has_intersection_1 = front.vertices().iter().any(|v| {
884 approx_eq(v.x, 0.5, 1e-5) && approx_eq(v.y, 0.0, 1e-5)
885 });
886 let has_intersection_2 = front.vertices().iter().any(|v| {
887 approx_eq(v.x, -0.5, 1e-5) && approx_eq(v.y, 0.0, 1e-5)
888 });
889
890 assert!(has_intersection_1, "Should have intersection at (0.5, 0, 0)");
891 assert!(has_intersection_2, "Should have intersection at (-0.5, 0, 0)");
892 }
893
894 #[test]
895 fn split_preserves_z_coordinates() {
896 let polygon = Polygon::new(vec![
898 Point3::new(0.0, 1.0, 5.0),
899 Point3::new(1.0, 1.0, 5.0),
900 Point3::new(1.0, -1.0, 5.0),
901 Point3::new(0.0, -1.0, 5.0),
902 ]);
903 let plane = horizontal_plane(0.0);
904
905 let (front, back) = polygon.cut(&plane);
906
907 for v in front.unwrap().vertices() {
909 assert!(
910 approx_eq(v.z, 5.0, 1e-5),
911 "Z coordinate should be preserved: got {}",
912 v.z
913 );
914 }
915 for v in back.unwrap().vertices() {
916 assert!(
917 approx_eq(v.z, 5.0, 1e-5),
918 "Z coordinate should be preserved: got {}",
919 v.z
920 );
921 }
922 }
923
924 #[test]
925 fn split_produces_valid_polygons() {
926 let polygon = Polygon::new(vec![
928 Point3::new(0.0, 2.0, 0.0),
929 Point3::new(2.0, 0.0, 1.0),
930 Point3::new(0.0, -2.0, 0.0),
931 Point3::new(-2.0, 0.0, -1.0),
932 ]);
933 let plane = horizontal_plane(0.0);
934
935 let (front, back) = polygon.cut(&plane);
936
937 let front = front.unwrap();
938 let back = back.unwrap();
939
940 assert!(front.len() >= 3);
942 assert!(back.len() >= 3);
943
944 assert!(front.unit_normal().is_some());
946 assert!(back.unit_normal().is_some());
947 }
948
949 #[test]
954 fn very_thin_slice() {
955 let polygon = Polygon::new(vec![
957 Point3::new(0.0, 0.001, 0.0), Point3::new(1.0, 0.001, 0.0), Point3::new(1.0, -1.0, 0.0), Point3::new(0.0, -1.0, 0.0), ]);
962 let plane = horizontal_plane(0.0);
963
964 let (front, back) = polygon.cut(&plane);
965
966 assert!(front.is_some());
968 assert!(back.is_some());
969 }
970
971 #[test]
972 fn large_polygon_many_vertices() {
973 let polygon = Polygon::new(vec![
975 Point3::new(1.0, 0.0, 0.0),
976 Point3::new(0.5, 0.866, 0.0),
977 Point3::new(-0.5, 0.866, 0.0),
978 Point3::new(-1.0, 0.0, 0.0),
979 Point3::new(-0.5, -0.866, 0.0),
980 Point3::new(0.5, -0.866, 0.0),
981 ]);
982 let plane = horizontal_plane(0.0);
983
984 let (front, back) = polygon.cut(&plane);
985
986 assert!(front.is_some());
987 assert!(back.is_some());
988
989 assert_vertex_count(&front.unwrap(), 4);
991 assert_vertex_count(&back.unwrap(), 4);
992 }
993
994 #[test]
995 fn polygon_with_offset_plane() {
996 let polygon = Polygon::new(vec![
998 Point3::new(0.0, 5.0, 0.0),
999 Point3::new(1.0, 5.0, 0.0),
1000 Point3::new(1.0, 3.0, 0.0),
1001 Point3::new(0.0, 3.0, 0.0),
1002 ]);
1003 let plane = horizontal_plane(4.0); let (front, back) = polygon.cut(&plane);
1006
1007 assert!(front.is_some());
1008 assert!(back.is_some());
1009
1010 assert_all_vertices_on_side(&front.unwrap(), &plane, PlaneSide::Front);
1011 assert_all_vertices_on_side(&back.unwrap(), &plane, PlaneSide::Back);
1012 }
1013
1014 #[test]
1015 fn flipped_plane_reverses_classification() {
1016 let polygon = Polygon::new(vec![
1017 Point3::new(0.0, 1.0, 0.0),
1018 Point3::new(1.0, 1.0, 0.0),
1019 Point3::new(0.5, 2.0, 0.0),
1020 ]);
1021
1022 let plane = horizontal_plane(0.0);
1023 let flipped_plane = plane.flipped();
1024
1025 let (front1, back1) = polygon.cut(&plane);
1026 let (front2, back2) = polygon.cut(&flipped_plane);
1027
1028 assert!(front1.is_some());
1030 assert!(back1.is_none());
1031
1032 assert!(front2.is_none());
1034 assert!(back2.is_some());
1035 }
1036
1037 #[test]
1042 fn split_does_not_produce_degenerate_polygons() {
1043 let test_cases = vec![
1045 vec![
1047 Point3::new(0.0, 0.0, 0.0),
1048 Point3::new(1.0, 1.0, 0.0),
1049 Point3::new(-1.0, 1.0, 0.0),
1050 ],
1051 vec![
1053 Point3::new(-1.0, 0.0, 0.0),
1054 Point3::new(1.0, 0.0, 0.0),
1055 Point3::new(1.0, 1.0, 0.0),
1056 Point3::new(-1.0, 1.0, 0.0),
1057 ],
1058 ];
1059
1060 let plane = horizontal_plane(0.0);
1061
1062 for vertices in test_cases {
1063 let polygon = Polygon::new(vertices);
1064 let (front, back) = polygon.cut(&plane);
1065
1066 if let Some(f) = front {
1067 assert!(f.len() >= 3, "Front polygon is degenerate");
1068 }
1069 if let Some(b) = back {
1070 assert!(b.len() >= 3, "Back polygon is degenerate");
1071 }
1072 }
1073 }
1074
1075 #[test]
1076 fn consistent_results_with_equivalent_planes() {
1077 let polygon = Polygon::new(vec![
1078 Point3::new(0.0, 1.0, 0.0),
1079 Point3::new(1.0, -1.0, 0.0),
1080 Point3::new(-1.0, -1.0, 0.0),
1081 ]);
1082
1083 let plane1 = horizontal_plane(0.0);
1085 let plane2 = Plane3D::new(Vector3::new(0.0, 1.0, 0.0), 0.0);
1086
1087 let (front1, back1) = polygon.cut(&plane1);
1088 let (front2, back2) = polygon.cut(&plane2);
1089
1090 assert_eq!(front1.as_ref().map(|p| p.len()), front2.as_ref().map(|p| p.len()));
1092 assert_eq!(back1.as_ref().map(|p| p.len()), back2.as_ref().map(|p| p.len()));
1093 }
1094}