# Objective > Old MR: #5072 > ~~Associated UI MR: #5070~~ > Adresses #1618 Unify sprite management ## Solution - Remove the `Handle<Image>` field in `TextureAtlas` which is the main cause for all the boilerplate - Remove the redundant `TextureAtlasSprite` component - Renamed `TextureAtlas` asset to `TextureAtlasLayout` ([suggestion](https://github.com/bevyengine/bevy/pull/5103#discussion_r917281844)) - Add a `TextureAtlas` component, containing the atlas layout handle and the section index The difference between this solution and #5072 is that instead of the `enum` approach is that we can more easily manipulate texture sheets without any breaking changes for classic `SpriteBundle`s (@mockersf [comment](https://github.com/bevyengine/bevy/pull/5072#issuecomment-1165836139)) Also, this approach is more *data oriented* extracting the `Handle<Image>` and avoiding complex texture atlas manipulations to retrieve the texture in both applicative and engine code. With this method, the only difference between a `SpriteBundle` and a `SpriteSheetBundle` is an **additional** component storing the atlas handle and the index. ~~This solution can be applied to `bevy_ui` as well (see #5070).~~ EDIT: I also applied this solution to Bevy UI ## Changelog - (**BREAKING**) Removed `TextureAtlasSprite` - (**BREAKING**) Renamed `TextureAtlas` to `TextureAtlasLayout` - (**BREAKING**) `SpriteSheetBundle`: - Uses a `Sprite` instead of a `TextureAtlasSprite` component - Has a `texture` field containing a `Handle<Image>` like the `SpriteBundle` - Has a new `TextureAtlas` component instead of a `Handle<TextureAtlasLayout>` - (**BREAKING**) `DynamicTextureAtlasBuilder::add_texture` takes an additional `&Handle<Image>` parameter - (**BREAKING**) `TextureAtlasLayout::from_grid` no longer takes a `Handle<Image>` parameter - (**BREAKING**) `TextureAtlasBuilder::finish` now returns a `Result<(TextureAtlasLayout, Handle<Image>), _>` - `bevy_text`: - `GlyphAtlasInfo` stores the texture `Handle<Image>` - `FontAtlas` stores the texture `Handle<Image>` - `bevy_ui`: - (**BREAKING**) Removed `UiAtlasImage` , the atlas bundle is now identical to the `ImageBundle` with an additional `TextureAtlas` ## Migration Guide * Sprites ```diff fn my_system( mut images: ResMut<Assets<Image>>, - mut atlases: ResMut<Assets<TextureAtlas>>, + mut atlases: ResMut<Assets<TextureAtlasLayout>>, asset_server: Res<AssetServer> ) { let texture_handle: asset_server.load("my_texture.png"); - let layout = TextureAtlas::from_grid(texture_handle, Vec2::new(25.0, 25.0), 5, 5, None, None); + let layout = TextureAtlasLayout::from_grid(Vec2::new(25.0, 25.0), 5, 5, None, None); let layout_handle = atlases.add(layout); commands.spawn(SpriteSheetBundle { - sprite: TextureAtlasSprite::new(0), - texture_atlas: atlas_handle, + atlas: TextureAtlas { + layout: layout_handle, + index: 0 + }, + texture: texture_handle, ..Default::default() }); } ``` * UI ```diff fn my_system( mut images: ResMut<Assets<Image>>, - mut atlases: ResMut<Assets<TextureAtlas>>, + mut atlases: ResMut<Assets<TextureAtlasLayout>>, asset_server: Res<AssetServer> ) { let texture_handle: asset_server.load("my_texture.png"); - let layout = TextureAtlas::from_grid(texture_handle, Vec2::new(25.0, 25.0), 5, 5, None, None); + let layout = TextureAtlasLayout::from_grid(Vec2::new(25.0, 25.0), 5, 5, None, None); let layout_handle = atlases.add(layout); commands.spawn(AtlasImageBundle { - texture_atlas_image: UiTextureAtlasImage { - index: 0, - flip_x: false, - flip_y: false, - }, - texture_atlas: atlas_handle, + atlas: TextureAtlas { + layout: layout_handle, + index: 0 + }, + image: UiImage { + texture: texture_handle, + flip_x: false, + flip_y: false, + }, ..Default::default() }); } ``` --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com> Co-authored-by: François <mockersf@gmail.com> Co-authored-by: IceSentry <IceSentry@users.noreply.github.com>
105 lines
3.7 KiB
Rust
105 lines
3.7 KiB
Rust
use crate::TextureAtlasLayout;
|
|
use bevy_asset::{Assets, Handle};
|
|
use bevy_math::{IVec2, Rect, Vec2};
|
|
use bevy_render::{
|
|
render_asset::RenderAssetPersistencePolicy,
|
|
texture::{Image, TextureFormatPixelInfo},
|
|
};
|
|
use guillotiere::{size2, Allocation, AtlasAllocator};
|
|
|
|
/// Helper utility to update [`TextureAtlasLayout`] on the fly.
|
|
///
|
|
/// Helpful in cases when texture is created procedurally,
|
|
/// e.g: in a font glyph [`TextureAtlasLayout`], only add the [`Image`] texture for letters to be rendered.
|
|
pub struct DynamicTextureAtlasBuilder {
|
|
atlas_allocator: AtlasAllocator,
|
|
padding: i32,
|
|
}
|
|
|
|
impl DynamicTextureAtlasBuilder {
|
|
/// Create a new [`DynamicTextureAtlasBuilder`]
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `size` - total size for the atlas
|
|
/// * `padding` - gap added between textures in the atlas, both in x axis and y axis
|
|
pub fn new(size: Vec2, padding: i32) -> Self {
|
|
Self {
|
|
atlas_allocator: AtlasAllocator::new(to_size2(size)),
|
|
padding,
|
|
}
|
|
}
|
|
|
|
/// Add a new texture to `atlas_layout`
|
|
/// It is the user's responsibility to pass in the correct [`TextureAtlasLayout`]
|
|
/// and that `atlas_texture_handle` has [`Image::cpu_persistent_access`]
|
|
/// set to [`RenderAssetPersistencePolicy::Keep`]
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `altas_layout` - The atlas to add the texture to
|
|
/// * `textures` - The texture assets container
|
|
/// * `texture` - The new texture to add to the atlas
|
|
/// * `atlas_texture_handle` - The atlas texture to edit
|
|
pub fn add_texture(
|
|
&mut self,
|
|
atlas_layout: &mut TextureAtlasLayout,
|
|
textures: &mut Assets<Image>,
|
|
texture: &Image,
|
|
atlas_texture_handle: &Handle<Image>,
|
|
) -> Option<usize> {
|
|
let allocation = self.atlas_allocator.allocate(size2(
|
|
texture.width() as i32 + self.padding,
|
|
texture.height() as i32 + self.padding,
|
|
));
|
|
if let Some(allocation) = allocation {
|
|
let atlas_texture = textures.get_mut(atlas_texture_handle).unwrap();
|
|
assert_eq!(
|
|
atlas_texture.cpu_persistent_access,
|
|
RenderAssetPersistencePolicy::Keep
|
|
);
|
|
|
|
self.place_texture(atlas_texture, allocation, texture);
|
|
let mut rect: Rect = to_rect(allocation.rectangle);
|
|
rect.max -= self.padding as f32;
|
|
Some(atlas_layout.add_texture(rect))
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
fn place_texture(
|
|
&mut self,
|
|
atlas_texture: &mut Image,
|
|
allocation: Allocation,
|
|
texture: &Image,
|
|
) {
|
|
let mut rect = allocation.rectangle;
|
|
rect.max.x -= self.padding;
|
|
rect.max.y -= self.padding;
|
|
let atlas_width = atlas_texture.width() as usize;
|
|
let rect_width = rect.width() as usize;
|
|
let format_size = atlas_texture.texture_descriptor.format.pixel_size();
|
|
|
|
for (texture_y, bound_y) in (rect.min.y..rect.max.y).map(|i| i as usize).enumerate() {
|
|
let begin = (bound_y * atlas_width + rect.min.x as usize) * format_size;
|
|
let end = begin + rect_width * format_size;
|
|
let texture_begin = texture_y * rect_width * format_size;
|
|
let texture_end = texture_begin + rect_width * format_size;
|
|
atlas_texture.data[begin..end]
|
|
.copy_from_slice(&texture.data[texture_begin..texture_end]);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn to_rect(rectangle: guillotiere::Rectangle) -> Rect {
|
|
Rect {
|
|
min: IVec2::new(rectangle.min.x, rectangle.min.y).as_vec2(),
|
|
max: IVec2::new(rectangle.max.x, rectangle.max.y).as_vec2(),
|
|
}
|
|
}
|
|
|
|
fn to_size2(vec2: Vec2) -> guillotiere::Size {
|
|
guillotiere::Size::new(vec2.x as i32, vec2.y as i32)
|
|
}
|