Skip to content

Commit

Permalink
Add winding order for Triangle2d (bevyengine#10620)
Browse files Browse the repository at this point in the history
# Objective

This PR adds some helpers for `Triangle2d` to work with its winding
order. This could also be extended to polygons (and `Triangle3d` once
it's added).

## Solution

- Add `WindingOrder` enum with `Clockwise`, `Counterclockwise` and
`Invalid` variants
- `Invalid` is for cases where the winding order can not be reliably
computed, i.e. the points lie on a single line and the area is zero
- Add `Triangle2d::winding_order` method that uses a signed surface area
to determine the winding order
- Add `Triangle2d::reverse` method that reverses the winding order by
swapping the second and third vertices

The API looks like this:

```rust
let mut triangle = Triangle2d::new(
    Vec2::new(0.0, 2.0),
    Vec2::new(-0.5, -1.2),
    Vec2::new(-1.0, -1.0),
);
assert_eq!(triangle.winding_order(), WindingOrder::Clockwise);

// Reverse winding order
triangle.reverse();
assert_eq!(triangle.winding_order(), WindingOrder::Counterclockwise);
```

I also added tests to make sure the methods work correctly. For now,
they live in the same file as the primitives.

## Open questions

- Should it be `Counterclockwise` or `CounterClockwise`? The first one
is more correct but perhaps a bit less readable. Counter-clockwise is
also a valid spelling, but it seems to be a lot less common than
counterclockwise.
- Is `WindingOrder::Invalid` a good name? Parry uses
`TriangleOrientation::Degenerate`, but I'm not a huge fan, at least as a
non-native English speaker. Any better suggestions?
- Is `WindingOrder` fine in `bevy_math::primitives`? It's not specific
to a dimension, so I put it there for now.
  • Loading branch information
Jondolf authored and Ray Redondo committed Jan 9, 2024
1 parent c0a65f8 commit dc0cf44
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 3 deletions.
60 changes: 57 additions & 3 deletions crates/bevy_math/src/primitives/dim2.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use super::Primitive2d;
use super::{Primitive2d, WindingOrder};
use crate::Vec2;

/// A normalized vector pointing in a direction in 2D space
Expand Down Expand Up @@ -174,20 +174,40 @@ impl BoxedPolyline2d {
}

/// A triangle in 2D space
#[derive(Clone, Debug)]
#[derive(Clone, Debug, PartialEq)]
pub struct Triangle2d {
/// The vertices of the triangle
pub vertices: [Vec2; 3],
}
impl Primitive2d for Triangle2d {}

impl Triangle2d {
/// Create a new `Triangle2d` from `a`, `b`, and `c`,
/// Create a new `Triangle2d` from points `a`, `b`, and `c`
pub fn new(a: Vec2, b: Vec2, c: Vec2) -> Self {
Self {
vertices: [a, b, c],
}
}

/// Get the [`WindingOrder`] of the triangle
#[doc(alias = "orientation")]
pub fn winding_order(&self) -> WindingOrder {
let [a, b, c] = self.vertices;
let area = (b - a).perp_dot(c - a);
if area > f32::EPSILON {
WindingOrder::CounterClockwise
} else if area < -f32::EPSILON {
WindingOrder::Clockwise
} else {
WindingOrder::Invalid
}
}

/// Reverse the [`WindingOrder`] of the triangle
/// by swapping the second and third vertices
pub fn reverse(&mut self) {
self.vertices.swap(1, 2);
}
}

/// A rectangle primitive
Expand Down Expand Up @@ -298,3 +318,37 @@ impl RegularPolygon {
}
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn triangle_winding_order() {
let mut cw_triangle = Triangle2d::new(
Vec2::new(0.0, 2.0),
Vec2::new(-0.5, -1.2),
Vec2::new(-1.0, -1.0),
);
assert_eq!(cw_triangle.winding_order(), WindingOrder::Clockwise);

let ccw_triangle = Triangle2d::new(
Vec2::new(0.0, 2.0),
Vec2::new(-1.0, -1.0),
Vec2::new(-0.5, -1.2),
);
assert_eq!(ccw_triangle.winding_order(), WindingOrder::CounterClockwise);

// The clockwise triangle should be the same as the counterclockwise
// triangle when reversed
cw_triangle.reverse();
assert_eq!(cw_triangle, ccw_triangle);

let invalid_triangle = Triangle2d::new(
Vec2::new(0.0, 2.0),
Vec2::new(0.0, -1.0),
Vec2::new(0.0, -1.2),
);
assert_eq!(invalid_triangle.winding_order(), WindingOrder::Invalid);
}
}
13 changes: 13 additions & 0 deletions crates/bevy_math/src/primitives/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,16 @@ pub trait Primitive2d {}

/// A marker trait for 3D primitives
pub trait Primitive3d {}

/// The winding order for a set of points
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum WindingOrder {
/// A clockwise winding order
Clockwise,
/// A counterclockwise winding order
CounterClockwise,
/// An invalid winding order indicating that it could not be computed reliably.
/// This often happens in *degenerate cases* where the points lie on the same line
#[doc(alias = "Degenerate")]
Invalid,
}

0 comments on commit dc0cf44

Please sign in to comment.