1use axiolid_contracts::{GeomError, GeomResult};
9use axiolid_core::{Point3, Scalar};
10use axiolid_curve::{BSplineCurve3, KnotSpec};
11use axiolid_surface::BSplineSurface;
12
13use crate::{
14 certified_bezier::Interval,
15 certified_curve_surface_intersection::{
16 intersect_curve_surface_certified, CertifiedCurveSurfaceIntersection3,
17 CertifiedCurveSurfaceIntersectionOptions, TransverseCurveSurfaceIntersection3,
18 },
19 certified_projection::ParameterInterval,
20 certified_refinement::RefinementBudget,
21 certified_surface_bezier::{piecewise_bezier_patches, Patch},
22};
23
24const MAX_REFINEMENT_WORK: u32 = 100_000;
25const MAX_BOUNDARY_NODES: u32 = 100_000;
26const MAX_DEPTH: u16 = 64;
27const BOUNDARY_QUERY_COUNT: u8 = 8;
28
29#[derive(Debug, Clone, Copy, PartialEq)]
31pub struct CertifiedSurfaceSurfaceIntersectionOptions {
32 parameter_tolerance: Scalar,
33 max_refinement_work: u32,
34 max_boundary_nodes: u32,
35 max_depth: u16,
36}
37
38impl CertifiedSurfaceSurfaceIntersectionOptions {
39 pub fn new(
45 parameter_tolerance: Scalar,
46 max_refinement_work: u32,
47 max_boundary_nodes: u32,
48 max_depth: u16,
49 ) -> GeomResult<Self> {
50 if !parameter_tolerance.is_finite() || parameter_tolerance <= 0.0 {
51 return Err(GeomError::InvalidInput(
52 "surface/surface parameter tolerance must be finite and positive".to_owned(),
53 ));
54 }
55 if max_refinement_work == 0 || max_refinement_work > MAX_REFINEMENT_WORK {
56 return Err(GeomError::InvalidInput(format!(
57 "surface/surface max_refinement_work must be in 1..={MAX_REFINEMENT_WORK}"
58 )));
59 }
60 if max_boundary_nodes == 0 || max_boundary_nodes > MAX_BOUNDARY_NODES {
61 return Err(GeomError::InvalidInput(format!(
62 "surface/surface max_boundary_nodes must be in 1..={MAX_BOUNDARY_NODES}"
63 )));
64 }
65 if max_depth == 0 || max_depth > MAX_DEPTH {
66 return Err(GeomError::InvalidInput(format!(
67 "surface/surface max_depth must be in 1..={MAX_DEPTH}"
68 )));
69 }
70 Ok(Self {
71 parameter_tolerance,
72 max_refinement_work,
73 max_boundary_nodes,
74 max_depth,
75 })
76 }
77}
78
79impl Default for CertifiedSurfaceSurfaceIntersectionOptions {
80 fn default() -> Self {
81 Self {
82 parameter_tolerance: 1.0e-8,
83 max_refinement_work: MAX_REFINEMENT_WORK,
84 max_boundary_nodes: MAX_BOUNDARY_NODES,
85 max_depth: MAX_DEPTH,
86 }
87 }
88}
89
90#[derive(Debug, Clone, Copy, PartialEq)]
92#[non_exhaustive]
93pub struct SurfaceSurfaceParameterBox {
94 pub first_u: ParameterInterval,
96 pub first_v: ParameterInterval,
98 pub second_u: ParameterInterval,
100 pub second_v: ParameterInterval,
102}
103
104#[derive(Debug, Clone, PartialEq)]
106#[non_exhaustive]
107pub struct SurfaceSurfaceTraceEndpoint3 {
108 pub parameters: SurfaceSurfaceParameterBox,
110 pub point: Point3,
112 pub residual_upper_bound: Scalar,
114}
115
116#[derive(Debug, Clone, PartialEq)]
118#[non_exhaustive]
119pub struct TransverseSurfaceSurfaceTrace3 {
120 pub start: SurfaceSurfaceTraceEndpoint3,
122 pub end: SurfaceSurfaceTraceEndpoint3,
124 pub normal_cross_squared_lower_bound: Scalar,
126}
127
128#[derive(Debug, Clone, PartialEq)]
130#[non_exhaustive]
131pub enum CertifiedSurfaceSurfaceIntersection3 {
132 Complete {
134 traces: Vec<TransverseSurfaceSurfaceTrace3>,
136 visited_patch_pairs: u32,
138 boundary_queries: u8,
140 },
141 Unresolved {
143 traces: Vec<TransverseSurfaceSurfaceTrace3>,
145 candidate_boxes: Vec<SurfaceSurfaceParameterBox>,
147 visited_patch_pairs: u32,
149 boundary_queries: u8,
151 },
152}
153
154pub fn intersect_surface_surface_certified(
161 first: &BSplineSurface,
162 second: &BSplineSurface,
163 options: CertifiedSurfaceSurfaceIntersectionOptions,
164) -> GeomResult<CertifiedSurfaceSurfaceIntersection3> {
165 let options = CertifiedSurfaceSurfaceIntersectionOptions::new(
166 options.parameter_tolerance,
167 options.max_refinement_work,
168 options.max_boundary_nodes,
169 options.max_depth,
170 )?;
171 let mut budget = RefinementBudget::new(
172 options.max_refinement_work,
173 "certified surface/surface refinement budget",
174 );
175 let first_patches = piecewise_bezier_patches(first, &mut budget)?;
176 let second_patches = piecewise_bezier_patches(second, &mut budget)?;
177 let pair_count = first_patches
178 .len()
179 .checked_mul(second_patches.len())
180 .ok_or(GeomError::BudgetExceeded {
181 resource: "certified surface/surface refinement budget",
182 })?;
183 budget.charge(u128::try_from(pair_count).ok())?;
184 let visited_patch_pairs = u32::try_from(pair_count).map_err(|_| GeomError::BudgetExceeded {
185 resource: "certified surface/surface refinement budget",
186 })?;
187 let mut candidates = Vec::new();
188 candidates
189 .try_reserve_exact(pair_count)
190 .map_err(|_| allocation_error("certified surface/surface candidate allocation"))?;
191 for first_patch in &first_patches {
192 for second_patch in &second_patches {
193 if !patches_are_disjoint(first_patch, second_patch)? {
194 candidates.push(parameter_box(first_patch, second_patch));
195 }
196 }
197 }
198 if candidates.is_empty() {
199 return Ok(CertifiedSurfaceSurfaceIntersection3::Complete {
200 traces: Vec::new(),
201 visited_patch_pairs,
202 boundary_queries: 0,
203 });
204 }
205 if candidates.len() != 1
206 || first_patches.len() != 1
207 || second_patches.len() != 1
208 || !is_exact_single_span_affine(first)
209 || !is_exact_single_span_affine(second)
210 {
211 return unresolved(candidates, visited_patch_pairs, 0);
212 }
213 let normal_lower = normal_cross_squared_lower_bound(&first_patches[0], &second_patches[0])?;
214 if normal_lower <= 0.0 {
215 return unresolved(candidates, visited_patch_pairs, 0);
216 }
217 match trace_affine_pair(first, second, options, normal_lower)? {
218 AffineTraceOutcome::Complete(traces) => {
219 Ok(CertifiedSurfaceSurfaceIntersection3::Complete {
220 traces,
221 visited_patch_pairs,
222 boundary_queries: BOUNDARY_QUERY_COUNT,
223 })
224 }
225 AffineTraceOutcome::Unresolved(traces) => {
226 Ok(CertifiedSurfaceSurfaceIntersection3::Unresolved {
227 traces,
228 candidate_boxes: candidates,
229 visited_patch_pairs,
230 boundary_queries: BOUNDARY_QUERY_COUNT,
231 })
232 }
233 }
234}
235
236fn unresolved(
237 candidate_boxes: Vec<SurfaceSurfaceParameterBox>,
238 visited_patch_pairs: u32,
239 boundary_queries: u8,
240) -> GeomResult<CertifiedSurfaceSurfaceIntersection3> {
241 Ok(CertifiedSurfaceSurfaceIntersection3::Unresolved {
242 traces: Vec::new(),
243 candidate_boxes,
244 visited_patch_pairs,
245 boundary_queries,
246 })
247}
248
249fn allocation_error(resource: &'static str) -> GeomError {
250 GeomError::BudgetExceeded { resource }
251}
252
253fn parameter_box(first: &Patch, second: &Patch) -> SurfaceSurfaceParameterBox {
254 SurfaceSurfaceParameterBox {
255 first_u: ParameterInterval {
256 start: first.u_start,
257 end: first.u_end,
258 },
259 first_v: ParameterInterval {
260 start: first.v_start,
261 end: first.v_end,
262 },
263 second_u: ParameterInterval {
264 start: second.u_start,
265 end: second.u_end,
266 },
267 second_v: ParameterInterval {
268 start: second.v_start,
269 end: second.v_end,
270 },
271 }
272}
273
274pub(crate) fn patches_are_disjoint(first: &Patch, second: &Patch) -> GeomResult<bool> {
275 let first_bounds = first.coordinate_intervals()?;
276 let second_bounds = second.coordinate_intervals()?;
277 Ok((0..3).any(|axis| {
278 first_bounds[axis].upper() < second_bounds[axis].lower()
279 || second_bounds[axis].upper() < first_bounds[axis].lower()
280 }))
281}
282
283fn is_exact_single_span_affine(surface: &BSplineSurface) -> bool {
284 if surface.u_degree != 1
285 || surface.v_degree != 1
286 || surface.control_points.len() != 2
287 || surface.control_points.iter().any(|row| row.len() != 2)
288 || surface.weights.is_some()
289 || surface.u_knots.len() != 2
290 || surface.v_knots.len() != 2
291 {
292 return false;
293 }
294 let p00 = surface.control_points[0][0];
295 let p01 = surface.control_points[0][1];
296 let p10 = surface.control_points[1][0];
297 let p11 = surface.control_points[1][1];
298 [
299 [p11.x, -p10.x, -p01.x, p00.x],
300 [p11.y, -p10.y, -p01.y, p00.y],
301 [p11.z, -p10.z, -p01.z, p00.z],
302 ]
303 .into_iter()
304 .all(exact_sum_is_zero)
305}
306
307fn exact_sum_is_zero(values: [Scalar; 4]) -> bool {
311 let mut expansion = [0.0; 8];
312 let mut length = 1usize;
313 expansion[0] = values[0];
314 for value in values.into_iter().skip(1) {
315 let mut next = [0.0; 8];
316 let mut next_length = 0usize;
317 let mut accumulator = value;
318 for component in expansion.iter().take(length).copied() {
319 let (sum, error) = two_sum(accumulator, component);
320 if error != 0.0 {
321 next[next_length] = error;
322 next_length += 1;
323 }
324 accumulator = sum;
325 }
326 if accumulator != 0.0 || next_length == 0 {
327 next[next_length] = accumulator;
328 next_length += 1;
329 }
330 expansion = next;
331 length = next_length;
332 }
333 expansion
334 .iter()
335 .take(length)
336 .all(|component| *component == 0.0)
337}
338
339fn two_sum(left: Scalar, right: Scalar) -> (Scalar, Scalar) {
340 let sum = left + right;
341 let right_virtual = sum - left;
342 let left_virtual = sum - right_virtual;
343 let right_roundoff = right - right_virtual;
344 let left_roundoff = left - left_virtual;
345 (sum, left_roundoff + right_roundoff)
346}
347
348pub(crate) fn normal_cross_squared_lower_bound(
349 first: &Patch,
350 second: &Patch,
351) -> GeomResult<Scalar> {
352 let first_normal = cross_intervals(first.partial_u_intervals()?, first.partial_v_intervals()?)?;
353 let second_normal =
354 cross_intervals(second.partial_u_intervals()?, second.partial_v_intervals()?)?;
355 let cross = cross_intervals(first_normal, second_normal)?;
356 let mut squared = Interval::exact(0.0)?;
357 for component in cross {
358 let lower = Interval::exact(component.absolute_lower_bound())?;
359 squared = squared.add(lower.multiply(lower)?)?;
360 }
361 Ok(squared.lower().max(0.0))
362}
363
364fn cross_intervals(left: [Interval; 3], right: [Interval; 3]) -> GeomResult<[Interval; 3]> {
365 Ok([
366 left[1]
367 .multiply(right[2])?
368 .subtract(left[2].multiply(right[1])?)?,
369 left[2]
370 .multiply(right[0])?
371 .subtract(left[0].multiply(right[2])?)?,
372 left[0]
373 .multiply(right[1])?
374 .subtract(left[1].multiply(right[0])?)?,
375 ])
376}
377
378#[derive(Debug, Clone, Copy)]
379enum Boundary {
380 UStart,
381 UEnd,
382 VStart,
383 VEnd,
384}
385
386const BOUNDARIES: [Boundary; 4] = [
387 Boundary::UStart,
388 Boundary::UEnd,
389 Boundary::VStart,
390 Boundary::VEnd,
391];
392
393enum AffineTraceOutcome {
394 Complete(Vec<TransverseSurfaceSurfaceTrace3>),
395 Unresolved(Vec<TransverseSurfaceSurfaceTrace3>),
396}
397
398fn trace_affine_pair(
399 first: &BSplineSurface,
400 second: &BSplineSurface,
401 options: CertifiedSurfaceSurfaceIntersectionOptions,
402 normal_lower: Scalar,
403) -> GeomResult<AffineTraceOutcome> {
404 let boundary_options = CertifiedCurveSurfaceIntersectionOptions::new(
405 options.parameter_tolerance,
406 options.max_boundary_nodes,
407 options.max_depth,
408 )?;
409 let mut endpoints = Vec::new();
410 endpoints
411 .try_reserve_exact(BOUNDARY_QUERY_COUNT.into())
412 .map_err(|_| allocation_error("certified surface/surface endpoint allocation"))?;
413 let mut any_unresolved = false;
414 for boundary in BOUNDARIES {
415 if let Err(error) = collect_boundary_roots(
416 first,
417 second,
418 boundary,
419 true,
420 boundary_options,
421 &mut endpoints,
422 &mut any_unresolved,
423 ) {
424 if matches!(error, GeomError::BudgetExceeded { .. }) {
425 any_unresolved = true;
426 continue;
427 }
428 return Err(error);
429 }
430 }
431 for boundary in BOUNDARIES {
432 if let Err(error) = collect_boundary_roots(
433 second,
434 first,
435 boundary,
436 false,
437 boundary_options,
438 &mut endpoints,
439 &mut any_unresolved,
440 ) {
441 if matches!(error, GeomError::BudgetExceeded { .. }) {
442 any_unresolved = true;
443 continue;
444 }
445 return Err(error);
446 }
447 }
448 dedupe_coincident_endpoints(&mut endpoints);
454 if any_unresolved || endpoints.len() == 1 || endpoints.len() > 2 {
455 return Ok(AffineTraceOutcome::Unresolved(Vec::new()));
456 }
457 if endpoints.is_empty() {
458 return Ok(AffineTraceOutcome::Complete(Vec::new()));
459 }
460 endpoints.sort_by(|left, right| lexicographic_point_cmp(left.point, right.point));
461 if endpoint_boxes_overlap(&endpoints[0], &endpoints[1]) {
462 return Ok(AffineTraceOutcome::Unresolved(Vec::new()));
463 }
464 let mut traces = Vec::new();
465 traces
466 .try_reserve_exact(1)
467 .map_err(|_| allocation_error("certified surface/surface trace allocation"))?;
468 traces.push(TransverseSurfaceSurfaceTrace3 {
469 start: endpoints.remove(0),
470 end: endpoints.remove(0),
471 normal_cross_squared_lower_bound: normal_lower,
472 });
473 Ok(AffineTraceOutcome::Complete(traces))
474}
475
476fn collect_boundary_roots(
477 owner: &BSplineSurface,
478 other: &BSplineSurface,
479 boundary: Boundary,
480 owner_is_first: bool,
481 options: CertifiedCurveSurfaceIntersectionOptions,
482 endpoints: &mut Vec<SurfaceSurfaceTraceEndpoint3>,
483 any_unresolved: &mut bool,
484) -> GeomResult<()> {
485 let curve = boundary_curve(owner, boundary)?;
486 match intersect_curve_surface_certified(&curve, other, options)? {
487 CertifiedCurveSurfaceIntersection3::Complete { intersections, .. } => {
488 for intersection in intersections {
489 push_endpoint(
490 endpoints,
491 map_endpoint(owner, boundary, owner_is_first, intersection),
492 )?;
493 }
494 }
495 CertifiedCurveSurfaceIntersection3::Unresolved { .. } => *any_unresolved = true,
496 }
497 Ok(())
498}
499
500fn push_endpoint(
501 endpoints: &mut Vec<SurfaceSurfaceTraceEndpoint3>,
502 endpoint: SurfaceSurfaceTraceEndpoint3,
503) -> GeomResult<()> {
504 if endpoints.len() == endpoints.capacity() {
505 endpoints
506 .try_reserve_exact(1)
507 .map_err(|_| allocation_error("certified surface/surface endpoint allocation"))?;
508 }
509 endpoints.push(endpoint);
510 Ok(())
511}
512
513fn boundary_curve(surface: &BSplineSurface, boundary: Boundary) -> GeomResult<BSplineCurve3> {
514 let (degree, knots, multiplicities, controls) = match boundary {
515 Boundary::UStart => (
516 surface.v_degree,
517 &surface.v_knots,
518 &surface.v_multiplicities,
519 clone_points(surface.control_points[0].iter().copied())?,
520 ),
521 Boundary::UEnd => (
522 surface.v_degree,
523 &surface.v_knots,
524 &surface.v_multiplicities,
525 clone_points(surface.control_points[1].iter().copied())?,
526 ),
527 Boundary::VStart => (
528 surface.u_degree,
529 &surface.u_knots,
530 &surface.u_multiplicities,
531 clone_points(surface.control_points.iter().map(|row| row[0]))?,
532 ),
533 Boundary::VEnd => (
534 surface.u_degree,
535 &surface.u_knots,
536 &surface.u_multiplicities,
537 clone_points(surface.control_points.iter().map(|row| row[1]))?,
538 ),
539 };
540 Ok(BSplineCurve3 {
541 degree,
542 control_points: controls,
543 knots: clone_scalars(knots)?,
544 multiplicities: clone_multiplicities(multiplicities)?,
545 weights: None,
546 knot_spec: KnotSpec::Unspecified,
547 closed: false,
548 self_intersect: None,
549 })
550}
551
552fn map_endpoint(
553 owner: &BSplineSurface,
554 boundary: Boundary,
555 owner_is_first: bool,
556 root: TransverseCurveSurfaceIntersection3,
557) -> SurfaceSurfaceTraceEndpoint3 {
558 let fixed_u = |value: Scalar| ParameterInterval {
559 start: value,
560 end: value,
561 };
562 let (owner_u, owner_v) = match boundary {
563 Boundary::UStart => (fixed_u(owner.u_knots[0]), root.curve_parameter),
564 Boundary::UEnd => (
565 fixed_u(owner.u_knots[owner.u_knots.len() - 1]),
566 root.curve_parameter,
567 ),
568 Boundary::VStart => (root.curve_parameter, fixed_u(owner.v_knots[0])),
569 Boundary::VEnd => (
570 root.curve_parameter,
571 fixed_u(owner.v_knots[owner.v_knots.len() - 1]),
572 ),
573 };
574 let other_u = root.surface_u_parameter;
575 let other_v = root.surface_v_parameter;
576 let parameters = if owner_is_first {
577 SurfaceSurfaceParameterBox {
578 first_u: owner_u,
579 first_v: owner_v,
580 second_u: other_u,
581 second_v: other_v,
582 }
583 } else {
584 SurfaceSurfaceParameterBox {
585 first_u: other_u,
586 first_v: other_v,
587 second_u: owner_u,
588 second_v: owner_v,
589 }
590 };
591 SurfaceSurfaceTraceEndpoint3 {
592 parameters,
593 point: root.point,
594 residual_upper_bound: root.residual_upper_bound,
595 }
596}
597
598fn dedupe_coincident_endpoints(endpoints: &mut Vec<SurfaceSurfaceTraceEndpoint3>) {
609 let mut kept: Vec<SurfaceSurfaceTraceEndpoint3> = Vec::new();
610 for endpoint in endpoints.drain(..) {
611 if kept
612 .iter()
613 .any(|existing| endpoint_boxes_overlap(existing, &endpoint))
614 {
615 continue;
616 }
617 kept.push(endpoint);
618 }
619 *endpoints = kept;
620}
621
622fn endpoint_boxes_overlap(
623 first: &SurfaceSurfaceTraceEndpoint3,
624 second: &SurfaceSurfaceTraceEndpoint3,
625) -> bool {
626 let left = first.parameters;
627 let right = second.parameters;
628 intervals_overlap(left.first_u, right.first_u)
629 && intervals_overlap(left.first_v, right.first_v)
630 && intervals_overlap(left.second_u, right.second_u)
631 && intervals_overlap(left.second_v, right.second_v)
632}
633
634fn intervals_overlap(first: ParameterInterval, second: ParameterInterval) -> bool {
635 first.start <= second.end && second.start <= first.end
636}
637
638fn lexicographic_point_cmp(left: Point3, right: Point3) -> std::cmp::Ordering {
639 left.x
640 .total_cmp(&right.x)
641 .then_with(|| left.y.total_cmp(&right.y))
642 .then_with(|| left.z.total_cmp(&right.z))
643}
644
645fn clone_points(values: impl ExactSizeIterator<Item = Point3>) -> GeomResult<Vec<Point3>> {
646 let count = values.len();
647 let mut output = Vec::new();
648 output
649 .try_reserve_exact(count)
650 .map_err(|_| allocation_error("certified surface/surface boundary allocation"))?;
651 for value in values {
652 output.push(value);
653 }
654 Ok(output)
655}
656
657fn clone_scalars(values: &[Scalar]) -> GeomResult<Vec<Scalar>> {
658 let mut output = Vec::new();
659 output
660 .try_reserve_exact(values.len())
661 .map_err(|_| allocation_error("certified surface/surface boundary allocation"))?;
662 output.extend(values.iter().copied());
663 Ok(output)
664}
665
666fn clone_multiplicities(values: &[u32]) -> GeomResult<Vec<u32>> {
667 let mut output = Vec::new();
668 output
669 .try_reserve_exact(values.len())
670 .map_err(|_| allocation_error("certified surface/surface boundary allocation"))?;
671 output.extend(values.iter().copied());
672 Ok(output)
673}