1use std::collections::HashMap;
14
15use crate::adjacency::ZERO_PER_ELEMENT;
16use crate::column::{try_reserve, try_reserve_exact, try_reserve_map, try_vec};
17use crate::{Mesh, Result, StoreError, TriangleId, VertexId};
18
19const MIN_OVERLAY: usize = 4096;
21
22#[derive(Debug)]
24pub struct Incidence {
25 offsets: Vec<u32>,
27 triangles: Vec<TriangleId>,
28 overlay: HashMap<VertexId, Vec<TriangleId>>,
31 overlay_len: usize,
33}
34
35fn distinct(tri: [VertexId; 3]) -> impl Iterator<Item = VertexId> {
38 let [a, b, c] = tri;
39 [
40 Some(a),
41 (b != a).then_some(b),
42 (c != a && c != b).then_some(c),
43 ]
44 .into_iter()
45 .flatten()
46}
47
48impl Incidence {
49 pub(crate) fn build(mesh: &Mesh) -> Result<Self> {
50 let mut build = IncidenceBuild::new(mesh);
51 build.step(mesh, usize::MAX)?;
52 build.take().ok_or(StoreError::OutOfMemory)
53 }
54
55 pub fn triangles(&self, v: VertexId) -> &[TriangleId] {
58 match self.overlay.get(&v) {
59 Some(row) => row,
60 None => compressed_row(&self.offsets, &self.triangles, v),
61 }
62 }
63
64 fn insert(&mut self, t: TriangleId, tri: [VertexId; 3]) -> Result<()> {
66 for v in distinct(tri) {
67 if self.triangles(v).binary_search(&t).is_ok() {
68 continue;
69 }
70 let row = self.row_mut(v)?;
71 if let Err(i) = row.binary_search(&t) {
72 try_reserve(row, 1)?;
73 row.insert(i, t);
74 self.overlay_len += 1;
75 }
76 }
77 Ok(())
78 }
79
80 fn row_mut(&mut self, v: VertexId) -> Result<&mut Vec<TriangleId>> {
82 if !self.overlay.contains_key(&v) {
83 let base = compressed_row(&self.offsets, &self.triangles, v);
84 let mut row = Vec::new();
85 try_reserve(&mut row, base.len() + 1)?;
86 row.extend_from_slice(base);
87 try_reserve_map(&mut self.overlay, 1)?;
88 self.overlay_len += row.len();
89 self.overlay.insert(v, row);
90 }
91 Ok(self.overlay.get_mut(&v).expect("inserted above"))
92 }
93
94 pub(crate) fn remove_alive(&mut self, mesh: &Mesh, ids: &[&[TriangleId]]) -> Result<()> {
96 for &t in ids.iter().flat_map(|ids| ids.iter()) {
97 if let Some(tri) = mesh.triangle(t) {
98 self.remove(t, tri)?;
99 }
100 }
101 Ok(())
102 }
103
104 pub(crate) fn insert_alive(&mut self, mesh: &Mesh, ids: &[&[TriangleId]]) -> Result<()> {
106 for &t in ids.iter().flat_map(|ids| ids.iter()) {
107 if let Some(tri) = mesh.triangle(t) {
108 self.insert(t, tri)?;
109 }
110 }
111 Ok(())
112 }
113
114 pub(crate) fn remove(&mut self, t: TriangleId, tri: [VertexId; 3]) -> Result<()> {
117 for v in distinct(tri) {
118 if self.triangles(v).binary_search(&t).is_err() {
119 continue;
120 }
121 let row = self.row_mut(v)?;
122 if let Ok(i) = row.binary_search(&t) {
123 row.remove(i);
124 self.overlay_len -= 1;
125 }
126 }
127 Ok(())
128 }
129
130 fn overgrown(&self) -> bool {
133 self.overlay_len + self.overlay.len() > MIN_OVERLAY.max(self.triangles.len() / 8)
134 }
135}
136
137#[derive(Debug, Clone, Copy, PartialEq, Eq)]
140enum Pass {
141 Count(usize),
143 Prefix(usize),
145 Zero(usize),
147 Fill(usize),
149 Done,
151}
152
153#[derive(Debug)]
163pub struct IncidenceBuild {
164 uid: u64,
165 epoch: u32,
166 revision: u64,
167 slots: (usize, usize),
170 pass: Pass,
171 offsets: Vec<u32>,
172 triangles: Vec<TriangleId>,
173 cursor: Vec<u32>,
174}
175
176impl IncidenceBuild {
177 pub fn new(mesh: &Mesh) -> IncidenceBuild {
180 IncidenceBuild {
181 uid: mesh.uid(),
182 epoch: mesh.epoch(),
183 revision: mesh.revision(),
184 slots: (mesh.vertex_slots(), mesh.triangle_slots()),
185 pass: Pass::Count(0),
186 offsets: Vec::new(),
187 triangles: Vec::new(),
188 cursor: Vec::new(),
189 }
190 }
191
192 pub fn is_for(&self, mesh: &Mesh) -> bool {
194 self.uid == mesh.uid()
195 && self.epoch == mesh.epoch()
196 && self.revision == mesh.revision()
197 && self.slots == (mesh.vertex_slots(), mesh.triangle_slots())
198 }
199
200 pub fn is_done(&self) -> bool {
202 self.pass == Pass::Done
203 }
204
205 fn step(&mut self, mesh: &Mesh, budget: usize) -> Result<bool> {
209 debug_assert!(
210 self.is_for(mesh),
211 "an incidence build stepped on another mesh"
212 );
213 let tris = mesh.triangles();
214 let slots = mesh.vertex_slots();
215 let mut left = budget.max(1);
216 while left > 0 {
217 match self.pass {
218 Pass::Count(from) => {
219 if from == 0 {
220 self.offsets = try_vec(slots + 1, 0u32)?;
221 }
222 let to = tris.len().min(from.saturating_add(left));
223 for (t, &tri) in tris.iter().enumerate().take(to).skip(from) {
224 if mesh.is_triangle_alive(t as TriangleId) {
225 for v in distinct(tri) {
226 self.offsets[v as usize + 1] += 1;
227 }
228 }
229 }
230 left -= (to - from).min(left);
231 self.pass = if to == tris.len() {
232 Pass::Prefix(0)
233 } else {
234 Pass::Count(to)
235 };
236 }
237 Pass::Prefix(from) => {
238 let to = slots.min(from.saturating_add(left));
239 for v in from..to {
240 self.offsets[v + 1] += self.offsets[v];
241 }
242 left -= (to - from).min(left);
243 if to < slots {
244 self.pass = Pass::Prefix(to);
245 continue;
246 }
247 try_reserve_exact(&mut self.triangles, self.offsets[slots] as usize)?;
248 self.pass = Pass::Zero(0);
249 }
250 Pass::Zero(from) => {
251 let total = self.offsets[slots] as usize;
252 let to = total.min(from.saturating_add(left.saturating_mul(ZERO_PER_ELEMENT)));
253 self.triangles.resize(to, 0 as TriangleId);
254 left -= (to - from).div_ceil(ZERO_PER_ELEMENT).min(left);
255 if to < total {
256 self.pass = Pass::Zero(to);
257 continue;
258 }
259 self.cursor = try_vec(slots, 0u32)?;
260 self.cursor.copy_from_slice(&self.offsets[..slots]);
261 self.pass = Pass::Fill(0);
262 }
263 Pass::Fill(from) => {
264 let to = tris.len().min(from.saturating_add(left));
265 for (t, &tri) in tris.iter().enumerate().take(to).skip(from) {
268 if mesh.is_triangle_alive(t as TriangleId) {
269 for v in distinct(tri) {
270 let at = &mut self.cursor[v as usize];
271 self.triangles[*at as usize] = t as TriangleId;
272 *at += 1;
273 }
274 }
275 }
276 left -= (to - from).min(left);
277 if to < tris.len() {
278 self.pass = Pass::Fill(to);
279 continue;
280 }
281 self.cursor = Vec::new();
282 self.pass = Pass::Done;
283 }
284 Pass::Done => return Ok(true),
285 }
286 }
287 Ok(self.pass == Pass::Done)
288 }
289
290 fn take(&mut self) -> Option<Incidence> {
292 (self.pass == Pass::Done).then(|| Incidence {
293 offsets: std::mem::take(&mut self.offsets),
294 triangles: std::mem::take(&mut self.triangles),
295 overlay: HashMap::new(),
296 overlay_len: 0,
297 })
298 }
299}
300
301fn compressed_row<'a>(
302 offsets: &[u32],
303 triangles: &'a [TriangleId],
304 v: VertexId,
305) -> &'a [TriangleId] {
306 let v = v as usize;
307 if v + 1 >= offsets.len() {
308 return &[];
309 }
310 &triangles[offsets[v] as usize..offsets[v + 1] as usize]
311}
312
313impl Mesh {
314 pub fn ensure_incidence(&mut self) -> Result<()> {
319 debug_assert!(!self.incidence_pending, "called inside a transaction");
320 if self.incidence.is_none() {
321 self.incidence = Some(Incidence::build(self)?);
322 }
323 Ok(())
324 }
325
326 pub fn incidence(&self) -> Option<&Incidence> {
329 self.incidence.as_ref().filter(|_| !self.incidence_pending)
330 }
331
332 pub fn step_incidence(&mut self, build: &mut IncidenceBuild, budget: usize) -> Result<bool> {
341 debug_assert!(!self.incidence_pending, "called inside a transaction");
342 if self.incidence.is_some() {
343 return Ok(true);
344 }
345 if !build.is_for(self) {
346 *build = IncidenceBuild::new(self);
347 }
348 match build.step(self, budget) {
349 Ok(done) => {
350 if done {
351 self.incidence = build.take();
352 }
353 Ok(done)
354 }
355 Err(e) => {
356 *build = IncidenceBuild::new(self);
357 Err(e)
358 }
359 }
360 }
361
362 pub(crate) fn with_incidence(&mut self, f: impl FnOnce(&mut Incidence, &Mesh) -> Result<()>) {
365 if let Some(mut inc) = self.incidence.take()
366 && f(&mut inc, self).is_ok()
367 && !inc.overgrown()
368 {
369 self.incidence = Some(inc);
370 }
371 }
372}
373
374#[cfg(test)]
375mod tests {
376 use crate::Mesh;
377 use crate::column::fail_allocations_after;
378
379 fn fan_and_bowtie() -> Mesh {
382 Mesh::from_buffers(
383 &[
384 0., 0., 0., 1., 0., 0., 1., 1., 0., 0., 1., 0., -1., 0., 0., -1., -1., 0.,
385 ],
386 &[0, 1, 2, 0, 2, 3, 3, 2, 1, 0, 4, 5],
387 )
388 .unwrap()
389 }
390
391 fn expected(m: &Mesh) -> Vec<Vec<u32>> {
393 let mut rows = vec![Vec::new(); m.vertex_slots()];
394 for t in 0..m.triangle_slots() as u32 {
395 if let Some(tri) = m.triangle(t) {
396 for v in tri {
397 if rows[v as usize].last() != Some(&t) {
398 rows[v as usize].push(t);
399 }
400 }
401 }
402 }
403 rows
404 }
405
406 fn assert_matches(m: &Mesh) {
407 let inc = m.incidence().expect("index kept");
408 for (v, row) in expected(m).iter().enumerate() {
409 assert_eq!(inc.triangles(v as u32), row.as_slice(), "vertex {v}");
410 }
411 }
412
413 #[test]
414 fn lists_every_triangle_of_every_fan() {
415 let mut m = fan_and_bowtie();
416 m.ensure_incidence().unwrap();
417 let inc = m.incidence().unwrap();
418 assert_eq!(inc.triangles(0), &[0, 1, 3]);
419 assert_eq!(inc.triangles(2), &[0, 1, 2]);
420 assert_eq!(inc.triangles(5), &[3]);
421 assert!(inc.triangles(99).is_empty());
422 }
423
424 #[test]
425 fn repeated_vertices_are_listed_once() {
426 let mut m = Mesh::from_buffers(&[0., 0., 0., 1., 0., 0.], &[0, 0, 1]).unwrap();
427 m.ensure_incidence().unwrap();
428 assert_eq!(m.incidence().unwrap().triangles(0), &[0]);
429 assert_eq!(m.incidence().unwrap().triangles(1), &[0]);
430 }
431
432 #[test]
433 fn topology_edits_undo_and_redo_keep_the_index_current() {
434 let mut m = fan_and_bowtie();
435 m.ensure_incidence().unwrap();
436 let mut tx = m.begin_edit();
437 tx.set_positions(&[0], &[[0., 0., 1.]]).unwrap();
438 tx.commit().unwrap();
439 assert_matches(&m);
440
441 let mut tx = m.begin_edit();
442 tx.remove_triangles(&[1]).unwrap();
443 assert!(tx.mesh().incidence().is_none(), "hidden while stale");
444 let v = tx.add_vertices(&[[2., 2., 0.]]).unwrap()[0];
445 tx.add_triangles(&[[v, 2, 3], [0, 0, v]]).unwrap();
446 tx.set_triangles(&[2], &[[3, 1, 5]]).unwrap();
447 let patch = tx.commit().unwrap();
448 assert_matches(&m);
449 assert_eq!(m.incidence().unwrap().triangles(0), &[0, 3, 5]);
450
451 patch.revert(&mut m).unwrap();
452 assert_matches(&m);
453 patch.apply(&mut m).unwrap();
454 assert_matches(&m);
455 }
456
457 #[test]
458 fn rollback_restores_the_index() {
459 let mut m = fan_and_bowtie();
460 m.ensure_incidence().unwrap();
461 let mut tx = m.begin_edit();
462 tx.remove_triangles(&[0, 3]).unwrap();
463 tx.add_triangles(&[[1, 2, 3]]).unwrap();
464 tx.rollback();
465 assert_matches(&m);
466 }
467
468 fn strip(n: u32) -> Mesh {
470 let positions: Vec<f32> = (0..n + 2)
471 .flat_map(|i| [(i / 2) as f32, (i % 2) as f32, 0.])
472 .collect();
473 let indices: Vec<u32> = (0..n).flat_map(|i| [i, i + 1, i + 2]).collect();
474 Mesh::from_buffers(&positions, &indices).unwrap()
475 }
476
477 #[test]
478 fn small_edits_keep_the_index_and_large_ones_drop_it() {
479 let mut m = strip(10_000);
480 m.ensure_incidence().unwrap();
481 let mut tx = m.begin_edit();
482 tx.set_triangles(&[5_000], &[[5_002, 5_001, 5_000]])
483 .unwrap();
484 tx.commit().unwrap();
485 assert_matches(&m);
486
487 let ids: Vec<u32> = (0..10_000).collect();
489 let flipped: Vec<[u32; 3]> = ids.iter().map(|&i| [i + 2, i + 1, i]).collect();
490 let mut tx = m.begin_edit();
491 tx.set_triangles(&ids, &flipped).unwrap();
492 let patch = tx.commit().unwrap();
493 assert!(m.incidence().is_none());
494 m.ensure_incidence().unwrap();
495 assert_matches(&m);
496 patch.revert(&mut m).unwrap();
497 assert!(m.incidence().is_none());
498 }
499
500 #[test]
501 fn compaction_drops_the_index() {
502 let mut m = fan_and_bowtie();
503 m.ensure_incidence().unwrap();
504 let mut tx = m.begin_edit();
505 tx.remove_triangles(&[1]).unwrap();
506 tx.commit().unwrap();
507 m.compact().unwrap();
508 assert!(m.incidence().is_none());
509 m.ensure_incidence().unwrap();
510 assert_matches(&m);
511 }
512
513 #[test]
514 fn running_out_of_memory_drops_the_index_but_not_the_edit() {
515 let mut dropped = false;
516 for n in 0..64 {
517 let mut m = fan_and_bowtie();
518 m.ensure_incidence().unwrap();
519 let mut tx = m.begin_edit();
520 tx.remove_triangles(&[1]).unwrap();
521 fail_allocations_after(Some(n));
522 let committed = tx.commit().is_ok();
523 fail_allocations_after(None);
524 match m.incidence() {
525 Some(_) => assert_matches(&m),
526 None => {
527 assert!(committed, "a rollback keeps the index");
528 dropped = true;
529 }
530 }
531 }
532 assert!(dropped, "some budget must drop the index");
533 }
534
535 #[test]
536 fn stepped_build_matches_one_call() {
537 use crate::IncidenceBuild;
538 let mixed = crate::adjacency::tests::mixed;
539 let mut whole = mixed();
540 whole.ensure_incidence().unwrap();
541 let reference = whole.incidence().unwrap();
542 for budget in [1, 3, 50, usize::MAX] {
543 let mut m = mixed();
544 let mut build = IncidenceBuild::new(&m);
545 while !m.step_incidence(&mut build, budget).unwrap() {
546 assert!(m.incidence().is_none(), "cached before the last step");
547 }
548 assert!(build.is_done());
549 let got = m.incidence().unwrap();
550 assert_eq!(got.offsets, reference.offsets, "budget {budget}");
551 assert_eq!(got.triangles, reference.triangles, "budget {budget}");
552 assert_matches(&m);
553 }
554 let mut m = mixed();
556 let mut build = IncidenceBuild::new(&m);
557 assert!(!m.step_incidence(&mut build, 5).unwrap());
558 let mut tx = m.begin_edit();
559 tx.remove_triangles(&[7]).unwrap();
560 tx.commit().unwrap();
561 assert!(!build.is_for(&m));
562 while !m.step_incidence(&mut build, 5).unwrap() {}
563 assert_matches(&m);
564 }
565
566 #[test]
567 fn stepped_build_starts_over_after_edit_and_undo() {
568 use crate::IncidenceBuild;
569 let mut m = crate::adjacency::tests::mixed();
570 let mut build = IncidenceBuild::new(&m);
571 for _ in 0..(m.triangle_slots() + 3) {
572 m.step_incidence(&mut build, 1).unwrap();
573 }
574 crate::adjacency::tests::grow_and_undo(&mut m);
575 assert!(!build.is_for(&m));
576 while !m.step_incidence(&mut build, 1000).unwrap() {}
577 assert_matches(&m);
578 }
579}