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 new file mode 100644 index 000000000..ff6220a6e --- /dev/null +++ b/crates/fj-kernel/src/algorithms/intersection/mod.rs @@ -0,0 +1,9 @@ +//! Intersection algorithms + +mod line_segment; +mod surface; + +pub use self::{ + line_segment::{line_segment, LineSegmentIntersection}, + surface::surface, +}; diff --git a/crates/fj-kernel/src/algorithms/intersection.rs b/crates/fj-kernel/src/algorithms/intersection/surface.rs similarity index 98% rename from crates/fj-kernel/src/algorithms/intersection.rs rename to crates/fj-kernel/src/algorithms/intersection/surface.rs index 4d1b64a31..fc008a0c5 100644 --- a/crates/fj-kernel/src/algorithms/intersection.rs +++ b/crates/fj-kernel/src/algorithms/intersection/surface.rs @@ -1,5 +1,3 @@ -//! Intersection algorithms - use fj_math::{Line, Point, Scalar, Vector}; use crate::geometry::{Curve, Surface}; 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; 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>,