#![allow(dead_code)]
use robust::{Coord, orient2d};
use crate::constants::{DIVISION_EPSILON, GEOMETRIC_EPSILON};
use crate::prelude::*;
use std::{fmt::Display, sync::atomic::AtomicUsize};
pub type Arcline = Vec<Arc>;
static ID_COUNT: AtomicUsize = AtomicUsize::new(0);
const ARC_COLLAPSED_TOLERANCE: f64 = GEOMETRIC_EPSILON;
#[derive(Debug, Copy, Clone)]
pub struct Arc {
pub a: Point,
pub b: Point,
pub c: Point,
pub r: f64,
pub id: usize,
}
impl PartialEq for Arc {
fn eq(&self, other: &Self) -> bool {
self.a == other.a && self.b == other.b && self.c == other.c && self.r == other.r
}
}
impl Display for Arc {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "[{}, {}, {}, {:.20}]", self.a, self.b, self.c, self.r)
}
}
impl Arc {
#[inline]
pub fn new(a: Point, b: Point, c: Point, r: f64) -> Self {
let id = ID_COUNT.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
Arc { a, b, c, r, id }
}
#[inline]
pub fn id(&mut self, id: usize) {
self.id = id;
}
#[inline]
#[must_use]
pub fn is_arc(&self) -> bool {
self.r != f64::INFINITY
}
#[inline]
#[must_use]
pub fn is_seg(&self) -> bool {
self.r == f64::INFINITY
}
#[inline]
pub fn translate(&mut self, point: Point) {
self.a = self.a + point;
self.b = self.b + point;
self.c = self.c + point;
}
#[inline]
pub fn scale(&mut self, factor: f64) {
self.a = self.a * factor;
self.b = self.b * factor;
self.c = self.c * factor;
self.r *= factor;
}
#[inline]
#[must_use]
pub fn reverse(&self) -> Arc {
arc(self.b, self.a, self.c, self.r)
}
#[inline]
#[must_use]
pub fn contains(&self, p: Point) -> bool {
let pa = Coord {
x: self.a.x,
y: self.a.y,
};
let pb = Coord {
x: self.b.x,
y: self.b.y,
};
let pp = Coord { x: p.x, y: p.y };
let perp = orient2d(pa, pp, pb);
perp >= 0f64
}
}
#[inline]
#[must_use]
pub fn arc(a: Point, b: Point, c: Point, r: f64) -> Arc {
Arc::new(a, b, c, r)
}
#[inline]
#[must_use]
pub fn arcseg(a: Point, b: Point) -> Arc {
arc(a, b, point(f64::INFINITY, f64::INFINITY), f64::INFINITY)
}
#[must_use]
pub fn arcline_translate(arcline: &Arcline, translation: Point) -> Arcline {
let mut result: Arcline = Vec::with_capacity(arcline.len());
for arc in arcline {
let mut translated_arc = *arc;
translated_arc.translate(translation);
result.push(translated_arc);
}
result
}
#[must_use]
pub fn arcline_scale(arcline: &Arcline, scale: f64) -> Arcline {
let mut result: Arcline = Vec::with_capacity(arcline.len());
for arc in arcline {
if arc.is_seg() {
let scaled_arc = arcseg(arc.a * scale, arc.b * scale);
result.push(scaled_arc);
} else {
let mut scaled_arc = *arc;
scaled_arc.a = scaled_arc.a * scale;
scaled_arc.b = scaled_arc.b * scale;
scaled_arc.c = scaled_arc.c * scale;
scaled_arc.r = scaled_arc.r * scale.abs();
result.push(scaled_arc);
}
}
result
}
#[cfg(test)]
mod test_arc {
use super::*;
#[test]
fn test_new() {
let arc0 = Arc::new(point(1.0, 1.0), point(1.0, 3.0), point(2.0, -1.0), 1.0);
let arc1 = arc(point(1.0, 1.0), point(1.0, 3.0), point(2.0, -1.0), 1.0);
assert_eq!(arc0, arc1);
}
#[test]
fn test_display() {
let arc = arc(point(1.0, 1.0), point(1.0, 3.0), point(2.0, -1.0), 1.0);
assert_eq!(
"[[1.00000000000000000000, 1.00000000000000000000], [1.00000000000000000000, 3.00000000000000000000], [2.00000000000000000000, -1.00000000000000000000], 1.00000000000000000000]",
format!("{}", arc)
);
}
#[test]
fn test_id_set() {
let mut arc = arc(point(1.0, 1.0), point(1.0, 3.0), point(2.0, -1.0), 1.0);
arc.id(42);
assert!(arc.id == 42);
}
#[test]
fn test_is_arc() {
let arc = arcseg(point(1.0, 1.0), point(1.0, 3.0));
assert!(arc.is_seg());
assert!(!arc.is_arc());
}
#[test]
fn test_contains_orientation() {
let a = arc(point(1.0, 0.0), point(0.0, 1.0), point(0.0, 0.0), 1.0);
assert!(a.contains(point(0.7071067811865476, 0.7071067811865476)));
assert!(!a.contains(point(0.7071067811865476, -0.7071067811865476)));
assert!(a.contains(point(1.0, 0.0)));
assert!(a.contains(point(0.0, 1.0)));
}
#[test]
fn test_arcseg_creation() {
let line_arc = arcseg(point(0.0, 0.0), point(5.0, 5.0));
assert!(line_arc.is_seg());
assert!(!line_arc.is_arc());
assert_eq!(line_arc.r, f64::INFINITY);
assert_eq!(line_arc.a, point(0.0, 0.0));
assert_eq!(line_arc.b, point(5.0, 5.0));
}
#[test]
fn test_arc_reverse() {
let original = arc(point(1.0, 0.0), point(0.0, 1.0), point(0.0, 0.0), 1.0);
let reversed = original.reverse();
assert_eq!(reversed.a, original.b);
assert_eq!(reversed.b, original.a);
assert_eq!(reversed.c, original.c);
assert_eq!(reversed.r, original.r);
}
#[test]
fn test_arc_translate() {
let mut arc = arc(point(1.0, 1.0), point(2.0, 2.0), point(1.5, 1.5), 0.5);
let translation = point(10.0, -5.0);
arc.translate(translation);
assert_eq!(arc.a, point(11.0, -4.0));
assert_eq!(arc.b, point(12.0, -3.0));
assert_eq!(arc.c, point(11.5, -3.5));
assert_eq!(arc.r, 0.5);
}
#[test]
fn test_copy() {
let arc = arcseg(point(1.0, 1.0), point(1.0, 3.0));
let arc2 = arc;
assert_eq!(arc, arc2);
}
#[test]
fn test_reverse() {
let original = arc(point(1.0, 0.0), point(0.0, 1.0), point(0.0, 0.0), 1.0);
let reversed = original.reverse();
assert_eq!(reversed.a, original.b);
assert_eq!(reversed.b, original.a);
assert_eq!(reversed.c, original.c);
assert_eq!(reversed.r, original.r);
assert_eq!(reversed.a, point(0.0, 1.0));
assert_eq!(reversed.b, point(1.0, 0.0));
assert_eq!(reversed.c, point(0.0, 0.0));
assert_eq!(reversed.r, 1.0);
}
#[test]
fn test_reverse_twice_returns_original() {
let original = arc(point(3.0, 4.0), point(1.0, 2.0), point(2.0, 3.0), 2.5);
let double_reversed = original.reverse().reverse();
assert_eq!(double_reversed.a, original.a);
assert_eq!(double_reversed.b, original.b);
assert_eq!(double_reversed.c, original.c);
assert_eq!(double_reversed.r, original.r);
}
#[test]
fn test_arcline_translate_empty() {
let empty_arcline: Arcline = vec![];
let translation = point(5.0, -3.0);
let result = arcline_translate(&empty_arcline, translation);
assert_eq!(result.len(), 0);
}
#[test]
fn test_arcline_translate_single_arc() {
let arcline = vec![arc(point(0.0, 0.0), point(2.0, 0.0), point(1.0, 0.0), 1.0)];
let translation = point(10.0, 5.0);
let result = arcline_translate(&arcline, translation);
assert_eq!(result.len(), 1);
assert_eq!(result[0].a, point(10.0, 5.0));
assert_eq!(result[0].b, point(12.0, 5.0));
assert_eq!(result[0].c, point(11.0, 5.0));
assert_eq!(result[0].r, 1.0); }
#[test]
fn test_arcline_translate_multiple_arcs() {
let arcline = vec![
arc(point(0.0, 0.0), point(1.0, 0.0), point(0.5, 0.0), 0.5),
arcseg(point(1.0, 0.0), point(3.0, 2.0)), arc(
point(3.0, 2.0),
point(3.0, 4.0),
point(4.0, 3.0),
1.414213562373095,
),
];
let translation = point(-2.0, 3.0);
let result = arcline_translate(&arcline, translation);
assert_eq!(result.len(), 3);
}
#[test]
fn test_arcline_scale_empty() {
let empty_arcline: Arcline = vec![];
let result = arcline_scale(&empty_arcline, 2.0);
assert_eq!(result.len(), 0);
}
#[test]
fn test_arcline_scale_single_arc() {
let arcline = vec![arc(point(1.0, 1.0), point(2.0, 1.0), point(1.5, 1.0), 0.5)];
let result = arcline_scale(&arcline, 2.0);
assert_eq!(result.len(), 1);
assert_eq!(result[0].a, point(2.0, 2.0));
assert_eq!(result[0].b, point(4.0, 2.0));
assert_eq!(result[0].c, point(3.0, 2.0));
assert_eq!(result[0].r, 1.0); }
#[test]
fn test_arcline_scale_multiple_arcs() {
let arcline = vec![
arc(point(0.0, 0.0), point(1.0, 0.0), point(0.5, 0.0), 0.5),
arcseg(point(1.0, 0.0), point(2.0, 1.0)),
arc(point(2.0, 1.0), point(2.0, 2.0), point(1.0, 1.5), 1.0),
];
let result = arcline_scale(&arcline, 0.5);
assert_eq!(result.len(), 3);
assert_eq!(result[0].a, point(0.0, 0.0));
assert_eq!(result[0].b, point(0.5, 0.0));
assert_eq!(result[0].c, point(0.25, 0.0));
assert_eq!(result[0].r, 0.25);
}
}
impl Arc {
pub fn is_collapsed_radius(&self, eps: f64) -> bool {
if self.r < eps || self.r.is_nan() {
return true;
}
false
}
pub fn is_collapsed_ends(&self, eps: f64) -> bool {
if self.a.close_enough(self.b, eps) {
return true;
}
false
}
pub fn is_consistent(&self, eps: f64) -> bool {
if self.is_seg() {
return true;
}
let ac = ((self.a - self.c).norm() - self.r).abs();
let bc = ((self.b - self.c).norm() - self.r).abs();
if ac > eps || bc > eps {
return false; }
true
}
#[must_use]
pub fn is_valid(&self, eps: f64) -> bool {
if self.is_seg() {
if self.is_collapsed_ends(eps) {
return false;
}
}
if self.is_arc() {
if self.is_collapsed_ends(eps)
|| self.is_collapsed_radius(eps)
|| !self.is_consistent(eps)
{
return false;
}
}
true
}
}
#[cfg(test)]
mod test_arc_contains {
use super::*;
#[test]
fn test_arc_contains_01() {
let arc1 = arc(point(2.0, 1.0), point(1.0, 0.0), point(1.0, 1.0), 1.0);
assert_eq!(arc1.contains(point(0.0, 0.0)), true);
assert_eq!(arc1.contains(point(-1.0, 1.0)), true);
}
#[test]
fn test_arc_contains_02() {
let arc1 = arc(point(-1.0, 1.0), point(1.0, 1.0), point(0.0, 1.0), 1.0);
assert_eq!(arc1.contains(point(0.0, 0.0)), true);
}
#[test]
fn test_arc_contains_large_r() {
let arc = arc_from_bulge(point(1e20, 30.0), point(10.0, 30.0), 1f64);
assert_eq!(arc.contains(point(1e20 + 1000.0, 30.0)), true);
}
#[test]
fn test_arc_contains_00() {
let sgrt_2_2 = std::f64::consts::SQRT_2 / 2.0;
let arc0 = arc(point(1.0, 1.0), point(0.0, 0.0), point(0.5, 0.5), sgrt_2_2);
assert!(arc0.contains(point(0.0, 1.0)));
}
#[test]
fn test_arc_contains_03() {
let arc0 = arc(point(1.0, 0.0), point(0.0, 1.0), point(0.0, 0.0), 1.0);
assert!(arc0.contains(point(0.0, 1.0)));
}
#[test]
fn test_arc_not_contains() {
let arc = arc(point(0.0, -1.0), point(0.0, 1.0), point(0.0, 0.0), 1.0);
let p = point(-1.0, 0.0);
assert_eq!(arc.contains(p), false);
}
}
#[cfg(test)]
mod test_arc_validation {
use super::*;
const ARC_COLLAPSED_TOLERANCE: f64 = 1E-8;
#[test]
fn test_arc_is_collapsed_radius_normal_values() {
let arc1 = arc(point(0.0, 0.0), point(1.0, 0.0), point(0.5, 0.0), 1.0);
let arc2 = arc(point(0.0, 0.0), point(1.0, 0.0), point(0.5, 0.0), 0.1);
let arc3 = arc(point(0.0, 0.0), point(1.0, 0.0), point(0.5, 0.0), 100.0);
let arc4 = arc(
point(0.0, 0.0),
point(1.0, 0.0),
point(0.5, 0.0),
f64::INFINITY,
);
assert!(!arc1.is_collapsed_radius(ARC_COLLAPSED_TOLERANCE));
assert!(!arc2.is_collapsed_radius(ARC_COLLAPSED_TOLERANCE));
assert!(!arc3.is_collapsed_radius(ARC_COLLAPSED_TOLERANCE));
assert!(!arc4.is_collapsed_radius(ARC_COLLAPSED_TOLERANCE));
}
#[test]
fn test_arc_is_collapsed_radius_small_values() {
let arc1 = arc(point(0.0, 0.0), point(1.0, 0.0), point(0.5, 0.0), 1E-9);
let arc2 = arc(point(0.0, 0.0), point(1.0, 0.0), point(0.5, 0.0), 1E-10);
let arc3 = arc(point(0.0, 0.0), point(1.0, 0.0), point(0.5, 0.0), 0.0);
assert!(arc1.is_collapsed_radius(ARC_COLLAPSED_TOLERANCE));
assert!(arc2.is_collapsed_radius(ARC_COLLAPSED_TOLERANCE));
assert!(arc3.is_collapsed_radius(ARC_COLLAPSED_TOLERANCE));
}
#[test]
fn test_arc_is_collapsed_radius_boundary_values() {
let arc1 = arc(
point(0.0, 0.0),
point(1.0, 0.0),
point(0.5, 0.0),
ARC_COLLAPSED_TOLERANCE / 2.0,
);
let arc2 = arc(
point(0.0, 0.0),
point(1.0, 0.0),
point(0.5, 0.0),
ARC_COLLAPSED_TOLERANCE * 2.0,
);
let arc3 = arc(
point(0.0, 0.0),
point(1.0, 0.0),
point(0.5, 0.0),
ARC_COLLAPSED_TOLERANCE - f64::EPSILON,
);
assert!(arc1.is_collapsed_radius(ARC_COLLAPSED_TOLERANCE));
assert!(!arc2.is_collapsed_radius(ARC_COLLAPSED_TOLERANCE));
assert!(arc3.is_collapsed_radius(ARC_COLLAPSED_TOLERANCE));
}
#[test]
fn test_arc_is_collapsed_radius_negative_values() {
let arc1 = arc(point(0.0, 0.0), point(1.0, 0.0), point(0.5, 0.0), -1.0);
let arc2 = arc(point(0.0, 0.0), point(1.0, 0.0), point(0.5, 0.0), -0.1);
let arc3 = arc(point(0.0, 0.0), point(1.0, 0.0), point(0.5, 0.0), -1E-10);
assert!(arc1.is_collapsed_radius(ARC_COLLAPSED_TOLERANCE));
assert!(arc2.is_collapsed_radius(ARC_COLLAPSED_TOLERANCE));
assert!(arc3.is_collapsed_radius(ARC_COLLAPSED_TOLERANCE));
}
#[test]
fn test_arc_is_collapsed_radius_nan() {
let arc = arc(point(0.0, 0.0), point(1.0, 0.0), point(0.5, 0.0), f64::NAN);
assert!(arc.is_collapsed_radius(ARC_COLLAPSED_TOLERANCE));
}
#[test]
fn test_arc_is_collapsed_ends_normal_points() {
let arc1 = arc(point(0.0, 0.0), point(1.0, 0.0), point(0.5, 0.5), 1.0);
let arc2 = arc(point(0.0, 0.0), point(0.0, 1.0), point(0.5, 0.5), 1.0);
let arc3 = arc(point(-1.0, -1.0), point(1.0, 1.0), point(0.0, 0.0), 2.0);
let arc4 = arc(
point(100.0, 200.0),
point(300.0, 400.0),
point(200.0, 300.0),
100.0,
);
assert!(!arc1.is_collapsed_ends(ARC_COLLAPSED_TOLERANCE));
assert!(!arc2.is_collapsed_ends(ARC_COLLAPSED_TOLERANCE));
assert!(!arc3.is_collapsed_ends(ARC_COLLAPSED_TOLERANCE));
assert!(!arc4.is_collapsed_ends(ARC_COLLAPSED_TOLERANCE));
}
#[test]
fn test_arc_is_collapsed_ends_identical_points() {
let arc1 = arc(point(0.0, 0.0), point(0.0, 0.0), point(0.5, 0.0), 1.0);
let arc2 = arc(point(1.0, 1.0), point(1.0, 1.0), point(1.5, 1.0), 1.0);
let arc3 = arc(point(-5.0, 10.0), point(-5.0, 10.0), point(-4.0, 10.0), 1.0);
assert!(arc1.is_collapsed_ends(ARC_COLLAPSED_TOLERANCE));
assert!(arc2.is_collapsed_ends(ARC_COLLAPSED_TOLERANCE));
assert!(arc3.is_collapsed_ends(ARC_COLLAPSED_TOLERANCE));
}
#[test]
fn test_arc_is_collapsed_ends_very_close_points() {
let p1 = point(0.0, 0.0);
let p2 = point(ARC_COLLAPSED_TOLERANCE / 2.0, 0.0);
let test_arc1 = arc(p1, p2, point(0.0, 0.0), 1.0);
assert!(test_arc1.is_collapsed_ends(ARC_COLLAPSED_TOLERANCE));
let p3 = point(100.0, 100.0);
let p4 = point(100.0 + ARC_COLLAPSED_TOLERANCE / 3.0, 100.0 + ARC_COLLAPSED_TOLERANCE / 3.0);
let test_arc2 = arc(p3, p4, point(100.0, 100.0), 1.0);
assert!(test_arc2.is_collapsed_ends(ARC_COLLAPSED_TOLERANCE));
}
#[test]
fn test_arc_is_collapsed_ends_boundary_distance() {
let p1 = point(0.0, 0.0);
let p2 = point(ARC_COLLAPSED_TOLERANCE, 0.0);
let test_arc1 = arc(p1, p2, point(0.0, 0.0), 1.0);
assert!(test_arc1.is_collapsed_ends(ARC_COLLAPSED_TOLERANCE));
let p3 = point(0.0, 0.0);
let p4 = point(ARC_COLLAPSED_TOLERANCE * 2.0, 0.0);
let test_arc2 = arc(p3, p4, point(0.0, 0.0), 1.0);
assert!(!test_arc2.is_collapsed_ends(ARC_COLLAPSED_TOLERANCE));
}
#[test]
fn test_arc_check_valid_arcs() {
let valid_arc1 = arc(point(0.0, 0.0), point(1.0, 0.0), point(0.5, 0.0), 0.5);
assert!(valid_arc1.is_valid(ARC_COLLAPSED_TOLERANCE));
let valid_arc2 = arc(
point(-1.0, -1.0),
point(1.0, 1.0),
point(0.0, 0.0),
std::f64::consts::SQRT_2,
);
assert!(valid_arc2.is_valid(ARC_COLLAPSED_TOLERANCE));
let valid_line = arcseg(point(0.0, 0.0), point(10.0, 0.0));
assert!(valid_line.is_valid(ARC_COLLAPSED_TOLERANCE));
}
#[test]
fn test_arc_check_collapsed_radius() {
let collapsed_radius_arc1 = arc(point(0.0, 0.0), point(1.0, 0.0), point(0.5, 0.0), 1E-10);
assert!(!collapsed_radius_arc1.is_valid(ARC_COLLAPSED_TOLERANCE));
let collapsed_radius_arc2 = arc(point(0.0, 0.0), point(1.0, 0.0), point(0.5, 0.0), -1.0);
assert!(!collapsed_radius_arc2.is_valid(ARC_COLLAPSED_TOLERANCE));
let nan_radius_arc = arc(point(0.0, 0.0), point(1.0, 0.0), point(0.5, 0.0), f64::NAN);
assert!(!nan_radius_arc.is_valid(ARC_COLLAPSED_TOLERANCE));
}
#[test]
fn test_arc_check_collapsed_ends() {
let collapsed_ends_arc1 = arc(point(0.0, 0.0), point(0.0, 0.0), point(0.0, 1.0), 1.0);
assert!(!collapsed_ends_arc1.is_valid(ARC_COLLAPSED_TOLERANCE));
let close_points = point(0.0, 0.0);
let very_close_points = point(ARC_COLLAPSED_TOLERANCE / 2.0, 0.0);
let collapsed_ends_arc2 = arc(close_points, very_close_points, point(0.0, 1.0), 1.0);
assert!(!collapsed_ends_arc2.is_valid(ARC_COLLAPSED_TOLERANCE));
let collapsed_line = arcseg(point(1.0, 1.0), point(1.0, 1.0));
assert!(!collapsed_line.is_valid(ARC_COLLAPSED_TOLERANCE));
}
#[test]
fn test_arc_check_both_collapsed() {
let both_collapsed = arc(point(0.0, 0.0), point(0.0, 0.0), point(0.0, 1.0), 1E-10);
assert!(!both_collapsed.is_valid(ARC_COLLAPSED_TOLERANCE));
let both_collapsed2 = arc(point(5.0, 5.0), point(5.0, 5.0), point(0.0, 0.0), f64::NAN);
assert!(!both_collapsed2.is_valid(ARC_COLLAPSED_TOLERANCE));
}
#[test]
fn test_arc_check_edge_cases() {
let large_coord_arc = arc(
point(1E10, 1E10),
point(1E10 + 1.0, 1E10),
point(1E10 + 0.5, 1E10),
0.5,
);
assert!(large_coord_arc.is_valid(ARC_COLLAPSED_TOLERANCE));
let small_radius_arc = arc(point(0.0, 0.0), point(1.0, 0.0), point(0.5, 0.0), 0.5);
assert!(small_radius_arc.is_valid(ARC_COLLAPSED_TOLERANCE));
let large_radius_arc = arc(point(0.0, 0.0), point(1E-6, 0.0), point(0.0, 1E6), 1E6);
assert!(large_radius_arc.is_valid(ARC_COLLAPSED_TOLERANCE));
}
#[test]
fn test_arcline_reverse_basic() {
let arc1 = Arc {
a: point(0.0, 0.0),
b: point(1.0, 0.0),
c: point(0.5, 0.5),
r: 1.0,
id: 1,
};
let arc2 = Arc {
a: point(1.0, 0.0),
b: point(1.0, 1.0),
c: point(1.0, 0.5),
r: 1.0,
id: 2,
};
let arcline = vec![arc1, arc2];
let reversed = arcline_reverse(&arcline);
assert_eq!(reversed.len(), 2);
assert_eq!(reversed[0].a, arc2.a); assert_eq!(reversed[0].b, arc2.b);
assert_eq!(reversed[1].a, arc1.a); assert_eq!(reversed[1].b, arc1.b);
assert_eq!(reversed[0].id, arc2.id);
assert_eq!(reversed[1].id, arc1.id);
}
#[test]
fn test_arcline_reverse_empty() {
let arcline: Vec<Arc> = vec![];
let reversed = arcline_reverse(&arcline);
assert_eq!(reversed.len(), 0);
}
#[test]
fn test_arcline_reverse_single_arc() {
let arc = Arc {
a: point(2.0, 2.0),
b: point(3.0, 3.0),
c: point(2.5, 2.5),
r: 2.0,
id: 42,
};
let arcline = vec![arc];
let reversed = arcline_reverse(&arcline);
assert_eq!(reversed.len(), 1);
assert_eq!(reversed[0].a, arc.a);
assert_eq!(reversed[0].b, arc.b);
assert_eq!(reversed[0].id, arc.id);
}
#[test]
fn test_arcline_reverse_all_lines() {
let arc1 = Arc {
a: point(0.0, 0.0),
b: point(1.0, 0.0),
c: point(0.0, 0.0),
r: f64::INFINITY,
id: 1,
};
let arc2 = Arc {
a: point(1.0, 0.0),
b: point(2.0, 0.0),
c: point(0.0, 0.0),
r: f64::INFINITY,
id: 2,
};
let arcline = vec![arc1, arc2];
let reversed = arcline_reverse(&arcline);
assert_eq!(reversed[0].r, f64::INFINITY);
assert_eq!(reversed[1].r, f64::INFINITY);
assert_eq!(reversed[0].a, arc2.b); assert_eq!(reversed[0].b, arc2.a); assert_eq!(reversed[1].a, arc1.b); assert_eq!(reversed[1].b, arc1.a); }
#[test]
fn test_arcline_reverse_all_arcs() {
let arc1 = Arc {
a: point(0.0, 0.0),
b: point(1.0, 0.0),
c: point(0.5, 0.5),
r: 2.0,
id: 1,
};
let arc2 = Arc {
a: point(1.0, 0.0),
b: point(2.0, 0.0),
c: point(1.5, 0.5),
r: 2.0,
id: 2,
};
let arcline = vec![arc1, arc2];
let reversed = arcline_reverse(&arcline);
assert!(reversed.iter().all(|arc| arc.r == 2.0));
assert_eq!(reversed[0].a, arc2.a); assert_eq!(reversed[0].b, arc2.b);
assert_eq!(reversed[1].a, arc1.a); assert_eq!(reversed[1].b, arc1.b);
}
}
#[must_use]
pub fn bulge_from_arc(a: Point, b: Point, c: Point, r: f64) -> f64 {
let dist = (b - a).norm();
if dist <= 1E-10 {
return 0f64;
}
let pa = Coord { x: a.x, y: a.y };
let pb = Coord { x: b.x, y: b.y };
let pc = Coord { x: c.x, y: c.y };
let perp = orient2d(pa, pb, pc);
let ddd = (4.0 * r * r) - dist * dist;
let ddd_clamped = ddd.max(0.0);
let seg = if perp <= 0f64 {
r - (0.5 * ddd_clamped.sqrt())
} else {
r + (0.5 * ddd_clamped.sqrt())
};
if seg.abs() <= 1E-10 || !seg.is_finite() {
return 0f64;
}
let bulge = dist / (2.0 * seg);
if !bulge.is_finite() {
return 0f64;
}
bulge
}
const ZERO: f64 = 0f64;
#[must_use]
pub fn arc_from_bulge(p1: Point, p2: Point, bulge: f64) -> Arc {
let mut pp1 = p1;
let mut pp2 = p2;
let mut bulge = bulge;
if !bulge.is_finite() {
return arcseg(p1, p2);
}
if bulge < 0f64 {
pp1 = p2;
pp2 = p1;
bulge = -bulge;
}
if bulge.abs() < DIVISION_EPSILON || pp1.close_enough(pp2, ARC_COLLAPSED_TOLERANCE) {
return arcseg(pp1, pp2);
}
let t2 = (pp2 - pp1).norm();
let dt2 = (1.0 + bulge) * (1.0 - bulge) / (4.0 * bulge);
let cx = (0.5 * pp1.x + 0.5 * pp2.x) + dt2 * (pp1.y - pp2.y);
let cy = (0.5 * pp1.y + 0.5 * pp2.y) + dt2 * (pp2.x - pp1.x);
let r = 0.25 * t2 * (1.0 / bulge + bulge).abs();
if !cx.is_finite() || !cy.is_finite() || !r.is_finite() {
return arcseg(pp1, pp2);
}
arc(pp1, pp2, point(cx, cy), r)
}
#[cfg(test)]
mod test_arc_g_from_points {
use crate::constants::GEOMETRIC_EPSILON;
use crate::prelude::*;
#[test]
fn test_a_b_are_close() {
let a = point(114.31083505599867, 152.84458247200070);
let b = point(114.31083505599865, 152.84458247200067);
let arc = arc_from_bulge(a, b, 16.0);
assert_eq!(bulge_from_arc(a, b, arc.c, arc.r), 0.0);
}
#[test]
fn test_a_b_are_the_same() {
let a = point(114.31083505599865, 152.84458247200067);
let b = point(114.31083505599865, 152.84458247200067);
let arc = arc_from_bulge(a, b, 16.0);
assert_eq!(bulge_from_arc(a, b, arc.c, arc.r), 0.0);
}
#[test]
fn test_small_arc_perp_negative() {
let a = point(0.0, 0.0);
let b = point(2.0, 0.0);
let bulge = 0.3;
let arc = arc_from_bulge(a, b, bulge);
let result = bulge_from_arc(arc.a, arc.b, arc.c, arc.r);
assert!(close_enough(bulge, result, GEOMETRIC_EPSILON));
assert!(result.is_finite());
}
#[test]
fn test_large_arc_perp_positive() {
let a = point(0.0, 0.0);
let b = point(2.0, 0.0);
let bulge = 1.5;
let arc = arc_from_bulge(a, b, bulge);
let result = bulge_from_arc(arc.a, arc.b, arc.c, arc.r);
assert!(close_enough(bulge, result, GEOMETRIC_EPSILON));
assert!(result.is_finite());
}
#[test]
fn test_semicircle() {
let a = point(0.0, 0.0);
let b = point(2.0, 0.0);
let bulge = 1.0;
let arc = arc_from_bulge(a, b, bulge);
let result = bulge_from_arc(arc.a, arc.b, arc.c, arc.r);
assert!(close_enough(bulge, result, GEOMETRIC_EPSILON));
assert!(result.is_finite());
}
#[test]
fn test_quarter_circle() {
let a = point(0.0, 0.0);
let b = point(1.0, 1.0);
let bulge = 0.41421356;
let arc = arc_from_bulge(a, b, bulge);
let result = bulge_from_arc(arc.a, arc.b, arc.c, arc.r);
assert!(close_enough(bulge, result, GEOMETRIC_EPSILON));
assert!(result.is_finite());
}
#[test]
fn test_very_small_distance() {
let a = point(0.0, 0.0);
let b = point(1.0, 0.0);
let bulge = 1e-9;
let arc = arc_from_bulge(a, b, bulge);
if arc.r == f64::INFINITY {
let result = bulge_from_arc(arc.a, arc.b, arc.c, arc.r);
assert!(result == 0.0 || result.is_infinite());
} else {
let result = bulge_from_arc(arc.a, arc.b, arc.c, arc.r);
assert!(close_enough(bulge, result, GEOMETRIC_EPSILON));
assert!(result.is_finite());
}
}
#[test]
fn test_collinear_points() {
let a = point(0.0, 0.0);
let b = point(2.0, 0.0);
let bulge = 0.0;
let arc = arc_from_bulge(a, b, bulge);
if arc.r == f64::INFINITY {
let result = bulge_from_arc(arc.a, arc.b, arc.c, arc.r);
assert!(result == 0.0 || result.is_infinite());
} else {
let result = bulge_from_arc(arc.a, arc.b, arc.c, arc.r);
assert!(close_enough(bulge, result, GEOMETRIC_EPSILON));
assert!(result.is_finite());
}
}
#[test]
fn test_large_radius() {
let a = point(0.0, 0.0);
let b = point(100.0, 0.0);
let bulge = 0.01;
let arc = arc_from_bulge(a, b, bulge);
let result = bulge_from_arc(arc.a, arc.b, arc.c, arc.r);
assert!(close_enough(bulge, result, GEOMETRIC_EPSILON));
assert!(result.is_finite());
}
#[test]
fn test_minimal_radius() {
let a = point(0.0, 0.0);
let b = point(2.0, 0.0);
let bulge = 1.0;
let arc = arc_from_bulge(a, b, bulge);
let result = bulge_from_arc(arc.a, arc.b, arc.c, arc.r);
assert!(close_enough(bulge, result, GEOMETRIC_EPSILON));
assert!(result.is_finite());
}
#[test]
fn test_consistency_with_parametrization() {
let a = point(1.0, 2.0);
let b = point(3.0, 4.0);
let bulge = 0.5;
let arc = arc_from_bulge(a, b, bulge);
let calculated_bulge = bulge_from_arc(a, b, arc.c, arc.r);
println!(
"Original bulge: {}, Calculated bulge: {}, Ratio: {}",
bulge,
calculated_bulge,
calculated_bulge / bulge
);
assert!(
(calculated_bulge - bulge).abs() < 1e-10,
"Expected {}, got {}",
bulge,
calculated_bulge
);
}
#[test]
fn test_negative_bulge_consistency() {
let a = point(0.0, 0.0);
let b = point(2.0, 2.0);
let bulge = -0.8;
let arc = arc_from_bulge(a, b, bulge);
let calculated_bulge = bulge_from_arc(arc.a, arc.b, arc.c, arc.r);
assert!(close_enough(-bulge, calculated_bulge, GEOMETRIC_EPSILON));
assert!(calculated_bulge.is_finite());
}
#[test]
fn test_various_bulge_values() {
let test_bulges = [0.1, 0.25, 0.5, 0.75, 1.0, 1.5, 2.0];
let a = point(0.0, 0.0);
let b = point(1.0, 0.0);
for &bulge in &test_bulges {
let arc = arc_from_bulge(a, b, bulge);
if arc.r == f64::INFINITY {
continue;
}
let calculated_bulge = bulge_from_arc(arc.a, arc.b, arc.c, arc.r);
assert!(
(calculated_bulge - bulge).abs() < 1e-10,
"Bulge {} resulted in {} (difference: {})",
bulge,
calculated_bulge,
(calculated_bulge - bulge).abs()
);
}
}
#[test]
fn test_different_point_positions() {
let test_cases = [
(point(0.0, 0.0), point(1.0, 0.0)), (point(0.0, 0.0), point(0.0, 1.0)), (point(0.0, 0.0), point(1.0, 1.0)), (point(-1.0, -1.0), point(1.0, 1.0)), (point(10.0, 20.0), point(30.0, 40.0)), ];
let bulge = 0.5;
for (a, b) in test_cases.iter() {
let arc = arc_from_bulge(*a, *b, bulge);
let calculated_bulge = bulge_from_arc(arc.a, arc.b, arc.c, arc.r);
assert!(
(calculated_bulge - bulge).abs() < 1e-10,
"Points {:?} -> {:?}: expected {}, got {}",
a,
b,
bulge,
calculated_bulge
);
}
}
#[test]
fn test_close_points_large_bulge() {
let r = 1.0;
let bulge = bulge_from_arc(point(0.0, 0.0), point(0.0, 3e-5), point(0.0, 1.0), r);
assert!(bulge > 133333.0);
let arc = arc_from_bulge(point(0.0, 0.0), point(0.0, 3e-5), bulge);
assert_eq!(close_enough(arc.r, r, 1e-6), true);
}
#[test]
fn test_degenerate_tiny_bulge_near_division_epsilon() {
use crate::constants::DIVISION_EPSILON;
let a = point(0.0, 0.0);
let b = point(2.0, 0.0);
let arc1 = arc_from_bulge(a, b, DIVISION_EPSILON / 2.0);
assert!(arc1.is_seg(), "Very tiny bulge should be line segment");
let arc2 = arc_from_bulge(a, b, DIVISION_EPSILON * 10.0);
assert!(arc2.is_arc(), "Bulge above guard should be arc");
assert!(arc2.r.is_finite(), "Arc radius should be finite");
}
#[test]
fn test_degenerate_nan_bulge() {
let a = point(0.0, 0.0);
let b = point(2.0, 0.0);
let arc = arc_from_bulge(a, b, f64::NAN);
assert!(arc.is_seg(), "NaN bulge should produce line segment");
}
#[test]
fn test_degenerate_infinite_bulge() {
let a = point(0.0, 0.0);
let b = point(2.0, 0.0);
let arc_pos_inf = arc_from_bulge(a, b, f64::INFINITY);
assert!(arc_pos_inf.is_seg(), "Positive infinite bulge should produce line segment");
let arc_neg_inf = arc_from_bulge(a, b, f64::NEG_INFINITY);
assert!(arc_neg_inf.is_seg(), "Negative infinite bulge should produce line segment");
}
#[test]
fn test_degenerate_coincident_endpoints() {
use crate::constants::GEOMETRIC_EPSILON;
let a = point(1.5, 2.5);
let arc1 = arc_from_bulge(a, a, 1.0);
assert!(arc1.is_seg(), "Identical endpoints should produce line segment");
let b = point(1.5 + GEOMETRIC_EPSILON / 2.0, 2.5);
let arc2 = arc_from_bulge(a, b, 1.0);
assert!(arc2.is_seg(), "Nearly identical endpoints should produce line segment");
}
#[test]
fn test_degenerate_extreme_bulge_values() {
let a = point(0.0, 0.0);
let b = point(1.0, 0.0);
let arc_large = arc_from_bulge(a, b, 1e10);
assert!(arc_large.is_arc(), "Very large bulge should produce arc");
let arc_small = arc_from_bulge(a, b, 1e-15);
assert!(arc_small.is_seg(), "Extremely small bulge should be line segment");
}
#[test]
fn test_degenerate_negative_bulge_endpoints() {
let a = point(0.0, 0.0);
let b = point(2.0, 0.0);
let bulge = 0.5;
let arc_pos = arc_from_bulge(a, b, bulge);
let arc_neg = arc_from_bulge(a, b, -bulge);
assert!(arc_pos.is_arc(), "Positive bulge should be arc");
assert!(arc_neg.is_arc(), "Negative bulge should be arc");
assert_eq!(arc_pos.a, arc_neg.b, "Negative bulge swaps endpoints");
assert_eq!(arc_pos.b, arc_neg.a, "Negative bulge swaps endpoints");
}
#[test]
fn test_degenerate_zero_length_chord() {
let test_points = [
(point(0.0, 0.0), point(0.0, 0.0)),
(point(100.0, 100.0), point(100.0, 100.0)),
(point(-1e6, -1e6), point(-1e6, -1e6)),
];
for (a, b) in &test_points {
let arc = arc_from_bulge(*a, *b, 1.0);
assert!(arc.is_seg(), "Zero-length chord should produce line segment");
}
}
#[test]
fn test_degenerate_computed_geometry_validation() {
let a = point(0.0, 0.0);
let b = point(1.0, 0.0);
let arc = arc_from_bulge(a, b, 0.5);
assert!(arc.c.x.is_finite(), "Arc center x should be finite");
assert!(arc.c.y.is_finite(), "Arc center y should be finite");
assert!(arc.r.is_finite(), "Arc radius should be finite");
}
#[test]
fn test_degenerate_roundtrip_stability() {
let a = point(0.0, 0.0);
let b = point(1.0, 0.0);
let test_bulges = [
1e-11, 1e-10, 1e-8, 0.1,
0.5,
1.0,
2.0,
1e2,
];
for &bulge in &test_bulges {
let arc = arc_from_bulge(a, b, bulge);
if arc.is_seg() {
assert!(bulge < 1e-9, "Only very small bulges should be lines");
} else {
let calculated = bulge_from_arc(arc.a, arc.b, arc.c, arc.r);
let max_error = (GEOMETRIC_EPSILON * 1000.0).max(1e-6);
assert!(
(calculated - bulge).abs() < max_error,
"Bulge {} round-trip error too large: {} (max allowed: {})",
bulge,
(calculated - bulge).abs(),
max_error
);
}
}
}
}
#[must_use]
pub fn arcline_reverse(arcs: &Arcline) -> Arcline {
let mut reversed: Vec<Arc> = Vec::with_capacity(arcs.len());
for arc in arcs.iter().rev() {
if arc.is_seg() {
reversed.push(arc.reverse());
} else {
reversed.push(*arc);
}
}
reversed
}
impl Arc {
pub fn make_consistent(&mut self) {
if self.is_seg() {
return;
}
if self.a.close_enough(self.b, 1e-12) {
*self = arcseg(self.a, self.b);
return;
}
let dist_a_c = (self.a - self.c).norm();
let dist_b_c = (self.b - self.c).norm();
let avg_radius = (dist_a_c + dist_b_c) / 2.0;
let chord = self.b - self.a;
let chord_length = chord.norm();
let half_chord = chord_length / 2.0;
let new_radius = if avg_radius < half_chord {
half_chord
} else {
avg_radius
};
let midpoint = (self.a + self.b) / 2.0;
let distance_to_center = (new_radius * new_radius - half_chord * half_chord).sqrt();
let perp = if chord_length > 1e-12 {
point(-chord.y, chord.x) / chord_length
} else {
point(0.0, 1.0) };
let c1 = midpoint + perp * distance_to_center;
let c2 = midpoint - perp * distance_to_center;
let dist1 = (c1 - self.c).norm();
let dist2 = (c2 - self.c).norm();
let new_center = if dist1 < dist2 { c1 } else { c2 };
*self = Arc {
a: self.a,
b: self.b,
c: new_center,
r: new_radius,
id: self.id, };
}
}
#[cfg(test)]
mod test_arc_make_consistent {
use crate::prelude::*;
const GEOMETRIC_EPSILON: f64 = 1E-10;
#[test]
fn test_arc_make_consistent() {
let mut arc = arc(point(0.0, 0.0), point(1.0, 0.0), point(0.5, 0.5), 0.5);
arc.make_consistent();
assert!(arc.is_consistent(GEOMETRIC_EPSILON));
}
#[test]
fn test_arc_make_consistent_already_consistent() {
let mut arc = arc(point(0.0, 0.0), point(2.0, 0.0), point(1.0, 0.0), 1.0);
arc.make_consistent();
assert!(arc.is_consistent(GEOMETRIC_EPSILON));
assert!(close_enough(arc.c.x, 1.0, GEOMETRIC_EPSILON));
assert!(close_enough(arc.c.y, 0.0, GEOMETRIC_EPSILON));
assert!(close_enough(arc.r, 1.0, GEOMETRIC_EPSILON));
}
#[test]
fn test_arc_make_consistent_different_distances() {
let mut arc = arc(point(0.0, 0.0), point(3.0, 4.0), point(1.0, 1.0), 2.0);
arc.make_consistent();
assert!(arc.is_consistent(GEOMETRIC_EPSILON));
let dist_a = (arc.a - arc.c).norm();
let dist_b = (arc.b - arc.c).norm();
assert!(close_enough(dist_a, arc.r, GEOMETRIC_EPSILON));
assert!(close_enough(dist_b, arc.r, GEOMETRIC_EPSILON));
}
#[test]
fn test_arc_make_consistent_degenerate_endpoints() {
let mut arc = arc(point(1.0, 1.0), point(1.0, 1.0), point(2.0, 2.0), 1.0);
arc.make_consistent();
assert!(arc.is_consistent(GEOMETRIC_EPSILON));
assert!(arc.is_seg());
}
#[test]
fn test_arc_make_consistent_line_segment() {
let mut line_arc = arc(
point(0.0, 0.0),
point(1.0, 1.0),
point(0.0, 0.0),
f64::INFINITY,
);
line_arc.make_consistent();
assert_eq!(line_arc.r, f64::INFINITY);
assert_eq!(line_arc.a, line_arc.a);
assert_eq!(line_arc.b, line_arc.b);
}
#[test]
fn test_arc_make_consistent_small_radius() {
let mut arc = arc(point(0.0, 0.0), point(4.0, 0.0), point(2.0, 1.0), 1.0);
let dist_a_c = (arc.a - arc.c).norm(); let dist_b_c = (arc.b - arc.c).norm(); let avg_radius = (dist_a_c + dist_b_c) / 2.0;
arc.make_consistent();
assert!(arc.is_consistent(GEOMETRIC_EPSILON));
assert!(close_enough(arc.r, avg_radius, GEOMETRIC_EPSILON));
let new_dist_a = (arc.a - arc.c).norm();
let new_dist_b = (arc.b - arc.c).norm();
assert!(close_enough(new_dist_a, arc.r, GEOMETRIC_EPSILON));
assert!(close_enough(new_dist_b, arc.r, GEOMETRIC_EPSILON));
}
#[test]
fn test_arc_make_consistent_radius_too_small() {
let mut arc = arc(point(0.0, 0.0), point(10.0, 0.0), point(1.0, 0.1), 0.5);
let dist_a_c = (arc.a - arc.c).norm();
let dist_b_c = (arc.b - arc.c).norm();
let avg_radius = (dist_a_c + dist_b_c) / 2.0;
let chord_length = (arc.b - arc.a).norm();
let half_chord = chord_length / 2.0;
arc.make_consistent();
assert!(arc.is_consistent(GEOMETRIC_EPSILON));
if avg_radius < half_chord {
assert!(close_enough(arc.r, half_chord, GEOMETRIC_EPSILON));
assert!(close_enough(arc.c.x, chord_length / 2.0, GEOMETRIC_EPSILON));
assert!(close_enough(arc.c.y, 0.0, GEOMETRIC_EPSILON));
} else {
assert!(close_enough(arc.r, avg_radius, GEOMETRIC_EPSILON));
}
}
}
#[must_use]
pub fn is_really_intersecting(arc1: &Arc, arc2: &Arc) -> bool {
if arc1.is_seg() && arc2.is_seg() {
let seg1 = segment(arc1.a, arc1.b);
let seg2 = segment(arc2.a, arc2.b);
return if_really_intersecting_segment_segment(&seg1, &seg2);
}
if arc1.is_arc() && arc2.is_arc() {
return if_really_intersecting_arc_arc(arc1, arc2);
}
if arc1.is_seg() && arc2.is_arc() {
let seg1 = segment(arc1.a, arc1.b);
return if_really_intersecting_segment_arc(&seg1, &arc2);
}
if arc1.is_arc() && arc2.is_seg() {
let seg2 = segment(arc2.a, arc2.b);
return if_really_intersecting_segment_arc(&seg2, arc1);
}
false
}
#[cfg(test)]
mod test_is_really_intersecting {
use super::*;
use crate::point::point;
#[test]
fn test_crossing_line_segments() {
let line1 = arcseg(point(0.0, 0.0), point(2.0, 2.0));
let line2 = arcseg(point(0.0, 2.0), point(2.0, 0.0));
assert!(is_really_intersecting(&line1, &line2));
}
#[test]
fn test_endpoint_touching_segments() {
let line1 = arcseg(point(0.0, 0.0), point(1.0, 0.0));
let line2 = arcseg(point(1.0, 0.0), point(2.0, 0.0));
assert!(!is_really_intersecting(&line1, &line2));
}
#[test]
fn test_parallel_segments() {
let line1 = arcseg(point(0.0, 0.0), point(1.0, 0.0));
let line2 = arcseg(point(0.0, 1.0), point(1.0, 1.0));
assert!(!is_really_intersecting(&line1, &line2));
}
#[test]
fn test_overlapping_segments() {
let line1 = arcseg(point(0.0, 0.0), point(2.0, 0.0));
let line2 = arcseg(point(1.0, 0.0), point(3.0, 0.0));
assert!(is_really_intersecting(&line1, &line2));
}
#[test]
fn test_arc_to_arc_intersecting() {
let arc1 = arc(point(-1.0, 0.0), point(1.0, 0.0), point(0.0, 1.0), 1.0);
let arc2 = arc(point(0.0, -1.0), point(0.0, 1.0), point(1.0, 0.0), 1.0);
assert!(is_really_intersecting(&arc1, &arc2));
}
#[test]
fn test_arc_to_arc_touching_endpoints() {
let arc1 = arc(point(0.0, 0.0), point(1.0, 0.0), point(0.5, 0.5), 1.0);
let arc2 = arc(point(1.0, 0.0), point(2.0, 0.0), point(1.5, 0.5), 1.0);
assert!(!is_really_intersecting(&arc1, &arc2));
}
#[test]
fn test_arc_to_arc_no_intersection() {
let arc1 = arc(point(0.0, 0.0), point(1.0, 0.0), point(0.5, 0.5), 1.0);
let arc2 = arc(point(2.0, 2.0), point(3.0, 2.0), point(2.5, 2.5), 1.0);
assert!(!is_really_intersecting(&arc1, &arc2));
}
#[test]
fn test_segment_to_arc_intersecting() {
let arc = arc(point(-1.0, 0.0), point(1.0, 0.0), point(0.0, 1.0), 1.0);
let line = arcseg(point(0.0, -0.5), point(0.0, 1.5));
assert!(is_really_intersecting(&line, &arc));
}
#[test]
fn test_segment_to_arc_touching_endpoint() {
let arc = arc(point(-1.0, 0.0), point(1.0, 0.0), point(0.0, 1.0), 1.0);
let line = arcseg(point(-1.0, 0.0), point(-2.0, 0.0));
assert!(!is_really_intersecting(&line, &arc));
}
#[test]
fn test_segment_to_arc_no_intersection() {
let arc = arc(point(-1.0, 0.0), point(1.0, 0.0), point(0.0, 1.0), 1.0);
let line = arcseg(point(2.0, 0.0), point(3.0, 0.0));
assert!(!is_really_intersecting(&line, &arc));
}
#[test]
fn test_arc_to_segment_intersecting() {
let line = arcseg(point(0.0, -0.5), point(0.0, 1.5));
let arc = arc(point(-1.0, 0.0), point(1.0, 0.0), point(0.0, 1.0), 1.0);
assert!(is_really_intersecting(&arc, &line));
}
#[test]
fn test_tangent_cases() {
let arc = arc(point(-1.0, 0.0), point(1.0, 0.0), point(0.0, 1.0), 1.0);
let line = arcseg(point(-1.0, 1.0), point(1.0, 1.0));
assert!(!is_really_intersecting(&line, &arc));
}
#[test]
fn test_collinear_segments() {
let line1 = arcseg(point(0.0, 0.0), point(1.0, 0.0));
let line2 = arcseg(point(2.0, 0.0), point(3.0, 0.0));
assert!(!is_really_intersecting(&line1, &line2));
}
#[test]
fn test_perpendicular_segments_intersecting() {
let line1 = arcseg(point(-1.0, 0.0), point(1.0, 0.0));
let line2 = arcseg(point(0.0, -1.0), point(0.0, 1.0));
assert!(is_really_intersecting(&line1, &line2));
}
}
#[derive(Debug, PartialEq)]
pub enum ArclineValidation {
Valid,
Invalid,
InvalidArc(Arc),
GapBetweenArcs(Arc),
ZeroDegreeAngle(Arc, Arc),
IntersectingArcs(Arc, Arc),
NotCCW(Option<(usize, Arc)>),
}
#[must_use]
pub fn arcline_is_valid(arcs: &Arcline) -> ArclineValidation {
let size = arcs.len();
if size < 2 {
return ArclineValidation::Invalid;
}
for arc in arcs {
if !arc.is_valid(1e-8) {
return ArclineValidation::InvalidArc(arc.clone());
}
}
for i in 0..size {
let arc0 = arcs[i % size];
let arc1 = arcs[(i + 1) % size]; let arc2 = arcs[(i + 2) % size];
if !arc_have_two_connected_ends(&arc0, &arc1, &arc2) {
return ArclineValidation::GapBetweenArcs(arc1.clone());
}
if arc_tangents_are_collinear(&arc0, &arc1) {
return ArclineValidation::ZeroDegreeAngle(arc0.clone(), arc1.clone());
}
}
for i in 0..size {
let arc = arcs[i];
if arc.is_seg() {
let prev_arc = arcs[if i == 0 { size - 1 } else { i - 1 }];
let next_arc = arcs[(i + 1) % size];
if prev_arc.b == arc.b && prev_arc.b != arc.a {
return ArclineValidation::NotCCW(Some((i, arc.clone())));
}
if arc.a == next_arc.a && arc.b != next_arc.a {
return ArclineValidation::NotCCW(Some((i, arc.clone())));
}
}
}
for i in 0..size {
for j in (i + 2)..size {
let arc0 = arcs[i];
let arc1 = arcs[j];
if is_really_intersecting(&arc0, &arc1) {
return ArclineValidation::IntersectingArcs(arc0.clone(), arc1.clone());
}
}
}
let area = crate::algo::arcline_area(arcs);
if area < -1e-10 {
return ArclineValidation::NotCCW(None);
}
ArclineValidation::Valid
}
#[must_use]
fn arc_have_two_connected_ends(arc1: &Arc, arc2: &Arc, arc3: &Arc) -> bool {
let arc1_to_arc2 = arc1.b == arc2.a ||
arc1.b == arc2.b ||
arc1.a == arc2.a ||
arc1.a == arc2.b;
let arc2_to_arc3 = arc2.b == arc3.a ||
arc2.b == arc3.b ||
arc2.a == arc3.a ||
arc2.a == arc3.b;
arc1_to_arc2 && arc2_to_arc3
}
#[must_use]
fn arc_tangents_are_collinear(arc1: &Arc, arc2: &Arc) -> bool {
let x = vec![arc1.a, arc1.b];
let y = vec![arc2.a, arc2.b];
for i in 0..2 {
for j in 0..2 {
if x[i] == y[j] {
let t1 = arc1.tangents()[i];
let t2 = arc2.tangents()[j];
if t1.close_enough(t2, 1e-8) {
return true;
}
}
}
}
false
}
impl Arc {
#[must_use]
pub fn tangents(&self) -> [Point; 2] {
if self.is_seg() {
let (t, _) = (self.b - self.a).normalize(false);
return [-t, t];
}
let a_to_c = self.a - self.c;
let b_to_c = self.b - self.c;
let (va, _) = point(a_to_c.y, -a_to_c.x).normalize(false);
let (vb, _) = point(b_to_c.y, -b_to_c.x).normalize(false);
[va, -vb]
}
}
#[cfg(test)]
mod test_tangents {
use super::*;
use crate::utils::close_enough;
#[test]
fn test_tangents_semicircle() {
let arc = arc(point(1.0, 0.0), point(-1.0, 0.0), point(0.0, 0.0), 1.0);
let tangents = arc.tangents();
let t1 = tangents[0];
let t2 = tangents[1];
assert_eq!(t1, point(0.0, -1.0));
assert_eq!(t2, point(0.0, -1.0));
}
#[test]
fn test_tangents_quarter_circle() {
let arc = arc(point(1.0, 0.0), point(0.0, 1.0), point(0.0, 0.0), 1.0);
let tangents = arc.tangents();
let t_start = tangents[0];
let t_end = tangents[1];
assert!(close_enough(t_start.x, 0.0, 1e-10));
assert!(close_enough(t_start.y, -1.0, 1e-10));
assert!(close_enough(t_end.x, -1.0, 1e-10));
assert!(close_enough(t_end.y, 0.0, 1e-10));
}
#[test]
fn test_tangents_line_segment() {
let line = arcseg(point(0.0, 0.0), point(3.0, 4.0));
let tangents = line.tangents();
let t_start = tangents[0];
let t_end = tangents[1];
let expected_dir = point(0.6, 0.8);
assert!(close_enough(t_start.x, -expected_dir.x, 1e-10));
assert!(close_enough(t_start.y, -expected_dir.y, 1e-10));
assert!(close_enough(t_end.x, expected_dir.x, 1e-10));
assert!(close_enough(t_end.y, expected_dir.y, 1e-10));
}
#[test]
fn test_tangents_horizontal_line() {
let line = arcseg(point(-2.0, 5.0), point(2.0, 5.0));
let tangents = line.tangents();
let t_start = tangents[0];
let t_end = tangents[1];
assert!(close_enough(t_start.x, -1.0, 1e-10));
assert!(close_enough(t_start.y, 0.0, 1e-10));
assert!(close_enough(t_end.x, 1.0, 1e-10));
assert!(close_enough(t_end.y, 0.0, 1e-10));
}
#[test]
fn test_tangents_vertical_line() {
let line = arcseg(point(3.0, -1.0), point(3.0, 1.0));
let tangents = line.tangents();
let t_start = tangents[0];
let t_end = tangents[1];
assert!(close_enough(t_start.x, 0.0, 1e-10));
assert!(close_enough(t_start.y, -1.0, 1e-10));
assert!(close_enough(t_end.x, 0.0, 1e-10));
assert!(close_enough(t_end.y, 1.0, 1e-10));
}
#[test]
fn test_tangents_semicircle_arc() {
let arc = arc(point(1.0, 0.0), point(-1.0, 0.0), point(0.0, 0.0), 1.0);
let tangents = arc.tangents();
let t_start = tangents[0];
let t_end = tangents[1];
assert!(close_enough(t_start.norm(), 1.0, 1e-10));
assert!(close_enough(t_end.norm(), 1.0, 1e-10));
assert!(close_enough(t_start.x, 0.0, 1e-10));
assert!(close_enough(t_start.y, -1.0, 1e-10));
assert!(close_enough(t_end.x, 0.0, 1e-10));
assert!(close_enough(t_end.y, -1.0, 1e-10));
}
#[test]
fn test_tangents_small_arc() {
let arc = arc(
point(1.0, 0.1),
point(0.1, 1.0),
point(0.0, 0.0),
((1.0_f64 - 0.0).powi(2) + (0.1_f64 - 0.0).powi(2)).sqrt(),
);
let tangents = arc.tangents();
let t_start = tangents[0];
let t_end = tangents[1];
assert!(close_enough(t_start.norm(), 1.0, 1e-10));
assert!(close_enough(t_end.norm(), 1.0, 1e-10));
let radius_start = point(1.0, 0.1) - arc.c;
let radius_end = point(0.1, 1.0) - arc.c;
assert!(close_enough(t_start.dot(radius_start), 0.0, 1e-10));
assert!(close_enough(t_end.dot(radius_end), 0.0, 1e-10));
}
#[test]
fn test_tangents_counterclockwise_vs_clockwise() {
let ccw_arc = arc(point(1.0, 0.0), point(0.0, 1.0), point(0.0, 0.0), 1.0);
let cw_arc = arc(point(0.0, 1.0), point(1.0, 0.0), point(0.0, 0.0), 1.0);
let ccw_tangents = ccw_arc.tangents();
let ccw_t_start = ccw_tangents[0];
let ccw_t_end = ccw_tangents[1];
let cw_tangents = cw_arc.tangents();
let cw_t_start = cw_tangents[0];
let cw_t_end = cw_tangents[1];
assert!(close_enough(ccw_t_start.norm(), 1.0, 1e-10));
assert!(close_enough(ccw_t_end.norm(), 1.0, 1e-10));
assert!(close_enough(cw_t_start.norm(), 1.0, 1e-10));
assert!(close_enough(cw_t_end.norm(), 1.0, 1e-10));
}
#[test]
fn test_tangents_translated_arc() {
let center = point(5.0, -3.0);
let arc = arc(point(6.0, -3.0), point(4.0, -3.0), center, 1.0);
let tangents = arc.tangents();
let t_start = tangents[0];
let t_end = tangents[1];
assert!(close_enough(t_start.norm(), 1.0, 1e-10));
assert!(close_enough(t_end.norm(), 1.0, 1e-10));
assert!(close_enough(t_start.x, 0.0, 1e-10));
assert!(close_enough(t_end.x, 0.0, 1e-10));
assert!(close_enough(t_start.y.abs(), 1.0, 1e-10));
assert!(close_enough(t_end.y.abs(), 1.0, 1e-10));
}
#[test]
fn test_tangents_mathematical_properties() {
let arc = arc(point(2.0, 0.0), point(0.0, 2.0), point(0.0, 0.0), 2.0);
let tangents = arc.tangents();
let t_start = tangents[0];
let t_end = tangents[1];
let radius_start = arc.a - arc.c; let radius_end = arc.b - arc.c;
assert!(close_enough(t_start.dot(radius_start), 0.0, 1e-10));
assert!(close_enough(t_end.dot(radius_end), 0.0, 1e-10));
assert!(close_enough(t_start.norm(), 1.0, 1e-10));
assert!(close_enough(t_end.norm(), 1.0, 1e-10));
}
#[test]
fn test_tangents_arbitrary_arc() {
let center = point(3.0, -2.0);
let radius = 1.5;
let start = center + point(radius, 0.0); let end = center + point(0.0, radius); let arc = arc(start, end, center, radius);
let tangents = arc.tangents();
let t_start = tangents[0];
let t_end = tangents[1];
assert!(close_enough(t_start.norm(), 1.0, 1e-10));
assert!(close_enough(t_end.norm(), 1.0, 1e-10));
let radius_start = start - center;
let radius_end = end - center;
assert!(close_enough(t_start.dot(radius_start), 0.0, 1e-10));
assert!(close_enough(t_end.dot(radius_end), 0.0, 1e-10));
}
#[test]
fn test_tangents_very_small_line_segment() {
let line = arcseg(point(0.0, 0.0), point(1e-6, 1e-6));
let tangents = line.tangents();
let t_start = tangents[0];
let t_end = tangents[1];
assert!(close_enough(t_start.norm(), 1.0, 1e-10));
assert!(close_enough(t_end.norm(), 1.0, 1e-10));
let expected = 1.0 / (2.0_f64.sqrt());
assert!(close_enough(t_start.x, -expected, 1e-10));
assert!(close_enough(t_start.y, -expected, 1e-10));
assert!(close_enough(t_end.x, expected, 1e-10));
assert!(close_enough(t_end.y, expected, 1e-10));
}
}
#[cfg(test)]
mod test_is_valid_arcline {
use super::*;
const EPS: f64 = 1e-10;
#[test]
fn test_is_valid_arcline_invalid_case() {
let arc1 = arcseg(point(0.0, 0.0), point(1.0, 0.0));
let arc2 = arc(point(1.0, 0.0), point(0.0, 0.0), point(0.5, 0.0), 0.5);
let arcline = vec![arc1, arc2];
assert_eq!(arcline_is_valid(&arcline), ArclineValidation::Valid);
}
#[test]
fn test_is_valid_arcline_empty() {
let empty_arcline: Arcline = vec![];
assert_eq!(arcline_is_valid(&empty_arcline), ArclineValidation::Invalid);
}
#[test]
fn test_is_valid_arcline_single_arc() {
let arc = arcseg(point(0.0, 0.0), point(1.0, 1.0));
let arcline = vec![arc];
assert_eq!(arcline_is_valid(&arcline), ArclineValidation::Invalid);
}
#[test]
fn test_is_valid_arcline_invalid_arc() {
let invalid_arc1 = arcseg(point(0.0, 0.0), point(0.0, 0.0));
let valid_arc = arcseg(point(1.0, 1.0), point(2.0, 2.0));
let arcline = vec![invalid_arc1, valid_arc];
match arcline_is_valid(&arcline) {
ArclineValidation::InvalidArc(arcx) => {
assert_eq!(arcx, invalid_arc1);
} other => assert!(false, "Expected InvalidArc, got {:?}", other),
}
}
#[test]
fn test_is_valid_arcline_gap_between_arcs() {
let arc1 = arcseg(point(0.0, 0.0), point(1.0, 0.0));
let arc2 = arcseg(point(2.0, 0.0), point(3.0, 0.0));
let arcline = vec![arc1, arc2];
match arcline_is_valid(&arcline) {
ArclineValidation::GapBetweenArcs(_) => {} other => assert!(false, "Expected GapBetweenArcs, got {:?}", other),
}
}
#[test]
fn test_is_valid_arcline_intersecting_arcs() {
let arc1 = arcseg(point(0.0, 0.0), point(1.0, 0.0));
let arc2 = arcseg(point(1.0, 0.0), point(1.0, 1.0)); let arc3 = arcseg(point(0.5, -0.5), point(0.5, 1.5));
let arcline = vec![arc1, arc2, arc3];
match arcline_is_valid(&arcline) {
ArclineValidation::IntersectingArcs(_, _) => {} ArclineValidation::GapBetweenArcs(_) => {} other => assert!(
false,
"Expected IntersectingArcs or GapBetweenArcs, got {:?}",
other
),
}
}
#[test]
fn test_is_valid_arcline_connected() {
let arc1 = arcseg(point(0.0, 0.0), point(1.0, 0.0)); let arc2 = arc(point(1.0, 0.0), point(0.0, 0.0), point(0.5, 0.0), 0.5);
let arcline = vec![arc1, arc2];
assert_eq!(arcline_is_valid(&arcline), ArclineValidation::Valid);
}
#[test]
fn test_is_valid_arcline_closed_triangle() {
let p1 = point(0.0, 0.0);
let p2 = point(1.0, 0.0);
let p3 = point(0.5, 1.0);
let arc1 = arcseg(p1, p2);
let arc2 = arcseg(p2, p3);
let arc3 = arcseg(p3, p1);
let arcline = vec![arc1, arc2, arc3];
assert_eq!(arcline_is_valid(&arcline), ArclineValidation::Valid);
}
#[test]
fn test_is_valid_arcline_connected_arcs_and_segments() {
let arc1 = arcseg(point(0.0, 0.0), point(1.0, 0.0));
let arc2 = arc(
point(1.0, 0.0),
point(2.0, 1.0),
point(1.5, 0.5),
0.7071067811865476,
);
let arc3 = arcseg(point(2.0, 1.0), point(0.0, 1.0));
let arc4 = arcseg(point(0.0, 1.0), point(0.0, 0.0));
let arcline = vec![arc1, arc2, arc3, arc4];
assert_eq!(arcline_is_valid(&arcline), ArclineValidation::Valid);
}
#[test]
fn test_is_valid_arcline_multiple_invalid_arcs() {
let mut invalid_arc1 = arcseg(point(0.0, 0.0), point(1.0, 1.0));
invalid_arc1.a = invalid_arc1.b;
let valid_arc = arcseg(point(1.0, 1.0), point(2.0, 2.0));
let mut invalid_arc2 = arcseg(point(2.0, 2.0), point(3.0, 3.0));
invalid_arc2.a = invalid_arc2.b;
let arcline = vec![invalid_arc1, valid_arc, invalid_arc2];
match arcline_is_valid(&arcline) {
ArclineValidation::InvalidArc(arc) => {
assert_eq!(arc.a, arc.b);
}
other => assert!(false, "Expected InvalidArc, got {:?}", other),
}
}
#[test]
fn test_is_valid_arcline_non_adjacent_intersecting_arcs() {
let arc1 = arcseg(point(0.0, 0.0), point(1.0, 0.0));
let arc2 = arcseg(point(1.0, 0.0), point(2.0, 1.0));
let arc3 = arcseg(point(2.0, 1.0), point(0.5, 2.0));
let arcline = vec![arc1, arc2, arc3];
match arcline_is_valid(&arcline) {
ArclineValidation::IntersectingArcs(_, _) => {} ArclineValidation::GapBetweenArcs(_) => {} other => assert!(
false,
"Expected IntersectingArcs or GapBetweenArcs, got {:?}",
other
),
}
}
#[test]
fn test_is_valid_arcline_circular_arc_with_segments() {
let p1 = point(0.0, 0.0);
let p2 = point(1.0, 0.0);
let p3 = point(0.0, 1.0);
let arc1 = arcseg(p1, p2); let arc2 = arc(p2, p3, point(0.0, 0.0), 1.0); let arc3 = arcseg(p3, p1);
let arcline = vec![arc1, arc2, arc3];
assert_eq!(arcline_is_valid(&arcline), ArclineValidation::Valid);
}
#[test]
fn test_is_valid_arcline_edge_case_very_small_segments() {
let arc1 = arcseg(point(0.0, 0.0), point(1e-6, 0.0));
let arc2 = arc(point(1e-6, 0.0), point(0.0, 0.0), point(5e-7, 0.0), 5e-7);
let arcline = vec![arc1, arc2];
assert_eq!(arcline_is_valid(&arcline), ArclineValidation::Valid);
}
#[test]
fn test_arcline200_validity_with_ccw_check() {
use crate::poly::data::arcline200;
let arcline = arcline200();
let result = arcline_is_valid(&arcline);
println!("arcline200 validation result: {:?}", result);
println!("arcline200 length: {}", arcline.len());
let area = crate::algo::arcline_area(&arcline);
println!("arcline200 area: {}", area);
println!("\nConnection and spiral info:");
println!("Arc 0: {} -> {}", arcline[0].a, arcline[0].b);
println!("Arc 1: {} -> {}", arcline[1].a, arcline[1].b);
let gap_0_1 = ((arcline[0].b.x - arcline[1].a.x).powi(2) + (arcline[0].b.y - arcline[1].a.y).powi(2)).sqrt();
println!("Gap between Arc 0.b and Arc 1.a: {}", gap_0_1);
println!("\nArc 98: {} -> {}", arcline[98].a, arcline[98].b);
println!("Arc 99: {} -> {}", arcline[99].a, arcline[99].b);
println!("Arc 100 (connection1): {} -> {} (is_seg: {})", arcline[100].a, arcline[100].b, arcline[100].is_seg());
let gap_99_100 = ((arcline[99].b.x - arcline[100].a.x).powi(2) + (arcline[99].b.y - arcline[100].a.y).powi(2)).sqrt();
println!("Gap between Arc 99.b and Arc 100.a: {}", gap_99_100);
println!("Arc 101 (spiral2_rev[0]): {} -> {} (is_seg: {})", arcline[101].a, arcline[101].b, arcline[101].is_seg());
let gap_100_101 = ((arcline[100].b.x - arcline[101].a.x).powi(2) + (arcline[100].b.y - arcline[101].a.y).powi(2)).sqrt();
println!("Gap between Arc 100.b and Arc 101.a: {}", gap_100_101);
let spiral2_start_len = arcline.len() - 101 - 1; println!("Expected spiral2_reversed length: {}", spiral2_start_len);
let last_spiral2_idx = 100 + spiral2_start_len;
if last_spiral2_idx < arcline.len() {
println!("Arc {} (spiral2_rev[last]): {} -> {} (is_seg: {})",
last_spiral2_idx, arcline[last_spiral2_idx].a, arcline[last_spiral2_idx].b, arcline[last_spiral2_idx].is_seg());
println!("Arc {} (connection2): {} -> {} (is_seg: {})",
last_spiral2_idx+1, arcline[last_spiral2_idx+1].a, arcline[last_spiral2_idx+1].b, arcline[last_spiral2_idx+1].is_seg());
let gap_spiral2_conn2 = ((arcline[last_spiral2_idx].b.x - arcline[last_spiral2_idx+1].a.x).powi(2)
+ (arcline[last_spiral2_idx].b.y - arcline[last_spiral2_idx+1].a.y).powi(2)).sqrt();
println!("Gap between spiral2_rev[last].b and connection2.a: {}", gap_spiral2_conn2);
}
if arcline.len() > 0 {
let last_idx = arcline.len() - 1;
let gap_close_loop = ((arcline[last_idx].b.x - arcline[0].a.x).powi(2)
+ (arcline[last_idx].b.y - arcline[0].a.y).powi(2)).sqrt();
println!("Gap between connection2.b and Arc 0.a (close loop): {}", gap_close_loop);
}
for i in 0..arcline.len() {
let arc = &arcline[i];
if arc.is_seg() {
let seg_contribution = arc.a.perp(arc.b);
if seg_contribution < -1e-10 {
println!("REVERSED SEGMENT at index {}: {} -> {}, contribution: {}",
i, arc.a, arc.b, seg_contribution);
}
}
}
assert!(matches!(result, ArclineValidation::Valid),
"Expected Valid for arcline200, got {:?}", result);
}
#[test]
fn test_arcline500_validity_with_ccw_check() {
use crate::poly::data::arcline500;
let arcline = arcline500();
let result = arcline_is_valid(&arcline);
println!("arcline500 validation result: {:?}", result);
let area = crate::algo::arcline_area(&arcline);
println!("arcline500 area: {}", area);
assert!(area > -1e-10, "arcline500 should be CCW (positive area), got {}", area);
}
#[test]
fn test_arcline1000_validity_with_ccw_check() {
use crate::poly::data::arcline1000;
let arcline = arcline1000();
let result = arcline_is_valid(&arcline);
println!("arcline1000 validation result: {:?}", result);
let area = crate::algo::arcline_area(&arcline);
println!("arcline1000 area: {}", area);
assert!(area > -1e-10, "arcline1000 should be CCW (positive area), got {}", area);
}
#[test]
fn test_reversed_segment_detection() {
let p0 = point(0.0, 0.0);
let p1 = point(1.0, 0.5);
let p2 = point(2.0, 0.0);
let p3 = point(1.0, -1.0);
let arc1 = arc_from_bulge(p0, p1, 0.1);
let arc2 = arc_from_bulge(p1, p2, 0.1);
let seg = arcseg(p2, p3); let arc3 = arc_from_bulge(p3, p0, 0.1);
let arcline = vec![arc1, arc2, seg, arc3];
let result = arcline_is_valid(&arcline);
match result {
ArclineValidation::Valid => {}, ArclineValidation::NotCCW(None) => {}, other => panic!("Unexpected result: {:?}", other),
}
}
#[test]
fn test_normal_arc_normal_segment_normal_arc() {
let p0 = point(0.0, 0.0);
let p1 = point(1.2, 0.6);
let p2 = point(2.0, 0.3);
let p3 = point(1.0, -0.8);
let arc1 = arc_from_bulge(p0, p1, 0.12);
let seg = arcseg(p1, p2);
let arc2 = arc_from_bulge(p2, p3, 0.15);
let arc3 = arc_from_bulge(p3, p0, 0.1);
let arcline = vec![arc1, seg, arc2, arc3];
let result = arcline_is_valid(&arcline);
assert!(matches!(result, ArclineValidation::Valid | ArclineValidation::NotCCW(None)),
"Expected Valid or CCW area check, got {:?}", result);
}
#[test]
fn test_reversed_arc_normal_segment() {
let p0 = point(0.0, 0.0);
let p1 = point(1.0, 0.0);
let p2 = point(1.0, 1.0);
let p3 = point(0.0, 1.0);
let arc1 = arc_from_bulge(p0, p1, -0.05);
let seg = arcseg(p1, p2);
let arc2 = arc_from_bulge(p2, p3, 0.05);
let arc3 = arc_from_bulge(p3, p0, 0.05);
let arcline = vec![arc1, seg, arc2, arc3];
let result = arcline_is_valid(&arcline);
let _ = result;
}
#[test]
fn test_all_negative_bulge_arcs() {
let p0 = point(0.0, 0.0);
let p1 = point(1.0, 0.0);
let p2 = point(1.0, 1.0);
let p3 = point(0.0, 1.0);
let arc1 = arc_from_bulge(p0, p1, -0.08);
let arc2 = arc_from_bulge(p1, p2, -0.08);
let arc3 = arc_from_bulge(p2, p3, -0.08);
let arc4 = arc_from_bulge(p3, p0, -0.08);
let arcline = vec![arc1, arc2, arc3, arc4];
let result = arcline_is_valid(&arcline);
let _ = result;
}
#[test]
fn test_mixed_bulge_with_segments() {
let p0 = point(0.0, 0.0);
let p1 = point(1.0, 0.0);
let p2 = point(1.0, 1.0);
let p3 = point(0.0, 1.0);
let arc1 = arc_from_bulge(p0, p1, 0.08);
let seg1 = arcseg(p1, p2);
let arc2 = arc_from_bulge(p2, p3, -0.08);
let seg2 = arcseg(p3, p0);
let arcline = vec![arc1, seg1, arc2, seg2];
let result = arcline_is_valid(&arcline);
let _ = result;
}
#[test]
fn test_alternating_bulge_signs() {
let p0 = point(0.0, 0.0);
let p1 = point(1.0, 0.0);
let p2 = point(1.0, 1.0);
let p3 = point(0.5, 1.5);
let p4 = point(0.0, 1.0);
let arc1 = arc_from_bulge(p0, p1, 0.1);
let arc2 = arc_from_bulge(p1, p2, -0.1);
let arc3 = arc_from_bulge(p2, p3, 0.08);
let arc4 = arc_from_bulge(p3, p4, -0.08);
let arc5 = arc_from_bulge(p4, p0, 0.08);
let arcline = vec![arc1, arc2, arc3, arc4, arc5];
let result = arcline_is_valid(&arcline);
let _ = result;
}
#[test]
fn test_reversed_segment_1() {
let p0 = point(0.0, 0.0);
let p1 = point(1.0, 0.0);
let p2 = point(2.0, 0.0);
let p3 = point(1.0, -1.0);
let arc1 = arc_from_bulge(p0, p1, 0.1);
let seg = arcseg(p1, p0); let arc2 = arc_from_bulge(p0, p2, 0.1);
let arc3 = arc_from_bulge(p2, p3, 0.1);
let arcline = vec![arc1, seg, arc2, arc3];
let result = arcline_is_valid(&arcline);
assert!(!matches!(result, ArclineValidation::Valid),
"Expected validation error for reversed segment, got Valid");
}
#[test]
fn test_reversed_segment_2() {
let p0 = point(0.0, 0.0);
let p1 = point(1.0, 0.0);
let p2 = point(2.0, 0.0);
let p3 = point(1.0, -1.0);
let arc1 = arc_from_bulge(p0, p1, 0.1);
let arc2 = arc_from_bulge(p1, p2, 0.1);
let seg = arcseg(p2, p1); let arc3 = arc_from_bulge(p1, p3, 0.1);
let arcline = vec![arc1, arc2, seg, arc3];
let result = arcline_is_valid(&arcline);
assert!(!matches!(result, ArclineValidation::Valid),
"Expected validation error for reversed segment, got Valid");
}
#[test]
fn test_reversed_segment_3() {
let p0 = point(0.0, 0.0);
let p1 = point(1.0, 0.0);
let p2 = point(2.0, 0.0);
let p3 = point(1.0, -1.0);
let arc1 = arc_from_bulge(p0, p1, 0.1);
let arc2 = arc_from_bulge(p1, p2, 0.1);
let arc3 = arc_from_bulge(p2, p3, 0.1);
let seg = arcseg(p3, p2);
let arcline = vec![arc1, arc2, arc3, seg];
let result = arcline_is_valid(&arcline);
assert!(!matches!(result, ArclineValidation::Valid),
"Expected validation error for reversed segment, got Valid");
}
#[test]
fn test_reversed_segment_as_first() {
let p0 = point(0.0, 0.0);
let p1 = point(1.0, 0.0);
let p2 = point(2.0, 0.0);
let p3 = point(1.0, -1.0);
let seg = arcseg(p1, p0); let arc1 = arc_from_bulge(p1, p2, 0.1);
let arc2 = arc_from_bulge(p2, p3, 0.1);
let arc3 = arc_from_bulge(p3, p0, 0.1);
let arcline = vec![seg, arc1, arc2, arc3];
let result = arcline_is_valid(&arcline);
assert!(matches!(result, ArclineValidation::GapBetweenArcs(_) | ArclineValidation::NotCCW(Some(_))),
"Expected validation error for reversed segment, got {:?}", result);
}
#[test]
fn test_reversed_segment_as_last() {
let p0 = point(0.0, 0.0);
let p1 = point(1.0, 0.0);
let p2 = point(2.0, 0.0);
let p3 = point(1.0, -1.0);
let arc1 = arc_from_bulge(p0, p1, 0.1);
let arc2 = arc_from_bulge(p1, p2, 0.1);
let arc3 = arc_from_bulge(p2, p3, 0.1);
let seg = arcseg(p0, p3);
let arcline = vec![arc1, arc2, arc3, seg];
let result = arcline_is_valid(&arcline);
assert!(!matches!(result, ArclineValidation::Valid),
"Expected validation error for reversed segment at end, got Valid");
}
#[test]
fn test_two_reversed_segments() {
let p0 = point(0.0, 0.0);
let p1 = point(1.0, 0.0);
let p2 = point(2.0, 0.0);
let arc1 = arc_from_bulge(p0, p1, 0.1);
let seg1 = arcseg(p1, p0); let arc2 = arc_from_bulge(p0, p2, 0.1);
let seg2 = arcseg(p2, p0);
let arcline = vec![arc1, seg1, arc2, seg2];
let result = arcline_is_valid(&arcline);
assert!(!matches!(result, ArclineValidation::Valid),
"Expected validation error for reversed segments, got Valid");
}
#[test]
fn test_three_reversed_segments() {
let p0 = point(0.0, 0.0);
let p1 = point(1.0, 0.0);
let p2 = point(2.0, 0.0);
let p3 = point(1.0, -1.0);
let seg1 = arcseg(p0, p1);
let seg2 = arcseg(p1, p0); let seg3 = arcseg(p0, p2);
let arc1 = arc_from_bulge(p2, p3, 0.1);
let arcline = vec![seg1, seg2, seg3, arc1];
let result = arcline_is_valid(&arcline);
assert!(!matches!(result, ArclineValidation::Valid),
"Expected validation error for reversed segments, got Valid");
}
#[test]
fn test_reversed_with_negative_bulge() {
let p0 = point(0.0, 0.0);
let p1 = point(1.0, 0.0);
let p2 = point(2.0, 0.0);
let p3 = point(1.0, -1.0);
let arc1 = arc_from_bulge(p0, p1, -0.2);
let seg = arcseg(p1, p0); let arc2 = arc_from_bulge(p0, p2, -0.2);
let arc3 = arc_from_bulge(p2, p3, -0.2);
let arcline = vec![arc1, seg, arc2, arc3];
let result = arcline_is_valid(&arcline);
assert!(!matches!(result, ArclineValidation::Valid),
"Expected validation error for reversed segment with negative bulge, got Valid");
}
#[test]
fn test_reversed_with_mixed_bulge() {
let p0 = point(0.0, 0.0);
let p1 = point(1.0, 0.0);
let p2 = point(2.0, 0.0);
let p3 = point(1.0, -1.0);
let arc1 = arc_from_bulge(p0, p1, 0.15);
let seg = arcseg(p1, p0); let arc2 = arc_from_bulge(p0, p2, -0.2);
let arc3 = arc_from_bulge(p2, p3, 0.15);
let arcline = vec![arc1, seg, arc2, arc3];
let result = arcline_is_valid(&arcline);
assert!(!matches!(result, ArclineValidation::Valid),
"Expected validation error for reversed segment with mixed bulge, got Valid");
}
#[test]
fn test_reversed_segment_multiple_positions() {
let p0 = point(0.0, 0.0);
let p1 = point(1.0, 0.0);
let p2 = point(1.0, 1.0);
let p3 = point(0.0, 1.0);
let seg1 = arcseg(p0, p1);
let seg2 = arcseg(p1, p0); let arc1 = arc_from_bulge(p0, p2, 0.1);
let arc2 = arc_from_bulge(p2, p3, 0.1);
let arcline = vec![seg1, seg2, arc1, arc2];
let result = arcline_is_valid(&arcline);
assert!(!matches!(result, ArclineValidation::Valid),
"Expected validation error for reversed segment, got Valid");
}
#[test]
fn test_connection_segment_with_numerical_drift() {
let p0 = point(0.0, 0.0);
let p1 = point(1.0, 0.0);
let p2 = point(2.0, 0.0);
let p3 = point(2.0, 1.0);
let p4 = point(0.0, 1.0);
let arc1 = arc_from_bulge(p0, p1, 0.1);
let arc1_end = arc1.b;
let connection = arcseg(arc1_end, p2);
let arc2 = arc_from_bulge(p2, p3, 0.1);
let arc2_end = arc2.b;
let connection2 = arcseg(arc2_end, p4);
let arc3 = arc_from_bulge(p4, p0, 0.1);
let arcline = vec![arc1, connection, arc2, connection2, arc3];
let result = arcline_is_valid(&arcline);
assert!(matches!(result, ArclineValidation::Valid),
"Expected valid arcline with proper connections, got {:?}", result);
}
#[test]
fn test_connection_endpoints_must_match_arc_endpoints() {
let p0 = point(0.0, 0.0);
let p1 = point(1.0, 0.0);
let p2 = point(2.0, 0.0);
let p3 = point(2.0, 1.0);
let arc1 = arc_from_bulge(p0, p1, 0.1);
let _arc1_end = arc1.b;
let connection = arcseg(point(0.5, 0.0), p2);
let arc2 = arc_from_bulge(p2, p3, 0.1);
let arcline = vec![arc1, connection, arc2];
let result = arcline_is_valid(&arcline);
assert!(matches!(result, ArclineValidation::GapBetweenArcs(_)),
"Expected GapBetweenArcs for mismatched connection, got {:?}", result);
}
}