From d78c82c70b08cf06095c303672bfea4d22ff6126 Mon Sep 17 00:00:00 2001 From: Hanno Braun Date: Mon, 21 Mar 2022 17:14:45 +0100 Subject: [PATCH 1/6] Update comment --- fj-kernel/src/algorithms/triangulation.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fj-kernel/src/algorithms/triangulation.rs b/fj-kernel/src/algorithms/triangulation.rs index fcd23dd50..6629990c7 100644 --- a/fj-kernel/src/algorithms/triangulation.rs +++ b/fj-kernel/src/algorithms/triangulation.rs @@ -39,7 +39,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; From 51d2c82c042ec2d0283b5051a7799bd7e6c15d26 Mon Sep 17 00:00:00 2001 From: Hanno Braun Date: Mon, 21 Mar 2022 17:17:08 +0100 Subject: [PATCH 2/6] Make function name more specific --- fj-kernel/src/algorithms/mod.rs | 2 +- fj-kernel/src/algorithms/triangulation.rs | 2 +- fj-kernel/src/topology/faces.rs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/fj-kernel/src/algorithms/mod.rs b/fj-kernel/src/algorithms/mod.rs index 14e0f3d1d..c8d22fa98 100644 --- a/fj-kernel/src/algorithms/mod.rs +++ b/fj-kernel/src/algorithms/mod.rs @@ -9,5 +9,5 @@ mod triangulation; pub use self::{ approximation::Approximation, sweep::sweep_shape, - triangulation::triangulate, + triangulation::delaunay, }; diff --git a/fj-kernel/src/algorithms/triangulation.rs b/fj-kernel/src/algorithms/triangulation.rs index 6629990c7..d288edfae 100644 --- a/fj-kernel/src/algorithms/triangulation.rs +++ b/fj-kernel/src/algorithms/triangulation.rs @@ -5,7 +5,7 @@ use spade::HasPosition; use crate::geometry; /// Create a Delaunay triangulation of all points -pub fn triangulate( +pub fn delaunay( points: Vec>, ) -> Vec<[geometry::Point<2>; 3]> { use spade::Triangulation as _; diff --git a/fj-kernel/src/topology/faces.rs b/fj-kernel/src/topology/faces.rs index 30529231a..2f38d2adf 100644 --- a/fj-kernel/src/topology/faces.rs +++ b/fj-kernel/src/topology/faces.rs @@ -9,7 +9,7 @@ use parry2d_f64::query::{Ray as Ray2, RayCast as _}; use parry3d_f64::query::Ray as Ray3; use crate::{ - algorithms::{triangulate, Approximation}, + algorithms::{delaunay, Approximation}, geometry::Surface, shape::Handle, }; @@ -134,7 +134,7 @@ impl Face { ); let outside = aabb.max * 2.; - let mut triangles = triangulate(points); + let mut triangles = delaunay(points); let face_as_polygon = segments; triangles.retain(|t| { From 7a7eca57889f517dad8c94bc689ebe49cd9e42b6 Mon Sep 17 00:00:00 2001 From: Hanno Braun Date: Mon, 21 Mar 2022 17:23:30 +0100 Subject: [PATCH 3/6] Add dependency on `fj-kernel` to `fj-app` --- Cargo.lock | 1 + fj-app/Cargo.toml | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 2c10d6bc0..ecb2e9e5f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -633,6 +633,7 @@ dependencies = [ "fj", "fj-debug", "fj-host", + "fj-kernel", "fj-math", "fj-operations", "futures", diff --git a/fj-app/Cargo.toml b/fj-app/Cargo.toml index 1b9da326b..c117455b3 100644 --- a/fj-app/Cargo.toml +++ b/fj-app/Cargo.toml @@ -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" From b483a5dd4a72f7d24ce818631dedb3f3c162e592 Mon Sep 17 00:00:00 2001 From: Hanno Braun Date: Mon, 21 Mar 2022 17:24:54 +0100 Subject: [PATCH 4/6] Convert `Topology::triangles` into free function --- fj-app/src/main.rs | 11 +++++++---- fj-kernel/src/algorithms/mod.rs | 5 +++-- fj-kernel/src/algorithms/triangulation.rs | 17 +++++++++++++++-- fj-kernel/src/shape/topology.rs | 15 +-------------- 4 files changed, 26 insertions(+), 22 deletions(-) diff --git a/fj-app/src/main.rs b/fj-app/src/main.rs index cfbf2aee7..910805161 100644 --- a/fj-app/src/main.rs +++ b/fj-app/src/main.rs @@ -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, diff --git a/fj-kernel/src/algorithms/mod.rs b/fj-kernel/src/algorithms/mod.rs index c8d22fa98..5c2298dbb 100644 --- a/fj-kernel/src/algorithms/mod.rs +++ b/fj-kernel/src/algorithms/mod.rs @@ -8,6 +8,7 @@ mod sweep; mod triangulation; pub use self::{ - approximation::Approximation, sweep::sweep_shape, - triangulation::delaunay, + approximation::Approximation, + sweep::sweep_shape, + triangulation::{delaunay, triangulate}, }; diff --git a/fj-kernel/src/algorithms/triangulation.rs b/fj-kernel/src/algorithms/triangulation.rs index d288edfae..4e055a627 100644 --- a/fj-kernel/src/algorithms/triangulation.rs +++ b/fj-kernel/src/algorithms/triangulation.rs @@ -1,8 +1,21 @@ -use fj_math::Scalar; +use fj_debug::DebugInfo; +use fj_math::{Scalar, Triangle}; use parry2d_f64::utils::point_in_triangle::{corner_direction, Orientation}; use spade::HasPosition; -use crate::geometry; +use crate::{geometry, shape::Shape}; + +/// Triangulate a shape +pub fn triangulate( + mut shape: Shape, + tolerance: Scalar, + out: &mut Vec>, + debug_info: &mut DebugInfo, +) { + for face in shape.topology().faces() { + face.get().triangles(tolerance, out, debug_info); + } +} /// Create a Delaunay triangulation of all points pub fn delaunay( diff --git a/fj-kernel/src/shape/topology.rs b/fj-kernel/src/shape/topology.rs index 46cb5cc94..6f1866994 100644 --- a/fj-kernel/src/shape/topology.rs +++ b/fj-kernel/src/shape/topology.rs @@ -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 { Iter::new(self.geometry.faces) } - - /// Triangulate the shape - pub fn triangles( - &self, - tolerance: Scalar, - out: &mut Vec>, - debug_info: &mut DebugInfo, - ) { - for face in &*self.geometry.faces { - face.get().triangles(tolerance, out, debug_info); - } - } } #[cfg(test)] From 6a0417bb2d6bb9e92dbe6c5cb797c8921d4bc0d7 Mon Sep 17 00:00:00 2001 From: Hanno Braun Date: Mon, 21 Mar 2022 17:33:53 +0100 Subject: [PATCH 5/6] Inline method call --- fj-kernel/src/algorithms/triangulation.rs | 152 ++++++++++++++++++++- fj-kernel/src/topology/faces.rs | 159 +--------------------- 2 files changed, 150 insertions(+), 161 deletions(-) diff --git a/fj-kernel/src/algorithms/triangulation.rs b/fj-kernel/src/algorithms/triangulation.rs index 4e055a627..c7e8c7c73 100644 --- a/fj-kernel/src/algorithms/triangulation.rs +++ b/fj-kernel/src/algorithms/triangulation.rs @@ -1,9 +1,17 @@ -use fj_debug::DebugInfo; -use fj_math::{Scalar, Triangle}; -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, shape::Shape}; +use crate::{geometry, shape::Shape, topology::Face}; + +use super::Approximation; /// Triangulate a shape pub fn triangulate( @@ -13,7 +21,141 @@ pub fn triangulate( debug_info: &mut DebugInfo, ) { for face in shape.topology().faces() { - face.get().triangles(tolerance, out, debug_info); + 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), + } } } diff --git a/fj-kernel/src/topology/faces.rs b/fj-kernel/src/topology/faces.rs index 2f38d2adf..d5120cbf2 100644 --- a/fj-kernel/src/topology/faces.rs +++ b/fj-kernel/src/topology/faces.rs @@ -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::{delaunay, 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>, - 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 = 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 - })); - } - Self::Triangles(triangles) => out.extend(triangles), - } - } } impl PartialEq for Face { From 4a3b4042ecc32756e32a3ee096dfacfc131fa389 Mon Sep 17 00:00:00 2001 From: Hanno Braun Date: Mon, 21 Mar 2022 17:34:52 +0100 Subject: [PATCH 6/6] Make method private It's not used anywhere else. The only reason that it was public in the first place, was that the triangulation code used to be scattered all over the kernel. --- fj-kernel/src/algorithms/mod.rs | 5 ++--- fj-kernel/src/algorithms/triangulation.rs | 4 +--- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/fj-kernel/src/algorithms/mod.rs b/fj-kernel/src/algorithms/mod.rs index 5c2298dbb..14e0f3d1d 100644 --- a/fj-kernel/src/algorithms/mod.rs +++ b/fj-kernel/src/algorithms/mod.rs @@ -8,7 +8,6 @@ mod sweep; mod triangulation; pub use self::{ - approximation::Approximation, - sweep::sweep_shape, - triangulation::{delaunay, triangulate}, + approximation::Approximation, sweep::sweep_shape, + triangulation::triangulate, }; diff --git a/fj-kernel/src/algorithms/triangulation.rs b/fj-kernel/src/algorithms/triangulation.rs index c7e8c7c73..e2daeffa1 100644 --- a/fj-kernel/src/algorithms/triangulation.rs +++ b/fj-kernel/src/algorithms/triangulation.rs @@ -160,9 +160,7 @@ pub fn triangulate( } /// Create a Delaunay triangulation of all points -pub fn delaunay( - points: Vec>, -) -> Vec<[geometry::Point<2>; 3]> { +fn delaunay(points: Vec>) -> Vec<[geometry::Point<2>; 3]> { use spade::Triangulation as _; let triangulation = spade::DelaunayTriangulation::<_>::bulk_load(points)