1use axiolid_core::{Frame3, Interval, Point3, Scalar, Vec3};
14use axiolid_curve::{Circle3, Curve3, Ellipse3};
15use axiolid_exact::{Arith, Dyadic};
16use axiolid_guarantees::Sign;
17use axiolid_surface::{Cylinder, Plane, Sphere, Surface};
18
19type D3 = [Dyadic; 3];
29
30fn exact3(v: Vec3) -> Option<D3> {
32 Some([
33 Dyadic::try_from_f64(v.x)?,
34 Dyadic::try_from_f64(v.y)?,
35 Dyadic::try_from_f64(v.z)?,
36 ])
37}
38
39fn edot(a: &D3, b: &D3) -> Dyadic {
40 a[0].mul(&b[0]).add(&a[1].mul(&b[1])).add(&a[2].mul(&b[2]))
41}
42
43fn esub(a: &D3, b: &D3) -> D3 {
44 [a[0].sub(&b[0]), a[1].sub(&b[1]), a[2].sub(&b[2])]
45}
46
47fn ecross_is_zero(a: &D3, b: &D3) -> bool {
48 let c = [
49 a[1].mul(&b[2]).sub(&a[2].mul(&b[1])),
50 a[2].mul(&b[0]).sub(&a[0].mul(&b[2])),
51 a[0].mul(&b[1]).sub(&a[1].mul(&b[0])),
52 ];
53 c.iter().all(|v| esign(v) == Sign::Zero)
54}
55
56fn esign(v: &Dyadic) -> Sign {
57 v.sign().expect("dyadic signs are always decided")
58}
59
60fn within_radius(
64 radius: Scalar,
65 normal: Vec3,
66 point: Point3,
67 plane_origin: Point3,
68) -> Result<(Dyadic, Dyadic), ExactIntersectionRefusal> {
69 let bad = ExactIntersectionRefusal::DegenerateFrame;
70 let n = exact3(normal).ok_or(bad.clone())?;
71 let c = exact3(point).ok_or(bad.clone())?;
72 let o = exact3(plane_origin).ok_or(bad.clone())?;
73 let r = Dyadic::try_from_f64(radius).ok_or(bad.clone())?;
74 let nn = edot(&n, &n);
75 if esign(&nn) == Sign::Zero {
76 return Err(bad);
77 }
78 let nd = edot(&n, &esub(&c, &o));
79 Ok((r.square().mul(&nn).sub(&nd.square()), nn))
80}
81
82fn rounded_square(numerator: &Dyadic, nn: &Dyadic) -> Result<Scalar, ExactIntersectionRefusal> {
84 let value = numerator.to_f64() / nn.to_f64();
85 if value > 0.0 && value.is_finite() {
86 Ok(value)
87 } else {
88 Err(ExactIntersectionRefusal::DegenerateFrame)
89 }
90}
91
92#[derive(Debug, Clone, PartialEq, Eq)]
94#[non_exhaustive]
95pub enum ExactIntersectionRefusal {
96 UnsupportedPair,
101 Disjoint,
103 NotRegularCurve,
109 DegenerateFrame,
111 UnrepresentableConic,
119 Undecided,
124}
125
126#[derive(Debug, Clone, PartialEq)]
132#[non_exhaustive]
133pub struct ExactIntersectionCurve {
134 pub branches: Vec<Curve3>,
144 pub derivation: Derivation,
146 pub spans: Vec<Option<Interval>>,
152}
153
154impl ExactIntersectionCurve {
155 pub(crate) fn whole(branches: Vec<Curve3>, derivation: Derivation) -> Self {
157 let spans = vec![None; branches.len()];
158 Self {
159 branches,
160 derivation,
161 spans,
162 }
163 }
164
165 pub(crate) fn with_spans(
167 branches: Vec<Curve3>,
168 spans: Vec<Option<Interval>>,
169 derivation: Derivation,
170 ) -> Self {
171 Self {
172 branches,
173 derivation,
174 spans,
175 }
176 }
177
178 pub fn single(&self) -> &Curve3 {
184 assert_eq!(self.branches.len(), 1, "expected one branch");
185 &self.branches[0]
186 }
187}
188
189#[derive(Debug, Clone, Copy, PartialEq, Eq)]
191#[non_exhaustive]
192pub enum Derivation {
193 PlanePlaneLine,
195 CylinderPlanePerpendicularCircle,
198 CylinderPlaneObliqueEllipse,
201 SpherePlaneCircle,
204 SphereSphereCircle,
206 CylinderCylinderSteinmetzEllipses,
208 ParallelCylinderLines,
210 CylinderPlaneParallelRulings,
212 CoaxialRevolutionCircles,
215 RuledQuadricSection,
220 TorusAngleSection,
224 ConeApexRulings,
227 ImplicitTrace,
231 PairTrace,
235}
236
237pub fn exact_surface_intersection(
244 first: &Surface,
245 second: &Surface,
246) -> Result<ExactIntersectionCurve, ExactIntersectionRefusal> {
247 if let (Surface::BSpline(a), Surface::BSpline(b)) = (first, second) {
250 let branches: Vec<Curve3> = crate::pair_trace::spline_pair_intersection(a, b, None)?
251 .into_iter()
252 .map(Curve3::PairSection)
253 .collect();
254 let spans = branches
255 .iter()
256 .map(|c| match c {
257 Curve3::PairSection(s) => Some(Interval::new(0.0, s.end())),
258 _ => None,
259 })
260 .collect();
261 return Ok(ExactIntersectionCurve {
262 branches,
263 derivation: Derivation::PairTrace,
264 spans,
265 });
266 }
267 if let (Surface::Cone(c), Surface::Plane(p)) | (Surface::Plane(p), Surface::Cone(c)) =
269 (first, second)
270 {
271 if let Some(curve) = cone_apex_plane(c, p)? {
272 return Ok(curve);
273 }
274 }
275 match closed_form(first, second) {
276 Ok(curve) => Ok(curve),
277 Err(
279 refusal @ (ExactIntersectionRefusal::Disjoint
280 | ExactIntersectionRefusal::DegenerateFrame),
281 ) => Err(refusal),
282 Err(refusal) => {
285 if let Some(curve) = crate::ruled_section::ruled_section(first, second)? {
286 return Ok(curve);
287 }
288 if let Some(curve) = crate::torus_section::torus_section(first, second)? {
289 return Ok(curve);
290 }
291 match crate::implicit_section::traced_section(first, second)? {
294 Some(curve) => Ok(curve),
295 None => Err(refusal),
296 }
297 }
298 }
299}
300
301fn closed_form(
303 first: &Surface,
304 second: &Surface,
305) -> Result<ExactIntersectionCurve, ExactIntersectionRefusal> {
306 match (first, second) {
307 (Surface::Plane(a), Surface::Plane(b)) => plane_plane(a, b),
308 (Surface::Cylinder(c), Surface::Plane(p)) => cylinder_plane(c, p),
309 (Surface::Plane(p), Surface::Cylinder(c)) => cylinder_plane(c, p),
310 (Surface::Sphere(s), Surface::Plane(p)) => sphere_plane(s, p),
311 (Surface::Plane(p), Surface::Sphere(s)) => sphere_plane(s, p),
312 (Surface::Cone(c), Surface::Plane(p)) | (Surface::Plane(p), Surface::Cone(c)) => {
317 cone_plane(c, p)
318 }
319 (Surface::Sphere(a), Surface::Sphere(b)) => sphere_sphere(a, b),
320 (Surface::Cylinder(a), Surface::Cylinder(b)) => cylinder_cylinder(a, b),
321 _ => crate::revolution_profile::coaxial_revolution_intersection(first, second),
324 }
325}
326
327fn plane_plane(
334 first: &Plane,
335 second: &Plane,
336) -> Result<ExactIntersectionCurve, ExactIntersectionRefusal> {
337 let first_normal = first.frame.z;
338 let second_normal = second.frame.z;
339 let direction = first_normal.cross(second_normal);
340 let direction_squared = direction.dot(direction);
341 if direction_squared == 0.0 {
342 return Err(ExactIntersectionRefusal::Disjoint);
345 }
346 let first_offset = first_normal.dot(first.frame.origin);
347 let second_offset = second_normal.dot(second.frame.origin);
348 let origin = (second_normal.cross(direction) * first_offset
349 + direction.cross(first_normal) * second_offset)
350 / direction_squared;
351 let unit_direction = direction / direction_squared.sqrt();
352 if !origin.is_finite() || !unit_direction.is_finite() {
353 return Err(ExactIntersectionRefusal::DegenerateFrame);
354 }
355 Ok(ExactIntersectionCurve {
356 spans: vec![None],
357 branches: vec![Curve3::Line(axiolid_curve::Line3 {
358 origin,
359 direction: unit_direction,
360 })],
361 derivation: Derivation::PlanePlaneLine,
362 })
363}
364
365fn sphere_plane(
373 sphere: &Sphere,
374 plane: &Plane,
375) -> Result<ExactIntersectionCurve, ExactIntersectionRefusal> {
376 let normal = plane.frame.z;
377 let normal_squared = normal.dot(normal);
378 if normal_squared == 0.0 {
379 return Err(ExactIntersectionRefusal::DegenerateFrame);
380 }
381 let unit_normal = normal / normal_squared.sqrt();
382 let centre = sphere.frame.origin;
383 let signed_distance = unit_normal.dot(centre - plane.frame.origin);
384 let (numerator, nn) = within_radius(sphere.radius, normal, centre, plane.frame.origin)?;
387 match esign(&numerator) {
388 Sign::Positive => {}
389 Sign::Zero => return Err(ExactIntersectionRefusal::NotRegularCurve),
391 _ => return Err(ExactIntersectionRefusal::Disjoint),
392 }
393 let radius_squared = rounded_square(&numerator, &nn)?;
394 let section_centre = centre - unit_normal * signed_distance;
395 let frame = frame_from_normal(section_centre, unit_normal)?;
396 Ok(ExactIntersectionCurve {
397 spans: vec![None],
398 branches: vec![Curve3::Circle(Circle3 {
399 frame,
400 radius: radius_squared.sqrt(),
401 })],
402 derivation: Derivation::SpherePlaneCircle,
403 })
404}
405
406fn cylinder_plane(
417 cylinder: &Cylinder,
418 plane: &Plane,
419) -> Result<ExactIntersectionCurve, ExactIntersectionRefusal> {
420 let axis_squared = cylinder.frame.z.dot(cylinder.frame.z);
421 let normal_squared = plane.frame.z.dot(plane.frame.z);
422 if axis_squared == 0.0 || normal_squared == 0.0 {
423 return Err(ExactIntersectionRefusal::DegenerateFrame);
424 }
425 let axis = cylinder.frame.z / axis_squared.sqrt();
426 let normal = plane.frame.z / normal_squared.sqrt();
427 let bad = ExactIntersectionRefusal::DegenerateFrame;
431 let exact_axis = exact3(cylinder.frame.z).ok_or(bad.clone())?;
432 let exact_normal = exact3(plane.frame.z).ok_or(bad.clone())?;
433 let along = edot(&exact_axis, &exact_normal);
434 if esign(&along) == Sign::Zero {
435 return cylinder_plane_parallel(cylinder, plane, axis, normal);
439 }
440 let perpendicular = ecross_is_zero(&exact_axis, &exact_normal);
441 let cosine = if perpendicular {
446 1.0
447 } else {
448 let scale = (edot(&exact_axis, &exact_axis).to_f64()
449 * edot(&exact_normal, &exact_normal).to_f64())
450 .sqrt();
451 (along.to_f64().abs() / scale).min(1.0_f64.next_down())
452 };
453 if cosine.is_nan() || cosine <= 0.0 {
455 return Err(bad);
456 }
457 let axis_origin = cylinder.frame.origin;
459 let to_plane = normal.dot(plane.frame.origin - axis_origin);
460 let centre = axis_origin + axis * (to_plane / axis.dot(normal));
461 if !centre.is_finite() {
462 return Err(ExactIntersectionRefusal::DegenerateFrame);
463 }
464 Ok(ExactIntersectionCurve {
465 spans: vec![None],
466 branches: vec![cylinder_section_curve(
467 centre,
468 axis,
469 normal,
470 cylinder.radius,
471 cosine,
472 )?],
473 derivation: if perpendicular {
474 Derivation::CylinderPlanePerpendicularCircle
475 } else {
476 Derivation::CylinderPlaneObliqueEllipse
477 },
478 })
479}
480
481fn cylinder_section_curve(
487 centre: Point3,
488 axis: Vec3,
489 normal: Vec3,
490 radius: Scalar,
491 cosine: Scalar,
492) -> Result<Curve3, ExactIntersectionRefusal> {
493 if cosine == 1.0 {
494 let frame = frame_from_normal(centre, normal)?;
495 return Ok(Curve3::Circle(Circle3 { frame, radius }));
496 }
497 let across = axis.cross(normal);
498 let across_squared = across.dot(across);
499 if across_squared == 0.0 {
500 return Err(ExactIntersectionRefusal::DegenerateFrame);
501 }
502 let minor = across / across_squared.sqrt();
503 let major = normal.cross(minor);
504 if !minor.is_finite() || !major.is_finite() {
505 return Err(ExactIntersectionRefusal::DegenerateFrame);
506 }
507 let frame = Frame3 {
508 origin: centre,
509 x: minor,
510 y: major,
511 z: normal,
512 };
513 Ok(Curve3::Ellipse(Ellipse3 {
514 frame,
515 semi_axis_x: radius,
516 semi_axis_y: radius / cosine,
517 }))
518}
519
520pub(crate) fn frame_from_normal(
528 origin: Point3,
529 normal: Vec3,
530) -> Result<Frame3, ExactIntersectionRefusal> {
531 let seed = if normal.x.abs() <= normal.y.abs() && normal.x.abs() <= normal.z.abs() {
532 Vec3::new(1.0, 0.0, 0.0)
533 } else if normal.y.abs() <= normal.z.abs() {
534 Vec3::new(0.0, 1.0, 0.0)
535 } else {
536 Vec3::new(0.0, 0.0, 1.0)
537 };
538 let x_axis = normal.cross(seed);
539 let x_squared = x_axis.dot(x_axis);
540 if x_squared == 0.0 {
541 return Err(ExactIntersectionRefusal::DegenerateFrame);
542 }
543 let x = x_axis / x_squared.sqrt();
544 let y = normal.cross(x);
545 if !x.is_finite() || !y.is_finite() {
546 return Err(ExactIntersectionRefusal::DegenerateFrame);
547 }
548 Ok(Frame3 {
549 origin,
550 x,
551 y,
552 z: normal,
553 })
554}
555
556fn sphere_sphere(
563 first: &Sphere,
564 second: &Sphere,
565) -> Result<ExactIntersectionCurve, ExactIntersectionRefusal> {
566 let separation = second.frame.origin - first.frame.origin;
567 let distance = separation.length();
568 if distance == 0.0 {
569 return Err(if first.radius == second.radius {
571 ExactIntersectionRefusal::NotRegularCurve
572 } else {
573 ExactIntersectionRefusal::Disjoint
574 });
575 }
576 if distance > first.radius + second.radius {
577 return Err(ExactIntersectionRefusal::Disjoint);
578 }
579 if distance < (first.radius - second.radius).abs() {
580 return Err(ExactIntersectionRefusal::Disjoint);
582 }
583 let axis = separation / distance;
584 let along = (distance * distance + first.radius * first.radius - second.radius * second.radius)
585 / (2.0 * distance);
586 let squared = first.radius * first.radius - along * along;
587 if squared <= 0.0 {
588 return Err(ExactIntersectionRefusal::NotRegularCurve);
590 }
591 let centre = first.frame.origin + axis * along;
592 let frame = frame_from_normal(centre, axis)?;
593 Ok(ExactIntersectionCurve {
594 spans: vec![None],
595 branches: vec![Curve3::Circle(Circle3 {
596 frame,
597 radius: squared.sqrt(),
598 })],
599 derivation: Derivation::SphereSphereCircle,
600 })
601}
602
603fn cylinder_cylinder(
620 first: &Cylinder,
621 second: &Cylinder,
622) -> Result<ExactIntersectionCurve, ExactIntersectionRefusal> {
623 let first_axis = first.frame.z;
624 let second_axis = second.frame.z;
625 let cross = first_axis.cross(second_axis);
626 if cross.length() == 0.0 {
627 return parallel_cylinders(first, second, first_axis);
628 }
629 if first.radius != second.radius {
630 return Err(ExactIntersectionRefusal::NotRegularCurve);
631 }
632 let between = second.frame.origin - first.frame.origin;
636 let unit_cross = cross / cross.length();
637 if between.dot(unit_cross) != 0.0 {
638 return Err(ExactIntersectionRefusal::NotRegularCurve);
639 }
640 let denominator = first_axis
642 .dot(second_axis)
643 .mul_add(-first_axis.dot(second_axis), 1.0);
644 if denominator == 0.0 {
645 return Err(ExactIntersectionRefusal::NotRegularCurve);
646 }
647 let along = (between.dot(first_axis) - first_axis.dot(second_axis) * between.dot(second_axis))
648 / denominator;
649 let meeting = first.frame.origin + first_axis * along;
650 let mut branches = Vec::new();
651 for normal in [first_axis - second_axis, first_axis + second_axis] {
652 let length = normal.length();
653 if length == 0.0 {
654 continue;
655 }
656 let unit_normal = normal / length;
657 let cosine = unit_normal.dot(first_axis).abs();
658 if cosine == 0.0 {
659 return Err(ExactIntersectionRefusal::NotRegularCurve);
660 }
661 branches.push(cylinder_section_curve(
662 meeting,
663 first_axis,
664 unit_normal,
665 first.radius,
666 cosine,
667 )?);
668 }
669 Ok(ExactIntersectionCurve::whole(
670 branches,
671 Derivation::CylinderCylinderSteinmetzEllipses,
672 ))
673}
674
675fn parallel_cylinders(
683 first: &Cylinder,
684 second: &Cylinder,
685 axis: Vec3,
686) -> Result<ExactIntersectionCurve, ExactIntersectionRefusal> {
687 let between = second.frame.origin - first.frame.origin;
688 let offset = between - axis * between.dot(axis);
689 let distance = offset.length();
690 if distance == 0.0 {
691 return Err(if first.radius == second.radius {
692 ExactIntersectionRefusal::NotRegularCurve
693 } else {
694 ExactIntersectionRefusal::Disjoint
695 });
696 }
697 if distance > first.radius + second.radius || distance < (first.radius - second.radius).abs() {
698 return Err(ExactIntersectionRefusal::Disjoint);
699 }
700 let toward = offset / distance;
701 let along = (distance * distance + first.radius * first.radius - second.radius * second.radius)
702 / (2.0 * distance);
703 let squared = first.radius * first.radius - along * along;
704 let base = first.frame.origin + toward * along;
705 if squared <= 0.0 {
706 return Ok(ExactIntersectionCurve {
708 spans: vec![None],
709 branches: vec![Curve3::Line(axiolid_curve::Line3 {
710 origin: base,
711 direction: axis,
712 })],
713 derivation: Derivation::ParallelCylinderLines,
714 });
715 }
716 let across = axis.cross(toward);
717 let half = squared.sqrt();
718 let mut branches = Vec::new();
719 for sign in [1.0, -1.0] {
720 branches.push(Curve3::Line(axiolid_curve::Line3 {
721 origin: base + across * (sign * half),
722 direction: axis,
723 }));
724 }
725 Ok(ExactIntersectionCurve::whole(
726 branches,
727 Derivation::ParallelCylinderLines,
728 ))
729}
730
731fn cone_plane(
746 cone: &axiolid_surface::Cone,
747 plane: &axiolid_surface::Plane,
748) -> Result<ExactIntersectionCurve, ExactIntersectionRefusal> {
749 let axis = cone.frame.z;
750 let normal = plane.frame.z;
751 let alignment = axis.dot(normal).abs().clamp(0.0, 1.0);
752 let plane_axis_angle = alignment.asin();
754 let semi = cone.semi_angle.abs();
755 let perpendicular = match (exact3(axis), exact3(normal)) {
756 (Some(a), Some(n)) => ecross_is_zero(&a, &n),
757 _ => return Err(ExactIntersectionRefusal::DegenerateFrame),
758 };
759 if perpendicular {
760 return crate::revolution_profile::coaxial_revolution_intersection(
763 &Surface::Cone(*cone),
764 &Surface::Plane(*plane),
765 );
766 }
767 if plane_axis_angle > semi {
768 return Err(ExactIntersectionRefusal::UnsupportedPair);
769 }
770 Err(ExactIntersectionRefusal::UnrepresentableConic)
771}
772
773fn cone_apex_plane(
785 cone: &axiolid_surface::Cone,
786 plane: &Plane,
787) -> Result<Option<ExactIntersectionCurve>, ExactIntersectionRefusal> {
788 let slope = cone.semi_angle.tan();
789 if slope == 0.0 || !slope.is_finite() {
790 return Ok(None);
791 }
792 let axis = cone.frame.z.normalize();
793 let apex = cone.frame.origin - axis * (cone.radius / slope);
794 let n = plane.frame.z.normalize();
795 let offset = n.dot(apex - plane.frame.origin);
796 let scale = 1.0 + apex.length() + plane.frame.origin.length();
797 if offset.abs() > 1e-12 * scale {
798 return Ok(None);
799 }
800 let z = axis * slope.signum();
802 let x = cone.frame.x.normalize();
803 let y = z.cross(x).normalize();
804 let x = y.cross(z);
805 let alpha = slope.abs().atan();
806 let (sa, ca) = alpha.sin_cos();
807 let (a, b, c) = (sa * n.dot(x), sa * n.dot(y), -ca * n.dot(z));
808 let rr = a * a + b * b;
809 let e = rr - c * c;
810 if e < -1e-14 * (rr + c * c) || rr == 0.0 {
811 return Err(ExactIntersectionRefusal::NotRegularCurve);
813 }
814 let root = e.max(0.0).sqrt();
815 let tangent = e <= 1e-14 * (rr + c * c);
816 let signs: &[Scalar] = if tangent { &[0.0] } else { &[1.0, -1.0] };
817 let mut branches = Vec::new();
818 for s in signs {
819 let (cos_phi, sin_phi) = ((a * c - s * b * root) / rr, (b * c + s * a * root) / rr);
820 let direction = z * ca + (x * cos_phi + y * sin_phi) * sa;
821 if !direction.is_finite() || !apex.is_finite() {
822 return Err(ExactIntersectionRefusal::DegenerateFrame);
823 }
824 branches.push(Curve3::Line(axiolid_curve::Line3 {
825 origin: apex,
826 direction: direction.normalize(),
827 }));
828 }
829 let spans = vec![Some(Interval::new(0.0, Scalar::INFINITY)); branches.len()];
830 Ok(Some(ExactIntersectionCurve {
831 branches,
832 derivation: Derivation::ConeApexRulings,
833 spans,
834 }))
835}
836
837fn cylinder_plane_parallel(
846 cylinder: &Cylinder,
847 plane: &Plane,
848 axis: Vec3,
849 normal: Vec3,
850) -> Result<ExactIntersectionCurve, ExactIntersectionRefusal> {
851 let distance = normal.dot(cylinder.frame.origin - plane.frame.origin);
852 let (numerator, nn) = within_radius(
854 cylinder.radius,
855 plane.frame.z,
856 cylinder.frame.origin,
857 plane.frame.origin,
858 )?;
859 let tangent_plane = match esign(&numerator) {
860 Sign::Positive => false,
861 Sign::Zero => true,
862 _ => return Err(ExactIntersectionRefusal::Disjoint),
863 };
864 let tangent = axis.cross(normal);
867 let foot = cylinder.frame.origin - normal * distance;
868 if !foot.is_finite() || !tangent.is_finite() {
869 return Err(ExactIntersectionRefusal::DegenerateFrame);
870 }
871 let half_chord = if tangent_plane {
872 0.0
873 } else {
874 rounded_square(&numerator, &nn)?.sqrt()
875 };
876 let mut branches = Vec::new();
877 let offsets: &[Scalar] = if tangent_plane {
878 &[0.0]
879 } else {
880 &[half_chord, -half_chord]
881 };
882 branches
883 .try_reserve_exact(offsets.len())
884 .map_err(|_| ExactIntersectionRefusal::DegenerateFrame)?;
885 for offset in offsets {
886 branches.push(Curve3::Line(axiolid_curve::Line3 {
887 origin: foot + tangent * *offset,
888 direction: axis,
889 }));
890 }
891 Ok(ExactIntersectionCurve::whole(
892 branches,
893 Derivation::CylinderPlaneParallelRulings,
894 ))
895}