use crate::arrangement::{Arrangement, Engine, 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<'_>);
fn visit_polygons<'a>(&'a self, _f: &mut dyn FnMut(&'a Polygon)) -> bool {
false
}
}
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)
}
}
fn visit_polygons<'a>(&'a self, f: &mut dyn FnMut(&'a Polygon)) -> bool {
f(self);
true
}
}
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)
}
}
fn visit_polygons<'a>(&'a self, f: &mut dyn FnMut(&'a Polygon)) -> bool {
self.iter().all(|x| x.visit_polygons(f))
}
}
impl<T: RingSource> RingSource for Vec<T> {
fn visit_rings(&self, f: &mut VertexVisitor<'_>) {
self.as_slice().visit_rings(f)
}
fn visit_polygons<'a>(&'a self, f: &mut dyn FnMut(&'a Polygon)) -> bool {
self.as_slice().visit_polygons(f)
}
}
impl<T: RingSource, const N: usize> RingSource for [T; N] {
fn visit_rings(&self, f: &mut VertexVisitor<'_>) {
self.as_slice().visit_rings(f)
}
fn visit_polygons<'a>(&'a self, f: &mut dyn FnMut(&'a Polygon)) -> bool {
self.as_slice().visit_polygons(f)
}
}
impl<T: RingSource + ?Sized> RingSource for &T {
fn visit_rings(&self, f: &mut VertexVisitor<'_>) {
(**self).visit_rings(f)
}
fn visit_polygons<'a>(&'a self, f: &mut dyn FnMut(&'a Polygon)) -> bool {
(**self).visit_polygons(f)
}
}
#[derive(Clone, Debug, Default)]
pub struct Boolean {
edges: Vec<InEdge>,
ring_starts: Vec<u32>,
fill: [FillRule; 2],
op: Op,
keep_collinear: bool,
clustering: Clustering,
error: Option<Error>,
}
impl Boolean {
pub fn new() -> Self {
Self::default()
}
pub fn clear(&mut self) {
self.edges.clear();
self.ring_starts.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
}
#[doc(hidden)]
pub fn monolithic(mut self, yes: bool) -> Self {
self.clustering = if yes {
Clustering::Never
} else {
Clustering::Auto
};
self
}
#[doc(hidden)]
pub fn force_clusters(mut self, yes: bool) -> Self {
self.clustering = if yes {
Clustering::Always
} else {
Clustering::Auto
};
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 starts = &mut self.ring_starts;
let error = &mut self.error;
rings.visit_rings(&mut |pts, tags| {
let n = pts.len();
starts.push(edges.len() as u32);
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 inside = move |w: [i32; 2]| op.apply(fill[0].is_inside(w[0]), fill[1].is_inside(w[1]));
let engine = crate::arrangement::engine();
if engine != Engine::Auto {
let boundary = crate::arrangement::boundary(&self.edges, inside, engine);
return Ok(assemble(boundary, self.keep_collinear));
}
if self.clustering != Clustering::Never
&& !ALWAYS_MONOLITHIC.with(|m| m.get())
&& let Some(tree) = crate::cluster::execute(
&self.edges,
&self.ring_starts,
inside,
self.keep_collinear,
self.clustering == Clustering::Always,
)
{
return Ok(tree);
}
let boundary = compute(&self.edges, inside);
Ok(assemble(boundary, self.keep_collinear))
}
}
std::thread_local! {
static ALWAYS_MONOLITHIC: core::cell::Cell<bool> = const { core::cell::Cell::new(false) };
}
#[doc(hidden)]
pub fn set_always_monolithic(yes: bool) {
ALWAYS_MONOLITHIC.with(|m| m.set(yes));
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
enum Clustering {
#[default]
Auto,
Never,
Always,
}
pub(crate) fn compute(edges: &[InEdge], inside: impl Fn([i32; 2]) -> bool + Sync) -> Vec<DirEdge> {
crate::arrangement::boundary(edges, inside, Engine::Auto)
}
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 n = 0usize;
clip.visit_rings(&mut |pts, _| n += pts.len());
let mut edges: Vec<InEdge> = Vec::with_capacity(n);
let mut ring_starts: Vec<u32> = Vec::new();
let mut err = None;
clip.visit_rings(&mut |pts, tags| {
ring_starts.push(edges.len() as u32);
if err.is_none()
&& let Some(&p) = pts.iter().find(|p| !p.in_range())
{
err = Some(Error::CoordinateOutOfRange(p));
}
let tag = |i: usize| tags.and_then(|t| t.get(i)).copied().unwrap_or(0);
let mut push = |i: usize, a: Point, b: Point| {
if a != b {
edges.push(InEdge {
a,
b,
tag: tag(i),
operand: 1,
});
}
};
for (i, w) in pts.windows(2).enumerate() {
push(i, w[0], w[1]);
}
if let (Some(&a), Some(&b)) = (pts.last(), pts.first()) {
push(pts.len() - 1, a, b);
}
});
if let Some(e) = err {
return Err(e);
}
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 first_open = path_starts.first().copied().unwrap_or(edges.len());
let frags = (!ALWAYS_MONOLITHIC.with(|m| m.get()))
.then(|| crate::clip::clustered(&edges, &ring_starts, first_open, &path_starts, rule))
.flatten()
.unwrap_or_else(|| clip_in_one_piece(&edges, rule));
let mut res = ClippedPaths::default();
let mut cur: Option<(bool, TaggedPath)> = None;
let mut path_idx = 0usize;
let mut cur_path = usize::MAX;
for &(a, b, src, ins) in &frags {
let o = (a, b, edges[src as usize].tag, src);
while path_idx < path_starts.len() && path_starts[path_idx] <= o.3 as usize {
path_idx += 1;
}
let pid = path_idx - 1;
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)
}
fn clip_in_one_piece(edges: &[InEdge], rule: FillRule) -> Vec<crate::clip::OpenFrag> {
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]);
}
}
arr.open_frags
.iter()
.zip(inside_flag)
.map(|(f, ins)| (f.a, f.b, f.src, ins))
.collect()
}