mirror of
https://github.com/hannobraun/Fornjot
synced 2025-02-25 08:35:51 +00:00
Merge pull request #386 from hannobraun/triangulation
Consolidate triangulation code in `fj_kernel::algorithms::triangulation`
This commit is contained in:
commit
2ef0550feb
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -633,6 +633,7 @@ dependencies = [
|
||||
"fj",
|
||||
"fj-debug",
|
||||
"fj-host",
|
||||
"fj-kernel",
|
||||
"fj-math",
|
||||
"fj-operations",
|
||||
"futures",
|
||||
|
@ -44,6 +44,10 @@ path = "../fj-debug"
|
||||
version = "0.5.0"
|
||||
path = "../fj-host"
|
||||
|
||||
[dependencies.fj-kernel]
|
||||
version = "0.5.0"
|
||||
path = "../fj-kernel"
|
||||
|
||||
[dependencies.fj-math]
|
||||
version = "0.5.0"
|
||||
path = "../fj-math"
|
||||
|
@ -11,6 +11,7 @@ use std::{collections::HashMap, time::Instant};
|
||||
|
||||
use fj_debug::DebugInfo;
|
||||
use fj_host::Model;
|
||||
use fj_kernel::algorithms::triangulate;
|
||||
use fj_math::{Aabb, Scalar, Triangle};
|
||||
use fj_operations::ToShape as _;
|
||||
use futures::executor::block_on;
|
||||
@ -296,10 +297,12 @@ impl ShapeProcessor {
|
||||
|
||||
let mut debug_info = DebugInfo::new();
|
||||
let mut triangles = Vec::new();
|
||||
shape
|
||||
.to_shape(tolerance, &mut debug_info)
|
||||
.topology()
|
||||
.triangles(tolerance, &mut triangles, &mut debug_info);
|
||||
triangulate(
|
||||
shape.to_shape(tolerance, &mut debug_info),
|
||||
tolerance,
|
||||
&mut triangles,
|
||||
&mut debug_info,
|
||||
);
|
||||
|
||||
ProcessedShape {
|
||||
aabb,
|
||||
|
@ -1,13 +1,166 @@
|
||||
use fj_math::Scalar;
|
||||
use parry2d_f64::utils::point_in_triangle::{corner_direction, Orientation};
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use fj_debug::{DebugInfo, TriangleEdgeCheck};
|
||||
use fj_math::{Aabb, Scalar, Segment, Triangle};
|
||||
use parry2d_f64::{
|
||||
query::{Ray as Ray2, RayCast as _},
|
||||
utils::point_in_triangle::{corner_direction, Orientation},
|
||||
};
|
||||
use parry3d_f64::query::Ray as Ray3;
|
||||
use spade::HasPosition;
|
||||
|
||||
use crate::geometry;
|
||||
use crate::{geometry, shape::Shape, topology::Face};
|
||||
|
||||
use super::Approximation;
|
||||
|
||||
/// Triangulate a shape
|
||||
pub fn triangulate(
|
||||
mut shape: Shape,
|
||||
tolerance: Scalar,
|
||||
out: &mut Vec<Triangle<3>>,
|
||||
debug_info: &mut DebugInfo,
|
||||
) {
|
||||
for face in shape.topology().faces() {
|
||||
let face = face.get();
|
||||
match &*face {
|
||||
Face::Face { surface, color, .. } => {
|
||||
let surface = surface.get();
|
||||
let approx = Approximation::for_face(&face, tolerance);
|
||||
|
||||
let points: Vec<_> = approx
|
||||
.points
|
||||
.into_iter()
|
||||
.map(|vertex| {
|
||||
// Can't panic, unless the approximation wrongfully
|
||||
// generates points that are not in the surface.
|
||||
surface.point_model_to_surface(vertex)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let segments: Vec<_> = approx
|
||||
.segments
|
||||
.into_iter()
|
||||
.map(|segment| {
|
||||
let [a, b] = segment.points();
|
||||
|
||||
// Can't panic, unless the approximation wrongfully
|
||||
// generates points that are not in the surface.
|
||||
let a = surface.point_model_to_surface(a);
|
||||
let b = surface.point_model_to_surface(b);
|
||||
|
||||
[a, b]
|
||||
})
|
||||
.collect();
|
||||
|
||||
// We're also going to need a point outside of the polygon, for
|
||||
// the point-in-polygon tests.
|
||||
let aabb = Aabb::<2>::from_points(
|
||||
points.iter().map(|vertex| vertex.native()),
|
||||
);
|
||||
let outside = aabb.max * 2.;
|
||||
|
||||
let mut triangles = delaunay(points);
|
||||
let face_as_polygon = segments;
|
||||
|
||||
triangles.retain(|t| {
|
||||
for segment in [t[0], t[1], t[2], t[0]].windows(2) {
|
||||
// This can't panic, as we passed `2` to `windows`. It
|
||||
// can be cleaned up a bit, once `array_windows` is
|
||||
// stable.
|
||||
let segment = [segment[0], segment[1]];
|
||||
let inverted_segment = [segment[1], segment[0]];
|
||||
|
||||
// If the segment is an edge of the face, we don't need
|
||||
// to take a closer look.
|
||||
if face_as_polygon.contains(&segment) {
|
||||
continue;
|
||||
}
|
||||
if face_as_polygon.contains(&inverted_segment) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// To determine if the edge is within the polygon, we
|
||||
// determine if its center point is in the polygon.
|
||||
let center = segment[0]
|
||||
+ (segment[1] - segment[0]) / Scalar::TWO;
|
||||
|
||||
let origin = center;
|
||||
let dir = outside - center;
|
||||
let ray = Ray2 {
|
||||
origin: origin.to_na(),
|
||||
dir: dir.to_na(),
|
||||
};
|
||||
|
||||
let mut check = TriangleEdgeCheck::new(Ray3 {
|
||||
origin: surface
|
||||
.point_surface_to_model(&origin)
|
||||
.to_na(),
|
||||
dir: surface.vector_surface_to_model(&dir).to_na(),
|
||||
});
|
||||
|
||||
// We need to keep track of where our ray hits the
|
||||
// edges. Otherwise, if the ray hits a vertex, we might
|
||||
// count that hit twice, as every vertex is attached to
|
||||
// two edges.
|
||||
let mut hits = BTreeSet::new();
|
||||
|
||||
// Use ray-casting to determine if `center` is within
|
||||
// the face-polygon.
|
||||
for edge in &face_as_polygon {
|
||||
// Please note that we if we get to this point, then
|
||||
// the point is not on a polygon edge, due to the
|
||||
// check above. We don't need to handle any edge
|
||||
// cases that would arise from that case.
|
||||
|
||||
let edge =
|
||||
Segment::from(edge.map(|point| point.native()));
|
||||
|
||||
let intersection = edge
|
||||
.to_parry()
|
||||
.cast_local_ray(&ray, f64::INFINITY, true)
|
||||
.map(Scalar::from_f64);
|
||||
|
||||
if let Some(t) = intersection {
|
||||
// Due to slight inaccuracies, we might get
|
||||
// different values for the same intersections.
|
||||
// Let's round `t` before using it.
|
||||
let eps = 1_000_000.0;
|
||||
let t = (t * eps).round() / eps;
|
||||
|
||||
if hits.insert(t) {
|
||||
check.hits.push(t.into_f64());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
debug_info.triangle_edge_checks.push(check);
|
||||
|
||||
if hits.len() % 2 == 0 {
|
||||
// The segment is outside of the face. This means we
|
||||
// can throw away the whole triangle.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// If we didn't throw away the triangle up till now, this
|
||||
// means all its edges are within the face.
|
||||
true
|
||||
});
|
||||
|
||||
out.extend(triangles.into_iter().map(|triangle| {
|
||||
let [a, b, c] = triangle.map(|point| point.canonical());
|
||||
let mut t = Triangle::from([a, b, c]);
|
||||
t.set_color(*color);
|
||||
t
|
||||
}));
|
||||
}
|
||||
Face::Triangles(triangles) => out.extend(triangles),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a Delaunay triangulation of all points
|
||||
pub fn triangulate(
|
||||
points: Vec<geometry::Point<2>>,
|
||||
) -> Vec<[geometry::Point<2>; 3]> {
|
||||
fn delaunay(points: Vec<geometry::Point<2>>) -> Vec<[geometry::Point<2>; 3]> {
|
||||
use spade::Triangulation as _;
|
||||
|
||||
let triangulation = spade::DelaunayTriangulation::<_>::bulk_load(points)
|
||||
@ -39,7 +192,7 @@ pub fn triangulate(
|
||||
triangles
|
||||
}
|
||||
|
||||
// Enables the use of `SurfacePoint` in the triangulation.
|
||||
// Enables the use of `geometry::Point` in the triangulation.
|
||||
impl HasPosition for geometry::Point<2> {
|
||||
type Scalar = Scalar;
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use fj_debug::DebugInfo;
|
||||
use fj_math::{Point, Scalar, Triangle, Vector};
|
||||
use fj_math::{Point, Scalar, Vector};
|
||||
|
||||
use crate::{
|
||||
geometry::{Circle, Curve, Line},
|
||||
@ -238,18 +237,6 @@ impl Topology<'_> {
|
||||
pub fn faces(&self) -> Iter<Face> {
|
||||
Iter::new(self.geometry.faces)
|
||||
}
|
||||
|
||||
/// Triangulate the shape
|
||||
pub fn triangles(
|
||||
&self,
|
||||
tolerance: Scalar,
|
||||
out: &mut Vec<Triangle<3>>,
|
||||
debug_info: &mut DebugInfo,
|
||||
) {
|
||||
for face in &*self.geometry.faces {
|
||||
face.get().triangles(tolerance, out, debug_info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -1,18 +1,8 @@
|
||||
use std::{
|
||||
collections::BTreeSet,
|
||||
hash::{Hash, Hasher},
|
||||
};
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
||||
use fj_debug::{DebugInfo, TriangleEdgeCheck};
|
||||
use fj_math::{Aabb, Scalar, Segment, Triangle};
|
||||
use parry2d_f64::query::{Ray as Ray2, RayCast as _};
|
||||
use parry3d_f64::query::Ray as Ray3;
|
||||
use fj_math::Triangle;
|
||||
|
||||
use crate::{
|
||||
algorithms::{triangulate, Approximation},
|
||||
geometry::Surface,
|
||||
shape::Handle,
|
||||
};
|
||||
use crate::{geometry::Surface, shape::Handle};
|
||||
|
||||
use super::edges::Cycle;
|
||||
|
||||
@ -89,149 +79,6 @@ impl Face {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Triangulate the face
|
||||
pub fn triangles(
|
||||
&self,
|
||||
tolerance: Scalar,
|
||||
out: &mut Vec<Triangle<3>>,
|
||||
debug_info: &mut DebugInfo,
|
||||
) {
|
||||
match self {
|
||||
Self::Face { surface, color, .. } => {
|
||||
let surface = surface.get();
|
||||
let approx = Approximation::for_face(self, tolerance);
|
||||
|
||||
let points: Vec<_> = approx
|
||||
.points
|
||||
.into_iter()
|
||||
.map(|vertex| {
|
||||
// Can't panic, unless the approximation wrongfully
|
||||
// generates points that are not in the surface.
|
||||
surface.point_model_to_surface(vertex)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let segments: Vec<_> = approx
|
||||
.segments
|
||||
.into_iter()
|
||||
.map(|segment| {
|
||||
let [a, b] = segment.points();
|
||||
|
||||
// Can't panic, unless the approximation wrongfully
|
||||
// generates points that are not in the surface.
|
||||
let a = surface.point_model_to_surface(a);
|
||||
let b = surface.point_model_to_surface(b);
|
||||
|
||||
[a, b]
|
||||
})
|
||||
.collect();
|
||||
|
||||
// We're also going to need a point outside of the polygon, for
|
||||
// the point-in-polygon tests.
|
||||
let aabb = Aabb::<2>::from_points(
|
||||
points.iter().map(|vertex| vertex.native()),
|
||||
);
|
||||
let outside = aabb.max * 2.;
|
||||
|
||||
let mut triangles = triangulate(points);
|
||||
let face_as_polygon = segments;
|
||||
|
||||
triangles.retain(|t| {
|
||||
for segment in [t[0], t[1], t[2], t[0]].windows(2) {
|
||||
// This can't panic, as we passed `2` to `windows`. It
|
||||
// can be cleaned up a bit, once `array_windows` is
|
||||
// stable.
|
||||
let segment = [segment[0], segment[1]];
|
||||
let inverted_segment = [segment[1], segment[0]];
|
||||
|
||||
// If the segment is an edge of the face, we don't need
|
||||
// to take a closer look.
|
||||
if face_as_polygon.contains(&segment) {
|
||||
continue;
|
||||
}
|
||||
if face_as_polygon.contains(&inverted_segment) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// To determine if the edge is within the polygon, we
|
||||
// determine if its center point is in the polygon.
|
||||
let center = segment[0]
|
||||
+ (segment[1] - segment[0]) / Scalar::TWO;
|
||||
|
||||
let origin = center;
|
||||
let dir = outside - center;
|
||||
let ray = Ray2 {
|
||||
origin: origin.to_na(),
|
||||
dir: dir.to_na(),
|
||||
};
|
||||
|
||||
let mut check = TriangleEdgeCheck::new(Ray3 {
|
||||
origin: surface
|
||||
.point_surface_to_model(&origin)
|
||||
.to_na(),
|
||||
dir: surface.vector_surface_to_model(&dir).to_na(),
|
||||
});
|
||||
|
||||
// We need to keep track of where our ray hits the
|
||||
// edges. Otherwise, if the ray hits a vertex, we might
|
||||
// count that hit twice, as every vertex is attached to
|
||||
// two edges.
|
||||
let mut hits = BTreeSet::new();
|
||||
|
||||
// Use ray-casting to determine if `center` is within
|
||||
// the face-polygon.
|
||||
for edge in &face_as_polygon {
|
||||
// Please note that we if we get to this point, then
|
||||
// the point is not on a polygon edge, due to the
|
||||
// check above. We don't need to handle any edge
|
||||
// cases that would arise from that case.
|
||||
|
||||
let edge =
|
||||
Segment::from(edge.map(|point| point.native()));
|
||||
|
||||
let intersection = edge
|
||||
.to_parry()
|
||||
.cast_local_ray(&ray, f64::INFINITY, true)
|
||||
.map(Scalar::from_f64);
|
||||
|
||||
if let Some(t) = intersection {
|
||||
// Due to slight inaccuracies, we might get
|
||||
// different values for the same intersections.
|
||||
// Let's round `t` before using it.
|
||||
let eps = 1_000_000.0;
|
||||
let t = (t * eps).round() / eps;
|
||||
|
||||
if hits.insert(t) {
|
||||
check.hits.push(t.into_f64());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
debug_info.triangle_edge_checks.push(check);
|
||||
|
||||
if hits.len() % 2 == 0 {
|
||||
// The segment is outside of the face. This means we
|
||||
// can throw away the whole triangle.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// If we didn't throw away the triangle up till now, this
|
||||
// means all its edges are within the face.
|
||||
true
|
||||
});
|
||||
|
||||
out.extend(triangles.into_iter().map(|triangle| {
|
||||
let [a, b, c] = triangle.map(|point| point.canonical());
|
||||
let mut t = Triangle::from([a, b, c]);
|
||||
t.set_color(*color);
|
||||
t
|
||||
}));
|
||||
}
|
||||
Self::Triangles(triangles) => out.extend(triangles),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Face {
|
||||
|
Loading…
Reference in New Issue
Block a user