From 3d8e56f76612869acac3bf2bec2b2872275a7e86 Mon Sep 17 00:00:00 2001 From: Tomi Fontanilles Date: Tue, 8 Oct 2024 17:02:38 +0200 Subject: [PATCH] introduction of `ConvexPolygon` and `ConvexPolygonMeshBuilder` (#15544) # 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. --- crates/bevy_math/src/primitives/dim2.rs | 62 ++++++++++++++++++++ crates/bevy_mesh/src/primitives/dim2.rs | 53 ++++++++++++++++- crates/bevy_mesh/src/primitives/extrusion.rs | 2 +- 3 files changed, 114 insertions(+), 3 deletions(-) diff --git a/crates/bevy_math/src/primitives/dim2.rs b/crates/bevy_math/src/primitives/dim2.rs index 597fe8e8916ca..1bf415715111d 100644 --- a/crates/bevy_math/src/primitives/dim2.rs +++ b/crates/bevy_math/src/primitives/dim2.rs @@ -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::{ @@ -1603,6 +1604,67 @@ impl Polygon { } } +/// 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 { + /// The vertices of the [`ConvexPolygon`]. + #[cfg_attr(feature = "serialize", serde(with = "super::serde::array"))] + pub vertices: [Vec2; N], +} +impl Primitive2d for ConvexPolygon {} + +/// 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 ConvexPolygon { + 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 { + 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]>`. /// diff --git a/crates/bevy_mesh/src/primitives/dim2.rs b/crates/bevy_mesh/src/primitives/dim2.rs index 70b1fd672231f..a8b3237b7a80b 100644 --- a/crates/bevy_mesh/src/primitives/dim2.rs +++ b/crates/bevy_mesh/src/primitives/dim2.rs @@ -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, }; @@ -398,6 +398,55 @@ impl From for Mesh { } } +/// A builder used for creating a [`Mesh`] with a [`ConvexPolygon`] shape. +pub struct ConvexPolygonMeshBuilder { + pub vertices: [Vec2; N], +} + +impl Meshable for ConvexPolygon { + type Output = ConvexPolygonMeshBuilder; + + fn mesh(&self) -> Self::Output { + Self::Output { + vertices: self.vertices, + } + } +} + +impl MeshBuilder for ConvexPolygonMeshBuilder { + 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 Extrudable for ConvexPolygonMeshBuilder { + fn perimeter(&self) -> Vec { + vec![PerimeterSegment::Flat { + indices: (0..N as u32).chain([0]).collect(), + }] + } +} + +impl From> for Mesh { + fn from(polygon: ConvexPolygon) -> Self { + polygon.mesh().build() + } +} + /// A builder used for creating a [`Mesh`] with a [`RegularPolygon`] shape. pub struct RegularPolygonMeshBuilder { circumradius: f32, diff --git a/crates/bevy_mesh/src/primitives/extrusion.rs b/crates/bevy_mesh/src/primitives/extrusion.rs index 0093404b8ed77..64531a1cacebf 100644 --- a/crates/bevy_mesh/src/primitives/extrusion.rs +++ b/crates/bevy_mesh/src/primitives/extrusion.rs @@ -67,7 +67,7 @@ impl PerimeterSegment { } } -/// A trait for required for implementing `Meshable` for `Extrusion`. +/// A trait required for implementing `Meshable` for `Extrusion`. /// /// ## Warning ///