1use super::{
17 Intersections,
18 coincidence::{self, Hit, Overlap},
19};
20use crate::{
21 aabb::{aabb_could_overlap, curve_could_meet_aabb},
22 contains::surface::{
23 boundary_curve, collapsed_boundary, surface_could_contain, surface_extents,
24 },
25 fat_line::{
26 Stalled, carried_width, chord, clip_tensor, extent, greville_abscissae, restriction,
27 spanning, stalled,
28 },
29 intersection::curve_curve,
30 nurb_curve::{NurbCurve, dehomogenize},
31 nurb_surface::NurbSurface,
32 search::{MAX_NODES, min_subdivision_size},
33};
34use geop_core_math::{
35 disjoint_set::DisjointSet,
36 geop_error::{GeopError, GeopResult, WithContext},
37 matrix::Matrix,
38 scalars::Scalar,
39 solvers::{
40 linear::solve_linear_system,
41 newton,
42 subdivide::{Order, Searched, Split, subdivide},
43 },
44 vector::{Vector, Vector2},
45};
46
47fn clip(c: &NurbCurve<4>, s: &NurbSurface<4>) -> GeopResult<Option<[Scalar; 3]>> {
60 let (u0, u1) = s.domain_u();
61 let (v0, v1) = s.domain_v();
62 let mut hats = [c.domain_as_scalar(), u0.union(u1), v0.union(v1)];
63 let (nc, nu, nv) = (c.control_points.len(), s.num_u, s.num_v);
64 let greville = [
65 greville_abscissae(&c.knot_vector, c.degree, nc)?,
66 greville_abscissae(&s.knot_vector_u, s.degree_u, nu)?,
67 greville_abscissae(&s.knot_vector_v, s.degree_v, nv)?,
68 ];
69
70 let cp = &s.control_points;
71 let corners = dehomogenize::<4, 3>(&[cp[0], cp[nv - 1], cp[(nu - 1) * nv], cp[nu * nv - 1]]);
72 let (p00, p01, p10, p11) = (corners[0], corners[1], corners[2], corners[3]);
73 let e_u = p10.add(&p11).sub(&p00.add(&p01)).sharpen();
74 let e_v = p01.add(&p11).sub(&p00.add(&p10)).sharpen();
75 let c_dir = chord::<4, 3>(&c.control_points);
76 let dirs = [
77 e_u.prod_cross(&e_v),
78 c_dir.prod_cross(&e_v),
79 c_dir.prod_cross(&e_u),
80 ]
81 .into_iter()
82 .filter_map(|n| n.normalize().ok())
83 .collect();
84
85 let mut d = Vec::with_capacity(nc * nu * nv);
86 for n in spanning(dirs) {
87 let ns: Vec<Scalar> = cp.iter().map(|q| n.prod_dot(&q.head())).collect();
88 d.clear();
89 for p in &c.control_points {
90 let np = n.prod_dot(&p.head());
91 d.extend(
92 s.control_points
93 .iter()
94 .zip(&ns)
95 .map(|(q, &nq)| np.mul(q[3]).sub(p[3].mul(nq))),
96 );
97 }
98 if !clip_tensor(&d, &[nc, nu, nv], &greville, &mut hats) {
99 return Ok(None);
100 }
101 }
102 Ok(Some(hats))
103}
104
105fn solve_pinned(
113 c: &NurbCurve<4>,
114 s: &NurbSurface<4>,
115 hats: [Scalar; 3],
116 budget: usize,
117 min_subdivision_size: Scalar,
118) -> GeopResult<Option<Vec<[Scalar; 3]>>> {
119 let overlapping = |found: [Scalar; 3]| -> Option<[Scalar; 3]> {
120 (0..3)
121 .all(|i| found[i].could_be_equal(hats[i]))
122 .then(|| std::array::from_fn(|i| hats[i].intersect(found[i])))
123 };
124
125 if let Some(end) = c.pinned_end::<3>(hats[0]) {
126 let found = surface_could_contain(s, &end, budget, min_subdivision_size)?;
127 return Ok(Some(
128 found
129 .and_then(|(u, v)| overlapping([hats[0], u, v]))
130 .into_iter()
131 .collect(),
132 ));
133 }
134
135 if let Some((boundary, along_v)) = collapsed_boundary(s, hats[1], hats[2]) {
136 let pairs =
137 curve_curve::curve_curve_crossings::<4, 3>(c, &boundary, budget, min_subdivision_size)?;
138 return Ok(Some(
139 pairs
140 .into_iter()
141 .filter_map(|(t, w)| {
142 overlapping(if along_v {
143 [t, hats[1], w]
144 } else {
145 [t, w, hats[2]]
146 })
147 })
148 .collect(),
149 ));
150 }
151 Ok(None)
152}
153
154pub fn curve_surface_crossings(
169 curve: &NurbCurve<4>,
170 surface: &NurbSurface<4>,
171 max_nodes: usize,
172 min_subdivision_size: Scalar,
173) -> GeopResult<Vec<(Scalar, Vector2)>> {
174 let mut solutions: DisjointSet<(Scalar, Vector2)> = DisjointSet::new();
175 let mut insert = |[t, u, v]: [Scalar; 3]| solutions.insert((t, Vector2::from_array([u, v])));
176
177 let root = (curve.clone(), surface.clone());
178 let searched = subdivide(
179 [root],
180 Order::BreadthFirst,
181 max_nodes,
182 |(c, s), budget| -> GeopResult<_> {
183 if !aabb_could_overlap(&c.aabb, &s.aabb, 3) {
184 return Ok(Split::Done);
185 }
186 let Some(hats) = clip(&c, &s)? else {
187 return Ok(Split::Done);
188 };
189
190 if let Some(found) = solve_pinned(&c, &s, hats, budget, min_subdivision_size)? {
191 found.into_iter().for_each(&mut insert);
192 return Ok(Split::Done);
193 }
194
195 let ranges = [c.domain(), s.domain_u(), s.domain_v()];
196 let [size_u, size_v] = surface_extents(&s);
197 let sizes = [extent([c.control_points.clone()]), size_u, size_v];
198 let carried = carried_width(&c.control_points).max(carried_width(&s.control_points));
199 if let Some(b) = restriction(&hats, &ranges, &sizes, carried)?
200 && let (Ok(rc), Ok(rs)) = (c.sub_curve(b[0].0, b[0].1), s.sub_surface(b[1], b[2]))
201 {
202 return Ok(Split::Restrict((rc, rs)));
203 }
204
205 let order = match stalled(&ranges, &sizes, carried, min_subdivision_size) {
206 Stalled::Converged => {
207 let (u0, u1) = s.domain_u();
211 let (v0, v1) = s.domain_v();
212 insert([c.domain_as_scalar(), u0.union(u1), v0.union(v1)]);
213 return Ok(Split::Done);
214 }
215 Stalled::Bisect(order) => order,
216 };
217 let children = order.iter().find_map(|&dir| match dir {
218 0 => {
219 let (l, r) = c.split_mid().ok()?;
220 Some(((l, s.clone()), (r, s.clone())))
221 }
222 1 => {
223 let (l, r) = s.split_u_mid().ok()?;
224 Some(((c.clone(), l), (c.clone(), r)))
225 }
226 _ => {
227 let (l, r) = s.split_v_mid().ok()?;
228 Some(((c.clone(), l), (c.clone(), r)))
229 }
230 });
231 Ok(match children {
232 Some((first, second)) => Split::Halves(first, second),
233 None => {
235 insert(hats);
236 Split::Done
237 }
238 })
239 },
240 )?;
241 if let Searched::Exhausted { pending } = searched {
242 return Err(GeopError::new(format!(
243 "curve_surface_crossings: exhausted max_nodes={max_nodes} with {pending} \
244 pairs pending; the result would be incomplete"
245 )));
246 }
247
248 Ok(solutions.into_vec())
249}
250
251fn point_on(
253 curve: &NurbCurve<4>,
254 surface: &NurbSurface<4>,
255 t: Scalar,
256 max_nodes: usize,
257 min_subdivision_size: Scalar,
258) -> GeopResult<Option<Vector2>> {
259 let point = curve.evaluate(t)?;
260 Ok(
261 surface_could_contain(surface, &point, max_nodes, min_subdivision_size)?
262 .map(|(u, v)| Vector2::from_array([u, v])),
263 )
264}
265
266pub fn curve_surface_overlaps(
279 curve: &NurbCurve<4>,
280 surface: &NurbSurface<4>,
281 reach: Scalar,
282 max_nodes: usize,
283 min_subdivision_size: Scalar,
284) -> GeopResult<Vec<Overlap<Vector2>>> {
285 let widened;
286 let surface = if reach.definitely_greater(Scalar::ZERO) {
287 let mut wide = surface.clone();
288 wide.widen(reach);
289 widened = wide;
290 &widened
291 } else {
292 surface
293 };
294 let on_surface = |t: Scalar| point_on(curve, surface, t, max_nodes, min_subdivision_size);
295
296 let mut candidates = Vec::new();
297 let (t0, t1) = curve.domain();
298 for t in [t0, t1] {
299 if let Some(uv) = on_surface(t)? {
300 candidates.push(Hit { t, partner: uv });
301 }
302 }
303
304 let (u0, u1) = surface.domain_u();
305 let (v0, v1) = surface.domain_v();
306 for (u_fixed, first, fixed) in [
307 (true, true, u0),
308 (true, false, u1),
309 (false, true, v0),
310 (false, false, v1),
311 ] {
312 let Some(boundary) = boundary_curve(surface, u_fixed, first) else {
313 continue;
314 };
315 let uv = |w: Scalar| Vector2::from_array(if u_fixed { [fixed, w] } else { [w, fixed] });
316 let (overlaps, crossings) = curve_curve::curve_curve_overlaps_and_crossings::<4, 3>(
317 curve,
318 &boundary,
319 Scalar::ZERO,
320 max_nodes,
321 min_subdivision_size,
322 )?;
323 let ends = overlaps
324 .iter()
325 .flat_map(|o| [o.start, o.end])
326 .map(|h| (h.t, h.partner));
327 for (t, w) in ends.chain(crossings) {
328 candidates.push(Hit { t, partner: uv(w) });
329 }
330 }
331
332 coincidence::find_overlaps(candidates, on_surface)
333}
334
335pub fn curve_surface_intersect(
347 curve: &NurbCurve<4>,
348 surface: &NurbSurface<4>,
349 max_solutions: usize,
350 max_nodes: usize,
351 min_subdivision_size: Scalar,
352) -> GeopResult<Intersections<(Scalar, Vector2)>> {
353 if max_solutions == 0 || !curve_could_meet_aabb(curve, &surface.aabb, 3) {
357 return Ok(Intersections::Found(vec![]));
358 }
359 let ctx = |e: GeopError| {
360 e.with_context(format!(
361 "curve_surface_intersect(curve={curve:?}, surface={surface:?}, max_nodes={max_nodes}, \
362 min_subdivision_size={min_subdivision_size:?})"
363 ))
364 };
365 let overlaps = curve_surface_overlaps(
366 curve,
367 surface,
368 Scalar::ZERO,
369 max_nodes,
370 min_subdivision_size,
371 )
372 .with_context(&ctx)?;
373 let crossings = coincidence::crossings_between(curve, &overlaps, |piece| {
374 curve_surface_crossings(piece, surface, max_nodes, min_subdivision_size)
375 })
376 .with_context(&|e: GeopError| {
377 ctx(e.with_context(format!(
378 "the crossing search away from the overlaps {overlaps:?}"
379 )))
380 })?;
381 let samples = coincidence::samples(&overlaps, crossings.len(), max_solutions, |t| {
382 point_on(curve, surface, t, max_nodes, min_subdivision_size)
383 })?;
384 Ok(coincidence::assemble(
385 &overlaps,
386 crossings,
387 samples,
388 max_solutions,
389 ))
390}
391
392pub fn refine_crossing(
411 curve: &NurbCurve<4>,
412 surface: &NurbSurface<4>,
413 t: Scalar,
414 uv: Vector2,
415) -> (Scalar, Vector2) {
416 let refined = newton::refine(
417 [t, uv[0], uv[1]],
418 [curve.domain(), surface.domain_u(), surface.domain_v()],
419 |[tt, uu, vv]| {
420 let c = curve.evaluate(tt).ok()?;
421 let s = surface.evaluate(uu, vv).ok()?;
422 let ct = curve.tangent(tt).ok()?;
423 let (su, sv) = surface.derivatives(uu, vv).ok()?;
424 let mut a = [[Scalar::ZERO; 3]; 3];
425 let mut b = [Scalar::ZERO; 3];
426 for row in 0..3 {
427 a[row] = [ct[row], su[row].neg(), sv[row].neg()];
428 b[row] = s[row].sub(c[row]);
429 }
430 let delta = solve_linear_system(&Matrix::from_rows(a), &Vector::from_array(b)).ok()?;
431 Some([tt.add(delta[0]), uu.add(delta[1]), vv.add(delta[2])])
432 },
433 );
434 let [t_ref, u_ref, v_ref] = refined;
435 let holds_a_crossing = match (curve.evaluate(t_ref), surface.evaluate(u_ref, v_ref)) {
436 (Ok(c), Ok(s)) => c.could_be_equal(&s),
437 _ => false,
438 };
439 if !holds_a_crossing {
440 return (t, uv);
441 }
442 (t_ref, Vector2::from_array([u_ref, v_ref]))
443}
444
445pub fn crossing_points(
452 curve: &NurbCurve<4>,
453 surface: &NurbSurface<4>,
454) -> GeopResult<Vec<(Vector<3>, Vector2)>> {
455 let hits = match curve_surface_intersect(curve, surface, 8, MAX_NODES, min_subdivision_size())?
456 {
457 Intersections::Found(hits) => hits,
458 Intersections::Coincident(_) => {
459 return Err(GeopError::new(
460 "the curve lies on the surface: where it crosses it is no point",
461 ));
462 }
463 };
464 let mut points = Vec::with_capacity(hits.len());
465 for (t, uv) in hits {
466 let (t, uv) = refine_crossing(curve, surface, t, uv);
467 let ctx = |e: GeopError| {
468 e.with_context(format!(
469 "the curve crossing the surface at t={t:?}, uv={uv:?}, the surface's domain {:?} x {:?}",
470 surface.domain_u(),
471 surface.domain_v()
472 ))
473 };
474 let on_curve = curve.evaluate(t).map_err(ctx)?;
475 let on_surface = match surface.pole_at(uv[0], uv[1]) {
476 Some(pole) => pole,
477 None => surface.evaluate(uv[0], uv[1]).map_err(ctx)?,
478 };
479 if on_curve.could_be_equal(&on_surface) {
480 let point = Vector::from_array(std::array::from_fn(|k| {
481 on_curve[k].intersect(on_surface[k])
482 }));
483 points.push((point, uv));
484 }
485 }
486 Ok(points)
487}
488
489#[cfg(test)]
490mod tests {
491 use super::curve_surface_crossings;
492 use crate::test_fixtures::{knots, line, sphere_octant, weighted};
493 use crate::{nurb_curve::NurbCurve, nurb_surface::NurbSurface};
494 use geop_core_math::{scalars::Scalar, vector::Vector4};
495
496 const MAX: usize = 5000;
497 const MIN_SUBDIVISION_SIZE: f64 = 1e-6;
498
499 fn plane() -> NurbSurface<4> {
501 NurbSurface::try_new(
502 1,
503 1,
504 vec![
505 weighted(0., 0., 0., 1.),
506 weighted(0., 1., 0., 1.),
507 weighted(1., 0., 0., 1.),
508 weighted(1., 1., 0., 1.),
509 ],
510 knots(&[0., 0., 1., 1.]),
511 knots(&[0., 0., 1., 1.]),
512 )
513 .unwrap()
514 }
515
516 fn solve(c: &NurbCurve<4>, s: &NurbSurface<4>) -> Vec<(Scalar, [Scalar; 2])> {
517 curve_surface_crossings(c, s, MAX, Scalar::from_f64(MIN_SUBDIVISION_SIZE))
518 .unwrap()
519 .into_iter()
520 .map(|(t, uv)| (t, [uv[0], uv[1]]))
521 .collect()
522 }
523
524 #[test]
526 fn vertical_line_through_plane() {
527 let sols = solve(&line([0.25, 0.75, -1.], [0.25, 0.75, 1.]), &plane());
528 assert_eq!(sols.len(), 1, "{sols:?}");
529 let (t, [u, v]) = sols[0];
530 let f = Scalar::from_f64;
531 assert!(t.could_be_equal(f(0.5)) && u.could_be_equal(f(0.25)) && v.could_be_equal(f(0.75)));
532 }
533
534 #[test]
535 fn oblique_line_through_plane() {
536 let sols = solve(&line([0., 0., -1.], [1., 0.5, 1.]), &plane());
537 assert_eq!(sols.len(), 1, "{sols:?}");
538 let (t, [u, v]) = sols[0];
539 let f = Scalar::from_f64;
540 assert!(t.could_be_equal(f(0.5)) && u.could_be_equal(f(0.5)) && v.could_be_equal(f(0.25)));
541 }
542
543 #[test]
546 fn line_pierces_sphere() {
547 let sols = solve(&line([0., 0., 0.], [1., 1., 1.]), &sphere_octant());
548 assert_eq!(sols.len(), 1, "{sols:?}");
549 let r = 1.0 / 3f64.sqrt();
550 assert!(sols[0].0.could_be_equal(Scalar::from_f64(r)), "{sols:?}");
551 }
552
553 #[test]
555 fn endpoint_on_surface() {
556 let sols = solve(&line([0.3, 0.6, 1.], [0.3, 0.6, 0.]), &plane());
557 assert_eq!(sols.len(), 1, "{sols:?}");
558 assert!(sols[0].0.could_be_equal(Scalar::ONE), "{sols:?}");
559 }
560
561 #[test]
563 fn line_through_pole() {
564 let sols = solve(&line([0., 0., 0.5], [0., 0., 1.5]), &sphere_octant());
565 assert!(!sols.is_empty(), "{sols:?}");
566 for (t, [_, v]) in &sols {
567 assert!(
568 t.could_be_equal(Scalar::from_f64(0.5)) && v.could_be_equal(Scalar::ONE),
569 "{sols:?}"
570 );
571 }
572 }
573
574 #[test]
575 fn misses() {
576 assert!(solve(&line([0., 0., 0.1], [1., 1., 0.5]), &plane()).is_empty());
577 assert!(solve(&line([0., 0., 0.], [0.5, 0.5, 0.5]), &sphere_octant()).is_empty());
578 assert!(solve(&line([2., 2., -1.], [2., 2., 1.]), &plane()).is_empty());
579 }
580
581 #[test]
582 fn budget_exhaustion_is_an_error() {
583 let c = line([0., 0., -1.], [1., 0.5, 1.]);
584 assert!(
585 curve_surface_crossings(&c, &plane(), 1, Scalar::from_f64(MIN_SUBDIVISION_SIZE))
586 .is_err()
587 );
588 }
589
590 use super::curve_surface_intersect;
593 use crate::intersection::Intersections;
594 use geop_core_math::vector::Vector2;
595
596 fn wrap(c: &NurbCurve<4>, s: &NurbSurface<4>) -> Intersections<(Scalar, Vector2)> {
597 curve_surface_intersect(c, s, 5, MAX, Scalar::from_f64(MIN_SUBDIVISION_SIZE)).unwrap()
598 }
599
600 #[test]
602 fn equator_on_sphere_is_coincident() {
603 let w = std::f64::consts::FRAC_1_SQRT_2;
604 let equator = NurbCurve::try_new(
605 2,
606 vec![
607 weighted(1., 0., 0., 1.),
608 weighted(1., 1., 0., w),
609 weighted(0., 1., 0., 1.),
610 ],
611 knots(&[0., 0., 0., 1., 1., 1.]),
612 )
613 .unwrap();
614 let r = wrap(&equator, &sphere_octant());
615 assert!(r.is_coincident() && r.len() == 5, "{r:?}");
616 for (_, uv) in r.as_slice() {
617 assert!(uv[1].could_be_equal(Scalar::ZERO), "{r:?}");
618 }
619 }
620
621 #[test]
623 fn curve_along_patch_boundary() {
624 let r = wrap(&line([0.2, 0., 0.], [0.8, 0., 0.]), &plane());
625 assert!(r.is_coincident(), "{r:?}");
626 for (_, uv) in r.as_slice() {
627 assert!(uv[1].could_be_equal(Scalar::ZERO), "{r:?}");
628 }
629 }
630
631 #[test]
634 fn line_partly_on_plane() {
635 let r = wrap(&line([-0.5, 0.5, 0.], [0.5, 0.5, 0.]), &plane());
636 assert!(r.is_coincident(), "{r:?}");
637 let v = r.as_slice();
638 let half = Scalar::from_f64(0.5);
639 assert!(
640 v[0].0.could_be_equal(half) && v[0].1[0].could_be_equal(Scalar::ZERO),
641 "{v:?}"
642 );
643 assert!(
644 v[1].0.could_be_equal(Scalar::ONE) && v[1].1[0].could_be_equal(half),
645 "{v:?}"
646 );
647 for (t, _) in v {
648 assert!(!t.definitely_less(half), "{v:?}");
649 }
650 }
651
652 #[test]
661 fn the_point_room_is_left_for_is_the_middle_of_an_overlap() {
662 let r = curve_surface_intersect(
663 &line([0.5, 0.2, 0.], [0.5, 0.8, 0.]),
664 &plane(),
665 3,
666 MAX,
667 Scalar::from_f64(1e-4),
668 )
669 .unwrap();
670 assert!(r.is_coincident() && r.len() == 3, "{r:?}");
671 let middle = |t: &Scalar| (t.to_f64() - 0.5).abs() < 1e-12;
672 assert!(r.as_slice().iter().any(|(t, _)| middle(t)), "{r:?}");
673 }
674
675 #[test]
678 fn bump_with_ends_on_plane_is_not_coincident() {
679 let bump = NurbCurve::try_new(
680 2,
681 vec![
682 weighted(0.2, 0.5, 0., 1.),
683 weighted(0.5, 0.5, 0.6, 1.),
684 weighted(0.8, 0.5, 0., 1.),
685 ],
686 knots(&[0., 0., 0., 1., 1., 1.]),
687 )
688 .unwrap();
689 let r = wrap(&bump, &plane());
690 assert!(!r.is_coincident() && r.len() == 2, "{r:?}");
691 }
692
693 #[test]
701 fn spoke_touching_cap_quadrant_at_its_pole() {
702 let w = std::f64::consts::FRAC_1_SQRT_2;
703 let pole = weighted(0., 0., 2., 1.);
704 let quadrant = NurbSurface::try_new(
705 2,
706 1,
707 vec![
708 pole,
709 weighted(1., 0., 2., 1.),
710 pole,
711 weighted(1., -1., 2., w),
712 pole,
713 weighted(0., -1., 2., 1.),
714 ],
715 knots(&[0., 0., 0., 1., 1., 1.]),
716 knots(&[0., 0., 1., 1.]),
717 )
718 .unwrap();
719 let spoke = line([0., 1., 2.], [0., 0., 2.]);
720 let r =
721 curve_surface_intersect(&spoke, &quadrant, 7, 5000, Scalar::from_f64(1e-4)).unwrap();
722 assert!(!r.is_coincident() && !r.is_empty(), "{r:?}");
723 for (t, uv) in r.as_slice() {
724 assert!(
725 t.could_be_equal(Scalar::ONE) && uv[1].could_be_equal(Scalar::ZERO),
726 "{r:?}"
727 );
728 }
729 }
730
731 #[test]
746 fn spoke_touching_cap_quadrant_at_its_pole_far_from_the_origin() {
747 let f = Scalar::from_f64;
748 let w = f(std::f64::consts::FRAC_1_SQRT_2);
749 let at = |x: f64, z: f64, w: Scalar| {
750 let p = [x, -500., z].map(|c| f(c).mul(w));
751 Vector4::from_array([p[0], p[1], p[2], w])
752 };
753 let quadrant = NurbSurface::try_new(
754 2,
755 1,
756 vec![
757 at(1000., 53., Scalar::ONE),
758 at(1000., 50., Scalar::ONE),
759 at(997., 53., w),
760 at(1000., 50., w),
761 at(997., 50., Scalar::ONE),
762 at(1000., 50., Scalar::ONE),
763 ],
764 knots(&[0., 0., 0., 1., 1., 1.]),
765 knots(&[0., 0., 1., 1.]),
766 )
767 .unwrap();
768 let spoke = line([1000., -500., 47.], [1000., -500., 50.]);
769 let r =
770 curve_surface_intersect(&spoke, &quadrant, 7, 5000, Scalar::from_f64(1e-4)).unwrap();
771 assert!(!r.is_coincident() && !r.is_empty(), "{r:?}");
772 for (t, uv) in r.as_slice() {
773 assert!(
774 t.could_be_equal(Scalar::ONE) && uv[1].could_be_equal(Scalar::ONE),
775 "{r:?}"
776 );
777 }
778 }
779
780 #[test]
786 fn blended_spoke_touching_cap_quadrant_at_its_pole() {
787 let i = |lo: f64, hi: f64| Scalar::from_f64(lo).union(Scalar::from_f64(hi));
788 let x = i(460.99999999999983, 461.00000000000017);
789 let tiny = |e: f64| i(-e, e);
790 let w = i(0.7071067811865475, 0.7071067811865477);
791 let centre = Vector4::from_array([
792 x,
793 tiny(6.675221575521606e-308),
794 tiny(6.675221575521606e-308),
795 Scalar::ONE,
796 ]);
797 let quadrant = NurbSurface::try_new(
798 2,
799 1,
800 vec![
801 centre,
802 Vector4::from_array([
803 x,
804 tiny(1.7800590868057623e-307),
805 i(-3.0000000000000027, -2.999999999999998),
806 Scalar::ONE,
807 ]),
808 Vector4::from_array([
809 i(325.97622612699826, 325.9762261269986),
810 tiny(4.720094441974079e-308),
811 tiny(4.720094441974079e-308),
812 w,
813 ]),
814 Vector4::from_array([
815 i(325.97622612699826, 325.9762261269986),
816 i(-2.121320343559646, -2.1213203435596406),
817 i(-2.121320343559646, -2.1213203435596406),
818 w,
819 ]),
820 centre,
821 Vector4::from_array([
822 x,
823 i(-3.0000000000000027, -2.999999999999998),
824 tiny(1.7800590868057623e-307),
825 Scalar::ONE,
826 ]),
827 ],
828 knots(&[0., 0., 0., 1., 1., 1.]),
829 knots(&[0., 0., 1., 1.]),
830 )
831 .unwrap();
832 let two_thirds = i(0.6666666666619685, 0.6666666666713679);
833 let spoke = NurbCurve::try_new(
834 1,
835 vec![
836 centre,
837 Vector4::from_array([
838 i(460.99999999999955, 461.00000000000045),
839 i(1.9999999999859022, 2.000000000014108),
840 tiny(1.4092134437327347e-307),
841 i(0.9999999999999999, 1.0000000000000002),
842 ]),
843 ],
844 vec![Scalar::ZERO, Scalar::ZERO, two_thirds, two_thirds],
845 )
846 .unwrap();
847 let r =
848 curve_surface_intersect(&spoke, &quadrant, 7, 5000, Scalar::from_f64(1e-4)).unwrap();
849 assert!(!r.is_coincident() && !r.is_empty(), "{r:?}");
850 for (t, uv) in r.as_slice() {
851 assert!(
852 t.could_be_equal(Scalar::ZERO) && uv[1].could_be_equal(Scalar::ZERO),
853 "{r:?}"
854 );
855 }
856 }
857
858 mod old_suite {
861 use super::super::curve_surface_intersect;
862 use crate::{nurb_curve::NurbCurve, nurb_surface::NurbSurface};
863 use geop_core_math::{scalars::Scalar, vector::Vector4};
864
865 const MAX_NODES: usize = 2000;
866
867 fn ptc(x: f64, y: f64, z: f64, w: f64) -> Vector4 {
868 Vector4::from_array([
869 Scalar::from_f64(x),
870 Scalar::from_f64(y),
871 Scalar::from_f64(z),
872 Scalar::from_f64(w),
873 ])
874 }
875
876 fn pts(x: f64, y: f64, z: f64) -> Vector4 {
877 Vector4::from_array([
878 Scalar::from_f64(x),
879 Scalar::from_f64(y),
880 Scalar::from_f64(z),
881 Scalar::ONE,
882 ])
883 }
884
885 fn flat_xy() -> NurbSurface<4> {
887 let f = Scalar::from_f64;
888 NurbSurface::try_new(
889 1,
890 1,
891 vec![
892 pts(0., 0., 0.),
893 pts(0., 1., 0.),
894 pts(1., 0., 0.),
895 pts(1., 1., 0.),
896 ],
897 vec![f(0.), f(0.), f(1.), f(1.)],
898 vec![f(0.), f(0.), f(1.), f(1.)],
899 )
900 .unwrap()
901 }
902
903 fn vertical_crossing_line() -> NurbCurve<4> {
905 let f = Scalar::from_f64;
906 NurbCurve::try_new(
907 1,
908 vec![ptc(0.5, 0.5, -1., 1.), ptc(0.5, 0.5, 1., 1.)],
909 vec![f(0.), f(0.), f(1.), f(1.)],
910 )
911 .unwrap()
912 }
913
914 fn line_above_surface() -> NurbCurve<4> {
916 let f = Scalar::from_f64;
917 NurbCurve::try_new(
918 1,
919 vec![ptc(0.5, 0.5, 1., 1.), ptc(0.5, 0.5, 2., 1.)],
920 vec![f(0.), f(0.), f(1.), f(1.)],
921 )
922 .unwrap()
923 }
924
925 fn double_dip_curve() -> NurbCurve<4> {
930 let f = Scalar::from_f64;
931 NurbCurve::try_new(
932 2,
933 vec![
934 ptc(0.2, 0.3, 1.0, 1.),
935 ptc(0.5, 0.3, -2.0, 1.),
936 ptc(0.8, 0.3, 1.0, 1.),
937 ],
938 vec![f(0.), f(0.), f(0.), f(1.), f(1.), f(1.)],
939 )
940 .unwrap()
941 }
942
943 fn coplanar_line() -> NurbCurve<4> {
946 let f = Scalar::from_f64;
947 NurbCurve::try_new(
948 1,
949 vec![ptc(0.2, 0.5, 0., 1.), ptc(0.8, 0.5, 0., 1.)],
950 vec![f(0.), f(0.), f(1.), f(1.)],
951 )
952 .unwrap()
953 }
954
955 fn partial_overlap_line() -> NurbCurve<4> {
958 let f = Scalar::from_f64;
959 NurbCurve::try_new(
960 1,
961 vec![ptc(-0.5, 0.5, 0., 1.), ptc(0.5, 0.5, 0., 1.)],
962 vec![f(0.), f(0.), f(1.), f(1.)],
963 )
964 .unwrap()
965 }
966
967 fn oversized_line() -> NurbCurve<4> {
971 let f = Scalar::from_f64;
972 NurbCurve::try_new(
973 1,
974 vec![ptc(-1.0, 0.5, 0., 1.), ptc(2.0, 0.5, 0., 1.)],
975 vec![f(0.), f(0.), f(1.), f(1.)],
976 )
977 .unwrap()
978 }
979
980 fn full_width_line() -> NurbCurve<4> {
983 let f = Scalar::from_f64;
984 NurbCurve::try_new(
985 1,
986 vec![ptc(0., 0.5, 0., 1.), ptc(1., 0.5, 0., 1.)],
987 vec![f(0.), f(0.), f(1.), f(1.)],
988 )
989 .unwrap()
990 }
991
992 fn ptw(x: f64, y: f64, z: f64, w: f64) -> Vector4 {
995 Vector4::from_array([
996 Scalar::from_f64(x * w),
997 Scalar::from_f64(y * w),
998 Scalar::from_f64(z * w),
999 Scalar::from_f64(w),
1000 ])
1001 }
1002
1003 fn bent_surface() -> NurbSurface<4> {
1006 let f = Scalar::from_f64;
1007 NurbSurface::try_new(
1008 1,
1009 1,
1010 vec![
1011 pts(0., 0., 0.),
1012 pts(0., 2., 1.),
1013 pts(2., 0., 1.),
1014 pts(2., 2., 0.),
1015 ],
1016 vec![f(0.), f(0.), f(1.), f(1.)],
1017 vec![f(0.), f(0.), f(1.), f(1.)],
1018 )
1019 .unwrap()
1020 }
1021
1022 fn bent_curve() -> NurbCurve<4> {
1026 let f = Scalar::from_f64;
1027 NurbCurve::try_new(
1028 2,
1029 vec![
1030 ptc(0.2, 1.0, -1.0, 1.),
1031 ptc(1.0, 1.0, 3.0, 1.),
1032 ptc(1.8, 1.0, -1.0, 1.),
1033 ],
1034 vec![f(0.), f(0.), f(0.), f(1.), f(1.), f(1.)],
1035 )
1036 .unwrap()
1037 }
1038
1039 fn sphere_octant_patch() -> NurbSurface<4> {
1044 let f = Scalar::from_f64;
1045 let w = 1.0 / 2.0_f64.sqrt();
1046 NurbSurface::try_new(
1047 2,
1048 2,
1049 vec![
1050 ptw(1., 0., 0., 1.),
1052 ptw(1., 0., 1., w),
1053 ptw(0., 0., 1., 1.),
1054 ptw(1., 1., 0., w),
1056 ptw(1., 1., 1., 0.5),
1057 ptw(0., 0., 1., w),
1058 ptw(0., 1., 0., 1.),
1060 ptw(0., 1., 1., w),
1061 ptw(0., 0., 1., 1.),
1062 ],
1063 vec![f(0.), f(0.), f(0.), f(1.), f(1.), f(1.)],
1064 vec![f(0.), f(0.), f(0.), f(1.), f(1.), f(1.)],
1065 )
1066 .unwrap()
1067 }
1068
1069 fn equator_quarter_circle() -> NurbCurve<4> {
1072 let f = Scalar::from_f64;
1073 let w = 1.0 / 2.0_f64.sqrt();
1074 NurbCurve::try_new(
1075 2,
1076 vec![ptw(1., 0., 0., 1.), ptw(1., 1., 0., w), ptw(0., 1., 0., 1.)],
1077 vec![f(0.), f(0.), f(0.), f(1.), f(1.), f(1.)],
1078 )
1079 .unwrap()
1080 }
1081
1082 const MIN_SUBDIVISION_SIZE: f64 = 1e-2;
1083
1084 #[test]
1087 fn single_crossing_curve_has_one_solution() {
1088 let curve = vertical_crossing_line();
1089 let surf = flat_xy();
1090 let result = curve_surface_intersect(
1091 &curve,
1092 &surf,
1093 5,
1094 MAX_NODES,
1095 Scalar::from_f64(MIN_SUBDIVISION_SIZE),
1096 )
1097 .unwrap();
1098 assert_eq!(result.len(), 1);
1099 }
1100
1101 #[test]
1104 fn curve_missing_surface_has_no_solution() {
1105 let curve = line_above_surface();
1106 let surf = flat_xy();
1107 let result = curve_surface_intersect(
1108 &curve,
1109 &surf,
1110 5,
1111 MAX_NODES,
1112 Scalar::from_f64(MIN_SUBDIVISION_SIZE),
1113 )
1114 .unwrap();
1115 assert!(result.is_empty());
1116 }
1117
1118 #[test]
1121 fn max_solutions_zero_returns_empty() {
1122 let curve = vertical_crossing_line();
1123 let surf = flat_xy();
1124 let result = curve_surface_intersect(
1125 &curve,
1126 &surf,
1127 0,
1128 MAX_NODES,
1129 Scalar::from_f64(MIN_SUBDIVISION_SIZE),
1130 )
1131 .unwrap();
1132 assert!(result.is_empty());
1133 }
1134
1135 #[test]
1136 fn max_nodes_exhausted_errors() {
1137 let curve = double_dip_curve();
1144 let surf = flat_xy();
1145 let result = curve_surface_intersect(
1146 &curve,
1147 &surf,
1148 1000,
1149 1,
1150 Scalar::from_f64(MIN_SUBDIVISION_SIZE),
1151 );
1152 assert!(result.is_err());
1153 }
1154
1155 #[test]
1158 fn two_crossings_found_when_budget_allows() {
1159 let curve = double_dip_curve();
1160 let surf = flat_xy();
1161 let result = curve_surface_intersect(
1162 &curve,
1163 &surf,
1164 2,
1165 MAX_NODES,
1166 Scalar::from_f64(MIN_SUBDIVISION_SIZE),
1167 )
1168 .unwrap()
1169 .into_vec();
1170 assert_eq!(result.len(), 2);
1171 assert!(
1172 !result[0].0.could_be_equal(result[1].0),
1173 "the two crossings should remain distinct after unification"
1174 );
1175 }
1176
1177 #[test]
1178 fn max_solutions_one_caps_at_one_even_with_two_crossings() {
1179 let curve = double_dip_curve();
1180 let surf = flat_xy();
1181 let result = curve_surface_intersect(
1182 &curve,
1183 &surf,
1184 1,
1185 MAX_NODES,
1186 Scalar::from_f64(MIN_SUBDIVISION_SIZE),
1187 )
1188 .unwrap();
1189 assert_eq!(result.len(), 1);
1190 }
1191
1192 #[test]
1195 fn min_subdivision_size_controls_precision() {
1196 let curve = vertical_crossing_line();
1197 let surf = flat_xy();
1198 let result =
1199 curve_surface_intersect(&curve, &surf, 5, MAX_NODES, Scalar::from_f64(1e-3))
1200 .unwrap()
1201 .into_vec();
1202 assert_eq!(result.len(), 1);
1203
1204 let (_, uv) = result[0];
1205 assert!(
1206 uv[0]
1207 .sub(Scalar::from_f64(0.5))
1208 .abs()
1209 .could_be_less(Scalar::from_f64(1e-2))
1210 );
1211 assert!(
1212 uv[1]
1213 .sub(Scalar::from_f64(0.5))
1214 .abs()
1215 .could_be_less(Scalar::from_f64(1e-2))
1216 );
1217 }
1218
1219 #[test]
1222 fn coplanar_curve_terminates() {
1223 let curve = coplanar_line();
1224 let surf = flat_xy();
1225
1226 let result_one = curve_surface_intersect(
1228 &curve,
1229 &surf,
1230 1,
1231 MAX_NODES,
1232 Scalar::from_f64(MIN_SUBDIVISION_SIZE),
1233 )
1234 .unwrap();
1235 assert_eq!(result_one.len(), 1);
1236
1237 let result_many = curve_surface_intersect(
1240 &curve,
1241 &surf,
1242 5,
1243 MAX_NODES,
1244 Scalar::from_f64(MIN_SUBDIVISION_SIZE),
1245 )
1246 .unwrap();
1247 assert!(!result_many.is_empty());
1248 assert!(result_many.len() <= 5);
1249 }
1250
1251 #[test]
1254 fn coincident_curve_reaches_max_solutions() {
1255 let curve = full_width_line();
1256 let surf = flat_xy();
1257 let result =
1263 curve_surface_intersect(&curve, &surf, 5, 5000, Scalar::from_f64(1e-3)).unwrap();
1264 assert!(result.is_coincident());
1265 assert_eq!(result.len(), 5);
1266 }
1267
1268 #[test]
1271 fn partial_overlap_coplanar_line() {
1272 let curve = partial_overlap_line();
1273 let surf = flat_xy();
1274 let result = curve_surface_intersect(
1275 &curve,
1276 &surf,
1277 5,
1278 MAX_NODES,
1279 Scalar::from_f64(MIN_SUBDIVISION_SIZE),
1280 )
1281 .unwrap();
1282 assert!(!result.is_empty());
1283 assert!(result.len() <= 5);
1284
1285 let lower_bound = Scalar::from_f64(0.5 - MIN_SUBDIVISION_SIZE);
1288 for &(t, _) in result.as_slice() {
1289 assert!(!t.definitely_less(lower_bound));
1290 }
1291 }
1292
1293 #[test]
1296 fn curve_larger_than_surface_terminates() {
1297 let curve = oversized_line();
1298 let surf = flat_xy();
1299 let result = curve_surface_intersect(
1300 &curve,
1301 &surf,
1302 5,
1303 MAX_NODES,
1304 Scalar::from_f64(MIN_SUBDIVISION_SIZE),
1305 )
1306 .unwrap();
1307 assert!(!result.is_empty());
1308 assert!(result.len() <= 5);
1309
1310 let lower_bound = Scalar::from_f64(1.0 / 3.0 - MIN_SUBDIVISION_SIZE);
1313 let upper_bound = Scalar::from_f64(2.0 / 3.0 + MIN_SUBDIVISION_SIZE);
1314 for &(t, _) in result.as_slice() {
1315 assert!(!t.definitely_less(lower_bound));
1316 assert!(!t.definitely_greater(upper_bound));
1317 }
1318 }
1319
1320 #[test]
1323 fn curve_same_size_as_surface_terminates() {
1324 let curve = full_width_line();
1325 let surf = flat_xy();
1326
1327 let result_one = curve_surface_intersect(
1328 &curve,
1329 &surf,
1330 1,
1331 MAX_NODES,
1332 Scalar::from_f64(MIN_SUBDIVISION_SIZE),
1333 )
1334 .unwrap();
1335 assert_eq!(result_one.len(), 1);
1336
1337 let result_many = curve_surface_intersect(
1338 &curve,
1339 &surf,
1340 5,
1341 MAX_NODES,
1342 Scalar::from_f64(MIN_SUBDIVISION_SIZE),
1343 )
1344 .unwrap();
1345 assert!(!result_many.is_empty());
1346 assert!(result_many.len() <= 5);
1347 }
1348
1349 #[test]
1352 fn bent_surface_bent_curve_two_distinct_crossings() {
1353 let curve = bent_curve();
1354 let surf = bent_surface();
1355 let result = curve_surface_intersect(
1356 &curve,
1357 &surf,
1358 4,
1359 MAX_NODES,
1360 Scalar::from_f64(MIN_SUBDIVISION_SIZE),
1361 )
1362 .unwrap()
1363 .into_vec();
1364 assert_eq!(result.len(), 2);
1365 assert!(
1366 !result[0].0.could_be_equal(result[1].0),
1367 "the two crossings of a bent curve through a bent surface should be distinct"
1368 );
1369 }
1370
1371 #[test]
1374 fn coincident_circle_on_sphere_patch_terminates() {
1375 let curve = equator_quarter_circle();
1376 let surf = sphere_octant_patch();
1377
1378 let result_one = curve_surface_intersect(
1379 &curve,
1380 &surf,
1381 1,
1382 MAX_NODES,
1383 Scalar::from_f64(MIN_SUBDIVISION_SIZE),
1384 )
1385 .unwrap();
1386 assert_eq!(result_one.len(), 1);
1387
1388 let result_many = curve_surface_intersect(
1389 &curve,
1390 &surf,
1391 5,
1392 MAX_NODES,
1393 Scalar::from_f64(MIN_SUBDIVISION_SIZE),
1394 )
1395 .unwrap();
1396 assert!(!result_many.is_empty());
1397 assert!(result_many.len() <= 5);
1398 }
1399 }
1400}