use crate::{Classification, Plane3D, PlaneSide, Polygon, Rectangle, Triangle};
pub trait Cuttable {
fn cut(&self, plane: &Plane3D) -> (Option<Polygon>, Option<Polygon>);
}
impl Cuttable for Polygon {
fn cut(&self, plane: &Plane3D) -> (Option<Polygon>, Option<Polygon>) {
match self.classify(plane) {
Classification::Front | Classification::Coplanar => {
(Some(self.clone()), None)
}
Classification::Back => {
(None, Some(self.clone()))
}
Classification::Spanning => {
split_polygon(self, plane)
}
}
}
}
fn split_polygon(polygon: &Polygon, plane: &Plane3D) -> (Option<Polygon>, Option<Polygon>) {
let vertices = polygon.vertices();
let n = vertices.len();
let mut front_verts = Vec::with_capacity(n + 1);
let mut back_verts = Vec::with_capacity(n + 1);
let sides: Vec<PlaneSide> = vertices
.iter()
.map(|v| plane.classify_point(*v))
.collect();
for i in 0..n {
let current = vertices[i];
let current_side = sides[i];
let next_idx = (i + 1) % n;
let next = vertices[next_idx];
let next_side = sides[next_idx];
match current_side {
PlaneSide::Front => front_verts.push(current),
PlaneSide::Back => back_verts.push(current),
PlaneSide::OnPlane => {
front_verts.push(current);
back_verts.push(current);
}
}
let crosses = matches!(
(current_side, next_side),
(PlaneSide::Front, PlaneSide::Back) | (PlaneSide::Back, PlaneSide::Front)
);
if crosses {
if let Some((_, intersection)) = plane.intersect_segment(current, next) {
front_verts.push(intersection);
back_verts.push(intersection);
}
}
}
let front = if front_verts.len() >= 3 {
Some(Polygon::new(front_verts))
} else {
None
};
let back = if back_verts.len() >= 3 {
Some(Polygon::new(back_verts))
} else {
None
};
(front, back)
}
impl Cuttable for Triangle {
fn cut(&self, plane: &Plane3D) -> (Option<Polygon>, Option<Polygon>) {
Polygon::from(self).cut(plane)
}
}
impl Cuttable for Rectangle {
fn cut(&self, plane: &Plane3D) -> (Option<Polygon>, Option<Polygon>) {
Polygon::from(self).cut(plane)
}
}
#[cfg(test)]
mod tests {
use super::*;
use nalgebra::{Point3, Vector3};
fn horizontal_plane(height: f32) -> Plane3D {
Plane3D::from_point_and_normal(
Point3::new(0.0, height, 0.0),
Vector3::new(0.0, 1.0, 0.0),
)
}
fn vertical_plane_x(offset: f32) -> Plane3D {
Plane3D::from_point_and_normal(
Point3::new(offset, 0.0, 0.0),
Vector3::new(1.0, 0.0, 0.0),
)
}
fn vertical_plane_z(offset: f32) -> Plane3D {
Plane3D::from_point_and_normal(
Point3::new(0.0, 0.0, offset),
Vector3::new(0.0, 0.0, 1.0),
)
}
fn assert_all_vertices_on_side(polygon: &Polygon, plane: &Plane3D, expected: PlaneSide) {
for (i, v) in polygon.vertices().iter().enumerate() {
let side = plane.classify_point(*v);
assert!(
side == expected || side == PlaneSide::OnPlane,
"Vertex {i} at {v:?} is on {side:?}, expected {expected:?} or OnPlane"
);
}
}
fn assert_vertex_count(polygon: &Polygon, expected: usize) {
assert_eq!(
polygon.len(),
expected,
"Expected {expected} vertices, got {}",
polygon.len()
);
}
fn approx_eq(a: f32, b: f32, epsilon: f32) -> bool {
(a - b).abs() < epsilon
}
fn assert_point_on_plane(point: Point3<f32>, plane: &Plane3D) {
let dist = plane.signed_distance(point).abs();
assert!(
dist < 1e-4,
"Point {point:?} should be on plane, but distance is {dist}"
);
}
fn same_polygon_vertices(actual: &[Point3<f32>], expected: &[Point3<f32>]) -> bool {
if actual.len() != expected.len() {
return false;
}
let n = actual.len();
if n == 0 {
return true;
}
let Some(offset) = actual.iter().position(|v| v == &expected[0]) else {
return false;
};
for i in 0..n {
if actual[(offset + i) % n] != expected[i] {
return false;
}
}
true
}
#[test]
fn polygon_entirely_in_front() {
let polygon = Polygon::new(vec![
Point3::new(0.0, 1.0, 0.0),
Point3::new(1.0, 2.0, 0.0),
Point3::new(0.0, 1.5, 1.0),
]);
let plane = horizontal_plane(0.0);
let (front, back) = polygon.cut(&plane);
assert!(front.is_some(), "Front should contain the polygon");
assert!(back.is_none(), "Back should be empty");
assert_eq!(front.unwrap().vertices(), polygon.vertices());
}
#[test]
fn polygon_entirely_behind() {
let polygon = Polygon::new(vec![
Point3::new(0.0, -1.0, 0.0),
Point3::new(1.0, -2.0, 0.0),
Point3::new(0.0, -1.5, 1.0),
]);
let plane = horizontal_plane(0.0);
let (front, back) = polygon.cut(&plane);
assert!(front.is_none(), "Front should be empty");
assert!(back.is_some(), "Back should contain the polygon");
assert_eq!(back.unwrap().vertices(), polygon.vertices());
}
#[test]
fn polygon_coplanar_with_plane() {
let polygon = Polygon::new(vec![
Point3::new(0.0, 0.0, 0.0),
Point3::new(1.0, 0.0, 0.0),
Point3::new(0.5, 0.0, 1.0),
]);
let plane = horizontal_plane(0.0);
let (front, back) = polygon.cut(&plane);
assert!(front.is_some(), "Coplanar should be treated as front");
assert!(back.is_none(), "Back should be empty for coplanar");
assert_eq!(front.unwrap().vertices(), polygon.vertices());
}
#[test]
fn polygon_split_triangle_one_vertex_front() {
let polygon = Polygon::new(vec![
Point3::new(0.0, 2.0, 0.0), Point3::new(-1.0, -1.0, 0.0), Point3::new(1.0, -1.0, 0.0), ]);
let plane = horizontal_plane(0.0);
let (front, back) = polygon.cut(&plane);
assert!(front.is_some(), "Should have front part");
assert!(back.is_some(), "Should have back part");
let front = front.unwrap();
let back = back.unwrap();
assert_vertex_count(&front, 3);
assert_vertex_count(&back, 4);
assert_all_vertices_on_side(&front, &plane, PlaneSide::Front);
assert_all_vertices_on_side(&back, &plane, PlaneSide::Back);
}
#[test]
fn polygon_split_triangle_two_vertices_front() {
let polygon = Polygon::new(vec![
Point3::new(-1.0, 1.0, 0.0), Point3::new(1.0, 1.0, 0.0), Point3::new(0.0, -2.0, 0.0), ]);
let plane = horizontal_plane(0.0);
let (front, back) = polygon.cut(&plane);
assert!(front.is_some(), "Should have front part");
assert!(back.is_some(), "Should have back part");
let front = front.unwrap();
let back = back.unwrap();
assert_vertex_count(&front, 4);
assert_vertex_count(&back, 3);
assert_all_vertices_on_side(&front, &plane, PlaneSide::Front);
assert_all_vertices_on_side(&back, &plane, PlaneSide::Back);
}
#[test]
fn polygon_split_quad_in_half() {
let polygon = Polygon::new(vec![
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), ]);
let plane = horizontal_plane(0.0);
let (front, back) = polygon.cut(&plane);
assert!(front.is_some());
assert!(back.is_some());
let front = front.unwrap();
let back = back.unwrap();
assert_vertex_count(&front, 4);
assert_vertex_count(&back, 4);
assert_all_vertices_on_side(&front, &plane, PlaneSide::Front);
assert_all_vertices_on_side(&back, &plane, PlaneSide::Back);
}
#[test]
fn polygon_split_pentagon() {
let polygon = Polygon::new(vec![
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), ]);
let plane = horizontal_plane(0.0);
let (front, back) = polygon.cut(&plane);
assert!(front.is_some());
assert!(back.is_some());
let front = front.unwrap();
let back = back.unwrap();
assert_vertex_count(&front, 5);
assert_vertex_count(&back, 4);
assert_all_vertices_on_side(&front, &plane, PlaneSide::Front);
assert_all_vertices_on_side(&back, &plane, PlaneSide::Back);
}
#[test]
fn polygon_split_preserves_intersection_points_on_plane() {
let polygon = Polygon::new(vec![
Point3::new(0.0, 3.0, 0.0),
Point3::new(2.0, -1.0, 1.0),
Point3::new(-2.0, -1.0, -1.0),
]);
let plane = horizontal_plane(0.0);
let (front, back) = polygon.cut(&plane);
let front = front.unwrap();
let back = back.unwrap();
for fv in front.vertices() {
if plane.classify_point(*fv) == PlaneSide::OnPlane {
assert_point_on_plane(*fv, &plane);
}
}
for bv in back.vertices() {
if plane.classify_point(*bv) == PlaneSide::OnPlane {
assert_point_on_plane(*bv, &plane);
}
}
}
#[test]
fn polygon_one_vertex_exactly_on_plane() {
let polygon = Polygon::new(vec![
Point3::new(0.0, 0.0, 0.0), Point3::new(1.0, 1.0, 0.0), Point3::new(-1.0, 1.0, 0.0), ]);
let plane = horizontal_plane(0.0);
let (front, back) = polygon.cut(&plane);
assert!(front.is_some());
assert!(back.is_none());
}
#[test]
fn polygon_one_vertex_on_plane_spanning() {
let polygon = Polygon::new(vec![
Point3::new(0.0, 0.0, 0.0), Point3::new(1.0, 1.0, 0.0), Point3::new(1.0, -1.0, 0.0), ]);
let plane = horizontal_plane(0.0);
let (front, back) = polygon.cut(&plane);
assert!(front.is_some());
assert!(back.is_some());
let front = front.unwrap();
let back = back.unwrap();
let on_plane_point = Point3::new(0.0, 0.0, 0.0);
assert!(
front.vertices().contains(&on_plane_point),
"On-plane vertex should be in front polygon"
);
assert!(
back.vertices().contains(&on_plane_point),
"On-plane vertex should be in back polygon"
);
}
#[test]
fn polygon_edge_lies_on_plane() {
let polygon = Polygon::new(vec![
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), ]);
let plane = horizontal_plane(0.0);
let (front, back) = polygon.cut(&plane);
assert!(front.is_some());
assert!(back.is_none());
}
#[test]
fn polygon_edge_on_plane_with_back_vertices() {
let polygon = Polygon::new(vec![
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), ]);
let plane = horizontal_plane(0.0);
let (front, back) = polygon.cut(&plane);
assert!(front.is_none());
assert!(back.is_some());
}
#[test]
fn polygon_two_vertices_on_plane_spanning() {
let polygon = Polygon::new(vec![
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), ]);
let plane = horizontal_plane(0.0);
let (front, back) = polygon.cut(&plane);
assert!(front.is_some());
assert!(back.is_some());
let front = front.unwrap();
let back = back.unwrap();
let on_plane_1 = Point3::new(1.0, 0.0, 0.0);
let on_plane_2 = Point3::new(-1.0, 0.0, 0.0);
assert!(
front.vertices().contains(&on_plane_1),
"On-plane vertex 1 should be in front polygon"
);
assert!(
front.vertices().contains(&on_plane_2),
"On-plane vertex 2 should be in front polygon"
);
assert!(
back.vertices().contains(&on_plane_1),
"On-plane vertex 1 should be in back polygon"
);
assert!(
back.vertices().contains(&on_plane_2),
"On-plane vertex 2 should be in back polygon"
);
let expected_front = vec![
Point3::new(0.0, 1.0, 0.0),
Point3::new(1.0, 0.0, 0.0),
Point3::new(-1.0, 0.0, 0.0),
Point3::new(-1.0, 1.0, 0.0),
];
let expected_back = vec![
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),
];
assert!(
same_polygon_vertices(front.vertices(), &expected_front),
"Front polygon vertices mismatch.\nExpected: {expected_front:?}\nActual: {:?}",
front.vertices()
);
assert!(
same_polygon_vertices(back.vertices(), &expected_back),
"Back polygon vertices mismatch.\nExpected: {expected_back:?}\nActual: {:?}",
back.vertices()
);
}
#[test]
fn triangle_entirely_front() {
let triangle = Triangle::new(
Point3::new(0.0, 1.0, 0.0),
Point3::new(1.0, 2.0, 0.0),
Point3::new(0.5, 1.5, 1.0),
);
let plane = horizontal_plane(0.0);
let (front, back) = triangle.cut(&plane);
assert!(front.is_some());
assert!(back.is_none());
assert_vertex_count(&front.unwrap(), 3);
}
#[test]
fn triangle_entirely_back() {
let triangle = Triangle::new(
Point3::new(0.0, -1.0, 0.0),
Point3::new(1.0, -2.0, 0.0),
Point3::new(0.5, -1.5, 1.0),
);
let plane = horizontal_plane(0.0);
let (front, back) = triangle.cut(&plane);
assert!(front.is_none());
assert!(back.is_some());
assert_vertex_count(&back.unwrap(), 3);
}
#[test]
fn triangle_coplanar() {
let triangle = Triangle::new(
Point3::new(0.0, 0.0, 0.0),
Point3::new(1.0, 0.0, 0.0),
Point3::new(0.5, 0.0, 1.0),
);
let plane = horizontal_plane(0.0);
let (front, back) = triangle.cut(&plane);
assert!(front.is_some());
assert!(back.is_none());
}
#[test]
fn triangle_split_one_front_two_back() {
let triangle = Triangle::new(
Point3::new(0.0, 2.0, 0.0), Point3::new(-1.0, -1.0, 0.0), Point3::new(1.0, -1.0, 0.0), );
let plane = horizontal_plane(0.0);
let (front, back) = triangle.cut(&plane);
assert!(front.is_some());
assert!(back.is_some());
assert_vertex_count(&front.unwrap(), 3);
assert_vertex_count(&back.unwrap(), 4);
}
#[test]
fn triangle_split_two_front_one_back() {
let triangle = Triangle::new(
Point3::new(-1.0, 1.0, 0.0), Point3::new(1.0, 1.0, 0.0), Point3::new(0.0, -2.0, 0.0), );
let plane = horizontal_plane(0.0);
let (front, back) = triangle.cut(&plane);
assert!(front.is_some());
assert!(back.is_some());
assert_vertex_count(&front.unwrap(), 4);
assert_vertex_count(&back.unwrap(), 3);
}
#[test]
fn rectangle_entirely_front() {
let rect = Rectangle::new(
Point3::new(0.0, 1.0, 0.0),
Vector3::new(1.0, 0.0, 0.0),
Vector3::new(0.0, 1.0, 0.0),
);
let plane = horizontal_plane(0.0);
let (front, back) = rect.cut(&plane);
assert!(front.is_some());
assert!(back.is_none());
assert_vertex_count(&front.unwrap(), 4);
}
#[test]
fn rectangle_entirely_back() {
let rect = Rectangle::new(
Point3::new(0.0, -2.0, 0.0),
Vector3::new(1.0, 0.0, 0.0),
Vector3::new(0.0, 0.5, 0.0),
);
let plane = horizontal_plane(0.0);
let (front, back) = rect.cut(&plane);
assert!(front.is_none());
assert!(back.is_some());
assert_vertex_count(&back.unwrap(), 4);
}
#[test]
fn rectangle_coplanar() {
let rect = Rectangle::new(
Point3::new(0.0, 0.0, 0.0),
Vector3::new(1.0, 0.0, 0.0),
Vector3::new(0.0, 0.0, 1.0),
);
let plane = horizontal_plane(0.0);
let (front, back) = rect.cut(&plane);
assert!(front.is_some());
assert!(back.is_none());
}
#[test]
fn rectangle_split_horizontal() {
let rect = Rectangle::new(
Point3::new(0.0, -1.0, 0.0),
Vector3::new(2.0, 0.0, 0.0),
Vector3::new(0.0, 2.0, 0.0),
);
let plane = horizontal_plane(0.0);
let (front, back) = rect.cut(&plane);
assert!(front.is_some());
assert!(back.is_some());
assert_vertex_count(&front.unwrap(), 4);
assert_vertex_count(&back.unwrap(), 4);
}
#[test]
fn rectangle_split_diagonal() {
let rect = Rectangle::new(
Point3::new(0.0, 0.0, 0.0),
Vector3::new(2.0, 0.0, 0.0),
Vector3::new(0.0, 0.0, 2.0),
);
let plane = Plane3D::from_point_and_normal(
Point3::new(1.0, 0.0, 1.0),
Vector3::new(1.0, 0.0, 1.0),
);
let (front, back) = rect.cut(&plane);
assert!(front.is_some());
assert!(back.is_some());
assert_vertex_count(&front.unwrap(), 3);
assert_vertex_count(&back.unwrap(), 3);
}
#[test]
fn rectangle_cut_off_corner() {
let rect = Rectangle::new(
Point3::new(0.0, 0.0, 0.0),
Vector3::new(2.0, 0.0, 0.0),
Vector3::new(0.0, 0.0, 2.0),
);
let plane = Plane3D::from_point_and_normal(
Point3::new(1.5, 0.0, 1.5),
Vector3::new(1.0, 0.0, 1.0),
);
let (front, back) = rect.cut(&plane);
assert!(front.is_some());
assert!(back.is_some());
let front = front.unwrap();
let back = back.unwrap();
let (small, large) = if front.len() < back.len() {
(front, back)
} else {
(back, front)
};
assert_vertex_count(&small, 3);
assert_vertex_count(&large, 5);
}
#[test]
fn split_with_vertical_plane_x() {
let polygon = Polygon::new(vec![
Point3::new(-1.0, 0.0, 0.0), Point3::new(2.0, 0.0, 0.0), Point3::new(0.5, 1.0, 0.0), ]);
let plane = vertical_plane_x(0.0);
let (front, back) = polygon.cut(&plane);
assert!(front.is_some());
assert!(back.is_some());
assert_all_vertices_on_side(&front.unwrap(), &plane, PlaneSide::Front);
assert_all_vertices_on_side(&back.unwrap(), &plane, PlaneSide::Back);
}
#[test]
fn split_with_vertical_plane_z() {
let polygon = Polygon::new(vec![
Point3::new(0.0, 0.0, -1.0), Point3::new(0.0, 0.0, 2.0), Point3::new(0.0, 1.0, 0.5), ]);
let plane = vertical_plane_z(0.0);
let (front, back) = polygon.cut(&plane);
assert!(front.is_some());
assert!(back.is_some());
assert_all_vertices_on_side(&front.unwrap(), &plane, PlaneSide::Front);
assert_all_vertices_on_side(&back.unwrap(), &plane, PlaneSide::Back);
}
#[test]
fn split_with_tilted_plane() {
let polygon = Polygon::new(vec![
Point3::new(0.0, 0.0, 0.0),
Point3::new(2.0, 2.0, 0.0),
Point3::new(2.0, 0.0, 0.0),
]);
let plane = Plane3D::from_point_and_normal(
Point3::new(0.5, 0.5, 0.0),
Vector3::new(1.0, 1.0, 0.0),
);
let (front, back) = polygon.cut(&plane);
assert!(front.is_some());
assert!(back.is_some());
assert_all_vertices_on_side(&front.unwrap(), &plane, PlaneSide::Front);
assert_all_vertices_on_side(&back.unwrap(), &plane, PlaneSide::Back);
}
#[test]
fn split_intersection_points_are_correct() {
let polygon = Polygon::new(vec![
Point3::new(0.0, -1.0, 0.0),
Point3::new(1.0, 1.0, 0.0),
Point3::new(-1.0, 1.0, 0.0),
]);
let plane = horizontal_plane(0.0);
let (front, back) = polygon.cut(&plane);
let front = front.unwrap();
let _back = back.unwrap();
let has_intersection_1 = front.vertices().iter().any(|v| {
approx_eq(v.x, 0.5, 1e-5) && approx_eq(v.y, 0.0, 1e-5)
});
let has_intersection_2 = front.vertices().iter().any(|v| {
approx_eq(v.x, -0.5, 1e-5) && approx_eq(v.y, 0.0, 1e-5)
});
assert!(has_intersection_1, "Should have intersection at (0.5, 0, 0)");
assert!(has_intersection_2, "Should have intersection at (-0.5, 0, 0)");
}
#[test]
fn split_preserves_z_coordinates() {
let polygon = Polygon::new(vec![
Point3::new(0.0, 1.0, 5.0),
Point3::new(1.0, 1.0, 5.0),
Point3::new(1.0, -1.0, 5.0),
Point3::new(0.0, -1.0, 5.0),
]);
let plane = horizontal_plane(0.0);
let (front, back) = polygon.cut(&plane);
for v in front.unwrap().vertices() {
assert!(
approx_eq(v.z, 5.0, 1e-5),
"Z coordinate should be preserved: got {}",
v.z
);
}
for v in back.unwrap().vertices() {
assert!(
approx_eq(v.z, 5.0, 1e-5),
"Z coordinate should be preserved: got {}",
v.z
);
}
}
#[test]
fn split_produces_valid_polygons() {
let polygon = Polygon::new(vec![
Point3::new(0.0, 2.0, 0.0),
Point3::new(2.0, 0.0, 1.0),
Point3::new(0.0, -2.0, 0.0),
Point3::new(-2.0, 0.0, -1.0),
]);
let plane = horizontal_plane(0.0);
let (front, back) = polygon.cut(&plane);
let front = front.unwrap();
let back = back.unwrap();
assert!(front.len() >= 3);
assert!(back.len() >= 3);
assert!(front.unit_normal().is_some());
assert!(back.unit_normal().is_some());
}
#[test]
fn very_thin_slice() {
let polygon = Polygon::new(vec![
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), ]);
let plane = horizontal_plane(0.0);
let (front, back) = polygon.cut(&plane);
assert!(front.is_some());
assert!(back.is_some());
}
#[test]
fn large_polygon_many_vertices() {
let polygon = Polygon::new(vec![
Point3::new(1.0, 0.0, 0.0),
Point3::new(0.5, 0.866, 0.0),
Point3::new(-0.5, 0.866, 0.0),
Point3::new(-1.0, 0.0, 0.0),
Point3::new(-0.5, -0.866, 0.0),
Point3::new(0.5, -0.866, 0.0),
]);
let plane = horizontal_plane(0.0);
let (front, back) = polygon.cut(&plane);
assert!(front.is_some());
assert!(back.is_some());
assert_vertex_count(&front.unwrap(), 4);
assert_vertex_count(&back.unwrap(), 4);
}
#[test]
fn polygon_with_offset_plane() {
let polygon = Polygon::new(vec![
Point3::new(0.0, 5.0, 0.0),
Point3::new(1.0, 5.0, 0.0),
Point3::new(1.0, 3.0, 0.0),
Point3::new(0.0, 3.0, 0.0),
]);
let plane = horizontal_plane(4.0);
let (front, back) = polygon.cut(&plane);
assert!(front.is_some());
assert!(back.is_some());
assert_all_vertices_on_side(&front.unwrap(), &plane, PlaneSide::Front);
assert_all_vertices_on_side(&back.unwrap(), &plane, PlaneSide::Back);
}
#[test]
fn flipped_plane_reverses_classification() {
let polygon = Polygon::new(vec![
Point3::new(0.0, 1.0, 0.0),
Point3::new(1.0, 1.0, 0.0),
Point3::new(0.5, 2.0, 0.0),
]);
let plane = horizontal_plane(0.0);
let flipped_plane = plane.flipped();
let (front1, back1) = polygon.cut(&plane);
let (front2, back2) = polygon.cut(&flipped_plane);
assert!(front1.is_some());
assert!(back1.is_none());
assert!(front2.is_none());
assert!(back2.is_some());
}
#[test]
fn split_does_not_produce_degenerate_polygons() {
let test_cases = vec![
vec![
Point3::new(0.0, 0.0, 0.0),
Point3::new(1.0, 1.0, 0.0),
Point3::new(-1.0, 1.0, 0.0),
],
vec![
Point3::new(-1.0, 0.0, 0.0),
Point3::new(1.0, 0.0, 0.0),
Point3::new(1.0, 1.0, 0.0),
Point3::new(-1.0, 1.0, 0.0),
],
];
let plane = horizontal_plane(0.0);
for vertices in test_cases {
let polygon = Polygon::new(vertices);
let (front, back) = polygon.cut(&plane);
if let Some(f) = front {
assert!(f.len() >= 3, "Front polygon is degenerate");
}
if let Some(b) = back {
assert!(b.len() >= 3, "Back polygon is degenerate");
}
}
}
#[test]
fn consistent_results_with_equivalent_planes() {
let polygon = Polygon::new(vec![
Point3::new(0.0, 1.0, 0.0),
Point3::new(1.0, -1.0, 0.0),
Point3::new(-1.0, -1.0, 0.0),
]);
let plane1 = horizontal_plane(0.0);
let plane2 = Plane3D::new(Vector3::new(0.0, 1.0, 0.0), 0.0);
let (front1, back1) = polygon.cut(&plane1);
let (front2, back2) = polygon.cut(&plane2);
assert_eq!(front1.as_ref().map(|p| p.len()), front2.as_ref().map(|p| p.len()));
assert_eq!(back1.as_ref().map(|p| p.len()), back2.as_ref().map(|p| p.len()));
}
}