Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/examples/3d/generate_custom_mesh.rs
6592 views
1
//! This example demonstrates how to create a custom mesh,
2
//! assign a custom UV mapping for a custom texture,
3
//! and how to change the UV mapping at run-time.
4
5
use bevy::{
6
asset::RenderAssetUsages,
7
mesh::{Indices, VertexAttributeValues},
8
prelude::*,
9
render::render_resource::PrimitiveTopology,
10
};
11
12
// Define a "marker" component to mark the custom mesh. Marker components are often used in Bevy for
13
// filtering entities in queries with `With`, they're usually not queried directly since they don't
14
// contain information within them.
15
#[derive(Component)]
16
struct CustomUV;
17
18
fn main() {
19
App::new()
20
.add_plugins(DefaultPlugins)
21
.add_systems(Startup, setup)
22
.add_systems(Update, input_handler)
23
.run();
24
}
25
26
fn setup(
27
mut commands: Commands,
28
asset_server: Res<AssetServer>,
29
mut materials: ResMut<Assets<StandardMaterial>>,
30
mut meshes: ResMut<Assets<Mesh>>,
31
) {
32
// Import the custom texture.
33
let custom_texture_handle: Handle<Image> = asset_server.load("textures/array_texture.png");
34
// Create and save a handle to the mesh.
35
let cube_mesh_handle: Handle<Mesh> = meshes.add(create_cube_mesh());
36
37
// Render the mesh with the custom texture, and add the marker.
38
commands.spawn((
39
Mesh3d(cube_mesh_handle),
40
MeshMaterial3d(materials.add(StandardMaterial {
41
base_color_texture: Some(custom_texture_handle),
42
..default()
43
})),
44
CustomUV,
45
));
46
47
// Transform for the camera and lighting, looking at (0,0,0) (the position of the mesh).
48
let camera_and_light_transform =
49
Transform::from_xyz(1.8, 1.8, 1.8).looking_at(Vec3::ZERO, Vec3::Y);
50
51
// Camera in 3D space.
52
commands.spawn((Camera3d::default(), camera_and_light_transform));
53
54
// Light up the scene.
55
commands.spawn((PointLight::default(), camera_and_light_transform));
56
57
// Text to describe the controls.
58
commands.spawn((
59
Text::new("Controls:\nSpace: Change UVs\nX/Y/Z: Rotate\nR: Reset orientation"),
60
Node {
61
position_type: PositionType::Absolute,
62
top: px(12),
63
left: px(12),
64
..default()
65
},
66
));
67
}
68
69
// System to receive input from the user,
70
// check out examples/input/ for more examples about user input.
71
fn input_handler(
72
keyboard_input: Res<ButtonInput<KeyCode>>,
73
mesh_query: Query<&Mesh3d, With<CustomUV>>,
74
mut meshes: ResMut<Assets<Mesh>>,
75
mut query: Query<&mut Transform, With<CustomUV>>,
76
time: Res<Time>,
77
) {
78
if keyboard_input.just_pressed(KeyCode::Space) {
79
let mesh_handle = mesh_query.single().expect("Query not successful");
80
let mesh = meshes.get_mut(mesh_handle).unwrap();
81
toggle_texture(mesh);
82
}
83
if keyboard_input.pressed(KeyCode::KeyX) {
84
for mut transform in &mut query {
85
transform.rotate_x(time.delta_secs() / 1.2);
86
}
87
}
88
if keyboard_input.pressed(KeyCode::KeyY) {
89
for mut transform in &mut query {
90
transform.rotate_y(time.delta_secs() / 1.2);
91
}
92
}
93
if keyboard_input.pressed(KeyCode::KeyZ) {
94
for mut transform in &mut query {
95
transform.rotate_z(time.delta_secs() / 1.2);
96
}
97
}
98
if keyboard_input.pressed(KeyCode::KeyR) {
99
for mut transform in &mut query {
100
transform.look_to(Vec3::NEG_Z, Vec3::Y);
101
}
102
}
103
}
104
105
#[rustfmt::skip]
106
fn create_cube_mesh() -> Mesh {
107
// Keep the mesh data accessible in future frames to be able to mutate it in toggle_texture.
108
Mesh::new(PrimitiveTopology::TriangleList, RenderAssetUsages::MAIN_WORLD | RenderAssetUsages::RENDER_WORLD)
109
.with_inserted_attribute(
110
Mesh::ATTRIBUTE_POSITION,
111
// Each array is an [x, y, z] coordinate in local space.
112
// The camera coordinate space is right-handed x-right, y-up, z-back. This means "forward" is -Z.
113
// Meshes always rotate around their local [0, 0, 0] when a rotation is applied to their Transform.
114
// By centering our mesh around the origin, rotating the mesh preserves its center of mass.
115
vec![
116
// top (facing towards +y)
117
[-0.5, 0.5, -0.5], // vertex with index 0
118
[0.5, 0.5, -0.5], // vertex with index 1
119
[0.5, 0.5, 0.5], // etc. until 23
120
[-0.5, 0.5, 0.5],
121
// bottom (-y)
122
[-0.5, -0.5, -0.5],
123
[0.5, -0.5, -0.5],
124
[0.5, -0.5, 0.5],
125
[-0.5, -0.5, 0.5],
126
// right (+x)
127
[0.5, -0.5, -0.5],
128
[0.5, -0.5, 0.5],
129
[0.5, 0.5, 0.5], // This vertex is at the same position as vertex with index 2, but they'll have different UV and normal
130
[0.5, 0.5, -0.5],
131
// left (-x)
132
[-0.5, -0.5, -0.5],
133
[-0.5, -0.5, 0.5],
134
[-0.5, 0.5, 0.5],
135
[-0.5, 0.5, -0.5],
136
// back (+z)
137
[-0.5, -0.5, 0.5],
138
[-0.5, 0.5, 0.5],
139
[0.5, 0.5, 0.5],
140
[0.5, -0.5, 0.5],
141
// forward (-z)
142
[-0.5, -0.5, -0.5],
143
[-0.5, 0.5, -0.5],
144
[0.5, 0.5, -0.5],
145
[0.5, -0.5, -0.5],
146
],
147
)
148
// Set-up UV coordinates to point to the upper (V < 0.5), "dirt+grass" part of the texture.
149
// Take a look at the custom image (assets/textures/array_texture.png)
150
// so the UV coords will make more sense
151
// Note: (0.0, 0.0) = Top-Left in UV mapping, (1.0, 1.0) = Bottom-Right in UV mapping
152
.with_inserted_attribute(
153
Mesh::ATTRIBUTE_UV_0,
154
vec![
155
// Assigning the UV coords for the top side.
156
[0.0, 0.2], [0.0, 0.0], [1.0, 0.0], [1.0, 0.2],
157
// Assigning the UV coords for the bottom side.
158
[0.0, 0.45], [0.0, 0.25], [1.0, 0.25], [1.0, 0.45],
159
// Assigning the UV coords for the right side.
160
[1.0, 0.45], [0.0, 0.45], [0.0, 0.2], [1.0, 0.2],
161
// Assigning the UV coords for the left side.
162
[1.0, 0.45], [0.0, 0.45], [0.0, 0.2], [1.0, 0.2],
163
// Assigning the UV coords for the back side.
164
[0.0, 0.45], [0.0, 0.2], [1.0, 0.2], [1.0, 0.45],
165
// Assigning the UV coords for the forward side.
166
[0.0, 0.45], [0.0, 0.2], [1.0, 0.2], [1.0, 0.45],
167
],
168
)
169
// For meshes with flat shading, normals are orthogonal (pointing out) from the direction of
170
// the surface.
171
// Normals are required for correct lighting calculations.
172
// Each array represents a normalized vector, which length should be equal to 1.0.
173
.with_inserted_attribute(
174
Mesh::ATTRIBUTE_NORMAL,
175
vec![
176
// Normals for the top side (towards +y)
177
[0.0, 1.0, 0.0],
178
[0.0, 1.0, 0.0],
179
[0.0, 1.0, 0.0],
180
[0.0, 1.0, 0.0],
181
// Normals for the bottom side (towards -y)
182
[0.0, -1.0, 0.0],
183
[0.0, -1.0, 0.0],
184
[0.0, -1.0, 0.0],
185
[0.0, -1.0, 0.0],
186
// Normals for the right side (towards +x)
187
[1.0, 0.0, 0.0],
188
[1.0, 0.0, 0.0],
189
[1.0, 0.0, 0.0],
190
[1.0, 0.0, 0.0],
191
// Normals for the left side (towards -x)
192
[-1.0, 0.0, 0.0],
193
[-1.0, 0.0, 0.0],
194
[-1.0, 0.0, 0.0],
195
[-1.0, 0.0, 0.0],
196
// Normals for the back side (towards +z)
197
[0.0, 0.0, 1.0],
198
[0.0, 0.0, 1.0],
199
[0.0, 0.0, 1.0],
200
[0.0, 0.0, 1.0],
201
// Normals for the forward side (towards -z)
202
[0.0, 0.0, -1.0],
203
[0.0, 0.0, -1.0],
204
[0.0, 0.0, -1.0],
205
[0.0, 0.0, -1.0],
206
],
207
)
208
// Create the triangles out of the 24 vertices we created.
209
// To construct a square, we need 2 triangles, therefore 12 triangles in total.
210
// To construct a triangle, we need the indices of its 3 defined vertices, adding them one
211
// by one, in a counter-clockwise order (relative to the position of the viewer, the order
212
// should appear counter-clockwise from the front of the triangle, in this case from outside the cube).
213
// Read more about how to correctly build a mesh manually in the Bevy documentation of a Mesh,
214
// further examples and the implementation of the built-in shapes.
215
//
216
// The first two defined triangles look like this (marked with the vertex indices,
217
// and the axis), when looking down at the top (+y) of the cube:
218
// -Z
219
// ^
220
// 0---1
221
// | /|
222
// | / | -> +X
223
// |/ |
224
// 3---2
225
//
226
// The right face's (+x) triangles look like this, seen from the outside of the cube.
227
// +Y
228
// ^
229
// 10--11
230
// | /|
231
// | / | -> -Z
232
// |/ |
233
// 9---8
234
//
235
// The back face's (+z) triangles look like this, seen from the outside of the cube.
236
// +Y
237
// ^
238
// 17--18
239
// |\ |
240
// | \ | -> +X
241
// | \|
242
// 16--19
243
.with_inserted_indices(Indices::U32(vec![
244
0,3,1 , 1,3,2, // triangles making up the top (+y) facing side.
245
4,5,7 , 5,6,7, // bottom (-y)
246
8,11,9 , 9,11,10, // right (+x)
247
12,13,15 , 13,14,15, // left (-x)
248
16,19,17 , 17,19,18, // back (+z)
249
20,21,23 , 21,22,23, // forward (-z)
250
]))
251
}
252
253
// Function that changes the UV mapping of the mesh, to apply the other texture.
254
fn toggle_texture(mesh_to_change: &mut Mesh) {
255
// Get a mutable reference to the values of the UV attribute, so we can iterate over it.
256
let uv_attribute = mesh_to_change.attribute_mut(Mesh::ATTRIBUTE_UV_0).unwrap();
257
// The format of the UV coordinates should be Float32x2.
258
let VertexAttributeValues::Float32x2(uv_attribute) = uv_attribute else {
259
panic!("Unexpected vertex format, expected Float32x2.");
260
};
261
262
// Iterate over the UV coordinates, and change them as we want.
263
for uv_coord in uv_attribute.iter_mut() {
264
// If the UV coordinate points to the upper, "dirt+grass" part of the texture...
265
if (uv_coord[1] + 0.5) < 1.0 {
266
// ... point to the equivalent lower, "sand+water" part instead,
267
uv_coord[1] += 0.5;
268
} else {
269
// else, point back to the upper, "dirt+grass" part.
270
uv_coord[1] -= 0.5;
271
}
272
}
273
}
274
275