1use axiolid_core::{Frame3, Point3, Vec3};
39use axiolid_curve::{Curve2, Curve3};
40use axiolid_evaluate::evaluate3;
41use axiolid_exact::{Arith, Dyadic, IntPoly, RealRoot};
42use axiolid_guarantees::Sign;
43use axiolid_surface::Surface;
44
45#[derive(Debug, Clone, PartialEq, Eq)]
47#[non_exhaustive]
48pub enum ExactCurveRefusal {
49 UnsupportedCurve,
52 UnsupportedSurface,
54 NonFinite,
56 Degenerate,
59 PartialOverlap,
63 Undecided,
68}
69
70#[derive(Debug, Clone, PartialEq, Eq)]
72#[non_exhaustive]
73pub enum ExactCurveParameter {
74 Line(RealRoot),
76 HalfAngle(RealRoot),
78 Antipode,
80 Certified(Isolated),
83}
84
85#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
88pub struct Isolated(u64);
89
90impl Isolated {
91 #[must_use]
93 pub fn new(value: f64) -> Self {
94 Self(value.to_bits())
95 }
96
97 #[must_use]
99 pub fn value(self) -> f64 {
100 f64::from_bits(self.0)
101 }
102}
103
104impl ExactCurveParameter {
105 #[must_use]
108 pub fn approx(&self) -> f64 {
109 match self {
110 Self::Line(t) => t.approx(),
111 Self::HalfAngle(w) => {
112 let theta = 2.0 * w.approx().atan();
113 if theta < 0.0 {
114 theta + std::f64::consts::TAU
115 } else {
116 theta
117 }
118 }
119 Self::Antipode => std::f64::consts::PI,
120 Self::Certified(t) => t.value(),
121 }
122 }
123}
124
125#[derive(Debug, Clone, PartialEq)]
127#[non_exhaustive]
128pub struct ExactCurveHit {
129 pub parameter: ExactCurveParameter,
131 pub multiplicity: usize,
138 pub point: Point3,
141}
142
143impl ExactCurveHit {
144 #[must_use]
146 pub fn is_tangent(&self) -> bool {
147 self.multiplicity >= 2
148 }
149}
150
151#[derive(Debug, Clone, PartialEq)]
153#[non_exhaustive]
154pub enum ExactCurveIntersection {
155 Contained,
157 Points(Vec<ExactCurveHit>),
160}
161
162pub fn exact_curve_surface_intersection(
171 curve: &Curve3,
172 surface: &Surface,
173) -> Result<ExactCurveIntersection, ExactCurveRefusal> {
174 if let Curve3::ImplicitSection(section) = curve {
178 let span = axiolid_core::Interval::new(0.0, section.curve.end());
179 return crate::implicit_ops::section_curve_surface_intersection(curve, span, surface);
180 }
181 if let (Curve3::BSpline(c), Surface::BSpline(s)) = (curve, surface) {
184 let hits = crate::pair_trace::spline_curve_surface_hits(c, s)
185 .ok_or(ExactCurveRefusal::UnsupportedCurve)?;
186 return Ok(ExactCurveIntersection::Points(
187 hits.into_iter()
188 .map(|(t, point)| ExactCurveHit {
189 parameter: ExactCurveParameter::Certified(Isolated::new(t)),
190 multiplicity: 1,
191 point,
192 })
193 .collect(),
194 ));
195 }
196 if matches!(surface, Surface::BSpline(_)) {
198 return crate::implicit_ops::conic_spline_intersection(curve, surface);
199 }
200 let param = Param::of(curve)?;
201 let locus = surface_locus(surface, ¶m)?;
202 let result = solve(curve, ¶m, &locus);
203 if matches!(result, ExactCurveIntersection::Contained) {
204 if let Some(condition) = &locus.condition {
205 if !nonnegative_everywhere(condition) {
206 return Err(ExactCurveRefusal::PartialOverlap);
207 }
208 }
209 }
210 Ok(result)
211}
212
213fn nonnegative_everywhere(c: &DPoly) -> bool {
219 let int = c.to_int();
220 if int.is_zero() {
221 return true;
222 }
223 let odd = int
224 .real_roots()
225 .iter()
226 .any(|root| multiplicity(root, &int) % 2 == 1);
227 !odd && c.lead_sign() != Sign::Negative
228}
229
230pub fn exact_curve_curve_intersection3(
238 first: &Curve3,
239 second: &Curve3,
240) -> Result<ExactCurveIntersection, ExactCurveRefusal> {
241 let param = Param::of(first)?;
242 let locus = curve_locus(second, ¶m)?;
243 Ok(solve(first, ¶m, &locus))
244}
245
246pub fn exact_curve_curve_intersection2(
254 first: &Curve2,
255 second: &Curve2,
256) -> Result<ExactCurveIntersection, ExactCurveRefusal> {
257 exact_curve_curve_intersection3(&lift(first)?, &lift(second)?)
258}
259
260#[derive(Debug, Clone)]
264struct DPoly(Vec<Dyadic>);
265
266impl DPoly {
267 fn constant(c: Dyadic) -> Self {
268 Self(vec![c])
269 }
270
271 fn add(&self, other: &Self) -> Self {
272 let n = self.0.len().max(other.0.len());
273 Self(
274 (0..n)
275 .map(|i| match (self.0.get(i), other.0.get(i)) {
276 (Some(a), Some(b)) => a.add(b),
277 (Some(a), None) | (None, Some(a)) => a.clone(),
278 (None, None) => Dyadic::zero(),
279 })
280 .collect(),
281 )
282 }
283
284 fn scale(&self, c: &Dyadic) -> Self {
285 Self(self.0.iter().map(|a| a.mul(c)).collect())
286 }
287
288 fn sub(&self, other: &Self) -> Self {
289 self.add(&other.scale(&dy(-1.0)))
290 }
291
292 fn mul(&self, other: &Self) -> Self {
293 if self.0.is_empty() || other.0.is_empty() {
294 return Self(Vec::new());
295 }
296 let mut out = vec![Dyadic::zero(); self.0.len() + other.0.len() - 1];
297 for (i, a) in self.0.iter().enumerate() {
298 for (j, b) in other.0.iter().enumerate() {
299 out[i + j] = out[i + j].add(&a.mul(b));
300 }
301 }
302 Self(out)
303 }
304
305 fn square(&self) -> Self {
306 self.mul(self)
307 }
308
309 fn to_int(&self) -> IntPoly {
310 IntPoly::from_dyadic(&self.0)
311 }
312
313 fn lead_sign(&self) -> Sign {
316 self.0
317 .iter()
318 .rev()
319 .filter_map(Arith::sign)
320 .find(|s| *s != Sign::Zero)
321 .unwrap_or(Sign::Zero)
322 }
323}
324
325fn dy(value: f64) -> Dyadic {
326 Dyadic::from_f64(value)
327}
328
329type V3 = [Dyadic; 3];
330
331fn v3(v: Vec3) -> V3 {
332 [dy(v.x), dy(v.y), dy(v.z)]
333}
334
335fn dot(a: &V3, b: &V3) -> Dyadic {
336 a[0].mul(&b[0]).add(&a[1].mul(&b[1])).add(&a[2].mul(&b[2]))
337}
338
339fn cross(a: &V3, b: &V3) -> V3 {
340 [
341 a[1].mul(&b[2]).sub(&a[2].mul(&b[1])),
342 a[2].mul(&b[0]).sub(&a[0].mul(&b[2])),
343 a[0].mul(&b[1]).sub(&a[1].mul(&b[0])),
344 ]
345}
346
347fn is_zero(v: &Dyadic) -> bool {
348 v.sign() == Some(Sign::Zero)
349}
350
351fn finite(values: &[f64]) -> Result<(), ExactCurveRefusal> {
352 if values.iter().all(|v| v.is_finite()) {
353 Ok(())
354 } else {
355 Err(ExactCurveRefusal::NonFinite)
356 }
357}
358
359fn positive(values: &[f64]) -> Result<(), ExactCurveRefusal> {
360 finite(values)?;
361 if values.iter().all(|v| *v > 0.0) {
362 Ok(())
363 } else {
364 Err(ExactCurveRefusal::Degenerate)
365 }
366}
367
368fn frame_finite(f: &Frame3) -> Result<(), ExactCurveRefusal> {
369 finite(&[
370 f.origin.x, f.origin.y, f.origin.z, f.x.x, f.x.y, f.x.z, f.y.x, f.y.y, f.y.z, f.z.x, f.z.y,
371 f.z.z,
372 ])
373}
374
375struct Param {
379 n: [DPoly; 3],
380 w: DPoly,
381 w_degree: usize,
383}
384
385impl Param {
386 fn of(curve: &Curve3) -> Result<Self, ExactCurveRefusal> {
387 match curve {
388 Curve3::Line(l) => {
389 finite(&[
390 l.origin.x,
391 l.origin.y,
392 l.origin.z,
393 l.direction.x,
394 l.direction.y,
395 l.direction.z,
396 ])?;
397 let (o, d) = (v3(l.origin), v3(l.direction));
398 if d.iter().all(is_zero) {
399 return Err(ExactCurveRefusal::Degenerate);
400 }
401 Ok(Self {
402 n: [0, 1, 2].map(|i| DPoly(vec![o[i].clone(), d[i].clone()])),
403 w: DPoly::constant(dy(1.0)),
404 w_degree: 0,
405 })
406 }
407 Curve3::Circle(c) => Self::conic(&c.frame, c.radius, c.radius),
408 Curve3::Ellipse(e) => Self::conic(&e.frame, e.semi_axis_x, e.semi_axis_y),
409 _ => Err(ExactCurveRefusal::UnsupportedCurve),
410 }
411 }
412
413 fn conic(frame: &Frame3, a: f64, b: f64) -> Result<Self, ExactCurveRefusal> {
416 frame_finite(frame)?;
417 positive(&[a, b])?;
418 let (o, x, y) = (v3(frame.origin), v3(frame.x), v3(frame.y));
419 if cross(&x, &y).iter().all(is_zero) {
420 return Err(ExactCurveRefusal::Degenerate);
421 }
422 let (a, b) = (dy(a), dy(b));
423 let n = [0, 1, 2].map(|i| {
424 let xa = x[i].mul(&a);
425 DPoly(vec![
426 o[i].add(&xa),
427 dy(2.0).mul(&y[i]).mul(&b),
428 o[i].sub(&xa),
429 ])
430 });
431 Ok(Self {
432 n,
433 w: DPoly(vec![dy(1.0), Dyadic::zero(), dy(1.0)]),
434 w_degree: 2,
435 })
436 }
437
438 fn linear(&self, u: &V3, o: &V3) -> DPoly {
440 let mut out = self.w.scale(&dot(u, o).neg());
441 for (n, c) in self.n.iter().zip(u) {
442 out = out.add(&n.scale(c));
443 }
444 out
445 }
446}
447
448struct Locus {
453 equations: Vec<(DPoly, usize)>,
454 condition: Option<DPoly>,
455}
456
457struct Local {
460 alpha: DPoly,
461 beta: DPoly,
462 gamma: DPoly,
463 det: Dyadic,
464}
465
466fn local(frame: (&V3, &V3, &V3, &V3), param: &Param) -> Result<Local, ExactCurveRefusal> {
467 let (o, x, y, z) = frame;
468 let det = dot(x, &cross(y, z));
469 if is_zero(&det) {
470 return Err(ExactCurveRefusal::Degenerate);
471 }
472 Ok(Local {
473 alpha: param.linear(&cross(y, z), o),
474 beta: param.linear(&cross(z, x), o),
475 gamma: param.linear(&cross(x, y), o),
476 det,
477 })
478}
479
480fn frame_of(f: &Frame3) -> Result<(V3, V3, V3, V3), ExactCurveRefusal> {
481 frame_finite(f)?;
482 Ok((v3(f.origin), v3(f.x), v3(f.y), v3(f.z)))
483}
484
485fn surface_locus(surface: &Surface, param: &Param) -> Result<Locus, ExactCurveRefusal> {
486 let one = |p: DPoly, k: usize| Locus {
487 equations: vec![(p, k)],
488 condition: None,
489 };
490 let dw2 = |det: &Dyadic| param.w.scale(det).square();
492 match surface {
493 Surface::Plane(p) => {
494 let (o, x, y, _) = frame_of(&p.frame)?;
495 let normal = cross(&x, &y);
496 if normal.iter().all(is_zero) {
497 return Err(ExactCurveRefusal::Degenerate);
498 }
499 Ok(one(param.linear(&normal, &o), 1))
500 }
501 Surface::Cylinder(c) => {
502 positive(&[c.radius])?;
503 let f = frame_of(&c.frame)?;
504 let l = local((&f.0, &f.1, &f.2, &f.3), param)?;
505 let r2 = dy(c.radius).square();
506 let p = l
507 .alpha
508 .square()
509 .add(&l.beta.square())
510 .sub(&dw2(&l.det).scale(&r2));
511 Ok(one(p, 2))
512 }
513 Surface::EllipticalCylinder(c) => {
514 positive(&[c.semi_axis_x, c.semi_axis_y])?;
515 let f = frame_of(&c.frame)?;
516 let l = local((&f.0, &f.1, &f.2, &f.3), param)?;
517 let (a2, b2) = (dy(c.semi_axis_x).square(), dy(c.semi_axis_y).square());
518 let p = l
519 .alpha
520 .square()
521 .scale(&b2)
522 .add(&l.beta.square().scale(&a2))
523 .sub(&dw2(&l.det).scale(&a2.mul(&b2)));
524 Ok(one(p, 2))
525 }
526 Surface::Sphere(s) => {
527 positive(&[s.radius])?;
528 let f = frame_of(&s.frame)?;
529 let l = local((&f.0, &f.1, &f.2, &f.3), param)?;
530 let r2 = dy(s.radius).square();
531 let p = l
532 .alpha
533 .square()
534 .add(&l.beta.square())
535 .add(&l.gamma.square())
536 .sub(&dw2(&l.det).scale(&r2));
537 Ok(one(p, 2))
538 }
539 Surface::Cone(c) => {
540 let slope = c.semi_angle.tan();
541 finite(&[c.radius, c.semi_angle, slope])?;
542 let f = frame_of(&c.frame)?;
543 let l = local((&f.0, &f.1, &f.2, &f.3), param)?;
544 let reach = param
546 .w
547 .scale(&dy(c.radius).mul(&l.det))
548 .add(&l.gamma.scale(&dy(slope)));
549 let p = l.alpha.square().add(&l.beta.square()).sub(&reach.square());
550 let condition = if l.det.sign() == Some(Sign::Negative) {
553 reach.scale(&dy(-1.0))
554 } else {
555 reach
556 };
557 Ok(Locus {
558 equations: vec![(p, 2)],
559 condition: Some(condition),
560 })
561 }
562 Surface::Torus(t) => {
563 positive(&[t.major_radius, t.minor_radius])?;
564 let f = frame_of(&t.frame)?;
565 let l = local((&f.0, &f.1, &f.2, &f.3), param)?;
566 let (big2, small2) = (dy(t.major_radius).square(), dy(t.minor_radius).square());
567 let planar = l.alpha.square().add(&l.beta.square());
568 let dw2 = dw2(&l.det);
569 let s = planar
571 .add(&l.gamma.square())
572 .add(&dw2.scale(&big2.sub(&small2)));
573 let p = s.square().sub(&dw2.mul(&planar).scale(&dy(4.0).mul(&big2)));
574 Ok(one(p, 4))
575 }
576 _ => Err(ExactCurveRefusal::UnsupportedSurface),
577 }
578}
579
580fn curve_locus(curve: &Curve3, param: &Param) -> Result<Locus, ExactCurveRefusal> {
581 match curve {
582 Curve3::Line(l) => {
583 finite(&[
584 l.origin.x,
585 l.origin.y,
586 l.origin.z,
587 l.direction.x,
588 l.direction.y,
589 l.direction.z,
590 ])?;
591 let (o, d) = (v3(l.origin), v3(l.direction));
592 if d.iter().all(is_zero) {
593 return Err(ExactCurveRefusal::Degenerate);
594 }
595 let z = Dyadic::zero;
597 let rows = [
598 [z(), d[2].clone(), d[1].neg()],
599 [d[2].neg(), z(), d[0].clone()],
600 [d[1].clone(), d[0].neg(), z()],
601 ];
602 Ok(Locus {
603 equations: rows.iter().map(|u| (param.linear(u, &o), 1)).collect(),
604 condition: None,
605 })
606 }
607 Curve3::Circle(c) => conic_locus(&c.frame, c.radius, c.radius, param),
608 Curve3::Ellipse(e) => conic_locus(&e.frame, e.semi_axis_x, e.semi_axis_y, param),
609 _ => Err(ExactCurveRefusal::UnsupportedCurve),
610 }
611}
612
613fn conic_locus(frame: &Frame3, a: f64, b: f64, param: &Param) -> Result<Locus, ExactCurveRefusal> {
616 positive(&[a, b])?;
617 let (o, x, y, _) = frame_of(frame)?;
618 let z = cross(&x, &y);
619 let l = local((&o, &x, &y, &z), param)?;
620 let (a2, b2) = (dy(a).square(), dy(b).square());
621 let ring = l
622 .alpha
623 .square()
624 .scale(&b2)
625 .add(&l.beta.square().scale(&a2))
626 .sub(¶m.w.scale(&l.det).square().scale(&a2.mul(&b2)));
627 Ok(Locus {
628 equations: vec![(l.gamma, 1), (ring, 2)],
629 condition: None,
630 })
631}
632
633fn lift(curve: &Curve2) -> Result<Curve3, ExactCurveRefusal> {
634 use axiolid_curve::{Circle3, Ellipse3, Line3};
635 let frame = |f: &axiolid_core::Frame2| Frame3 {
636 origin: Point3::new(f.origin.x, f.origin.y, 0.0),
637 x: Vec3::new(f.x.x, f.x.y, 0.0),
638 y: Vec3::new(f.y.x, f.y.y, 0.0),
639 z: Vec3::Z,
640 };
641 Ok(match curve {
642 Curve2::Line(l) => Curve3::Line(Line3 {
643 origin: Point3::new(l.origin.x, l.origin.y, 0.0),
644 direction: Vec3::new(l.direction.x, l.direction.y, 0.0),
645 }),
646 Curve2::Circle(c) => Curve3::Circle(Circle3 {
647 frame: frame(&c.frame),
648 radius: c.radius,
649 }),
650 Curve2::Ellipse(e) => Curve3::Ellipse(Ellipse3 {
651 frame: frame(&e.frame),
652 semi_axis_x: e.semi_axis_x,
653 semi_axis_y: e.semi_axis_y,
654 }),
655 _ => return Err(ExactCurveRefusal::UnsupportedCurve),
656 })
657}
658
659fn multiplicity(root: &RealRoot, g: &IntPoly) -> usize {
664 let mut order = 1;
665 let mut q = g.derivative();
666 while !q.is_zero() && root.sign_of(&q) == Sign::Zero {
667 order += 1;
668 q = q.derivative();
669 }
670 order
671}
672
673fn solve(curve: &Curve3, param: &Param, locus: &Locus) -> ExactCurveIntersection {
674 let polys: Vec<(IntPoly, usize)> = locus
678 .equations
679 .iter()
680 .map(|(p, k)| (p.to_int(), k * param.w_degree))
681 .filter(|(p, _)| !p.is_zero())
682 .collect();
683 if polys.is_empty() {
684 return ExactCurveIntersection::Contained;
685 }
686 let g = polys
688 .iter()
689 .skip(1)
690 .fold(polys[0].0.clone(), |acc, (p, _)| acc.gcd(p));
691 let condition_int = locus.condition.as_ref().map(DPoly::to_int);
692 let allowed = |root: &RealRoot| {
693 condition_int
694 .as_ref()
695 .is_none_or(|c| c.is_zero() || root.sign_of(c) != Sign::Negative)
696 };
697
698 let is_line = param.w_degree == 0;
699 let mut hits: Vec<(i8, ExactCurveHit)> = Vec::new();
700 for root in g.real_roots() {
701 if !allowed(&root) {
702 continue;
703 }
704 let multiplicity = multiplicity(&root, &g);
705 let band = if is_line || root.cmp_dyadic(&Dyadic::zero()) != Sign::Negative {
708 0
709 } else {
710 2
711 };
712 let parameter = if is_line {
713 ExactCurveParameter::Line(root)
714 } else {
715 ExactCurveParameter::HalfAngle(root)
716 };
717 hits.push((band, hit(curve, parameter, multiplicity)));
718 }
719 if !is_line {
720 let lost = polys
723 .iter()
724 .map(|(p, formal)| formal - p.degree().unwrap_or(0))
725 .min()
726 .unwrap_or(0);
727 let on_nappe = locus.condition.as_ref().is_none_or(|c| {
728 c.0.get(2)
731 .is_none_or(|lead| lead.sign() != Some(Sign::Negative))
732 });
733 if lost >= 1 && on_nappe {
734 hits.push((1, hit(curve, ExactCurveParameter::Antipode, lost)));
735 }
736 }
737 hits.sort_by_key(|(band, _)| *band);
739 ExactCurveIntersection::Points(hits.into_iter().map(|(_, h)| h).collect())
740}
741
742fn hit(curve: &Curve3, parameter: ExactCurveParameter, multiplicity: usize) -> ExactCurveHit {
743 let point =
744 evaluate3(curve, parameter.approx()).unwrap_or(Point3::new(f64::NAN, f64::NAN, f64::NAN));
745 ExactCurveHit {
746 parameter,
747 multiplicity,
748 point,
749 }
750}