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