Skip to content

Commit e3b6b12

Browse files
bas-iejanhohenheim
andauthored
Add sprite and mesh alteration examples (#15298)
# Objective Add examples for manipulating sprites and meshes by either mutating the handle or direct manipulation of the asset, as described in #15056. Closes #3130. (The previous PR suffered a Git-tastrophe, and was unceremoniously closed, sry! 😅 ) --------- Co-authored-by: Jan Hohenheim <[email protected]>
1 parent 02a9ed4 commit e3b6b12

File tree

4 files changed

+425
-0
lines changed

4 files changed

+425
-0
lines changed

Cargo.toml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1354,6 +1354,28 @@ category = "Application"
13541354
wasm = false
13551355

13561356
# Assets
1357+
[[example]]
1358+
name = "alter_mesh"
1359+
path = "examples/asset/alter_mesh.rs"
1360+
doc-scrape-examples = true
1361+
1362+
[package.metadata.example.alter_mesh]
1363+
name = "Alter Mesh"
1364+
description = "Shows how to modify the underlying asset of a Mesh after spawning."
1365+
category = "Assets"
1366+
wasm = false
1367+
1368+
[[example]]
1369+
name = "alter_sprite"
1370+
path = "examples/asset/alter_sprite.rs"
1371+
doc-scrape-examples = true
1372+
1373+
[package.metadata.example.alter_sprite]
1374+
name = "Alter Sprite"
1375+
description = "Shows how to modify texture assets after spawning."
1376+
category = "Assets"
1377+
wasm = false
1378+
13571379
[[example]]
13581380
name = "asset_loading"
13591381
path = "examples/asset/asset_loading.rs"

examples/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,8 @@ Example | Description
219219

220220
Example | Description
221221
--- | ---
222+
[Alter Mesh](../examples/asset/alter_mesh.rs) | Shows how to modify the underlying asset of a Mesh after spawning.
223+
[Alter Sprite](../examples/asset/alter_sprite.rs) | Shows how to modify texture assets after spawning.
222224
[Asset Decompression](../examples/asset/asset_decompression.rs) | Demonstrates loading a compressed asset
223225
[Asset Loading](../examples/asset/asset_loading.rs) | Demonstrates various methods to load assets
224226
[Asset Processing](../examples/asset/processing/asset_processing.rs) | Demonstrates how to process and load custom assets

examples/asset/alter_mesh.rs

Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
//! Shows how to modify mesh assets after spawning.
2+
3+
use bevy::{
4+
gltf::GltfLoaderSettings, input::common_conditions::input_just_pressed, prelude::*,
5+
render::mesh::VertexAttributeValues, render::render_asset::RenderAssetUsages,
6+
};
7+
8+
fn 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)]
24+
enum Shape {
25+
Cube,
26+
Sphere,
27+
}
28+
29+
impl 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)]
46+
struct Left;
47+
48+
fn 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+
PbrBundle {
106+
mesh: left_shape_model,
107+
material: material_handle.clone(),
108+
transform: Transform::from_xyz(-3.0, 0.0, 0.0),
109+
..default()
110+
},
111+
left_shape,
112+
));
113+
114+
commands.spawn((
115+
Name::new("Right Shape"),
116+
PbrBundle {
117+
mesh: right_shape_model,
118+
material: material_handle,
119+
transform: Transform::from_xyz(3.0, 0.0, 0.0),
120+
..default()
121+
},
122+
right_shape,
123+
));
124+
125+
commands.spawn((
126+
Name::new("Point Light"),
127+
PointLightBundle {
128+
transform: Transform::from_xyz(4.0, 5.0, 4.0),
129+
..default()
130+
},
131+
));
132+
133+
commands.spawn((
134+
Name::new("Camera"),
135+
Camera3dBundle {
136+
transform: Transform::from_xyz(0.0, 3.0, 20.0).looking_at(Vec3::ZERO, Vec3::Y),
137+
..default()
138+
},
139+
));
140+
}
141+
142+
fn spawn_text(mut commands: Commands) {
143+
commands
144+
.spawn((
145+
Name::new("Instructions"),
146+
NodeBundle {
147+
style: Style {
148+
align_items: AlignItems::Start,
149+
flex_direction: FlexDirection::Column,
150+
justify_content: JustifyContent::Start,
151+
width: Val::Percent(100.),
152+
..default()
153+
},
154+
..default()
155+
},
156+
))
157+
.with_children(|parent| {
158+
parent.spawn(TextBundle::from_section(
159+
"Space: swap meshes by mutating a Handle<Mesh>",
160+
TextStyle::default(),
161+
));
162+
parent.spawn(TextBundle::from_section(
163+
"Return: mutate the mesh itself, changing all copies of it",
164+
TextStyle::default(),
165+
));
166+
});
167+
}
168+
169+
fn alter_handle(
170+
asset_server: Res<AssetServer>,
171+
mut right_shape: Query<(&mut Handle<Mesh>, &mut Shape), Without<Left>>,
172+
) {
173+
// Mesh handles, like other parts of the ECS, can be queried as mutable and modified at
174+
// runtime. We only spawned one shape without the `Left` marker component.
175+
let Ok((mut handle, mut shape)) = right_shape.get_single_mut() else {
176+
return;
177+
};
178+
179+
// Switch to a new Shape variant
180+
shape.set_next_variant();
181+
182+
// Modify the handle associated with the Shape on the right side. Note that we will only
183+
// have to load the same path from storage media once: repeated attempts will re-use the
184+
// asset.
185+
*handle = asset_server.load(
186+
GltfAssetLabel::Primitive {
187+
mesh: 0,
188+
primitive: 0,
189+
}
190+
.from_asset(shape.get_model_path()),
191+
);
192+
}
193+
194+
fn alter_mesh(
195+
mut is_mesh_scaled: Local<bool>,
196+
left_shape: Query<&Handle<Mesh>, With<Left>>,
197+
mut meshes: ResMut<Assets<Mesh>>,
198+
) {
199+
// It's convenient to retrieve the asset handle stored with the shape on the left. However,
200+
// we could just as easily have retained this in a resource or a dedicated component.
201+
let Ok(handle) = left_shape.get_single() else {
202+
return;
203+
};
204+
205+
// Obtain a mutable reference to the Mesh asset.
206+
let Some(mesh) = meshes.get_mut(handle) else {
207+
return;
208+
};
209+
210+
// Now we can directly manipulate vertices on the mesh. Here, we're just scaling in and out
211+
// for demonstration purposes. This will affect all entities currently using the asset.
212+
//
213+
// To do this, we need to grab the stored attributes of each vertex. `Float32x3` just describes
214+
// the format in which the attributes will be read: each position consists of an array of three
215+
// f32 corresponding to x, y, and z.
216+
//
217+
// `ATTRIBUTE_POSITION` is a constant indicating that we want to know where the vertex is
218+
// located in space (as opposed to which way its normal is facing, vertex color, or other
219+
// details).
220+
if let Some(VertexAttributeValues::Float32x3(positions)) =
221+
mesh.attribute_mut(Mesh::ATTRIBUTE_POSITION)
222+
{
223+
// Check a Local value (which only this system can make use of) to determine if we're
224+
// currently scaled up or not.
225+
let scale_factor = if *is_mesh_scaled { 0.5 } else { 2.0 };
226+
227+
for position in positions.iter_mut() {
228+
// Apply the scale factor to each of x, y, and z.
229+
position[0] *= scale_factor;
230+
position[1] *= scale_factor;
231+
position[2] *= scale_factor;
232+
}
233+
234+
// Flip the local value to reverse the behaviour next time the key is pressed.
235+
*is_mesh_scaled = !*is_mesh_scaled;
236+
}
237+
}

0 commit comments

Comments
 (0)