Skip to content

Commit

Permalink
Merge pull request #884 from hannobraun/intersection
Browse files Browse the repository at this point in the history
Implement curve/edge intersection
  • Loading branch information
hannobraun authored Jul 28, 2022
2 parents 016d5d2 + 3307e18 commit 14bc880
Show file tree
Hide file tree
Showing 3 changed files with 217 additions and 124 deletions.
134 changes: 134 additions & 0 deletions crates/fj-kernel/src/algorithms/intersection/curve_edge.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
use fj_math::{Point, Segment};
use parry2d_f64::query::{Ray, RayCast};

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

/// The intersection between a [`Curve`] and an [`Edge`], in curve coordinates
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub struct CurveEdgeIntersection {
point_on_curve: Point<1>,
}

impl CurveEdgeIntersection {
/// Construct an instance from a point on a curve
pub fn from_point_on_curve(point_on_curve: impl Into<Point<1>>) -> Self {
let point_on_curve = point_on_curve.into();
Self { point_on_curve }
}

/// Compute the intersection
///
/// # Panics
///
/// Currently, only intersections between lines and line segments can be
/// computed. Panics, if a different type of [`Curve`] or [`Edge`] is
/// passed.
pub fn compute(curve: &Curve<2>, edge: &Edge) -> Option<Self> {
let curve_as_line = match curve {
Curve::Line(line) => line,
_ => todo!("Curve-edge intersection only supports lines"),
};

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

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

Segment::from_points(vertices)
};

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 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_on_curve: Point::from([result]),
});
}
if let Some(result_inv) = result_inv {
return Some(Self {
point_on_curve: Point::from([-result_inv]),
});
}

None
}

/// Access the intersection point on the curve
pub fn point_on_curve(&self) -> Point<1> {
self.point_on_curve
}
}

#[cfg(test)]
mod tests {
use fj_math::Point;

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

use super::CurveEdgeIntersection;

#[test]
fn compute() {
let surface = Surface::xy_plane();

let curve = Curve::u_axis();

let edge_left = Edge::build()
.line_segment_from_points(&surface, [[-1., -1.], [-1., 1.]]);
let edge_right = Edge::build()
.line_segment_from_points(&surface, [[1., -1.], [1., 1.]]);
let edge_below = Edge::build()
.line_segment_from_points(&surface, [[-1., -1.], [1., -1.]]);

let intersection_with_edge_left =
CurveEdgeIntersection::compute(&curve, &edge_left);
let intersection_with_edge_right =
CurveEdgeIntersection::compute(&curve, &edge_right);
let intersection_with_edge_below =
CurveEdgeIntersection::compute(&curve, &edge_below);

assert_eq!(
intersection_with_edge_left,
Some(CurveEdgeIntersection::from_point_on_curve(Point::from([
-1.
])))
);
assert_eq!(
intersection_with_edge_right,
Some(CurveEdgeIntersection::from_point_on_curve(Point::from([
1.
])))
);
assert!(intersection_with_edge_below.is_none());
}
}
205 changes: 81 additions & 124 deletions crates/fj-kernel/src/algorithms/intersection/curve_face.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
use std::vec;

use fj_math::{Scalar, Segment};
use parry2d_f64::query::{Ray, RayCast};
use fj_math::Point;

use crate::objects::{Curve, Face};
use crate::{
algorithms::intersection::CurveEdgeIntersection,
objects::{Curve, Face},
};

