use crate::prelude::*;
use crate::intersection::tangent::{external_tangents_between_circles, tangent_point_to_circle};
fn is_arc_convex(arcs: &Arcline, index: usize) -> bool {
if arcs.is_empty() || index >= arcs.len() {
return true; }
let prev_idx = if index == 0 { arcs.len() - 1 } else { index - 1 };
let arc = arcs[index];
let prev_arc = arcs[prev_idx];
let forward = prev_arc.b == arc.a;
forward
}
fn find_start_point(arcs: &Arcline) -> Option<(usize, Point)> {
if arcs.is_empty() {
return None;
}
let mut min_point = arcs[0].a;
let mut min_arc_idx = 0;
for (i, arc) in arcs.iter().enumerate() {
if arc.a.y < min_point.y || (arc.a.y == min_point.y && arc.a.x < min_point.x) {
min_point = arc.a;
min_arc_idx = i;
}
if arc.b.y < min_point.y || (arc.b.y == min_point.y && arc.b.x < min_point.x) {
min_point = arc.b;
min_arc_idx = i;
}
if arc.is_arc() {
let bottom = point(arc.c.x, arc.c.y - arc.r);
if arc.contains(bottom) {
if bottom.y < min_point.y || (bottom.y == min_point.y && bottom.x < min_point.x) {
min_point = bottom;
min_arc_idx = i;
}
}
}
}
Some((min_arc_idx, min_point))
}
fn select_tangent_point_to_circle(
from_point: Point,
to_circle: Circle,
prev_direction: Point,
) -> Option<Point> {
let tangents = tangent_point_to_circle(from_point, to_circle)?;
let (t1, t2) = tangents;
let dir_to_t1 = t1 - from_point;
let dir_to_t2 = t2 - from_point;
let cross1 = prev_direction.perp(dir_to_t1);
let cross2 = prev_direction.perp(dir_to_t2);
if cross1 > cross2 {
Some(t1)
} else {
Some(t2)
}
}
fn select_external_tangent_between_circles(
from_circle: Circle,
to_circle: Circle,
prev_direction: Point,
) -> Option<(Point, Point)> {
let tangents = external_tangents_between_circles(from_circle, to_circle)?;
let (t1_c1, t1_c2, t2_c1, t2_c2) = tangents;
let dir1 = t1_c2 - t1_c1;
let dir2 = t2_c2 - t2_c1;
let cross1 = prev_direction.perp(dir1);
let cross2 = prev_direction.perp(dir2);
if cross1 > cross2 {
Some((t1_c1, t1_c2))
} else {
Some((t2_c1, t2_c2))
}
}
#[must_use]
pub fn arcline_convex_hull(arcs: &Arcline) -> Arcline {
if arcs.is_empty() {
return Arcline::new();
}
let mut hull = Arcline::new();
let n = arcs.len();
let mut i = 0;
while i < n {
if is_arc_convex(arcs, i) {
let current_arc = arcs[i];
let next_idx = (i + 1) % n;
if is_arc_convex(arcs, next_idx) {
let next_arc = arcs[next_idx];
if current_arc.is_arc() && next_arc.is_arc() {
let c1 = Circle { c: current_arc.c, r: current_arc.r };
let c2 = Circle { c: next_arc.c, r: next_arc.r };
let prev_idx = if i == 0 { n - 1 } else { i - 1 };
let prev_arc = arcs[prev_idx];
let initial_dir = current_arc.a - prev_arc.b;
if let Some((t1, t2)) = select_external_tangent_between_circles(c1, c2, initial_dir) {
if current_arc.contains(t1) {
hull.push(arc(current_arc.a, t1, current_arc.c, current_arc.r));
} else {
hull.push(arcseg(current_arc.a, t1));
}
hull.push(arcseg(t1, t2));
} else {
hull.push(current_arc);
}
} else if current_arc.is_arc() && !next_arc.is_arc() {
hull.push(arcseg(current_arc.a, current_arc.b));
} else if !current_arc.is_arc() && next_arc.is_arc() {
hull.push(current_arc);
} else {
hull.push(current_arc);
}
} else {
hull.push(current_arc);
}
i += 1;
} else {
let mut j = i;
while j < n && !is_arc_convex(arcs, j) {
j += 1;
}
if j > i && j < n {
let end_arc = arcs[j];
let prev_idx = if i == 0 { n - 1 } else { i - 1 };
let prev_arc = arcs[prev_idx];
hull.push(arcseg(prev_arc.b, end_arc.a));
}
i = j;
}
}
hull
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_arcline_convex_hull_empty() {
let arcs: Arcline = vec![];
let hull = arcline_convex_hull(&arcs);
assert!(hull.is_empty());
}
#[test]
fn test_arcline_convex_hull_single_arc() {
let arcs = vec![arc(point(0.0, 0.0), point(1.0, 0.0), point(0.5, 0.0), f64::INFINITY)];
let hull = arcline_convex_hull(&arcs);
assert_eq!(hull.len(), 0);
}
#[test]
fn test_arcline_convex_hull_square() {
let arcs = vec![
arcseg(point(0.0, 0.0), point(1.0, 0.0)),
arcseg(point(1.0, 0.0), point(1.0, 1.0)),
arcseg(point(1.0, 1.0), point(0.0, 1.0)),
arcseg(point(0.0, 1.0), point(0.0, 0.0)),
];
let hull = arcline_convex_hull(&arcs);
assert_eq!(hull.len(), 4); }
#[test]
fn test_is_arc_convex_basic() {
let arcs = vec![
arcseg(point(0.0, 0.0), point(1.0, 0.0)),
arcseg(point(1.0, 0.0), point(1.0, 1.0)),
];
assert!(is_arc_convex(&arcs, 1)); }
#[test]
fn test_find_start_point_simple() {
let arcs = vec![
arcseg(point(0.0, 1.0), point(1.0, 1.0)),
arcseg(point(1.0, 0.0), point(0.0, 0.0)),
];
let result = find_start_point(&arcs);
assert!(result.is_some());
let (_, start) = result.unwrap();
assert_eq!(start.y, 0.0);
}
#[test]
fn test_arcline_convex_hull_circle() {
let arcs = vec![
arc(point(1.0, 0.0), point(0.0, 1.0), point(0.0, 0.0), 1.0), arc(point(0.0, 1.0), point(-1.0, 0.0), point(0.0, 0.0), 1.0), arc(point(-1.0, 0.0), point(0.0, -1.0), point(0.0, 0.0), 1.0), arc(point(0.0, -1.0), point(1.0, 0.0), point(0.0, 0.0), 1.0), ];
let hull = arcline_convex_hull(&arcs);
assert_eq!(hull.len(), 4);
let hull_points: Vec<Point> = hull.iter().map(|arc| arc.a).collect();
assert!(hull_points.contains(&point(1.0, 0.0))); assert!(hull_points.contains(&point(0.0, 1.0))); assert!(hull_points.contains(&point(-1.0, 0.0))); assert!(hull_points.contains(&point(0.0, -1.0))); }
#[test]
fn test_arcline_convex_hull_semicircle() {
let arcs = vec![
arc(point(1.0, 0.0), point(0.0, 1.0), point(0.0, 0.0), 1.0), arc(point(0.0, 1.0), point(-1.0, 0.0), point(0.0, 0.0), 1.0), arcseg(point(-1.0, 0.0), point(1.0, 0.0)), ];
let hull = arcline_convex_hull(&arcs);
assert_eq!(hull.len(), 3);
let hull_points: Vec<Point> = hull.iter().map(|arc| arc.a).collect();
assert!(hull_points.contains(&point(1.0, 0.0)));
assert!(hull_points.contains(&point(0.0, 1.0)));
assert!(hull_points.contains(&point(-1.0, 0.0)));
}
#[test]
fn test_arcline_convex_hull_rounded_rectangle() {
let r = 0.5;
let arcs = vec![
arcseg(point(r, 0.0), point(4.0 - r, 0.0)),
arc(point(4.0 - r, 0.0), point(4.0, r), point(4.0 - r, r), r),
arcseg(point(4.0, r), point(4.0, 2.0 - r)),
arc(point(4.0, 2.0 - r), point(4.0 - r, 2.0), point(4.0 - r, 2.0 - r), r),
arcseg(point(4.0 - r, 2.0), point(r, 2.0)),
arc(point(r, 2.0), point(0.0, 2.0 - r), point(r, 2.0 - r), r),
arcseg(point(0.0, 2.0 - r), point(0.0, r)),
arc(point(0.0, r), point(r, 0.0), point(r, r), r),
];
let hull = arcline_convex_hull(&arcs);
assert!(hull.len() >= 4);
let hull_points: Vec<Point> = hull.iter().map(|arc| arc.a).collect();
let max_x = hull_points.iter().map(|p| p.x).fold(f64::NEG_INFINITY, f64::max);
let min_x = hull_points.iter().map(|p| p.x).fold(f64::INFINITY, f64::min);
let max_y = hull_points.iter().map(|p| p.y).fold(f64::NEG_INFINITY, f64::max);
let min_y = hull_points.iter().map(|p| p.y).fold(f64::INFINITY, f64::min);
assert!((max_x - 4.0).abs() < 1e-6);
assert!((min_x - 0.0).abs() < 1e-6);
assert!((max_y - 2.0).abs() < 1e-6);
assert!((min_y - 0.0).abs() < 1e-6);
}
#[test]
fn test_arcline_convex_hull_concave_arc() {
let arcs = vec![
arcseg(point(0.0, 0.0), point(2.0, 0.0)), arcseg(point(2.0, 0.0), point(2.0, 2.0)), arcseg(point(2.0, 2.0), point(0.0, 2.0)), arc(point(0.0, 2.0), point(0.0, 0.0), point(0.0, 1.0), 0.5), ];
let hull = arcline_convex_hull(&arcs);
assert!(hull.len() >= 3);
let hull_points: Vec<Point> = hull.iter().map(|arc| arc.a).collect();
assert!(hull_points.contains(&point(0.0, 0.0)));
assert!(hull_points.contains(&point(2.0, 0.0)));
assert!(hull_points.contains(&point(2.0, 2.0)));
assert!(hull_points.contains(&point(0.0, 2.0)));
}
#[test]
fn test_find_start_point_with_arc() {
let arcs = vec![
arc(point(-1.0, 0.0), point(1.0, 0.0), point(0.0, 0.0), 1.0), ];
let result = find_start_point(&arcs);
assert!(result.is_some());
let (idx, start) = result.unwrap();
assert_eq!(idx, 0);
assert!((start.x - 0.0).abs() < 1e-10);
assert!((start.y - (-1.0)).abs() < 1e-10);
}
#[test]
fn test_is_arc_convex_with_curved_arc() {
let arcs = vec![
arc(point(1.0, 0.0), point(0.0, 1.0), point(0.0, 0.0), 1.0), arc(point(0.0, 1.0), point(-1.0, 0.0), point(0.0, 0.0), 1.0), arcseg(point(-1.0, 0.0), point(1.0, 0.0)), ];
assert!(is_arc_convex(&arcs, 1));
assert!(is_arc_convex(&arcs, 2)); }
#[test]
fn test_arcline_convex_hull_square_with_convex_arcs() {
let arcs = vec![
arc(point(0.0, 0.0), point(1.0, 0.0), point(0.5, 0.0), 0.50),
arc(point(1.0, 0.0), point(1.0, 1.0), point(1.0, 0.5), 0.50),
arc(point(1.0, 1.0), point(0.0, 1.0), point(0.5, 1.0), 0.50),
arc(point(0.0, 1.0), point(0.0, 0.0), point(0.0, 0.5), 0.50),
];
let hull = arcline_convex_hull(&arcs);
assert!(hull.len() == 8);
let hull_points: Vec<Point> = hull.iter().map(|arc| arc.a).collect();
assert!(hull_points.contains(&point(0.0, 0.0)));
assert!(hull_points.contains(&point(1.0, 0.0)));
assert!(hull_points.contains(&point(1.0, 1.0)));
assert!(hull_points.contains(&point(0.0, 1.0)));
}
}