Skip to content

Commit

Permalink
introduction of ConvexPolygon and ConvexPolygonMeshBuilder (#15544)
Browse files Browse the repository at this point in the history
# Objective

- As discussed on
[Discord](https://discord.com/channels/691052431525675048/1203087353850364004/1285300659746246849),
implement a `ConvexPolygon` 2D math primitive and associated mesh
builder.
- The original goal was to have a mesh builder for the simplest (i.e.
convex) polygons.

## Solution

- The `ConvexPolygon` is created from its vertices.
- The convexity of the polygon is checked when created via `new()` by
verifying that the winding order of all the triangles formed with
adjacent vertices is the same.
- The `ConvexPolygonMeshBuilder` uses an anchor vertex and goes through
every adjacent pair of vertices in the polygon to form triangles that
fill up the polygon.

## Testing

- Tested locally with my own simple `ConvexPolygonMeshBuilder` usage.
  • Loading branch information
tomi-font authored Oct 8, 2024
1 parent b48f9e2 commit 3d8e56f
Show file tree
Hide file tree
Showing 3 changed files with 114 additions and 3 deletions.
62 changes: 62 additions & 0 deletions crates/bevy_math/src/primitives/dim2.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use core::f32::consts::{FRAC_1_SQRT_2, FRAC_PI_2, FRAC_PI_3, PI};
use thiserror::Error;

use super::{Measured2d, Primitive2d, WindingOrder};
use crate::{
Expand Down Expand Up @@ -1603,6 +1604,67 @@ impl<const N: usize> Polygon<N> {
}
}

/// A convex polygon with `N` vertices.
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
pub struct ConvexPolygon<const N: usize> {
/// The vertices of the [`ConvexPolygon`].
#[cfg_attr(feature = "serialize", serde(with = "super::serde::array"))]
pub vertices: [Vec2; N],
}
impl<const N: usize> Primitive2d for ConvexPolygon<N> {}

/// An error that happens when creating a [`ConvexPolygon`].
#[derive(Error, Debug, Clone)]
pub enum ConvexPolygonError {
/// The created polygon is not convex.
#[error("The created polygon is not convex")]
Concave,
}

impl<const N: usize> ConvexPolygon<N> {
fn triangle_winding_order(
&self,
a_index: usize,
b_index: usize,
c_index: usize,
) -> WindingOrder {
let a = self.vertices[a_index];
let b = self.vertices[b_index];
let c = self.vertices[c_index];
Triangle2d::new(a, b, c).winding_order()
}

/// Create a [`ConvexPolygon`] from its `vertices`.
///
/// # Errors
///
/// Returns [`ConvexPolygonError::Concave`] if the `vertices` do not form a convex polygon.
pub fn new(vertices: [Vec2; N]) -> Result<Self, ConvexPolygonError> {
let polygon = Self::new_unchecked(vertices);
let ref_winding_order = polygon.triangle_winding_order(N - 1, 0, 1);
for i in 1..N {
let winding_order = polygon.triangle_winding_order(i - 1, i, (i + 1) % N);
if winding_order != ref_winding_order {
return Err(ConvexPolygonError::Concave);
}
}
Ok(polygon)
}

/// Create a [`ConvexPolygon`] from its `vertices`, without checks.
/// Use this version only if you know that the `vertices` make up a convex polygon.
#[inline(always)]
pub fn new_unchecked(vertices: [Vec2; N]) -> Self {
Self { vertices }
}
}

/// A polygon with a variable number of vertices, allocated on the heap
/// in a `Box<[Vec2]>`.
///
Expand Down
53 changes: 51 additions & 2 deletions crates/bevy_mesh/src/primitives/dim2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ use super::{Extrudable, MeshBuilder, Meshable};
use bevy_math::{
ops,
primitives::{
Annulus, Capsule2d, Circle, CircularSector, CircularSegment, Ellipse, Rectangle,
RegularPolygon, Rhombus, Triangle2d, Triangle3d, WindingOrder,
Annulus, Capsule2d, Circle, CircularSector, CircularSegment, ConvexPolygon, Ellipse,
Rectangle, RegularPolygon, Rhombus, Triangle2d, Triangle3d, WindingOrder,
},
FloatExt, Vec2,
};
Expand Down Expand Up @@ -398,6 +398,55 @@ impl From<CircularSegment> for Mesh {
}
}

/// A builder used for creating a [`Mesh`] with a [`ConvexPolygon`] shape.
pub struct ConvexPolygonMeshBuilder<const N: usize> {
pub vertices: [Vec2; N],
}

impl<const N: usize> Meshable for ConvexPolygon<N> {
type Output = ConvexPolygonMeshBuilder<N>;

fn mesh(&self) -> Self::Output {
Self::Output {
vertices: self.vertices,
}
}
}

impl<const N: usize> MeshBuilder for ConvexPolygonMeshBuilder<N> {
fn build(&self) -> Mesh {
let mut indices = Vec::with_capacity((N - 2) * 3);
let mut positions = Vec::with_capacity(N);

for vertex in self.vertices {
positions.push([vertex.x, vertex.y, 0.0]);
}
for i in 2..N as u32 {
indices.extend_from_slice(&[0, i - 1, i]);
}
Mesh::new(
PrimitiveTopology::TriangleList,
RenderAssetUsages::default(),
)
.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)
.with_inserted_indices(Indices::U32(indices))
}
}

impl<const N: usize> Extrudable for ConvexPolygonMeshBuilder<N> {
fn perimeter(&self) -> Vec<PerimeterSegment> {
vec![PerimeterSegment::Flat {
indices: (0..N as u32).chain([0]).collect(),
}]
}
}

impl<const N: usize> From<ConvexPolygon<N>> for Mesh {
fn from(polygon: ConvexPolygon<N>) -> Self {
polygon.mesh().build()
}
}

/// A builder used for creating a [`Mesh`] with a [`RegularPolygon`] shape.
pub struct RegularPolygonMeshBuilder {
circumradius: f32,
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_mesh/src/primitives/extrusion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ impl PerimeterSegment {
}
}

/// A trait for required for implementing `Meshable` for `Extrusion<T>`.
/// A trait required for implementing `Meshable` for `Extrusion<T>`.
///
/// ## Warning
///
Expand Down

0 comments on commit 3d8e56f

Please sign in to comment.