Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_image/src/texture_atlas.rs
6595 views
1
use bevy_app::prelude::*;
2
use bevy_asset::{Asset, AssetApp as _, AssetId, Assets, Handle};
3
use bevy_math::{Rect, URect, UVec2};
4
use bevy_platform::collections::HashMap;
5
#[cfg(not(feature = "bevy_reflect"))]
6
use bevy_reflect::TypePath;
7
#[cfg(feature = "bevy_reflect")]
8
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
9
#[cfg(feature = "serialize")]
10
use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
11
12
use crate::Image;
13
14
/// Adds support for texture atlases.
15
pub struct TextureAtlasPlugin;
16
17
impl Plugin for TextureAtlasPlugin {
18
fn build(&self, app: &mut App) {
19
app.init_asset::<TextureAtlasLayout>();
20
21
#[cfg(feature = "bevy_reflect")]
22
app.register_asset_reflect::<TextureAtlasLayout>();
23
}
24
}
25
26
/// Stores a mapping from sub texture handles to the related area index.
27
///
28
/// Generated by [`TextureAtlasBuilder`].
29
///
30
/// [`TextureAtlasBuilder`]: crate::TextureAtlasBuilder
31
#[derive(Debug)]
32
pub struct TextureAtlasSources {
33
/// Maps from a specific image handle to the index in `textures` where they can be found.
34
pub texture_ids: HashMap<AssetId<Image>, usize>,
35
}
36
37
impl TextureAtlasSources {
38
/// Retrieves the texture *section* index of the given `texture` handle.
39
pub fn texture_index(&self, texture: impl Into<AssetId<Image>>) -> Option<usize> {
40
let id = texture.into();
41
self.texture_ids.get(&id).cloned()
42
}
43
44
/// Creates a [`TextureAtlas`] handle for the given `texture` handle.
45
pub fn handle(
46
&self,
47
layout: Handle<TextureAtlasLayout>,
48
texture: impl Into<AssetId<Image>>,
49
) -> Option<TextureAtlas> {
50
Some(TextureAtlas {
51
layout,
52
index: self.texture_index(texture)?,
53
})
54
}
55
56
/// Retrieves the texture *section* rectangle of the given `texture` handle in pixels.
57
pub fn texture_rect(
58
&self,
59
layout: &TextureAtlasLayout,
60
texture: impl Into<AssetId<Image>>,
61
) -> Option<URect> {
62
layout.textures.get(self.texture_index(texture)?).cloned()
63
}
64
65
/// Retrieves the texture *section* rectangle of the given `texture` handle in UV coordinates.
66
/// These are within the range [0..1], as a fraction of the entire texture atlas' size.
67
pub fn uv_rect(
68
&self,
69
layout: &TextureAtlasLayout,
70
texture: impl Into<AssetId<Image>>,
71
) -> Option<Rect> {
72
self.texture_rect(layout, texture).map(|rect| {
73
let rect = rect.as_rect();
74
let size = layout.size.as_vec2();
75
Rect::from_corners(rect.min / size, rect.max / size)
76
})
77
}
78
}
79
80
/// Stores a map used to lookup the position of a texture in a [`TextureAtlas`].
81
/// This can be used to either use and look up a specific section of a texture, or animate frame-by-frame as a sprite sheet.
82
///
83
/// Optionally it can store a mapping from sub texture handles to the related area index (see
84
/// [`TextureAtlasBuilder`]).
85
///
86
/// [Example usage animating sprite.](https://github.com/bevyengine/bevy/blob/latest/examples/2d/sprite_sheet.rs)
87
/// [Example usage animating sprite in response to an event.](https://github.com/bevyengine/bevy/blob/latest/examples/2d/sprite_animation.rs)
88
/// [Example usage loading sprite sheet.](https://github.com/bevyengine/bevy/blob/latest/examples/2d/texture_atlas.rs)
89
///
90
/// [`TextureAtlasBuilder`]: crate::TextureAtlasBuilder
91
#[derive(Asset, PartialEq, Eq, Debug, Clone)]
92
#[cfg_attr(
93
feature = "bevy_reflect",
94
derive(Reflect),
95
reflect(Debug, PartialEq, Clone)
96
)]
97
#[cfg_attr(
98
feature = "serialize",
99
derive(serde::Serialize, serde::Deserialize),
100
reflect(Serialize, Deserialize)
101
)]
102
#[cfg_attr(not(feature = "bevy_reflect"), derive(TypePath))]
103
pub struct TextureAtlasLayout {
104
/// Total size of texture atlas.
105
pub size: UVec2,
106
/// The specific areas of the atlas where each texture can be found
107
pub textures: Vec<URect>,
108
}
109
110
impl TextureAtlasLayout {
111
/// Create a new empty layout with custom `dimensions`
112
pub fn new_empty(dimensions: UVec2) -> Self {
113
Self {
114
size: dimensions,
115
textures: Vec::new(),
116
}
117
}
118
119
/// Generate a [`TextureAtlasLayout`] as a grid where each
120
/// `tile_size` by `tile_size` grid-cell is one of the *section* in the
121
/// atlas. Grid cells are separated by some `padding`, and the grid starts
122
/// at `offset` pixels from the top left corner. Resulting layout is
123
/// indexed left to right, top to bottom.
124
///
125
/// # Arguments
126
///
127
/// * `tile_size` - Each layout grid cell size
128
/// * `columns` - Grid column count
129
/// * `rows` - Grid row count
130
/// * `padding` - Optional padding between cells
131
/// * `offset` - Optional global grid offset
132
pub fn from_grid(
133
tile_size: UVec2,
134
columns: u32,
135
rows: u32,
136
padding: Option<UVec2>,
137
offset: Option<UVec2>,
138
) -> Self {
139
let padding = padding.unwrap_or_default();
140
let offset = offset.unwrap_or_default();
141
let mut sprites = Vec::new();
142
let mut current_padding = UVec2::ZERO;
143
144
for y in 0..rows {
145
if y > 0 {
146
current_padding.y = padding.y;
147
}
148
for x in 0..columns {
149
if x > 0 {
150
current_padding.x = padding.x;
151
}
152
153
let cell = UVec2::new(x, y);
154
let rect_min = (tile_size + current_padding) * cell + offset;
155
156
sprites.push(URect {
157
min: rect_min,
158
max: rect_min + tile_size,
159
});
160
}
161
}
162
163
let grid_size = UVec2::new(columns, rows);
164
165
Self {
166
size: ((tile_size + current_padding) * grid_size) - current_padding,
167
textures: sprites,
168
}
169
}
170
171
/// Add a *section* to the list in the layout and returns its index
172
/// which can be used with [`TextureAtlas`]
173
///
174
/// # Arguments
175
///
176
/// * `rect` - The section of the texture to be added
177
///
178
/// [`TextureAtlas`]: crate::TextureAtlas
179
pub fn add_texture(&mut self, rect: URect) -> usize {
180
self.textures.push(rect);
181
self.textures.len() - 1
182
}
183
184
/// The number of textures in the [`TextureAtlasLayout`]
185
pub fn len(&self) -> usize {
186
self.textures.len()
187
}
188
189
pub fn is_empty(&self) -> bool {
190
self.textures.is_empty()
191
}
192
}
193
194
/// An index into a [`TextureAtlasLayout`], which corresponds to a specific section of a texture.
195
///
196
/// It stores a handle to [`TextureAtlasLayout`] and the index of the current section of the atlas.
197
/// The texture atlas contains various *sections* of a given texture, allowing users to have a single
198
/// image file for either sprite animation or global mapping.
199
/// You can change the texture [`index`](Self::index) of the atlas to animate the sprite or display only a *section* of the texture
200
/// for efficient rendering of related game objects.
201
///
202
/// Check the following examples for usage:
203
/// - [`animated sprite sheet example`](https://github.com/bevyengine/bevy/blob/latest/examples/2d/sprite_sheet.rs)
204
/// - [`sprite animation event example`](https://github.com/bevyengine/bevy/blob/latest/examples/2d/sprite_animation.rs)
205
/// - [`texture atlas example`](https://github.com/bevyengine/bevy/blob/latest/examples/2d/texture_atlas.rs)
206
#[derive(Default, Debug, Clone, PartialEq, Eq, Hash)]
207
#[cfg_attr(
208
feature = "bevy_reflect",
209
derive(Reflect),
210
reflect(Default, Debug, PartialEq, Hash, Clone)
211
)]
212
pub struct TextureAtlas {
213
/// Texture atlas layout handle
214
pub layout: Handle<TextureAtlasLayout>,
215
/// Texture atlas section index
216
pub index: usize,
217
}
218
219
impl TextureAtlas {
220
/// Retrieves the current texture [`URect`] of the sprite sheet according to the section `index`
221
pub fn texture_rect(&self, texture_atlases: &Assets<TextureAtlasLayout>) -> Option<URect> {
222
let atlas = texture_atlases.get(&self.layout)?;
223
atlas.textures.get(self.index).copied()
224
}
225
226
/// Returns this [`TextureAtlas`] with the specified index.
227
pub fn with_index(mut self, index: usize) -> Self {
228
self.index = index;
229
self
230
}
231
232
/// Returns this [`TextureAtlas`] with the specified [`TextureAtlasLayout`] handle.
233
pub fn with_layout(mut self, layout: Handle<TextureAtlasLayout>) -> Self {
234
self.layout = layout;
235
self
236
}
237
}
238
239
impl From<Handle<TextureAtlasLayout>> for TextureAtlas {
240
fn from(texture_atlas: Handle<TextureAtlasLayout>) -> Self {
241
Self {
242
layout: texture_atlas,
243
index: 0,
244
}
245
}
246
}
247
248