1use crate::certified_bezier::{distance_between_point_intervals_upper, Interval};
4use crate::certified_projection::{CurvePairParameterBox, ParameterInterval};
5use crate::certified_refinement::{piecewise_bezier_cells, RefinementBudget};
6use axiolid_contracts::{GeomError, GeomResult, Sign};
7use axiolid_core::{Point2, Scalar};
8use axiolid_curve::BSplineCurve2;
9use axiolid_evaluate::curve::bspline_jet2;
10use axiolid_predicates::orient2d;
11
12const MAX_CERTIFIED_CURVE_INTERSECTION_NODES: u32 = 100_000;
13const MAX_CERTIFIED_CURVE_INTERSECTION_DEPTH: u16 = 64;
14
15#[derive(Debug, Clone, Copy, PartialEq)]
17pub struct CertifiedCurveIntersectionOptions {
18 parameter_tolerance: Scalar,
19 max_nodes: u32,
20 max_depth: u16,
21}
22
23impl CertifiedCurveIntersectionOptions {
24 pub fn new(parameter_tolerance: Scalar, max_nodes: u32, max_depth: u16) -> GeomResult<Self> {
29 if !parameter_tolerance.is_finite()
30 || parameter_tolerance <= 0.0
31 || max_nodes == 0
32 || max_nodes > MAX_CERTIFIED_CURVE_INTERSECTION_NODES
33 || max_depth == 0
34 || max_depth > MAX_CERTIFIED_CURVE_INTERSECTION_DEPTH
35 {
36 return Err(GeomError::InvalidInput(
37 "curve-intersection tolerance must be finite and positive; max_nodes must be in 1..=100000 and max_depth in 1..=64".to_owned(),
38 ));
39 }
40 Ok(Self {
41 parameter_tolerance,
42 max_nodes,
43 max_depth,
44 })
45 }
46
47 pub const fn parameter_tolerance(self) -> Scalar {
49 self.parameter_tolerance
50 }
51
52 pub const fn max_nodes(self) -> u32 {
54 self.max_nodes
55 }
56
57 pub const fn max_depth(self) -> u16 {
59 self.max_depth
60 }
61}
62
63#[non_exhaustive]
65#[derive(Debug, Clone, PartialEq)]
66pub struct TransverseCurveIntersection2 {
67 pub first_parameter: ParameterInterval,
69 pub second_parameter: ParameterInterval,
71 pub point: Point2,
73 pub residual_upper_bound: Scalar,
75 pub jacobian_determinant_lower_bound: Scalar,
77}
78
79#[non_exhaustive]
81#[derive(Debug, Clone, Copy, PartialEq)]
82pub struct ClassifiedCurveContact2 {
83 pub parameters: CurvePairParameterBox,
85 pub classification: CurveIntersectionDegeneracy,
87}
88
89#[non_exhaustive]
91#[derive(Debug, Clone, Copy, PartialEq, Eq)]
92pub enum CurveIntersectionDegeneracy {
93 PointContact,
95 Tangency,
97 Overlap,
99 Unresolved,
101 BoundaryCrossing,
110}
111
112#[non_exhaustive]
114#[derive(Debug, Clone, PartialEq)]
115pub enum CertifiedCurveIntersection2 {
116 Complete {
119 intersections: Vec<TransverseCurveIntersection2>,
121 visited_nodes: u32,
123 },
124 Degenerate {
126 classification: CurveIntersectionDegeneracy,
132 contacts: Vec<ClassifiedCurveContact2>,
135 visited_nodes: u32,
137 },
138}
139
140pub fn intersect_curve2_certified(
148 first: &BSplineCurve2,
149 second: &BSplineCurve2,
150 options: CertifiedCurveIntersectionOptions,
151) -> GeomResult<CertifiedCurveIntersection2> {
152 let mut budget =
153 RefinementBudget::new(options.max_nodes(), "certified curve intersection budget");
154 let first_cells = piecewise_bezier_cells(first, |p| [p.x, p.y, 0.0], &mut budget)?;
155 let second_cells = piecewise_bezier_cells(second, |p| [p.x, p.y, 0.0], &mut budget)?;
156 let count = first_cells.len().checked_mul(second_cells.len());
157 let visited_nodes = count
158 .and_then(|value| u32::try_from(value).ok())
159 .filter(|&value| value <= options.max_nodes())
160 .ok_or(axiolid_contracts::GeomError::BudgetExceeded {
161 resource: "certified curve intersection budget",
162 })?;
163 if first == second {
164 let mut contacts = Vec::new();
165 contacts
166 .try_reserve_exact(first_cells.len())
167 .map_err(|_| GeomError::BudgetExceeded {
168 resource: "certified curve intersection result allocation",
169 })?;
170 let classification = if has_control_point_extent(first) {
171 CurveIntersectionDegeneracy::Overlap
172 } else {
173 CurveIntersectionDegeneracy::PointContact
174 };
175 for (first_cell, second_cell) in first_cells.iter().zip(&second_cells) {
176 push_contact(
177 &mut contacts,
178 pair_box(first_cell, second_cell),
179 classification,
180 )?;
181 }
182 return Ok(degenerate(contacts, visited_nodes));
183 }
184 if structural_start_tangency(first, second) || structural_start_tangency(second, first) {
185 let mut contacts = Vec::new();
186 push_contact(
187 &mut contacts,
188 pair_box(&first_cells[0], &second_cells[0]),
189 CurveIntersectionDegeneracy::Tangency,
190 )?;
191 return Ok(degenerate(contacts, visited_nodes));
192 }
193 if linear_polynomial(first) && linear_polynomial(second) {
194 return classify_lines(
195 first,
196 second,
197 visited_nodes,
198 vec![pair_box(&first_cells[0], &second_cells[0])],
199 options,
200 );
201 }
202
203 classify_nonlinear(
204 first,
205 second,
206 first_cells,
207 second_cells,
208 visited_nodes,
209 options,
210 )
211}
212
213#[derive(Debug, Clone, Copy)]
214struct PendingCurvePair {
215 first_index: usize,
216 second_index: usize,
217 parameters: CurvePairParameterBox,
218 depth: u16,
219}
220
221fn classify_nonlinear(
222 first: &BSplineCurve2,
223 second: &BSplineCurve2,
224 first_cells: Vec<crate::certified_bezier::Cell>,
225 second_cells: Vec<crate::certified_bezier::Cell>,
226 mut visited_nodes: u32,
227 options: CertifiedCurveIntersectionOptions,
228) -> GeomResult<CertifiedCurveIntersection2> {
229 let mut pending = Vec::new();
230 pending
231 .try_reserve_exact(usize::from(options.max_depth()) + 1)
232 .map_err(|_| GeomError::BudgetExceeded {
233 resource: "certified curve intersection pending allocation",
234 })?;
235 let mut intersections = Vec::new();
236 let mut contacts = Vec::new();
237
238 for (first_index, first_base) in first_cells.iter().enumerate() {
239 for (second_index, second_base) in second_cells.iter().enumerate() {
240 pending.push(PendingCurvePair {
241 first_index,
242 second_index,
243 parameters: pair_box(first_base, second_base),
244 depth: 0,
245 });
246 while let Some(current) = pending.pop() {
247 let first_cell = first_cells[current.first_index]
248 .restrict(current.parameters.first.start, current.parameters.first.end)?;
249 let second_cell = second_cells[current.second_index].restrict(
250 current.parameters.second.start,
251 current.parameters.second.end,
252 )?;
253 let depth = current.depth;
254 if residual_excludes_zero(&first_cell, &second_cell)? {
255 continue;
256 }
257 if let Some(root) = krawczyk_root(first, second, &first_cell, &second_cell)? {
258 if root.first_parameter.end - root.first_parameter.start
259 <= options.parameter_tolerance()
260 && root.second_parameter.end - root.second_parameter.start
261 <= options.parameter_tolerance()
262 {
263 push_root(&mut intersections, root)?;
264 continue;
265 }
266 if depth >= options.max_depth() {
267 push_contact(
268 &mut contacts,
269 CurvePairParameterBox {
270 first: root.first_parameter,
271 second: root.second_parameter,
272 },
273 CurveIntersectionDegeneracy::Unresolved,
274 )?;
275 continue;
276 }
277 visited_nodes = visited_nodes
278 .checked_add(1)
279 .filter(|&count| count <= options.max_nodes())
280 .ok_or(GeomError::BudgetExceeded {
281 resource: "certified curve intersection budget",
282 })?;
283 pending.push(PendingCurvePair {
284 first_index: current.first_index,
285 second_index: current.second_index,
286 parameters: CurvePairParameterBox {
287 first: contract_interval(
288 &first_cell,
289 root.first_parameter,
290 options.parameter_tolerance(),
291 ),
292 second: contract_interval(
293 &second_cell,
294 root.second_parameter,
295 options.parameter_tolerance(),
296 ),
297 },
298 depth: depth.checked_add(1).ok_or_else(|| {
299 GeomError::Degenerate("curve-intersection depth overflow".to_owned())
300 })?,
301 });
302 continue;
303 }
304 if depth >= options.max_depth() {
305 push_contact(
306 &mut contacts,
307 pair_box(&first_cell, &second_cell),
308 classify_exhausted_box(&first_cell, &second_cell, first_base, second_base)?,
309 )?;
310 continue;
311 }
312 visited_nodes = visited_nodes
313 .checked_add(2)
314 .filter(|&count| count <= options.max_nodes())
315 .ok_or(axiolid_contracts::GeomError::BudgetExceeded {
316 resource: "certified curve intersection budget",
317 })?;
318 if first_cell.end - first_cell.start >= second_cell.end - second_cell.start {
319 let (left, right) = split_parameter_interval(current.parameters.first)?;
320 pending.push(PendingCurvePair {
321 parameters: CurvePairParameterBox {
322 first: left,
323 ..current.parameters
324 },
325 depth: depth + 1,
326 ..current
327 });
328 pending.push(PendingCurvePair {
329 parameters: CurvePairParameterBox {
330 first: right,
331 ..current.parameters
332 },
333 depth: depth + 1,
334 ..current
335 });
336 } else {
337 let (left, right) = split_parameter_interval(current.parameters.second)?;
338 pending.push(PendingCurvePair {
339 parameters: CurvePairParameterBox {
340 second: left,
341 ..current.parameters
342 },
343 depth: depth + 1,
344 ..current
345 });
346 pending.push(PendingCurvePair {
347 parameters: CurvePairParameterBox {
348 second: right,
349 ..current.parameters
350 },
351 depth: depth + 1,
352 ..current
353 });
354 }
355 }
356 }
357 }
358
359 if contacts.is_empty() {
360 Ok(CertifiedCurveIntersection2::Complete {
361 intersections,
362 visited_nodes,
363 })
364 } else {
365 Ok(degenerate(contacts, visited_nodes))
366 }
367}
368
369fn classification_rank(classification: CurveIntersectionDegeneracy) -> u8 {
374 match classification {
375 CurveIntersectionDegeneracy::Overlap => 4,
376 CurveIntersectionDegeneracy::Tangency => 3,
377 CurveIntersectionDegeneracy::BoundaryCrossing => 2,
378 CurveIntersectionDegeneracy::PointContact => 1,
379 CurveIntersectionDegeneracy::Unresolved => 0,
380 }
381}
382
383fn same_contact(left: &CurvePairParameterBox, right: &CurvePairParameterBox) -> bool {
390 left.first.start == right.first.start
391 && left.first.end == right.first.end
392 && left.second.start == right.second.start
393 && left.second.end == right.second.end
394}
395
396fn push_contact(
397 contacts: &mut Vec<ClassifiedCurveContact2>,
398 parameters: CurvePairParameterBox,
399 classification: CurveIntersectionDegeneracy,
400) -> GeomResult<()> {
401 if let Some(existing) = contacts
402 .iter_mut()
403 .find(|contact| same_contact(&contact.parameters, ¶meters))
404 {
405 if classification_rank(classification) > classification_rank(existing.classification) {
408 existing.classification = classification;
409 }
410 return Ok(());
411 }
412 push_result(
413 contacts,
414 ClassifiedCurveContact2 {
415 parameters,
416 classification,
417 },
418 )
419}
420
421fn fuse_touching(mut contacts: Vec<ClassifiedCurveContact2>) -> Vec<ClassifiedCurveContact2> {
430 let mut fused: Vec<ClassifiedCurveContact2> = Vec::new();
431 for contact in contacts.drain(..) {
432 if let Some(existing) = fused.iter_mut().find(|owner| {
433 owner.classification == contact.classification
435 && owner.classification != CurveIntersectionDegeneracy::Overlap
436 && intervals_touch(owner.parameters.first, contact.parameters.first)
437 && intervals_touch(owner.parameters.second, contact.parameters.second)
438 }) {
439 existing.parameters = hull_box(existing.parameters, contact.parameters);
440 continue;
441 }
442 fused.push(contact);
443 }
444 fused
445}
446
447fn hull_box(left: CurvePairParameterBox, right: CurvePairParameterBox) -> CurvePairParameterBox {
448 CurvePairParameterBox {
449 first: hull_interval(left.first, right.first),
450 second: hull_interval(left.second, right.second),
451 }
452}
453
454fn hull_interval(left: ParameterInterval, right: ParameterInterval) -> ParameterInterval {
455 ParameterInterval {
456 start: left.start.min(right.start),
457 end: left.end.max(right.end),
458 }
459}
460
461fn degenerate(
462 contacts: Vec<ClassifiedCurveContact2>,
463 visited_nodes: u32,
464) -> CertifiedCurveIntersection2 {
465 let contacts = fuse_touching(contacts);
466 let classification = contacts
467 .iter()
468 .map(|contact| contact.classification)
469 .max_by_key(|&class| classification_rank(class))
470 .unwrap_or(CurveIntersectionDegeneracy::Unresolved);
471 CertifiedCurveIntersection2::Degenerate {
472 classification,
473 contacts,
474 visited_nodes,
475 }
476}
477
478fn push_root(
485 intersections: &mut Vec<TransverseCurveIntersection2>,
486 root: TransverseCurveIntersection2,
487) -> GeomResult<()> {
488 if intersections
489 .iter()
490 .any(|existing| roots_overlap(existing, &root))
491 {
492 return Ok(());
493 }
494 push_result(intersections, root)
495}
496
497fn roots_overlap(
503 left: &TransverseCurveIntersection2,
504 right: &TransverseCurveIntersection2,
505) -> bool {
506 intervals_touch(left.first_parameter, right.first_parameter)
507 && intervals_touch(left.second_parameter, right.second_parameter)
508}
509
510fn intervals_touch(left: ParameterInterval, right: ParameterInterval) -> bool {
511 left.start <= right.end && right.start <= left.end
512}
513
514fn classify_exhausted_box(
531 first: &crate::certified_bezier::Cell,
532 second: &crate::certified_bezier::Cell,
533 first_base: &crate::certified_bezier::Cell,
534 second_base: &crate::certified_bezier::Cell,
535) -> GeomResult<CurveIntersectionDegeneracy> {
536 let first_derivative = first.derivative_intervals()?;
537 let second_derivative = second.derivative_intervals()?;
538
539 if derivative_hull_contains_zero(first_derivative)
541 || derivative_hull_contains_zero(second_derivative)
542 {
543 return Ok(CurveIntersectionDegeneracy::Unresolved);
544 }
545
546 let cross = first_derivative[0]
548 .multiply(second_derivative[1])?
549 .subtract(first_derivative[1].multiply(second_derivative[0])?)?;
550 if cross.contains_zero() {
551 return Ok(CurveIntersectionDegeneracy::Tangency);
552 }
553 let on_boundary =
560 touches_base_endpoint(first, first_base) || touches_base_endpoint(second, second_base);
561 if on_boundary {
562 return Ok(CurveIntersectionDegeneracy::BoundaryCrossing);
563 }
564 Ok(CurveIntersectionDegeneracy::Unresolved)
565}
566
567fn touches_base_endpoint(
572 refined: &crate::certified_bezier::Cell,
573 base: &crate::certified_bezier::Cell,
574) -> bool {
575 refined.start == base.start || refined.end == base.end
576}
577
578fn derivative_hull_contains_zero(derivative: [Interval; 3]) -> bool {
579 derivative[0].contains_zero() && derivative[1].contains_zero()
580}
581
582fn push_result<T>(target: &mut Vec<T>, value: T) -> GeomResult<()> {
583 target
584 .try_reserve(1)
585 .map_err(|_| GeomError::BudgetExceeded {
586 resource: "certified curve intersection result allocation",
587 })?;
588 target.push(value);
589 Ok(())
590}
591
592fn residual_excludes_zero(
593 first: &crate::certified_bezier::Cell,
594 second: &crate::certified_bezier::Cell,
595) -> GeomResult<bool> {
596 let first = first.coordinate_intervals()?;
597 let second = second.coordinate_intervals()?;
598 Ok(!first[0].subtract(second[0])?.contains_zero()
599 || !first[1].subtract(second[1])?.contains_zero())
600}
601
602fn pair_box(
603 first: &crate::certified_bezier::Cell,
604 second: &crate::certified_bezier::Cell,
605) -> CurvePairParameterBox {
606 CurvePairParameterBox {
607 first: ParameterInterval {
608 start: first.start,
609 end: first.end,
610 },
611 second: ParameterInterval {
612 start: second.start,
613 end: second.end,
614 },
615 }
616}
617
618fn krawczyk_root(
619 first_curve: &BSplineCurve2,
620 second_curve: &BSplineCurve2,
621 first: &crate::certified_bezier::Cell,
622 second: &crate::certified_bezier::Cell,
623) -> GeomResult<Option<TransverseCurveIntersection2>> {
624 let first_mid = first.start * 0.5 + first.end * 0.5;
625 let second_mid = second.start * 0.5 + second.end * 0.5;
626 let first_jet = bspline_jet2(first_curve, first_mid)?;
627 let second_jet = bspline_jet2(second_curve, second_mid)?;
628 let j00 = first_jet.first.x;
629 let j01 = -second_jet.first.x;
630 let j10 = first_jet.first.y;
631 let j11 = -second_jet.first.y;
632 let determinant = j00 * j11 - j01 * j10;
633 if determinant == 0.0 || !determinant.is_finite() {
634 return Ok(None);
635 }
636 let inverse = [
637 [j11 / determinant, -j01 / determinant],
638 [-j10 / determinant, j00 / determinant],
639 ];
640 if inverse.iter().flatten().any(|value| !value.is_finite()) {
641 return Ok(None);
642 }
643
644 let first_point = first.midpoint_point()?.euclidean()?;
645 let second_point = second.midpoint_point()?.euclidean()?;
646 let residual = [
647 first_point[0].subtract(second_point[0])?,
648 first_point[1].subtract(second_point[1])?,
649 ];
650 let first_derivative = first.derivative_intervals()?;
651 let second_derivative = second.derivative_intervals()?;
652 let minus_one = Interval::exact(-1.0)?;
653 let jacobian = [
654 [
655 first_derivative[0],
656 second_derivative[0].multiply(minus_one)?,
657 ],
658 [
659 first_derivative[1],
660 second_derivative[1].multiply(minus_one)?,
661 ],
662 ];
663
664 let corrected = [
665 Interval::exact(first_mid)?.subtract(linear_combination(
666 inverse[0][0],
667 residual[0],
668 inverse[0][1],
669 residual[1],
670 )?)?,
671 Interval::exact(second_mid)?.subtract(linear_combination(
672 inverse[1][0],
673 residual[0],
674 inverse[1][1],
675 residual[1],
676 )?)?,
677 ];
678 let zero = Interval::exact(0.0)?;
679 let one = Interval::exact(1.0)?;
680 let matrix = [
681 [
682 one.subtract(linear_combination(
683 inverse[0][0],
684 jacobian[0][0],
685 inverse[0][1],
686 jacobian[1][0],
687 )?)?,
688 zero.subtract(linear_combination(
689 inverse[0][0],
690 jacobian[0][1],
691 inverse[0][1],
692 jacobian[1][1],
693 )?)?,
694 ],
695 [
696 zero.subtract(linear_combination(
697 inverse[1][0],
698 jacobian[0][0],
699 inverse[1][1],
700 jacobian[1][0],
701 )?)?,
702 one.subtract(linear_combination(
703 inverse[1][0],
704 jacobian[0][1],
705 inverse[1][1],
706 jacobian[1][1],
707 )?)?,
708 ],
709 ];
710 let delta = [
713 Interval::hull([
714 Interval::exact(first.start)?.subtract(Interval::exact(first_mid)?)?,
715 Interval::exact(first.end)?.subtract(Interval::exact(first_mid)?)?,
716 ])?,
717 Interval::hull([
718 Interval::exact(second.start)?.subtract(Interval::exact(second_mid)?)?,
719 Interval::exact(second.end)?.subtract(Interval::exact(second_mid)?)?,
720 ])?,
721 ];
722 let image = [
723 corrected[0]
724 .add(matrix[0][0].multiply(delta[0])?)?
725 .add(matrix[0][1].multiply(delta[1])?)?,
726 corrected[1]
727 .add(matrix[1][0].multiply(delta[0])?)?
728 .add(matrix[1][1].multiply(delta[1])?)?,
729 ];
730 if !(image[0].lower() > first.start
731 && image[0].upper() < first.end
732 && image[1].lower() > second.start
733 && image[1].upper() < second.end)
734 {
735 return Ok(None);
736 }
737
738 let determinant_interval = jacobian[0][0]
739 .multiply(jacobian[1][1])?
740 .subtract(jacobian[0][1].multiply(jacobian[1][0])?)?;
741 let determinant_lower = determinant_interval.absolute_lower_bound();
742 if determinant_lower == 0.0 {
743 return Ok(None);
744 }
745 let first_parameter = image[0].lower() * 0.5 + image[0].upper() * 0.5;
746 let second_parameter = image[1].lower() * 0.5 + image[1].upper() * 0.5;
747 let first_value = bspline_jet2(first_curve, first_parameter)?.point;
748 let second_value = bspline_jet2(second_curve, second_parameter)?.point;
749 let residual = distance_between_point_intervals_upper(
750 point_intervals(first_value)?,
751 point_intervals(second_value)?,
752 2,
753 )?;
754 Ok(Some(TransverseCurveIntersection2 {
755 first_parameter: ParameterInterval {
756 start: image[0].lower(),
757 end: image[0].upper(),
758 },
759 second_parameter: ParameterInterval {
760 start: image[1].lower(),
761 end: image[1].upper(),
762 },
763 point: Point2::new(
764 first_value.x * 0.5 + second_value.x * 0.5,
765 first_value.y * 0.5 + second_value.y * 0.5,
766 ),
767 residual_upper_bound: residual,
768 jacobian_determinant_lower_bound: determinant_lower,
769 }))
770}
771
772fn point_intervals(point: Point2) -> GeomResult<[Interval; 3]> {
773 Ok([
774 Interval::exact(point.x)?,
775 Interval::exact(point.y)?,
776 Interval::exact(0.0)?,
777 ])
778}
779
780fn stable_start(cell: Scalar, root: Scalar, desired: Scalar) -> Scalar {
781 if desired > cell {
782 desired.min(root)
783 } else {
784 root
785 }
786}
787
788fn stable_end(cell: Scalar, root: Scalar, desired: Scalar) -> Scalar {
789 if desired < cell {
790 desired.max(root)
791 } else {
792 root
793 }
794}
795
796fn stable_contraction(
797 cell: &crate::certified_bezier::Cell,
798 root: ParameterInterval,
799 tolerance: Scalar,
800) -> ParameterInterval {
801 let center = midpoint(root);
802 let half = tolerance * 0.5;
803 ParameterInterval {
804 start: stable_start(cell.start, root.start, center - half),
805 end: stable_end(cell.end, root.end, center + half),
806 }
807}
808
809fn contract_interval(
810 cell: &crate::certified_bezier::Cell,
811 interval: ParameterInterval,
812 tolerance: Scalar,
813) -> ParameterInterval {
814 if cell.end - cell.start <= tolerance {
815 ParameterInterval {
816 start: cell.start,
817 end: cell.end,
818 }
819 } else {
820 stable_contraction(cell, interval, tolerance)
821 }
822}
823
824fn split_parameter_interval(
825 interval: ParameterInterval,
826) -> GeomResult<(ParameterInterval, ParameterInterval)> {
827 let split = midpoint(interval);
828 if split <= interval.start || split >= interval.end {
829 return Err(GeomError::Degenerate(
830 "certified curve intersection parameter split did not advance".to_owned(),
831 ));
832 }
833 Ok((
834 ParameterInterval {
835 start: interval.start,
836 end: split,
837 },
838 ParameterInterval {
839 start: split,
840 end: interval.end,
841 },
842 ))
843}
844
845fn linear_combination(
846 left_scalar: Scalar,
847 left: Interval,
848 right_scalar: Scalar,
849 right: Interval,
850) -> GeomResult<Interval> {
851 Interval::exact(left_scalar)?
852 .multiply(left)?
853 .add(Interval::exact(right_scalar)?.multiply(right)?)
854}
855
856fn classify_lines(
857 first: &BSplineCurve2,
858 second: &BSplineCurve2,
859 visited_nodes: u32,
860 boxes: Vec<CurvePairParameterBox>,
861 options: CertifiedCurveIntersectionOptions,
862) -> GeomResult<CertifiedCurveIntersection2> {
863 let [a, b] = [first.control_points[0], first.control_points[1]];
864 let [c, d] = [second.control_points[0], second.control_points[1]];
865 if a == b || c == d {
866 let intersects = match (a == b, c == d) {
867 (true, true) => a == c,
868 (true, false) => point_on_segment(a, c, d),
869 (false, true) => point_on_segment(c, a, b),
870 (false, false) => unreachable!("a degenerate segment was already established"),
871 };
872 return Ok(if intersects {
873 let mut contacts = Vec::new();
874 for owned in boxes {
875 push_contact(
876 &mut contacts,
877 owned,
878 CurveIntersectionDegeneracy::PointContact,
879 )?;
880 }
881 degenerate(contacts, visited_nodes)
882 } else {
883 CertifiedCurveIntersection2::Complete {
884 intersections: Vec::new(),
885 visited_nodes,
886 }
887 });
888 }
889 let signs = [sign(a, b, c), sign(a, b, d), sign(c, d, a), sign(c, d, b)];
890 let first_collinear = signs[0] == Sign::Zero && signs[1] == Sign::Zero;
891 if first_collinear {
892 let classification = if boxes_overlap_positive(a, b, c, d) {
893 CurveIntersectionDegeneracy::Overlap
894 } else if boxes_overlap(a, b, c, d) {
895 CurveIntersectionDegeneracy::Tangency
896 } else {
897 return Ok(CertifiedCurveIntersection2::Complete {
898 intersections: Vec::new(),
899 visited_nodes,
900 });
901 };
902 return Ok(degenerate(
903 {
904 let mut contacts = Vec::new();
905 for owned in boxes {
906 push_contact(&mut contacts, owned, classification)?;
907 }
908 contacts
909 },
910 visited_nodes,
911 ));
912 }
913 let intersects = !same_strict_sign(signs[0], signs[1])
914 && !same_strict_sign(signs[2], signs[3])
915 && boxes_overlap(a, b, c, d);
916 if !intersects {
917 return Ok(CertifiedCurveIntersection2::Complete {
918 intersections: Vec::new(),
919 visited_nodes,
920 });
921 }
922
923 let rx = Interval::exact(b.x)?.subtract(Interval::exact(a.x)?)?;
924 let ry = Interval::exact(b.y)?.subtract(Interval::exact(a.y)?)?;
925 let sx = Interval::exact(d.x)?.subtract(Interval::exact(c.x)?)?;
926 let sy = Interval::exact(d.y)?.subtract(Interval::exact(c.y)?)?;
927 let determinant = rx.multiply(sy)?.subtract(ry.multiply(sx)?)?;
928 let ox = Interval::exact(c.x)?.subtract(Interval::exact(a.x)?)?;
929 let oy = Interval::exact(c.y)?.subtract(Interval::exact(a.y)?)?;
930 let t = ox
931 .multiply(sy)?
932 .subtract(oy.multiply(sx)?)?
933 .divide_nonzero(determinant)?;
934 let u = ox
935 .multiply(ry)?
936 .subtract(oy.multiply(rx)?)?
937 .divide_nonzero(determinant)?;
938 let first_interval = native_parameter_interval(first, t)?;
939 let second_interval = native_parameter_interval(second, u)?;
940 if !intervals_resolved(
941 first_interval,
942 second_interval,
943 options.parameter_tolerance(),
944 ) {
945 return Ok(unresolved_intervals(
946 first_interval,
947 second_interval,
948 visited_nodes,
949 ));
950 }
951 let first_parameter = midpoint(first_interval);
952 let second_parameter = midpoint(second_interval);
953 let first_point = bspline_jet2(first, first_parameter)?.point;
954 let second_point = bspline_jet2(second, second_parameter)?.point;
955 let residual = distance_between_point_intervals_upper(
956 point_intervals(first_point)?,
957 point_intervals(second_point)?,
958 2,
959 )?;
960 let point = Point2::new(
961 first_point.x * 0.5 + second_point.x * 0.5,
962 first_point.y * 0.5 + second_point.y * 0.5,
963 );
964
965 Ok(CertifiedCurveIntersection2::Complete {
966 intersections: vec![TransverseCurveIntersection2 {
967 first_parameter: first_interval,
968 second_parameter: second_interval,
969 point,
970 residual_upper_bound: residual,
971 jacobian_determinant_lower_bound: determinant_lower(a, b, c, d)?,
972 }],
973 visited_nodes,
974 })
975}
976
977fn determinant_lower(a: Point2, b: Point2, c: Point2, d: Point2) -> GeomResult<Scalar> {
978 let rx = Interval::exact(b.x)?.subtract(Interval::exact(a.x)?)?;
979 let ry = Interval::exact(b.y)?.subtract(Interval::exact(a.y)?)?;
980 let sx = Interval::exact(d.x)?.subtract(Interval::exact(c.x)?)?;
981 let sy = Interval::exact(d.y)?.subtract(Interval::exact(c.y)?)?;
982 Ok(rx
983 .multiply(sy)?
984 .subtract(ry.multiply(sx)?)?
985 .absolute_lower_bound())
986}
987
988fn structural_start_tangency(line: &BSplineCurve2, quadratic: &BSplineCurve2) -> bool {
989 if !linear_polynomial(line)
990 || quadratic.degree != 2
991 || quadratic.weights.is_some()
992 || quadratic.knots.len() != 2
993 || quadratic.control_points.len() != 3
994 {
995 return false;
996 }
997 let [a, b] = [line.control_points[0], line.control_points[1]];
998 let q = &quadratic.control_points;
999 a == q[0] && q[1] != a && sign(a, b, q[1]) == Sign::Zero && sign(a, b, q[2]) != Sign::Zero
1000}
1001
1002fn linear_polynomial(curve: &BSplineCurve2) -> bool {
1003 curve.degree == 1
1004 && curve.weights.is_none()
1005 && curve.knots.len() == 2
1006 && curve.control_points.len() == 2
1007}
1008
1009fn sign(a: Point2, b: Point2, c: Point2) -> Sign {
1010 orient2d(a, b, c).sign().expect("orient2d always escalates")
1011}
1012
1013fn same_strict_sign(a: Sign, b: Sign) -> bool {
1014 a != Sign::Zero && a == b
1015}
1016
1017fn has_control_point_extent(curve: &BSplineCurve2) -> bool {
1018 curve
1019 .control_points
1020 .first()
1021 .is_some_and(|first| curve.control_points.iter().any(|point| point != first))
1022}
1023
1024fn point_on_segment(point: Point2, start: Point2, end: Point2) -> bool {
1025 sign(start, end, point) == Sign::Zero && boxes_overlap(point, point, start, end)
1026}
1027
1028fn boxes_overlap(a: Point2, b: Point2, c: Point2, d: Point2) -> bool {
1029 let overlap = |a0: Scalar, a1: Scalar, b0: Scalar, b1: Scalar| {
1030 a0.min(a1) <= b0.max(b1) && b0.min(b1) <= a0.max(a1)
1031 };
1032 overlap(a.x, b.x, c.x, d.x) && overlap(a.y, b.y, c.y, d.y)
1033}
1034
1035fn boxes_overlap_positive(a: Point2, b: Point2, c: Point2, d: Point2) -> bool {
1036 let width = |a0: Scalar, a1: Scalar, b0: Scalar, b1: Scalar| {
1037 a0.max(a1).min(b0.max(b1)) - a0.min(a1).max(b0.min(b1))
1038 };
1039 boxes_overlap(a, b, c, d)
1040 && (width(a.x, b.x, c.x, d.x) > 0.0 || width(a.y, b.y, c.y, d.y) > 0.0)
1041}
1042
1043fn midpoint(interval: ParameterInterval) -> Scalar {
1044 interval.start * 0.5 + interval.end * 0.5
1045}
1046
1047fn intervals_resolved(
1048 first: ParameterInterval,
1049 second: ParameterInterval,
1050 tolerance: Scalar,
1051) -> bool {
1052 first.end - first.start <= tolerance && second.end - second.start <= tolerance
1053}
1054
1055fn unresolved_box(box_: CurvePairParameterBox, visited_nodes: u32) -> CertifiedCurveIntersection2 {
1056 CertifiedCurveIntersection2::Degenerate {
1057 classification: CurveIntersectionDegeneracy::Unresolved,
1058 contacts: vec![ClassifiedCurveContact2 {
1059 parameters: box_,
1060 classification: CurveIntersectionDegeneracy::Unresolved,
1061 }],
1062 visited_nodes,
1063 }
1064}
1065
1066fn unresolved_intervals(
1067 first: ParameterInterval,
1068 second: ParameterInterval,
1069 visited: u32,
1070) -> CertifiedCurveIntersection2 {
1071 unresolved_box(CurvePairParameterBox { first, second }, visited)
1072}
1073
1074fn checked_parameter_interval(start: Scalar, end: Scalar) -> GeomResult<ParameterInterval> {
1075 (start <= end)
1076 .then_some(ParameterInterval { start, end })
1077 .ok_or_else(|| {
1078 GeomError::Degenerate("certified line parameter interval is empty".to_owned())
1079 })
1080}
1081
1082fn native_interval(bounds: ParameterInterval, value: Interval) -> GeomResult<ParameterInterval> {
1083 checked_parameter_interval(
1084 value.lower().max(bounds.start),
1085 value.upper().min(bounds.end),
1086 )
1087}
1088
1089fn native_parameter_interval_inner(
1090 bounds: ParameterInterval,
1091 unit: Interval,
1092) -> GeomResult<ParameterInterval> {
1093 let span = Interval::exact(bounds.end)?.subtract(Interval::exact(bounds.start)?)?;
1094 native_interval(
1095 bounds,
1096 Interval::exact(bounds.start)?.add(unit.multiply(span)?)?,
1097 )
1098}
1099
1100fn native_parameter_interval(
1101 curve: &BSplineCurve2,
1102 unit: Interval,
1103) -> GeomResult<ParameterInterval> {
1104 native_parameter_interval_inner(domain(curve), unit)
1105}
1106
1107fn domain(curve: &BSplineCurve2) -> ParameterInterval {
1108 ParameterInterval {
1109 start: curve.knots[0],
1110 end: curve.knots[curve.knots.len() - 1],
1111 }
1112}
1113
1114#[cfg(test)]
1115mod pending_storage_tests {
1116 use super::{CurvePairParameterBox, PendingCurvePair};
1117
1118 #[test]
1119 fn pending_curve_pairs_store_only_indices_parameters_and_depth() {
1120 let raw = 2 * size_of::<usize>() + size_of::<CurvePairParameterBox>() + size_of::<u16>();
1121 let alignment = align_of::<PendingCurvePair>();
1122 let expected = raw.next_multiple_of(alignment);
1123 assert_eq!(size_of::<PendingCurvePair>(), expected);
1124 }
1125}