Skip to content

Commit e1c8d60

Browse files
authored
Add winding order for Triangle2d (#10620)
# 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.
1 parent 96444b2 commit e1c8d60

File tree

2 files changed

+70
-3
lines changed

2 files changed

+70
-3
lines changed

crates/bevy_math/src/primitives/dim2.rs

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use super::Primitive2d;
1+
use super::{Primitive2d, WindingOrder};
22
use crate::Vec2;
33

44
/// A normalized vector pointing in a direction in 2D space
@@ -174,20 +174,40 @@ impl BoxedPolyline2d {
174174
}
175175

176176
/// A triangle in 2D space
177-
#[derive(Clone, Debug)]
177+
#[derive(Clone, Debug, PartialEq)]
178178
pub struct Triangle2d {
179179
/// The vertices of the triangle
180180
pub vertices: [Vec2; 3],
181181
}
182182
impl Primitive2d for Triangle2d {}
183183

184184
impl Triangle2d {
185-
/// Create a new `Triangle2d` from `a`, `b`, and `c`,
185+
/// Create a new `Triangle2d` from points `a`, `b`, and `c`
186186
pub fn new(a: Vec2, b: Vec2, c: Vec2) -> Self {
187187
Self {
188188
vertices: [a, b, c],
189189
}
190190
}
191+
192+
/// Get the [`WindingOrder`] of the triangle
193+
#[doc(alias = "orientation")]
194+
pub fn winding_order(&self) -> WindingOrder {
195+
let [a, b, c] = self.vertices;
196+
let area = (b - a).perp_dot(c - a);
197+
if area > f32::EPSILON {
198+
WindingOrder::CounterClockwise
199+
} else if area < -f32::EPSILON {
200+
WindingOrder::Clockwise
201+
} else {
202+
WindingOrder::Invalid
203+
}
204+
}
205+
206+
/// Reverse the [`WindingOrder`] of the triangle
207+
/// by swapping the second and third vertices
208+
pub fn reverse(&mut self) {
209+
self.vertices.swap(1, 2);
210+
}
191211
}
192212

193213
/// A rectangle primitive
@@ -298,3 +318,37 @@ impl RegularPolygon {
298318
}
299319
}
300320
}
321+
322+
#[cfg(test)]
323+
mod tests {
324+
use super::*;
325+
326+
#[test]
327+
fn triangle_winding_order() {
328+
let mut cw_triangle = Triangle2d::new(
329+
Vec2::new(0.0, 2.0),
330+
Vec2::new(-0.5, -1.2),
331+
Vec2::new(-1.0, -1.0),
332+
);
333+
assert_eq!(cw_triangle.winding_order(), WindingOrder::Clockwise);
334+
335+
let ccw_triangle = Triangle2d::new(
336+
Vec2::new(0.0, 2.0),
337+
Vec2::new(-1.0, -1.0),
338+
Vec2::new(-0.5, -1.2),
339+
);
340+
assert_eq!(ccw_triangle.winding_order(), WindingOrder::CounterClockwise);
341+
342+
// The clockwise triangle should be the same as the counterclockwise
343+
// triangle when reversed
344+
cw_triangle.reverse();
345+
assert_eq!(cw_triangle, ccw_triangle);
346+
347+
let invalid_triangle = Triangle2d::new(
348+
Vec2::new(0.0, 2.0),
349+
Vec2::new(0.0, -1.0),
350+
Vec2::new(0.0, -1.2),
351+
);
352+
assert_eq!(invalid_triangle.winding_order(), WindingOrder::Invalid);
353+
}
354+
}

crates/bevy_math/src/primitives/mod.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,16 @@ pub trait Primitive2d {}
1212

1313
/// A marker trait for 3D primitives
1414
pub trait Primitive3d {}
15+
16+
/// The winding order for a set of points
17+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
18+
pub enum WindingOrder {
19+
/// A clockwise winding order
20+
Clockwise,
21+
/// A counterclockwise winding order
22+
CounterClockwise,
23+
/// An invalid winding order indicating that it could not be computed reliably.
24+
/// This often happens in *degenerate cases* where the points lie on the same line
25+
#[doc(alias = "Degenerate")]
26+
Invalid,
27+
}

0 commit comments

Comments
 (0)