fj_kernel/algorithms/intersect/
surface_surface.rs

1use fj_math::{Line, Plane, Point, Scalar};
2
3use crate::{
4    geometry::curve::{Curve, GlobalPath},
5    objects::Surface,
6    storage::Handle,
7};
8
9/// The intersection between two surfaces
10#[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
11pub struct SurfaceSurfaceIntersection {
12    /// The intersection curves
13    pub intersection_curves: [Curve; 2],
14}
15
16impl SurfaceSurfaceIntersection {
17    /// Compute the intersection between two surfaces
18    pub fn compute(surfaces: [Handle<Surface>; 2]) -> Option<Self> {
19        // Algorithm from Real-Time Collision Detection by Christer Ericson. See
20        // section 5.4.4, Intersection of Two Planes.
21        //
22        // Adaptations were made to get the intersection curves in local
23        // coordinates for each surface.
24
25        let planes = surfaces.map(|surface| plane_from_surface(&surface));
26
27        let [(a_distance, a_normal), (b_distance, b_normal)] =
28            planes.map(|plane| plane.constant_normal_form());
29
30        let direction = a_normal.cross(&b_normal);
31
32        let denom = direction.dot(&direction);
33        if denom == Scalar::ZERO {
34            // Comparing `denom` against zero looks fishy. It's probably better
35            // to compare it against an epsilon value, but I don't know how
36            // large that epsilon should be.
37            //
38            // I'll just leave it like that, until we had the opportunity to
39            // collect some experience with this code.
40            // - @hannobraun
41            return None;
42        }
43
44        let origin = (b_normal * a_distance - a_normal * b_distance)
45            .cross(&direction)
46            / denom;
47        let origin = Point { coords: origin };
48
49        let line = Line::from_origin_and_direction(origin, direction);
50
51        let curves = planes.map(|plane| Curve::Line(plane.project_line(&line)));
52
53        Some(Self {
54            intersection_curves: curves,
55        })
56    }
57}
58
59fn plane_from_surface(surface: &Surface) -> Plane {
60    let (line, path) = {
61        let line = match surface.geometry().u {
62            GlobalPath::Line(line) => line,
63            _ => todo!("Only plane-plane intersection is currently supported."),
64        };
65
66        (line, surface.geometry().v)
67    };
68
69    Plane::from_parametric(line.origin(), line.direction(), path)
70}
71
72#[cfg(test)]
73mod tests {
74    use fj_math::Transform;
75    use pretty_assertions::assert_eq;
76
77    use crate::{
78        algorithms::transform::TransformObject, geometry::curve::Curve,
79        services::Services,
80    };
81
82    use super::SurfaceSurfaceIntersection;
83
84    #[test]
85    fn plane_plane() {
86        let mut services = Services::new();
87
88        let xy = services.objects.surfaces.xy_plane();
89        let xz = services.objects.surfaces.xz_plane();
90
91        // Coincident and parallel planes don't have an intersection curve.
92        assert_eq!(
93            SurfaceSurfaceIntersection::compute([
94                xy.clone(),
95                xy.clone().transform(
96                    &Transform::translation([0., 0., 1.],),
97                    &mut services
98                )
99            ],),
100            None,
101        );
102
103        let expected_xy = Curve::u_axis();
104        let expected_xz = Curve::u_axis();
105
106        assert_eq!(
107            SurfaceSurfaceIntersection::compute([xy, xz],),
108            Some(SurfaceSurfaceIntersection {
109                intersection_curves: [expected_xy, expected_xz],
110            })
111        );
112    }
113}