Skip to content

Commit f8e0fc1

Browse files
committed
Add RegularPolygon and Circle meshes (#3730)
# Objective Bevy users often want to create circles and other simple shapes. All the machinery is in place to accomplish this, and there are external crates that help. But when writing code for e.g. a new bevy example, it's not really possible to draw a circle without bringing in a new asset, writing a bunch of scary looking mesh code, or adding a dependency. In particular, this PR was inspired by this interaction in another PR: #3721 (comment) ## Solution This PR adds `shape::RegularPolygon` and `shape::Circle` (which is just a `RegularPolygon` that defaults to a large number of sides) ## Discussion There's a lot of ongoing discussion about shapes in <bevyengine/rfcs#12> and at least one other lingering shape PR (although it seems incomplete). That RFC currently includes `RegularPolygon` and `Circle` shapes, so I don't think that having working mesh generation code in the engine for those shapes would add much burden to an author of an implementation. But if we'd prefer not to add additional shapes until after that's sorted out, I'm happy to close this for now. ## Alternatives for users For any users stumbling on this issue, here are some plugins that will help if you need more shapes. https://github.com/Nilirad/bevy_prototype_lyon https://github.com/johanhelsing/bevy_smud https://github.com/Weasy666/bevy_svg https://github.com/redpandamonium/bevy_more_shapes https://github.com/ForesightMiningSoftwareCorporation/bevy_polyline
1 parent f02bea5 commit f8e0fc1

File tree

6 files changed

+150
-23
lines changed

6 files changed

+150
-23
lines changed

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,8 +144,8 @@ name = "mesh2d_manual"
144144
path = "examples/2d/mesh2d_manual.rs"
145145

146146
[[example]]
147-
name = "rect"
148-
path = "examples/2d/rect.rs"
147+
name = "shapes"
148+
path = "examples/2d/shapes.rs"
149149

150150
[[example]]
151151
name = "sprite"

crates/bevy_render/src/mesh/shape/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,11 +224,13 @@ impl From<Plane> for Mesh {
224224

225225
mod capsule;
226226
mod icosphere;
227+
mod regular_polygon;
227228
mod torus;
228229
mod uvsphere;
229230

230231
pub use capsule::{Capsule, CapsuleUvProfile};
231232
pub use icosphere::Icosphere;
233+
pub use regular_polygon::{Circle, RegularPolygon};
232234
pub use torus::Torus;
233235
pub use uvsphere::UVSphere;
234236
use wgpu::PrimitiveTopology;
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
use crate::mesh::{Indices, Mesh};
2+
use wgpu::PrimitiveTopology;
3+
4+
/// A regular polygon in the xy plane
5+
#[derive(Debug, Copy, Clone)]
6+
pub struct RegularPolygon {
7+
/// Inscribed radius in the xy plane.
8+
pub radius: f32,
9+
/// Number of sides.
10+
pub sides: usize,
11+
}
12+
13+
impl Default for RegularPolygon {
14+
fn default() -> Self {
15+
Self {
16+
radius: 0.5,
17+
sides: 6,
18+
}
19+
}
20+
}
21+
22+
impl RegularPolygon {
23+
/// Creates a regular polygon in the xy plane
24+
pub fn new(radius: f32, sides: usize) -> Self {
25+
Self { radius, sides }
26+
}
27+
}
28+
29+
impl From<RegularPolygon> for Mesh {
30+
fn from(polygon: RegularPolygon) -> Self {
31+
let RegularPolygon { radius, sides } = polygon;
32+
33+
debug_assert!(sides > 2, "RegularPolygon requires at least 3 sides.");
34+
35+
let mut positions = Vec::with_capacity(sides);
36+
let mut normals = Vec::with_capacity(sides);
37+
let mut uvs = Vec::with_capacity(sides);
38+
39+
let step = std::f32::consts::TAU / sides as f32;
40+
for i in 0..sides {
41+
let theta = std::f32::consts::FRAC_PI_2 - i as f32 * step;
42+
let (sin, cos) = theta.sin_cos();
43+
44+
positions.push([cos * radius, sin * radius, 0.0]);
45+
normals.push([0.0, 0.0, 1.0]);
46+
uvs.push([0.5 * (cos + 1.0), 1.0 - 0.5 * (sin + 1.0)]);
47+
}
48+
49+
let mut indices = Vec::with_capacity((sides - 2) * 3);
50+
for i in 1..(sides as u32 - 1) {
51+
indices.extend_from_slice(&[0, i + 1, i]);
52+
}
53+
54+
let mut mesh = Mesh::new(PrimitiveTopology::TriangleList);
55+
mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, positions);
56+
mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, normals);
57+
mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, uvs);
58+
mesh.set_indices(Some(Indices::U32(indices)));
59+
mesh
60+
}
61+
}
62+
63+
/// A circle in the xy plane
64+
pub struct Circle {
65+
/// Inscribed radius in the xy plane.
66+
pub radius: f32,
67+
/// The number of vertices used.
68+
pub vertices: usize,
69+
}
70+
71+
impl Default for Circle {
72+
fn default() -> Self {
73+
Self {
74+
radius: 0.5,
75+
vertices: 64,
76+
}
77+
}
78+
}
79+
80+
impl Circle {
81+
/// Creates a circle in the xy plane
82+
pub fn new(radius: f32) -> Self {
83+
Self {
84+
radius,
85+
..Default::default()
86+
}
87+
}
88+
}
89+
90+
impl From<Circle> for RegularPolygon {
91+
fn from(circle: Circle) -> Self {
92+
Self {
93+
radius: circle.radius,
94+
sides: circle.vertices,
95+
}
96+
}
97+
}
98+
99+
impl From<Circle> for Mesh {
100+
fn from(circle: Circle) -> Self {
101+
Mesh::from(RegularPolygon::from(circle))
102+
}
103+
}

