1use axiolid_core::{Interval, Point2, Scalar, Vec2};
14use axiolid_curve::implicit::{bound_simple, partial, Cell, Range};
15use axiolid_curve::{
16 Axis, Basis, Carrier, Curve3, Field2, ImplicitCell, ImplicitCurve2, ImplicitSection3,
17 SeriesField2, Trig2,
18};
19use axiolid_surface::Surface;
20use core::f64::consts::{PI, TAU};
21
22use crate::exact_curve_intersection::{
23 ExactCurveHit, ExactCurveIntersection, ExactCurveParameter, ExactCurveRefusal, Isolated,
24};
25use crate::exact_surface_intersection::ExactIntersectionRefusal;
26use crate::field::{carrier_of, section_field};
27use crate::implicit_trace::Periodic;
28
29pub fn trace_section_pcurves(
39 surface: &Surface,
40 other: &Surface,
41 window: (Point2, Point2),
42) -> Result<Vec<ImplicitCurve2>, ExactIntersectionRefusal> {
43 let carrier = carrier_of(surface).ok_or(ExactIntersectionRefusal::UnsupportedPair)?;
44 let field = section_field(&carrier, other).ok_or(ExactIntersectionRefusal::UnsupportedPair)?;
45 trace_field(&carrier, &field, window, crate::implicit_trace::BUDGET / 4)
48}
49
50fn trace_field(
51 carrier: &Carrier,
52 field: &Field2,
53 window: (Point2, Point2),
54 budget: usize,
55) -> Result<Vec<ImplicitCurve2>, ExactIntersectionRefusal> {
56 let (pu, pv) = carrier.periodic();
57 let (lo, hi) = window;
58 let wraps = |periodic: bool, a: Scalar, b: Scalar| {
59 periodic && ((b - a) - TAU).abs() <= 1e-12 * (1.0 + a.abs())
60 };
61 let periodic = Periodic {
62 u: wraps(pu, lo.x, hi.x),
63 v: wraps(pv, lo.y, hi.y),
64 };
65 crate::implicit_trace::trace_within(field, Cell { lo, hi }, periodic, budget)
66 .map_err(crate::implicit_section::refusal_of)
67}
68
69#[must_use]
75pub fn extract_stretch(
76 curves: &[ImplicitCurve2],
77 periodic: (bool, bool),
78 start: Point2,
79 [first, second]: [Point2; 2],
80 end: Point2,
81 closed: bool,
82) -> Option<ImplicitCurve2> {
83 let shifts = |p: Point2| -> Vec<Point2> {
84 let ku: &[Scalar] = if periodic.0 {
85 &[0.0, -1.0, 1.0, -2.0, 2.0]
86 } else {
87 &[0.0]
88 };
89 let kv: &[Scalar] = if periodic.1 {
90 &[0.0, -1.0, 1.0, -2.0, 2.0]
91 } else {
92 &[0.0]
93 };
94 let mut out = Vec::new();
95 for a in ku {
96 for b in kv {
97 out.push(Point2::new(p.x + a * TAU, p.y + b * TAU));
98 }
99 }
100 out
101 };
102 let find = |curve: &ImplicitCurve2, p: Point2| -> Option<Scalar> {
103 let eps = 1e-7 * (1.0 + p.x.abs().max(p.y.abs()));
104 let mut best: Option<(Scalar, Scalar)> = None;
105 for q in shifts(p) {
106 let Some(t) = curve.parameter_of(q) else {
107 continue;
108 };
109 let Some(on) = curve.point(t) else {
110 continue;
111 };
112 let mut d = on - q;
114 if periodic.0 {
115 d.x -= (d.x / TAU).round() * TAU;
116 }
117 if periodic.1 {
118 d.y -= (d.y / TAU).round() * TAU;
119 }
120 let miss = d.length();
121 if miss <= eps && best.is_none_or(|(m, _)| miss < m) {
122 best = Some((miss, t));
123 }
124 }
125 best.map(|(_, t)| t)
126 };
127 for curve in curves {
128 let (Some(ts), Some(t1), Some(t2), Some(te)) = (
129 find(curve, start),
130 find(curve, first),
131 find(curve, second),
132 find(curve, end),
133 ) else {
134 continue;
135 };
136 let n = curve.end();
137 let closure = curve.closure(periodic.0, periodic.1);
138 let ahead = |t: Scalar| (t - ts).rem_euclid(n);
139 let stretch = if closed {
140 let whole = curve.rotated(ts, closure?)?;
142 if ahead(t1) < ahead(t2) {
143 whole
144 } else {
145 whole.reversed()
146 }
147 } else if let Some(closure) = closure {
148 if ahead(t1) < ahead(t2) && ahead(t2) < ahead(te) {
151 curve.sub(ts, te, Some(closure))?
152 } else {
153 curve.sub(te, ts, Some(closure))?.reversed()
154 }
155 } else if ts <= t1 && t1 <= t2 && t2 <= te {
156 curve.sub(ts, te, None)?
157 } else if te <= t2 && t2 <= t1 && t1 <= ts {
158 curve.sub(te, ts, None)?.reversed()
159 } else {
160 continue;
161 };
162 return Some(stretch);
163 }
164 None
165}
166
167fn defining_field(curve: &Curve3) -> Option<(Carrier, Field2)> {
170 let fourier = |t: &Trig2| vec![t.constant, t.cos, t.sin, t.cos2, t.sin2];
171 match curve {
172 Curve3::ImplicitSection(s) => Some((s.carrier.clone(), s.curve.field.clone())),
173 Curve3::RuledSection(r) => {
174 let (a, b, c) = (
176 fourier(&r.graph.a),
177 fourier(&r.graph.b),
178 fourier(&r.graph.c),
179 );
180 let coefficients = (0..5).map(|i| vec![c[i], b[i], a[i]]).collect();
181 Some((
182 Carrier::Ruled(r.carrier),
183 Field2::Series(SeriesField2 {
184 u: Basis::Fourier,
185 v: Basis::Power,
186 coefficients,
187 }),
188 ))
189 }
190 Curve3::TorusSection(t) => {
191 let (a, b, c) = (
193 fourier(&t.graph.a),
194 fourier(&t.graph.b),
195 fourier(&t.graph.c),
196 );
197 let coefficients = vec![c.iter().map(|x| -x).collect(), a, b];
198 Some((
199 Carrier::Torus(t.torus),
200 Field2::Series(SeriesField2 {
201 u: Basis::Fourier,
202 v: Basis::Fourier,
203 coefficients,
204 }),
205 ))
206 }
207 _ => None,
208 }
209}
210
211#[must_use]
215pub fn implicit_view(curve: &Curve3, span: Interval) -> Option<ImplicitSection3> {
216 if let Curve3::ImplicitSection(s) = curve {
217 let closed = (span.end - span.start).abs() >= s.curve.end() - 1e-12;
218 let sub = if closed {
219 s.curve.clone()
220 } else {
221 let (a, b) = (span.start.min(span.end), span.start.max(span.end));
222 let piece = s.curve.sub(a, b, None)?;
223 if span.end < span.start {
224 piece.reversed()
225 } else {
226 piece
227 }
228 };
229 return Some(ImplicitSection3 {
230 carrier: s.carrier.clone(),
231 curve: sub,
232 });
233 }
234 let (carrier, field) = defining_field(curve)?;
235 let at = |t: Scalar| -> Option<Point2> {
236 let p = axiolid_evaluate::evaluate3(curve, t).ok()?;
237 let (u, v) = carrier.parameters(p);
238 Some(Point2::new(u, v))
239 };
240 let n = 64;
242 let mut samples = Vec::with_capacity(n + 1);
243 for i in 0..=n {
244 let t = span.start + (span.end - span.start) * i as Scalar / n as Scalar;
245 let mut p = at(t)?;
246 if let Some(q) = samples.last().copied() {
247 let q: Point2 = q;
248 let (pu, pv) = carrier.periodic();
249 if pu {
250 p.x += ((q.x - p.x) / TAU).round() * TAU;
251 }
252 if pv {
253 p.y += ((q.y - p.y) / TAU).round() * TAU;
254 }
255 }
256 samples.push(p);
257 }
258 let (mut lo, mut hi) = (samples[0], samples[0]);
259 for p in &samples {
260 lo = lo.min(*p);
261 hi = hi.max(*p);
262 }
263 let pad = (hi - lo) * 0.25 + Vec2::splat(0.05);
264 let (mut lo, mut hi) = (lo - pad, hi + pad);
265 let (pu, pv) = carrier.periodic();
267 if pu && hi.x - lo.x >= TAU {
268 let c = 0.5 * (lo.x + hi.x);
269 (lo.x, hi.x) = (c - PI, c + PI);
270 }
271 if pv && hi.y - lo.y >= TAU {
272 let c = 0.5 * (lo.y + hi.y);
273 (lo.y, hi.y) = (c - PI, c + PI);
274 }
275 let curves = trace_field(&carrier, &field, (lo, hi), crate::implicit_trace::BUDGET).ok()?;
276 let (first, last) = (
278 axiolid_evaluate::evaluate3(curve, span.start).ok()?,
279 axiolid_evaluate::evaluate3(curve, span.end).ok()?,
280 );
281 let closed = (first - last).length() <= 1e-9 * (1.0 + first.length());
282 let stretch = extract_stretch(
283 &curves,
284 (pu, pv),
285 samples[0],
286 [samples[n / 3], samples[2 * n / 3]],
287 samples[n],
288 closed,
289 )?;
290 Some(ImplicitSection3 {
291 carrier,
292 curve: stretch,
293 })
294}
295
296pub fn section_curve_surface_intersection(
312 curve: &Curve3,
313 span: Interval,
314 surface: &Surface,
315) -> Result<ExactCurveIntersection, ExactCurveRefusal> {
316 let view = implicit_view(curve, span).ok_or(ExactCurveRefusal::UnsupportedCurve)?;
317 let other =
318 section_field(&view.carrier, surface).ok_or(ExactCurveRefusal::UnsupportedSurface)?;
319 let own = &view.curve;
320 let scale = 1e-9 * other.magnitude().max(1.0);
323 let samples: Vec<Scalar> = (0..=32)
324 .filter_map(|i| {
325 let p = own.point(own.end() * i as Scalar / 32.0)?;
326 Some(other.value(p))
327 })
328 .collect();
329 if samples.len() == 33 && samples.iter().all(|h| h.abs() <= scale) {
330 return Ok(ExactCurveIntersection::Contained);
331 }
332 let mut roots = roots_along(own, &other);
333 roots.sort_by(|a, b| a.0.total_cmp(&b.0));
334 roots.dedup_by(|a, b| (a.0 - b.0).abs() <= 1e-9);
335 let mut hits = Vec::new();
336 for (t, multiplicity) in roots {
337 let Some(p) = own.point(t) else { continue };
338 let point = view.carrier.jet(p.x, p.y).point;
339 let parameter = match curve {
341 Curve3::ImplicitSection(_) => {
342 if span.end < span.start {
343 span.start - t
344 } else {
345 span.start + t
346 }
347 }
348 _ => {
349 match axiolid_evaluate::curve::invert3(curve, point, axiolid_core::Tolerance::METRE)
350 {
351 Ok(x) => nearest_turn(x, span),
352 Err(_) => continue,
353 }
354 }
355 };
356 hits.push(ExactCurveHit {
357 parameter: ExactCurveParameter::Certified(Isolated::new(parameter)),
358 multiplicity,
359 point,
360 });
361 }
362 Ok(ExactCurveIntersection::Points(hits))
363}
364
365pub(crate) fn roots_along(own: &ImplicitCurve2, other: &Field2) -> Vec<(Scalar, usize)> {
372 let d_u = partial(&own.field, true);
373 let d_v = partial(&own.field, false);
374 let h_u = partial(other, true);
375 let h_v = partial(other, false);
376 let mut roots: Vec<(Scalar, usize)> = Vec::new();
377 for (index, cell) in own.cells.iter().enumerate() {
378 let job = CellRoots {
379 curve: own,
380 cell,
381 other,
382 d_u: &d_u,
383 d_v: &d_v,
384 h_u: &h_u,
385 h_v: &h_v,
386 };
387 let mut found = Vec::new();
388 job.roots(0.0, 1.0, 0, &mut found);
389 for (s, m) in found {
390 roots.push((index as Scalar + s, m));
391 }
392 }
393 roots.sort_by(|a, b| a.0.total_cmp(&b.0));
394 roots.dedup_by(|a, b| (a.0 - b.0).abs() <= 1e-9);
395 roots
396}
397
398fn nearest_turn(x: Scalar, span: Interval) -> Scalar {
400 let (lo, hi) = (span.start.min(span.end), span.start.max(span.end));
401 for k in [0.0, 1.0, -1.0, 2.0, -2.0] {
402 let y = x + k * TAU;
403 if y >= lo - 1e-9 && y <= hi + 1e-9 {
404 return y;
405 }
406 }
407 x
408}
409
410struct CellRoots<'a> {
412 curve: &'a ImplicitCurve2,
413 cell: &'a ImplicitCell,
414 other: &'a Field2,
415 d_u: &'a Field2,
416 d_v: &'a Field2,
417 h_u: &'a Field2,
418 h_v: &'a Field2,
419}
420
421impl CellRoots<'_> {
422 fn free(&self, s: Scalar) -> Scalar {
423 self.cell.from + (self.cell.to - self.cell.from) * s
424 }
425
426 fn place(&self, free: Scalar, solved: Scalar) -> Point2 {
427 match self.cell.axis {
428 Axis::U => Point2::new(free, solved),
429 Axis::V => Point2::new(solved, free),
430 }
431 }
432
433 fn solved(&self, free: Scalar) -> Option<Scalar> {
434 if self.cell.bridge.is_some() {
435 return self.curve.solve_cell(self.cell, free);
436 }
437 let one = ImplicitCurve2 {
438 field: self.curve.field.clone(),
439 cells: vec![ImplicitCell {
440 from: free,
441 to: free,
442 ..*self.cell
443 }],
444 };
445 let p = one.point(0.0)?;
446 Some(match self.cell.axis {
447 Axis::U => p.y,
448 Axis::V => p.x,
449 })
450 }
451
452 fn h(&self, s: Scalar) -> Option<Scalar> {
453 let free = self.free(s);
454 Some(self.other.value(self.place(free, self.solved(free)?)))
455 }
456
457 fn hull(&self, s0: Scalar, s1: Scalar) -> Option<(Cell, Range)> {
460 let (f0, f1) = (self.free(s0), self.free(s1));
461 let (f_lo, f_hi) = (f0.min(f1), f0.max(f1));
462 let w0 = self.solved(f0)?;
463 let make = |w_lo: Scalar, w_hi: Scalar| {
464 let (a, b) = (self.place(f_lo, w_lo), self.place(f_hi, w_hi));
465 Cell {
466 lo: a.min(b),
467 hi: a.max(b),
468 }
469 };
470 let (d_free, d_solved) = match self.cell.axis {
471 Axis::U => (self.d_u, self.d_v),
472 Axis::V => (self.d_v, self.d_u),
473 };
474 let whole = make(self.cell.low, self.cell.high);
475 let free = bound_simple(d_free, &whole);
476 let solved = bound_simple(d_solved, &whole);
477 if solved.straddles_zero() {
478 return None;
479 }
480 let floor = solved.lo.abs().min(solved.hi.abs());
481 let big = free.lo.abs().max(free.hi.abs());
482 let reach = big / floor * (f_hi - f_lo);
483 let hull = make(
484 (w0 - reach).max(self.cell.low),
485 (w0 + reach).min(self.cell.high),
486 );
487 let free = bound_simple(d_free, &hull);
489 let solved = bound_simple(d_solved, &hull);
490 let q = [
491 -free.lo / solved.lo,
492 -free.lo / solved.hi,
493 -free.hi / solved.lo,
494 -free.hi / solved.hi,
495 ];
496 let slope = Range {
497 lo: q.iter().copied().fold(Scalar::INFINITY, Scalar::min),
498 hi: q.iter().copied().fold(Scalar::NEG_INFINITY, Scalar::max),
499 };
500 Some((hull, slope))
501 }
502
503 fn roots(&self, s0: Scalar, s1: Scalar, depth: u32, out: &mut Vec<(Scalar, usize)>) {
504 if self.cell.bridge.is_some() {
507 let n = 64;
508 let at = |k: usize| s0 + (s1 - s0) * k as Scalar / n as Scalar;
509 let mut last = self.h(at(0));
510 for k in 1..=n {
511 let now = self.h(at(k));
512 if let (Some(a), Some(b)) = (last, now) {
513 if a == 0.0 && k == 1 {
514 out.push((at(0), 1));
515 } else if (a < 0.0) != (b < 0.0) && b != 0.0 {
516 out.push((self.bisect(at(k - 1), at(k), a), 1));
517 } else if b == 0.0 {
518 out.push((at(k), 1));
519 }
520 }
521 last = now;
522 }
523 return;
524 }
525 let Some((hull, slope)) = self.hull(s0, s1) else {
526 return;
527 };
528 if !bound_simple(self.other, &hull).straddles_zero() {
529 return;
530 }
531 let (Some(a), Some(b)) = (self.h(s0), self.h(s1)) else {
532 return;
533 };
534 let (h_free, h_solved) = match self.cell.axis {
536 Axis::U => (self.h_u, self.h_v),
537 Axis::V => (self.h_v, self.h_u),
538 };
539 let hf = bound_simple(h_free, &hull);
540 let hs = bound_simple(h_solved, &hull);
541 let p = [
542 hs.lo * slope.lo,
543 hs.lo * slope.hi,
544 hs.hi * slope.lo,
545 hs.hi * slope.hi,
546 ];
547 let lo = hf.lo + p.iter().copied().fold(Scalar::INFINITY, Scalar::min);
548 let hi = hf.hi + p.iter().copied().fold(Scalar::NEG_INFINITY, Scalar::max);
549 let monotone = lo > 0.0 || hi < 0.0;
550 if monotone {
551 if a == 0.0 {
552 out.push((s0, 1));
553 } else if (a < 0.0) != (b < 0.0) {
554 out.push((self.bisect(s0, s1, a), 1));
555 }
556 return;
557 }
558 if depth >= 44 || s1 - s0 <= 1e-13 {
559 if (a < 0.0) != (b < 0.0) {
560 out.push((self.bisect(s0, s1, a), 1));
561 } else {
562 let scale = 1e-9 * self.other.magnitude().max(1.0);
564 if a.abs().min(b.abs()) <= scale {
565 out.push((0.5 * (s0 + s1), 2));
566 }
567 }
568 return;
569 }
570 let m = 0.5 * (s0 + s1);
571 self.roots(s0, m, depth + 1, out);
572 self.roots(m, s1, depth + 1, out);
573 }
574
575 fn bisect(&self, mut s0: Scalar, mut s1: Scalar, a: Scalar) -> Scalar {
576 let negative = a < 0.0;
577 for _ in 0..100 {
578 let m = 0.5 * (s0 + s1);
579 if m <= s0 || m >= s1 {
580 break;
581 }
582 match self.h(m) {
583 Some(0.0) => return m,
584 Some(h) if (h < 0.0) == negative => s0 = m,
585 Some(_) => s1 = m,
586 None => break,
587 }
588 }
589 0.5 * (s0 + s1)
590 }
591}
592
593pub fn conic_spline_intersection(
604 curve: &Curve3,
605 surface: &Surface,
606) -> Result<ExactCurveIntersection, ExactCurveRefusal> {
607 use axiolid_core::Frame3;
608 use axiolid_surface::{EllipticalCylinder, Plane, Sphere};
609 if !matches!(surface, Surface::BSpline(_)) {
610 return Err(ExactCurveRefusal::UnsupportedSurface);
611 }
612 let plane = |origin: axiolid_core::Point3, z: axiolid_core::Vec3| -> Surface {
613 let z = z.normalize();
614 let helper = if z.x.abs() < 0.9 {
615 axiolid_core::Vec3::X
616 } else {
617 axiolid_core::Vec3::Y
618 };
619 let x = helper.cross(z).normalize();
620 Surface::Plane(Plane {
621 frame: Frame3 {
622 origin,
623 x,
624 y: z.cross(x),
625 z,
626 },
627 })
628 };
629 let (first, second) = match curve {
630 Curve3::Line(l) => {
631 let d = l.direction.normalize();
632 let helper = if d.x.abs() < 0.9 {
633 axiolid_core::Vec3::X
634 } else {
635 axiolid_core::Vec3::Y
636 };
637 let n1 = d.cross(helper).normalize();
638 (plane(l.origin, n1), plane(l.origin, d.cross(n1)))
639 }
640 Curve3::Circle(c) => (
641 plane(c.frame.origin, c.frame.x.cross(c.frame.y)),
642 Surface::Sphere(Sphere {
643 frame: c.frame,
644 radius: c.radius,
645 }),
646 ),
647 Curve3::Ellipse(e) => (
648 plane(e.frame.origin, e.frame.x.cross(e.frame.y)),
649 Surface::EllipticalCylinder(EllipticalCylinder {
650 frame: Frame3 {
651 z: e.frame.x.cross(e.frame.y).normalize(),
652 ..e.frame
653 },
654 semi_axis_x: e.semi_axis_x,
655 semi_axis_y: e.semi_axis_y,
656 }),
657 ),
658 _ => return Err(ExactCurveRefusal::UnsupportedCurve),
659 };
660 let pieces = match crate::implicit_section::implicit_surface_intersection(surface, &first, None)
661 {
662 Ok(pieces) => pieces,
663 Err(ExactIntersectionRefusal::Disjoint) => {
664 return Ok(ExactCurveIntersection::Points(Vec::new()))
665 }
666 Err(_) => return Err(ExactCurveRefusal::UnsupportedSurface),
667 };
668 let mut hits = Vec::new();
669 for piece in pieces {
670 let end = piece.curve.end();
671 let traced = Curve3::ImplicitSection(piece);
672 match section_curve_surface_intersection(&traced, Interval::new(0.0, end), &second)? {
673 ExactCurveIntersection::Points(found) => {
674 for hit in found {
675 let t = match curve {
676 Curve3::Line(l) => {
677 (hit.point - l.origin).dot(l.direction) / l.direction.length_squared()
678 }
679 _ => match axiolid_evaluate::curve::invert3(
680 curve,
681 hit.point,
682 axiolid_core::Tolerance::METRE,
683 ) {
684 Ok(t) => t,
685 Err(_) => continue,
686 },
687 };
688 hits.push(ExactCurveHit {
689 parameter: ExactCurveParameter::Certified(Isolated::new(t)),
690 ..hit
691 });
692 }
693 }
694 _ => return Ok(ExactCurveIntersection::Contained),
697 }
698 }
699 hits.sort_by(|a, b| a.parameter.approx().total_cmp(&b.parameter.approx()));
700 Ok(ExactCurveIntersection::Points(hits))
701}