Skip to main content

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