#![allow(dead_code)]
use crate::constants::COLLINEARITY_TOLERANCE;
use crate::prelude::*;
#[must_use]
fn akl_toussaint_filter(points: &Pointline) -> Pointline {
if points.len() < 5 {
return points.to_vec();
}
let mut min_x_idx = 0;
let mut max_x_idx = 0;
let mut min_y_idx = 0;
let mut max_y_idx = 0;
for (i, p) in points.iter().enumerate() {
if p.x < points[min_x_idx].x
|| (p.x == points[min_x_idx].x && p.y < points[min_x_idx].y)
{
min_x_idx = i;
}
if p.x > points[max_x_idx].x
|| (p.x == points[max_x_idx].x && p.y > points[max_x_idx].y)
{
max_x_idx = i;
}
if p.y < points[min_y_idx].y
|| (p.y == points[min_y_idx].y && p.x < points[min_y_idx].x)
{
min_y_idx = i;
}
if p.y > points[max_y_idx].y
|| (p.y == points[max_y_idx].y && p.x > points[max_y_idx].x)
{
max_y_idx = i;
}
}
let quad = [
points[min_x_idx], points[max_y_idx], points[max_x_idx], points[min_y_idx], ];
let mut filtered = Vec::with_capacity(points.len());
for p in points.iter() {
if !is_strictly_inside_quadrilateral(p, &quad) {
filtered.push(*p);
}
}
if filtered.is_empty() {
points.to_vec()
} else {
filtered
}
}
fn is_strictly_inside_quadrilateral(point: &Point, quad: &[Point]) -> bool {
if quad.len() != 4 {
return false;
}
let p0 = quad[0];
let p1 = quad[1];
let v0x = p1.x - p0.x;
let v0y = p1.y - p0.y;
let v0px = point.x - p0.x;
let v0py = point.y - p0.y;
let first_cross = v0x.mul_add(v0py, -(v0y * v0px));
if first_cross.abs() <= COLLINEARITY_TOLERANCE {
return false; }
let first_sign = first_cross > 0.0;
for i in 1..4 {
let p_curr = quad[i];
let p_next = quad[(i + 1) % 4];
let vx = p_next.x - p_curr.x;
let vy = p_next.y - p_curr.y;
let vpx = point.x - p_curr.x;
let vpy = point.y - p_curr.y;
let cross = vx.mul_add(vpy, -(vy * vpx));
if cross.abs() <= COLLINEARITY_TOLERANCE {
return false;
}
if (cross > 0.0) != first_sign {
return false;
}
}
true
}
#[must_use]
pub fn points_convex_hull(points: &Pointline) -> Pointline {
let mut valid_points = Vec::with_capacity(points.len());
for p in points.iter() {
if p.x.is_finite() && p.y.is_finite() {
valid_points.push(*p);
}
}
if valid_points.is_empty() {
return Vec::new();
}
if valid_points.len() < 3 {
return valid_points;
}
let candidates = akl_toussaint_filter(&valid_points);
let mut hull = Vec::with_capacity((candidates.len() as f64).sqrt().ceil() as usize);
let start = candidates
.iter()
.enumerate()
.min_by(|(_, a), (_, b)| {
match a.x.partial_cmp(&b.x) {
Some(std::cmp::Ordering::Equal) => a.y.partial_cmp(&b.y).unwrap_or(std::cmp::Ordering::Equal),
Some(other) => other,
None => unreachable!(), }
})
.map_or(0, |(idx, _)| idx);
let mut current = start;
loop {
hull.push(candidates[current]);
let mut next = (current + 1) % candidates.len();
for i in 0..candidates.len() {
if i == current {
continue;
}
let dx_next = candidates[next].x - candidates[current].x;
let dy_next = candidates[next].y - candidates[current].y;
let dx_i = candidates[i].x - candidates[current].x;
let dy_i = candidates[i].y - candidates[current].y;
let cross = dx_next.mul_add(dy_i, -(dy_next * dx_i));
if cross < 0.0
|| (cross.abs() < COLLINEARITY_TOLERANCE
&& {
let dist_i_sq = {
let dx = candidates[i].x - candidates[current].x;
let dy = candidates[i].y - candidates[current].y;
dx * dx + dy * dy
};
let dist_next_sq = {
let dx = candidates[next].x - candidates[current].x;
let dy = candidates[next].y - candidates[current].y;
dx * dx + dy * dy
};
dist_i_sq > dist_next_sq
})
{
next = i;
}
}
current = next;
if hull.len() > candidates.len() {
break;
}
if current == start {
break;
}
}
hull
}
#[cfg(test)]
mod test_akl_toussaint_filter {
use super::*;
#[test]
fn test_akl_toussaint_empty() {
let points: Pointline = vec![];
let filtered = akl_toussaint_filter(&points);
assert!(filtered.is_empty());
}
#[test]
fn test_akl_toussaint_single_point() {
let points = vec![point(0.0, 0.0)];
let filtered = akl_toussaint_filter(&points);
assert_eq!(filtered.len(), 1);
}
#[test]
fn test_akl_toussaint_two_points() {
let points = vec![point(0.0, 0.0), point(1.0, 1.0)];
let filtered = akl_toussaint_filter(&points);
assert_eq!(filtered.len(), 2);
}
#[test]
fn test_akl_toussaint_three_points() {
let points = vec![point(0.0, 0.0), point(1.0, 0.0), point(0.5, 1.0)];
let filtered = akl_toussaint_filter(&points);
assert_eq!(filtered.len(), 3);
}
#[test]
fn test_akl_toussaint_four_points_square() {
let points = vec![
point(0.0, 0.0),
point(1.0, 0.0),
point(1.0, 1.0),
point(0.0, 1.0),
];
let filtered = akl_toussaint_filter(&points);
assert_eq!(filtered.len(), 4);
}
#[test]
fn test_akl_toussaint_removes_interior_point() {
let points = vec![
point(0.0, 0.0),
point(2.0, 0.0),
point(2.0, 2.0),
point(0.0, 2.0),
point(1.0, 1.0), ];
let filtered = akl_toussaint_filter(&points);
assert!(filtered.len() <= points.len());
}
#[test]
fn test_akl_toussaint_removes_multiple_interior_points() {
let points = vec![
point(0.0, 0.0),
point(100.0, 0.0),
point(100.0, 100.0),
point(0.0, 100.0),
point(25.0, 25.0),
point(50.0, 50.0),
point(75.0, 75.0),
];
let filtered = akl_toussaint_filter(&points);
assert!(filtered.contains(&point(0.0, 0.0)));
assert!(filtered.contains(&point(100.0, 0.0)));
assert!(filtered.contains(&point(100.0, 100.0)));
assert!(filtered.contains(&point(0.0, 100.0)));
}
#[test]
fn test_akl_toussaint_keeps_boundary_points() {
let points = vec![
point(0.0, 0.0), point(1.0, 0.0), point(1.0, 1.0), point(0.0, 1.0), point(0.5, 0.0), point(1.0, 0.5), point(0.5, 1.0), point(0.0, 0.5), ];
let filtered = akl_toussaint_filter(&points);
assert_eq!(filtered.len(), 8);
}
#[test]
fn test_akl_toussaint_large_interior_region() {
let points = vec![
point(0.0, 0.0),
point(1000.0, 0.0),
point(1000.0, 500.0),
point(0.0, 500.0),
point(100.0, 100.0),
point(200.0, 100.0),
point(300.0, 100.0),
point(100.0, 200.0),
point(200.0, 200.0),
point(300.0, 200.0),
point(100.0, 300.0),
point(200.0, 300.0),
point(300.0, 300.0),
];
let filtered = akl_toussaint_filter(&points);
assert!(filtered.contains(&point(0.0, 0.0)));
assert!(filtered.contains(&point(1000.0, 0.0)));
assert!(filtered.contains(&point(1000.0, 500.0)));
assert!(filtered.contains(&point(0.0, 500.0)));
}
#[test]
fn test_akl_toussaint_large_coordinates() {
let points = vec![
point(0.0, 0.0),
point(1000.0, 0.0),
point(1000.0, 1000.0),
point(0.0, 1000.0),
point(500.0, 500.0),
];
let filtered = akl_toussaint_filter(&points);
assert!(filtered.contains(&point(0.0, 0.0)));
assert!(filtered.contains(&point(1000.0, 0.0)));
assert!(filtered.contains(&point(1000.0, 1000.0)));
assert!(filtered.contains(&point(0.0, 1000.0)));
}
#[test]
fn test_akl_toussaint_negative_coordinates() {
let points = vec![
point(-100.0, -100.0),
point(100.0, -100.0),
point(100.0, 100.0),
point(-100.0, 100.0),
point(0.0, 0.0),
];
let filtered = akl_toussaint_filter(&points);
assert!(filtered.contains(&point(-100.0, -100.0)));
assert!(filtered.contains(&point(100.0, -100.0)));
assert!(filtered.contains(&point(100.0, 100.0)));
assert!(filtered.contains(&point(-100.0, 100.0)));
}
#[test]
fn test_akl_toussaint_all_collinear_horizontal() {
let points = vec![
point(0.0, 0.0),
point(1.0, 0.0),
point(2.0, 0.0),
point(3.0, 0.0),
];
let filtered = akl_toussaint_filter(&points);
assert!(!filtered.is_empty());
}
#[test]
fn test_akl_toussaint_triangle() {
let points = vec![point(0.0, 0.0), point(2.0, 0.0), point(1.0, 2.0)];
let filtered = akl_toussaint_filter(&points);
assert_eq!(filtered.len(), 3);
}
#[test]
fn test_akl_toussaint_diamond_simple() {
let points = vec![
point(1.0, 0.0), point(0.0, 1.0), point(-1.0, 0.0), point(0.0, -1.0), point(0.0, 0.0), ];
let filtered = akl_toussaint_filter(&points);
assert_eq!(filtered.len(), 4);
assert!(!filtered.contains(&point(0.0, 0.0)));
}
}
#[cfg(test)]
mod test_points_convex_hull {
use super::*;
#[test]
fn test_points_convex_hull_empty() {
let points: Pointline = vec![];
let hull = points_convex_hull(&points);
assert!(hull.is_empty());
}
#[test]
fn test_points_convex_hull_single_point() {
let points = vec![point(1.0, 2.0)];
let hull = points_convex_hull(&points);
assert_eq!(hull.len(), 1);
assert_eq!(hull[0], point(1.0, 2.0));
}
#[test]
fn test_points_convex_hull_two_points() {
let points = vec![point(0.0, 0.0), point(1.0, 1.0)];
let hull = points_convex_hull(&points);
assert_eq!(hull.len(), 2);
assert!(hull.contains(&point(0.0, 0.0)));
assert!(hull.contains(&point(1.0, 1.0)));
}
#[test]
fn test_points_convex_hull_triangle() {
let points = vec![point(0.0, 0.0), point(2.0, 0.0), point(1.0, 2.0)];
let hull = points_convex_hull(&points);
assert_eq!(hull.len(), 3);
assert!(hull.contains(&point(0.0, 0.0)));
assert!(hull.contains(&point(2.0, 0.0)));
assert!(hull.contains(&point(1.0, 2.0)));
}
#[test]
fn test_points_convex_hull_square() {
let points = vec![
point(0.0, 0.0),
point(1.0, 0.0),
point(1.0, 1.0),
point(0.0, 1.0),
];
let hull = points_convex_hull(&points);
assert_eq!(hull.len(), 4);
assert!(hull.contains(&point(0.0, 0.0)));
assert!(hull.contains(&point(1.0, 0.0)));
assert!(hull.contains(&point(1.0, 1.0)));
assert!(hull.contains(&point(0.0, 1.0)));
}
#[test]
fn test_points_convex_hull_square_with_interior_point() {
let points = vec![
point(0.0, 0.0),
point(1.0, 0.0),
point(1.0, 1.0),
point(0.0, 1.0),
point(0.5, 0.5), ];
let hull = points_convex_hull(&points);
assert_eq!(hull.len(), 4);
assert!(!hull.contains(&point(0.5, 0.5)));
assert!(hull.contains(&point(0.0, 0.0)));
assert!(hull.contains(&point(1.0, 0.0)));
assert!(hull.contains(&point(1.0, 1.0)));
assert!(hull.contains(&point(0.0, 1.0)));
}
#[test]
fn test_points_convex_hull_collinear_points() {
let points = vec![
point(0.0, 0.0),
point(1.0, 0.0),
point(2.0, 0.0),
point(3.0, 0.0),
];
let hull = points_convex_hull(&points);
assert!(hull.len() >= 2);
assert!(hull.contains(&point(0.0, 0.0)));
assert!(hull.contains(&point(3.0, 0.0)));
}
#[test]
fn test_points_convex_hull_pentagon() {
let points = vec![
point(0.0, 1.0),
point(1.0, 0.0),
point(2.0, 1.0),
point(1.5, 2.5),
point(0.5, 2.5),
];
let hull = points_convex_hull(&points);
assert_eq!(hull.len(), 5);
for point in &points {
assert!(hull.contains(point));
}
}
#[test]
fn test_points_convex_hull_random_points() {
let points = vec![
point(0.0, 0.0),
point(4.0, 0.0),
point(4.0, 3.0),
point(0.0, 3.0),
point(1.0, 1.0), point(2.0, 1.5), point(3.0, 2.0), ];
let hull = points_convex_hull(&points);
assert_eq!(hull.len(), 4);
assert!(hull.contains(&point(0.0, 0.0)));
assert!(hull.contains(&point(4.0, 0.0)));
assert!(hull.contains(&point(4.0, 3.0)));
assert!(hull.contains(&point(0.0, 3.0)));
assert!(!hull.contains(&point(1.0, 1.0)));
assert!(!hull.contains(&point(2.0, 1.5)));
assert!(!hull.contains(&point(3.0, 2.0)));
}
#[test]
fn test_points_convex_hull_duplicate_points() {
let points = vec![
point(0.0, 0.0),
point(1.0, 0.0),
point(1.0, 1.0),
point(0.0, 1.0),
point(0.0, 0.0), point(1.0, 1.0), ];
let hull = points_convex_hull(&points);
assert!(hull.contains(&point(0.0, 0.0)));
assert!(hull.contains(&point(1.0, 0.0)));
assert!(hull.contains(&point(1.0, 1.0)));
assert!(hull.contains(&point(0.0, 1.0)));
}
#[test]
fn test_points_convex_hull_negative_coordinates() {
let points = vec![
point(-2.0, -2.0),
point(2.0, -2.0),
point(2.0, 2.0),
point(-2.0, 2.0),
point(0.0, 0.0), ];
let hull = points_convex_hull(&points);
assert_eq!(hull.len(), 4);
assert!(hull.contains(&point(-2.0, -2.0)));
assert!(hull.contains(&point(2.0, -2.0)));
assert!(hull.contains(&point(2.0, 2.0)));
assert!(hull.contains(&point(-2.0, 2.0)));
assert!(!hull.contains(&point(0.0, 0.0)));
}
#[test]
fn test_points_convex_hull_star_shape() {
let points = vec![
point(0.0, 3.0), point(1.0, 1.0), point(3.0, 0.0), point(1.0, -1.0), point(0.0, -3.0), point(-1.0, -1.0), point(-3.0, 0.0), point(-1.0, 1.0), ];
let hull = points_convex_hull(&points);
assert_eq!(hull.len(), 4);
assert!(hull.contains(&point(0.0, 3.0)));
assert!(hull.contains(&point(3.0, 0.0)));
assert!(hull.contains(&point(0.0, -3.0)));
assert!(hull.contains(&point(-3.0, 0.0)));
}
#[test]
fn test_points_convex_hull_large_coordinates() {
let points = vec![
point(1e6, 1e6),
point(1e6 + 100.0, 1e6),
point(1e6 + 100.0, 1e6 + 100.0),
point(1e6, 1e6 + 100.0),
point(1e6 + 50.0, 1e6 + 50.0), ];
let hull = points_convex_hull(&points);
assert_eq!(hull.len(), 4);
assert!(!hull.contains(&point(1e6 + 50.0, 1e6 + 50.0)));
}
#[test]
fn test_points_convex_hull_counter_clockwise_order() {
let points = vec![
point(0.0, 0.0),
point(2.0, 0.0),
point(2.0, 2.0),
point(0.0, 2.0),
];
let hull = points_convex_hull(&points);
assert_eq!(hull.len(), 4);
for i in 0..hull.len() {
let p1 = hull[i];
let p2 = hull[(i + 1) % hull.len()];
let p3 = hull[(i + 2) % hull.len()];
let cross = (p2 - p1).perp(p3 - p2);
assert!(cross >= 0.0, "Hull is not in counter-clockwise order");
}
}
}
#[cfg(test)]
mod test_pointline_convex_hull {
use super::*;
#[test]
fn test_pointline_convex_hull_empty() {
let points: Pointline = vec![];
let hull = pointline_convex_hull(&points);
assert!(hull.is_empty());
}
#[test]
fn test_pointline_convex_hull_single_vertex() {
let points = vec![point(1.0, 2.0)];
let hull = pointline_convex_hull(&points);
assert_eq!(hull.len(), 1);
assert_eq!(hull[0], point(1.0, 2.0));
}
#[test]
fn test_pointline_convex_hull_two_vertices() {
let points = vec![
point(0.0, 0.0),
point(1.0, 1.0),
];
let hull = pointline_convex_hull(&points);
assert_eq!(hull.len(), 2);
assert_eq!(hull[0], point(0.0, 0.0));
assert_eq!(hull[1], point(1.0, 1.0));
}
#[test]
fn test_pointline_convex_hull_triangle() {
let points = vec![
point(0.0, 0.0),
point(2.0, 0.0),
point(1.0, 2.0),
];
let hull = pointline_convex_hull(&points);
assert_eq!(hull.len(), 3);
assert!(hull.contains(&point(0.0, 0.0)));
assert!(hull.contains(&point(2.0, 0.0)));
assert!(hull.contains(&point(1.0, 2.0)));
}
#[test]
fn test_pointline_convex_hull_square() {
let points = vec![
point(0.0, 0.0),
point(1.0, 0.0),
point(1.0, 1.0),
point(0.0, 1.0),
];
let hull = pointline_convex_hull(&points);
assert_eq!(hull.len(), 4);
assert!(hull.contains(&point(0.0, 0.0)));
assert!(hull.contains(&point(1.0, 0.0)));
assert!(hull.contains(&point(1.0, 1.0)));
assert!(hull.contains(&point(0.0, 1.0)));
}
#[test]
fn test_pointline_convex_hull_square_with_concave_vertex() {
let points = vec![
point(0.0, 0.0),
point(1.0, 0.0),
point(1.0, 1.0),
point(0.5, 0.5), point(0.0, 1.0),
];
let hull = pointline_convex_hull(&points);
assert!(!hull.contains(&point(0.5, 0.5)));
assert!(hull.contains(&point(0.0, 0.0)));
assert!(hull.contains(&point(1.0, 0.0)));
assert!(hull.contains(&point(1.0, 1.0)));
assert!(hull.contains(&point(0.0, 1.0)));
}
#[test]
fn test_pointline_convex_hull_octagon() {
let points = vec![
point(2.0, 0.0), point(3.0, 1.0), point(3.0, 3.0), point(2.0, 4.0), point(0.0, 4.0), point(-1.0, 3.0), point(-1.0, 1.0), point(0.0, 0.0), ];
let hull = pointline_convex_hull(&points);
assert_eq!(hull.len(), 8);
}
#[test]
fn test_pointline_convex_hull_pentagon_with_one_concave() {
let points = vec![
point(0.0, 0.0), point(2.0, -1.0), point(3.0, 1.0), point(1.0, 0.9), point(0.0, 2.0), ];
let hull = pointline_convex_hull(&points);
assert!(!hull.contains(&point(1.0, 0.9)));
assert!(hull.contains(&point(0.0, 0.0)));
assert!(hull.contains(&point(2.0, -1.0)));
assert!(hull.contains(&point(3.0, 1.0)));
assert!(hull.contains(&point(0.0, 2.0)));
}
#[test]
fn test_pointline_convex_hull_multiple_concave_vertices() {
let points = vec![
point(0.0, 0.0),
point(2.0, 0.0),
point(2.0, 1.0),
point(1.0, 0.5), point(2.0, 2.0),
point(0.0, 2.0),
point(1.0, 1.5), ];
let hull = pointline_convex_hull(&points);
assert!(!hull.contains(&point(1.0, 0.5)));
assert!(!hull.contains(&point(1.0, 1.5)));
assert!(hull.contains(&point(0.0, 0.0)));
assert!(hull.contains(&point(2.0, 0.0)));
assert!(hull.contains(&point(2.0, 2.0)));
assert!(hull.contains(&point(0.0, 2.0)));
}
#[test]
fn test_pointline_convex_hull_large_coordinates() {
let points = vec![
point(1e6, 1e6),
point(1e6 + 100.0, 1e6),
point(1e6 + 100.0, 1e6 + 100.0),
point(1e6, 1e6 + 100.0),
];
let hull = pointline_convex_hull(&points);
assert_eq!(hull.len(), 4);
assert!(hull.contains(&point(1e6, 1e6)));
assert!(hull.contains(&point(1e6 + 100.0, 1e6)));
assert!(hull.contains(&point(1e6 + 100.0, 1e6 + 100.0)));
assert!(hull.contains(&point(1e6, 1e6 + 100.0)));
}
#[test]
fn test_pointline_convex_hull_negative_coordinates() {
let points = vec![
point(-2.0, -2.0),
point(2.0, -2.0),
point(2.0, 2.0),
point(-2.0, 2.0),
];
let hull = pointline_convex_hull(&points);
assert_eq!(hull.len(), 4);
assert!(hull.contains(&point(-2.0, -2.0)));
assert!(hull.contains(&point(2.0, -2.0)));
assert!(hull.contains(&point(2.0, 2.0)));
assert!(hull.contains(&point(-2.0, 2.0)));
}
#[test]
fn test_pointline_convex_hull_with_bulge_factors() {
let points = vec![
point(0.0, 0.0),
point(1.0, 0.0),
point(1.0, 1.0),
point(0.0, 1.0),
];
let hull = pointline_convex_hull(&points);
assert_eq!(hull.len(), 4);
assert!(hull.contains(&point(0.0, 0.0)));
assert!(hull.contains(&point(1.0, 0.0)));
assert!(hull.contains(&point(1.0, 1.0)));
assert!(hull.contains(&point(0.0, 1.0)));
}
#[test]
fn test_pointline_convex_hull_concave_shape() {
let points = vec![
point(0.0, 0.0), point(4.0, 0.0), point(4.0, 4.0), point(3.0, 4.0), point(3.0, 2.0), point(1.0, 2.0), point(1.0, 4.0), point(0.0, 4.0), ];
let hull = pointline_convex_hull(&points);
assert!(!hull.contains(&point(3.0, 2.0)));
assert!(!hull.contains(&point(1.0, 2.0)));
assert!(hull.contains(&point(3.0, 4.0)));
assert!(hull.contains(&point(1.0, 4.0)));
assert!(hull.contains(&point(0.0, 0.0)));
assert!(hull.contains(&point(4.0, 0.0)));
assert!(hull.contains(&point(4.0, 4.0)));
assert!(hull.contains(&point(0.0, 4.0)));
}
#[test]
fn test_pointline_convex_hull_nearly_collinear() {
let points = vec![
point(0.0, 0.0),
point(1.0, 0.1), point(2.0, -0.1), point(3.0, 0.0),
];
let hull = pointline_convex_hull(&points);
assert!(!hull.is_empty());
assert!(hull.len() >= 2);
}
}
#[cfg(test)]
mod test_arcline_convex_hull {
}
#[must_use]
pub fn pointline_convex_hull(points: &Pointline) -> Pointline {
if points.len() < 3 {
return points.to_vec();
}
let n = points.len();
let mut hull = Vec::with_capacity((n + 1) / 2);
for i in 0..n {
let prev_idx = if i == 0 { n - 1 } else { i - 1 };
let curr_idx = i;
let next_idx = (i + 1) % n;
let p_prev = points[prev_idx];
let p_curr = points[curr_idx];
let p_next = points[next_idx];
let dx1 = p_curr.x - p_prev.x;
let dy1 = p_curr.y - p_prev.y;
let dx2 = p_next.x - p_curr.x;
let dy2 = p_next.y - p_curr.y;
let cross = dx1.mul_add(dy2, -(dy1 * dx2));
if cross > COLLINEARITY_TOLERANCE {
hull.push(p_curr);
}
}
if hull.is_empty() && n > 0 {
return vec![points[0], points[n / 2]];
}
hull
}