Skip to main content

polygon_unionfind/
lib.rs

1// SPDX-FileCopyrightText: 2026 polygon_unionfind contributors
2//
3// SPDX-License-Identifier: MIT OR Apache-2.0
4
5use std::{collections::BTreeSet, marker::PhantomData};
6
7use i_overlay::{
8    core::{fill_rule::FillRule, overlay_rule::OverlayRule},
9    float::single::SingleFloatOverlay,
10    i_float::float::compatible::FloatPointCompatible,
11};
12
13use maplike::{Clear, Get, Insert, IntoIter, KeyedCollection, Push, Set};
14use num_traits::{FromPrimitive, ToPrimitive};
15use rstar::{
16    AABB, Envelope, RTree, RTreeNum, RTreeObject,
17    primitives::{GeomWithData, Rectangle},
18};
19use rstared::AsRefRTree;
20#[cfg(feature = "undoredo")]
21use std::collections::BTreeMap;
22#[cfg(feature = "undoredo")]
23use undoredo::{ApplyDelta, Delta, FlushDelta, Recorder};
24
25use crate::unionfind::UnionFind;
26
27#[cfg(feature = "std")]
28extern crate std;
29
30// No feature for `alloc` because it would be always enabled anyway.
31extern crate alloc;
32
33mod unionfind;
34
35#[derive(Clone, Debug)]
36pub struct Polygon<K, W = ()> {
37    pub vertices: Vec<Point<K>>,
38    pub weight: W,
39}
40
41#[derive(Clone, Copy, Debug)]
42pub struct Point<K> {
43    pub x: K,
44    pub y: K,
45}
46
47impl<K: Copy> From<[K; 2]> for Point<K> {
48    #[inline]
49    fn from(coords: [K; 2]) -> Self {
50        Self {
51            x: coords[0],
52            y: coords[1],
53        }
54    }
55}
56
57impl<K: FromPrimitive + ToPrimitive> FloatPointCompatible<f64> for Point<K>
58where
59    Point<K>: Copy,
60{
61    #[inline]
62    fn from_xy(x: f64, y: f64) -> Self {
63        Self {
64            x: K::from_f64(x).unwrap(),
65            y: K::from_f64(y).unwrap(),
66        }
67    }
68
69    #[inline]
70    fn x(&self) -> f64 {
71        self.x.to_f64().unwrap()
72    }
73
74    #[inline]
75    fn y(&self) -> f64 {
76        self.y.to_f64().unwrap()
77    }
78}
79
80#[derive(Clone, Debug)]
81pub struct PolygonUnionFind<
82    K: RTreeNum,
83    W = (),
84    PC: KeyedCollection = Vec<Polygon<K, W>>,
85    PR: KeyedCollection = AsRefRTree<GeomWithData<Rectangle<[K; 2]>, usize>>,
86    UFPC = Vec<usize>,
87    UFRC = Vec<usize>,
88> {
89    polygons: PC,
90    rtree: PR,
91    unionfind: UnionFind<UFPC, UFRC>,
92    scalar_marker: PhantomData<K>,
93    polygon_weight_marker: PhantomData<W>,
94}
95
96impl<
97    K: RTreeNum,
98    W,
99    PC: Default + KeyedCollection,
100    PR: Default + KeyedCollection,
101    UFPC: Default,
102    UFRC: Default,
103> PolygonUnionFind<K, W, PC, PR, UFPC, UFRC>
104{
105    #[inline]
106    pub fn new() -> Self {
107        Self {
108            polygons: Default::default(),
109            rtree: Default::default(),
110            unionfind: UnionFind::new(),
111            scalar_marker: PhantomData,
112            polygon_weight_marker: PhantomData,
113        }
114    }
115}
116
117impl<K: RTreeNum, W, PC: KeyedCollection, PR: KeyedCollection, UFPC, UFRC>
118    PolygonUnionFind<K, W, PC, PR, UFPC, UFRC>
119{
120    #[inline]
121    pub fn raw_polygons(&self) -> &PC {
122        &self.polygons
123    }
124
125    #[inline]
126    pub fn rtree(&self) -> &PR {
127        &self.rtree
128    }
129
130    #[inline]
131    pub fn unionfind(&self) -> &UnionFind<UFPC, UFRC> {
132        &self.unionfind
133    }
134
135    /// Dissolve the polygon-union-find, returning its internal polygons,
136    /// R-tree, and union-find, ceding ownership over them.
137    #[inline]
138    pub fn dissolve(self) -> (PC, PR, UnionFind<UFPC, UFRC>) {
139        (self.polygons, self.rtree, self.unionfind)
140    }
141}
142
143impl<
144    K: FromPrimitive + RTreeNum + ToPrimitive,
145    W,
146    PC: Clone + IntoIter<usize> + Get<usize, Value = Polygon<K, W>> + Push<usize> + Set<usize>,
147    PR: AsRef<RTree<GeomWithData<Rectangle<[K; 2]>, usize>>>
148        + Insert<GeomWithData<Rectangle<[K; 2]>, usize>, Value = ()>,
149    UFPC: Get<usize, Value = usize> + Push<usize> + Set<usize>,
150    UFRC: Get<usize, Value = usize> + Push<usize> + Set<usize>,
151> PolygonUnionFind<K, W, PC, PR, UFPC, UFRC>
152where
153    Point<K>: Copy,
154{
155    #[inline]
156    pub fn polygons(&mut self) -> impl Iterator<Item = &PC::Value> {
157        let mut deduplicating_set = BTreeSet::new();
158
159        for (i, _polygon) in self.polygons.clone().into_iter() {
160            deduplicating_set.insert(self.unionfind.find(i));
161        }
162
163        IntoIterator::into_iter(deduplicating_set).map(|i| self.polygons.get(&i).unwrap())
164    }
165}
166
167impl<
168    K: FromPrimitive + RTreeNum + ToPrimitive,
169    W: Clone,
170    PC: Get<usize, Value = Polygon<K, W>> + Push<usize> + Set<usize>,
171    PR: AsRef<RTree<GeomWithData<Rectangle<[K; 2]>, usize>>>
172        + Insert<GeomWithData<Rectangle<[K; 2]>, usize>, Value = ()>,
173    UFPC: Get<usize, Value = usize> + Push<usize> + Set<usize>,
174    UFRC: Get<usize, Value = usize> + Push<usize> + Set<usize>,
175> PolygonUnionFind<K, W, PC, PR, UFPC, UFRC>
176where
177    Point<K>: Copy,
178{
179    pub fn insert(&mut self, mut polygon: Polygon<K, W>) -> usize {
180        let new_polygon_index = self.unionfind.new_set();
181
182        let rectangle = Self::rectangle_from_polygon(&polygon);
183        self.rtree
184            .insert(GeomWithData::new(rectangle.clone(), new_polygon_index), ());
185
186        let id = self.polygons.push(polygon.clone());
187
188        for neighbor in self
189            .rtree
190            .as_ref()
191            .locate_in_envelope_intersecting(&rectangle.envelope())
192        {
193            let neighbor_representative = self.unionfind.find_compress(neighbor.data);
194
195            let vertices: Vec<[f64; 2]> = polygon
196                .vertices
197                .iter()
198                .map(|vertex| [vertex.x(), vertex.y()])
199                .collect();
200            let neighbor_vertices: Vec<[f64; 2]> = self
201                .polygons
202                .get(&neighbor_representative)
203                .unwrap()
204                .vertices
205                .iter()
206                .map(|vertex| [vertex.x(), vertex.y()])
207                .collect();
208            let union = vertices.overlay(&neighbor_vertices, OverlayRule::Union, FillRule::EvenOdd);
209
210            if union.len() >= 2 {
211                continue;
212            }
213
214            if let Some(union) = union.get(&0) {
215                self.unionfind
216                    .union(neighbor_representative, new_polygon_index);
217
218                //let representative = self.unionfind.find_compress(new_polygon_index);
219                let representative = self.unionfind.find(new_polygon_index);
220                polygon = Polygon {
221                    vertices: union[0]
222                        .iter()
223                        .map(|vertex| Point {
224                            x: K::from_f64(vertex[0]).unwrap(),
225                            y: K::from_f64(vertex[1]).unwrap(),
226                        })
227                        .collect(),
228                    weight: self.polygons.get(&representative).unwrap().weight.clone(),
229                };
230                self.polygons.set(representative, polygon.clone());
231            }
232        }
233
234        id
235    }
236
237    pub fn set_weight(&mut self, id: usize, weight: W) {
238        let polygon = self.polygons.get(&id).unwrap();
239        self.polygons.set(
240            id,
241            Polygon {
242                vertices: polygon.vertices.clone(),
243                weight,
244            },
245        );
246    }
247
248    #[inline]
249    pub fn find(&self, index: usize) -> &Polygon<K, W> {
250        self.polygons.get(&self.unionfind.find(index)).unwrap()
251    }
252
253    pub fn find_compress(&mut self, index: usize) -> &Polygon<K, W> {
254        self.polygons
255            .get(&self.unionfind.find_compress(index))
256            .unwrap()
257    }
258
259    fn rectangle_from_polygon(polygon: &Polygon<K, W>) -> Rectangle<[K; 2]> {
260        Rectangle::from_aabb(
261            polygon
262                .vertices
263                .iter()
264                .fold(AABB::new_empty(), |aabb, vertex| {
265                    aabb.merged(&AABB::from_point([vertex.x, vertex.y]))
266                }),
267        )
268    }
269}
270
271impl<K: RTreeNum, W, PC: Clear, PR: Clear, UFPC: Clear, UFRC: Clear>
272    PolygonUnionFind<K, W, PC, PR, UFPC, UFRC>
273{
274    pub fn clear(&mut self) {
275        self.polygons.clear();
276        self.rtree.clear();
277        self.unionfind.clear();
278    }
279}
280
281#[cfg(feature = "undoredo")]
282pub type RecordingPolygonUnionFind<K, W = ()> = PolygonUnionFind<
283    K,
284    W,
285    Recorder<Vec<Polygon<K, W>>, BTreeMap<usize, Polygon<K, W>>>,
286    Recorder<RTree<GeomWithData<Rectangle<[K; 2]>, usize>>>,
287    Recorder<Vec<usize>>,
288    Recorder<Vec<usize>>,
289>;
290
291#[cfg(feature = "undoredo")]
292pub type PolygonUnionFindDelta<K, W = ()> = PolygonUnionFind<
293    K,
294    W,
295    BTreeMap<usize, Polygon<K, W>>,
296    BTreeMap<GeomWithData<Rectangle<[K; 2]>, usize>, ()>,
297    BTreeMap<usize, usize>,
298    BTreeMap<usize, usize>,
299>;
300
301#[cfg(feature = "undoredo")]
302impl<
303    K: RTreeNum,
304    W: Clone,
305    PCE: Clone + KeyedCollection,
306    PC: KeyedCollection + Clone + ApplyDelta<PCE>,
307    PRE: Clone + KeyedCollection,
308    PR: KeyedCollection + Clone + ApplyDelta<PRE>,
309    UFPCE: Clone + KeyedCollection,
310    UFPC: Clone + ApplyDelta<UFPCE>,
311    UFRCE: Clone + KeyedCollection,
312    UFRC: Clone + ApplyDelta<UFRCE>,
313> ApplyDelta<PolygonUnionFind<K, W, PCE, PRE, UFPCE, UFRCE>>
314    for PolygonUnionFind<K, W, PC, PR, UFPC, UFRC>
315{
316    fn apply_delta(&mut self, delta: &Delta<PolygonUnionFind<K, W, PCE, PRE, UFPCE, UFRCE>>) {
317        let (removed, inserted) = delta.clone().dissolve();
318
319        let polygons_delta = Delta::with_removed_inserted(removed.polygons, inserted.polygons);
320        self.polygons.apply_delta(&polygons_delta);
321
322        let rtree_delta = Delta::with_removed_inserted(removed.rtree, inserted.rtree);
323        self.rtree.apply_delta(&rtree_delta);
324
325        let unionfind_delta = Delta::with_removed_inserted(removed.unionfind, inserted.unionfind);
326        self.unionfind.apply_delta(&unionfind_delta);
327    }
328}
329
330#[cfg(feature = "undoredo")]
331impl<
332    K: RTreeNum,
333    W: Clone,
334    PCE: Clone + KeyedCollection,
335    PC: KeyedCollection + FlushDelta<PCE>,
336    PRE: Clone + KeyedCollection,
337    PR: KeyedCollection + FlushDelta<PRE>,
338    UFPCE: Clone + KeyedCollection,
339    UFPC: FlushDelta<UFPCE>,
340    UFRCE: Clone + KeyedCollection,
341    UFRC: FlushDelta<UFRCE>,
342> FlushDelta<PolygonUnionFind<K, W, PCE, PRE, UFPCE, UFRCE>>
343    for PolygonUnionFind<K, W, PC, PR, UFPC, UFRC>
344{
345    fn flush_delta(&mut self) -> Delta<PolygonUnionFind<K, W, PCE, PRE, UFPCE, UFRCE>> {
346        let (removed_polygons, inserted_polygons) = self.polygons.flush_delta().dissolve();
347        let (removed_rtree, inserted_rtree) = self.rtree.flush_delta().dissolve();
348        let (removed_unionfind, inserted_unionfind) = self.unionfind.flush_delta().dissolve();
349
350        Delta::with_removed_inserted(
351            PolygonUnionFind {
352                polygons: removed_polygons,
353                rtree: removed_rtree,
354                unionfind: removed_unionfind,
355                scalar_marker: PhantomData,
356                polygon_weight_marker: PhantomData,
357            },
358            PolygonUnionFind {
359                polygons: inserted_polygons,
360                rtree: inserted_rtree,
361                unionfind: inserted_unionfind,
362                scalar_marker: PhantomData,
363                polygon_weight_marker: PhantomData,
364            },
365        )
366    }
367}
368
369#[cfg(test)]
370mod tests {
371    //use super::*;
372
373    // TODO: tests.
374}