mirror of
https://github.com/hannobraun/Fornjot
synced 2025-02-25 16:45:52 +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",
|
||||||
"fj-debug",
|
"fj-debug",
|
||||||
"fj-host",
|
"fj-host",
|
||||||
|
"fj-kernel",
|
||||||
"fj-math",
|
"fj-math",
|
||||||
"fj-operations",
|
"fj-operations",
|
||||||
"futures",
|
"futures",
|
||||||
|
@ -44,6 +44,10 @@ path = "../fj-debug"
|
|||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
path = "../fj-host"
|
path = "../fj-host"
|
||||||
|
|
||||||
|
[dependencies.fj-kernel]
|
||||||
|
version = "0.5.0"
|
||||||
|
path = "../fj-kernel"
|
||||||
|
|
||||||
[dependencies.fj-math]
|
[dependencies.fj-math]
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
path = "../fj-math"
|
path = "../fj-math"
|
||||||
|
@ -11,6 +11,7 @@ use std::{collections::HashMap, time::Instant};
|
|||||||
|
|
||||||
use fj_debug::DebugInfo;
|
use fj_debug::DebugInfo;
|
||||||
use fj_host::Model;
|
use fj_host::Model;
|
||||||
|
use fj_kernel::algorithms::triangulate;
|
||||||
use fj_math::{Aabb, Scalar, Triangle};
|
use fj_math::{Aabb, Scalar, Triangle};
|
||||||
use fj_operations::ToShape as _;
|
use fj_operations::ToShape as _;
|
||||||
use futures::executor::block_on;
|
use futures::executor::block_on;
|
||||||
@ -296,10 +297,12 @@ impl ShapeProcessor {
|
|||||||
|
|
||||||
let mut debug_info = DebugInfo::new();
|
let mut debug_info = DebugInfo::new();
|
||||||
let mut triangles = Vec::new();
|
let mut triangles = Vec::new();
|
||||||
shape
|
triangulate(
|
||||||
.to_shape(tolerance, &mut debug_info)
|
shape.to_shape(tolerance, &mut debug_info),
|
||||||
.topology()
|
tolerance,
|
||||||
.triangles(tolerance, &mut triangles, &mut debug_info);
|
&mut triangles,
|
||||||
|
&mut debug_info,
|
||||||
|
);
|
||||||
|
|
||||||
ProcessedShape {
|
ProcessedShape {
|
||||||
aabb,
|
aabb,
|
||||||
|
@ -1,13 +1,166 @@
|
|||||||
use fj_math::Scalar;
|
use std::collections::BTreeSet;
|
||||||
use parry2d_f64::utils::point_in_triangle::{corner_direction, Orientation};
|
|
||||||
|
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 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
|
/// Create a Delaunay triangulation of all points
|
||||||
pub fn triangulate(
|
fn delaunay(points: Vec<geometry::Point<2>>) -> Vec<[geometry::Point<2>; 3]> {
|
||||||
points: Vec<geometry::Point<2>>,
|
|
||||||
) -> Vec<[geometry::Point<2>; 3]> {
|
|
||||||
use spade::Triangulation as _;
|
use spade::Triangulation as _;
|
||||||
|
|
||||||
let triangulation = spade::DelaunayTriangulation::<_>::bulk_load(points)
|
let triangulation = spade::DelaunayTriangulation::<_>::bulk_load(points)
|
||||||
@ -39,7 +192,7 @@ pub fn triangulate(
|
|||||||
triangles
|
triangles
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enables the use of `SurfacePoint` in the triangulation.
|
// Enables the use of `geometry::Point` in the triangulation.
|
||||||
impl HasPosition for geometry::Point<2> {
|
impl HasPosition for geometry::Point<2> {
|
||||||
type Scalar = Scalar;
|
type Scalar = Scalar;
|
||||||
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
use fj_debug::DebugInfo;
|
use fj_math::{Point, Scalar, Vector};
|
||||||
use fj_math::{Point, Scalar, Triangle, Vector};
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
geometry::{Circle, Curve, Line},
|
geometry::{Circle, Curve, Line},
|
||||||
@ -238,18 +237,6 @@ impl Topology<'_> {
|
|||||||
pub fn faces(&self) -> Iter<Face> {
|
pub fn faces(&self) -> Iter<Face> {
|
||||||
Iter::new(self.geometry.faces)
|
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)]
|
#[cfg(test)]
|
||||||
|
@ -1,18 +1,8 @@
|
|||||||
use std::{
|
use std::hash::{Hash, Hasher};
|
||||||
collections::BTreeSet,
|
|
||||||
hash::{Hash, Hasher},
|
|
||||||
};
|
|
||||||
|
|
||||||
use fj_debug::{DebugInfo, TriangleEdgeCheck};
|
use fj_math::Triangle;
|
||||||
use fj_math::{Aabb, Scalar, Segment, Triangle};
|
|
||||||
use parry2d_f64::query::{Ray as Ray2, RayCast as _};
|
|
||||||
use parry3d_f64::query::Ray as Ray3;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{geometry::Surface, shape::Handle};
|
||||||
algorithms::{triangulate, Approximation},
|
|
||||||
geometry::Surface,
|
|
||||||
shape::Handle,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::edges::Cycle;
|
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 {
|
impl PartialEq for Face {
|
||||||
|
Loading…
Reference in New Issue
Block a user