Skip to content

Commit 3c7c24c

Browse files
committed
Create an example for the use of asset arcing.
1 parent 2889283 commit 3c7c24c

File tree

4 files changed

+269
-0
lines changed

4 files changed

+269
-0
lines changed

Cargo.toml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1561,6 +1561,17 @@ description = "Demonstrates various methods to load assets"
15611561
category = "Assets"
15621562
wasm = false
15631563

1564+
[[example]]
1565+
name = "arc_asset"
1566+
path = "examples/asset/arc_asset.rs"
1567+
doc-scrape-examples = true
1568+
1569+
[package.metadata.example.arc_asset]
1570+
name = "Arced Assets"
1571+
description = "Demonstrates how to acquire Arc'd assets and use them in an async context"
1572+
category = "Assets"
1573+
wasm = false
1574+
15641575
[[example]]
15651576
name = "asset_settings"
15661577
path = "examples/asset/asset_settings.rs"

examples/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,7 @@ Example | Description
232232
--- | ---
233233
[Alter Mesh](../examples/asset/alter_mesh.rs) | Shows how to modify the underlying asset of a Mesh after spawning.
234234
[Alter Sprite](../examples/asset/alter_sprite.rs) | Shows how to modify texture assets after spawning.
235+
[Arced Assets](../examples/asset/arc_asset.rs) | Demonstrates how to acquire Arc'd assets and use them in an async context
235236
[Asset Decompression](../examples/asset/asset_decompression.rs) | Demonstrates loading a compressed asset
236237
[Asset Loading](../examples/asset/asset_loading.rs) | Demonstrates various methods to load assets
237238
[Asset Processing](../examples/asset/processing/asset_processing.rs) | Demonstrates how to process and load custom assets

