Add Meshable trait and implement meshing for 2D primitives (#11431)
				
					
				
			# Objective The first part of #10569, split up from #11007. The goal is to implement meshing support for Bevy's new geometric primitives, starting with 2D primitives. 3D meshing will be added in a follow-up, and we can consider removing the old mesh shapes completely. ## Solution Add a `Meshable` trait that primitives need to implement to support meshing, as suggested by the [RFC](https://github.com/bevyengine/rfcs/blob/main/rfcs/12-primitive-shapes.md#meshing). ```rust /// A trait for shapes that can be turned into a [`Mesh`]. pub trait Meshable { /// The output of [`Self::mesh`]. This can either be a [`Mesh`] /// or a builder used for creating a [`Mesh`]. type Output; /// Creates a [`Mesh`] for a shape. fn mesh(&self) -> Self::Output; } ``` This PR implements it for the following primitives: - `Circle` - `Ellipse` - `Rectangle` - `RegularPolygon` - `Triangle2d` The `mesh` method typically returns a builder-like struct such as `CircleMeshBuilder`. This is needed to support shape-specific configuration for things like mesh resolution or UV configuration: ```rust meshes.add(Circle { radius: 0.5 }.mesh().resolution(64)); ``` Note that if no configuration is needed, you can even skip calling `mesh` because `From<MyPrimitive>` is implemented for `Mesh`: ```rust meshes.add(Circle { radius: 0.5 }); ``` I also updated the `2d_shapes` example to use primitives, and tweaked the colors to have better contrast against the dark background. Before:  After:  Here you can see the UVs and different facing directions: (taken from #11007, so excuse the 3D primitives at the bottom left)  --- ## Changelog - Added `bevy_render::mesh::primitives` module - Added `Meshable` trait and implemented it for: - `Circle` - `Ellipse` - `Rectangle` - `RegularPolygon` - `Triangle2d` - Implemented `Default` and `Copy` for several 2D primitives - Updated `2d_shapes` example to use primitives - Tweaked colors in `2d_shapes` example to have better contrast against the (new-ish) dark background --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
This commit is contained in:
		
							parent
							
								
									149a313850
								
							
						
					
					
						commit
						2bf481c03b
					
				| @ -402,7 +402,7 @@ doc-scrape-examples = true | |||||||
| 
 | 
 | ||||||
| [package.metadata.example.2d_shapes] | [package.metadata.example.2d_shapes] | ||||||
| name = "2D Shapes" | name = "2D Shapes" | ||||||
| description = "Renders a rectangle, circle, and hexagon" | description = "Renders simple 2D primitive shapes like circles and polygons" | ||||||
| category = "2D Rendering" | category = "2D Rendering" | ||||||
| wasm = true | wasm = true | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -90,6 +90,13 @@ pub struct Circle { | |||||||
| } | } | ||||||
| impl Primitive2d for Circle {} | impl Primitive2d for Circle {} | ||||||
| 
 | 
 | ||||||
