Merge pull request #453 from hannobraun/triangle

Fix triangulation edge case
This commit is contained in:
Hanno Braun 2022-04-11 16:55:37 +02:00 committed by GitHub
commit a30266de3d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 84 additions and 22 deletions

View File

@ -57,7 +57,7 @@ pub fn triangulate(
let mut triangles = delaunay(points); let mut triangles = delaunay(points);
triangles.retain(|triangle| { triangles.retain(|triangle| {
face_as_polygon.contains_triangle( face_as_polygon.contains_triangle(
&triangle.map(|point| point.native()), triangle.map(|point| point.native()),
debug_info, debug_info,
) )
}); });

View File

@ -12,6 +12,21 @@ pub struct Polygon {
} }
impl Polygon { impl Polygon {
/// Construct an instance of `Polygon`
///
/// # Implementation note
///
/// This method takes a `Surface`, but `Polygon` only uses that for
/// generating debug info. It might be better, if `Polygon` had a field
/// where it stored debug info specific to its algorithm. Then code using
/// `Polygon` could access that `Polygon`-specific debug info and translate
/// that into `DebugInfo`, as necessary.
///
/// This would have the advantage of removing this dependency on `Surface`.
/// It would also make the test code a bit cleaner, as it wouldn't have to
/// bother with the `DebugInfo` anymore. Also, the `Polygon`-specific debug
/// info could potentially be more useful in test code, as a debugging tool
/// there.
pub fn new(surface: Surface) -> Self { pub fn new(surface: Surface) -> Self {
Self { Self {
surface, surface,
@ -46,22 +61,41 @@ impl Polygon {
pub fn contains_triangle( pub fn contains_triangle(
&self, &self,
&[a, b, c]: &[Point<2>; 3], triangle: [impl Into<Point<2>>; 3],
debug_info: &mut DebugInfo, debug_info: &mut DebugInfo,
) -> bool { ) -> bool {
let [a, b, c] = triangle.map(Into::into);
let mut might_be_hole = true;
for edge in [a, b, c, a].windows(2) { for edge in [a, b, c, a].windows(2) {
// This can't panic, as we passed `2` to `windows`. It can be // This can't panic, as we passed `2` to `windows`. It can be
// cleaned up a bit, once `array_windows` is stable. // cleaned up a bit, once `array_windows` is stable.
let edge = Segment::from([edge[0], edge[1]]); let edge = Segment::from([edge[0], edge[1]]);
// If the segment is an edge of the face, we don't need to take a let is_exterior_edge = self.contains_exterior_edge(edge);
// closer look. let is_interior_edge = self.contains_interior_edge(edge);
if self.contains_edge(edge) {
// If the triangle edge is not an interior edge of the polygon, we
// can rule out that the triangle is identical with a hole in the
// polygon.
if !is_interior_edge {
might_be_hole = false;
}
// If the triangle edge is an edge of the face, we don't need to
// take a closer look.
if is_exterior_edge || is_interior_edge {
continue; continue;
} }
// To determine if the edge is within the polygon, we determine if // To determine if the edge is within the polygon, we determine if
// its center point is in the polygon. // its center point is in the polygon.
//
// Since we already checked above, whether the triangle edge is a
// polygon edge (and if we reached this point, it isn't), we don't
// need to care about the distinction between "inside the polygon"
// and "on the polygon boundary".
if !self.contains_point(edge.center(), debug_info) { if !self.contains_point(edge.center(), debug_info) {
// The segment is outside of the face. This means we can throw // The segment is outside of the face. This means we can throw
// away the whole triangle. // away the whole triangle.
@ -69,15 +103,26 @@ impl Polygon {
} }
} }
// We haven't rules out that the triangle is a polygon hole. Since we
// checked all its edges, this means we now know for certain that is is.
if might_be_hole {
return false;
}
// If we didn't throw away the triangle up till now, this means all its // If we didn't throw away the triangle up till now, this means all its
// edges are within the face. // edges are within the face.
true true
} }
pub fn contains_edge(&self, edge: Segment<2>) -> bool { pub fn contains_exterior_edge(&self, edge: Segment<2>) -> bool {
self.exterior.segments().contains(&edge)
|| self.exterior.segments().contains(&edge.reverse())
}
pub fn contains_interior_edge(&self, edge: Segment<2>) -> bool {
let mut contains = false; let mut contains = false;
for chain in Some(&self.exterior).into_iter().chain(&self.interiors) { for chain in &self.interiors {
contains |= chain.segments().contains(&edge); contains |= chain.segments().contains(&edge);
contains |= chain.segments().contains(&edge.reverse()); contains |= chain.segments().contains(&edge.reverse());
} }
@ -123,26 +168,26 @@ impl Polygon {
} }
(Some(Hit::UpperVertex), Some(Hit::LowerVertex)) (Some(Hit::UpperVertex), Some(Hit::LowerVertex))
| (Some(Hit::LowerVertex), Some(Hit::UpperVertex)) => { | (Some(Hit::LowerVertex), Some(Hit::UpperVertex)) => {
// If we're hitting a vertex, only count it if we've hit the // If we're hitting a vertex, only count it if we've hit
// other kind of vertex right before. // the other kind of vertex right before.
// //
// That means, we're passing through the polygon boundary // That means, we're passing through the polygon
// at where two edges touch. Depending on the order in which // boundary at where two edges touch. Depending on the
// edges are checked, we're seeing this as a hit to one // order in which edges are checked, we're seeing this
// edge's lower/upper vertex, then the other edge's opposite // as a hit to one edge's lower/upper vertex, then the
// vertex. // other edge's opposite vertex.
// //
// If we're seeing two of the same vertices in a row, we're // If we're seeing two of the same vertices in a row,
// not actually passing through the polygon boundary. Then // we're not actually passing through the polygon
// we're just touching a vertex without passing through // boundary. Then we're just touching a vertex without
// anything. // passing through anything.
true true
} }
(Some(Hit::Parallel), _) => { (Some(Hit::Parallel), _) => {
// A parallel edge must be completely ignored. Its presence // A parallel edge must be completely ignored. Its
// won't change anything, so we can treat it as if it // presence won't change anything, so we can treat it as
// wasn't there, and its neighbors were connected to each // if it wasn't there, and its neighbors were connected
// other. // to each other.
continue; continue;
} }
_ => { _ => {
@ -180,6 +225,23 @@ mod tests {
use super::Polygon; use super::Polygon;
#[test]
fn contains_triangle_with_triangular_hole() {
let a = [0., 0.];
let b = [3., 0.];
let c = [0., 3.];
let d = [1., 1.];
let e = [2., 1.];
let f = [1., 2.];
let polygon = Polygon::new(Surface::x_y_plane())
.with_exterior(PolyChain::from([a, b, c]).close())
.with_interiors([PolyChain::from([d, e, f]).close()]);
assert!(!polygon.contains_triangle([d, e, f], &mut DebugInfo::new()));
}
#[test] #[test]
fn contains_point_ray_hits_vertex_while_passing_outside() { fn contains_point_ray_hits_vertex_while_passing_outside() {
let a = [0., 0.]; let a = [0., 0.];