1use ogeom_core::{FastMap, OgeomResult, ogeom_bail};
67use ogeom_math::{Aabb, Point, Vector};
68use ogeom_topo::Triangulation;
69
70pub mod cut;
71pub mod fill;
72pub mod offset;
73pub mod simplify;
74pub mod smooth;
75
76pub use cut::{Cap, CutReport, Side};
77pub use fill::{Fairing, Fill, FillReport, OPTIMAL_UP_TO};
78pub use offset::{OFFSET_EDGE_ANGLE, OFFSET_FARTHEST, OffsetReport};
79pub use simplify::SimplifyReport;
80pub use smooth::{PASSES, Smooth, SmoothReport};
81
82const NONE: u32 = u32::MAX;
84
85const fn next(h: u32) -> u32 {
87 if h % 3 == 2 { h - 2 } else { h + 1 }
88}
89
90const fn prev(h: u32) -> u32 {
92 if h.is_multiple_of(3) { h + 2 } else { h - 1 }
93}
94
95fn index(i: usize) -> u32 {
96 u32::try_from(i).unwrap_or(NONE)
97}
98
99#[derive(Debug, Clone, PartialEq)]
107pub struct Editable {
108 positions: Vec<Point>,
109 point: Vec<u32>,
112 copies: Vec<u32>,
114 source: Vec<u32>,
117 merged: FastMap<u32, Vec<u32>>,
120 source_count: usize,
122 out: Vec<u32>,
126 gone: Vec<bool>,
128 corners: Vec<[u32; 3]>,
129 live: Vec<bool>,
131 twin: Vec<u32>,
132 deflection_met: bool,
133}
134
135#[derive(Debug, Clone, PartialEq)]
137pub struct Component {
138 pub faces: Vec<u32>,
140 pub bounds: Aabb,
142 pub area: f64,
144}
145
146#[derive(Debug, Clone, PartialEq)]
148pub struct Rim {
149 pub edges: Vec<u32>,
153 pub length: f64,
155 pub span: f64,
157}
158
159#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
164pub struct RimId(pub u32);
165
166impl Rim {
167 #[must_use]
169 pub fn id(&self) -> RimId {
170 RimId(self.edges[0])
171 }
172}
173
174#[derive(Default)]
176struct Scratch {
177 parent: Vec<usize>,
178 fan: Vec<usize>,
179 taken: Vec<u32>,
180}
181
182enum Scope {
184 All(usize),
185 Points(Vec<u32>),
186}
187
188impl Scope {
189 fn len(&self) -> usize {
190 match self {
191 Self::All(n) => *n,
192 Self::Points(p) => p.len(),
193 }
194 }
195
196 fn point(&self, slot: usize) -> u32 {
197 match self {
198 Self::All(_) => index(slot),
199 Self::Points(p) => p[slot],
200 }
201 }
202
203 fn slot(&self, point: u32) -> Option<usize> {
204 match self {
205 Self::All(n) => ((point as usize) < *n).then_some(point as usize),
206 Self::Points(p) => p.binary_search(&point).ok(),
207 }
208 }
209}
210
211impl Editable {
212 pub fn from(mesh: &Triangulation, weld: f64) -> OgeomResult<Self> {
234 if !weld.is_finite() || weld < 0.0 {
235 ogeom_bail!(Numeric, "a weld distance of {weld} is not a distance");
236 }
237 if let Some(k) = mesh.positions.iter().position(|p| !p.is_finite()) {
238 ogeom_bail!(Numeric, "mesh vertex {k} is not finite");
239 }
240 let count = mesh.positions.len();
241 if let Some(&k) = mesh
242 .triangles
243 .iter()
244 .flatten()
245 .find(|&&k| k as usize >= count)
246 {
247 ogeom_bail!(Range, "a triangle names vertex {k} of {count}");
248 }
249 if count.saturating_add(mesh.triangles.len().saturating_mul(3)) >= NONE as usize {
252 ogeom_bail!(
253 Range,
254 "a mesh of {count} vertices and {} triangles is past u32 numbering",
255 mesh.triangles.len()
256 );
257 }
258
259 let (positions, remap, source) = if weld > 0.0 {
260 weld_points(&mesh.positions, weld)
261 } else {
262 let all: Vec<u32> = (0..count).map(index).collect();
263 (mesh.positions.clone(), all.clone(), all)
264 };
265 let n = positions.len();
266 let corners: Vec<[u32; 3]> = mesh
267 .triangles
268 .iter()
269 .map(|t| t.map(|v| remap[v as usize]))
270 .collect();
271 let live = corners
272 .iter()
273 .map(|&[a, b, c]| a != b && b != c && c != a)
274 .collect();
275 let mut mesh = Self {
276 positions,
277 point: (0..n).map(index).collect(),
278 copies: (0..n).map(index).collect(),
279 source,
280 merged: FastMap::default(),
281 source_count: count,
282 out: vec![NONE; n],
283 gone: vec![false; n],
284 twin: vec![NONE; corners.len() * 3],
285 corners,
286 live,
287 deflection_met: mesh.deflection_met,
288 };
289 let faces: Vec<u32> = (0..mesh.corners.len())
290 .filter(|&f| mesh.live[f])
291 .map(index)
292 .collect();
293 mesh.relink(&Scope::All(n), &faces);
294 Ok(mesh)
295 }
296
297 #[must_use]
304 pub fn to_triangulation(&self) -> Triangulation {
305 let mut renumber = vec![NONE; self.positions.len()];
306 let mut positions = Vec::with_capacity(self.positions.len());
307 for (v, &p) in self.positions.iter().enumerate() {
308 if !self.gone[v] {
309 renumber[v] = index(positions.len());
310 positions.push(p);
311 }
312 }
313 let triangles = self
314 .corners
315 .iter()
316 .zip(&self.live)
317 .filter(|&(_, &live)| live)
318 .map(|(t, _)| t.map(|v| renumber[v as usize]))
319 .collect();
320 Triangulation {
321 positions,
322 triangles,
323 deflection_met: self.deflection_met,
324 ..Triangulation::new()
325 }
326 }
327
328 #[must_use]
331 pub fn vertex_count(&self) -> usize {
332 self.positions.len()
333 }
334
335 #[must_use]
337 pub fn face_count(&self) -> usize {
338 self.corners.len()
339 }
340
341 #[must_use]
343 pub fn position(&self, v: u32) -> Option<Point> {
344 let k = v as usize;
345 (k < self.positions.len() && !self.gone[k]).then(|| self.positions[k])
346 }
347
348 #[must_use]
351 pub fn face(&self, f: u32) -> Option<[u32; 3]> {
352 let k = f as usize;
353 (k < self.corners.len() && self.live[k]).then(|| self.corners[k])
354 }
355
356 #[must_use]
358 pub fn edge(&self, h: u32) -> Option<[u32; 2]> {
359 self.face(h / 3)
360 .is_some()
361 .then(|| [self.tail(h), self.head(h)])
362 }
363
364 #[must_use]
370 pub fn source_vertex(&self, v: u32) -> Option<u32> {
371 match self.source_vertices(v) {
372 &[s] => Some(s),
373 _ => None,
374 }
375 }
376
377 #[must_use]
382 pub fn source_vertices(&self, v: u32) -> &[u32] {
383 let k = v as usize;
384 if k >= self.source.len() || self.source[k] == NONE {
385 return &[];
386 }
387 self.merged
388 .get(&v)
389 .map_or_else(|| core::slice::from_ref(&self.source[k]), Vec::as_slice)
390 }
391
392 #[must_use]
395 pub fn source_count(&self) -> usize {
396 self.source_count
397 }
398
399 pub fn region_from_sources(&self, marked: &[bool]) -> OgeomResult<Vec<bool>> {
411 if marked.len() != self.source_count {
412 ogeom_bail!(
413 Range,
414 "a marking of {} entries for {} source vertices",
415 marked.len(),
416 self.source_count
417 );
418 }
419 Ok((0..self.positions.len())
420 .map(|k| {
421 let sources = self.source_vertices(index(k));
422 !self.gone[k] && !sources.is_empty() && sources.iter().all(|&s| marked[s as usize])
423 })
424 .collect())
425 }
426
427 fn copy_sources(&mut self, to: u32, from: u32) {
429 self.source[to as usize] = self.source[from as usize];
430 match self.merged.get(&from).cloned() {
431 Some(set) => self.merged.insert(to, set),
432 None => self.merged.remove(&to),
433 };
434 }
435
436 fn absorb_sources(&mut self, to: u32, from: u32) {
439 let mut set: Vec<u32> = self.source_vertices(to).to_vec();
440 set.extend_from_slice(self.source_vertices(from));
441 set.sort_unstable();
442 set.dedup();
443 self.set_sources(to, set);
444 }
445
446 fn set_sources(&mut self, v: u32, set: Vec<u32>) {
448 self.source[v as usize] = set.first().copied().unwrap_or(NONE);
449 if set.len() > 1 {
450 self.merged.insert(v, set);
451 } else {
452 self.merged.remove(&v);
453 }
454 }
455
456 #[must_use]
459 pub fn is_closed(&self) -> bool {
460 let mut any = false;
461 for (f, &live) in self.live.iter().enumerate() {
462 if live {
463 any = true;
464 if self.twin[3 * f..3 * f + 3].contains(&NONE) {
465 return false;
466 }
467 }
468 }
469 any
470 }
471
472 #[must_use]
477 pub fn non_manifold(&self) -> Vec<u32> {
478 let mut flagged = vec![false; self.positions.len()];
479 for v in 0..self.positions.len() {
480 if self.out[v] == NONE || flagged[v] || self.copies[v] as usize == v {
481 continue;
482 }
483 let ring = self.ring(index(v));
484 let with_faces: Vec<u32> = ring
485 .into_iter()
486 .filter(|&w| self.out[w as usize] != NONE)
487 .collect();
488 if with_faces.len() > 1 {
489 for w in with_faces {
490 flagged[w as usize] = true;
491 }
492 }
493 }
494 let mut edges: Vec<(u64, u32)> = Vec::new();
495 for (f, &live) in self.live.iter().enumerate() {
496 if live {
497 for h in index(3 * f)..index(3 * f + 3) {
498 edges.push((self.edge_key(h), h));
499 }
500 }
501 }
502 edges.sort_unstable();
503 for run in edges.chunk_by(|a, b| a.0 == b.0) {
504 let clean =
505 run.len() == 1 || (run.len() == 2 && self.twin[run[0].1 as usize] == run[1].1);
506 if !clean {
507 for &(_, h) in run {
508 flagged[self.tail(h) as usize] = true;
509 flagged[self.head(h) as usize] = true;
510 }
511 }
512 }
513 (0..self.positions.len())
514 .filter(|&v| flagged[v])
515 .map(index)
516 .collect()
517 }
518
519 #[must_use]
522 pub fn components(&self) -> Vec<Component> {
523 let mut seen = vec![false; self.corners.len()];
524 let mut pieces = Vec::new();
525 let mut stack = Vec::new();
526 for seed in 0..self.corners.len() {
527 if !self.live[seed] || seen[seed] {
528 continue;
529 }
530 seen[seed] = true;
531 stack.push(index(seed));
532 let mut faces = Vec::new();
533 let (mut lo, mut hi) = (
534 Point::new(f64::MAX, f64::MAX, f64::MAX),
535 Point::new(f64::MIN, f64::MIN, f64::MIN),
536 );
537 let mut area = 0.0;
538 while let Some(f) = stack.pop() {
539 faces.push(f);
540 let [a, b, c] = self.corners[f as usize].map(|v| self.positions[v as usize]);
541 lo = lo.min(a).min(b).min(c);
542 hi = hi.max(a).max(b).max(c);
543 area += (b - a).cross(c - a).magnitude() * 0.5;
544 for h in 3 * f..3 * f + 3 {
545 let g = self.twin[h as usize];
546 if g != NONE && !seen[(g / 3) as usize] {
547 seen[(g / 3) as usize] = true;
548 stack.push(g / 3);
549 }
550 }
551 }
552 faces.sort_unstable();
553 pieces.push(Component {
554 faces,
555 bounds: Aabb::of_corners(lo, hi),
556 area,
557 });
558 }
559 pieces
560 }
561
562 #[must_use]
565 pub fn rims(&self) -> Vec<Rim> {
566 let mut seen = vec![false; self.twin.len()];
567 let mut rims = Vec::new();
568 for start in 0..self.twin.len() {
569 if self.twin[start] != NONE || seen[start] || !self.live[start / 3] {
570 continue;
571 }
572 let mut edges = Vec::new();
573 let mut points = Vec::new();
574 let mut length = 0.0;
575 let mut h = index(start);
576 loop {
577 seen[h as usize] = true;
578 edges.push(h);
579 let (a, b) = (self.tail(h), self.head(h));
580 let p = self.positions[a as usize];
581 points.push(p);
582 length += p.distance(self.positions[b as usize]);
583 h = self.rim_after(h);
584 if h as usize == start {
585 break;
586 }
587 }
588 rims.push(Rim {
589 edges,
590 length,
591 span: diameter(&points),
592 });
593 }
594 rims
595 }
596
597 pub fn remove_faces(&mut self, faces: &[u32]) {
601 let removed = self.listed(faces);
602 if removed.is_empty() {
603 return;
604 }
605 let (scope, around) = self.around(&removed);
606 for &f in &removed {
607 self.live[f as usize] = false;
608 for h in 3 * f..3 * f + 3 {
609 self.twin[h as usize] = NONE;
610 }
611 }
612 let kept: Vec<u32> = around
613 .into_iter()
614 .filter(|f| removed.binary_search(f).is_err())
615 .collect();
616 self.relink(&scope, &kept);
617 }
618
619 pub fn flip_faces(&mut self, faces: &[u32]) {
625 let flipped = self.listed(faces);
626 if flipped.is_empty() {
627 return;
628 }
629 let (scope, around) = self.around(&flipped);
630 for &f in &flipped {
631 self.corners[f as usize].swap(1, 2);
632 }
633 self.relink(&scope, &around);
634 }
635
636 fn listed(&self, faces: &[u32]) -> Vec<u32> {
638 let mut listed: Vec<u32> = faces
639 .iter()
640 .copied()
641 .filter(|&f| (f as usize) < self.corners.len() && self.live[f as usize])
642 .collect();
643 listed.sort_unstable();
644 listed.dedup();
645 listed
646 }
647
648 fn around(&self, faces: &[u32]) -> (Scope, Vec<u32>) {
651 let mut points: Vec<u32> = faces
652 .iter()
653 .flat_map(|&f| self.corners[f as usize])
654 .map(|v| self.point[v as usize])
655 .collect();
656 points.sort_unstable();
657 points.dedup();
658 let around = self.about(&points);
659 (Scope::Points(points), around)
660 }
661
662 fn about(&self, points: &[u32]) -> Vec<u32> {
664 let mut around = Vec::new();
665 for &p in points {
666 for v in self.ring(p) {
667 let start = self.out[v as usize];
668 if start == NONE {
669 continue;
670 }
671 let mut h = start;
672 loop {
673 around.push(h / 3);
674 h = self.twin[prev(h) as usize];
675 if h == NONE || h == start {
676 break;
677 }
678 }
679 }
680 }
681 around.sort_unstable();
682 around.dedup();
683 around
684 }
685
686 fn ring(&self, p: u32) -> Vec<u32> {
688 let mut ring = vec![p];
689 let mut v = self.copies[p as usize];
690 while v != p {
691 ring.push(v);
692 v = self.copies[v as usize];
693 }
694 ring
695 }
696
697 fn tail(&self, h: u32) -> u32 {
698 self.corners[(h / 3) as usize][(h % 3) as usize]
699 }
700
701 fn head(&self, h: u32) -> u32 {
702 self.tail(next(h))
703 }
704
705 fn edge_key(&self, h: u32) -> u64 {
707 let (a, b) = (
708 self.point[self.tail(h) as usize],
709 self.point[self.head(h) as usize],
710 );
711 (u64::from(a.min(b)) << 32) | u64::from(a.max(b))
712 }
713
714 fn rim_after(&self, h: u32) -> u32 {
718 let mut g = next(h);
719 while self.twin[g as usize] != NONE {
720 g = next(self.twin[g as usize]);
721 }
722 g
723 }
724
725 fn relink(&mut self, scope: &Scope, faces: &[u32]) {
729 let mut start = vec![0_usize; scope.len() + 1];
732 for &f in faces {
733 for v in self.corners[f as usize] {
734 if let Some(s) = scope.slot(self.point[v as usize]) {
735 start[s + 1] += 1;
736 }
737 }
738 }
739 for s in 0..scope.len() {
740 start[s + 1] += start[s];
741 }
742 let mut fill = start.clone();
743 let mut leaving = vec![0_u32; start[scope.len()]];
744 for &f in faces {
745 for (k, v) in self.corners[f as usize].into_iter().enumerate() {
746 if let Some(s) = scope.slot(self.point[v as usize]) {
747 leaving[fill[s]] = 3 * f + index(k);
748 fill[s] += 1;
749 }
750 }
751 }
752
753 let mut on_edges: Vec<(u32, u32)> = Vec::new();
754 for s in 0..scope.len() {
755 let p = scope.point(s);
756 on_edges.clear();
757 for &h in &leaving[start[s]..start[s + 1]] {
758 let into = prev(h);
759 on_edges.push((self.point[self.head(h) as usize], h));
760 on_edges.push((self.point[self.tail(into) as usize], into));
761 }
762 on_edges.sort_unstable();
763 for run in on_edges.chunk_by(|a, b| a.0 == b.0) {
764 let other = run[0].0;
765 if other < p && scope.slot(other).is_some() {
768 continue;
769 }
770 self.join(run, p.min(other));
771 }
772 }
773
774 let mut scratch = Scratch::default();
775 for s in 0..scope.len() {
776 let fan = &leaving[start[s]..start[s + 1]];
777 self.give_vertices(scope.point(s), fan, &mut scratch);
778 }
779 }
780
781 fn join(&mut self, run: &[(u32, u32)], low: u32) {
785 for &(_, h) in run {
786 self.twin[h as usize] = NONE;
787 }
788 let rising = |me: &Self, h: u32| me.point[me.tail(h) as usize] == low;
789 match *run {
790 [_] => {}
791 [(_, a), (_, b)] => {
792 if rising(self, a) != rising(self, b) {
793 self.twin[a as usize] = b;
794 self.twin[b as usize] = a;
795 }
796 }
797 _ => {
798 let h0 = run[0].1;
804 let (from, to) = if rising(self, h0) {
805 (self.tail(h0), self.head(h0))
806 } else {
807 (self.head(h0), self.tail(h0))
808 };
809 let from = self.positions[from as usize];
810 let axis = self.positions[to as usize] - from;
811 let length = axis.magnitude();
812 let axis = if length > 0.0 { axis / length } else { axis };
813 let across: Vec<Vector> = run
814 .iter()
815 .map(|&(_, h)| {
816 let w = self.positions[self.tail(prev(h)) as usize] - from;
817 w - axis * w.dot(axis)
818 })
819 .collect();
820 let mut turn: Vec<(f64, u32, bool)> = match across
821 .iter()
822 .find(|w| w.magnitude() > 0.0)
823 {
824 Some(&first) => {
825 let e1 = first / first.magnitude();
826 let e2 = axis.cross(e1);
827 run.iter()
828 .zip(&across)
829 .map(|(&(_, h), w)| (w.dot(e2).atan2(w.dot(e1)), h, rising(self, h)))
830 .collect()
831 }
832 None => run
833 .iter()
834 .map(|&(_, h)| (0.0, h, rising(self, h)))
835 .collect(),
836 };
837 turn.sort_by(|a, b| a.0.total_cmp(&b.0).then(a.1.cmp(&b.1)));
838 let n = turn.len();
839 for i in 0..n {
840 let (a, b) = (turn[i], turn[(i + 1) % n]);
841 if !a.2 && b.2 {
842 self.twin[a.1 as usize] = b.1;
843 self.twin[b.1 as usize] = a.1;
844 }
845 }
846 }
847 }
848 }
849
850 fn give_vertices(&mut self, p: u32, leaving: &[u32], scratch: &mut Scratch) {
856 fn root(parent: &mut [usize], mut i: usize) -> usize {
857 while parent[i] != i {
858 parent[i] = parent[parent[i]];
859 i = parent[i];
860 }
861 i
862 }
863 let Scratch { parent, fan, taken } = scratch;
864 parent.clear();
865 parent.extend(0..leaving.len());
866 for (i, &h) in leaving.iter().enumerate() {
867 let g = self.twin[h as usize];
868 if g == NONE {
869 continue;
870 }
871 if let Ok(j) = leaving.binary_search(&next(g)) {
872 let (a, b) = (root(parent, i), root(parent, j));
873 parent[a.max(b)] = a.min(b);
874 }
875 }
876 fan.clear();
879 let mut fans = 0;
880 for i in 0..leaving.len() {
881 let r = root(parent, i);
882 if r == i {
883 fan.push(fans);
884 fans += 1;
885 } else {
886 let k = fan[r];
887 fan.push(k);
888 }
889 }
890
891 taken.clear();
892 for k in 0..fans {
893 let members = || {
894 leaving
895 .iter()
896 .zip(fan.iter())
897 .filter(move |&(_, &f)| f == k)
898 .map(|(&h, _)| h)
899 };
900 let held = members()
901 .map(|h| self.tail(h))
902 .filter(|v| !taken.contains(v))
903 .min();
904 let first = members().next().unwrap_or(NONE);
905 let v = held.unwrap_or_else(|| self.copy_of(p, self.tail(first)));
906 taken.push(v);
907 let mut out = NONE;
908 for h in members() {
909 self.corners[(h / 3) as usize][(h % 3) as usize] = v;
910 if out == NONE && self.twin[h as usize] == NONE {
911 out = h;
912 }
913 }
914 self.out[v as usize] = if out == NONE { first } else { out };
915 self.gone[v as usize] = false;
916 }
917 let mut v = p;
918 loop {
919 let k = v as usize;
920 if !taken.contains(&v) && self.out[k] != NONE {
921 self.out[k] = NONE;
922 self.gone[k] = true;
923 }
924 v = self.copies[k];
925 if v == p {
926 break;
927 }
928 }
929 }
930
931 fn copy_of(&mut self, p: u32, like: u32) -> u32 {
934 let v = index(self.positions.len());
935 self.positions.push(self.positions[like as usize]);
936 self.point.push(p);
937 self.source.push(NONE);
938 self.copy_sources(v, like);
939 self.out.push(NONE);
940 self.gone.push(false);
941 self.copies.push(self.copies[p as usize]);
942 self.copies[p as usize] = v;
943 v
944 }
945}
946
947fn weld_points(positions: &[Point], distance: f64) -> (Vec<Point>, Vec<u32>, Vec<u32>) {
952 #[allow(clippy::cast_possible_truncation, reason = "saturating")]
955 let cell = |p: Point| {
956 (
957 (p.x / distance).floor() as i64,
958 (p.y / distance).floor() as i64,
959 (p.z / distance).floor() as i64,
960 )
961 };
962 let mut grid: FastMap<(i64, i64, i64), Vec<u32>> =
963 FastMap::with_capacity_and_hasher(positions.len(), Default::default());
964 let mut kept: Vec<Point> = Vec::with_capacity(positions.len());
965 let mut founders = Vec::with_capacity(positions.len());
966 let mut remap = Vec::with_capacity(positions.len());
967 for (i, p) in positions.iter().enumerate() {
968 let (x, y, z) = cell(*p);
969 let mut found = NONE;
970 for dx in -1..=1_i64 {
971 for dy in -1..=1_i64 {
972 for dz in -1..=1_i64 {
973 let key = (
974 x.saturating_add(dx),
975 y.saturating_add(dy),
976 z.saturating_add(dz),
977 );
978 if let Some(list) = grid.get(&key)
979 && let Some(&k) = list
980 .iter()
981 .find(|&&k| kept[k as usize].distance(*p) <= distance)
982 {
983 found = found.min(k);
984 }
985 }
986 }
987 }
988 if found == NONE {
989 found = index(kept.len());
990 kept.push(*p);
991 founders.push(index(i));
992 grid.entry((x, y, z)).or_default().push(found);
993 }
994 remap.push(found);
995 }
996 (kept, remap, founders)
997}
998
999fn diameter(points: &[Point]) -> f64 {
1005 struct Node {
1006 lo: Point,
1007 hi: Point,
1008 range: (usize, usize),
1009 children: Option<(usize, usize)>,
1010 }
1011 fn build(points: &mut [Point], at: usize, nodes: &mut Vec<Node>) -> usize {
1012 let lo = points.iter().fold(points[0], |a, &p| a.min(p));
1013 let hi = points.iter().fold(points[0], |a, &p| a.max(p));
1014 let k = nodes.len();
1015 nodes.push(Node {
1016 lo,
1017 hi,
1018 range: (at, at + points.len()),
1019 children: None,
1020 });
1021 if points.len() > 8 {
1023 let size = hi - lo;
1024 let axis = if size.x >= size.y && size.x >= size.z {
1025 0
1026 } else if size.y >= size.z {
1027 1
1028 } else {
1029 2
1030 };
1031 let half = points.len() / 2;
1032 let along = |p: &Point| [p.x, p.y, p.z][axis];
1033 points.select_nth_unstable_by(half, |a, b| along(a).total_cmp(&along(b)));
1034 let (left, right) = points.split_at_mut(half);
1035 let a = build(left, at, nodes);
1036 let b = build(right, at + half, nodes);
1037 nodes[k].children = Some((a, b));
1038 }
1039 k
1040 }
1041 fn reach(a: &Node, b: &Node) -> f64 {
1042 let span =
1043 |lo1: f64, hi1: f64, lo2: f64, hi2: f64| (hi1 - lo2).abs().max((hi2 - lo1).abs());
1044 Vector::new(
1045 span(a.lo.x, a.hi.x, b.lo.x, b.hi.x),
1046 span(a.lo.y, a.hi.y, b.lo.y, b.hi.y),
1047 span(a.lo.z, a.hi.z, b.lo.z, b.hi.z),
1048 )
1049 .magnitude()
1050 }
1051
1052 if points.len() < 2 {
1053 return 0.0;
1054 }
1055 let mut points = points.to_vec();
1056 let mut nodes = Vec::new();
1057 build(&mut points, 0, &mut nodes);
1058 let farthest = |from: Point| {
1061 points
1062 .iter()
1063 .copied()
1064 .max_by(|a, b| a.distance(from).total_cmp(&b.distance(from)))
1065 .unwrap_or(from)
1066 };
1067 let a = farthest(points[0]);
1068 let mut best = a.distance(farthest(a));
1069 let mut stack = vec![(0_usize, 0_usize)];
1070 while let Some((i, j)) = stack.pop() {
1071 let (a, b) = (&nodes[i], &nodes[j]);
1072 if reach(a, b) <= best {
1073 continue;
1074 }
1075 match (a.children, b.children) {
1076 (None, None) => {
1077 for p in &points[a.range.0..a.range.1] {
1078 for q in &points[b.range.0..b.range.1] {
1079 best = best.max(p.distance(*q));
1080 }
1081 }
1082 }
1083 (Some((l, r)), None) => stack.extend([(l, j), (r, j)]),
1084 (None, Some((l, r))) => stack.extend([(i, l), (i, r)]),
1085 (Some((l, r)), Some(_)) if i == j => {
1086 stack.extend([(l, l), (l, r), (r, r)]);
1087 }
1088 (Some((l, r)), Some((m, s))) => {
1089 if a.range.1 - a.range.0 >= b.range.1 - b.range.0 {
1090 stack.extend([(l, j), (r, j)]);
1091 } else {
1092 stack.extend([(i, m), (i, s)]);
1093 }
1094 }
1095 }
1096 }
1097 best
1098}
1099
1100#[cfg(test)]
1101mod tests {
1102 #![allow(
1103 clippy::unwrap_used,
1104 clippy::cast_possible_truncation,
1105 reason = "test code; the f32 casts are the rounding under test"
1106 )]
1107 use super::*;
1108 use core::f64::consts::{SQRT_2, TAU};
1109 use proptest::prelude::*;
1110
1111 pub(super) fn mesh(positions: Vec<Point>, triangles: Vec<[u32; 3]>) -> Triangulation {
1112 Triangulation {
1113 positions,
1114 triangles,
1115 ..Triangulation::new()
1116 }
1117 }
1118
1119 pub(super) fn torus(rings: u32, sides: u32) -> Triangulation {
1122 let mut positions = Vec::new();
1123 for i in 0..rings {
1124 let u = f64::from(i) / f64::from(rings) * TAU;
1125 for j in 0..sides {
1126 let v = f64::from(j) / f64::from(sides) * TAU;
1127 let r = 3.0 + v.cos();
1128 positions.push(Point::new(r * u.cos(), r * u.sin(), v.sin()));
1129 }
1130 }
1131 let at = |i: u32, j: u32| (i % rings) * sides + (j % sides);
1132 let mut triangles = Vec::new();
1133 for i in 0..rings {
1134 for j in 0..sides {
1135 let (a, b, c, d) = (at(i, j), at(i + 1, j), at(i + 1, j + 1), at(i, j + 1));
1136 triangles.push([a, b, c]);
1137 triangles.push([a, c, d]);
1138 }
1139 }
1140 mesh(positions, triangles)
1141 }
1142
1143 pub(super) fn grid(n: u32) -> Triangulation {
1146 let mut positions = Vec::new();
1147 for j in 0..=n {
1148 for i in 0..=n {
1149 positions.push(Point::new(f64::from(i), f64::from(j), 0.0));
1150 }
1151 }
1152 let at = |i: u32, j: u32| j * (n + 1) + i;
1153 let mut triangles = Vec::new();
1154 for j in 0..n {
1155 for i in 0..n {
1156 triangles.push([at(i, j), at(i + 1, j), at(i + 1, j + 1)]);
1157 triangles.push([at(i, j), at(i + 1, j + 1), at(i, j + 1)]);
1158 }
1159 }
1160 mesh(positions, triangles)
1161 }
1162
1163 fn tetrahedron(points: [Point; 4], first: u32) -> Vec<[u32; 3]> {
1166 [[0, 1, 2], [0, 1, 3], [0, 2, 3], [1, 2, 3]]
1167 .into_iter()
1168 .map(|[a, b, c]| {
1169 let opposite = 6 - a - b - c;
1170 let [pa, pb, pc] = [a, b, c].map(|k| points[k]);
1171 let normal = (pb - pa).cross(pc - pa);
1172 let t = if normal.dot(points[opposite] - pa) < 0.0 {
1173 [a, b, c]
1174 } else {
1175 [a, c, b]
1176 };
1177 t.map(|k| first + index(k))
1178 })
1179 .collect()
1180 }
1181
1182 pub(super) fn cube_of_grids(n: u32, h: f64) -> Triangulation {
1185 let side = f64::from(n) * h;
1186 let mut out = Triangulation::new();
1187 let faces = [
1190 ([0.0, 0.0, 0.0], [0.0, 1.0, 0.0], [1.0, 0.0, 0.0]),
1191 ([0.0, 0.0, side], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]),
1192 ([0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 0.0, 1.0]),
1193 ([0.0, side, 0.0], [0.0, 0.0, 1.0], [1.0, 0.0, 0.0]),
1194 ([0.0, 0.0, 0.0], [0.0, 0.0, 1.0], [0.0, 1.0, 0.0]),
1195 ([side, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]),
1196 ];
1197 for (o, u, v) in faces {
1198 let mut g = grid(n);
1199 for p in &mut g.positions {
1200 let (a, b) = (p.x * h, p.y * h);
1201 *p = Point::new(
1202 o[0] + a * u[0] + b * v[0],
1203 o[1] + a * u[1] + b * v[1],
1204 o[2] + a * u[2] + b * v[2],
1205 );
1206 }
1207 out.append(&g);
1208 }
1209 out
1210 }
1211
1212 fn soup(m: &Triangulation) -> Triangulation {
1214 let mut out = Triangulation::new();
1215 for t in &m.triangles {
1216 let first = index(out.positions.len());
1217 out.positions.extend(t.map(|v| m.positions[v as usize]));
1218 out.triangles.push([first, first + 1, first + 2]);
1219 }
1220 out
1221 }
1222
1223 pub(super) fn check(e: &Editable) {
1227 let mut at: Vec<Vec<u32>> = vec![Vec::new(); e.positions.len()];
1228 for f in 0..e.corners.len() {
1229 if !e.live[f] {
1230 continue;
1231 }
1232 for h in index(3 * f)..index(3 * f + 3) {
1233 let v = e.tail(h);
1234 assert!(!e.gone[v as usize], "face {f} names gone vertex {v}");
1235 at[v as usize].push(h);
1236 let g = e.twin[h as usize];
1237 if g != NONE {
1238 assert!(e.live[(g / 3) as usize]);
1239 assert_eq!(e.twin[g as usize], h);
1240 assert_eq!((e.tail(g), e.head(g)), (e.head(h), e.tail(h)));
1241 }
1242 }
1243 }
1244 for (v, leaving) in at.iter().enumerate() {
1245 let start = e.out[v];
1246 if leaving.is_empty() {
1247 assert_eq!(start, NONE, "vertex {v} has no face");
1248 continue;
1249 }
1250 if leaving.iter().any(|&h| e.twin[h as usize] == NONE) {
1251 assert_eq!(
1252 e.twin[start as usize], NONE,
1253 "vertex {v} starts off its rim"
1254 );
1255 }
1256 let mut swept = Vec::new();
1257 let mut h = start;
1258 loop {
1259 swept.push(h);
1260 h = e.twin[prev(h) as usize];
1261 if h == NONE || h == start {
1262 break;
1263 }
1264 }
1265 swept.sort_unstable();
1266 assert_eq!(&swept, leaving, "vertex {v} has more than one fan");
1267 assert!(e.ring(e.point[v]).contains(&index(v)));
1268 }
1269 }
1270
1271 fn reads_back(e: &Editable, m: &Triangulation) {
1272 let back = e.to_triangulation();
1273 assert_eq!(back.positions, m.positions);
1274 assert_eq!(back.triangles, m.triangles);
1275 }
1276
1277 #[test]
1278 fn a_manifold_mesh_reads_back_unchanged() {
1279 let closed = torus(12, 8);
1280 let e = Editable::from(&closed, 0.0).unwrap();
1281 check(&e);
1282 reads_back(&e, &closed);
1283 assert!(e.is_closed());
1284 assert!(e.rims().is_empty());
1285 assert!(e.non_manifold().is_empty());
1286 assert_eq!(e.components().len(), 1);
1287 assert!((0..e.vertex_count()).all(|v| e.source_vertex(index(v)) == Some(index(v))));
1288
1289 let open = grid(5);
1290 let e = Editable::from(&open, 0.0).unwrap();
1291 check(&e);
1292 reads_back(&e, &open);
1293 assert!(!e.is_closed());
1294 assert!(e.non_manifold().is_empty());
1295 let rims = e.rims();
1296 assert_eq!(rims.len(), 1);
1297 assert_eq!(rims[0].edges.len(), 20);
1298 assert_eq!(rims[0].length, 20.0);
1300 assert_eq!(rims[0].span, 50.0_f64.sqrt());
1301 let pieces = e.components();
1302 assert_eq!(pieces.len(), 1);
1303 assert_eq!(pieces[0].area, 25.0);
1304 }
1305
1306 #[test]
1307 fn three_islands_are_three_components_with_their_bounds() {
1308 let mut positions = Vec::new();
1309 let mut triangles = Vec::new();
1310 let corners = [[0.0, 0.0, 0.0], [5.0, 1.0, -2.0], [-4.0, 3.0, 7.0]];
1311 let sizes = [1.0, 2.0, 0.5];
1312 for (o, s) in corners.into_iter().zip(sizes) {
1313 let o = Point::new(o[0], o[1], o[2]);
1314 let points = [
1315 o,
1316 o + Vector::new(s, 0.0, 0.0),
1317 o + Vector::new(0.0, s, 0.0),
1318 o + Vector::new(0.0, 0.0, s),
1319 ];
1320 triangles.extend(tetrahedron(points, index(positions.len())));
1321 positions.extend(points);
1322 }
1323 let m = mesh(positions, triangles);
1324 let e = Editable::from(&m, 0.0).unwrap();
1325 check(&e);
1326 assert!(e.is_closed());
1327 let pieces = e.components();
1328 assert_eq!(pieces.len(), 3);
1329 for (k, (piece, (o, s))) in pieces
1330 .iter()
1331 .zip(corners.into_iter().zip(sizes))
1332 .enumerate()
1333 {
1334 let k = index(k);
1335 assert_eq!(piece.faces, vec![4 * k, 4 * k + 1, 4 * k + 2, 4 * k + 3]);
1336 let o = Point::new(o[0], o[1], o[2]);
1337 assert_eq!(piece.bounds, Aabb::of_corners(o, o + Vector::new(s, s, s)));
1338 let area = 1.5 * s * s + 3.0_f64.sqrt() / 2.0 * s * s;
1341 assert!((piece.area - area).abs() < 1e-14 * area);
1342 }
1343 }
1344
1345 #[test]
1346 fn removing_a_patch_leaves_one_rim_round_it() {
1347 let (n, h) = (6, 0.5);
1348 let side = f64::from(n) * h;
1349 let cube = cube_of_grids(n, h);
1350 let mut e = Editable::from(&cube, 1e-9).unwrap();
1351 check(&e);
1352 assert!(e.is_closed());
1353 assert_eq!(e.vertex_count(), 6 * 36 + 2);
1354 assert!(e.non_manifold().is_empty());
1355 let patch: Vec<u32> = (0..e.face_count())
1357 .map(index)
1358 .filter(|&f| {
1359 let ps = e.face(f).unwrap().map(|v| e.position(v).unwrap());
1360 let c = (ps[0] + (ps[1] - ps[0]) / 3.0) + (ps[2] - ps[0]) / 3.0;
1361 ps.iter().all(|p| p.z == side)
1362 && (c.x - side / 2.0).abs() < h
1363 && (c.y - side / 2.0).abs() < h
1364 })
1365 .collect();
1366 assert_eq!(patch.len(), 8);
1367 e.remove_faces(&patch);
1368 check(&e);
1369 assert!(!e.is_closed());
1370 assert!(e.non_manifold().is_empty());
1371 let rims = e.rims();
1372 assert_eq!(rims.len(), 1);
1373 assert_eq!(rims[0].edges.len(), 8);
1374 assert_eq!(rims[0].length, 8.0 * h);
1376 assert_eq!(rims[0].span, 2.0_f64.sqrt());
1377 let [a, b] = e.edge(rims[0].edges[0]).unwrap();
1380 let [_, c] = e.edge(rims[0].edges[1]).unwrap();
1381 let [a, b, c] = [a, b, c].map(|v| e.position(v).unwrap());
1382 assert!((b - a).cross(c - b).z <= 0.0);
1383 let pieces = e.components();
1384 assert_eq!(pieces.len(), 1);
1385 assert_eq!(pieces[0].area, 6.0 * side * side - 4.0 * h * h);
1386 let back = e.to_triangulation();
1388 assert_eq!(back.positions.len(), 6 * 36 + 2 - 1);
1389 assert_eq!(back.triangles.len(), cube.triangles.len() - 8);
1390 }
1391
1392 #[test]
1393 fn a_bowtie_is_split_into_a_vertex_per_fan() {
1394 let positions = vec![
1395 Point::new(0.0, 0.0, 0.0),
1396 Point::new(1.0, 0.0, 0.0),
1397 Point::new(1.0, 1.0, 0.0),
1398 Point::new(-1.0, 0.0, 0.0),
1399 Point::new(-1.0, -1.0, 0.0),
1400 ];
1401 let m = mesh(positions.clone(), vec![[0, 1, 2], [0, 3, 4]]);
1402 let e = Editable::from(&m, 0.0).unwrap();
1403 check(&e);
1404 assert_eq!(e.vertex_count(), 6);
1405 assert_eq!(e.non_manifold(), vec![0, 5]);
1406 assert_eq!(e.source_vertex(5), Some(0));
1407 assert_eq!(e.components().len(), 2);
1408 let rims = e.rims();
1409 assert_eq!(rims.len(), 2);
1410 assert_eq!(rims[0].length, 1.0 + SQRT_2 + 1.0);
1411 let back = e.to_triangulation();
1412 assert_eq!(back.triangles, vec![[0, 1, 2], [5, 3, 4]]);
1413 assert_eq!(back.positions[5], positions[0]);
1414 assert_eq!(&back.positions[..5], &positions[..]);
1415 }
1416
1417 #[test]
1418 fn removing_faces_splits_the_pinch_they_leave() {
1419 let m = grid(2);
1422 let mut e = Editable::from(&m, 0.0).unwrap();
1423 e.remove_faces(&[0, 1, 6, 7]);
1424 check(&e);
1425 assert_eq!(e.non_manifold(), vec![4, 9]);
1426 assert_eq!(e.source_vertex(9), Some(4));
1427 assert_eq!(e.components().len(), 2);
1428 let rims = e.rims();
1429 assert_eq!(rims.len(), 2);
1430 assert!(rims.iter().all(|r| r.length == 4.0));
1431 e.flip_faces(&[2, 3, 4, 5]);
1434 check(&e);
1435 assert_eq!(e.non_manifold(), vec![4, 9]);
1436 }
1437
1438 #[test]
1439 fn an_edge_of_three_faces_joins_the_sheet_and_leaves_the_fin() {
1440 let m = mesh(
1442 vec![
1443 Point::new(0.0, 0.0, 0.0),
1444 Point::new(1.0, 0.0, 0.0),
1445 Point::new(0.5, 1.0, 0.0),
1446 Point::new(0.5, -1.0, 0.0),
1447 Point::new(0.5, 0.0, 1.0),
1448 ],
1449 vec![[0, 1, 2], [1, 0, 3], [0, 1, 4]],
1450 );
1451 let e = Editable::from(&m, 0.0).unwrap();
1452 check(&e);
1453 assert_eq!(e.twin[0], 3);
1454 assert_eq!(e.face(2), Some([5, 6, 4]));
1455 assert_eq!(e.non_manifold(), vec![0, 1, 5, 6]);
1456 assert_eq!(e.source_vertex(5), Some(0));
1457 assert_eq!(e.source_vertex(6), Some(1));
1458 let pieces = e.components();
1459 assert_eq!(pieces.len(), 2);
1460 assert_eq!(pieces[1].faces, vec![2]);
1461 }
1462
1463 #[test]
1464 fn two_bodies_sharing_an_edge_each_close() {
1465 let points = [
1466 Point::new(0.0, 0.0, 0.0),
1467 Point::new(1.0, 0.0, 0.0),
1468 Point::new(0.5, 1.0, 0.0),
1469 Point::new(0.5, 0.5, 1.0),
1470 Point::new(0.5, -1.0, 0.0),
1471 Point::new(0.5, -0.5, -1.0),
1472 ];
1473 let first = tetrahedron([points[0], points[1], points[2], points[3]], 0);
1474 let mut second = tetrahedron([points[0], points[1], points[4], points[5]], 0);
1475 for t in &mut second {
1476 *t = t.map(|v| if v >= 2 { v + 2 } else { v });
1477 }
1478 let m = mesh(points.to_vec(), [first, second].concat());
1479 let e = Editable::from(&m, 0.0).unwrap();
1480 check(&e);
1481 assert!(e.is_closed());
1482 assert_eq!(e.components().len(), 2);
1483 assert_eq!(e.non_manifold(), vec![0, 1, 6, 7]);
1484 }
1485
1486 #[test]
1487 fn flipping_faces_reopens_and_recloses_edges() {
1488 let closed = torus(10, 6);
1489 let mut e = Editable::from(&closed, 0.0).unwrap();
1490 let volume = closed.volume();
1491
1492 let all: Vec<u32> = (0..e.face_count()).map(index).collect();
1493 e.flip_faces(&all);
1494 check(&e);
1495 assert!(e.is_closed());
1496 assert!(e.non_manifold().is_empty());
1497 let flipped = e.to_triangulation();
1498 assert_eq!(flipped.volume(), -volume);
1500 e.flip_faces(&all);
1501 reads_back(&e, &closed);
1502
1503 e.flip_faces(&[17, 17]);
1505 check(&e);
1506 assert!(!e.is_closed());
1507 let rims = e.rims();
1508 assert_eq!(rims.len(), 2);
1509 assert!((rims[0].length - rims[1].length).abs() <= 4.0 * f64::EPSILON * rims[0].length);
1511 assert_eq!(e.components().len(), 2);
1512 assert!(!e.non_manifold().is_empty());
1513 assert!(
1514 (0..e.vertex_count())
1515 .filter_map(|v| e.position(index(v)).map(|p| (v, p)))
1516 .all(|(v, p)| closed.positions[e.source_vertex(index(v)).unwrap() as usize] == p)
1517 );
1518 e.flip_faces(&[17]);
1519 check(&e);
1520 assert!(e.is_closed());
1521 assert!(e.non_manifold().is_empty());
1522 reads_back(&e, &closed);
1523 }
1524
1525 #[test]
1526 fn sources_survive_welding_removal_and_flips() {
1527 let pieces = soup(&grid(4));
1528 let mut e = Editable::from(&pieces, 1e-9).unwrap();
1529 check(&e);
1530 assert_eq!(e.vertex_count(), 25);
1531 for v in 0..25 {
1533 let s = e.source_vertex(v).unwrap() as usize;
1534 let p = e.position(v).unwrap();
1535 assert_eq!(pieces.positions[s], p);
1536 assert_eq!(pieces.positions.iter().position(|&q| q == p), Some(s));
1537 }
1538 e.remove_faces(&[0, 1, 10, 11, 12, 13, 22, 23]);
1539 e.flip_faces(&[2, 3, 30, 31]);
1540 check(&e);
1541 assert!(e.vertex_count() > 25);
1542 for v in 0..e.vertex_count() {
1543 if let Some(p) = e.position(index(v)) {
1544 let s = e.source_vertex(index(v)).unwrap();
1545 assert_eq!(pieces.positions[s as usize], p);
1546 }
1547 }
1548 assert_eq!(e.source_vertex(index(e.vertex_count())), None);
1549 }
1550
1551 #[test]
1552 fn single_precision_copies_weld_within_their_quantum() {
1553 let cube = cube_of_grids(1, 100.0);
1556 let mut pieces = soup(&cube);
1557 let mut seen: Vec<Point> = Vec::new();
1558 for p in &mut pieces.positions {
1559 let single = |x: f64| f64::from(x as f32);
1560 let up = |x: f64| f64::from(f32::from_bits((x as f32).to_bits() + 1));
1561 let q = Point::new(single(p.x), single(p.y), single(p.z));
1562 *p = if seen.contains(&q) {
1563 Point::new(up(q.x), q.y, up(q.z))
1564 } else {
1565 seen.push(q);
1566 q
1567 };
1568 }
1569 let quantum = 2.0_f64.powi(-17);
1571 let e = Editable::from(&pieces, 2.0 * quantum).unwrap();
1572 check(&e);
1573 assert!(e.is_closed());
1574 assert_eq!(e.to_triangulation().positions.len(), 8);
1575 for v in 0..8 {
1576 let s = e.source_vertex(v).unwrap() as usize;
1577 assert_eq!(e.position(v), Some(seen[v as usize]));
1578 assert_eq!(pieces.positions[s], seen[v as usize]);
1579 }
1580 let apart = Editable::from(&pieces, quantum / 2.0).unwrap();
1582 assert!(!apart.is_closed());
1583 }
1584
1585 #[test]
1586 fn a_weld_tie_goes_to_the_lowest_kept_point() {
1587 let points = [1.5, 0.0, 0.75].map(|x| Point::new(x, 0.0, 0.0));
1590 let (kept, remap, founders) = weld_points(&points, 1.0);
1591 assert_eq!(kept.len(), 2);
1592 assert_eq!(remap, vec![0, 1, 0]);
1593 assert_eq!(founders, vec![0, 1]);
1594 }
1595
1596 #[test]
1597 fn bad_input_is_refused() {
1598 let refusal = |m: &Triangulation, weld: f64| Editable::from(m, weld).unwrap_err();
1599 let m = mesh(vec![Point::new(0.0, 0.0, 0.0); 3], vec![[0, 1, 3]]);
1600 let e = refusal(&m, 0.0);
1601 assert!(matches!(e, ogeom_core::OgeomError::Range(_)));
1602 assert!(e.to_string().contains("names vertex 3 of 3"), "{e}");
1603 let m = grid(1);
1604 for weld in [-1.0, f64::NAN] {
1605 let e = refusal(&m, weld);
1606 assert!(matches!(e, ogeom_core::OgeomError::Numeric(_)));
1607 assert!(e.to_string().contains("weld distance"), "{e}");
1608 }
1609 let mut m = grid(1);
1610 m.positions[2].x = f64::NAN;
1611 let e = refusal(&m, 0.0);
1612 assert!(e.to_string().contains("vertex 2 is not finite"), "{e}");
1613 }
1614
1615 #[test]
1616 fn an_editable_mesh_crosses_threads() {
1617 fn send_sync<T: Send + Sync>() {}
1618 send_sync::<Editable>();
1619 }
1620
1621 #[test]
1622 fn the_span_is_the_farthest_pair() {
1623 let points: Vec<Point> = (0..200)
1624 .map(|k| {
1625 let t = f64::from(k) * 0.37;
1626 Point::new(t.sin() * 3.0, (t * 1.7).cos() * 2.0, (t * 0.3).sin())
1627 })
1628 .collect();
1629 let brute = points
1630 .iter()
1631 .flat_map(|p| points.iter().map(move |q| p.distance(*q)))
1632 .fold(0.0, f64::max);
1633 assert_eq!(diameter(&points), brute);
1634 }
1635
1636 proptest! {
1637 #![proptest_config(ProptestConfig::with_cases(64))]
1638
1639 #[test]
1642 fn edits_keep_a_sound_manifold(
1643 edits in prop::collection::vec(
1644 (any::<bool>(), prop::collection::vec(0_u32..60, 0..8)),
1645 1..6,
1646 )
1647 ) {
1648 let mut e = Editable::from(&torus(6, 5), 0.0).unwrap();
1649 for (remove, faces) in &edits {
1650 if *remove {
1651 e.remove_faces(faces);
1652 } else {
1653 e.flip_faces(faces);
1654 }
1655 check(&e);
1656 }
1657 let written = e.to_triangulation();
1658 let again = Editable::from(&written, 0.0).unwrap();
1659 prop_assert_eq!(again.to_triangulation(), written);
1660 prop_assert_eq!(again.is_closed(), e.is_closed());
1661 prop_assert_eq!(again.components().len(), e.components().len());
1662 let lengths = |m: &Editable| {
1663 let mut l: Vec<usize> = m.rims().iter().map(|r| r.edges.len()).collect();
1664 l.sort_unstable();
1665 l
1666 };
1667 prop_assert_eq!(lengths(&again), lengths(&e));
1668 }
1669 }
1670}