examples/asset/arc_asset.rs

Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
//! This example illustrates how to use assets in an async context (through locking).
2+
3+
use std::sync::Arc;
4+
5+
use bevy::{
6+
asset::AssetLoader,
7+
color::palettes::tailwind,
8+
math::FloatOrd,
9+
prelude::*,
10+
render::{mesh::Indices, render_asset::RenderAssetUsages},
11+
tasks::AsyncComputeTaskPool,
12+
};
13+
use rand::{Rng, SeedableRng};
14+
use serde::{Deserialize, Serialize};
15+
use thiserror::Error;
16+
17+
fn main() {
18+
App::new()
19+
.add_plugins(
20+
// This just tells the asset server to look in the right examples folder
21+
DefaultPlugins.set(AssetPlugin {
22+
file_path: "examples/asset/files".to_string(),
23+
..Default::default()
24+
}),
25+
)
26+
.init_asset::<LinearInterpolation>()
27+
.register_asset_loader(LinearInterpolationLoader)
28+
.add_systems(Startup, setup)
29+
.add_systems(Update, (start_mesh_generation, finish_mesh_generation))
30+
.run();
31+
}
32+
33+
fn setup(
34+
asset_server: Res<AssetServer>,
35+
mut materials: ResMut<Assets<StandardMaterial>>,
36+
meshes: Res<Assets<Mesh>>,
37+
mut commands: Commands,
38+
) {
39+
// Spawn a camera.
40+
commands.spawn((
41+
Transform::from_translation(Vec3::new(15.0, 15.0, 15.0)).looking_at(Vec3::ZERO, Vec3::Y),
42+
Camera3d::default(),
43+
));
44+
45+
// Spawn a light.
46+
commands.spawn((
47+
Transform::default().looking_to(Dir3::from_xyz(1.0, -1.0, 0.0).unwrap(), Dir3::Y),
48+
DirectionalLight::default(),
49+
));
50+
51+
// Spawn the mesh. Reserve the handle so we can generate it later.
52+
let mesh = meshes.reserve_handle();
53+
commands.spawn((
54+
Mesh3d(mesh.clone()),
55+
MeshMaterial3d(materials.add(StandardMaterial {
56+
base_color: tailwind::SLATE_100.into(),
57+
..Default::default()
58+
})),
59+
));
60+
61+
// Create the parameters for mesh generation.
62+
commands.insert_resource(MeshGeneration {
63+
height_interpolation: asset_server.load("heights.li.ron"),
64+
mesh,
65+
size: UVec2::new(30, 30),
66+
});
67+
68+
// Create the channel we will communicate across.
69+
let (sender, receiver) = crossbeam_channel::bounded(1);
70+
commands.insert_resource(MeshGenerationChannel { sender, receiver });
71+
}
72+
73+
#[derive(Resource)]
74+
struct MeshGeneration {
75+
height_interpolation: Handle<LinearInterpolation>,
76+
mesh: Handle<Mesh>,
77+
size: UVec2,
78+
}
79+
80+
#[derive(Resource)]
81+
struct MeshGenerationChannel {
82+
sender: crossbeam_channel::Sender<Mesh>,
83+
receiver: crossbeam_channel::Receiver<Mesh>,
84+
}
85+
86+
/// Starts a mesh generation task whenever the height interpolation asset is updated.
87+
fn start_mesh_generation(
88+
mut asset_events: EventReader<AssetEvent<LinearInterpolation>>,
89+
linear_interpolations: Res<Assets<LinearInterpolation>>,
90+
mesh_generation: Res<MeshGeneration>,
91+
channel: Res<MeshGenerationChannel>,
92+
) {
93+
// Only recompute if the height interpolation asset has changed.
94+
let regenerate_id = mesh_generation.height_interpolation.id();
95+
let mut recompute = false;
96+
for asset_event in asset_events.read() {
97+
match asset_event {
98+
AssetEvent::Added { id } | AssetEvent::Modified { id } if *id == regenerate_id => {
99+
recompute = true;
100+
}
101+
_ => {}
102+
}
103+
}
104+
105+
if !recompute {
106+
return;
107+
}
108+
109+
let task_pool = AsyncComputeTaskPool::get();
110+
let size = mesh_generation.size;
111+
// Get an `Arc` of the height interpolation asset to pass to the spawned task.
112+
let height_interpolation = linear_interpolations
113+
.get_arc(&mesh_generation.height_interpolation)
114+
.expect("The asset is loaded");
115+
let channel = channel.sender.clone();
116+
// Spawn a task to generate the mesh, then send the resulting mesh across the channel.
117+
task_pool
118+
.spawn(async move {
119+
let mesh = generate_mesh(size, height_interpolation);
120+
channel.send(mesh).expect("The channel never closes");
121+
})
122+
.detach();
123+
}
124+
125+
/// Reads from the mesh generation channel and inserts the mesh asset.
126+
fn finish_mesh_generation(
127+
mesh_generation: Res<MeshGeneration>,
128+
channel: Res<MeshGenerationChannel>,
129+
mut meshes: ResMut<Assets<Mesh>>,
130+
) {
131+
let Ok(mesh) = channel.receiver.try_recv() else {
132+
return;
133+
};
134+
meshes.insert(&mesh_generation.mesh, mesh);
135+
}
136+
137+
/// A basic linear interpolation curve implementation.
138+
#[derive(Asset, TypePath, Serialize, Deserialize)]
139+
struct LinearInterpolation(Vec<(f32, f32)>);
140+
141+
impl LinearInterpolation {
142+
/// Samples the linear interpolation at `value`.
143+
fn sample(&self, value: f32) -> f32 {
144+
match self.0.iter().position(|(x, _)| value < *x) {
145+
None => self.0.last().expect("The interpolation is non-empty").1,
146+
Some(0) => self.0.first().expect("The interpolation is non-empty").1,
147+
Some(next) => {
148+
let previous = next - 1;
149+
150+
let (next_x, next_y) = self.0[next];
151+
let (previous_x, previous_y) = self.0[previous];
152+
153+
let alpha = (value - previous_x) / (next_x - previous_x);
154+
155+
alpha * (next_y - previous_y) + previous_y
156+
}
157+
}
158+
}
159+
}
160+
161+
#[derive(Default)]
162+
struct LinearInterpolationLoader;
163+
164+
#[derive(Debug, Error)]
165+
enum LinearInterpolationLoaderError {
166+
#[error(transparent)]
167+
Io(#[from] std::io::Error),
168+
#[error(transparent)]
169+
RonSpannedError(#[from] ron::error::SpannedError),
170+
#[error("The loaded interpolation is empty.")]
171+
Empty,
172+
#[error("The loaded interpolation contains duplicate X values")]
173+
DuplicateXValues,
174+
}
175+
176+
impl AssetLoader for LinearInterpolationLoader {
177+
type Asset = LinearInterpolation;
178+
type Settings = ();
179+
type Error = LinearInterpolationLoaderError;
180+
181+
async fn load(
182+
&self,
183+
reader: &mut dyn bevy::asset::io::Reader,
184+
_settings: &Self::Settings,
185+
_load_context: &mut bevy::asset::LoadContext<'_>,
186+
) -> Result<Self::Asset, Self::Error> {
187+
let mut bytes = Vec::new();
188+
reader.read_to_end(&mut bytes).await?;
189+
let mut interpolation: LinearInterpolation = ron::de::from_bytes(&bytes)?;
190+
if interpolation.0.is_empty() {
191+
return Err(Self::Error::Empty);
192+
}
193+
interpolation.0.sort_by_key(|(key, _)| FloatOrd(*key));
194+
if interpolation
195+
.0
196+
.windows(2)
197+
.any(|window| window[0].0 == window[1].0)
198+
{
199+
return Err(Self::Error::DuplicateXValues);
200+
}
201+
Ok(interpolation)
202+
}
203+
204+
fn extensions(&self) -> &[&str] {
205+
&["li.ron"]
206+
}
207+
}
208+
209+
/// Generates the mesh given the interpolation curve and the size of the mesh.
210+
fn generate_mesh(size: UVec2, interpolation: Arc<LinearInterpolation>) -> Mesh {
211+
let mut rng = rand_chacha::ChaChaRng::seed_from_u64(12345);
212+
213+
let center = Vec3::new((size.x as f32) / 2.0, 0.0, (size.y as f32) / -2.0);
214+
215+
let mut vertices = Vec::with_capacity(((size.x + 1) * (size.y + 1)) as usize);
216+
let mut uvs = Vec::with_capacity(((size.x + 1) * (size.y + 1)) as usize);
217+
for y in 0..size.y + 1 {
218+
for x in 0..size.x + 1 {
219+
let height = interpolation.sample(rng.gen());
220+
vertices.push(Vec3::new(x as f32, height, -(y as f32)) - center);
221+
uvs.push(Vec2::new(x as f32, -(y as f32)));
222+
}
223+
}
224+
225+
let y_stride = size.x + 1;
226+
let mut indices = Vec::with_capacity((size.x * size.y * 6) as usize);
227+
for y in 0..size.y {
228+
for x in 0..size.x {
229+
indices.push(x + y * y_stride);
230+
indices.push(x + 1 + y * y_stride);
231+
indices.push(x + 1 + (y + 1) * y_stride);
232+
indices.push(x + y * y_stride);
233+
indices.push(x + 1 + (y + 1) * y_stride);
234+
indices.push(x + (y + 1) * y_stride);
235+
}
236+
}
237+
238+
let mut mesh = Mesh::new(
239+
bevy_render::mesh::PrimitiveTopology::TriangleList,
240+
RenderAssetUsages::RENDER_WORLD,
241+
)
242+
.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, vertices)
243+
.with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)
244+
.with_inserted_indices(Indices::U32(indices));
245+
246+
mesh.compute_normals();
247+
mesh.generate_tangents()
248+
.expect("The tangents are well formed");
249+
250+
mesh
251+
}

examples/asset/files/heights.li.ron

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
([
2+
(0.0, 0.0),
3+
(0.5, 0.1),
4+
(0.7, 1.0),
5+
(1.0, 2.0),
6+
])

0 commit comments

Comments
 (0)