generate_custom_mesh/generate_custom_mesh.rs
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
5use 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)]
16struct CustomUV;
17
18fn main() {
19 App::new()
20 .add_plugins(DefaultPlugins)
21 .add_systems(Startup, setup)
22 .add_systems(Update, input_handler)
23 .run();
24}
25
26fn 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.
71fn 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]
106fn 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.
254fn 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}