Skip to main content

mesh_graph/ops/
collapse.rs

1use glam::Vec3;
2use hashbrown::HashSet;
3use itertools::Itertools;
4use tracing::{error, instrument};
5
6use crate::{Face, FaceId, HalfedgeId, MeshGraph, VertexId, error_none, utils::unwrap_or_return};
7
8impl MeshGraph {
9    /// Collapses edges until all edges have a length above the minimum length.
10    ///
11    /// This will schedule necessary updates to the BVH but you have to call
12    /// `refit_bvh()` after the operation.
13    #[instrument(skip(self))]
14    pub fn collapse_until_edges_above_min_length(
15        &mut self,
16        min_length_squared: f32,
17        marked_vertices: &mut HashSet<VertexId>,
18    ) {
19        let mut halfedges_to_collapse = self.halfedges_map(|len_sqr| len_sqr < min_length_squared);
20
21        while !halfedges_to_collapse.is_empty() {
22            let mut min_len = f32::MAX;
23            let mut min_he_id = None;
24            let mut min_center = Vec3::ZERO;
25            let mut min_twin_id = HalfedgeId::default();
26            let mut min_start_v_id = VertexId::default();
27            let mut min_end_v_id = VertexId::default();
28
29            for (&he_id, &len) in &halfedges_to_collapse {
30                if len < min_len
31                    && let Some((twin_id, start_v_id, end_v_id, center)) =
32                        self.can_collapse_edge_inner(he_id)
33                {
34                    min_len = len;
35                    min_he_id = Some(he_id);
36                    min_twin_id = twin_id;
37                    min_start_v_id = start_v_id;
38                    min_end_v_id = end_v_id;
39                    min_center = center;
40                }
41            }
42
43            let Some(min_he_id) = min_he_id else {
44                // couldn't find a valid halfedge to collapse
45                break;
46            };
47
48            let start_vertex_id = unwrap_or_return!(
49                self.halfedges[min_he_id].start_vertex(self),
50                "Start vertex not found"
51            );
52
53            let collapse_edge_result = self.collapse_edge_inner(
54                min_he_id,
55                min_twin_id,
56                min_start_v_id,
57                min_end_v_id,
58                min_center,
59            );
60
61            let vertex_neighborhoods_to_check = if collapse_edge_result.added_vertices.is_empty() {
62                if collapse_edge_result.removed_halfedges.is_empty() {
63                    vec![]
64                } else {
65                    vec![start_vertex_id]
66                }
67            } else {
68                marked_vertices.extend(collapse_edge_result.added_vertices.iter().copied());
69
70                let mut neighborhood = collapse_edge_result.added_vertices;
71                neighborhood.push(start_vertex_id);
72
73                neighborhood
74            };
75
76            halfedges_to_collapse.remove(&min_he_id);
77
78            for removed_he_id in collapse_edge_result.removed_halfedges {
79                halfedges_to_collapse.remove(&removed_he_id);
80            }
81
82            let mut halfedges_to_check = HashSet::new();
83
84            for vertex_id in vertex_neighborhoods_to_check {
85                let Some(outgoing_halfedges) = self.outgoing_halfedges.get(vertex_id) else {
86                    // the vertex might have been removed by a previous cleanup
87                    continue;
88                };
89
90                for &halfedge_id in outgoing_halfedges {
91                    let Some(halfedge) = self.halfedges.get(halfedge_id) else {
92                        error!("Halfedge not found");
93                        continue;
94                    };
95
96                    let twin_id = unwrap_or_return!(halfedge.twin, "Twin not found");
97
98                    halfedges_to_check.insert(halfedge_id.min(twin_id));
99
100                    if let Some(face_id) = halfedge.face {
101                        if let Some(face) = self.faces.get(face_id) {
102                            self.bvh
103                                .insert_or_update_partially(face.aabb(self), face.index, 0.0);
104                        } else {
105                            error!("Face not found. BVH will not be updated.");
106                        }
107                    }
108                }
109            }
110
111            for he_id in halfedges_to_check {
112                let he = self.halfedges[he_id]; // already checked above
113
114                let len_sqr = he.length_squared(self);
115
116                if len_sqr < min_length_squared {
117                    halfedges_to_collapse.insert(he_id, len_sqr);
118                } else {
119                    halfedges_to_collapse.remove(&he_id);
120                }
121            }
122        }
123
124        #[cfg(feature = "rerun")]
125        self.log_rerun();
126    }
127
128    #[inline]
129    pub fn can_collapse_edge(&mut self, halfedge_id: HalfedgeId) -> bool {
130        self.can_collapse_edge_inner(halfedge_id).is_some()
131    }
132
133    #[instrument(skip(self))]
134    fn can_collapse_edge_inner(
135        &mut self,
136        halfedge_id: HalfedgeId,
137    ) -> Option<(HalfedgeId, VertexId, VertexId, Vec3)> {
138        // TODO : consider boundary edge
139        //
140        //          end_vertex
141        //  .            .            .
142        // ( ) ◀─────── ( ) ───────▶ ( )
143        //  '      3     '     2      '
144        //            ╱ ▲ │ ╲
145        //          4╱  │ │  ╲1
146        //          ╱   │ │0  ╲
147        //         ╱    │ │    ╲
148        //        ▼     │ │     ▼
149        //       .      │ │      .
150        //      ( )   he│ │twin ( )
151        //       '      │ │      '
152        //        ▲     │ │     ▲
153        //         ╲    │ │    ╱
154        //          ╲  0│ │   ╱
155        //          1╲  │ │  ╱4
156        //            ╲ │ ▼ ╱
157        //  .      2     .     3      .
158        // ( ) ◀─────── ( ) ───────▶ ( )
159        //  '            '            '
160        //         start_vertex
161
162        let he = self
163            .halfedges
164            .get(halfedge_id)
165            .or_else(error_none!("Halfedge not found"))?;
166        let twin_id = he.twin.or_else(error_none!("Twin halfedge not found"))?;
167        let twin = self
168            .halfedges
169            .get(twin_id)
170            .or_else(error_none!("Twin halfedge not found"))?;
171
172        let start_vertex_id = twin.end_vertex;
173        self.vertices
174            .get_mut(start_vertex_id)
175            .or_else(error_none!("Start vertex not found"))?
176            .outgoing_halfedge = Some(halfedge_id);
177
178        let end_vertex_id = he.end_vertex;
179        self.vertices
180            .get_mut(end_vertex_id)
181            .or_else(error_none!("End vertex not found"))?
182            .outgoing_halfedge = Some(twin_id);
183
184        let start_pos = self
185            .positions
186            .get(start_vertex_id)
187            .or_else(error_none!("Start position not found"))?;
188
189        let end_pos = self
190            .positions
191            .get(end_vertex_id)
192            .or_else(error_none!("End position not found"))?;
193
194        let center = (start_pos + end_pos) * 0.5;
195
196        self.check_inverted_faces(start_vertex_id, center)?;
197        self.check_inverted_faces(end_vertex_id, center)?;
198
199        Some((twin_id, start_vertex_id, end_vertex_id, center))
200    }
201
202    fn check_inverted_faces(&self, vertex_id: VertexId, center: Vec3) -> Option<()> {
203        // just made sure that this exists
204        let face_ids = self.vertices[vertex_id].faces(self).skip(2).collect_vec();
205
206        for face_id in face_ids {
207            let mut orig_positions = Vec::with_capacity(3);
208            let mut new_positions = Vec::with_capacity(3);
209
210            let face = self
211                .faces
212                .get(face_id)
213                .or_else(error_none!("Face not found"))?;
214
215            for v_id in face.vertices(self) {
216                let pos = *self
217                    .positions
218                    .get(v_id)
219                    .or_else(error_none!("Vertex pos not found"))?;
220
221                if v_id == vertex_id {
222                    new_positions.push(center);
223                } else {
224                    new_positions.push(pos);
225                }
226                orig_positions.push(pos);
227            }
228
229            let orig_normal = Face::normal_from_positions(&orig_positions);
230            let new_normal = Face::normal_from_positions(&new_positions);
231
232            if orig_normal.dot(new_normal) < 0.0 {
233                return None;
234            }
235        }
236
237        Some(())
238    }
239
240    #[instrument(skip(self))]
241    pub fn collapse_edge_inner(
242        &mut self,
243        halfedge_id: HalfedgeId,
244        twin_id: HalfedgeId,
245        start_v_id: VertexId,
246        end_v_id: VertexId,
247        center_pos: Vec3,
248    ) -> CollapseEdge {
249        let mut result = CollapseEdge::default();
250
251        #[cfg(feature = "rerun")]
252        {
253            self.log_he_rerun("collapse/he", halfedge_id);
254        }
255        // TODO : consider border vertices
256
257        let end_outgoing_halfedges = self.vertices[end_v_id]
258            .outgoing_halfedges(self)
259            .collect::<Vec<_>>();
260        let end_incoming_halfedges = self.vertices[end_v_id]
261            .incoming_halfedges(self)
262            .collect::<Vec<_>>();
263
264        let he = self.halfedges[halfedge_id];
265        let twin = self.halfedges[twin_id];
266
267        if !he.is_boundary() {
268            let (face_id, halfedge_ids) = unwrap_or_return!(
269                self.remove_halfedge_face(halfedge_id),
270                "Could not remove face",
271                result
272            );
273
274            self.remove_outgoing_halfedge(end_v_id, halfedge_ids[0]);
275
276            result.removed_faces.push(face_id);
277            result.removed_halfedges.extend(halfedge_ids);
278        }
279        result.removed_halfedges.push(halfedge_id);
280
281        self.remove_outgoing_halfedge(start_v_id, halfedge_id);
282
283        if !twin.is_boundary() {
284            let (face_id, halfedge_ids) = unwrap_or_return!(
285                self.remove_halfedge_face(twin_id),
286                "Failed to remove halfedge face",
287                result
288            );
289
290            self.remove_outgoing_halfedge(start_v_id, halfedge_ids[0]);
291
292            result.removed_faces.push(face_id);
293            result.removed_halfedges.extend(halfedge_ids);
294        }
295        result.removed_halfedges.push(twin_id);
296
297        self.remove_outgoing_halfedge(end_v_id, twin_id);
298
299        let outgoing_end_halfedges = self
300            .outgoing_halfedges
301            .get(end_v_id)
302            .cloned()
303            .unwrap_or_default();
304        self.outgoing_halfedges
305            .entry(start_v_id)
306            .unwrap() // unwrap is safe here because key exists because we accessed it above in self.positions
307            .or_default()
308            .extend(outgoing_end_halfedges);
309
310        for end_incoming_he in end_incoming_halfedges {
311            if let Some(end_incoming_he_mut) = self.halfedges.get_mut(end_incoming_he) {
312                end_incoming_he_mut.end_vertex = start_v_id;
313            }
314        }
315
316        self.remove_only_vertex(end_v_id);
317        result.removed_vertices.push(end_v_id);
318
319        self.halfedges.remove(halfedge_id);
320        self.halfedges.remove(twin_id);
321
322        self.positions[start_v_id] = center_pos;
323
324        let new_outgoing_he_id = end_outgoing_halfedges
325            .into_iter()
326            .find(|he_id| self.halfedges.contains_key(*he_id));
327
328        if let Some(new_outgoing_he_id) = new_outgoing_he_id {
329            self.vertices[start_v_id].outgoing_halfedge = Some(new_outgoing_he_id);
330
331            #[cfg(feature = "rerun")]
332            {
333                self.log_he_rerun(
334                    "collapse/outgoing",
335                    self.vertices[start_v_id].outgoing_halfedge.unwrap(),
336                );
337            }
338
339            let cleanup = self.make_vertex_neighborhood_manifold(start_v_id);
340
341            result.added_vertices = cleanup.added_vertices;
342            result.removed_vertices.extend(cleanup.removed_vertices);
343            result.removed_halfedges.extend(cleanup.removed_halfedges);
344            result.removed_faces.extend(cleanup.removed_faces);
345        } else {
346            self.remove_only_vertex(start_v_id);
347
348            result.removed_vertices.push(start_v_id);
349        }
350
351        result
352    }
353
354    /// Collapse an edge in the mesh graph.
355    ///
356    /// This moves the start vertex of the edge to the center of the edge
357    /// and removes the end vertex and the adjacent and opposite faces.
358    ///
359    /// It also performs a cleanup afterwards to remove flaps (faces that share the same vertices).
360    ///
361    /// Returns the vertices, halfedges and faces that were removed.
362    #[instrument(skip(self))]
363    pub fn collapse_edge(&mut self, halfedge_id: HalfedgeId) -> CollapseEdge {
364        let he = *unwrap_or_return!(
365            self.halfedges.get(halfedge_id),
366            "Halfedge not found",
367            CollapseEdge::default()
368        );
369        let twin_id = unwrap_or_return!(he.twin, "Twin missing", CollapseEdge::default());
370        let twin = unwrap_or_return!(
371            self.halfedges.get(twin_id),
372            "Halfedge not found",
373            CollapseEdge::default()
374        );
375
376        let start_v_id = twin.end_vertex;
377        let end_v_id = he.end_vertex;
378
379        let start_pos = *unwrap_or_return!(
380            self.positions.get(start_v_id),
381            "Start position not found",
382            CollapseEdge::default()
383        );
384        let end_pos = *unwrap_or_return!(
385            self.positions.get(end_v_id),
386            "End position not found",
387            CollapseEdge::default()
388        );
389
390        let center_pos = (start_pos + end_pos) * 0.5;
391
392        self.collapse_edge_inner(halfedge_id, twin_id, start_v_id, end_v_id, center_pos)
393    }
394
395    /// Remove a halfedge face and re-connecting the adjacent halfedges.
396    /// Only works on manifold triangle meshes.
397    #[instrument(skip(self))]
398    fn remove_halfedge_face(
399        &mut self,
400        halfedge_id: HalfedgeId,
401    ) -> Option<(FaceId, [HalfedgeId; 2])> {
402        let he = self
403            .halfedges
404            .get(halfedge_id)
405            .or_else(error_none!("Halfedge not found"))?;
406
407        let face_id = he.face.or_else(error_none!("Face not found"))?;
408
409        let next_he_id = he.next.or_else(error_none!("Next halfedge is None"))?;
410        let prev_he_id = he
411            .prev(self)
412            .or_else(error_none!("Previous halfedge is None"))?;
413
414        let next_twin_id = self.halfedges[next_he_id]
415            .twin
416            .or_else(error_none!("Next twin halfedge not found"))?;
417        let prev_twin_id = self.halfedges[prev_he_id]
418            .twin
419            .or_else(error_none!("Previous twin halfedge not found"))?;
420
421        let next_he = self
422            .halfedges
423            .get(next_he_id)
424            .or_else(error_none!("Next halfedge not found"))?;
425        let prev_he = self
426            .halfedges
427            .get(prev_he_id)
428            .or_else(error_none!("Previous halfedge not found"))?;
429
430        let next_end_v_id = next_he.end_vertex;
431        let prev_end_v_id = prev_he.end_vertex;
432
433        self.vertices
434            .get_mut(next_end_v_id)
435            .or_else(error_none!("Next end vertex not found"))?
436            .outgoing_halfedge = next_he.next.or_else(error_none!("Next next is None"));
437        self.vertices
438            .get_mut(prev_end_v_id)
439            .or_else(error_none!("Previous end vertex not found"))?
440            .outgoing_halfedge = prev_he.next.or_else(error_none!("Previous next is None"));
441
442        let prev_start_v_id = prev_he
443            .start_vertex(self)
444            .or_else(error_none!("Previous start vertex ID not found"))?;
445        let prev_start_v = self
446            .vertices
447            .get(prev_start_v_id)
448            .or_else(error_none!("Previous start vertex not found"))?;
449
450        if prev_start_v.outgoing_halfedge == Some(prev_he_id) {
451            self.vertices[prev_start_v_id].outgoing_halfedge = prev_he
452                .ccw_rotated_neighbour(self)
453                .or_else(|| prev_he.cw_rotated_neighbour(self))
454                .or_else(error_none!(
455                    "Previous start vertex new outgoing halfedge not found"
456                ));
457        }
458
459        self.bvh.remove(
460            self.faces
461                .get(face_id)
462                .or_else(error_none!("Face not found"))?
463                .index,
464        );
465
466        self.halfedges.remove(next_he_id);
467        self.halfedges.remove(prev_he_id);
468        self.remove_outgoing_halfedge(next_end_v_id, prev_he_id);
469
470        if let Some(face) = self.faces.remove(face_id) {
471            self.bvh.remove(face.index);
472        }
473
474        self.halfedges
475            .get_mut(next_twin_id)
476            .or_else(error_none!("Next twin halfedge not found"))?
477            .twin = Some(prev_twin_id);
478
479        self.halfedges
480            .get_mut(prev_twin_id)
481            .or_else(error_none!("Previous twin halfedge not found"))?
482            .twin = Some(next_twin_id);
483
484        Some((face_id, [next_he_id, prev_he_id]))
485    }
486}
487
488#[derive(Default, Debug)]
489pub struct CollapseEdge {
490    pub removed_vertices: Vec<VertexId>,
491    pub removed_halfedges: Vec<HalfedgeId>,
492    pub removed_faces: Vec<FaceId>,
493
494    pub added_vertices: Vec<VertexId>,
495}
496
497#[cfg(test)]
498mod test {
499    use super::*;
500
501    #[test]
502    #[allow(unused_variables)]
503    fn test_collapse_edge() {
504        let mut mesh_graph = MeshGraph::new();
505
506        let face1 = mesh_graph
507            .add_face_from_positions(
508                Vec3::new(0.0, 0.0, 0.0),
509                Vec3::new(0.0, 4.0, 0.0),
510                Vec3::new(1.0, 2.0, 0.0),
511            )
512            .face_id;
513
514        let he1 = mesh_graph.faces[face1]
515            .halfedges(&mesh_graph)
516            .collect::<Vec<_>>()[1];
517
518        let face2 = mesh_graph
519            .add_face_from_halfedge_and_position(he1, Vec3::new(2.0, 4.0, 0.0))
520            .unwrap()
521            .face_id;
522
523        let he2 = mesh_graph.faces[face2]
524            .halfedges(&mesh_graph)
525            .collect::<Vec<_>>()[2];
526
527        let face3 = mesh_graph
528            .add_face_from_halfedge_and_position(he2, Vec3::new(3.0, 2.0, 0.0))
529            .unwrap()
530            .face_id;
531
532        let he3 = mesh_graph.faces[face3]
533            .halfedges(&mesh_graph)
534            .collect::<Vec<_>>()[1];
535
536        let face4 = mesh_graph
537            .add_face_from_halfedge_and_position(he3, Vec3::new(4.0, 4.0, 0.0))
538            .unwrap()
539            .face_id;
540
541        let he4 = mesh_graph.faces[face4]
542            .halfedges(&mesh_graph)
543            .nth(2)
544            .unwrap();
545
546        let face5 = mesh_graph
547            .add_face_from_halfedge_and_position(he4, Vec3::new(4.0, 0.0, 0.0))
548            .unwrap()
549            .face_id;
550
551        let he5 = mesh_graph.faces[face5]
552            .halfedges(&mesh_graph)
553            .nth(2)
554            .unwrap();
555
556        let face6 = mesh_graph
557            .add_face_from_halfedge_and_position(he5, Vec3::new(2.0, 0.0, 0.0))
558            .unwrap()
559            .face_id;
560
561        let he6 = mesh_graph.faces[face6]
562            .halfedges(&mesh_graph)
563            .nth(2)
564            .unwrap();
565
566        let he3 = mesh_graph.faces[face3]
567            .halfedges(&mesh_graph)
568            .nth(2)
569            .unwrap();
570
571        let face7 = mesh_graph
572            .add_face_from_halfedges(he6, he3)
573            .unwrap()
574            .face_id;
575
576        let he7 = mesh_graph.faces[face7]
577            .halfedges(&mesh_graph)
578            .next()
579            .unwrap();
580
581        let he1 = mesh_graph.faces[face1]
582            .halfedges(&mesh_graph)
583            .nth(2)
584            .unwrap();
585
586        mesh_graph.add_face_from_halfedges(he1, he7).unwrap();
587
588        assert_eq!(mesh_graph.vertices.len(), 8);
589        assert_eq!(mesh_graph.halfedges.len(), 30);
590        assert_eq!(mesh_graph.faces.len(), 8);
591
592        #[cfg(feature = "rerun")]
593        mesh_graph.log_rerun();
594
595        let edge_to_collapse = mesh_graph.faces[face3]
596            .halfedges(&mesh_graph)
597            .nth(2)
598            .unwrap();
599
600        let start_v_id = mesh_graph.halfedges[edge_to_collapse]
601            .start_vertex(&mesh_graph)
602            .unwrap();
603
604        assert_eq!(mesh_graph.outgoing_halfedges[start_v_id].len(), 5);
605
606        let CollapseEdge {
607            removed_vertices,
608            removed_halfedges,
609            removed_faces,
610            added_vertices,
611        } = mesh_graph.collapse_edge(edge_to_collapse);
612
613        #[cfg(feature = "rerun")]
614        {
615            mesh_graph.log_rerun();
616            crate::RR.flush_blocking().unwrap();
617        }
618
619        assert_eq!(removed_vertices.len(), 1);
620        assert_eq!(removed_halfedges.len(), 6);
621        assert_eq!(removed_faces.len(), 2);
622
623        assert_eq!(mesh_graph.vertices.len(), 7);
624        assert_eq!(mesh_graph.halfedges.len(), 24);
625        assert_eq!(mesh_graph.faces.len(), 6);
626
627        assert_eq!(mesh_graph.outgoing_halfedges[start_v_id].len(), 6);
628    }
629
630    #[cfg(feature = "gltf")]
631    #[test]
632    fn test_can_collapse_edge() {
633        use crate::{integrations::gltf, utils::get_tracing_subscriber};
634
635        get_tracing_subscriber();
636        let mut meshgraph = gltf::load("src/ops/glb/can_collapse_edge.glb").unwrap();
637
638        #[cfg(feature = "rerun")]
639        meshgraph.log_rerun();
640
641        let mut v_top_id = VertexId::default();
642        let mut v_bottom_id = VertexId::default();
643
644        for (v_id, pos) in &meshgraph.positions {
645            if pos.x == 0.0 {
646                if pos.y == -1.0 {
647                    v_top_id = v_id;
648                } else if pos.y == 1.0 {
649                    v_bottom_id = v_id;
650                }
651            }
652        }
653
654        if v_top_id == VertexId::default() {
655            panic!("No top vertex found");
656        }
657
658        if v_bottom_id == VertexId::default() {
659            panic!("No bottom vertex found");
660        }
661
662        let he_id = meshgraph.halfedge_from_to(v_top_id, v_bottom_id).unwrap();
663
664        let result = meshgraph.can_collapse_edge(he_id);
665
666        assert!(result);
667
668        #[cfg(feature = "rerun")]
669        crate::RR.flush_blocking().unwrap();
670    }
671
672    #[cfg(feature = "gltf")]
673    #[test]
674    fn test_cannot_collapse_edge() {
675        use crate::{integrations::gltf, utils::get_tracing_subscriber};
676
677        get_tracing_subscriber();
678        let mut meshgraph = gltf::load("src/ops/glb/cannot_collapse_edge.glb").unwrap();
679
680        #[cfg(feature = "rerun")]
681        meshgraph.log_rerun();
682
683        let mut v_top_id = VertexId::default();
684        let mut v_bottom_id = VertexId::default();
685
686        for (v_id, pos) in &meshgraph.positions {
687            if pos.x == 0.0 {
688                if pos.y == -1.0 {
689                    v_top_id = v_id;
690                } else if pos.y == 1.0 {
691                    v_bottom_id = v_id;
692                }
693            }
694        }
695
696        if v_top_id == VertexId::default() {
697            panic!("No top vertex found");
698        }
699
700        if v_bottom_id == VertexId::default() {
701            panic!("No bottom vertex found");
702        }
703
704        let he_id = meshgraph.halfedge_from_to(v_top_id, v_bottom_id).unwrap();
705
706        let result = meshgraph.can_collapse_edge(he_id);
707
708        assert!(!result);
709
710        #[cfg(feature = "rerun")]
711        crate::RR.flush_blocking().unwrap();
712    }
713}