use super::{swap_remove_to_first, trivial_hull};
use crate::kernels::*;
use crate::{Coord, GeoNum, LineString};
pub fn graham_hull<T>(mut points: &mut [Coord<T>], include_on_hull: bool) -> LineString<T>
where
T: GeoNum,
{
if points.len() < 4 {
return trivial_hull(points, include_on_hull);
}
let mut output = Vec::with_capacity(points.len());
use crate::utils::least_index;
use std::cmp::Ordering;
let min_idx = least_index(points);
let head = swap_remove_to_first(&mut points, min_idx);
output.push(*head);
let cmp = |q: &Coord<T>, r: &Coord<T>| match T::Ker::orient2d(*q, *head, *r) {
Orientation::CounterClockwise => Ordering::Greater,
Orientation::Clockwise => Ordering::Less,
Orientation::Collinear => {
let dist1 = T::Ker::square_euclidean_distance(*head, *q);
let dist2 = T::Ker::square_euclidean_distance(*head, *r);
dist1.partial_cmp(&dist2).unwrap()
}
};
points.sort_unstable_by(cmp);
for pt in points.iter() {
while output.len() > 1 {
let len = output.len();
match T::Ker::orient2d(output[len - 2], output[len - 1], *pt) {
Orientation::CounterClockwise => {
break;
}
Orientation::Clockwise => {
output.pop();
}
Orientation::Collinear => {
if include_on_hull {
break;
} else {
output.pop();
}
}
}
}
if include_on_hull || pt != output.last().unwrap() {
output.push(*pt);
}
}
let mut output = LineString::new(output);
output.close();
output
}
#[cfg(test)]
mod test {
use super::*;
use crate::IsConvex;
fn test_convexity<T: GeoNum>(mut initial: Vec<Coord<T>>) {
let hull = graham_hull(&mut initial, false);
assert!(hull.is_strictly_ccw_convex());
let hull = graham_hull(&mut initial, true);
assert!(hull.is_ccw_convex());
}
#[test]
fn test_graham_hull_ccw() {
let initial = vec![
(1.0, 0.0),
(2.0, 1.0),
(1.75, 1.1),
(1.0, 2.0),
(0.0, 1.0),
(1.0, 0.0),
];
let initial = initial.iter().map(|e| Coord::from((e.0, e.1))).collect();
test_convexity(initial);
}
#[test]
fn graham_hull_test1() {
let v: Vec<_> = vec![(0, 0), (4, 0), (4, 1), (1, 1), (1, 4), (0, 4), (0, 0)];
let initial = v.iter().map(|e| Coord::from((e.0, e.1))).collect();
test_convexity(initial);
}
#[test]
fn graham_hull_test2() {
let v = vec![
(0, 10),
(1, 1),
(10, 0),
(1, -1),
(0, -10),
(-1, -1),
(-10, 0),
(-1, 1),
(0, 10),
];
let initial = v.iter().map(|e| Coord::from((e.0, e.1))).collect();
test_convexity(initial);
}
#[test]
fn graham_test_complex() {
test_convexity(geo_test_fixtures::poly1::<f64>().0);
}
#[test]
fn quick_hull_test_complex_2() {
test_convexity(geo_test_fixtures::poly2::<f64>().0);
}
}