Skip to main content

mesh_graph/ops/merge_one_ring/
mod.rs

1#[cfg(test)]
2mod tests;
3
4use std::ops::RangeInclusive;
5
6use hashbrown::HashSet;
7use itertools::Itertools;
8use tracing::{error, instrument};
9
10use crate::{
11    AddEdge, FaceId, HalfedgeId, MeshGraph, Vertex, VertexId, error_none,
12    ops::{add::AddFace, collapse::CollapseEdge, edit::MergeVertices},
13    utils::unwrap_or_return,
14};
15
16#[derive(Default)]
17pub struct MergeVerticesOneRing {
18    pub removed_vertices: Vec<VertexId>,
19    pub removed_halfedges: Vec<HalfedgeId>,
20    pub removed_faces: Vec<FaceId>,
21
22    pub added_vertices: Vec<VertexId>,
23    pub added_halfedges: Vec<HalfedgeId>,
24    pub added_faces: Vec<FaceId>,
25}
26
27impl MeshGraph {
28    /// Merge two vertices by connecting their 1-rings.
29    ///
30    /// The vertices are deleted on top of everything that is returned in the `removed_...` fields.
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, marked_halfedges, marked_vertices))]
35    pub fn merge_vertices_one_rings(
36        &mut self,
37        vertex_id1: VertexId,
38        vertex_id2: VertexId,
39        flip_threshold_sqr: f32,
40        marked_halfedges: &mut HashSet<HalfedgeId>,
41        marked_vertices: &mut HashSet<VertexId>,
42    ) -> MergeVerticesOneRing {
43        #[cfg(feature = "instrumentation")]
44        crate::set_current_op("merge_one_ring");
45        #[cfg(feature = "instrumentation")]
46        crate::probe_chain_begin(self);
47        let mut result = MergeVerticesOneRing::default();
48
49        let vertex1 = *unwrap_or_return!(self.vertices.get(vertex_id1), "Vertex not found", result);
50        let vertex2 = *unwrap_or_return!(self.vertices.get(vertex_id2), "Vertex not found", result);
51
52        if vertex_id1 == vertex_id2 {
53            error!("Cannot merge a vertex with itself");
54            return result;
55        }
56
57        let one_ring_he_ids1 = vertex1.one_ring(self).collect_vec();
58        let mut one_ring_he_ids2 = vertex2
59            .one_ring(self)
60            // halfedges are reversed, so twin halfedges are actually the next halfedges.
61            // also existence is checked in `one_ring()`.
62            .filter_map(|he_id| {
63                // `one_ring` derives the ids from the outgoing star of the vertex via
64                // twins; a dead id here (stale seed or dangling twin) would panic on
65                // the index below, so skip it defensively.
66                let he = self.halfedges.get(he_id)?;
67                he.twin.or_else(error_none!("Twin not found"))
68            })
69            .collect_vec();
70        one_ring_he_ids2.reverse();
71
72        if one_ring_he_ids1.len() < 3 || one_ring_he_ids2.len() < 3 {
73            error!(
74                "One rings are too small. One ring of {vertex_id1:?} = {}; one ring of {vertex_id2:?} = {}",
75                one_ring_he_ids1.len(),
76                one_ring_he_ids2.len()
77            );
78            return result;
79        }
80
81        // halfedges' existence already checked in `one_ring()`.
82        let mut one_ring_v_ids1 = one_ring_he_ids1
83            .iter()
84            .filter_map(|he_id| {
85                // `one_ring` derives the ids from the outgoing star of the vertex via
86                // twins; a dead id here (stale seed or dangling twin) would panic on
87                // the index below, so skip it defensively.
88                if self.halfedges.contains_key(*he_id) {
89                    Some(self.halfedges[*he_id].end_vertex)
90                } else {
91                    None
92                }
93            })
94            .collect_vec();
95        one_ring_v_ids1.rotate_right(1);
96        let mut one_ring_v_ids2 = one_ring_he_ids2
97            .iter()
98            .filter_map(|he_id| {
99                if self.halfedges.contains_key(*he_id) {
100                    Some(self.halfedges[*he_id].end_vertex)
101                } else {
102                    None
103                }
104            })
105            .collect_vec();
106        one_ring_v_ids2.rotate_right(1);
107
108        if one_ring_v_ids1.len() < 3 || one_ring_v_ids2.len() < 3 {
109            error!("One rings are too small");
110            return result;
111        }
112
113        let one_ring_he_set1 = HashSet::<HalfedgeId>::from_iter(one_ring_he_ids1.iter().copied());
114        let one_ring_he_set2 = HashSet::<HalfedgeId>::from_iter(one_ring_he_ids2.iter().copied());
115        let shared_he_ids = HashSet::<HalfedgeId>::from_iter(
116            one_ring_he_set1.intersection(&one_ring_he_set2).copied(),
117        );
118
119        let one_ring_v_set1 = HashSet::<VertexId>::from_iter(one_ring_v_ids1.iter().copied());
120        let one_ring_v_set2 = HashSet::<VertexId>::from_iter(one_ring_v_ids2.iter().copied());
121        let shared_v_ids =
122            HashSet::<VertexId>::from_iter(one_ring_v_set1.intersection(&one_ring_v_set2).copied());
123
124        tracing::debug!("ring1 (v_ids): {one_ring_v_ids1:?}");
125        tracing::debug!("ring2 (v_ids): {one_ring_v_ids2:?}");
126        tracing::debug!("shared_v_ids: {shared_v_ids:?}");
127
128        if self.check_and_flip_single_shared_he(&shared_he_ids, flip_threshold_sqr, &mut result) {
129            return self.finish_merge(result);
130        }
131
132        self.remove_neighbor_faces(&vertex1, &vertex2, &mut result);
133
134        #[cfg(feature = "rerun")]
135        self.log_rerun();
136
137        let (already_connected_face_ids, connected_v_ids, connected_he_ids) = match self
138            .find_already_connected_pairings(&one_ring_v_ids1, &one_ring_v_ids2, &shared_v_ids)
139        {
140            Some(pairings) => pairings,
141            None => {
142                tracing::error!("Error in find_already_connected_pairings");
143                return self.finish_merge(result);
144            }
145        };
146
147        #[cfg(feature = "rerun")]
148        {
149            self.log_faces_rerun("already_connected", &already_connected_face_ids);
150            self.log_hes_rerun("already_connected", &connected_he_ids);
151        }
152
153        let range_pairs_to_connect = self.compute_range_pairs_to_connect(
154            &one_ring_v_ids1,
155            &one_ring_v_ids2,
156            &one_ring_he_ids1,
157            &one_ring_he_ids2,
158            &shared_v_ids,
159            &shared_he_ids,
160            connected_v_ids,
161            connected_he_ids,
162        );
163
164        if range_pairs_to_connect.is_empty() {
165            error!("No range pairs to connect");
166            return self.finish_merge(result);
167        }
168
169        tracing::debug!("Range pairs to connect: {range_pairs_to_connect:#?}");
170
171        let planned_faces =
172            self.plan_new_faces(&range_pairs_to_connect, &one_ring_v_ids1, &one_ring_v_ids2);
173        tracing::debug!("planned faces: {planned_faces:#?}");
174
175        for face_id in already_connected_face_ids {
176            let (v_ids, he_ids) = self.remove_face(face_id);
177            result.removed_faces.push(face_id);
178            result.removed_halfedges.extend(he_ids);
179            result.removed_vertices.extend(v_ids);
180        }
181
182        self.add_planned_faces(
183            planned_faces,
184            &one_ring_v_set1,
185            &one_ring_v_set2,
186            marked_halfedges,
187            &mut result,
188        );
189
190        #[cfg(feature = "rerun")]
191        self.log_rerun();
192
193        self.cleanup_bookkeeping(
194            &one_ring_v_ids1,
195            &one_ring_v_ids2,
196            marked_vertices,
197            marked_halfedges,
198            &mut result,
199        );
200
201        self.merge_remaining_unconnected(
202            one_ring_v_ids1.clone(),
203            one_ring_v_ids2.clone(),
204            &mut result,
205        );
206
207        self.smooth_vertices(
208            one_ring_v_ids1
209                .iter()
210                .chain(&one_ring_v_ids2)
211                .chain(&result.added_vertices)
212                .copied(),
213        );
214
215        self.finish_merge(result)
216    }
217
218    /// Common op-end path: run the integrity/hole inspectors and push the mesh
219    /// onto the state-history ring (hunt instrumentation), then return the result.
220    /// Every exit of the op goes through here (including the early failure
221    /// exits), so the ring stays continuous with the journal (resume
222    /// continuity) and no corruption can skip the checks.
223    #[cfg(feature = "instrumentation")]
224    fn finish_merge(&self, result: MergeVerticesOneRing) -> MergeVerticesOneRing {
225        if self.probe_chain_integrity("merge_vertices_one_rings") {
226            crate::state_history_push(self, "merge_vertices_one_rings");
227        }
228        result
229    }
230
231    #[cfg(not(feature = "instrumentation"))]
232    fn finish_merge(&self, result: MergeVerticesOneRing) -> MergeVerticesOneRing {
233        result
234    }
235
236    fn merge_remaining_unconnected(
237        &mut self,
238        mut one_ring_v_ids1: Vec<VertexId>,
239        mut one_ring_v_ids2: Vec<VertexId>,
240        result: &mut MergeVerticesOneRing,
241    ) {
242        while let Some(v_id) = one_ring_v_ids1.pop() {
243            let Some(vert) = self.vertices.get(v_id) else {
244                continue;
245            };
246
247            let mut vertices = vec![];
248
249            for he_id in vert.boundary_halfedgdes(self) {
250                let Some(he) = self.halfedges.get(he_id) else {
251                    error!("Halfedge not found: {he_id:?}");
252                    continue;
253                };
254
255                vertices.push(he.end_vertex);
256            }
257
258            if !vertices.is_empty() {
259                one_ring_v_ids1.retain(|v_id| !vertices.contains(v_id));
260                one_ring_v_ids2.retain(|v_id| !vertices.contains(v_id));
261
262                let MergeVertices {
263                    removed_vertices,
264                    removed_halfedges,
265                    removed_faces,
266                } = self.merge_vertices(vertices);
267
268                result.removed_vertices.extend(removed_vertices);
269                result.removed_halfedges.extend(removed_halfedges);
270                result.removed_faces.extend(removed_faces);
271            }
272        }
273    }
274
275    fn find_already_connected_pairings(
276        &mut self,
277        one_ring_v_ids1: &[VertexId],
278        one_ring_v_ids2: &[VertexId],
279        shared_v_ids: &HashSet<VertexId>,
280    ) -> Option<(Vec<FaceId>, HashSet<VertexId>, HashSet<HalfedgeId>)> {
281        let mut connected_v_ids = HashSet::new();
282        let mut connected_he_ids = HashSet::new();
283        let mut already_connected_face_ids = vec![];
284
285        // Only the first strip of already existing faces is taken. A second one
286        // that isn't contiguous with it would mark its ring vertices as connected
287        // too, and the ranges to connect are the complement of that: the region
288        // between the two strips ends up in neither. Since the faces of both
289        // strips are removed to make room for the new faces, that region is then
290        // left as a hole. See `strip_found` below.
291        let mut strip_found = false;
292
293        for ((idx1, &v_id1), (idx2, &v_id2)) in one_ring_v_ids1
294            .iter()
295            .enumerate()
296            .cartesian_product(one_ring_v_ids2.iter().enumerate())
297        {
298            if shared_v_ids.contains(&v_id1) || shared_v_ids.contains(&v_id2) {
299                continue;
300            }
301
302            if v_id1 == v_id2 {
303                connected_v_ids.insert(v_id1);
304                continue;
305            }
306
307            if connected_v_ids.contains(&v_id1) && connected_v_ids.contains(&v_id2) {
308                continue;
309            }
310
311            // The fan of the first strip is already extended over the whole strip
312            // by `find_connected_triangle_fans` below, so any fan found from here
313            // on belongs to a separate strip. Leave its faces in place and let the
314            // new faces span it instead.
315            if strip_found {
316                continue;
317            }
318
319            if let Some(he_id) = self.halfedge_from_to(v_id1, v_id2) {
320                let pairing = match self.find_triangle_fan(
321                    he_id,
322                    idx1,
323                    idx2,
324                    one_ring_v_ids1,
325                    one_ring_v_ids2,
326                    &mut connected_he_ids,
327                    &mut already_connected_face_ids,
328                ) {
329                    TriangleFan::Spanning(pairing) => pairing,
330                    // The halfedge only touches the rings from the outside, so it
331                    // doesn't connect them.
332                    TriangleFan::NotSpanning => continue,
333                    TriangleFan::Failed => return None,
334                };
335
336                #[cfg(feature = "rerun")]
337                pairing.log_rerun(
338                    "already_connected_init",
339                    [one_ring_v_ids1, one_ring_v_ids2],
340                    self,
341                );
342
343                let mut connected_pairings = self.find_connected_triangle_fans(
344                    one_ring_v_ids1,
345                    one_ring_v_ids2,
346                    &pairing,
347                    -1,
348                    &mut connected_he_ids,
349                    &mut already_connected_face_ids,
350                );
351
352                connected_pairings.push(pairing.clone());
353
354                connected_pairings.extend(self.find_connected_triangle_fans(
355                    one_ring_v_ids1,
356                    one_ring_v_ids2,
357                    &pairing,
358                    1,
359                    &mut connected_he_ids,
360                    &mut already_connected_face_ids,
361                ));
362
363                for pairing in &connected_pairings {
364                    for v_id in pairing.all_vertex_ids([one_ring_v_ids1, one_ring_v_ids2]) {
365                        connected_v_ids.insert(v_id);
366                    }
367                }
368
369                strip_found = true;
370            }
371        }
372
373        Some((
374            already_connected_face_ids,
375            connected_v_ids,
376            connected_he_ids,
377        ))
378    }
379
380    fn find_connected_triangle_fans(
381        &self,
382        one_ring_v_ids1: &[VertexId],
383        one_ring_v_ids2: &[VertexId],
384        pairing: &Pairing,
385        idx_step: i32,
386        connected_he_ids: &mut HashSet<HalfedgeId>,
387        connected_face_ids: &mut Vec<FaceId>,
388    ) -> Vec<Pairing> {
389        let (single_v_ids, other_v_ids) = if pairing.single_range_idx == 0 {
390            (one_ring_v_ids1, one_ring_v_ids2)
391        } else {
392            (one_ring_v_ids2, one_ring_v_ids1)
393        };
394
395        let next_single_idx = ((pairing.single_idx_in_range + single_v_ids.len()) as i32 + idx_step)
396            as usize
397            % single_v_ids.len();
398        let next_v_id = single_v_ids[next_single_idx];
399
400        let mut other_idx = if idx_step < 0 {
401            *pairing.other_range.start()
402        } else {
403            *pairing.other_range.end()
404        };
405        other_idx %= other_v_ids.len();
406
407        let current_single_v_id = single_v_ids[pairing.single_idx_in_range];
408
409        let mut pairings = vec![];
410
411        if self
412            .face_with_vertices(next_v_id, current_single_v_id, other_v_ids[other_idx])
413            .is_some()
414        {
415            // swap single range and other range
416            let mut current_pairing = Pairing::new_triangle(
417                1 - pairing.single_range_idx,
418                other_idx,
419                next_single_idx,
420                pairing.single_idx_in_range,
421                single_v_ids.len(),
422            );
423
424            self.extend_triangle_pairing_fan(
425                one_ring_v_ids1,
426                one_ring_v_ids2,
427                &mut current_pairing,
428                connected_he_ids,
429                connected_face_ids,
430            );
431
432            #[cfg(feature = "rerun")]
433            current_pairing.log_rerun(
434                format!("extended/step_{}", idx_step),
435                [one_ring_v_ids1, one_ring_v_ids2],
436                self,
437            );
438
439            let next_pairings = self.find_connected_triangle_fans(
440                one_ring_v_ids1,
441                one_ring_v_ids2,
442                &current_pairing,
443                idx_step,
444                connected_he_ids,
445                connected_face_ids,
446            );
447
448            pairings.push(current_pairing);
449            pairings.extend(next_pairings);
450        }
451
452        pairings
453    }
454
455    /// Finds the fan of already existing faces that connects the two one rings
456    /// across the halfedge `he_id` (which runs from a ring 1 vertex to a ring 2
457    /// vertex). See [`TriangleFan`] for the possible outcomes.
458    #[allow(clippy::too_many_arguments)]
459    fn find_triangle_fan(
460        &self,
461        he_id: HalfedgeId,
462        idx1: usize,
463        idx2: usize,
464        one_ring_v_ids1: &[VertexId],
465        one_ring_v_ids2: &[VertexId],
466        connected_he_ids: &mut HashSet<HalfedgeId>,
467        connected_face_ids: &mut Vec<FaceId>,
468    ) -> TriangleFan {
469        let he = unwrap_or_return!(
470            self.halfedges.get(he_id),
471            "Halfedge not found",
472            TriangleFan::Failed
473        );
474
475        let mut current_pairing = Pairing {
476            single_range_idx: 0,
477            single_idx_in_range: idx1,
478            other_range: idx2..=idx2,
479        };
480
481        // might not exist. we removed some faces (neighbors).
482        let Some(opposite_v_id) = he.opposite_vertex(self) else {
483            // The face on this side was one of the removed neighbor faces, so the
484            // halfedge does span the region between the rings.
485            return TriangleFan::Spanning(current_pairing);
486        };
487
488        if !self.create_triangle_pairing(
489            one_ring_v_ids1,
490            one_ring_v_ids2,
491            idx1,
492            idx2,
493            opposite_v_id,
494            &mut current_pairing,
495        ) {
496            let twin_id = unwrap_or_return!(he.twin, "Twin not found", TriangleFan::Failed);
497            let twin = unwrap_or_return!(
498                self.halfedges.get(twin_id),
499                "Twin not found",
500                TriangleFan::Failed
501            );
502
503            // might not exist. we removed some faces (neighbors).
504            let Some(opposite_v_id) = twin.opposite_vertex(self) else {
505                return TriangleFan::Spanning(current_pairing);
506            };
507
508            if !self.create_triangle_pairing(
509                one_ring_v_ids1,
510                one_ring_v_ids2,
511                idx1,
512                idx2,
513                opposite_v_id,
514                &mut current_pairing,
515            ) {
516                return TriangleFan::NotSpanning;
517            }
518        }
519
520        self.extend_triangle_pairing_fan(
521            one_ring_v_ids1,
522            one_ring_v_ids2,
523            &mut current_pairing,
524            connected_he_ids,
525            connected_face_ids,
526        );
527
528        TriangleFan::Spanning(current_pairing)
529    }
530
531    fn add_face_to_connected_he_ids(
532        &self,
533        face_id: FaceId,
534        connected_he_ids: &mut HashSet<HalfedgeId>,
535    ) {
536        let face = unwrap_or_return!(
537            self.faces.get(face_id),
538            "failed to get start face for pairing fan"
539        );
540        connected_he_ids.extend(
541            face.halfedges(self)
542                .filter_map(|he_id| {
543                    let he = self
544                        .halfedges
545                        .get(he_id)
546                        .or_else(error_none!("Halfedge not found"))?;
547                    Some([
548                        he_id,
549                        he.twin.or_else(error_none!("Twin halfedge not found"))?,
550                    ])
551                })
552                .flatten(),
553        );
554    }
555
556    fn extend_triangle_pairing_fan(
557        &self,
558        one_ring_v_ids1: &[VertexId],
559        one_ring_v_ids2: &[VertexId],
560        current_pairing: &mut Pairing,
561        connected_he_ids: &mut HashSet<HalfedgeId>,
562        connected_face_ids: &mut Vec<FaceId>,
563    ) {
564        let (single_ids, other_ids) = if current_pairing.single_range_idx == 0 {
565            (one_ring_v_ids1, one_ring_v_ids2)
566        } else {
567            (one_ring_v_ids2, one_ring_v_ids1)
568        };
569
570        let other_len = other_ids.len();
571        let single_v_id = single_ids[current_pairing.single_idx_in_range % single_ids.len()];
572
573        // Walk backwards from the start of other_range
574        let mut range_start = *current_pairing.other_range.start();
575
576        let start_face_id = unwrap_or_return!(
577            self.face_with_vertices(
578                single_v_id,
579                other_ids[range_start % other_len],
580                other_ids[*current_pairing.other_range.end() % other_len],
581            ),
582            "failed to find start face for pairing fan"
583        );
584        self.add_face_to_connected_he_ids(start_face_id, connected_he_ids);
585        connected_face_ids.push(start_face_id);
586
587        // Cap the number of steps so a complete face fan around the single vertex
588        // can't make the walk wrap around the ring indefinitely.
589        let mut steps = 0;
590        loop {
591            // Wrap backwards (using saturating to avoid underflow; handle wrap via modular arithmetic)
592            let prev_idx = (range_start + other_len - 1) % other_len;
593            let prev_v_id = other_ids[prev_idx];
594
595            let boundary_v_id = other_ids[range_start % other_len];
596            if let Some(face_id) = self.face_with_vertices(single_v_id, prev_v_id, boundary_v_id) {
597                // Adjust range_start to go one step back; keep values un-modded so the
598                // range arithmetic stays consistent with how ConnectPair::new handles wrapping.
599                if range_start == 0 {
600                    range_start = other_len - 1;
601                } else {
602                    range_start -= 1;
603                }
604                current_pairing.other_range = range_start..=*current_pairing.other_range.end();
605
606                self.add_face_to_connected_he_ids(face_id, connected_he_ids);
607                connected_face_ids.push(face_id);
608
609                steps += 1;
610                if steps >= other_len {
611                    error!("Pairing fan wrapped around the entire one ring");
612                    break;
613                }
614            } else {
615                break;
616            }
617        }
618
619        // Walk forwards from the end of other_range
620        let mut range_end = *current_pairing.other_range.end();
621        // Cap the number of steps so a complete face fan around the single vertex
622        // can't make the walk wrap around the ring indefinitely.
623        let mut steps = 0;
624        loop {
625            let next_idx = (range_end + 1) % other_len;
626            let next_v_id = other_ids[next_idx];
627
628            let boundary_v_id = other_ids[range_end % other_len];
629            if let Some(face_id) = self.face_with_vertices(single_v_id, boundary_v_id, next_v_id) {
630                range_end += 1;
631                current_pairing.other_range = *current_pairing.other_range.start()..=range_end;
632
633                self.add_face_to_connected_he_ids(face_id, connected_he_ids);
634                connected_face_ids.push(face_id);
635
636                steps += 1;
637                if steps >= other_len {
638                    error!("Pairing fan wrapped around the entire one ring");
639                    break;
640                }
641            } else {
642                break;
643            }
644        }
645    }
646
647    fn create_triangle_pairing(
648        &self,
649        one_ring_v_ids1: &[VertexId],
650        one_ring_v_ids2: &[VertexId],
651        idx1: usize,
652        idx2: usize,
653        third_v_id: VertexId,
654        current_pairing: &mut Pairing,
655    ) -> bool {
656        let (idx1, idx2) =
657            if let Some(idx) = one_ring_v_ids1.iter().position(|v_id| *v_id == third_v_id) {
658                current_pairing.single_range_idx = 1;
659                current_pairing.single_idx_in_range = idx2;
660
661                (idx, idx1)
662            } else if let Some(idx) = one_ring_v_ids2.iter().position(|v_id| *v_id == third_v_id) {
663                (idx, idx2)
664            } else {
665                return false;
666            };
667
668        let other_len = if current_pairing.single_range_idx == 0 {
669            one_ring_v_ids2.len()
670        } else {
671            one_ring_v_ids1.len()
672        };
673
674        *current_pairing = Pairing::new_triangle(
675            current_pairing.single_range_idx,
676            current_pairing.single_idx_in_range,
677            idx1,
678            idx2,
679            other_len,
680        );
681
682        true
683    }
684
685    fn cleanup_bookkeeping(
686        &mut self,
687        one_ring_v_ids1: &[VertexId],
688        one_ring_v_ids2: &[VertexId],
689        marked_vertices: &mut HashSet<VertexId>,
690        marked_halfedges: &mut HashSet<HalfedgeId>,
691        result: &mut MergeVerticesOneRing,
692    ) {
693        for vertex_id in one_ring_v_ids1.iter().chain(one_ring_v_ids2).copied() {
694            if !self.vertices.contains_key(vertex_id) {
695                continue;
696            }
697
698            let cleanup = self.make_vertex_neighborhood_manifold(vertex_id);
699
700            if !cleanup.added_vertices.is_empty() {
701                marked_vertices.insert(vertex_id);
702            }
703
704            result.added_vertices.extend(cleanup.added_vertices.clone());
705            marked_vertices.extend(cleanup.added_vertices);
706
707            for removed_v_id in cleanup.removed_vertices {
708                if result.added_vertices.contains(&removed_v_id) {
709                    result.added_vertices.retain(|&v_id| v_id != removed_v_id);
710                } else {
711                    result.removed_vertices.push(removed_v_id);
712                }
713
714                marked_vertices.remove(&removed_v_id);
715            }
716
717            for removed_he_id in cleanup.removed_halfedges {
718                if result.added_halfedges.contains(&removed_he_id) {
719                    result
720                        .added_halfedges
721                        .retain(|&he_id| he_id != removed_he_id);
722                } else {
723                    result.removed_halfedges.push(removed_he_id);
724                }
725
726                marked_halfedges.remove(&removed_he_id);
727            }
728
729            for removed_face_id in cleanup.removed_faces {
730                if result.added_faces.contains(&removed_face_id) {
731                    result
732                        .added_faces
733                        .retain(|&face_id| face_id != removed_face_id);
734                } else {
735                    result.removed_faces.push(removed_face_id);
736                }
737            }
738        }
739    }
740
741    fn add_planned_faces(
742        &mut self,
743        planned_faces: Vec<PlannedFace>,
744        one_ring_set1: &HashSet<VertexId>,
745        one_ring_set2: &HashSet<VertexId>,
746        marked_halfedges: &mut HashSet<HalfedgeId>,
747        result: &mut MergeVerticesOneRing,
748    ) {
749        let mut prev_he = None;
750
751        for planned_face in planned_faces {
752            let inserted = if let Some(prev_he) = prev_he {
753                planned_face.add_to_mesh_graph_and_he(self, prev_he, result)
754            } else {
755                planned_face.add_to_mesh_graph(self, result)
756            };
757
758            let Some(inserted) = inserted else {
759                // depending on the topology inserting a face can fail sometimes. this is fine.
760                prev_he = None;
761                continue;
762            };
763
764            prev_he = inserted.0;
765            let inserted = inserted.1;
766
767            for he_id in inserted.halfedge_ids.iter().copied() {
768                // just inserted => must exist
769                let he = self.halfedges[he_id];
770                let start_v_id =
771                    unwrap_or_return!(he.start_vertex(self), "Couldn't find start vertex");
772                let end_v_id = he.end_vertex;
773
774                if one_ring_set1.contains(&start_v_id) && one_ring_set2.contains(&end_v_id)
775                    || one_ring_set2.contains(&start_v_id) && one_ring_set1.contains(&end_v_id)
776                {
777                    marked_halfedges.insert(he_id);
778                }
779            }
780
781            result.added_halfedges.extend(inserted.halfedge_ids);
782            result.added_faces.push(inserted.face_id);
783        }
784    }
785
786    fn remove_neighbor_faces(
787        &mut self,
788        vertex1: &Vertex,
789        vertex2: &Vertex,
790        result: &mut MergeVerticesOneRing,
791    ) {
792        let face_ids1 = vertex1.faces(self).collect_vec();
793        let face_ids2 = vertex2.faces(self).collect_vec();
794
795        for face_id in face_ids1.into_iter().chain(face_ids2) {
796            let (del_v, del_he) = self.remove_face(face_id);
797
798            result.removed_faces.push(face_id);
799            result.removed_vertices.extend(del_v);
800            result.removed_halfedges.extend(del_he);
801        }
802    }
803
804    /// Dismantles the face (if any) that still claims `he_id` as a chain member.
805    /// Planned bridge faces re-file halfedges that an overlapping earlier merge's
806    /// bridge/fan may still own; re-filing a live chain member corrupts the old
807    /// face's chain (face-stealing), so such faces are dismantled before the
808    /// re-file. Only faces inside the merged region are affected: a halfedge that
809    /// is re-filed by a planned face always has its other side owned by a face of
810    /// the same region, never by an outside surface.
811    fn dismantle_faced_member(&mut self, he_id: HalfedgeId, result: &mut MergeVerticesOneRing) {
812        if let Some(he) = self.halfedges.get(he_id)
813            && let Some(face_id) = he.face
814            && self.faces.contains_key(face_id)
815        {
816            let (del_v, del_he) = self.remove_face(face_id);
817            result.removed_faces.push(face_id);
818            result.removed_halfedges.extend(del_he);
819            result.removed_vertices.extend(del_v);
820        }
821    }
822
823    #[instrument(skip_all)]
824    fn flip_and_collapse_single_shared_edge_if_below_threshold(
825        &mut self,
826        single_shared_he_id: HalfedgeId,
827        flip_threshold_sqr: f32,
828        result: &mut MergeVerticesOneRing,
829    ) -> bool {
830        // checked already in the call chain
831        let he = self.halfedges[single_shared_he_id];
832
833        let twin_id = unwrap_or_return!(he.twin, "Twin not found", false);
834        let twin = unwrap_or_return!(self.halfedges.get(twin_id), "Twin not found", false);
835
836        let other_v_id1 =
837            unwrap_or_return!(he.opposite_vertex(self), "Opposite vertex not found", false);
838        let other_v_id2 = unwrap_or_return!(
839            twin.opposite_vertex(self),
840            "Opposite vertex not found",
841            false
842        );
843
844        let pos1 = *unwrap_or_return!(self.positions.get(other_v_id1), "Position not found", false);
845        let pos2 = *unwrap_or_return!(self.positions.get(other_v_id2), "Position not found", false);
846
847        if pos1.distance_squared(pos2) <= flip_threshold_sqr {
848            tracing::debug!("Flipping edge {single_shared_he_id:?}");
849
850            self.flip_edge(single_shared_he_id);
851
852            let CollapseEdge {
853                removed_vertices,
854                removed_halfedges,
855                removed_faces,
856                added_vertices,
857            } = self.collapse_edge(single_shared_he_id);
858
859            result.added_vertices.extend(added_vertices);
860            result.removed_vertices.extend(removed_vertices);
861            result.removed_halfedges.extend(removed_halfedges);
862            result.removed_faces.extend(removed_faces);
863
864            true
865        } else {
866            false
867        }
868    }
869
870    fn plan_new_faces(
871        &self,
872        range_pairs_to_connect: &[ConnectPair],
873        one_ring_v_ids1: &[VertexId],
874        one_ring_v_ids2: &[VertexId],
875    ) -> Vec<PlannedFace> {
876        let mut planned_faces = Vec::with_capacity(one_ring_v_ids1.len());
877
878        for range_pair_to_connect in range_pairs_to_connect {
879            let start_pairing_index = planned_faces.len();
880
881            let pairings = range_pair_to_connect.compute_pairings();
882
883            tracing::debug!("Pairings: {:#?}", pairings);
884
885            if pairings.is_empty() {
886                continue;
887            }
888
889            let mut prev_single_v_id = None;
890            let mut prev_other_v_id = None;
891
892            if matches!(range_pair_to_connect.start_cap, ConnectPairCap::Open) {
893                let last_pairing = pairings.last().unwrap();
894
895                let (s, o) = last_pairing.last_pair([one_ring_v_ids1, one_ring_v_ids2]);
896
897                prev_single_v_id = Some(s);
898                prev_other_v_id = Some(o);
899            }
900
901            for pairing in pairings {
902                if let Some(prev_single_v_id) = prev_single_v_id
903                    && let Some(prev_other_v_id) = prev_other_v_id
904                {
905                    // add the quad between the previous and current pairings
906                    let (single_v_id, other_v_id) =
907                        pairing.first_pair([one_ring_v_ids1, one_ring_v_ids2]);
908
909                    let face_order = if prev_single_v_id == prev_other_v_id {
910                        PlannedFaceOrder::Start
911                    } else if single_v_id == other_v_id {
912                        PlannedFaceOrder::End
913                    } else {
914                        PlannedFaceOrder::Middle
915                    };
916
917                    // make sure the triangle vertices are CCW
918                    if pairing.single_range_idx == 0 {
919                        if prev_single_v_id != prev_other_v_id {
920                            planned_faces.push(PlannedFace::new(
921                                prev_single_v_id,
922                                single_v_id,
923                                prev_other_v_id,
924                                face_order,
925                            ));
926                        }
927                        if single_v_id != other_v_id {
928                            planned_faces.push(PlannedFace::new(
929                                prev_other_v_id,
930                                single_v_id,
931                                other_v_id,
932                                face_order,
933                            ));
934                        }
935                    } else {
936                        if prev_single_v_id != prev_other_v_id {
937                            planned_faces.push(PlannedFace::new(
938                                prev_single_v_id,
939                                prev_other_v_id,
940                                single_v_id,
941                                face_order,
942                            ));
943                        }
944                        if single_v_id != other_v_id {
945                            planned_faces.push(PlannedFace::new(
946                                prev_other_v_id,
947                                other_v_id,
948                                single_v_id,
949                                face_order,
950                            ));
951                        }
952                    }
953                }
954
955                // add the faces of the current pairing
956                planned_faces.extend(pairing.fill_faces([one_ring_v_ids1, one_ring_v_ids2]));
957
958                let (single_v_id, other_v_id) =
959                    pairing.last_pair([one_ring_v_ids1, one_ring_v_ids2]);
960
961                prev_single_v_id = Some(single_v_id);
962                prev_other_v_id = Some(other_v_id);
963            }
964
965            if planned_faces.len() == start_pairing_index {
966                // This range pair produced no faces (e.g. a single shared vertex).
967                continue;
968            }
969
970            if !matches!(
971                range_pair_to_connect.start_cap,
972                ConnectPairCap::AlreadyConnected
973            ) {
974                planned_faces[start_pairing_index].order = PlannedFaceOrder::Start;
975            }
976
977            let len = planned_faces.len();
978            planned_faces[len - 1].order = if len - 1 == start_pairing_index {
979                PlannedFaceOrder::Single
980            } else {
981                PlannedFaceOrder::End
982            };
983        }
984
985        planned_faces
986    }
987
988    #[instrument(skip_all)]
989    fn check_and_flip_single_shared_he(
990        &mut self,
991        shared_he_ids: &HashSet<HalfedgeId>,
992        flip_threshold_sqr: f32,
993        result: &mut MergeVerticesOneRing,
994    ) -> bool {
995        if shared_he_ids.len() == 1 {
996            let shared_he_id = shared_he_ids.iter().copied().next().unwrap();
997
998            #[cfg(feature = "rerun")]
999            self.log_he_rerun("common_one_ring_he", shared_he_id);
1000
1001            return self.flip_and_collapse_single_shared_edge_if_below_threshold(
1002                shared_he_id,
1003                flip_threshold_sqr,
1004                result,
1005            );
1006        }
1007
1008        false
1009    }
1010
1011    #[instrument(skip_all)]
1012    #[allow(clippy::too_many_arguments)]
1013    fn compute_range_pairs_to_connect(
1014        &mut self,
1015        one_ring_v_ids1: &[VertexId],
1016        one_ring_v_ids2: &[VertexId],
1017        one_ring_he_ids1: &[HalfedgeId],
1018        one_ring_he_ids2: &[HalfedgeId],
1019        shared_v_ids: &HashSet<VertexId>,
1020        shared_he_ids: &HashSet<HalfedgeId>,
1021        mut connected_v_ids: HashSet<VertexId>,
1022        mut connected_he_ids: HashSet<HalfedgeId>,
1023    ) -> Vec<ConnectPair> {
1024        let mut range_pairs_to_connect = vec![];
1025
1026        let (orig_start_idx1, orig_start_idx2, orig_start_cap) = unwrap_or_return!(
1027            self.find_start_indices(
1028                one_ring_v_ids1,
1029                one_ring_v_ids2,
1030                one_ring_he_ids1,
1031                one_ring_he_ids2,
1032                shared_v_ids,
1033                shared_he_ids,
1034                &connected_v_ids,
1035                &connected_he_ids,
1036            ),
1037            "Couldn't find start indices",
1038            range_pairs_to_connect
1039        );
1040
1041        tracing::debug!(
1042            "start idx1: {}, start idx2: {}",
1043            orig_start_idx1,
1044            orig_start_idx2
1045        );
1046
1047        let len1 = one_ring_v_ids1.len();
1048        let len2 = one_ring_v_ids2.len();
1049
1050        let mut start_idx1 = orig_start_idx1;
1051        let mut start_idx2 = orig_start_idx2;
1052        let mut start_cap = orig_start_cap;
1053
1054        let mut end_idx1 = (start_idx1 + 1) % len1;
1055        let mut end_idx2 = (start_idx2 + 1) % len2;
1056
1057        #[cfg(feature = "rerun")]
1058        {
1059            self.log_verts_w_labels_rerun(
1060                "pairs_start_idx",
1061                &[one_ring_v_ids1[start_idx1], one_ring_v_ids2[start_idx2]],
1062                &["1", "2"],
1063            );
1064            self.log_vert_rerun("pairs_end_idx1", one_ring_v_ids1[end_idx1]);
1065            self.log_vert_rerun("pairs_end_idx2", one_ring_v_ids2[end_idx2]);
1066        }
1067        let mut v_id1;
1068        let mut v_id2;
1069
1070        while end_idx1 != orig_start_idx1 {
1071            v_id1 = one_ring_v_ids1[end_idx1];
1072            v_id2 = one_ring_v_ids2[end_idx2];
1073
1074            if !self.vertices.contains_key(v_id1) || !self.vertices.contains_key(v_id2) {
1075                break;
1076            }
1077
1078            let mut end_cap = ConnectPairCap::Open;
1079
1080            if shared_v_ids.contains(&v_id1) {
1081                let start_end_idx2 = end_idx2;
1082                while v_id2 != v_id1 {
1083                    end_idx2 = (end_idx2 + 1) % len2;
1084                    v_id2 = one_ring_v_ids2[end_idx2];
1085
1086                    #[cfg(feature = "rerun")]
1087                    self.log_vert_rerun("pairs_end_idx2", v_id2);
1088
1089                    if end_idx2 == start_end_idx2 {
1090                        error!("Full cycle without finding shared vertex in one ring 2");
1091                        return range_pairs_to_connect;
1092                    }
1093                }
1094
1095                end_cap = ConnectPairCap::Closed;
1096            } else if shared_v_ids.contains(&v_id2) {
1097                let start_end_idx1 = end_idx1;
1098                while v_id1 != v_id2 {
1099                    end_idx1 = (end_idx1 + 1) % len1;
1100                    v_id1 = one_ring_v_ids1[end_idx1];
1101
1102                    #[cfg(feature = "rerun")]
1103                    self.log_vert_rerun("pairs_end_idx1", v_id1);
1104
1105                    if end_idx1 == start_end_idx1 {
1106                        error!("Full cycle without finding shared vertex in one ring 1");
1107                        return range_pairs_to_connect;
1108                    }
1109                }
1110
1111                end_cap = ConnectPairCap::Closed;
1112            } else if connected_v_ids.contains(&v_id1) {
1113                let start_end_idx2 = end_idx2;
1114                while !connected_v_ids.contains(&v_id2) {
1115                    end_idx2 = (end_idx2 + 1) % len2;
1116                    v_id2 = one_ring_v_ids2[end_idx2];
1117
1118                    #[cfg(feature = "rerun")]
1119                    self.log_vert_rerun("pairs_end_idx2", v_id2);
1120
1121                    if end_idx2 == start_end_idx2 {
1122                        error!("Full cycle without finding connected vertex in one ring 2");
1123                        return range_pairs_to_connect;
1124                    }
1125                }
1126
1127                end_cap = ConnectPairCap::AlreadyConnected;
1128            } else if connected_v_ids.contains(&v_id2) {
1129                let start_end_idx1 = end_idx1;
1130                while !connected_v_ids.contains(&v_id1) {
1131                    end_idx1 = (end_idx1 + 1) % len1;
1132                    v_id1 = one_ring_v_ids1[end_idx1];
1133
1134                    #[cfg(feature = "rerun")]
1135                    self.log_vert_rerun("pairs_end_idx1", v_id1);
1136
1137                    if end_idx1 == start_end_idx1 {
1138                        error!("Full cycle without finding connected vertex in one ring 1");
1139                        return range_pairs_to_connect;
1140                    }
1141                }
1142
1143                end_cap = ConnectPairCap::AlreadyConnected;
1144            }
1145
1146            if !matches!(end_cap, ConnectPairCap::Open) {
1147                range_pairs_to_connect.push(ConnectPair::new(
1148                    start_idx1..=end_idx1,
1149                    len1,
1150                    start_idx2..=end_idx2,
1151                    len2,
1152                    start_cap,
1153                ));
1154
1155                self.remember_range_pair_connections(
1156                    start_idx1,
1157                    end_idx1,
1158                    start_idx2,
1159                    end_idx2,
1160                    len1,
1161                    len2,
1162                    one_ring_v_ids1,
1163                    one_ring_v_ids2,
1164                    one_ring_he_ids1,
1165                    one_ring_he_ids2,
1166                    &mut connected_v_ids,
1167                    &mut connected_he_ids,
1168                );
1169
1170                if let Some((idx1, idx2, start)) = self.find_start_indices(
1171                    one_ring_v_ids1,
1172                    one_ring_v_ids2,
1173                    one_ring_he_ids1,
1174                    one_ring_he_ids2,
1175                    shared_v_ids,
1176                    shared_he_ids,
1177                    &connected_v_ids,
1178                    &connected_he_ids,
1179                ) {
1180                    start_idx1 = idx1;
1181                    start_idx2 = idx2;
1182                    start_cap = start;
1183                } else {
1184                    return range_pairs_to_connect;
1185                }
1186
1187                end_idx1 = (start_idx1 + 1) % len1;
1188                end_idx2 = (start_idx2 + 1) % len2;
1189
1190                #[cfg(feature = "rerun")]
1191                {
1192                    self.log_verts_w_labels_rerun(
1193                        "pairs_start_idx",
1194                        &[one_ring_v_ids1[start_idx1], one_ring_v_ids2[start_idx2]],
1195                        &["1", "2"],
1196                    );
1197                    self.log_vert_rerun("pairs_end_idx1", one_ring_v_ids1[end_idx1]);
1198                    self.log_vert_rerun("pairs_end_idx2", one_ring_v_ids2[end_idx2]);
1199                }
1200
1201                if start_idx1 == orig_start_idx1 {
1202                    break;
1203                }
1204            } else {
1205                end_idx1 = (end_idx1 + 1) % len1;
1206                end_idx2 = (end_idx2 + 1) % len2;
1207
1208                #[cfg(feature = "rerun")]
1209                {
1210                    self.log_vert_rerun("pairs_end_idx1", one_ring_v_ids1[end_idx1]);
1211                    self.log_vert_rerun("pairs_end_idx2", one_ring_v_ids2[end_idx2]);
1212                }
1213            }
1214        }
1215
1216        let diff1 = (start_idx1 as i32 - end_idx1 as i32).unsigned_abs() as usize;
1217        let diff1 = diff1.min(len1 - diff1);
1218
1219        let diff2 = (start_idx2 as i32 - end_idx2 as i32).unsigned_abs() as usize;
1220        let diff2 = diff2.min(len2 - diff2);
1221
1222        if range_pairs_to_connect.is_empty() {
1223            let (end1, end2, cap) = if shared_v_ids.is_empty() {
1224                (
1225                    (orig_start_idx1 + len1 - 1).rem_euclid(len1),
1226                    (orig_start_idx2 + len2 - 1).rem_euclid(len2),
1227                    ConnectPairCap::Open,
1228                )
1229            } else {
1230                (orig_start_idx1, orig_start_idx2, ConnectPairCap::Closed)
1231            };
1232
1233            range_pairs_to_connect.push(ConnectPair::new(
1234                orig_start_idx1..=end1,
1235                len1,
1236                orig_start_idx2..=end2,
1237                len2,
1238                cap,
1239            ));
1240        } else if diff1 > 1 || diff2 > 1 {
1241            range_pairs_to_connect.push(ConnectPair::new(
1242                start_idx1..=end_idx1,
1243                len1,
1244                start_idx2..=end_idx2,
1245                len2,
1246                start_cap,
1247            ));
1248        }
1249
1250        #[cfg(feature = "rerun")]
1251        {
1252            for range_pair in &range_pairs_to_connect {
1253                let mut v1s = vec![];
1254                let mut v2s = vec![];
1255                let mut l1s = vec![];
1256                let mut l2s = vec![];
1257
1258                for i in range_pair.ranges[0].clone() {
1259                    let v_id = one_ring_v_ids1[i % len1];
1260                    v1s.push(v_id);
1261                    l1s.push(format!("{i} - {v_id:?}"));
1262                }
1263                for i in range_pair.ranges[1].clone() {
1264                    let v_id = one_ring_v_ids2[i % len2];
1265                    v2s.push(v_id);
1266                    l2s.push(format!("{i} - {v_id:?}"));
1267                }
1268
1269                self.log_verts_w_labels_rerun("range_pair_1", &v1s, &l1s);
1270                self.log_verts_w_labels_rerun("range_pair_2", &v2s, &l2s);
1271            }
1272        }
1273
1274        range_pairs_to_connect
1275    }
1276
1277    #[instrument(skip(self))]
1278    #[allow(clippy::too_many_arguments)]
1279    fn remember_range_pair_connections(
1280        &mut self,
1281        start_idx1: usize,
1282        end_idx1: usize,
1283        start_idx2: usize,
1284        end_idx2: usize,
1285        len1: usize,
1286        len2: usize,
1287        one_ring_v_ids1: &[VertexId],
1288        one_ring_v_ids2: &[VertexId],
1289        one_ring_he_ids1: &[HalfedgeId],
1290        one_ring_he_ids2: &[HalfedgeId],
1291        connected_v_ids: &mut HashSet<VertexId>,
1292        connected_he_ids: &mut HashSet<HalfedgeId>,
1293    ) {
1294        let mut idx = start_idx1;
1295        loop {
1296            connected_v_ids.insert(one_ring_v_ids1[idx]);
1297            if idx == end_idx1 {
1298                break;
1299            }
1300            let he_id = one_ring_he_ids1[idx];
1301            connected_he_ids.insert(he_id);
1302
1303            // could have been deleted by neighbor face deletion
1304            if let Some(he) = self.halfedges.get(he_id) {
1305                // halfedge existence already checked in `one_ring()`
1306                if let Some(twin_id) = he.twin {
1307                    connected_he_ids.insert(twin_id);
1308                } else {
1309                    error!("halfedge {:?} has no twin", he_id);
1310                }
1311            }
1312
1313            idx = (idx + 1) % len1;
1314        }
1315
1316        idx = start_idx2;
1317        loop {
1318            connected_v_ids.insert(one_ring_v_ids2[idx]);
1319            if idx == end_idx2 {
1320                break;
1321            }
1322            let he_id = one_ring_he_ids2[idx];
1323            connected_he_ids.insert(he_id);
1324
1325            // could have been deleted by neighbor face deletion
1326            if let Some(he) = self.halfedges.get(he_id) {
1327                // halfedge existence already checked in `one_ring()`
1328                if let Some(twin_id) = he.twin {
1329                    connected_he_ids.insert(twin_id);
1330                } else {
1331                    error!("halfedge {:?} has no twin", he_id);
1332                }
1333            }
1334
1335            idx = (idx + 1) % len2;
1336        }
1337    }
1338
1339    #[instrument(skip(self))]
1340    #[allow(clippy::too_many_arguments)]
1341    fn find_shared_start_indices_from_ring(
1342        &self,
1343        one_ring_v_ids: &[VertexId],
1344        other_one_ring_v_ids: &[VertexId],
1345        one_ring_he_ids: &[HalfedgeId],
1346        other_one_ring_he_ids: &[HalfedgeId],
1347        shared_v_ids: &HashSet<VertexId>,
1348        shared_he_ids: &HashSet<HalfedgeId>,
1349        connected_v_ids: &HashSet<VertexId>,
1350        connected_he_ids: &HashSet<HalfedgeId>,
1351    ) -> Option<(usize, usize, ConnectPairCap)> {
1352        let len = one_ring_v_ids.len();
1353        let other_len = other_one_ring_v_ids.len();
1354
1355        debug_assert_eq!(len, one_ring_he_ids.len());
1356        debug_assert_eq!(other_len, other_one_ring_he_ids.len());
1357
1358        for (he_idx, &he_id1) in one_ring_he_ids.iter().enumerate() {
1359            if self.halfedges.contains_key(he_id1)
1360                && !shared_he_ids.contains(&he_id1)
1361                && !connected_he_ids.contains(&he_id1)
1362            {
1363                // walk backwards until we find a connection between the two one-rings
1364
1365                let mut start_idx = he_idx;
1366                let mut start_he_id = &one_ring_he_ids[start_idx];
1367
1368                let mut start_v_id = &one_ring_v_ids[start_idx];
1369
1370                while self.halfedges.contains_key(*start_he_id)
1371                    && !shared_he_ids.contains(start_he_id)
1372                    && !connected_he_ids.contains(start_he_id)
1373                    && self.vertices.contains_key(*start_v_id)
1374                    && !shared_v_ids.contains(start_v_id)
1375                    && !connected_v_ids.contains(start_v_id)
1376                {
1377                    start_idx = (start_idx + len - 1) % len;
1378                    start_he_id = &one_ring_he_ids[start_idx];
1379                    start_v_id = &one_ring_v_ids[start_idx];
1380
1381                    if start_idx == he_idx {
1382                        return None;
1383                    }
1384                }
1385
1386                // find the corresponding vertex in the other one-ring
1387
1388                // either it's the same vertex ...
1389                if let Some(other_idx) = other_one_ring_v_ids
1390                    .iter()
1391                    .position(|v_id| start_v_id == v_id)
1392                {
1393                    return Some((start_idx, other_idx, ConnectPairCap::Closed));
1394                }
1395
1396                // ... or it's a different vertex that is connected by a halfedge
1397                for (other_idx, &other_v_id) in other_one_ring_v_ids.iter().enumerate() {
1398                    if self.halfedge_from_to(*start_v_id, other_v_id).is_some() {
1399                        let other_he_id = &other_one_ring_he_ids[other_idx];
1400
1401                        if self.halfedges.contains_key(*other_he_id)
1402                            && !shared_he_ids.contains(other_he_id)
1403                            && !connected_he_ids.contains(other_he_id)
1404                        {
1405                            return Some((start_idx, other_idx, ConnectPairCap::AlreadyConnected));
1406                        }
1407                    }
1408                }
1409            }
1410        }
1411
1412        None
1413    }
1414
1415    #[allow(clippy::too_many_arguments)]
1416    #[instrument(skip(self))]
1417    fn find_start_indices(
1418        &self,
1419        one_ring_v_ids1: &[VertexId],
1420        one_ring_v_ids2: &[VertexId],
1421        one_ring_he_ids1: &[HalfedgeId],
1422        one_ring_he_ids2: &[HalfedgeId],
1423        shared_v_ids: &HashSet<VertexId>,
1424        shared_he_ids: &HashSet<HalfedgeId>,
1425        connected_v_ids: &HashSet<VertexId>,
1426        connected_he_ids: &HashSet<HalfedgeId>,
1427    ) -> Option<(usize, usize, ConnectPairCap)> {
1428        if !shared_v_ids.is_empty() || !connected_v_ids.is_empty() {
1429            self.find_shared_start_indices_from_ring(
1430                one_ring_v_ids1,
1431                one_ring_v_ids2,
1432                one_ring_he_ids1,
1433                one_ring_he_ids2,
1434                shared_v_ids,
1435                shared_he_ids,
1436                connected_v_ids,
1437                connected_he_ids,
1438            )
1439        } else {
1440            let first_v_id = one_ring_v_ids1[0];
1441            let first_pos = *self
1442                .positions
1443                .get(first_v_id)
1444                .or_else(error_none!("Position not found"))?;
1445
1446            let mut min_dist_sqr = f32::INFINITY;
1447            let mut start_idx2 = 0;
1448
1449            for (idx, v_id) in one_ring_v_ids2.iter().enumerate() {
1450                let pos = self
1451                    .positions
1452                    .get(*v_id)
1453                    .or_else(error_none!("Position not found"))?;
1454
1455                let dist_sqr = pos.distance_squared(first_pos);
1456
1457                if dist_sqr < min_dist_sqr {
1458                    min_dist_sqr = dist_sqr;
1459                    start_idx2 = idx;
1460                }
1461            }
1462
1463            Some((0, start_idx2, ConnectPairCap::Open))
1464        }
1465    }
1466}
1467
1468#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1469enum ConnectPairCap {
1470    /// Indices reference the same vertex, thus forming a closed polygon.
1471    Closed,
1472    /// Indices reference different vertices, thus forming an open polygon.
1473    Open,
1474    /// Indices are already connected by neighboring faces, so no need to connect them again.
1475    AlreadyConnected,
1476}
1477
1478/// Two ranges of elements that need to be connected by faces.
1479#[derive(Debug)]
1480struct ConnectPair {
1481    /// Wether the first indices in the two ranges reference the same vertex, thus forming a closed polygon.
1482    start_cap: ConnectPairCap,
1483
1484    /// The pair of ranges of vertex indices that need to be connected.
1485    ranges: [RangeInclusive<usize>; 2],
1486}
1487
1488impl ConnectPair {
1489    fn new(
1490        mut range1: RangeInclusive<usize>,
1491        len1: usize,
1492        mut range2: RangeInclusive<usize>,
1493        len2: usize,
1494        start_cap: ConnectPairCap,
1495    ) -> Self {
1496        if range1.end() <= range1.start() {
1497            range1 = (*range1.start())..=(*range1.end() + len1);
1498        }
1499        if range2.end() <= range2.start() {
1500            range2 = (*range2.start())..=(*range2.end() + len2);
1501        }
1502
1503        ConnectPair {
1504            start_cap,
1505            ranges: [range1, range2],
1506        }
1507    }
1508
1509    // This is a modified Bresenham algorithm usually used for drawing lines on a grid.
1510    fn compute_pairings(&self) -> Vec<Pairing> {
1511        let range1 = self.ranges[0].clone();
1512        let range2 = self.ranges[1].clone();
1513
1514        let count1 = range1.clone().count() as i32;
1515        let count2 = range2.clone().count() as i32;
1516
1517        let (single_range_idx, single_count, other_count, single_range, other_range) =
1518            if count1 > count2 {
1519                (1, count2, count1, range2, range1)
1520            } else {
1521                (0, count1, count2, range1, range2)
1522            };
1523
1524        let mut single_idx_in_range = *single_range.start();
1525
1526        if single_count == 0 {
1527            return vec![];
1528        }
1529        if single_count == 1 {
1530            return vec![Pairing {
1531                single_range_idx,
1532                single_idx_in_range,
1533                other_range,
1534            }];
1535        }
1536
1537        let mut error = 2 * single_count - other_count;
1538
1539        let mut other_start = *other_range.start();
1540        let mut other_end = other_start;
1541
1542        let mut pairings = Vec::new();
1543
1544        while other_end <= *other_range.end() {
1545            if error > 0 {
1546                pairings.push(Pairing {
1547                    single_range_idx,
1548                    single_idx_in_range,
1549                    other_range: other_start..=other_end,
1550                });
1551
1552                other_start = other_end + 1;
1553                single_idx_in_range += 1;
1554                error += 2 * (single_count - other_count);
1555            } else {
1556                error += 2 * single_count;
1557            }
1558
1559            other_end += 1;
1560        }
1561
1562        if let Some(last_pairing) = pairings.last_mut() {
1563            last_pairing.other_range = *last_pairing.other_range.start()..=*other_range.end();
1564        }
1565
1566        pairings
1567    }
1568}
1569
1570/// The outcome of [`MeshGraph::find_triangle_fan`].
1571enum TriangleFan {
1572    /// The halfedge spans the region between the two one rings. Holds the pairing
1573    /// of the fan of already existing faces around it.
1574    Spanning(Pairing),
1575
1576    /// The halfedge doesn't span the region between the two one rings: both of its
1577    /// faces are intact and neither of them has its third vertex on one of the
1578    /// rings, so the halfedge runs along the outside of the rings instead of
1579    /// across the region between them. The vertices it joins must not count as
1580    /// connected.
1581    NotSpanning,
1582
1583    /// The mesh connectivity around the halfedge is broken. The merge is aborted.
1584    Failed,
1585}
1586
1587/// Pairs one element from one range with one or more elements from the other range.
1588#[derive(Debug, Clone)]
1589struct Pairing {
1590    /// The index of the range in which each single element is paired to one or more elements in the other range.
1591    /// Can only be `0` or `1`.
1592    single_range_idx: usize,
1593
1594    /// The index of the single element in the range referenced by `single_range_idx`.
1595    single_idx_in_range: usize,
1596
1597    /// The range of elements in the other range that are paired to the single element referenced in `single_idx_in_range`.
1598    other_range: RangeInclusive<usize>,
1599}
1600
1601impl Pairing {
1602    fn new_triangle(
1603        single_range_idx: usize,
1604        single_idx_in_range: usize,
1605        other_idx1: usize,
1606        other_idx2: usize,
1607        other_len: usize,
1608    ) -> Self {
1609        let other_range = if other_idx1 == other_idx2 {
1610            other_idx1..=other_idx2
1611        } else if (other_idx1 as i32 - other_idx2 as i32).abs() == 1 {
1612            other_idx1.min(other_idx2)..=other_idx1.max(other_idx2)
1613        } else {
1614            other_idx1.max(other_idx2)..=other_idx1.min(other_idx2) + other_len
1615        };
1616
1617        Self {
1618            single_range_idx,
1619            single_idx_in_range,
1620            other_range,
1621        }
1622    }
1623
1624    fn all_vertex_ids(&self, v_ids: [&[VertexId]; 2]) -> Vec<VertexId> {
1625        let single_ids = &v_ids[self.single_range_idx];
1626        let other_ids = &v_ids[1 - self.single_range_idx];
1627
1628        let mut all_ids = vec![];
1629
1630        all_ids.push(single_ids[self.single_idx_in_range % single_ids.len()]);
1631
1632        let mut idx = *self.other_range.start() % other_ids.len();
1633        all_ids.push(other_ids[idx]);
1634        while idx != *self.other_range.end() % other_ids.len() {
1635            idx += 1;
1636            idx %= other_ids.len();
1637            all_ids.push(other_ids[idx]);
1638        }
1639
1640        all_ids
1641    }
1642
1643    fn first_pair(&self, v_ids: [&[VertexId]; 2]) -> (VertexId, VertexId) {
1644        let single_ids = &v_ids[self.single_range_idx];
1645        let other_ids = &v_ids[1 - self.single_range_idx];
1646
1647        let single_v_id = single_ids[self.single_idx_in_range % single_ids.len()];
1648        let other_v_id = other_ids[*self.other_range.start() % other_ids.len()];
1649
1650        (single_v_id, other_v_id)
1651    }
1652
1653    fn last_pair(&self, v_ids: [&[VertexId]; 2]) -> (VertexId, VertexId) {
1654        let single_ids = &v_ids[self.single_range_idx];
1655        let other_ids = &v_ids[1 - self.single_range_idx];
1656
1657        let single_v_id = single_ids[self.single_idx_in_range % single_ids.len()];
1658        let other_v_id = other_ids[*self.other_range.end() % other_ids.len()];
1659
1660        (single_v_id, other_v_id)
1661    }
1662
1663    fn fill_faces(&self, v_ids: [&[VertexId]; 2]) -> Vec<PlannedFace> {
1664        let single_ids = v_ids[self.single_range_idx];
1665        let other_ids = v_ids[1 - self.single_range_idx];
1666
1667        let single_v_id = single_ids[self.single_idx_in_range % single_ids.len()];
1668        let mut faces = Vec::<PlannedFace>::new();
1669
1670        let mut others = self.other_range.clone();
1671
1672        if others.start() == others.end() {
1673            return faces;
1674        }
1675
1676        let mut prev_other_v_id = other_ids[others.next().unwrap() % other_ids.len()];
1677        let mut face_order = PlannedFaceOrder::Middle;
1678
1679        if prev_other_v_id == single_v_id {
1680            face_order = PlannedFaceOrder::Start;
1681
1682            if let Some(second_idx) = others.next() {
1683                prev_other_v_id = other_ids[second_idx % other_ids.len()];
1684            } else {
1685                return faces;
1686            }
1687        }
1688
1689        for other_idx in others {
1690            let other_v_id = other_ids[other_idx % other_ids.len()];
1691
1692            if other_v_id == single_v_id {
1693                break;
1694            }
1695
1696            if other_v_id == prev_other_v_id {
1697                // Consecutive duplicates in the "other" ring would produce a
1698                // degenerate (V, S, V) self-connected face. Skip the face but
1699                // keep advancing so the following triangles stay well-formed.
1700                prev_other_v_id = other_v_id;
1701                continue;
1702            }
1703
1704            // make sure the triangle vertices are CCW
1705            if self.single_range_idx == 0 {
1706                faces.push(PlannedFace::new(
1707                    prev_other_v_id,
1708                    single_v_id,
1709                    other_v_id,
1710                    face_order,
1711                ));
1712            } else {
1713                faces.push(PlannedFace::new(
1714                    prev_other_v_id,
1715                    other_v_id,
1716                    single_v_id,
1717                    face_order,
1718                ));
1719            }
1720
1721            face_order = PlannedFaceOrder::Middle;
1722            prev_other_v_id = other_v_id;
1723        }
1724
1725        faces
1726    }
1727
1728    #[cfg(feature = "rerun")]
1729    fn log_rerun(
1730        &self,
1731        label: impl AsRef<str>,
1732        v_ids: [&[VertexId]; 2],
1733        mesh_graph: &MeshGraph,
1734    ) -> Option<()> {
1735        use crate::utils::vec3_array;
1736
1737        let single_v_ids = v_ids[self.single_range_idx];
1738        let other_v_ids = v_ids[1 - self.single_range_idx];
1739
1740        let single_v_id = single_v_ids[self.single_idx_in_range];
1741        let single_pos = vec3_array(mesh_graph.positions.get(single_v_id)?);
1742
1743        let mut other_pos = vec![];
1744        let mut idx = self.other_range.start() % other_v_ids.len();
1745
1746        other_pos.push(vec3_array(mesh_graph.positions.get(other_v_ids[idx])?));
1747        while idx != *self.other_range.end() % other_v_ids.len() {
1748            idx += 1;
1749            idx %= other_v_ids.len();
1750            other_pos.push(vec3_array(mesh_graph.positions.get(other_v_ids[idx])?));
1751        }
1752
1753        crate::RR
1754            .log(
1755                format!("pairing/{}/single", label.as_ref()),
1756                &rerun::Points3D::new([single_pos]),
1757            )
1758            .unwrap();
1759        crate::RR
1760            .log(
1761                format!("pairing/{}/other", label.as_ref()),
1762                &rerun::LineStrips3D::new(
1763                    other_pos
1764                        .into_iter()
1765                        .map(|other_pos| [single_pos, other_pos]),
1766                ),
1767            )
1768            .unwrap();
1769
1770        Some(())
1771    }
1772}
1773
1774/// Represents a triangle that is planned to be added to the mesh graph.
1775///
1776/// v1 ()◀───────────────() new_he_v2
1777///      ╲              ▲
1778///       ╲            ╱
1779///        ╲          ╱
1780///         ╲        ╱
1781///          ╲      ╱
1782///           ╲    ╱
1783///            ▼  ╱
1784///             ()
1785///         new_he_v1
1786///
1787#[derive(Debug)]
1788struct PlannedFace {
1789    order: PlannedFaceOrder,
1790    v1: VertexId,
1791    new_he_v1: VertexId,
1792    new_he_v2: VertexId,
1793}
1794
1795#[derive(Debug, Copy, Clone, Eq, PartialEq)]
1796enum PlannedFaceOrder {
1797    Start,
1798    Middle,
1799    End,
1800    Single,
1801}
1802
1803impl PlannedFace {
1804    fn new(
1805        v_id1: VertexId,
1806        new_he_v_id1: VertexId,
1807        new_he_v_id2: VertexId,
1808        order: PlannedFaceOrder,
1809    ) -> Self {
1810        PlannedFace {
1811            order,
1812            v1: v_id1,
1813            new_he_v1: new_he_v_id1,
1814            new_he_v2: new_he_v_id2,
1815        }
1816    }
1817
1818    #[instrument(skip(mesh_graph, result))]
1819    fn add_to_mesh_graph(
1820        &self,
1821        mesh_graph: &mut MeshGraph,
1822        result: &mut MergeVerticesOneRing,
1823    ) -> Option<(Option<HalfedgeId>, AddFace)> {
1824        #[cfg(feature = "rerun")]
1825        self.log_rerun("add_to_mesh_graph", mesh_graph);
1826
1827        if self.v1 == self.new_he_v1
1828            || self.v1 == self.new_he_v2
1829            || self.new_he_v1 == self.new_he_v2
1830        {
1831            error!(
1832                "Skipping degenerate planned face with repeated vertices: {:?}",
1833                (self.v1, self.new_he_v1, self.new_he_v2)
1834            );
1835            return None;
1836        }
1837
1838        let add_or_get_edge1 = mesh_graph.add_or_get_boundary_edge(self.v1, self.new_he_v1)?;
1839        let add_or_get_edge2 =
1840            mesh_graph.add_or_get_boundary_edge(self.new_he_v1, self.new_he_v2)?;
1841
1842        // The re-filed halfedges may still belong to a live face (an overlapping
1843        // earlier merge's bridge or fan). Re-filing a live chain member would leave
1844        // the old face's chain referencing a halfedge that now claims a different
1845        // face (face-stealing corruption); dismantle such faces up front. They lie
1846        // inside the merged region and are rebuilt by the new faces.
1847        for he_id in [
1848            add_or_get_edge1.start_to_end_he_id,
1849            add_or_get_edge2.start_to_end_he_id,
1850        ] {
1851            mesh_graph.dismantle_faced_member(he_id, result);
1852        }
1853
1854        let mut add_face = mesh_graph.add_face_from_halfedges(
1855            add_or_get_edge1.start_to_end_he_id,
1856            add_or_get_edge2.start_to_end_he_id,
1857        )?;
1858
1859        if add_or_get_edge1.new_start_to_end {
1860            add_face
1861                .halfedge_ids
1862                .push(add_or_get_edge1.start_to_end_he_id);
1863        }
1864        if add_or_get_edge1.new_twin {
1865            add_face.halfedge_ids.push(add_or_get_edge1.twin_he_id);
1866        }
1867        if add_or_get_edge2.new_start_to_end {
1868            add_face
1869                .halfedge_ids
1870                .push(add_or_get_edge2.start_to_end_he_id);
1871        }
1872        if add_or_get_edge2.new_twin {
1873            add_face.halfedge_ids.push(add_or_get_edge2.twin_he_id);
1874        }
1875
1876        Some((
1877            if matches!(
1878                self.order,
1879                PlannedFaceOrder::Start | PlannedFaceOrder::Middle
1880            ) && mesh_graph.halfedges[add_or_get_edge2.twin_he_id].is_boundary()
1881            {
1882                Some(add_or_get_edge2.twin_he_id)
1883            } else {
1884                None
1885            },
1886            add_face,
1887        ))
1888    }
1889
1890    #[instrument(skip(mesh_graph, result))]
1891    fn add_to_mesh_graph_and_he(
1892        &self,
1893        mesh_graph: &mut MeshGraph,
1894        existing_he_id: HalfedgeId,
1895        result: &mut MergeVerticesOneRing,
1896    ) -> Option<(Option<HalfedgeId>, AddFace)> {
1897        #[cfg(feature = "rerun")]
1898        self.log_rerun("add_to_mesh_graph_and_he", mesh_graph);
1899
1900        if self.v1 == self.new_he_v1
1901            || self.v1 == self.new_he_v2
1902            || self.new_he_v1 == self.new_he_v2
1903        {
1904            error!(
1905                "Skipping degenerate planned face with repeated vertices: {:?}",
1906                (self.v1, self.new_he_v1, self.new_he_v2)
1907            );
1908            return None;
1909        }
1910
1911        match self.order {
1912            PlannedFaceOrder::Middle => {
1913                let AddEdge {
1914                    start_to_end_he_id,
1915                    twin_he_id,
1916                } = mesh_graph.add_edge(self.new_he_v1, self.new_he_v2)?;
1917
1918                // The edge may already exist (created by the previous planned face
1919                // of this merge or an overlapping earlier merge): its halfedge still
1920                // belongs to that face's chain. Dismantle the claiming face before
1921                // re-filing, and the existing edge (the other side of the shared
1922                // border) likewise, so no live chain keeps referencing a re-filed
1923                // halfedge.
1924                mesh_graph.dismantle_faced_member(existing_he_id, result);
1925                mesh_graph.dismantle_faced_member(start_to_end_he_id, result);
1926
1927                let mut add_face =
1928                    mesh_graph.add_face_from_halfedges(existing_he_id, start_to_end_he_id)?;
1929                add_face.halfedge_ids.push(start_to_end_he_id);
1930                add_face.halfedge_ids.push(twin_he_id);
1931
1932                Some((Some(twin_he_id), add_face))
1933            }
1934            PlannedFaceOrder::End => {
1935                let add_or_get_edge =
1936                    mesh_graph.add_or_get_boundary_edge(self.new_he_v1, self.new_he_v2)?;
1937
1938                mesh_graph.dismantle_faced_member(existing_he_id, result);
1939                mesh_graph.dismantle_faced_member(add_or_get_edge.start_to_end_he_id, result);
1940
1941                let mut add_face = mesh_graph
1942                    .add_face_from_halfedges(existing_he_id, add_or_get_edge.start_to_end_he_id)?;
1943                if add_or_get_edge.new_start_to_end {
1944                    add_face
1945                        .halfedge_ids
1946                        .push(add_or_get_edge.start_to_end_he_id);
1947                }
1948                if add_or_get_edge.new_twin {
1949                    add_face.halfedge_ids.push(add_or_get_edge.twin_he_id);
1950                }
1951
1952                Some((None, add_face))
1953            }
1954            _ => {
1955                error!("Invalid face order {:?}", self.order);
1956                None
1957            }
1958        }
1959    }
1960
1961    #[cfg(feature = "rerun")]
1962    fn log_rerun(&self, label: &str, mesh_graph: &MeshGraph) {
1963        use crate::{RR, utils::vec3_array};
1964
1965        let (positions, labels): (Vec<_>, Vec<_>) =
1966            [(self.v1, "1"), (self.new_he_v1, "2"), (self.new_he_v2, "3")]
1967                .into_iter()
1968                .filter_map(|(v_id, labl)| mesh_graph.positions.get(v_id).map(|a| (a, labl)))
1969                .unzip();
1970
1971        RR.log(
1972            format!("meshgraph/planned_face/{label}/positions"),
1973            &rerun::Points3D::new(positions.iter().map(vec3_array)).with_labels(labels),
1974        )
1975        .unwrap();
1976
1977        RR.log(
1978            format!("meshgraph/planned_face/{label}/edges"),
1979            &rerun::Arrows3D::from_vectors(
1980                positions
1981                    .iter()
1982                    .circular_array_windows()
1983                    .map(|[a, b]| vec3_array(*b - *a)),
1984            )
1985            .with_origins(positions.iter().map(vec3_array)),
1986        )
1987        .unwrap();
1988    }
1989}