diff --git a/crates/bevy_sprite/src/bundle.rs b/crates/bevy_sprite/src/bundle.rs index 092e5a302bb8b..7c49be8fddb98 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}, - Sprite, -}; +use crate::{Sprite, TextureAtlas}; use bevy_asset::Handle; use bevy_ecs::bundle::Bundle; use bevy_render::{ @@ -34,17 +31,25 @@ impl Default for 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, - /// 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 sprite: Sprite, 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, /// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering diff --git a/crates/bevy_sprite/src/dynamic_texture_atlas_builder.rs b/crates/bevy_sprite/src/dynamic_texture_atlas_builder.rs index 18dd13a0ed8ff..3ec890ef23057 100644 --- a/crates/bevy_sprite/src/dynamic_texture_atlas_builder.rs +++ b/crates/bevy_sprite/src/dynamic_texture_atlas_builder.rs @@ -1,13 +1,13 @@ -use crate::TextureAtlas; -use bevy_asset::Assets; +use crate::TextureAtlasLayout; +use bevy_asset::{Assets, Handle}; use bevy_math::{IVec2, Rect, Vec2}; use bevy_render::texture::{Image, TextureFormatPixelInfo}; 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, @@ -27,24 +27,32 @@ impl DynamicTextureAtlasBuilder { } } - /// Add a new texture to [`TextureAtlas`]. - /// It is user's responsibility to pass in the correct [`TextureAtlas`] + /// Add a new texture to `atlas_layout` + /// It is user's responsibility to pass in the correct [`TextureAtlasLayout`] + /// + /// # 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.texture_descriptor.size.width as i32 + self.padding, texture.texture_descriptor.size.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(); 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 403e00722e220..c880d1eba4ae9 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::Sprite, - texture_atlas::{TextureAtlas, TextureAtlasSprite}, + texture_atlas::{TextureAtlas, TextureAtlasLayout}, ColorMaterial, ColorMesh2dBundle, TextureAtlasBuilder, }; } @@ -59,11 +59,11 @@ impl Plugin for SpritePlugin { let mut shaders = app.world.resource_mut::>(); let sprite_shader = Shader::from_wgsl(include_str!("render/sprite.wgsl")); shaders.set_untracked(SPRITE_SHADER_HANDLE, sprite_shader); - app.add_asset::() - .register_asset_reflect::() + app.add_asset::() + .register_asset_reflect::() .register_type::() - .register_type::() .register_type::() + .register_type::() .register_type::() .add_plugin(Mesh2dRenderPlugin) .add_plugin(ColorMaterialPlugin) @@ -107,14 +107,10 @@ 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_without_aabb: Query< - (Entity, &Sprite, &Handle), - (Without, Without), - >, - atlases_without_aabb: Query< - (Entity, &TextureAtlasSprite, &Handle), + (Entity, &Sprite, &Handle, Option<&TextureAtlas>), (Without, Without), >, ) { @@ -125,27 +121,17 @@ pub fn calculate_bounds_2d( } } } - for (entity, sprite, texture_handle) in &sprites_without_aabb { - if let Some(size) = sprite - .custom_size - .or_else(|| images.get(texture_handle).map(|image| image.size())) - { - 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).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_without_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()), + // We default to the drawn rect for atlas sprites + Some(atlas) => atlas + .texture_rect(&atlases) + .map(|rect| (rect.min - rect.max).abs()), }) { 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).insert(aabb); diff --git a/crates/bevy_sprite/src/render/mod.rs b/crates/bevy_sprite/src/render/mod.rs index 725aab3234df0..fcfb22f84127d 100644 --- a/crates/bevy_sprite/src/render/mod.rs +++ b/crates/bevy_sprite/src/render/mod.rs @@ -1,9 +1,6 @@ use std::cmp::Ordering; -use crate::{ - texture_atlas::{TextureAtlas, TextureAtlasSprite}, - Sprite, SPRITE_SHADER_HANDLE, -}; +use crate::{Sprite, TextureAtlas, TextureAtlasLayout, SPRITE_SHADER_HANDLE}; use bevy_asset::{AssetEvent, Assets, Handle, HandleId}; use bevy_core_pipeline::{ core_2d::Transparent2d, @@ -347,7 +344,7 @@ pub fn extract_sprite_events( pub fn extract_sprites( mut extracted_sprites: ResMut, - texture_atlases: Extract>>, + texture_atlases: Extract>>, sprite_query: Extract< Query<( Entity, @@ -355,29 +352,23 @@ pub fn extract_sprites( &Sprite, &GlobalTransform, &Handle, - )>, - >, - atlas_query: Extract< - Query<( - Entity, - &ComputedVisibility, - &TextureAtlasSprite, - &GlobalTransform, - &Handle, + Option<&TextureAtlas>, )>, >, ) { extracted_sprites.sprites.clear(); - for (entity, visibility, sprite, transform, handle) in sprite_query.iter() { + for (entity, visibility, sprite, transform, handle, sheet) in sprite_query.iter() { if !visibility.is_visible() { continue; } + 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.push(ExtractedSprite { entity, color: sprite.color, transform: *transform, - rect: sprite.rect, + rect, // Pass the custom size custom_size: sprite.custom_size, flip_x: sprite.flip_x, @@ -386,38 +377,6 @@ pub fn extract_sprites( anchor: sprite.anchor.as_vec(), }); } - for (entity, visibility, atlas_sprite, transform, texture_atlas_handle) in atlas_query.iter() { - if !visibility.is_visible() { - 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.push(ExtractedSprite { - entity, - 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(), - }); - } - } } #[repr(C)] diff --git a/crates/bevy_sprite/src/texture_atlas.rs b/crates/bevy_sprite/src/texture_atlas.rs index 479e28927e945..ed0e3ed095392 100644 --- a/crates/bevy_sprite/src/texture_atlas.rs +++ b/crates/bevy_sprite/src/texture_atlas.rs @@ -1,95 +1,85 @@ -use crate::Anchor; -use bevy_asset::Handle; -use bevy_ecs::{component::Component, reflect::ReflectComponent}; +use bevy_asset::{Assets, Handle}; +use bevy_ecs::component::Component; use bevy_math::{Rect, Vec2}; use bevy_reflect::{FromReflect, Reflect, TypeUuid}; -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(Reflect, FromReflect, Debug, Clone, TypeUuid)] #[uuid = "7233c597-ccfa-411f-bd59-9af349432ada"] #[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 - pub(crate) texture_handles: Option, usize>>, + /// 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 texture_handles: Option, usize>>, } -#[derive(Component, Debug, Clone, Reflect, FromReflect)] -#[reflect(Component)] -pub struct TextureAtlasSprite { - /// The tint color used to draw the sprite, defaulting to [`Color::WHITE`] - pub color: Color, - /// Texture index in [`TextureAtlas`] +/// 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. +/// +/// 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(); @@ -117,40 +107,60 @@ 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`] + /// How many textures are in the `TextureAtlas` 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: &Handle) -> Option { self.texture_handles .as_ref() .and_then(|texture_handles| texture_handles.get(texture).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 f77e0cce90c31..dd827affdf2b7 100644 --- a/crates/bevy_sprite/src/texture_atlas_builder.rs +++ b/crates/bevy_sprite/src/texture_atlas_builder.rs @@ -12,7 +12,7 @@ use rectangle_pack::{ }; use thiserror::Error; -use crate::texture_atlas::TextureAtlas; +use crate::TextureAtlasLayout; #[derive(Debug, Error)] pub enum TextureAtlasBuilderError { @@ -136,13 +136,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 + /// into a new texture atlas will use. 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 /// /// If there is not enough space in the atlas texture, an error will @@ -150,7 +178,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; @@ -227,14 +255,16 @@ impl TextureAtlasBuilder { } self.copy_converted_texture(&mut atlas_texture, texture, packed_location); } - Ok(TextureAtlas { - size: Vec2::new( - atlas_texture.texture_descriptor.size.width as f32, - atlas_texture.texture_descriptor.size.height as f32, - ), - texture: textures.add(atlas_texture), - textures: texture_rects, - texture_handles: Some(texture_handles), - }) + Ok(( + TextureAtlasLayout { + size: Vec2::new( + atlas_texture.texture_descriptor.size.width as f32, + atlas_texture.texture_descriptor.size.height as f32, + ), + textures: texture_rects, + texture_handles: Some(texture_handles), + }, + textures.add(atlas_texture), + )) } } diff --git a/crates/bevy_text/src/font_atlas.rs b/crates/bevy_text/src/font_atlas.rs index be1903c121b36..38c71a1614b7f 100644 --- a/crates/bevy_text/src/font_atlas.rs +++ b/crates/bevy_text/src/font_atlas.rs @@ -5,7 +5,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")] @@ -42,16 +42,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, @@ -61,11 +62,12 @@ impl FontAtlas { &[0, 0, 0, 0], TextureFormat::Rgba8UnormSrgb, )); - 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, 1), + texture, } } @@ -87,16 +89,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 b92f664fea22c..acf4f746d8e52 100644 --- a/crates/bevy_text/src/font_atlas_set.rs +++ b/crates/bevy_text/src/font_atlas_set.rs @@ -4,7 +4,7 @@ use bevy_asset::{Assets, Handle}; use bevy_math::Vec2; use bevy_reflect::TypeUuid; use bevy_render::texture::Image; -use bevy_sprite::TextureAtlas; +use bevy_sprite::TextureAtlasLayout; use bevy_utils::FloatOrd; use bevy_utils::HashMap; @@ -21,7 +21,8 @@ pub struct FontAtlasSet { #[derive(Debug, Clone)] pub struct GlyphAtlasInfo { - pub texture_atlas: Handle, + pub texture_atlas: Handle, + pub texture: Handle, pub glyph_index: usize, } @@ -51,7 +52,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 { @@ -124,10 +125,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 9b17f09a7b910..2f830702fa426 100644 --- a/crates/bevy_text/src/glyph_brush.rs +++ b/crates/bevy_text/src/glyph_brush.rs @@ -2,7 +2,7 @@ use ab_glyph::{Font as _, FontArc, Glyph, ScaleFont as _}; use bevy_asset::{Assets, Handle}; use bevy_math::Vec2; 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, @@ -59,7 +59,7 @@ impl GlyphBrush { sections: &[SectionText], font_atlas_set_storage: &mut Assets, 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 e71e49c030e67..82edfecd0e029 100644 --- a/crates/bevy_text/src/pipeline.rs +++ b/crates/bevy_text/src/pipeline.rs @@ -4,7 +4,7 @@ use bevy_ecs::component::Component; use bevy_ecs::system::Resource; use bevy_math::Vec2; 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}; @@ -48,7 +48,7 @@ impl TextPipeline { linebreak_behavior: BreakLineOn, bounds: Vec2, font_atlas_set_storage: &mut 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/text2d.rs b/crates/bevy_text/src/text2d.rs index a17a1c670950e..29dd1e03d867c 100644 --- a/crates/bevy_text/src/text2d.rs +++ b/crates/bevy_text/src/text2d.rs @@ -17,7 +17,7 @@ use bevy_render::{ view::{ComputedVisibility, 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}; @@ -78,7 +78,7 @@ pub struct Text2dBundle { pub fn extract_text2d_sprite( mut extracted_sprites: ResMut, - texture_atlases: Extract>>, + texture_atlases: Extract>>, windows: Extract>>, text2d_query: Extract< Query<( @@ -131,7 +131,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(), @@ -157,7 +157,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_set_storage: ResMut>, mut text_pipeline: ResMut, mut text_query: Query<(Entity, Ref, Ref, &mut TextLayoutInfo)>, diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index f203c4b227565..c62526474a02f 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -29,7 +29,7 @@ use bevy_render::{ }; use bevy_sprite::SpriteAssetEvents; #[cfg(feature = "bevy_text")] -use bevy_sprite::TextureAtlas; +use bevy_sprite::{TextureAtlas, TextureAtlasLayout}; #[cfg(feature = "bevy_text")] use bevy_text::{PositionedGlyph, Text, TextLayoutInfo}; use bevy_transform::components::GlobalTransform; @@ -165,6 +165,7 @@ pub fn extract_uinodes( mut extracted_uinodes: ResMut, images: Extract>>, ui_stack: Extract>, + atlas_layouts: Extract>>, uinode_query: Extract< Query<( &Node, @@ -172,13 +173,14 @@ pub fn extract_uinodes( &BackgroundColor, Option<&UiImage>, &ComputedVisibility, + Option<&TextureAtlas>, Option<&CalculatedClip>, )>, >, ) { extracted_uinodes.uinodes.clear(); for (stack_index, entity) in ui_stack.uinodes.iter().enumerate() { - if let Ok((uinode, transform, color, maybe_image, visibility, clip)) = + if let Ok((uinode, transform, color, maybe_image, visibility, atlas, clip)) = uinode_query.get(*entity) { // Skip invisible and completely transparent nodes @@ -195,17 +197,33 @@ pub fn extract_uinodes( } else { (DEFAULT_IMAGE_HANDLE.typed().clone_weak(), false, false) }; - + // Skip loading images + if !images.contains(&image) { + continue; + } + // Skip completely transparent nodes + if color.0.a() == 0.0 { + continue; + } + let (atlas_size, rect_min) = atlas + .and_then(|a| atlas_layouts.get(&a.layout).map(|l| (l, a.index))) + .and_then(|(atlas, index)| { + atlas + .textures + .get(index) + .map(|rect| (Some(atlas.size), rect.min)) + }) + .unwrap_or((None, Vec2::ZERO)); extracted_uinodes.uinodes.push(ExtractedUiNode { stack_index, transform: transform.compute_matrix(), color: color.0, rect: Rect { - min: Vec2::ZERO, - max: uinode.calculated_size, + min: rect_min, + max: rect_min + uinode.calculated_size, }, image, - atlas_size: None, + atlas_size, clip: clip.map(|clip| clip.clip), flip_x, flip_y, @@ -275,7 +293,7 @@ pub fn extract_default_ui_camera_view( #[cfg(feature = "bevy_text")] pub fn extract_text_uinodes( mut extracted_uinodes: ResMut, - texture_atlases: Extract>>, + texture_atlases: Extract>>, windows: Extract>>, ui_stack: Extract>, uinode_query: Extract< @@ -332,7 +350,7 @@ pub fn extract_text_uinodes( * Mat4::from_translation(position.extend(0.) * inverse_scale_factor), color, rect, - image: atlas.texture.clone_weak(), + image: atlas_info.texture.clone_weak(), atlas_size: Some(atlas.size * inverse_scale_factor), clip: clip.map(|clip| clip.clip), flip_x: false, diff --git a/crates/bevy_ui/src/widget/text.rs b/crates/bevy_ui/src/widget/text.rs index c56ae98bc5997..1b0efaa9ce62a 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::{ Font, FontAtlasSet, FontAtlasWarning, Text, TextError, TextLayoutInfo, TextMeasureInfo, TextPipeline, TextSettings, YAxisOrientation, @@ -163,7 +163,7 @@ fn queue_text( text_pipeline: &mut TextPipeline, font_atlas_warning: &mut FontAtlasWarning, font_atlas_set_storage: &mut Assets, - texture_atlases: &mut Assets, + texture_atlases: &mut Assets, textures: &mut Assets, text_settings: &TextSettings, scale_factor: f64, @@ -221,7 +221,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_set_storage: 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