scirs2_spatial/convex_hull/geometry/
calculations_2d.rs

1//! 2D geometric calculations for convex hull operations
2//!
3//! This module provides utility functions for 2D geometric computations
4//! commonly used in convex hull algorithms.
5
6use crate::error::SpatialResult;
7use scirs2_core::ndarray::{Array2, ArrayView2};
8
9/// Compute cross product for three 2D points (returns z-component of 3D cross product)
10///
11/// # Arguments
12///
13/// * `p1` - First point [x, y]
14/// * `p2` - Second point [x, y]
15/// * `p3` - Third point [x, y]
16///
17/// # Returns
18///
19/// * Cross product value. Positive indicates counterclockwise turn,
20///   negative indicates clockwise turn, zero indicates collinear points.
21///
22/// # Examples
23///
24/// ```
25/// use scirs2_spatial::convex_hull::geometry::calculations_2d::cross_product_2d;
26///
27/// let p1 = [0.0, 0.0];
28/// let p2 = [1.0, 0.0];
29/// let p3 = [0.0, 1.0];
30///
31/// let cross = cross_product_2d(p1, p2, p3);
32/// assert!(cross > 0.0); // Counterclockwise turn
33/// ```
34pub fn cross_product_2d(p1: [f64; 2], p2: [f64; 2], p3: [f64; 2]) -> f64 {
35    (p2[0] - p1[0]) * (p3[1] - p1[1]) - (p2[1] - p1[1]) * (p3[0] - p1[0])
36}
37
38/// Compute squared distance between two 2D points
39///
40/// # Arguments
41///
42/// * `p1` - First point [x, y]
43/// * `p2` - Second point [x, y]
44///
45/// # Returns
46///
47/// * Squared Euclidean distance between the points
48///
49/// # Examples
50///
51/// ```
52/// use scirs2_spatial::convex_hull::geometry::calculations_2d::distance_squared_2d;
53///
54/// let p1 = [0.0, 0.0];
55/// let p2 = [3.0, 4.0];
56///
57/// let dist_sq = distance_squared_2d(p1, p2);
58/// assert_eq!(dist_sq, 25.0); // 3² + 4² = 25
59/// ```
60pub fn distance_squared_2d(p1: [f64; 2], p2: [f64; 2]) -> f64 {
61    (p2[0] - p1[0]).powi(2) + (p2[1] - p1[1]).powi(2)
62}
63
64/// Compute facet equations for a 2D convex hull
65///
66/// Each equation represents a line in the form: ax + by + c = 0
67/// where (a, b) is the normal vector and c is the offset.
68///
69/// # Arguments
70///
71/// * `points` - Input points array
72/// * `vertex_indices` - Indices of hull vertices in counterclockwise order
73///
74/// # Returns
75///
76/// * Array2 of shape (n_edges, 3) containing line equations [a, b, c]
77///
78/// # Examples
79///
80/// ```
81/// use scirs2_spatial::convex_hull::geometry::calculations_2d::compute_2d_hull_equations;
82/// use scirs2_core::ndarray::array;
83///
84/// let points = array![[0.0, 0.0], [1.0, 0.0], [0.0, 1.0]];
85/// let vertices = vec![0, 1, 2];
86///
87/// let equations = compute_2d_hull_equations(&points.view(), &vertices);
88/// assert_eq!(equations.nrows(), 3); // Three edges
89/// assert_eq!(equations.ncols(), 3); // [a, b, c] format
90/// ```
91pub fn compute_2d_hull_equations(
92    points: &ArrayView2<'_, f64>,
93    vertex_indices: &[usize],
94) -> Array2<f64> {
95    let n = vertex_indices.len();
96    let mut equations = Array2::zeros((n, 3)); // 2D equations: ax + by + c = 0
97
98    for i in 0..n {
99        let j = (i + 1) % n;
100        let p1 = [
101            points[[vertex_indices[i], 0]],
102            points[[vertex_indices[i], 1]],
103        ];
104        let p2 = [
105            points[[vertex_indices[j], 0]],
106            points[[vertex_indices[j], 1]],
107        ];
108
109        // Line equation: (y2-y1)x - (x2-x1)y + (x2-x1)y1 - (y2-y1)x1 = 0
110        let a = p2[1] - p1[1];
111        let b = p1[0] - p2[0];
112        let c = (p2[0] - p1[0]) * p1[1] - (p2[1] - p1[1]) * p1[0];
113
114        equations[[i, 0]] = a;
115        equations[[i, 1]] = b;
116        equations[[i, 2]] = c;
117    }
118
119    equations
120}
121
122/// Compute the area of a 2D polygon using the shoelace formula
123///
124/// # Arguments
125///
126/// * `points` - Input points array
127/// * `vertex_indices` - Indices of polygon vertices in order
128///
129/// # Returns
130///
131/// * Result containing the polygon area
132///
133/// # Examples
134///
135/// ```
136/// use scirs2_spatial::convex_hull::geometry::calculations_2d::compute_polygon_area;
137/// use scirs2_core::ndarray::array;
138///
139/// // Unit square
140/// let points = array![[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]];
141/// let vertices = vec![0, 1, 2, 3];
142///
143/// let area = compute_polygon_area(&points.view(), &vertices).expect("Operation failed");
144/// assert!((area - 1.0).abs() < 1e-10);
145/// ```
146pub fn compute_polygon_area(
147    points: &ArrayView2<'_, f64>,
148    vertex_indices: &[usize],
149) -> SpatialResult<f64> {
150    if vertex_indices.len() < 3 {
151        return Ok(0.0);
152    }
153
154    let mut area = 0.0;
155    let n = vertex_indices.len();
156
157    for i in 0..n {
158        let j = (i + 1) % n;
159        let xi = points[[vertex_indices[i], 0]];
160        let yi = points[[vertex_indices[i], 1]];
161        let xj = points[[vertex_indices[j], 0]];
162        let yj = points[[vertex_indices[j], 1]];
163
164        area += xi * yj - xj * yi;
165    }
166
167    Ok(area.abs() / 2.0)
168}
169
170/// Compute the perimeter of a 2D polygon
171///
172/// # Arguments
173///
174/// * `points` - Input points array
175/// * `vertex_indices` - Indices of polygon vertices in order
176///
177/// # Returns
178///
179/// * Result containing the polygon perimeter
180///
181/// # Examples
182///
183/// ```
184/// use scirs2_spatial::convex_hull::geometry::calculations_2d::compute_polygon_perimeter;
185/// use scirs2_core::ndarray::array;
186///
187/// // Unit square
188/// let points = array![[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]];
189/// let vertices = vec![0, 1, 2, 3];
190///
191/// let perimeter = compute_polygon_perimeter(&points.view(), &vertices).expect("Operation failed");
192/// assert!((perimeter - 4.0).abs() < 1e-10);
193/// ```
194pub fn compute_polygon_perimeter(
195    points: &ArrayView2<'_, f64>,
196    vertex_indices: &[usize],
197) -> SpatialResult<f64> {
198    if vertex_indices.len() < 2 {
199        return Ok(0.0);
200    }
201
202    let mut perimeter = 0.0;
203    let n = vertex_indices.len();
204
205    for i in 0..n {
206        let j = (i + 1) % n;
207        let xi = points[[vertex_indices[i], 0]];
208        let yi = points[[vertex_indices[i], 1]];
209        let xj = points[[vertex_indices[j], 0]];
210        let yj = points[[vertex_indices[j], 1]];
211
212        let dx = xj - xi;
213        let dy = yj - yi;
214        perimeter += (dx * dx + dy * dy).sqrt();
215    }
216
217    Ok(perimeter)
218}
219
220/// Check if three points are ordered counterclockwise
221///
222/// # Arguments
223///
224/// * `p1` - First point [x, y]
225/// * `p2` - Second point [x, y]
226/// * `p3` - Third point [x, y]
227///
228/// # Returns
229///
230/// * true if points are in counterclockwise order, false otherwise
231///
232/// # Examples
233///
234/// ```
235/// use scirs2_spatial::convex_hull::geometry::calculations_2d::is_counterclockwise;
236///
237/// let p1 = [0.0, 0.0];
238/// let p2 = [1.0, 0.0];
239/// let p3 = [0.0, 1.0];
240///
241/// assert!(is_counterclockwise(p1, p2, p3));
242/// assert!(!is_counterclockwise(p1, p3, p2));
243/// ```
244pub fn is_counterclockwise(p1: [f64; 2], p2: [f64; 2], p3: [f64; 2]) -> bool {
245    cross_product_2d(p1, p2, p3) > 0.0
246}
247
248/// Calculate polar angle from a reference point
249///
250/// # Arguments
251///
252/// * `reference` - Reference point [x, y]
253/// * `point` - Target point [x, y]
254///
255/// # Returns
256///
257/// * Polar angle in radians
258///
259/// # Examples
260///
261/// ```
262/// use scirs2_spatial::convex_hull::geometry::calculations_2d::polar_angle;
263///
264/// let origin = [0.0, 0.0];
265/// let point = [1.0, 1.0];
266///
267/// let angle = polar_angle(origin, point);
268/// assert!((angle - std::f64::consts::PI / 4.0).abs() < 1e-10);
269/// ```
270pub fn polar_angle(reference: [f64; 2], point: [f64; 2]) -> f64 {
271    (point[1] - reference[1]).atan2(point[0] - reference[0])
272}
273
274#[cfg(test)]
275mod tests {
276    use super::*;
277    use scirs2_core::ndarray::arr2;
278
279    #[test]
280    fn test_cross_product_2d() {
281        let p1 = [0.0, 0.0];
282        let p2 = [1.0, 0.0];
283        let p3 = [0.0, 1.0];
284
285        let cross = cross_product_2d(p1, p2, p3);
286        assert!(cross > 0.0); // Counterclockwise
287
288        let cross_cw = cross_product_2d(p1, p3, p2);
289        assert!(cross_cw < 0.0); // Clockwise
290    }
291
292    #[test]
293    fn test_distance_squared_2d() {
294        let p1 = [0.0, 0.0];
295        let p2 = [3.0, 4.0];
296
297        let dist_sq = distance_squared_2d(p1, p2);
298        assert_eq!(dist_sq, 25.0);
299    }
300
301    #[test]
302    fn test_polygon_area() {
303        // Unit square
304        let points = arr2(&[[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]]);
305        let vertices = vec![0, 1, 2, 3];
306
307        let area = compute_polygon_area(&points.view(), &vertices).expect("Operation failed");
308        assert!((area - 1.0).abs() < 1e-10);
309    }
310
311    #[test]
312    fn test_polygon_perimeter() {
313        // Unit square
314        let points = arr2(&[[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]]);
315        let vertices = vec![0, 1, 2, 3];
316
317        let perimeter =
318            compute_polygon_perimeter(&points.view(), &vertices).expect("Operation failed");
319        assert!((perimeter - 4.0).abs() < 1e-10);
320    }
321
322    #[test]
323    fn test_counterclockwise() {
324        let p1 = [0.0, 0.0];
325        let p2 = [1.0, 0.0];
326        let p3 = [0.0, 1.0];
327
328        assert!(is_counterclockwise(p1, p2, p3));
329        assert!(!is_counterclockwise(p1, p3, p2));
330    }
331
332    #[test]
333    fn test_polar_angle() {
334        let origin = [0.0, 0.0];
335        let point = [1.0, 1.0];
336
337        let angle = polar_angle(origin, point);
338        assert!((angle - std::f64::consts::PI / 4.0).abs() < 1e-10);
339    }
340}