use axiolid_core::{Point2, Tolerance};
use crate::OverlayError;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ArcVertex {
pub point: Point2,
pub bulge: f64,
}
impl ArcVertex {
pub const fn straight(point: Point2) -> Self {
Self { point, bulge: 0.0 }
}
pub const fn bulged(point: Point2, bulge: f64) -> Self {
Self { point, bulge }
}
pub fn is_straight(&self) -> bool {
self.bulge == 0.0
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct ArcRing {
pub vertices: Vec<ArcVertex>,
}
impl ArcRing {
pub fn new(vertices: Vec<ArcVertex>) -> Self {
Self { vertices }
}
pub fn from_points(points: &[Point2]) -> Self {
Self {
vertices: points.iter().copied().map(ArcVertex::straight).collect(),
}
}
pub fn circle(centre: Point2, radius: f64) -> Self {
let left = Point2::new(centre.x - radius, centre.y);
let right = Point2::new(centre.x + radius, centre.y);
Self {
vertices: vec![ArcVertex::bulged(left, 1.0), ArcVertex::bulged(right, 1.0)],
}
}
pub fn edge_count(&self) -> usize {
self.vertices.len()
}
pub fn is_polygonal(&self) -> bool {
self.vertices.iter().all(ArcVertex::is_straight)
}
}
pub fn arc_ring_area(ring: &ArcRing) -> f64 {
let count = ring.vertices.len();
if count < 2 {
return 0.0;
}
let mut area = 0.0;
for index in 0..count {
let from = ring.vertices[index];
let to = ring.vertices[(index + 1) % count];
area += from.point.x * to.point.y - to.point.x * from.point.y;
}
area *= 0.5;
for index in 0..count {
let from = ring.vertices[index];
if from.is_straight() {
continue;
}
let to = ring.vertices[(index + 1) % count];
let chord = (to.point - from.point).length();
if chord == 0.0 {
continue;
}
let bulge = from.bulge;
let theta = 4.0 * bulge.atan();
let radius = chord * (1.0 + bulge * bulge) / (4.0 * bulge.abs());
area += 0.5 * radius * radius * (theta - theta.sin());
}
area
}
pub fn arc_edge_radius(from: ArcVertex, to: ArcVertex) -> Option<f64> {
if from.is_straight() {
return None;
}
let chord = (to.point - from.point).length();
if chord == 0.0 {
return None;
}
let bulge = from.bulge;
Some(chord * (1.0 + bulge * bulge) / (4.0 * bulge.abs()))
}
pub fn validate_arc_ring(ring: &ArcRing, tolerance: Tolerance) -> Result<(), OverlayError> {
let count = ring.vertices.len();
if count < 2 {
return Err(OverlayError::RingTooShort);
}
if ring.is_polygonal() && count < 3 {
return Err(OverlayError::RingTooShort);
}
if !ring
.vertices
.iter()
.all(|vertex| vertex.point.is_finite() && vertex.bulge.is_finite())
{
return Err(OverlayError::NonFinitePoint);
}
for index in 0..count {
let from = ring.vertices[index];
let to = ring.vertices[(index + 1) % count];
if (to.point - from.point).length() <= tolerance.linear() {
return Err(OverlayError::RepeatedVertex);
}
if let Some(radius) = arc_edge_radius(from, to) {
if radius <= tolerance.linear() {
return Err(OverlayError::ZeroRadiusArc);
}
}
}
if arc_ring_area(ring).abs() <= tolerance.linear().powi(2) {
return Err(OverlayError::ZeroArea);
}
Ok(())
}
pub fn reverse_arc_ring(ring: &ArcRing) -> ArcRing {
let count = ring.vertices.len();
if count == 0 {
return ArcRing::new(Vec::new());
}
let mut vertices = Vec::with_capacity(count);
for index in (0..count).rev() {
let point = ring.vertices[index].point;
let predecessor = (index + count - 1) % count;
vertices.push(ArcVertex::bulged(point, -ring.vertices[predecessor].bulge));
}
ArcRing::new(vertices)
}