From 3a2cd189f0765afccdb5bf73295c451d8b78fffa Mon Sep 17 00:00:00 2001 From: Hanno Braun Date: Mon, 9 May 2022 16:07:19 +0200 Subject: [PATCH 1/5] Add dedicated directory for `intersection` module --- .../src/algorithms/{intersection.rs => intersection/mod.rs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename crates/fj-kernel/src/algorithms/{intersection.rs => intersection/mod.rs} (100%) diff --git a/crates/fj-kernel/src/algorithms/intersection.rs b/crates/fj-kernel/src/algorithms/intersection/mod.rs similarity index 100% rename from crates/fj-kernel/src/algorithms/intersection.rs rename to crates/fj-kernel/src/algorithms/intersection/mod.rs From f58ffe7ffc08ae39545060e2cbbfe011ebf6ef15 Mon Sep 17 00:00:00 2001 From: Hanno Braun Date: Mon, 9 May 2022 16:09:28 +0200 Subject: [PATCH 2/5] Move `intersection::surface` to dedicated module --- .../src/algorithms/intersection/mod.rs | 80 +------------------ .../src/algorithms/intersection/surface.rs | 79 ++++++++++++++++++ 2 files changed, 81 insertions(+), 78 deletions(-) create mode 100644 crates/fj-kernel/src/algorithms/intersection/surface.rs diff --git a/crates/fj-kernel/src/algorithms/intersection/mod.rs b/crates/fj-kernel/src/algorithms/intersection/mod.rs index 4d1b64a31..7731a6936 100644 --- a/crates/fj-kernel/src/algorithms/intersection/mod.rs +++ b/crates/fj-kernel/src/algorithms/intersection/mod.rs @@ -1,81 +1,5 @@ //! Intersection algorithms -use fj_math::{Line, Point, Scalar, Vector}; +mod surface; -use crate::geometry::{Curve, Surface}; - -/// Test intersection between two surfaces -pub fn surface(a: &Surface, b: &Surface) -> Option { - // Algorithm from Real-Time Collision Detection by Christer Ericson. See - // section 5.4.4, Intersection of Two Planes. - - let (a_normal, a_distance) = extract_plane(a); - let (b_normal, b_distance) = extract_plane(b); - - let direction = a_normal.cross(&b_normal); - - let denom = direction.dot(&direction); - if denom == Scalar::ZERO { - // Comparing `denom` against zero looks fishy. It's probably better to - // compare it against an epsilon value, but I don't know how large that - // epsilon should be. - // - // I'll just leave it like that, until we had the opportunity to collect - // some experience with this code. - // - @hannobraun - return None; - } - - let origin = (b_normal * a_distance - a_normal * b_distance) - .cross(&direction) - / denom; - let origin = Point { coords: origin }; - - Some(Curve::Line(Line { origin, direction })) -} - -/// Extract a plane in constant-normal form from a `Surface` -/// -/// Panics, if the given `Surface` is not a plane. -fn extract_plane(surface: &Surface) -> (Vector<3>, Scalar) { - let Surface::SweptCurve(surface) = surface; - let line = match surface.curve { - Curve::Line(line) => line, - _ => todo!("Only plane-plane intersection is currently supported."), - }; - - // Convert plane from parametric form to three-point form. - let a = line.origin; - let b = line.origin + line.direction; - let c = line.origin + surface.path; - - // Convert plane from three-point form to constant-normal form. See - // Real-Time Collision Detection by Christer Ericson, section 3.6, Planes - // and Halfspaces. - let normal = (b - a).cross(&(c - a)).normalize(); - let distance = normal.dot(&a.coords); - - (normal, distance) -} - -#[cfg(test)] -mod tests { - use fj_math::Transform; - - use crate::geometry::{Curve, Surface}; - - use super::surface; - - #[test] - fn plane_plane() { - let xy = Surface::xy_plane(); - let xz = Surface::xz_plane(); - - assert_eq!(surface(&xy, &xy), None); - assert_eq!( - surface(&xy, &xy.transform(&Transform::translation([0., 0., 1.]))), - None, - ); - assert_eq!(surface(&xy, &xz), Some(Curve::x_axis())); - } -} +pub use self::surface::surface; diff --git a/crates/fj-kernel/src/algorithms/intersection/surface.rs b/crates/fj-kernel/src/algorithms/intersection/surface.rs new file mode 100644 index 000000000..fc008a0c5 --- /dev/null +++ b/crates/fj-kernel/src/algorithms/intersection/surface.rs @@ -0,0 +1,79 @@ +use fj_math::{Line, Point, Scalar, Vector}; + +use crate::geometry::{Curve, Surface}; + +/// Test intersection between two surfaces +pub fn surface(a: &Surface, b: &Surface) -> Option { + // Algorithm from Real-Time Collision Detection by Christer Ericson. See + // section 5.4.4, Intersection of Two Planes. + + let (a_normal, a_distance) = extract_plane(a); + let (b_normal, b_distance) = extract_plane(b); + + let direction = a_normal.cross(&b_normal); + + let denom = direction.dot(&direction); + if denom == Scalar::ZERO { + // Comparing `denom` against zero looks fishy. It's probably better to + // compare it against an epsilon value, but I don't know how large that + // epsilon should be. + // + // I'll just leave it like that, until we had the opportunity to collect + // some experience with this code. + // - @hannobraun + return None; + } + + let origin = (b_normal * a_distance - a_normal * b_distance) + .cross(&direction) + / denom; + let origin = Point { coords: origin }; + + Some(Curve::Line(Line { origin, direction })) +} + +/// Extract a plane in constant-normal form from a `Surface` +/// +/// Panics, if the given `Surface` is not a plane. +fn extract_plane(surface: &Surface) -> (Vector<3>, Scalar) { + let Surface::SweptCurve(surface) = surface; + let line = match surface.curve { + Curve::Line(line) => line, + _ => todo!("Only plane-plane intersection is currently supported."), + }; + + // Convert plane from parametric form to three-point form. + let a = line.origin; + let b = line.origin + line.direction; + let c = line.origin + surface.path; + + // Convert plane from three-point form to constant-normal form. See + // Real-Time Collision Detection by Christer Ericson, section 3.6, Planes + // and Halfspaces. + let normal = (b - a).cross(&(c - a)).normalize(); + let distance = normal.dot(&a.coords); + + (normal, distance) +} + +#[cfg(test)] +mod tests { + use fj_math::Transform; + + use crate::geometry::{Curve, Surface}; + + use super::surface; + + #[test] + fn plane_plane() { + let xy = Surface::xy_plane(); + let xz = Surface::xz_plane(); + + assert_eq!(surface(&xy, &xy), None); + assert_eq!( + surface(&xy, &xy.transform(&Transform::translation([0., 0., 1.]))), + None, + ); + assert_eq!(surface(&xy, &xz), Some(Curve::x_axis())); + } +} From cb60439adafc8059b7717f872df84b8a0a27300f Mon Sep 17 00:00:00 2001 From: Hanno Braun Date: Tue, 10 May 2022 14:54:45 +0200 Subject: [PATCH 3/5] Fix word in comment --- crates/fj-kernel/src/topology/faces.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/fj-kernel/src/topology/faces.rs b/crates/fj-kernel/src/topology/faces.rs index e3535d00e..bacbdcb7d 100644 --- a/crates/fj-kernel/src/topology/faces.rs +++ b/crates/fj-kernel/src/topology/faces.rs @@ -39,7 +39,7 @@ pub enum Face { /// lie in the surface. The data we're using here is 3-dimensional /// though, so no such limitation is enforced. /// - /// It might be less error-prone to specify the edges in surface + /// It might be less error-prone to specify the cycles in surface /// coordinates. exteriors: Vec>, From 49a34d8a5adbda64a06cd0fd31e4ac991feff95e Mon Sep 17 00:00:00 2001 From: Hanno Braun Date: Tue, 10 May 2022 14:58:56 +0200 Subject: [PATCH 4/5] Make comment a bit more clear --- crates/fj-kernel/src/geometry/points.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/fj-kernel/src/geometry/points.rs b/crates/fj-kernel/src/geometry/points.rs index 9e260fb40..567b99966 100644 --- a/crates/fj-kernel/src/geometry/points.rs +++ b/crates/fj-kernel/src/geometry/points.rs @@ -54,7 +54,7 @@ impl From> for Point<3> { } // Some math operations for convenience. Obviously those can never return a new -// `Point`, or the conversion back to 3D would be broken. +// `self::Point`, or the conversion back to 3D would be broken. impl Add> for Point { type Output = fj_math::Point; From f3ae2f7802b3688b8b37d44bc812ba0c31d82506 Mon Sep 17 00:00:00 2001 From: Hanno Braun Date: Tue, 10 May 2022 16:56:36 +0200 Subject: [PATCH 5/5] Implement line-segment intersection --- .../algorithms/intersection/line_segment.rs | 115 ++++++++++++++++++ .../src/algorithms/intersection/mod.rs | 6 +- 2 files changed, 120 insertions(+), 1 deletion(-) create mode 100644 crates/fj-kernel/src/algorithms/intersection/line_segment.rs diff --git a/crates/fj-kernel/src/algorithms/intersection/line_segment.rs b/crates/fj-kernel/src/algorithms/intersection/line_segment.rs new file mode 100644 index 000000000..2b1b3b0ba --- /dev/null +++ b/crates/fj-kernel/src/algorithms/intersection/line_segment.rs @@ -0,0 +1,115 @@ +use fj_math::{Aabb, Line, Point, Scalar, Segment, Vector}; + +/// Determine the intersection between a [`Line`] and a [`Segment`] +pub fn line_segment( + line: &Line<2>, + segment: &Segment<2>, +) -> Option { + // Algorithm adapted from Real-Time Collision Detection by Christer Ericson. + // See section 5.1.9.1, 2D Segment Intersection. + + let [a, b] = segment.points(); + + // Find vector that is orthogonal to `segment`. + let n = { + let ab = b - a; + Vector::from([ab.v, ab.u]) + }; + + let n_dot_origin = n.dot(&(b - line.origin)); + let n_dot_direction = n.dot(&line.direction); + + if n_dot_origin == Scalar::ZERO && n_dot_direction == Scalar::ZERO { + // `line` and `segment` are not just parallel, but coincident! + return Some(LineSegmentIntersection::Coincident); + } + + if n_dot_direction == Scalar::ZERO { + // `line` and `segment` are parallel, but not coincident + return None; + } + + // Now we ruled out the special cases. Compute where `line` hits the line + // defined by `segment`'s points. + let t = n_dot_origin / n_dot_direction; + + let point_is_on_segment = Aabb::<2>::from_points(segment.points()) + .contains(line.convert_point_from_line_coords(&Point::from([t]))); + if !point_is_on_segment { + return None; + } + + Some(LineSegmentIntersection::PointOnLine(t)) +} + +/// An intersection between a [`Line`] and a [`Segment`] +#[derive(Debug, Eq, PartialEq)] +pub enum LineSegmentIntersection { + /// Line and segment intersect on a point + /// + /// Point is given as a coordinate on the line. + PointOnLine(Scalar), + + /// Line and segment are coincident + Coincident, +} + +#[cfg(test)] +mod tests { + use fj_math::{Line, Point, Scalar, Segment, Vector}; + + use crate::algorithms::intersection::LineSegmentIntersection; + + #[test] + fn line_segment() { + let line = Line { + origin: Point::origin(), + direction: Vector::unit_u(), + }; + + // regular hit + assert_eq!( + super::line_segment( + &line, + &Segment::from_points([[1., -1.], [1., 1.]]), + ), + Some(LineSegmentIntersection::PointOnLine(Scalar::ONE)), + ); + + // hit, where line and segment are parallel + assert_eq!( + super::line_segment( + &line, + &Segment::from_points([[1., 0.], [2., 0.]]), + ), + Some(LineSegmentIntersection::Coincident), + ); + + // segment above line + assert_eq!( + super::line_segment( + &line, + &Segment::from_points([[1., 1.], [1., 2.]]), + ), + None, + ); + + // segment below line + assert_eq!( + super::line_segment( + &line, + &Segment::from_points([[1., -2.], [1., -1.]]), + ), + None, + ); + + // segment parallel to line + assert_eq!( + super::line_segment( + &line, + &Segment::from_points([[-1., 1.], [1., 1.]]), + ), + None, + ); + } +} diff --git a/crates/fj-kernel/src/algorithms/intersection/mod.rs b/crates/fj-kernel/src/algorithms/intersection/mod.rs index 7731a6936..ff6220a6e 100644 --- a/crates/fj-kernel/src/algorithms/intersection/mod.rs +++ b/crates/fj-kernel/src/algorithms/intersection/mod.rs @@ -1,5 +1,9 @@ //! Intersection algorithms +mod line_segment; mod surface; -pub use self::surface::surface; +pub use self::{ + line_segment::{line_segment, LineSegmentIntersection}, + surface::surface, +};