1use std::collections::VecDeque;
17
18use super::{
19 Intersections,
20 coincidence::{self, Hit, Overlap},
21};
22use crate::nurb_surface::clamp;
23use crate::{
24 aabb::aabb_could_overlap,
25 contains::surface::{
26 boundary_curve, collapsed_boundary, surface_could_contain, surface_extents,
27 weights_positive,
28 },
29 fat_line::{
30 Plan, carried_width, clip_tensor, converged, directions, extent, greville_abscissae, plan,
31 },
32 intersection::curve_curve,
33 knot_insertion::pinned_clamped_end,
34 nurb_curve::{NurbCurve, dehomogenize},
35 nurb_surface::NurbSurface,
36};
37use geop_core_math::{
38 disjoint_set::DisjointSet,
39 geop_error::{GeopError, GeopResult, WithContext},
40 matrix::{Matrix, solve_linear_system},
41 scalars::Scalar,
42 vector::{Vector, Vector2},
43};
44
45fn clip<S: Scalar>(c: &NurbCurve<S, 4>, s: &NurbSurface<S, 4>) -> GeopResult<Option<[S; 3]>> {
59 let (u0, u1) = s.domain_u();
60 let (v0, v1) = s.domain_v();
61 let mut hats = [c.domain_as_scalar(), u0.union(u1), v0.union(v1)];
62 if !weights_positive(s) {
63 return Ok(Some(hats));
64 }
65 let (nc, nu, nv) = (c.control_points.len(), s.num_u, s.num_v);
66 let greville = [
67 greville_abscissae(&c.knot_vector, c.degree, nc)?,
68 greville_abscissae(&s.knot_vector_u, s.degree_u, nu)?,
69 greville_abscissae(&s.knot_vector_v, s.degree_v, nv)?,
70 ];
71
72 let at = |i: usize, j: usize| directions::cartesian::<S, 4, 3>(&s.control_points[i * nv + j]);
73 let add = |a: [f64; 3], b: [f64; 3]| std::array::from_fn(|k| a[k] + b[k]);
74 let chord = directions::sub(
75 directions::cartesian::<S, 4, 3>(&c.control_points[nc - 1]),
76 directions::cartesian::<S, 4, 3>(&c.control_points[0]),
77 );
78 let e_u = directions::sub(
79 add(at(nu - 1, 0), at(nu - 1, nv - 1)),
80 add(at(0, 0), at(0, nv - 1)),
81 );
82 let e_v = directions::sub(
83 add(at(0, nv - 1), at(nu - 1, nv - 1)),
84 add(at(0, 0), at(nu - 1, 0)),
85 );
86 let dirs: Vec<[f64; 3]> = [
87 directions::cross(e_u, e_v),
88 directions::cross(chord, e_v),
89 directions::cross(chord, e_u),
90 ]
91 .into_iter()
92 .filter_map(directions::unit)
93 .collect();
94 let dirs = directions::spanning(dirs);
95
96 let mut d = Vec::with_capacity(nc * nu * nv);
97 for n in dirs {
98 let n = directions::sharp::<S, 3>(n);
99 let ns: Vec<S> = s
100 .control_points
101 .iter()
102 .map(|q| directions::dot(&n, q))
103 .collect();
104 d.clear();
105 for p in &c.control_points {
106 let np = directions::dot(&n, p);
107 d.extend(
108 s.control_points
109 .iter()
110 .zip(&ns)
111 .map(|(q, &nq)| np.mul(q[3]).sub(p[3].mul(nq))),
112 );
113 }
114 if !clip_tensor(&d, &[nc, nu, nv], &greville, &mut hats) {
115 return Ok(None);
116 }
117 }
118 Ok(Some(hats))
119}
120
121fn solve_pinned<S: Scalar>(
129 c: &NurbCurve<S, 4>,
130 s: &NurbSurface<S, 4>,
131 hats: [S; 3],
132 budget: usize,
133 min_subdivision_size: S,
134) -> GeopResult<Option<Vec<[S; 3]>>> {
135 let overlapping = |found: [S; 3]| -> Option<[S; 3]> {
136 (0..3)
137 .all(|i| found[i].could_be_equal(hats[i]))
138 .then(|| std::array::from_fn(|i| hats[i].intersect(found[i])))
139 };
140
141 if let Some(first) =
142 pinned_clamped_end(hats[0], &c.knot_vector, c.control_points.len(), c.degree)
143 {
144 let cp = if first {
145 c.control_points[0]
146 } else {
147 c.control_points[c.control_points.len() - 1]
148 };
149 let Ok(end) = dehomogenize::<S, 4, 3>(&[cp]) else {
150 return Ok(None);
151 };
152 let found = surface_could_contain(s, &end[0], budget, min_subdivision_size)?;
153 return Ok(Some(
154 found
155 .and_then(|(u, v)| overlapping([hats[0], u, v]))
156 .into_iter()
157 .collect(),
158 ));
159 }
160
161 if let Some((boundary, along_v)) = collapsed_boundary(s, hats[1], hats[2]) {
162 let pairs = curve_curve::curve_curve_crossings::<S, 4, 3>(
163 c,
164 &boundary,
165 budget,
166 min_subdivision_size,
167 )?;
168 return Ok(Some(
169 pairs
170 .into_iter()
171 .filter_map(|(t, w)| {
172 overlapping(if along_v {
173 [t, hats[1], w]
174 } else {
175 [t, w, hats[2]]
176 })
177 })
178 .collect(),
179 ));
180 }
181 Ok(None)
182}
183
184pub fn curve_surface_crossings<S: Scalar>(
198 curve: &NurbCurve<S, 4>,
199 surface: &NurbSurface<S, 4>,
200 max_nodes: usize,
201 min_subdivision_size: S,
202) -> GeopResult<Vec<(S, Vector2<S>)>> {
203 let mut queue: VecDeque<(NurbCurve<S, 4>, NurbSurface<S, 4>)> = VecDeque::new();
204 queue.push_back((curve.clone(), surface.clone()));
205 let mut explored = 0usize;
206 let mut solutions: DisjointSet<(S, Vector2<S>)> = DisjointSet::new();
207 let mut insert = |[t, u, v]: [S; 3]| solutions.insert((t, Vector2::from_array([u, v])));
208
209 while let Some((c, s)) = queue.pop_front() {
210 if explored >= max_nodes {
211 return Err(GeopError::new(format!(
212 "curve_surface_crossings: exhausted max_nodes={max_nodes} with {} \
213 pairs pending; the result would be incomplete",
214 queue.len() + 1
215 )));
216 }
217 explored += 1;
218
219 if !aabb_could_overlap(&c.aabb, &s.aabb, 3) {
220 continue;
221 }
222 let Some(hats) = clip(&c, &s)? else {
223 continue;
224 };
225
226 let [size_u, size_v] = surface_extents(&s);
227 let sizes = [extent([c.control_points.clone()]), size_u, size_v];
228 let carried = carried_width(&c.control_points).max(carried_width(&s.control_points));
229 if converged(&sizes, carried, min_subdivision_size) {
230 let (u0, u1) = s.domain_u();
233 let (v0, v1) = s.domain_v();
234 insert([c.domain_as_scalar(), u0.union(u1), v0.union(v1)]);
235 continue;
236 }
237
238 if let Some(found) = solve_pinned(&c, &s, hats, max_nodes - explored, min_subdivision_size)?
239 {
240 found.into_iter().for_each(&mut insert);
241 continue;
242 }
243
244 let ranges = [c.domain(), s.domain_u(), s.domain_v()];
245 let order = match plan(&hats, &ranges, &sizes, min_subdivision_size)? {
246 Plan::Restrict(b) => match (c.sub_curve(b[0].0, b[0].1), s.sub_surface(b[1], b[2])) {
247 (Ok(rc), Ok(rs)) => {
248 queue.push_back((rc, rs));
249 continue;
250 }
251 _ => vec![0, 1, 2],
252 },
253 Plan::Bisect(order) => order,
254 };
255 let children = order.iter().find_map(|&dir| match dir {
256 0 => {
257 let (l, r) = c.split_mid().ok()?;
258 Some([(l, s.clone()), (r, s.clone())])
259 }
260 1 => {
261 let (l, r) = s.split_u_mid().ok()?;
262 Some([(c.clone(), l), (c.clone(), r)])
263 }
264 _ => {
265 let (l, r) = s.split_v_mid().ok()?;
266 Some([(c.clone(), l), (c.clone(), r)])
267 }
268 });
269 match children {
270 Some(children) => queue.extend(children),
271 None => insert(hats),
273 }
274 }
275
276 Ok(solutions.into_vec())
277}
278
279pub(crate) fn curve_surface_overlaps<S: Scalar>(
287 curve: &NurbCurve<S, 4>,
288 surface: &NurbSurface<S, 4>,
289 max_nodes: usize,
290 min_subdivision_size: S,
291) -> GeopResult<Vec<Overlap<S, Vector2<S>>>> {
292 let on_surface = |t: S| -> GeopResult<Option<Vector2<S>>> {
293 let point = curve.evaluate(t)?;
294 Ok(
295 surface_could_contain(surface, &point, max_nodes, min_subdivision_size)?
296 .map(|(u, v)| Vector2::from_array([u, v])),
297 )
298 };
299
300 let mut candidates = Vec::new();
301 let (t0, t1) = curve.domain();
302 for t in [t0, t1] {
303 if let Some(uv) = on_surface(t)? {
304 candidates.push(Hit { t, partner: uv });
305 }
306 }
307
308 let (u0, u1) = surface.domain_u();
309 let (v0, v1) = surface.domain_v();
310 for (u_fixed, first, fixed) in [
311 (true, true, u0),
312 (true, false, u1),
313 (false, true, v0),
314 (false, false, v1),
315 ] {
316 let Some(boundary) = boundary_curve(surface, u_fixed, first) else {
317 continue;
318 };
319 let uv = |w: S| Vector2::from_array(if u_fixed { [fixed, w] } else { [w, fixed] });
320 let (overlaps, crossings) = curve_curve::curve_curve_overlaps_and_crossings::<S, 4, 3>(
321 curve,
322 &boundary,
323 max_nodes,
324 min_subdivision_size,
325 )?;
326 let ends = overlaps
327 .iter()
328 .flat_map(|o| [o.start, o.end])
329 .map(|h| (h.t, h.partner));
330 for (t, w) in ends.chain(crossings) {
331 candidates.push(Hit { t, partner: uv(w) });
332 }
333 }
334
335 coincidence::find_overlaps(candidates, on_surface)
336}
337
338pub fn curve_surface_intersect<S: Scalar>(
352 curve: &NurbCurve<S, 4>,
353 surface: &NurbSurface<S, 4>,
354 max_solutions: usize,
355 max_nodes: usize,
356 min_subdivision_size: S,
357) -> GeopResult<Intersections<(S, Vector2<S>)>> {
358 if max_solutions == 0 || !aabb_could_overlap(&curve.aabb, &surface.aabb, 3) {
361 return Ok(Intersections::Found(vec![]));
362 }
363 let ctx = |e: GeopError| {
364 e.with_context(format!(
365 "curve_surface_intersect(curve={curve:?}, surface={surface:?}, max_nodes={max_nodes}, \
366 min_subdivision_size={min_subdivision_size:?})"
367 ))
368 };
369 let overlaps = curve_surface_overlaps(curve, surface, max_nodes, min_subdivision_size)
370 .with_context(&ctx)?;
371 let crossings = if overlaps.is_empty() {
372 curve_surface_crossings(curve, surface, max_nodes, min_subdivision_size).with_context(
373 &|e: GeopError| ctx(e.with_context("no overlap found; crossing search")),
374 )?
375 } else {
376 let mut crossings = Vec::new();
377 for (lo, hi) in coincidence::gaps(curve.domain(), &overlaps) {
378 let piece = curve.sub_curve(lo, hi)?;
379 crossings.extend(curve_surface_crossings(
380 &piece,
381 surface,
382 max_nodes,
383 min_subdivision_size,
384 )?);
385 }
386 crossings
387 };
388 let samples = coincidence::samples(&overlaps, max_solutions, |t| {
389 let point = curve.evaluate(t)?;
390 Ok(
391 surface_could_contain(surface, &point, max_nodes, min_subdivision_size)?
392 .map(|(u, v)| Vector2::from_array([u, v])),
393 )
394 })?;
395 Ok(coincidence::assemble(
396 &overlaps,
397 crossings,
398 samples,
399 max_solutions,
400 ))
401}
402
403const REFINE_ITERATIONS: usize = 12;
407
408pub fn refine_crossing<S: Scalar>(
436 curve: &NurbCurve<S, 4>,
437 surface: &NurbSurface<S, 4>,
438 t: S,
439 uv: Vector2<S>,
440) -> (S, Vector2<S>) {
441 let (t_lo, t_hi) = curve.domain();
442 let (u_lo, u_hi) = surface.domain_u();
443 let (v_lo, v_hi) = surface.domain_v();
444
445 let (mut tt, mut uu, mut vv) = (t.sharpen(), uv[0].sharpen(), uv[1].sharpen());
446 for iteration in 0..REFINE_ITERATIONS {
447 let (Ok(c), Ok(s)) = (curve.evaluate(tt), surface.evaluate(uu, vv)) else {
448 return (t, uv);
449 };
450 let (Ok(ct), Ok((su, sv))) = (curve.tangent(tt), surface.derivatives(uu, vv)) else {
451 return (t, uv);
452 };
453
454 let mut a = [[S::ZERO; 3]; 3];
455 let mut b = [S::ZERO; 3];
456 for row in 0..3 {
457 a[row] = [ct[row], su[row].neg(), sv[row].neg()];
458 b[row] = s[row].sub(c[row]);
459 }
460 let Ok(delta) = solve_linear_system(&Matrix::from_rows(a), &Vector::from_array(b)) else {
461 return (t, uv);
462 };
463
464 let next = [tt.add(delta[0]), uu.add(delta[1]), vv.add(delta[2])];
465 let next = if iteration + 1 == REFINE_ITERATIONS {
466 next
467 } else {
468 [next[0].sharpen(), next[1].sharpen(), next[2].sharpen()]
469 };
470 tt = clamp(next[0], t_lo, t_hi);
471 uu = clamp(next[1], u_lo, u_hi);
472 vv = clamp(next[2], v_lo, v_hi);
473 }
474
475 if !tt.could_be_equal(t) || !uu.could_be_equal(uv[0]) || !vv.could_be_equal(uv[1]) {
476 return (t, uv);
477 }
478 let (t_ref, u_ref, v_ref) = (t.intersect(tt), uv[0].intersect(uu), uv[1].intersect(vv));
479 let holds_a_crossing = match (curve.evaluate(t_ref), surface.evaluate(u_ref, v_ref)) {
486 (Ok(c), Ok(s)) => c.could_be_equal(&s),
487 _ => false,
488 };
489 if !holds_a_crossing {
490 return (t, uv);
491 }
492 (t_ref, Vector2::from_array([u_ref, v_ref]))
493}
494
495#[cfg(test)]
496mod tests {
497 use super::curve_surface_crossings;
498 use crate::{nurb_curve::NurbCurve, nurb_surface::NurbSurface};
499 use geop_core_math::for_all_scalars;
500 use geop_core_math::{scalars::Scalar, vector::Vector4};
501
502 const MAX: usize = 5000;
503 const EPS: f64 = 1e-6;
504
505 fn pt<S: Scalar>(x: f64, y: f64, z: f64, w: f64) -> Vector4<S> {
506 Vector4::from_array([
507 S::from_f64(x * w),
508 S::from_f64(y * w),
509 S::from_f64(z * w),
510 S::from_f64(w),
511 ])
512 }
513
514 fn knots<S: Scalar>(k: &[f64]) -> Vec<S> {
515 k.iter().map(|&x| S::from_f64(x)).collect()
516 }
517
518 fn line<S: Scalar>(a: [f64; 3], b: [f64; 3]) -> NurbCurve<S, 4> {
519 NurbCurve::try_new(
520 1,
521 vec![pt(a[0], a[1], a[2], 1.), pt(b[0], b[1], b[2], 1.)],
522 knots(&[0., 0., 1., 1.]),
523 )
524 .unwrap()
525 }
526
527 fn plane<S: Scalar>() -> NurbSurface<S, 4> {
529 NurbSurface::try_new(
530 1,
531 1,
532 vec![
533 pt(0., 0., 0., 1.),
534 pt(0., 1., 0., 1.),
535 pt(1., 0., 0., 1.),
536 pt(1., 1., 0., 1.),
537 ],
538 knots(&[0., 0., 1., 1.]),
539 knots(&[0., 0., 1., 1.]),
540 )
541 .unwrap()
542 }
543
544 fn sphere_octant<S: Scalar>() -> NurbSurface<S, 4> {
546 let w = std::f64::consts::FRAC_1_SQRT_2;
547 let circle = [(1., 0., 1.), (1., 1., w), (0., 1., 1.)];
548 let cps = circle
549 .iter()
550 .flat_map(|&(x, y, wu)| {
551 circle
552 .iter()
553 .map(move |&(r, z, wv)| pt(x * r, y * r, z, wu * wv))
554 })
555 .collect();
556 let k = knots(&[0., 0., 0., 1., 1., 1.]);
557 NurbSurface::try_new(2, 2, cps, k.clone(), k).unwrap()
558 }
559
560 fn solve<S: Scalar>(c: &NurbCurve<S, 4>, s: &NurbSurface<S, 4>) -> Vec<(S, [S; 2])> {
561 curve_surface_crossings(c, s, MAX, S::from_f64(EPS))
562 .unwrap()
563 .into_iter()
564 .map(|(t, uv)| (t, [uv[0], uv[1]]))
565 .collect()
566 }
567
568 fn check_vertical_line_through_plane<S: Scalar>() {
570 let sols = solve(&line::<S>([0.25, 0.75, -1.], [0.25, 0.75, 1.]), &plane());
571 assert_eq!(sols.len(), 1, "{sols:?}");
572 let (t, [u, v]) = sols[0];
573 let f = S::from_f64;
574 assert!(t.could_be_equal(f(0.5)) && u.could_be_equal(f(0.25)) && v.could_be_equal(f(0.75)));
575 }
576 #[test]
577 fn vertical_line_through_plane() {
578 for_all_scalars!(check_vertical_line_through_plane);
579 }
580
581 fn check_oblique_line_through_plane<S: Scalar>() {
582 let sols = solve(&line::<S>([0., 0., -1.], [1., 0.5, 1.]), &plane());
583 assert_eq!(sols.len(), 1, "{sols:?}");
584 let (t, [u, v]) = sols[0];
585 let f = S::from_f64;
586 assert!(t.could_be_equal(f(0.5)) && u.could_be_equal(f(0.5)) && v.could_be_equal(f(0.25)));
587 }
588 #[test]
589 fn oblique_line_through_plane() {
590 for_all_scalars!(check_oblique_line_through_plane);
591 }
592
593 fn check_line_pierces_sphere<S: Scalar>() {
596 let sols = solve(&line::<S>([0., 0., 0.], [1., 1., 1.]), &sphere_octant());
597 assert_eq!(sols.len(), 1, "{sols:?}");
598 let r = 1.0 / 3f64.sqrt();
599 assert!(sols[0].0.could_be_equal(S::from_f64(r)), "{sols:?}");
600 }
601 #[test]
602 fn line_pierces_sphere() {
603 for_all_scalars!(check_line_pierces_sphere);
604 }
605
606 fn check_endpoint_on_surface<S: Scalar>() {
608 let sols = solve(&line::<S>([0.3, 0.6, 1.], [0.3, 0.6, 0.]), &plane());
609 assert_eq!(sols.len(), 1, "{sols:?}");
610 assert!(sols[0].0.could_be_equal(S::ONE), "{sols:?}");
611 }
612 #[test]
613 fn endpoint_on_surface() {
614 for_all_scalars!(check_endpoint_on_surface);
615 }
616
617 fn check_line_through_pole<S: Scalar>() {
619 let sols = solve(&line::<S>([0., 0., 0.5], [0., 0., 1.5]), &sphere_octant());
620 assert!(!sols.is_empty(), "{sols:?}");
621 for (t, [_, v]) in &sols {
622 assert!(
623 t.could_be_equal(S::from_f64(0.5)) && v.could_be_equal(S::ONE),
624 "{sols:?}"
625 );
626 }
627 }
628 #[test]
629 fn line_through_pole() {
630 for_all_scalars!(check_line_through_pole);
631 }
632
633 fn check_misses<S: Scalar>() {
634 assert!(solve(&line::<S>([0., 0., 0.1], [1., 1., 0.5]), &plane()).is_empty());
635 assert!(solve(&line::<S>([0., 0., 0.], [0.5, 0.5, 0.5]), &sphere_octant()).is_empty());
636 assert!(solve(&line::<S>([2., 2., -1.], [2., 2., 1.]), &plane()).is_empty());
637 }
638 #[test]
639 fn misses() {
640 for_all_scalars!(check_misses);
641 }
642
643 fn check_budget_exhaustion_is_an_error<S: Scalar>() {
644 let c = line::<S>([0., 0., -1.], [1., 0.5, 1.]);
645 assert!(curve_surface_crossings(&c, &plane(), 1, S::from_f64(EPS)).is_err());
646 }
647 #[test]
648 fn budget_exhaustion_is_an_error() {
649 for_all_scalars!(check_budget_exhaustion_is_an_error);
650 }
651
652 use super::curve_surface_intersect;
655 use crate::intersection::Intersections;
656 use geop_core_math::vector::Vector2;
657
658 fn wrap<S: Scalar>(
659 c: &NurbCurve<S, 4>,
660 s: &NurbSurface<S, 4>,
661 ) -> Intersections<(S, Vector2<S>)> {
662 curve_surface_intersect(c, s, 5, MAX, S::from_f64(EPS)).unwrap()
663 }
664
665 fn check_equator_on_sphere_is_coincident<S: Scalar>() {
667 let w = std::f64::consts::FRAC_1_SQRT_2;
668 let equator = NurbCurve::try_new(
669 2,
670 vec![pt(1., 0., 0., 1.), pt(1., 1., 0., w), pt(0., 1., 0., 1.)],
671 knots(&[0., 0., 0., 1., 1., 1.]),
672 )
673 .unwrap();
674 let r = wrap(&equator, &sphere_octant::<S>());
675 assert!(r.is_coincident() && r.len() == 5, "{r:?}");
676 for (_, uv) in r.as_slice() {
677 assert!(uv[1].could_be_equal(S::ZERO), "{r:?}");
678 }
679 }
680 #[test]
681 fn equator_on_sphere_is_coincident() {
682 for_all_scalars!(check_equator_on_sphere_is_coincident);
683 }
684
685 fn check_curve_along_patch_boundary<S: Scalar>() {
687 let r = wrap(&line::<S>([0.2, 0., 0.], [0.8, 0., 0.]), &plane());
688 assert!(r.is_coincident(), "{r:?}");
689 for (_, uv) in r.as_slice() {
690 assert!(uv[1].could_be_equal(S::ZERO), "{r:?}");
691 }
692 }
693 #[test]
694 fn curve_along_patch_boundary() {
695 for_all_scalars!(check_curve_along_patch_boundary);
696 }
697
698 fn check_line_partly_on_plane<S: Scalar>() {
701 let r = wrap(&line::<S>([-0.5, 0.5, 0.], [0.5, 0.5, 0.]), &plane());
702 assert!(r.is_coincident(), "{r:?}");
703 let v = r.as_slice();
704 let half = S::from_f64(0.5);
705 assert!(
706 v[0].0.could_be_equal(half) && v[0].1[0].could_be_equal(S::ZERO),
707 "{v:?}"
708 );
709 assert!(
710 v[1].0.could_be_equal(S::ONE) && v[1].1[0].could_be_equal(half),
711 "{v:?}"
712 );
713 for (t, _) in v {
714 assert!(!t.definitely_less(half), "{v:?}");
715 }
716 }
717 #[test]
718 fn line_partly_on_plane() {
719 for_all_scalars!(check_line_partly_on_plane);
720 }
721
722 fn check_bump_with_ends_on_plane_is_not_coincident<S: Scalar>() {
725 let bump = NurbCurve::try_new(
726 2,
727 vec![
728 pt(0.2, 0.5, 0., 1.),
729 pt(0.5, 0.5, 0.6, 1.),
730 pt(0.8, 0.5, 0., 1.),
731 ],
732 knots(&[0., 0., 0., 1., 1., 1.]),
733 )
734 .unwrap();
735 let r = wrap(&bump, &plane::<S>());
736 assert!(!r.is_coincident() && r.len() == 2, "{r:?}");
737 }
738 #[test]
739 fn bump_with_ends_on_plane_is_not_coincident() {
740 for_all_scalars!(check_bump_with_ends_on_plane_is_not_coincident);
741 }
742
743 fn check_spoke_touching_cap_quadrant_at_its_pole<S: Scalar>() {
751 let w = std::f64::consts::FRAC_1_SQRT_2;
752 let pole = pt(0., 0., 2., 1.);
753 let quadrant = NurbSurface::try_new(
754 2,
755 1,
756 vec![
757 pole,
758 pt(1., 0., 2., 1.),
759 pole,
760 pt(1., -1., 2., w),
761 pole,
762 pt(0., -1., 2., 1.),
763 ],
764 knots(&[0., 0., 0., 1., 1., 1.]),
765 knots(&[0., 0., 1., 1.]),
766 )
767 .unwrap();
768 let spoke = line::<S>([0., 1., 2.], [0., 0., 2.]);
769 let r = curve_surface_intersect(&spoke, &quadrant, 7, 5000, S::from_f64(1e-4)).unwrap();
770 assert!(!r.is_coincident() && !r.is_empty(), "{r:?}");
771 for (t, uv) in r.as_slice() {
772 assert!(
773 t.could_be_equal(S::ONE) && uv[1].could_be_equal(S::ZERO),
774 "{r:?}"
775 );
776 }
777 }
778 #[test]
779 fn spoke_touching_cap_quadrant_at_its_pole() {
780 for_all_scalars!(check_spoke_touching_cap_quadrant_at_its_pole);
781 }
782
783 mod old_suite {
786 use super::super::curve_surface_intersect;
787 use crate::{nurb_curve::NurbCurve, nurb_surface::NurbSurface};
788 use geop_core_math::for_all_scalars;
789 use geop_core_math::{scalars::Scalar, vector::Vector4};
790
791 const MAX_NODES: usize = 2000;
792
793 fn ptc<S: Scalar>(x: f64, y: f64, z: f64, w: f64) -> Vector4<S> {
794 Vector4::from_array([
795 S::from_f64(x),
796 S::from_f64(y),
797 S::from_f64(z),
798 S::from_f64(w),
799 ])
800 }
801
802 fn pts<S: Scalar>(x: f64, y: f64, z: f64) -> Vector4<S> {
803 Vector4::from_array([S::from_f64(x), S::from_f64(y), S::from_f64(z), S::ONE])
804 }
805
806 fn flat_xy<S: Scalar>() -> NurbSurface<S, 4> {
808 let f = S::from_f64;
809 NurbSurface::try_new(
810 1,
811 1,
812 vec![
813 pts(0., 0., 0.),
814 pts(0., 1., 0.),
815 pts(1., 0., 0.),
816 pts(1., 1., 0.),
817 ],
818 vec![f(0.), f(0.), f(1.), f(1.)],
819 vec![f(0.), f(0.), f(1.), f(1.)],
820 )
821 .unwrap()
822 }
823
824 fn vertical_crossing_line<S: Scalar>() -> NurbCurve<S, 4> {
826 let f = S::from_f64;
827 NurbCurve::try_new(
828 1,
829 vec![ptc(0.5, 0.5, -1., 1.), ptc(0.5, 0.5, 1., 1.)],
830 vec![f(0.), f(0.), f(1.), f(1.)],
831 )
832 .unwrap()
833 }
834
835 fn line_above_surface<S: Scalar>() -> NurbCurve<S, 4> {
837 let f = S::from_f64;
838 NurbCurve::try_new(
839 1,
840 vec![ptc(0.5, 0.5, 1., 1.), ptc(0.5, 0.5, 2., 1.)],
841 vec![f(0.), f(0.), f(1.), f(1.)],
842 )
843 .unwrap()
844 }
845
846 fn double_dip_curve<S: Scalar>() -> NurbCurve<S, 4> {
851 let f = S::from_f64;
852 NurbCurve::try_new(
853 2,
854 vec![
855 ptc(0.2, 0.3, 1.0, 1.),
856 ptc(0.5, 0.3, -2.0, 1.),
857 ptc(0.8, 0.3, 1.0, 1.),
858 ],
859 vec![f(0.), f(0.), f(0.), f(1.), f(1.), f(1.)],
860 )
861 .unwrap()
862 }
863
864 fn coplanar_line<S: Scalar>() -> NurbCurve<S, 4> {
867 let f = S::from_f64;
868 NurbCurve::try_new(
869 1,
870 vec![ptc(0.2, 0.5, 0., 1.), ptc(0.8, 0.5, 0., 1.)],
871 vec![f(0.), f(0.), f(1.), f(1.)],
872 )
873 .unwrap()
874 }
875
876 fn partial_overlap_line<S: Scalar>() -> NurbCurve<S, 4> {
879 let f = S::from_f64;
880 NurbCurve::try_new(
881 1,
882 vec![ptc(-0.5, 0.5, 0., 1.), ptc(0.5, 0.5, 0., 1.)],
883 vec![f(0.), f(0.), f(1.), f(1.)],
884 )
885 .unwrap()
886 }
887
888 fn oversized_line<S: Scalar>() -> NurbCurve<S, 4> {
892 let f = S::from_f64;
893 NurbCurve::try_new(
894 1,
895 vec![ptc(-1.0, 0.5, 0., 1.), ptc(2.0, 0.5, 0., 1.)],
896 vec![f(0.), f(0.), f(1.), f(1.)],
897 )
898 .unwrap()
899 }
900
901 fn full_width_line<S: Scalar>() -> NurbCurve<S, 4> {
904 let f = S::from_f64;
905 NurbCurve::try_new(
906 1,
907 vec![ptc(0., 0.5, 0., 1.), ptc(1., 0.5, 0., 1.)],
908 vec![f(0.), f(0.), f(1.), f(1.)],
909 )
910 .unwrap()
911 }
912
913 fn ptw<S: Scalar>(x: f64, y: f64, z: f64, w: f64) -> Vector4<S> {
916 Vector4::from_array([
917 S::from_f64(x * w),
918 S::from_f64(y * w),
919 S::from_f64(z * w),
920 S::from_f64(w),
921 ])
922 }
923
924 fn bent_surface<S: Scalar>() -> NurbSurface<S, 4> {
927 let f = S::from_f64;
928 NurbSurface::try_new(
929 1,
930 1,
931 vec![
932 pts(0., 0., 0.),
933 pts(0., 2., 1.),
934 pts(2., 0., 1.),
935 pts(2., 2., 0.),
936 ],
937 vec![f(0.), f(0.), f(1.), f(1.)],
938 vec![f(0.), f(0.), f(1.), f(1.)],
939 )
940 .unwrap()
941 }
942
943 fn bent_curve<S: Scalar>() -> NurbCurve<S, 4> {
947 let f = S::from_f64;
948 NurbCurve::try_new(
949 2,
950 vec![
951 ptc(0.2, 1.0, -1.0, 1.),
952 ptc(1.0, 1.0, 3.0, 1.),
953 ptc(1.8, 1.0, -1.0, 1.),
954 ],
955 vec![f(0.), f(0.), f(0.), f(1.), f(1.), f(1.)],
956 )
957 .unwrap()
958 }
959
960 fn sphere_octant_patch<S: Scalar>() -> NurbSurface<S, 4> {
965 let f = S::from_f64;
966 let w = 1.0 / 2.0_f64.sqrt();
967 NurbSurface::try_new(
968 2,
969 2,
970 vec![
971 ptw(1., 0., 0., 1.),
973 ptw(1., 0., 1., w),
974 ptw(0., 0., 1., 1.),
975 ptw(1., 1., 0., w),
977 ptw(1., 1., 1., 0.5),
978 ptw(0., 0., 1., w),
979 ptw(0., 1., 0., 1.),
981 ptw(0., 1., 1., w),
982 ptw(0., 0., 1., 1.),
983 ],
984 vec![f(0.), f(0.), f(0.), f(1.), f(1.), f(1.)],
985 vec![f(0.), f(0.), f(0.), f(1.), f(1.), f(1.)],
986 )
987 .unwrap()
988 }
989
990 fn equator_quarter_circle<S: Scalar>() -> NurbCurve<S, 4> {
993 let f = S::from_f64;
994 let w = 1.0 / 2.0_f64.sqrt();
995 NurbCurve::try_new(
996 2,
997 vec![ptw(1., 0., 0., 1.), ptw(1., 1., 0., w), ptw(0., 1., 0., 1.)],
998 vec![f(0.), f(0.), f(0.), f(1.), f(1.), f(1.)],
999 )
1000 .unwrap()
1001 }
1002
1003 const EPS: f64 = 1e-2;
1004
1005 fn check_single_crossing_curve_has_one_solution<S: Scalar>() {
1008 let curve = vertical_crossing_line::<S>();
1009 let surf = flat_xy::<S>();
1010 let result =
1011 curve_surface_intersect(&curve, &surf, 5, MAX_NODES, S::from_f64(EPS)).unwrap();
1012 assert_eq!(result.len(), 1);
1013 }
1014 #[test]
1015 fn single_crossing_curve_has_one_solution() {
1016 for_all_scalars!(check_single_crossing_curve_has_one_solution);
1017 }
1018
1019 fn check_curve_missing_surface_has_no_solution<S: Scalar>() {
1022 let curve = line_above_surface::<S>();
1023 let surf = flat_xy::<S>();
1024 let result =
1025 curve_surface_intersect(&curve, &surf, 5, MAX_NODES, S::from_f64(EPS)).unwrap();
1026 assert!(result.is_empty());
1027 }
1028 #[test]
1029 fn curve_missing_surface_has_no_solution() {
1030 for_all_scalars!(check_curve_missing_surface_has_no_solution);
1031 }
1032
1033 fn check_max_solutions_zero_returns_empty<S: Scalar>() {
1036 let curve = vertical_crossing_line::<S>();
1037 let surf = flat_xy::<S>();
1038 let result =
1039 curve_surface_intersect(&curve, &surf, 0, MAX_NODES, S::from_f64(EPS)).unwrap();
1040 assert!(result.is_empty());
1041 }
1042 #[test]
1043 fn max_solutions_zero_returns_empty() {
1044 for_all_scalars!(check_max_solutions_zero_returns_empty);
1045 }
1046
1047 fn check_max_nodes_exhausted_errors<S: Scalar>() {
1048 let curve = double_dip_curve::<S>();
1055 let surf = flat_xy::<S>();
1056 let result = curve_surface_intersect(&curve, &surf, 1000, 1, S::from_f64(EPS));
1057 assert!(result.is_err());
1058 }
1059 #[test]
1060 fn max_nodes_exhausted_errors() {
1061 for_all_scalars!(check_max_nodes_exhausted_errors);
1062 }
1063
1064 fn check_two_crossings_found_when_budget_allows<S: Scalar>() {
1067 let curve = double_dip_curve::<S>();
1068 let surf = flat_xy::<S>();
1069 let result = curve_surface_intersect(&curve, &surf, 2, MAX_NODES, S::from_f64(EPS))
1070 .unwrap()
1071 .into_vec();
1072 assert_eq!(result.len(), 2);
1073 assert!(
1074 !result[0].0.could_be_equal(result[1].0),
1075 "the two crossings should remain distinct after unification"
1076 );
1077 }
1078 #[test]
1079 fn two_crossings_found_when_budget_allows() {
1080 for_all_scalars!(check_two_crossings_found_when_budget_allows);
1081 }
1082
1083 fn check_max_solutions_one_caps_at_one_even_with_two_crossings<S: Scalar>() {
1084 let curve = double_dip_curve::<S>();
1085 let surf = flat_xy::<S>();
1086 let result =
1087 curve_surface_intersect(&curve, &surf, 1, MAX_NODES, S::from_f64(EPS)).unwrap();
1088 assert_eq!(result.len(), 1);
1089 }
1090 #[test]
1091 fn max_solutions_one_caps_at_one_even_with_two_crossings() {
1092 for_all_scalars!(check_max_solutions_one_caps_at_one_even_with_two_crossings);
1093 }
1094
1095 fn check_min_subdivision_size_controls_precision<S: Scalar>() {
1098 let curve = vertical_crossing_line::<S>();
1099 let surf = flat_xy::<S>();
1100 let result = curve_surface_intersect(&curve, &surf, 5, MAX_NODES, S::from_f64(1e-3))
1101 .unwrap()
1102 .into_vec();
1103 assert_eq!(result.len(), 1);
1104
1105 let (_, uv) = result[0];
1106 assert!(
1107 uv[0]
1108 .sub(S::from_f64(0.5))
1109 .abs()
1110 .could_be_less(S::from_f64(1e-2))
1111 );
1112 assert!(
1113 uv[1]
1114 .sub(S::from_f64(0.5))
1115 .abs()
1116 .could_be_less(S::from_f64(1e-2))
1117 );
1118 }
1119 #[test]
1120 fn min_subdivision_size_controls_precision() {
1121 for_all_scalars!(check_min_subdivision_size_controls_precision);
1122 }
1123
1124 fn check_coplanar_curve_terminates<S: Scalar>() {
1127 let curve = coplanar_line::<S>();
1128 let surf = flat_xy::<S>();
1129
1130 let result_one =
1132 curve_surface_intersect(&curve, &surf, 1, MAX_NODES, S::from_f64(EPS)).unwrap();
1133 assert_eq!(result_one.len(), 1);
1134
1135 let result_many =
1138 curve_surface_intersect(&curve, &surf, 5, MAX_NODES, S::from_f64(EPS)).unwrap();
1139 assert!(!result_many.is_empty());
1140 assert!(result_many.len() <= 5);
1141 }
1142 #[test]
1143 fn coplanar_curve_terminates() {
1144 for_all_scalars!(check_coplanar_curve_terminates);
1145 }
1146
1147 fn check_coincident_curve_reaches_max_solutions<S: Scalar>() {
1150 let curve = full_width_line::<S>();
1151 let surf = flat_xy::<S>();
1152 let result =
1158 curve_surface_intersect(&curve, &surf, 5, 5000, S::from_f64(1e-3)).unwrap();
1159 assert!(result.is_coincident());
1160 assert_eq!(result.len(), 5);
1161 }
1162 #[test]
1163 fn coincident_curve_reaches_max_solutions() {
1164 for_all_scalars!(check_coincident_curve_reaches_max_solutions);
1165 }
1166
1167 fn check_partial_overlap_coplanar_line<S: Scalar>() {
1170 let curve = partial_overlap_line::<S>();
1171 let surf = flat_xy::<S>();
1172 let result =
1173 curve_surface_intersect(&curve, &surf, 5, MAX_NODES, S::from_f64(EPS)).unwrap();
1174 assert!(!result.is_empty());
1175 assert!(result.len() <= 5);
1176
1177 let lower_bound = S::from_f64(0.5 - EPS);
1180 for &(t, _) in result.as_slice() {
1181 assert!(!t.definitely_less(lower_bound));
1182 }
1183 }
1184 #[test]
1185 fn partial_overlap_coplanar_line() {
1186 for_all_scalars!(check_partial_overlap_coplanar_line);
1187 }
1188
1189 fn check_curve_larger_than_surface_terminates<S: Scalar>() {
1192 let curve = oversized_line::<S>();
1193 let surf = flat_xy::<S>();
1194 let result =
1195 curve_surface_intersect(&curve, &surf, 5, MAX_NODES, S::from_f64(EPS)).unwrap();
1196 assert!(!result.is_empty());
1197 assert!(result.len() <= 5);
1198
1199 let lower_bound = S::from_f64(1.0 / 3.0 - EPS);
1202 let upper_bound = S::from_f64(2.0 / 3.0 + EPS);
1203 for &(t, _) in result.as_slice() {
1204 assert!(!t.definitely_less(lower_bound));
1205 assert!(!t.definitely_greater(upper_bound));
1206 }
1207 }
1208 #[test]
1209 fn curve_larger_than_surface_terminates() {
1210 for_all_scalars!(check_curve_larger_than_surface_terminates);
1211 }
1212
1213 fn check_curve_same_size_as_surface_terminates<S: Scalar>() {
1216 let curve = full_width_line::<S>();
1217 let surf = flat_xy::<S>();
1218
1219 let result_one =
1220 curve_surface_intersect(&curve, &surf, 1, MAX_NODES, S::from_f64(EPS)).unwrap();
1221 assert_eq!(result_one.len(), 1);
1222
1223 let result_many =
1224 curve_surface_intersect(&curve, &surf, 5, MAX_NODES, S::from_f64(EPS)).unwrap();
1225 assert!(!result_many.is_empty());
1226 assert!(result_many.len() <= 5);
1227 }
1228 #[test]
1229 fn curve_same_size_as_surface_terminates() {
1230 for_all_scalars!(check_curve_same_size_as_surface_terminates);
1231 }
1232
1233 fn check_bent_surface_bent_curve_two_distinct_crossings<S: Scalar>() {
1236 let curve = bent_curve::<S>();
1237 let surf = bent_surface::<S>();
1238 let result = curve_surface_intersect(&curve, &surf, 4, MAX_NODES, S::from_f64(EPS))
1239 .unwrap()
1240 .into_vec();
1241 assert_eq!(result.len(), 2);
1242 assert!(
1243 !result[0].0.could_be_equal(result[1].0),
1244 "the two crossings of a bent curve through a bent surface should be distinct"
1245 );
1246 }
1247 #[test]
1248 fn bent_surface_bent_curve_two_distinct_crossings() {
1249 for_all_scalars!(check_bent_surface_bent_curve_two_distinct_crossings);
1250 }
1251
1252 fn check_coincident_circle_on_sphere_patch_terminates<S: Scalar>() {
1255 let curve = equator_quarter_circle::<S>();
1256 let surf = sphere_octant_patch::<S>();
1257
1258 let result_one =
1259 curve_surface_intersect(&curve, &surf, 1, MAX_NODES, S::from_f64(EPS)).unwrap();
1260 assert_eq!(result_one.len(), 1);
1261
1262 let result_many =
1263 curve_surface_intersect(&curve, &surf, 5, MAX_NODES, S::from_f64(EPS)).unwrap();
1264 assert!(!result_many.is_empty());
1265 assert!(result_many.len() <= 5);
1266 }
1267 #[test]
1268 fn coincident_circle_on_sphere_patch_terminates() {
1269 for_all_scalars!(check_coincident_circle_on_sphere_patch_terminates);
1270 }
1271 }
1272}