examples/2d/rect.rs

Lines changed: 0 additions & 20 deletions
This file was deleted.

examples/2d/shapes.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
use bevy::{prelude::*, sprite::MaterialMesh2dBundle};
2+
3+
fn main() {
4+
App::new()
5+
.add_plugins(DefaultPlugins)
6+
.add_startup_system(setup)
7+
.run();
8+
}
9+
10+
fn setup(
11+
mut commands: Commands,
12+
mut meshes: ResMut<Assets<Mesh>>,
13+
mut materials: ResMut<Assets<ColorMaterial>>,
14+
) {
15+
commands.spawn_bundle(OrthographicCameraBundle::new_2d());
16+
17+
// Rectangle
18+
commands.spawn_bundle(SpriteBundle {
19+
sprite: Sprite {
20+
color: Color::rgb(0.25, 0.25, 0.75),
21+
custom_size: Some(Vec2::new(50.0, 100.0)),
22+
..default()
23+
},
24+
..default()
25+
});
26+
27+
// Circle
28+
commands.spawn_bundle(MaterialMesh2dBundle {
29+
mesh: meshes.add(shape::Circle::new(50.).into()).into(),
30+
material: materials.add(ColorMaterial::from(Color::PURPLE)),
31+
transform: Transform::from_translation(Vec3::new(-100., 0., 0.)),
32+
..default()
33+
});
34+
35+
// Hexagon
36+
commands.spawn_bundle(MaterialMesh2dBundle {
37+
mesh: meshes.add(shape::RegularPolygon::new(50., 6).into()).into(),
38+
material: materials.add(ColorMaterial::from(Color::TURQUOISE)),
39+
transform: Transform::from_translation(Vec3::new(100., 0., 0.)),
40+
..default()
41+
});
42+
}

examples/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ Example | File | Description
8989
`move_sprite` | [`2d/move_sprite.rs`](./2d/move_sprite.rs) | Changes the transform of a sprite.
9090
`mesh2d` | [`2d/mesh2d.rs`](./2d/mesh2d.rs) | Renders a 2d mesh
9191
`mesh2d_manual` | [`2d/mesh2d_manual.rs`](./2d/mesh2d_manual.rs) | Renders a custom mesh "manually" with "mid-level" renderer apis.
92-
`rect` | [`2d/rect.rs`](./2d/rect.rs) | Renders a rectangle
92+
`shapes` | [`2d/shapes.rs`](./2d/shapes.rs) | Renders a rectangle, circle, and hexagon
9393
`sprite` | [`2d/sprite.rs`](./2d/sprite.rs) | Renders a sprite
9494
`sprite_sheet` | [`2d/sprite_sheet.rs`](./2d/sprite_sheet.rs) | Renders an animated sprite
9595
`text2d` | [`2d/text2d.rs`](./2d/text2d.rs) | Generates text in 2d

0 commit comments

Comments
 (0)