Skip to content

Commit

Permalink
Merge pull request #889 from hannobraun/intersection
Browse files Browse the repository at this point in the history
Clean up line/segment intersection, use it in curve/edge intersection
  • Loading branch information
hannobraun authored Jul 29, 2022
2 parents 1e22bc6 + 55e999f commit bf499bd
Show file tree
Hide file tree
Showing 4 changed files with 141 additions and 132 deletions.
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,
};

0 comments on commit bf499bd

Please sign in to comment.