1use std::cmp::Ordering;
7use std::collections::BinaryHeap;
8
9use crate::{
10 aabb::aabb_could_overlap,
11 fat_axis::{curve_fat_axes_separate, surface_fat_plane_separates},
12 nurb_curve::{NurbCurve, dehomogenize},
13 nurb_surface::NurbSurface,
14};
15use geop_core_math::{
16 disjoint_set::DisjointSet,
17 geop_error::{GeopError, GeopResult},
18 scalars::Scalar,
19 vector::Vector2,
20};
21
22use super::Intersections;
23
24struct QueueEntry<S: Scalar> {
29 level: usize,
30 curve_seg: NurbCurve<S, 4>,
31 surf_patch: NurbSurface<S, 4>,
32}
33
34impl<S: Scalar> PartialEq for QueueEntry<S> {
35 fn eq(&self, other: &Self) -> bool {
36 self.level == other.level
37 }
38}
39impl<S: Scalar> Eq for QueueEntry<S> {}
40impl<S: Scalar> PartialOrd for QueueEntry<S> {
41 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
42 Some(self.cmp(other))
43 }
44}
45impl<S: Scalar> Ord for QueueEntry<S> {
46 fn cmp(&self, other: &Self) -> Ordering {
47 other.level.cmp(&self.level)
49 }
50}
51
52enum DfsOutcome<S: Scalar> {
54 NoSolution,
56 Found {
61 solution: (S, Vector2<S>),
62 unexplored: Vec<(NurbCurve<S, 4>, NurbSurface<S, 4>, usize)>,
63 },
64}
65
66fn dfs<S: Scalar>(
83 curve_seg: NurbCurve<S, 4>,
84 surf_patch: NurbSurface<S, 4>,
85 level: usize,
86 min_subdivision_size: S,
87 explored: &mut usize,
88 max_nodes: usize,
89) -> GeopResult<DfsOutcome<S>> {
90 *explored += 1;
91 if *explored > max_nodes {
92 return Err(GeopError::new(
93 "curve_surface_intersect: exhausted max_nodes before the search converged",
94 ));
95 }
96
97 let found_here = |curve_seg: &NurbCurve<S, 4>, surf_patch: &NurbSurface<S, 4>| {
103 let (t0, t1) = curve_seg.domain();
104 let (u0, u1) = surf_patch.domain_u();
105 let (v0, v1) = surf_patch.domain_v();
106 DfsOutcome::Found {
107 solution: (
108 t0.union(t1),
109 Vector2::from_array([u0.union(u1), v0.union(v1)]),
110 ),
111 unexplored: vec![],
112 }
113 };
114
115 if !aabb_could_overlap(&curve_seg.aabb, &surf_patch.aabb, 3) {
119 return Ok(DfsOutcome::NoSolution);
120 }
121
122 if let (Ok(pts_c), Ok(pts_s)) = (
128 dehomogenize::<S, 4, 3>(&curve_seg.control_points),
129 dehomogenize::<S, 4, 3>(&surf_patch.control_points),
130 ) {
131 if surface_fat_plane_separates(&surf_patch, &pts_c)
132 || curve_fat_axes_separate(&curve_seg, &pts_s)
133 {
134 return Ok(DfsOutcome::NoSolution);
135 }
136 }
137
138 let (hull_c, hull_s) = match (curve_seg.convex_hull(), surf_patch.convex_hull()) {
139 (Ok(c), Ok(s)) => (c, s),
140 _ => return Ok(found_here(&curve_seg, &surf_patch)),
144 };
145 if hull_c.definitely_no_overlap(&hull_s) {
146 return Ok(DfsOutcome::NoSolution);
147 }
148
149 let (curve_size, surf_size) = match (curve_seg.size(), surf_patch.size()) {
150 (Ok(c), Ok(s)) => (c, s),
151 _ => return Ok(found_here(&curve_seg, &surf_patch)),
152 };
153
154 let (t0, t1) = curve_seg.domain();
166 let curve_width = t1.sub(t0);
167 let (u0, u1) = surf_patch.domain_u();
168 let (v0, v1) = surf_patch.domain_v();
169 let surf_width = u1.sub(u0).union(v1.sub(v0));
170 let curve_converged = !curve_size.definitely_greater(min_subdivision_size)
171 || !curve_width.definitely_greater(min_subdivision_size);
172 let surf_converged = !surf_size.definitely_greater(min_subdivision_size)
173 || !surf_width.definitely_greater(min_subdivision_size);
174
175 if curve_converged && surf_converged {
176 return Ok(found_here(&curve_seg, &surf_patch));
177 }
178
179 let split_curve = if curve_converged {
184 false
185 } else if surf_converged {
186 true
187 } else {
188 curve_size.definitely_greater(surf_size)
189 };
190 let (left, right) = if split_curve {
191 match curve_seg.split_mid() {
192 Ok((l, r)) => ((l, surf_patch.clone()), (r, surf_patch)),
193 Err(_) => return Ok(found_here(&curve_seg, &surf_patch)),
194 }
195 } else {
196 match surf_patch.split_mid() {
197 Ok((s0, s1)) => ((curve_seg.clone(), s0), (curve_seg, s1)),
198 Err(_) => return Ok(found_here(&curve_seg, &surf_patch)),
199 }
200 };
201
202 match dfs(
203 left.0,
204 left.1,
205 level + 1,
206 min_subdivision_size,
207 explored,
208 max_nodes,
209 )? {
210 DfsOutcome::Found {
211 solution,
212 mut unexplored,
213 } => {
214 unexplored.push((right.0, right.1, level + 1));
215 Ok(DfsOutcome::Found {
216 solution,
217 unexplored,
218 })
219 }
220 DfsOutcome::NoSolution => dfs(
221 right.0,
222 right.1,
223 level + 1,
224 min_subdivision_size,
225 explored,
226 max_nodes,
227 ),
228 }
229}
230
231pub fn curve_surface_intersect<S: Scalar>(
271 curve: &NurbCurve<S, 4>,
272 surface: &NurbSurface<S, 4>,
273 max_solutions: usize,
274 max_nodes: usize,
275 min_subdivision_size: S,
276) -> GeopResult<Intersections<(S, Vector2<S>)>> {
277 let mut queue: BinaryHeap<QueueEntry<S>> = BinaryHeap::new();
278 queue.push(QueueEntry {
279 level: 0,
280 curve_seg: curve.clone(),
281 surf_patch: surface.clone(),
282 });
283
284 let mut explored = 0usize;
285 let mut solutions: DisjointSet<(S, Vector2<S>)> = DisjointSet::new();
286
287 while solutions.len() < max_solutions {
288 let Some(entry) = queue.pop() else { break };
289
290 match dfs(
291 entry.curve_seg,
292 entry.surf_patch,
293 entry.level,
294 min_subdivision_size,
295 &mut explored,
296 max_nodes,
297 )? {
298 DfsOutcome::NoSolution => continue,
299 DfsOutcome::Found {
300 solution,
301 unexplored,
302 } => {
303 solutions.insert(solution);
304 for (c, s, lvl) in unexplored {
305 queue.push(QueueEntry {
306 level: lvl,
307 curve_seg: c,
308 surf_patch: s,
309 });
310 }
311 }
312 }
313 }
314
315 let result = solutions.into_vec();
316 Ok(if max_solutions > 0 && result.len() >= max_solutions {
317 Intersections::Coincident(result)
318 } else {
319 Intersections::Found(result)
320 })
321}
322
323#[cfg(test)]
324mod tests {
325 use super::curve_surface_intersect;
326 use crate::{nurb_curve::NurbCurve, nurb_surface::NurbSurface};
327 use geop_core_math::for_all_scalars;
328 use geop_core_math::{scalars::Scalar, vector::Vector4};
329
330 const MAX_NODES: usize = 2000;
331
332 fn ptc<S: Scalar>(x: f64, y: f64, z: f64, w: f64) -> Vector4<S> {
333 Vector4::from_array([
334 S::from_f64(x),
335 S::from_f64(y),
336 S::from_f64(z),
337 S::from_f64(w),
338 ])
339 }
340
341 fn pts<S: Scalar>(x: f64, y: f64, z: f64) -> Vector4<S> {
342 Vector4::from_array([S::from_f64(x), S::from_f64(y), S::from_f64(z), S::ONE])
343 }
344
345 fn flat_xy<S: Scalar>() -> NurbSurface<S, 4> {
347 let f = S::from_f64;
348 NurbSurface::try_new(
349 1,
350 1,
351 vec![
352 pts(0., 0., 0.),
353 pts(0., 1., 0.),
354 pts(1., 0., 0.),
355 pts(1., 1., 0.),
356 ],
357 vec![f(0.), f(0.), f(1.), f(1.)],
358 vec![f(0.), f(0.), f(1.), f(1.)],
359 )
360 .unwrap()
361 }
362
363 fn vertical_crossing_line<S: Scalar>() -> NurbCurve<S, 4> {
365 let f = S::from_f64;
366 NurbCurve::try_new(
367 1,
368 vec![ptc(0.5, 0.5, -1., 1.), ptc(0.5, 0.5, 1., 1.)],
369 vec![f(0.), f(0.), f(1.), f(1.)],
370 )
371 .unwrap()
372 }
373
374 fn line_above_surface<S: Scalar>() -> NurbCurve<S, 4> {
376 let f = S::from_f64;
377 NurbCurve::try_new(
378 1,
379 vec![ptc(0.5, 0.5, 1., 1.), ptc(0.5, 0.5, 2., 1.)],
380 vec![f(0.), f(0.), f(1.), f(1.)],
381 )
382 .unwrap()
383 }
384
385 fn double_dip_curve<S: Scalar>() -> NurbCurve<S, 4> {
390 let f = S::from_f64;
391 NurbCurve::try_new(
392 2,
393 vec![
394 ptc(0.2, 0.3, 1.0, 1.),
395 ptc(0.5, 0.3, -2.0, 1.),
396 ptc(0.8, 0.3, 1.0, 1.),
397 ],
398 vec![f(0.), f(0.), f(0.), f(1.), f(1.), f(1.)],
399 )
400 .unwrap()
401 }
402
403 fn coplanar_line<S: Scalar>() -> NurbCurve<S, 4> {
406 let f = S::from_f64;
407 NurbCurve::try_new(
408 1,
409 vec![ptc(0.2, 0.5, 0., 1.), ptc(0.8, 0.5, 0., 1.)],
410 vec![f(0.), f(0.), f(1.), f(1.)],
411 )
412 .unwrap()
413 }
414
415 fn partial_overlap_line<S: Scalar>() -> NurbCurve<S, 4> {
418 let f = S::from_f64;
419 NurbCurve::try_new(
420 1,
421 vec![ptc(-0.5, 0.5, 0., 1.), ptc(0.5, 0.5, 0., 1.)],
422 vec![f(0.), f(0.), f(1.), f(1.)],
423 )
424 .unwrap()
425 }
426
427 fn oversized_line<S: Scalar>() -> NurbCurve<S, 4> {
431 let f = S::from_f64;
432 NurbCurve::try_new(
433 1,
434 vec![ptc(-1.0, 0.5, 0., 1.), ptc(2.0, 0.5, 0., 1.)],
435 vec![f(0.), f(0.), f(1.), f(1.)],
436 )
437 .unwrap()
438 }
439
440 fn full_width_line<S: Scalar>() -> NurbCurve<S, 4> {
443 let f = S::from_f64;
444 NurbCurve::try_new(
445 1,
446 vec![ptc(0., 0.5, 0., 1.), ptc(1., 0.5, 0., 1.)],
447 vec![f(0.), f(0.), f(1.), f(1.)],
448 )
449 .unwrap()
450 }
451
452 fn ptw<S: Scalar>(x: f64, y: f64, z: f64, w: f64) -> Vector4<S> {
455 Vector4::from_array([
456 S::from_f64(x * w),
457 S::from_f64(y * w),
458 S::from_f64(z * w),
459 S::from_f64(w),
460 ])
461 }
462
463 fn bent_surface<S: Scalar>() -> NurbSurface<S, 4> {
466 let f = S::from_f64;
467 NurbSurface::try_new(
468 1,
469 1,
470 vec![
471 pts(0., 0., 0.),
472 pts(0., 2., 1.),
473 pts(2., 0., 1.),
474 pts(2., 2., 0.),
475 ],
476 vec![f(0.), f(0.), f(1.), f(1.)],
477 vec![f(0.), f(0.), f(1.), f(1.)],
478 )
479 .unwrap()
480 }
481
482 fn bent_curve<S: Scalar>() -> NurbCurve<S, 4> {
486 let f = S::from_f64;
487 NurbCurve::try_new(
488 2,
489 vec![
490 ptc(0.2, 1.0, -1.0, 1.),
491 ptc(1.0, 1.0, 3.0, 1.),
492 ptc(1.8, 1.0, -1.0, 1.),
493 ],
494 vec![f(0.), f(0.), f(0.), f(1.), f(1.), f(1.)],
495 )
496 .unwrap()
497 }
498
499 fn sphere_octant_patch<S: Scalar>() -> NurbSurface<S, 4> {
504 let f = S::from_f64;
505 let w = 1.0 / 2.0_f64.sqrt();
506 NurbSurface::try_new(
507 2,
508 2,
509 vec![
510 ptw(1., 0., 0., 1.),
512 ptw(1., 0., 1., w),
513 ptw(0., 0., 1., 1.),
514 ptw(1., 1., 0., w),
516 ptw(1., 1., 1., 0.5),
517 ptw(0., 0., 1., w),
518 ptw(0., 1., 0., 1.),
520 ptw(0., 1., 1., w),
521 ptw(0., 0., 1., 1.),
522 ],
523 vec![f(0.), f(0.), f(0.), f(1.), f(1.), f(1.)],
524 vec![f(0.), f(0.), f(0.), f(1.), f(1.), f(1.)],
525 )
526 .unwrap()
527 }
528
529 fn equator_quarter_circle<S: Scalar>() -> NurbCurve<S, 4> {
532 let f = S::from_f64;
533 let w = 1.0 / 2.0_f64.sqrt();
534 NurbCurve::try_new(
535 2,
536 vec![ptw(1., 0., 0., 1.), ptw(1., 1., 0., w), ptw(0., 1., 0., 1.)],
537 vec![f(0.), f(0.), f(0.), f(1.), f(1.), f(1.)],
538 )
539 .unwrap()
540 }
541
542 const EPS: f64 = 1e-2;
543
544 fn check_single_crossing_curve_has_one_solution<S: Scalar>() {
547 let curve = vertical_crossing_line::<S>();
548 let surf = flat_xy::<S>();
549 let result =
550 curve_surface_intersect(&curve, &surf, 5, MAX_NODES, S::from_f64(EPS)).unwrap();
551 assert_eq!(result.len(), 1);
552 }
553 #[test]
554 fn single_crossing_curve_has_one_solution() {
555 for_all_scalars!(check_single_crossing_curve_has_one_solution);
556 }
557
558 fn check_curve_missing_surface_has_no_solution<S: Scalar>() {
561 let curve = line_above_surface::<S>();
562 let surf = flat_xy::<S>();
563 let result =
564 curve_surface_intersect(&curve, &surf, 5, MAX_NODES, S::from_f64(EPS)).unwrap();
565 assert!(result.is_empty());
566 }
567 #[test]
568 fn curve_missing_surface_has_no_solution() {
569 for_all_scalars!(check_curve_missing_surface_has_no_solution);
570 }
571
572 fn check_max_solutions_zero_returns_empty<S: Scalar>() {
575 let curve = vertical_crossing_line::<S>();
576 let surf = flat_xy::<S>();
577 let result =
578 curve_surface_intersect(&curve, &surf, 0, MAX_NODES, S::from_f64(EPS)).unwrap();
579 assert!(result.is_empty());
580 }
581 #[test]
582 fn max_solutions_zero_returns_empty() {
583 for_all_scalars!(check_max_solutions_zero_returns_empty);
584 }
585
586 fn check_max_nodes_exhausted_errors<S: Scalar>() {
587 let curve = coplanar_line::<S>();
588 let surf = flat_xy::<S>();
589 let result = curve_surface_intersect(&curve, &surf, 1000, 3, S::from_f64(EPS));
593 assert!(result.is_err());
594 }
595 #[test]
596 fn max_nodes_exhausted_errors() {
597 for_all_scalars!(check_max_nodes_exhausted_errors);
598 }
599
600 fn check_two_crossings_found_when_budget_allows<S: Scalar>() {
603 let curve = double_dip_curve::<S>();
604 let surf = flat_xy::<S>();
605 let result = curve_surface_intersect(&curve, &surf, 2, MAX_NODES, S::from_f64(EPS))
606 .unwrap()
607 .into_vec();
608 assert_eq!(result.len(), 2);
609 assert!(
610 !result[0].0.could_be_equal(result[1].0),
611 "the two crossings should remain distinct after unification"
612 );
613 }
614 #[test]
615 fn two_crossings_found_when_budget_allows() {
616 for_all_scalars!(check_two_crossings_found_when_budget_allows);
617 }
618
619 fn check_max_solutions_one_caps_at_one_even_with_two_crossings<S: Scalar>() {
620 let curve = double_dip_curve::<S>();
621 let surf = flat_xy::<S>();
622 let result =
623 curve_surface_intersect(&curve, &surf, 1, MAX_NODES, S::from_f64(EPS)).unwrap();
624 assert_eq!(result.len(), 1);
625 }
626 #[test]
627 fn max_solutions_one_caps_at_one_even_with_two_crossings() {
628 for_all_scalars!(check_max_solutions_one_caps_at_one_even_with_two_crossings);
629 }
630
631 fn check_min_subdivision_size_controls_precision<S: Scalar>() {
634 let curve = vertical_crossing_line::<S>();
635 let surf = flat_xy::<S>();
636 let result = curve_surface_intersect(&curve, &surf, 5, MAX_NODES, S::from_f64(1e-3))
637 .unwrap()
638 .into_vec();
639 assert_eq!(result.len(), 1);
640
641 let (_, uv) = result[0];
642 assert!(
643 uv[0]
644 .sub(S::from_f64(0.5))
645 .abs()
646 .could_be_less(S::from_f64(1e-2))
647 );
648 assert!(
649 uv[1]
650 .sub(S::from_f64(0.5))
651 .abs()
652 .could_be_less(S::from_f64(1e-2))
653 );
654 }
655 #[test]
656 fn min_subdivision_size_controls_precision() {
657 for_all_scalars!(check_min_subdivision_size_controls_precision);
658 }
659
660 fn check_coplanar_curve_terminates<S: Scalar>() {
663 let curve = coplanar_line::<S>();
664 let surf = flat_xy::<S>();
665
666 let result_one =
668 curve_surface_intersect(&curve, &surf, 1, MAX_NODES, S::from_f64(EPS)).unwrap();
669 assert_eq!(result_one.len(), 1);
670
671 let result_many =
674 curve_surface_intersect(&curve, &surf, 5, MAX_NODES, S::from_f64(EPS)).unwrap();
675 assert!(!result_many.is_empty());
676 assert!(result_many.len() <= 5);
677 }
678 #[test]
679 fn coplanar_curve_terminates() {
680 for_all_scalars!(check_coplanar_curve_terminates);
681 }
682
683 fn check_coincident_curve_reaches_max_solutions<S: Scalar>() {
686 let curve = full_width_line::<S>();
687 let surf = flat_xy::<S>();
688 let result = curve_surface_intersect(&curve, &surf, 5, 5000, S::from_f64(1e-3)).unwrap();
694 assert!(result.is_coincident());
695 assert_eq!(result.len(), 5);
696 }
697 #[test]
698 fn coincident_curve_reaches_max_solutions() {
699 for_all_scalars!(check_coincident_curve_reaches_max_solutions);
700 }
701
702 fn check_partial_overlap_coplanar_line<S: Scalar>() {
705 let curve = partial_overlap_line::<S>();
706 let surf = flat_xy::<S>();
707 let result =
708 curve_surface_intersect(&curve, &surf, 5, MAX_NODES, S::from_f64(EPS)).unwrap();
709 assert!(!result.is_empty());
710 assert!(result.len() <= 5);
711
712 let lower_bound = S::from_f64(0.5 - EPS);
715 for &(t, _) in result.as_slice() {
716 assert!(!t.definitely_less(lower_bound));
717 }
718 }
719 #[test]
720 fn partial_overlap_coplanar_line() {
721 for_all_scalars!(check_partial_overlap_coplanar_line);
722 }
723
724 fn check_curve_larger_than_surface_terminates<S: Scalar>() {
727 let curve = oversized_line::<S>();
728 let surf = flat_xy::<S>();
729 let result =
730 curve_surface_intersect(&curve, &surf, 5, MAX_NODES, S::from_f64(EPS)).unwrap();
731 assert!(!result.is_empty());
732 assert!(result.len() <= 5);
733
734 let lower_bound = S::from_f64(1.0 / 3.0 - EPS);
737 let upper_bound = S::from_f64(2.0 / 3.0 + EPS);
738 for &(t, _) in result.as_slice() {
739 assert!(!t.definitely_less(lower_bound));
740 assert!(!t.definitely_greater(upper_bound));
741 }
742 }
743 #[test]
744 fn curve_larger_than_surface_terminates() {
745 for_all_scalars!(check_curve_larger_than_surface_terminates);
746 }
747
748 fn check_curve_same_size_as_surface_terminates<S: Scalar>() {
751 let curve = full_width_line::<S>();
752 let surf = flat_xy::<S>();
753
754 let result_one =
755 curve_surface_intersect(&curve, &surf, 1, MAX_NODES, S::from_f64(EPS)).unwrap();
756 assert_eq!(result_one.len(), 1);
757
758 let result_many =
759 curve_surface_intersect(&curve, &surf, 5, MAX_NODES, S::from_f64(EPS)).unwrap();
760 assert!(!result_many.is_empty());
761 assert!(result_many.len() <= 5);
762 }
763 #[test]
764 fn curve_same_size_as_surface_terminates() {
765 for_all_scalars!(check_curve_same_size_as_surface_terminates);
766 }
767
768 fn check_bent_surface_bent_curve_two_distinct_crossings<S: Scalar>() {
771 let curve = bent_curve::<S>();
772 let surf = bent_surface::<S>();
773 let result = curve_surface_intersect(&curve, &surf, 4, MAX_NODES, S::from_f64(EPS))
774 .unwrap()
775 .into_vec();
776 assert_eq!(result.len(), 2);
777 assert!(
778 !result[0].0.could_be_equal(result[1].0),
779 "the two crossings of a bent curve through a bent surface should be distinct"
780 );
781 }
782 #[test]
783 fn bent_surface_bent_curve_two_distinct_crossings() {
784 for_all_scalars!(check_bent_surface_bent_curve_two_distinct_crossings);
785 }
786
787 fn check_coincident_circle_on_sphere_patch_terminates<S: Scalar>() {
790 let curve = equator_quarter_circle::<S>();
791 let surf = sphere_octant_patch::<S>();
792
793 let result_one =
794 curve_surface_intersect(&curve, &surf, 1, MAX_NODES, S::from_f64(EPS)).unwrap();
795 assert_eq!(result_one.len(), 1);
796
797 let result_many =
798 curve_surface_intersect(&curve, &surf, 5, MAX_NODES, S::from_f64(EPS)).unwrap();
799 assert!(!result_many.is_empty());
800 assert!(result_many.len() <= 5);
801 }
802 #[test]
803 fn coincident_circle_on_sphere_patch_terminates() {
804 for_all_scalars!(check_coincident_circle_on_sphere_patch_terminates);
805 }
806}