1use crate::curve::interior_knot_count;
2use crate::fit::solve_small;
3use crate::projection::project_point_to_surface_from_seed;
4use crate::{project_point_to_surface, KnotVector, NurbsSurface, Vec3};
5use serde::{Deserialize, Serialize};
6
7const EPSILON: f64 = crate::tolerance::FP_FLOOR;
8const LINEAR_TOLERANCE: f64 = crate::tolerance::DEFAULT_MODEL;
9
10const TRANSVERSE_START_CROSS: f64 = 1e-2;
25
26pub(crate) const TRANSVERSE_SEED_CROSS: f64 = TRANSVERSE_START_CROSS;
31
32const MARCH_REACH_EXTENTS: f64 = 4.0;
60
61fn step_budget(maximum_steps: usize, maximum_step: f64, diagonal: f64) -> usize {
64 if !(maximum_step > 0.0) || !diagonal.is_finite() {
65 return maximum_steps;
66 }
67 let needed = (MARCH_REACH_EXTENTS * diagonal / maximum_step).ceil();
68 if !needed.is_finite() || needed <= maximum_steps as f64 {
69 return maximum_steps;
70 }
71 needed as usize
72}
73
74use crate::spatial::Aabb as Bounds;
75
76
77#[derive(Clone, Copy)]
78struct SurfaceInfo<'a> {
79 surface: &'a NurbsSurface,
80 u0: f64,
81 u1: f64,
82 v0: f64,
83 v1: f64,
84 closed_u: bool,
85 closed_v: bool,
86}
87
88fn surface_info(surface: &NurbsSurface) -> Result<SurfaceInfo<'_>, String> {
89 let ku = KnotVector::new(surface.knots_u.clone(), surface.degree_u)?;
90 let kv = KnotVector::new(surface.knots_v.clone(), surface.degree_v)?;
91 let [u0, u1] = ku.domain();
92 let [v0, v1] = kv.domain();
93 let mut closed_u = true;
94 let mut closed_v = true;
95 for fraction in [0.17, 0.5, 0.83] {
96 let v = v0 + (v1 - v0) * fraction;
97 if surface
98 .evaluate(u0, v)?
99 .sub(surface.evaluate(u1, v)?)
100 .length()
101 > LINEAR_TOLERANCE * 10.0
102 {
103 closed_u = false;
104 }
105 let u = u0 + (u1 - u0) * fraction;
106 if surface
107 .evaluate(u, v0)?
108 .sub(surface.evaluate(u, v1)?)
109 .length()
110 > LINEAR_TOLERANCE * 10.0
111 {
112 closed_v = false;
113 }
114 }
115 Ok(SurfaceInfo {
116 surface,
117 u0,
118 u1,
119 v0,
120 v1,
121 closed_u,
122 closed_v,
123 })
124}
125
126fn fit(value: f64, minimum: f64, maximum: f64, closed: bool) -> f64 {
127 crate::projection::fit_parameter(value, minimum, maximum, closed)
128}
129
130
131#[derive(Clone, Copy)]
132struct IntersectionPoint {
133 ua: f64,
134 va: f64,
135 ub: f64,
136 vb: f64,
137 point: Vec3,
138}
139
140fn refine_point(
141 first: SurfaceInfo<'_>,
142 second: SurfaceInfo<'_>,
143 mut ua: f64,
144 mut va: f64,
145 mut ub: f64,
146 mut vb: f64,
147 tolerance: f64,
148) -> Result<Option<IntersectionPoint>, String> {
149 for _ in 0..40 {
150 let da = first.surface.derivatives(ua, va, 1)?;
151 let db = second.surface.derivatives(ub, vb, 1)?;
152 let residual = da[0][0].sub(db[0][0]);
153 if residual.length() <= tolerance * 0.01 + EPSILON {
154 return Ok(Some(IntersectionPoint {
155 ua,
156 va,
157 ub,
158 vb,
159 point: da[0][0].add(db[0][0]).scale(0.5),
160 }));
161 }
162 let columns = [
163 da[1][0],
164 da[0][1],
165 db[1][0].scale(-1.0),
166 db[0][1].scale(-1.0),
167 ];
168 let mut matrix = [[0.0f64; 3]; 3];
169 for column in columns {
170 let values = [column.x, column.y, column.z];
171 for row in 0..3 {
172 for col in 0..3 {
173 matrix[row][col] += values[row] * values[col];
174 }
175 }
176 }
177 let lambda = match solve_small(matrix, [-residual.x, -residual.y, -residual.z], 3) {
178 Ok(value) => value,
179 Err(_) => return Ok(None),
180 };
181 let multiplier = Vec3::new(lambda[0], lambda[1], lambda[2]);
182 let mut changes = columns.map(|column| column.dot(multiplier));
183 let domains = [
184 first.u1 - first.u0,
185 first.v1 - first.v0,
186 second.u1 - second.u0,
187 second.v1 - second.v0,
188 ];
189 for index in 0..4 {
190 changes[index] = changes[index].clamp(-domains[index] / 4.0, domains[index] / 4.0);
191 }
192 ua = fit(ua + changes[0], first.u0, first.u1, first.closed_u);
193 va = fit(va + changes[1], first.v0, first.v1, first.closed_v);
194 ub = fit(ub + changes[2], second.u0, second.u1, second.closed_u);
195 vb = fit(vb + changes[3], second.v0, second.v1, second.closed_v);
196 }
197 let a = first.surface.evaluate(ua, va)?;
198 let b = second.surface.evaluate(ub, vb)?;
199 if a.sub(b).length() <= tolerance {
200 Ok(Some(IntersectionPoint {
201 ua,
202 va,
203 ub,
204 vb,
205 point: a.add(b).scale(0.5),
206 }))
207 } else {
208 Ok(None)
209 }
210}
211
212fn find_start_points(
213 first: SurfaceInfo<'_>,
214 second: SurfaceInfo<'_>,
215 tolerance: f64,
216 density: f64,
217) -> Result<Vec<IntersectionPoint>, String> {
218 let grid_count = |info: SurfaceInfo<'_>, direction_u: bool| {
219 let (knots, degree) = if direction_u {
220 (&info.surface.knots_u, info.surface.degree_u)
221 } else {
222 (&info.surface.knots_v, info.surface.degree_v)
223 };
224 (8.0_f64)
225 .max(((interior_knot_count(knots, degree) + 1) * (degree + 1)) as f64 * density)
226 .ceil()
227 .min(24.0) as usize
228 };
229 let nu = grid_count(first, true);
230 let nv = grid_count(first, false);
231 let first_box = Bounds::from_surface_controls(first.surface)?;
232 let second_box = Bounds::from_surface_controls(second.surface)?;
233 let gate = first_box.diagonal().min(second_box.diagonal()) * 0.15 + tolerance;
234 let expanded_second = second_box.expanded((tolerance * 100.0).max(gate));
235 let mut starts = Vec::new();
236 for i in 0..=nu {
237 for j in 0..=nv {
238 let ua = first.u0 + (first.u1 - first.u0) * i as f64 / nu as f64;
239 let va = first.v0 + (first.v1 - first.v0) * j as f64 / nv as f64;
240 let point = first.surface.evaluate(ua, va)?;
241 if !expanded_second.contains(point) {
242 continue;
243 }
244 let projection = project_point_to_surface(second.surface, point)?;
245 if projection.distance > gate {
246 continue;
247 }
248 if let Some(refined) =
249 refine_point(first, second, ua, va, projection.u, projection.v, tolerance)?
250 {
251 starts.push(refined);
252 }
253 }
254 }
255 Ok(starts)
256}
257
258struct SpanLeaf {
261 bounds: Bounds,
262 centre: [f64; 2],
263}
264
265fn span_leaves(info: SurfaceInfo<'_>) -> Result<Vec<SpanLeaf>, String> {
269 let surface = info.surface;
270 let (p, q) = (surface.degree_u, surface.degree_v);
271 let rows = surface.control_points.len();
272 let columns = surface.control_points.first().map_or(0, Vec::len);
273 let spans = |knots: &[f64], degree: usize, count: usize| -> Vec<usize> {
274 (degree..count).filter(|&i| i + 1 < knots.len() && knots[i] < knots[i + 1]).collect()
275 };
276 let (spans_u, spans_v) = (spans(&surface.knots_u, p, rows), spans(&surface.knots_v, q, columns));
277 if spans_u.is_empty() || spans_v.is_empty() {
278 return Ok(vec![SpanLeaf {
279 bounds: Bounds::from_surface_controls(surface)?,
280 centre: [0.5 * (info.u0 + info.u1), 0.5 * (info.v0 + info.v1)],
281 }]);
282 }
283 let mut leaves = Vec::with_capacity(spans_u.len() * spans_v.len());
284 for &i in &spans_u {
285 for &j in &spans_v {
286 let mut minimum = Vec3::new(f64::INFINITY, f64::INFINITY, f64::INFINITY);
287 let mut maximum = Vec3::new(f64::NEG_INFINITY, f64::NEG_INFINITY, f64::NEG_INFINITY);
288 for row in &surface.control_points[i - p..=i] {
289 for control in &row[j - q..=j] {
290 let point = control.point()?;
291 minimum.x = minimum.x.min(point.x);
292 minimum.y = minimum.y.min(point.y);
293 minimum.z = minimum.z.min(point.z);
294 maximum.x = maximum.x.max(point.x);
295 maximum.y = maximum.y.max(point.y);
296 maximum.z = maximum.z.max(point.z);
297 }
298 }
299 leaves.push(SpanLeaf {
300 bounds: Bounds { minimum, maximum },
301 centre: [
302 0.5 * (surface.knots_u[i] + surface.knots_u[i + 1]),
303 0.5 * (surface.knots_v[j] + surface.knots_v[j + 1]),
304 ],
305 });
306 }
307 }
308 Ok(leaves)
309}
310
311const TREE_SEED_PAIR_CAP: usize = 4096;
316
317fn tree_start_points(
324 first: SurfaceInfo<'_>,
325 second: SurfaceInfo<'_>,
326 tolerance: f64,
327) -> Result<Option<(Vec<IntersectionPoint>, usize)>, String> {
328 let first_box = Bounds::from_surface_controls(first.surface)?;
329 let second_box = Bounds::from_surface_controls(second.surface)?;
330 let gate = first_box.diagonal().min(second_box.diagonal()) * 0.15 + tolerance;
331 let grow = (tolerance * 100.0).max(gate);
332 let leaves_a = span_leaves(first)?;
333 let leaves_b = span_leaves(second)?;
334 let mut pairs = Vec::new();
335 for (ia, leaf_a) in leaves_a.iter().enumerate() {
336 for (ib, leaf_b) in leaves_b.iter().enumerate() {
337 if leaf_a.bounds.intersects(leaf_b.bounds, grow) {
338 pairs.push((ia, ib));
339 if pairs.len() > TREE_SEED_PAIR_CAP {
340 return Ok(None);
341 }
342 }
343 }
344 }
345 let mut starts = Vec::new();
346 for (ia, ib) in &pairs {
347 let (leaf_a, leaf_b) = (&leaves_a[*ia], &leaves_b[*ib]);
348 let reach = gate.max(leaf_a.bounds.diagonal().max(leaf_b.bounds.diagonal()));
354 for (from, onto, from_info, onto_info, from_first) in [
357 (leaf_a, leaf_b, first, second, true),
358 (leaf_b, leaf_a, second, first, false),
359 ] {
360 let point = from_info.surface.evaluate(from.centre[0], from.centre[1])?;
361 let projection = project_point_to_surface_from_seed(onto_info.surface, point, onto.centre)?;
362 if projection.distance > reach {
363 continue;
364 }
365 let refined = if from_first {
366 refine_point(first, second, from.centre[0], from.centre[1], projection.u, projection.v, tolerance)?
367 } else {
368 refine_point(first, second, projection.u, projection.v, from.centre[0], from.centre[1], tolerance)?
369 };
370 if let Some(refined) = refined {
371 starts.push(refined);
372 }
373 }
374 }
375 Ok(Some((starts, pairs.len())))
376}
377
378fn transverse_at(
382 first: SurfaceInfo<'_>,
383 second: SurfaceInfo<'_>,
384 point: IntersectionPoint,
385) -> bool {
386 crossing_at(first, second, point) >= TRANSVERSE_START_CROSS
387}
388
389fn crossing_at(
391 first: SurfaceInfo<'_>,
392 second: SurfaceInfo<'_>,
393 point: IntersectionPoint,
394) -> f64 {
395 match (
396 first.surface.normal(point.ua, point.va),
397 second.surface.normal(point.ub, point.vb),
398 ) {
399 (Ok(a), Ok(b)) => a.cross(b).length(),
400 _ => 0.0,
401 }
402}
403
404fn tangent_at(
405 first: SurfaceInfo<'_>,
406 second: SurfaceInfo<'_>,
407 point: IntersectionPoint,
408) -> Result<Option<Vec3>, String> {
409 let first_normal = first.surface.normal(point.ua, point.va).ok();
410 let second_normal = second.surface.normal(point.ub, point.vb).ok();
411 match (first_normal, second_normal) {
412 (Some(a), Some(b)) => {
413 let tangent = a.cross(b);
414 if tangent.length() <= 1e-9 {
415 Ok(None)
416 } else {
417 Ok(Some(tangent.normalized()?))
418 }
419 }
420 _ => Ok(None),
421 }
422}
423
424fn polish_boundary(
425 first: SurfaceInfo<'_>,
426 second: SurfaceInfo<'_>,
427 mut point: IntersectionPoint,
428 tolerance: f64,
429) -> Result<Option<IntersectionPoint>, String> {
430 let free = [
431 first.closed_u || (point.ua > first.u0 && point.ua < first.u1),
432 first.closed_v || (point.va > first.v0 && point.va < first.v1),
433 second.closed_u || (point.ub > second.u0 && point.ub < second.u1),
434 second.closed_v || (point.vb > second.v0 && point.vb < second.v1),
435 ];
436 for _ in 0..40 {
437 let da = first.surface.derivatives(point.ua, point.va, 1)?;
438 let db = second.surface.derivatives(point.ub, point.vb, 1)?;
439 let residual = da[0][0].sub(db[0][0]);
440 if residual.length() <= tolerance {
441 point.point = da[0][0].add(db[0][0]).scale(0.5);
442 return Ok(Some(point));
443 }
444 let all_columns = [
445 da[1][0],
446 da[0][1],
447 db[1][0].scale(-1.0),
448 db[0][1].scale(-1.0),
449 ];
450 let indices: Vec<usize> = (0..4).filter(|index| free[*index]).collect();
451 if indices.is_empty() {
452 return Ok(None);
453 }
454 let mut matrix = [[0.0f64; 4]; 4];
455 let mut rhs = [0.0f64; 4];
456 for (row, &r) in indices.iter().enumerate() {
457 rhs[row] = -all_columns[r].dot(residual);
458 for (column, &c) in indices.iter().enumerate() {
459 matrix[row][column] = all_columns[r].dot(all_columns[c]);
460 }
461 }
462 let changes = match solve_small(matrix, rhs, indices.len()) {
463 Ok(value) => value,
464 Err(_) => return Ok(None),
465 };
466 let mut update = [0.0; 4];
467 for (index, ¶meter_index) in indices.iter().enumerate() {
468 update[parameter_index] = changes[index];
469 }
470 point.ua = fit(point.ua + update[0], first.u0, first.u1, first.closed_u);
471 point.va = fit(point.va + update[1], first.v0, first.v1, first.closed_v);
472 point.ub = fit(point.ub + update[2], second.u0, second.u1, second.closed_u);
473 point.vb = fit(point.vb + update[3], second.v0, second.v1, second.closed_v);
474 }
475 Ok(None)
476}
477
478fn correct(
479 first: SurfaceInfo<'_>,
480 second: SurfaceInfo<'_>,
481 from: IntersectionPoint,
482 predicted: Vec3,
483 tangent: Vec3,
484 tolerance: f64,
485) -> Result<Option<(IntersectionPoint, bool)>, String> {
486 let mut point = from;
487 let mut boundary = false;
488 for _ in 0..30 {
489 let da = first.surface.derivatives(point.ua, point.va, 1)?;
490 let db = second.surface.derivatives(point.ub, point.vb, 1)?;
491 let residual = da[0][0].sub(db[0][0]);
492 let plane_residual = da[0][0].sub(predicted).dot(tangent);
493 if residual.length() <= tolerance * 0.01 + EPSILON
494 && plane_residual.abs() <= tolerance * 0.01 + EPSILON
495 {
496 point.point = da[0][0].add(db[0][0]).scale(0.5);
497 return Ok(Some((point, boundary)));
498 }
499 let matrix = [
500 [da[1][0].x, da[0][1].x, -db[1][0].x, -db[0][1].x],
501 [da[1][0].y, da[0][1].y, -db[1][0].y, -db[0][1].y],
502 [da[1][0].z, da[0][1].z, -db[1][0].z, -db[0][1].z],
503 [da[1][0].dot(tangent), da[0][1].dot(tangent), 0.0, 0.0],
504 ];
505 let changes = match solve_small(
506 matrix,
507 [-residual.x, -residual.y, -residual.z, -plane_residual],
508 4,
509 ) {
510 Ok(value) => value,
511 Err(_) => return Ok(None),
512 };
513 let scale = [
514 changes[0].abs() / ((first.u1 - first.u0) / 4.0),
515 changes[1].abs() / ((first.v1 - first.v0) / 4.0),
516 changes[2].abs() / ((second.u1 - second.u0) / 4.0),
517 changes[3].abs() / ((second.v1 - second.v0) / 4.0),
518 1.0,
519 ]
520 .into_iter()
521 .fold(1.0_f64, f64::max);
522 let raw = [
523 point.ua + changes[0] / scale,
524 point.va + changes[1] / scale,
525 point.ub + changes[2] / scale,
526 point.vb + changes[3] / scale,
527 ];
528 let domains = [
529 (first.u0, first.u1, first.closed_u),
530 (first.v0, first.v1, first.closed_v),
531 (second.u0, second.u1, second.closed_u),
532 (second.v0, second.v1, second.closed_v),
533 ];
534 boundary = false;
535 let mut next = [0.0; 4];
536 for index in 0..4 {
537 next[index] = fit(
538 raw[index],
539 domains[index].0,
540 domains[index].1,
541 domains[index].2,
542 );
543 if !domains[index].2
544 && (next[index] == domains[index].0 || next[index] == domains[index].1)
545 && raw[index] != next[index]
546 {
547 boundary = true;
548 }
549 }
550 let old = [point.ua, point.va, point.ub, point.vb];
551 let spans = [
552 first.u1 - first.u0,
553 first.v1 - first.v0,
554 second.u1 - second.u0,
555 second.v1 - second.v0,
556 ];
557 let stalled = (0..4).all(|index| (next[index] - old[index]).abs() <= 1e-14 * spans[index]);
558 point.ua = next[0];
559 point.va = next[1];
560 point.ub = next[2];
561 point.vb = next[3];
562 if stalled {
563 if boundary {
564 if let Some(polished) = polish_boundary(first, second, point, tolerance)? {
565 return Ok(Some((polished, true)));
566 }
567 }
568 return Ok(None);
569 }
570 }
571 Ok(None)
572}
573
574struct Trace {
575 points: Vec<IntersectionPoint>,
576 tangents: Vec<Vec3>,
578 closed: bool,
579 deviation: f64,
581}
582
583struct Spliced {
586 points: Vec<IntersectionPoint>,
587 tangents: Vec<Vec3>,
588 deviation: f64,
589}
590
591impl From<Trace> for Spliced {
592 fn from(trace: Trace) -> Self {
593 Self {
594 points: trace.points,
595 tangents: trace.tangents,
596 deviation: trace.deviation,
597 }
598 }
599}
600
601fn splice(backward: Trace, forward: Trace) -> Spliced {
604 let mut points: Vec<_> = backward.points.into_iter().skip(1).rev().collect();
605 let mut tangents: Vec<_> = backward
606 .tangents
607 .into_iter()
608 .skip(1)
609 .rev()
610 .map(|tangent| tangent.scale(-1.0))
611 .collect();
612 points.extend(forward.points);
613 tangents.extend(forward.tangents);
614 Spliced {
615 points,
616 tangents,
617 deviation: backward.deviation.max(forward.deviation),
618 }
619}
620
621fn curve_from(all: Spliced, closed: bool, measured: bool) -> SurfaceIntersectionCurve {
624 let complete = all.tangents.len() == all.points.len();
625 SurfaceIntersectionCurve {
626 points: all.points.iter().map(|point| point.point).collect(),
627 params_a: all.points.iter().map(|point| [point.ua, point.va]).collect(),
628 params_b: all.points.iter().map(|point| [point.ub, point.vb]).collect(),
629 closed,
630 tangents: (complete && (measured || (crate::imprint::carried_uv_enabled() && all.points.len() >= crate::imprint::carried_uv_min_stations()))).then_some(all.tangents),
631 span_deviation: (complete && measured).then_some(all.deviation),
632 tangent: false,
633 }
634}
635
636fn hermite_at(p0: Vec3, d0: Vec3, p1: Vec3, d1: Vec3, s: f64) -> Vec3 {
639 let s2 = s * s;
640 let s3 = s2 * s;
641 p0.scale(2.0 * s3 - 3.0 * s2 + 1.0)
642 .add(d0.scale(s3 - 2.0 * s2 + s))
643 .add(p1.scale(-2.0 * s3 + 3.0 * s2))
644 .add(d1.scale(s3 - s2))
645}
646
647struct ParameterHermite {
653 p0: [f64; 2],
654 d0: [f64; 2],
655 p1: [f64; 2],
656 d1: [f64; 2],
657}
658
659impl ParameterHermite {
660 fn new(
661 info: SurfaceInfo<'_>,
662 p0: [f64; 2],
663 t0: Vec3,
664 p1: [f64; 2],
665 t1: Vec3,
666 chord: f64,
667 ) -> Result<Self, String> {
668 let image = |uv: [f64; 2], tangent: Vec3| -> Result<[f64; 2], String> {
669 let d = info.surface.derivatives(uv[0], uv[1], 1)?;
670 let (su, sv) = (d[1][0], d[0][1]);
671 let (e, f, g) = (su.dot(su), su.dot(sv), sv.dot(sv));
672 let (bu, bv) = (su.dot(tangent), sv.dot(tangent));
673 let det = e * g - f * f;
674 if det.abs() <= EPSILON * (1.0 + e * g) {
675 return Ok([0.0, 0.0]);
676 }
677 Ok([
678 (g * bu - f * bv) / det * chord,
679 (e * bv - f * bu) / det * chord,
680 ])
681 };
682 let mut p1 = p1;
683 let unwrap = |value: f64, near: f64, closed: bool, minimum: f64, maximum: f64| {
684 let period = maximum - minimum;
685 if closed && period > 0.0 && (value - near).abs() > 0.5 * period {
686 value - period * ((value - near) / period).round()
687 } else {
688 value
689 }
690 };
691 p1[0] = unwrap(p1[0], p0[0], info.closed_u, info.u0, info.u1);
692 p1[1] = unwrap(p1[1], p0[1], info.closed_v, info.v0, info.v1);
693 Ok(Self {
694 p0,
695 d0: image(p0, t0)?,
696 p1,
697 d1: image([fit(p1[0], info.u0, info.u1, info.closed_u), fit(p1[1], info.v0, info.v1, info.closed_v)], t1)?,
698 })
699 }
700
701 fn at(&self, s: f64) -> [f64; 2] {
702 let s2 = s * s;
703 let s3 = s2 * s;
704 let (h00, h10, h01, h11) = (
705 2.0 * s3 - 3.0 * s2 + 1.0,
706 s3 - 2.0 * s2 + s,
707 -2.0 * s3 + 3.0 * s2,
708 s3 - s2,
709 );
710 [
711 h00 * self.p0[0] + h10 * self.d0[0] + h01 * self.p1[0] + h11 * self.d1[0],
712 h00 * self.p0[1] + h10 * self.d0[1] + h01 * self.p1[1] + h11 * self.d1[1],
713 ]
714 }
715}
716
717fn span_deviation(
721 first: SurfaceInfo<'_>,
722 second: SurfaceInfo<'_>,
723 from: IntersectionPoint,
724 from_tangent: Vec3,
725 to: IntersectionPoint,
726 to_tangent: Vec3,
727) -> Result<f64, String> {
728 let chord = to.point.sub(from.point).length();
729 if chord <= EPSILON {
730 return Ok(0.0);
731 }
732 let on_a = ParameterHermite::new(first, [from.ua, from.va], from_tangent, [to.ua, to.va], to_tangent, chord)?;
733 let on_b = ParameterHermite::new(second, [from.ub, from.vb], from_tangent, [to.ub, to.vb], to_tangent, chord)?;
734 let (d0, d1) = (from_tangent.scale(chord), to_tangent.scale(chord));
735 let mut worst: f64 = 0.0;
736 for s in [0.17, 0.33, 0.5, 0.67, 0.83] {
737 let c = hermite_at(from.point, d0, to.point, d1, s);
738 let [ua, va] = on_a.at(s);
739 let a = first.surface.evaluate(fit(ua, first.u0, first.u1, first.closed_u), fit(va, first.v0, first.v1, first.closed_v))?;
740 let [ub, vb] = on_b.at(s);
741 let b = second.surface.evaluate(fit(ub, second.u0, second.u1, second.closed_u), fit(vb, second.v0, second.v1, second.closed_v))?;
742 worst = worst
743 .max(a.sub(b).length())
744 .max(a.sub(c).length())
745 .max(b.sub(c).length());
746 }
747 Ok(worst)
748}
749
750fn vector_angle(a: Vec3, b: Vec3) -> f64 {
751 (a.dot(b) / (a.length() * b.length()))
752 .clamp(-1.0, 1.0)
753 .acos()
754}
755
756fn normal_turn_step(
765 first: SurfaceInfo<'_>,
766 second: SurfaceInfo<'_>,
767 point: IntersectionPoint,
768 tangent: Vec3,
769 budget: f64,
770) -> Option<f64> {
771 let mut best: Option<f64> = None;
772 for (surface, u, v) in [
773 (first.surface, point.ua, point.va),
774 (second.surface, point.ub, point.vb),
775 ] {
776 let derivatives = surface.derivatives(u, v, 2).ok()?;
777 let normal = derivatives[1][0].cross(derivatives[0][1]);
778 let normal_length = normal.length();
779 if normal_length <= 1e-12 {
780 continue;
781 }
782 let unit_normal = normal.scale(1.0 / normal_length);
783 for (first_derivative, second_derivative) in [
784 (derivatives[1][0], derivatives[2][0]),
785 (derivatives[0][1], derivatives[0][2]),
786 ] {
787 let metric = first_derivative.dot(first_derivative);
788 if metric <= 1e-16 {
789 continue;
790 }
791 let curvature = second_derivative.dot(unit_normal).abs();
792 if curvature <= 1e-12 {
793 continue; }
795 let advance = budget * metric / curvature;
796 let alignment = first_derivative
797 .scale(1.0 / metric.sqrt())
798 .dot(tangent)
799 .abs();
800 if alignment <= 0.1 {
801 continue; }
803 let along = advance * alignment;
804 if best.map(|value| along < value).unwrap_or(true) {
805 best = Some(along);
806 }
807 }
808 }
809 best
810}
811
812#[derive(Clone, Copy)]
815struct TraceSwitches {
816 first_step_ceiling: bool,
819 debug: bool,
821}
822
823impl TraceSwitches {
824 fn from_env() -> Self {
825 Self {
826 first_step_ceiling: std::env::var("BREP_SSI_FIRST_STEP_BUDGET").as_deref() != Ok("0"),
827 debug: std::env::var_os("BREP_DEBUG_SSI_TRACE").is_some(),
828 }
829 }
830}
831
832#[allow(clippy::too_many_arguments)]
833fn trace(
834 first: SurfaceInfo<'_>,
835 second: SurfaceInfo<'_>,
836 start: IntersectionPoint,
837 direction: f64,
838 tolerance: f64,
839 initial_step: f64,
840 minimum_step: f64,
841 maximum_step: f64,
842 maximum_steps: usize,
843 switches: TraceSwitches,
844 span_budget: Option<f64>,
845) -> Result<Trace, String> {
846 let mut points = vec![start];
847 let mut current = start;
848 let Some(mut tangent) = tangent_at(first, second, start)? else {
849 return Ok(Trace {
850 points,
851 tangents: Vec::new(),
852 closed: false,
853 deviation: 0.0,
854 });
855 };
856 tangent = tangent.scale(direction);
857 let mut tangents = vec![tangent];
858 let mut deviation: f64 = 0.0;
859 let mut step_size = initial_step.min(maximum_step);
870 if switches.first_step_ceiling {
871 if let Some(budget) = normal_turn_step(first, second, start, tangent, 0.1) {
872 step_size = step_size.min(budget).clamp(minimum_step, maximum_step);
873 }
874 }
875 let mut terminated = false;
876 for _ in 0..maximum_steps {
877 let mut accepted = None;
878 while step_size >= minimum_step {
879 let predicted = current.point.add(tangent.scale(step_size));
880 if let Some((corrected, boundary)) =
881 correct(first, second, current, predicted, tangent, tolerance)?
882 {
883 if corrected.point.sub(current.point).length() <= 2.5 * step_size {
884 accepted = Some((corrected, boundary));
885 break;
886 }
887 }
888 step_size /= 2.0;
889 }
890 let Some((candidate, boundary)) = accepted else {
891 terminated = true;
892 break;
893 };
894 points.push(candidate);
895 if switches.debug {
896 let turn = tangent_at(first, second, candidate)
900 .ok()
901 .flatten()
902 .map(|next| vector_angle(tangent, if next.dot(tangent) < 0.0 { next.scale(-1.0) } else { next }))
903 .unwrap_or(f64::NAN);
904 let budget = normal_turn_step(first, second, current, tangent, 0.1).unwrap_or(f64::NAN);
905 let at = candidate.point;
906 eprintln!(
907 "SSI-TRACE dir {direction} step {step_size:.4e} jump {:.4e} turn {turn:.4} ceiling {budget:.4e} boundary {boundary} p ({:.6},{:.6},{:.6})",
908 at.sub(current.point).length(),
909 at.x,
910 at.y,
911 at.z
912 );
913 }
914 let next = tangent_at(first, second, candidate)?.map(|next| {
918 if next.dot(tangent) < 0.0 { next.scale(-1.0) } else { next }
919 });
920 if boundary {
921 tangents.push(next.unwrap_or(tangent));
922 terminated = true;
923 break;
924 }
925 let Some(next_tangent) = next else {
926 tangents.push(tangent);
927 terminated = true;
928 break;
929 };
930 let angle = vector_angle(tangent, next_tangent);
931 if angle > 0.35 {
932 points.pop();
933 step_size = minimum_step.max(step_size / 2.0);
934 if step_size <= minimum_step * 1.01 {
935 points.push(candidate);
936 } else {
937 continue;
938 }
939 }
940 let mut span_fine = true;
945 if let Some(budget) = span_budget {
946 let measured = span_deviation(first, second, current, tangent, candidate, next_tangent)?;
947 if measured > budget && step_size > minimum_step * 1.01 {
948 points.pop();
949 step_size = minimum_step.max(step_size / 2.0);
950 continue;
951 }
952 deviation = deviation.max(measured);
953 span_fine = measured < budget / 8.0;
954 }
955 tangents.push(next_tangent);
956 let reactive = if angle < 0.03 && span_fine {
961 step_size * 1.5
962 } else if angle > 0.15 {
963 step_size * 0.6
964 } else {
965 step_size
966 };
967 step_size = match normal_turn_step(first, second, candidate, next_tangent, 0.1) {
968 Some(budget) => reactive.min(budget),
969 None => reactive,
970 }
971 .clamp(minimum_step, maximum_step);
972 if points.len() > 4 && candidate.point.sub(start.point).length() <= step_size * 1.2 {
973 let to_start = start.point.sub(candidate.point);
974 if to_start.length() <= EPSILON || to_start.dot(next_tangent) >= 0.0 {
975 points.push(start);
976 tangents.push(tangents[0]);
977 return Ok(Trace {
978 points,
979 tangents,
980 closed: true,
981 deviation,
982 });
983 }
984 }
985 current = candidate;
986 tangent = next_tangent;
987 }
988 if !terminated {
989 return Err(format!(
993 "surface intersection trace exhausted {maximum_steps} steps \
994 ({} points, step {step_size:.3e}) without reaching a boundary or closure",
995 points.len()
996 ));
997 }
998 Ok(Trace {
999 points,
1000 tangents,
1001 closed: false,
1002 deviation,
1003 })
1004}
1005
1006fn point_segment_distance(point: Vec3, start: Vec3, end: Vec3) -> f64 {
1007 point.distance_to_segment(start, end, EPSILON)
1008}
1009
1010#[derive(Clone, Debug, Serialize)]
1011pub struct SurfaceIntersectionCurve {
1012 pub points: Vec<Vec3>,
1013 pub params_a: Vec<[f64; 2]>,
1014 pub params_b: Vec<[f64; 2]>,
1015 pub closed: bool,
1016 #[serde(skip_serializing_if = "Option::is_none")]
1020 pub tangents: Option<Vec<Vec3>>,
1021 #[serde(skip_serializing_if = "Option::is_none")]
1024 pub span_deviation: Option<f64>,
1025 #[serde(skip_serializing_if = "std::ops::Not::not")]
1030 pub tangent: bool,
1031}
1032
1033#[derive(Clone, Debug, Deserialize)]
1034pub struct SurfaceIntersectionOptions {
1035 #[serde(default = "default_tolerance")]
1036 pub tolerance: f64,
1037 #[serde(default = "default_density")]
1038 pub seed_density: f64,
1039 #[serde(default = "default_maximum_steps")]
1040 pub maximum_steps: usize,
1041 pub maximum_step: Option<f64>,
1042 #[serde(default)]
1043 pub seed_points: Vec<Vec3>,
1044 #[serde(default)]
1045 pub seed_only: bool,
1046 pub seed_gate: Option<f64>,
1047 #[serde(default)]
1064 pub span_budget: Option<f64>,
1065 #[serde(default)]
1067 pub ordered_starts: bool,
1068 #[serde(default)]
1074 pub consume_starts: bool,
1075 #[serde(default)]
1077 pub tree_seeds: bool,
1078 #[serde(default)]
1094 pub seed_start_cross: Option<f64>,
1095}
1096
1097fn default_tolerance() -> f64 {
1098 LINEAR_TOLERANCE
1099}
1100fn default_density() -> f64 {
1101 1.0
1102}
1103fn default_maximum_steps() -> usize {
1104 4000
1105}
1106
1107impl Default for SurfaceIntersectionOptions {
1108 fn default() -> Self {
1109 Self {
1110 tolerance: default_tolerance(),
1111 seed_density: default_density(),
1112 maximum_steps: default_maximum_steps(),
1113 maximum_step: None,
1114 seed_points: Vec::new(),
1115 seed_only: false,
1116 seed_gate: None,
1117 span_budget: None,
1118 ordered_starts: false,
1119 consume_starts: false,
1120 tree_seeds: false,
1121 seed_start_cross: None,
1122 }
1123 }
1124}
1125
1126#[derive(Clone, Copy, Debug)]
1129pub(crate) struct EmissionSample {
1130 pub point: Vec3,
1131 pub uv_a: [f64; 2],
1132 pub uv_b: [f64; 2],
1133 pub tangent: Vec3,
1134}
1135
1136pub(crate) fn refine_emission_samples(
1140 first_surface: &NurbsSurface,
1141 second_surface: &NurbsSurface,
1142 samples: &[EmissionSample],
1143 budget: f64,
1144) -> Result<Option<Vec<EmissionSample>>, String> {
1145 if samples.len() < 2 || !(budget > 0.0 && budget.is_finite()) { return Ok(None); }
1146 let first = surface_info(first_surface)?;
1147 let second = surface_info(second_surface)?;
1148 let point = |s: EmissionSample| IntersectionPoint {
1149 point: s.point, ua: s.uv_a[0], va: s.uv_a[1], ub: s.uv_b[0], vb: s.uv_b[1],
1150 };
1151 for s in samples {
1154 if !(first_surface.evaluate(s.uv_a[0], s.uv_a[1])?.sub(s.point).length() <= budget)
1155 || !(second_surface.evaluate(s.uv_b[0], s.uv_b[1])?.sub(s.point).length() <= budget)
1156 { return Ok(None); }
1157 }
1158 let mut pending: Vec<_> = samples.windows(2).rev().map(|p| (p[0], p[1], 0usize)).collect();
1159 let mut out = vec![samples[0]];
1160 let mut inserted = 0;
1161 while let Some((a, b, depth)) = pending.pop() {
1162 let pa = point(a);
1163 let pb = point(b);
1164 if span_deviation(first, second, pa, a.tangent, pb, b.tangent)? <= budget {
1165 out.push(b);
1166 continue;
1167 }
1168 let chord_vector = b.point.sub(a.point);
1169 let chord = chord_vector.length();
1170 if depth >= 8 || inserted >= 4096 || chord <= 4.0 * crate::KNOT_IDENTITY_TOL
1171 || crossing_at(first, second, pa) < 0.05 || crossing_at(first, second, pb) < 0.05
1172 || a.tangent.dot(b.tangent) < 0.5
1173 || a.tangent.dot(chord_vector) <= 0.5 * chord || b.tangent.dot(chord_vector) <= 0.5 * chord
1174 { return Ok(None); }
1175 let ha = ParameterHermite::new(first, a.uv_a, a.tangent, b.uv_a, b.tangent, chord)?;
1176 let hb = ParameterHermite::new(second, a.uv_b, a.tangent, b.uv_b, b.tangent, chord)?;
1177 let ua = ha.at(0.5);
1178 let ub = hb.at(0.5);
1179 let predicted = hermite_at(a.point, a.tangent.scale(chord), b.point, b.tangent.scale(chord), 0.5);
1180 let direction = a.tangent.add(b.tangent).normalized()?;
1181 let seed = IntersectionPoint {
1182 point: predicted,
1183 ua: fit(ua[0], first.u0, first.u1, first.closed_u),
1184 va: fit(ua[1], first.v0, first.v1, first.closed_v),
1185 ub: fit(ub[0], second.u0, second.u1, second.closed_u),
1186 vb: fit(ub[1], second.v0, second.v1, second.closed_v),
1187 };
1188 let Some((mid, boundary)) = correct(first, second, seed, predicted, direction, budget * 0.1)? else { return Ok(None); };
1189 let along = mid.point.sub(a.point).dot(chord_vector) / (chord * chord);
1190 if boundary || mid.point.sub(predicted).length() > 0.25 * chord
1191 || !(0.2..=0.8).contains(&along) || crossing_at(first, second, mid) < 0.05
1192 { return Ok(None); }
1193 let Some(mut tangent) = tangent_at(first, second, mid)? else { return Ok(None); };
1194 if tangent.dot(direction) < 0.0 { tangent = tangent.scale(-1.0); }
1195 if tangent.dot(a.tangent) < 0.5 || tangent.dot(b.tangent) < 0.5 { return Ok(None); }
1196 let m = EmissionSample { point: mid.point, uv_a: [mid.ua, mid.va], uv_b: [mid.ub, mid.vb], tangent };
1197 inserted += 1;
1198 pending.push((m, b, depth + 1));
1199 pending.push((a, m, depth + 1));
1200 }
1201 Ok(Some(out))
1202}
1203
1204pub(crate) fn refine_to_intersection(
1213 first_surface: &NurbsSurface,
1214 second_surface: &NurbsSurface,
1215 seed: Vec3,
1216 tolerance: f64,
1217 reach: f64,
1218) -> Result<Option<Vec3>, String> {
1219 let first = surface_info(first_surface)?;
1220 let second = surface_info(second_surface)?;
1221 let on_first = project_point_to_surface(first_surface, seed)?;
1222 let on_second = project_point_to_surface(second_surface, seed)?;
1223 let Some(refined) = refine_point(
1224 first,
1225 second,
1226 on_first.u,
1227 on_first.v,
1228 on_second.u,
1229 on_second.v,
1230 tolerance,
1231 )?
1232 else {
1233 return Ok(None);
1234 };
1235 if refined.point.sub(seed).length() > reach {
1236 return Ok(None);
1237 }
1238 Ok(Some(refined.point))
1239}
1240
1241pub fn intersect_surfaces(
1242 first_surface: &NurbsSurface,
1243 second_surface: &NurbsSurface,
1244 options: &SurfaceIntersectionOptions,
1245) -> Result<Vec<SurfaceIntersectionCurve>, String> {
1246 let _wall = crate::wall_profile::Scope::enter(crate::wall_profile::MARCH);
1247 let _lap = crate::projection_profile::Lap::enter_pair(first_surface, second_surface);
1248 let first_box = Bounds::from_surface_controls(first_surface)?;
1249 let second_box = Bounds::from_surface_controls(second_surface)?;
1250 if !first_box.intersects(second_box, options.tolerance * 10.0) {
1251 return Ok(Vec::new());
1252 }
1253 let first = surface_info(first_surface)?;
1254 let second = surface_info(second_surface)?;
1255 let diagonal = first_box.diagonal().min(second_box.diagonal());
1256 let initial_step = diagonal / 100.0;
1257 let minimum_step = (options.tolerance * 100.0).max(diagonal * 1e-6);
1258 let maximum_step = (diagonal / 15.0).min(options.maximum_step.unwrap_or(f64::INFINITY));
1259 let maximum_steps = step_budget(options.maximum_steps, maximum_step, diagonal);
1260 let mut starts = if options.seed_only {
1261 Vec::new()
1262 } else if options.tree_seeds {
1263 match tree_start_points(first, second, options.tolerance)? {
1266 Some((starts, _pairs)) => starts,
1267 None => find_start_points(first, second, options.tolerance, options.seed_density)?,
1268 }
1269 } else {
1270 find_start_points(first, second, options.tolerance, options.seed_density)?
1271 };
1272 starts.retain(|start| transverse_at(first, second, *start));
1277 let seed_gate = options.seed_gate.unwrap_or(options.tolerance * 100.0);
1278 let grid_count = starts.len();
1280 for seed in &options.seed_points {
1281 let projection_a = project_point_to_surface(first_surface, *seed)?;
1282 if projection_a.distance > seed_gate {
1283 continue;
1284 }
1285 let projection_b = project_point_to_surface(second_surface, *seed)?;
1286 if projection_b.distance > seed_gate {
1287 continue;
1288 }
1289 let Some(refined) = refine_point(
1290 first,
1291 second,
1292 projection_a.u,
1293 projection_a.v,
1294 projection_b.u,
1295 projection_b.v,
1296 options.tolerance,
1297 )?
1298 else {
1299 continue;
1300 };
1301 let admitted = match options.seed_start_cross {
1304 Some(bound) => crossing_at(first, second, refined) >= bound.min(TRANSVERSE_START_CROSS),
1305 None => transverse_at(first, second, refined),
1306 };
1307 if !admitted {
1308 continue;
1309 }
1310 starts.push(refined);
1311 }
1312 if options.ordered_starts {
1313 let crossing = |start: &IntersectionPoint| -> f64 {
1317 match (
1318 first.surface.normal(start.ua, start.va),
1319 second.surface.normal(start.ub, start.vb),
1320 ) {
1321 (Ok(a), Ok(b)) => a.cross(b).length(),
1322 _ => 0.0,
1323 }
1324 };
1325 let mut seeds = starts.split_off(grid_count);
1326 seeds.sort_by(|a, b| crossing(b).total_cmp(&crossing(a)));
1327 starts.sort_by(|a, b| crossing(b).total_cmp(&crossing(a)));
1328 seeds.append(&mut starts);
1329 starts = seeds;
1330 }
1331
1332 let switches = TraceSwitches::from_env();
1333 let mut curves: Vec<SurfaceIntersectionCurve> = Vec::new();
1334 for start in starts {
1335 let mut claimed = false;
1336 for curve in &mut curves {
1337 let Some((index, distance)) = curve
1338 .points
1339 .windows(2)
1340 .enumerate()
1341 .map(|(index, segment)| (index, point_segment_distance(start.point, segment[0], segment[1])))
1342 .filter(|(_, distance)| *distance <= initial_step * 1.5)
1343 .min_by(|a, b| a.1.total_cmp(&b.1))
1344 else {
1345 continue;
1346 };
1347 claimed = true;
1348 if options.consume_starts
1353 && distance > 0.0
1354 && start.point.sub(curve.points[index]).length() > minimum_step
1355 && start.point.sub(curve.points[index + 1]).length() > minimum_step
1356 {
1357 curve.points.insert(index + 1, start.point);
1358 if let Some(tangents) = curve.tangents.as_mut() {
1359 let along = curve.points[index + 2].sub(curve.points[index]);
1360 let tangent = match tangent_at(first, second, start)? {
1361 Some(tangent) if tangent.dot(along) < 0.0 => tangent.scale(-1.0),
1362 Some(tangent) => tangent,
1363 None => tangents[index],
1364 };
1365 tangents.insert(index + 1, tangent);
1366 }
1367 if curve.params_a.len() > index + 1 && curve.params_b.len() > index + 1 {
1368 curve.params_a.insert(index + 1, [start.ua, start.va]);
1369 curve.params_b.insert(index + 1, [start.ub, start.vb]);
1370 }
1371 }
1372 break;
1373 }
1374 if switches.debug {
1375 let at = start.point;
1376 eprintln!(
1377 "SSI-START claimed {claimed} p ({:.6},{:.6},{:.6}) initial {initial_step:.4e} minimum {minimum_step:.4e} maximum {maximum_step:.4e}",
1378 at.x, at.y, at.z
1379 );
1380 }
1381 if claimed {
1382 continue;
1383 }
1384 let forward = trace(
1385 first,
1386 second,
1387 start,
1388 1.0,
1389 options.tolerance,
1390 initial_step,
1391 minimum_step,
1392 maximum_step,
1393 maximum_steps,
1394 switches,
1395 options.span_budget,
1396 )?;
1397 let (all, closed) = if forward.closed {
1398 (Spliced::from(forward), true)
1399 } else {
1400 let backward = trace(
1401 first,
1402 second,
1403 start,
1404 -1.0,
1405 options.tolerance,
1406 initial_step,
1407 minimum_step,
1408 maximum_step,
1409 maximum_steps,
1410 switches,
1411 options.span_budget,
1412 )?;
1413 (splice(backward, forward), false)
1414 };
1415 if all.points.len() < 2 {
1416 continue;
1417 }
1418 curves.push(curve_from(all, closed, options.span_budget.is_some()));
1419 }
1420 Ok(curves)
1421}
1422
1423const SUPPLEMENTAL_TRANSVERSE_CROSS: f64 = TRANSVERSE_START_CROSS;
1427
1428pub fn intersect_surfaces_supplemental(
1455 first_surface: &NurbsSurface,
1456 second_surface: &NurbsSurface,
1457 options: &SurfaceIntersectionOptions,
1458) -> Result<Vec<SurfaceIntersectionCurve>, String> {
1459 let _wall = crate::wall_profile::Scope::enter(crate::wall_profile::MARCH);
1460 let first_box = Bounds::from_surface_controls(first_surface)?;
1461 let second_box = Bounds::from_surface_controls(second_surface)?;
1462 if !first_box.intersects(second_box, options.tolerance * 10.0) {
1463 return Ok(Vec::new());
1464 }
1465 let first = surface_info(first_surface)?;
1466 let second = surface_info(second_surface)?;
1467 let diagonal = first_box.diagonal().min(second_box.diagonal());
1468 let initial_step = diagonal / 100.0;
1469 let minimum_step = (options.tolerance * 100.0).max(diagonal * 1e-6);
1470 let maximum_step = (diagonal / 15.0).min(options.maximum_step.unwrap_or(f64::INFINITY));
1471 let maximum_steps = step_budget(options.maximum_steps, maximum_step, diagonal);
1472
1473 let density = options.seed_density.max(2.0);
1478 let mut starts: Vec<IntersectionPoint> = Vec::new();
1479 for gridded_second in [false, true] {
1480 let (a, b) = if gridded_second {
1481 (second, first)
1482 } else {
1483 (first, second)
1484 };
1485 for start in find_start_points(a, b, options.tolerance, density)? {
1486 let start = if gridded_second {
1489 IntersectionPoint {
1490 ua: start.ub,
1491 va: start.vb,
1492 ub: start.ua,
1493 vb: start.va,
1494 point: start.point,
1495 }
1496 } else {
1497 start
1498 };
1499 starts.push(start);
1500 }
1501 }
1502 starts.retain(|start| {
1506 match (
1507 first.surface.normal(start.ua, start.va),
1508 second.surface.normal(start.ub, start.vb),
1509 ) {
1510 (Ok(na), Ok(nb)) => na.cross(nb).length() > SUPPLEMENTAL_TRANSVERSE_CROSS,
1511 _ => false,
1512 }
1513 });
1514
1515 let switches = TraceSwitches::from_env();
1516 let mut curves: Vec<SurfaceIntersectionCurve> = Vec::new();
1517 for start in starts {
1518 let claimed = curves.iter().any(|curve| {
1519 curve.points.windows(2).any(|segment| {
1520 point_segment_distance(start.point, segment[0], segment[1]) <= initial_step * 1.5
1521 })
1522 });
1523 if claimed {
1524 continue;
1525 }
1526 let Ok(forward) = trace(
1530 first,
1531 second,
1532 start,
1533 1.0,
1534 options.tolerance,
1535 initial_step,
1536 minimum_step,
1537 maximum_step,
1538 maximum_steps,
1539 switches,
1540 options.span_budget,
1541 ) else {
1542 continue;
1543 };
1544 let (all, closed) = if forward.closed {
1545 (Spliced::from(forward), true)
1546 } else {
1547 let Ok(backward) = trace(
1548 first,
1549 second,
1550 start,
1551 -1.0,
1552 options.tolerance,
1553 initial_step,
1554 minimum_step,
1555 maximum_step,
1556 maximum_steps,
1557 switches,
1558 options.span_budget,
1559 ) else {
1560 continue;
1561 };
1562 (splice(backward, forward), false)
1563 };
1564 if all.points.len() < 2 {
1565 continue;
1566 }
1567 let transverse = all.points.iter().step_by((all.points.len() / 5).max(1)).any(|point| {
1571 match (
1572 first.surface.normal(point.ua, point.va),
1573 second.surface.normal(point.ub, point.vb),
1574 ) {
1575 (Ok(na), Ok(nb)) => na.cross(nb).length() > SUPPLEMENTAL_TRANSVERSE_CROSS,
1576 _ => false,
1577 }
1578 });
1579 if !transverse {
1580 continue;
1581 }
1582 curves.push(curve_from(all, closed, options.span_budget.is_some()));
1583 }
1584 Ok(curves)
1585}
1586