Skip to main content

mesh_graph/ops/cleanup/
vertex_neighborhood.rs

1use hashbrown::HashSet;
2use itertools::Itertools;
3use tracing::{error, instrument};
4
5use crate::{FaceId, HalfedgeId, MeshGraph, VertexId, error_none};
6
7#[derive(Default)]
8pub struct VertexNeighborhoodCleanup {
9    pub removed_vertices: Vec<VertexId>,
10    pub removed_halfedges: Vec<HalfedgeId>,
11    pub removed_faces: Vec<FaceId>,
12
13    pub added_vertices: Vec<VertexId>,
14}
15
16#[derive(Default)]
17struct VertexNeighborhoodCleanupStep {
18    pub removed_vertices: Vec<VertexId>,
19    pub removed_halfedges: Vec<HalfedgeId>,
20    pub removed_faces: Vec<FaceId>,
21
22    pub added_duplicated_vertices: Vec<VertexId>,
23    pub touched_vertices: Vec<VertexId>,
24}
25
26impl MeshGraph {
27    /// Ensure that the neighborhood of a vertex is manifold.
28    ///
29    /// It removes flaps (two neighboring coincident triangles) and splits the given vertex
30    /// in two if there are non-neighboring degenerate triangles or edges.
31    ///
32    /// See [Freestyle: Sculpting meshes with self-adaptive topology DOI 10.1016/j.cag.2011.03.033](https://inria.hal.science/inria-00606516v1/document)
33    /// Chapters 3.2 and 5.1
34    #[instrument(skip(self))]
35    pub fn make_vertex_neighborhood_manifold(
36        &mut self,
37        vertex_id: VertexId,
38    ) -> VertexNeighborhoodCleanup {
39        #[cfg(feature = "rerun")]
40        self.log_vert_rerun("make_neigh_manifold", vertex_id);
41
42        let mut result = VertexNeighborhoodCleanup::default();
43
44        let mut vertices = vec![vertex_id];
45
46        let vertex_count = self.vertices.len();
47        let mut iterations = 0;
48
49        while let Some(&v_id) = vertices.first()
50            && iterations < vertex_count
51        {
52            if !self.vertices.contains_key(v_id) {
53                vertices.swap_remove(0);
54                continue;
55            }
56
57            let VertexNeighborhoodCleanupStep {
58                added_duplicated_vertices,
59                touched_vertices,
60                removed_vertices,
61                removed_halfedges,
62                removed_faces,
63            } = self.make_vertex_neighborhood_manifold_step(v_id);
64
65            if removed_vertices.is_empty()
66                && removed_halfedges.is_empty()
67                && removed_faces.is_empty()
68                && added_duplicated_vertices.is_empty()
69                && touched_vertices.is_empty()
70            {
71                vertices.swap_remove(0);
72            }
73
74            vertices.extend(touched_vertices);
75            for added_vertex in added_duplicated_vertices {
76                vertices.push(added_vertex);
77
78                result.added_vertices.push(added_vertex);
79            }
80
81            result.removed_vertices.extend(removed_vertices);
82            result.removed_halfedges.extend(removed_halfedges);
83            result.removed_faces.extend(removed_faces);
84
85            iterations += 1;
86        }
87
88        for &cancelled_v_id in
89            HashSet::<VertexId>::from_iter(result.removed_vertices.iter().copied())
90                .intersection(&HashSet::from_iter(result.added_vertices.iter().copied()))
91        {
92            result.added_vertices.retain(|&v_id| v_id != cancelled_v_id);
93            result
94                .removed_vertices
95                .retain(|&v_id| v_id != cancelled_v_id);
96        }
97
98        result
99    }
100
101    fn make_vertex_neighborhood_manifold_step(
102        &mut self,
103        vertex_id: VertexId,
104    ) -> VertexNeighborhoodCleanupStep {
105        let mut removed_vertices = Vec::new();
106        let mut removed_halfedges = Vec::new();
107        let mut removed_faces = Vec::new();
108
109        let added_duplicated_vertices = self
110            .make_vertex_neighborhood_manifold_inner(
111                vertex_id,
112                &mut removed_vertices,
113                &mut removed_halfedges,
114                &mut removed_faces,
115            )
116            .unwrap_or_default();
117
118        VertexNeighborhoodCleanupStep {
119            added_duplicated_vertices,
120            touched_vertices: vec![],
121            removed_vertices,
122            removed_halfedges,
123            removed_faces,
124        }
125    }
126
127    pub fn make_vertex_neighborhood_manifold_inner(
128        &mut self,
129        vertex_id: VertexId,
130        removed_vertices: &mut Vec<VertexId>,
131        removed_halfedges: &mut Vec<HalfedgeId>,
132        removed_faces: &mut Vec<FaceId>,
133    ) -> Option<Vec<VertexId>> {
134        self.vertices.get(vertex_id)?;
135
136        if self.remove_single_face(
137            vertex_id,
138            removed_vertices,
139            removed_halfedges,
140            removed_faces,
141        )? {
142            return Some(vec![]);
143        }
144
145        if self.remove_neighboring_flaps(
146            vertex_id,
147            removed_vertices,
148            removed_halfedges,
149            removed_faces,
150        )? {
151            return Some(vec![]);
152        }
153
154        if let Some(new_vertices) = self.split_disconnected_neighborhoods(vertex_id)
155            && !new_vertices.is_empty()
156        {
157            return Some(new_vertices);
158        }
159
160        if let Some(inserted_duplicated_vertex) = self.remove_degenerate_faces(
161            vertex_id,
162            removed_vertices,
163            removed_halfedges,
164            removed_faces,
165        ) {
166            return Some(vec![inserted_duplicated_vertex]);
167        }
168
169        self.remove_degenerate_edges(vertex_id).map(|v| vec![v])
170    }
171
172    fn split_disconnected_neighborhoods(&mut self, vertex_id: VertexId) -> Option<Vec<VertexId>> {
173        let mut new_vertices = Vec::new();
174
175        let mut outgoing_halfedges = HashSet::<HalfedgeId>::from_iter(
176            self.outgoing_halfedges
177                .get(vertex_id)
178                .or_else(error_none!("Outgoing halfedges not found"))?
179                .iter()
180                .copied(),
181        );
182
183        while let Some(&start_he_id) = outgoing_halfedges.iter().next() {
184            let mut current_he_id = start_he_id;
185
186            let len = outgoing_halfedges.len();
187            let mut current_outgoing_halfedges = Vec::with_capacity(len);
188
189            loop {
190                if !outgoing_halfedges.remove(&current_he_id) {
191                    return None;
192                }
193                current_outgoing_halfedges.push(current_he_id);
194                if current_outgoing_halfedges.len() == len {
195                    return None;
196                }
197
198                let cur_he = self
199                    .halfedges
200                    .get(current_he_id)
201                    .or_else(error_none!("Halfedge not found"))?;
202
203                let Some(next_he_id) = cur_he.cw_rotated_neighbour(self) else {
204                    // The fan runs into a boundary, so rotating clockwise can't
205                    // reach the rest of it. Whether the walk started in the
206                    // middle of the fan or at its end decides how much is left,
207                    // so collect the remainder by rotating the other way around
208                    // the vertex, back from `start_he_id`. Otherwise a perfectly
209                    // connected boundary neighborhood looks disconnected and the
210                    // vertex gets split for no reason.
211                    let mut back_he_id = start_he_id;
212                    while let Some(prev_he_id) = self
213                        .halfedges
214                        .get(back_he_id)
215                        .or_else(error_none!("Halfedge not found"))?
216                        .ccw_rotated_neighbour(self)
217                    {
218                        if !outgoing_halfedges.remove(&prev_he_id) {
219                            break;
220                        }
221                        current_outgoing_halfedges.push(prev_he_id);
222                        back_he_id = prev_he_id;
223                    }
224
225                    break;
226                };
227
228                if next_he_id == start_he_id {
229                    break;
230                }
231
232                current_he_id = next_he_id;
233            }
234
235            if !outgoing_halfedges.is_empty() {
236                let new_vertex_id = self
237                    .duplicate_vertex_and_assign_halfedges(vertex_id, current_outgoing_halfedges)?;
238
239                new_vertices.push(new_vertex_id);
240
241                self.vertices
242                    .get_mut(vertex_id)
243                    .or_else(error_none!("Vertex not found"))?
244                    .outgoing_halfedge = Some(*outgoing_halfedges.iter().next().unwrap()); // check above that outgoing_halfedges is not empty
245
246                self.outgoing_halfedges
247                    .insert(vertex_id, Vec::from_iter(outgoing_halfedges));
248
249                return Some(new_vertices);
250            }
251        }
252
253        Some(new_vertices)
254    }
255
256    fn duplicate_vertex_and_assign_halfedges(
257        &mut self,
258        vertex_id: VertexId,
259        outgoing_halfedges: Vec<HalfedgeId>,
260    ) -> Option<VertexId> {
261        let new_vert_id = self.add_vertex(
262            *self
263                .positions
264                .get(vertex_id)
265                .or_else(error_none!("Position not found"))?
266                + glam::Vec3::new(0.1, 0.0, 0.0),
267        );
268
269        tracing::debug!("Duplicated {vertex_id:?}: {new_vert_id:?}");
270
271        for &he_id in &outgoing_halfedges {
272            let he = self
273                .halfedges
274                .get(he_id)
275                .or_else(error_none!("Halfedge not found"))?;
276
277            let twin_id = he.twin.or_else(error_none!("Twin not found"))?;
278
279            self.halfedges
280                .get_mut(twin_id)
281                .or_else(error_none!("Twin not found"))?
282                .end_vertex = new_vert_id;
283        }
284
285        // just created above
286        self.vertices[new_vert_id].outgoing_halfedge = Some(outgoing_halfedges[0]);
287
288        self.outgoing_halfedges
289            .insert(new_vert_id, outgoing_halfedges);
290
291        Some(new_vert_id)
292    }
293
294    fn remove_degenerate_edges(&mut self, vertex_id: VertexId) -> Option<VertexId> {
295        let halfedges = self
296            .vertices
297            .get(vertex_id)
298            .or_else(error_none!("Vertex not found"))?
299            .outgoing_halfedges(self)
300            .collect_vec();
301
302        for [he_id1, he_id2] in halfedges.into_iter().array_combinations() {
303            if self.halfedges_share_all_vertices(he_id1, he_id2) {
304                let he1 = self.halfedges[he_id1]; // checked in `halfedges_share_all_vertices`
305                let twin_id1 = he1.twin.or_else(error_none!("Twin not found"))?;
306                let face_id1 = self
307                    .halfedges
308                    .get(twin_id1)
309                    .or_else(error_none!("Halfedge not found"))?
310                    .face
311                    .or_else(error_none!("Face not found"))?;
312                let face_id2 = self.halfedges[he_id2] // checked in `halfedges_share_all_vertices`
313                    .face
314                    .or_else(error_none!("Face not found"))?;
315
316                // checked above
317                let coincident_face_ids = self.vertices[vertex_id].faces(self).collect_vec();
318                let mut start_idx = 0;
319
320                for (idx, face_id) in coincident_face_ids.iter().enumerate() {
321                    if *face_id == face_id1 {
322                        start_idx = idx;
323                        break;
324                    }
325                }
326
327                let mut end_idx = start_idx;
328                let mut side_one = vec![];
329
330                for _ in 0..coincident_face_ids.len() {
331                    let face_id = coincident_face_ids[end_idx];
332
333                    side_one.push(face_id);
334
335                    end_idx += 1;
336                    end_idx %= coincident_face_ids.len();
337
338                    if face_id == face_id2 {
339                        break;
340                    }
341                }
342
343                let mut side_two = vec![];
344
345                for _ in 0..coincident_face_ids.len() {
346                    if end_idx == start_idx {
347                        break;
348                    }
349
350                    let face_id = coincident_face_ids[end_idx];
351
352                    side_two.push(face_id);
353
354                    end_idx += 1;
355                    end_idx %= coincident_face_ids.len();
356                }
357
358                return self.split_regions_at_edge(vertex_id, he1.end_vertex, side_one, side_two);
359            }
360        }
361
362        None
363    }
364
365    #[instrument(skip(self))]
366    fn split_regions_at_edge(
367        &mut self,
368        vertex_id: VertexId,
369        other_vertex_id: VertexId,
370        side_one: Vec<FaceId>,
371        side_two: Vec<FaceId>,
372    ) -> Option<VertexId> {
373        if side_one.len() < 2 || side_two.len() < 2 {
374            error!("Not enough halfedges to split");
375            return None;
376        }
377
378        #[cfg(feature = "rerun")]
379        {
380            self.log_vert_rerun("split_regions_at_edge", vertex_id);
381            self.log_faces_rerun("split_regions_at_edge/side_one", &side_one);
382            self.log_faces_rerun("split_regions_at_edge/side_two", &side_two);
383        }
384
385        let new_vertex_id = self.add_vertex(
386            *self
387                .positions
388                .get(vertex_id)
389                .or_else(error_none!("Vertex position not found"))?,
390        );
391
392        // Updating vertices
393        for face_id in &side_two {
394            let face = self
395                .faces
396                .get(*face_id)
397                .or_else(error_none!("Face not found"))?;
398
399            for he_id in face.halfedges(self).collect_vec() {
400                // already checked in iterator that this he exists
401                let he = &mut self.halfedges[he_id];
402
403                if he.end_vertex == vertex_id {
404                    he.end_vertex = new_vertex_id;
405                }
406            }
407        }
408
409        self.weld_faces_at(
410            vertex_id,
411            other_vertex_id,
412            side_one[side_one.len() - 1],
413            side_one[0],
414        );
415        self.weld_faces_at(
416            new_vertex_id,
417            other_vertex_id,
418            side_two[0],
419            side_two[side_two.len() - 1],
420        );
421
422        // Ground-truth rebuild: a seed-walk rebuild could miss halfedges behind a
423        // temporarily detached fan (the re-points above already partitioned the
424        // star), which would silently drop live outgoing entries.
425        #[cfg(feature = "instrumentation")]
426        crate::record_op_trace!(
427            "split_regions_at_edge({vertex_id:?}, {other_vertex_id:?}): new {new_vertex_id:?}"
428        );
429        self.rebuild_vertex_outgoing_list(vertex_id);
430        self.rebuild_vertex_outgoing_list(new_vertex_id);
431
432        Some(new_vertex_id)
433    }
434
435    #[instrument(skip_all)]
436    pub fn remove_degenerate_faces(
437        &mut self,
438        vertex_id: VertexId,
439        removed_vertices: &mut Vec<VertexId>,
440        removed_halfedges: &mut Vec<HalfedgeId>,
441        removed_faces: &mut Vec<FaceId>,
442    ) -> Option<VertexId> {
443        let faces = self
444            .vertices
445            .get(vertex_id)
446            .or_else(error_none!("Vertex not found"))?
447            .faces(self)
448            .collect_vec();
449
450        for [face_id1, face_id2] in faces.into_iter().array_combinations() {
451            if self.faces_share_all_vertices(face_id1, face_id2) {
452                // checked above
453                let coincident_face_ids = self.vertices[vertex_id].faces(self).collect_vec();
454                let mut start_idx = 0;
455
456                for (idx, face_id) in coincident_face_ids.iter().enumerate() {
457                    if *face_id == face_id1 {
458                        start_idx = (idx + 1) % coincident_face_ids.len();
459                        break;
460                    }
461                }
462
463                let mut end_idx = start_idx;
464                let mut side_one = vec![];
465
466                for _ in 0..coincident_face_ids.len() {
467                    let face_id = coincident_face_ids[end_idx];
468
469                    end_idx += 1;
470                    end_idx %= coincident_face_ids.len();
471
472                    if face_id == face_id2 {
473                        break;
474                    }
475
476                    side_one.push(face_id);
477                }
478
479                let mut side_two = vec![];
480
481                for _ in 0..coincident_face_ids.len() {
482                    if end_idx == start_idx {
483                        break;
484                    }
485
486                    let face_id = coincident_face_ids[end_idx];
487
488                    side_two.push(face_id);
489
490                    end_idx += 1;
491                    end_idx %= coincident_face_ids.len();
492                }
493
494                side_two.pop();
495
496                let new_vertex_id =
497                    self.split_regions_at_vertex(vertex_id, side_one, side_two, removed_halfedges);
498
499                let (del_v_ids, del_he_ids) = self.remove_face(face_id1);
500                removed_vertices.extend(del_v_ids);
501                removed_halfedges.extend(del_he_ids);
502                removed_faces.push(face_id1);
503
504                let (del_v_ids, del_he_ids) = self.remove_face(face_id2);
505                removed_vertices.extend(del_v_ids);
506                removed_halfedges.extend(del_he_ids);
507                removed_faces.push(face_id2);
508
509                return new_vertex_id;
510            }
511        }
512
513        None
514    }
515
516    #[instrument(skip(self))]
517    fn split_regions_at_vertex(
518        &mut self,
519        vertex_id: VertexId,
520        side_one: Vec<FaceId>,
521        side_two: Vec<FaceId>,
522        removed_halfedges: &mut Vec<HalfedgeId>,
523    ) -> Option<VertexId> {
524        if side_one.len() < 2 || side_two.len() < 2 {
525            error!("Not enough halfedges to split");
526            return None;
527        }
528
529        #[cfg(feature = "rerun")]
530        {
531            self.log_vert_rerun("split_regions_at_vertex", vertex_id);
532            self.log_faces_rerun("split_regions_at_vertex/side_one", &side_one);
533            self.log_faces_rerun("split_regions_at_vertex/side_two", &side_two);
534        }
535
536        let new_vertex_id = self.add_vertex(
537            *self
538                .positions
539                .get(vertex_id)
540                .or_else(error_none!("Vertex position not found"))?,
541        );
542
543        // Updating start vertex
544        for face_id in &side_two {
545            let face = self
546                .faces
547                .get(*face_id)
548                .or_else(error_none!("Face not found"))?;
549
550            for he_id in face.halfedges(self).collect_vec() {
551                // already checked in iterator that this he exists
552                let he = &mut self.halfedges[he_id];
553                if he.end_vertex == vertex_id {
554                    he.end_vertex = new_vertex_id;
555                }
556            }
557        }
558
559        self.weld_faces(vertex_id, side_one[side_one.len() - 1], side_one[0]);
560        self.weld_faces(new_vertex_id, side_two[0], side_two[side_two.len() - 1]);
561
562        // Ground-truth rebuild: see `split_regions_at_edge`.
563        #[cfg(feature = "instrumentation")]
564        crate::record_op_trace!("split_regions_at_vertex({vertex_id:?}): new {new_vertex_id:?}");
565        self.rebuild_vertex_outgoing_list(vertex_id);
566        self.rebuild_vertex_outgoing_list(new_vertex_id);
567
568        Some(new_vertex_id)
569    }
570
571    /// This finds the common edge between the two faces and welds them together by connecting
572    /// the two `twin` relationships. The old twins are re-paired with each other so
573    /// every halfedge stays paired and the twin relationship stays symmetric (see
574    /// [`Self::weld_faces_at`]).
575    ///
576    /// `start_vertex_id` is shared by `face_id1` and `face_id2`.
577    fn weld_faces(
578        &mut self,
579        start_vertex_id: VertexId,
580        face_id1: FaceId,
581        face_id2: FaceId,
582    ) -> Option<()> {
583        // #[cfg(feature = "rerun")]
584        // {
585        //     self.log_face_rerun("face1", face_id1);
586        //     self.log_face_rerun("face2", face_id2);
587        // }
588
589        let face1 = self
590            .faces
591            .get(face_id1)
592            .or_else(error_none!("Face not found"))?;
593        let face2 = self
594            .faces
595            .get(face_id2)
596            .or_else(error_none!("Face not found"))?;
597
598        let face1_vertices = face1.vertices(self).collect::<HashSet<_>>();
599        let face2_vertices = face2.vertices(self).collect::<HashSet<_>>();
600
601        let mut other_common_vertex_id = None;
602
603        for &v_id in face1_vertices.intersection(&face2_vertices) {
604            if v_id != start_vertex_id {
605                other_common_vertex_id = Some(v_id);
606                break;
607            }
608        }
609
610        let other_common_vertex_id =
611            other_common_vertex_id.or_else(error_none!("No other common vertex found"))?;
612
613        self.weld_faces_at(start_vertex_id, other_common_vertex_id, face_id1, face_id2)
614    }
615
616    #[instrument(skip(self))]
617    fn weld_faces_at(
618        &mut self,
619        start_vertex_id: VertexId,
620        other_common_vertex_id: VertexId,
621        face_id1: FaceId,
622        face_id2: FaceId,
623    ) -> Option<()> {
624        let face1 = self
625            .faces
626            .get(face_id1)
627            .or_else(error_none!("Face not found"))?;
628        let face2 = self
629            .faces
630            .get(face_id2)
631            .or_else(error_none!("Face not found"))?;
632
633        let he1_id = face1
634            .halfedge_between(start_vertex_id, other_common_vertex_id, self)
635            .or_else(error_none!("Halfedge between vertices not found"))?;
636
637        let he2_id = face2
638            .halfedge_between(start_vertex_id, other_common_vertex_id, self)
639            .or_else(error_none!("Halfedge between vertices not found"))?;
640
641        // The face chains can be stale after flap removals, so verify the halfedges are
642        // still present before re-pairing them (a dangling twin write corrupts the mesh).
643        if !self.halfedges.contains_key(he1_id) || !self.halfedges.contains_key(he2_id) {
644            error!("Halfedge not found in weld_faces_at");
645            return None;
646        }
647        // Two coincident faces on the same chain share the same halfedge(s): re-pairing
648        // would write a self-twin (`he1.twin = he1`), which later makes the collapse's
649        // `remove_halfedge_face` re-pair fail and strands live face members twinless.
650        // Leave the pairing untouched; coincident faces are handled by the flap cleanup.
651        if he1_id == he2_id {
652            error!("weld_faces_at: faces share the same halfedge {he1_id:?}");
653            return None;
654        }
655        // The old twins' back-references must not be left dangling: if `he1_id` was not
656        // already `he2_id`'s twin, the weld re-pairs the two faces' edge halves while
657        // the old partners (`he1_id`'s and `he2_id`'s previous twins) would keep
658        // pointing at the re-paired halfedges — one-sided references that later
659        // removals cannot see from the removed halfedge's own twin field and that no
660        // cleanup pass repairs anymore. When both old partners are clean symmetric
661        // partners they are re-paired with each other, which keeps every halfedge
662        // paired (no twinless survivors, no one-sided twins); a partner that cannot
663        // be re-paired is severed instead.
664        let t1 = self.halfedges.get(he1_id).and_then(|h| h.twin);
665        let t2 = self.halfedges.get(he2_id).and_then(|h| h.twin);
666        let t1_swappable = t1.is_some_and(|t1| {
667            t1 != he2_id
668                && self
669                    .halfedges
670                    .get(t1)
671                    .is_some_and(|t| t.twin == Some(he1_id))
672        });
673        let t2_swappable = t2.is_some_and(|t2| {
674            t2 != he1_id
675                && self
676                    .halfedges
677                    .get(t2)
678                    .is_some_and(|t| t.twin == Some(he2_id))
679        });
680        match (t1_swappable, t2_swappable) {
681            (true, true) => {
682                // Both old partners are clean: re-pair them with each other (the two
683                // duplicate edges swap partners), keeping all four halfedges paired.
684                let t1 = t1.unwrap();
685                let t2 = t2.unwrap();
686                if t1 == t2 {
687                    // Both sides are the same halfedge (identical faces); pairing it
688                    // with itself is a self-twin. Leave it as-is.
689                    error!("weld_faces_at: old partners are identical {t1:?}");
690                } else {
691                    self.halfedges.get_mut(t1).unwrap().twin = Some(t2);
692                    self.halfedges.get_mut(t2).unwrap().twin = Some(t1);
693                }
694            }
695            (true, false) => {
696                // `he2`'s old partner is gone/unpaired: `t1` loses its partner when
697                // `he1` is re-paired below. Give it a fresh boundary partner instead
698                // of leaving it twinless.
699                let t1 = t1.unwrap();
700                // `t1` is `he1`'s twin, so `t1.start == he1.end_vertex`.
701                let t1_start = self
702                    .halfedges
703                    .get(he1_id)
704                    .map(|h| h.end_vertex)
705                    .or_else(error_none!("Weld sever start vertex not found"))?;
706                if self.pair_with_fresh_boundary_half(t1, t1_start).is_none() {
707                    error!("weld_faces_at: could not re-pair severed partner {t1:?}");
708                }
709            }
710            (false, true) => {
711                // `t2` is `he2`'s twin, so `t2.start == he2.end_vertex`.
712                let t2 = t2.unwrap();
713                let t2_start = self
714                    .halfedges
715                    .get(he2_id)
716                    .map(|h| h.end_vertex)
717                    .or_else(error_none!("Weld sever start vertex not found"))?;
718                if self.pair_with_fresh_boundary_half(t2, t2_start).is_none() {
719                    error!("weld_faces_at: could not re-pair severed partner {t2:?}");
720                }
721            }
722            _ => {}
723        }
724        self.halfedges[he1_id].twin = Some(he2_id);
725        self.halfedges[he2_id].twin = Some(he1_id);
726
727        let (start_out_he_id, other_out_he_id) =
728            if self.halfedges[he1_id].end_vertex == start_vertex_id {
729                (he2_id, he1_id)
730            } else {
731                (he1_id, he2_id)
732            };
733
734        self.vertices
735            .get_mut(start_vertex_id)
736            .or_else(error_none!("Start vertex not found"))?
737            .outgoing_halfedge = Some(start_out_he_id);
738        self.vertices
739            .get_mut(other_common_vertex_id)
740            .or_else(error_none!("Other vertex not found"))?
741            .outgoing_halfedge = Some(other_out_he_id);
742
743        Some(())
744    }
745
746    pub(crate) fn remove_single_face(
747        &mut self,
748        vertex_id: VertexId,
749        removed_vertices: &mut Vec<VertexId>,
750        removed_halfedges: &mut Vec<HalfedgeId>,
751        removed_faces: &mut Vec<FaceId>,
752    ) -> Option<bool> {
753        let face_ids = self
754            .outgoing_halfedges
755            .get(vertex_id)
756            .or_else(error_none!(
757                "Outgoing halfedges not found for vertex {vertex_id:?}"
758            ))?
759            .iter()
760            .filter_map(|&he_id| {
761                self.halfedges
762                    .get(he_id)
763                    .or_else(error_none!("Halfedge not found"))
764                    .and_then(|he| he.face)
765            })
766            .collect_vec();
767
768        if face_ids.len() == 1 {
769            let face_id = face_ids[0];
770
771            let (v_ids, he_ids) = self.remove_face(face_id);
772
773            removed_vertices.extend(v_ids);
774            removed_halfedges.extend(he_ids);
775            removed_faces.push(face_id);
776
777            Some(true)
778        } else {
779            Some(false)
780        }
781    }
782
783    /// Removes all neighboring flaps (triangles that share all the same vertices) connected to the given vertex.
784    pub fn remove_neighboring_flaps(
785        &mut self,
786        vertex_id: VertexId,
787        removed_vertices: &mut Vec<VertexId>,
788        removed_halfedges: &mut Vec<HalfedgeId>,
789        removed_faces: &mut Vec<FaceId>,
790    ) -> Option<bool> {
791        let faces = self
792            .vertices
793            .get(vertex_id)
794            .or_else(error_none!("Vertex not found"))?
795            .faces(self)
796            .collect_vec();
797
798        if faces.len() < 2 {
799            return Some(false);
800        }
801
802        let mut face_tuples = faces.into_iter().circular_array_windows().collect_vec();
803
804        while let Some([face_id1, face_id2]) = face_tuples.pop() {
805            if self.faces_share_all_vertices(face_id1, face_id2) {
806                #[cfg(feature = "rerun")]
807                {
808                    self.log_vert_rerun("flap", vertex_id);
809                    self.log_face_rerun("flap1", face_id1);
810                    self.log_face_rerun("flap2", face_id2);
811                }
812
813                let mut halfedges_of_faces = HashSet::<HalfedgeId>::from_iter(
814                    self.halfedges.iter().filter_map(|(he_id, he)| {
815                        if he.face == Some(face_id1) || he.face == Some(face_id2) {
816                            Some(he_id)
817                        } else {
818                            None
819                        }
820                    }),
821                );
822
823                let (vs, hes) = self.remove_face(face_id1);
824                for he_id in &hes {
825                    halfedges_of_faces.remove(he_id);
826                }
827                removed_vertices.extend(vs);
828                removed_halfedges.extend(hes);
829                removed_faces.push(face_id1);
830
831                let (vs, hes) = self.remove_face(face_id2);
832                for he_id in &hes {
833                    halfedges_of_faces.remove(he_id);
834                }
835                removed_vertices.extend(vs);
836                removed_halfedges.extend(hes);
837                removed_faces.push(face_id2);
838
839                // TODO : Handle the case when there is only one halfedge?
840                if halfedges_of_faces.len() >= 2 {
841                    for [he_id1, he_id2] in halfedges_of_faces.iter().copied().array_combinations()
842                    {
843                        let Some(he1) = self.halfedges.get(he_id1) else {
844                            // might have been deleted by prior iteration
845                            continue;
846                        };
847                        let twin_id1 = he1.twin.or_else(error_none!("Twin 1 missing"))?;
848
849                        let Some(he2) = self.halfedges.get(he_id2) else {
850                            continue;
851                        };
852                        let twin_id2 = he2.twin.or_else(error_none!("Twin 2 missing"))?;
853
854                        let start_v_id1 = he1
855                            .start_vertex(self)
856                            .or_else(error_none!("Start vertex 1 missing"))?;
857                        let start_v_id2 = he2
858                            .start_vertex(self)
859                            .or_else(error_none!("Start vertex 2 missing"))?;
860
861                        if he1.end_vertex != start_v_id2 || he2.end_vertex != start_v_id1 {
862                            continue;
863                        }
864
865                        // the twin edges of the neighboring faces of the deleted faces are still there
866                        // we need to remove them and re-connect (twin) their twin edges
867
868                        let he1_end_v = he1.end_vertex;
869                        let he2_end_v = he2.end_vertex;
870
871                        self.remove_only_halfedge(he_id1);
872                        self.remove_only_halfedge(he_id2);
873                        removed_halfedges.push(he_id1);
874                        removed_halfedges.push(he_id2);
875
876                        #[cfg(feature = "instrumentation")]
877                        crate::record_op_trace!(
878                            "flap removal: removed {he_id1:?}+{he_id2:?} (edge {start_v_id1:?}-{start_v_id2:?}); twins {twin_id1:?}/{twin_id2:?}"
879                        );
880
881                        // `remove_only_halfedge` above cleared the twins' back-pointers, so
882                        // `twin_id1`/`twin_id2` are temporarily twinless. Re-pair them with
883                        // each other below; if one is already gone (dangling twin in a
884                        // degenerate neighborhood) the other must still get a partner — never
885                        // leave a surviving halfedge twinless (that would later make
886                        // `remove_face` of its face abort mid-detach and break the chain).
887                        let twin1_alive = self.halfedges.contains_key(twin_id1);
888                        let twin2_alive = self.halfedges.contains_key(twin_id2);
889
890                        // Sever any remaining one-sided back-references to the re-paired
891                        // halfedges (see `weld_faces_at`): removing the two halves below
892                        // touches no other twin pointer, so a pre-existing asymmetric
893                        // pair can still reference `twin_id1`/`twin_id2` from elsewhere.
894                        // Re-pair such a stray referencer with a fresh boundary half
895                        // instead of leaving it twinless.
896                        if twin1_alive
897                            && let Some(t1) = self.halfedges.get(twin_id1).and_then(|h| h.twin)
898                            && t1 != twin_id2
899                            && let Some(t1_he) = self.halfedges.get(t1)
900                            && t1_he.twin == Some(twin_id1)
901                            && self
902                                .pair_with_fresh_boundary_half(
903                                    t1,
904                                    self.halfedges[twin_id1].end_vertex,
905                                )
906                                .is_none()
907                        {
908                            error!("remove_neighboring_flaps: could not re-pair {t1:?}");
909                        }
910                        if twin2_alive
911                            && let Some(t2) = self.halfedges.get(twin_id2).and_then(|h| h.twin)
912                            && t2 != twin_id1
913                            && let Some(t2_he) = self.halfedges.get(t2)
914                            && t2_he.twin == Some(twin_id2)
915                            && self
916                                .pair_with_fresh_boundary_half(
917                                    t2,
918                                    self.halfedges[twin_id2].end_vertex,
919                                )
920                                .is_none()
921                        {
922                            error!("remove_neighboring_flaps: could not re-pair {t2:?}");
923                        }
924
925                        // The removed halves are opposite halves of one edge (`he1.end ==
926                        // start_v_id2`), so the survivors take over as the vertex seeds:
927                        // `twin_id1` is outgoing from `start_v_id2`, `twin_id2` from
928                        // `start_v_id1`. When a survivor gets a fresh partner, that fresh
929                        // half occupies the survivor's own start side instead.
930                        match (twin1_alive, twin2_alive) {
931                            (true, true) => {
932                                self.halfedges.get_mut(twin_id1).unwrap().twin = Some(twin_id2);
933                                self.halfedges.get_mut(twin_id2).unwrap().twin = Some(twin_id1);
934                                #[cfg(feature = "instrumentation")]
935                                crate::record_op_trace!(
936                                    "flap removal: re-paired {twin_id1:?}<->{twin_id2:?}"
937                                );
938                                if let Some(v) = self.vertices.get_mut(start_v_id1) {
939                                    v.outgoing_halfedge = Some(twin_id2);
940                                }
941                                if let Some(v) = self.vertices.get_mut(start_v_id2) {
942                                    v.outgoing_halfedge = Some(twin_id1);
943                                }
944                            }
945                            (true, false) => {
946                                // `twin_id2` is gone; re-pair `twin_id1` with a fresh
947                                // boundary half (outgoing from `start_v_id1`).
948                                match self.pair_with_fresh_boundary_half(twin_id1, he1_end_v) {
949                                    Some(fresh) => {
950                                        if let Some(v) = self.vertices.get_mut(start_v_id1) {
951                                            v.outgoing_halfedge = Some(fresh);
952                                        }
953                                        if let Some(v) = self.vertices.get_mut(start_v_id2) {
954                                            v.outgoing_halfedge = Some(twin_id1);
955                                        }
956                                    }
957                                    None => error!(
958                                        "remove_neighboring_flaps: could not re-pair twin {twin_id1:?}"
959                                    ),
960                                }
961                            }
962                            (false, true) => {
963                                match self.pair_with_fresh_boundary_half(twin_id2, he2_end_v) {
964                                    Some(fresh) => {
965                                        if let Some(v) = self.vertices.get_mut(start_v_id1) {
966                                            v.outgoing_halfedge = Some(twin_id2);
967                                        }
968                                        if let Some(v) = self.vertices.get_mut(start_v_id2) {
969                                            v.outgoing_halfedge = Some(fresh);
970                                        }
971                                    }
972                                    None => error!(
973                                        "remove_neighboring_flaps: could not re-pair twin {twin_id2:?}"
974                                    ),
975                                }
976                            }
977                            (false, false) => {
978                                // Both twins of this edge are already gone, so nothing
979                                // survives to take over as either endpoint's vertex seed.
980                                // If a `outgoing_halfedge` seed still points at one of the
981                                // just-removed halfedges, repair it so ring traversals
982                                // don't start from a dead id.
983                                self.reseed_outgoing_if_dead(start_v_id1);
984                                self.reseed_outgoing_if_dead(start_v_id2);
985                            }
986                        }
987                    }
988                } else if halfedges_of_faces.len() == 1 {
989                    tracing::debug!("Single orphaned halfedge after flap removal (boundary edge)");
990
991                    if let Some(&single_he_id) = halfedges_of_faces.iter().next() {
992                        if let Some(single_he) = self.halfedges.get(single_he_id) {
993                            if single_he.twin.is_none() {
994                                tracing::error!("Single orphaned halfedge found and has no twin");
995                            }
996                        } else {
997                            tracing::error!("Single orphaned halfedge not found")
998                        }
999                    }
1000                }
1001
1002                return Some(true);
1003            }
1004        }
1005
1006        Some(false)
1007    }
1008}
1009
1010#[cfg(test)]
1011mod tests {
1012    use crate::ops::AddOrGetEdge;
1013
1014    use super::*;
1015    use glam::Vec3;
1016
1017    #[test]
1018    fn test_remove_degenerate_faces() {
1019        crate::utils::get_tracing_subscriber();
1020        let mut meshgraph = MeshGraph::new();
1021
1022        let center_v_id = meshgraph.add_vertex(Vec3::new(0.0, 0.0, 1.0));
1023
1024        let v1_id = meshgraph.add_vertex(Vec3::new(-0.2, 0.0, 0.0));
1025        let he_c_1_id = meshgraph
1026            .add_or_get_edge(center_v_id, v1_id)
1027            .unwrap()
1028            .start_to_end_he_id;
1029        let v1p_id = meshgraph.add_vertex(Vec3::new(-0.2, 0.0, 0.0));
1030        let AddOrGetEdge {
1031            start_to_end_he_id: he_c_1p_id,
1032            twin_he_id: he_1p_c_id,
1033            ..
1034        } = meshgraph.add_or_get_edge(center_v_id, v1p_id).unwrap();
1035
1036        let v2_id = meshgraph.add_vertex(Vec3::new(-1.0, 1.0, 0.0));
1037        let he_c_2_id = meshgraph
1038            .add_or_get_edge(center_v_id, v2_id)
1039            .unwrap()
1040            .start_to_end_he_id;
1041
1042        let v3_id = meshgraph.add_vertex(Vec3::new(-1.0, -1.0, 0.0));
1043        let he_c_3_id = meshgraph
1044            .add_or_get_edge(center_v_id, v3_id)
1045            .unwrap()
1046            .start_to_end_he_id;
1047
1048        let v4_id = meshgraph.add_vertex(Vec3::new(0.2, 0.0, 0.0));
1049        let he_c_4_id = meshgraph
1050            .add_or_get_edge(center_v_id, v4_id)
1051            .unwrap()
1052            .start_to_end_he_id;
1053
1054        let v4p_id = meshgraph.add_vertex(Vec3::new(0.2, 0.0, 0.0));
1055        let AddOrGetEdge {
1056            start_to_end_he_id: he_c_4p_id,
1057            twin_he_id: he_4p_c_id,
1058            ..
1059        } = meshgraph.add_or_get_edge(center_v_id, v4p_id).unwrap();
1060
1061        let v5_id = meshgraph.add_vertex(Vec3::new(1.0, -1.0, 0.0));
1062        let he_c_5_id = meshgraph
1063            .add_or_get_edge(center_v_id, v5_id)
1064            .unwrap()
1065            .start_to_end_he_id;
1066
1067        let v6_id = meshgraph.add_vertex(Vec3::new(1.0, 1.0, 0.0));
1068        let he_c_6_id = meshgraph
1069            .add_or_get_edge(center_v_id, v6_id)
1070            .unwrap()
1071            .start_to_end_he_id;
1072
1073        meshgraph
1074            .add_face_from_halfedges(he_c_1_id, he_c_2_id)
1075            .unwrap();
1076        meshgraph
1077            .add_face_from_halfedges(he_c_2_id, he_c_3_id)
1078            .unwrap();
1079        meshgraph
1080            .add_face_from_halfedges(he_c_3_id, he_c_1p_id)
1081            .unwrap();
1082
1083        meshgraph
1084            .add_face_from_halfedges(he_c_1p_id, he_c_4p_id)
1085            .unwrap();
1086
1087        meshgraph
1088            .add_face_from_halfedges(he_c_4p_id, he_c_5_id)
1089            .unwrap();
1090        meshgraph
1091            .add_face_from_halfedges(he_c_5_id, he_c_6_id)
1092            .unwrap();
1093        meshgraph
1094            .add_face_from_halfedges(he_c_6_id, he_c_4_id)
1095            .unwrap();
1096
1097        meshgraph
1098            .add_face_from_halfedges(he_c_1_id, he_c_4_id)
1099            .unwrap();
1100
1101        meshgraph.halfedges[he_c_1p_id].end_vertex = v1_id;
1102        meshgraph.halfedges[he_c_4p_id].end_vertex = v4_id;
1103
1104        // already created from face above
1105        let AddOrGetEdge {
1106            start_to_end_he_id: he_1p_4p_id,
1107            twin_he_id: he_4p_1p_id,
1108            ..
1109        } = meshgraph.add_or_get_edge(v1p_id, v4p_id).unwrap();
1110        meshgraph.halfedges[he_1p_4p_id].end_vertex = v4_id;
1111        meshgraph.halfedges[he_4p_1p_id].end_vertex = v1_id;
1112
1113        let AddOrGetEdge {
1114            start_to_end_he_id: he_3_1p_id,
1115            twin_he_id: he_1p_3_id,
1116            ..
1117        } = meshgraph.add_or_get_edge(v3_id, v1p_id).unwrap();
1118        meshgraph.halfedges[he_3_1p_id].end_vertex = v1_id;
1119
1120        let AddOrGetEdge {
1121            start_to_end_he_id: he_4p_5_id,
1122            twin_he_id: he_5_4p_id,
1123            ..
1124        } = meshgraph.add_or_get_edge(v4p_id, v5_id).unwrap();
1125        meshgraph.halfedges[he_5_4p_id].end_vertex = v4_id;
1126
1127        meshgraph.outgoing_halfedges[v1_id].push(he_1p_4p_id);
1128        meshgraph.outgoing_halfedges[v1_id].push(he_1p_c_id);
1129        meshgraph.outgoing_halfedges[v1_id].push(he_1p_3_id);
1130        meshgraph.outgoing_halfedges[v4_id].push(he_4p_1p_id);
1131        meshgraph.outgoing_halfedges[v4_id].push(he_4p_c_id);
1132        meshgraph.outgoing_halfedges[v4_id].push(he_4p_5_id);
1133
1134        meshgraph.remove_only_vertex(v1p_id);
1135        meshgraph.remove_only_vertex(v4p_id);
1136
1137        #[cfg(feature = "rerun")]
1138        meshgraph.log_rerun();
1139
1140        let mut removed_vertices = vec![];
1141        let mut removed_halfedges = vec![];
1142        let mut removed_faces = vec![];
1143
1144        meshgraph.remove_degenerate_faces(
1145            center_v_id,
1146            &mut removed_vertices,
1147            &mut removed_halfedges,
1148            &mut removed_faces,
1149        );
1150
1151        #[cfg(feature = "rerun")]
1152        {
1153            meshgraph.log_rerun();
1154            crate::RR.flush_blocking().unwrap();
1155        }
1156    }
1157
1158    #[test]
1159    fn test_remove_degenerate_edges() {
1160        crate::utils::get_tracing_subscriber();
1161
1162        let mut meshgraph = MeshGraph::new();
1163
1164        let center_v_id = meshgraph.add_vertex(Vec3::new(0.0, 0.0, 1.0));
1165
1166        let v1_id = meshgraph.add_vertex(Vec3::new(0.0, 0.0, 0.0));
1167        let he_c_1_id = meshgraph
1168            .add_or_get_edge(center_v_id, v1_id)
1169            .unwrap()
1170            .start_to_end_he_id;
1171
1172        let v1p_id = meshgraph.add_vertex(Vec3::new(0.0, 0.0, 0.0));
1173        let AddOrGetEdge {
1174            start_to_end_he_id: he_c_1p_id,
1175            twin_he_id: he_1p_c_id,
1176            ..
1177        } = meshgraph.add_or_get_edge(center_v_id, v1p_id).unwrap();
1178
1179        let v2_id = meshgraph.add_vertex(Vec3::new(-1.0, 1.0, 0.0));
1180        let he_c_2_id = meshgraph
1181            .add_or_get_edge(center_v_id, v2_id)
1182            .unwrap()
1183            .start_to_end_he_id;
1184
1185        let v3_id = meshgraph.add_vertex(Vec3::new(-1.0, -1.0, 0.0));
1186        let he_c_3_id = meshgraph
1187            .add_or_get_edge(center_v_id, v3_id)
1188            .unwrap()
1189            .start_to_end_he_id;
1190
1191        let v5_id = meshgraph.add_vertex(Vec3::new(1.0, -1.0, 0.0));
1192        let he_c_5_id = meshgraph
1193            .add_or_get_edge(center_v_id, v5_id)
1194            .unwrap()
1195            .start_to_end_he_id;
1196
1197        let v6_id = meshgraph.add_vertex(Vec3::new(1.0, 1.0, 0.0));
1198        let he_c_6_id = meshgraph
1199            .add_or_get_edge(center_v_id, v6_id)
1200            .unwrap()
1201            .start_to_end_he_id;
1202
1203        meshgraph
1204            .add_face_from_halfedges(he_c_1_id, he_c_2_id)
1205            .unwrap();
1206        meshgraph
1207            .add_face_from_halfedges(he_c_2_id, he_c_3_id)
1208            .unwrap();
1209        meshgraph
1210            .add_face_from_halfedges(he_c_3_id, he_c_1p_id)
1211            .unwrap();
1212
1213        meshgraph
1214            .add_face_from_halfedges(he_c_1p_id, he_c_5_id)
1215            .unwrap();
1216
1217        meshgraph
1218            .add_face_from_halfedges(he_c_5_id, he_c_6_id)
1219            .unwrap();
1220        meshgraph
1221            .add_face_from_halfedges(he_c_6_id, he_c_1_id)
1222            .unwrap();
1223
1224        meshgraph.halfedges[he_c_1p_id].end_vertex = v1_id;
1225
1226        // already created from face above
1227        let AddOrGetEdge {
1228            start_to_end_he_id: he_3_1p_id,
1229            twin_he_id: he_1p_3_id,
1230            ..
1231        } = meshgraph.add_or_get_edge(v3_id, v1p_id).unwrap();
1232        meshgraph.halfedges[he_3_1p_id].end_vertex = v1_id;
1233
1234        let AddOrGetEdge {
1235            start_to_end_he_id: he_1p_5_id,
1236            twin_he_id: he_5_1p_id,
1237            ..
1238        } = meshgraph.add_or_get_edge(v1p_id, v5_id).unwrap();
1239        meshgraph.halfedges[he_5_1p_id].end_vertex = v1_id;
1240
1241        meshgraph.outgoing_halfedges[v1_id].push(he_1p_c_id);
1242        meshgraph.outgoing_halfedges[v1_id].push(he_1p_5_id);
1243        meshgraph.outgoing_halfedges[v1_id].push(he_1p_3_id);
1244
1245        meshgraph.remove_only_vertex(v1p_id);
1246
1247        #[cfg(feature = "rerun")]
1248        meshgraph.log_rerun();
1249
1250        meshgraph.remove_degenerate_edges(center_v_id);
1251
1252        #[cfg(feature = "rerun")]
1253        {
1254            meshgraph.log_rerun();
1255            crate::RR.flush_blocking().unwrap();
1256        }
1257    }
1258
1259    #[test]
1260    fn test_remove_flap() {
1261        crate::utils::get_tracing_subscriber();
1262
1263        let mut mesh_graph = MeshGraph::new();
1264
1265        let v1 = mesh_graph.add_vertex(Vec3::new(0.0, 0.0, 0.0));
1266        let v2 = mesh_graph.add_vertex(Vec3::new(1.0, 0.0, 0.0));
1267        let v3 = mesh_graph.add_vertex(Vec3::new(0.0, 1.0, 0.0));
1268
1269        let v4 = mesh_graph.add_vertex(Vec3::new(1.0, 1.0, 0.5));
1270        let v5 = mesh_graph.add_vertex(Vec3::new(1.0, 1.0, -0.5));
1271
1272        let edge1 = mesh_graph.add_edge(v1, v2).unwrap();
1273        let edge2 = mesh_graph.add_edge(v2, v3).unwrap();
1274        let edge2_d = mesh_graph.add_edge(v2, v3).unwrap();
1275
1276        mesh_graph
1277            .add_face_from_halfedges(edge1.start_to_end_he_id, edge2.start_to_end_he_id)
1278            .unwrap();
1279        mesh_graph
1280            .add_face_from_halfedges(edge2_d.twin_he_id, edge1.twin_he_id)
1281            .unwrap();
1282
1283        mesh_graph
1284            .add_face_from_halfedge_and_vertex(edge2.twin_he_id, v4)
1285            .unwrap();
1286        mesh_graph
1287            .add_face_from_halfedge_and_vertex(edge2_d.start_to_end_he_id, v5)
1288            .unwrap();
1289
1290        #[cfg(feature = "rerun")]
1291        mesh_graph.log_rerun();
1292
1293        let mut removed_vertices = Vec::new();
1294        let mut removed_halfedges = Vec::new();
1295        let mut removed_faces = Vec::new();
1296
1297        mesh_graph.remove_neighboring_flaps(
1298            v1,
1299            &mut removed_vertices,
1300            &mut removed_halfedges,
1301            &mut removed_faces,
1302        );
1303
1304        #[cfg(feature = "rerun")]
1305        {
1306            mesh_graph.log_rerun();
1307            crate::RR.flush_blocking().unwrap();
1308        }
1309
1310        assert_eq!(removed_vertices.len(), 1);
1311        assert_eq!(removed_halfedges.len(), 6);
1312        assert_eq!(removed_faces.len(), 2);
1313    }
1314
1315    #[test]
1316    fn test_remove_double_flaps() {
1317        crate::utils::get_tracing_subscriber();
1318
1319        let mut mesh_graph = MeshGraph::new();
1320
1321        let v1 = mesh_graph.add_vertex(Vec3::new(0.0, 0.0, 0.0));
1322        let v2 = mesh_graph.add_vertex(Vec3::new(1.0, 0.0, 0.0));
1323        let v3 = mesh_graph.add_vertex(Vec3::new(0.0, 1.0, 0.0));
1324        let v4 = mesh_graph.add_vertex(Vec3::new(1.0, 1.0, 0.0));
1325
1326        let v5 = mesh_graph.add_vertex(Vec3::new(0.0, -1.0, 0.0));
1327
1328        let edge1 = mesh_graph.add_edge(v1, v2).unwrap();
1329        let edge1_d = mesh_graph.add_edge(v1, v2).unwrap();
1330        let edge2 = mesh_graph.add_edge(v2, v3).unwrap();
1331        let edge2_d = mesh_graph.add_edge(v2, v3).unwrap();
1332
1333        mesh_graph
1334            .add_face_from_halfedges(edge1.start_to_end_he_id, edge2.start_to_end_he_id)
1335            .unwrap();
1336        mesh_graph
1337            .add_face_from_halfedge_and_vertex(edge2.twin_he_id, v4)
1338            .unwrap();
1339
1340        mesh_graph
1341            .add_face_from_halfedge_and_vertex(edge2_d.start_to_end_he_id, v4)
1342            .unwrap();
1343        mesh_graph
1344            .add_face_from_halfedges(edge2_d.twin_he_id, edge1_d.twin_he_id)
1345            .unwrap();
1346
1347        mesh_graph
1348            .add_face_from_halfedge_and_vertex(edge1_d.start_to_end_he_id, v5)
1349            .unwrap();
1350
1351        #[cfg(feature = "rerun")]
1352        mesh_graph.log_rerun();
1353
1354        let mut removed_vertices = Vec::new();
1355        let mut removed_halfedges = Vec::new();
1356        let mut removed_faces = Vec::new();
1357
1358        mesh_graph.remove_neighboring_flaps(
1359            v1,
1360            &mut removed_vertices,
1361            &mut removed_halfedges,
1362            &mut removed_faces,
1363        );
1364
1365        #[cfg(feature = "rerun")]
1366        {
1367            mesh_graph.log_rerun();
1368            crate::RR.flush_blocking().unwrap();
1369        }
1370
1371        assert_eq!(removed_vertices.len(), 0);
1372        assert_eq!(removed_halfedges.len(), 6);
1373        assert_eq!(removed_faces.len(), 2);
1374    }
1375}