1use std::collections::HashSet;
10
11use crate::column::{try_reserve_exact, try_vec};
12use crate::{Mesh, Result, StoreError, TriangleId, VertexId};
13
14const NONE: u32 = u32::MAX;
15
16pub(crate) const ZERO_PER_ELEMENT: usize = 16;
19const NON_MANIFOLD: u32 = u32::MAX - 1;
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub enum EdgeKind {
24 Interior,
26 Boundary,
28 NonManifold,
30}
31
32#[derive(Debug)]
38pub struct Adjacency {
39 opposite: Vec<u32>,
40 vertex_corner: Vec<u32>,
41 non_manifold_vertices: Vec<u64>,
42}
43
44impl Adjacency {
45 pub(crate) fn build(mesh: &Mesh) -> Result<Self> {
46 let mut build = AdjacencyBuild::new(mesh);
47 build.step(mesh, usize::MAX)?;
48 build.take().ok_or(StoreError::OutOfMemory)
49 }
50
51 pub fn opposite(&self, corner: u32) -> Option<u32> {
53 self.opposite
54 .get(corner as usize)
55 .copied()
56 .filter(|&o| o < NON_MANIFOLD)
57 }
58
59 pub fn edge_kind(&self, corner: u32) -> EdgeKind {
61 match self.opposite.get(corner as usize).copied().unwrap_or(NONE) {
62 NONE => EdgeKind::Boundary,
63 NON_MANIFOLD => EdgeKind::NonManifold,
64 _ => EdgeKind::Interior,
65 }
66 }
67
68 pub fn vertex_corner(&self, v: VertexId) -> Option<u32> {
70 self.vertex_corner
71 .get(v as usize)
72 .copied()
73 .filter(|&c| c != NONE)
74 }
75
76 pub fn is_non_manifold_vertex(&self, v: VertexId) -> bool {
79 self.non_manifold_vertices
80 .get(v as usize / 64)
81 .is_some_and(|w| (w >> (v % 64)) & 1 == 1)
82 }
83
84 pub fn triangles_around(&self, mesh: &Mesh, v: VertexId) -> Vec<TriangleId> {
87 let Some(start) = self.vertex_corner(v) else {
88 return Vec::new();
89 };
90 let tris = mesh.triangles();
91 let mut fan = vec![start / 3];
92 let mut visited = HashSet::from([start / 3]);
93 let mut stack = vec![start];
94 while let Some(c) = stack.pop() {
95 let (t, i) = (c / 3, c % 3);
96 for e in [(i + 1) % 3, (i + 2) % 3] {
97 let Some(o) = self.opposite(3 * t + e) else {
98 continue;
99 };
100 let s = o / 3;
101 if visited.contains(&s) {
102 continue;
103 }
104 if let Some(j) = tris[s as usize].iter().position(|&x| x == v) {
105 visited.insert(s);
106 fan.push(s);
107 stack.push(3 * s + j as u32);
108 }
109 }
110 }
111 fan
112 }
113}
114
115#[derive(Debug, Clone, Copy, PartialEq, Eq)]
118enum Pass {
119 Count(usize),
121 Prefix(usize),
123 Zero(usize),
125 Fill(usize),
127 Opposite(usize),
129 Fans(usize),
131 Done,
133}
134
135#[derive(Debug)]
145pub struct AdjacencyBuild {
146 uid: u64,
147 epoch: u32,
148 revision: u64,
149 slots: (usize, usize),
152 pass: Pass,
153 offsets: Vec<u32>,
154 incident: Vec<u32>,
155 cursor: Vec<u32>,
156 opposite: Vec<u32>,
157 vertex_corner: Vec<u32>,
158 non_manifold_vertices: Vec<u64>,
159 max_valence: u32,
160 scratch: FanScratch,
161}
162
163impl AdjacencyBuild {
164 pub fn new(mesh: &Mesh) -> AdjacencyBuild {
166 AdjacencyBuild {
167 uid: mesh.uid(),
168 epoch: mesh.epoch(),
169 revision: mesh.revision(),
170 slots: (mesh.vertex_slots(), mesh.triangle_slots()),
171 pass: Pass::Count(0),
172 offsets: Vec::new(),
173 incident: Vec::new(),
174 cursor: Vec::new(),
175 opposite: Vec::new(),
176 vertex_corner: Vec::new(),
177 non_manifold_vertices: Vec::new(),
178 max_valence: 0,
179 scratch: FanScratch {
180 by_triangle: Vec::new(),
181 parent: Vec::new(),
182 },
183 }
184 }
185
186 pub fn is_for(&self, mesh: &Mesh) -> bool {
188 self.uid == mesh.uid()
189 && self.epoch == mesh.epoch()
190 && self.revision == mesh.revision()
191 && self.slots == (mesh.vertex_slots(), mesh.triangle_slots())
192 }
193
194 pub fn is_done(&self) -> bool {
196 self.pass == Pass::Done
197 }
198
199 fn step(&mut self, mesh: &Mesh, budget: usize) -> Result<bool> {
203 debug_assert!(
204 self.is_for(mesh),
205 "an adjacency build stepped on another mesh"
206 );
207 let tris = mesh.triangles();
208 let alive = |t: usize| mesh.is_triangle_alive(t as TriangleId);
209 let vslots = mesh.vertex_slots();
210 let mut left = budget.max(1);
211 while left > 0 {
212 match self.pass {
213 Pass::Count(from) => {
214 if from == 0 {
215 self.offsets = try_vec(vslots + 1, 0u32)?;
217 }
218 let to = tris.len().min(from.saturating_add(left));
219 for (t, tri) in tris.iter().enumerate().take(to).skip(from) {
220 if alive(t) {
221 for &v in tri {
222 self.offsets[v as usize + 1] += 1;
223 }
224 }
225 }
226 left -= (to - from).min(left);
227 self.pass = if to == tris.len() {
228 Pass::Prefix(0)
229 } else {
230 Pass::Count(to)
231 };
232 }
233 Pass::Prefix(from) => {
234 let to = vslots.min(from.saturating_add(left));
235 for v in from..to {
236 self.offsets[v + 1] += self.offsets[v];
237 let valence = self.offsets[v + 1] - self.offsets[v];
238 self.max_valence = self.max_valence.max(valence);
239 }
240 left -= (to - from).min(left);
241 if to < vslots {
242 self.pass = Pass::Prefix(to);
243 continue;
244 }
245 try_reserve_exact(&mut self.incident, self.offsets[vslots] as usize)?;
246 self.pass = Pass::Zero(0);
247 }
248 Pass::Zero(from) => {
249 let total = self.offsets[vslots] as usize;
251 let to = total.min(from.saturating_add(left.saturating_mul(ZERO_PER_ELEMENT)));
252 self.incident.resize(to, 0);
253 left -= (to - from).div_ceil(ZERO_PER_ELEMENT).min(left);
254 if to < total {
255 self.pass = Pass::Zero(to);
256 continue;
257 }
258 self.cursor = try_vec(vslots + 1, 0u32)?;
259 self.cursor.copy_from_slice(&self.offsets);
260 self.pass = Pass::Fill(0);
261 }
262 Pass::Fill(from) => {
263 let to = tris.len().min(from.saturating_add(left));
264 for (t, tri) in tris.iter().enumerate().take(to).skip(from) {
265 if alive(t) {
266 for (i, &v) in tri.iter().enumerate() {
267 let at = &mut self.cursor[v as usize];
268 self.incident[*at as usize] = (3 * t + i) as u32;
269 *at += 1;
270 }
271 }
272 }
273 left -= (to - from).min(left);
274 if to < tris.len() {
275 self.pass = Pass::Fill(to);
276 continue;
277 }
278 self.cursor = Vec::new();
279 try_reserve_exact(&mut self.opposite, 3 * tris.len())?;
281 self.pass = Pass::Opposite(0);
282 }
283 Pass::Opposite(from) => {
284 let to = tris.len().min(from.saturating_add(left));
285 let (offsets, incident) = (&self.offsets, &self.incident);
286 let corners_of = |v: u32| {
287 &incident[offsets[v as usize] as usize..offsets[v as usize + 1] as usize]
288 };
289 for (t, tri) in tris.iter().enumerate().take(to).skip(from) {
290 if !alive(t) {
291 self.opposite.extend([NONE; 3]);
292 continue;
293 }
294 for i in 0..3 {
295 let (u, w) = (tri[(i + 1) % 3], tri[(i + 2) % 3]);
296 if u == w {
297 self.opposite.push(NONE);
298 continue;
299 }
300 let (scan, other_end) = if corners_of(u).len() <= corners_of(w).len() {
304 (u, w)
305 } else {
306 (w, u)
307 };
308 let (mut count, mut found, mut last) = (0, NONE, NONE);
309 for &d in corners_of(scan) {
310 let s = d / 3;
311 if s as usize == t || s == last {
312 continue;
313 }
314 last = s;
315 let other = tris[s as usize];
316 if !other.contains(&other_end) {
317 continue;
318 }
319 count += 1;
320 found = (0..3)
321 .find(|&j| other[j] != u && other[j] != w)
322 .map_or(NON_MANIFOLD, |j| 3 * s + j as u32);
323 }
324 self.opposite.push(match count {
325 0 => NONE,
326 1 => found,
327 _ => NON_MANIFOLD,
328 });
329 }
330 }
331 left -= (to - from).min(left);
332 if to < tris.len() {
333 self.pass = Pass::Opposite(to);
334 continue;
335 }
336 self.vertex_corner = try_vec(vslots, NONE)?;
337 self.non_manifold_vertices = try_vec(vslots.div_ceil(64), 0u64)?;
338 let most = self.max_valence as usize;
340 try_reserve_exact(&mut self.scratch.by_triangle, most)?;
341 try_reserve_exact(&mut self.scratch.parent, most)?;
342 self.pass = Pass::Fans(0);
343 }
344 Pass::Fans(from) => {
345 let to = vslots.min(from.saturating_add(left));
346 for v in from as u32..to as u32 {
347 let (lo, hi) = (self.offsets[v as usize], self.offsets[v as usize + 1]);
348 let corners = &self.incident[lo as usize..hi as usize];
349 if let Some(&c) = corners.first() {
350 self.vertex_corner[v as usize] = c;
351 }
352 if !corners.is_empty()
353 && fan_count(corners, &self.opposite, &mut self.scratch) != 1
354 {
355 self.non_manifold_vertices[v as usize / 64] |= 1 << (v % 64);
356 }
357 }
358 left -= (to - from).min(left);
359 if to < vslots {
360 self.pass = Pass::Fans(to);
361 continue;
362 }
363 self.offsets = Vec::new();
364 self.incident = Vec::new();
365 self.pass = Pass::Done;
366 }
367 Pass::Done => return Ok(true),
368 }
369 }
370 Ok(self.pass == Pass::Done)
371 }
372
373 fn take(&mut self) -> Option<Adjacency> {
375 (self.pass == Pass::Done).then(|| Adjacency {
376 opposite: std::mem::take(&mut self.opposite),
377 vertex_corner: std::mem::take(&mut self.vertex_corner),
378 non_manifold_vertices: std::mem::take(&mut self.non_manifold_vertices),
379 })
380 }
381}
382
383#[derive(Debug)]
385struct FanScratch {
386 by_triangle: Vec<(u32, u32)>,
388 parent: Vec<u32>,
390}
391
392fn fan_count(corners: &[u32], opposite: &[u32], scratch: &mut FanScratch) -> usize {
396 let FanScratch {
397 by_triangle,
398 parent,
399 } = scratch;
400 by_triangle.clear();
401 by_triangle.extend(corners.iter().enumerate().map(|(a, &c)| (c / 3, a as u32)));
402 by_triangle.sort_unstable();
403 parent.clear();
404 parent.extend(0..corners.len() as u32);
405 fn root(parent: &mut [u32], mut a: u32) -> u32 {
406 while parent[a as usize] != a {
407 parent[a as usize] = parent[parent[a as usize] as usize];
408 a = parent[a as usize];
409 }
410 a
411 }
412 for (a, &c) in corners.iter().enumerate() {
413 let (t, i) = (c / 3, c % 3);
414 for e in [(i + 1) % 3, (i + 2) % 3] {
415 match opposite[(3 * t + e) as usize] {
416 NON_MANIFOLD => return 0,
417 NONE => {}
418 o => {
419 let s = o / 3;
421 let k = by_triangle.partition_point(|&(tri, _)| tri < s);
422 if let Some(&(tri, b)) = by_triangle.get(k)
423 && tri == s
424 {
425 let (ra, rb) = (root(parent, a as u32), root(parent, b));
426 parent[ra as usize] = rb;
427 }
428 }
429 }
430 }
431 }
432 (0..corners.len() as u32)
433 .filter(|&a| root(parent, a) == a)
434 .count()
435}
436
437impl Mesh {
438 pub fn ensure_adjacency(&mut self) -> Result<()> {
441 if self.adjacency.is_none() {
442 self.adjacency = Some(Adjacency::build(self)?);
443 }
444 Ok(())
445 }
446
447 pub fn adjacency(&self) -> Option<&Adjacency> {
450 self.adjacency.as_ref()
451 }
452
453 pub fn step_adjacency(&mut self, build: &mut AdjacencyBuild, budget: usize) -> Result<bool> {
462 if self.adjacency.is_some() {
463 return Ok(true);
464 }
465 if !build.is_for(self) {
466 *build = AdjacencyBuild::new(self);
467 }
468 match build.step(self, budget) {
469 Ok(done) => {
470 if done {
471 self.adjacency = build.take();
472 }
473 Ok(done)
474 }
475 Err(e) => {
476 *build = AdjacencyBuild::new(self);
477 Err(e)
478 }
479 }
480 }
481}
482
483#[cfg(test)]
484pub(crate) mod tests {
485 use super::*;
486
487 fn quad() -> Mesh {
489 Mesh::from_buffers(
490 &[0., 0., 0., 1., 0., 0., 1., 1., 0., 0., 1., 0.],
491 &[0, 1, 2, 0, 2, 3],
492 )
493 .unwrap()
494 }
495
496 #[test]
497 fn finds_the_shared_edge() {
498 let mut m = quad();
499 m.ensure_adjacency().unwrap();
500 let a = m.adjacency().unwrap();
501 assert_eq!(a.opposite(1), Some(5));
504 assert_eq!(a.opposite(5), Some(1));
505 assert_eq!(a.edge_kind(0), EdgeKind::Boundary);
506 assert_eq!(a.edge_kind(1), EdgeKind::Interior);
507 assert!(!a.is_non_manifold_vertex(0));
508 let mut around = a.triangles_around(&m, 0);
509 around.sort();
510 assert_eq!(around, vec![0, 1]);
511 }
512
513 #[test]
514 fn three_triangles_on_one_edge_are_non_manifold() {
515 let mut m = Mesh::from_buffers(
516 &[0., 0., 0., 1., 0., 0., 0., 1., 0., 0., -1., 0., 0., 0., 1.],
517 &[0, 1, 2, 0, 1, 3, 0, 1, 4],
518 )
519 .unwrap();
520 m.ensure_adjacency().unwrap();
521 let a = m.adjacency().unwrap();
522 assert_eq!(a.edge_kind(2), EdgeKind::NonManifold);
523 assert_eq!(a.opposite(2), None);
524 assert!(a.is_non_manifold_vertex(0));
525 assert!(!a.is_non_manifold_vertex(2));
526 }
527
528 #[test]
529 fn bowtie_vertex_is_non_manifold() {
530 let mut m = Mesh::from_buffers(
532 &[
533 0., 0., 0., 1., 0., 0., 1., 1., 0., -1., 0., 0., -1., -1., 0.,
534 ],
535 &[0, 1, 2, 0, 3, 4],
536 )
537 .unwrap();
538 m.ensure_adjacency().unwrap();
539 let a = m.adjacency().unwrap();
540 assert!(a.is_non_manifold_vertex(0));
541 assert_eq!(
542 a.triangles_around(&m, 0).len(),
543 1,
544 "only one fan is reachable"
545 );
546 }
547
548 #[test]
549 fn inconsistent_orientation_still_connects() {
550 let mut m = Mesh::from_buffers(
552 &[0., 0., 0., 1., 0., 0., 1., 1., 0., 0., 1., 0.],
553 &[0, 1, 2, 0, 3, 2],
554 )
555 .unwrap();
556 m.ensure_adjacency().unwrap();
557 assert_eq!(m.adjacency().unwrap().opposite(1), Some(4));
558 }
559
560 #[test]
561 fn high_valence_fan_is_built_correctly() {
562 const N: u32 = 20_000;
564 let positions = vec![0.0f32; 3 * (N as usize + 1)];
565 let indices: Vec<u32> = (1..N).flat_map(|i| [0, i, i + 1]).collect();
566 let mut m = Mesh::from_buffers(&positions, &indices).unwrap();
567 m.ensure_adjacency().unwrap();
568 let a = m.adjacency().unwrap();
569 let tris = N - 1;
570 for t in 0..tris {
571 assert_eq!(a.edge_kind(3 * t), EdgeKind::Boundary);
573 let next = (t + 1 < tris).then(|| 3 * (t + 1) + 2);
576 assert_eq!(a.opposite(3 * t + 1), next, "triangle {t}");
577 let prev = (t > 0).then(|| 3 * (t - 1) + 1);
580 assert_eq!(a.opposite(3 * t + 2), prev, "triangle {t}");
581 }
582 assert!(!a.is_non_manifold_vertex(0));
583 assert!(!a.is_non_manifold_vertex(N / 2));
584 let mut around = a.triangles_around(&m, 0);
585 assert_eq!(around.len(), tris as usize);
586 around.sort_unstable();
587 assert!(around.iter().copied().eq(0..tris));
588 }
589
590 #[test]
591 fn topology_changes_drop_the_cache_but_moves_keep_it() {
592 let mut m = quad();
593 m.ensure_adjacency().unwrap();
594 let mut tx = m.begin_edit();
595 tx.set_positions(&[0], &[[0., 0., 1.]]).unwrap();
596 tx.commit().unwrap();
597 assert!(m.adjacency().is_some());
598
599 let mut tx = m.begin_edit();
600 tx.remove_triangles(&[1]).unwrap();
601 let patch = tx.commit().unwrap();
602 assert!(m.adjacency().is_none());
603 m.ensure_adjacency().unwrap();
604 assert_eq!(m.adjacency().unwrap().edge_kind(1), EdgeKind::Boundary);
605
606 patch.revert(&mut m).unwrap();
607 assert!(m.adjacency().is_none());
608 }
609
610 pub(crate) fn mixed() -> Mesh {
614 let n = 12u32;
615 let mut positions = Vec::new();
616 for y in 0..=n {
617 for x in 0..=n {
618 positions.extend([x as f32, y as f32, ((x * 7 + y * 3) % 5) as f32 * 0.1]);
619 }
620 }
621 let mut indices = Vec::new();
622 for y in 0..n {
623 for x in 0..n {
624 let a = y * (n + 1) + x;
625 indices.extend([a, a + 1, a + n + 2, a, a + n + 2, a + n + 1]);
626 }
627 }
628 let extra = (n + 1) * (n + 1);
629 positions.extend([0., 0., 5., 3., 3., 5., -1., -1., 0., 50., 50., 50.]);
630 indices.extend([0, 1, extra, n, extra + 1, extra + 2, 5, 5, 6]);
633 let mut m = Mesh::from_buffers(&positions, &indices).unwrap();
634 let mut tx = m.begin_edit();
635 tx.remove_triangles(&[3, 40, 41, 100]).unwrap();
636 tx.commit().unwrap();
637 m
638 }
639
640 fn table(a: &Adjacency) -> (Vec<u32>, Vec<u32>, Vec<u64>) {
641 (
642 a.opposite.clone(),
643 a.vertex_corner.clone(),
644 a.non_manifold_vertices.clone(),
645 )
646 }
647
648 #[test]
649 fn stepped_build_matches_one_call() {
650 let mut whole = mixed();
651 whole.ensure_adjacency().unwrap();
652 let reference = table(whole.adjacency().unwrap());
653 for budget in [1, 2, 7, 64, 1000, usize::MAX] {
654 let mut m = mixed();
655 let mut build = AdjacencyBuild::new(&m);
656 let mut steps = 0;
657 while !m.step_adjacency(&mut build, budget).unwrap() {
658 steps += 1;
659 assert!(m.adjacency().is_none(), "cached before the last step");
660 }
661 assert!(build.is_done());
662 assert_eq!(table(m.adjacency().unwrap()), reference, "budget {budget}");
663 if budget == 1 {
664 assert!(
665 steps > 2 * m.triangle_slots(),
666 "{steps} steps of one element"
667 );
668 }
669 assert!(m.step_adjacency(&mut build, 1).unwrap());
671 }
672 }
673
674 #[test]
675 fn stepped_build_starts_over_after_an_edit() {
676 let mut m = mixed();
677 let mut build = AdjacencyBuild::new(&m);
678 for _ in 0..50 {
679 assert!(!m.step_adjacency(&mut build, 3).unwrap());
680 }
681 let mut tx = m.begin_edit();
682 tx.remove_triangles(&[10, 11]).unwrap();
683 tx.commit().unwrap();
684 assert!(!build.is_for(&m));
685 while !m.step_adjacency(&mut build, 5).unwrap() {}
686 let got = table(m.adjacency().unwrap());
687 m.adjacency = None;
688 m.ensure_adjacency().unwrap();
689 assert_eq!(got, table(m.adjacency().unwrap()));
690 }
691
692 #[test]
693 fn stepped_build_out_of_memory_starts_over() {
694 use crate::column::fail_allocations_after;
695 let mut m = mixed();
696 let mut build = AdjacencyBuild::new(&m);
697 assert!(!m.step_adjacency(&mut build, 4).unwrap());
698 fail_allocations_after(Some(0));
699 let mut failed = false;
700 for _ in 0..10_000 {
701 match m.step_adjacency(&mut build, 16) {
702 Err(e) => {
703 assert_eq!(e, StoreError::OutOfMemory);
704 failed = true;
705 break;
706 }
707 Ok(done) => assert!(!done),
708 }
709 }
710 fail_allocations_after(None);
711 assert!(failed, "some pass allocates");
712 assert!(m.adjacency().is_none());
713 while !m.step_adjacency(&mut build, 64).unwrap() {}
714 let got = table(m.adjacency().unwrap());
715 m.adjacency = None;
716 m.ensure_adjacency().unwrap();
717 assert_eq!(got, table(m.adjacency().unwrap()));
718 }
719
720 pub(crate) fn grow_and_undo(m: &mut Mesh) {
723 let mut tx = m.begin_edit();
724 let vs = tx
725 .add_vertices(&[[20.0, 20.0, 0.0], [21.0, 20.0, 0.0], [20.0, 21.0, 0.0]])
726 .unwrap();
727 tx.add_triangles(&[[vs[0], vs[1], vs[2]]]).unwrap();
728 let patch = tx.commit().unwrap();
729 patch.revert(m).unwrap();
730 }
731
732 #[test]
733 fn stepped_build_starts_over_after_edit_and_undo() {
734 let mut m = mixed();
737 let mut build = AdjacencyBuild::new(&m);
738 for _ in 0..(m.triangle_slots() + 3) {
739 m.step_adjacency(&mut build, 1).unwrap();
740 }
741 let revision = m.revision();
742 grow_and_undo(&mut m);
743 assert_eq!(m.revision(), revision);
744 assert!(!build.is_for(&m));
745 while !m.step_adjacency(&mut build, 1000).unwrap() {}
746 let got = table(m.adjacency().unwrap());
747 m.adjacency = None;
748 m.ensure_adjacency().unwrap();
749 assert_eq!(got, table(m.adjacency().unwrap()));
750 }
751}