1use fast_mesh_store::{Mesh, TriangleId, VertexId};
8
9use crate::{OpsError, Result, filled, push, reserve};
10
11pub use fast_mesh_store::EdgeKind;
15
16const NONE: u32 = u32::MAX;
17
18fn key(a: VertexId, b: VertexId) -> (VertexId, VertexId) {
20 if a < b { (a, b) } else { (b, a) }
21}
22
23pub(crate) fn proper_triangle(mesh: &Mesh, t: TriangleId) -> Option<[VertexId; 3]> {
25 let [a, b, c] = mesh.triangle(t)?;
26 (a != b && b != c && a != c).then_some([a, b, c])
27}
28
29pub(crate) fn point(mesh: &Mesh, v: VertexId) -> [f64; 3] {
31 mesh.positions()[v as usize].map(f64::from)
32}
33
34pub(crate) fn sub(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
35 [a[0] - b[0], a[1] - b[1], a[2] - b[2]]
36}
37
38pub(crate) fn cross(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
39 [
40 a[1] * b[2] - a[2] * b[1],
41 a[2] * b[0] - a[0] * b[2],
42 a[0] * b[1] - a[1] * b[0],
43 ]
44}
45
46pub(crate) fn dot(a: [f64; 3], b: [f64; 3]) -> f64 {
47 a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
48}
49
50fn norm(a: [f64; 3]) -> f64 {
51 dot(a, a).sqrt()
52}
53
54pub(crate) fn triangle_area(mesh: &Mesh, [a, b, c]: [VertexId; 3]) -> f64 {
56 let p = point(mesh, a);
57 0.5 * norm(cross(sub(point(mesh, b), p), sub(point(mesh, c), p)))
58}
59
60pub(crate) struct EdgeMap {
63 keys: Vec<(VertexId, VertexId)>,
64 starts: Vec<u32>,
66 tris: Vec<TriangleId>,
67}
68
69impl EdgeMap {
70 pub(crate) fn build(mesh: &Mesh) -> Result<EdgeMap> {
73 let mut pairs: Vec<((VertexId, VertexId), TriangleId)> = Vec::new();
74 reserve(&mut pairs, 3 * mesh.triangle_count())?;
75 for t in 0..mesh.triangle_slots() as TriangleId {
76 if let Some([a, b, c]) = proper_triangle(mesh, t) {
77 for (u, v) in [(a, b), (b, c), (c, a)] {
78 push(&mut pairs, (key(u, v), t))?;
79 }
80 }
81 }
82 if pairs.len() >= NONE as usize {
83 return Err(OpsError::InvalidInput("too many triangles for an edge map"));
84 }
85 pairs.sort_unstable();
86 let distinct = 1 + pairs.windows(2).filter(|w| w[0].0 != w[1].0).count();
87 let distinct = if pairs.is_empty() { 0 } else { distinct };
88 let mut map = EdgeMap {
89 keys: Vec::new(),
90 starts: Vec::new(),
91 tris: Vec::new(),
92 };
93 reserve(&mut map.keys, distinct)?;
94 reserve(&mut map.starts, distinct + 1)?;
95 reserve(&mut map.tris, pairs.len())?;
96 for (i, &(k, t)) in pairs.iter().enumerate() {
97 if map.keys.last() != Some(&k) {
98 map.keys.push(k);
99 map.starts.push(i as u32);
100 }
101 map.tris.push(t);
102 }
103 map.starts.push(pairs.len() as u32);
104 Ok(map)
105 }
106
107 fn index(&self, a: VertexId, b: VertexId) -> Option<usize> {
109 self.keys.binary_search(&key(a, b)).ok()
110 }
111
112 fn at(&self, i: usize) -> &[TriangleId] {
113 &self.tris[self.starts[i] as usize..self.starts[i + 1] as usize]
114 }
115
116 pub(crate) fn len(&self) -> usize {
118 self.keys.len()
119 }
120
121 pub(crate) fn kind(&self, a: VertexId, b: VertexId) -> Option<EdgeKind> {
123 self.index(a, b).map(|i| kind_of(self.at(i).len()))
124 }
125
126 pub(crate) fn triangles(&self, a: VertexId, b: VertexId) -> &[TriangleId] {
129 self.index(a, b).map_or(&[], |i| self.at(i))
130 }
131
132 pub(crate) fn edges(&self) -> impl Iterator<Item = ((VertexId, VertexId), &[TriangleId])> {
134 (0..self.keys.len()).map(|i| (self.keys[i], self.at(i)))
135 }
136}
137
138fn kind_of(triangles: usize) -> EdgeKind {
139 match triangles {
140 1 => EdgeKind::Boundary,
141 2 => EdgeKind::Interior,
142 _ => EdgeKind::NonManifold,
143 }
144}
145
146pub(crate) struct VertexFans {
148 starts: Vec<u32>,
150 tris: Vec<TriangleId>,
151}
152
153impl VertexFans {
154 pub(crate) fn build(mesh: &Mesh) -> Result<VertexFans> {
155 let slots = mesh.vertex_slots();
156 let mut starts = filled(slots + 1, 0u32)?;
157 let mut corners = 0usize;
158 for t in 0..mesh.triangle_slots() as TriangleId {
159 if let Some(tri) = proper_triangle(mesh, t) {
160 for v in tri {
161 starts[v as usize + 1] += 1;
162 }
163 corners += 3;
164 }
165 }
166 if corners >= NONE as usize {
167 return Err(OpsError::InvalidInput("too many triangles for vertex fans"));
168 }
169 for v in 0..slots {
173 starts[v + 1] += starts[v];
174 }
175 let mut tris = filled(corners, 0)?;
176 for t in (0..mesh.triangle_slots() as TriangleId).rev() {
177 if let Some(tri) = proper_triangle(mesh, t) {
178 for v in tri {
179 let end = &mut starts[v as usize + 1];
180 *end -= 1;
181 tris[*end as usize] = t;
182 }
183 }
184 }
185 starts.copy_within(1.., 0);
187 starts[slots] = corners as u32;
188 Ok(VertexFans { starts, tris })
189 }
190
191 pub(crate) fn fan(&self, v: VertexId) -> &[TriangleId] {
193 &self.tris[self.starts[v as usize] as usize..self.starts[v as usize + 1] as usize]
194 }
195}
196
197pub(crate) struct UnionFind {
199 parent: Vec<u32>,
200}
201
202impl UnionFind {
203 pub(crate) fn new() -> Self {
204 UnionFind { parent: Vec::new() }
205 }
206
207 pub(crate) fn reset(&mut self, n: usize) -> Result<()> {
209 self.parent.clear();
210 reserve(&mut self.parent, n)?;
211 self.parent.extend(0..n as u32);
212 Ok(())
213 }
214
215 pub(crate) fn find(&mut self, mut x: u32) -> u32 {
216 while self.parent[x as usize] != x {
217 let up = self.parent[self.parent[x as usize] as usize];
218 self.parent[x as usize] = up;
219 x = up;
220 }
221 x
222 }
223
224 pub(crate) fn union(&mut self, a: u32, b: u32) -> bool {
226 let (ra, rb) = (self.find(a), self.find(b));
227 if ra == rb {
228 return false;
229 }
230 let (lo, hi) = if ra < rb { (ra, rb) } else { (rb, ra) };
231 self.parent[hi as usize] = lo;
232 true
233 }
234}
235
236#[derive(Debug, Clone, PartialEq)]
238pub struct Component {
239 pub triangles: Vec<TriangleId>,
241 pub area: f64,
243 pub closed: bool,
245}
246
247pub(crate) fn components(mesh: &Mesh, edges: &EdgeMap) -> Result<Vec<Component>> {
250 let slots = mesh.triangle_slots();
251 let mut uf = UnionFind::new();
252 uf.reset(slots)?;
253 for (_, tris) in edges.edges() {
254 for &t in &tris[1..] {
255 uf.union(tris[0], t);
256 }
257 }
258 let mut index = filled(slots, NONE)?;
261 let mut out: Vec<Component> = Vec::new();
262 for t in 0..slots as TriangleId {
263 let Some(tri) = proper_triangle(mesh, t) else {
264 continue;
265 };
266 let root = uf.find(t) as usize;
267 if index[root] == NONE {
268 index[root] = out.len() as u32;
269 push(
270 &mut out,
271 Component {
272 triangles: Vec::new(),
273 area: 0.0,
274 closed: true,
275 },
276 )?;
277 }
278 let c = &mut out[index[root] as usize];
279 push(&mut c.triangles, t)?;
280 c.area += triangle_area(mesh, tri);
281 }
282 for (_, tris) in edges.edges() {
283 if tris.len() != 2 {
284 let root = uf.find(tris[0]) as usize;
285 out[index[root] as usize].closed = false;
286 }
287 }
288 Ok(out)
289}
290
291pub(crate) fn pinch_vertices(mesh: &Mesh, edges: &EdgeMap) -> Result<Vec<VertexId>> {
296 let fans = VertexFans::build(mesh)?;
297 let mut uf = UnionFind::new();
298 let mut out = Vec::new();
299 for v in 0..mesh.vertex_slots() as VertexId {
300 let fan = fans.fan(v);
301 if fan.len() < 2 {
302 continue;
303 }
304 uf.reset(fan.len())?;
305 let mut pieces = fan.len();
306 for (i, &t) in fan.iter().enumerate() {
307 for x in mesh.triangles()[t as usize] {
308 if x == v {
309 continue;
310 }
311 for &u in edges.triangles(v, x) {
312 if u > t {
313 let j = fan.binary_search(&u).expect("edge triangle in fan");
314 if uf.union(i as u32, j as u32) {
315 pieces -= 1;
316 }
317 }
318 }
319 }
320 }
321 if pieces > 1 {
322 push(&mut out, v)?;
323 }
324 }
325 Ok(out)
326}
327
328#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
331pub struct LoopId {
332 pub min_vertex: VertexId,
334 pub len: u32,
336 pub hash: u64,
340}
341
342#[derive(Debug, Clone, PartialEq)]
344pub struct BoundaryLoop {
345 pub id: LoopId,
349 pub vertices: Vec<VertexId>,
355 pub perimeter: f64,
358 pub area: f64,
360 pub closed: bool,
365 pub clean: bool,
377}
378
379pub(crate) fn fnv1a(ids: &[VertexId]) -> u64 {
381 let mut h: u64 = 0xcbf2_9ce4_8422_2325;
382 for id in ids {
383 for byte in id.to_le_bytes() {
384 h ^= u64::from(byte);
385 h = h.wrapping_mul(0x0000_0100_0000_01b3);
386 }
387 }
388 h
389}
390
391pub fn boundary_loops(mesh: &Mesh) -> Result<Vec<BoundaryLoop>> {
411 let edges = EdgeMap::build(mesh)?;
412 loops_with(mesh, &edges)
413}
414
415#[derive(Clone, Copy)]
418struct Step {
419 from: VertexId,
420 to: VertexId,
421 tri: TriangleId,
422 edge: usize,
423}
424
425pub(crate) fn loops_with(mesh: &Mesh, edges: &EdgeMap) -> Result<Vec<BoundaryLoop>> {
427 let mut non_manifold = filled(mesh.vertex_slots(), false)?;
428 let mut half: Vec<Step> = Vec::new();
431 for (i, ((a, b), tris)) in edges.edges().enumerate() {
432 match tris.len() {
433 1 => {
434 let t = tris[0];
435 let [x, y, z] = mesh.triangles()[t as usize];
436 let forward = (x, y) == (a, b) || (y, z) == (a, b) || (z, x) == (a, b);
437 let (from, to) = if forward { (a, b) } else { (b, a) };
438 push(
439 &mut half,
440 Step {
441 from,
442 to,
443 tri: t,
444 edge: i,
445 },
446 )?;
447 }
448 2 => {}
449 _ => {
450 non_manifold[a as usize] = true;
451 non_manifold[b as usize] = true;
452 }
453 }
454 }
455 half.sort_unstable_by_key(|s| (s.from, s.to));
456 let mut excess = filled(mesh.vertex_slots(), 0i64)?;
459 for s in &half {
460 excess[s.from as usize] += 1;
461 excess[s.to as usize] -= 1;
462 }
463 let mut visited = filled(edges.len(), false)?;
464 let mut loops: Vec<BoundaryLoop> = Vec::new();
465 let mut verts: Vec<VertexId> = Vec::new();
466 let order = (0..2).flat_map(|pass| (0..half.len()).map(move |h| (pass, h)));
467 for (pass, h) in order {
468 let start = half[h];
469 if visited[start.edge] || (pass == 0 && excess[start.from as usize] <= 0) {
470 continue;
471 }
472 let mut closed = true;
473 verts.clear();
474 let mut clean = true;
475 let mut cur = start;
476 loop {
477 visited[cur.edge] = true;
478 push(&mut verts, cur.from)?;
479 if !runs_forward(mesh, cur) {
482 clean = false;
483 }
484 let b = cur.to;
485 let lo = half.partition_point(|s| s.from < b);
486 let hi = lo + half[lo..].iter().take_while(|s| s.from == b).count();
487 let leaving = &half[lo..hi];
488 let walked = fan_walk(mesh, edges, cur.tri, cur.from, b).and_then(|(c, t)| {
492 let e = edges.index(b, c).expect("walked edge in map");
493 let s = across_gap(mesh, leaving, e, cur).unwrap_or(Step {
494 from: b,
495 to: c,
496 tri: t,
497 edge: e,
498 });
499 (s.edge == start.edge || !visited[s.edge]).then_some(s)
500 });
501 let next = match walked {
502 Some(s) => Some(s),
503 None => {
504 clean = false;
507 leaving
508 .iter()
509 .find(|s| s.edge == start.edge || !visited[s.edge])
510 .copied()
511 }
512 };
513 match next {
514 Some(s) if s.edge == start.edge => break,
515 Some(s) => cur = s,
516 None => {
517 push(&mut verts, b)?;
520 closed = false;
521 clean = false;
522 break;
523 }
524 }
525 }
526 if verts
529 .iter()
530 .any(|&v| non_manifold[v as usize] || (closed && excess[v as usize] != 0))
531 {
532 clean = false;
533 }
534 push(&mut loops, make_loop(mesh, &verts, closed, clean)?)?;
535 }
536 loops.sort_unstable_by(|a, b| {
537 (a.id.min_vertex, &a.vertices).cmp(&(b.id.min_vertex, &b.vertices))
538 });
539 Ok(loops)
540}
541
542fn runs_forward(mesh: &Mesh, s: Step) -> bool {
544 let [x, y, z] = mesh.triangles()[s.tri as usize];
545 [(x, y), (y, z), (z, x)].contains(&(s.from, s.to))
546}
547
548fn across_gap(mesh: &Mesh, leaving: &[Step], walked: usize, cur: Step) -> Option<Step> {
556 let other = |s: &&Step| s.edge != walked && s.edge != cur.edge;
559 let mut others = leaving.iter().filter(other);
560 let first = *others.next()?;
561 if others.clone().next().is_none() {
562 return Some(first);
563 }
564 let normal = |t: TriangleId| {
565 let [x, y, z] = mesh.triangles()[t as usize].map(|v| point(mesh, v));
566 cross(sub(y, x), sub(z, x))
567 };
568 let mut n = normal(cur.tri);
569 for s in leaving.iter().filter(other) {
570 let m = normal(s.tri);
571 n = [n[0] + m[0], n[1] + m[1], n[2] + m[2]];
572 }
573 let pb = point(mesh, cur.to);
574 let nn = dot(n, n);
575 let flat = |v: VertexId| {
576 let d = sub(point(mesh, v), pb);
577 let k = dot(n, d) / nn;
578 [d[0] - k * n[0], d[1] - k * n[1], d[2] - k * n[2]]
579 };
580 let d0 = flat(cur.from);
581 let turn = |s: &Step| {
582 let d = flat(s.to);
583 let a = dot(n, cross(d0, d)).atan2(dot(d0, d) * norm(n));
584 let a = if a <= 0.0 {
585 a + std::f64::consts::TAU
586 } else {
587 a
588 };
589 if a.is_finite() { a } else { f64::INFINITY }
590 };
591 let mut best = (turn(&first), first);
592 for s in others {
593 let a = turn(s);
594 if a < best.0 {
595 best = (a, *s);
596 }
597 }
598 Some(best.1)
599}
600
601fn fan_walk(
606 mesh: &Mesh,
607 edges: &EdgeMap,
608 t: TriangleId,
609 a: VertexId,
610 b: VertexId,
611) -> Option<(VertexId, TriangleId)> {
612 let (mut tri, mut prev) = (t, a);
613 for _ in 0..=mesh.corner_count(b) {
614 let c = mesh.triangles()[tri as usize]
615 .into_iter()
616 .find(|&v| v != b && v != prev)?;
617 match edges.triangles(b, c) {
618 [_] => return Some((c, tri)),
619 &[x, y] => {
620 tri = if x == tri { y } else { x };
621 prev = c;
622 }
623 _ => return None,
624 }
625 }
626 None
627}
628
629fn make_loop(mesh: &Mesh, verts: &[VertexId], closed: bool, clean: bool) -> Result<BoundaryLoop> {
632 let n = verts.len();
633 let min = *verts.iter().min().expect("a loop has vertices");
634 let rotation = |i: usize| verts[i..].iter().chain(&verts[..i]);
638 let mut best = 0;
639 if closed {
640 best = verts
641 .iter()
642 .position(|&v| v == min)
643 .expect("min is present");
644 for (i, &v) in verts.iter().enumerate().skip(best + 1) {
645 if v == min && rotation(i).cmp(rotation(best)).is_lt() {
646 best = i;
647 }
648 }
649 }
650 let mut vertices = Vec::new();
651 reserve(&mut vertices, n)?;
652 vertices.extend(rotation(best));
653 let (mut perimeter, mut vector) = (0.0, [0.0; 3]);
654 for i in 0..n {
655 let (p, q) = (point(mesh, vertices[i]), point(mesh, vertices[(i + 1) % n]));
656 perimeter += norm(sub(q, p));
657 let c = cross(p, q);
658 vector = [vector[0] + c[0], vector[1] + c[1], vector[2] + c[2]];
659 }
660 Ok(BoundaryLoop {
661 id: LoopId {
662 min_vertex: min,
663 len: n as u32,
665 hash: fnv1a(&vertices),
666 },
667 vertices,
668 perimeter,
669 area: 0.5 * norm(vector),
670 closed,
671 clean,
672 })
673}
674
675#[cfg(test)]
676mod tests {
677 use super::*;
678 use crate::test_util::*;
679 use crate::{OpsError, fail_allocations_after};
680 use fast_mesh_store::{Mesh, StoreError};
681
682 fn kinds(mesh: &Mesh) -> Vec<((VertexId, VertexId), EdgeKind)> {
683 let map = EdgeMap::build(mesh).unwrap();
684 map.edges()
685 .map(|(k, _)| (k, map.kind(k.0, k.1).unwrap()))
686 .collect()
687 }
688
689 fn count(mesh: &Mesh, kind: EdgeKind) -> usize {
690 kinds(mesh).iter().filter(|(_, k)| *k == kind).count()
691 }
692
693 #[test]
694 fn edge_kinds_on_fixtures() {
695 let sphere = uv_sphere(8, 12, 1.0);
696 assert_eq!(count(&sphere, EdgeKind::Boundary), 0);
697 assert_eq!(count(&sphere, EdgeKind::NonManifold), 0);
698 assert_eq!(
699 count(&sphere, EdgeKind::Interior),
700 3 * sphere.triangle_count() / 2
701 );
702 assert_eq!(count(&grid(4), EdgeKind::Boundary), 16);
703 assert_eq!(count(&grid(4), EdgeKind::NonManifold), 0);
704 let fin = fin();
705 assert_eq!(count(&fin, EdgeKind::NonManifold), 1);
706 let map = EdgeMap::build(&fin).unwrap();
707 assert_eq!(map.kind(1, 0), Some(EdgeKind::NonManifold));
708 assert_eq!(map.triangles(1, 0), &[1, 3, 5]);
709 assert_eq!(map.kind(0, 3), Some(EdgeKind::Interior));
710 assert_eq!(map.triangles(3, 0), &[0, 1]);
711 assert_eq!(map.kind(2, 4), None);
712 assert_eq!(map.triangles(2, 4), &[] as &[TriangleId]);
713 }
714
715 #[test]
716 fn components_count_and_closed() {
717 let two = combine(&[&uv_sphere(8, 12, 1.0), &uv_sphere(6, 8, 0.25)]);
718 let map = EdgeMap::build(&two).unwrap();
719 let comps = components(&two, &map).unwrap();
720 assert_eq!(comps.len(), 2);
721 assert!(comps.iter().all(|c| c.closed));
722 assert_eq!(comps[0].triangles.len(), 2 * 12 * 7);
723 assert_eq!(comps[1].triangles.len(), 2 * 8 * 5);
724 assert_eq!(comps[1].triangles[0], 2 * 12 * 7);
725 assert!(comps[0].area > comps[1].area);
726 assert!(comps[0].area < 4.0 * std::f64::consts::PI && comps[0].area > 3.5);
728
729 let hemi = hemisphere(4, 16, 1.0);
730 let map = EdgeMap::build(&hemi).unwrap();
731 let comps = components(&hemi, &map).unwrap();
732 assert_eq!(comps.len(), 1);
733 assert!(!comps[0].closed);
734 assert_eq!(comps[0].triangles.len(), hemi.triangle_count());
735
736 let fin = fin();
738 let comps = components(&fin, &EdgeMap::build(&fin).unwrap()).unwrap();
739 assert_eq!(comps.len(), 1);
740 assert!(!comps[0].closed);
741 }
742
743 #[test]
744 fn hemisphere_has_one_loop() {
745 let r = 2.0;
746 let loops = boundary_loops(&hemisphere(8, 64, r)).unwrap();
747 assert_eq!(loops.len(), 1);
748 let l = &loops[0];
749 assert_eq!(l.vertices.len(), 64);
750 assert_eq!(l.id.len, 64);
751 assert!(l.clean);
752 let circumference = 2.0 * std::f64::consts::PI * f64::from(r);
753 assert!((l.perimeter - circumference).abs() < 0.01 * circumference);
754 let disc = std::f64::consts::PI * f64::from(r * r);
756 assert!((l.area - disc).abs() < 0.01 * disc);
757 let first = 1 + 7 * 64;
760 assert_eq!(l.id.min_vertex, first);
761 assert_eq!(l.vertices[0], first);
762 assert_eq!(l.vertices[1], first + 1);
763 }
764
765 #[test]
766 fn bowtie_holes_are_two_clean_loops() {
767 let mesh = bowtie_holes();
768 let loops = boundary_loops(&mesh).unwrap();
769 let seqs: Vec<&[VertexId]> = loops.iter().map(|l| l.vertices.as_slice()).collect();
770 assert_eq!(
771 seqs,
772 [
773 &[0, 1, 2, 3, 4, 9, 14, 19, 24, 23, 22, 21, 20, 15, 10, 5][..],
775 &[6, 11, 12, 7][..],
777 &[12, 17, 18, 13][..],
779 ]
780 );
781 assert!(loops.iter().all(|l| l.clean && l.closed));
782 assert_eq!(loops[1].id.len, 4);
783 assert_eq!(loops[2].id.len, 4);
784 assert_eq!(loops[1].id.min_vertex, 6);
785 assert_eq!(loops[2].id.min_vertex, 12);
786 for l in &loops[1..] {
787 assert!((l.perimeter - 4.0).abs() < 1e-12);
788 assert!((l.area - 1.0).abs() < 1e-12);
789 }
790 assert!((loops[0].perimeter - 16.0).abs() < 1e-12);
791 assert!((loops[0].area - 16.0).abs() < 1e-12);
792 let map = EdgeMap::build(&mesh).unwrap();
793 assert_eq!(pinch_vertices(&mesh, &map).unwrap(), vec![12]);
794 let g = grid(4);
796 assert!(
797 pinch_vertices(&g, &EdgeMap::build(&g).unwrap())
798 .unwrap()
799 .is_empty()
800 );
801 }
802
803 #[test]
804 fn three_holes_at_one_vertex_are_three_loops() {
805 let mesh = tri_pinch();
808 let loops = boundary_loops(&mesh).unwrap();
809 let seqs: Vec<&[VertexId]> = loops.iter().map(|l| l.vertices.as_slice()).collect();
810 assert_eq!(
811 seqs,
812 [
813 &[0, 1, 6][..],
814 &[0, 3, 2][..],
815 &[0, 5, 4][..],
816 &[7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18][..],
817 ]
818 );
819 assert!(loops.iter().all(|l| l.clean));
820 let map = EdgeMap::build(&mesh).unwrap();
821 assert_eq!(pinch_vertices(&mesh, &map).unwrap(), vec![0]);
822 }
823
824 #[test]
825 fn fin_loops_unclean() {
826 let mesh = fin();
827 let loops = boundary_loops(&mesh).unwrap();
828 let seqs: Vec<&[VertexId]> = loops.iter().map(|l| l.vertices.as_slice()).collect();
832 assert_eq!(
833 seqs,
834 [&[0, 2, 3, 1][..], &[0, 4, 5, 1][..], &[0, 6, 7, 1][..]]
835 );
836 assert!(loops.iter().all(|l| !l.clean && !l.closed));
837 assert_eq!(loops[0].id.hash, fnv1a(&[0, 2, 3, 1]));
838 let map = EdgeMap::build(&mesh).unwrap();
840 assert!(pinch_vertices(&mesh, &map).unwrap().is_empty());
841 }
842
843 #[test]
844 fn fin_chains_whole_whatever_the_numbering() {
845 let (old, _) = parts(&fin());
849 let new = [5, 1, 7, 3, 2, 4, 6, 8];
851 let mut positions = vec![0.0f32; 27];
852 for (o, &n) in new.iter().enumerate() {
853 positions[3 * n..3 * n + 3].copy_from_slice(&old[3 * o..3 * o + 3]);
854 }
855 let tris = [
856 [5, 7, 3],
857 [5, 3, 1],
858 [5, 2, 4],
859 [5, 4, 1],
860 [5, 6, 8],
861 [5, 8, 1],
862 ];
863 let indices: Vec<u32> = tris.iter().flatten().copied().collect();
864 let mesh = Mesh::from_buffers(&positions, &indices).unwrap();
865 let loops = boundary_loops(&mesh).unwrap();
866 let seqs: Vec<&[VertexId]> = loops.iter().map(|l| l.vertices.as_slice()).collect();
867 assert_eq!(
868 seqs,
869 [&[5, 2, 4, 1][..], &[5, 6, 8, 1][..], &[5, 7, 3, 1][..]]
870 );
871 assert!(loops.iter().all(|l| !l.clean && !l.closed));
872 assert!(loops.iter().all(|l| l.id.min_vertex == 1 && l.id.len == 4));
873 assert_eq!(loops[0].id.hash, fnv1a(&[5, 2, 4, 1]));
874 }
875
876 #[test]
877 fn three_holes_at_one_vertex_out_of_plane() {
878 let bowl: fn(f64, f64) -> f64 = |r, _| 0.3 * r * r;
882 let saddle: fn(f64, f64) -> f64 = |r, deg| 0.7 * r * (3.0 * deg.to_radians()).cos();
883 let fold: fn(f64, f64) -> f64 = |r, deg| (0.8 * r * deg.to_radians().cos()).max(0.0);
884 for lift in [bowl, saddle, fold] {
885 let mesh = tri_pinch_lifted(lift);
886 let loops = boundary_loops(&mesh).unwrap();
887 let seqs: Vec<&[VertexId]> = loops.iter().map(|l| l.vertices.as_slice()).collect();
888 assert_eq!(
889 seqs,
890 [
891 &[0, 1, 6][..],
892 &[0, 3, 2][..],
893 &[0, 5, 4][..],
894 &[7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18][..],
895 ]
896 );
897 assert!(loops.iter().all(|l| l.clean && l.closed));
898 }
899 }
900
901 #[test]
902 fn loop_id_is_rotation_invariant_and_stable() {
903 let mesh = bowtie_holes();
904 let before = boundary_loops(&mesh).unwrap();
905 let (positions, triangles) = parts(&mesh);
908 let mut shuffled: Vec<u32> = Vec::new();
909 for (i, t) in triangles.iter().enumerate().rev() {
910 let r = i % 3;
911 shuffled.extend([t[r], t[(r + 1) % 3], t[(r + 2) % 3]]);
912 }
913 let again = Mesh::from_buffers(&positions, &shuffled).unwrap();
914 let after = boundary_loops(&again).unwrap();
915 let ids = |ls: &[BoundaryLoop]| ls.iter().map(|l| l.id).collect::<Vec<_>>();
916 assert_eq!(ids(&before), ids(&after));
917 assert_eq!(before[1].vertices, after[1].vertices);
918 let mut hashes: Vec<u64> = before.iter().map(|l| l.id.hash).collect();
920 hashes.sort_unstable();
921 hashes.dedup();
922 assert_eq!(hashes.len(), 3);
923 assert_eq!(before[1].id.hash, fnv1a(&[6, 11, 12, 7]));
925 assert_ne!(fnv1a(&[6, 11, 12, 7]), fnv1a(&[6, 7, 12, 11]));
926 }
927
928 #[test]
929 fn degenerate_triangles_ignored_by_maps() {
930 for mesh in [bowtie_holes(), fin(), hemisphere(3, 8, 1.0)] {
931 let (_, tris) = parts(&mesh);
932 let extra: Vec<[u32; 3]> = tris
933 .iter()
934 .step_by(3)
935 .flat_map(|&[a, b, c]| [[a, a, b], [b, c, c], [c, c, c]])
936 .collect();
937 let dirty = with_triangles(&mesh, &extra);
938 assert_eq!(kinds(&mesh), kinds(&dirty));
939 let (l0, l1) = (
940 boundary_loops(&mesh).unwrap(),
941 boundary_loops(&dirty).unwrap(),
942 );
943 assert_eq!(l0, l1);
944 let m0 = EdgeMap::build(&mesh).unwrap();
945 let m1 = EdgeMap::build(&dirty).unwrap();
946 assert_eq!(
947 pinch_vertices(&mesh, &m0).unwrap(),
948 pinch_vertices(&dirty, &m1).unwrap()
949 );
950 let c1 = components(&dirty, &m1).unwrap();
951 assert_eq!(components(&mesh, &m0).unwrap(), c1);
952 }
953 }
954
955 #[test]
956 fn fixtures_sanity() {
957 let m = mobius(12);
960 let map = EdgeMap::build(&m).unwrap();
961 assert_eq!(components(&m, &map).unwrap().len(), 1);
962 let loops = boundary_loops(&m).unwrap();
963 assert_eq!(loops.len(), 1);
964 assert_eq!(loops[0].vertices.len(), 24);
965 assert!(loops[0].closed && !loops[0].clean);
966 let punched = punch_holes(
968 &uv_sphere(16, 16, 1.0),
969 &[[0.0, 0.0, 1.0], [0.0, 0.0, -1.0]],
970 0.3,
971 );
972 let map = EdgeMap::build(&punched).unwrap();
973 assert_eq!(components(&punched, &map).unwrap().len(), 1);
974 assert_eq!(boundary_loops(&punched).unwrap().len(), 2);
975 let g = grid(3);
977 let flipped = flip(&g, &[4]);
978 assert_eq!(kinds(&g), kinds(&flipped));
979 let loops = boundary_loops(&flipped).unwrap();
980 assert_eq!(loops.len(), 1);
981 assert_eq!(loops[0].vertices.len(), 12);
982 let b = crossed_boxes();
984 let comps = components(&b, &EdgeMap::build(&b).unwrap()).unwrap();
985 assert_eq!(comps.len(), 2);
986 assert!(comps.iter().all(|c| c.closed && c.triangles.len() == 12));
987 assert!((comps[0].area - 24.0).abs() < 1e-9);
988 }
989
990 #[test]
991 fn boundary_loops_oom_is_clean() {
992 let mesh = bowtie_holes();
993 let expected = boundary_loops(&mesh).unwrap();
994 let mut n = 0;
995 loop {
996 fail_allocations_after(Some(n));
997 let got = boundary_loops(&mesh);
998 fail_allocations_after(None);
999 match got {
1000 Ok(loops) => {
1001 assert_eq!(loops, expected);
1002 break;
1003 }
1004 Err(e) => assert_eq!(e, OpsError::Store(StoreError::OutOfMemory)),
1005 }
1006 n += 1;
1007 }
1008 assert!(n > 5, "only {n} fallible reservations");
1009 }
1010}