iron_shapes_booleanop/
connectivity_extraction.rs

1// SPDX-FileCopyrightText: 2022 Thomas Kramer
2//
3// SPDX-License-Identifier: AGPL-3.0-or-later
4
5//! Extract the connectivity graph of polygons.
6
7use iron_shapes::prelude::*;
8
9use std::fmt::Debug;
10
11use crate::sweep_line::intersection::subdivide_segments;
12use crate::sweep_line::sweep_event::SweepEvent;
13use crate::PolygonSemantics;
14use std::collections::{BinaryHeap, HashMap, HashSet};
15use std::hash::Hash;
16use std::rc::Rc;
17
18/// Extract a connectivity of a set of polygons.
19/// The polygons are supplied in the form of an interator over the polygon edges. If the supplied edges are inconsistent, e.g. don't form closed polygon shapes,
20/// then the result is undefined.
21///
22/// Each edge must be labelled with the identifier of the polygon it belongs to.
23/// Connectivity is encoded as an iterator over multi-graph edges.
24/// Each consists of the IDs of the two touching polygons and a location where the both polygons touch.
25/// Edges can appear many times.
26///
27/// # Examples
28/// ```
29/// use iron_shapes::prelude::*;
30/// use iron_shapes::traits::Translate;
31/// use iron_shapes_booleanop::connectivity_extraction::*;
32/// use iron_shapes_booleanop::*;
33/// use std::collections::HashSet;
34///
35/// let p0 = Polygon::from(vec![(0f64, 0.), (2., 0.), (2., 2.), (0., 2.)]);
36/// let p1 = p0.translate((1., 1.).into());
37/// let p2 = p0.translate((0., 100.).into()); // Far away, not touching anything.
38///
39/// let polygons = vec![p0, p1, p2];
40///
41/// // Convert the polygons into edges labelled with an identifier of their polygon.
42/// let polygon_edges = polygons
43///     .iter()
44///     .enumerate()
45///     .flat_map(|(id, polygon)| polygon.all_edges_iter().map(move |e| (e, id)));
46///
47/// let connectivity_graph = extract_connectivity_graph(
48///     edge_intersection_float,
49///     polygon_edges,
50///     PolygonSemantics::Union,
51/// );
52///
53/// // p0 and p1 are connected.
54/// assert_eq!(connectivity_graph.get(&0), Some(&HashSet::from([1])));
55/// assert_eq!(connectivity_graph.get(&1), Some(&HashSet::from([0])));
56///
57/// // p2 is not connected to any other polygon.
58/// assert_eq!(connectivity_graph.get(&2), None);
59/// ```
60pub fn extract_connectivity<'a, I, T, ID>(
61    edge_intersection: I,
62    polygon_edges: impl Iterator<Item = (Edge<T>, ID)>,
63    polygon_semantics: PolygonSemantics,
64) -> impl Iterator<Item = (ID, ID, Point<T>)>
65where
66    I: Fn(&Edge<T>, &Edge<T>) -> EdgeIntersection<T, T, Edge<T>>,
67    T: CoordinateType + Debug + 'a,
68    ID: Clone + Hash + Eq,
69{
70    // Prepare the event queue.
71    let mut event_queue: BinaryHeap<Rc<SweepEvent<_, Counter<ID>, ID>>> =
72        crate::init_events::fill_queue(polygon_edges);
73
74    // Compute the edge intersections, the result is a set of sorted non-intersecting edges stored
75    // as events.
76    let sorted_events = subdivide_segments(edge_intersection, &mut event_queue, |event, prev| {
77        update_counter(event, prev)
78    });
79
80    // Extract connectivity graph.
81    {
82        // Take left events only.
83        let left_events = sorted_events.into_iter().filter(|e| e.is_left_event());
84
85        // Check if an edge count signals the inside or the outside of a polygon.
86        let is_inside = move |count: i32| -> bool {
87            match polygon_semantics {
88                PolygonSemantics::Union => count != 0,
89                PolygonSemantics::XOR => count % 2 != 0,
90            }
91        };
92
93        // Create iterator of graph edges.
94        // Edges may appear multiple times. Each edge comes with a point which indicates the location of the connectivity.
95        let multi_graph_edges = left_events.flat_map(move |current_event| {
96            // Get connectivity of this event.
97
98            debug_assert!(current_event.is_left_event());
99            let shape_id = current_event.property.as_ref().unwrap().clone(); // Left event must have the property.
100            let point = current_event.p; // Location of connectivity.
101
102            // Take counter from event to own it.
103            // This destroys the structure of the sweep events.
104            let counter =
105                current_event.with_counter_mut(|ctr| std::mem::replace(ctr, Default::default()));
106
107            // Look at edge counts and find shapes enclosing the current event.
108            let shape_id_clone = shape_id.clone();
109            let connected_ids = counter
110                .counters
111                .into_iter()
112                .filter(move |(id, _count)| id != &shape_id_clone) // Look only at other polygons.
113                .filter(move |(_id, count)| is_inside(*count))
114                .map(|(id, _)| id);
115
116            connected_ids.map(move |id| (shape_id.clone(), id, point))
117        });
118
119        multi_graph_edges
120    }
121}
122
123/// Extract a connectivity graph of a set of polygons.
124pub fn extract_connectivity_graph<'a, I, T, ID>(
125    edge_intersection: I,
126    polygon_edges: impl Iterator<Item = (Edge<T>, ID)>,
127    polygon_semantics: PolygonSemantics,
128) -> HashMap<ID, HashSet<ID>>
129where
130    I: Fn(&Edge<T>, &Edge<T>) -> EdgeIntersection<T, T, Edge<T>>,
131    T: CoordinateType + Debug + 'a,
132    ID: Clone + Hash + Eq,
133{
134    let multi_graph_edges =
135        extract_connectivity(edge_intersection, polygon_edges, polygon_semantics);
136
137    let mut graph: HashMap<ID, HashSet<ID>> = Default::default();
138    // Insert graph edges.
139    for (id_a, id_b, _point) in multi_graph_edges {
140        graph
141            .entry(id_a.clone())
142            .or_insert(Default::default())
143            .insert(id_b.clone());
144        // Insert reverse edge.
145        graph.entry(id_b).or_insert(Default::default()).insert(id_a);
146    }
147
148    graph
149}
150
151#[derive(Clone)]
152struct Counter<K> {
153    counters: HashMap<K, i32>,
154}
155
156impl<K> Default for Counter<K> {
157    fn default() -> Self {
158        Self {
159            counters: Default::default(),
160        }
161    }
162}
163
164fn update_counter<T, ID>(
165    event: &Rc<SweepEvent<T, Counter<ID>, ID>>,
166    maybe_prev: Option<&Rc<SweepEvent<T, Counter<ID>, ID>>>,
167) where
168    T: CoordinateType,
169    ID: Clone + Hash + Eq,
170{
171    debug_assert!(event.is_left_event());
172
173    // Update counter.
174    event.with_counter_mut(|ctr| {
175        let edge_id = event.property.as_ref().unwrap(); // Property must be set for all left events.
176
177        // Clone counters of previous edge.
178        let prev_counter = maybe_prev
179            .as_ref()
180            .map(|prev| {
181                debug_assert!(prev.is_left_event());
182                prev.with_counter(|ctr| ctr.clone())
183            })
184            .unwrap_or(Default::default());
185
186        *ctr = prev_counter;
187
188        // Increment counter.
189        {
190            let edge_weight = event.edge_weight();
191            let current_counter = ctr.counters.entry(edge_id.clone()).or_insert(0);
192            *current_counter += edge_weight;
193
194            // Remove counter if it is zero.
195            if *current_counter == 0 {
196                ctr.counters.remove(edge_id);
197            };
198        }
199    });
200}