use axiolid_core::{Point2, Tolerance, Vec2};
use crate::minkowski::MorphologyBound;
use crate::offset::{offset_polygons, total_area, JoinStyle};
use crate::{canonical, validate_ring, OverlayError, Polygon, Ring};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct RegionEvidence {
pub polygons: usize,
pub holes: usize,
pub emptied: bool,
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct Region {
polygons: Vec<Polygon>,
evidence: RegionEvidence,
bound: Option<MorphologyBound>,
}
fn normalise(mut polygons: Vec<Polygon>) -> Vec<Polygon> {
polygons = polygons
.into_iter()
.map(|polygon| Polygon {
outer: canonical(polygon.outer, true),
holes: polygon
.holes
.into_iter()
.map(|hole| canonical(hole, false))
.collect(),
})
.collect();
for polygon in &mut polygons {
polygon.holes.sort_by(|a, b| {
a.points[0]
.x
.total_cmp(&b.points[0].x)
.then(a.points[0].y.total_cmp(&b.points[0].y))
});
}
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
}
impl Region {
#[must_use]
pub fn empty() -> Self {
Self::default()
}
pub fn new(polygons: Vec<Polygon>, tolerance: Tolerance) -> Result<Self, OverlayError> {
for polygon in &polygons {
validate_ring(&polygon.outer, tolerance)?;
for hole in &polygon.holes {
validate_ring(hole, tolerance)?;
}
}
Ok(Self::from_valid(normalise(polygons), false))
}
fn from_valid(polygons: Vec<Polygon>, had_input: bool) -> Self {
let evidence = RegionEvidence {
polygons: polygons.len(),
holes: polygons.iter().map(|p| p.holes.len()).sum(),
emptied: had_input && polygons.is_empty(),
};
Self {
polygons: normalise(polygons),
evidence,
bound: None,
}
}
pub(crate) fn from_valid_polygons(polygons: Vec<Polygon>, had_input: bool) -> Self {
Self::from_valid(polygons, had_input)
}
pub(crate) fn set_bound(&mut self, bound: MorphologyBound) {
self.bound = Some(bound);
}
#[must_use]
pub const fn bound(&self) -> Option<MorphologyBound> {
self.bound
}
#[must_use]
pub fn polygons(&self) -> &[Polygon] {
&self.polygons
}
#[must_use]
pub const fn evidence(&self) -> RegionEvidence {
self.evidence
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.polygons.is_empty()
}
#[must_use]
pub fn area(&self) -> f64 {
total_area(&self.polygons)
}
#[must_use]
pub fn component_count(&self) -> usize {
self.polygons.len()
}
#[must_use]
pub fn boundary_rings(&self) -> Vec<Ring> {
let mut rings: Vec<Ring> = self.polygons.iter().map(|p| p.outer.clone()).collect();
for polygon in &self.polygons {
rings.extend(polygon.holes.iter().cloned());
}
rings
}
pub fn union(&self, other: &Self, tolerance: Tolerance) -> Result<Self, OverlayError> {
self.combine(other, crate::OverlayOperation::Union, tolerance)
}
pub fn intersection(&self, other: &Self, tolerance: Tolerance) -> Result<Self, OverlayError> {
self.combine(other, crate::OverlayOperation::Intersection, tolerance)
}
pub fn difference(&self, other: &Self, tolerance: Tolerance) -> Result<Self, OverlayError> {
self.combine(other, crate::OverlayOperation::Difference, tolerance)
}
fn combine(
&self,
other: &Self,
operation: crate::OverlayOperation,
tolerance: Tolerance,
) -> Result<Self, OverlayError> {
let had_input = !self.is_empty() || !other.is_empty();
match operation {
crate::OverlayOperation::Union if other.is_empty() => {
return Ok(Self::from_valid(self.polygons.clone(), had_input));
}
crate::OverlayOperation::Union if self.is_empty() => {
return Ok(Self::from_valid(other.polygons.clone(), had_input));
}
crate::OverlayOperation::Intersection if self.is_empty() || other.is_empty() => {
return Ok(Self::from_valid(Vec::new(), had_input));
}
crate::OverlayOperation::Difference if other.is_empty() => {
return Ok(Self::from_valid(self.polygons.clone(), had_input));
}
crate::OverlayOperation::Difference if self.is_empty() => {
return Ok(Self::from_valid(Vec::new(), had_input));
}
_ => {}
}
let frame = axiolid_core::Frame2 {
origin: Point2::new(0.0, 0.0),
x: Vec2::new(1.0, 0.0),
y: Vec2::new(0.0, 1.0),
};
let subject = crate::OverlayInput {
frame,
polygons: self.polygons.clone(),
};
let clip = crate::OverlayInput {
frame,
polygons: other.polygons.clone(),
};
let result = crate::overlay(
&subject,
&clip,
operation,
crate::FillRule::NonZero,
tolerance,
)?;
Ok(Self::from_valid(result.polygons, had_input))
}
pub fn dilate(&self, radius: f64, tolerance: Tolerance) -> Result<Self, OverlayError> {
self.morphology(radius, tolerance)
}
pub fn erode(&self, radius: f64, tolerance: Tolerance) -> Result<Self, OverlayError> {
if radius < 0.0 {
return Err(OverlayError::InvalidOffsetDistance);
}
self.morphology(-radius, tolerance)
}
fn morphology(&self, distance: f64, tolerance: Tolerance) -> Result<Self, OverlayError> {
if !distance.is_finite() {
return Err(OverlayError::InvalidOffsetDistance);
}
if self.is_empty() || distance == 0.0 {
return Ok(Self::from_valid(self.polygons.clone(), false));
}
let join = JoinStyle::Round {
max_segment_ratio: 0.1,
};
let result = offset_polygons(&self.polygons, distance, join, tolerance)?;
Ok(Self::from_valid(result.polygons, true))
}
pub fn translate(&self, offset: Vec2) -> Result<Self, OverlayError> {
if !offset.is_finite() {
return Err(OverlayError::NonFinitePoint);
}
let shift = |ring: &Ring| Ring {
points: ring
.points
.iter()
.map(|p| Point2::new(p.x + offset.x, p.y + offset.y))
.collect(),
};
let polygons = self
.polygons
.iter()
.map(|polygon| Polygon {
outer: shift(&polygon.outer),
holes: polygon.holes.iter().map(shift).collect(),
})
.collect();
Ok(Self::from_valid(polygons, !self.is_empty()))
}
pub fn sweep(&self, direction: Vec2, tolerance: Tolerance) -> Result<Self, OverlayError> {
if !direction.is_finite() {
return Err(OverlayError::NonFinitePoint);
}
if self.is_empty() || direction.length() == 0.0 {
return Ok(Self::from_valid(self.polygons.clone(), false));
}
let moved = self.translate(direction)?;
let mut swept = self.union(&moved, tolerance)?;
for ring in self.boundary_rings() {
let count = ring.points.len();
for index in 0..count {
let a = ring.points[index];
let b = ring.points[(index + 1) % count];
let quad = Polygon {
outer: Ring {
points: vec![
a,
b,
Point2::new(b.x + direction.x, b.y + direction.y),
Point2::new(a.x + direction.x, a.y + direction.y),
],
},
holes: Vec::new(),
};
if let Ok(band) = Self::new(vec![quad], tolerance) {
swept = swept.union(&band, tolerance)?;
}
}
}
Ok(swept)
}
}