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, R3Key};
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};
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<R3>>>; 2] = [
328 vec![None; p.len()],
329 vec![None; q.len()],
330 ];
331 let cand_work: Vec<(usize, usize)> = (0..2)
332 .flat_map(|m| (0..meshes[m].len()).map(move |ti| (m, ti)))
333 .filter(|&(m, ti)| {
334 let pr = &prims[m][ti];
335 !pr.points.is_empty() || !pr.segments.is_empty()
336 })
337 .collect();
338 begin_phase(progress, Phase::CandidatePoints, cand_work.len() as u64);
339 let cand_results = maybe_par_map_ct_progress(cand_work.len(), 16, token, progress, |i| {
340 let (m, ti) = cand_work[i];
341 let pr = &prims[m][ti];
342 let input = ArrangementInput {
343 points: pr.points.clone(),
344 segments: pr.segments.clone(),
345 };
346 arrangement::candidate_points(meshes[m][ti], &input, token)
347 })?;
348 for (&(m, ti), cands) in cand_work.iter().zip(cand_results) {
349 candidates[m][ti] = Some(cands?);
350 }
351
352 crate::timing::print("robust: candidate points", t_cand);
353 let t_reg = crate::timing::start();
354
355 let reg_work: Vec<(usize, usize)> = (0..2)
364 .flat_map(|m| (0..meshes[m].len()).map(move |ti| (m, ti)))
365 .filter(|&(m, ti)| candidates[m][ti].is_some())
366 .collect();
367 begin_phase(progress, Phase::Registries, 2 * reg_work.len() as u64);
370
371 let mut edge_registry: [HashMap<BitEdgeKey, (Vec<R3>, HashSet<R3Key>)>; 2] =
378 [HashMap::default(), HashMap::default()];
379 let edge_hits = maybe_par_map_ct_progress(reg_work.len(), 16, token, progress, |i| {
380 let (m, ti) = reg_work[i];
381 let cands = candidates[m][ti].as_ref().expect("filtered to Some");
382 let t = meshes[m][ti];
383 let corners = [
384 R3::from_vec3(t[0]),
385 R3::from_vec3(t[1]),
386 R3::from_vec3(t[2]),
387 ];
388 let ca: [[f64; 3]; 3] = [
389 [t[0].x, t[0].y, t[0].z],
390 [t[1].x, t[1].y, t[1].z],
391 [t[2].x, t[2].y, t[2].z],
392 ];
393 let cands_a: Vec<[f64; 3]> = cands.iter().map(approx3).collect();
394 let mut hits: Vec<(BitEdgeKey, R3)> = Vec::new();
395 for e in 0..3 {
396 let a = &corners[e];
397 let b = &corners[(e + 1) % 3];
398 let key = bit_edge_key(t[e], t[(e + 1) % 3]);
399 let sbox = seg_box3(ca[e], ca[(e + 1) % 3]);
400 for (pt, pt_a) in cands.iter().zip(&cands_a) {
401 if !box3_contains(&sbox, *pt_a) {
404 continue;
405 }
406 if !r3_eq(pt, a)
407 && !r3_eq(pt, b)
408 && point_on_segment_f(*pt_a, pt, ca[e], a, ca[(e + 1) % 3], b)
409 {
410 hits.push((key, pt.clone()));
411 }
412 }
413 }
414 hits
415 })?;
416 for (&(m, _), hits) in reg_work.iter().zip(&edge_hits) {
417 for (key, pt) in hits {
418 let e = edge_registry[m].entry(*key).or_default();
419 if e.1.insert(R3Key(pt.clone())) {
420 e.0.push(pt.clone());
421 }
422 }
423 }
424
425 let mut seg_splits: HashMap<(R3Key, R3Key), (Vec<R3>, HashSet<R3Key>)> = HashMap::default();
433 let split_hits = maybe_par_map_ct_progress(reg_work.len(), 16, token, progress, |i| {
434 let (m, ti) = reg_work[i];
435 let cands = candidates[m][ti].as_ref().expect("filtered to Some");
436 let cands_a: Vec<[f64; 3]> = cands.iter().map(approx3).collect();
437 let mut hits: Vec<(GeoEdgeKey, R3)> = Vec::new();
438 for (a, b, _prov) in &prims[m][ti].segments {
439 let key = geo_edge_key(a, b);
440 let (aa, ba) = (approx3(a), approx3(b));
441 let sbox = seg_box3(aa, ba);
442 for (pt, pt_a) in cands.iter().zip(&cands_a) {
443 if !box3_contains(&sbox, *pt_a) {
444 continue;
445 }
446 if !r3_eq(pt, a) && !r3_eq(pt, b) && point_on_segment_f(*pt_a, pt, aa, a, ba, b) {
447 hits.push((key.clone(), pt.clone()));
448 }
449 }
450 }
451 hits
452 })?;
453 for hits in &split_hits {
454 for (key, pt) in hits {
455 let e = seg_splits.entry(key.clone()).or_default();
456 if e.1.insert(R3Key(pt.clone())) {
457 e.0.push(pt.clone());
458 }
459 }
460 }
461
462 crate::timing::print("robust: split registries", t_reg);
463 let t_arr = crate::timing::start();
464
465 enum TriResult {
471 Untouched,
473 Arranged(arrangement::Arrangement),
474 }
475 let arr_work: Vec<(usize, usize)> = (0..2)
476 .flat_map(|m| (0..meshes[m].len()).map(move |ti| (m, ti)))
477 .filter(|&(m, ti)| live[m][ti])
478 .collect();
479 begin_phase(progress, Phase::Arrangements, arr_work.len() as u64);
480 let arr_results = maybe_par_map_ct_progress(arr_work.len(), 16, token, progress, |i| {
481 let (m, ti) = arr_work[i];
482 let t = meshes[m][ti];
483 let pr = &prims[m][ti];
484 let mut extra: BTreeSet<R3> = BTreeSet::new();
487 for e in 0..3 {
488 if let Some(set) = edge_registry[m].get(&bit_edge_key(t[e], t[(e + 1) % 3])) {
489 extra.extend(set.0.iter().cloned());
490 }
491 }
492 for (a, b, _) in &pr.segments {
495 if let Some(set) = seg_splits.get(&geo_edge_key(a, b)) {
496 extra.extend(set.0.iter().cloned());
497 }
498 }
499
500 if pr.points.is_empty() && pr.segments.is_empty() && extra.is_empty() {
501 return Some(TriResult::Untouched);
502 }
503 let mut input = ArrangementInput {
504 points: pr.points.clone(),
505 segments: pr.segments.clone(),
506 };
507 for pt in extra {
508 input.points.push((pt, usize::MAX));
509 }
510 arrangement::build(t, &input, token).map(TriResult::Arranged)
511 })?;
512
513 let mut pieces: Vec<Piece> = Vec::new();
514 let mut isect_edges: HashSet<EdgeKey> = HashSet::default();
516 let mut interner = VertInterner::default();
517 for (&(m, ti), result) in arr_work.iter().zip(arr_results) {
518 let t = meshes[m][ti];
519 match result? {
520 TriResult::Untouched => {
521 pieces.push(Piece {
522 mesh: m as u8,
523 tri: ti,
524 vi: [
525 interner.intern_f64(t[0]),
526 interner.intern_f64(t[1]),
527 interner.intern_f64(t[2]),
528 ],
529 });
530 }
531 TriResult::Arranged(arr) => {
532 let ids: Vec<u32> = arr.points3.iter().map(|p| interner.intern(p)).collect();
535 for (u, w) in arr.constraints.keys() {
536 isect_edges.insert(edge_key(ids[*u], ids[*w]));
537 }
538 for st in &arr.tris {
539 let (a, b, c) = (st[0], st[1], st[2]);
540 let vi = if arr.flipped {
541 [ids[a], ids[c], ids[b]]
542 } else {
543 [ids[a], ids[b], ids[c]]
544 };
545 pieces.push(Piece {
546 mesh: m as u8,
547 tri: ti,
548 vi,
549 });
550 }
551 }
552 }
553 }
554
555 crate::timing::print("robust: arrangements", t_arr);
556 crate::timing::print_count(&format!(
557 "robust: arrangement phases: {}",
558 arrangement::stats::snapshot_and_reset()
559 ));
560
561 Some(IntersectionGraph {
562 pieces,
563 verts: interner.verts,
564 verts_f64: interner.verts_f64,
565 isect_edges,
566 any_intersections,
567 })
568}