From 916124d48dd766c4670b2431de30010b95e84bca Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Thu, 30 Jan 2025 11:08:12 +0100 Subject: [PATCH 1/9] begin of monotone polygons --- crates/triangulation/src/lib.rs | 1 + crates/triangulation/src/monotone_polygon.rs | 128 +++++++++++++++++++ crates/triangulation/src/point.rs | 29 +++++ 3 files changed, 158 insertions(+) create mode 100644 crates/triangulation/src/monotone_polygon.rs diff --git a/crates/triangulation/src/lib.rs b/crates/triangulation/src/lib.rs index 04b3e88..2752c70 100644 --- a/crates/triangulation/src/lib.rs +++ b/crates/triangulation/src/lib.rs @@ -1,3 +1,4 @@ +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..2a95864 --- /dev/null +++ b/crates/triangulation/src/monotone_polygon.rs @@ -0,0 +1,128 @@ +use crate::monotone_polygon::Side::LEFT; +use crate::point::{orientation, Orientation, Point, PointTriangle}; +use std::cmp::PartialEq; +use std::collections::VecDeque; + +#[derive(Debug, Clone)] +pub struct MonotonePolygon { + top: Point, + bottom: Option, + left: Vec, + right: Vec, +} + +impl MonotonePolygon { + pub fn new(top: Point) -> Self { + MonotonePolygon { + top, + bottom: None, + left: Vec::new(), + right: Vec::new(), + } + } + + /// Check if monotone polygon is finished by checking if bottom is set + pub fn finished(&self) -> bool { + self.bottom.is_some() + } +} + +fn _build_triangles_opposite_edge( + stack: &mut VecDeque, + result: &mut Vec, + current_point: Point, +) { + 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.pop_front(); // Remove the first element. + stack.push_front(back); // Put last element at the beginning (equivalent to stack[0] = stack.back()) + stack.push_front(current_point); // Put the current point at the beginning (equivalent to stack[1] = current_point) + + // In Rust we don't need to manually erase from index 2 onwards. The + // previous pop/push operations have already left only the first two elements + // in the stack. +} + +fn _build_triangles_current_edge( + stack: &mut VecDeque, + result: &mut Vec, + current_point: Point, + expected_orientation: &Side, +) { + // Conversion of iterator logic to Rust using indices is generally preferred + let mut i = stack.len() - 1; + let orientation_ = if (*expected_orientation == Side::LEFT) { + Orientation::Collinear + } else { + Orientation::CounterClockwise + }; + + 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); // Efficiently remove elements from the back by truncating the vector + + stack.push_back(current_point); +} + +#[derive(Debug, Clone, PartialEq, Eq)] +enum Side { + TopOrBottom, + LEFT, + RIGHT, +} + +pub fn triangulate_monotone_polygon(polygon: &MonotonePolygon) -> Vec { + 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::new(); + + 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 a9d96a8..6bbbeb4 100644 --- a/crates/triangulation/src/point.rs +++ b/crates/triangulation/src/point.rs @@ -271,6 +271,23 @@ impl Ord for Segment { } #[derive(Debug, Clone)] +/// A structure representing a triangle using three indices of its 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, @@ -283,6 +300,18 @@ impl Triangle { } } +pub struct PointTriangle { + pub x: Point, + pub y: Point, + pub z: Point, +} + +impl PointTriangle { + pub fn new(x: Point, y: Point, z: Point) -> Self { + PointTriangle { x, y, z } + } +} + /// Calculates the Euclidean distance between two points. /// /// # Arguments From 865d07464e84043ace011b278bdbe07bf9f37329 Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Thu, 30 Jan 2025 23:06:57 +0100 Subject: [PATCH 2/9] update naminng of Side enum --- crates/triangulation/src/monotone_polygon.rs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/crates/triangulation/src/monotone_polygon.rs b/crates/triangulation/src/monotone_polygon.rs index 2a95864..f4f2868 100644 --- a/crates/triangulation/src/monotone_polygon.rs +++ b/crates/triangulation/src/monotone_polygon.rs @@ -1,4 +1,3 @@ -use crate::monotone_polygon::Side::LEFT; use crate::point::{orientation, Orientation, Point, PointTriangle}; use std::cmp::PartialEq; use std::collections::VecDeque; @@ -54,7 +53,7 @@ fn _build_triangles_current_edge( ) { // Conversion of iterator logic to Rust using indices is generally preferred let mut i = stack.len() - 1; - let orientation_ = if (*expected_orientation == Side::LEFT) { + let orientation_ = if (*expected_orientation == Side::Left) { Orientation::Collinear } else { Orientation::CounterClockwise @@ -73,8 +72,8 @@ fn _build_triangles_current_edge( #[derive(Debug, Clone, PartialEq, Eq)] enum Side { TopOrBottom, - LEFT, - RIGHT, + Left, + Right, } pub fn triangulate_monotone_polygon(polygon: &MonotonePolygon) -> Vec { @@ -91,21 +90,21 @@ pub fn triangulate_monotone_polygon(polygon: &MonotonePolygon) -> Vec Date: Fri, 31 Jan 2025 00:27:47 +0100 Subject: [PATCH 3/9] initial tests --- crates/bermuda/tests/test_monotone_polygon.rs | 22 +++++++++++++++++++ crates/triangulation/src/monotone_polygon.rs | 18 +++++++-------- crates/triangulation/src/point.rs | 18 +++++++++++++++ 3 files changed, 49 insertions(+), 9 deletions(-) create mode 100644 crates/bermuda/tests/test_monotone_polygon.rs diff --git a/crates/bermuda/tests/test_monotone_polygon.rs b/crates/bermuda/tests/test_monotone_polygon.rs new file mode 100644 index 0000000..d276670 --- /dev/null +++ b/crates/bermuda/tests/test_monotone_polygon.rs @@ -0,0 +1,22 @@ +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); + 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(top, left, right)); + assert_eq!(result[1], PointTriangle::new(left, right, bottom)); +} diff --git a/crates/triangulation/src/monotone_polygon.rs b/crates/triangulation/src/monotone_polygon.rs index f4f2868..e9263d2 100644 --- a/crates/triangulation/src/monotone_polygon.rs +++ b/crates/triangulation/src/monotone_polygon.rs @@ -4,10 +4,10 @@ use std::collections::VecDeque; #[derive(Debug, Clone)] pub struct MonotonePolygon { - top: Point, - bottom: Option, - left: Vec, - right: Vec, + pub top: Point, + pub bottom: Option, + pub left: Vec, + pub right: Vec, } impl MonotonePolygon { @@ -26,7 +26,7 @@ impl MonotonePolygon { } } -fn _build_triangles_opposite_edge( +fn build_triangles_opposite_edge( stack: &mut VecDeque, result: &mut Vec, current_point: Point, @@ -45,7 +45,7 @@ fn _build_triangles_opposite_edge( // in the stack. } -fn _build_triangles_current_edge( +fn build_triangles_current_edge( stack: &mut VecDeque, result: &mut Vec, current_point: Point, @@ -53,7 +53,7 @@ fn _build_triangles_current_edge( ) { // Conversion of iterator logic to Rust using indices is generally preferred let mut i = stack.len() - 1; - let orientation_ = if (*expected_orientation == Side::Left) { + let orientation_ = if *expected_orientation == Side::Left { Orientation::Collinear } else { Orientation::CounterClockwise @@ -116,9 +116,9 @@ pub fn triangulate_monotone_polygon(polygon: &MonotonePolygon) -> Vec Self { PointTriangle { x, y, z } } + + fn get_points_set(&self) -> HashSet { + let mut points_set = HashSet::with_capacity(3); + points_set.insert(self.x); + points_set.insert(self.y); + points_set.insert(self.z); + points_set + } +} + +impl PartialEq for PointTriangle { + fn eq(&self, other: &Self) -> bool { + self.get_points_set() == other.get_points_set() + } } +impl Eq for PointTriangle {} + /// Calculates the Euclidean distance between two points. /// /// # Arguments From bea04db4411ad18ec38afa7bae36f8964598078c Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Tue, 4 Feb 2025 20:48:48 +0100 Subject: [PATCH 4/9] add test, fix code --- crates/bermuda/tests/test_monotone_polygon.rs | 51 +++++++++++++++++-- crates/triangulation/src/monotone_polygon.rs | 30 +++++++---- crates/triangulation/src/point.rs | 33 ++++-------- 3 files changed, 77 insertions(+), 37 deletions(-) diff --git a/crates/bermuda/tests/test_monotone_polygon.rs b/crates/bermuda/tests/test_monotone_polygon.rs index d276670..bade64b 100644 --- a/crates/bermuda/tests/test_monotone_polygon.rs +++ b/crates/bermuda/tests/test_monotone_polygon.rs @@ -8,7 +8,7 @@ fn test_monotone_polygon_simple() { 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); + let mut poly = MonotonePolygon::new_top(top); assert!(!poly.finished()); poly.left.push(left); poly.right.push(right); @@ -17,6 +17,51 @@ fn test_monotone_polygon_simple() { assert!(poly.finished()); let result = triangulate_monotone_polygon(&poly); assert_eq!(result.len(), 2); - assert_eq!(result[0], PointTriangle::new(top, left, right)); - assert_eq!(result[1], PointTriangle::new(left, right, bottom)); + 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/monotone_polygon.rs b/crates/triangulation/src/monotone_polygon.rs index e9263d2..4e75e9e 100644 --- a/crates/triangulation/src/monotone_polygon.rs +++ b/crates/triangulation/src/monotone_polygon.rs @@ -11,12 +11,21 @@ pub struct MonotonePolygon { } impl MonotonePolygon { - pub fn new(top: Point) -> Self { - 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::new(), - right: Vec::new(), + left: vec![], + right: vec![], } } @@ -36,9 +45,9 @@ fn build_triangles_opposite_edge( } let back = stack.pop_back().unwrap(); // Get the last element - stack.pop_front(); // Remove the first element. - stack.push_front(back); // Put last element at the beginning (equivalent to stack[0] = stack.back()) - stack.push_front(current_point); // Put the current point at the beginning (equivalent to stack[1] = current_point) + stack.clear(); // Remove the first element. + stack.push_back(back); // Put last element at the beginning (equivalent to stack[0] = stack.back()) + stack.push_back(current_point); // Put the current point at the beginning (equivalent to stack[1] = current_point) // In Rust we don't need to manually erase from index 2 onwards. The // previous pop/push operations have already left only the first two elements @@ -54,11 +63,10 @@ fn build_triangles_current_edge( // Conversion of iterator logic to Rust using indices is generally preferred let mut i = stack.len() - 1; let orientation_ = if *expected_orientation == Side::Left { - Orientation::Collinear - } else { 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; @@ -81,7 +89,7 @@ pub fn triangulate_monotone_polygon(polygon: &MonotonePolygon) -> Vec = VecDeque::new(); // Using VecDeque for O(1) push_front - let mut points = Vec::new(); + 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); diff --git a/crates/triangulation/src/point.rs b/crates/triangulation/src/point.rs index a807ecc..9236453 100644 --- a/crates/triangulation/src/point.rs +++ b/crates/triangulation/src/point.rs @@ -1,5 +1,4 @@ use std::cmp::Ordering; -use std::collections::HashSet; use std::fmt; use std::hash::{Hash, Hasher}; @@ -301,35 +300,23 @@ impl Triangle { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct PointTriangle { - pub x: Point, - pub y: Point, - pub z: Point, + pub p1: Point, + pub p2: Point, + pub p3: Point, } impl PointTriangle { - pub fn new(x: Point, y: Point, z: Point) -> Self { - PointTriangle { x, y, z } - } - - fn get_points_set(&self) -> HashSet { - let mut points_set = HashSet::with_capacity(3); - points_set.insert(self.x); - points_set.insert(self.y); - points_set.insert(self.z); - points_set - } -} - -impl PartialEq for PointTriangle { - fn eq(&self, other: &Self) -> bool { - self.get_points_set() == other.get_points_set() + pub fn new(p1: Point, p2: Point, p3: Point) -> Self { + if (p2.x - p1.x) * (p3.y - p1.y) - (p2.y - p1.y) * (p3.x - p1.x) < 0.0 { + Self { p1: p3, p2, p3: p1 } + } else { + Self { p1, p2, p3 } + } } } -impl Eq for PointTriangle {} - /// Calculates the Euclidean distance between two points. /// /// # Arguments From 3ed86460d3f609922a63226f47bc032861a2eb7b Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Wed, 5 Feb 2025 21:44:48 +0100 Subject: [PATCH 5/9] add docstrings --- crates/triangulation/src/monotone_polygon.rs | 140 +++++++++++++++++-- crates/triangulation/src/point.rs | 48 ++++++- 2 files changed, 174 insertions(+), 14 deletions(-) diff --git a/crates/triangulation/src/monotone_polygon.rs b/crates/triangulation/src/monotone_polygon.rs index 4e75e9e..780f306 100644 --- a/crates/triangulation/src/monotone_polygon.rs +++ b/crates/triangulation/src/monotone_polygon.rs @@ -4,6 +4,56 @@ 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, @@ -35,6 +85,21 @@ impl MonotonePolygon { } } +/// Builds triangles when the current point is from the opposite edge 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, @@ -45,24 +110,36 @@ fn build_triangles_opposite_edge( } let back = stack.pop_back().unwrap(); // Get the last element - stack.clear(); // Remove the first element. - stack.push_back(back); // Put last element at the beginning (equivalent to stack[0] = stack.back()) - stack.push_back(current_point); // Put the current point at the beginning (equivalent to stack[1] = current_point) - - // In Rust we don't need to manually erase from index 2 onwards. The - // previous pop/push operations have already left only the first two elements - // in the stack. + 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, + expected_orientation: Side, ) { - // Conversion of iterator logic to Rust using indices is generally preferred let mut i = stack.len() - 1; - let orientation_ = if *expected_orientation == Side::Left { + // Decide which orientation depending on from which chain current point is + let orientation_ = if expected_orientation == Side::Left { Orientation::CounterClockwise } else { Orientation::Clockwise @@ -72,18 +149,55 @@ fn build_triangles_current_edge( i -= 1; } - stack.truncate(i + 1); // Efficiently remove elements from the back by truncating the vector + stack.truncate(i + 1); // remove all consumed points stack.push_back(current_point); } -#[derive(Debug, Clone, PartialEq, Eq)] +/// 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 { let mut result = Vec::new(); let mut left_index = 0; @@ -124,7 +238,7 @@ pub fn triangulate_monotone_polygon(polygon: &MonotonePolygon) -> Vec Self { - if (p2.x - p1.x) * (p3.y - p1.y) - (p2.y - p1.y) * (p3.x - p1.x) < 0.0 { + let orientation = (p2.x - p1.x) * (p3.y - p1.y) - (p2.y - p1.y) * (p3.x - p1.x); + if orientation < 0.0 { Self { p1: p3, p2, p3: p1 } } else { Self { p1, p2, p3 } From 13a8540973266da74ba76b216444afe36d7bae17 Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Wed, 5 Feb 2025 21:51:06 +0100 Subject: [PATCH 6/9] update docstrings --- crates/triangulation/src/monotone_polygon.rs | 2 +- crates/triangulation/src/point.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/triangulation/src/monotone_polygon.rs b/crates/triangulation/src/monotone_polygon.rs index 780f306..427c9b0 100644 --- a/crates/triangulation/src/monotone_polygon.rs +++ b/crates/triangulation/src/monotone_polygon.rs @@ -85,7 +85,7 @@ impl MonotonePolygon { } } -/// Builds triangles when the current point is from the opposite edge than the previous one. +/// 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 diff --git a/crates/triangulation/src/point.rs b/crates/triangulation/src/point.rs index 36fffd6..9117596 100644 --- a/crates/triangulation/src/point.rs +++ b/crates/triangulation/src/point.rs @@ -271,7 +271,7 @@ impl Ord for Segment { } #[derive(Debug, Clone)] -/// A structure representing a triangle using three indices of its vertices. +/// A structure representing a triangle as indexes of vertices. /// /// # Fields /// * `x` - The index of the first vertex. @@ -370,7 +370,7 @@ impl PointTriangle { /// * `p2` - The second point. /// /// # Returns -/// Returns the distance between `p1` and `p2` as a `Coord`. +/// Distance between `p1` and `p2` as a `Coord`. /// /// # Examples /// ``` From b082bfb7cfb89e01ac824f3dcf6e7558f01ced45 Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Wed, 5 Feb 2025 22:01:17 +0100 Subject: [PATCH 7/9] add checks for ensure proper usage --- crates/triangulation/src/monotone_polygon.rs | 9 ++++ crates/triangulation/src/point.rs | 52 ++------------------ 2 files changed, 12 insertions(+), 49 deletions(-) diff --git a/crates/triangulation/src/monotone_polygon.rs b/crates/triangulation/src/monotone_polygon.rs index 427c9b0..2ff3769 100644 --- a/crates/triangulation/src/monotone_polygon.rs +++ b/crates/triangulation/src/monotone_polygon.rs @@ -105,6 +105,12 @@ fn build_triangles_opposite_edge( 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])); } @@ -199,6 +205,9 @@ enum Side { /// 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; diff --git a/crates/triangulation/src/point.rs b/crates/triangulation/src/point.rs index 9117596..9236453 100644 --- a/crates/triangulation/src/point.rs +++ b/crates/triangulation/src/point.rs @@ -271,7 +271,7 @@ impl Ord for Segment { } #[derive(Debug, Clone)] -/// A structure representing a triangle as indexes of vertices. +/// A structure representing a triangle using three indices of its vertices. /// /// # Fields /// * `x` - The index of the first vertex. @@ -301,26 +301,6 @@ impl Triangle { } #[derive(Debug, Clone, PartialEq, Eq)] -/// A structure representing a triangle using three [`Point`] as its vertices. -/// -/// # Fields -/// * `p1` - The first vertex of the triangle. -/// * `p2` - The second vertex of the triangle. -/// * `p3` - The third vertex of the triangle. -/// -/// # Examples -/// ``` -/// use triangulation::point::{Point, PointTriangle}; -/// -/// let p1 = Point::new(0.0, 0.0); -/// let p2 = Point::new(1.0, 0.0); -/// let p3 = Point::new(0.0, 1.0); -/// -/// let triangle = PointTriangle::new(p1, p2, p3); -/// assert_eq!(triangle.p1, p1); -/// assert_eq!(triangle.p2, p2); -/// assert_eq!(triangle.p3, p3); -/// ``` pub struct PointTriangle { pub p1: Point, pub p2: Point, @@ -328,34 +308,8 @@ pub struct PointTriangle { } impl PointTriangle { - /// Creates a new `PointTriangle`. - /// - /// The vertices of the triangle are reordered such that the triangle has a counterclockwise orientation. - /// - /// # Arguments - /// * `p1` - The first vertex of the triangle. - /// * `p2` - The second vertex of the triangle. - /// * `p3` - The third vertex of the triangle. - /// - /// # Returns - /// A `PointTriangle` instance with reordered vertices, if needed, to ensure counterclockwise orientation. - /// - /// # Examples - /// ``` - /// use triangulation::point::{Point, PointTriangle}; - /// - /// let p1 = Point::new(0.0, 0.0); - /// let p2 = Point::new(1.0, 0.0); - /// let p3 = Point::new(0.0, 1.0); - /// - /// let triangle = PointTriangle::new(p1, p2, p3); - /// assert_eq!(triangle.p1, p1); - /// assert_eq!(triangle.p2, p2); - /// assert_eq!(triangle.p3, p3); - /// ``` pub fn new(p1: Point, p2: Point, p3: Point) -> Self { - let orientation = (p2.x - p1.x) * (p3.y - p1.y) - (p2.y - p1.y) * (p3.x - p1.x); - if orientation < 0.0 { + if (p2.x - p1.x) * (p3.y - p1.y) - (p2.y - p1.y) * (p3.x - p1.x) < 0.0 { Self { p1: p3, p2, p3: p1 } } else { Self { p1, p2, p3 } @@ -370,7 +324,7 @@ impl PointTriangle { /// * `p2` - The second point. /// /// # Returns -/// Distance between `p1` and `p2` as a `Coord`. +/// Returns the distance between `p1` and `p2` as a `Coord`. /// /// # Examples /// ``` From 19f5cbf0db2d8450aca161dc6c4fb59819c29d0d Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Thu, 6 Feb 2025 08:49:46 +0100 Subject: [PATCH 8/9] Apply suggestions from code review Co-authored-by: Carol Willing --- crates/triangulation/src/lib.rs | 4 ++++ crates/triangulation/src/point.rs | 5 ++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/crates/triangulation/src/lib.rs b/crates/triangulation/src/lib.rs index 2752c70..0c6e627 100644 --- a/crates/triangulation/src/lib.rs +++ b/crates/triangulation/src/lib.rs @@ -1,3 +1,7 @@ +//! This library provides computational algorithms for triangulation. +//! +//! These algorithms are designed for performance when working with polygons. + pub mod monotone_polygon; pub mod path_triangulation; pub mod point; diff --git a/crates/triangulation/src/point.rs b/crates/triangulation/src/point.rs index 9236453..880748d 100644 --- a/crates/triangulation/src/point.rs +++ b/crates/triangulation/src/point.rs @@ -271,7 +271,7 @@ impl Ord for Segment { } #[derive(Debug, Clone)] -/// A structure representing a triangle using three indices of its vertices. +/// Represents a triangle using indices of its three vertices. /// /// # Fields /// * `x` - The index of the first vertex. @@ -300,6 +300,7 @@ impl Triangle { } } +/// Represents a triangle using three points. #[derive(Debug, Clone, PartialEq, Eq)] pub struct PointTriangle { pub p1: Point, @@ -309,7 +310,9 @@ pub struct PointTriangle { 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 } From f0acca184915c6663d80f2bb5e1e02b18b202ba3 Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Mon, 10 Feb 2025 16:54:00 +0100 Subject: [PATCH 9/9] fix import order --- crates/triangulation/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/triangulation/src/lib.rs b/crates/triangulation/src/lib.rs index 5108ade..c50010b 100644 --- a/crates/triangulation/src/lib.rs +++ b/crates/triangulation/src/lib.rs @@ -2,8 +2,8 @@ //! //! These algorithms are designed for performance when working with polygons. -pub mod monotone_polygon; pub mod intersection; +pub mod monotone_polygon; pub mod path_triangulation; pub mod point;