#[inline]
fn cross(o: &[f64; 2], a: &[f64; 2], b: &[f64; 2]) -> f64 {
(a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0])
}
pub fn convex_hull_2d(mut points: Vec<[f64; 2]>) -> Vec<[f64; 2]> {
let n = points.len();
if n < 3 {
return points;
}
points.sort_by(|a, b| {
a[0].partial_cmp(&b[0])
.unwrap_or(std::cmp::Ordering::Equal)
.then(a[1].partial_cmp(&b[1]).unwrap_or(std::cmp::Ordering::Equal))
});
let mut lower: Vec<[f64; 2]> = Vec::with_capacity(n);
for p in &points {
while lower.len() >= 2 && cross(&lower[lower.len() - 2], &lower[lower.len() - 1], p) <= 0.0 {
lower.pop();
}
lower.push(*p);
}
let mut upper: Vec<[f64; 2]> = Vec::with_capacity(n);
for p in points.iter().rev() {
while upper.len() >= 2 && cross(&upper[upper.len() - 2], &upper[upper.len() - 1], p) <= 0.0 {
upper.pop();
}
upper.push(*p);
}
lower.pop();
upper.pop();
lower.extend(upper);
lower
}
pub struct GrahamScan;
impl GrahamScan {
pub fn compute(points: &[[f64; 2]]) -> Vec<[f64; 2]> {
convex_hull_2d(points.to_vec())
}
}
pub struct JarvisMarch;
impl JarvisMarch {
pub fn compute(points: &[[f64; 2]]) -> Vec<[f64; 2]> {
let n = points.len();
if n < 3 {
return points.to_vec();
}
let start = points
.iter()
.enumerate()
.min_by(|(_, a), (_, b)| {
a[0].partial_cmp(&b[0])
.unwrap_or(std::cmp::Ordering::Equal)
.then(a[1].partial_cmp(&b[1]).unwrap_or(std::cmp::Ordering::Equal))
})
.map(|(i, _)| i)
.unwrap_or(0);
let mut hull: Vec<[f64; 2]> = Vec::new();
let mut current = start;
loop {
hull.push(points[current]);
let mut next = (current + 1) % n;
for i in 0..n {
let c = cross(&points[current], &points[next], &points[i]);
if c < 0.0 {
next = i;
} else if c == 0.0 {
let d_next = dist2(points[current], points[next]);
let d_i = dist2(points[current], points[i]);
if d_i > d_next {
next = i;
}
}
}
current = next;
if current == start {
break;
}
if hull.len() >= n {
break;
}
}
hull
}
}
#[inline]
fn dist2(a: [f64; 2], b: [f64; 2]) -> f64 {
(a[0] - b[0]).powi(2) + (a[1] - b[1]).powi(2)
}
pub fn point_set_diameter(points: &[[f64; 2]]) -> Option<(usize, usize, f64)> {
let n = points.len();
if n < 2 {
return None;
}
if n == 2 {
let d = ((points[0][0]-points[1][0]).powi(2) + (points[0][1]-points[1][1]).powi(2)).sqrt();
return Some((0, 1, d));
}
let hull = convex_hull_2d(points.to_vec());
let h = hull.len();
if h < 2 {
let d = ((points[0][0]-points[1][0]).powi(2) + (points[0][1]-points[1][1]).powi(2)).sqrt();
return Some((0, 1, d));
}
let mut max_d2 = 0.0_f64;
let mut pi = 0usize;
let mut pj = 0usize;
let mut j = 1usize;
for i in 0..h {
let ni = (i + 1) % h;
loop {
let nj = (j + 1) % h;
let ex = hull[ni][0] - hull[i][0];
let ey = hull[ni][1] - hull[i][1];
let cross_j = ex * (hull[j][1] - hull[i][1]) - ey * (hull[j][0] - hull[i][0]);
let cross_nj = ex * (hull[nj][1] - hull[i][1]) - ey * (hull[nj][0] - hull[i][0]);
if cross_nj > cross_j {
j = nj;
} else {
break;
}
}
let d2 = (hull[i][0]-hull[j][0]).powi(2) + (hull[i][1]-hull[j][1]).powi(2);
if d2 > max_d2 {
max_d2 = d2;
pi = find_idx(points, hull[i]);
pj = find_idx(points, hull[j]);
}
}
Some((pi, pj, max_d2.sqrt()))
}
fn find_idx(points: &[[f64; 2]], target: [f64; 2]) -> usize {
points.iter().enumerate()
.min_by(|(_, a), (_, b)| {
let da = (a[0]-target[0]).powi(2) + (a[1]-target[1]).powi(2);
let db = (b[0]-target[0]).powi(2) + (b[1]-target[1]).powi(2);
da.partial_cmp(&db).unwrap_or(std::cmp::Ordering::Equal)
})
.map(|(i, _)| i)
.unwrap_or(0)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_convex_hull_square() {
let pts = vec![
[0.0_f64, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0], [0.5, 0.5],
];
let hull = convex_hull_2d(pts);
assert_eq!(hull.len(), 4, "Square interior point should be excluded");
}
#[test]
fn test_convex_hull_triangle() {
let pts = vec![[0.0_f64, 0.0], [1.0, 0.0], [0.5, 1.0]];
let hull = convex_hull_2d(pts);
assert_eq!(hull.len(), 3);
}
#[test]
fn test_convex_hull_collinear() {
let pts: Vec<[f64; 2]> = (0..5).map(|i| [i as f64, 0.0]).collect();
let hull = convex_hull_2d(pts);
assert!(hull.len() <= 2, "Got {} hull points", hull.len());
}
#[test]
fn test_graham_scan_square() {
let pts = vec![
[0.0_f64, 0.0], [4.0, 0.0], [4.0, 3.0], [0.0, 3.0], [2.0, 1.5],
];
let hull = GrahamScan::compute(&pts);
assert_eq!(hull.len(), 4);
}
#[test]
fn test_jarvis_march_square() {
let pts = vec![
[0.0_f64, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0], [0.5, 0.5],
];
let hull = JarvisMarch::compute(&pts);
assert_eq!(hull.len(), 4);
}
#[test]
fn test_convex_hull_too_few_points() {
assert_eq!(convex_hull_2d(vec![]).len(), 0);
assert_eq!(convex_hull_2d(vec![[0.0, 0.0]]).len(), 1);
assert_eq!(convex_hull_2d(vec![[0.0, 0.0], [1.0, 1.0]]).len(), 2);
}
#[test]
fn test_point_set_diameter() {
let pts = vec![[0.0_f64, 0.0], [3.0, 0.0], [0.0, 4.0]];
let (_, _, d) = point_set_diameter(&pts).expect("Should return diameter");
assert!((d - 5.0).abs() < 1e-9, "Expected 5.0, got {}", d);
}
}