use crate::arrangement::{Arrangement, InEdge, Noding};
use crate::assemble::{DirEdge, assemble};
use crate::error::{Error, Result};
use crate::geom::{
Path, Point, PolyTree, Polygon, PolygonSet, Ring, TaggedPath, TaggedPolygon, TaggedRing,
};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum FillRule {
EvenOdd,
#[default]
NonZero,
Positive,
Negative,
}
impl FillRule {
#[inline]
pub fn is_inside(self, w: i32) -> bool {
match self {
FillRule::EvenOdd => w & 1 != 0,
FillRule::NonZero => w != 0,
FillRule::Positive => w > 0,
FillRule::Negative => w < 0,
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Op {
#[default]
Union,
Intersection,
Difference,
Xor,
}
impl Op {
#[inline]
fn apply(self, s: bool, c: bool) -> bool {
match self {
Op::Union => s || c,
Op::Intersection => s && c,
Op::Difference => s && !c,
Op::Xor => s != c,
}
}
}
pub type VertexVisitor<'a> = dyn FnMut(&[Point], Option<&[u64]>) + 'a;
pub trait RingSource {
fn visit_rings(&self, f: &mut VertexVisitor<'_>);
}
impl RingSource for Ring {
fn visit_rings(&self, f: &mut VertexVisitor<'_>) {
f(&self.0, None)
}
}
impl RingSource for Vec<Point> {
fn visit_rings(&self, f: &mut VertexVisitor<'_>) {
f(self, None)
}
}
impl RingSource for TaggedRing {
fn visit_rings(&self, f: &mut VertexVisitor<'_>) {
f(&self.points, Some(&self.tags))
}
}
impl RingSource for Polygon {
fn visit_rings(&self, f: &mut VertexVisitor<'_>) {
for r in self.rings() {
f(&r.0, None)
}
}
}
impl RingSource for TaggedPolygon {
fn visit_rings(&self, f: &mut VertexVisitor<'_>) {
f(&self.outer.points, Some(&self.outer.tags));
for h in &self.holes {
f(&h.points, Some(&h.tags));
}
}
}
impl RingSource for PolyTree {
fn visit_rings(&self, f: &mut VertexVisitor<'_>) {
for n in &self.nodes {
f(&n.ring.0, Some(&n.tags))
}
}
}
impl<T: RingSource> RingSource for [T] {
fn visit_rings(&self, f: &mut VertexVisitor<'_>) {
for x in self {
x.visit_rings(f)
}
}
}
impl<T: RingSource> RingSource for Vec<T> {
fn visit_rings(&self, f: &mut VertexVisitor<'_>) {
self.as_slice().visit_rings(f)
}
}
impl<T: RingSource, const N: usize> RingSource for [T; N] {
fn visit_rings(&self, f: &mut VertexVisitor<'_>) {
self.as_slice().visit_rings(f)
}
}
impl<T: RingSource + ?Sized> RingSource for &T {
fn visit_rings(&self, f: &mut VertexVisitor<'_>) {
(**self).visit_rings(f)
}
}
#[derive(Clone, Debug, Default)]
pub struct Boolean {
edges: Vec<InEdge>,
fill: [FillRule; 2],
op: Op,
keep_collinear: bool,
error: Option<Error>,
}
impl Boolean {
pub fn new() -> Self {
Self::default()
}
pub fn clear(&mut self) {
self.edges.clear();
self.error = None;
}
pub fn subject(mut self, rings: &(impl RingSource + ?Sized), rule: FillRule) -> Self {
self.add_subject(rings, rule);
self
}
pub fn clip(mut self, rings: &(impl RingSource + ?Sized), rule: FillRule) -> Self {
self.add_clip(rings, rule);
self
}
pub fn op(mut self, op: Op) -> Self {
self.op = op;
self
}
pub fn keep_collinear(mut self, keep: bool) -> Self {
self.keep_collinear = keep;
self
}
pub fn add_subject(&mut self, rings: &(impl RingSource + ?Sized), rule: FillRule) -> &mut Self {
self.fill[0] = rule;
self.add_rings(rings, 0);
self
}
pub fn add_clip(&mut self, rings: &(impl RingSource + ?Sized), rule: FillRule) -> &mut Self {
self.fill[1] = rule;
self.add_rings(rings, 1);
self
}
pub fn set_op(&mut self, op: Op) -> &mut Self {
self.op = op;
self
}
pub fn set_keep_collinear(&mut self, keep: bool) -> &mut Self {
self.keep_collinear = keep;
self
}
fn add_rings(&mut self, rings: &(impl RingSource + ?Sized), operand: u8) {
let edges = &mut self.edges;
let error = &mut self.error;
rings.visit_rings(&mut |pts, tags| {
let n = pts.len();
if error.is_none()
&& let Some(&p) = pts.iter().find(|p| !p.in_range())
{
*error = Some(Error::CoordinateOutOfRange(p));
}
for i in 0..n {
let a = pts[i];
let b = pts[(i + 1) % n];
if a != b {
let tag = tags.and_then(|t| t.get(i)).copied().unwrap_or(0);
edges.push(InEdge { a, b, tag, operand });
}
}
});
}
pub fn execute(&mut self) -> Result<PolygonSet> {
Ok(self.execute_tree()?.to_polygon_set())
}
pub fn execute_tagged(&mut self) -> Result<Vec<TaggedPolygon>> {
Ok(self.execute_tree()?.to_tagged_polygons())
}
pub fn execute_tree(&mut self) -> Result<PolyTree> {
if let Some(e) = &self.error {
return Err(e.clone());
}
if self.edges.len() > u32::MAX as usize / 2 {
return Err(Error::TooLarge);
}
let (fill, op) = (self.fill, self.op);
let boundary = compute(&self.edges, |w| {
op.apply(fill[0].is_inside(w[0]), fill[1].is_inside(w[1]))
});
Ok(assemble(boundary, self.keep_collinear))
}
}
fn compute(edges: &[InEdge], inside: impl Fn([i32; 2]) -> bool) -> Vec<DirEdge> {
let Ok(arr) = Arrangement::build(edges, Noding::Snap) else {
unreachable!("snap rounding never fails")
};
let mut out = Vec::new();
for (e, wb) in arr.edges.iter().zip(arr.below.iter()) {
let wa = [wb[0] + e.delta[0], wb[1] + e.delta[1]];
let ib = inside(*wb);
let ia = inside(wa);
if ib != ia {
if ia {
out.push(DirEdge {
from: e.lo,
to: e.hi,
tag: e.tag,
});
} else {
out.push(DirEdge {
from: e.hi,
to: e.lo,
tag: e.tag,
});
}
}
}
out
}
pub fn boolean(
op: Op,
subject: &(impl RingSource + ?Sized),
clip: &(impl RingSource + ?Sized),
rule: FillRule,
) -> Result<PolygonSet> {
Boolean::new()
.subject(subject, rule)
.clip(clip, rule)
.op(op)
.execute()
}
pub fn union_all(rings: &(impl RingSource + ?Sized), rule: FillRule) -> Result<PolygonSet> {
Boolean::new().subject(rings, rule).execute()
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct ClippedPaths {
pub inside: Vec<TaggedPath>,
pub outside: Vec<TaggedPath>,
}
impl ClippedPaths {
pub fn inside_paths(&self) -> Vec<Path> {
self.inside.iter().map(|p| Path(p.points.clone())).collect()
}
pub fn outside_paths(&self) -> Vec<Path> {
self.outside
.iter()
.map(|p| Path(p.points.clone()))
.collect()
}
}
pub trait PathSource {
fn visit_paths(&self, f: &mut VertexVisitor<'_>);
}
impl PathSource for Path {
fn visit_paths(&self, f: &mut VertexVisitor<'_>) {
f(&self.0, None)
}
}
impl PathSource for TaggedPath {
fn visit_paths(&self, f: &mut VertexVisitor<'_>) {
f(&self.points, Some(&self.tags))
}
}
impl<T: PathSource> PathSource for [T] {
fn visit_paths(&self, f: &mut VertexVisitor<'_>) {
for x in self {
x.visit_paths(f)
}
}
}
impl<T: PathSource> PathSource for Vec<T> {
fn visit_paths(&self, f: &mut VertexVisitor<'_>) {
self.as_slice().visit_paths(f)
}
}
impl<T: PathSource + ?Sized> PathSource for &T {
fn visit_paths(&self, f: &mut VertexVisitor<'_>) {
(**self).visit_paths(f)
}
}
pub fn clip_paths(
paths: &(impl PathSource + ?Sized),
clip: &(impl RingSource + ?Sized),
rule: FillRule,
) -> Result<ClippedPaths> {
let mut eng = Boolean::new();
eng.add_clip(clip, rule);
if let Some(e) = eng.error.take() {
return Err(e);
}
let mut edges = core::mem::take(&mut eng.edges);
let mut path_starts: Vec<usize> = Vec::new();
let mut err = None;
paths.visit_paths(&mut |pts, tags| {
path_starts.push(edges.len());
if err.is_none()
&& let Some(&p) = pts.iter().find(|p| !p.in_range())
{
err = Some(Error::CoordinateOutOfRange(p));
}
for (i, w) in pts.windows(2).enumerate() {
if w[0] != w[1] {
let tag = tags.and_then(|t| t.get(i)).copied().unwrap_or(0);
edges.push(InEdge {
a: w[0],
b: w[1],
tag,
operand: 2,
});
}
}
});
if let Some(e) = err {
return Err(e);
}
let Ok(arr) = Arrangement::build(&edges, Noding::Snap) else {
unreachable!("snap rounding never fails")
};
let mut inside_flag = vec![false; arr.open_frags.len()];
for (k, e) in arr.edges.iter().enumerate() {
if let Some(o) = e.open {
let (wb, wa) = arr.sides(k);
inside_flag[o as usize] = rule.is_inside(wb[1]) || rule.is_inside(wa[1]);
}
}
let mut res = ClippedPaths::default();
let mut cur: Option<(bool, TaggedPath)> = None;
let mut path_idx = 0usize;
let mut cur_path = usize::MAX;
for (k, fr) in arr.open_frags.iter().enumerate() {
let o = (fr.a, fr.b, edges[fr.src as usize].tag, fr.src);
while path_idx < path_starts.len() && path_starts[path_idx] <= o.3 as usize {
path_idx += 1;
}
let pid = path_idx - 1;
let ins = inside_flag[k];
let cont = matches!(&cur, Some((f, p)) if *f == ins && cur_path == pid && p.points.last() == Some(&o.0));
if !cont {
if let Some((f, p)) = cur.take() {
if f {
res.inside.push(p)
} else {
res.outside.push(p)
}
}
cur = Some((
ins,
TaggedPath {
points: vec![o.0],
tags: Vec::new(),
},
));
cur_path = pid;
}
let p = &mut cur.as_mut().unwrap().1;
p.points.push(o.1);
p.tags.push(o.2);
}
if let Some((f, p)) = cur.take() {
if f {
res.inside.push(p)
} else {
res.outside.push(p)
}
}
Ok(res)
}