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.
This commit is contained in:
parent
96444b24fd
commit
e1c8d60f91
@ -1,4 +1,4 @@
|
|||||||
use super::Primitive2d;
|
use super::{Primitive2d, WindingOrder};
|
||||||
use crate::Vec2;
|
use crate::Vec2;
|
||||||
|
|
||||||
/// A normalized vector pointing in a direction in 2D space
|
/// A normalized vector pointing in a direction in 2D space
|
||||||
@ -174,7 +174,7 @@ impl BoxedPolyline2d {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A triangle in 2D space
|
/// A triangle in 2D space
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub struct Triangle2d {
|
pub struct Triangle2d {
|
||||||
/// The vertices of the triangle
|
/// The vertices of the triangle
|
||||||
pub vertices: [Vec2; 3],
|
pub vertices: [Vec2; 3],
|
||||||
@ -182,12 +182,32 @@ pub struct Triangle2d {
|
|||||||
impl Primitive2d for Triangle2d {}
|
impl Primitive2d for Triangle2d {}
|
||||||
|
|
||||||
impl 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 {
|
pub fn new(a: Vec2, b: Vec2, c: Vec2) -> Self {
|
||||||
Self {
|
Self {
|
||||||
vertices: [a, b, c],
|
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
|
/// 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -12,3 +12,16 @@ pub trait Primitive2d {}
|
|||||||
|
|
||||||
/// A marker trait for 3D primitives
|
/// A marker trait for 3D primitives
|
||||||
pub trait Primitive3d {}
|
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,
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user