1use crate::curve::interior_knot_count;
2use crate::fit::solve_small;
3use crate::{project_point_to_surface, KnotVector, NurbsSurface, Vec3};
4use serde::{Deserialize, Serialize};
5
6const EPSILON: f64 = 1e-12;
7const LINEAR_TOLERANCE: f64 = 1e-7;
8
9const TRANSVERSE_START_CROSS: f64 = 1e-2;
24
25pub(crate) const TRANSVERSE_SEED_CROSS: f64 = TRANSVERSE_START_CROSS;
30
31#[derive(Clone, Copy)]
32struct Bounds {
33 minimum: Vec3,
34 maximum: Vec3,
35}
36
37impl Bounds {
38 fn from_surface(surface: &NurbsSurface) -> Result<Self, String> {
39 let mut minimum = Vec3::new(f64::INFINITY, f64::INFINITY, f64::INFINITY);
40 let mut maximum = Vec3::new(f64::NEG_INFINITY, f64::NEG_INFINITY, f64::NEG_INFINITY);
41 for point in surface.control_points.iter().flatten() {
42 let point = point.point()?;
43 minimum.x = minimum.x.min(point.x);
44 minimum.y = minimum.y.min(point.y);
45 minimum.z = minimum.z.min(point.z);
46 maximum.x = maximum.x.max(point.x);
47 maximum.y = maximum.y.max(point.y);
48 maximum.z = maximum.z.max(point.z);
49 }
50 Ok(Self { minimum, maximum })
51 }
52
53 fn diagonal(self) -> f64 {
54 self.maximum.sub(self.minimum).length()
55 }
56
57 fn expanded(self, amount: f64) -> Self {
58 let delta = Vec3::new(amount, amount, amount);
59 Self {
60 minimum: self.minimum.sub(delta),
61 maximum: self.maximum.add(delta),
62 }
63 }
64
65 fn contains(self, point: Vec3) -> bool {
66 point.x >= self.minimum.x
67 && point.x <= self.maximum.x
68 && point.y >= self.minimum.y
69 && point.y <= self.maximum.y
70 && point.z >= self.minimum.z
71 && point.z <= self.maximum.z
72 }
73
74 fn intersects(self, other: Self, tolerance: f64) -> bool {
75 self.expanded(tolerance).minimum.x <= other.maximum.x
76 && self.maximum.x + tolerance >= other.minimum.x
77 && self.minimum.y - tolerance <= other.maximum.y
78 && self.maximum.y + tolerance >= other.minimum.y
79 && self.minimum.z - tolerance <= other.maximum.z
80 && self.maximum.z + tolerance >= other.minimum.z
81 }
82}
83
84#[derive(Clone, Copy)]
85struct SurfaceInfo<'a> {
86 surface: &'a NurbsSurface,
87 u0: f64,
88 u1: f64,
89 v0: f64,
90 v1: f64,
91 closed_u: bool,
92 closed_v: bool,
93}
94
95fn surface_info(surface: &NurbsSurface) -> Result<SurfaceInfo<'_>, String> {
96 let ku = KnotVector::new(surface.knots_u.clone(), surface.degree_u)?;
97 let kv = KnotVector::new(surface.knots_v.clone(), surface.degree_v)?;
98 let [u0, u1] = ku.domain();
99 let [v0, v1] = kv.domain();
100 let mut closed_u = true;
101 let mut closed_v = true;
102 for fraction in [0.17, 0.5, 0.83] {
103 let v = v0 + (v1 - v0) * fraction;
104 if surface
105 .evaluate(u0, v)?
106 .sub(surface.evaluate(u1, v)?)
107 .length()
108 > LINEAR_TOLERANCE * 10.0
109 {
110 closed_u = false;
111 }
112 let u = u0 + (u1 - u0) * fraction;
113 if surface
114 .evaluate(u, v0)?
115 .sub(surface.evaluate(u, v1)?)
116 .length()
117 > LINEAR_TOLERANCE * 10.0
118 {
119 closed_v = false;
120 }
121 }
122 Ok(SurfaceInfo {
123 surface,
124 u0,
125 u1,
126 v0,
127 v1,
128 closed_u,
129 closed_v,
130 })
131}
132
133fn fit(value: f64, minimum: f64, maximum: f64, closed: bool) -> f64 {
134 if closed {
135 (value - minimum).rem_euclid(maximum - minimum) + minimum
136 } else {
137 value.clamp(minimum, maximum)
138 }
139}
140
141
142#[derive(Clone, Copy)]
143struct IntersectionPoint {
144 ua: f64,
145 va: f64,
146 ub: f64,
147 vb: f64,
148 point: Vec3,
149}
150
151fn refine_point(
152 first: SurfaceInfo<'_>,
153 second: SurfaceInfo<'_>,
154 mut ua: f64,
155 mut va: f64,
156 mut ub: f64,
157 mut vb: f64,
158 tolerance: f64,
159) -> Result<Option<IntersectionPoint>, String> {
160 for _ in 0..40 {
161 let da = first.surface.derivatives(ua, va, 1)?;
162 let db = second.surface.derivatives(ub, vb, 1)?;
163 let residual = da[0][0].sub(db[0][0]);
164 if residual.length() <= tolerance * 0.01 + EPSILON {
165 return Ok(Some(IntersectionPoint {
166 ua,
167 va,
168 ub,
169 vb,
170 point: da[0][0].add(db[0][0]).scale(0.5),
171 }));
172 }
173 let columns = [
174 da[1][0],
175 da[0][1],
176 db[1][0].scale(-1.0),
177 db[0][1].scale(-1.0),
178 ];
179 let mut matrix = [[0.0f64; 3]; 3];
180 for column in columns {
181 let values = [column.x, column.y, column.z];
182 for row in 0..3 {
183 for col in 0..3 {
184 matrix[row][col] += values[row] * values[col];
185 }
186 }
187 }
188 let lambda = match solve_small(matrix, [-residual.x, -residual.y, -residual.z], 3) {
189 Ok(value) => value,
190 Err(_) => return Ok(None),
191 };
192 let multiplier = Vec3::new(lambda[0], lambda[1], lambda[2]);
193 let mut changes = columns.map(|column| column.dot(multiplier));
194 let domains = [
195 first.u1 - first.u0,
196 first.v1 - first.v0,
197 second.u1 - second.u0,
198 second.v1 - second.v0,
199 ];
200 for index in 0..4 {
201 changes[index] = changes[index].clamp(-domains[index] / 4.0, domains[index] / 4.0);
202 }
203 ua = fit(ua + changes[0], first.u0, first.u1, first.closed_u);
204 va = fit(va + changes[1], first.v0, first.v1, first.closed_v);
205 ub = fit(ub + changes[2], second.u0, second.u1, second.closed_u);
206 vb = fit(vb + changes[3], second.v0, second.v1, second.closed_v);
207 }
208 let a = first.surface.evaluate(ua, va)?;
209 let b = second.surface.evaluate(ub, vb)?;
210 if a.sub(b).length() <= tolerance {
211 Ok(Some(IntersectionPoint {
212 ua,
213 va,
214 ub,
215 vb,
216 point: a.add(b).scale(0.5),
217 }))
218 } else {
219 Ok(None)
220 }
221}
222
223fn find_start_points(
224 first: SurfaceInfo<'_>,
225 second: SurfaceInfo<'_>,
226 tolerance: f64,
227 density: f64,
228) -> Result<Vec<IntersectionPoint>, String> {
229 let grid_count = |info: SurfaceInfo<'_>, direction_u: bool| {
230 let (knots, degree) = if direction_u {
231 (&info.surface.knots_u, info.surface.degree_u)
232 } else {
233 (&info.surface.knots_v, info.surface.degree_v)
234 };
235 (8.0_f64)
236 .max(((interior_knot_count(knots, degree) + 1) * (degree + 1)) as f64 * density)
237 .ceil()
238 .min(24.0) as usize
239 };
240 let nu = grid_count(first, true);
241 let nv = grid_count(first, false);
242 let first_box = Bounds::from_surface(first.surface)?;
243 let second_box = Bounds::from_surface(second.surface)?;
244 let gate = first_box.diagonal().min(second_box.diagonal()) * 0.15 + tolerance;
245 let expanded_second = second_box.expanded((tolerance * 100.0).max(gate));
246 let mut starts = Vec::new();
247 for i in 0..=nu {
248 for j in 0..=nv {
249 let ua = first.u0 + (first.u1 - first.u0) * i as f64 / nu as f64;
250 let va = first.v0 + (first.v1 - first.v0) * j as f64 / nv as f64;
251 let point = first.surface.evaluate(ua, va)?;
252 if !expanded_second.contains(point) {
253 continue;
254 }
255 let projection = project_point_to_surface(second.surface, point)?;
256 if projection.distance > gate {
257 continue;
258 }
259 if let Some(refined) =
260 refine_point(first, second, ua, va, projection.u, projection.v, tolerance)?
261 {
262 starts.push(refined);
263 }
264 }
265 }
266 Ok(starts)
267}
268
269fn transverse_at(
273 first: SurfaceInfo<'_>,
274 second: SurfaceInfo<'_>,
275 point: IntersectionPoint,
276) -> bool {
277 match (
278 first.surface.normal(point.ua, point.va),
279 second.surface.normal(point.ub, point.vb),
280 ) {
281 (Ok(a), Ok(b)) => a.cross(b).length() >= TRANSVERSE_START_CROSS,
282 _ => false,
283 }
284}
285
286fn tangent_at(
287 first: SurfaceInfo<'_>,
288 second: SurfaceInfo<'_>,
289 point: IntersectionPoint,
290) -> Result<Option<Vec3>, String> {
291 let first_normal = first.surface.normal(point.ua, point.va).ok();
292 let second_normal = second.surface.normal(point.ub, point.vb).ok();
293 match (first_normal, second_normal) {
294 (Some(a), Some(b)) => {
295 let tangent = a.cross(b);
296 if tangent.length() <= 1e-9 {
297 Ok(None)
298 } else {
299 Ok(Some(tangent.normalized()?))
300 }
301 }
302 _ => Ok(None),
303 }
304}
305
306fn polish_boundary(
307 first: SurfaceInfo<'_>,
308 second: SurfaceInfo<'_>,
309 mut point: IntersectionPoint,
310 tolerance: f64,
311) -> Result<Option<IntersectionPoint>, String> {
312 let free = [
313 first.closed_u || (point.ua > first.u0 && point.ua < first.u1),
314 first.closed_v || (point.va > first.v0 && point.va < first.v1),
315 second.closed_u || (point.ub > second.u0 && point.ub < second.u1),
316 second.closed_v || (point.vb > second.v0 && point.vb < second.v1),
317 ];
318 for _ in 0..40 {
319 let da = first.surface.derivatives(point.ua, point.va, 1)?;
320 let db = second.surface.derivatives(point.ub, point.vb, 1)?;
321 let residual = da[0][0].sub(db[0][0]);
322 if residual.length() <= tolerance {
323 point.point = da[0][0].add(db[0][0]).scale(0.5);
324 return Ok(Some(point));
325 }
326 let all_columns = [
327 da[1][0],
328 da[0][1],
329 db[1][0].scale(-1.0),
330 db[0][1].scale(-1.0),
331 ];
332 let indices: Vec<usize> = (0..4).filter(|index| free[*index]).collect();
333 if indices.is_empty() {
334 return Ok(None);
335 }
336 let mut matrix = [[0.0f64; 4]; 4];
337 let mut rhs = [0.0f64; 4];
338 for (row, &r) in indices.iter().enumerate() {
339 rhs[row] = -all_columns[r].dot(residual);
340 for (column, &c) in indices.iter().enumerate() {
341 matrix[row][column] = all_columns[r].dot(all_columns[c]);
342 }
343 }
344 let changes = match solve_small(matrix, rhs, indices.len()) {
345 Ok(value) => value,
346 Err(_) => return Ok(None),
347 };
348 let mut update = [0.0; 4];
349 for (index, ¶meter_index) in indices.iter().enumerate() {
350 update[parameter_index] = changes[index];
351 }
352 point.ua = fit(point.ua + update[0], first.u0, first.u1, first.closed_u);
353 point.va = fit(point.va + update[1], first.v0, first.v1, first.closed_v);
354 point.ub = fit(point.ub + update[2], second.u0, second.u1, second.closed_u);
355 point.vb = fit(point.vb + update[3], second.v0, second.v1, second.closed_v);
356 }
357 Ok(None)
358}
359
360fn correct(
361 first: SurfaceInfo<'_>,
362 second: SurfaceInfo<'_>,
363 from: IntersectionPoint,
364 predicted: Vec3,
365 tangent: Vec3,
366 tolerance: f64,
367) -> Result<Option<(IntersectionPoint, bool)>, String> {
368 let mut point = from;
369 let mut boundary = false;
370 for _ in 0..30 {
371 let da = first.surface.derivatives(point.ua, point.va, 1)?;
372 let db = second.surface.derivatives(point.ub, point.vb, 1)?;
373 let residual = da[0][0].sub(db[0][0]);
374 let plane_residual = da[0][0].sub(predicted).dot(tangent);
375 if residual.length() <= tolerance * 0.01 + EPSILON
376 && plane_residual.abs() <= tolerance * 0.01 + EPSILON
377 {
378 point.point = da[0][0].add(db[0][0]).scale(0.5);
379 return Ok(Some((point, boundary)));
380 }
381 let matrix = [
382 [da[1][0].x, da[0][1].x, -db[1][0].x, -db[0][1].x],
383 [da[1][0].y, da[0][1].y, -db[1][0].y, -db[0][1].y],
384 [da[1][0].z, da[0][1].z, -db[1][0].z, -db[0][1].z],
385 [da[1][0].dot(tangent), da[0][1].dot(tangent), 0.0, 0.0],
386 ];
387 let changes = match solve_small(
388 matrix,
389 [-residual.x, -residual.y, -residual.z, -plane_residual],
390 4,
391 ) {
392 Ok(value) => value,
393 Err(_) => return Ok(None),
394 };
395 let scale = [
396 changes[0].abs() / ((first.u1 - first.u0) / 4.0),
397 changes[1].abs() / ((first.v1 - first.v0) / 4.0),
398 changes[2].abs() / ((second.u1 - second.u0) / 4.0),
399 changes[3].abs() / ((second.v1 - second.v0) / 4.0),
400 1.0,
401 ]
402 .into_iter()
403 .fold(1.0_f64, f64::max);
404 let raw = [
405 point.ua + changes[0] / scale,
406 point.va + changes[1] / scale,
407 point.ub + changes[2] / scale,
408 point.vb + changes[3] / scale,
409 ];
410 let domains = [
411 (first.u0, first.u1, first.closed_u),
412 (first.v0, first.v1, first.closed_v),
413 (second.u0, second.u1, second.closed_u),
414 (second.v0, second.v1, second.closed_v),
415 ];
416 boundary = false;
417 let mut next = [0.0; 4];
418 for index in 0..4 {
419 next[index] = fit(
420 raw[index],
421 domains[index].0,
422 domains[index].1,
423 domains[index].2,
424 );
425 if !domains[index].2
426 && (next[index] == domains[index].0 || next[index] == domains[index].1)
427 && raw[index] != next[index]
428 {
429 boundary = true;
430 }
431 }
432 let old = [point.ua, point.va, point.ub, point.vb];
433 let spans = [
434 first.u1 - first.u0,
435 first.v1 - first.v0,
436 second.u1 - second.u0,
437 second.v1 - second.v0,
438 ];
439 let stalled = (0..4).all(|index| (next[index] - old[index]).abs() <= 1e-14 * spans[index]);
440 point.ua = next[0];
441 point.va = next[1];
442 point.ub = next[2];
443 point.vb = next[3];
444 if stalled {
445 if boundary {
446 if let Some(polished) = polish_boundary(first, second, point, tolerance)? {
447 return Ok(Some((polished, true)));
448 }
449 }
450 return Ok(None);
451 }
452 }
453 Ok(None)
454}
455
456struct Trace {
457 points: Vec<IntersectionPoint>,
458 closed: bool,
459}
460
461fn vector_angle(a: Vec3, b: Vec3) -> f64 {
462 (a.dot(b) / (a.length() * b.length()))
463 .clamp(-1.0, 1.0)
464 .acos()
465}
466
467fn normal_turn_step(
476 first: SurfaceInfo<'_>,
477 second: SurfaceInfo<'_>,
478 point: IntersectionPoint,
479 tangent: Vec3,
480 budget: f64,
481) -> Option<f64> {
482 let mut best: Option<f64> = None;
483 for (surface, u, v) in [
484 (first.surface, point.ua, point.va),
485 (second.surface, point.ub, point.vb),
486 ] {
487 let derivatives = surface.derivatives(u, v, 2).ok()?;
488 let normal = derivatives[1][0].cross(derivatives[0][1]);
489 let normal_length = normal.length();
490 if normal_length <= 1e-12 {
491 continue;
492 }
493 let unit_normal = normal.scale(1.0 / normal_length);
494 for (first_derivative, second_derivative) in [
495 (derivatives[1][0], derivatives[2][0]),
496 (derivatives[0][1], derivatives[0][2]),
497 ] {
498 let metric = first_derivative.dot(first_derivative);
499 if metric <= 1e-16 {
500 continue;
501 }
502 let curvature = second_derivative.dot(unit_normal).abs();
503 if curvature <= 1e-12 {
504 continue; }
506 let advance = budget * metric / curvature;
507 let alignment = first_derivative
508 .scale(1.0 / metric.sqrt())
509 .dot(tangent)
510 .abs();
511 if alignment <= 0.1 {
512 continue; }
514 let along = advance * alignment;
515 if best.map(|value| along < value).unwrap_or(true) {
516 best = Some(along);
517 }
518 }
519 }
520 best
521}
522
523fn trace(
524 first: SurfaceInfo<'_>,
525 second: SurfaceInfo<'_>,
526 start: IntersectionPoint,
527 direction: f64,
528 tolerance: f64,
529 initial_step: f64,
530 minimum_step: f64,
531 maximum_step: f64,
532 maximum_steps: usize,
533) -> Result<Trace, String> {
534 let mut points = vec![start];
535 let mut current = start;
536 let Some(mut tangent) = tangent_at(first, second, start)? else {
537 return Ok(Trace {
538 points,
539 closed: false,
540 });
541 };
542 tangent = tangent.scale(direction);
543 let mut step_size = initial_step.min(maximum_step);
544 let mut terminated = false;
545 for _ in 0..maximum_steps {
546 let mut accepted = None;
547 while step_size >= minimum_step {
548 let predicted = current.point.add(tangent.scale(step_size));
549 if let Some((corrected, boundary)) =
550 correct(first, second, current, predicted, tangent, tolerance)?
551 {
552 if corrected.point.sub(current.point).length() <= 2.5 * step_size {
553 accepted = Some((corrected, boundary));
554 break;
555 }
556 }
557 step_size /= 2.0;
558 }
559 let Some((candidate, boundary)) = accepted else {
560 terminated = true;
561 break;
562 };
563 points.push(candidate);
564 if boundary {
565 terminated = true;
566 break;
567 }
568 let Some(mut next_tangent) = tangent_at(first, second, candidate)? else {
569 terminated = true;
570 break;
571 };
572 if next_tangent.dot(tangent) < 0.0 {
573 next_tangent = next_tangent.scale(-1.0);
574 }
575 let angle = vector_angle(tangent, next_tangent);
576 if angle > 0.35 {
577 points.pop();
578 step_size = minimum_step.max(step_size / 2.0);
579 if step_size <= minimum_step * 1.01 {
580 points.push(candidate);
581 } else {
582 continue;
583 }
584 }
585 let reactive = if angle < 0.03 {
590 step_size * 1.5
591 } else if angle > 0.15 {
592 step_size * 0.6
593 } else {
594 step_size
595 };
596 step_size = match normal_turn_step(first, second, candidate, next_tangent, 0.1) {
597 Some(budget) => reactive.min(budget),
598 None => reactive,
599 }
600 .clamp(minimum_step, maximum_step);
601 if points.len() > 4 && candidate.point.sub(start.point).length() <= step_size * 1.2 {
602 let to_start = start.point.sub(candidate.point);
603 if to_start.length() <= EPSILON || to_start.dot(next_tangent) >= 0.0 {
604 points.push(start);
605 return Ok(Trace {
606 points,
607 closed: true,
608 });
609 }
610 }
611 current = candidate;
612 tangent = next_tangent;
613 }
614 if !terminated {
615 return Err(format!(
619 "surface intersection trace exhausted {maximum_steps} steps \
620 ({} points, step {step_size:.3e}) without reaching a boundary or closure",
621 points.len()
622 ));
623 }
624 Ok(Trace {
625 points,
626 closed: false,
627 })
628}
629
630fn point_segment_distance(point: Vec3, start: Vec3, end: Vec3) -> f64 {
631 let segment = end.sub(start);
632 let length_squared = segment.length_squared();
633 if length_squared <= EPSILON {
634 return point.sub(start).length();
635 }
636 let parameter = point.sub(start).dot(segment) / length_squared;
637 let parameter = parameter.clamp(0.0, 1.0);
638 point.sub(start.add(segment.scale(parameter))).length()
639}
640
641#[derive(Clone, Debug, Serialize)]
642pub struct SurfaceIntersectionCurve {
643 pub points: Vec<Vec3>,
644 pub params_a: Vec<[f64; 2]>,
645 pub params_b: Vec<[f64; 2]>,
646 pub closed: bool,
647}
648
649#[derive(Clone, Debug, Deserialize)]
650pub struct SurfaceIntersectionOptions {
651 #[serde(default = "default_tolerance")]
652 pub tolerance: f64,
653 #[serde(default = "default_density")]
654 pub seed_density: f64,
655 #[serde(default = "default_maximum_steps")]
656 pub maximum_steps: usize,
657 pub maximum_step: Option<f64>,
658 #[serde(default)]
659 pub seed_points: Vec<Vec3>,
660 #[serde(default)]
661 pub seed_only: bool,
662 pub seed_gate: Option<f64>,
663}
664
665fn default_tolerance() -> f64 {
666 LINEAR_TOLERANCE
667}
668fn default_density() -> f64 {
669 1.0
670}
671fn default_maximum_steps() -> usize {
672 4000
673}
674
675impl Default for SurfaceIntersectionOptions {
676 fn default() -> Self {
677 Self {
678 tolerance: default_tolerance(),
679 seed_density: default_density(),
680 maximum_steps: default_maximum_steps(),
681 maximum_step: None,
682 seed_points: Vec::new(),
683 seed_only: false,
684 seed_gate: None,
685 }
686 }
687}
688
689pub fn intersect_surfaces(
690 first_surface: &NurbsSurface,
691 second_surface: &NurbsSurface,
692 options: &SurfaceIntersectionOptions,
693) -> Result<Vec<SurfaceIntersectionCurve>, String> {
694 let first_box = Bounds::from_surface(first_surface)?;
695 let second_box = Bounds::from_surface(second_surface)?;
696 if !first_box.intersects(second_box, options.tolerance * 10.0) {
697 return Ok(Vec::new());
698 }
699 let first = surface_info(first_surface)?;
700 let second = surface_info(second_surface)?;
701 let diagonal = first_box.diagonal().min(second_box.diagonal());
702 let initial_step = diagonal / 100.0;
703 let minimum_step = (options.tolerance * 100.0).max(diagonal * 1e-6);
704 let maximum_step = (diagonal / 15.0).min(options.maximum_step.unwrap_or(f64::INFINITY));
705 let mut starts = if options.seed_only {
706 Vec::new()
707 } else {
708 find_start_points(first, second, options.tolerance, options.seed_density)?
709 };
710 starts.retain(|start| transverse_at(first, second, *start));
715 let seed_gate = options.seed_gate.unwrap_or(options.tolerance * 100.0);
716 for seed in &options.seed_points {
717 let projection_a = project_point_to_surface(first_surface, *seed)?;
718 if projection_a.distance > seed_gate {
719 continue;
720 }
721 let projection_b = project_point_to_surface(second_surface, *seed)?;
722 if projection_b.distance > seed_gate {
723 continue;
724 }
725 let Some(refined) = refine_point(
726 first,
727 second,
728 projection_a.u,
729 projection_a.v,
730 projection_b.u,
731 projection_b.v,
732 options.tolerance,
733 )?
734 else {
735 continue;
736 };
737 if !transverse_at(first, second, refined) {
738 continue;
739 }
740 starts.push(refined);
741 }
742
743 let mut curves: Vec<SurfaceIntersectionCurve> = Vec::new();
744 for start in starts {
745 let claimed = curves.iter().any(|curve| {
746 curve.points.windows(2).any(|segment| {
747 point_segment_distance(start.point, segment[0], segment[1]) <= initial_step * 1.5
748 })
749 });
750 if claimed {
751 continue;
752 }
753 let forward = trace(
754 first,
755 second,
756 start,
757 1.0,
758 options.tolerance,
759 initial_step,
760 minimum_step,
761 maximum_step,
762 options.maximum_steps,
763 )?;
764 let (all, closed) = if forward.closed {
765 (forward.points, true)
766 } else {
767 let backward = trace(
768 first,
769 second,
770 start,
771 -1.0,
772 options.tolerance,
773 initial_step,
774 minimum_step,
775 maximum_step,
776 options.maximum_steps,
777 )?;
778 let mut all: Vec<_> = backward.points.into_iter().skip(1).rev().collect();
779 all.extend(forward.points);
780 (all, false)
781 };
782 if all.len() < 2 {
783 continue;
784 }
785 curves.push(SurfaceIntersectionCurve {
786 points: all.iter().map(|point| point.point).collect(),
787 params_a: all.iter().map(|point| [point.ua, point.va]).collect(),
788 params_b: all.iter().map(|point| [point.ub, point.vb]).collect(),
789 closed,
790 });
791 }
792 Ok(curves)
793}
794
795const SUPPLEMENTAL_TRANSVERSE_CROSS: f64 = TRANSVERSE_START_CROSS;
799
800pub fn intersect_surfaces_supplemental(
827 first_surface: &NurbsSurface,
828 second_surface: &NurbsSurface,
829 options: &SurfaceIntersectionOptions,
830) -> Result<Vec<SurfaceIntersectionCurve>, String> {
831 let first_box = Bounds::from_surface(first_surface)?;
832 let second_box = Bounds::from_surface(second_surface)?;
833 if !first_box.intersects(second_box, options.tolerance * 10.0) {
834 return Ok(Vec::new());
835 }
836 let first = surface_info(first_surface)?;
837 let second = surface_info(second_surface)?;
838 let diagonal = first_box.diagonal().min(second_box.diagonal());
839 let initial_step = diagonal / 100.0;
840 let minimum_step = (options.tolerance * 100.0).max(diagonal * 1e-6);
841 let maximum_step = (diagonal / 15.0).min(options.maximum_step.unwrap_or(f64::INFINITY));
842
843 let density = options.seed_density.max(2.0);
848 let mut starts: Vec<IntersectionPoint> = Vec::new();
849 for gridded_second in [false, true] {
850 let (a, b) = if gridded_second {
851 (second, first)
852 } else {
853 (first, second)
854 };
855 for start in find_start_points(a, b, options.tolerance, density)? {
856 let start = if gridded_second {
859 IntersectionPoint {
860 ua: start.ub,
861 va: start.vb,
862 ub: start.ua,
863 vb: start.va,
864 point: start.point,
865 }
866 } else {
867 start
868 };
869 starts.push(start);
870 }
871 }
872 starts.retain(|start| {
876 match (
877 first.surface.normal(start.ua, start.va),
878 second.surface.normal(start.ub, start.vb),
879 ) {
880 (Ok(na), Ok(nb)) => na.cross(nb).length() > SUPPLEMENTAL_TRANSVERSE_CROSS,
881 _ => false,
882 }
883 });
884
885 let mut curves: Vec<SurfaceIntersectionCurve> = Vec::new();
886 for start in starts {
887 let claimed = curves.iter().any(|curve| {
888 curve.points.windows(2).any(|segment| {
889 point_segment_distance(start.point, segment[0], segment[1]) <= initial_step * 1.5
890 })
891 });
892 if claimed {
893 continue;
894 }
895 let Ok(forward) = trace(
899 first,
900 second,
901 start,
902 1.0,
903 options.tolerance,
904 initial_step,
905 minimum_step,
906 maximum_step,
907 options.maximum_steps,
908 ) else {
909 continue;
910 };
911 let (all, closed) = if forward.closed {
912 (forward.points, true)
913 } else {
914 let Ok(backward) = trace(
915 first,
916 second,
917 start,
918 -1.0,
919 options.tolerance,
920 initial_step,
921 minimum_step,
922 maximum_step,
923 options.maximum_steps,
924 ) else {
925 continue;
926 };
927 let mut all: Vec<_> = backward.points.into_iter().skip(1).rev().collect();
928 all.extend(forward.points);
929 (all, false)
930 };
931 if all.len() < 2 {
932 continue;
933 }
934 let transverse = all.iter().step_by((all.len() / 5).max(1)).any(|point| {
938 match (
939 first.surface.normal(point.ua, point.va),
940 second.surface.normal(point.ub, point.vb),
941 ) {
942 (Ok(na), Ok(nb)) => na.cross(nb).length() > SUPPLEMENTAL_TRANSVERSE_CROSS,
943 _ => false,
944 }
945 });
946 if !transverse {
947 continue;
948 }
949 curves.push(SurfaceIntersectionCurve {
950 points: all.iter().map(|point| point.point).collect(),
951 params_a: all.iter().map(|point| [point.ua, point.va]).collect(),
952 params_b: all.iter().map(|point| [point.ub, point.vb]).collect(),
953 closed,
954 });
955 }
956 Ok(curves)
957}
958
959