diff --git a/crates/bermuda/tests/test_monotone_polygon.rs b/crates/bermuda/tests/test_monotone_polygon.rs new file mode 100644 index 0000000..bade64b --- /dev/null +++ b/crates/bermuda/tests/test_monotone_polygon.rs @@ -0,0 +1,67 @@ +use rstest::rstest; +use triangulation::monotone_polygon::{triangulate_monotone_polygon, MonotonePolygon}; +use triangulation::point::{Point, PointTriangle}; + +#[rstest] +fn test_monotone_polygon_simple() { + let top = Point::new(0.0, 10.0); + let left = Point::new(-1.0, 7.0); + let right = Point::new(1.0, 5.0); + let bottom = Point::new(0.0, 0.0); + let mut poly = MonotonePolygon::new_top(top); + assert!(!poly.finished()); + poly.left.push(left); + poly.right.push(right); + assert!(!poly.finished()); + poly.bottom = Option::from(bottom); + assert!(poly.finished()); + let result = triangulate_monotone_polygon(&poly); + assert_eq!(result.len(), 2); + assert_eq!(result[0], PointTriangle::new(right, top, left)); + assert_eq!(result[1], PointTriangle::new(bottom, left, right)); +} + +#[rstest] +#[case::diamond( + MonotonePolygon::new(Point::new(1.0, 2.0), Point::new(1.0, 0.0), vec![Point::new(0.0, 1.0)], vec![Point::new(2.0, 1.0)]), + vec![PointTriangle::new(Point::new(2.0, 1.0), Point::new(1.0, 2.0), Point::new(0.0, 1.0)), PointTriangle::new(Point::new(1.0, 0.0), Point::new(2.0, 1.0), Point::new(0.0, 1.0))] +)] +#[case::wide_diamond( + MonotonePolygon::new(Point::new(5.0, 2.0), Point::new(5.0, 0.0), vec![Point::new(0.0, 1.0)], vec![Point::new(2.0, 1.0)]), + vec![PointTriangle::new(Point::new(2.0, 1.0), Point::new(5.0, 2.0), Point::new(0.0, 1.0)), PointTriangle::new(Point::new(5.0, 0.0), Point::new(2.0, 1.0), Point::new(0.0, 1.0))] +)] +#[case::double_diamond( + MonotonePolygon::new(Point::new(1.0, 3.0), Point::new(1.0, 0.0), + vec![Point::new(0.0, 2.0), Point::new(0.0, 1.0)], + vec![Point::new(2.0, 2.0), Point::new(2.0, 1.0)]), + vec![ + PointTriangle::new(Point::new(2.0, 2.0), Point::new(1.0, 3.0), Point::new(0.0, 2.0)), + PointTriangle::new(Point::new(2.0, 1.0), Point::new(2.0, 2.0), Point::new(0.0, 2.0)), + PointTriangle::new(Point::new(2.0, 1.0), Point::new(0.0, 2.0), Point::new(0.0, 1.0)), + PointTriangle::new(Point::new(1.0, 0.0), Point::new(2.0, 1.0), Point::new(0.0, 1.0)) + ] +)] +#[case::zigzag_right( + MonotonePolygon::new(Point::new(0.0, 4.0), Point::new(0.5, 0.0), + vec![], + vec![Point::new(2.0, 3.0), Point::new(3.0, 2.0), Point::new(2.0, 1.0)]), + vec![ + PointTriangle::new(Point::new(3.0, 2.0), Point::new(2.0, 3.0), Point::new(0.0, 4.0)), + PointTriangle::new(Point::new(2.0, 1.0), Point::new(3.0, 2.0), Point::new(0.0, 4.0)), + PointTriangle::new(Point::new(2.0, 1.0), Point::new(0.0, 4.0), Point::new(0.5, 0.0)) + ] +)] +#[case::zigzag_right_shallow( + MonotonePolygon::new(Point::new(0.0, 4.0), Point::new(0.0, 0.0), + vec![], + vec![Point::new(2.0, 3.0), Point::new(1.0, 2.0), Point::new(2.0, 1.0)]), + vec![ + PointTriangle::new(Point::new(1.0, 2.0), Point::new(2.0, 3.0), Point::new(0.0, 4.0)), + PointTriangle::new(Point::new(1.0, 2.0), Point::new(0.0, 4.0), Point::new(0.0, 0.0)), + PointTriangle::new(Point::new(2.0, 1.0), Point::new(1.0, 2.0), Point::new(0.0, 0.0)) + ] +)] +fn test_monotone_polygon(#[case] poly: MonotonePolygon, #[case] expected: Vec) { + let result = triangulate_monotone_polygon(&poly); + assert_eq!(result, expected); +} diff --git a/crates/triangulation/src/lib.rs b/crates/triangulation/src/lib.rs index 144e664..c50010b 100644 --- a/crates/triangulation/src/lib.rs +++ b/crates/triangulation/src/lib.rs @@ -1,4 +1,9 @@ +//! This library provides computational algorithms for triangulation. +//! +//! These algorithms are designed for performance when working with polygons. + pub mod intersection; +pub mod monotone_polygon; pub mod path_triangulation; pub mod point; diff --git a/crates/triangulation/src/monotone_polygon.rs b/crates/triangulation/src/monotone_polygon.rs new file mode 100644 index 0000000..2ff3769 --- /dev/null +++ b/crates/triangulation/src/monotone_polygon.rs @@ -0,0 +1,258 @@ +use crate::point::{orientation, Orientation, Point, PointTriangle}; +use std::cmp::PartialEq; +use std::collections::VecDeque; + +#[derive(Debug, Clone)] +pub struct MonotonePolygon { + /// Represents a y-monotone polygon, which is a polygon whose vertices are split + /// into two chains (left and right) based on their y-coordinates, and the topmost + /// and bottommost vertices are connected by these chains. + /// + /// A polygon is considered y-monotone if a straight horizontal line (parallel + /// to the x-axis) intersects it at most twice. In other words, the vertices of the + /// polygon can be divided into two chains, often referred to as the "left chain" + /// and the "right chain," where each chain is monotonic with respect to the + /// y-coordinate (i.e., the y-coordinates of the vertices are sorted either + /// in strictly increasing or strictly decreasing order). + /// + /// The top-most and bottom-most vertices of a y-monotone polygon are common + /// to both chains, and no other horizontal line intersects more than two points + /// of the polygon. + /// + /// # Fields + /// + /// - `top`: The topmost vertex of the polygon. + /// - `bottom`: An optional bottom vertex of the polygon. If this is `None`, + /// the polygon is considered incomplete. + /// - `left`: A collection of vertices forming the left chain of the polygon. + /// The vertices are stored in descending order based on their y-coordinates. + /// - `right`: A collection of vertices forming the right chain of the polygon. + /// The vertices are stored in descending order based on their y-coordinates. + /// + /// # Usage + /// + /// The `MonotonePolygon` can be used to construct a polygon by adding vertices + /// to its left and right chains. Typically, it is used in computational geometry + /// algorithms such as triangulation. + /// + /// - To create a fully defined monotone polygon, use `MonotonePolygon::new`. + /// - For an incomplete polygon where only the top vertex is known, use + /// `MonotonePolygon::new_top`. + /// - To check if the polygon is complete, use the `finished` method. + /// + /// # Example + /// + /// ```rust + /// use triangulation::point::Point; + /// use triangulation::monotone_polygon::MonotonePolygon; + /// + /// let top = Point::new(0.0, 10.0); + /// let bottom = Point::new(0.0, 0.0); + /// let left_chain = vec![Point::new(-1.0, 8.0), Point::new(-2.0, 6.0)]; + /// let right_chain = vec![Point::new(1.0, 8.0), Point::new(2.0, 6.0)]; + /// + /// let polygon = MonotonePolygon::new(top, bottom, left_chain, right_chain); + /// assert!(polygon.finished()); + /// ``` + pub top: Point, + pub bottom: Option, + pub left: Vec, + pub right: Vec, +} + +impl MonotonePolygon { + pub fn new(top: Point, bottom: Point, left: Vec, right: Vec) -> Self { + Self { + top, + bottom: Some(bottom), + left, + right, + } + } + + pub fn new_top(top: Point) -> Self { + Self { + top, + bottom: None, + left: vec![], + right: vec![], + } + } + + /// Check if monotone polygon is finished by checking if bottom is set + pub fn finished(&self) -> bool { + self.bottom.is_some() + } +} + +/// Builds triangles when the current point is from the opposite edger than the previous one. +/// +/// This function is invoked during the y-monotone polygon triangulation process +/// to handle the scenario where the current point is located on the opposite edge +/// compared to the previous point. It generates triangles using the points in the +/// stack as one edge of the triangle, and the current point as the opposite vertex. +/// +/// # Arguments +/// +/// - `stack`: A mutable reference to a `VecDeque` containing points representing the +/// current chain of the polygon being processed. The points are used to form triangles. +/// - `result`: A mutable reference to a vector of `PointTriangle` that will hold the +/// resulting triangles generated during the triangulation process. +/// - `current_point`: The current point being processed, which belongs to the opposite +/// edge of the polygon relative to the previous point. +fn build_triangles_opposite_edge( + stack: &mut VecDeque, + result: &mut Vec, + current_point: Point, +) { + #[cfg(debug_assertions)] + { + if stack.is_empty() { + panic!("Cannot build triangles when stack is empty. Ensure the polygon is not empty."); + } + } + for i in 0..stack.len() - 1 { + result.push(PointTriangle::new(current_point, stack[i], stack[i + 1])); + } + + let back = stack.pop_back().unwrap(); // Get the last element + stack.clear(); // clear all consumed points + stack.push_back(back); // add points to stack for next triangle + stack.push_back(current_point); +} + +/// Builds triangles when the current point is along the same edge chain as the previous point. +/// +/// This function processes points that are part of the same chain (left or right) of the y-monotone +/// polygon and builds triangles based on the expected orientation. It ensures that the formed triangles +/// are valid, following the order of the points in the chain. +/// +/// # Arguments +/// +/// - `stack`: A mutable reference to a `VecDeque` holding points that are part of the current chain +/// being processed. These points are used as vertices of the triangle. +/// - `result`: A mutable reference to a `Vec` that stores the triangles generated +/// during the triangulation process. +/// - `current_point`: The point currently being processed, which is on the same edge chain as the +/// previous point. +/// - `expected_orientation`: The expected orientation of the triangles to be formed, determined based +/// on whether the current chain belongs to the left or right edge of the polygon. +fn build_triangles_current_edge( + stack: &mut VecDeque, + result: &mut Vec, + current_point: Point, + expected_orientation: Side, +) { + let mut i = stack.len() - 1; + // Decide which orientation depending on from which chain current point is + let orientation_ = if expected_orientation == Side::Left { + Orientation::CounterClockwise + } else { + Orientation::Clockwise + }; + while i > 0 && orientation(stack[i - 1], stack[i], current_point) == orientation_ { + result.push(PointTriangle::new(current_point, stack[i], stack[i - 1])); + i -= 1; + } + + stack.truncate(i + 1); // remove all consumed points + + stack.push_back(current_point); +} + +/// Enum to store information from which chain of +/// y-monotone polygon points comes +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +enum Side { + TopOrBottom, + Left, + Right, +} + +/// Triangulates a y-monotone polygon into a set of triangles. +/// +/// This function takes a y-monotone polygon as input and performs triangulation, +/// splitting it into a collection of triangles. The algorithm relies on the +/// structure of the polygon, dividing it into left and right chains based on +/// y-coordinates, and processes the vertices using a stack-based approach. +/// Triangles are formed either between points on the same chain or points across +/// opposite chains. +/// +/// # Arguments +/// +/// - `polygon`: A reference to a `MonotonePolygon` structure, which contains +/// the top, bottom, left chain, and right chain vertices of the y-monotone polygon. +/// +/// # Returns +/// +/// A vector of `PointTriangle` representing the resulting triangles formed +/// during the triangulation process. +/// +/// # Example +/// +/// ```rust +/// use triangulation::point::{Point, PointTriangle}; +/// use triangulation::monotone_polygon::{MonotonePolygon, triangulate_monotone_polygon}; +/// +/// let top = Point::new(0.0, 10.0); +/// let bottom = Point::new(0.0, 0.0); +/// let left_chain = vec![Point::new(-1.0, 8.0), Point::new(-2.0, 6.0)]; +/// let right_chain = vec![Point::new(1.0, 8.0), Point::new(2.0, 6.0)]; +/// +/// let polygon = MonotonePolygon::new(top, bottom, left_chain, right_chain); +/// let triangles = triangulate_monotone_polygon(&polygon); +/// +/// assert_eq!(triangles.len(), 4); +/// ``` +pub fn triangulate_monotone_polygon(polygon: &MonotonePolygon) -> Vec { + if !polygon.finished() { + panic!("Cannot triangulate an unfinished polygon. Ensure the bottom is set before calling this function."); + } + let mut result = Vec::new(); + let mut left_index = 0; + let mut right_index = 0; + let mut stack: VecDeque = VecDeque::new(); // Using VecDeque for O(1) push_front + let mut points = Vec::with_capacity(polygon.left.len() + polygon.right.len() + 2); + + result.reserve(polygon.left.len() + polygon.right.len()); + points.reserve(polygon.left.len() + polygon.right.len() + 2); + + points.push((polygon.top, Side::TopOrBottom)); + + while left_index < polygon.left.len() && right_index < polygon.right.len() { + if polygon.left[left_index] < polygon.right[right_index] { + points.push((polygon.right[right_index], Side::Right)); + right_index += 1; + } else { + points.push((polygon.left[left_index], Side::Left)); + left_index += 1; + } + } + + while left_index < polygon.left.len() { + points.push((polygon.left[left_index], Side::Left)); + left_index += 1; + } + + while right_index < polygon.right.len() { + points.push((polygon.right[right_index], Side::Right)); + right_index += 1; + } + + points.push((polygon.bottom.unwrap(), Side::TopOrBottom)); + + stack.push_back(points[0].0); + stack.push_back(points[1].0); + let mut side = &points[1].1; + + for i in 2..points.len() { + if *side == points[i].1 { + build_triangles_current_edge(&mut stack, &mut result, points[i].0, *side); + } else { + build_triangles_opposite_edge(&mut stack, &mut result, points[i].0); + } + side = &points[i].1; + } + + result +} diff --git a/crates/triangulation/src/point.rs b/crates/triangulation/src/point.rs index 366ffca..5f46f4c 100644 --- a/crates/triangulation/src/point.rs +++ b/crates/triangulation/src/point.rs @@ -276,6 +276,23 @@ impl Ord for Segment { } #[derive(Debug, Clone)] +/// Represents a triangle using indices of its three vertices. +/// +/// # Fields +/// * `x` - The index of the first vertex. +/// * `y` - The index of the second vertex. +/// * `z` - The index of the third vertex. +/// +/// # Examples +/// ``` +/// use triangulation::point::Triangle; +/// +/// let triangle = Triangle::new(0, 1, 2); +/// assert_eq!(triangle.x, 0); +/// assert_eq!(triangle.y, 1); +/// assert_eq!(triangle.z, 2); +/// ``` + pub struct Triangle { pub x: Index, pub y: Index, @@ -288,6 +305,26 @@ impl Triangle { } } +/// Represents a triangle using three points. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct PointTriangle { + pub p1: Point, + pub p2: Point, + pub p3: Point, +} + +impl PointTriangle { + pub fn new(p1: Point, p2: Point, p3: Point) -> Self { + /// Check if points are ordered counter-clockwise. + if (p2.x - p1.x) * (p3.y - p1.y) - (p2.y - p1.y) * (p3.x - p1.x) < 0.0 { + /// Reorder points to be counter-clockwise. + Self { p1: p3, p2, p3: p1 } + } else { + Self { p1, p2, p3 } + } + } +} + /// Calculates the Euclidean distance between two points. /// /// # Arguments