|  | impl Default for Circle { | ||||||
|  |     /// Returns the default [`Circle`] with a radius of `0.5`.
 | ||||||
|  |     fn default() -> Self { | ||||||
|  |         Self { radius: 0.5 } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| impl Circle { | impl Circle { | ||||||
|     /// Create a new [`Circle`] from a `radius`
 |     /// Create a new [`Circle`] from a `radius`
 | ||||||
|     #[inline(always)] |     #[inline(always)] | ||||||
| @ -147,6 +154,15 @@ pub struct Ellipse { | |||||||
| } | } | ||||||
| impl Primitive2d for Ellipse {} | impl Primitive2d for Ellipse {} | ||||||
| 
 | 
 | ||||||
|  | impl Default for Ellipse { | ||||||
|  |     /// Returns the default [`Ellipse`] with a half-width of `1.0` and a half-height of `0.5`.
 | ||||||
|  |     fn default() -> Self { | ||||||
|  |         Self { | ||||||
|  |             half_size: Vec2::new(1.0, 0.5), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| impl Ellipse { | impl Ellipse { | ||||||
|     /// Create a new `Ellipse` from half of its width and height.
 |     /// Create a new `Ellipse` from half of its width and height.
 | ||||||
|     ///
 |     ///
 | ||||||
| @ -197,6 +213,15 @@ pub struct Plane2d { | |||||||
| } | } | ||||||
| impl Primitive2d for Plane2d {} | impl Primitive2d for Plane2d {} | ||||||
| 
 | 
 | ||||||
|  | impl Default for Plane2d { | ||||||
|  |     /// Returns the default [`Plane2d`] with a normal pointing in the `+Y` direction.
 | ||||||
|  |     fn default() -> Self { | ||||||
|  |         Self { | ||||||
|  |             normal: Direction2d::Y, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| impl Plane2d { | impl Plane2d { | ||||||
|     /// Create a new `Plane2d` from a normal
 |     /// Create a new `Plane2d` from a normal
 | ||||||
|     ///
 |     ///
 | ||||||
| @ -343,10 +368,19 @@ pub struct Triangle2d { | |||||||
| } | } | ||||||
| impl Primitive2d for Triangle2d {} | impl Primitive2d for Triangle2d {} | ||||||
| 
 | 
 | ||||||
|  | impl Default for Triangle2d { | ||||||
|  |     /// Returns the default [`Triangle2d`] with the vertices `[0.0, 0.5]`, `[-0.5, -0.5]`, and `[0.5, -0.5]`.
 | ||||||
|  |     fn default() -> Self { | ||||||
|  |         Self { | ||||||
|  |             vertices: [Vec2::Y * 0.5, Vec2::new(-0.5, -0.5), Vec2::new(0.5, -0.5)], | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| impl Triangle2d { | impl Triangle2d { | ||||||
|     /// Create a new `Triangle2d` from points `a`, `b`, and `c`
 |     /// Create a new `Triangle2d` from points `a`, `b`, and `c`
 | ||||||
|     #[inline(always)] |     #[inline(always)] | ||||||
|     pub fn new(a: Vec2, b: Vec2, c: Vec2) -> Self { |     pub const fn new(a: Vec2, b: Vec2, c: Vec2) -> Self { | ||||||
|         Self { |         Self { | ||||||
|             vertices: [a, b, c], |             vertices: [a, b, c], | ||||||
|         } |         } | ||||||
| @ -438,6 +472,15 @@ pub struct Rectangle { | |||||||
|     pub half_size: Vec2, |     pub half_size: Vec2, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | impl Default for Rectangle { | ||||||
|  |     /// Returns the default [`Rectangle`] with a half-width and half-height of `0.5`.
 | ||||||
|  |     fn default() -> Self { | ||||||
|  |         Self { | ||||||
|  |             half_size: Vec2::splat(0.5), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| impl Rectangle { | impl Rectangle { | ||||||
|     /// Create a new `Rectangle` from a full width and height
 |     /// Create a new `Rectangle` from a full width and height
 | ||||||
|     #[inline(always)] |     #[inline(always)] | ||||||
| @ -559,9 +602,19 @@ pub struct RegularPolygon { | |||||||
| } | } | ||||||
| impl Primitive2d for RegularPolygon {} | impl Primitive2d for RegularPolygon {} | ||||||
| 
 | 
 | ||||||
|  | impl Default for RegularPolygon { | ||||||
|  |     /// Returns the default [`RegularPolygon`] with six sides (a hexagon) and a circumradius of `0.5`.
 | ||||||
|  |     fn default() -> Self { | ||||||
|  |         Self { | ||||||
|  |             circumcircle: Circle { radius: 0.5 }, | ||||||
|  |             sides: 6, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| impl RegularPolygon { | impl RegularPolygon { | ||||||
|     /// Create a new `RegularPolygon`
 |     /// Create a new `RegularPolygon`
 | ||||||
|     /// from the radius of the circumcircle and number of sides
 |     /// from the radius of the circumcircle and a number of sides
 | ||||||
|     ///
 |     ///
 | ||||||
|     /// # Panics
 |     /// # Panics
 | ||||||
|     ///
 |     ///
 | ||||||
|  | |||||||
| @ -34,7 +34,7 @@ pub mod prelude { | |||||||
|             Projection, |             Projection, | ||||||
|         }, |         }, | ||||||
|         color::Color, |         color::Color, | ||||||
|         mesh::{morph::MorphWeights, shape, Mesh}, |         mesh::{morph::MorphWeights, primitives::Meshable, shape, Mesh}, | ||||||
|         render_resource::Shader, |         render_resource::Shader, | ||||||
|         spatial_bundle::SpatialBundle, |         spatial_bundle::SpatialBundle, | ||||||
|         texture::{Image, ImagePlugin}, |         texture::{Image, ImagePlugin}, | ||||||
|  | |||||||
| @ -1,10 +1,12 @@ | |||||||
| #[allow(clippy::module_inception)] | #[allow(clippy::module_inception)] | ||||||
| mod mesh; | mod mesh; | ||||||
| pub mod morph; | pub mod morph; | ||||||
|  | pub mod primitives; | ||||||
| /// Generation for some primitive shape meshes.
 | /// Generation for some primitive shape meshes.
 | ||||||
| pub mod shape; | pub mod shape; | ||||||
| 
 | 
 | ||||||
| pub use mesh::*; | pub use mesh::*; | ||||||
|  | pub use primitives::*; | ||||||
| 
 | 
 | ||||||
| use crate::{prelude::Image, render_asset::RenderAssetPlugin}; | use crate::{prelude::Image, render_asset::RenderAssetPlugin}; | ||||||
| use bevy_app::{App, Plugin}; | use bevy_app::{App, Plugin}; | ||||||
|  | |||||||
							
								
								
									
										268
									
								
								crates/bevy_render/src/mesh/primitives/dim2.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										268
									
								
								crates/bevy_render/src/mesh/primitives/dim2.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,268 @@ | |||||||
|  | use crate::{ | ||||||
|  |     mesh::{Indices, Mesh}, | ||||||
|  |     render_asset::RenderAssetPersistencePolicy, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | use super::Meshable; | ||||||
|  | use bevy_math::{ | ||||||
|  |     primitives::{Circle, Ellipse, Rectangle, RegularPolygon, Triangle2d, WindingOrder}, | ||||||
|  |     Vec2, | ||||||
|  | }; | ||||||
|  | use wgpu::PrimitiveTopology; | ||||||
|  | 
 | ||||||
|  | /// A builder used for creating a [`Mesh`] with a [`Circle`] shape.
 | ||||||
|  | #[derive(Clone, Copy, Debug)] | ||||||
|  | pub struct CircleMeshBuilder { | ||||||
|  |     /// The [`Circle`] shape.
 | ||||||
|  |     pub circle: Circle, | ||||||
|  |     /// The number of vertices used for the circle mesh.
 | ||||||
|  |     /// The default is `32`.
 | ||||||
|  |     #[doc(alias = "vertices")] | ||||||
|  |     pub resolution: usize, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl Default for CircleMeshBuilder { | ||||||
|  |     fn default() -> Self { | ||||||
|  |         Self { | ||||||
|  |             circle: Circle::default(), | ||||||
|  |             resolution: 32, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl CircleMeshBuilder { | ||||||
|  |     /// Creates a new [`CircleMeshBuilder`] from a given radius and vertex count.
 | ||||||
|  |     #[inline] | ||||||
|  |     pub const fn new(radius: f32, resolution: usize) -> Self { | ||||||
|  |         Self { | ||||||
|  |             circle: Circle { radius }, | ||||||
|  |             resolution, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// Sets the number of vertices used for the circle mesh.
 | ||||||
|  |     #[inline] | ||||||
|  |     #[doc(alias = "vertices")] | ||||||
|  |     pub const fn resolution(mut self, resolution: usize) -> Self { | ||||||
|  |         self.resolution = resolution; | ||||||
|  |         self | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// Builds a [`Mesh`] based on the configuration in `self`.
 | ||||||
|  |     pub fn build(&self) -> Mesh { | ||||||
|  |         RegularPolygon::new(self.circle.radius, self.resolution).mesh() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl Meshable for Circle { | ||||||
|  |     type Output = CircleMeshBuilder; | ||||||
|  | 
 | ||||||
|  |     fn mesh(&self) -> Self::Output { | ||||||
|  |         CircleMeshBuilder { | ||||||
|  |             circle: *self, | ||||||
|  |             ..Default::default() | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl From<Circle> for Mesh { | ||||||
|  |     fn from(circle: Circle) -> Self { | ||||||
|  |         circle.mesh().build() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl From<CircleMeshBuilder> for Mesh { | ||||||
|  |     fn from(circle: CircleMeshBuilder) -> Self { | ||||||
|  |         circle.build() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl Meshable for RegularPolygon { | ||||||
|  |     type Output = Mesh; | ||||||
|  | 
 | ||||||
|  |     fn mesh(&self) -> Self::Output { | ||||||
|  |         // The ellipse mesh is just a regular polygon with two radii
 | ||||||
|  |         Ellipse::new(self.circumcircle.radius, self.circumcircle.radius) | ||||||
|  |             .mesh() | ||||||
|  |             .resolution(self.sides) | ||||||
|  |             .build() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl From<RegularPolygon> for Mesh { | ||||||
|  |     fn from(polygon: RegularPolygon) -> Self { | ||||||
|  |         polygon.mesh() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /// A builder used for creating a [`Mesh`] with an [`Ellipse`] shape.
 | ||||||
|  | #[derive(Clone, Copy, Debug)] | ||||||
|  | pub struct EllipseMeshBuilder { | ||||||
|  |     /// The [`Ellipse`] shape.
 | ||||||
|  |     pub ellipse: Ellipse, | ||||||
|  |     /// The number of vertices used for the ellipse mesh.
 | ||||||
|  |     /// The default is `32`.
 | ||||||
|  |     #[doc(alias = "vertices")] | ||||||
|  |     pub resolution: usize, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl Default for EllipseMeshBuilder { | ||||||
|  |     fn default() -> Self { | ||||||
|  |         Self { | ||||||
|  |             ellipse: Ellipse::default(), | ||||||
|  |             resolution: 32, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl EllipseMeshBuilder { | ||||||
|  |     /// Creates a new [`EllipseMeshBuilder`] from a given half width and half height and a vertex count.
 | ||||||
|  |     #[inline] | ||||||
|  |     pub const fn new(half_width: f32, half_height: f32, resolution: usize) -> Self { | ||||||
|  |         Self { | ||||||
|  |             ellipse: Ellipse::new(half_width, half_height), | ||||||
|  |             resolution, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// Sets the number of vertices used for the ellipse mesh.
 | ||||||
|  |     #[inline] | ||||||
|  |     #[doc(alias = "vertices")] | ||||||
|  |     pub const fn resolution(mut self, resolution: usize) -> Self { | ||||||
|  |         self.resolution = resolution; | ||||||
|  |         self | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// Builds a [`Mesh`] based on the configuration in `self`.
 | ||||||
|  |     pub fn build(&self) -> Mesh { | ||||||
|  |         let mut indices = Vec::with_capacity((self.resolution - 2) * 3); | ||||||
|  |         let mut positions = Vec::with_capacity(self.resolution); | ||||||
|  |         let normals = vec![[0.0, 0.0, 1.0]; self.resolution]; | ||||||
|  |         let mut uvs = Vec::with_capacity(self.resolution); | ||||||
|  | 
 | ||||||
|  |         // Add pi/2 so that there is a vertex at the top (sin is 1.0 and cos is 0.0)
 | ||||||
|  |         let start_angle = std::f32::consts::FRAC_PI_2; | ||||||
|  |         let step = std::f32::consts::TAU / self.resolution as f32; | ||||||
|  | 
 | ||||||
|  |         for i in 0..self.resolution { | ||||||
|  |             // Compute vertex position at angle theta
 | ||||||
|  |             let theta = start_angle + i as f32 * step; | ||||||
|  |             let (sin, cos) = theta.sin_cos(); | ||||||
|  |             let x = cos * self.ellipse.half_size.x; | ||||||
|  |             let y = sin * self.ellipse.half_size.y; | ||||||
|  | 
 | ||||||
|  |             positions.push([x, y, 0.0]); | ||||||
|  |             uvs.push([0.5 * (cos + 1.0), 1.0 - 0.5 * (sin + 1.0)]); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         for i in 1..(self.resolution as u32 - 1) { | ||||||
|  |             indices.extend_from_slice(&[0, i, i + 1]); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         Mesh::new( | ||||||
|  |             PrimitiveTopology::TriangleList, | ||||||
|  |             RenderAssetPersistencePolicy::Keep, | ||||||
|  |         ) | ||||||
|  |         .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) | ||||||
|  |         .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) | ||||||
|  |         .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) | ||||||
|  |         .with_indices(Some(Indices::U32(indices))) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl Meshable for Ellipse { | ||||||
|  |     type Output = EllipseMeshBuilder; | ||||||
|  | 
 | ||||||
|  |     fn mesh(&self) -> Self::Output { | ||||||
|  |         EllipseMeshBuilder { | ||||||
|  |             ellipse: *self, | ||||||
|  |             ..Default::default() | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl From<Ellipse> for Mesh { | ||||||
|  |     fn from(ellipse: Ellipse) -> Self { | ||||||
|  |         ellipse.mesh().build() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl From<EllipseMeshBuilder> for Mesh { | ||||||
|  |     fn from(ellipse: EllipseMeshBuilder) -> Self { | ||||||
|  |         ellipse.build() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl Meshable for Triangle2d { | ||||||
|  |     type Output = Mesh; | ||||||
|  | 
 | ||||||
|  |     fn mesh(&self) -> Self::Output { | ||||||
|  |         let [a, b, c] = self.vertices; | ||||||
|  | 
 | ||||||
|  |         let positions = vec![[a.x, a.y, 0.0], [b.x, b.y, 0.0], [c.x, c.y, 0.0]]; | ||||||
|  |         let normals = vec![[0.0, 0.0, 1.0]; 3]; | ||||||
|  | 
 | ||||||
|  |         // The extents of the bounding box of the triangle,
 | ||||||
|  |         // used to compute the UV coordinates of the points.
 | ||||||
|  |         let extents = a.min(b).min(c).abs().max(a.max(b).max(c)) * Vec2::new(1.0, -1.0); | ||||||
|  |         let uvs = vec![ | ||||||
|  |             a / extents / 2.0 + 0.5, | ||||||
|  |             b / extents / 2.0 + 0.5, | ||||||
|  |             c / extents / 2.0 + 0.5, | ||||||
|  |         ]; | ||||||
|  | 
 | ||||||
|  |         let is_ccw = self.winding_order() == WindingOrder::CounterClockwise; | ||||||
|  |         let indices = if is_ccw { | ||||||
|  |             Indices::U32(vec![0, 1, 2]) | ||||||
|  |         } else { | ||||||
|  |             Indices::U32(vec![0, 2, 1]) | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         Mesh::new( | ||||||
|  |             PrimitiveTopology::TriangleList, | ||||||
|  |             RenderAssetPersistencePolicy::Keep, | ||||||
|  |         ) | ||||||
|  |         .with_indices(Some(indices)) | ||||||
|  |         .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) | ||||||
|  |         .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) | ||||||
|  |         .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl From<Triangle2d> for Mesh { | ||||||
|  |     fn from(triangle: Triangle2d) -> Self { | ||||||
|  |         triangle.mesh() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl Meshable for Rectangle { | ||||||
|  |     type Output = Mesh; | ||||||
|  | 
 | ||||||
|  |     fn mesh(&self) -> Self::Output { | ||||||
|  |         let [hw, hh] = [self.half_size.x, self.half_size.y]; | ||||||
|  |         let positions = vec![ | ||||||
|  |             [hw, hh, 0.0], | ||||||
|  |             [-hw, hh, 0.0], | ||||||
|  |             [-hw, -hh, 0.0], | ||||||
|  |             [hw, -hh, 0.0], | ||||||
|  |         ]; | ||||||
|  |         let normals = vec![[0.0, 0.0, 1.0]; 4]; | ||||||
|  |         let uvs = vec![[1.0, 0.0], [0.0, 0.0], [0.0, 1.0], [1.0, 1.0]]; | ||||||
|  |         let indices = Indices::U32(vec![0, 1, 2, 0, 2, 3]); | ||||||
|  | 
 | ||||||
|  |         Mesh::new( | ||||||
|  |             PrimitiveTopology::TriangleList, | ||||||
|  |             RenderAssetPersistencePolicy::Keep, | ||||||
|  |         ) | ||||||
|  |         .with_indices(Some(indices)) | ||||||
|  |         .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) | ||||||
|  |         .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) | ||||||
|  |         .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl From<Rectangle> for Mesh { | ||||||
|  |     fn from(rectangle: Rectangle) -> Self { | ||||||
|  |         rectangle.mesh() | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										35
									
								
								crates/bevy_render/src/mesh/primitives/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								crates/bevy_render/src/mesh/primitives/mod.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,35 @@ | |||||||
|  | //! Mesh generation for [primitive shapes](bevy_math::primitives).
 | ||||||
|  | //!
 | ||||||
|  | //! Primitives that support meshing implement the [`Meshable`] trait.
 | ||||||
|  | //! Calling [`mesh`](Meshable::mesh) will return either a [`Mesh`](super::Mesh) or a builder
 | ||||||
|  | //! that can be used to specify shape-specific configuration for creating the [`Mesh`](super::Mesh).
 | ||||||
|  | //!
 | ||||||
|  | //! ```
 | ||||||
|  | //! # use bevy_asset::Assets;
 | ||||||
|  | //! # use bevy_ecs::prelude::ResMut;
 | ||||||
|  | //! # use bevy_math::prelude::Circle;
 | ||||||
|  | //! # use bevy_render::prelude::*;
 | ||||||
|  | //! #
 | ||||||
|  | //! # fn setup(mut meshes: ResMut<Assets<Mesh>>) {
 | ||||||
|  | //! // Create circle mesh with default configuration
 | ||||||
|  | //! let circle = meshes.add(Circle { radius: 25.0 });
 | ||||||
|  | //!
 | ||||||
|  | //! // Specify number of vertices
 | ||||||
|  | //! let circle = meshes.add(Circle { radius: 25.0 }.mesh().resolution(64));
 | ||||||
|  | //! # }
 | ||||||
|  | //! ```
 | ||||||
|  | 
 | ||||||
|  | #![warn(missing_docs)] | ||||||
|  | 
 | ||||||
|  | mod dim2; | ||||||
|  | pub use dim2::{CircleMeshBuilder, EllipseMeshBuilder}; | ||||||
|  | 
 | ||||||
|  | /// A trait for shapes that can be turned into a [`Mesh`](super::Mesh).
 | ||||||
|  | pub trait Meshable { | ||||||
|  |     /// The output of [`Self::mesh`]. This can either be a [`Mesh`](super::Mesh)
 | ||||||
|  |     /// or a builder used for creating a [`Mesh`](super::Mesh).
 | ||||||
|  |     type Output; | ||||||
|  | 
 | ||||||
|  |     /// Creates a [`Mesh`](super::Mesh) for a shape.
 | ||||||
|  |     fn mesh(&self) -> Self::Output; | ||||||
|  | } | ||||||
| @ -18,36 +18,47 @@ fn setup( | |||||||
| 
 | 
 | ||||||
|     // Circle
 |     // Circle
 | ||||||
|     commands.spawn(MaterialMesh2dBundle { |     commands.spawn(MaterialMesh2dBundle { | ||||||
|         mesh: meshes.add(shape::Circle::new(50.)).into(), |         mesh: meshes.add(Circle { radius: 50.0 }).into(), | ||||||
|         material: materials.add(Color::PURPLE), |         material: materials.add(Color::VIOLET), | ||||||
|         transform: Transform::from_translation(Vec3::new(-150., 0., 0.)), |         transform: Transform::from_translation(Vec3::new(-225.0, 0.0, 0.0)), | ||||||
|  |         ..default() | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     // Ellipse
 | ||||||
|  |     commands.spawn(MaterialMesh2dBundle { | ||||||
|  |         mesh: meshes.add(Ellipse::new(25.0, 50.0)).into(), | ||||||
|  |         material: materials.add(Color::TURQUOISE), | ||||||
|  |         transform: Transform::from_translation(Vec3::new(-100.0, 0.0, 0.0)), | ||||||
|         ..default() |         ..default() | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     // Rectangle
 |     // Rectangle
 | ||||||
|     commands.spawn(SpriteBundle { |  | ||||||
|         sprite: Sprite { |  | ||||||
|             color: Color::rgb(0.25, 0.25, 0.75), |  | ||||||
|             custom_size: Some(Vec2::new(50.0, 100.0)), |  | ||||||
|             ..default() |  | ||||||
|         }, |  | ||||||
|         transform: Transform::from_translation(Vec3::new(-50., 0., 0.)), |  | ||||||
|         ..default() |  | ||||||
|     }); |  | ||||||
| 
 |  | ||||||
|     // Quad
 |  | ||||||
|     commands.spawn(MaterialMesh2dBundle { |     commands.spawn(MaterialMesh2dBundle { | ||||||
|         mesh: meshes.add(shape::Quad::new(Vec2::new(50., 100.))).into(), |         mesh: meshes.add(Rectangle::new(50.0, 100.0)).into(), | ||||||
|         material: materials.add(Color::LIME_GREEN), |         material: materials.add(Color::LIME_GREEN), | ||||||
|         transform: Transform::from_translation(Vec3::new(50., 0., 0.)), |         transform: Transform::from_translation(Vec3::new(0.0, 0.0, 0.0)), | ||||||
|         ..default() |         ..default() | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     // Hexagon
 |     // Hexagon
 | ||||||
|     commands.spawn(MaterialMesh2dBundle { |     commands.spawn(MaterialMesh2dBundle { | ||||||
|         mesh: meshes.add(shape::RegularPolygon::new(50., 6)).into(), |         mesh: meshes.add(RegularPolygon::new(50.0, 6)).into(), | ||||||
|         material: materials.add(Color::TURQUOISE), |         material: materials.add(Color::YELLOW), | ||||||
|         transform: Transform::from_translation(Vec3::new(150., 0., 0.)), |         transform: Transform::from_translation(Vec3::new(125.0, 0.0, 0.0)), | ||||||
|  |         ..default() | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     // Triangle
 | ||||||
|  |     commands.spawn(MaterialMesh2dBundle { | ||||||
|  |         mesh: meshes | ||||||
|  |             .add(Triangle2d::new( | ||||||
|  |                 Vec2::Y * 50.0, | ||||||
|  |                 Vec2::new(-50.0, -50.0), | ||||||
|  |                 Vec2::new(50.0, -50.0), | ||||||
|  |             )) | ||||||
|  |             .into(), | ||||||
|  |         material: materials.add(Color::ORANGE), | ||||||
|  |         transform: Transform::from_translation(Vec3::new(250.0, 0.0, 0.0)), | ||||||
|         ..default() |         ..default() | ||||||
|     }); |     }); | ||||||
| } | } | ||||||
|  | |||||||
| @ -95,7 +95,7 @@ Example | Description | |||||||
| [2D Bloom](../examples/2d/bloom_2d.rs) | Illustrates bloom post-processing in 2d | [2D Bloom](../examples/2d/bloom_2d.rs) | Illustrates bloom post-processing in 2d | ||||||
| [2D Gizmos](../examples/2d/2d_gizmos.rs) | A scene showcasing 2D gizmos | [2D Gizmos](../examples/2d/2d_gizmos.rs) | A scene showcasing 2D gizmos | ||||||
| [2D Rotation](../examples/2d/rotation.rs) | Demonstrates rotating entities in 2D with quaternions | [2D Rotation](../examples/2d/rotation.rs) | Demonstrates rotating entities in 2D with quaternions | ||||||
| [2D Shapes](../examples/2d/2d_shapes.rs) | Renders a rectangle, circle, and hexagon | [2D Shapes](../examples/2d/2d_shapes.rs) | Renders simple 2D primitive shapes like circles and polygons | ||||||
| [2D Viewport To World](../examples/2d/2d_viewport_to_world.rs) | Demonstrates how to use the `Camera::viewport_to_world_2d` method | [2D Viewport To World](../examples/2d/2d_viewport_to_world.rs) | Demonstrates how to use the `Camera::viewport_to_world_2d` method | ||||||
| [Custom glTF vertex attribute 2D](../examples/2d/custom_gltf_vertex_attribute.rs) | Renders a glTF mesh in 2D with a custom vertex attribute | [Custom glTF vertex attribute 2D](../examples/2d/custom_gltf_vertex_attribute.rs) | Renders a glTF mesh in 2D with a custom vertex attribute | ||||||
| [Manual Mesh 2D](../examples/2d/mesh2d_manual.rs) | Renders a custom mesh "manually" with "mid-level" renderer apis | [Manual Mesh 2D](../examples/2d/mesh2d_manual.rs) | Renders a custom mesh "manually" with "mid-level" renderer apis | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 Joona Aalto
						Joona Aalto