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, EllipticalCylinder, 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 EllipticalCylinderPlaneSection,
241 EllipticalCylinderPlaneRulings,
245 ParallelEllipticalCylinderRulings,
250 TorusMeridianCircles,
253}
254
255pub fn exact_surface_intersection(
262 first: &Surface,
263 second: &Surface,
264) -> Result<ExactIntersectionCurve, ExactIntersectionRefusal> {
265 if let (Surface::BSpline(a), Surface::BSpline(b)) = (first, second) {
268 let branches: Vec<Curve3> = crate::pair_trace::spline_pair_intersection(a, b, None)?
269 .into_iter()
270 .map(Curve3::PairSection)
271 .collect();
272 let spans = branches
273 .iter()
274 .map(|c| match c {
275 Curve3::PairSection(s) => Some(Interval::new(0.0, s.end())),
276 _ => None,
277 })
278 .collect();
279 return Ok(ExactIntersectionCurve {
280 branches,
281 derivation: Derivation::PairTrace,
282 spans,
283 });
284 }
285 if let (Surface::Cone(c), Surface::Plane(p)) | (Surface::Plane(p), Surface::Cone(c)) =
287 (first, second)
288 {
289 if let Some(curve) = cone_apex_plane(c, p)? {
290 return Ok(curve);
291 }
292 }
293 match closed_form(first, second) {
294 Ok(curve) => Ok(curve),
295 Err(
297 refusal @ (ExactIntersectionRefusal::Disjoint
298 | ExactIntersectionRefusal::DegenerateFrame),
299 ) => Err(refusal),
300 Err(refusal) => {
303 if let Some(curve) = crate::ruled_section::ruled_section(first, second)? {
304 return Ok(curve);
305 }
306 if let Some(curve) = crate::torus_section::torus_section(first, second)? {
307 return Ok(curve);
308 }
309 match crate::implicit_section::traced_section(first, second)? {
312 Some(curve) => Ok(curve),
313 None => Err(refusal),
314 }
315 }
316 }
317}
318
319fn closed_form(
321 first: &Surface,
322 second: &Surface,
323) -> Result<ExactIntersectionCurve, ExactIntersectionRefusal> {
324 match (first, second) {
325 (Surface::Plane(a), Surface::Plane(b)) => plane_plane(a, b),
326 (Surface::Cylinder(c), Surface::Plane(p)) => cylinder_plane(c, p),
327 (Surface::Plane(p), Surface::Cylinder(c)) => cylinder_plane(c, p),
328 (Surface::Sphere(s), Surface::Plane(p)) => sphere_plane(s, p),
329 (Surface::Plane(p), Surface::Sphere(s)) => sphere_plane(s, p),
330 (Surface::Cone(c), Surface::Plane(p)) | (Surface::Plane(p), Surface::Cone(c)) => {
335 cone_plane(c, p)
336 }
337 (Surface::EllipticalCylinder(c), Surface::Plane(p))
338 | (Surface::Plane(p), Surface::EllipticalCylinder(c)) => elliptical_cylinder_plane(c, p),
339 (Surface::Sphere(a), Surface::Sphere(b)) => sphere_sphere(a, b),
340 (Surface::Cylinder(a), Surface::Cylinder(b)) => cylinder_cylinder(a, b),
341 _ => crate::revolution_profile::coaxial_revolution_intersection(first, second),
344 }
345}
346
347fn plane_plane(
354 first: &Plane,
355 second: &Plane,
356) -> Result<ExactIntersectionCurve, ExactIntersectionRefusal> {
357 let first_normal = first.frame.z;
358 let second_normal = second.frame.z;
359 let direction = first_normal.cross(second_normal);
360 let direction_squared = direction.dot(direction);
361 if direction_squared == 0.0 {
362 return Err(ExactIntersectionRefusal::Disjoint);
365 }
366 let first_offset = first_normal.dot(first.frame.origin);
367 let second_offset = second_normal.dot(second.frame.origin);
368 let origin = (second_normal.cross(direction) * first_offset
369 + direction.cross(first_normal) * second_offset)
370 / direction_squared;
371 let unit_direction = direction / direction_squared.sqrt();
372 if !origin.is_finite() || !unit_direction.is_finite() {
373 return Err(ExactIntersectionRefusal::DegenerateFrame);
374 }
375 Ok(ExactIntersectionCurve {
376 spans: vec![None],
377 branches: vec![Curve3::Line(axiolid_curve::Line3 {
378 origin,
379 direction: unit_direction,
380 })],
381 derivation: Derivation::PlanePlaneLine,
382 })
383}
384
385fn sphere_plane(
393 sphere: &Sphere,
394 plane: &Plane,
395) -> Result<ExactIntersectionCurve, ExactIntersectionRefusal> {
396 let normal = plane.frame.z;
397 let normal_squared = normal.dot(normal);
398 if normal_squared == 0.0 {
399 return Err(ExactIntersectionRefusal::DegenerateFrame);
400 }
401 let unit_normal = normal / normal_squared.sqrt();
402 let centre = sphere.frame.origin;
403 let signed_distance = unit_normal.dot(centre - plane.frame.origin);
404 let (numerator, nn) = within_radius(sphere.radius, normal, centre, plane.frame.origin)?;
407 match esign(&numerator) {
408 Sign::Positive => {}
409 Sign::Zero => return Err(ExactIntersectionRefusal::NotRegularCurve),
411 _ => return Err(ExactIntersectionRefusal::Disjoint),
412 }
413 let radius_squared = rounded_square(&numerator, &nn)?;
414 let section_centre = centre - unit_normal * signed_distance;
415 let frame = frame_from_normal(section_centre, unit_normal)?;
416 Ok(ExactIntersectionCurve {
417 spans: vec![None],
418 branches: vec![Curve3::Circle(Circle3 {
419 frame,
420 radius: radius_squared.sqrt(),
421 })],
422 derivation: Derivation::SpherePlaneCircle,
423 })
424}
425
426fn cylinder_plane(
437 cylinder: &Cylinder,
438 plane: &Plane,
439) -> Result<ExactIntersectionCurve, ExactIntersectionRefusal> {
440 let axis_squared = cylinder.frame.z.dot(cylinder.frame.z);
441 let normal_squared = plane.frame.z.dot(plane.frame.z);
442 if axis_squared == 0.0 || normal_squared == 0.0 {
443 return Err(ExactIntersectionRefusal::DegenerateFrame);
444 }
445 let axis = cylinder.frame.z / axis_squared.sqrt();
446 let normal = plane.frame.z / normal_squared.sqrt();
447 let bad = ExactIntersectionRefusal::DegenerateFrame;
451 let exact_axis = exact3(cylinder.frame.z).ok_or(bad.clone())?;
452 let exact_normal = exact3(plane.frame.z).ok_or(bad.clone())?;
453 let along = edot(&exact_axis, &exact_normal);
454 if esign(&along) == Sign::Zero {
455 return cylinder_plane_parallel(cylinder, plane, axis, normal);
459 }
460 let perpendicular = ecross_is_zero(&exact_axis, &exact_normal);
461 let cosine = if perpendicular {
466 1.0
467 } else {
468 let scale = (edot(&exact_axis, &exact_axis).to_f64()
469 * edot(&exact_normal, &exact_normal).to_f64())
470 .sqrt();
471 (along.to_f64().abs() / scale).min(1.0_f64.next_down())
472 };
473 if cosine.is_nan() || cosine <= 0.0 {
475 return Err(bad);
476 }
477 let axis_origin = cylinder.frame.origin;
479 let to_plane = normal.dot(plane.frame.origin - axis_origin);
480 let centre = axis_origin + axis * (to_plane / axis.dot(normal));
481 if !centre.is_finite() {
482 return Err(ExactIntersectionRefusal::DegenerateFrame);
483 }
484 Ok(ExactIntersectionCurve {
485 spans: vec![None],
486 branches: vec![cylinder_section_curve(
487 centre,
488 axis,
489 normal,
490 cylinder.radius,
491 cosine,
492 )?],
493 derivation: if perpendicular {
494 Derivation::CylinderPlanePerpendicularCircle
495 } else {
496 Derivation::CylinderPlaneObliqueEllipse
497 },
498 })
499}
500
501fn cylinder_section_curve(
507 centre: Point3,
508 axis: Vec3,
509 normal: Vec3,
510 radius: Scalar,
511 cosine: Scalar,
512) -> Result<Curve3, ExactIntersectionRefusal> {
513 if cosine == 1.0 {
514 let frame = frame_from_normal(centre, normal)?;
515 return Ok(Curve3::Circle(Circle3 { frame, radius }));
516 }
517 let across = axis.cross(normal);
518 let across_squared = across.dot(across);
519 if across_squared == 0.0 {
520 return Err(ExactIntersectionRefusal::DegenerateFrame);
521 }
522 let minor = across / across_squared.sqrt();
523 let major = normal.cross(minor);
524 if !minor.is_finite() || !major.is_finite() {
525 return Err(ExactIntersectionRefusal::DegenerateFrame);
526 }
527 let frame = Frame3 {
528 origin: centre,
529 x: minor,
530 y: major,
531 z: normal,
532 };
533 Ok(Curve3::Ellipse(Ellipse3 {
534 frame,
535 semi_axis_x: radius,
536 semi_axis_y: radius / cosine,
537 }))
538}
539
540pub(crate) fn frame_from_normal(
548 origin: Point3,
549 normal: Vec3,
550) -> Result<Frame3, ExactIntersectionRefusal> {
551 let seed = if normal.x.abs() <= normal.y.abs() && normal.x.abs() <= normal.z.abs() {
552 Vec3::new(1.0, 0.0, 0.0)
553 } else if normal.y.abs() <= normal.z.abs() {
554 Vec3::new(0.0, 1.0, 0.0)
555 } else {
556 Vec3::new(0.0, 0.0, 1.0)
557 };
558 let x_axis = normal.cross(seed);
559 let x_squared = x_axis.dot(x_axis);
560 if x_squared == 0.0 {
561 return Err(ExactIntersectionRefusal::DegenerateFrame);
562 }
563 let x = x_axis / x_squared.sqrt();
564 let y = normal.cross(x);
565 if !x.is_finite() || !y.is_finite() {
566 return Err(ExactIntersectionRefusal::DegenerateFrame);
567 }
568 Ok(Frame3 {
569 origin,
570 x,
571 y,
572 z: normal,
573 })
574}
575
576fn sphere_sphere(
583 first: &Sphere,
584 second: &Sphere,
585) -> Result<ExactIntersectionCurve, ExactIntersectionRefusal> {
586 let separation = second.frame.origin - first.frame.origin;
587 let distance = separation.length();
588 if distance == 0.0 {
589 return Err(if first.radius == second.radius {
591 ExactIntersectionRefusal::NotRegularCurve
592 } else {
593 ExactIntersectionRefusal::Disjoint
594 });
595 }
596 if distance > first.radius + second.radius {
597 return Err(ExactIntersectionRefusal::Disjoint);
598 }
599 if distance < (first.radius - second.radius).abs() {
600 return Err(ExactIntersectionRefusal::Disjoint);
602 }
603 let axis = separation / distance;
604 let along = (distance * distance + first.radius * first.radius - second.radius * second.radius)
605 / (2.0 * distance);
606 let squared = first.radius * first.radius - along * along;
607 if squared <= 0.0 {
608 return Err(ExactIntersectionRefusal::NotRegularCurve);
610 }
611 let centre = first.frame.origin + axis * along;
612 let frame = frame_from_normal(centre, axis)?;
613 Ok(ExactIntersectionCurve {
614 spans: vec![None],
615 branches: vec![Curve3::Circle(Circle3 {
616 frame,
617 radius: squared.sqrt(),
618 })],
619 derivation: Derivation::SphereSphereCircle,
620 })
621}
622
623fn cylinder_cylinder(
640 first: &Cylinder,
641 second: &Cylinder,
642) -> Result<ExactIntersectionCurve, ExactIntersectionRefusal> {
643 let first_axis = first.frame.z;
644 let second_axis = second.frame.z;
645 let cross = first_axis.cross(second_axis);
646 if cross.length() == 0.0 {
647 return parallel_cylinders(first, second, first_axis);
648 }
649 if first.radius != second.radius {
650 return Err(ExactIntersectionRefusal::NotRegularCurve);
651 }
652 let between = second.frame.origin - first.frame.origin;
656 let unit_cross = cross / cross.length();
657 if between.dot(unit_cross) != 0.0 {
658 return Err(ExactIntersectionRefusal::NotRegularCurve);
659 }
660 let denominator = first_axis
662 .dot(second_axis)
663 .mul_add(-first_axis.dot(second_axis), 1.0);
664 if denominator == 0.0 {
665 return Err(ExactIntersectionRefusal::NotRegularCurve);
666 }
667 let along = (between.dot(first_axis) - first_axis.dot(second_axis) * between.dot(second_axis))
668 / denominator;
669 let meeting = first.frame.origin + first_axis * along;
670 let mut branches = Vec::new();
671 for normal in [first_axis - second_axis, first_axis + second_axis] {
672 let length = normal.length();
673 if length == 0.0 {
674 continue;
675 }
676 let unit_normal = normal / length;
677 let cosine = unit_normal.dot(first_axis).abs();
678 if cosine == 0.0 {
679 return Err(ExactIntersectionRefusal::NotRegularCurve);
680 }
681 branches.push(cylinder_section_curve(
682 meeting,
683 first_axis,
684 unit_normal,
685 first.radius,
686 cosine,
687 )?);
688 }
689 Ok(ExactIntersectionCurve::whole(
690 branches,
691 Derivation::CylinderCylinderSteinmetzEllipses,
692 ))
693}
694
695fn parallel_cylinders(
703 first: &Cylinder,
704 second: &Cylinder,
705 axis: Vec3,
706) -> Result<ExactIntersectionCurve, ExactIntersectionRefusal> {
707 let between = second.frame.origin - first.frame.origin;
708 let offset = between - axis * between.dot(axis);
709 let distance = offset.length();
710 if distance == 0.0 {
711 return Err(if first.radius == second.radius {
712 ExactIntersectionRefusal::NotRegularCurve
713 } else {
714 ExactIntersectionRefusal::Disjoint
715 });
716 }
717 if distance > first.radius + second.radius || distance < (first.radius - second.radius).abs() {
718 return Err(ExactIntersectionRefusal::Disjoint);
719 }
720 let toward = offset / distance;
721 let along = (distance * distance + first.radius * first.radius - second.radius * second.radius)
722 / (2.0 * distance);
723 let squared = first.radius * first.radius - along * along;
724 let base = first.frame.origin + toward * along;
725 if squared <= 0.0 {
726 return Ok(ExactIntersectionCurve {
728 spans: vec![None],
729 branches: vec![Curve3::Line(axiolid_curve::Line3 {
730 origin: base,
731 direction: axis,
732 })],
733 derivation: Derivation::ParallelCylinderLines,
734 });
735 }
736 let across = axis.cross(toward);
737 let half = squared.sqrt();
738 let mut branches = Vec::new();
739 for sign in [1.0, -1.0] {
740 branches.push(Curve3::Line(axiolid_curve::Line3 {
741 origin: base + across * (sign * half),
742 direction: axis,
743 }));
744 }
745 Ok(ExactIntersectionCurve::whole(
746 branches,
747 Derivation::ParallelCylinderLines,
748 ))
749}
750
751fn cone_plane(
766 cone: &axiolid_surface::Cone,
767 plane: &axiolid_surface::Plane,
768) -> Result<ExactIntersectionCurve, ExactIntersectionRefusal> {
769 let axis = cone.frame.z;
770 let normal = plane.frame.z;
771 let alignment = axis.dot(normal).abs().clamp(0.0, 1.0);
772 let plane_axis_angle = alignment.asin();
774 let semi = cone.semi_angle.abs();
775 let perpendicular = match (exact3(axis), exact3(normal)) {
776 (Some(a), Some(n)) => ecross_is_zero(&a, &n),
777 _ => return Err(ExactIntersectionRefusal::DegenerateFrame),
778 };
779 if perpendicular {
780 return crate::revolution_profile::coaxial_revolution_intersection(
783 &Surface::Cone(*cone),
784 &Surface::Plane(*plane),
785 );
786 }
787 if plane_axis_angle > semi {
788 return Err(ExactIntersectionRefusal::UnsupportedPair);
789 }
790 Err(ExactIntersectionRefusal::UnrepresentableConic)
791}
792
793fn cone_apex_plane(
805 cone: &axiolid_surface::Cone,
806 plane: &Plane,
807) -> Result<Option<ExactIntersectionCurve>, ExactIntersectionRefusal> {
808 let slope = cone.semi_angle.tan();
809 if slope == 0.0 || !slope.is_finite() {
810 return Ok(None);
811 }
812 let axis = cone.frame.z.normalize();
813 let apex = cone.frame.origin - axis * (cone.radius / slope);
814 let n = plane.frame.z.normalize();
815 let offset = n.dot(apex - plane.frame.origin);
816 let scale = 1.0 + apex.length() + plane.frame.origin.length();
817 if offset.abs() > 1e-12 * scale {
818 return Ok(None);
819 }
820 let z = axis * slope.signum();
822 let x = cone.frame.x.normalize();
823 let y = z.cross(x).normalize();
824 let x = y.cross(z);
825 let alpha = slope.abs().atan();
826 let (sa, ca) = alpha.sin_cos();
827 let (a, b, c) = (sa * n.dot(x), sa * n.dot(y), -ca * n.dot(z));
828 let rr = a * a + b * b;
829 let e = rr - c * c;
830 if e < -1e-14 * (rr + c * c) || rr == 0.0 {
831 return Err(ExactIntersectionRefusal::NotRegularCurve);
833 }
834 let root = e.max(0.0).sqrt();
835 let tangent = e <= 1e-14 * (rr + c * c);
836 let signs: &[Scalar] = if tangent { &[0.0] } else { &[1.0, -1.0] };
837 let mut branches = Vec::new();
838 for s in signs {
839 let (cos_phi, sin_phi) = ((a * c - s * b * root) / rr, (b * c + s * a * root) / rr);
840 let direction = z * ca + (x * cos_phi + y * sin_phi) * sa;
841 if !direction.is_finite() || !apex.is_finite() {
842 return Err(ExactIntersectionRefusal::DegenerateFrame);
843 }
844 branches.push(Curve3::Line(axiolid_curve::Line3 {
845 origin: apex,
846 direction: direction.normalize(),
847 }));
848 }
849 let spans = vec![Some(Interval::new(0.0, Scalar::INFINITY)); branches.len()];
850 Ok(Some(ExactIntersectionCurve {
851 branches,
852 derivation: Derivation::ConeApexRulings,
853 spans,
854 }))
855}
856
857fn cylinder_plane_parallel(
866 cylinder: &Cylinder,
867 plane: &Plane,
868 axis: Vec3,
869 normal: Vec3,
870) -> Result<ExactIntersectionCurve, ExactIntersectionRefusal> {
871 let distance = normal.dot(cylinder.frame.origin - plane.frame.origin);
872 let (numerator, nn) = within_radius(
874 cylinder.radius,
875 plane.frame.z,
876 cylinder.frame.origin,
877 plane.frame.origin,
878 )?;
879 let tangent_plane = match esign(&numerator) {
880 Sign::Positive => false,
881 Sign::Zero => true,
882 _ => return Err(ExactIntersectionRefusal::Disjoint),
883 };
884 let tangent = axis.cross(normal);
887 let foot = cylinder.frame.origin - normal * distance;
888 if !foot.is_finite() || !tangent.is_finite() {
889 return Err(ExactIntersectionRefusal::DegenerateFrame);
890 }
891 let half_chord = if tangent_plane {
892 0.0
893 } else {
894 rounded_square(&numerator, &nn)?.sqrt()
895 };
896 let mut branches = Vec::new();
897 let offsets: &[Scalar] = if tangent_plane {
898 &[0.0]
899 } else {
900 &[half_chord, -half_chord]
901 };
902 branches
903 .try_reserve_exact(offsets.len())
904 .map_err(|_| ExactIntersectionRefusal::DegenerateFrame)?;
905 for offset in offsets {
906 branches.push(Curve3::Line(axiolid_curve::Line3 {
907 origin: foot + tangent * *offset,
908 direction: axis,
909 }));
910 }
911 Ok(ExactIntersectionCurve::whole(
912 branches,
913 Derivation::CylinderPlaneParallelRulings,
914 ))
915}
916
917fn elliptical_cylinder_plane(
941 cylinder: &EllipticalCylinder,
942 plane: &Plane,
943) -> Result<ExactIntersectionCurve, ExactIntersectionRefusal> {
944 let bad = ExactIntersectionRefusal::DegenerateFrame;
945 let (a, b) = (cylinder.semi_axis_x, cylinder.semi_axis_y);
946 if !(a > 0.0 && b > 0.0 && a.is_finite() && b.is_finite()) {
947 return Err(bad);
948 }
949 let frame = cylinder.frame;
950 let ex = exact3(frame.x).ok_or(bad.clone())?;
951 let ey = exact3(frame.y).ok_or(bad.clone())?;
952 let ez = exact3(frame.z).ok_or(bad.clone())?;
953 let en = exact3(plane.frame.z).ok_or(bad.clone())?;
954 let eo = exact3(frame.origin).ok_or(bad.clone())?;
955 let eq = exact3(plane.frame.origin).ok_or(bad.clone())?;
956 let (ea, eb) = (
957 Dyadic::try_from_f64(a).ok_or(bad.clone())?,
958 Dyadic::try_from_f64(b).ok_or(bad.clone())?,
959 );
960 let (nx, ny, nz) = (edot(&en, &ex), edot(&en, &ey), edot(&en, &ez));
961 let k = edot(&en, &esub(&eo, &eq));
962 if esign(&nz) == Sign::Zero {
963 return elliptical_cylinder_plane_rulings(cylinder, &ea.mul(&nx), &eb.mul(&ny), &k);
964 }
965 let nz2 = nz.square();
967 let along = ea.mul(&eb).mul(&nx).mul(&ny);
968 let across_a = ea.square().mul(&nz2.add(&nx.square()));
969 let across_b = eb.square().mul(&nz2.add(&ny.square()));
970 let circle = esign(&along) == Sign::Zero && esign(&across_a.sub(&across_b)) == Sign::Zero;
971 let (fx, fy, fz) = (nx.to_f64(), ny.to_f64(), nz.to_f64());
972 let z = frame.z;
973 let centre = frame.origin - z * (k.to_f64() / fz);
974 let first = (frame.x - z * (fx / fz)) * a;
975 let second = (frame.y - z * (fy / fz)) * b;
976 let normal = {
977 let n = plane.frame.z;
978 let length = n.length();
979 if length == 0.0 || !length.is_finite() {
980 return Err(bad);
981 }
982 n / length
983 };
984 if !centre.is_finite() || !first.is_finite() || !second.is_finite() {
985 return Err(bad);
986 }
987 let curve = if circle {
988 let radius = first.length();
989 let x = first / radius;
990 let y = normal.cross(x);
991 if !(radius > 0.0 && x.is_finite() && y.is_finite()) {
992 return Err(bad);
993 }
994 Curve3::Circle(Circle3 {
995 frame: Frame3 {
996 origin: centre,
997 x,
998 y,
999 z: normal,
1000 },
1001 radius,
1002 })
1003 } else {
1004 let t0 = if esign(&along) == Sign::Zero {
1007 0.0
1008 } else {
1009 0.5 * (2.0 * first.dot(second)).atan2(first.dot(first) - second.dot(second))
1010 };
1011 let (s0, c0) = t0.sin_cos();
1012 let major = first * c0 + second * s0;
1013 let minor = second * c0 - first * s0;
1014 let (semi_x, semi_y) = (major.length(), minor.length());
1015 if !(semi_x > 0.0 && semi_y > 0.0 && semi_x.is_finite() && semi_y.is_finite()) {
1016 return Err(bad);
1017 }
1018 let x = major / semi_x;
1019 let y = normal.cross(x);
1022 if !x.is_finite() || !y.is_finite() {
1023 return Err(bad);
1024 }
1025 Curve3::Ellipse(Ellipse3 {
1026 frame: Frame3 {
1027 origin: centre,
1028 x,
1029 y,
1030 z: normal,
1031 },
1032 semi_axis_x: semi_x,
1033 semi_axis_y: semi_y,
1034 })
1035 };
1036 Ok(ExactIntersectionCurve::whole(
1037 vec![curve],
1038 Derivation::EllipticalCylinderPlaneSection,
1039 ))
1040}
1041
1042fn elliptical_cylinder_plane_rulings(
1046 cylinder: &EllipticalCylinder,
1047 ma: &Dyadic,
1048 mb: &Dyadic,
1049 k: &Dyadic,
1050) -> Result<ExactIntersectionCurve, ExactIntersectionRefusal> {
1051 let bad = ExactIntersectionRefusal::DegenerateFrame;
1052 let reach = ma.square().add(&mb.square());
1053 if esign(&reach) == Sign::Zero {
1054 return Err(bad);
1055 }
1056 let gap = reach.sub(&k.square());
1057 let touching = match esign(&gap) {
1058 Sign::Positive => false,
1059 Sign::Zero => true,
1060 _ => return Err(ExactIntersectionRefusal::Disjoint),
1061 };
1062 let (mx, my, kf) = (ma.to_f64(), mb.to_f64(), k.to_f64());
1063 let r2 = reach.to_f64();
1064 let r = r2.sqrt();
1065 let (fs, ft) = (-kf * mx / r2, -kf * my / r2);
1067 let (ws, wt) = (-my / r, mx / r);
1068 let half = if touching {
1069 0.0
1070 } else {
1071 (gap.to_f64() / r2).sqrt()
1072 };
1073 let offsets: &[Scalar] = if touching { &[0.0] } else { &[1.0, -1.0] };
1074 let frame = cylinder.frame;
1075 let mut branches = Vec::with_capacity(offsets.len());
1076 for sign in offsets {
1077 let (s, t) = (fs + sign * half * ws, ft + sign * half * wt);
1078 let origin = frame.origin
1079 + frame.x * (cylinder.semi_axis_x * s)
1080 + frame.y * (cylinder.semi_axis_y * t);
1081 if !origin.is_finite() || !frame.z.is_finite() {
1082 return Err(bad);
1083 }
1084 branches.push(Curve3::Line(axiolid_curve::Line3 {
1085 origin,
1086 direction: frame.z,
1087 }));
1088 }
1089 Ok(ExactIntersectionCurve::whole(
1090 branches,
1091 Derivation::EllipticalCylinderPlaneRulings,
1092 ))
1093}