
# Objective A big step in the migration to required components: meshes and materials! ## Solution As per the [selected proposal](https://hackmd.io/@bevy/required_components/%2Fj9-PnF-2QKK0on1KQ29UWQ): - Deprecate `MaterialMesh2dBundle`, `MaterialMeshBundle`, and `PbrBundle`. - Add `Mesh2d` and `Mesh3d` components, which wrap a `Handle<Mesh>`. - Add `MeshMaterial2d<M: Material2d>` and `MeshMaterial3d<M: Material>`, which wrap a `Handle<M>`. - Meshes *without* a mesh material should be rendered with a default material. The existence of a material is determined by `HasMaterial2d`/`HasMaterial3d`, which is required by `MeshMaterial2d`/`MeshMaterial3d`. This gets around problems with the generics. Previously: ```rust commands.spawn(MaterialMesh2dBundle { mesh: meshes.add(Circle::new(100.0)).into(), material: materials.add(Color::srgb(7.5, 0.0, 7.5)), transform: Transform::from_translation(Vec3::new(-200., 0., 0.)), ..default() }); ``` Now: ```rust commands.spawn(( Mesh2d(meshes.add(Circle::new(100.0))), MeshMaterial2d(materials.add(Color::srgb(7.5, 0.0, 7.5))), Transform::from_translation(Vec3::new(-200., 0., 0.)), )); ``` If the mesh material is missing, previously nothing was rendered. Now, it renders a white default `ColorMaterial` in 2D and a `StandardMaterial` in 3D (this can be overridden). Below, only every other entity has a material:   Why white? This is still open for discussion, but I think white makes sense for a *default* material, while *invalid* asset handles pointing to nothing should have something like a pink material to indicate that something is broken (I don't handle that in this PR yet). This is kind of a mix of Godot and Unity: Godot just renders a white material for non-existent materials, while Unity renders nothing when no materials exist, but renders pink for invalid materials. I can also change the default material to pink if that is preferable though. ## Testing I ran some 2D and 3D examples to test if anything changed visually. I have not tested all examples or features yet however. If anyone wants to test more extensively, it would be appreciated! ## Implementation Notes - The relationship between `bevy_render` and `bevy_pbr` is weird here. `bevy_render` needs `Mesh3d` for its own systems, but `bevy_pbr` has all of the material logic, and `bevy_render` doesn't depend on it. I feel like the two crates should be refactored in some way, but I think that's out of scope for this PR. - I didn't migrate meshlets to required components yet. That can probably be done in a follow-up, as this is already a huge PR. - It is becoming increasingly clear to me that we really, *really* want to disallow raw asset handles as components. They caused me a *ton* of headache here already, and it took me a long time to find every place that queried for them or inserted them directly on entities, since there were no compiler errors for it. If we don't remove the `Component` derive, I expect raw asset handles to be a *huge* footgun for users as we transition to wrapper components, especially as handles as components have been the norm so far. I personally consider this to be a blocker for 0.15: we need to migrate to wrapper components for asset handles everywhere, and remove the `Component` derive. Also see https://github.com/bevyengine/bevy/issues/14124. --- ## Migration Guide Asset handles for meshes and mesh materials must now be wrapped in the `Mesh2d` and `MeshMaterial2d` or `Mesh3d` and `MeshMaterial3d` components for 2D and 3D respectively. Raw handles as components no longer render meshes. Additionally, `MaterialMesh2dBundle`, `MaterialMeshBundle`, and `PbrBundle` have been deprecated. Instead, use the mesh and material components directly. Previously: ```rust commands.spawn(MaterialMesh2dBundle { mesh: meshes.add(Circle::new(100.0)).into(), material: materials.add(Color::srgb(7.5, 0.0, 7.5)), transform: Transform::from_translation(Vec3::new(-200., 0., 0.)), ..default() }); ``` Now: ```rust commands.spawn(( Mesh2d(meshes.add(Circle::new(100.0))), MeshMaterial2d(materials.add(Color::srgb(7.5, 0.0, 7.5))), Transform::from_translation(Vec3::new(-200., 0., 0.)), )); ``` If the mesh material is missing, a white default material is now used. Previously, nothing was rendered if the material was missing. The `WithMesh2d` and `WithMesh3d` query filter type aliases have also been removed. Simply use `With<Mesh2d>` or `With<Mesh3d>`. --------- Co-authored-by: Tim Blackbird <justthecooldude@gmail.com> Co-authored-by: Carter Anderson <mcanders1@gmail.com>
282 lines
10 KiB
Rust
282 lines
10 KiB
Rust
//! This example demonstrates how to create a custom mesh,
|
|
//! assign a custom UV mapping for a custom texture,
|
|
//! and how to change the UV mapping at run-time.
|
|
|
|
use bevy::{
|
|
prelude::*,
|
|
render::{
|
|
mesh::{Indices, VertexAttributeValues},
|
|
render_asset::RenderAssetUsages,
|
|
render_resource::PrimitiveTopology,
|
|
},
|
|
};
|
|
|
|
// Define a "marker" component to mark the custom mesh. Marker components are often used in Bevy for
|
|
// filtering entities in queries with `With`, they're usually not queried directly since they don't
|
|
// contain information within them.
|
|
#[derive(Component)]
|
|
struct CustomUV;
|
|
|
|
fn main() {
|
|
App::new()
|
|
.add_plugins(DefaultPlugins)
|
|
.add_systems(Startup, setup)
|
|
.add_systems(Update, input_handler)
|
|
.run();
|
|
}
|
|
|
|
fn setup(
|
|
mut commands: Commands,
|
|
asset_server: Res<AssetServer>,
|
|
mut materials: ResMut<Assets<StandardMaterial>>,
|
|
mut meshes: ResMut<Assets<Mesh>>,
|
|
) {
|
|
// Import the custom texture.
|
|
let custom_texture_handle: Handle<Image> = asset_server.load("textures/array_texture.png");
|
|
// Create and save a handle to the mesh.
|
|
let cube_mesh_handle: Handle<Mesh> = meshes.add(create_cube_mesh());
|
|
|
|
// Render the mesh with the custom texture, and add the marker.
|
|
commands.spawn((
|
|
Mesh3d(cube_mesh_handle),
|
|
MeshMaterial3d(materials.add(StandardMaterial {
|
|
base_color_texture: Some(custom_texture_handle),
|
|
..default()
|
|
})),
|
|
CustomUV,
|
|
));
|
|
|
|
// Transform for the camera and lighting, looking at (0,0,0) (the position of the mesh).
|
|
let camera_and_light_transform =
|
|
Transform::from_xyz(1.8, 1.8, 1.8).looking_at(Vec3::ZERO, Vec3::Y);
|
|
|
|
// Camera in 3D space.
|
|
commands.spawn(Camera3dBundle {
|
|
transform: camera_and_light_transform,
|
|
..default()
|
|
});
|
|
|
|
// Light up the scene.
|
|
commands.spawn((PointLight::default(), camera_and_light_transform));
|
|
|
|
// Text to describe the controls.
|
|
commands.spawn(
|
|
TextBundle::from_section(
|
|
"Controls:\nSpace: Change UVs\nX/Y/Z: Rotate\nR: Reset orientation",
|
|
TextStyle::default(),
|
|
)
|
|
.with_style(Style {
|
|
position_type: PositionType::Absolute,
|
|
top: Val::Px(12.0),
|
|
left: Val::Px(12.0),
|
|
..default()
|
|
}),
|
|
);
|
|
}
|
|
|
|
// System to receive input from the user,
|
|
// check out examples/input/ for more examples about user input.
|
|
fn input_handler(
|
|
keyboard_input: Res<ButtonInput<KeyCode>>,
|
|
mesh_query: Query<&Mesh3d, With<CustomUV>>,
|
|
mut meshes: ResMut<Assets<Mesh>>,
|
|
mut query: Query<&mut Transform, With<CustomUV>>,
|
|
time: Res<Time>,
|
|
) {
|
|
if keyboard_input.just_pressed(KeyCode::Space) {
|
|
let mesh_handle = mesh_query.get_single().expect("Query not successful");
|
|
let mesh = meshes.get_mut(mesh_handle).unwrap();
|
|
toggle_texture(mesh);
|
|
}
|
|
if keyboard_input.pressed(KeyCode::KeyX) {
|
|
for mut transform in &mut query {
|
|
transform.rotate_x(time.delta_seconds() / 1.2);
|
|
}
|
|
}
|
|
if keyboard_input.pressed(KeyCode::KeyY) {
|
|
for mut transform in &mut query {
|
|
transform.rotate_y(time.delta_seconds() / 1.2);
|
|
}
|
|
}
|
|
if keyboard_input.pressed(KeyCode::KeyZ) {
|
|
for mut transform in &mut query {
|
|
transform.rotate_z(time.delta_seconds() / 1.2);
|
|
}
|
|
}
|
|
if keyboard_input.pressed(KeyCode::KeyR) {
|
|
for mut transform in &mut query {
|
|
transform.look_to(Vec3::NEG_Z, Vec3::Y);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[rustfmt::skip]
|
|
fn create_cube_mesh() -> Mesh {
|
|
// Keep the mesh data accessible in future frames to be able to mutate it in toggle_texture.
|
|
Mesh::new(PrimitiveTopology::TriangleList, RenderAssetUsages::MAIN_WORLD | RenderAssetUsages::RENDER_WORLD)
|
|
.with_inserted_attribute(
|
|
Mesh::ATTRIBUTE_POSITION,
|
|
// Each array is an [x, y, z] coordinate in local space.
|
|
// The camera coordinate space is right-handed x-right, y-up, z-back. This means "forward" is -Z.
|
|
// Meshes always rotate around their local [0, 0, 0] when a rotation is applied to their Transform.
|
|
// By centering our mesh around the origin, rotating the mesh preserves its center of mass.
|
|
vec![
|
|
// top (facing towards +y)
|
|
[-0.5, 0.5, -0.5], // vertex with index 0
|
|
[0.5, 0.5, -0.5], // vertex with index 1
|
|
[0.5, 0.5, 0.5], // etc. until 23
|
|
[-0.5, 0.5, 0.5],
|
|
// bottom (-y)
|
|
[-0.5, -0.5, -0.5],
|
|
[0.5, -0.5, -0.5],
|
|
[0.5, -0.5, 0.5],
|
|
[-0.5, -0.5, 0.5],
|
|
// right (+x)
|
|
[0.5, -0.5, -0.5],
|
|
[0.5, -0.5, 0.5],
|
|
[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
|
|
[0.5, 0.5, -0.5],
|
|
// left (-x)
|
|
[-0.5, -0.5, -0.5],
|
|
[-0.5, -0.5, 0.5],
|
|
[-0.5, 0.5, 0.5],
|
|
[-0.5, 0.5, -0.5],
|
|
// back (+z)
|
|
[-0.5, -0.5, 0.5],
|
|
[-0.5, 0.5, 0.5],
|
|
[0.5, 0.5, 0.5],
|
|
[0.5, -0.5, 0.5],
|
|
// forward (-z)
|
|
[-0.5, -0.5, -0.5],
|
|
[-0.5, 0.5, -0.5],
|
|
[0.5, 0.5, -0.5],
|
|
[0.5, -0.5, -0.5],
|
|
],
|
|
)
|
|
// Set-up UV coordinates to point to the upper (V < 0.5), "dirt+grass" part of the texture.
|
|
// Take a look at the custom image (assets/textures/array_texture.png)
|
|
// so the UV coords will make more sense
|
|
// Note: (0.0, 0.0) = Top-Left in UV mapping, (1.0, 1.0) = Bottom-Right in UV mapping
|
|
.with_inserted_attribute(
|
|
Mesh::ATTRIBUTE_UV_0,
|
|
vec![
|
|
// Assigning the UV coords for the top side.
|
|
[0.0, 0.2], [0.0, 0.0], [1.0, 0.0], [1.0, 0.2],
|
|
// Assigning the UV coords for the bottom side.
|
|
[0.0, 0.45], [0.0, 0.25], [1.0, 0.25], [1.0, 0.45],
|
|
// Assigning the UV coords for the right side.
|
|
[1.0, 0.45], [0.0, 0.45], [0.0, 0.2], [1.0, 0.2],
|
|
// Assigning the UV coords for the left side.
|
|
[1.0, 0.45], [0.0, 0.45], [0.0, 0.2], [1.0, 0.2],
|
|
// Assigning the UV coords for the back side.
|
|
[0.0, 0.45], [0.0, 0.2], [1.0, 0.2], [1.0, 0.45],
|
|
// Assigning the UV coords for the forward side.
|
|
[0.0, 0.45], [0.0, 0.2], [1.0, 0.2], [1.0, 0.45],
|
|
],
|
|
)
|
|
// For meshes with flat shading, normals are orthogonal (pointing out) from the direction of
|
|
// the surface.
|
|
// Normals are required for correct lighting calculations.
|
|
// Each array represents a normalized vector, which length should be equal to 1.0.
|
|
.with_inserted_attribute(
|
|
Mesh::ATTRIBUTE_NORMAL,
|
|
vec![
|
|
// Normals for the top side (towards +y)
|
|
[0.0, 1.0, 0.0],
|
|
[0.0, 1.0, 0.0],
|
|
[0.0, 1.0, 0.0],
|
|
[0.0, 1.0, 0.0],
|
|
// Normals for the bottom side (towards -y)
|
|
[0.0, -1.0, 0.0],
|
|
[0.0, -1.0, 0.0],
|
|
[0.0, -1.0, 0.0],
|
|
[0.0, -1.0, 0.0],
|
|
// Normals for the right side (towards +x)
|
|
[1.0, 0.0, 0.0],
|
|
[1.0, 0.0, 0.0],
|
|
[1.0, 0.0, 0.0],
|
|
[1.0, 0.0, 0.0],
|
|
// Normals for the left side (towards -x)
|
|
[-1.0, 0.0, 0.0],
|
|
[-1.0, 0.0, 0.0],
|
|
[-1.0, 0.0, 0.0],
|
|
[-1.0, 0.0, 0.0],
|
|
// Normals for the back side (towards +z)
|
|
[0.0, 0.0, 1.0],
|
|
[0.0, 0.0, 1.0],
|
|
[0.0, 0.0, 1.0],
|
|
[0.0, 0.0, 1.0],
|
|
// Normals for the forward side (towards -z)
|
|
[0.0, 0.0, -1.0],
|
|
[0.0, 0.0, -1.0],
|
|
[0.0, 0.0, -1.0],
|
|
[0.0, 0.0, -1.0],
|
|
],
|
|
)
|
|
// Create the triangles out of the 24 vertices we created.
|
|
// To construct a square, we need 2 triangles, therefore 12 triangles in total.
|
|
// To construct a triangle, we need the indices of its 3 defined vertices, adding them one
|
|
// by one, in a counter-clockwise order (relative to the position of the viewer, the order
|
|
// should appear counter-clockwise from the front of the triangle, in this case from outside the cube).
|
|
// Read more about how to correctly build a mesh manually in the Bevy documentation of a Mesh,
|
|
// further examples and the implementation of the built-in shapes.
|
|
//
|
|
// The first two defined triangles look like this (marked with the vertex indices,
|
|
// and the axis), when looking down at the top (+y) of the cube:
|
|
// -Z
|
|
// ^
|
|
// 0---1
|
|
// | /|
|
|
// | / | -> +X
|
|
// |/ |
|
|
// 3---2
|
|
//
|
|
// The right face's (+x) triangles look like this, seen from the outside of the cube.
|
|
// +Y
|
|
// ^
|
|
// 10--11
|
|
// | /|
|
|
// | / | -> -Z
|
|
// |/ |
|
|
// 9---8
|
|
//
|
|
// The back face's (+z) triangles look like this, seen from the outside of the cube.
|
|
// +Y
|
|
// ^
|
|
// 17--18
|
|
// |\ |
|
|
// | \ | -> +X
|
|
// | \|
|
|
// 16--19
|
|
.with_inserted_indices(Indices::U32(vec![
|
|
0,3,1 , 1,3,2, // triangles making up the top (+y) facing side.
|
|
4,5,7 , 5,6,7, // bottom (-y)
|
|
8,11,9 , 9,11,10, // right (+x)
|
|
12,13,15 , 13,14,15, // left (-x)
|
|
16,19,17 , 17,19,18, // back (+z)
|
|
20,21,23 , 21,22,23, // forward (-z)
|
|
]))
|
|
}
|
|
|
|
// Function that changes the UV mapping of the mesh, to apply the other texture.
|
|
fn toggle_texture(mesh_to_change: &mut Mesh) {
|
|
// Get a mutable reference to the values of the UV attribute, so we can iterate over it.
|
|
let uv_attribute = mesh_to_change.attribute_mut(Mesh::ATTRIBUTE_UV_0).unwrap();
|
|
// The format of the UV coordinates should be Float32x2.
|
|
let VertexAttributeValues::Float32x2(uv_attribute) = uv_attribute else {
|
|
panic!("Unexpected vertex format, expected Float32x2.");
|
|
};
|
|
|
|
// Iterate over the UV coordinates, and change them as we want.
|
|
for uv_coord in uv_attribute.iter_mut() {
|
|
// If the UV coordinate points to the upper, "dirt+grass" part of the texture...
|
|
if (uv_coord[1] + 0.5) < 1.0 {
|
|
// ... point to the equivalent lower, "sand+water" part instead,
|
|
uv_coord[1] += 0.5;
|
|
} else {
|
|
// else, point back to the upper, "dirt+grass" part.
|
|
uv_coord[1] -= 0.5;
|
|
}
|
|
}
|
|
}
|