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

Fix triangulation of sharp, concave faces #1369

Merged
merged 8 commits into from
Nov 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions crates/fj-kernel/src/algorithms/approx/face.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ impl Approx for &FaceSet {
let min_distance = ValidationConfig::default().distinct_min_distance;
let mut all_points: BTreeSet<ApproxPoint<2>> = BTreeSet::new();

// Run some validation code on the approximation.
for approx in &approx {
let approx: &FaceApprox = approx;

Expand Down
39 changes: 35 additions & 4 deletions crates/fj-kernel/src/algorithms/triangulate/delaunay.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,48 @@
use std::collections::BTreeMap;

use fj_math::{Point, Scalar, Triangle, Winding};
use spade::HasPosition;

use crate::objects::Handedness;
use crate::{algorithms::approx::cycle::CycleApprox, objects::Handedness};

/// Create a Delaunay triangulation of all points
pub fn triangulate(
points: Vec<TriangulationPoint>,
cycles: impl IntoIterator<Item = CycleApprox>,
coord_handedness: Handedness,
) -> Vec<[TriangulationPoint; 3]> {
use spade::Triangulation as _;

let triangulation = spade::DelaunayTriangulation::<_>::bulk_load(points)
.expect("Inserted invalid values into triangulation");
let mut triangulation = spade::ConstrainedDelaunayTriangulation::<_>::new();

let mut points = BTreeMap::new();

for cycle_approx in cycles {
let mut handle_prev = None;

for point in cycle_approx.points() {
let handle = match points.get(&point) {
Some(handle) => *handle,
None => {
let handle = triangulation
.insert(TriangulationPoint {
point_surface: point.local_form,
point_global: point.global_form,
})
.expect("Inserted invalid point into triangulation");

points.insert(point, handle);

handle
}
};

if let Some(handle_prev) = handle_prev {
triangulation.add_constraint(handle_prev, handle);
}

handle_prev = Some(handle);
}
}

let mut triangles = Vec::new();
for triangle in triangulation.inner_faces() {
Expand Down
64 changes: 26 additions & 38 deletions crates/fj-kernel/src/algorithms/triangulate/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ mod polygon;
use fj_interop::mesh::Mesh;
use fj_math::Point;

use self::{delaunay::TriangulationPoint, polygon::Polygon};
use self::polygon::Polygon;

use super::approx::{face::FaceApprox, Approx, Tolerance};

Expand Down Expand Up @@ -44,27 +44,20 @@ where

impl Triangulate for FaceApprox {
fn triangulate_into_mesh(self, mesh: &mut Mesh<Point<3>>) {
let points: Vec<_> = self
.points()
.into_iter()
.map(|point| TriangulationPoint {
point_surface: point.local_form,
point_global: point.global_form,
})
.collect();
let face_as_polygon = Polygon::new()
.with_exterior(
self.exterior
.points()
.into_iter()
.map(|point| point.local_form),
)
.with_interiors(self.interiors.into_iter().map(|interior| {
.with_interiors(self.interiors.iter().map(|interior| {
interior.points().into_iter().map(|point| point.local_form)
}));

let cycles = [self.exterior].into_iter().chain(self.interiors);
let mut triangles =
delaunay::triangulate(points, self.coord_handedness);
delaunay::triangulate(cycles, self.coord_handedness);
triangles.retain(|triangle| {
face_as_polygon
.contains_triangle(triangle.map(|point| point.point_surface))
Expand Down Expand Up @@ -176,29 +169,26 @@ mod tests {
Ok(())
}

#[ignore]
#[test]
fn sharp_concave_shape() -> anyhow::Result<()> {
let objects = Objects::new();

//
// c
// /|
// e / |
// |\ / |
// | | / |
// | \ / |
// | \ / |
// | d |
// a ---------- b
//
// e c
// |\ /|
// \ \ / b
// \ \ / /
// \ d /
// \a/

let a = [0., 0.];
let b = [0.4, 0.];
//let b = [0.5, 0.]; // test passes with this change
let c = [0.4, 1.0];
// Naive Delaunay triangulation will create a triangle (c, d, e), which
// is not part of the polygon. The higher-level triangulation will
// filter that out, but it will result in missing triangles.

let a = [0.1, 0.0];
let b = [0.2, 0.9];
let c = [0.2, 1.0];
let d = [0.1, 0.1];
let e = [0., 0.8];
let e = [0.0, 1.0];

let surface = objects.surfaces.xy_plane();
let face = Face::partial()
Expand All @@ -209,17 +199,15 @@ mod tests {

let triangles = triangulate(face)?;

let a3 = surface.geometry().point_from_surface_coords(a);
let b3 = surface.geometry().point_from_surface_coords(b);
let c3 = surface.geometry().point_from_surface_coords(c);
let d3 = surface.geometry().point_from_surface_coords(d);
let e3 = surface.geometry().point_from_surface_coords(e);

assert!(triangles.contains_triangle([a3, b3, d3]));
assert!(triangles.contains_triangle([b3, c3, d3]));
assert!(triangles.contains_triangle([a3, d3, e3]));
let a = surface.geometry().point_from_surface_coords(a);
let b = surface.geometry().point_from_surface_coords(b);
let c = surface.geometry().point_from_surface_coords(c);
let d = surface.geometry().point_from_surface_coords(d);
let e = surface.geometry().point_from_surface_coords(e);

assert!(!triangles.contains_triangle([b3, e3, d3]));
assert!(triangles.contains_triangle([a, b, d]));
assert!(triangles.contains_triangle([a, d, e]));
assert!(triangles.contains_triangle([b, c, d]));

Ok(())
}
Expand Down