Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clean up line/segment intersection, use it in curve/edge intersection #889

Merged
merged 14 commits into from
Jul 29, 2022
100 changes: 36 additions & 64 deletions crates/fj-kernel/src/algorithms/intersection/curve_edge.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
use fj_math::{Point, Segment};
use parry2d_f64::query::{Ray, RayCast};

use crate::objects::{Curve, Edge};

use super::LineSegmentIntersection;

/// The intersection between a [`Curve`] and an [`Edge`], in curve coordinates
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub enum CurveEdgeIntersection {
Expand All @@ -14,11 +15,8 @@ pub enum CurveEdgeIntersection {

/// The edge lies on the curve
Coincident {
/// The first vertex of the edge, in curve coordinates
a_on_curve: Point<1>,

/// The second vertex of the edge, in curve coordinates
b_on_curve: Point<1>,
/// The end points of the edge, in curve coordinates on the curve
points_on_curve: [Point<1>; 2],
},
}

Expand All @@ -36,66 +34,41 @@ impl CurveEdgeIntersection {
_ => todo!("Curve-edge intersection only supports lines"),
};

let edge_curve_as_line = match edge.curve().local_form() {
Curve::Line(line) => line,
_ => {
todo!("Curve-edge intersection only supports line segments")
}
let edge_as_segment = {
let edge_curve_as_line = match edge.curve().local_form() {
Curve::Line(line) => line,
_ => {
todo!("Curve-edge intersection only supports line segments")
}
};

let edge_vertices = match edge.vertices().get() {
Some(vertices) => vertices.map(|vertex| {
edge_curve_as_line.point_from_line_coords(vertex.position())
}),
None => todo!(
"Curve-edge intersection does not support continuous edges"
),
};

Segment::from_points(edge_vertices)
};

let edge_vertices = match edge.vertices().get() {
Some(vertices) => vertices.map(|vertex| {
edge_curve_as_line.point_from_line_coords(vertex.position())
}),
None => todo!(
"Curve-edge intersection does not support continuous edges"
),
};

let edge_as_segment = Segment::from_points(edge_vertices);

if curve_as_line.is_coincident_with(edge_curve_as_line) {
let [a_on_curve, b_on_curve] = edge_vertices
.map(|vertex| curve_as_line.point_to_line_coords(vertex));

return Some(Self::Coincident {
a_on_curve,
b_on_curve,
});
}

let ray = Ray {
origin: curve_as_line.origin.to_na(),
dir: curve_as_line.direction.to_na(),
};
let ray_inv = Ray {
origin: curve_as_line.origin.to_na(),
dir: -curve_as_line.direction.to_na(),
let intersection =
LineSegmentIntersection::compute(curve_as_line, &edge_as_segment)?;

let intersection = match intersection {
LineSegmentIntersection::Point { point_on_line } => Self::Point {
point_on_curve: point_on_line,
},
LineSegmentIntersection::Coincident { points_on_line } => {
Self::Coincident {
points_on_curve: points_on_line,
}
}
};

let result = edge_as_segment.to_parry().cast_local_ray(
&ray,
f64::INFINITY,
false,
);
let result_inv = edge_as_segment.to_parry().cast_local_ray(
&ray_inv,
f64::INFINITY,
false,
);

if let Some(result) = result {
return Some(Self::Point {
point_on_curve: Point::from([result]),
});
}
if let Some(result_inv) = result_inv {
return Some(Self::Point {
point_on_curve: Point::from([-result_inv]),
});
}

None
Some(intersection)
}
}

Expand Down Expand Up @@ -165,8 +138,7 @@ mod tests {
assert_eq!(
intersection,
Some(CurveEdgeIntersection::Coincident {
a_on_curve: Point::from([-1.]),
b_on_curve: Point::from([1.]),
points_on_curve: [Point::from([-1.]), Point::from([1.]),]
})
);
}
Expand Down
8 changes: 2 additions & 6 deletions crates/fj-kernel/src/algorithms/intersection/curve_face.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,8 @@ impl CurveFaceIntersectionList {
CurveEdgeIntersection::Point { point_on_curve } => {
intersections.push(point_on_curve);
}
CurveEdgeIntersection::Coincident {
a_on_curve,
b_on_curve,
} => {
intersections.push(a_on_curve);
intersections.push(b_on_curve);
CurveEdgeIntersection::Coincident { points_on_curve } => {
intersections.extend(points_on_curve);
}
}
}
Expand Down
163 changes: 102 additions & 61 deletions crates/fj-kernel/src/algorithms/intersection/line_segment.rs
Original file line number Diff line number Diff line change
@@ -1,57 +1,67 @@
use fj_math::{Aabb, Line, Scalar, Segment, Vector};

/// Determine the intersection between a [`Line`] and a [`Segment`]
pub fn line_segment(
line: &Line<2>,
segment: &Segment<2>,
) -> Option<LineSegmentIntersection> {
// 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.point_from_line_coords([t]));
if !point_is_on_segment {
return None;
}

Some(LineSegmentIntersection::PointOnLine(t))
}
use fj_math::{Aabb, Line, Point, Scalar, Segment, Vector};

/// 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 intersect at a point
Point {
/// The intersection point, given as a coordinate on the line
point_on_line: Point<1>,
},

/// Line and segment are coincident
Coincident,
Coincident {
/// The end points of the segment, given as coordinates on the line
points_on_line: [Point<1>; 2],
},
}

