Skip to main content

physdes/
algorithms.rs

1//! Additional geometric algorithms
2//!
3//! This module provides additional geometric algorithms beyond the basic operations.
4
5use crate::Polygon;
6
7/// Checks if a point is inside a polygon using the ray casting algorithm
8///
9/// A horizontal ray from the point to the right intersects an edge if:
10///
11/// $$(y_i > q_y) \neq (y_j > q_y) \land q_x < \frac{x_j - x_i}{y_j - y_i}(q_y - y_i) + x_i$$
12///
13/// # Arguments
14///
15/// * `point` - The point to check
16/// * `polygon` - The polygon to test against
17///
18/// # Returns
19///
20/// `true` if the point is inside the polygon, `false` otherwise
21///
22/// # Examples
23///
24/// ```
25/// use physdes::{Point, algorithms::point_in_polygon};
26/// use physdes::Polygon;
27///
28/// let points = vec![
29///     Point::new(0, 0),
30///     Point::new(10, 0),
31///     Point::new(10, 10),
32///     Point::new(0, 10),
33/// ];
34/// let polygon = Polygon::new(&points);
35///
36/// assert!(point_in_polygon(&Point::new(5, 5), &polygon));
37/// assert!(!point_in_polygon(&Point::new(15, 15), &polygon));
38/// ```
39pub fn point_in_polygon<T>(point: &crate::Point<T, T>, polygon: &Polygon<T>) -> bool
40where
41    T: Clone
42        + Copy
43        + PartialOrd
44        + std::ops::Sub<Output = T>
45        + std::ops::Mul<Output = T>
46        + std::ops::Div<Output = T>
47        + std::ops::Add<Output = T>
48        + num_traits::Zero
49        + num_traits::Num
50        + std::ops::AddAssign
51        + Ord,
52{
53    let vertices = polygon.get_vertices();
54    let n = vertices.len();
55
56    if n < 3 {
57        return false;
58    }
59
60    let mut inside = false;
61    let mut j = n - 1;
62
63    for i in 0..n {
64        let xi = vertices[i].xcoord;
65        let yi = vertices[i].ycoord;
66        let xj = vertices[j].xcoord;
67        let yj = vertices[j].ycoord;
68
69        let intersect = ((yi > point.ycoord) != (yj > point.ycoord))
70            && (point.xcoord < (xj - xi) * (point.ycoord - yi) / (yj - yi) + xi);
71
72        if intersect {
73            inside = !inside;
74        }
75
76        j = i;
77    }
78
79    inside
80}
81
82/// Computes the centroid (geometric center) of a polygon
83///
84/// The centroid is the arithmetic mean of all vertex positions:
85///
86/// $$\bar{x} = \frac{1}{n}\sum_{i=1}^{n} x_i,\qquad \bar{y} = \frac{1}{n}\sum_{i=1}^{n} y_i$$
87///
88/// # Arguments
89///
90/// * `polygon` - The polygon
91///
92/// # Returns
93///
94/// The centroid point of the polygon
95///
96/// # Examples
97///
98/// ```
99/// use physdes::{Point, algorithms::polygon_centroid};
100/// use physdes::Polygon;
101///
102/// let points = vec![
103///     Point::new(0, 0),
104///     Point::new(4, 0),
105///     Point::new(4, 3),
106/// ];
107/// let polygon = Polygon::new(&points);
108///
109/// let centroid = polygon_centroid(&polygon);
110/// // For a triangle, centroid is at the average of vertices
111/// ```
112pub fn polygon_centroid<T>(polygon: &Polygon<T>) -> crate::Point<T, T>
113where
114    T: Clone
115        + Copy
116        + std::ops::Add<Output = T>
117        + std::ops::Div<Output = T>
118        + From<i32>
119        + num_traits::Zero
120        + num_traits::Num
121        + std::ops::AddAssign
122        + Ord,
123{
124    let vertices = polygon.get_vertices();
125    let n = vertices.len();
126
127    if n == 0 {
128        return crate::Point::new(T::from(0), T::from(0));
129    }
130
131    let mut sum_x = T::from(0);
132    let mut sum_y = T::from(0);
133
134    for vertex in &vertices {
135        sum_x += vertex.xcoord;
136        sum_y += vertex.ycoord;
137    }
138
139    let n_t = T::from(n as i32);
140    crate::Point::new(sum_x / n_t, sum_y / n_t)
141}
142
143/// Computes the perimeter of a polygon
144///
145/// Sums the Manhattan distance between consecutive vertices (including wrap-around):
146///
147/// $$P = \sum_{i=0}^{n-1} (|x_{i+1} - x_i| + |y_{i+1} - y_i|)$$
148///
149/// where vertex $n$ wraps to vertex $0$.
150///
151/// # Arguments
152///
153/// * `polygon` - The polygon
154///
155/// # Returns
156///
157/// The perimeter length
158///
159/// # Examples
160///
161/// ```
162/// use physdes::{Point, algorithms::polygon_perimeter};
163/// use physdes::Polygon;
164///
165/// let points = vec![
166///     Point::new(0, 0),
167///     Point::new(3, 0),
168///     Point::new(3, 4),
169///     Point::new(0, 4),
170/// ];
171/// let polygon = Polygon::new(&points);
172///
173/// let perimeter = polygon_perimeter(&polygon);
174/// assert_eq!(perimeter, 14); // 3 + 4 + 3 + 4
175/// ```
176pub fn polygon_perimeter<T>(polygon: &Polygon<T>) -> T
177where
178    T: Clone
179        + Copy
180        + std::ops::Add<Output = T>
181        + std::ops::Sub<Output = T>
182        + std::ops::Mul<Output = T>
183        + num_traits::Zero
184        + num_traits::Num
185        + std::ops::AddAssign
186        + Ord,
187{
188    let vertices = polygon.get_vertices();
189    let n = vertices.len();
190
191    if n < 2 {
192        return T::zero();
193    }
194
195    let mut perimeter = T::zero();
196
197    for i in 0..n {
198        let current = vertices[i];
199        let next = vertices[(i + 1) % n];
200
201        let dx = if current.xcoord > next.xcoord {
202            current.xcoord - next.xcoord
203        } else {
204            next.xcoord - current.xcoord
205        };
206
207        let dy = if current.ycoord > next.ycoord {
208            current.ycoord - next.ycoord
209        } else {
210            next.ycoord - current.ycoord
211        };
212
213        perimeter = perimeter + dx + dy;
214    }
215
216    perimeter
217}
218
219#[cfg(test)]
220mod tests {
221    use super::*;
222
223    #[test]
224    fn test_point_in_polygon() {
225        let points = vec![
226            crate::Point::new(0, 0),
227            crate::Point::new(10, 0),
228            crate::Point::new(10, 10),
229            crate::Point::new(0, 10),
230        ];
231        let polygon = Polygon::new(&points);
232
233        assert!(point_in_polygon(&crate::Point::new(5, 5), &polygon));
234        assert!(!point_in_polygon(&crate::Point::new(15, 15), &polygon));
235        assert!(!point_in_polygon(&crate::Point::new(-5, 5), &polygon));
236    }
237
238    #[test]
239    fn test_polygon_centroid() {
240        let points = vec![
241            crate::Point::new(0, 0),
242            crate::Point::new(3, 0),
243            crate::Point::new(3, 3),
244        ];
245        let polygon = Polygon::new(&points);
246
247        let centroid = polygon_centroid(&polygon);
248        assert_eq!(centroid.xcoord, 2);
249        assert_eq!(centroid.ycoord, 1);
250    }
251
252    #[test]
253    fn test_polygon_perimeter() {
254        let points = vec![
255            crate::Point::new(0, 0),
256            crate::Point::new(3, 0),
257            crate::Point::new(3, 4),
258            crate::Point::new(0, 4),
259        ];
260        let polygon = Polygon::new(&points);
261
262        let perimeter = polygon_perimeter(&polygon);
263        assert_eq!(perimeter, 14);
264    }
265
266    #[test]
267    fn test_point_in_polygon_less_than_3_points() {
268        let pts = vec![crate::Point::new(0, 0), crate::Point::new(1, 0)];
269        let polygon = Polygon::new(&pts);
270        assert!(!point_in_polygon(&crate::Point::new(0, 0), &polygon));
271    }
272
273    #[test]
274    fn test_polygon_centroid_empty() {
275        let polygon = Polygon::from_origin_and_vectors(crate::Point::new(0, 0), vec![]);
276        let result = polygon_centroid(&polygon);
277        assert_eq!(result, crate::Point::new(0, 0));
278    }
279
280    #[test]
281    fn test_polygon_perimeter_less_than_2_points() {
282        let polygon = Polygon::from_origin_and_vectors(crate::Point::new(0, 0), vec![]);
283        assert_eq!(polygon_perimeter(&polygon), 0);
284
285        let polygon2 = Polygon::new(&[crate::Point::new(0, 0)]);
286        assert_eq!(polygon_perimeter(&polygon2), 0);
287    }
288}