Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/examples/3d/3d_shapes.rs
9353 views
1
//! Here we use shape primitives to generate meshes for 3d objects as well as attaching a runtime-generated patterned texture to each 3d object.
2
//!
3
//! "Shape primitives" here are just the mathematical definition of certain shapes, they're not meshes on their own! A sphere with radius `1.0` can be defined with [`Sphere::new(1.0)`][Sphere::new] but all this does is store the radius. So we need to turn these descriptions of shapes into meshes.
4
//!
5
//! While a shape is not a mesh, turning it into one in Bevy is easy. In this example we call [`meshes.add(/* Shape here! */)`][`Assets<A>::add`] on the shape, which works because the [`Assets<A>::add`] method takes anything that can be turned into the asset type it stores. There's an implementation for [`From`] on shape primitives into [`Mesh`], so that will get called internally by [`Assets<A>::add`].
6
//!
7
//! [`Extrusion`] lets us turn 2D shape primitives into versions of those shapes that have volume by extruding them. A 1x1 square that gets wrapped in this with an extrusion depth of 2 will give us a rectangular prism of size 1x1x2, but here we're just extruding these 2d shapes by depth 1.
8
//!
9
//! The material applied to these shapes is a texture that we generate at run time by looping through a "palette" of RGBA values (stored adjacent to each other in the array) and writing values to positions in another array that represents the buffer for an 8x8 texture. This texture is then registered with the assets system just one time, with that [`Handle<StandardMaterial>`] then applied to all the shapes in this example.
10
//!
11
//! The mesh and material are [`Handle<Mesh>`] and [`Handle<StandardMaterial>`] at the moment, neither of which implement `Component` on their own. 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 [`Mesh3d`] and [`MeshMaterial3d`] 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
use std::f32::consts::PI;
17
18
#[cfg(not(target_arch = "wasm32"))]
19
use bevy::pbr::wireframe::{WireframeConfig, WireframePlugin};
20
use bevy::{
21
asset::RenderAssetUsages,
22
color::palettes::basic::SILVER,
23
input::common_conditions::{input_just_pressed, input_toggle_active},
24
prelude::*,
25
render::render_resource::{Extent3d, TextureDimension, TextureFormat},
26
};
27
28
fn main() {
29
App::new()
30
.add_plugins((
31
DefaultPlugins.set(ImagePlugin::default_nearest()),
32
#[cfg(not(target_arch = "wasm32"))]
33
WireframePlugin::default(),
34
))
35
.add_systems(Startup, setup)
36
.add_systems(
37
Update,
38
(
39
rotate.run_if(input_toggle_active(true, KeyCode::KeyR)),
40
advance_rows.run_if(input_just_pressed(KeyCode::Tab)),
41
#[cfg(not(target_arch = "wasm32"))]
42
toggle_wireframe,
43
),
44
)
45
.run();
46
}
47
48
/// A marker component for our shapes so we can query them separately from the ground plane
49
#[derive(Component)]
50
struct Shape;
51
52
const SHAPES_X_EXTENT: f32 = 14.0;
53
const EXTRUSION_X_EXTENT: f32 = 14.0;
54
const Z_EXTENT: f32 = 8.0;
55
const THICKNESS: f32 = 0.1;
56
57
fn setup(
58
mut commands: Commands,
59
mut meshes: ResMut<Assets<Mesh>>,
60
mut images: ResMut<Assets<Image>>,
61
mut materials: ResMut<Assets<StandardMaterial>>,
62
) {
63
let debug_material = materials.add(StandardMaterial {
64
base_color_texture: Some(images.add(uv_debug_texture())),
65
..default()
66
});
67
68
let shapes = [
69
meshes.add(Cuboid::default()),
70
meshes.add(Tetrahedron::default()),
71
meshes.add(Capsule3d::default()),
72
meshes.add(Torus::default()),
73
meshes.add(Cylinder::default()),
74
meshes.add(Cone::default()),
75
meshes.add(ConicalFrustum::default()),
76
meshes.add(Sphere::default().mesh().ico(5).unwrap()),
77
meshes.add(Sphere::default().mesh().uv(32, 18)),
78
meshes.add(Segment3d::default()),
79
meshes.add(Polyline3d::new(vec![
80
Vec3::new(-0.5, 0.0, 0.0),
81
Vec3::new(0.5, 0.0, 0.0),
82
Vec3::new(0.0, 0.5, 0.0),
83
])),
84
];
85
86
let extrusions = [
87
meshes.add(Extrusion::new(Rectangle::default(), 1.)),
88
meshes.add(Extrusion::new(Capsule2d::default(), 1.)),
89
meshes.add(Extrusion::new(Annulus::default(), 1.)),
90
meshes.add(Extrusion::new(Circle::default(), 1.)),
91
meshes.add(Extrusion::new(Ellipse::default(), 1.)),
92
meshes.add(Extrusion::new(RegularPolygon::default(), 1.)),
93
meshes.add(Extrusion::new(Triangle2d::default(), 1.)),
94
];
95
96
let ring_extrusions = [
97
meshes.add(Extrusion::new(Rectangle::default().to_ring(THICKNESS), 1.)),
98
meshes.add(Extrusion::new(Capsule2d::default().to_ring(THICKNESS), 1.)),
99
meshes.add(Extrusion::new(
100
Ring::new(Circle::new(1.0), Circle::new(0.5)),
101
1.,
102
)),
103
meshes.add(Extrusion::new(Circle::default().to_ring(THICKNESS), 1.)),
104
meshes.add(Extrusion::new(
105
{
106
// This is an approximation; Ellipse does not implement Inset as concentric ellipses do not have parallel curves
107
let outer = Ellipse::default();
108
let mut inner = outer;
109
inner.half_size -= Vec2::splat(THICKNESS);
110
Ring::new(outer, inner)
111
},
112
1.,
113
)),
114
meshes.add(Extrusion::new(
115
RegularPolygon::default().to_ring(THICKNESS),
116
1.,
117
)),
118
meshes.add(Extrusion::new(Triangle2d::default().to_ring(THICKNESS), 1.)),
119
];
120
121
let num_shapes = shapes.len();
122
123
for (i, shape) in shapes.into_iter().enumerate() {
124
commands.spawn((
125
Mesh3d(shape),
126
MeshMaterial3d(debug_material.clone()),
127
Transform::from_xyz(
128
-SHAPES_X_EXTENT / 2. + i as f32 / (num_shapes - 1) as f32 * SHAPES_X_EXTENT,
129
2.0,
130
Row::Front.z(),
131
)
132
.with_rotation(Quat::from_rotation_x(-PI / 4.)),
133
Shape,
134
Row::Front,
135
));
136
}
137
138
let num_extrusions = extrusions.len();
139
140
for (i, shape) in extrusions.into_iter().enumerate() {
141
commands.spawn((
142
Mesh3d(shape),
143
MeshMaterial3d(debug_material.clone()),
144
Transform::from_xyz(
145
-EXTRUSION_X_EXTENT / 2.
146
+ i as f32 / (num_extrusions - 1) as f32 * EXTRUSION_X_EXTENT,
147
2.0,
148
Row::Middle.z(),
149
)
150
.with_rotation(Quat::from_rotation_x(-PI / 4.)),
151
Shape,
152
Row::Middle,
153
));
154
}
155
156
let num_ring_extrusions = ring_extrusions.len();
157
158
for (i, shape) in ring_extrusions.into_iter().enumerate() {
159
commands.spawn((
160
Mesh3d(shape),
161
MeshMaterial3d(debug_material.clone()),
162
Transform::from_xyz(
163
-EXTRUSION_X_EXTENT / 2.
164
+ i as f32 / (num_ring_extrusions - 1) as f32 * EXTRUSION_X_EXTENT,
165
2.0,
166
Row::Rear.z(),
167
)
168
.with_rotation(Quat::from_rotation_x(-PI / 4.)),
169
Shape,
170
Row::Rear,
171
));
172
}
173
174
commands.spawn((
175
PointLight {
176
shadow_maps_enabled: true,
177
intensity: 10_000_000.,
178
range: 100.0,
179
shadow_depth_bias: 0.2,
180
..default()
181
},
182
Transform::from_xyz(8.0, 16.0, 8.0),
183
));
184
185
// ground plane
186
commands.spawn((
187
Mesh3d(meshes.add(Plane3d::default().mesh().size(50.0, 50.0).subdivisions(10))),
188
MeshMaterial3d(materials.add(Color::from(SILVER))),
189
));
190
191
commands.spawn((
192
Camera3d::default(),
193
Transform::from_xyz(0.0, 7., 14.0).looking_at(Vec3::new(0., 1., 0.), Vec3::Y),
194
));
195
196
let mut text = "\
197
Press 'R' to pause/resume rotation\n\
198
Press 'Tab' to cycle through rows"
199
.to_string();
200
#[cfg(not(target_arch = "wasm32"))]
201
text.push_str("\nPress 'Space' to toggle wireframes");
202
203
commands.spawn((
204
Text::new(text),
205
Node {
206
position_type: PositionType::Absolute,
207
top: px(12),
208
left: px(12),
209
..default()
210
},
211
));
212
}
213
214
fn rotate(mut query: Query<&mut Transform, With<Shape>>, time: Res<Time>) {
215
for mut transform in &mut query {
216
transform.rotate_y(time.delta_secs() / 2.);
217
}
218
}
219
220
/// Creates a colorful test pattern
221
fn uv_debug_texture() -> Image {
222
const TEXTURE_SIZE: usize = 8;
223
224
let mut palette: [u8; 32] = [
225
255, 102, 159, 255, 255, 159, 102, 255, 236, 255, 102, 255, 121, 255, 102, 255, 102, 255,
226
198, 255, 102, 198, 255, 255, 121, 102, 255, 255, 236, 102, 255, 255,
227
];
228
229
let mut texture_data = [0; TEXTURE_SIZE * TEXTURE_SIZE * 4];
230
for y in 0..TEXTURE_SIZE {
231
let offset = TEXTURE_SIZE * y * 4;
232
texture_data[offset..(offset + TEXTURE_SIZE * 4)].copy_from_slice(&palette);
233
palette.rotate_right(4);
234
}
235
236
Image::new_fill(
237
Extent3d {
238
width: TEXTURE_SIZE as u32,
239
height: TEXTURE_SIZE as u32,
240
depth_or_array_layers: 1,
241
},
242
TextureDimension::D2,
243
&texture_data,
244
TextureFormat::Rgba8UnormSrgb,
245
RenderAssetUsages::RENDER_WORLD,
246
)
247
}
248
249
#[cfg(not(target_arch = "wasm32"))]
250
fn toggle_wireframe(
251
mut wireframe_config: ResMut<WireframeConfig>,
252
keyboard: Res<ButtonInput<KeyCode>>,
253
) {
254
if keyboard.just_pressed(KeyCode::Space) {
255
wireframe_config.global = !wireframe_config.global;
256
}
257
}
258
259
#[derive(Component, Clone, Copy)]
260
enum Row {
261
Front,
262
Middle,
263
Rear,
264
}
265
266
impl Row {
267
fn z(self) -> f32 {
268
match self {
269
Row::Front => Z_EXTENT / 2.,
270
Row::Middle => 0.,
271
Row::Rear => -Z_EXTENT / 2.,
272
}
273
}
274
275
fn advance(self) -> Self {
276
match self {
277
Row::Front => Row::Rear,
278
Row::Middle => Row::Front,
279
Row::Rear => Row::Middle,
280
}
281
}
282
}
283
284
fn advance_rows(mut shapes: Query<(&mut Row, &mut Transform), With<Shape>>) {
285
for (mut row, mut transform) in &mut shapes {
286
*row = row.advance();
287
transform.translation.z = row.z();
288
}
289
}
290
291