From 135c7240f1c00dba941dad4282dee9080dae56df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Lescaudey=20de=20Maneville?= Date: Tue, 16 Jan 2024 14:59:08 +0100 Subject: [PATCH] Texture Atlas rework (#5103) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Objective > Old MR: #5072 > ~~Associated UI MR: #5070~~ > Adresses #1618 Unify sprite management ## Solution - Remove the `Handle` 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` 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` like the `SpriteBundle` - Has a new `TextureAtlas` component instead of a `Handle` - (**BREAKING**) `DynamicTextureAtlasBuilder::add_texture` takes an additional `&Handle` parameter - (**BREAKING**) `TextureAtlasLayout::from_grid` no longer takes a `Handle` parameter - (**BREAKING**) `TextureAtlasBuilder::finish` now returns a `Result<(TextureAtlasLayout, Handle), _>` - `bevy_text`: - `GlyphAtlasInfo` stores the texture `Handle` - `FontAtlas` stores the texture `Handle` - `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>, - mut atlases: ResMut>, + mut atlases: ResMut>, asset_server: Res ) { 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>, - mut atlases: ResMut>, + mut atlases: ResMut>, asset_server: Res ) { 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 Co-authored-by: François Co-authored-by: IceSentry --- crates/bevy_sprite/src/bundle.rs | 24 +-- .../src/dynamic_texture_atlas_builder.rs | 28 +-- crates/bevy_sprite/src/lib.rs | 46 ++--- crates/bevy_sprite/src/render/mod.rs | 58 +------ crates/bevy_sprite/src/texture_atlas.rs | 145 ++++++++-------- .../bevy_sprite/src/texture_atlas_builder.rs | 57 ++++-- crates/bevy_text/src/font_atlas.rs | 24 +-- crates/bevy_text/src/font_atlas_set.rs | 18 +- crates/bevy_text/src/glyph_brush.rs | 4 +- crates/bevy_text/src/pipeline.rs | 4 +- crates/bevy_text/src/text2d.rs | 8 +- crates/bevy_ui/src/lib.rs | 16 +- crates/bevy_ui/src/node_bundles.rs | 10 +- crates/bevy_ui/src/render/mod.rs | 164 +++++------------- crates/bevy_ui/src/ui_node.rs | 12 -- crates/bevy_ui/src/widget/image.rs | 59 ++----- crates/bevy_ui/src/widget/text.rs | 6 +- examples/2d/sprite_sheet.rs | 28 ++- examples/2d/texture_atlas.rs | 62 ++++--- .../stress_tests/many_animated_sprites.rs | 33 ++-- examples/ui/font_atlas_debug.rs | 7 +- examples/ui/ui_texture_atlas.rs | 11 +- 22 files changed, 354 insertions(+), 470 deletions(-) diff --git a/crates/bevy_sprite/src/bundle.rs b/crates/bevy_sprite/src/bundle.rs index 16aa7f2b58cd3..efe3337070299 100644 --- a/crates/bevy_sprite/src/bundle.rs +++ b/crates/bevy_sprite/src/bundle.rs @@ -1,7 +1,4 @@ -use crate::{ - texture_atlas::{TextureAtlas, TextureAtlasSprite}, - ImageScaleMode, Sprite, -}; +use crate::{texture_atlas::TextureAtlas, ImageScaleMode, Sprite}; use bevy_asset::Handle; use bevy_ecs::bundle::Bundle; use bevy_render::{ @@ -32,18 +29,25 @@ pub struct SpriteBundle { } /// A [`Bundle`] of components for drawing a single sprite from a sprite sheet (also referred -/// to as a `TextureAtlas`). +/// to as a `TextureAtlas`) or for animated sprites. +/// +/// Note: +/// This bundle is identical to [`SpriteBundle`] with an additional [`TextureAtlas`] component. +/// +/// Check the following examples for usage: +/// - [`animated sprite sheet example`](https://github.com/bevyengine/bevy/blob/latest/examples/2d/sprite_sheet.rs) +/// - [`texture atlas example`](https://github.com/bevyengine/bevy/blob/latest/examples/2d/texture_atlas.rs) #[derive(Bundle, Clone, Default)] pub struct SpriteSheetBundle { - /// The specific sprite from the texture atlas to be drawn, defaulting to the sprite at index 0. - pub sprite: TextureAtlasSprite, + pub sprite: Sprite, /// Controls how the image is altered when scaled. pub scale_mode: ImageScaleMode, - /// A handle to the texture atlas that holds the sprite images - pub texture_atlas: Handle, - /// Data pertaining to how the sprite is drawn on the screen pub transform: Transform, pub global_transform: GlobalTransform, + /// The sprite sheet base texture + pub texture: Handle, + /// The sprite sheet texture atlas, allowing to draw a custom section of `texture`. + pub atlas: TextureAtlas, /// User indication of whether an entity is visible pub visibility: Visibility, pub inherited_visibility: InheritedVisibility, diff --git a/crates/bevy_sprite/src/dynamic_texture_atlas_builder.rs b/crates/bevy_sprite/src/dynamic_texture_atlas_builder.rs index 28db250887205..e1af48f930432 100644 --- a/crates/bevy_sprite/src/dynamic_texture_atlas_builder.rs +++ b/crates/bevy_sprite/src/dynamic_texture_atlas_builder.rs @@ -1,5 +1,5 @@ -use crate::TextureAtlas; -use bevy_asset::Assets; +use crate::TextureAtlasLayout; +use bevy_asset::{Assets, Handle}; use bevy_math::{IVec2, Rect, Vec2}; use bevy_render::{ render_asset::RenderAssetPersistencePolicy, @@ -7,10 +7,10 @@ use bevy_render::{ }; use guillotiere::{size2, Allocation, AtlasAllocator}; -/// Helper utility to update [`TextureAtlas`] on the fly. +/// Helper utility to update [`TextureAtlasLayout`] on the fly. /// /// Helpful in cases when texture is created procedurally, -/// e.g: in a font glyph [`TextureAtlas`], only add the [`Image`] texture for letters to be rendered. +/// 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, @@ -30,22 +30,30 @@ impl DynamicTextureAtlasBuilder { } } - /// Add a new texture to [`TextureAtlas`]. - /// It is user's responsibility to pass in the correct [`TextureAtlas`], - /// and that [`TextureAtlas::texture`] has [`Image::cpu_persistent_access`] + /// 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, - texture_atlas: &mut TextureAtlas, + atlas_layout: &mut TextureAtlasLayout, textures: &mut Assets, texture: &Image, + atlas_texture_handle: &Handle, ) -> Option { 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(&texture_atlas.texture).unwrap(); + let atlas_texture = textures.get_mut(atlas_texture_handle).unwrap(); assert_eq!( atlas_texture.cpu_persistent_access, RenderAssetPersistencePolicy::Keep @@ -54,7 +62,7 @@ impl DynamicTextureAtlasBuilder { self.place_texture(atlas_texture, allocation, texture); let mut rect: Rect = to_rect(allocation.rectangle); rect.max -= self.padding as f32; - Some(texture_atlas.add_texture(rect)) + Some(atlas_layout.add_texture(rect)) } else { None } diff --git a/crates/bevy_sprite/src/lib.rs b/crates/bevy_sprite/src/lib.rs index babef34c3f85d..3ca92858c314c 100644 --- a/crates/bevy_sprite/src/lib.rs +++ b/crates/bevy_sprite/src/lib.rs @@ -15,7 +15,7 @@ pub mod prelude { pub use crate::{ bundle::{SpriteBundle, SpriteSheetBundle}, sprite::{ImageScaleMode, Sprite}, - texture_atlas::{TextureAtlas, TextureAtlasSprite}, + texture_atlas::{TextureAtlas, TextureAtlasLayout}, texture_slice::{BorderRect, SliceScaleMode, TextureSlicer}, ColorMaterial, ColorMesh2dBundle, TextureAtlasBuilder, }; @@ -65,13 +65,13 @@ impl Plugin for SpritePlugin { "render/sprite.wgsl", Shader::from_wgsl ); - app.init_asset::() - .register_asset_reflect::() + app.init_asset::() + .register_asset_reflect::() .register_type::() .register_type::() .register_type::() - .register_type::() .register_type::() + .register_type::() .register_type::() .add_plugins((Mesh2dRenderPlugin, ColorMaterialPlugin)) .add_systems( @@ -131,19 +131,15 @@ pub fn calculate_bounds_2d( mut commands: Commands, meshes: Res>, images: Res>, - atlases: Res>, + atlases: Res>, meshes_without_aabb: Query<(Entity, &Mesh2dHandle), (Without, Without)>, sprites_to_recalculate_aabb: Query< - (Entity, &Sprite, &Handle), + (Entity, &Sprite, &Handle, Option<&TextureAtlas>), ( Or<(Without, Changed)>, Without, ), >, - atlases_without_aabb: Query< - (Entity, &TextureAtlasSprite, &Handle), - (Without, Without), - >, ) { for (entity, mesh_handle) in &meshes_without_aabb { if let Some(mesh) = meshes.get(&mesh_handle.0) { @@ -152,27 +148,15 @@ pub fn calculate_bounds_2d( } } } - for (entity, sprite, texture_handle) in &sprites_to_recalculate_aabb { - if let Some(size) = sprite - .custom_size - .or_else(|| images.get(texture_handle).map(|image| image.size_f32())) - { - let aabb = Aabb { - center: (-sprite.anchor.as_vec() * size).extend(0.0).into(), - half_extents: (0.5 * size).extend(0.0).into(), - }; - commands.entity(entity).try_insert(aabb); - } - } - for (entity, atlas_sprite, atlas_handle) in &atlases_without_aabb { - if let Some(size) = atlas_sprite.custom_size.or_else(|| { - atlases - .get(atlas_handle) - .and_then(|atlas| atlas.textures.get(atlas_sprite.index)) - .map(|rect| (rect.min - rect.max).abs()) + for (entity, sprite, texture_handle, atlas) in &sprites_to_recalculate_aabb { + if let Some(size) = sprite.custom_size.or_else(|| match atlas { + // We default to the texture size for regular sprites + None => images.get(texture_handle).map(|image| image.size_f32()), + // We default to the drawn rect for atlas sprites + Some(atlas) => atlas.texture_rect(&atlases).map(|rect| rect.size()), }) { let aabb = Aabb { - center: (-atlas_sprite.anchor.as_vec() * size).extend(0.0).into(), + center: (-sprite.anchor.as_vec() * size).extend(0.0).into(), half_extents: (0.5 * size).extend(0.0).into(), }; commands.entity(entity).try_insert(aabb); @@ -199,7 +183,7 @@ mod test { app.insert_resource(image_assets); let mesh_assets = Assets::::default(); app.insert_resource(mesh_assets); - let texture_atlas_assets = Assets::::default(); + let texture_atlas_assets = Assets::::default(); app.insert_resource(texture_atlas_assets); // Add system @@ -237,7 +221,7 @@ mod test { app.insert_resource(image_assets); let mesh_assets = Assets::::default(); app.insert_resource(mesh_assets); - let texture_atlas_assets = Assets::::default(); + let texture_atlas_assets = Assets::::default(); app.insert_resource(texture_atlas_assets); // Add system diff --git a/crates/bevy_sprite/src/render/mod.rs b/crates/bevy_sprite/src/render/mod.rs index a5d89ce0c82f5..60c96584146e4 100644 --- a/crates/bevy_sprite/src/render/mod.rs +++ b/crates/bevy_sprite/src/render/mod.rs @@ -1,7 +1,7 @@ use std::ops::Range; use crate::{ - texture_atlas::{TextureAtlas, TextureAtlasSprite}, + texture_atlas::{TextureAtlas, TextureAtlasLayout}, ComputedTextureSlices, Sprite, SPRITE_SHADER_HANDLE, }; use bevy_asset::{AssetEvent, AssetId, Assets, Handle}; @@ -335,7 +335,7 @@ pub fn extract_sprite_events( pub fn extract_sprites( mut commands: Commands, mut extracted_sprites: ResMut, - texture_atlases: Extract>>, + texture_atlases: Extract>>, sprite_query: Extract< Query<( Entity, @@ -343,25 +343,17 @@ pub fn extract_sprites( &Sprite, &GlobalTransform, &Handle, + Option<&TextureAtlas>, Option<&ComputedTextureSlices>, )>, >, - atlas_query: Extract< - Query<( - Entity, - &ViewVisibility, - &TextureAtlasSprite, - &GlobalTransform, - &Handle, - )>, - >, ) { extracted_sprites.sprites.clear(); - - for (entity, view_visibility, sprite, transform, handle, slices) in sprite_query.iter() { + for (entity, view_visibility, sprite, transform, handle, sheet, slices) in sprite_query.iter() { if !view_visibility.get() { continue; } + if let Some(slices) = slices { extracted_sprites.sprites.extend( slices @@ -369,13 +361,14 @@ pub fn extract_sprites( .map(|e| (commands.spawn_empty().id(), e)), ); } else { + let rect = sheet.and_then(|s| s.texture_rect(&texture_atlases)); // PERF: we don't check in this function that the `Image` asset is ready, since it should be in most cases and hashing the handle is expensive extracted_sprites.sprites.insert( entity, ExtractedSprite { color: sprite.color, transform: *transform, - rect: sprite.rect, + rect, // Pass the custom size custom_size: sprite.custom_size, flip_x: sprite.flip_x, @@ -387,43 +380,6 @@ pub fn extract_sprites( ); } } - for (entity, view_visibility, atlas_sprite, transform, texture_atlas_handle) in - atlas_query.iter() - { - if !view_visibility.get() { - continue; - } - if let Some(texture_atlas) = texture_atlases.get(texture_atlas_handle) { - let rect = Some( - *texture_atlas - .textures - .get(atlas_sprite.index) - .unwrap_or_else(|| { - panic!( - "Sprite index {:?} does not exist for texture atlas handle {:?}.", - atlas_sprite.index, - texture_atlas_handle.id(), - ) - }), - ); - extracted_sprites.sprites.insert( - entity, - ExtractedSprite { - color: atlas_sprite.color, - transform: *transform, - // Select the area in the texture atlas - rect, - // Pass the custom size - custom_size: atlas_sprite.custom_size, - flip_x: atlas_sprite.flip_x, - flip_y: atlas_sprite.flip_y, - image_handle_id: texture_atlas.texture.id(), - anchor: atlas_sprite.anchor.as_vec(), - original_entity: None, - }, - ); - } - } } #[repr(C)] diff --git a/crates/bevy_sprite/src/texture_atlas.rs b/crates/bevy_sprite/src/texture_atlas.rs index fa1cf53aeb22c..2feed94da0f4f 100644 --- a/crates/bevy_sprite/src/texture_atlas.rs +++ b/crates/bevy_sprite/src/texture_atlas.rs @@ -1,97 +1,84 @@ -use crate::Anchor; -use bevy_asset::{Asset, AssetId, Handle}; -use bevy_ecs::{component::Component, reflect::ReflectComponent}; +use bevy_asset::{Asset, AssetId, Assets, Handle}; +use bevy_ecs::component::Component; use bevy_math::{Rect, Vec2}; use bevy_reflect::Reflect; -use bevy_render::{color::Color, texture::Image}; +use bevy_render::texture::Image; use bevy_utils::HashMap; -/// An atlas containing multiple textures (like a spritesheet or a tilemap). +/// Stores a map used to lookup the position of a texture in a [`TextureAtlas`]. +/// 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. +/// +/// Optionaly it can store a mapping from sub texture handles to the related area index (see +/// [`TextureAtlasBuilder`]). +/// /// [Example usage animating sprite.](https://github.com/bevyengine/bevy/blob/latest/examples/2d/sprite_sheet.rs) /// [Example usage loading sprite sheet.](https://github.com/bevyengine/bevy/blob/latest/examples/2d/texture_atlas.rs) +/// +/// [`TextureAtlasBuilder`]: crate::TextureAtlasBuilder #[derive(Asset, Reflect, Debug, Clone)] #[reflect(Debug)] -pub struct TextureAtlas { - /// The handle to the texture in which the sprites are stored - pub texture: Handle, +pub struct TextureAtlasLayout { // TODO: add support to Uniforms derive to write dimensions and sprites to the same buffer pub size: Vec2, /// The specific areas of the atlas where each texture can be found pub textures: Vec, - /// Mapping from texture handle to index + /// Maps from a specific image handle to the index in `textures` where they can be found. + /// + /// This field is set by [`TextureAtlasBuilder`]. + /// + /// [`TextureAtlasBuilder`]: crate::TextureAtlasBuilder pub(crate) texture_handles: Option, usize>>, } -/// Specifies the rendering properties of a sprite from a sprite sheet. +/// Component used to draw a specific section of a texture. +/// +/// It stores a handle to [`TextureAtlasLayout`] and the index of the current section of the atlas. +/// The texture atlas contains various *sections* of a given texture, allowing users to have a single +/// image file for either sprite animation or global mapping. +/// You can change the texture [`index`](Self::index) of the atlas to animate the sprite or dsplay only a *section* of the texture +/// for efficient rendering of related game objects. /// -/// This is commonly used as a component within [`SpriteSheetBundle`](crate::bundle::SpriteSheetBundle). -#[derive(Component, Debug, Clone, Reflect)] -#[reflect(Component)] -pub struct TextureAtlasSprite { - /// The tint color used to draw the sprite, defaulting to [`Color::WHITE`] - pub color: Color, - /// Texture index in [`TextureAtlas`] +/// Check the following examples for usage: +/// - [`animated sprite sheet example`](https://github.com/bevyengine/bevy/blob/latest/examples/2d/sprite_sheet.rs) +/// - [`texture atlas example`](https://github.com/bevyengine/bevy/blob/latest/examples/2d/texture_atlas.rs) +#[derive(Component, Default, Debug, Clone, Reflect)] +pub struct TextureAtlas { + /// Texture atlas handle + pub layout: Handle, + /// Texture atlas section index pub index: usize, - /// Whether to flip the sprite in the X axis - pub flip_x: bool, - /// Whether to flip the sprite in the Y axis - pub flip_y: bool, - /// An optional custom size for the sprite that will be used when rendering, instead of the size - /// of the sprite's image in the atlas - pub custom_size: Option, - /// [`Anchor`] point of the sprite in the world - pub anchor: Anchor, } -impl Default for TextureAtlasSprite { - fn default() -> Self { +impl TextureAtlasLayout { + /// Create a new empty layout with custom `dimensions` + pub fn new_empty(dimensions: Vec2) -> Self { Self { - index: 0, - color: Color::WHITE, - flip_x: false, - flip_y: false, - custom_size: None, - anchor: Anchor::default(), - } - } -} - -impl TextureAtlasSprite { - /// Create a new [`TextureAtlasSprite`] with a sprite index, - /// it should be valid in the corresponding [`TextureAtlas`] - pub fn new(index: usize) -> TextureAtlasSprite { - Self { - index, - ..Default::default() - } - } -} - -impl TextureAtlas { - /// Create a new [`TextureAtlas`] that has a texture, but does not have - /// any individual sprites specified - pub fn new_empty(texture: Handle, dimensions: Vec2) -> Self { - Self { - texture, size: dimensions, texture_handles: None, textures: Vec::new(), } } - /// Generate a [`TextureAtlas`] by splitting a texture into a grid where each - /// `tile_size` by `tile_size` grid-cell is one of the textures in the + /// Generate a [`TextureAtlasLayout`] as a grid where each + /// `tile_size` by `tile_size` grid-cell is one of the *section* in the /// atlas. Grid cells are separated by some `padding`, and the grid starts - /// at `offset` pixels from the top left corner. The resulting [`TextureAtlas`] is + /// at `offset` pixels from the top left corner. Resulting layout is /// indexed left to right, top to bottom. + /// + /// # Arguments + /// + /// * `tile_size` - Each layout grid cell size + /// * `columns` - Grid column count + /// * `rows` - Grid row count + /// * `padding` - Optional padding between cells + /// * `offset` - Optional global grid offset pub fn from_grid( - texture: Handle, tile_size: Vec2, columns: usize, rows: usize, padding: Option, offset: Option, - ) -> TextureAtlas { + ) -> Self { let padding = padding.unwrap_or_default(); let offset = offset.unwrap_or_default(); let mut sprites = Vec::new(); @@ -119,37 +106,40 @@ impl TextureAtlas { let grid_size = Vec2::new(columns as f32, rows as f32); - TextureAtlas { + Self { size: ((tile_size + current_padding) * grid_size) - current_padding, textures: sprites, - texture, texture_handles: None, } } - /// Add a sprite to the list of textures in the [`TextureAtlas`] - /// returns an index to the texture which can be used with [`TextureAtlasSprite`] + /// Add a *section* to the list in the layout and returns its index + /// which can be used with [`TextureAtlas`] /// /// # Arguments /// - /// * `rect` - The section of the atlas that contains the texture to be added, - /// from the top-left corner of the texture to the bottom-right corner + /// * `rect` - The section of the texture to be added + /// + /// [`TextureAtlas`]: crate::TextureAtlas pub fn add_texture(&mut self, rect: Rect) -> usize { self.textures.push(rect); self.textures.len() - 1 } - /// The number of textures in the [`TextureAtlas`] + /// The number of textures in the [`TextureAtlasLayout`] pub fn len(&self) -> usize { self.textures.len() } - /// Returns `true` if there are no textures in the [`TextureAtlas`] pub fn is_empty(&self) -> bool { self.textures.is_empty() } - /// Returns the index of the texture corresponding to the given image handle in the [`TextureAtlas`] + /// Retrieves the texture *section* index of the given `texture` handle. + /// + /// This requires the layout to have been built using a [`TextureAtlasBuilder`] + /// + /// [`TextureAtlasBuilder`]: crate::TextureAtlasBuilder pub fn get_texture_index(&self, texture: impl Into>) -> Option { let id = texture.into(); self.texture_handles @@ -157,3 +147,20 @@ impl TextureAtlas { .and_then(|texture_handles| texture_handles.get(&id).cloned()) } } + +impl TextureAtlas { + /// Retrieves the current texture [`Rect`] of the sprite sheet according to the section `index` + pub fn texture_rect(&self, texture_atlases: &Assets) -> Option { + let atlas = texture_atlases.get(&self.layout)?; + atlas.textures.get(self.index).copied() + } +} + +impl From> for TextureAtlas { + fn from(texture_atlas: Handle) -> Self { + Self { + layout: texture_atlas, + index: 0, + } + } +} diff --git a/crates/bevy_sprite/src/texture_atlas_builder.rs b/crates/bevy_sprite/src/texture_atlas_builder.rs index 3fd655642ac97..3a8eae428ce0c 100644 --- a/crates/bevy_sprite/src/texture_atlas_builder.rs +++ b/crates/bevy_sprite/src/texture_atlas_builder.rs @@ -1,3 +1,4 @@ +use bevy_asset::Handle; use bevy_asset::{AssetId, Assets}; use bevy_log::{debug, error, warn}; use bevy_math::{Rect, UVec2, Vec2}; @@ -13,7 +14,7 @@ use rectangle_pack::{ }; use thiserror::Error; -use crate::texture_atlas::TextureAtlas; +use crate::TextureAtlasLayout; #[derive(Debug, Error)] pub enum TextureAtlasBuilderError { @@ -146,12 +147,41 @@ impl TextureAtlasBuilder { } } - /// Consumes the builder and returns a result with a new texture atlas. + /// Consumes the builder, and returns the newly created texture handle and + /// the assciated atlas layout. /// /// Internally it copies all rectangles from the textures and copies them - /// into a new texture which the texture atlas will use. It is not useful to - /// hold a strong handle to the texture afterwards else it will exist twice - /// in memory. + /// 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 + /// + /// ```rust + /// # use bevy_sprite::prelude::*; + /// # use bevy_ecs::prelude::*; + /// # use bevy_asset::*; + /// # use bevy_render::prelude::*; + /// + /// fn my_system(mut commands: Commands, mut textures: ResMut>, mut layouts: ResMut>) { + /// // Declare your builder + /// let mut builder = TextureAtlasBuilder::default(); + /// // Customize it + /// // ... + /// // Build your texture and the atlas layout + /// let (atlas_layout, texture) = builder.finish(&mut textures).unwrap(); + /// let layout = layouts.add(atlas_layout); + /// // Spawn your sprite + /// commands.spawn(SpriteSheetBundle { + /// texture, + /// atlas: TextureAtlas { + /// layout, + /// index: 0 + /// }, + /// ..Default::default() + /// }); + /// } + /// ``` /// /// # Errors /// @@ -160,7 +190,7 @@ impl TextureAtlasBuilder { pub fn finish( self, textures: &mut Assets, - ) -> Result { + ) -> Result<(TextureAtlasLayout, Handle), 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; @@ -248,11 +278,14 @@ impl TextureAtlasBuilder { } self.copy_converted_texture(&mut atlas_texture, texture, packed_location); } - Ok(TextureAtlas { - size: atlas_texture.size_f32(), - texture: textures.add(atlas_texture), - textures: texture_rects, - texture_handles: Some(texture_ids), - }) + + Ok(( + TextureAtlasLayout { + size: atlas_texture.size_f32(), + textures: texture_rects, + texture_handles: Some(texture_ids), + }, + textures.add(atlas_texture), + )) } } diff --git a/crates/bevy_text/src/font_atlas.rs b/crates/bevy_text/src/font_atlas.rs index e46bf3cd53615..78d81dfd322a4 100644 --- a/crates/bevy_text/src/font_atlas.rs +++ b/crates/bevy_text/src/font_atlas.rs @@ -6,7 +6,7 @@ use bevy_render::{ render_resource::{Extent3d, TextureDimension, TextureFormat}, texture::Image, }; -use bevy_sprite::{DynamicTextureAtlasBuilder, TextureAtlas}; +use bevy_sprite::{DynamicTextureAtlasBuilder, TextureAtlasLayout}; use bevy_utils::HashMap; #[cfg(feature = "subpixel_glyph_atlas")] @@ -43,16 +43,17 @@ impl From for SubpixelOffset { pub struct FontAtlas { pub dynamic_texture_atlas_builder: DynamicTextureAtlasBuilder, pub glyph_to_atlas_index: HashMap<(GlyphId, SubpixelOffset), usize>, - pub texture_atlas: Handle, + pub texture_atlas: Handle, + pub texture: Handle, } impl FontAtlas { pub fn new( textures: &mut Assets, - texture_atlases: &mut Assets, + texture_atlases: &mut Assets, size: Vec2, ) -> FontAtlas { - let atlas_texture = textures.add(Image::new_fill( + let texture = textures.add(Image::new_fill( Extent3d { width: size.x as u32, height: size.y as u32, @@ -64,11 +65,12 @@ impl FontAtlas { // Need to keep this image CPU persistent in order to add additional glyphs later on RenderAssetPersistencePolicy::Keep, )); - let texture_atlas = TextureAtlas::new_empty(atlas_texture, size); + let texture_atlas = TextureAtlasLayout::new_empty(size); Self { texture_atlas: texture_atlases.add(texture_atlas), glyph_to_atlas_index: HashMap::default(), dynamic_texture_atlas_builder: DynamicTextureAtlasBuilder::new(size, 0), + texture, } } @@ -90,16 +92,18 @@ impl FontAtlas { pub fn add_glyph( &mut self, textures: &mut Assets, - texture_atlases: &mut Assets, + texture_atlases: &mut Assets, glyph_id: GlyphId, subpixel_offset: SubpixelOffset, texture: &Image, ) -> bool { let texture_atlas = texture_atlases.get_mut(&self.texture_atlas).unwrap(); - if let Some(index) = - self.dynamic_texture_atlas_builder - .add_texture(texture_atlas, textures, texture) - { + if let Some(index) = self.dynamic_texture_atlas_builder.add_texture( + texture_atlas, + textures, + texture, + &self.texture, + ) { self.glyph_to_atlas_index .insert((glyph_id, subpixel_offset), index); true diff --git a/crates/bevy_text/src/font_atlas_set.rs b/crates/bevy_text/src/font_atlas_set.rs index 451db26bd50a1..bef50ac5ad0a7 100644 --- a/crates/bevy_text/src/font_atlas_set.rs +++ b/crates/bevy_text/src/font_atlas_set.rs @@ -6,7 +6,7 @@ use bevy_ecs::prelude::*; use bevy_math::Vec2; use bevy_reflect::Reflect; use bevy_render::texture::Image; -use bevy_sprite::TextureAtlas; +use bevy_sprite::TextureAtlasLayout; use bevy_utils::FloatOrd; use bevy_utils::HashMap; @@ -43,7 +43,8 @@ pub struct FontAtlasSet { #[derive(Debug, Clone, Reflect)] pub struct GlyphAtlasInfo { - pub texture_atlas: Handle, + pub texture_atlas: Handle, + pub texture: Handle, pub glyph_index: usize, } @@ -72,7 +73,7 @@ impl FontAtlasSet { pub fn add_glyph_to_atlas( &mut self, - texture_atlases: &mut Assets, + texture_atlases: &mut Assets, textures: &mut Assets, outlined_glyph: OutlinedGlyph, ) -> Result { @@ -145,10 +146,17 @@ impl FontAtlasSet { .find_map(|atlas| { atlas .get_glyph_index(glyph_id, position.into()) - .map(|glyph_index| (glyph_index, atlas.texture_atlas.clone_weak())) + .map(|glyph_index| { + ( + glyph_index, + atlas.texture_atlas.clone_weak(), + atlas.texture.clone_weak(), + ) + }) }) - .map(|(glyph_index, texture_atlas)| GlyphAtlasInfo { + .map(|(glyph_index, texture_atlas, texture)| GlyphAtlasInfo { texture_atlas, + texture, glyph_index, }) }) diff --git a/crates/bevy_text/src/glyph_brush.rs b/crates/bevy_text/src/glyph_brush.rs index b1765c0f038d0..f49211d610838 100644 --- a/crates/bevy_text/src/glyph_brush.rs +++ b/crates/bevy_text/src/glyph_brush.rs @@ -3,7 +3,7 @@ use bevy_asset::{AssetId, Assets}; use bevy_math::{Rect, Vec2}; use bevy_reflect::Reflect; use bevy_render::texture::Image; -use bevy_sprite::TextureAtlas; +use bevy_sprite::TextureAtlasLayout; use bevy_utils::tracing::warn; use glyph_brush_layout::{ BuiltInLineBreaker, FontId, GlyphPositioner, Layout, SectionGeometry, SectionGlyph, @@ -60,7 +60,7 @@ impl GlyphBrush { sections: &[SectionText], font_atlas_sets: &mut FontAtlasSets, fonts: &Assets, - texture_atlases: &mut Assets, + texture_atlases: &mut Assets, textures: &mut Assets, text_settings: &TextSettings, font_atlas_warning: &mut FontAtlasWarning, diff --git a/crates/bevy_text/src/pipeline.rs b/crates/bevy_text/src/pipeline.rs index 90fb69c737273..56c861016cd23 100644 --- a/crates/bevy_text/src/pipeline.rs +++ b/crates/bevy_text/src/pipeline.rs @@ -12,7 +12,7 @@ use bevy_math::Vec2; use bevy_reflect::prelude::ReflectDefault; use bevy_reflect::Reflect; use bevy_render::texture::Image; -use bevy_sprite::TextureAtlas; +use bevy_sprite::TextureAtlasLayout; use bevy_utils::HashMap; use glyph_brush_layout::{FontId, GlyphPositioner, SectionGeometry, SectionText, ToSectionText}; @@ -51,7 +51,7 @@ impl TextPipeline { linebreak_behavior: BreakLineOn, bounds: Vec2, font_atlas_sets: &mut FontAtlasSets, - texture_atlases: &mut Assets, + texture_atlases: &mut Assets, textures: &mut Assets, text_settings: &TextSettings, font_atlas_warning: &mut FontAtlasWarning, diff --git a/crates/bevy_text/src/text2d.rs b/crates/bevy_text/src/text2d.rs index 5c380aae0e001..6e6ae8ae00856 100644 --- a/crates/bevy_text/src/text2d.rs +++ b/crates/bevy_text/src/text2d.rs @@ -21,7 +21,7 @@ use bevy_render::{ view::{InheritedVisibility, ViewVisibility, Visibility}, Extract, }; -use bevy_sprite::{Anchor, ExtractedSprite, ExtractedSprites, TextureAtlas}; +use bevy_sprite::{Anchor, ExtractedSprite, ExtractedSprites, TextureAtlasLayout}; use bevy_transform::prelude::{GlobalTransform, Transform}; use bevy_utils::HashSet; use bevy_window::{PrimaryWindow, Window, WindowScaleFactorChanged}; @@ -83,7 +83,7 @@ pub struct Text2dBundle { pub fn extract_text2d_sprite( mut commands: Commands, mut extracted_sprites: ResMut, - texture_atlases: Extract>>, + texture_atlases: Extract>>, windows: Extract>>, text2d_query: Extract< Query<( @@ -138,7 +138,7 @@ pub fn extract_text2d_sprite( color, rect: Some(atlas.textures[atlas_info.glyph_index]), custom_size: None, - image_handle_id: atlas.texture.id(), + image_handle_id: atlas_info.texture.id(), flip_x: false, flip_y: false, anchor: Anchor::Center.as_vec(), @@ -166,7 +166,7 @@ pub fn update_text2d_layout( mut font_atlas_warning: ResMut, windows: Query<&Window, With>, mut scale_factor_changed: EventReader, - mut texture_atlases: ResMut>, + mut texture_atlases: ResMut>, mut font_atlas_sets: ResMut, mut text_pipeline: ResMut, mut text_query: Query<(Entity, Ref, Ref, &mut TextLayoutInfo)>, diff --git a/crates/bevy_ui/src/lib.rs b/crates/bevy_ui/src/lib.rs index ee4c27baa8409..6aafd6dce85fe 100644 --- a/crates/bevy_ui/src/lib.rs +++ b/crates/bevy_ui/src/lib.rs @@ -118,7 +118,6 @@ impl Plugin for UiPlugin { .register_type::() .register_type::() .register_type::() - .register_type::() .register_type::() .register_type::() .register_type::() @@ -151,8 +150,7 @@ impl Plugin for UiPlugin { .ambiguous_with(bevy_text::update_text2d_layout) // We assume Text is on disjoint UI entities to UiImage and UiTextureAtlasImage // FIXME: Add an archetype invariant for this https://github.com/bevyengine/bevy/issues/1481. - .ambiguous_with(widget::update_image_content_size_system) - .ambiguous_with(widget::update_atlas_content_size_system), + .ambiguous_with(widget::update_image_content_size_system), widget::text_system .after(UiSystem::Layout) .after(bevy_text::remove_dropped_font_atlas_sets) @@ -163,11 +161,7 @@ impl Plugin for UiPlugin { #[cfg(feature = "bevy_text")] app.add_plugins(accessibility::AccessibilityPlugin); app.add_systems(PostUpdate, { - let system = widget::update_image_content_size_system - .before(UiSystem::Layout) - // We assume UiImage, UiTextureAtlasImage are disjoint UI entities - // FIXME: Add an archetype invariant for this https://github.com/bevyengine/bevy/issues/1481. - .ambiguous_with(widget::update_atlas_content_size_system); + let system = widget::update_image_content_size_system.before(UiSystem::Layout); // Potential conflicts: `Assets` // They run independently since `widget::image_node_system` will only ever observe // its own UiImage, and `widget::text_system` & `bevy_text::update_text2d_layout` @@ -183,11 +177,7 @@ impl Plugin for UiPlugin { app.add_systems( PostUpdate, ( - ( - widget::update_atlas_content_size_system, - update_target_camera_system, - ) - .before(UiSystem::Layout), + update_target_camera_system.before(UiSystem::Layout), apply_deferred .after(update_target_camera_system) .before(UiSystem::Layout), diff --git a/crates/bevy_ui/src/node_bundles.rs b/crates/bevy_ui/src/node_bundles.rs index 4446c875fbda7..95fa780f906e0 100644 --- a/crates/bevy_ui/src/node_bundles.rs +++ b/crates/bevy_ui/src/node_bundles.rs @@ -5,7 +5,7 @@ use crate::widget::TextFlags; use crate::{ widget::{Button, UiImageSize}, BackgroundColor, BorderColor, ContentSize, FocusPolicy, Interaction, Node, Style, UiImage, - UiMaterial, UiTextureAtlasImage, ZIndex, + UiMaterial, ZIndex, }; use bevy_asset::Handle; use bevy_ecs::bundle::Bundle; @@ -115,6 +115,8 @@ pub struct ImageBundle { } /// A UI node that is a texture atlas sprite +/// +/// This bundle is identical to [`ImageBundle`] with an additional [`TextureAtlas`] component. #[derive(Bundle, Debug, Default)] pub struct AtlasImageBundle { /// Describes the logical size of the node @@ -128,10 +130,10 @@ pub struct AtlasImageBundle { /// /// Combines with `UiImage` to tint the provided image. pub background_color: BackgroundColor, + /// The image of the node + pub image: UiImage, /// A handle to the texture atlas to use for this Ui Node - pub texture_atlas: Handle, - /// The descriptor for which sprite to use from the given texture atlas - pub texture_atlas_image: UiTextureAtlasImage, + pub texture_atlas: TextureAtlas, /// Whether this node should block interaction with lower nodes pub focus_policy: FocusPolicy, /// The size of the image in pixels diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index 58855c2afdc28..b4d333735b6ef 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -8,13 +8,13 @@ use bevy_render::{ render_phase::PhaseItem, render_resource::BindGroupEntries, view::ViewVisibility, ExtractSchedule, Render, }; +use bevy_sprite::{SpriteAssetEvents, TextureAtlas}; pub use pipeline::*; pub use render_pass::*; pub use ui_material_pipeline::*; use crate::{ - BackgroundColor, BorderColor, CalculatedClip, ContentSize, Node, Style, UiImage, UiScale, - UiTextureAtlasImage, Val, + BackgroundColor, BorderColor, CalculatedClip, ContentSize, Node, Style, UiImage, UiScale, Val, }; use crate::{DefaultUiCamera, Outline, TargetCamera}; @@ -34,7 +34,8 @@ use bevy_render::{ view::{ExtractedView, ViewUniforms}, Extract, RenderApp, RenderSet, }; -use bevy_sprite::{SpriteAssetEvents, TextureAtlas}; +#[cfg(feature = "bevy_text")] +use bevy_sprite::TextureAtlasLayout; #[cfg(feature = "bevy_text")] use bevy_text::{PositionedGlyph, Text, TextLayoutInfo}; use bevy_transform::components::GlobalTransform; @@ -82,9 +83,6 @@ pub fn build_ui_render(app: &mut App) { extract_default_ui_camera_view::, extract_default_ui_camera_view::, extract_uinodes.in_set(RenderUiSystem::ExtractNode), - extract_atlas_uinodes - .in_set(RenderUiSystem::ExtractAtlasNode) - .after(RenderUiSystem::ExtractNode), extract_uinode_borders.after(RenderUiSystem::ExtractAtlasNode), #[cfg(feature = "bevy_text")] extract_text_uinodes.after(RenderUiSystem::ExtractAtlasNode), @@ -174,99 +172,6 @@ pub struct ExtractedUiNodes { pub uinodes: EntityHashMap, } -pub fn extract_atlas_uinodes( - mut extracted_uinodes: ResMut, - images: Extract>>, - texture_atlases: Extract>>, - default_ui_camera: Extract, - uinode_query: Extract< - Query< - ( - Entity, - &Node, - &GlobalTransform, - &BackgroundColor, - &ViewVisibility, - Option<&CalculatedClip>, - &Handle, - &UiTextureAtlasImage, - Option<&TargetCamera>, - ), - Without, - >, - >, -) { - for ( - entity, - uinode, - transform, - color, - view_visibility, - clip, - texture_atlas_handle, - atlas_image, - camera, - ) in uinode_query.iter() - { - let Some(camera_entity) = camera.map(TargetCamera::entity).or(default_ui_camera.get()) - else { - continue; - }; - // Skip invisible and completely transparent nodes - if !view_visibility.get() || color.0.is_fully_transparent() { - continue; - } - - let (mut atlas_rect, mut atlas_size, image) = - if let Some(texture_atlas) = texture_atlases.get(texture_atlas_handle) { - let atlas_rect = *texture_atlas - .textures - .get(atlas_image.index) - .unwrap_or_else(|| { - panic!( - "Atlas index {:?} does not exist for texture atlas handle {:?}.", - atlas_image.index, - texture_atlas_handle.id(), - ) - }); - ( - atlas_rect, - texture_atlas.size, - texture_atlas.texture.clone(), - ) - } else { - // Atlas not present in assets resource (should this warn the user?) - continue; - }; - - // Skip loading images - if !images.contains(&image) { - continue; - } - - let scale = uinode.size() / atlas_rect.size(); - atlas_rect.min *= scale; - atlas_rect.max *= scale; - atlas_size *= scale; - - extracted_uinodes.uinodes.insert( - entity, - ExtractedUiNode { - stack_index: uinode.stack_index, - transform: transform.compute_matrix(), - color: color.0, - rect: atlas_rect, - clip: clip.map(|clip| clip.clip), - image: image.id(), - atlas_size: Some(atlas_size), - flip_x: atlas_image.flip_x, - flip_y: atlas_image.flip_y, - camera_entity, - }, - ); - } -} - pub(crate) fn resolve_border_thickness(value: Val, parent_width: f32, viewport_size: Vec2) -> f32 { match value { Val::Auto => 0., @@ -495,24 +400,23 @@ pub fn extract_uinode_outlines( pub fn extract_uinodes( mut extracted_uinodes: ResMut, images: Extract>>, + texture_atlases: Extract>>, default_ui_camera: Extract, uinode_query: Extract< - Query< - ( - Entity, - &Node, - &GlobalTransform, - &BackgroundColor, - Option<&UiImage>, - &ViewVisibility, - Option<&CalculatedClip>, - Option<&TargetCamera>, - ), - Without, - >, + Query<( + Entity, + &Node, + &GlobalTransform, + &BackgroundColor, + Option<&UiImage>, + &ViewVisibility, + Option<&CalculatedClip>, + Option<&TextureAtlas>, + Option<&TargetCamera>, + )>, >, ) { - for (entity, uinode, transform, color, maybe_image, view_visibility, clip, camera) in + for (entity, uinode, transform, color, maybe_image, view_visibility, clip, atlas, camera) in uinode_query.iter() { let Some(camera_entity) = camera.map(TargetCamera::entity).or(default_ui_camera.get()) @@ -534,19 +438,39 @@ pub fn extract_uinodes( (AssetId::default(), false, false) }; + let (rect, atlas_size) = match atlas { + Some(atlas) => { + let Some(layout) = texture_atlases.get(&atlas.layout) else { + // Atlas not present in assets resource (should this warn the user?) + continue; + }; + let mut atlas_rect = layout.textures[atlas.index]; + let mut atlas_size = layout.size; + let scale = uinode.size() / atlas_rect.size(); + atlas_rect.min *= scale; + atlas_rect.max *= scale; + atlas_size *= scale; + (atlas_rect, Some(atlas_size)) + } + None => ( + Rect { + min: Vec2::ZERO, + max: uinode.calculated_size, + }, + None, + ), + }; + extracted_uinodes.uinodes.insert( entity, ExtractedUiNode { stack_index: uinode.stack_index, transform: transform.compute_matrix(), color: color.0, - rect: Rect { - min: Vec2::ZERO, - max: uinode.calculated_size, - }, + rect, clip: clip.map(|clip| clip.clip), image, - atlas_size: None, + atlas_size, flip_x, flip_y, camera_entity, @@ -635,7 +559,7 @@ pub fn extract_text_uinodes( mut extracted_uinodes: ResMut, camera_query: Extract>, default_ui_camera: Extract, - texture_atlases: Extract>>, + texture_atlases: Extract>>, ui_scale: Extract>, uinode_query: Extract< Query<( @@ -708,7 +632,7 @@ pub fn extract_text_uinodes( * Mat4::from_translation(position.extend(0.) * inverse_scale_factor), color, rect, - image: atlas.texture.id(), + image: atlas_info.texture.id(), atlas_size: Some(atlas.size * inverse_scale_factor), clip: clip.map(|clip| clip.clip), flip_x: false, diff --git a/crates/bevy_ui/src/ui_node.rs b/crates/bevy_ui/src/ui_node.rs index 8b2f42e699997..c92dafb5da4a9 100644 --- a/crates/bevy_ui/src/ui_node.rs +++ b/crates/bevy_ui/src/ui_node.rs @@ -1608,18 +1608,6 @@ impl From for BackgroundColor { } } -/// The atlas sprite to be used in a UI Texture Atlas Node -#[derive(Component, Clone, Debug, Reflect, Default)] -#[reflect(Component, Default)] -pub struct UiTextureAtlasImage { - /// Texture index in the TextureAtlas - pub index: usize, - /// Whether to flip the sprite in the X axis - pub flip_x: bool, - /// Whether to flip the sprite in the Y axis - pub flip_y: bool, -} - /// The border color of the UI node. #[derive(Component, Copy, Clone, Debug, Reflect)] #[reflect(Component, Default)] diff --git a/crates/bevy_ui/src/widget/image.rs b/crates/bevy_ui/src/widget/image.rs index ff365222110a5..715b5f829aa74 100644 --- a/crates/bevy_ui/src/widget/image.rs +++ b/crates/bevy_ui/src/widget/image.rs @@ -1,7 +1,5 @@ -use crate::{ - measurement::AvailableSpace, ContentSize, Measure, Node, UiImage, UiScale, UiTextureAtlasImage, -}; -use bevy_asset::{Assets, Handle}; +use crate::{measurement::AvailableSpace, ContentSize, Measure, Node, UiImage, UiScale}; +use bevy_asset::Assets; use bevy_ecs::change_detection::DetectChanges; use bevy_ecs::query::Without; @@ -14,7 +12,7 @@ use bevy_ecs::{ use bevy_math::Vec2; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_render::texture::Image; -use bevy_sprite::TextureAtlas; +use bevy_sprite::{TextureAtlas, TextureAtlasLayout}; use bevy_window::{PrimaryWindow, Window}; /// The size of the image's texture @@ -82,48 +80,15 @@ pub fn update_image_content_size_system( windows: Query<&Window, With>, ui_scale: Res, textures: Res>, - mut query: Query<(&mut ContentSize, &UiImage, &mut UiImageSize), UpdateImageFilter>, -) { - let combined_scale_factor = windows - .get_single() - .map(|window| window.resolution.scale_factor()) - .unwrap_or(1.) - * ui_scale.0; - - for (mut content_size, image, mut image_size) in &mut query { - if let Some(texture) = textures.get(&image.texture) { - let size = texture.size_f32(); - // Update only if size or scale factor has changed to avoid needless layout calculations - if size != image_size.size - || combined_scale_factor != *previous_combined_scale_factor - || content_size.is_added() - { - image_size.size = size; - content_size.set(ImageMeasure { - // multiply the image size by the scale factor to get the physical size - size: size * combined_scale_factor, - }); - } - } - } - - *previous_combined_scale_factor = combined_scale_factor; -} - -/// Updates content size of the node based on the texture atlas sprite -pub fn update_atlas_content_size_system( - mut previous_combined_scale_factor: Local, - windows: Query<&Window, With>, - ui_scale: Res, - atlases: Res>, - mut atlas_query: Query< + atlases: Res>, + mut query: Query< ( &mut ContentSize, - &Handle, - &UiTextureAtlasImage, + &UiImage, &mut UiImageSize, + Option<&TextureAtlas>, ), - (UpdateImageFilter, Without), + UpdateImageFilter, >, ) { let combined_scale_factor = windows @@ -132,9 +97,11 @@ pub fn update_atlas_content_size_system( .unwrap_or(1.) * ui_scale.0; - for (mut content_size, atlas, atlas_image, mut image_size) in &mut atlas_query { - if let Some(atlas) = atlases.get(atlas) { - let size = atlas.textures[atlas_image.index].size(); + for (mut content_size, image, mut image_size, atlas_image) in &mut query { + if let Some(size) = match atlas_image { + Some(atlas) => atlas.texture_rect(&atlases).map(|t| t.size()), + None => textures.get(&image.texture).map(|t| t.size_f32()), + } { // Update only if size or scale factor has changed to avoid needless layout calculations if size != image_size.size || combined_scale_factor != *previous_combined_scale_factor diff --git a/crates/bevy_ui/src/widget/text.rs b/crates/bevy_ui/src/widget/text.rs index 57f29a2dfc76c..77204e43c037c 100644 --- a/crates/bevy_ui/src/widget/text.rs +++ b/crates/bevy_ui/src/widget/text.rs @@ -10,7 +10,7 @@ use bevy_ecs::{ use bevy_math::Vec2; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_render::texture::Image; -use bevy_sprite::TextureAtlas; +use bevy_sprite::TextureAtlasLayout; use bevy_text::{ scale_value, BreakLineOn, Font, FontAtlasSets, FontAtlasWarning, Text, TextError, TextLayoutInfo, TextMeasureInfo, TextPipeline, TextSettings, YAxisOrientation, @@ -155,7 +155,7 @@ fn queue_text( text_pipeline: &mut TextPipeline, font_atlas_warning: &mut FontAtlasWarning, font_atlas_sets: &mut FontAtlasSets, - texture_atlases: &mut Assets, + texture_atlases: &mut Assets, textures: &mut Assets, text_settings: &TextSettings, scale_factor: f32, @@ -226,7 +226,7 @@ pub fn text_system( text_settings: Res, mut font_atlas_warning: ResMut, ui_scale: Res, - mut texture_atlases: ResMut>, + mut texture_atlases: ResMut>, mut font_atlas_sets: ResMut, mut text_pipeline: ResMut, mut text_query: Query<(Ref, &Text, &mut TextLayoutInfo, &mut TextFlags)>, diff --git a/examples/2d/sprite_sheet.rs b/examples/2d/sprite_sheet.rs index 66a4095a189cd..5aeca48f2290a 100644 --- a/examples/2d/sprite_sheet.rs +++ b/examples/2d/sprite_sheet.rs @@ -22,19 +22,15 @@ struct AnimationTimer(Timer); fn animate_sprite( time: Res