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}