diff --git a/crates/fj-core/src/algorithms/triangulate/delaunay.rs b/crates/fj-core/src/algorithms/triangulate/delaunay.rs index 306caefbbe..d3826334cf 100644 --- a/crates/fj-core/src/algorithms/triangulate/delaunay.rs +++ b/crates/fj-core/src/algorithms/triangulate/delaunay.rs @@ -47,20 +47,22 @@ pub fn triangulate( let mut triangles = Vec::new(); for triangle in triangulation.inner_faces() { let [v0, v1, v2] = triangle.vertices().map(|vertex| *vertex.data()); - let triangle_winding = Triangle::<2>::from_points([ + let triangle = Triangle::<2>::from_points([ v0.point_surface, v1.point_surface, v2.point_surface, - ]) - .expect("invalid triangle") - .winding(); + ]); + assert!( + triangle.is_valid(), + "Expecting triangles created by triangulation to be valid.", + ); let required_winding = match coord_handedness { Handedness::LeftHanded => Winding::Cw, Handedness::RightHanded => Winding::Ccw, }; - let triangle = if triangle_winding == required_winding { + let triangle = if triangle.winding() == required_winding { [v0, v1, v2] } else { [v0, v2, v1] diff --git a/crates/fj-core/src/algorithms/triangulate/polygon.rs b/crates/fj-core/src/algorithms/triangulate/polygon.rs index cb18de13fd..2795bd5b1a 100644 --- a/crates/fj-core/src/algorithms/triangulate/polygon.rs +++ b/crates/fj-core/src/algorithms/triangulate/polygon.rs @@ -42,7 +42,7 @@ impl Polygon { } pub fn contains_triangle(&self, triangle: impl Into>) -> bool { - let [a, b, c] = triangle.into().points(); + let [a, b, c] = triangle.into().points; let mut might_be_hole = true; diff --git a/crates/fj-export/src/lib.rs b/crates/fj-export/src/lib.rs index d193b1cef3..10d5b3df6b 100644 --- a/crates/fj-export/src/lib.rs +++ b/crates/fj-export/src/lib.rs @@ -89,7 +89,7 @@ pub fn export_stl( ) -> Result<(), Error> { let points = mesh .triangles() - .map(|triangle| triangle.inner.points()) + .map(|triangle| triangle.inner.points) .collect::>(); let vertices = points.iter().map(|points| { @@ -136,7 +136,7 @@ pub fn export_obj( ) -> Result<(), Error> { for (cnt, t) in mesh.triangles().enumerate() { // write each point of the triangle - for v in t.inner.points() { + for v in t.inner.points { wavefront_rs::obj::writer::Writer { auto_newline: true } .write( &mut write, diff --git a/crates/fj-interop/src/mesh.rs b/crates/fj-interop/src/mesh.rs index 032e5ef4fa..0635e56892 100644 --- a/crates/fj-interop/src/mesh.rs +++ b/crates/fj-interop/src/mesh.rs @@ -80,7 +80,7 @@ impl Mesh> { ) { let triangle = triangle.into(); - for point in triangle.points() { + for point in triangle.points { self.push_vertex(point); } diff --git a/crates/fj-math/src/line.rs b/crates/fj-math/src/line.rs index 1e598ae49e..df38100126 100644 --- a/crates/fj-math/src/line.rs +++ b/crates/fj-math/src/line.rs @@ -99,7 +99,7 @@ impl Line { // The triangle is valid only, if the three points are not on the // same line. - Triangle::from_points([a, b, c]).is_ok() + Triangle::from_points([a, b, c]).is_valid() }; if other_origin_is_not_on_self { diff --git a/crates/fj-math/src/transform.rs b/crates/fj-math/src/transform.rs index 61a32621a8..8b7e5d7ec1 100644 --- a/crates/fj-math/src/transform.rs +++ b/crates/fj-math/src/transform.rs @@ -78,7 +78,7 @@ impl Transform { /// Transform the given triangle pub fn transform_triangle(&self, triangle: &Triangle<3>) -> Triangle<3> { - let [a, b, c] = &triangle.points(); + let [a, b, c] = &triangle.points; Triangle::from([ self.transform_point(a), self.transform_point(b), diff --git a/crates/fj-math/src/triangle.rs b/crates/fj-math/src/triangle.rs index 0dcd417e33..92cbbb0fa7 100644 --- a/crates/fj-math/src/triangle.rs +++ b/crates/fj-math/src/triangle.rs @@ -1,3 +1,4 @@ +use approx::AbsDiffEq; use parry3d_f64::query::{Ray, RayCast as _}; use crate::Vector; @@ -11,34 +12,35 @@ use super::{Point, Scalar}; #[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash, Ord, PartialOrd)] #[repr(C)] pub struct Triangle { - points: [Point; 3], + /// The points that make up the triangle + pub points: [Point; 3], } impl Triangle { /// Construct a triangle from three points - /// - /// Returns an error, if the points don't form a triangle. - pub fn from_points( - points: [impl Into>; 3], - ) -> Result> { + pub fn from_points(points: [impl Into>; 3]) -> Self { let points = points.map(Into::into); - - let area = { - let [a, b, c] = points.map(Point::to_xyz); - (b - a).cross(&(c - a)).magnitude() - }; - - // A triangle is not valid if it doesn't span any area - if area != Scalar::from(0.0) { - Ok(Self { points }) - } else { - Err(NotATriangle { points }) - } + Self { points } } - /// Access the triangle's points - pub fn points(&self) -> [Point; 3] { - self.points + /// # Determine whether the triangle is valid + /// + /// A triangle is valid, if it is not degenerate. In a degenerate triangle, + /// the three points do not form an actual triangle, but a line or even a + /// single point. + /// + /// ## Implementation Note + /// + /// Right now, this function computes the area of the triangle, and compares + /// it against [`Scalar`]'s default epsilon value. This might not be + /// flexible enough for all use cases. + /// + /// Long-term, it might become necessary to add some way to override the + /// epsilon value used within this function. + pub fn is_valid(&self) -> bool { + let [a, b, c] = self.points; + let area = (b - a).outer(&(c - a)).magnitude(); + area > Scalar::default_epsilon() } /// Normalize the triangle @@ -81,7 +83,7 @@ impl Triangle<2> { impl Triangle<3> { /// Convert the triangle to a Parry triangle pub fn to_parry(self) -> parry3d_f64::shape::Triangle { - self.points().map(|vertex| vertex.to_na()).into() + self.points.map(|vertex| vertex.to_na()).into() } /// Cast a ray against the Triangle @@ -117,16 +119,10 @@ where P: Into>, { fn from(points: [P; 3]) -> Self { - Self::from_points(points).expect("invalid triangle") + Self::from_points(points) } } -/// Returned by [`Triangle::from_points`], if the points don't form a triangle -#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] -pub struct NotATriangle { - pub points: [Point; 3], -} - /// Winding direction of a triangle. #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] pub enum Winding { @@ -158,35 +154,37 @@ mod tests { #[test] fn valid_triangle_2d() { let a = Point::from([0.0, 0.0]); - let b = Point::from([1.0, 1.0]); - let c = Point::from([1.0, 2.0]); - let _triangle = Triangle::from([a, b, c]); + let b = Point::from([1.0, 0.0]); + let c = Point::from([0.0, 1.0]); + + assert!(Triangle::from_points([a, b, c]).is_valid()); } #[test] fn valid_triangle_3d() { let a = Point::from([0.0, 0.0, 0.0]); - let b = Point::from([1.0, 1.0, 0.0]); - let c = Point::from([1.0, 2.0, 0.0]); - let _triangle = Triangle::from([a, b, c]); + let b = Point::from([0.0, 1.0, 0.0]); + let c = Point::from([1.0, 0.0, 0.0]); + + assert!(Triangle::from_points([a, b, c]).is_valid()); } #[test] - #[should_panic] fn invalid_triangle_2d() { let a = Point::from([0.0, 0.0]); - let b = Point::from([1.0, 1.0]); - let c = Point::from([2.0, 2.0]); - let _triangle = Triangle::from([a, b, c]); + let b = Point::from([1.0, 0.0]); + let c = Point::from([2.0, 0.0]); + + assert!(!Triangle::from_points([a, b, c]).is_valid()); } #[test] - #[should_panic] fn invalid_triangle_3d() { let a = Point::from([0.0, 0.0, 0.0]); - let b = Point::from([1.0, 1.0, 1.0]); - let c = Point::from([2.0, 2.0, 2.0]); - let _triangle = Triangle::from([a, b, c]); + let b = Point::from([1.0, 0.0, 0.0]); + let c = Point::from([2.0, 0.0, 0.0]); + + assert!(!Triangle::from_points([a, b, c]).is_valid()); } #[test] diff --git a/crates/fj-viewer/src/graphics/vertices.rs b/crates/fj-viewer/src/graphics/vertices.rs index 2f2af8a6c7..14e1a29c8a 100644 --- a/crates/fj-viewer/src/graphics/vertices.rs +++ b/crates/fj-viewer/src/graphics/vertices.rs @@ -29,7 +29,7 @@ impl From<&Mesh>> for Vertices { let mut m = Mesh::new(); for triangle in mesh.triangles() { - let [a, b, c] = triangle.inner.points(); + let [a, b, c] = triangle.inner.points; let normal = (b - a).cross(&(c - a)).normalize(); let color = triangle.color;