/// The intersections between a [`Curve`] and a [`Face`], in curve coordinates
#[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
Expand All @@ -16,80 +18,33 @@ impl CurveFaceIntersectionList {
///
/// This method is useful for test code.
pub fn from_intervals(
intervals: impl IntoIterator<Item = [impl Into<Scalar>; 2]>,
intervals: impl IntoIterator<Item = [impl Into<Point<1>>; 2]>,
) -> Self {
let intervals = intervals
.into_iter()
.map(|interval| interval.map(Into::into))
.map(|interval| {
interval
.map(Into::into)
.map(CurveEdgeIntersection::from_point_on_curve)
})
.collect();
Self { intervals }
}

/// Compute the intersections between a [`Curve`] and a [`Face`]
pub fn compute(curve: &Curve<2>, face: &Face) -> Self {
let line = match curve {
Curve::Line(line) => line,
_ => todo!("Curve-face intersection only supports lines"),
};

let face_as_polygon = face
.exteriors()
.chain(face.interiors())
.flat_map(|cycle| {
let edges: Vec<_> = cycle.edges().cloned().collect();
edges
})
.map(|edge| {
let line = match edge.curve().local_form() {
Curve::Line(line) => line,
_ => {
todo!("Curve-face intersection only supports polygons")
}
};

let vertices = match edge.vertices().get() {
Some(vertices) => vertices.map(|&vertex| vertex),
None => todo!(
"Curve-face intersection does not support faces with \
continuous edges"
),
};

(*line, vertices)
});
let edges = face.all_cycles().flat_map(|cycle| {
let edges: Vec<_> = cycle.edges().cloned().collect();
edges
});

let mut intersections = Vec::new();

for (edge_line, vertices) in face_as_polygon {
let vertices = vertices.map(|vertex| {
edge_line.point_from_line_coords(vertex.position())
});
let segment = Segment::from_points(vertices);

let ray = Ray {
origin: line.origin.to_na(),
dir: line.direction.to_na(),
};
let ray_inv = Ray {
origin: line.origin.to_na(),
dir: -line.direction.to_na(),
};

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

if let Some(result) = result {
intersections.push(Scalar::from(result));
}
if let Some(result_inv) = result_inv {
intersections.push(-Scalar::from(result_inv));
for edge in edges {
let intersection = CurveEdgeIntersection::compute(curve, &edge);

if let Some(intersection) = intersection {
intersections.push(intersection);
}
}

Expand Down Expand Up @@ -178,7 +133,7 @@ impl IntoIterator for CurveFaceIntersectionList {
}

/// An intersection between a curve and a face, in curve coordinates
pub type CurveFaceIntersection = [Scalar; 2];
pub type CurveFaceIntersection = [CurveEdgeIntersection; 2];

#[cfg(test)]
mod tests {
Expand Down Expand Up @@ -215,77 +170,79 @@ mod tests {
.polygon_from_points(exterior)
.with_hole(interior);

let expected =
CurveFaceIntersectionList::from_intervals([[1., 2.], [4., 5.]]);
let expected = CurveFaceIntersectionList::from_intervals([
[[1.], [2.]],
[[4.], [5.]],
]);
assert_eq!(CurveFaceIntersectionList::compute(&curve, &face), expected);
}

#[test]
fn merge() {
let a = CurveFaceIntersectionList::from_intervals([
[0., 1.], // 1: `a` and `b` are equal
[2., 5.], // 2: `a` contains `b`
[7., 8.], // 3: `b` contains `a`
[9., 11.], // 4: overlap; `a` is left
[14., 16.], // 5: overlap; `a` is right
[18., 21.], // 6: one of `a` partially overlaps two of `b`
[23., 25.], // 7: two of `a` partially overlap one of `b`
[26., 28.], // 7
[31., 35.], // 8: one of `a` overlaps two of `b`; partial/complete
[36., 38.], // 9: two of `a` overlap one of `b`; partial/complete
[39., 40.], // 9
[41., 45.], // 10: one of `a` overlaps two of `b`; complete/partial
[48., 49.], // 11: two of `a` overlap one of `b`; complete/partial
[50., 52.], // 11
[53., 58.], // 12: one of `a` overlaps two of `b` completely
[60., 61.], // 13: one of `b` overlaps two of `a` completely
[62., 63.], // 13
[65., 66.], // 14: one of `a` with no overlap in `b`
[[0.], [1.]], // 1: `a` and `b` are equal
[[2.], [5.]], // 2: `a` contains `b`
[[7.], [8.]], // 3: `b` contains `a`
[[9.], [11.]], // 4: overlap; `a` is left
[[14.], [16.]], // 5: overlap; `a` is right
[[18.], [21.]], // 6: one of `a` partially overlaps two of `b`
[[23.], [25.]], // 7: two of `a` partially overlap one of `b`
[[26.], [28.]], // 7
[[31.], [35.]], // 8: partial/complete: one of `a`, two of `b`;
[[36.], [38.]], // 9: partial/complete: two of `a`, one of `b`
[[39.], [40.]], // 9
[[41.], [45.]], // 10: complete/partial: one of `a`, two of `b`
[[48.], [49.]], // 11: complete/partial: two of `a`, one of `b`
[[50.], [52.]], // 11
[[53.], [58.]], // 12: one of `a` overlaps two of `b` completely
[[60.], [61.]], // 13: one of `b` overlaps two of `a` completely
[[62.], [63.]], // 13
[[65.], [66.]], // 14: one of `a` with no overlap in `b`
]);
let b = CurveFaceIntersectionList::from_intervals([
[0., 1.], // 1
[3., 4.], // 2
[6., 9.], // 3
[10., 12.], // 4
[13., 15.], // 5
[17., 19.], // 6
[20., 22.], // 6
[24., 27.], // 7
[30., 32.], // 8
[33., 34.], // 8
[37., 41.], // 9
[42., 43.], // 10
[44., 46.], // 10
[47., 51.], // 11
[54., 55.], // 12
[56., 57.], // 12
[59., 64.], // 13
[[0.], [1.]], // 1
[[3.], [4.]], // 2
[[6.], [9.]], // 3
[[10.], [12.]], // 4
[[13.], [15.]], // 5
[[17.], [19.]], // 6
[[20.], [22.]], // 6
[[24.], [27.]], // 7
[[30.], [32.]], // 8
[[33.], [34.]], // 8
[[37.], [41.]], // 9
[[42.], [43.]], // 10
[[44.], [46.]], // 10
[[47.], [51.]], // 11
[[54.], [55.]], // 12
[[56.], [57.]], // 12
[[59.], [64.]], // 13
]);

let merged = a.merge(&b);

let expected = CurveFaceIntersectionList::from_intervals([
[0., 1.], // 1
[3., 4.], // 2
[7., 8.], // 3
[10., 11.], // 4
[14., 15.], // 5
[18., 19.], // 6
[20., 21.], // 6
[24., 25.], // 7
[26., 27.], // 7
[31., 32.], // 8
[33., 34.], // 8
[37., 38.], // 9
[39., 40.], // 9
[42., 43.], // 10
[44., 45.], // 10
[48., 49.], // 11
[50., 51.], // 11
[54., 55.], // 12
[56., 57.], // 12
[60., 61.], // 13
[62., 63.], // 13
[[0.], [1.]], // 1
[[3.], [4.]], // 2
[[7.], [8.]], // 3
[[10.], [11.]], // 4
[[14.], [15.]], // 5
[[18.], [19.]], // 6
[[20.], [21.]], // 6
[[24.], [25.]], // 7
[[26.], [27.]], // 7
[[31.], [32.]], // 8
[[33.], [34.]], // 8
[[37.], [38.]], // 9
[[39.], [40.]], // 9
[[42.], [43.]], // 10
[[44.], [45.]], // 10
[[48.], [49.]], // 11
[[50.], [51.]], // 11
[[54.], [55.]], // 12
[[56.], [57.]], // 12
[[60.], [61.]], // 13
[[62.], [63.]], // 13
]);
assert_eq!(merged, expected);
}
Expand Down
Loading

0 comments on commit 14bc880

Please sign in to comment.