alter_mesh/
alter_mesh.rs

1//! Shows how to modify mesh assets after spawning.
2
3use bevy::{
4    asset::RenderAssetUsages, gltf::GltfLoaderSettings,
5    input::common_conditions::input_just_pressed, mesh::VertexAttributeValues, prelude::*,
6};
7
8fn main() {
9    App::new()
10        .add_plugins(DefaultPlugins)
11        .add_systems(Startup, (setup, spawn_text))
12        .add_systems(
13            Update,
14            alter_handle.run_if(input_just_pressed(KeyCode::Space)),
15        )
16        .add_systems(
17            Update,
18            alter_mesh.run_if(input_just_pressed(KeyCode::Enter)),
19        )
20        .run();
21}
22
23#[derive(Component, Debug)]
24enum Shape {
25    Cube,
26    Sphere,
27}
28
29impl Shape {
30    fn get_model_path(&self) -> String {
31        match self {
32            Shape::Cube => "models/cube/cube.gltf".into(),
33            Shape::Sphere => "models/sphere/sphere.gltf".into(),
34        }
35    }
36
37    fn set_next_variant(&mut self) {
38        *self = match self {
39            Shape::Cube => Shape::Sphere,
40            Shape::Sphere => Shape::Cube,
41        }
42    }
43}
44
45#[derive(Component, Debug)]
46struct Left;
47
48fn setup(
49    mut commands: Commands,
50    asset_server: Res<AssetServer>,
51    mut materials: ResMut<Assets<StandardMaterial>>,
52) {
53    let left_shape = Shape::Cube;
54    let right_shape = Shape::Cube;
55
56    // In normal use, you can call `asset_server.load`, however see below for an explanation of
57    // `RenderAssetUsages`.
58    let left_shape_model = asset_server.load_with_settings(
59        GltfAssetLabel::Primitive {
60            mesh: 0,
61            // This field stores an index to this primitive in its parent mesh. In this case, we
62            // want the first one. You might also have seen the syntax:
63            //
64            //     models/cube/cube.gltf#Scene0
65            //
66            // which accomplishes the same thing.
67            primitive: 0,
68        }
69        .from_asset(left_shape.get_model_path()),
70        // `RenderAssetUsages::all()` is already the default, so the line below could be omitted.
71        // It's helpful to know it exists, however.
72        //
73        // `RenderAssetUsages` tell Bevy whether to keep the data around:
74        //   - for the GPU (`RenderAssetUsages::RENDER_WORLD`),
75        //   - for the CPU (`RenderAssetUsages::MAIN_WORLD`),
76        //   - or both.
77        // `RENDER_WORLD` is necessary to render the mesh, `MAIN_WORLD` is necessary to inspect
78        // and modify the mesh (via `ResMut<Assets<Mesh>>`).
79        //
80        // Since most games will not need to modify meshes at runtime, many developers opt to pass
81        // only `RENDER_WORLD`. This is more memory efficient, as we don't need to keep the mesh in
82        // RAM. For this example however, this would not work, as we need to inspect and modify the
83        // mesh at runtime.
84        |settings: &mut GltfLoaderSettings| settings.load_meshes = RenderAssetUsages::all(),
85    );
86
87    // Here, we rely on the default loader settings to achieve a similar result to the above.
88    let right_shape_model = asset_server.load(
89        GltfAssetLabel::Primitive {
90            mesh: 0,
91            primitive: 0,
92        }
93        .from_asset(right_shape.get_model_path()),
94    );
95
96    // Add a material asset directly to the materials storage
97    let material_handle = materials.add(StandardMaterial {
98        base_color: Color::srgb(0.6, 0.8, 0.6),
99        ..default()
100    });
101
102    commands.spawn((
103        Left,
104        Name::new("Left Shape"),
105        Mesh3d(left_shape_model),
106        MeshMaterial3d(material_handle.clone()),
107        Transform::from_xyz(-3.0, 0.0, 0.0),
108        left_shape,
109    ));
110
111    commands.spawn((
112        Name::new("Right Shape"),
113        Mesh3d(right_shape_model),
114        MeshMaterial3d(material_handle),
115        Transform::from_xyz(3.0, 0.0, 0.0),
116        right_shape,
117    ));
118
119    commands.spawn((
120        Name::new("Point Light"),
121        PointLight::default(),
122        Transform::from_xyz(4.0, 5.0, 4.0),
123    ));
124
125    commands.spawn((
126        Name::new("Camera"),
127        Camera3d::default(),
128        Transform::from_xyz(0.0, 3.0, 20.0).looking_at(Vec3::ZERO, Vec3::Y),
129    ));
130}
131
132fn spawn_text(mut commands: Commands) {
133    commands.spawn((
134        Name::new("Instructions"),
135        Text::new(
136            "Space: swap meshes by mutating a Handle<Mesh>\n\
137            Return: mutate the mesh itself, changing all copies of it",
138        ),
139        Node {
140            position_type: PositionType::Absolute,
141            top: px(12),
142            left: px(12),
143            ..default()
144        },
145    ));
146}
147
148fn alter_handle(
149    asset_server: Res<AssetServer>,
150    right_shape: Single<(&mut Mesh3d, &mut Shape), Without<Left>>,
151) {
152    // Mesh handles, like other parts of the ECS, can be queried as mutable and modified at
153    // runtime. We only spawned one shape without the `Left` marker component.
154    let (mut mesh, mut shape) = right_shape.into_inner();
155
156    // Switch to a new Shape variant
157    shape.set_next_variant();
158
159    // Modify the handle associated with the Shape on the right side. Note that we will only
160    // have to load the same path from storage media once: repeated attempts will re-use the
161    // asset.
162    mesh.0 = asset_server.load(
163        GltfAssetLabel::Primitive {
164            mesh: 0,
165            primitive: 0,
166        }
167        .from_asset(shape.get_model_path()),
168    );
169}
170
171fn alter_mesh(
172    mut is_mesh_scaled: Local<bool>,
173    left_shape: Single<&Mesh3d, With<Left>>,
174    mut meshes: ResMut<Assets<Mesh>>,
175) {
176    // Obtain a mutable reference to the Mesh asset.
177    let Some(mesh) = meshes.get_mut(*left_shape) else {
178        return;
179    };
180
181    // Now we can directly manipulate vertices on the mesh. Here, we're just scaling in and out
182    // for demonstration purposes. This will affect all entities currently using the asset.
183    //
184    // To do this, we need to grab the stored attributes of each vertex. `Float32x3` just describes
185    // the format in which the attributes will be read: each position consists of an array of three
186    // f32 corresponding to x, y, and z.
187    //
188    // `ATTRIBUTE_POSITION` is a constant indicating that we want to know where the vertex is
189    // located in space (as opposed to which way its normal is facing, vertex color, or other
190    // details).
191    if let Some(VertexAttributeValues::Float32x3(positions)) =
192        mesh.attribute_mut(Mesh::ATTRIBUTE_POSITION)
193    {
194        // Check a Local value (which only this system can make use of) to determine if we're
195        // currently scaled up or not.
196        let scale_factor = if *is_mesh_scaled { 0.5 } else { 2.0 };
197
198        for position in positions.iter_mut() {
199            // Apply the scale factor to each of x, y, and z.
200            position[0] *= scale_factor;
201            position[1] *= scale_factor;
202            position[2] *= scale_factor;
203        }
204
205        // Flip the local value to reverse the behavior next time the key is pressed.
206        *is_mesh_scaled = !*is_mesh_scaled;
207    }
208}