diff --git a/crates/bevy_math/src/primitives/dim2.rs b/crates/bevy_math/src/primitives/dim2.rs index 2752e4aed8040..35e5363dc6acd 100644 --- a/crates/bevy_math/src/primitives/dim2.rs +++ b/crates/bevy_math/src/primitives/dim2.rs @@ -1,4 +1,4 @@ -use super::Primitive2d; +use super::{Primitive2d, WindingOrder}; use crate::Vec2; /// A normalized vector pointing in a direction in 2D space @@ -174,7 +174,7 @@ 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], @@ -182,12 +182,32 @@ pub struct Triangle2d { 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 @@ -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); + } +} diff --git a/crates/bevy_math/src/primitives/mod.rs b/crates/bevy_math/src/primitives/mod.rs index a3e98d0397b8a..da3532397b6ea 100644 --- a/crates/bevy_math/src/primitives/mod.rs +++ b/crates/bevy_math/src/primitives/mod.rs @@ -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, +}