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