use crate::convex_hull::core::ConvexHull;
use crate::convex_hull::geometry::calculations_2d::{
compute_2d_hull_equations, cross_product_2d, distance_squared_2d,
};
use crate::error::{SpatialError, SpatialResult};
use scirs2_core::ndarray::ArrayView2;
pub fn compute_jarvis_march(points: &ArrayView2<'_, f64>) -> SpatialResult<ConvexHull> {
let npoints = points.nrows();
if points.ncols() != 2 {
return Err(SpatialError::ValueError(
"Jarvis march algorithm only supports 2D points".to_string(),
));
}
if npoints < 3 {
return Err(SpatialError::ValueError(
"Need at least 3 points for 2D convex hull".to_string(),
));
}
let mut leftmost = 0;
for i in 1..npoints {
if points[[i, 0]] < points[[leftmost, 0]] {
leftmost = i;
}
}
let mut hull_vertices = Vec::new();
let mut current = leftmost;
loop {
hull_vertices.push(current);
let mut next = (current + 1) % npoints;
for i in 0..npoints {
if i == current {
continue;
}
let p1 = [points[[current, 0]], points[[current, 1]]];
let p2 = [points[[next, 0]], points[[next, 1]]];
let p3 = [points[[i, 0]], points[[i, 1]]];
let cross = cross_product_2d(p1, p2, p3);
if cross > 0.0
|| (cross == 0.0 && distance_squared_2d(p1, p3) > distance_squared_2d(p1, p2))
{
next = i;
}
}
current = next;
if current == leftmost {
break; }
}
let vertex_indices = hull_vertices;
let n = vertex_indices.len();
let mut simplices = Vec::new();
for i in 0..n {
let j = (i + 1) % n;
simplices.push(vec![vertex_indices[i], vertex_indices[j]]);
}
let equations = compute_2d_hull_equations(points, &vertex_indices);
Ok(ConvexHull {
points: points.to_owned(),
vertex_indices,
simplices,
equations: Some(equations),
})
}
pub fn find_leftmost_point(points: &ArrayView2<'_, f64>) -> usize {
let npoints = points.nrows();
let mut leftmost = 0;
for i in 1..npoints {
if points[[i, 0]] < points[[leftmost, 0]] {
leftmost = i;
}
}
leftmost
}
pub fn find_most_counterclockwise(
points: &ArrayView2<'_, f64>,
current: usize,
candidate: usize,
) -> usize {
let npoints = points.nrows();
let mut best = candidate;
for i in 0..npoints {
if i == current {
continue;
}
let p1 = [points[[current, 0]], points[[current, 1]]];
let p2 = [points[[best, 0]], points[[best, 1]]];
let p3 = [points[[i, 0]], points[[i, 1]]];
let cross = cross_product_2d(p1, p2, p3);
if cross > 0.0
|| (cross == 0.0 && distance_squared_2d(p1, p3) > distance_squared_2d(p1, p2))
{
best = i;
}
}
best
}
pub fn is_more_counterclockwise(
reference: [f64; 2],
current_best: [f64; 2],
candidate: [f64; 2],
) -> bool {
let cross = cross_product_2d(reference, current_best, candidate);
if cross > 0.0 {
true } else if cross == 0.0 {
distance_squared_2d(reference, candidate) > distance_squared_2d(reference, current_best)
} else {
false
}
}
pub fn jarvis_step(points: &ArrayView2<'_, f64>, current: usize) -> usize {
let npoints = points.nrows();
let mut next = if current == 0 { 1 } else { 0 };
for i in 0..npoints {
if i == current {
continue;
}
let p_current = [points[[current, 0]], points[[current, 1]]];
let p_next = [points[[next, 0]], points[[next, 1]]];
let p_candidate = [points[[i, 0]], points[[i, 1]]];
if is_more_counterclockwise(p_current, p_next, p_candidate) {
next = i;
}
}
next
}
#[cfg(test)]
mod tests {
use super::*;
use scirs2_core::ndarray::arr2;
#[test]
fn test_jarvis_march_basic() {
let points = arr2(&[
[0.0, 0.0],
[1.0, 0.0],
[0.0, 1.0],
[0.5, 0.5], ]);
let hull = compute_jarvis_march(&points.view()).expect("Operation failed");
assert_eq!(hull.ndim(), 2);
assert_eq!(hull.vertex_indices().len(), 3);
assert!(!hull.vertex_indices().contains(&3));
}
#[test]
fn test_jarvis_march_square() {
let points = arr2(&[[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]]);
let hull = compute_jarvis_march(&points.view()).expect("Operation failed");
assert_eq!(hull.ndim(), 2);
assert_eq!(hull.vertex_indices().len(), 4); }
#[test]
fn test_find_leftmost_point() {
let points = arr2(&[[1.0, 0.0], [0.0, 1.0], [2.0, 0.0], [0.0, 0.0]]);
let leftmost = find_leftmost_point(&points.view());
assert!(leftmost == 1 || leftmost == 3);
}
#[test]
fn test_find_most_counterclockwise() {
let points = arr2(&[[0.0, 0.0], [1.0, 0.0], [0.0, 1.0], [1.0, 1.0]]);
let current = 0; let candidate = 1;
let most_ccw = find_most_counterclockwise(&points.view(), current, candidate);
assert_eq!(most_ccw, 2); }
#[test]
fn test_is_more_counterclockwise() {
let reference = [0.0, 0.0];
let current_best = [1.0, 0.0];
let candidate = [0.0, 1.0];
assert!(is_more_counterclockwise(reference, current_best, candidate));
assert!(!is_more_counterclockwise(
reference,
candidate,
current_best
));
}
#[test]
fn test_jarvis_step() {
let points = arr2(&[[0.0, 0.0], [1.0, 0.0], [0.0, 1.0]]);
let current = 0;
let next = jarvis_step(&points.view(), current);
assert!(next == 1 || next == 2);
}
#[test]
fn test_error_cases() {
let too_few = arr2(&[[0.0, 0.0], [1.0, 0.0]]);
let result = compute_jarvis_march(&too_few.view());
assert!(result.is_err());
let points_3d = arr2(&[[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]);
let result = compute_jarvis_march(&points_3d.view());
assert!(result.is_err());
}
#[test]
fn test_collinear_points() {
let points = arr2(&[
[0.0, 0.0],
[1.0, 0.0],
[2.0, 0.0],
[0.5, 1.0], ]);
let hull = compute_jarvis_march(&points.view()).expect("Operation failed");
assert_eq!(hull.vertex_indices().len(), 3);
assert!(hull.vertex_indices().contains(&3));
}
#[test]
fn test_identical_points() {
let points = arr2(&[
[0.0, 0.0],
[1.0, 0.0],
[0.0, 1.0],
[0.0, 0.0], ]);
let hull = compute_jarvis_march(&points.view()).expect("Operation failed");
assert_eq!(hull.vertex_indices().len(), 3);
}
}