1use std::collections::BTreeSet;
28
29use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};
33
34use crate::linalg::Vec3;
35use crate::types::Box;
36
37use super::arrangement::{self, ArrangementInput};
38use super::exact::rational::{r3_eq, R3};
39use super::tri_tri::{tri_tri_intersect, TriTriIsect};
40
41use super::graph_geom::{
42 approx3, box3_contains, clip_segment_to_polygon, point_in_polygon_coplanar, point_on_segment_f,
43 seg_box3,
44};
45use super::graph_types::{bit_edge_key, geo_edge_key, BitEdgeKey, GeoEdgeKey, PointTable};
46
47pub(super) use super::graph_geom::{is_degenerate, tri_box};
50pub(super) use super::graph_self_cut::{real_self_contact, SelfCutStats};
51pub use super::graph_types::{edge_key, EdgeKey, IntersectionGraph, Piece, VertInterner};
52
53#[derive(Clone, Debug, Default)]
56struct TriPrims {
57 points: Vec<(R3, usize)>,
58 segments: Vec<(R3, R3, usize)>,
59}
60
61pub fn build_graph(p: &[[Vec3; 3]], q: &[[Vec3; 3]]) -> IntersectionGraph {
64 build_graph_with_token(p, q, None).expect("uncancellable build_graph cannot cancel")
65}
66
67pub fn build_graph_with_token(
74 p: &[[Vec3; 3]],
75 q: &[[Vec3; 3]],
76 token: Option<&crate::cancel::CancelToken>,
77) -> Option<IntersectionGraph> {
78 build_graph_with_progress(p, q, token, None)
79}
80
81pub fn build_graph_with_progress(
86 p: &[[Vec3; 3]],
87 q: &[[Vec3; 3]],
88 token: Option<&crate::cancel::CancelToken>,
89 progress: Option<&crate::progress::ProgressReporter>,
90) -> Option<IntersectionGraph> {
91 use crate::progress::{begin_phase, maybe_par_map_ct_progress, Phase};
92 let cancelled = || crate::cancel::is_cancelled(token);
93 let t_all = crate::timing::start();
94 let meshes: [&[[Vec3; 3]]; 2] = [p, q];
95 let live: [Vec<bool>; 2] = [
96 p.iter().map(|t| !is_degenerate(t)).collect(),
97 q.iter().map(|t| !is_degenerate(t)).collect(),
98 ];
99
100 let p_boxes: Vec<Box> = p.iter().map(tri_box).collect();
108 let q_boxes: Vec<Box> = q.iter().map(tri_box).collect();
109
110 let scene_box = q_boxes
111 .iter()
112 .enumerate()
113 .filter(|(qi, _)| live[1][*qi])
114 .fold(Box::new(), |acc, (_, b)| acc.union_box(b));
115 let mut q_order: Vec<usize> = (0..q.len()).filter(|&qi| live[1][qi]).collect();
116 q_order.sort_by_key(|&qi| crate::sort::morton_code(q_boxes[qi].center(), &scene_box));
117 let leaf_boxes: Vec<Box> = q_order.iter().map(|&qi| q_boxes[qi]).collect();
118 let leaf_morton: Vec<u32> = q_order
119 .iter()
120 .map(|&qi| crate::sort::morton_code(q_boxes[qi].center(), &scene_box))
121 .collect();
122 let collider = crate::collider::Collider::new(leaf_boxes, leaf_morton);
123
124 let mut prims: [Vec<TriPrims>; 2] = [
126 vec![TriPrims::default(); p.len()],
127 vec![TriPrims::default(); q.len()],
128 ];
129 let mut coplanar_regions: Vec<(usize, usize, Vec<R3>)> = Vec::new();
132 let mut any_intersections = false;
133 let mut pair_count = 0usize;
134
135 let mut candidates_q: Vec<usize> = Vec::new();
136 begin_phase(progress, Phase::NarrowPhase, p.len() as u64);
137 for (pi, pt) in p.iter().enumerate() {
138 if cancelled() {
139 return None;
140 }
141 if let Some(pr) = progress {
142 pr.advance(1);
143 }
144 if !live[0][pi] {
145 continue;
146 }
147 candidates_q.clear();
148 collider.collisions_one(&p_boxes[pi], pi, |_, leaf| {
149 candidates_q.push(q_order[leaf]);
150 });
151 candidates_q.sort_unstable();
152 for &qi in &candidates_q {
153 let qt = &q[qi];
154 if !p_boxes[pi].does_overlap_box(&q_boxes[qi]) {
155 continue;
156 }
157 let isect = tri_tri_intersect(*pt, *qt);
158 let pair = pair_count;
159 match isect {
160 TriTriIsect::None => continue,
161 TriTriIsect::Point(x) => {
162 prims[0][pi].points.push((x.clone(), pair));
163 prims[1][qi].points.push((x, pair));
164 }
165 TriTriIsect::Segment(x, y) => {
166 prims[0][pi].segments.push((x.clone(), y.clone(), pair));
167 prims[1][qi].segments.push((x, y, pair));
168 }
169 TriTriIsect::Coplanar { polygon, .. } => {
170 for i in 0..polygon.len() {
171 let a = polygon[i].clone();
172 let b = polygon[(i + 1) % polygon.len()].clone();
173 prims[0][pi].segments.push((a.clone(), b.clone(), pair));
174 prims[1][qi].segments.push((a, b, pair));
175 }
176 coplanar_regions.push((pi, qi, polygon));
177 }
178 }
179 any_intersections = true;
180 pair_count += 1;
181 }
182 }
183
184 crate::timing::print("robust: pair narrow phase", t_all);
185 let t_self = crate::timing::start();
186
187 begin_phase(
196 progress,
197 Phase::SelfIntersections,
198 (p.len() + q.len()) as u64,
199 );
200 for m in 0..2 {
201 let (tris, boxes) = if m == 0 {
202 (p, &p_boxes)
203 } else {
204 (q, &q_boxes)
205 };
206 let self_scene = boxes
207 .iter()
208 .enumerate()
209 .filter(|(i, _)| live[m][*i])
210 .fold(Box::new(), |acc, (_, b)| acc.union_box(b));
211 let mut order: Vec<usize> = (0..tris.len()).filter(|&i| live[m][i]).collect();
212 order.sort_by_key(|&i| crate::sort::morton_code(boxes[i].center(), &self_scene));
213 let self_collider = crate::collider::Collider::new(
214 order.iter().map(|&i| boxes[i]).collect(),
215 order
216 .iter()
217 .map(|&i| crate::sort::morton_code(boxes[i].center(), &self_scene))
218 .collect(),
219 );
220 let mut n_pairs = 0usize;
225 let mut n_cut = 0usize;
226 let mut stats = SelfCutStats::default();
227 let contact_results = maybe_par_map_ct_progress(tris.len(), 64, token, progress, |i| {
228 let mut local = SelfCutStats::default();
229 let mut contacts: Vec<(usize, Vec<(R3, R3)>)> = Vec::new();
230 let mut local_pairs = 0usize;
231 if live[m][i] {
232 let mut cands: Vec<usize> = Vec::new();
233 self_collider.collisions_one(&boxes[i], i, |_, leaf| {
234 cands.push(order[leaf]);
235 });
236 cands.sort_unstable();
237 for &j in &cands {
238 if j <= i || !boxes[i].does_overlap_box(&boxes[j]) {
239 continue;
240 }
241 local_pairs += 1;
242 if let Some(segs) = real_self_contact(tris[i], tris[j], &mut local) {
243 contacts.push((j, segs));
244 }
245 }
246 }
247 (contacts, local, local_pairs)
248 })?;
249 for (i, (contacts, local, local_pairs)) in contact_results.into_iter().enumerate() {
250 n_pairs += local_pairs;
251 stats.add(&local);
252 for (j, segs) in contacts {
253 n_cut += 1;
254 for (x, y) in segs {
255 let pair = pair_count;
256 prims[m][i].segments.push((x.clone(), y.clone(), pair));
257 prims[m][j].segments.push((x, y, pair));
258 pair_count += 1;
259 }
260 }
261 }
262 crate::timing::print_count(
263 &format!("robust: self-cut mesh {m}: {n_pairs} box pairs, {n_cut} cutting"),
264 );
265 crate::timing::print_count(&format!(
266 "robust: self-cut mesh {m} tri_tri exits: {}",
267 super::tri_tri::stats::snapshot_and_reset()
268 ));
269 crate::timing::print_count(&format!(
270 "robust: self-cut mesh {m} paths: identical {}, edge-benign {}, vert-benign {}, \
271 full {} ({:.3}s: none {}, point {}, seg-benign {})",
272 stats.identical,
273 stats.edge_benign,
274 stats.vert_benign,
275 stats.full,
276 stats.full_secs,
277 stats.full_none,
278 stats.full_point,
279 stats.full_seg_benign,
280 ));
281 }
282
283 crate::timing::print("robust: self-intersection cuts", t_self);
284 let t_cross = crate::timing::start();
285
286 for (pi, qi, poly) in &coplanar_regions {
290 if cancelled() {
291 return None;
292 }
293 let from_p: TriPrims = prims[0][*pi].clone();
294 let from_q: TriPrims = prims[1][*qi].clone();
295 let copy = |src: &TriPrims, dst: &mut TriPrims| {
296 for (a, b, prov) in &src.segments {
297 if let Some((ca, cb)) = clip_segment_to_polygon(a, b, poly) {
298 if !dst
299 .segments
300 .iter()
301 .any(|(x, y, pv)| pv == prov && ((x, y) == (&ca, &cb) || (x, y) == (&cb, &ca)))
302 {
303 dst.segments.push((ca, cb, *prov));
304 }
305 }
306 }
307 for (pt, prov) in &src.points {
308 if clip_segment_to_polygon(pt, pt, poly).is_some()
309 || point_in_polygon_coplanar(pt, poly)
310 {
311 if !dst.points.iter().any(|(x, pv)| pv == prov && x == pt) {
312 dst.points.push((pt.clone(), *prov));
313 }
314 }
315 }
316 };
317 copy(&from_p, &mut prims[1][*qi]);
318 copy(&from_q, &mut prims[0][*pi]);
319 }
320
321 crate::timing::print("robust: coplanar cross-copy", t_cross);
322 let t_cand = crate::timing::start();
323
324 let mut candidates: [Vec<Option<Vec<u32>>>; 2] = [vec![None; p.len()], vec![None; q.len()]];
334 let mut ptab = PointTable::default();
335 let cand_work: Vec<(usize, usize)> = (0..2)
336 .flat_map(|m| (0..meshes[m].len()).map(move |ti| (m, ti)))
337 .filter(|&(m, ti)| {
338 let pr = &prims[m][ti];
339 !pr.points.is_empty() || !pr.segments.is_empty()
340 })
341 .collect();
342 begin_phase(progress, Phase::CandidatePoints, cand_work.len() as u64);
343 const CAND_CHUNK: usize = 1 << 16;
350 let mut n_cand_total = 0usize;
351 let mut base = 0usize;
352 while base < cand_work.len() {
353 let len = CAND_CHUNK.min(cand_work.len() - base);
354 let cand_results = maybe_par_map_ct_progress(len, 16, token, progress, |i| {
355 let (m, ti) = cand_work[base + i];
356 let pr = &prims[m][ti];
357 let input = ArrangementInput {
358 points: pr.points.clone(),
359 segments: pr.segments.clone(),
360 };
361 arrangement::candidate_points(meshes[m][ti], &input, token)
362 })?;
363 for (k, (&(m, ti), cands)) in cand_work[base..base + len]
364 .iter()
365 .zip(cand_results)
366 .enumerate()
367 {
368 if k % 1024 == 0 && cancelled() {
369 return None;
370 }
371 let cands = cands?;
372 n_cand_total += cands.len();
373 candidates[m][ti] = Some(cands.iter().map(|pt| ptab.intern(pt)).collect());
374 }
375 base += len;
376 }
377
378 let mut seg_off: [Vec<usize>; 2] = [Vec::new(), Vec::new()];
383 let mut seg_ends: [Vec<(u32, u32)>; 2] = [Vec::new(), Vec::new()];
384 for m in 0..2 {
385 seg_off[m].reserve(meshes[m].len() + 1);
386 seg_off[m].push(0);
387 for ti in 0..meshes[m].len() {
388 if ti % 1024 == 0 && cancelled() {
389 return None;
390 }
391 for (a, b, _) in &prims[m][ti].segments {
392 let ia = ptab.intern(a);
393 let ib = ptab.intern(b);
394 seg_ends[m].push((ia, ib));
395 }
396 seg_off[m].push(seg_ends[m].len());
397 }
398 }
399
400 let pts: Vec<&R3> = ptab.resolve();
404 let pts_a: Vec<[f64; 3]> = pts.iter().map(|p| approx3(p)).collect();
405
406 crate::timing::print("robust: candidate points", t_cand);
407 crate::timing::print_count(&format!(
408 "robust: candidate points: {n_cand_total} total, {} interned (incl. segment endpoints), \
409 {} segment instances",
410 ptab.len(),
411 seg_ends[0].len() + seg_ends[1].len()
412 ));
413
414 let t_reg = crate::timing::start();
415
416 let reg_work: Vec<(usize, usize)> = (0..2)
425 .flat_map(|m| (0..meshes[m].len()).map(move |ti| (m, ti)))
426 .filter(|&(m, ti)| candidates[m][ti].is_some())
427 .collect();
428 begin_phase(progress, Phase::Registries, 2 * reg_work.len() as u64);
431
432 let mut edge_registry: [HashMap<BitEdgeKey, (Vec<u32>, HashSet<u32>)>; 2] =
441 [HashMap::default(), HashMap::default()];
442 let edge_hits = maybe_par_map_ct_progress(reg_work.len(), 16, token, progress, |i| {
443 let (m, ti) = reg_work[i];
444 let cands = candidates[m][ti].as_ref().expect("filtered to Some");
445 let t = meshes[m][ti];
446 let corners = [
447 R3::from_vec3(t[0]),
448 R3::from_vec3(t[1]),
449 R3::from_vec3(t[2]),
450 ];
451 let ca: [[f64; 3]; 3] = [
452 [t[0].x, t[0].y, t[0].z],
453 [t[1].x, t[1].y, t[1].z],
454 [t[2].x, t[2].y, t[2].z],
455 ];
456 let mut hits: Vec<(BitEdgeKey, u32)> = Vec::new();
457 for e in 0..3 {
458 let a = &corners[e];
459 let b = &corners[(e + 1) % 3];
460 let key = bit_edge_key(t[e], t[(e + 1) % 3]);
461 let sbox = seg_box3(ca[e], ca[(e + 1) % 3]);
462 for &id in cands.iter() {
463 let (pt, pt_a) = (pts[id as usize], pts_a[id as usize]);
464 if !box3_contains(&sbox, pt_a) {
467 continue;
468 }
469 if !r3_eq(pt, a)
470 && !r3_eq(pt, b)
471 && point_on_segment_f(pt_a, pt, ca[e], a, ca[(e + 1) % 3], b)
472 {
473 hits.push((key, id));
474 }
475 }
476 }
477 hits
478 })?;
479 for (k, (&(m, _), hits)) in reg_work.iter().zip(&edge_hits).enumerate() {
480 if k % 1024 == 0 && cancelled() {
481 return None;
482 }
483 for &(key, id) in hits {
484 let e = edge_registry[m].entry(key).or_default();
485 if e.1.insert(id) {
486 e.0.push(id);
487 }
488 }
489 }
490 let n_edge_hits: usize = edge_hits.iter().map(|h| h.len()).sum();
491 drop(edge_hits);
492
493 let mut seg_splits: HashMap<GeoEdgeKey, (Vec<u32>, HashSet<u32>)> = HashMap::default();
502 let split_hits = maybe_par_map_ct_progress(reg_work.len(), 16, token, progress, |i| {
503 let (m, ti) = reg_work[i];
504 let cands = candidates[m][ti].as_ref().expect("filtered to Some");
505 let mut hits: Vec<(GeoEdgeKey, u32)> = Vec::new();
506 for &(ia, ib) in &seg_ends[m][seg_off[m][ti]..seg_off[m][ti + 1]] {
507 let key = geo_edge_key(ia, ib);
508 let (a, b) = (pts[ia as usize], pts[ib as usize]);
509 let (aa, ba) = (pts_a[ia as usize], pts_a[ib as usize]);
510 let sbox = seg_box3(aa, ba);
511 for &id in cands.iter() {
512 let (pt, pt_a) = (pts[id as usize], pts_a[id as usize]);
513 if !box3_contains(&sbox, pt_a) {
514 continue;
515 }
516 if id != ia && id != ib && point_on_segment_f(pt_a, pt, aa, a, ba, b) {
519 hits.push((key, id));
520 }
521 }
522 }
523 hits
524 })?;
525 for (k, hits) in split_hits.iter().enumerate() {
526 if k % 1024 == 0 && cancelled() {
527 return None;
528 }
529 for &(key, id) in hits {
530 let e = seg_splits.entry(key).or_default();
531 if e.1.insert(id) {
532 e.0.push(id);
533 }
534 }
535 }
536 let n_split_hits: usize = split_hits.iter().map(|h| h.len()).sum();
537 drop(split_hits);
538
539 for m in 0..2 {
545 for (k, v) in edge_registry[m].values_mut().enumerate() {
546 if k % 4096 == 0 && cancelled() {
547 return None;
548 }
549 v.1 = HashSet::default();
550 v.0.shrink_to_fit();
551 }
552 }
553 for (k, v) in seg_splits.values_mut().enumerate() {
554 if k % 4096 == 0 && cancelled() {
555 return None;
556 }
557 v.1 = HashSet::default();
558 v.0.shrink_to_fit();
559 }
560 drop(candidates);
561 drop(pts_a);
562
563 crate::timing::print("robust: split registries", t_reg);
564 crate::timing::print_count(&format!(
565 "robust: split registries: {n_edge_hits} edge hits over {} edges, \
566 {n_split_hits} segment hits over {} segments",
567 edge_registry[0].len() + edge_registry[1].len(),
568 seg_splits.len()
569 ));
570 let t_arr = crate::timing::start();
571
572 enum TriResult {
578 Untouched,
580 Arranged(arrangement::Arrangement),
581 }
582 let arr_work: Vec<(usize, usize)> = (0..2)
583 .flat_map(|m| (0..meshes[m].len()).map(move |ti| (m, ti)))
584 .filter(|&(m, ti)| live[m][ti])
585 .collect();
586 begin_phase(progress, Phase::Arrangements, arr_work.len() as u64);
587 let arr_results = maybe_par_map_ct_progress(arr_work.len(), 16, token, progress, |i| {
588 let (m, ti) = arr_work[i];
589 let t = meshes[m][ti];
590 let pr = &prims[m][ti];
591 let mut extra: BTreeSet<R3> = BTreeSet::new();
597 for e in 0..3 {
598 if let Some(set) = edge_registry[m].get(&bit_edge_key(t[e], t[(e + 1) % 3])) {
599 extra.extend(set.0.iter().map(|&id| pts[id as usize].clone()));
600 }
601 }
602 for &(ia, ib) in &seg_ends[m][seg_off[m][ti]..seg_off[m][ti + 1]] {
605 if let Some(set) = seg_splits.get(&geo_edge_key(ia, ib)) {
606 extra.extend(set.0.iter().map(|&id| pts[id as usize].clone()));
607 }
608 }
609
610 if pr.points.is_empty() && pr.segments.is_empty() && extra.is_empty() {
611 return Some(TriResult::Untouched);
612 }
613 let mut input = ArrangementInput {
614 points: pr.points.clone(),
615 segments: pr.segments.clone(),
616 };
617 for pt in extra {
618 input.points.push((pt, usize::MAX));
619 }
620 arrangement::build(t, &input, token).map(TriResult::Arranged)
621 })?;
622
623 let mut pieces: Vec<Piece> = Vec::new();
624 let mut isect_edges: HashSet<EdgeKey> = HashSet::default();
626 let mut interner = VertInterner::default();
627 for (k, (&(m, ti), result)) in arr_work.iter().zip(arr_results).enumerate() {
628 if k % 1024 == 0 && cancelled() {
629 return None;
630 }
631 let t = meshes[m][ti];
632 match result? {
633 TriResult::Untouched => {
634 pieces.push(Piece {
635 mesh: m as u8,
636 tri: ti,
637 vi: [
638 interner.intern_f64(t[0]),
639 interner.intern_f64(t[1]),
640 interner.intern_f64(t[2]),
641 ],
642 });
643 }
644 TriResult::Arranged(arr) => {
645 let ids: Vec<u32> = arr.points3.iter().map(|p| interner.intern(p)).collect();
648 for (u, w) in arr.constraints.keys() {
649 isect_edges.insert(edge_key(ids[*u], ids[*w]));
650 }
651 for st in &arr.tris {
652 let (a, b, c) = (st[0], st[1], st[2]);
653 let vi = if arr.flipped {
654 [ids[a], ids[c], ids[b]]
655 } else {
656 [ids[a], ids[b], ids[c]]
657 };
658 pieces.push(Piece {
659 mesh: m as u8,
660 tri: ti,
661 vi,
662 });
663 }
664 }
665 }
666 }
667
668 crate::timing::print("robust: arrangements", t_arr);
669 crate::timing::print_count(&format!(
670 "robust: arrangement phases: {}",
671 arrangement::stats::snapshot_and_reset()
672 ));
673
674 Some(IntersectionGraph {
675 pieces,
676 verts: interner.verts,
677 verts_f64: interner.verts_f64,
678 isect_edges,
679 any_intersections,
680 })
681}