Allow TextureAtlasBuilder in AssetLoader (#11548)
# Objective Allow TextureAtlasBuilder in AssetLoader. Fixes #2987 ## Solution - TextureAtlasBuilder no longer hold just AssetIds that are used to retrieve the actual image data in `finish`, but &Image instead. - TextureAtlasBuilder now required AssetId only optionally (and it is only used to retrieve the index from the AssetId in TextureAtlasLayout), ## Issues - The issue mentioned here https://github.com/bevyengine/bevy/pull/11474#issuecomment-1904676937 now also extends to the actual atlas texture. In short: Calling add_texture multiple times for the same texture will lead to duplicate image data in the atlas texture and additional indices. If you provide an AssetId we can probably do something to de-duplicate the entries while keeping insertion order (suggestions welcome on how exactly). But if you don't then we are out of luck (unless we can and want to hash the image, which I do not think we want). --- ## Changelog ### Changed - TextureAtlasBuilder `add_texture` can be called without providing an AssetId - TextureAtlasBuilder `finish` no longer takes Assets<Image> and no longer returns a Handle<Image> ## Migration Guide - For `add_texture` you need to wrap your AssetId in Some - `finish` now returns the atlas texture image directly instead of a handle. Provide the atlas texture to `add` on Assets<Texture> to get a Handle<Image>
This commit is contained in:
parent
b35b9e5005
commit
3851679173
@ -1,5 +1,4 @@
|
||||
use bevy_asset::Handle;
|
||||
use bevy_asset::{AssetId, Assets};
|
||||
use bevy_asset::AssetId;
|
||||
use bevy_log::{debug, error, warn};
|
||||
use bevy_math::{Rect, UVec2, Vec2};
|
||||
use bevy_render::{
|
||||
@ -28,9 +27,9 @@ pub enum TextureAtlasBuilderError {
|
||||
#[must_use]
|
||||
/// A builder which is used to create a texture atlas from many individual
|
||||
/// sprites.
|
||||
pub struct TextureAtlasBuilder {
|
||||
/// Collection of textures and their size to be packed into an atlas
|
||||
textures_to_place: Vec<(AssetId<Image>, Extent3d)>,
|
||||
pub struct TextureAtlasBuilder<'a> {
|
||||
/// Collection of texture's asset id (optional) and image data to be packed into an atlas
|
||||
textures_to_place: Vec<(Option<AssetId<Image>>, &'a Image)>,
|
||||
/// The initial atlas size in pixels.
|
||||
initial_size: Vec2,
|
||||
/// The absolute maximum size of the texture atlas in pixels.
|
||||
@ -43,7 +42,7 @@ pub struct TextureAtlasBuilder {
|
||||
padding: UVec2,
|
||||
}
|
||||
|
||||
impl Default for TextureAtlasBuilder {
|
||||
impl Default for TextureAtlasBuilder<'_> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
textures_to_place: Vec::new(),
|
||||
@ -58,7 +57,7 @@ impl Default for TextureAtlasBuilder {
|
||||
|
||||
pub type TextureAtlasBuilderResult<T> = Result<T, TextureAtlasBuilderError>;
|
||||
|
||||
impl TextureAtlasBuilder {
|
||||
impl<'a> TextureAtlasBuilder<'a> {
|
||||
/// Sets the initial size of the atlas in pixels.
|
||||
pub fn initial_size(mut self, size: Vec2) -> Self {
|
||||
self.initial_size = size;
|
||||
@ -85,10 +84,10 @@ impl TextureAtlasBuilder {
|
||||
|
||||
/// Adds a texture to be copied to the texture atlas.
|
||||
///
|
||||
/// Optionally an asset id can be passed that can later be used with the texture layout to retrieve the index of this texture.
|
||||
/// The insertion order will reflect the index of the added texture in the finished texture atlas.
|
||||
pub fn add_texture(&mut self, image_id: AssetId<Image>, texture: &Image) {
|
||||
self.textures_to_place
|
||||
.push((image_id, texture.texture_descriptor.size));
|
||||
pub fn add_texture(&mut self, image_id: Option<AssetId<Image>>, texture: &'a Image) {
|
||||
self.textures_to_place.push((image_id, texture));
|
||||
}
|
||||
|
||||
/// Sets the amount of padding in pixels to add between the textures in the texture atlas.
|
||||
@ -149,14 +148,12 @@ impl TextureAtlasBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
/// Consumes the builder, and returns the newly created texture handle and
|
||||
/// the assciated atlas layout.
|
||||
/// Consumes the builder, and returns the newly created texture atlas and
|
||||
/// the associated atlas layout.
|
||||
///
|
||||
/// Assigns indices to the textures based on the insertion order.
|
||||
/// Internally it copies all rectangles from the textures and copies them
|
||||
/// into a new texture.
|
||||
/// It is not useful to hold a strong handle to the texture afterwards else
|
||||
/// it will exist twice in memory.
|
||||
///
|
||||
/// # Usage
|
||||
///
|
||||
@ -172,7 +169,8 @@ impl TextureAtlasBuilder {
|
||||
/// // Customize it
|
||||
/// // ...
|
||||
/// // Build your texture and the atlas layout
|
||||
/// let (atlas_layout, texture) = builder.finish(&mut textures).unwrap();
|
||||
/// let (atlas_layout, texture) = builder.finish().unwrap();
|
||||
/// let texture = textures.add(texture);
|
||||
/// let layout = layouts.add(atlas_layout);
|
||||
/// // Spawn your sprite
|
||||
/// commands.spawn(SpriteSheetBundle {
|
||||
@ -190,10 +188,7 @@ impl TextureAtlasBuilder {
|
||||
///
|
||||
/// If there is not enough space in the atlas texture, an error will
|
||||
/// be returned. It is then recommended to make a larger sprite sheet.
|
||||
pub fn finish(
|
||||
self,
|
||||
textures: &mut Assets<Image>,
|
||||
) -> Result<(TextureAtlasLayout, Handle<Image>), TextureAtlasBuilderError> {
|
||||
pub fn finish(self) -> Result<(TextureAtlasLayout, Image), TextureAtlasBuilderError> {
|
||||
let initial_width = self.initial_size.x as u32;
|
||||
let initial_height = self.initial_size.y as u32;
|
||||
let max_width = self.max_size.x as u32;
|
||||
@ -203,14 +198,18 @@ impl TextureAtlasBuilder {
|
||||
let mut current_height = initial_height;
|
||||
let mut rect_placements = None;
|
||||
let mut atlas_texture = Image::default();
|
||||
let mut rects_to_place = GroupedRectsToPlace::<AssetId<Image>>::new();
|
||||
let mut rects_to_place = GroupedRectsToPlace::<usize>::new();
|
||||
|
||||
// Adds textures to rectangle group packer
|
||||
for (image_id, size) in &self.textures_to_place {
|
||||
for (index, (_, texture)) in self.textures_to_place.iter().enumerate() {
|
||||
rects_to_place.push_rect(
|
||||
*image_id,
|
||||
index,
|
||||
None,
|
||||
RectToInsert::new(size.width + self.padding.x, size.height + self.padding.y, 1),
|
||||
RectToInsert::new(
|
||||
texture.width() + self.padding.x,
|
||||
texture.height() + self.padding.y,
|
||||
1,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@ -263,17 +262,18 @@ impl TextureAtlasBuilder {
|
||||
let mut texture_rects = Vec::with_capacity(rect_placements.packed_locations().len());
|
||||
let mut texture_ids = HashMap::default();
|
||||
// We iterate through the textures to place to respect the insertion order for the texture indices
|
||||
for (image_id, _) in &self.textures_to_place {
|
||||
let (_, packed_location) = rect_placements.packed_locations().get(image_id).unwrap();
|
||||
for (index, (image_id, texture)) in self.textures_to_place.iter().enumerate() {
|
||||
let (_, packed_location) = rect_placements.packed_locations().get(&index).unwrap();
|
||||
|
||||
let texture = textures.get(*image_id).unwrap();
|
||||
let min = Vec2::new(packed_location.x() as f32, packed_location.y() as f32);
|
||||
let max = min
|
||||
+ Vec2::new(
|
||||
(packed_location.width() - self.padding.x) as f32,
|
||||
(packed_location.height() - self.padding.y) as f32,
|
||||
);
|
||||
texture_ids.insert(*image_id, texture_rects.len());
|
||||
if let Some(image_id) = image_id {
|
||||
texture_ids.insert(*image_id, index);
|
||||
}
|
||||
texture_rects.push(Rect { min, max });
|
||||
if texture.texture_descriptor.format != self.format && !self.auto_format_conversion {
|
||||
warn!(
|
||||
@ -291,7 +291,7 @@ impl TextureAtlasBuilder {
|
||||
textures: texture_rects,
|
||||
texture_handles: Some(texture_ids),
|
||||
},
|
||||
textures.add(atlas_texture),
|
||||
atlas_texture,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@ -220,10 +220,11 @@ fn create_texture_atlas(
|
||||
continue;
|
||||
};
|
||||
|
||||
texture_atlas_builder.add_texture(id, texture);
|
||||
texture_atlas_builder.add_texture(Some(id), texture);
|
||||
}
|
||||
|
||||
let (texture_atlas, texture) = texture_atlas_builder.finish(textures).unwrap();
|
||||
let (texture_atlas, texture) = texture_atlas_builder.finish().unwrap();
|
||||
let texture = textures.add(texture);
|
||||
|
||||
// Update the sampling settings of the texture atlas
|
||||
let image = textures.get_mut(&texture).unwrap();
|
||||
|
||||
Loading…
Reference in New Issue
Block a user