Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/examples/2d/2d_shapes.rs
9334 views
1
//! Here we use shape primitives to build meshes in a 2D rendering context, making each mesh a certain color by giving that mesh's entity a material based off a [`Color`].
2
//!
3
//! Meshes are better known for their use in 3D rendering, but we can use them in a 2D context too. Without a third dimension, the meshes we're building are flat – like paper on a table. These are still very useful for "vector-style" graphics, picking behavior, or as a foundation to build off of for where to apply a shader.
4
//!
5
//! A "shape definition" is not a mesh on its own. A circle can be defined with a radius, i.e. [`Circle::new(50.0)`][Circle::new], but rendering tends to happen with meshes built out of triangles. So we need to turn shape descriptions into meshes.
6
//!
7
//! Thankfully, we can add shape primitives directly to [`Assets<Mesh>`] because [`Mesh`] implements [`From`] for shape primitives and [`Assets<T>::add`] can be given any value that can be "turned into" `T`!
8
//!
9
//! We apply a material to the shape by first making a [`Color`] then calling [`Assets<ColorMaterial>::add`] with that color as its argument, which will create a material from that color through the same process [`Assets<Mesh>::add`] can take a shape primitive.
10
//!
11
//! Both the mesh and material need to be wrapped in their own "newtypes". The mesh and material are currently [`Handle<Mesh>`] and [`Handle<ColorMaterial>`] at the moment, which are not components. Handles are put behind "newtypes" to prevent ambiguity, as some entities might want to have handles to meshes (or images, or materials etc.) for different purposes! All we need to do to make them rendering-relevant components is wrap the mesh handle and the material handle in [`Mesh2d`] and [`MeshMaterial2d`] respectively.
12
//!
13
//! You can toggle wireframes with the space bar except on wasm. Wasm does not support
14
//! `POLYGON_MODE_LINE` on the gpu.
15
16
#[cfg(not(target_arch = "wasm32"))]
17
use bevy::{
18
input::common_conditions::input_just_pressed,
19
sprite_render::{Wireframe2dConfig, Wireframe2dPlugin},
20
};
21
use bevy::{input::common_conditions::input_toggle_active, prelude::*};
22
23
fn main() {
24
let mut app = App::new();
25
app.add_plugins((
26
DefaultPlugins,
27
#[cfg(not(target_arch = "wasm32"))]
28
Wireframe2dPlugin::default(),
29
))
30
.add_systems(Startup, setup);
31
#[cfg(not(target_arch = "wasm32"))]
32
app.add_systems(
33
Update,
34
toggle_wireframe.run_if(input_just_pressed(KeyCode::Space)),
35
);
36
app.add_systems(
37
Update,
38
rotate.run_if(input_toggle_active(false, KeyCode::KeyR)),
39
);
40
app.run();
41
}
42
43
const X_EXTENT: f32 = 1000.;
44
const Y_EXTENT: f32 = 150.;
45
const THICKNESS: f32 = 5.0;
46
47
fn setup(
48
mut commands: Commands,
49
mut meshes: ResMut<Assets<Mesh>>,
50
mut materials: ResMut<Assets<ColorMaterial>>,
51
) {
52
commands.spawn(Camera2d);
53
54
let shapes = [
55
meshes.add(Circle::new(50.0)),
56
meshes.add(CircularSector::new(50.0, 1.0)),
57
meshes.add(CircularSegment::new(50.0, 1.25)),
58
meshes.add(Ellipse::new(25.0, 50.0)),
59
meshes.add(Annulus::new(25.0, 50.0)),
60
meshes.add(Capsule2d::new(25.0, 50.0)),
61
meshes.add(Rhombus::new(75.0, 100.0)),
62
meshes.add(Rectangle::new(50.0, 100.0)),
63
meshes.add(RegularPolygon::new(50.0, 6)),
64
meshes.add(Triangle2d::new(
65
Vec2::Y * 50.0,
66
Vec2::new(-50.0, -50.0),
67
Vec2::new(50.0, -50.0),
68
)),
69
meshes.add(Segment2d::new(
70
Vec2::new(-50.0, 50.0),
71
Vec2::new(50.0, -50.0),
72
)),
73
meshes.add(Polyline2d::new(vec![
74
Vec2::new(-50.0, 50.0),
75
Vec2::new(0.0, -50.0),
76
Vec2::new(50.0, 50.0),
77
])),
78
];
79
let num_shapes = shapes.len();
80
81
for (i, shape) in shapes.into_iter().enumerate() {
82
// Distribute colors evenly across the rainbow.
83
let color = Color::hsl(360. * i as f32 / num_shapes as f32, 0.95, 0.7);
84
85
commands.spawn((
86
Mesh2d(shape),
87
MeshMaterial2d(materials.add(color)),
88
Transform::from_xyz(
89
// Distribute shapes from -X_EXTENT/2 to +X_EXTENT/2.
90
-X_EXTENT / 2. + i as f32 / (num_shapes - 1) as f32 * X_EXTENT,
91
Y_EXTENT / 2.,
92
0.0,
93
),
94
));
95
}
96
97
let rings = [
98
meshes.add(Circle::new(50.0).to_ring(THICKNESS)),
99
// this visually produces an arc segment but this is not technically accurate
100
meshes.add(Ring::new(
101
CircularSector::new(50.0, 1.0),
102
CircularSector::new(45.0, 1.0),
103
)),
104
meshes.add(CircularSegment::new(50.0, 1.25).to_ring(THICKNESS)),
105
meshes.add({
106
// This is an approximation; Ellipse does not implement Inset as concentric ellipses do not have parallel curves
107
let outer = Ellipse::new(25.0, 50.0);
108
let mut inner = outer;
109
inner.half_size -= Vec2::splat(THICKNESS);
110
Ring::new(outer, inner)
111
}),
112
// this is equivalent to the Annulus::new(25.0, 50.0) above
113
meshes.add(Ring::new(Circle::new(50.0), Circle::new(25.0))),
114
meshes.add(Capsule2d::new(25.0, 50.0).to_ring(THICKNESS)),
115
meshes.add(Rhombus::new(75.0, 100.0).to_ring(THICKNESS)),
116
meshes.add(Rectangle::new(50.0, 100.0).to_ring(THICKNESS)),
117
meshes.add(RegularPolygon::new(50.0, 6).to_ring(THICKNESS)),
118
meshes.add(
119
Triangle2d::new(
120
Vec2::Y * 50.0,
121
Vec2::new(-50.0, -50.0),
122
Vec2::new(50.0, -50.0),
123
)
124
.to_ring(THICKNESS),
125
),
126
];
127
// Allow for 2 empty spaces
128
let num_rings = rings.len() + 2;
129
130
for (i, shape) in rings.into_iter().enumerate() {
131
// Distribute colors evenly across the rainbow.
132
let color = Color::hsl(360. * i as f32 / num_rings as f32, 0.95, 0.7);
133
134
commands.spawn((
135
Mesh2d(shape),
136
MeshMaterial2d(materials.add(color)),
137
Transform::from_xyz(
138
// Distribute shapes from -X_EXTENT/2 to +X_EXTENT/2.
139
-X_EXTENT / 2. + i as f32 / (num_rings - 1) as f32 * X_EXTENT,
140
-Y_EXTENT / 2.,
141
0.0,
142
),
143
));
144
}
145
146
let mut text = "Press 'R' to pause/resume rotation".to_string();
147
#[cfg(not(target_arch = "wasm32"))]
148
text.push_str("\nPress 'Space' to toggle wireframes");
149
150
commands.spawn((
151
Text::new(text),
152
Node {
153
position_type: PositionType::Absolute,
154
top: px(12),
155
left: px(12),
156
..default()
157
},
158
));
159
}
160
161
#[cfg(not(target_arch = "wasm32"))]
162
fn toggle_wireframe(mut wireframe_config: ResMut<Wireframe2dConfig>) {
163
wireframe_config.global = !wireframe_config.global;
164
}
165
166
fn rotate(mut query: Query<&mut Transform, With<Mesh2d>>, time: Res<Time>) {
167
for mut transform in &mut query {
168
transform.rotate_z(time.delta_secs() / 2.0);
169
}
170
}
171
172