forked from bevyengine/bevy
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy path2d_shapes.rs
More file actions
170 lines (156 loc) · 6.86 KB
/
2d_shapes.rs
File metadata and controls
170 lines (156 loc) · 6.86 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
//! 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`].
//!
//! 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.
//!
//! 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.
//!
//! 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`!
//!
//! 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.
//!
//! 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.
//!
//! You can toggle wireframes with the space bar except on wasm. Wasm does not support
//! `POLYGON_MODE_LINE` on the gpu.
#[cfg(not(target_arch = "wasm32"))]
use bevy::{
input::common_conditions::input_just_pressed,
sprite_render::{Wireframe2dConfig, Wireframe2dPlugin},
};
use bevy::{input::common_conditions::input_toggle_active, prelude::*};
fn main() {
let mut app = App::new();
app.add_plugins((
DefaultPlugins,
#[cfg(not(target_arch = "wasm32"))]
Wireframe2dPlugin::default(),
))
.add_systems(Startup, setup);
#[cfg(not(target_arch = "wasm32"))]
app.add_systems(
Update,
toggle_wireframe.run_if(input_just_pressed(KeyCode::Space)),
);
app.add_systems(
Update,
rotate.run_if(input_toggle_active(false, KeyCode::KeyR)),
);
app.run();
}
const X_EXTENT: f32 = 1000.;
const Y_EXTENT: f32 = 150.;
const THICKNESS: f32 = 5.0;
fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<ColorMaterial>>,
) {
commands.spawn(Camera2d);
let shapes = [
meshes.add(Circle::new(50.0)),
meshes.add(CircularSector::new(50.0, 1.0)),
meshes.add(CircularSegment::new(50.0, 1.25)),
meshes.add(Ellipse::new(25.0, 50.0)),
meshes.add(Annulus::new(25.0, 50.0)),
meshes.add(Capsule2d::new(25.0, 50.0)),
meshes.add(Rhombus::new(75.0, 100.0)),
meshes.add(Rectangle::new(50.0, 100.0)),
meshes.add(RegularPolygon::new(50.0, 6)),
meshes.add(Triangle2d::new(
Vec2::Y * 50.0,
Vec2::new(-50.0, -50.0),
Vec2::new(50.0, -50.0),
)),
meshes.add(Segment2d::new(
Vec2::new(-50.0, 50.0),
Vec2::new(50.0, -50.0),
)),
meshes.add(Polyline2d::new(vec![
Vec2::new(-50.0, 50.0),
Vec2::new(0.0, -50.0),
Vec2::new(50.0, 50.0),
])),
];
let num_shapes = shapes.len();
for (i, shape) in shapes.into_iter().enumerate() {
// Distribute colors evenly across the rainbow.
let color = Color::hsl(360. * i as f32 / num_shapes as f32, 0.95, 0.7);
commands.spawn((
Mesh2d(shape),
MeshMaterial2d(materials.add(color)),
Transform::from_xyz(
// Distribute shapes from -X_EXTENT/2 to +X_EXTENT/2.
-X_EXTENT / 2. + i as f32 / (num_shapes - 1) as f32 * X_EXTENT,
Y_EXTENT / 2.,
0.0,
),
));
}
let rings = [
meshes.add(Circle::new(50.0).to_ring(THICKNESS)),
// this visually produces an arc segment but this is not technically accurate
meshes.add(Ring::new(
CircularSector::new(50.0, 1.0),
CircularSector::new(45.0, 1.0),
)),
meshes.add(CircularSegment::new(50.0, 1.25).to_ring(THICKNESS)),
meshes.add({
// This is an approximation; Ellipse does not implement Inset as concentric ellipses do not have parallel curves
let outer = Ellipse::new(25.0, 50.0);
let mut inner = outer;
inner.half_size -= Vec2::splat(THICKNESS);
Ring::new(outer, inner)
}),
// this is equivalent to the Annulus::new(25.0, 50.0) above
meshes.add(Ring::new(Circle::new(50.0), Circle::new(25.0))),
meshes.add(Capsule2d::new(25.0, 50.0).to_ring(THICKNESS)),
meshes.add(Rhombus::new(75.0, 100.0).to_ring(THICKNESS)),
meshes.add(Rectangle::new(50.0, 100.0).to_ring(THICKNESS)),
meshes.add(RegularPolygon::new(50.0, 6).to_ring(THICKNESS)),
meshes.add(
Triangle2d::new(
Vec2::Y * 50.0,
Vec2::new(-50.0, -50.0),
Vec2::new(50.0, -50.0),
)
.to_ring(THICKNESS),
),
];
// Allow for 2 empty spaces
let num_rings = rings.len() + 2;
for (i, shape) in rings.into_iter().enumerate() {
// Distribute colors evenly across the rainbow.
let color = Color::hsl(360. * i as f32 / num_rings as f32, 0.95, 0.7);
commands.spawn((
Mesh2d(shape),
MeshMaterial2d(materials.add(color)),
Transform::from_xyz(
// Distribute shapes from -X_EXTENT/2 to +X_EXTENT/2.
-X_EXTENT / 2. + i as f32 / (num_rings - 1) as f32 * X_EXTENT,
-Y_EXTENT / 2.,
0.0,
),
));
}
let mut text = "Press 'R' to pause/resume rotation".to_string();
#[cfg(not(target_arch = "wasm32"))]
text.push_str("\nPress 'Space' to toggle wireframes");
commands.spawn((
Text::new(text),
Node {
position_type: PositionType::Absolute,
top: px(12),
left: px(12),
..default()
},
));
}
#[cfg(not(target_arch = "wasm32"))]
fn toggle_wireframe(mut wireframe_config: ResMut<Wireframe2dConfig>) {
wireframe_config.global = !wireframe_config.global;
}
fn rotate(mut query: Query<&mut Transform, With<Mesh2d>>, time: Res<Time>) {
for mut transform in &mut query {
transform.rotate_z(time.delta_secs() / 2.0);
}
}