1use axiolid_contracts::{GeomError, GeomResult};
5use axiolid_core::{Point3, Scalar};
6use axiolid_curve::BSplineCurve3;
7use axiolid_evaluate::{curve::bspline_jet3, surface::bspline_jet};
8use axiolid_surface::BSplineSurface;
9
10use crate::{
11 certified_bezier::{Cell, Interval},
12 certified_projection::ParameterInterval,
13 certified_refinement::{piecewise_bezier_cells, RefinementBudget},
14 certified_surface_bezier::{piecewise_bezier_patches, Patch},
15};
16
17const MAX_NODES: u32 = 100_000;
18const MAX_DEPTH: u16 = 64;
19
20#[derive(Debug, Clone, Copy, PartialEq)]
22pub struct CertifiedCurveSurfaceIntersectionOptions {
23 parameter_tolerance: Scalar,
24 max_nodes: u32,
25 max_depth: u16,
26}
27
28impl CertifiedCurveSurfaceIntersectionOptions {
29 pub fn new(parameter_tolerance: Scalar, max_nodes: u32, max_depth: u16) -> GeomResult<Self> {
31 if !parameter_tolerance.is_finite() || parameter_tolerance <= 0.0 {
32 return Err(GeomError::InvalidInput(
33 "curve/surface parameter tolerance must be finite and positive".to_owned(),
34 ));
35 }
36 if max_nodes == 0 || max_nodes > MAX_NODES {
37 return Err(GeomError::InvalidInput(format!(
38 "curve/surface max_nodes must be in 1..={MAX_NODES}"
39 )));
40 }
41 if max_depth == 0 || max_depth > MAX_DEPTH {
42 return Err(GeomError::InvalidInput(format!(
43 "curve/surface max_depth must be in 1..={MAX_DEPTH}"
44 )));
45 }
46 Ok(Self {
47 parameter_tolerance,
48 max_nodes,
49 max_depth,
50 })
51 }
52
53 fn parameter_tolerance(self) -> Scalar {
54 self.parameter_tolerance
55 }
56 fn max_nodes(self) -> u32 {
57 self.max_nodes
58 }
59 fn max_depth(self) -> u16 {
60 self.max_depth
61 }
62}
63
64impl Default for CertifiedCurveSurfaceIntersectionOptions {
65 fn default() -> Self {
66 Self {
67 parameter_tolerance: 1.0e-8,
68 max_nodes: MAX_NODES,
69 max_depth: MAX_DEPTH,
70 }
71 }
72}
73
74#[derive(Debug, Clone, Copy, PartialEq)]
76#[non_exhaustive]
77pub struct CurveSurfaceParameterBox {
78 pub curve: ParameterInterval,
80 pub surface_u: ParameterInterval,
82 pub surface_v: ParameterInterval,
84}
85
86#[derive(Debug, Clone, PartialEq)]
88#[non_exhaustive]
89pub struct TransverseCurveSurfaceIntersection3 {
90 pub curve_parameter: ParameterInterval,
92 pub surface_u_parameter: ParameterInterval,
94 pub surface_v_parameter: ParameterInterval,
96 pub point: Point3,
98 pub residual_upper_bound: Scalar,
100 pub jacobian_determinant_lower_bound: Scalar,
102}
103
104#[derive(Debug, Clone, PartialEq)]
106#[non_exhaustive]
107pub enum CertifiedCurveSurfaceIntersection3 {
108 Complete {
110 intersections: Vec<TransverseCurveSurfaceIntersection3>,
112 visited_nodes: u32,
114 },
115 Unresolved {
117 intersections: Vec<TransverseCurveSurfaceIntersection3>,
119 candidate_boxes: Vec<CurveSurfaceParameterBox>,
121 visited_nodes: u32,
123 },
124}
125
126#[derive(Debug, Clone, Copy)]
127struct Pending {
128 curve_index: usize,
129 patch_index: usize,
130 parameters: CurveSurfaceParameterBox,
131 depth: u16,
132}
133
134pub fn intersect_curve_surface_certified(
143 curve: &BSplineCurve3,
144 surface: &BSplineSurface,
145 options: CertifiedCurveSurfaceIntersectionOptions,
146) -> GeomResult<CertifiedCurveSurfaceIntersection3> {
147 let options = CertifiedCurveSurfaceIntersectionOptions::new(
148 options.parameter_tolerance,
149 options.max_nodes,
150 options.max_depth,
151 )?;
152 let mut budget = RefinementBudget::new(
153 options.max_nodes(),
154 "certified curve/surface intersection budget",
155 );
156 let curve_cells =
157 piecewise_bezier_cells(curve, |point| [point.x, point.y, point.z], &mut budget)?;
158 let patches = piecewise_bezier_patches(surface, &mut budget)?;
159 let initial =
160 curve_cells
161 .len()
162 .checked_mul(patches.len())
163 .ok_or(GeomError::BudgetExceeded {
164 resource: "certified curve/surface intersection budget",
165 })?;
166 budget.charge(u128::try_from(initial).ok())?;
167 let mut visited_nodes = u32::try_from(initial).map_err(|_| GeomError::BudgetExceeded {
168 resource: "certified curve/surface intersection budget",
169 })?;
170
171 let mut intersections = Vec::new();
172 let mut unresolved = Vec::new();
173 let domain = SurfaceDomain {
176 u_start: *surface
177 .u_knots
178 .first()
179 .ok_or_else(|| GeomError::InvalidInput("surface has no u knots".to_owned()))?,
180 u_end: *surface
181 .u_knots
182 .last()
183 .ok_or_else(|| GeomError::InvalidInput("surface has no u knots".to_owned()))?,
184 v_start: *surface
185 .v_knots
186 .first()
187 .ok_or_else(|| GeomError::InvalidInput("surface has no v knots".to_owned()))?,
188 v_end: *surface
189 .v_knots
190 .last()
191 .ok_or_else(|| GeomError::InvalidInput("surface has no v knots".to_owned()))?,
192 };
193 let mut pending = Vec::new();
194 pending
195 .try_reserve(usize::from(options.max_depth()) + 1)
196 .map_err(|_| GeomError::BudgetExceeded {
197 resource: "certified curve/surface pending allocation",
198 })?;
199
200 for curve_index in 0..curve_cells.len() {
201 for patch_index in 0..patches.len() {
202 pending.push(Pending {
203 curve_index,
204 patch_index,
205 parameters: base_box(&curve_cells[curve_index], &patches[patch_index]),
206 depth: 0,
207 });
208 while let Some(current) = pending.pop() {
209 let curve_cell = curve_cells[current.curve_index]
210 .restrict(current.parameters.curve.start, current.parameters.curve.end)?;
211 let patch = patches[current.patch_index].restrict(
212 current.parameters.surface_u.start,
213 current.parameters.surface_u.end,
214 current.parameters.surface_v.start,
215 current.parameters.surface_v.end,
216 )?;
217 if residual_excludes_zero(&curve_cell, &patch)? {
218 continue;
219 }
220 if let Some(root) = krawczyk_root(curve, surface, &curve_cell, &patch)? {
221 if certificate_meets_resolution(&root, options.parameter_tolerance()) {
222 push_result(&mut intersections, root)?;
223 continue;
224 }
225 if current.depth >= options.max_depth() {
226 push_result(&mut unresolved, certificate_box(&root))?;
227 continue;
228 }
229 let contracted = CurveSurfaceParameterBox {
230 curve: contract_interval(
231 current.parameters.curve,
232 root.curve_parameter,
233 options.parameter_tolerance(),
234 ),
235 surface_u: contract_interval(
236 current.parameters.surface_u,
237 root.surface_u_parameter,
238 options.parameter_tolerance(),
239 ),
240 surface_v: contract_interval(
241 current.parameters.surface_v,
242 root.surface_v_parameter,
243 options.parameter_tolerance(),
244 ),
245 };
246 if contracted == current.parameters {
247 push_result(&mut unresolved, contracted)?;
248 continue;
249 }
250 budget.charge(Some(1))?;
251 visited_nodes = checked_nodes(visited_nodes, 1, options.max_nodes())?;
252 pending.push(Pending {
253 parameters: contracted,
254 depth: current.depth.checked_add(1).ok_or_else(|| {
255 GeomError::Degenerate(
256 "curve/surface intersection depth overflow".to_owned(),
257 )
258 })?,
259 ..current
260 });
261 continue;
262 }
263 if let Some(root) = edge_root(
275 curve,
276 surface,
277 &curve_cell,
278 &patch,
279 &domain,
280 options.parameter_tolerance(),
281 )? {
282 push_result(&mut intersections, root)?;
283 continue;
284 }
285 if current.depth >= options.max_depth() {
286 push_result(&mut unresolved, current.parameters)?;
287 continue;
288 }
289 budget.charge(Some(2))?;
290 visited_nodes = checked_nodes(visited_nodes, 2, options.max_nodes())?;
291 split_pending(current, &mut pending)?;
292 }
293 }
294 }
295
296 if unresolved.is_empty() {
297 Ok(CertifiedCurveSurfaceIntersection3::Complete {
298 intersections,
299 visited_nodes,
300 })
301 } else {
302 Ok(CertifiedCurveSurfaceIntersection3::Unresolved {
303 intersections,
304 candidate_boxes: unresolved,
305 visited_nodes,
306 })
307 }
308}
309
310fn checked_nodes(current: u32, additional: u32, maximum: u32) -> GeomResult<u32> {
311 current
312 .checked_add(additional)
313 .filter(|&value| value <= maximum)
314 .ok_or(GeomError::BudgetExceeded {
315 resource: "certified curve/surface intersection budget",
316 })
317}
318
319fn push_result<T>(target: &mut Vec<T>, value: T) -> GeomResult<()> {
320 target
321 .try_reserve(1)
322 .map_err(|_| GeomError::BudgetExceeded {
323 resource: "certified curve/surface result allocation",
324 })?;
325 target.push(value);
326 Ok(())
327}
328
329fn base_box(curve: &Cell, patch: &Patch) -> CurveSurfaceParameterBox {
330 CurveSurfaceParameterBox {
331 curve: ParameterInterval {
332 start: curve.start,
333 end: curve.end,
334 },
335 surface_u: ParameterInterval {
336 start: patch.u_start,
337 end: patch.u_end,
338 },
339 surface_v: ParameterInterval {
340 start: patch.v_start,
341 end: patch.v_end,
342 },
343 }
344}
345
346fn certificate_box(root: &TransverseCurveSurfaceIntersection3) -> CurveSurfaceParameterBox {
347 CurveSurfaceParameterBox {
348 curve: root.curve_parameter,
349 surface_u: root.surface_u_parameter,
350 surface_v: root.surface_v_parameter,
351 }
352}
353
354fn certificate_meets_resolution(
355 root: &TransverseCurveSurfaceIntersection3,
356 tolerance: Scalar,
357) -> bool {
358 root.curve_parameter.end - root.curve_parameter.start <= tolerance
359 && root.surface_u_parameter.end - root.surface_u_parameter.start <= tolerance
360 && root.surface_v_parameter.end - root.surface_v_parameter.start <= tolerance
361}
362
363fn residual_excludes_zero(curve: &Cell, patch: &Patch) -> GeomResult<bool> {
364 let curve = curve.coordinate_intervals()?;
365 let surface = patch.coordinate_intervals()?;
366 Ok((0..3).any(|axis| {
367 curve[axis].upper() < surface[axis].lower() || surface[axis].upper() < curve[axis].lower()
368 }))
369}
370
371fn split_interval(
372 interval: ParameterInterval,
373) -> GeomResult<(ParameterInterval, ParameterInterval)> {
374 let midpoint = interval.start * 0.5 + interval.end * 0.5;
375 if midpoint <= interval.start || midpoint >= interval.end {
376 return Err(GeomError::Degenerate(
377 "certified curve/surface parameter split did not advance".to_owned(),
378 ));
379 }
380 Ok((
381 ParameterInterval {
382 start: interval.start,
383 end: midpoint,
384 },
385 ParameterInterval {
386 start: midpoint,
387 end: interval.end,
388 },
389 ))
390}
391
392fn split_pending(current: Pending, pending: &mut Vec<Pending>) -> GeomResult<()> {
393 let widths = [
394 current.parameters.curve.end - current.parameters.curve.start,
395 current.parameters.surface_u.end - current.parameters.surface_u.start,
396 current.parameters.surface_v.end - current.parameters.surface_v.start,
397 ];
398 let axis = if widths[0] >= widths[1] && widths[0] >= widths[2] {
399 0
400 } else if widths[1] >= widths[2] {
401 1
402 } else {
403 2
404 };
405 let source = match axis {
406 0 => current.parameters.curve,
407 1 => current.parameters.surface_u,
408 _ => current.parameters.surface_v,
409 };
410 let (left, right) = split_interval(source)?;
411 let depth = current.depth.checked_add(1).ok_or_else(|| {
412 GeomError::Degenerate("curve/surface intersection depth overflow".to_owned())
413 })?;
414 let with_interval = |interval| {
415 let mut parameters = current.parameters;
416 match axis {
417 0 => parameters.curve = interval,
418 1 => parameters.surface_u = interval,
419 _ => parameters.surface_v = interval,
420 }
421 Pending {
422 parameters,
423 depth,
424 ..current
425 }
426 };
427 pending.push(with_interval(left));
428 pending.push(with_interval(right));
429 Ok(())
430}
431
432fn stable_start(cell: Scalar, root: Scalar, desired: Scalar) -> Scalar {
433 if desired > cell {
434 desired.min(root)
435 } else {
436 root
437 }
438}
439
440fn stable_end(cell: Scalar, root: Scalar, desired: Scalar) -> Scalar {
441 if desired < cell {
442 desired.max(root)
443 } else {
444 root
445 }
446}
447
448fn contract_interval(
449 cell: ParameterInterval,
450 root: ParameterInterval,
451 tolerance: Scalar,
452) -> ParameterInterval {
453 if cell.end - cell.start <= tolerance {
454 return cell;
455 }
456 let center = root.start * 0.5 + root.end * 0.5;
457 let half = tolerance * 0.5;
458 ParameterInterval {
459 start: stable_start(cell.start, root.start, center - half),
460 end: stable_end(cell.end, root.end, center + half),
461 }
462}
463
464fn krawczyk_root(
465 curve: &BSplineCurve3,
466 surface: &BSplineSurface,
467 curve_cell: &Cell,
468 patch: &Patch,
469) -> GeomResult<Option<TransverseCurveSurfaceIntersection3>> {
470 let center = [
471 curve_cell.start * 0.5 + curve_cell.end * 0.5,
472 patch.u_start * 0.5 + patch.u_end * 0.5,
473 patch.v_start * 0.5 + patch.v_end * 0.5,
474 ];
475 let curve_jet = bspline_jet3(curve, center[0])?;
476 let surface_jet = bspline_jet(surface, center[1], center[2])?;
477 let point_jacobian = [
478 [curve_jet.first.x, -surface_jet.du.x, -surface_jet.dv.x],
479 [curve_jet.first.y, -surface_jet.du.y, -surface_jet.dv.y],
480 [curve_jet.first.z, -surface_jet.du.z, -surface_jet.dv.z],
481 ];
482 let Some(inverse) = inverse3(point_jacobian) else {
483 return Ok(None);
484 };
485
486 let curve_midpoint = curve_cell.midpoint_point()?.euclidean()?;
487 let surface_midpoint = patch.midpoint_point()?.euclidean()?;
488 let residual = [
489 curve_midpoint[0].subtract(surface_midpoint[0])?,
490 curve_midpoint[1].subtract(surface_midpoint[1])?,
491 curve_midpoint[2].subtract(surface_midpoint[2])?,
492 ];
493 let curve_derivative = curve_cell.derivative_intervals()?;
494 let surface_u = patch.partial_u_intervals()?;
495 let surface_v = patch.partial_v_intervals()?;
496 let minus_one = Interval::exact(-1.0)?;
497 let jacobian = [
498 [
499 curve_derivative[0],
500 surface_u[0].multiply(minus_one)?,
501 surface_v[0].multiply(minus_one)?,
502 ],
503 [
504 curve_derivative[1],
505 surface_u[1].multiply(minus_one)?,
506 surface_v[1].multiply(minus_one)?,
507 ],
508 [
509 curve_derivative[2],
510 surface_u[2].multiply(minus_one)?,
511 surface_v[2].multiply(minus_one)?,
512 ],
513 ];
514
515 let zero = Interval::exact(0.0)?;
516 let one = Interval::exact(1.0)?;
517 let mut corrected = [zero; 3];
518 for row in 0..3 {
519 corrected[row] = Interval::exact(center[row])?.subtract(dot3(inverse[row], residual)?)?;
520 }
521 let mut matrix = [[zero; 3]; 3];
522 for row in 0..3 {
523 for column in 0..3 {
524 let jacobian_column = [
525 jacobian[0][column],
526 jacobian[1][column],
527 jacobian[2][column],
528 ];
529 let identity = if row == column { one } else { zero };
530 matrix[row][column] = identity.subtract(dot3(inverse[row], jacobian_column)?)?;
531 }
532 }
533 let bounds = [
534 ParameterInterval {
535 start: curve_cell.start,
536 end: curve_cell.end,
537 },
538 ParameterInterval {
539 start: patch.u_start,
540 end: patch.u_end,
541 },
542 ParameterInterval {
543 start: patch.v_start,
544 end: patch.v_end,
545 },
546 ];
547 let mut delta = [zero; 3];
548 for axis in 0..3 {
549 delta[axis] = Interval::hull([
550 Interval::exact(bounds[axis].start)?.subtract(Interval::exact(center[axis])?)?,
551 Interval::exact(bounds[axis].end)?.subtract(Interval::exact(center[axis])?)?,
552 ])?;
553 }
554 let mut image = [zero; 3];
555 for row in 0..3 {
556 image[row] = corrected[row].add(
557 matrix[row][0]
558 .multiply(delta[0])?
559 .add(matrix[row][1].multiply(delta[1])?)?
560 .add(matrix[row][2].multiply(delta[2])?)?,
561 )?;
562 }
563 if !(image[0].lower() > bounds[0].start
564 && image[0].upper() < bounds[0].end
565 && image[1].lower() > bounds[1].start
566 && image[1].upper() < bounds[1].end
567 && image[2].lower() > bounds[2].start
568 && image[2].upper() < bounds[2].end)
569 {
570 return Ok(None);
571 }
572 let determinant_lower = determinant3_interval(jacobian)?.absolute_lower_bound();
573 if determinant_lower == 0.0 {
574 return Ok(None);
575 }
576 let curve_parameter = ParameterInterval {
577 start: image[0].lower(),
578 end: image[0].upper(),
579 };
580 let surface_u_parameter = ParameterInterval {
581 start: image[1].lower(),
582 end: image[1].upper(),
583 };
584 let surface_v_parameter = ParameterInterval {
585 start: image[2].lower(),
586 end: image[2].upper(),
587 };
588 let root_curve = curve_cell.restrict(curve_parameter.start, curve_parameter.end)?;
589 let root_patch = patch.restrict(
590 surface_u_parameter.start,
591 surface_u_parameter.end,
592 surface_v_parameter.start,
593 surface_v_parameter.end,
594 )?;
595 let residual_upper_bound = residual_norm_upper(&root_curve, &root_patch)?;
596 let curve_value = bspline_jet3(curve, interval_midpoint(curve_parameter))?.point;
597 let surface_value = bspline_jet(
598 surface,
599 interval_midpoint(surface_u_parameter),
600 interval_midpoint(surface_v_parameter),
601 )?
602 .point;
603 let point = Point3::new(
604 curve_value.x * 0.5 + surface_value.x * 0.5,
605 curve_value.y * 0.5 + surface_value.y * 0.5,
606 curve_value.z * 0.5 + surface_value.z * 0.5,
607 );
608 if !point.is_finite() || !residual_upper_bound.is_finite() {
609 return Err(GeomError::Degenerate(
610 "certified curve/surface representative overflowed".to_owned(),
611 ));
612 }
613 Ok(Some(TransverseCurveSurfaceIntersection3 {
614 curve_parameter,
615 surface_u_parameter,
616 surface_v_parameter,
617 point,
618 residual_upper_bound,
619 jacobian_determinant_lower_bound: determinant_lower,
620 }))
621}
622
623fn dot3(coefficients: [Scalar; 3], values: [Interval; 3]) -> GeomResult<Interval> {
624 Interval::exact(coefficients[0])?
625 .multiply(values[0])?
626 .add(Interval::exact(coefficients[1])?.multiply(values[1])?)?
627 .add(Interval::exact(coefficients[2])?.multiply(values[2])?)
628}
629
630fn inverse3(matrix: [[Scalar; 3]; 3]) -> Option<[[Scalar; 3]; 3]> {
631 if matrix.iter().flatten().any(|value| !value.is_finite()) {
632 return None;
633 }
634 let [[a, b, c], [d, e, f], [g, h, i]] = matrix;
635 let determinant = a * (e * i - f * h) - b * (d * i - f * g) + c * (d * h - e * g);
636 if determinant == 0.0 || !determinant.is_finite() {
637 return None;
638 }
639 let inverse = [
640 [
641 (e * i - f * h) / determinant,
642 (c * h - b * i) / determinant,
643 (b * f - c * e) / determinant,
644 ],
645 [
646 (f * g - d * i) / determinant,
647 (a * i - c * g) / determinant,
648 (c * d - a * f) / determinant,
649 ],
650 [
651 (d * h - e * g) / determinant,
652 (b * g - a * h) / determinant,
653 (a * e - b * d) / determinant,
654 ],
655 ];
656 inverse
657 .iter()
658 .flatten()
659 .all(|value| value.is_finite())
660 .then_some(inverse)
661}
662
663fn determinant3_interval(matrix: [[Interval; 3]; 3]) -> GeomResult<Interval> {
664 let first = matrix[0][0].multiply(
665 matrix[1][1]
666 .multiply(matrix[2][2])?
667 .subtract(matrix[1][2].multiply(matrix[2][1])?)?,
668 )?;
669 let second = matrix[0][1].multiply(
670 matrix[1][0]
671 .multiply(matrix[2][2])?
672 .subtract(matrix[1][2].multiply(matrix[2][0])?)?,
673 )?;
674 let third = matrix[0][2].multiply(
675 matrix[1][0]
676 .multiply(matrix[2][1])?
677 .subtract(matrix[1][1].multiply(matrix[2][0])?)?,
678 )?;
679 first.subtract(second)?.add(third)
680}
681
682fn residual_norm_upper(curve: &Cell, patch: &Patch) -> GeomResult<Scalar> {
683 let curve = curve.coordinate_intervals()?;
684 let surface = patch.coordinate_intervals()?;
685 let mut maximum = [0.0; 3];
686 for axis in 0..3 {
687 let difference = curve[axis].subtract(surface[axis])?;
688 maximum[axis] = difference.lower().abs().max(difference.upper().abs());
689 }
690 let norm = maximum[0].hypot(maximum[1]).hypot(maximum[2]);
691 if !norm.is_finite() {
692 return Err(GeomError::Degenerate(
693 "certified curve/surface residual bound overflowed".to_owned(),
694 ));
695 }
696 Ok(next_up(norm))
697}
698
699fn interval_midpoint(interval: ParameterInterval) -> Scalar {
700 interval.start * 0.5 + interval.end * 0.5
701}
702
703fn next_up(value: Scalar) -> Scalar {
704 if value == Scalar::INFINITY {
705 return value;
706 }
707 if value == 0.0 {
708 return Scalar::from_bits(1);
709 }
710 let bits = value.to_bits();
711 if value > 0.0 {
712 Scalar::from_bits(bits + 1)
713 } else {
714 Scalar::from_bits(bits - 1)
715 }
716}
717
718#[cfg(test)]
719mod tests {
720 use super::*;
721
722 #[test]
723 fn rejects_unbounded_or_zero_policy() {
724 assert!(CertifiedCurveSurfaceIntersectionOptions::new(0.0, 1, 1).is_err());
725 assert!(CertifiedCurveSurfaceIntersectionOptions::new(1.0e-6, 0, 1).is_err());
726 assert!(CertifiedCurveSurfaceIntersectionOptions::new(1.0e-6, MAX_NODES + 1, 1).is_err());
727 assert!(CertifiedCurveSurfaceIntersectionOptions::new(1.0e-6, 1, MAX_DEPTH + 1).is_err());
728 }
729
730 #[test]
731 fn interval_determinant_encloses_identity() {
732 let zero = Interval::exact(0.0).expect("zero");
733 let one = Interval::exact(1.0).expect("one");
734 let determinant =
735 determinant3_interval([[one, zero, zero], [zero, one, zero], [zero, zero, one]])
736 .expect("determinant");
737 assert!(determinant.lower() <= 1.0 && determinant.upper() >= 1.0);
738 assert!(determinant.absolute_lower_bound() > 0.0);
739 }
740}
741
742#[derive(Debug, Clone, Copy, PartialEq, Eq)]
750enum PinnedEdge {
751 U,
753 V,
755}
756fn krawczyk_root_on_edge(
765 curve: &BSplineCurve3,
766 surface: &BSplineSurface,
767 curve_cell: &Cell,
768 patch: &Patch,
769 edge: PinnedEdge,
770 pinned_value: Scalar,
771) -> GeomResult<Option<TransverseCurveSurfaceIntersection3>> {
772 let (free_start, free_end) = match edge {
775 PinnedEdge::U => (patch.v_start, patch.v_end),
776 PinnedEdge::V => (patch.u_start, patch.u_end),
777 };
778 let center_t = curve_cell.start * 0.5 + curve_cell.end * 0.5;
779 let center_free = free_start * 0.5 + free_end * 0.5;
780 let (center_u, center_v) = match edge {
781 PinnedEdge::U => (pinned_value, center_free),
782 PinnedEdge::V => (center_free, pinned_value),
783 };
784
785 let curve_jet = bspline_jet3(curve, center_t)?;
786 let surface_jet = bspline_jet(surface, center_u, center_v)?;
787 let free_partial = match edge {
789 PinnedEdge::U => surface_jet.dv,
790 PinnedEdge::V => surface_jet.du,
791 };
792 let point_jacobian = [
793 [curve_jet.first.x, -free_partial.x],
794 [curve_jet.first.y, -free_partial.y],
795 [curve_jet.first.z, -free_partial.z],
796 ];
797
798 let rows = [(0usize, 1usize), (0, 2), (1, 2)];
803 let mut best: Option<RowSelection> = None;
804 let mut best_magnitude = 0.0;
805 for (first, second) in rows {
806 let candidate = [
807 [point_jacobian[first][0], point_jacobian[first][1]],
808 [point_jacobian[second][0], point_jacobian[second][1]],
809 ];
810 let Some(inverse) = inverse2(candidate) else {
811 continue;
812 };
813 let magnitude =
814 (candidate[0][0] * candidate[1][1] - candidate[0][1] * candidate[1][0]).abs();
815 if magnitude > best_magnitude {
816 best_magnitude = magnitude;
817 best = Some((candidate, inverse, (first, second)));
818 }
819 }
820 let Some((_, inverse, (row_a, row_b))) = best else {
821 return Ok(None);
822 };
823
824 let curve_midpoint = curve_cell.midpoint_point()?.euclidean()?;
825 let surface_midpoint = patch.midpoint_point()?.euclidean()?;
826 let residual_all = [
827 curve_midpoint[0].subtract(surface_midpoint[0])?,
828 curve_midpoint[1].subtract(surface_midpoint[1])?,
829 curve_midpoint[2].subtract(surface_midpoint[2])?,
830 ];
831 let residual = [residual_all[row_a], residual_all[row_b]];
832
833 let curve_derivative = curve_cell.derivative_intervals()?;
834 let free_intervals = match edge {
835 PinnedEdge::U => patch.partial_v_intervals()?,
836 PinnedEdge::V => patch.partial_u_intervals()?,
837 };
838 let minus_one = Interval::exact(-1.0)?;
839 let jacobian = [
840 [
841 curve_derivative[row_a],
842 free_intervals[row_a].multiply(minus_one)?,
843 ],
844 [
845 curve_derivative[row_b],
846 free_intervals[row_b].multiply(minus_one)?,
847 ],
848 ];
849
850 let zero = Interval::exact(0.0)?;
851 let one = Interval::exact(1.0)?;
852 let center = [center_t, center_free];
853 let mut corrected = [zero; 2];
854 for row in 0..2 {
855 corrected[row] = Interval::exact(center[row])?.subtract(dot2(inverse[row], residual)?)?;
856 }
857 let mut matrix = [[zero; 2]; 2];
858 for row in 0..2 {
859 for column in 0..2 {
860 let jacobian_column = [jacobian[0][column], jacobian[1][column]];
861 let identity = if row == column { one } else { zero };
862 matrix[row][column] = identity.subtract(dot2(inverse[row], jacobian_column)?)?;
863 }
864 }
865 let bounds = [
866 ParameterInterval {
867 start: curve_cell.start,
868 end: curve_cell.end,
869 },
870 ParameterInterval {
871 start: free_start,
872 end: free_end,
873 },
874 ];
875 let mut delta = [zero; 2];
876 for axis in 0..2 {
877 delta[axis] = Interval::hull([
878 Interval::exact(bounds[axis].start)?.subtract(Interval::exact(center[axis])?)?,
879 Interval::exact(bounds[axis].end)?.subtract(Interval::exact(center[axis])?)?,
880 ])?;
881 }
882 let mut image = [zero; 2];
883 for row in 0..2 {
884 image[row] = corrected[row].add(
885 matrix[row][0]
886 .multiply(delta[0])?
887 .add(matrix[row][1].multiply(delta[1])?)?,
888 )?;
889 }
890 if !(image[0].lower() > bounds[0].start
893 && image[0].upper() < bounds[0].end
894 && image[1].lower() > bounds[1].start
895 && image[1].upper() < bounds[1].end)
896 {
897 return Ok(None);
898 }
899 let determinant_lower = determinant2_interval(jacobian)?.absolute_lower_bound();
900 if determinant_lower == 0.0 {
901 return Ok(None);
902 }
903
904 let curve_parameter = ParameterInterval {
905 start: image[0].lower(),
906 end: image[0].upper(),
907 };
908 let free_parameter = ParameterInterval {
909 start: image[1].lower(),
910 end: image[1].upper(),
911 };
912 let pinned = ParameterInterval {
913 start: pinned_value,
914 end: pinned_value,
915 };
916 let (surface_u_parameter, surface_v_parameter) = match edge {
917 PinnedEdge::U => (pinned, free_parameter),
918 PinnedEdge::V => (free_parameter, pinned),
919 };
920
921 let root_curve = {
929 let (start, end) = sliver(
930 curve_parameter.start,
931 curve_parameter.end,
932 curve_cell.start,
933 curve_cell.end,
934 );
935 curve_cell.restrict(start, end)?
936 };
937 let root_patch = {
938 let (u_start, u_end) = sliver(
939 surface_u_parameter.start,
940 surface_u_parameter.end,
941 patch.u_start,
942 patch.u_end,
943 );
944 let (v_start, v_end) = sliver(
945 surface_v_parameter.start,
946 surface_v_parameter.end,
947 patch.v_start,
948 patch.v_end,
949 );
950 patch.restrict(u_start, u_end, v_start, v_end)?
951 };
952 let residual_upper_bound = residual_norm_upper(&root_curve, &root_patch)?;
965 let discarded = 3 - row_a - row_b;
966 let curve_span = root_curve.coordinate_intervals()?[discarded];
967 let patch_span = root_patch.coordinate_intervals()?[discarded];
968 if curve_span.upper() < patch_span.lower() || patch_span.upper() < curve_span.lower() {
969 return Ok(None);
970 }
971
972 let curve_value = bspline_jet3(curve, interval_midpoint(curve_parameter))?.point;
973 let surface_value = bspline_jet(
974 surface,
975 interval_midpoint(surface_u_parameter),
976 interval_midpoint(surface_v_parameter),
977 )?
978 .point;
979 let point = Point3::new(
980 curve_value.x * 0.5 + surface_value.x * 0.5,
981 curve_value.y * 0.5 + surface_value.y * 0.5,
982 curve_value.z * 0.5 + surface_value.z * 0.5,
983 );
984 if !point.is_finite() || !residual_upper_bound.is_finite() {
985 return Err(GeomError::Degenerate(
986 "certified curve/surface edge representative overflowed".to_owned(),
987 ));
988 }
989 Ok(Some(TransverseCurveSurfaceIntersection3 {
990 curve_parameter,
991 surface_u_parameter,
992 surface_v_parameter,
993 point,
994 residual_upper_bound,
995 jacobian_determinant_lower_bound: determinant_lower,
996 }))
997}
998
999fn sliver(start: Scalar, end: Scalar, low: Scalar, high: Scalar) -> (Scalar, Scalar) {
1006 if end > start {
1007 return (start, end);
1008 }
1009 if high <= low {
1010 return (low, high);
1011 }
1012 let widened_end = next_up(end);
1013 if widened_end < high {
1014 return (start, widened_end);
1015 }
1016 let widened_start = -next_up(-start);
1018 if widened_start > low {
1019 (widened_start, end)
1020 } else {
1021 (low, high)
1022 }
1023}
1024
1025type RowSelection = ([[Scalar; 2]; 2], [[Scalar; 2]; 2], (usize, usize));
1028
1029fn dot2(coefficients: [Scalar; 2], values: [Interval; 2]) -> GeomResult<Interval> {
1030 Interval::exact(coefficients[0])?
1031 .multiply(values[0])?
1032 .add(Interval::exact(coefficients[1])?.multiply(values[1])?)
1033}
1034
1035fn inverse2(matrix: [[Scalar; 2]; 2]) -> Option<[[Scalar; 2]; 2]> {
1036 if matrix.iter().flatten().any(|value| !value.is_finite()) {
1037 return None;
1038 }
1039 let [[a, b], [c, d]] = matrix;
1040 let determinant = a * d - b * c;
1041 if determinant == 0.0 || !determinant.is_finite() {
1042 return None;
1043 }
1044 let inverse = [
1045 [d / determinant, -b / determinant],
1046 [-c / determinant, a / determinant],
1047 ];
1048 inverse
1049 .iter()
1050 .flatten()
1051 .all(|value| value.is_finite())
1052 .then_some(inverse)
1053}
1054
1055fn determinant2_interval(matrix: [[Interval; 2]; 2]) -> GeomResult<Interval> {
1056 matrix[0][0]
1057 .multiply(matrix[1][1])?
1058 .subtract(matrix[0][1].multiply(matrix[1][0])?)
1059}
1060
1061#[derive(Debug, Clone, Copy)]
1064struct SurfaceDomain {
1065 u_start: Scalar,
1066 u_end: Scalar,
1067 v_start: Scalar,
1068 v_end: Scalar,
1069}
1070
1071fn edge_root(
1077 curve: &BSplineCurve3,
1078 surface: &BSplineSurface,
1079 curve_cell: &Cell,
1080 patch: &Patch,
1081 domain: &SurfaceDomain,
1082 tolerance: Scalar,
1083) -> GeomResult<Option<TransverseCurveSurfaceIntersection3>> {
1084 let candidates = [
1085 (
1086 patch.u_start == domain.u_start,
1087 PinnedEdge::U,
1088 patch.u_start,
1089 ),
1090 (patch.u_end == domain.u_end, PinnedEdge::U, patch.u_end),
1091 (
1092 patch.v_start == domain.v_start,
1093 PinnedEdge::V,
1094 patch.v_start,
1095 ),
1096 (patch.v_end == domain.v_end, PinnedEdge::V, patch.v_end),
1097 ];
1098 for (on_domain_edge, edge, value) in candidates {
1099 if !on_domain_edge {
1100 continue;
1101 }
1102 if let Some(root) = krawczyk_root_on_edge(curve, surface, curve_cell, patch, edge, value)? {
1103 if certificate_meets_resolution(&root, tolerance) {
1104 return Ok(Some(root));
1105 }
1106 }
1107 }
1108 Ok(None)
1109}