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

Allow degenerate triangles #2434

Merged
merged 12 commits into from
Jul 30, 2024
12 changes: 7 additions & 5 deletions crates/fj-core/src/algorithms/triangulate/delaunay.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
2 changes: 1 addition & 1 deletion crates/fj-core/src/algorithms/triangulate/polygon.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ impl Polygon {
}

pub fn contains_triangle(&self, triangle: impl Into<Triangle<2>>) -> bool {
let [a, b, c] = triangle.into().points();
let [a, b, c] = triangle.into().points;

let mut might_be_hole = true;

Expand Down
4 changes: 2 additions & 2 deletions crates/fj-export/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<Vec<_>>();

let vertices = points.iter().map(|points| {
Expand Down Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion crates/fj-interop/src/mesh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ impl Mesh<Point<3>> {
) {
let triangle = triangle.into();

for point in triangle.points() {
for point in triangle.points {
self.push_vertex(point);
}

Expand Down
2 changes: 1 addition & 1 deletion crates/fj-math/src/line.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ impl<const D: usize> Line<D> {

// 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 {
Expand Down
2 changes: 1 addition & 1 deletion crates/fj-math/src/transform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
84 changes: 41 additions & 43 deletions crates/fj-math/src/triangle.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use approx::AbsDiffEq;
use parry3d_f64::query::{Ray, RayCast as _};

use crate::Vector;
Expand All @@ -11,34 +12,35 @@ use super::{Point, Scalar};
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash, Ord, PartialOrd)]
#[repr(C)]
pub struct Triangle<const D: usize> {
points: [Point<D>; 3],
/// The points that make up the triangle
pub points: [Point<D>; 3],
}

impl<const D: usize> Triangle<D> {
/// Construct a triangle from three points
///
/// Returns an error, if the points don't form a triangle.
pub fn from_points(
points: [impl Into<Point<D>>; 3],
) -> Result<Self, NotATriangle<D>> {
pub fn from_points(points: [impl Into<Point<D>>; 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<D>; 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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -117,16 +119,10 @@ where
P: Into<Point<D>>,
{
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<const D: usize> {
pub points: [Point<D>; 3],
}

/// Winding direction of a triangle.
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub enum Winding {
Expand Down Expand Up @@ -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]
Expand Down
2 changes: 1 addition & 1 deletion crates/fj-viewer/src/graphics/vertices.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ impl From<&Mesh<fj_math::Point<3>>> 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;
Expand Down