Skip to main content

dcel/
merge.rs

1// SPDX-FileCopyrightText: 2026 dcel contributors
2//
3// SPDX-License-Identifier: MIT OR Apache-2.0
4
5use maplike::{Get, Insert, StableRemove};
6
7use crate::{
8    Dcel, EdgeId, Face, FaceId, HalfEdge, HalfEdgeId, Vertex, VertexId,
9    track::{HalfEdgesCounter, VertexesCounter},
10};
11
12impl<
13    VW: Clone,
14    HEW: Clone,
15    FW: Clone,
16    VC: Get<usize, Value = Vertex<VW>> + Insert<usize> + StableRemove<usize>,
17    HEC: Get<usize, Value = HalfEdge<HEW>> + Insert<usize> + StableRemove<usize>,
18    FC: Get<usize, Value = Face<FW>> + Insert<usize> + StableRemove<usize>,
19> Dcel<VW, HEW, FW, VC, HEC, FC>
20{
21    /// Remove a degree-2 vertex, merging its incident edges into one.
22    ///
23    /// In mathematics, this is called [graph
24    /// smoothing](https://mathworld.wolfram.com/GraphSmoothing.html), hence the
25    /// function name.
26    ///
27    /// If the degree of the passed vertex is not two, the behavior of this
28    /// method is undefined.
29    pub fn smoothen_vertex(&mut self, vertex: VertexId) {
30        let representative = self.vertex_representative(vertex);
31
32        self.link_subsequent_half_edges(self.prev(representative), self.next(representative));
33        self.link_subsequent_half_edges(
34            self.prev(self.twin(representative)),
35            self.next(self.twin(representative)),
36        );
37
38        self.remove_orphaned_edges([self.full_edge(representative)]);
39        self.remove_orphaned_vertices([vertex]);
40    }
41
42    /// Remove a degree-2 vertex together with its incident edges, merging all
43    /// of its incident faces into one.
44    ///
45    /// The id of one of the incident faces is reused as the id of the resulting
46    /// single merged face. If you want to choose which face it should be, call
47    /// [absorb_faces_around_vertex()] instead.
48    pub fn merge_faces_around_vertex(&mut self, inner_vertex: VertexId) {
49        let absorbing_face = self.incident_face(self.vertex_representative(inner_vertex));
50        self.absorb_faces_around_vertex(absorbing_face, inner_vertex);
51    }
52
53    /// Remove a degree-2 vertex together with its incident edges, absorbing all
54    /// of its incident faces into a chosen one of them.
55    ///
56    /// This method does the same as [merge_faces_around_vertex()], but the
57    /// face whose id is to be reused for the resulting single merged face is
58    /// specified by an additional argument, `absorbing_face`.
59    pub fn absorb_faces_around_vertex(&mut self, absorbing_face: FaceId, inner_vertex: VertexId) {
60        let initial_half_edge = self.vertex_representative(inner_vertex);
61
62        let inner_edges: Vec<EdgeId> = self.spokes(initial_half_edge).collect();
63        let perimeter_half_edges: Vec<HalfEdgeId> = self
64            .vertex_rim_half_edges(inner_vertex)
65            .collect::<Vec<HalfEdgeId>>();
66
67        self.absorb_faces_over_edges_and_vertices_in_perimeter(
68            absorbing_face,
69            self.interspokes(initial_half_edge)
70                .filter(|face| face.id() != absorbing_face.id())
71                .collect::<Vec<FaceId>>(),
72            inner_edges,
73            [inner_vertex],
74            &perimeter_half_edges,
75        );
76    }
77
78    /// Merge a contiguous set of faces into one, removing the specified edges
79    /// and vertices and only them.
80    ///
81    /// Because this method is relatively obscure and the complicated
82    /// responsibility for providing correct edges and vertices to remove now
83    /// lies on the caller, this method is non-public.
84    pub(crate) fn merge_faces_over_edges_and_vertices(
85        &mut self,
86        faces: impl IntoIterator<Item = FaceId>,
87        edges: impl IntoIterator<Item = EdgeId>,
88        vertices: impl IntoIterator<Item = VertexId>,
89    ) {
90        let mut faces = faces.into_iter();
91        let absorbing_face = faces.next().unwrap();
92
93        self.absorb_faces_over_edges_and_vertices(
94            absorbing_face,
95            faces.filter(|face| face.id() != absorbing_face.id()),
96            edges,
97            vertices,
98        );
99    }
100
101    /// Absorb a contiguous set of faces into one, removing the specified edges
102    /// and vertices and only them.
103    ///
104    /// Because this method is relatively obscure and the complicated
105    /// responsibility for providing correct edges and vertices to remove lies
106    /// on the caller, this method is non-public.
107    pub(crate) fn absorb_faces_over_edges_and_vertices(
108        &mut self,
109        absorbing_face: FaceId,
110        faces: impl IntoIterator<Item = FaceId>,
111        edges: impl IntoIterator<Item = EdgeId>,
112        vertices: impl IntoIterator<Item = VertexId>,
113    ) {
114        let edges: Vec<EdgeId> = edges.into_iter().collect();
115        let excluded_half_edges: Vec<HalfEdgeId> = edges
116            .iter()
117            .flat_map(|edge| [edge.lesser(), edge.greater()])
118            .collect();
119
120        // Find an initial half-edge that is not in the exclusion list.
121        // Otherwise, the circulator could end up starting from an excluded
122        // half-edge, which would result in an infinite loop, as the termination
123        // condition depends on returning to the initial edge.
124        let initial_half_edge = self
125            .face_half_edges(absorbing_face)
126            .find(|half_edge| !excluded_half_edges.contains(half_edge))
127            // XXX: If simple circulation around the absorbing face does not
128            // find an initial half-edge, try getting it from the rim.
129            .unwrap_or_else(|| {
130                self.face_rim_half_edges(absorbing_face)
131                    .find(|half_edge| !excluded_half_edges.contains(half_edge))
132                    .unwrap()
133            });
134
135        let perimeter_half_edges: Vec<HalfEdgeId> = self
136            .circulate_half_edges_with_excludes(initial_half_edge, excluded_half_edges.clone())
137            .collect();
138
139        self.absorb_faces_over_edges_and_vertices_in_perimeter(
140            absorbing_face,
141            faces,
142            edges,
143            vertices,
144            &perimeter_half_edges,
145        );
146    }
147
148    /// Merge a contiguous set of faces into one, removing their shared vertices
149    /// and edges.
150    ///
151    /// The set of input faces must be contiguous: the faces together must form
152    /// a single connected component. Otherwise, the behavior of this method
153    /// is undefined.
154    ///
155    /// The id of one of the incident faces is reused as the id of the resulting
156    /// single merged face. If you want to choose which face it should be, call
157    /// [absorb_faces()] instead.
158    pub fn merge_faces(&mut self, faces: impl IntoIterator<Item = FaceId>) {
159        let mut faces = faces.into_iter();
160        let absorbing_face = faces.next().unwrap();
161
162        self.absorb_faces(
163            absorbing_face,
164            faces.filter(|face| face.id() != absorbing_face.id()),
165        );
166    }
167
168    /// Absorb a contiguous set of faces into one.
169    ///
170    /// The set of input faces must be contiguous: the faces together must form
171    /// a single connected component. Otherwise, the behavior of this method
172    /// is undefined.
173    ///
174    /// This method does the same as [merge_faces()], but the face whose id is
175    /// to be reused for the resulting single merged face is specified by an
176    /// additional argument, `absorbing_face`.
177    pub fn absorb_faces(
178        &mut self,
179        absorbing_face: FaceId,
180        faces: impl IntoIterator<Item = FaceId>,
181    ) {
182        let mut half_edges_counter = HalfEdgesCounter::new();
183        let mut vertex_weights_counter = VertexesCounter::new();
184        let faces: Vec<FaceId> = faces.into_iter().collect();
185
186        // To detect the merged edges and vertices correctly, the absorbing face
187        // has to be visited in addition to the absorbed faces.
188        half_edges_counter.visit_face_half_edges(self, absorbing_face);
189        vertex_weights_counter.visit_face_vertices(self, absorbing_face);
190
191        for &face in &faces {
192            half_edges_counter.visit_face_half_edges(self, face);
193            vertex_weights_counter.visit_face_vertices(self, face);
194        }
195
196        self.absorb_faces_over_edges_and_vertices_in_perimeter(
197            absorbing_face,
198            faces,
199            half_edges_counter
200                .inner_edges(self)
201                .collect::<Vec<EdgeId>>(),
202            vertex_weights_counter
203                .visited_vertices()
204                .filter(|&vertex| {
205                    self.spokes_reverse(self.vertex_representative(vertex))
206                        .all(|edge| half_edges_counter.is_inner_edge(edge))
207                })
208                // PERF: Needless collect?
209                .collect::<Vec<VertexId>>(),
210            &half_edges_counter
211                .outer_edges(self)
212                .map(|(half_edge, _)| half_edge)
213                .collect::<Vec<HalfEdgeId>>(),
214        );
215    }
216
217    /// Remove the provided faces, edges, and vertices, and then rewire the
218    /// provided perimeter.
219    ///
220    /// This is the non-public method that actually does the merging or
221    /// absorbing internally. The other absorbing and merging methods merely
222    /// build arguments for this method and then call it.
223    pub(crate) fn absorb_faces_over_edges_and_vertices_in_perimeter(
224        &mut self,
225        absorbing_face: FaceId,
226        faces_to_absorb: impl IntoIterator<Item = FaceId>,
227        edges_to_remove: impl IntoIterator<Item = EdgeId>,
228        vertices_to_remove: impl IntoIterator<Item = VertexId>,
229        perimeter_half_edges: &[HalfEdgeId],
230    ) {
231        self.remove_orphaned_faces(faces_to_absorb);
232        self.remove_orphaned_edges(edges_to_remove);
233        self.remove_orphaned_vertices(vertices_to_remove);
234
235        self.wire_inner_half_edge_chain(absorbing_face, perimeter_half_edges);
236    }
237}
238
239#[cfg(all(test, feature = "stable-vec"))]
240mod test {
241    use crate::{
242        EdgeId, FaceId, HalfEdgeId, StableDcel, VertexId, assert_face_boundary,
243        init_dcel_with_3x3_hex_mesh,
244    };
245
246    #[test]
247    fn test_merge_faces_around_vertex() {
248        let mut dcel = init_dcel_with_3x3_hex_mesh!(StableDcel<(i32, i32)>);
249        dcel.merge_faces_around_vertex(VertexId::new(8));
250
251        // There are now eight faces in total: one unbounded and seven bounded.
252        assert_eq!(dcel.vertices().num_elements(), 29);
253        assert_eq!(dcel.half_edges().num_elements(), 70);
254        assert_eq!(dcel.faces().num_elements(), 8);
255
256        // Among the remaining faces, one is now a dodecagon, and the remaining
257        // six are hexagons.
258        assert_face_boundary!(&dcel, 0, 0);
259        assert_face_boundary!(&dcel, 1, 6);
260        // Face 2 does not exist.
261        assert_face_boundary!(&dcel, 3, 6);
262        // Face 4 does not exist.
263        assert_face_boundary!(&dcel, 5, 12);
264        assert_face_boundary!(&dcel, 6, 6);
265        assert_face_boundary!(&dcel, 7, 6);
266        assert_face_boundary!(&dcel, 8, 6);
267        assert_face_boundary!(&dcel, 9, 6);
268    }
269
270    #[test]
271    fn test_absorb_faces_around_vertex() {
272        let mut dcel = init_dcel_with_3x3_hex_mesh!(StableDcel<(i32, i32)>);
273        dcel.absorb_faces_around_vertex(FaceId::new(2), VertexId::new(8));
274
275        // There are now eight faces in total: one unbounded and seven bounded.
276        assert_eq!(dcel.vertices().num_elements(), 29);
277        assert_eq!(dcel.half_edges().num_elements(), 70);
278        assert_eq!(dcel.faces().num_elements(), 8);
279
280        // Among the remaining faces, one is now a dodecagon, and the remaining
281        // six are hexagons.
282        assert_face_boundary!(&dcel, 0, 0);
283        assert_face_boundary!(&dcel, 1, 6);
284        assert_face_boundary!(&dcel, 2, 12);
285        assert_face_boundary!(&dcel, 3, 6);
286        // Face 4 does not exist.
287        // Face 5 does not exist.
288        assert_face_boundary!(&dcel, 6, 6);
289        assert_face_boundary!(&dcel, 7, 6);
290        assert_face_boundary!(&dcel, 8, 6);
291        assert_face_boundary!(&dcel, 9, 6);
292    }
293
294    #[test]
295    fn test_merge_faces_over_edge() {
296        let mut dcel = init_dcel_with_3x3_hex_mesh!(StableDcel<(i32, i32)>);
297        dcel.merge_faces_over_edges_and_vertices(
298            [FaceId::new(5), FaceId::new(6)],
299            [EdgeId::new(HalfEdgeId::new(44), HalfEdgeId::new(45))],
300            [],
301        );
302
303        assert_eq!(dcel.vertices().num_elements(), 30);
304        assert_eq!(dcel.half_edges().num_elements(), 74);
305        assert_eq!(dcel.faces().num_elements(), 9);
306
307        assert_face_boundary!(&dcel, 0, 0);
308        assert_face_boundary!(&dcel, 1, 6);
309        assert_face_boundary!(&dcel, 2, 6);
310        assert_face_boundary!(&dcel, 3, 6);
311        assert_face_boundary!(&dcel, 4, 6);
312        assert_face_boundary!(&dcel, 5, 10);
313        // Face 6 does not exist.
314        assert_face_boundary!(&dcel, 7, 6);
315        assert_face_boundary!(&dcel, 8, 6);
316        assert_face_boundary!(&dcel, 9, 6);
317    }
318
319    #[test]
320    fn test_absorb_face_into_face_over_edge() {
321        let mut dcel = init_dcel_with_3x3_hex_mesh!(StableDcel<(i32, i32)>);
322        dcel.absorb_faces_over_edges_and_vertices(
323            FaceId::new(6),
324            [FaceId::new(5)],
325            [EdgeId::new(HalfEdgeId::new(44), HalfEdgeId::new(45))],
326            [],
327        );
328
329        assert_eq!(dcel.vertices().num_elements(), 30);
330        assert_eq!(dcel.half_edges().num_elements(), 74);
331        assert_eq!(dcel.faces().num_elements(), 9);
332
333        assert_face_boundary!(&dcel, 0, 0);
334        assert_face_boundary!(&dcel, 1, 6);
335        assert_face_boundary!(&dcel, 2, 6);
336        assert_face_boundary!(&dcel, 3, 6);
337        assert_face_boundary!(&dcel, 4, 6);
338        // Face 5 does not exist.
339        assert_face_boundary!(&dcel, 6, 10);
340        assert_face_boundary!(&dcel, 7, 6);
341        assert_face_boundary!(&dcel, 8, 6);
342        assert_face_boundary!(&dcel, 9, 6);
343    }
344
345    #[test]
346    fn merge_two_faces() {
347        let mut dcel = init_dcel_with_3x3_hex_mesh!(StableDcel<(i32, i32)>);
348        dcel.merge_faces([FaceId::new(7), FaceId::new(8)]);
349
350        assert_eq!(dcel.vertices().num_elements(), 30);
351        assert_eq!(dcel.half_edges().num_elements(), 74);
352        assert_eq!(dcel.faces().num_elements(), 9);
353
354        assert_face_boundary!(&dcel, 0, 0);
355        assert_face_boundary!(&dcel, 1, 6);
356        assert_face_boundary!(&dcel, 2, 6);
357        assert_face_boundary!(&dcel, 3, 6);
358        assert_face_boundary!(&dcel, 4, 6);
359        assert_face_boundary!(&dcel, 5, 6);
360        assert_face_boundary!(&dcel, 6, 6);
361        assert_face_boundary!(&dcel, 7, 10);
362        // Face 8 does not exist.
363        assert_face_boundary!(&dcel, 9, 6);
364    }
365
366    #[test]
367    fn absorb_face_into_face() {
368        let mut dcel = init_dcel_with_3x3_hex_mesh!(StableDcel<(i32, i32)>);
369        dcel.absorb_faces(FaceId::new(8), [FaceId::new(7)]);
370
371        assert_eq!(dcel.vertices().num_elements(), 30);
372        assert_eq!(dcel.half_edges().num_elements(), 74);
373        assert_eq!(dcel.faces().num_elements(), 9);
374
375        assert_face_boundary!(&dcel, 0, 0);
376        assert_face_boundary!(&dcel, 1, 6);
377        assert_face_boundary!(&dcel, 2, 6);
378        assert_face_boundary!(&dcel, 3, 6);
379        assert_face_boundary!(&dcel, 4, 6);
380        assert_face_boundary!(&dcel, 5, 6);
381        assert_face_boundary!(&dcel, 6, 6);
382        // Face 7 does not exist.
383        assert_face_boundary!(&dcel, 8, 10);
384        assert_face_boundary!(&dcel, 9, 6);
385    }
386}