impl LineSegmentIntersection {
/// Determine the intersection between a [`Line`] and a [`Segment`]
pub fn compute(line: &Line<2>, segment: &Segment<2>) -> Option<Self> {
// 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_direction == Scalar::ZERO {
// `line` and `segment` are parallel

if n_dot_origin == Scalar::ZERO {
// `line` and `segment` are not just parallel, but coincident!
return Some(Self::Coincident {
points_on_line: segment
.points()
.map(|point| line.point_to_line_coords(point)),
});
}

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.point_from_line_coords([t]));
if !point_is_on_segment {
return None;
}

Some(Self::Point {
point_on_line: Point::from([t]),
})
}
}

#[cfg(test)]
Expand All @@ -61,51 +71,82 @@ mod tests {
use crate::algorithms::intersection::LineSegmentIntersection;

#[test]
fn line_segment() {
fn compute_one_hit() {
let line = Line {
origin: Point::origin(),
direction: Vector::unit_u(),
};

// regular hit
assert_eq!(
super::line_segment(
LineSegmentIntersection::compute(
&line,
&Segment::from_points([[1., -1.], [1., 1.]]),
),
Some(LineSegmentIntersection::PointOnLine(Scalar::ONE)),
Some(LineSegmentIntersection::Point {
point_on_line: Point::from([Scalar::ONE])
}),
);
}

#[test]
fn compute_coincident() {
let line = Line {
origin: Point::origin(),
direction: Vector::unit_u(),
};

// hit, where line and segment are parallel
assert_eq!(
super::line_segment(
LineSegmentIntersection::compute(
&line,
&Segment::from_points([[1., 0.], [2., 0.]]),
),
Some(LineSegmentIntersection::Coincident),
Some(LineSegmentIntersection::Coincident {
points_on_line: [Point::from([1.]), Point::from([2.])],
}),
);
}

#[test]
fn compute_no_hit_above() {
let line = Line {
origin: Point::origin(),
direction: Vector::unit_u(),
};

// segment above line
assert_eq!(
super::line_segment(
LineSegmentIntersection::compute(
&line,
&Segment::from_points([[1., 1.], [1., 2.]]),
),
None,
);
}

#[test]
fn compute_no_hit_below() {
let line = Line {
origin: Point::origin(),
direction: Vector::unit_u(),
};

// segment below line
assert_eq!(
super::line_segment(
LineSegmentIntersection::compute(
&line,
&Segment::from_points([[1., -2.], [1., -1.]]),
),
None,
);
}

#[test]
fn compute_no_hit_parallel() {
let line = Line {
origin: Point::origin(),
direction: Vector::unit_u(),
};

// segment parallel to line
assert_eq!(
super::line_segment(
LineSegmentIntersection::compute(
&line,
&Segment::from_points([[-1., 1.], [1., 1.]]),
),
Expand Down
2 changes: 1 addition & 1 deletion crates/fj-kernel/src/algorithms/intersection/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@ mod surface_surface;
pub use self::{
curve_edge::CurveEdgeIntersection,
curve_face::{CurveFaceIntersection, CurveFaceIntersectionList},
line_segment::{line_segment, LineSegmentIntersection},
line_segment::LineSegmentIntersection,
surface_surface::surface_surface,
};