Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_sprite_render/src/texture_slice/computed_slices.rs
6600 views
1
use crate::{ExtractedSlice, TextureAtlasLayout};
2
use bevy_asset::{AssetEvent, Assets};
3
use bevy_ecs::prelude::*;
4
use bevy_image::Image;
5
use bevy_math::{Rect, Vec2};
6
use bevy_platform::collections::HashSet;
7
use bevy_sprite::{Sprite, SpriteImageMode, TextureSlice};
8
9
/// Component storing texture slices for tiled or sliced sprite entities
10
///
11
/// This component is automatically inserted and updated
12
#[derive(Debug, Clone, Component)]
13
pub struct ComputedTextureSlices(Vec<TextureSlice>);
14
15
impl ComputedTextureSlices {
16
/// Computes [`ExtractedSlice`] iterator from the sprite slices
17
///
18
/// # Arguments
19
///
20
/// * `sprite` - The sprite component
21
#[must_use]
22
pub(crate) fn extract_slices<'a>(
23
&'a self,
24
sprite: &'a Sprite,
25
anchor: Vec2,
26
) -> impl ExactSizeIterator<Item = ExtractedSlice> + 'a {
27
let mut flip = Vec2::ONE;
28
if sprite.flip_x {
29
flip.x *= -1.0;
30
}
31
if sprite.flip_y {
32
flip.y *= -1.0;
33
}
34
let anchor = anchor
35
* sprite
36
.custom_size
37
.unwrap_or(sprite.rect.unwrap_or_default().size());
38
self.0.iter().map(move |slice| ExtractedSlice {
39
offset: slice.offset * flip - anchor,
40
rect: slice.texture_rect,
41
size: slice.draw_size,
42
})
43
}
44
}
45
46
/// Generates sprite slices for a [`Sprite`] with [`SpriteImageMode::Sliced`] or [`SpriteImageMode::Sliced`]. The slices
47
/// will be computed according to the `image_handle` dimensions or the sprite rect.
48
///
49
/// Returns `None` if the image asset is not loaded
50
///
51
/// # Arguments
52
///
53
/// * `sprite` - The sprite component with the image handle and image mode
54
/// * `images` - The image assets, use to retrieve the image dimensions
55
/// * `atlas_layouts` - The atlas layout assets, used to retrieve the texture atlas section rect
56
#[must_use]
57
fn compute_sprite_slices(
58
sprite: &Sprite,
59
images: &Assets<Image>,
60
atlas_layouts: &Assets<TextureAtlasLayout>,
61
) -> Option<ComputedTextureSlices> {
62
let (image_size, texture_rect) = match &sprite.texture_atlas {
63
Some(a) => {
64
let layout = atlas_layouts.get(&a.layout)?;
65
(
66
layout.size.as_vec2(),
67
layout.textures.get(a.index)?.as_rect(),
68
)
69
}
70
None => {
71
let image = images.get(&sprite.image)?;
72
let size = Vec2::new(
73
image.texture_descriptor.size.width as f32,
74
image.texture_descriptor.size.height as f32,
75
);
76
let rect = sprite.rect.unwrap_or(Rect {
77
min: Vec2::ZERO,
78
max: size,
79
});
80
(size, rect)
81
}
82
};
83
let slices = match &sprite.image_mode {
84
SpriteImageMode::Sliced(slicer) => slicer.compute_slices(texture_rect, sprite.custom_size),
85
SpriteImageMode::Tiled {
86
tile_x,
87
tile_y,
88
stretch_value,
89
} => {
90
let slice = TextureSlice {
91
texture_rect,
92
draw_size: sprite.custom_size.unwrap_or(image_size),
93
offset: Vec2::ZERO,
94
};
95
slice.tiled(*stretch_value, (*tile_x, *tile_y))
96
}
97
SpriteImageMode::Auto => {
98
unreachable!("Slices should not be computed for SpriteImageMode::Stretch")
99
}
100
SpriteImageMode::Scale(_) => {
101
unreachable!("Slices should not be computed for SpriteImageMode::Scale")
102
}
103
};
104
Some(ComputedTextureSlices(slices))
105
}
106
107
/// System reacting to added or modified [`Image`] handles, and recompute sprite slices
108
/// on sprite entities with a matching [`SpriteImageMode`]
109
pub(crate) fn compute_slices_on_asset_event(
110
mut commands: Commands,
111
mut events: EventReader<AssetEvent<Image>>,
112
images: Res<Assets<Image>>,
113
atlas_layouts: Res<Assets<TextureAtlasLayout>>,
114
sprites: Query<(Entity, &Sprite)>,
115
) {
116
// We store the asset ids of added/modified image assets
117
let added_handles: HashSet<_> = events
118
.read()
119
.filter_map(|e| match e {
120
AssetEvent::Added { id } | AssetEvent::Modified { id } => Some(*id),
121
_ => None,
122
})
123
.collect();
124
if added_handles.is_empty() {
125
return;
126
}
127
// We recompute the sprite slices for sprite entities with a matching asset handle id
128
for (entity, sprite) in &sprites {
129
if !sprite.image_mode.uses_slices() {
130
continue;
131
}
132
if !added_handles.contains(&sprite.image.id()) {
133
continue;
134
}
135
if let Some(slices) = compute_sprite_slices(sprite, &images, &atlas_layouts) {
136
commands.entity(entity).insert(slices);
137
}
138
}
139
}
140
141
/// System reacting to changes on the [`Sprite`] component to compute the sprite slices
142
pub(crate) fn compute_slices_on_sprite_change(
143
mut commands: Commands,
144
images: Res<Assets<Image>>,
145
atlas_layouts: Res<Assets<TextureAtlasLayout>>,
146
changed_sprites: Query<(Entity, &Sprite), Changed<Sprite>>,
147
) {
148
for (entity, sprite) in &changed_sprites {
149
if !sprite.image_mode.uses_slices() {
150
continue;
151
}
152
if let Some(slices) = compute_sprite_slices(sprite, &images, &atlas_layouts) {
153
commands.entity(entity).insert(slices);
154
}
155
}
156
}
157
158