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