Skip to main content

fast_mesh_store/
adjacency.rs

1//! Corner-table adjacency.
2//!
3//! Corner `c = 3 * t + i` is the `i`-th corner of triangle `t`. The edge opposite a
4//! corner joins the triangle's other two vertices. For every corner the table stores
5//! the corner across that edge in the neighboring triangle, or marks the edge as a
6//! boundary (no neighbor) or non-manifold (more than one neighbor). Orientation is not
7//! assumed to be consistent.
8
9use 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
16/// Entries an [`AdjacencyBuild`] or [`IncidenceBuild`](crate::IncidenceBuild)
17/// step zeroes per element of its budget.
18pub(crate) const ZERO_PER_ELEMENT: usize = 16;
19const NON_MANIFOLD: u32 = u32::MAX - 1;
20
21/// Kind of the edge opposite a corner.
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub enum EdgeKind {
24    /// Shared by exactly two triangles.
25    Interior,
26    /// Used by one triangle only.
27    Boundary,
28    /// Shared by three or more triangles.
29    NonManifold,
30}
31
32/// Corner table of a [`Mesh`]: for every triangle corner, the corner across the
33/// opposite edge, plus one corner per vertex and a non-manifold flag per vertex.
34///
35/// Built by [`Mesh::ensure_adjacency`] and cached until the next topology change.
36/// See the module docs for the corner numbering.
37#[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    /// The corner across the edge opposite `corner`, if that edge is interior.
52    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    /// Kind of the edge opposite `corner`. Corners of dead triangles read as boundary.
60    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    /// One corner at vertex `v`, or `None` if no triangle uses it.
69    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    /// True when the triangles around `v` do not form a single fan: two surfaces meet
77    /// only at `v` (a "bowtie"), or an edge at `v` is non-manifold.
78    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    /// Triangles in the fan of `v` reached from [`Adjacency::vertex_corner`] through
85    /// interior edges. For a non-manifold vertex this is one fan, not every triangle.
86    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/// Where an [`AdjacencyBuild`] stands: the pass it is in, with that pass's
116/// cursor (a triangle or vertex slot).
117#[derive(Debug, Clone, Copy, PartialEq, Eq)]
118enum Pass {
119    /// Counting the corners of each vertex, triangle by triangle.
120    Count(usize),
121    /// Turning the counts into row offsets, vertex by vertex.
122    Prefix(usize),
123    /// Zeroing the corner rows, up to this many entries.
124    Zero(usize),
125    /// Listing each vertex's corners, triangle by triangle.
126    Fill(usize),
127    /// Finding each corner's opposite corner, triangle by triangle.
128    Opposite(usize),
129    /// A corner and the fan count of each vertex, vertex by vertex.
130    Fans(usize),
131    /// Finished: the table is ready to be taken.
132    Done,
133}
134
135/// A resumable build of a mesh's [`Adjacency`]: the build of
136/// [`Mesh::ensure_adjacency`] cut into steps of a bounded number of elements
137/// (triangle or vertex slots), so that a caller can yield between them.
138///
139/// Each step runs the same loop bodies as the one-call build, over the next
140/// elements in the same order, so the finished table is the same, entry for
141/// entry. A build is tied to the mesh state it started from (its uid, epoch,
142/// revision and slot counts); [`Mesh::step_adjacency`] starts over when the mesh has
143/// changed since, and caches the table when the build is done.
144#[derive(Debug)]
145pub struct AdjacencyBuild {
146    uid: u64,
147    epoch: u32,
148    revision: u64,
149    /// The slot counts: an undo brings the revision back but keeps slots
150    /// it added, so the revision alone does not tell the state.
151    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    /// A build of `mesh`'s adjacency as the mesh is now, with nothing done.
165    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    /// Whether the build started from `mesh` as it is now.
187    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    /// Whether every pass is done.
195    pub fn is_done(&self) -> bool {
196        self.pass == Pass::Done
197    }
198
199    /// Runs the build over up to `budget` more elements (at least one, unless
200    /// it is done) of `mesh`, which must be the mesh state it started from
201    /// (see [`AdjacencyBuild::is_for`]). Returns whether it is done.
202    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                        // Incident corners per vertex, in compressed rows.
216                        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                    // Zeroing is cheap: many entries per element of the budget.
250                    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                    // Filled in corner order by the next pass.
280                    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                            // Triangles on edge u-w are incident to both ends: scan the
301                            // shorter list, so a high-valence vertex is not scanned once
302                            // per edge.
303                            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                    // Scratch buffers for `fan_count`, sized for the highest valence once.
339                    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    /// The finished table, once [`AdjacencyBuild::step`] returned `true`.
374    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/// Reusable buffers for [`fan_count`], allocated once per build.
384#[derive(Debug)]
385struct FanScratch {
386    /// `(triangle, local corner index)`, sorted, to find a triangle's corner fast.
387    by_triangle: Vec<(u32, u32)>,
388    /// Union-find parents, one per corner at the vertex.
389    parent: Vec<u32>,
390}
391
392/// Number of fans the corners at one vertex form when joined across interior edges.
393/// A non-manifold edge at the vertex makes the result 0 (never "one fan").
394/// Runs in O(k log k) for k corners; the scratch buffers hold at least k entries.
395fn 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                    // First corner of the neighboring triangle at this vertex.
420                    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    /// Builds the adjacency table if it is not cached. It is cached until the next
439    /// topology change (commit, undo or redo that adds, removes or rewrites triangles).
440    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    /// The cached adjacency table, if [`Mesh::ensure_adjacency`] built it and no
448    /// topology change has happened since.
449    pub fn adjacency(&self) -> Option<&Adjacency> {
450        self.adjacency.as_ref()
451    }
452
453    /// [`Mesh::ensure_adjacency`] in steps: runs `build` over up to `budget`
454    /// more elements and, once it is done, caches its table. Returns whether
455    /// the adjacency is cached, at once when it already is. When the mesh
456    /// changed since `build` started, `build` starts over first. The table is
457    /// the same as from [`Mesh::ensure_adjacency`], entry for entry.
458    ///
459    /// # Errors
460    /// As for [`Mesh::ensure_adjacency`]; `build` then starts over.
461    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    /// Two triangles sharing edge 0-2: 0-1-2 and 0-2-3.
488    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        // Corner 1 (vertex 1 of triangle 0) faces edge 2-0, shared with triangle 1,
502        // whose corner facing that edge is vertex 3 (corner 5).
503        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        // Two triangles touching only at vertex 0.
531        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        // Same quad, second triangle wound the other way.
551        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        // Open fan around vertex 0: triangles (0, i, i + 1) for rim vertices 1..=N.
563        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            // Corner 0 (the center) faces the rim edge: boundary.
572            assert_eq!(a.edge_kind(3 * t), EdgeKind::Boundary);
573            // Corner 1 (vertex t + 1) faces edge (t + 2, 0), shared with triangle t + 1,
574            // whose corner there is vertex t + 3 (local 2).
575            let next = (t + 1 < tris).then(|| 3 * (t + 1) + 2);
576            assert_eq!(a.opposite(3 * t + 1), next, "triangle {t}");
577            // Corner 2 (vertex t + 2) faces edge (0, t + 1), shared with triangle t - 1,
578            // whose corner there is vertex t (local 1).
579            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    /// A mixed mesh for the stepped builds: a grid with some triangles
611    /// removed, a non-manifold edge, a bowtie vertex, a repeated-vertex
612    /// triangle and an unused vertex.
613    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        // A third triangle on edge 0-1, a bowtie at vertex `n`, and a triangle
631        // repeating a vertex.
632        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            // Cached: nothing more to do.
670            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    /// Edits `m` by adding three vertices and a triangle, then undoes it:
721    /// the revision is back to what it was, but the slots grew.
722    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        // Undo brings the revision back while the slots stay grown: the
735        // build must not take the mesh for the one it started from.
736        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}