#![forbid(unsafe_code)]
mod arc;
mod arc_overlay;
mod arrangement;
mod circle;
mod exact_arc;
mod exact_overlay;
mod minkowski;
mod offset;
mod rectangle;
mod region;
mod settle;
mod visibility;
pub use arc::{
arc_edge_radius, arc_ring_area, reverse_arc_ring, validate_arc_ring, ArcRing, ArcVertex,
};
pub use arc_overlay::{arc_overlay, ArcOverlayEvidence, ArcOverlayResult, ArcPolygon};
pub use arrangement::{
ArcArrangement, ArrangementEdge, ArrangementRegion, EdgeSource, EdgeUse as ArrangementEdgeUse,
};
pub use circle::{
minimum_enclosing_circle, CircleError, CircleEvidence, EnclosingCircle, MinimumCircle,
};
pub use minkowski::{BoundSide, MinkowskiError, MorphologyBound};
pub use offset::{
offset_polygons, polygon_area, ring_area, stroke_polyline, total_area, CapStyle, JoinStyle,
OffsetEvidence, OffsetResult,
};
pub use rectangle::{
minimum_area_rectangle, MinimumRectangle, OrientedRectangle, RectangleError, RectangleEvidence,
};
pub use region::{Region, RegionEvidence};
pub use visibility::VisibilityError;
use axiolid_core::{Frame2, Point2, Polygon2, Tolerance};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FillRule {
EvenOdd,
NonZero,
Positive,
Negative,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OverlayOperation {
Intersection,
Union,
Difference,
Xor,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Ring {
pub points: Vec<Point2>,
}
impl From<Polygon2> for Ring {
fn from(polygon: Polygon2) -> Self {
Self {
points: polygon.vertices,
}
}
}
impl From<Ring> for Polygon2 {
fn from(ring: Ring) -> Self {
Self::new(ring.points)
}
}
impl From<&Ring> for Polygon2 {
fn from(ring: &Ring) -> Self {
Self::new(ring.points.clone())
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Polygon {
pub outer: Ring,
pub holes: Vec<Ring>,
}
impl Polygon {
pub fn outline(&self) -> Polygon2 {
Polygon2::from(&self.outer)
}
pub fn has_holes(&self) -> bool {
!self.holes.is_empty()
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct OverlayInput {
pub frame: Frame2,
pub polygons: Vec<Polygon>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum OverlayError {
InvalidFrame,
NonFinitePoint,
RingTooShort,
RepeatedVertex,
ZeroArea,
SelfIntersection,
HoleOutsideOuter,
InvalidOffsetDistance,
InvalidOffsetStyle,
ZeroRadiusArc,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct OverlayEvidence {
pub subject_rings: usize,
pub clip_rings: usize,
pub output_polygons: usize,
pub output_holes: usize,
}
#[derive(Debug, Clone, PartialEq)]
pub struct OverlayResult {
pub polygons: Vec<Polygon>,
pub evidence: OverlayEvidence,
}
fn signed(r: &Ring) -> f64 {
r.points
.iter()
.zip(r.points.iter().cycle().skip(1))
.take(r.points.len())
.map(|(a, b)| a.x * b.y - b.x * a.y)
.sum::<f64>()
* 0.5
}
fn cross(a: Point2, b: Point2, c: Point2) -> f64 {
(b - a).perp_dot(c - a)
}
fn segments_intersect(a: Point2, b: Point2, c: Point2, d: Point2, epsilon: f64) -> bool {
let ac = cross(a, b, c);
let ad = cross(a, b, d);
let ca = cross(c, d, a);
let cb = cross(c, d, b);
(ac.abs() <= epsilon && within_extent(a, b, c, epsilon))
|| (ad.abs() <= epsilon && within_extent(a, b, d, epsilon))
|| (ca.abs() <= epsilon && within_extent(c, d, a, epsilon))
|| (cb.abs() <= epsilon && within_extent(c, d, b, epsilon))
|| ((ac > 0.0) != (ad > 0.0) && (ca > 0.0) != (cb > 0.0))
}
fn within_extent(a: Point2, b: Point2, p: Point2, epsilon: f64) -> bool {
p.x >= a.x.min(b.x) - epsilon
&& p.x <= a.x.max(b.x) + epsilon
&& p.y >= a.y.min(b.y) - epsilon
&& p.y <= a.y.max(b.y) + epsilon
}
fn self_intersects(r: &Ring, t: Tolerance) -> bool {
let n = r.points.len();
for i in 0..n {
for j in i + 1..n {
if j == i + 1 || (i == 0 && j + 1 == n) {
continue;
}
if segments_intersect(
r.points[i],
r.points[(i + 1) % n],
r.points[j],
r.points[(j + 1) % n],
t.linear(),
) {
return true;
}
}
}
false
}
pub(crate) fn validate_ring(r: &Ring, t: Tolerance) -> Result<(), OverlayError> {
if r.points.len() < 3 {
return Err(OverlayError::RingTooShort);
};
if !r.points.iter().all(|p| p.is_finite()) {
return Err(OverlayError::NonFinitePoint);
};
if r.points
.iter()
.zip(r.points.iter().cycle().skip(1))
.take(r.points.len())
.any(|(a, b)| (*a - *b).length() <= t.linear())
{
return Err(OverlayError::RepeatedVertex);
};
if self_intersects(r, t) {
return Err(OverlayError::SelfIntersection);
}
if signed(r).abs() <= t.linear().powi(2) {
return Err(OverlayError::ZeroArea);
};
Ok(())
}
fn validate(input: &OverlayInput, t: Tolerance) -> Result<(), OverlayError> {
let f = input.frame;
if !f.origin.is_finite()
|| !f.x.is_finite()
|| !f.y.is_finite()
|| (f.x.length() - 1.).abs() > t.linear()
|| (f.y.length() - 1.).abs() > t.linear()
|| f.x.dot(f.y).abs() > t.linear()
|| f.x.perp_dot(f.y) <= 0.
{
return Err(OverlayError::InvalidFrame);
}
for p in &input.polygons {
validate_ring(&p.outer, t)?;
for h in &p.holes {
validate_ring(h, t)?;
if hole_outside(&p.outer, h, t) {
return Err(OverlayError::HoleOutsideOuter);
}
}
}
Ok(())
}
fn hole_outside(outer: &Ring, hole: &Ring, t: Tolerance) -> bool {
let n = outer.points.len();
let on_boundary = |q: Point2| {
(0..n).any(|i| {
let (a, b) = (outer.points[i], outer.points[(i + 1) % n]);
cross(a, b, q).abs() <= t.linear() && within_extent(a, b, q, t.linear())
})
};
hole.points
.iter()
.any(|&q| !on_boundary(q) && !contains(outer, q))
}
fn contains(r: &Ring, p: Point2) -> bool {
let mut inside = false;
for (a, b) in r
.points
.iter()
.zip(r.points.iter().cycle().skip(1))
.take(r.points.len())
{
if (a.y > p.y) != (b.y > p.y) && p.x < (b.x - a.x) * (p.y - a.y) / (b.y - a.y) + a.x {
inside = !inside
}
}
inside
}
pub(crate) fn canonical(mut r: Ring, want_positive: bool) -> Ring {
if (signed(&r) > 0.) != want_positive {
r.points.reverse()
};
let k = r
.points
.iter()
.enumerate()
.min_by(|a, b| {
a.1.x
.total_cmp(&b.1.x)
.then(a.1.y.total_cmp(&b.1.y))
.then(a.0.cmp(&b.0))
})
.map(|x| x.0)
.unwrap_or(0);
r.points.rotate_left(k);
r
}
pub fn overlay(
subject: &OverlayInput,
clip: &OverlayInput,
operation: OverlayOperation,
fill: FillRule,
tolerance: Tolerance,
) -> Result<OverlayResult, OverlayError> {
validate(subject, tolerance)?;
validate(clip, tolerance)?;
if subject.frame != clip.frame {
return Err(OverlayError::InvalidFrame);
};
let rings = exact_overlay::boolean(&subject.polygons, &clip.polygons, operation, fill)?;
let polygons = settle::settle(canonical_polygons(rings), tolerance);
let evidence = OverlayEvidence {
subject_rings: subject.polygons.iter().map(|p| 1 + p.holes.len()).sum(),
clip_rings: clip.polygons.iter().map(|p| 1 + p.holes.len()).sum(),
output_polygons: polygons.len(),
output_holes: polygons.iter().map(|polygon| polygon.holes.len()).sum(),
};
Ok(OverlayResult { polygons, evidence })
}
pub fn union_soup(rings: &[Ring], tolerance: Tolerance) -> Result<Vec<Polygon>, OverlayError> {
for ring in rings {
validate_ring(ring, tolerance)?;
}
if rings.is_empty() {
return Ok(Vec::new());
}
let subject: Vec<Polygon> = rings
.iter()
.map(|ring| Polygon {
outer: ring.clone(),
holes: Vec::new(),
})
.collect();
let rings = exact_overlay::boolean(&subject, &[], OverlayOperation::Union, FillRule::NonZero)?;
Ok(settle::settle(canonical_polygons(rings), tolerance))
}
fn canonical_polygons(rings: Vec<(Ring, Vec<Ring>)>) -> Vec<Polygon> {
let mut polygons: Vec<Polygon> = rings
.into_iter()
.map(|(outer, holes)| Polygon {
outer: canonical(outer, true),
holes: holes.into_iter().map(|h| canonical(h, false)).collect(),
})
.collect();
polygons.sort_by(|a, b| {
a.outer.points[0]
.x
.total_cmp(&b.outer.points[0].x)
.then(a.outer.points[0].y.total_cmp(&b.outer.points[0].y))
});
polygons
}