1use crate::curve::KNOT_IDENTITY_TOL as KNOT_TOLERANCE;
2use crate::{NurbsCurve, Vec3};
3use serde::Serialize;
4
5const EPSILON: f64 = crate::tolerance::FP_FLOOR;
6
7use crate::spatial::Aabb as Bounds;
8
9
10#[derive(Clone, Copy)]
11struct Segment {
12 start: f64,
13 end: f64,
14 bounds: Bounds,
15}
16
17fn interior_knots(curve: &NurbsCurve) -> Vec<f64> {
18 let start = curve.knots[curve.degree];
19 let end = curve.knots[curve.knots.len() - 1 - curve.degree];
20 let mut result = Vec::new();
21 for &knot in &curve.knots {
22 if knot <= start + KNOT_TOLERANCE || knot >= end - KNOT_TOLERANCE {
23 continue;
24 }
25 if result
26 .last()
27 .is_none_or(|previous: &f64| (*previous - knot).abs() > KNOT_TOLERANCE)
28 {
29 result.push(knot);
30 }
31 }
32 result
33}
34
35fn segment_boxes_memoized(curve: &NurbsCurve, count: usize) -> Result<std::rc::Rc<Vec<Segment>>, String> {
42 thread_local! {
43 static SEGMENT_MEMO: std::cell::RefCell<Vec<([u64; 2], std::rc::Rc<Vec<Segment>>)>> = const { std::cell::RefCell::new(Vec::new()) };
44 }
45 let key = {
46 let mut lane = crate::mass_properties::TwoLane::new();
47 crate::mass_properties::hash_curve(&mut lane, curve);
48 lane.usize(count);
49 lane.finish()
50 };
51 let hit = SEGMENT_MEMO.with(|memo| {
52 let mut memo = memo.borrow_mut();
53 let at = memo.iter().position(|(stored, _)| *stored == key)?;
54 if at != 0 {
55 let entry = memo.remove(at);
56 memo.insert(0, entry);
57 }
58 Some(memo[0].1.clone())
59 });
60 if let Some(boxes) = hit {
61 return Ok(boxes);
62 }
63 let boxes = std::rc::Rc::new(segment_boxes(curve, count)?);
64 SEGMENT_MEMO.with(|memo| {
65 let mut memo = memo.borrow_mut();
66 memo.insert(0, (key, boxes.clone()));
67 memo.truncate(8);
68 });
69 Ok(boxes)
70}
71
72pub(crate) fn span_boxes(curve: &NurbsCurve) -> Result<Option<Vec<Bounds>>, String> {
78 if !curve.control_points.iter().all(|control| control.w > 0.0) {
79 return Ok(None);
80 }
81 Ok(Some(segment_boxes(curve, 1)?.into_iter().map(|segment| segment.bounds).collect()))
82}
83
84fn segment_boxes(curve: &NurbsCurve, count: usize) -> Result<Vec<Segment>, String> {
85 if crate::switch!(live "BREP_SEGMENT_BOX_WALK", true) {
86 if let Some(segments) = segment_boxes_walk(curve, count)? {
87 return Ok(segments);
88 }
89 }
90 segment_boxes_split(curve, count)
91}
92
93fn segment_boxes_walk(curve: &NurbsCurve, count: usize) -> Result<Option<Vec<Segment>>, String> {
108 let [start, end] = curve.domain()?;
109 let mut parameters: Vec<f64> = (0..=count)
110 .map(|index| start + (end - start) * index as f64 / count as f64)
111 .collect();
112 parameters.extend(interior_knots(curve));
113 parameters.sort_by(f64::total_cmp);
114 parameters.dedup_by(|a, b| (*a - *b).abs() <= KNOT_TOLERANCE);
115
116 let degree = curve.degree;
117 let (knots, points) = (&curve.knots, &curve.control_points);
118 let mut head_knots: Vec<f64> = Vec::new();
120 let mut head_points: Vec<crate::Vec4> = Vec::new();
121 let (mut tail_knot, mut tail_point) = (0usize, 0usize);
122 let mut segments = Vec::new();
123 let mut rest_start = start;
124 for (index, ¶meter) in parameters.iter().enumerate().skip(1) {
125 if parameter - rest_start <= KNOT_TOLERANCE {
126 continue;
127 }
128 let knot_count = head_knots.len() + knots.len() - tail_knot;
129 let point_count = head_points.len() + points.len() - tail_point;
130 let rest_knot = |at: usize| if at < head_knots.len() { head_knots[at] } else { knots[tail_knot + at - head_knots.len()] };
131 let rest_point = |at: usize| if at < head_points.len() { head_points[at] } else { points[tail_point + at - head_points.len()] };
132 if index == parameters.len() - 1 {
133 let rest = NurbsCurve::new(degree, (0..knot_count).map(rest_knot).collect(), (0..point_count).map(rest_point).collect())?;
134 segments.push(Segment { start: rest_start, end, bounds: Bounds::from_curve_controls(&rest)? });
135 continue;
136 }
137 let (rest_low, rest_high) = (rest_knot(degree), rest_knot(knot_count - 1 - degree));
139 if parameter <= rest_low + KNOT_TOLERANCE || parameter >= rest_high - KNOT_TOLERANCE {
140 return Ok(None);
141 }
142 let mut beyond = degree + 1;
143 while beyond < knot_count && rest_knot(beyond) <= parameter + KNOT_TOLERANCE {
144 beyond += 1;
145 }
146 let window_points = (beyond + degree + 1).max(head_points.len()).min(point_count);
147 let window_knots = window_points + degree + 1;
148 let window_knot_values: Vec<f64> = (0..window_knots).map(rest_knot).collect();
149 let window_point_values: Vec<crate::Vec4> = (0..window_points).map(rest_point).collect();
150 let Some((left_knots, left_points, right_knots, right_points)) =
151 split_window(&window_knot_values, &window_point_values, degree, parameter)
152 else {
153 return Ok(None);
154 };
155 let piece = NurbsCurve::new(degree, left_knots, left_points)?;
156 segments.push(Segment { start: rest_start, end: parameter, bounds: Bounds::from_curve_controls(&piece)? });
157 tail_point += window_points - head_points.len();
158 tail_knot += window_knots - head_knots.len();
159 head_points = right_points;
160 head_knots = right_knots;
161 rest_start = parameter;
162 }
163 Ok(Some(segments))
164}
165
166#[allow(clippy::type_complexity)]
174fn split_window(
175 knots: &[f64],
176 points: &[crate::Vec4],
177 degree: usize,
178 parameter: f64,
179) -> Option<(Vec<f64>, Vec<crate::Vec4>, Vec<f64>, Vec<crate::Vec4>)> {
180 use crate::curve::{knot_clamp, knot_find_span};
181 let parameter = knots.iter().copied().find(|knot| (knot - parameter).abs() <= KNOT_TOLERANCE).unwrap_or(parameter);
182 let multiplicity = knots.iter().filter(|knot| (**knot - parameter).abs() <= KNOT_TOLERANCE).count();
183 let requested = degree.saturating_sub(multiplicity);
184 let inserted = {
186 let at = knot_clamp(knots, degree, parameter);
187 let at = knots.iter().copied().find(|knot| (knot - at).abs() <= KNOT_TOLERANCE).unwrap_or(at);
188 let multiplicity = knots.iter().filter(|knot| (**knot - at).abs() <= KNOT_TOLERANCE).count();
189 let insertion_count = requested.min(degree.saturating_sub(multiplicity));
190 if insertion_count == 0 {
191 (knots.to_vec(), points.to_vec())
192 } else {
193 let span = knot_find_span(knots, degree, at);
194 let last_control = points.len() - 1;
195 let mut refined_knots = Vec::with_capacity(knots.len() + insertion_count);
196 refined_knots.extend_from_slice(&knots[..=span]);
197 refined_knots.extend(std::iter::repeat_n(at, insertion_count));
198 refined_knots.extend_from_slice(&knots[span + 1..]);
199 let mut output = vec![crate::Vec4 { x: 0.0, y: 0.0, z: 0.0, w: 1.0 }; last_control + 1 + insertion_count];
200 output[..=span - degree].copy_from_slice(&points[..=span - degree]);
201 for index in span - multiplicity..=last_control {
202 output[index + insertion_count] = points[index];
203 }
204 let mut affected = vec![crate::Vec4 { x: 0.0, y: 0.0, z: 0.0, w: 1.0 }; degree + 1];
205 affected[..=degree - multiplicity].copy_from_slice(&points[span - degree..=span - multiplicity]);
206 let mut left = 0;
207 for insertion in 1..=insertion_count {
208 left = span - degree + insertion;
209 for index in 0..=degree - insertion - multiplicity {
210 let denominator = knots[index + span + 1] - knots[left + index];
211 let alpha = (at - knots[left + index]) / denominator;
212 affected[index] = affected[index + 1].scale(alpha).add(affected[index].scale(1.0 - alpha));
213 }
214 output[left] = affected[0];
215 output[span + insertion_count - insertion - multiplicity] = affected[degree - insertion - multiplicity];
216 }
217 for index in left + 1..span - multiplicity {
218 output[index] = affected[index - left];
219 }
220 (refined_knots, output)
221 }
222 };
223 let (refined_knots, refined_points) = inserted;
224 let first = refined_knots.iter().position(|knot| (*knot - parameter).abs() <= KNOT_TOLERANCE)?;
225 if first == 0 {
226 return None;
227 }
228 let mut left_knots = refined_knots[..first + degree].to_vec();
229 left_knots.push(parameter);
230 let left_points = refined_points[..first].to_vec();
231 let mut right_knots = vec![parameter; degree + 1];
232 right_knots.extend_from_slice(&refined_knots[first + degree..]);
233 let right_points = refined_points[first - 1..].to_vec();
234 Some((left_knots, left_points, right_knots, right_points))
235}
236
237fn segment_boxes_split(curve: &NurbsCurve, count: usize) -> Result<Vec<Segment>, String> {
238 let [start, end] = curve.domain()?;
239 let mut parameters: Vec<f64> = (0..=count)
240 .map(|index| start + (end - start) * index as f64 / count as f64)
241 .collect();
242 parameters.extend(interior_knots(curve));
243 parameters.sort_by(f64::total_cmp);
244 parameters.dedup_by(|a, b| (*a - *b).abs() <= KNOT_TOLERANCE);
245
246 let mut segments = Vec::new();
247 let mut rest = curve.clone();
248 let mut rest_start = start;
249 for (index, ¶meter) in parameters.iter().enumerate().skip(1) {
250 if parameter - rest_start <= KNOT_TOLERANCE {
251 continue;
252 }
253 if index == parameters.len() - 1 {
254 segments.push(Segment {
255 start: rest_start,
256 end,
257 bounds: Bounds::from_curve_controls(&rest)?,
258 });
259 } else {
260 let (piece, remainder) = rest.split(parameter)?;
261 segments.push(Segment {
262 start: rest_start,
263 end: parameter,
264 bounds: Bounds::from_curve_controls(&piece)?,
265 });
266 rest = remainder;
267 rest_start = parameter;
268 }
269 }
270 Ok(segments)
271}
272
273fn newton_closest_pair(
274 first: &NurbsCurve,
275 second: &NurbsCurve,
276 seed_s: f64,
277 seed_t: f64,
278 curvature: bool,
279) -> Result<[f64; 2], String> {
280 let [s0, s1] = first.domain()?;
281 let [t0, t1] = second.domain()?;
282 let mut s = seed_s;
283 let mut t = seed_t;
284 for _ in 0..40 {
285 let first_derivatives = first.derivatives_small(s, 2)?;
286 let second_derivatives = second.derivatives_small(t, 2)?;
287 let residual = first_derivatives[0].sub(second_derivatives[0]);
288 let f = first_derivatives[1].dot(residual);
289 let g = -second_derivatives[1].dot(residual);
290 let j00 = first_derivatives[1].length_squared()
291 + if curvature { first_derivatives[2].dot(residual) } else { 0.0 };
292 let j01 = -first_derivatives[1].dot(second_derivatives[1]);
293 let j10 = -second_derivatives[1].dot(first_derivatives[1]);
294 let j11 = second_derivatives[1].length_squared()
295 - if curvature { second_derivatives[2].dot(residual) } else { 0.0 };
296 let residual_length = residual.length();
297 let converged_first =
298 f.abs() <= EPSILON + 1e-10 * first_derivatives[1].length() * residual_length.max(1e-30);
299 let converged_second = g.abs()
300 <= EPSILON + 1e-10 * second_derivatives[1].length() * residual_length.max(1e-30);
301 if (converged_first && converged_second) || residual_length <= EPSILON {
302 return Ok([s, t]);
303 }
304 let determinant = j00 * j11 - j01 * j10;
305 if determinant.abs() <= EPSILON {
306 return Ok([s, t]);
307 }
308 let mut ds = (-f * j11 + g * j01) / determinant;
309 let mut dt = (-g * j00 + f * j10) / determinant;
310 ds = ds.clamp(-(s1 - s0) / 4.0, (s1 - s0) / 4.0);
311 dt = dt.clamp(-(t1 - t0) / 4.0, (t1 - t0) / 4.0);
312 let next_s = (s + ds).clamp(s0, s1);
313 let next_t = (t + dt).clamp(t0, t1);
314 if (next_s - s).abs() <= 1e-15 * (s1 - s0) && (next_t - t).abs() <= 1e-15 * (t1 - t0) {
315 return Ok([next_s, next_t]);
316 }
317 s = next_s;
318 t = next_t;
319 }
320 Ok([s, t])
321}
322
323fn parameter_tolerance(curve: &NurbsCurve, parameter: f64, tolerance: f64) -> Result<f64, String> {
324 let speed = curve.deriv1(parameter)?.1.length();
325 let [start, end] = curve.domain()?;
326 if speed <= EPSILON {
327 Ok((end - start) * 1e-3)
328 } else {
329 Ok(((tolerance / speed) * 10.0).max((end - start) * 1e-9))
330 }
331}
332
333fn parameter_distance(a: f64, b: f64, start: f64, end: f64, closed: bool) -> f64 {
334 let distance = (a - b).abs();
335 if closed {
336 distance.min(end - start - distance)
337 } else {
338 distance
339 }
340}
341
342#[derive(Clone, Copy, Debug, Serialize)]
343pub struct CurveCurveIntersection {
344 pub s: f64,
345 pub t: f64,
346 pub point: Vec3,
347 pub gap: f64,
348 pub tangential: bool,
349}
350
351#[derive(Clone, Copy, PartialEq, Eq)]
367enum LineLineMode {
368 Off,
369 On,
370 Census,
371}
372
373fn line_line_mode() -> LineLineMode {
374 static MODE: std::sync::OnceLock<LineLineMode> = std::sync::OnceLock::new();
375 let mode = *MODE.get_or_init(|| match std::env::var("BREP_EXACT_LINE_LINE").as_deref() {
376 Ok("0") | Ok("off") => LineLineMode::Off,
377 Ok("census") => LineLineMode::Census,
378 _ => LineLineMode::On,
379 });
380 mode
381}
382
383
384#[derive(Clone, Copy, PartialEq, Eq)]
405enum TangentMergeMode {
406 Off,
407 On,
408 Census,
409}
410
411fn tangent_merge_mode() -> TangentMergeMode {
412 static MODE: std::sync::OnceLock<TangentMergeMode> = std::sync::OnceLock::new();
413 let mode = *MODE.get_or_init(|| match std::env::var("BREP_CURVE_TANGENT_MERGE_LOCAL").as_deref() {
416 Ok("0") | Ok("off") => TangentMergeMode::Off,
417 Ok("census") => TangentMergeMode::Census,
418 _ => TangentMergeMode::On,
419 });
420 mode
421}
422
423
424fn local_tangent_merge_radius(
430 first: &NurbsCurve,
431 second: &NurbsCurve,
432 s: f64,
433 t: f64,
434 tolerance: f64,
435 legacy: f64,
436) -> Result<(f64, f64, f64), String> {
437 let a = first.derivatives_small(s, 2)?;
438 let b = second.derivatives_small(t, 2)?;
439 let (speed_a, speed_b) = (a[1].length(), b[1].length());
440 if speed_a <= EPSILON || speed_b <= EPSILON {
441 return Ok((legacy, f64::NAN, f64::NAN));
442 }
443 let tangent_a = a[1].scale(1.0 / speed_a);
444 let tangent_b = b[1].scale(1.0 / speed_b);
445 let sin_theta = tangent_a.cross(tangent_b).length().min(1.0);
446 let curvature_a = a[2].sub(tangent_a.scale(a[2].dot(tangent_a))).scale(1.0 / (speed_a * speed_a));
449 let curvature_b = b[2].sub(tangent_b.scale(b[2].dot(tangent_b))).scale(1.0 / (speed_b * speed_b));
450 let relative = curvature_a.sub(curvature_b).length();
451 let mut radius = f64::INFINITY;
452 if sin_theta > 0.0 {
453 radius = radius.min(tolerance / sin_theta);
454 }
455 if relative > 0.0 {
456 radius = radius.min((2.0 * tolerance / relative).sqrt());
457 }
458 Ok((radius.min(legacy).max(10.0 * tolerance), sin_theta, relative))
459}
460
461fn straight_segment(curve: &NurbsCurve) -> Option<(Vec3, Vec3, [f64; 2])> {
464 if curve.degree != 1 || curve.control_points.len() != 2 || curve.knots.len() != 4 {
465 return None;
466 }
467 let k = &curve.knots;
468 if k[0] != k[1] || k[2] != k[3] || !(k[1] < k[2]) {
469 return None;
470 }
471 if curve.control_points[0].w != curve.control_points[1].w {
472 return None;
473 }
474 Some((curve.control_points[0].point().ok()?, curve.control_points[1].point().ok()?, [k[1], k[2]]))
475}
476
477pub(crate) fn exact_line_line(first: &NurbsCurve, second: &NurbsCurve, tolerance: f64) -> Option<Vec<CurveCurveIntersection>> {
484 let (p0, p1, [s0, s1]) = straight_segment(first)?;
485 let (q0, q1, [t0, t1]) = straight_segment(second)?;
486 let d1 = p1.sub(p0);
487 let d2 = q1.sub(q0);
488 let a = d1.dot(d1);
489 let e = d2.dot(d2);
490 if a <= EPSILON || e <= EPSILON {
491 return None;
492 }
493 if d1.cross(d2).length() <= 1e-3_f64.sin() * (a * e).sqrt() {
494 return None;
495 }
496 let r = p0.sub(q0);
497 let b = d1.dot(d2);
498 let c = d1.dot(r);
499 let f = d2.dot(r);
500 let denominator = a * e - b * b;
501 let mut s = ((b * f - c * e) / denominator).clamp(0.0, 1.0);
502 let mut t = (b * s + f) / e;
503 if t < 0.0 {
504 t = 0.0;
505 s = (-c / a).clamp(0.0, 1.0);
506 } else if t > 1.0 {
507 t = 1.0;
508 s = ((b - c) / a).clamp(0.0, 1.0);
509 }
510 let first_point = p0.add(d1.scale(s));
511 let second_point = q0.add(d2.scale(t));
512 let gap = first_point.sub(second_point).length();
513 if gap > tolerance {
514 return Some(Vec::new());
515 }
516 Some(vec![CurveCurveIntersection {
517 s: (s0 + (s1 - s0) * s).clamp(s0, s1),
518 t: (t0 + (t1 - t0) * t).clamp(t0, t1),
519 point: first_point.add(second_point).scale(0.5),
520 gap,
521 tangential: false,
522 }])
523}
524
525pub fn intersect_curves(
526 first: &NurbsCurve,
527 second: &NurbsCurve,
528 tolerance: f64,
529) -> Result<Vec<CurveCurveIntersection>, String> {
530 let mode = line_line_mode();
531 if mode != LineLineMode::Off {
532 if let Some(exact) = exact_line_line(first, second, tolerance) {
533 if mode == LineLineMode::On {
534 return Ok(exact);
535 }
536 let general = intersect_curves_general(first, second, tolerance)?;
537 line_line_census(first, second, tolerance, &general, &exact)?;
538 return Ok(general);
539 }
540 }
541 let mode = line_conic_mode();
542 if mode != LineLineMode::Off {
543 if let Some(seeds) = exact_line_conic_pairs(first, second, tolerance) {
544 let exact = intersect_curves_from_seeds(first, second, tolerance, seeds)?;
545 if mode == LineLineMode::On {
546 return Ok(exact);
547 }
548 let general = intersect_curves_general(first, second, tolerance)?;
549 crossing_census("line-conic", "BREP_EXACT_LINE_CONIC_CENSUS", first, second, tolerance, &general, &exact)?;
550 return Ok(general);
551 }
552 }
553 let mode = conic_conic_mode();
554 if mode != LineLineMode::Off {
555 if let Some(seeds) = exact_conic_conic_pairs(first, second, tolerance) {
556 let exact = intersect_curves_from_seeds(first, second, tolerance, seeds)?;
557 if mode == LineLineMode::On {
558 return Ok(exact);
559 }
560 let general = intersect_curves_general(first, second, tolerance)?;
561 general_rerun_census("conic-conic", "BREP_EXACT_CONIC_CONIC_CENSUS", first, second, tolerance, &general)?;
566 crossing_census("conic-conic", "BREP_EXACT_CONIC_CONIC_CENSUS", first, second, tolerance, &general, &exact)?;
567 return Ok(general);
568 }
569 }
570 intersect_curves_general(first, second, tolerance)
571}
572
573fn conic_conic_mode() -> LineLineMode {
581 static MODE: std::sync::OnceLock<LineLineMode> = std::sync::OnceLock::new();
582 let mode = *MODE.get_or_init(|| match std::env::var("BREP_EXACT_CONIC_CONIC").as_deref() {
585 Ok("0") | Ok("off") => LineLineMode::Off,
586 Ok("census") => LineLineMode::Census,
587 _ => LineLineMode::On,
588 });
589 mode
590}
591
592
593fn conic_plane(curve: &NurbsCurve) -> Option<(Vec3, Vec3, f64)> {
598 if curve.degree != 2 || curve.control_points.len() < 3 {
599 return None;
600 }
601 if curve.control_points.iter().any(|p| !(p.w > 0.0 && p.w.is_finite())) {
602 return None;
603 }
604 let points = curve.control_points.iter().map(|p| p.point().ok()).collect::<Option<Vec<_>>>()?;
605 let base = points[0];
606 let extent = points.iter().fold(0.0_f64, |m, p| m.max(p.sub(base).length()));
607 if !(extent > 0.0 && extent.is_finite()) {
608 return None;
609 }
610 let mut normal = Vec3::new(0.0, 0.0, 0.0);
611 let mut area = 0.0_f64;
612 for i in 1..points.len() {
613 for j in i + 1..points.len() {
614 let cross = points[i].sub(base).cross(points[j].sub(base));
615 if cross.length() > area {
616 area = cross.length();
617 normal = cross;
618 }
619 }
620 }
621 if !(area > 1e-9 * extent * extent) {
622 return None;
623 }
624 let normal = normal.scale(1.0 / area);
625 if points.iter().any(|p| normal.dot(p.sub(base)).abs() > 1e-12 * extent) {
626 return None;
627 }
628 Some((base, normal, extent))
629}
630
631fn exact_conic_conic_pairs(first: &NurbsCurve, second: &NurbsCurve, tolerance: f64) -> Option<Vec<[f64; 2]>> {
651 let (base_a, normal_a, extent_a) = conic_plane(first)?;
652 let (base_b, normal_b, extent_b) = conic_plane(second)?;
653 let extent = extent_a.max(extent_b);
654 let shared = normal_a.cross(normal_b);
655 let seeds = if shared.length() > 1e-3_f64.sin() {
656 let direction = shared.scale(1.0 / shared.length());
658 let (h_a, h_b) = (normal_a.dot(base_a), normal_b.dot(base_b));
659 let denominator = shared.dot(shared);
660 let on_line = normal_b.cross(shared).scale(h_a / denominator).add(shared.cross(normal_a).scale(h_b / denominator));
661 let foot = on_line.add(direction.scale(base_a.sub(on_line).dot(direction)));
662 let reach = 2.0 * (extent + base_a.sub(foot).length()).max(1.0);
663 crate::exact_line_surface::exact_line_conic_seeds(foot.sub(direction.scale(reach)), foot.add(direction.scale(reach)), first, tolerance)?
664 } else if normal_a.dot(base_b.sub(base_a)).abs() <= 1e-12 * extent {
665 let [t0, t1] = second.domain().ok()?;
666 let circle = crate::recognize_circle(second, t0, t1, 1e-12 * extent.max(1.0))?;
667 let seeds = crate::exact_line_surface::exact_curve_sphere_seeds(first, circle.centre, circle.radius, tolerance)?;
668 if seeds.is_empty() {
669 let [s0, s1] = first.domain().ok()?;
670 let band = 100.0 * tolerance;
671 for i in 0..=64 {
672 let point = first.evaluate(s0 + (s1 - s0) * i as f64 / 64.0).ok()?;
673 if (point.sub(circle.centre).length() - circle.radius).abs() <= band {
674 return None;
675 }
676 }
677 }
678 seeds
679 } else {
680 return None;
681 };
682 seeds
683 .into_iter()
684 .map(|s| {
685 let point = first.evaluate(s).ok()?;
686 let projection = crate::project_point_to_curve(second, point).ok()?;
687 Some([s, projection.u])
688 })
689 .collect()
690}
691
692fn line_conic_mode() -> LineLineMode {
702 static MODE: std::sync::OnceLock<LineLineMode> = std::sync::OnceLock::new();
703 let mode = *MODE.get_or_init(|| match std::env::var("BREP_EXACT_LINE_CONIC").as_deref() {
704 Ok("0") | Ok("off") => LineLineMode::Off,
705 Ok("census") => LineLineMode::Census,
706 _ => LineLineMode::On,
707 });
708 mode
709}
710
711
712fn exact_line_conic_pairs(first: &NurbsCurve, second: &NurbsCurve, tolerance: f64) -> Option<Vec<[f64; 2]>> {
716 let (line_first, (p0, p1, [l0, l1]), conic) = match (straight_segment(first), straight_segment(second)) {
717 (Some(segment), None) => (true, segment, second),
718 (None, Some(segment)) => (false, segment, first),
719 _ => return None,
720 };
721 let seeds = crate::exact_line_surface::exact_line_conic_seeds(p0, p1, conic, tolerance)?;
722 let direction = p1.sub(p0);
723 let length_squared = direction.dot(direction);
724 seeds
725 .into_iter()
726 .map(|t| {
727 let point = conic.evaluate(t).ok()?;
728 let along = (point.sub(p0).dot(direction) / length_squared).clamp(0.0, 1.0);
729 let s = l0 + along * (l1 - l0);
730 Some(if line_first { [s, t] } else { [t, s] })
731 })
732 .collect()
733}
734
735fn line_line_census(
739 first: &NurbsCurve,
740 second: &NurbsCurve,
741 tolerance: f64,
742 general: &[CurveCurveIntersection],
743 exact: &[CurveCurveIntersection],
744) -> Result<(), String> {
745 crossing_census("line-line", "BREP_EXACT_LINE_LINE_CENSUS", first, second, tolerance, general, exact)
746}
747
748fn general_rerun_census(
752 lane: &str,
753 variable: &str,
754 first: &NurbsCurve,
755 second: &NurbsCurve,
756 tolerance: f64,
757 general: &[CurveCurveIntersection],
758) -> Result<(), String> {
759 let Ok(path) = std::env::var(variable) else {
760 return Ok(());
761 };
762 let rerun = intersect_curves_general(first, second, tolerance)?;
763 let same = general.len() == rerun.len()
764 && general.iter().zip(&rerun).all(|(g, r)| g.s.to_bits() == r.s.to_bits() && g.t.to_bits() == r.t.to_bits());
765 if same {
766 return Ok(());
767 }
768 let mut line = format!("{lane} rerun census: tolerance={tolerance:e} general {} rerun {}", general.len(), rerun.len());
769 for g in general {
770 line.push_str(&format!(" | general s={:e} t={:e} gap={:e}", g.s, g.t, g.gap));
771 }
772 for r in &rerun {
773 line.push_str(&format!(" | rerun s={:e} t={:e} gap={:e}", r.s, r.t, r.gap));
774 }
775 if let (Ok(a), Ok(b)) = (serde_json::to_string(first), serde_json::to_string(second)) {
776 line.push_str(&format!(" | curves=[{a},{b}]"));
777 }
778 line.push('\n');
779 use std::io::Write;
780 if let Ok(mut file) = std::fs::OpenOptions::new().create(true).append(true).open(path) {
781 let _ = file.write_all(line.as_bytes());
782 }
783 Ok(())
784}
785
786fn crossing_census(
794 lane: &str,
795 variable: &str,
796 first: &NurbsCurve,
797 second: &NurbsCurve,
798 tolerance: f64,
799 general: &[CurveCurveIntersection],
800 exact: &[CurveCurveIntersection],
801) -> Result<(), String> {
802 let same = general.len() == exact.len()
803 && general.iter().zip(exact).all(|(g, x)| g.s.to_bits() == x.s.to_bits() && g.t.to_bits() == x.t.to_bits());
804 if same {
805 return Ok(());
806 }
807 let Ok(path) = std::env::var(variable) else {
808 return Ok(());
809 };
810 let mut line = format!(
811 "{lane} census: label {} tolerance={:e} general {} exact {}",
812 std::env::var("BREP_CENSUS_LABEL").unwrap_or_default(),
813 tolerance,
814 general.len(),
815 exact.len()
816 );
817 let mut exact_points = Vec::with_capacity(exact.len());
818 for x in exact {
819 exact_points.push((first.evaluate(x.s)?, second.evaluate(x.t)?));
820 line.push_str(&format!(" | exact s={:e} t={:e} gap={:e}", x.s, x.t, x.gap));
821 }
822 let mut unmatched = false;
823 for g in general {
824 let (on_first, on_second) = (first.evaluate(g.s)?, second.evaluate(g.t)?);
825 let nearest = exact
826 .iter()
827 .zip(&exact_points)
828 .map(|(x, (xf, xs))| (on_first.sub(*xf).length().max(on_second.sub(*xs).length()), x))
829 .min_by(|a, b| a.0.total_cmp(&b.0));
830 match nearest {
831 Some((residual, x)) => {
832 unmatched |= residual > tolerance;
833 line.push_str(&format!(
834 " | general s={:e} t={:e} gap={:e} ds={:e} dt={:e} residual={:e}",
835 g.s,
836 g.t,
837 g.gap,
838 g.s - x.s,
839 g.t - x.t,
840 residual
841 ));
842 }
843 None => {
844 unmatched = true;
845 line.push_str(&format!(" | general s={:e} t={:e} gap={:e}", g.s, g.t, g.gap));
846 }
847 }
848 }
849 if general.len() != exact.len() || unmatched {
850 if let (Ok(a), Ok(b)) = (serde_json::to_string(first), serde_json::to_string(second)) {
851 line.push_str(&format!(" | curves=[{a},{b}]"));
852 }
853 }
854 line.push('\n');
855 use std::io::Write;
856 if let Ok(mut file) = std::fs::OpenOptions::new().create(true).append(true).open(path) {
857 let _ = file.write_all(line.as_bytes());
858 }
859 Ok(())
860}
861
862fn overlapping_segment_pairs(first: &[Segment], second: &[Segment], tolerance: f64) -> Vec<(usize, usize)> {
870 let mut order: Vec<usize> = (0..second.len()).collect();
871 order.sort_by(|&a, &b| second[a].bounds.minimum.x.total_cmp(&second[b].bounds.minimum.x));
872 let minimum_x: Vec<f64> = order.iter().map(|&j| second[j].bounds.minimum.x).collect();
873 let mut pairs = Vec::new();
874 for (i, segment) in first.iter().enumerate() {
875 let limit = segment.bounds.maximum.x + tolerance;
876 let end = minimum_x.partition_point(|&x| x <= limit);
878 let mut row: Vec<usize> = order[..end]
879 .iter()
880 .copied()
881 .filter(|&j| segment.bounds.intersects_upper(second[j].bounds, tolerance))
882 .collect();
883 row.sort_unstable();
884 pairs.extend(row.into_iter().map(|j| (i, j)));
885 }
886 pairs
887}
888
889fn intersect_curves_general(
890 first: &NurbsCurve,
891 second: &NurbsCurve,
892 tolerance: f64,
893) -> Result<Vec<CurveCurveIntersection>, String> {
894 let first_segment_count = 8usize.max((interior_knots(first).len() + 1) * (first.degree + 1));
895 let second_segment_count = 8usize.max((interior_knots(second).len() + 1) * (second.degree + 1));
896 let first_segments = segment_boxes_memoized(first, first_segment_count)?;
897 let second_segments = segment_boxes_memoized(second, second_segment_count)?;
898 let seeds = overlapping_segment_pairs(&first_segments, &second_segments, tolerance)
907 .into_iter()
908 .map(|(i, j)| {
909 [
910 (first_segments[i].start + first_segments[i].end) * 0.5,
911 (second_segments[j].start + second_segments[j].end) * 0.5,
912 ]
913 })
914 .collect::<Vec<_>>();
915 intersect_curves_from_seeds(first, second, tolerance, seeds)
916}
917
918fn intersect_curves_from_seeds(
922 first: &NurbsCurve,
923 second: &NurbsCurve,
924 tolerance: f64,
925 seeds: Vec<[f64; 2]>,
926) -> Result<Vec<CurveCurveIntersection>, String> {
927 let [s0, s1] = first.domain()?;
928 let [t0, t1] = second.domain()?;
929 let closed_first = first.evaluate(s0)?.sub(first.evaluate(s1)?).length() <= tolerance;
930 let closed_second = second.evaluate(t0)?.sub(second.evaluate(t1)?).length() <= tolerance;
931 let mut candidates = Vec::with_capacity(seeds.len());
938 let mut failed = Vec::new();
939 for [seed_s, seed_t] in seeds {
940 let pair = newton_closest_pair(first, second, seed_s, seed_t, true)?;
941 if first.evaluate(pair[0])?.sub(second.evaluate(pair[1])?).length() <= tolerance {
942 candidates.push(pair);
943 } else {
944 failed.push([seed_s, seed_t]);
945 }
946 }
947 for [seed_s, seed_t] in failed {
948 candidates.push(newton_closest_pair(first, second, seed_s, seed_t, false)?);
949 }
950 let mut results: Vec<CurveCurveIntersection> = Vec::new();
951 let mut kept_radii: Vec<Option<f64>> = Vec::new();
956 for [s, t] in candidates {
957 let first_point = first.evaluate(s)?;
958 let second_point = second.evaluate(t)?;
959 let gap = first_point.sub(second_point).length();
960 if gap > tolerance {
961 continue;
962 }
963 let first_tangent = first.deriv1(s)?.1;
964 let second_tangent = second.deriv1(t)?.1;
965 let tangential = match (first_tangent.normalized(), second_tangent.normalized()) {
966 (Ok(a), Ok(b)) => a.cross(b).length() <= 1e-3_f64.sin(),
967 _ => false,
968 };
969 let point = first_point.add(second_point).scale(0.5);
970 let scale = 1.0 + point.length();
971 let spatial_tolerance = if tangential {
972 tolerance.sqrt() * scale
973 } else {
974 tolerance * 10.0
975 };
976 let merge_mode = tangent_merge_mode();
979 let local = if tangential && merge_mode != TangentMergeMode::Off {
980 Some(local_tangent_merge_radius(first, second, s, t, tolerance, spatial_tolerance)?)
981 } else {
982 None
983 };
984 let local_tolerance = local.map(|(radius, _, _)| radius).unwrap_or(spatial_tolerance);
985 let (mut legacy_duplicate, mut local_duplicate) = (None, None);
987 for (index, result) in results.iter().enumerate() {
988 let in_parameter = parameter_distance(result.s, s, s0, s1, closed_first)
989 <= parameter_tolerance(first, result.s, tolerance)?
990 && parameter_distance(result.t, t, t0, t1, closed_second)
991 <= parameter_tolerance(second, result.t, tolerance)?;
992 let distance = result.point.sub(point).length();
993 let either_tangential = result.tangential || tangential;
994 if legacy_duplicate.is_none()
995 && (in_parameter || distance <= if either_tangential { spatial_tolerance } else { tolerance * 10.0 })
996 {
997 legacy_duplicate = Some(index);
998 }
999 let local_radius = match (tangential, kept_radii.get(index).copied().flatten()) {
1002 (true, Some(kept)) => local_tolerance.max(kept),
1003 (true, None) => local_tolerance,
1004 (false, Some(kept)) => kept,
1005 (false, None) => tolerance * 10.0,
1006 };
1007 if local_duplicate.is_none() && (in_parameter || distance <= local_radius) {
1008 local_duplicate = Some(index);
1009 }
1010 if legacy_duplicate.is_some() && (merge_mode == TangentMergeMode::Off || local_duplicate.is_some()) {
1011 break;
1012 }
1013 }
1014 let candidate = CurveCurveIntersection {
1015 s: s.clamp(s0, s1),
1016 t: t.clamp(t0, t1),
1017 point,
1018 gap,
1019 tangential,
1020 };
1021 let candidate_radius = local.map(|(radius, _, _)| radius);
1022 match merge_mode {
1023 TangentMergeMode::Off => {
1024 if legacy_duplicate.is_some() {
1025 continue;
1026 }
1027 results.push(candidate);
1028 }
1029 TangentMergeMode::On => match local_duplicate {
1030 Some(index) => {
1032 if gap < results[index].gap {
1033 results[index] = candidate;
1034 kept_radii[index] = candidate_radius;
1035 }
1036 }
1037 None => {
1038 results.push(candidate);
1039 kept_radii.push(candidate_radius);
1040 }
1041 },
1042 TangentMergeMode::Census => {
1043 let legacy_keeps = legacy_duplicate.is_none();
1047 let local_keeps = local_duplicate.is_none();
1048 let would_replace = local_duplicate.is_some_and(|index| gap < results[index].gap);
1049 if legacy_keeps != local_keeps || would_replace {
1050 if let Ok(path) = std::env::var("BREP_CURVE_TANGENT_MERGE_CENSUS") {
1051 let (local_radius, sin_theta, relative) = local.unwrap_or((spatial_tolerance, f64::NAN, f64::NAN));
1052 let against = legacy_duplicate.or(local_duplicate).map(|index| &results[index]);
1053 let mut line = format!(
1054 "tangent-merge census: label {} tolerance={:e} p={:e} legacy_radius={:e} local_radius={:e} sin_theta={:e} relative_curvature={:e} gap={:e} tangential={} legacy_keeps={} local_keeps={} would_replace={}",
1055 std::env::var("BREP_CENSUS_LABEL").unwrap_or_default(),
1056 tolerance,
1057 point.length(),
1058 spatial_tolerance,
1059 local_radius,
1060 sin_theta,
1061 relative,
1062 gap,
1063 tangential,
1064 legacy_keeps,
1065 local_keeps,
1066 would_replace,
1067 );
1068 if let Some(other) = against {
1069 line.push_str(&format!(
1070 " | against s={:e} t={:e} gap={:e} tangential={} distance={:e}",
1071 other.s,
1072 other.t,
1073 other.gap,
1074 other.tangential,
1075 other.point.sub(point).length()
1076 ));
1077 }
1078 if let (Ok(a), Ok(b)) = (serde_json::to_string(first), serde_json::to_string(second)) {
1079 line.push_str(&format!(" | curves=[{a},{b}]"));
1080 }
1081 line.push('\n');
1082 use std::io::Write;
1083 if let Ok(mut file) = std::fs::OpenOptions::new().create(true).append(true).open(path) {
1084 let _ = file.write_all(line.as_bytes());
1085 }
1086 }
1087 }
1088 if legacy_duplicate.is_some() {
1089 continue;
1090 }
1091 results.push(candidate);
1092 kept_radii.push(candidate_radius);
1093 }
1094 }
1095 }
1096 results.sort_by(|a, b| a.s.total_cmp(&b.s));
1097 Ok(results)
1098}
1099
1100
1101