Skip to content

Commit

Permalink
Merge pull request #2434 from hannobraun/triangle
Browse files Browse the repository at this point in the history
Allow degenerate triangles
  • Loading branch information
hannobraun authored Jul 30, 2024
2 parents 1dbd431 + b553b1e commit d1d730a
Show file tree
Hide file tree
Showing 8 changed files with 55 additions and 55 deletions.
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

0 comments on commit d1d730a

Please sign in to comment.