Skip to main content

mesh_graph/ops/merge_one_ring/
mod.rs

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