use crate::data::{Point, Polygon, PolygonConvex};
use crate::{Error, Orientation, PolygonScalar, TotalOrd};
pub fn convex_hull<T>(mut pts: Vec<Point<T>>) -> Result<PolygonConvex<T>, Error>
where
T: PolygonScalar,
{
let smallest = &smallest_point(&pts)?;
pts.sort_unstable_by(|a, b| {
smallest
.ccw_cmp_around(a, b)
.then_with(|| (a.y_coord(), a.x_coord()).total_cmp(&(b.y_coord(), b.x_coord())))
});
if pts.len() < 3 {
return Err(Error::InsufficientVertices);
}
debug_assert_eq!(&pts[0], smallest);
let mut write_idx = 1;
let mut read_idx = 2;
{
let origin = pts[write_idx - 1].clone();
while read_idx < pts.len() {
if Point::orient(&origin, &pts[write_idx], &pts[read_idx]) == Orientation::CoLinear {
pts.swap(read_idx, write_idx);
read_idx += 1;
} else {
break;
}
}
}
while read_idx < pts.len() {
let p1 = &pts[read_idx];
let p2 = &pts[write_idx];
let p3 = &pts[write_idx - 1];
match Point::orient(p3, p2, p1) {
Orientation::CounterClockWise => {
pts.swap(read_idx, write_idx + 1);
read_idx += 1;
write_idx += 1;
}
Orientation::ClockWise | Orientation::CoLinear => {
write_idx -= 1;
}
}
}
pts.truncate(write_idx + 1);
if pts.len() < 3 {
return Err(Error::InsufficientVertices);
}
Ok(PolygonConvex::new_unchecked(Polygon::new_unchecked(pts)))
}
fn smallest_point<T>(pts: &[Point<T>]) -> Result<Point<T>, Error>
where
T: PolygonScalar,
{
Ok(
pts
.iter()
.min_by(|a, b| TotalOrd::total_cmp(&(a.y_coord(), a.x_coord()), &(b.y_coord(), b.x_coord())))
.ok_or(Error::InsufficientVertices)?
.clone(),
)
}
#[cfg(test)]
#[cfg(not(tarpaulin_include))]
mod tests {
use super::*;
use crate::data::PointLocation;
use crate::testing::*;
use claims::assert_ok;
use num_bigint::BigInt;
use proptest::collection::*;
use proptest::prelude::*;
use test_strategy::proptest;
#[test]
fn convex_hull_colinear() {
let points = vec![
Point::new([0, 0]),
Point::new([1, 0]),
Point::new([2, 0]),
Point::new([3, 0]),
Point::new([4, 0]),
Point::new([1, 1]),
];
let poly = convex_hull(points).unwrap();
assert_ok!(poly.validate());
}
#[test]
fn convex_hull_colinear_rev() {
let points = vec![
Point::new([0, 0]),
Point::new([1, 0]),
Point::new([0, 9]),
Point::new([0, 8]),
Point::new([0, 7]),
Point::new([0, 6]),
];
let poly = convex_hull(points).unwrap();
assert_ok!(poly.validate());
}
#[test]
fn convex_hull_dups() {
let points = vec![
Point::new([0, 0]),
Point::new([1, 0]),
Point::new([0, 0]),
Point::new([1, 0]),
Point::new([2, 2]),
Point::new([2, 2]),
Point::new([5, 1]),
Point::new([5, 1]),
];
let poly = convex_hull(points).unwrap();
assert_ok!(poly.validate());
}
#[test]
fn convex_hull_insufficient_dups() {
let points = vec![
Point::new([0, 0]),
Point::new([0, 0]),
Point::new([2, 2]),
Point::new([2, 2]),
Point::new([0, 0]),
Point::new([2, 2]),
];
assert_eq!(convex_hull(points).err(), Some(Error::InsufficientVertices));
}
#[test]
fn convex_hull_invalid() {
let points: Vec<Point<i64>> = vec![
Point { array: [0, 0] },
Point { array: [100, 0] },
Point { array: [50, 1] },
Point { array: [40, 1] },
Point { array: [0, 100] },
];
let points: Vec<Point<BigInt, 2>> = points.into_iter().map(|pt| pt.cast()).collect();
let poly = convex_hull(points).unwrap();
assert_ok!(poly.validate());
}
#[test]
fn unit_1() {
let points: Vec<Point<BigInt>> = vec![
Point::new([0, 0]).into(),
Point::new([-1, 1]).into(),
Point::new([0, 1]).into(),
Point::new([-717193444810564826, 1]).into(),
];
let poly = convex_hull(points).unwrap();
assert_ok!(poly.validate());
}
#[test]
fn unit_2() {
let points: Vec<Point<i8>> = vec![
Point::new([0, 0]),
Point::new([0, -10]),
Point::new([-13, 0]),
];
let poly = convex_hull(points).unwrap();
assert_ok!(poly.validate());
}
#[proptest]
fn convex_hull_prop(#[strategy(vec(any_r(), 0..100))] pts: Vec<Point<BigInt>>) {
if let Ok(poly) = convex_hull(pts.clone()) {
prop_assert_eq!(poly.validate().err(), None);
for pt in pts.iter() {
prop_assert_ne!(poly.locate(pt), PointLocation::Outside)
}
for pt in poly.iter() {
prop_assert!(pts.contains(pt))
}
}
}
#[proptest]
fn convex_hull_prop_i8(#[strategy(vec(any::<Point<i8>>(), 0..100))] pts: Vec<Point<i8>>) {
if let Ok(poly) = convex_hull(pts.clone()) {
prop_assert_eq!(poly.validate().err(), None);
for pt in pts.iter() {
prop_assert_ne!(poly.locate(pt), PointLocation::Outside)
}
for pt in poly.iter() {
prop_assert!(pts.contains(pt))
}
}
}
}