1use hashbrown::HashSet;
2use tracing::instrument;
3
4use crate::{
5 HalfedgeId, MeshGraph, Selection, SelectionOps, VertexId, error_none,
6 ops::{EdgeLengthCleanup, PendingEdges, PendingOrder},
7 utils::unwrap_or_return,
8};
9
10impl MeshGraph {
11 #[instrument(skip(self))]
20 pub fn subdivide_until_edges_below_max_length(
21 &mut self,
22 max_length_squared: f32,
23 marked_halfedge_ids: &mut HashSet<HalfedgeId>,
24 marked_vertex_ids: &mut HashSet<VertexId>,
25 ) -> EdgeLengthCleanup {
26 #[cfg(feature = "instrumentation")]
27 crate::set_current_op("subdivide");
28 #[cfg(feature = "instrumentation")]
29 crate::probe_chain_begin(self);
30 let pending = self.halfedges_map(|len_sqr| len_sqr > max_length_squared);
31
32 let depth = if max_length_squared > 0.0 {
48 let longest = pending.values().copied().fold(0.0_f32, f32::max);
49 if longest.is_finite() {
50 ((longest / max_length_squared).sqrt().ceil() as usize).clamp(1, 64)
51 } else {
52 1
53 }
54 } else {
55 1
56 };
57
58 let budget = pending.len() * 2 + 100 * depth;
59
60 let mut halfedges_to_subdivide = PendingEdges::new(pending, PendingOrder::LongestFirst);
61
62 for _ in 0..budget {
63 if halfedges_to_subdivide.is_empty() {
64 break;
65 }
66
67 let Some((max_he_id, _)) = halfedges_to_subdivide.pop_live() else {
77 debug_assert!(
80 halfedges_to_subdivide.is_empty(),
81 "heap drained but {} pending edges remain - a push site is missing",
82 halfedges_to_subdivide.len()
83 );
84 break;
85 };
86
87 halfedges_to_subdivide.remove(&max_he_id);
88
89 let mut affected_faces = Selection::default();
90
91 let max_he = self.halfedges[max_he_id];
93 if let Some(face_id) = max_he.face {
94 affected_faces.insert(face_id);
95 }
96
97 if let Some(twin_id) = max_he.twin.or_else(error_none!("Twin missing"))
98 && let Some(twin_he) = self
99 .halfedges
100 .get(twin_id)
101 .or_else(error_none!("Halfedge not found"))
102 && let Some(twin_face_id) = twin_he.face
103 {
104 affected_faces.insert(twin_face_id);
105 }
106
107 let subdivide_edge_result = unwrap_or_return!(
108 self.subdivide_edge(max_he_id),
109 "Couldn't subdivide edge",
110 EdgeLengthCleanup::Stalled
111 );
112
113 if marked_halfedge_ids.contains(&max_he_id) {
114 marked_halfedge_ids.extend(subdivide_edge_result.added_halfedges.iter().copied());
115 marked_vertex_ids.insert(subdivide_edge_result.added_vertex);
116 }
117
118 for new_he_id in subdivide_edge_result.added_halfedges {
133 let new_he = self.halfedges[new_he_id];
135
136 if let Some(face_id) = new_he.face {
137 affected_faces.insert(face_id);
138 }
139
140 let new_twin_id =
141 unwrap_or_return!(new_he.twin, "New twin missing", EdgeLengthCleanup::Stalled);
142 let new_twin = unwrap_or_return!(
143 self.halfedges.get(new_twin_id),
144 "New twin not found",
145 EdgeLengthCleanup::Stalled
146 );
147
148 if let Some(face_id) = new_twin.face {
149 affected_faces.insert(face_id);
150 #[cfg(feature = "rerun")]
151 self.log_face_rerun("subdivide/selected_new", face_id);
152 }
153 }
154
155 let mut new_hes_to_check = HashSet::new();
156
157 for he_id in affected_faces.resolve_to_halfedges(self) {
158 let he = unwrap_or_return!(
159 self.halfedges.get(he_id),
160 "Halfedge not found",
161 EdgeLengthCleanup::Stalled
162 );
163 let twin_id =
164 unwrap_or_return!(he.twin, "Twin missing", EdgeLengthCleanup::Stalled);
165
166 new_hes_to_check.insert(he_id.min(twin_id));
167 }
168
169 for he_id in new_hes_to_check {
178 let he = self.halfedges[he_id]; let len_sqr = he.length_squared(self);
181
182 if len_sqr > max_length_squared {
183 #[cfg(feature = "rerun")]
184 self.log_he_rerun("/subdivide/new_edge", he_id);
185
186 halfedges_to_subdivide.insert(he_id, len_sqr);
187 }
188 }
189 }
190
191 marked_vertex_ids.retain(|v_id| self.vertices.contains_key(*v_id));
195 marked_halfedge_ids.retain(|he_id| self.halfedges.contains_key(*he_id));
196
197 #[cfg(feature = "instrumentation")]
198 if self.probe_chain_integrity("subdivide_until_edges_below_max_length") {
199 crate::state_history_push(self, "subdivide_until_edges_below_max_length");
200 }
201
202 #[cfg(feature = "rerun")]
203 self.log_rerun();
204
205 if halfedges_to_subdivide.is_empty() {
208 EdgeLengthCleanup::Converged
209 } else {
210 EdgeLengthCleanup::Stalled
211 }
212 }
213
214 #[instrument(skip(self))]
224 pub fn subdivide_edge(&mut self, halfedge_id: HalfedgeId) -> Option<SubdivideEdge> {
225 let mut added_halfedges = Vec::with_capacity(3);
226
227 let he = self
228 .halfedges
229 .get(halfedge_id)
230 .or_else(error_none!("Halfedge not found"))?;
231 let twin_id = he.twin.or_else(error_none!("Twin halfedge not found"))?;
232
233 let start_v = he
234 .start_vertex(self)
235 .or_else(error_none!("Start vertex not found"))?;
236 let end_v = he.end_vertex;
237
238 let start_pos = self
239 .positions
240 .get(start_v)
241 .or_else(error_none!("Start position not found"))?;
242 let end_pos = self
243 .positions
244 .get(end_v)
245 .or_else(error_none!("End position not found"))?;
246
247 let center_pos = (start_pos + end_pos) * 0.5;
248
249 let center_v = self.add_vertex(center_pos);
268 if let Some(normals) = &mut self.vertex_normals {
269 let start_normal = normals
270 .get(start_v)
271 .or_else(error_none!("Start normal not found"))?;
272 let end_normal = normals
273 .get(end_v)
274 .or_else(error_none!("End normal not found"))?;
275 normals.insert(center_v, (start_normal + end_normal).normalize());
276 }
277
278 let new_he = self.add_halfedge(center_v, end_v)?;
279 self.vertices[center_v].outgoing_halfedge = Some(new_he);
281
282 added_halfedges.push(new_he);
283
284 if let Some(new_face_he) = self.subdivide_face(halfedge_id, new_he, center_v) {
285 added_halfedges.push(new_face_he);
286 } else {
287 self.halfedges[halfedge_id].end_vertex = center_v;
293 }
294
295 let new_twin = self.add_halfedge(center_v, start_v)?;
296
297 if let Some(new_face_he) = self.subdivide_face(twin_id, new_twin, center_v) {
298 added_halfedges.push(new_face_he);
299 } else {
300 self.halfedges[twin_id].end_vertex = center_v;
303 }
304
305 self.halfedges[new_he].twin = Some(twin_id);
307 self.halfedges
308 .get_mut(twin_id)
309 .or_else(error_none!("Twin halfedge not found"))?
310 .twin = Some(new_he);
311
312 self.halfedges[halfedge_id].twin = Some(new_twin);
314 self.halfedges[new_twin].twin = Some(halfedge_id);
316
317 Some(SubdivideEdge {
321 added_halfedges,
322 added_vertex: center_v,
323 })
324 }
325
326 #[instrument(skip(self))]
328 fn subdivide_face(
329 &mut self,
330 existing_halfedge_id: HalfedgeId,
331 new_halfedge_id: HalfedgeId,
332 center_v: VertexId,
333 ) -> Option<HalfedgeId> {
334 let he = self
335 .halfedges
336 .get(existing_halfedge_id)
337 .or_else(error_none!("Halfedge not found"))?;
338
339 let face_id = he.face?;
340 self.faces
341 .get_mut(face_id)
342 .or_else(error_none!("Facee not found"))?
343 .halfedge = existing_halfedge_id;
344
345 let next_he = self.halfedges[existing_halfedge_id]
347 .next
348 .or_else(error_none!("Next halfedge missing"))?;
349 let last_he = self
350 .halfedges
351 .get(next_he)
352 .or_else(error_none!("Next halfedge not found"))?
353 .next
354 .or_else(error_none!("Last halfedge not found"))?;
355
356 if !self
362 .vertices
363 .contains_key(self.halfedges[next_he].end_vertex)
364 {
365 tracing::error!("subdivide_face: next halfedge {next_he:?} ends at a missing vertex");
366 return None;
367 }
368 if !self.vertices.contains_key(center_v) {
369 tracing::error!("subdivide_face: center vertex {center_v:?} is missing");
370 return None;
371 }
372
373 let new_he = self.add_halfedge(center_v, self.halfedges[next_he].end_vertex)?; self.halfedges[existing_halfedge_id].next = Some(new_he); self.halfedges[new_he].next = Some(last_he); self.halfedges[new_he].face = Some(face_id); let new_twin = self.add_halfedge(self.halfedges[next_he].end_vertex, center_v)?; let new_face_id = self.add_face(new_halfedge_id, next_he, new_twin);
384
385 self.halfedges[new_twin].twin = Some(new_he); self.halfedges[new_he].twin = Some(new_twin); self.halfedges[existing_halfedge_id].end_vertex = center_v; let face = self.faces[face_id]; let new_face = self.faces[new_face_id]; self.bvh
393 .insert_or_update_partially(face.aabb(self), face.index, 0.0);
394 self.bvh
395 .insert_or_update_partially(new_face.aabb(self), new_face.index, 0.0);
396
397 Some(new_he)
404 }
405}
406
407pub struct SubdivideEdge {
408 added_halfedges: Vec<HalfedgeId>,
410 added_vertex: VertexId,
412}
413
414#[cfg(test)]
415mod test {
416 use super::*;
417 use crate::ops::EdgeLengthCleanup;
418 use crate::utils::{build_grid, mesh_invariant_violations};
419 use glam::Vec3;
420 use hashbrown::HashSet;
421
422 fn max_len_sqr(mg: &MeshGraph) -> f32 {
425 mg.halfedges
426 .values()
427 .map(|he| he.length_squared(mg))
428 .fold(0.0, f32::max)
429 }
430
431 fn count_above(mg: &MeshGraph, max_length_squared: f32) -> usize {
432 mg.halfedges
433 .values()
434 .filter(|he| he.length_squared(mg) > max_length_squared)
435 .count()
436 }
437
438 fn subdivide(mg: &mut MeshGraph, max_length_squared: f32) {
439 mg.subdivide_until_edges_below_max_length(
440 max_length_squared,
441 &mut HashSet::new(),
442 &mut HashSet::new(),
443 );
444 }
445
446 #[test]
452 fn test_subdivide_converges_on_a_small_mesh_needing_deep_refinement() {
453 use glam::Vec3;
454
455 let positions = vec![
456 Vec3::new(0.0, 0.0, 0.0),
457 Vec3::new(1.0, 0.0, 0.0),
458 Vec3::new(0.0, 1.0, 0.0),
459 Vec3::new(0.0, 0.0, 1.0),
460 ];
461 let indices: Vec<usize> = vec![0, 2, 1, 0, 1, 3, 1, 2, 3, 2, 0, 3];
462 let mut mg = MeshGraph::indexed_triangles(&positions, &indices);
463
464 let target: f32 = 0.2;
465 let outcome = mg.subdivide_until_edges_below_max_length(
466 target * target,
467 &mut HashSet::new(),
468 &mut HashSet::new(),
469 );
470
471 assert_eq!(
472 outcome,
473 EdgeLengthCleanup::Converged,
474 "one call left {} edges over-long",
475 count_above(&mg, target * target)
476 );
477 assert_eq!(count_above(&mg, target * target), 0);
478 assert!(mesh_invariant_violations(&mg).is_empty());
479 }
480
481 #[test]
482 fn test_subdivide_reports_converged_when_it_drains() {
483 let mut mg = build_grid(4);
484
485 let outcome = mg.subdivide_until_edges_below_max_length(
486 0.9,
487 &mut HashSet::new(),
488 &mut HashSet::new(),
489 );
490
491 assert_eq!(outcome, EdgeLengthCleanup::Converged);
492 assert_eq!(count_above(&mg, 0.9), 0);
493 }
494
495 #[test]
497 fn test_subdivide_reports_converged_on_a_clean_mesh() {
498 let mut mg = build_grid(3);
499 let faces = mg.faces.len();
500
501 let outcome = mg.subdivide_until_edges_below_max_length(
503 100.0,
504 &mut HashSet::new(),
505 &mut HashSet::new(),
506 );
507
508 assert_eq!(outcome, EdgeLengthCleanup::Converged);
509 assert_eq!(mg.faces.len(), faces);
510 }
511
512 #[test]
513 fn test_subdivide_until_max_length_holds_invariants() {
514 let mut mg = build_grid(6);
515 assert!(
516 mesh_invariant_violations(&mg).is_empty(),
517 "fixture is already broken"
518 );
519
520 let before_above = count_above(&mg, 0.5);
521 let before_max = max_len_sqr(&mg);
522
523 subdivide(&mut mg, 0.5);
524
525 assert!(
526 mesh_invariant_violations(&mg).is_empty(),
527 "invariants violated: {:?}",
528 mesh_invariant_violations(&mg)
529 );
530 assert!(count_above(&mg, 0.5) < before_above);
533 assert!(max_len_sqr(&mg) < before_max);
534 }
535
536 #[test]
540 fn test_subdivide_until_max_length_drains_completely() {
541 let mut mg = build_grid(4);
542
543 subdivide(&mut mg, 0.9);
544
545 assert!(mesh_invariant_violations(&mg).is_empty());
546 assert_eq!(
547 count_above(&mg, 0.9),
548 0,
549 "subdivision left {} edges above the threshold",
550 count_above(&mg, 0.9)
551 );
552 }
553
554 #[test]
558 fn test_subdivide_adds_geometry_and_moves_no_existing_vertex() {
559 let mut mg = build_grid(4);
560 let before: Vec<(crate::VertexId, Vec3)> =
561 mg.positions.iter().map(|(v, &p)| (v, p)).collect();
562 let faces_before = mg.faces.len();
563
564 subdivide(&mut mg, 0.9);
565
566 assert!(mg.faces.len() > faces_before);
567 for (v_id, pos) in before {
568 assert_eq!(
569 mg.positions.get(v_id),
570 Some(&pos),
571 "vertex {v_id:?} moved during subdivision"
572 );
573 }
574 }
575
576 #[test]
577 fn test_subdivide_is_idempotent_once_converged() {
578 let mut mg = build_grid(4);
579 subdivide(&mut mg, 0.9);
580
581 let verts = mg.vertices.len();
582 let faces = mg.faces.len();
583 let halfedges = mg.halfedges.len();
584
585 subdivide(&mut mg, 0.9);
586
587 assert_eq!(mg.vertices.len(), verts);
588 assert_eq!(mg.faces.len(), faces);
589 assert_eq!(mg.halfedges.len(), halfedges);
590 }
591
592 #[test]
598 fn test_subdivide_picks_longest_edges_first() {
599 let mut mg = build_grid(3);
600 let before: HashSet<crate::VertexId> = mg.positions.keys().collect();
601
602 subdivide(&mut mg, 1.5);
604
605 let added: Vec<Vec3> = mg
606 .positions
607 .iter()
608 .filter(|(v, _)| !before.contains(v))
609 .map(|(_, &p)| p)
610 .collect();
611
612 assert!(!added.is_empty(), "expected at least one diagonal split");
613 for p in added {
614 let frac_x = (p.x - p.x.floor() - 0.5).abs();
615 let frac_y = (p.y - p.y.floor() - 0.5).abs();
616 assert!(
617 frac_x < 1e-6 && frac_y < 1e-6,
618 "split at {p:?} is not a diagonal midpoint, so a shorter edge was chosen"
619 );
620 }
621 }
622
623 #[test]
628 fn test_subdivide_purges_dead_ids_from_marked_sets() {
629 let mut mg = build_grid(4);
630
631 let mut marked_vertices: HashSet<crate::VertexId> = mg.vertices.keys().collect();
633 let mut marked_halfedges: HashSet<HalfedgeId> = mg.halfedges.keys().collect();
634 mg.collapse_until_edges_above_min_length(1.5, &mut HashSet::new());
635 assert!(
636 marked_vertices
637 .iter()
638 .any(|v| !mg.vertices.contains_key(*v)),
639 "fixture failed to invalidate anything - test is vacuous"
640 );
641
642 mg.subdivide_until_edges_below_max_length(0.5, &mut marked_halfedges, &mut marked_vertices);
643
644 for v_id in &marked_vertices {
645 assert!(
646 mg.vertices.contains_key(*v_id),
647 "marked vertex {v_id:?} is dead"
648 );
649 }
650 for he_id in &marked_halfedges {
651 assert!(
652 mg.halfedges.contains_key(*he_id),
653 "marked halfedge {he_id:?} is dead"
654 );
655 }
656 }
657}