1use glam::Vec3;
2use hashbrown::{HashMap, HashSet};
3use itertools::Itertools;
4use slotmap::SparseSecondaryMap;
5use tracing::{error, instrument};
6
7use crate::{FaceId, HalfedgeId, MeshGraph, VertexId, error_none, utils::unwrap_or_return};
8
9pub struct MergeVertices {
10 pub removed_vertices: Vec<VertexId>,
11 pub removed_halfedges: Vec<HalfedgeId>,
12 pub removed_faces: Vec<FaceId>,
13}
14
15impl MeshGraph {
16 #[instrument(skip_all)]
18 pub fn merge_vertices(
19 &mut self,
20 vertices: impl IntoIterator<Item = VertexId>,
21 ) -> MergeVertices {
22 let vertex_ids: Vec<VertexId> = vertices
23 .into_iter()
24 .filter(|v| self.vertices.contains_key(*v))
25 .unique()
26 .collect();
27
28 if vertex_ids.len() < 2 {
29 return MergeVertices {
30 removed_vertices: Vec::new(),
31 removed_halfedges: Vec::new(),
32 removed_faces: Vec::new(),
33 };
34 }
35
36 let vertex_set: HashSet<VertexId> = vertex_ids.iter().copied().collect();
37 let survivor_id = vertex_ids[0];
38
39 let mut avg_pos = Vec3::ZERO;
41 let mut count = 0.0;
42 for &v_id in &vertex_ids {
43 if let Some(&pos) = self.positions.get(v_id) {
44 avg_pos += pos;
45 count += 1.0;
46 }
47 }
48 if count > 0.0 {
49 avg_pos /= count;
50 }
51
52 let mut faces_to_remove = HashSet::new();
54 for &v_id in &vertex_ids {
55 for face_id in self.vertex_adjacent_faces(v_id) {
56 if faces_to_remove.contains(&face_id) {
57 continue;
58 }
59 if let Some(face) = self.faces.get(face_id) {
60 let merged_count = face
61 .vertices(self)
62 .filter(|v| vertex_set.contains(v))
63 .count();
64 if merged_count >= 2 {
65 faces_to_remove.insert(face_id);
66 }
67 }
68 }
69 }
70
71 let mut halfedges_to_remove = HashSet::new();
73 for &face_id in &faces_to_remove {
74 let face_hes: Vec<_> = self
75 .halfedges
76 .iter()
77 .filter_map(|(he_id, he)| {
78 if he.face == Some(face_id) {
79 Some((he_id, *he))
80 } else {
81 None
82 }
83 })
84 .collect();
85
86 for (he_id, he) in face_hes {
87 if let Some(twin_id) = he.twin
88 && let Some(twin) = self.halfedges.get(twin_id)
89 {
90 if twin.is_boundary() || twin.face.is_some_and(|f| faces_to_remove.contains(&f))
91 {
92 halfedges_to_remove.insert(he_id);
93 halfedges_to_remove.insert(twin_id);
94 } else {
95 if let Some(he_mut) = self.halfedges.get_mut(he_id) {
96 he_mut.face = None;
97 he_mut.next = None;
98 }
99 }
100 }
101 }
102 }
103
104 for &face_id in &faces_to_remove {
106 if let Some(face) = self.faces.get(face_id) {
107 self.bvh.remove(face.index);
108 }
109 self.faces.remove(face_id);
110 }
111
112 let mut outgoing_cleanup: Vec<(VertexId, HalfedgeId)> = Vec::new();
115 for &he_id in &halfedges_to_remove {
116 if let Some(he) = self.halfedges.get(he_id)
117 && let Some(start_v_id) = he.start_vertex(self)
118 {
119 outgoing_cleanup.push((start_v_id, he_id));
120 }
121 }
122
123 for (v_id, he_id) in outgoing_cleanup {
124 if let Some(out_hes) = self.outgoing_halfedges.get_mut(v_id) {
125 out_hes.retain(|id| *id != he_id);
126 }
127 }
128
129 for &he_id in &halfedges_to_remove {
131 self.halfedges.remove(he_id);
132 }
133
134 let mut removed_vertices = Vec::with_capacity(vertex_ids.len() - 1);
135
136 for &v_id in &vertex_ids[1..] {
138 if !self.vertices.contains_key(v_id) {
139 continue;
140 }
141
142 removed_vertices.push(v_id);
143
144 let incoming_he_ids: Vec<HalfedgeId> = self
146 .outgoing_halfedges
147 .get(v_id)
148 .map(|out_hes| {
149 out_hes
150 .iter()
151 .filter_map(|he_id| self.halfedges.get(*he_id)?.twin)
152 .collect()
153 })
154 .unwrap_or_default();
155
156 for he_id in incoming_he_ids {
157 if let Some(he) = self.halfedges.get_mut(he_id) {
158 he.end_vertex = survivor_id;
159 }
160 }
161
162 let outgoing = self
164 .outgoing_halfedges
165 .get(v_id)
166 .cloned()
167 .unwrap_or_default();
168
169 if let Some(entry) = self.outgoing_halfedges.entry(survivor_id) {
170 entry.or_default().extend(outgoing);
171 }
172
173 self.remove_only_vertex(v_id);
174 }
175
176 let mut removed_halfedges: Vec<HalfedgeId> = halfedges_to_remove.into_iter().collect();
177 let removed_faces: Vec<FaceId> = faces_to_remove.into_iter().collect();
178
179 if !self.vertices.contains_key(survivor_id) {
181 return MergeVertices {
182 removed_vertices,
183 removed_halfedges,
184 removed_faces,
185 };
186 }
187
188 self.positions[survivor_id] = avg_pos;
189
190 if let Some(out_hes) = self.outgoing_halfedges.get_mut(survivor_id) {
192 out_hes.retain(|he_id| self.halfedges.contains_key(*he_id));
193 }
194
195 {
197 let out_hes = self
198 .outgoing_halfedges
199 .get(survivor_id)
200 .cloned()
201 .unwrap_or_default();
202
203 let mut by_end: HashMap<VertexId, Vec<HalfedgeId>> = HashMap::new();
204 for &he_id in &out_hes {
205 if let Some(he) = self.halfedges.get(he_id) {
206 by_end.entry(he.end_vertex).or_default().push(he_id);
207 }
208 }
209
210 let mut affected_vertices = HashSet::new();
211
212 for (end_v, group) in by_end {
213 if group.len() < 2 {
214 continue;
215 }
216
217 let best_fwd = group
219 .iter()
220 .copied()
221 .max_by_key(|&id| {
222 self.halfedges
223 .get(id)
224 .map_or(0u8, |h| if h.face.is_some() { 1 } else { 0 })
225 })
226 .unwrap();
227
228 let twins: Vec<HalfedgeId> = group
230 .iter()
231 .filter_map(|&id| {
232 self.halfedges
233 .get(id)?
234 .twin
235 .filter(|t| self.halfedges.contains_key(*t))
236 })
237 .collect();
238
239 let best_rev = twins.iter().copied().max_by_key(|&id| {
241 self.halfedges
242 .get(id)
243 .map_or(0u8, |h| if h.face.is_some() { 1 } else { 0 })
244 });
245
246 if let Some(rev_id) = best_rev {
248 if let Some(he) = self.halfedges.get_mut(best_fwd) {
249 he.twin = Some(rev_id);
250 }
251 if let Some(he) = self.halfedges.get_mut(rev_id) {
252 he.twin = Some(best_fwd);
253 }
254 }
255
256 for &he_id in &group {
258 if he_id != best_fwd {
259 self.halfedges.remove(he_id);
260 removed_halfedges.push(he_id);
261 }
262 }
263
264 for &twin_id in &twins {
266 if Some(twin_id) != best_rev {
267 self.halfedges.remove(twin_id);
268 removed_halfedges.push(twin_id);
269 }
270 }
271
272 affected_vertices.insert(end_v);
273 }
274
275 if let Some(out_hes) = self.outgoing_halfedges.get_mut(survivor_id) {
277 out_hes.retain(|he_id| self.halfedges.contains_key(*he_id));
278 }
279
280 for v in affected_vertices {
282 if let Some(out_hes) = self.outgoing_halfedges.get_mut(v) {
283 out_hes.retain(|he_id| self.halfedges.contains_key(*he_id));
284 }
285 let new_out = self
286 .outgoing_halfedges
287 .get(v)
288 .and_then(|hes| hes.first().copied());
289 if let Some(vertex) = self.vertices.get_mut(v) {
290 vertex.outgoing_halfedge = new_out;
291 }
292 }
293 }
294
295 let new_outgoing = self
297 .outgoing_halfedges
298 .get(survivor_id)
299 .and_then(|hes| hes.first().copied());
300
301 if let Some(vertex) = self.vertices.get_mut(survivor_id) {
302 vertex.outgoing_halfedge = new_outgoing;
303 }
304
305 self.make_outgoing_halfedge_boundary_if_possible(survivor_id);
306 self.compute_vertex_normal(survivor_id);
307
308 MergeVertices {
309 removed_vertices,
310 removed_halfedges,
311 removed_faces,
312 }
313 }
314
315 #[instrument(skip(self))]
329 pub fn flip_edge(&mut self, halfedge_id: HalfedgeId) {
330 #[cfg(feature = "rerun")]
331 self.log_he_rerun("flip", halfedge_id);
332
333 let he = unwrap_or_return!(self.halfedges.get(halfedge_id), "Halfedge not found");
334
335 let prev_he_id = unwrap_or_return!(he.prev(self), "Prev not found");
336 let prev_he = unwrap_or_return!(self.halfedges.get(prev_he_id), "Prev not found");
337 let start_v_id = prev_he.end_vertex;
338 let prev_twin_he_id = unwrap_or_return!(prev_he.twin, "Prev twin not found");
339
340 let next_he_id = unwrap_or_return!(he.next, "Next not found");
341 let next_he = unwrap_or_return!(self.halfedges.get(next_he_id), "Next not found");
342 let opposite_v_id = next_he.end_vertex;
343 let next_twin_he_id = unwrap_or_return!(next_he.twin, "Next twin not found");
344
345 let twin_he_id = unwrap_or_return!(he.twin, "Twin not found");
346 let twin_he = unwrap_or_return!(self.halfedges.get(twin_he_id), "Twin not found");
347
348 let twin_prev_he_id = unwrap_or_return!(twin_he.prev(self), "Prev not found");
349 let twin_prev_he = unwrap_or_return!(self.halfedges.get(twin_prev_he_id), "Prev not found");
350 let twin_start_v_id = twin_prev_he.end_vertex;
351 let twin_prev_twin_he_id = unwrap_or_return!(twin_prev_he.twin, "Prev twin twin not found");
352
353 let twin_next_he_id = unwrap_or_return!(twin_he.next, "Next not found");
354 let twin_next_he = unwrap_or_return!(self.halfedges.get(twin_next_he_id), "Next not found");
355 let twin_opposite_v_id = twin_next_he.end_vertex;
356 let twin_next_twin_he_id = unwrap_or_return!(twin_next_he.twin, "Next twin twin not found");
357
358 self.halfedges[halfedge_id].end_vertex = opposite_v_id;
359
360 self.halfedges[prev_he_id].end_vertex = twin_opposite_v_id;
361 self.make_twins(prev_he_id, twin_next_twin_he_id);
362 self.halfedges[next_he_id].end_vertex = start_v_id;
363 self.make_twins(next_he_id, prev_twin_he_id);
364
365 self.remove_outgoing_halfedge(start_v_id, halfedge_id);
366 self.remove_outgoing_halfedge(start_v_id, twin_next_he_id);
367 self.add_outgoing_halfedge(start_v_id, prev_he_id);
368
369 self.remove_outgoing_halfedge(opposite_v_id, prev_he_id);
370 self.add_outgoing_halfedge(opposite_v_id, next_he_id);
371 self.add_outgoing_halfedge(opposite_v_id, twin_he_id);
372
373 self.halfedges[twin_he_id].end_vertex = twin_opposite_v_id;
374
375 self.halfedges[twin_prev_he_id].end_vertex = opposite_v_id;
376 self.make_twins(twin_prev_he_id, next_twin_he_id);
377 self.halfedges[twin_next_he_id].end_vertex = twin_start_v_id;
378 self.make_twins(twin_next_he_id, twin_prev_twin_he_id);
379
380 self.remove_outgoing_halfedge(twin_start_v_id, twin_he_id);
381 self.remove_outgoing_halfedge(twin_start_v_id, next_he_id);
382 self.add_outgoing_halfedge(twin_start_v_id, twin_prev_he_id);
383
384 self.remove_outgoing_halfedge(twin_opposite_v_id, twin_prev_he_id);
385 self.add_outgoing_halfedge(twin_opposite_v_id, twin_next_he_id);
386 self.add_outgoing_halfedge(twin_opposite_v_id, halfedge_id);
387
388 let face_id1 = unwrap_or_return!(self.halfedges[halfedge_id].face, "Face not found");
390 let face1 = unwrap_or_return!(self.faces.get(face_id1), "Face not found");
391 self.bvh
392 .insert_or_update_partially(face1.aabb(self), face1.index, 0.0);
393
394 let face_id2 = unwrap_or_return!(self.halfedges[twin_he_id].face, "Face not found");
396 let face2 = unwrap_or_return!(self.faces.get(face_id2), "Face not found");
397 self.bvh
398 .insert_or_update_partially(face2.aabb(self), face2.index, 0.0);
399 }
400
401 pub fn make_twins(&mut self, he_id1: HalfedgeId, he_id2: HalfedgeId) {
403 let he1 = unwrap_or_return!(self.halfedges.get_mut(he_id1), "Halfedge not found");
404 he1.twin = Some(he_id2);
405
406 let he2 = unwrap_or_return!(self.halfedges.get_mut(he_id2), "Halfedge not found");
407 he2.twin = Some(he_id1);
408 }
409
410 #[instrument(skip(self))]
412 pub fn remove_outgoing_halfedge(&mut self, vertex_id: VertexId, halfedge_id: HalfedgeId) {
413 let outgoing_halfedges = unwrap_or_return!(
414 self.outgoing_halfedges.get_mut(vertex_id),
415 "No outgoing halfedges found"
416 );
417
418 outgoing_halfedges.retain(|he_id| *he_id != halfedge_id);
419 }
420
421 #[instrument(skip(self))]
423 pub fn add_outgoing_halfedge(&mut self, vertex_id: VertexId, outgoing_halfedge: HalfedgeId) {
424 let vertex = unwrap_or_return!(self.vertices.get_mut(vertex_id), "Vertex not found");
425 vertex.outgoing_halfedge = Some(outgoing_halfedge);
426
427 let v_outgoing_halfedges = unwrap_or_return!(
428 self.outgoing_halfedges.get_mut(vertex_id),
429 "No outgoing halfedges found"
430 );
431 v_outgoing_halfedges.push(outgoing_halfedge);
432 }
433
434 #[instrument(skip_all)]
437 pub fn smooth_vertices(&mut self, vertices: impl IntoIterator<Item = VertexId>) {
438 let mut new_positions = SparseSecondaryMap::new();
439
440 let mut affected_face_ids = HashSet::new();
441
442 for vertex_id in vertices.into_iter() {
443 let Some(pos) = self.compute_smoothed_vertex_pos(vertex_id) else {
444 continue;
445 };
446
447 new_positions.insert(vertex_id, pos);
448
449 affected_face_ids.extend(self.vertices[vertex_id].faces(self));
451 }
452
453 for (vertex_id, &pos) in &new_positions {
454 self.positions.insert(vertex_id, pos);
455 }
456
457 for vertex_id in new_positions.keys() {
458 self.compute_vertex_normal(vertex_id);
459 }
460
461 for face_id in affected_face_ids {
462 let Some(face) = self.faces.get(face_id) else {
463 error!("Face {:?} does not exist", face_id);
464 continue;
465 };
466
467 self.bvh
468 .insert_or_update_partially(face.aabb(self), face.index, 0.0);
469 }
470 }
471
472 #[instrument(skip(self))]
478 pub fn smooth_vertex(&mut self, vertex_id: VertexId) {
479 let pos = unwrap_or_return!(
480 self.compute_smoothed_vertex_pos(vertex_id),
481 "Couldn't compute smoothed position"
482 );
483
484 self.positions.insert(vertex_id, pos);
485 self.compute_vertex_normal(vertex_id);
486
487 for face_id in self.vertices[vertex_id].faces(self).collect_vec() {
489 let Some(face) = self.faces.get(face_id) else {
490 error!("Face {:?} does not exist", face_id);
491 continue;
492 };
493
494 self.bvh
495 .insert_or_update_partially(face.aabb(self), face.index, 0.0);
496 }
497 }
498
499 #[instrument(skip(self))]
500 fn compute_smoothed_vertex_pos(&mut self, vertex_id: VertexId) -> Option<Vec3> {
501 let vertex = self.vertices.get(vertex_id)?;
502
503 let mut pos = *self
504 .positions
505 .get(vertex_id)
506 .or_else(error_none!("Position not found for id {vertex_id:?}"))?;
507
508 let mut count = 1.0;
509 for neighbor_v_id in vertex.neighbours(self) {
510 let neighbor_pos = *self.positions.get(neighbor_v_id).or_else(error_none!(
511 "Neighbor position not found for id {neighbor_v_id:?}"
512 ))?;
513
514 pos += neighbor_pos;
515 count += 1.0;
516 }
517
518 pos /= count;
519
520 Some(pos)
521 }
522}
523
524#[cfg(test)]
525mod tests {
526 use glam::Vec3;
527
528 use super::*;
529
530 #[cfg(feature = "gltf")]
531 #[test]
532 fn test_merge_vertices_cube() {
533 use crate::{integrations::gltf, utils::get_tracing_subscriber};
534
535 get_tracing_subscriber();
536 let mut meshgraph = gltf::load("src/ops/glb/cube.glb").unwrap();
537
538 #[cfg(feature = "rerun")]
539 meshgraph.log_rerun();
540
541 let x0_vertices: Vec<VertexId> = meshgraph
542 .positions
543 .iter()
544 .filter_map(|(v_id, pos)| if pos.x == 0.0 { Some(v_id) } else { None })
545 .collect();
546
547 let x0_count = x0_vertices.len();
548 assert!(x0_count >= 2, "Expected at least 2 vertices with x=0");
549
550 let MergeVertices {
551 removed_vertices,
552 removed_halfedges,
553 removed_faces,
554 } = meshgraph.merge_vertices(x0_vertices);
555
556 #[cfg(feature = "rerun")]
557 {
558 meshgraph.log_rerun();
559 crate::RR.flush_blocking().unwrap();
560 }
561
562 let remaining_x0: Vec<_> = meshgraph
563 .positions
564 .iter()
565 .filter(|(_, pos)| pos.x == 0.0)
566 .collect();
567
568 assert_eq!(remaining_x0.len(), 1);
569
570 assert_eq!(meshgraph.vertices.len(), 5);
571 assert_eq!(meshgraph.halfedges.len(), 18);
572 assert_eq!(meshgraph.faces.len(), 6);
573
574 assert_eq!(removed_vertices.len(), 3);
575 assert_eq!(removed_halfedges.len(), 18);
576 assert_eq!(removed_faces.len(), 6);
577 }
578
579 #[cfg(feature = "gltf")]
580 #[test]
581 fn test_merge_vertices_cube_w_missing_triangle() {
582 use crate::{integrations::gltf, utils::get_tracing_subscriber};
583
584 get_tracing_subscriber();
585 let mut meshgraph = gltf::load("src/ops/glb/cube_w_missing_triangle.glb").unwrap();
586
587 #[cfg(feature = "rerun")]
588 meshgraph.log_rerun();
589
590 let x0_vertices: Vec<VertexId> = meshgraph
591 .positions
592 .iter()
593 .filter_map(|(v_id, pos)| if pos.x == 0.0 { Some(v_id) } else { None })
594 .collect();
595
596 let x0_count = x0_vertices.len();
597 assert!(x0_count >= 2, "Expected at least 2 vertices with x=0");
598
599 let MergeVertices {
600 removed_vertices,
601 removed_halfedges,
602 removed_faces,
603 } = meshgraph.merge_vertices(x0_vertices);
604
605 #[cfg(feature = "rerun")]
606 {
607 meshgraph.log_rerun();
608 crate::RR.flush_blocking().unwrap();
609 }
610
611 let remaining_x0: Vec<_> = meshgraph
612 .positions
613 .iter()
614 .filter(|(_, pos)| pos.x == 0.0)
615 .collect();
616
617 assert_eq!(remaining_x0.len(), 1);
618
619 assert_eq!(meshgraph.vertices.len(), 5);
620 assert_eq!(meshgraph.halfedges.len(), 18);
621 assert_eq!(meshgraph.faces.len(), 6);
622
623 assert_eq!(removed_vertices.len(), 3);
624 assert_eq!(removed_halfedges.len(), 18);
625 assert_eq!(removed_faces.len(), 5);
626 }
627
628 #[test]
629 fn test_flip_edge() {
630 let mut mesh_graph = MeshGraph::new();
631
632 let v_id1 = mesh_graph.add_vertex(Vec3::new(-1.0, 1.0, 0.0));
633 let v_id2 = mesh_graph.add_vertex(Vec3::new(-1.0, -1.0, 0.0));
634 let v_id3 = mesh_graph.add_vertex(Vec3::new(1.0, -1.0, 0.0));
635 let v_id4 = mesh_graph.add_vertex(Vec3::new(1.0, 1.0, 0.0));
636
637 mesh_graph.add_face_from_vertices(v_id1, v_id2, v_id3);
638 mesh_graph.add_face_from_vertices(v_id1, v_id3, v_id4);
639
640 #[cfg(feature = "rerun")]
641 mesh_graph.log_rerun();
642
643 assert!(mesh_graph.halfedge_from_to(v_id2, v_id4).is_none());
644
645 assert_eq!(mesh_graph.outgoing_halfedges[v_id1].len(), 3);
646 assert_eq!(mesh_graph.outgoing_halfedges[v_id2].len(), 2);
647 assert_eq!(mesh_graph.outgoing_halfedges[v_id3].len(), 3);
648 assert_eq!(mesh_graph.outgoing_halfedges[v_id4].len(), 2);
649
650 mesh_graph.flip_edge(mesh_graph.halfedge_from_to(v_id1, v_id3).unwrap());
651
652 #[cfg(feature = "rerun")]
653 {
654 mesh_graph.log_rerun();
655 crate::RR.flush_blocking().unwrap();
656 }
657
658 assert!(mesh_graph.halfedge_from_to(v_id1, v_id3).is_none());
659 assert!(mesh_graph.halfedge_from_to(v_id2, v_id4).is_some());
660
661 assert_eq!(mesh_graph.outgoing_halfedges[v_id1].len(), 2);
662 assert_eq!(mesh_graph.outgoing_halfedges[v_id2].len(), 3);
663 assert_eq!(mesh_graph.outgoing_halfedges[v_id3].len(), 2);
664 assert_eq!(mesh_graph.outgoing_halfedges[v_id4].len(), 3);
665 }
666}