diff --git a/Cargo.toml b/Cargo.toml index 1e3040f0bda6d..60b424bf56e29 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -144,8 +144,8 @@ name = "mesh2d_manual" path = "examples/2d/mesh2d_manual.rs" [[example]] -name = "rect" -path = "examples/2d/rect.rs" +name = "shapes" +path = "examples/2d/shapes.rs" [[example]] name = "sprite" diff --git a/crates/bevy_render/src/mesh/shape/mod.rs b/crates/bevy_render/src/mesh/shape/mod.rs index 31f9f8b5d4bd2..00ce6f1eefc43 100644 --- a/crates/bevy_render/src/mesh/shape/mod.rs +++ b/crates/bevy_render/src/mesh/shape/mod.rs @@ -224,11 +224,13 @@ impl From for Mesh { mod capsule; mod icosphere; +mod regular_polygon; mod torus; mod uvsphere; pub use capsule::{Capsule, CapsuleUvProfile}; pub use icosphere::Icosphere; +pub use regular_polygon::{Circle, RegularPolygon}; pub use torus::Torus; pub use uvsphere::UVSphere; use wgpu::PrimitiveTopology; diff --git a/crates/bevy_render/src/mesh/shape/regular_polygon.rs b/crates/bevy_render/src/mesh/shape/regular_polygon.rs new file mode 100644 index 0000000000000..ffb3e9d08e1d7 --- /dev/null +++ b/crates/bevy_render/src/mesh/shape/regular_polygon.rs @@ -0,0 +1,103 @@ +use crate::mesh::{Indices, Mesh}; +use wgpu::PrimitiveTopology; + +/// A regular polygon in the xy plane +#[derive(Debug, Copy, Clone)] +pub struct RegularPolygon { + /// Inscribed radius in the xy plane. + pub radius: f32, + /// Number of sides. + pub sides: usize, +} + +impl Default for RegularPolygon { + fn default() -> Self { + Self { + radius: 0.5, + sides: 6, + } + } +} + +impl RegularPolygon { + /// Creates a regular polygon in the xy plane + pub fn new(radius: f32, sides: usize) -> Self { + Self { radius, sides } + } +} + +impl From for Mesh { + fn from(polygon: RegularPolygon) -> Self { + let RegularPolygon { radius, sides } = polygon; + + debug_assert!(sides > 2, "RegularPolygon requires at least 3 sides."); + + let mut positions = Vec::with_capacity(sides); + let mut normals = Vec::with_capacity(sides); + let mut uvs = Vec::with_capacity(sides); + + let step = std::f32::consts::TAU / sides as f32; + for i in 0..sides { + let theta = std::f32::consts::FRAC_PI_2 - i as f32 * step; + let (sin, cos) = theta.sin_cos(); + + positions.push([cos * radius, sin * radius, 0.0]); + normals.push([0.0, 0.0, 1.0]); + uvs.push([0.5 * (cos + 1.0), 1.0 - 0.5 * (sin + 1.0)]); + } + + let mut indices = Vec::with_capacity((sides - 2) * 3); + for i in 1..(sides as u32 - 1) { + indices.extend_from_slice(&[0, i + 1, i]); + } + + let mut mesh = Mesh::new(PrimitiveTopology::TriangleList); + mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, positions); + mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, normals); + mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, uvs); + mesh.set_indices(Some(Indices::U32(indices))); + mesh + } +} + +/// A circle in the xy plane +pub struct Circle { + /// Inscribed radius in the xy plane. + pub radius: f32, + /// The number of vertices used. + pub vertices: usize, +} + +impl Default for Circle { + fn default() -> Self { + Self { + radius: 0.5, + vertices: 64, + } + } +} + +impl Circle { + /// Creates a circle in the xy plane + pub fn new(radius: f32) -> Self { + Self { + radius, + ..Default::default() + } + } +} + +impl From for RegularPolygon { + fn from(circle: Circle) -> Self { + Self { + radius: circle.radius, + sides: circle.vertices, + } + } +} + +impl From for Mesh { + fn from(circle: Circle) -> Self { + Mesh::from(RegularPolygon::from(circle)) + } +} diff --git a/examples/2d/rect.rs b/examples/2d/rect.rs deleted file mode 100644 index c87cced47bd5e..0000000000000 --- a/examples/2d/rect.rs +++ /dev/null @@ -1,20 +0,0 @@ -use bevy::prelude::*; - -fn main() { - App::new() - .add_plugins(DefaultPlugins) - .add_startup_system(setup) - .run(); -} - -fn setup(mut commands: Commands) { - commands.spawn_bundle(OrthographicCameraBundle::new_2d()); - commands.spawn_bundle(SpriteBundle { - sprite: Sprite { - color: Color::rgb(0.25, 0.25, 0.75), - custom_size: Some(Vec2::new(50.0, 50.0)), - ..default() - }, - ..default() - }); -} diff --git a/examples/2d/shapes.rs b/examples/2d/shapes.rs new file mode 100644 index 0000000000000..5fe8653ca68a6 --- /dev/null +++ b/examples/2d/shapes.rs @@ -0,0 +1,42 @@ +use bevy::{prelude::*, sprite::MaterialMesh2dBundle}; + +fn main() { + App::new() + .add_plugins(DefaultPlugins) + .add_startup_system(setup) + .run(); +} + +fn setup( + mut commands: Commands, + mut meshes: ResMut>, + mut materials: ResMut>, +) { + commands.spawn_bundle(OrthographicCameraBundle::new_2d()); + + // Rectangle + commands.spawn_bundle(SpriteBundle { + sprite: Sprite { + color: Color::rgb(0.25, 0.25, 0.75), + custom_size: Some(Vec2::new(50.0, 100.0)), + ..default() + }, + ..default() + }); + + // Circle + commands.spawn_bundle(MaterialMesh2dBundle { + mesh: meshes.add(shape::Circle::new(50.).into()).into(), + material: materials.add(ColorMaterial::from(Color::PURPLE)), + transform: Transform::from_translation(Vec3::new(-100., 0., 0.)), + ..default() + }); + + // Hexagon + commands.spawn_bundle(MaterialMesh2dBundle { + mesh: meshes.add(shape::RegularPolygon::new(50., 6).into()).into(), + material: materials.add(ColorMaterial::from(Color::TURQUOISE)), + transform: Transform::from_translation(Vec3::new(100., 0., 0.)), + ..default() + }); +} diff --git a/examples/README.md b/examples/README.md index 4d4103e729895..041d048f353a2 100644 --- a/examples/README.md +++ b/examples/README.md @@ -89,7 +89,7 @@ Example | File | Description `move_sprite` | [`2d/move_sprite.rs`](./2d/move_sprite.rs) | Changes the transform of a sprite. `mesh2d` | [`2d/mesh2d.rs`](./2d/mesh2d.rs) | Renders a 2d mesh `mesh2d_manual` | [`2d/mesh2d_manual.rs`](./2d/mesh2d_manual.rs) | Renders a custom mesh "manually" with "mid-level" renderer apis. -`rect` | [`2d/rect.rs`](./2d/rect.rs) | Renders a rectangle +`shapes` | [`2d/shapes.rs`](./2d/shapes.rs) | Renders a rectangle, circle, and hexagon `sprite` | [`2d/sprite.rs`](./2d/sprite.rs) | Renders a sprite `sprite_sheet` | [`2d/sprite_sheet.rs`](./2d/sprite_sheet.rs) | Renders an animated sprite `text2d` | [`2d/text2d.rs`](./2d/text2d.rs) | Generates text in 2d