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