Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Split TextureAtlasSources out of TextureAtlasLayout and make TextureAtlasLayout serializable #15344

Merged
merged 1 commit into from
Sep 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions crates/bevy_internal/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -74,17 +74,18 @@ shader_format_glsl = [
shader_format_spirv = ["bevy_render/shader_format_spirv"]

serialize = [
"bevy_color?/serialize",
"bevy_core/serialize",
"bevy_input/serialize",
"bevy_ecs/serialize",
"bevy_time/serialize",
"bevy_window/serialize",
"bevy_winit?/serialize",
"bevy_transform/serialize",
"bevy_input/serialize",
"bevy_math/serialize",
"bevy_scene?/serialize",
"bevy_sprite?/serialize",
"bevy_time/serialize",
"bevy_transform/serialize",
"bevy_ui?/serialize",
"bevy_color?/serialize",
"bevy_window/serialize",
"bevy_winit?/serialize",
Comment on lines +77 to +88
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the order seemed mostly random before, I decided to sort these. The big change is that bevy_sprite?/serialize is added here.

]
multi_threaded = [
"bevy_asset?/multi_threaded",
Expand Down
2 changes: 2 additions & 0 deletions crates/bevy_sprite/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ keywords = ["bevy"]
[features]
default = ["bevy_sprite_picking_backend"]
bevy_sprite_picking_backend = ["bevy_picking", "bevy_window"]
serialize = ["dep:serde"]
webgl = []
webgpu = []

Expand Down Expand Up @@ -41,6 +42,7 @@ rectangle-pack = "0.4"
bitflags = "2.3"
radsort = "0.1"
nonmax = "0.5"
serde = { version = "1", features = ["derive"], optional = true }

[lints]
workspace = true
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_sprite/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ pub mod prelude {
pub use crate::{
bundle::SpriteBundle,
sprite::{ImageScaleMode, Sprite},
texture_atlas::{TextureAtlas, TextureAtlasLayout},
texture_atlas::{TextureAtlas, TextureAtlasLayout, TextureAtlasSources},
texture_slice::{BorderRect, SliceScaleMode, TextureSlice, TextureSlicer},
ColorMaterial, ColorMesh2dBundle, TextureAtlasBuilder,
};
Expand Down
68 changes: 46 additions & 22 deletions crates/bevy_sprite/src/texture_atlas.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,50 @@ use bevy_asset::{Asset, AssetId, Assets, Handle};
use bevy_ecs::{component::Component, reflect::ReflectComponent};
use bevy_math::{URect, UVec2};
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
#[cfg(feature = "serialize")]
use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
use bevy_render::texture::Image;
use bevy_utils::HashMap;

/// Stores a mapping from sub texture handles to the related area index.
///
/// Generated by [`TextureAtlasBuilder`].
///
/// [`TextureAtlasBuilder`]: crate::TextureAtlasBuilder
#[derive(Debug)]
pub struct TextureAtlasSources {
/// Maps from a specific image handle to the index in `textures` where they can be found.
pub(crate) texture_ids: HashMap<AssetId<Image>, usize>,
}
impl TextureAtlasSources {
/// Retrieves the texture *section* index of the given `texture` handle.
pub fn texture_index(&self, texture: impl Into<AssetId<Image>>) -> Option<usize> {
let id = texture.into();
self.texture_ids.get(&id).cloned()
}

/// Creates a [`TextureAtlas`] handle for the given `texture` handle.
pub fn handle(
&self,
layout: Handle<TextureAtlasLayout>,
texture: impl Into<AssetId<Image>>,
) -> Option<TextureAtlas> {
Some(TextureAtlas {
layout,
index: self.texture_index(texture)?,
})
}

/// Retrieves the texture *section* rectangle of the given `texture` handle.
pub fn texture_rect(
&self,
layout: &TextureAtlasLayout,
texture: impl Into<AssetId<Image>>,
) -> Option<URect> {
layout.textures.get(self.texture_index(texture)?).cloned()
}
}

/// 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.
///
Expand All @@ -16,26 +57,22 @@ use bevy_utils::HashMap;
/// [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)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[derive(Asset, Reflect, PartialEq, Eq, Debug, Clone)]
#[reflect(Debug, PartialEq)]
#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
pub struct TextureAtlasLayout {
/// Total size of texture atlas.
pub size: UVec2,
/// The specific areas of the atlas where each texture can be found
pub textures: Vec<URect>,
/// 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<HashMap<AssetId<Image>, usize>>,
}

impl TextureAtlasLayout {
/// Create a new empty layout with custom `dimensions`
pub fn new_empty(dimensions: UVec2) -> Self {
Self {
size: dimensions,
texture_handles: None,
textures: Vec::new(),
}
}
Expand Down Expand Up @@ -89,7 +126,6 @@ impl TextureAtlasLayout {
Self {
size: ((tile_size + current_padding) * grid_size) - current_padding,
textures: sprites,
texture_handles: None,
}
}

Expand All @@ -114,18 +150,6 @@ impl TextureAtlasLayout {
pub fn is_empty(&self) -> bool {
self.textures.is_empty()
}

/// 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<AssetId<Image>>) -> Option<usize> {
let id = texture.into();
self.texture_handles
.as_ref()
.and_then(|texture_handles| texture_handles.get(&id).cloned())
}
}

/// Component used to draw a specific section of a texture.
Expand Down
14 changes: 9 additions & 5 deletions crates/bevy_sprite/src/texture_atlas_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use rectangle_pack::{
};
use thiserror::Error;

use crate::TextureAtlasLayout;
use crate::{TextureAtlasLayout, TextureAtlasSources};

#[derive(Debug, Error)]
pub enum TextureAtlasBuilderError {
Expand Down Expand Up @@ -159,7 +159,9 @@ impl<'a> TextureAtlasBuilder<'a> {
since = "0.14.0",
note = "TextureAtlasBuilder::finish() was not idiomatic. Use TextureAtlasBuilder::build() instead."
)]
pub fn finish(&mut self) -> Result<(TextureAtlasLayout, Image), TextureAtlasBuilderError> {
pub fn finish(
&mut self,
) -> Result<(TextureAtlasLayout, TextureAtlasSources, Image), TextureAtlasBuilderError> {
self.build()
}

Expand All @@ -184,7 +186,7 @@ impl<'a> TextureAtlasBuilder<'a> {
/// // Customize it
/// // ...
/// // Build your texture and the atlas layout
/// let (atlas_layout, texture) = builder.build().unwrap();
/// let (atlas_layout, atlas_sources, texture) = builder.build().unwrap();
/// let texture = textures.add(texture);
/// let layout = layouts.add(atlas_layout);
/// // Spawn your sprite
Expand All @@ -199,7 +201,9 @@ impl<'a> TextureAtlasBuilder<'a> {
///
/// 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 build(&mut self) -> Result<(TextureAtlasLayout, Image), TextureAtlasBuilderError> {
pub fn build(
&mut self,
) -> Result<(TextureAtlasLayout, TextureAtlasSources, Image), TextureAtlasBuilderError> {
let max_width = self.max_size.x;
let max_height = self.max_size.y;

Expand Down Expand Up @@ -295,8 +299,8 @@ impl<'a> TextureAtlasBuilder<'a> {
TextureAtlasLayout {
size: atlas_texture.size(),
textures: texture_rects,
texture_handles: Some(texture_ids),
},
TextureAtlasSources { texture_ids },
atlas_texture,
))
}
Expand Down
90 changes: 53 additions & 37 deletions examples/2d/texture_atlas.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,36 +59,38 @@ fn setup(

// create texture atlases with different padding and sampling

let (texture_atlas_linear, linear_texture) = create_texture_atlas(
let (texture_atlas_linear, linear_sources, linear_texture) = create_texture_atlas(
loaded_folder,
None,
Some(ImageSampler::linear()),
&mut textures,
);
let atlas_linear_handle = texture_atlases.add(texture_atlas_linear.clone());
let atlas_linear_handle = texture_atlases.add(texture_atlas_linear);

let (texture_atlas_nearest, nearest_texture) = create_texture_atlas(
let (texture_atlas_nearest, nearest_sources, nearest_texture) = create_texture_atlas(
loaded_folder,
None,
Some(ImageSampler::nearest()),
&mut textures,
);
let atlas_nearest_handle = texture_atlases.add(texture_atlas_nearest);

let (texture_atlas_linear_padded, linear_padded_texture) = create_texture_atlas(
loaded_folder,
Some(UVec2::new(6, 6)),
Some(ImageSampler::linear()),
&mut textures,
);
let (texture_atlas_linear_padded, linear_padded_sources, linear_padded_texture) =
create_texture_atlas(
loaded_folder,
Some(UVec2::new(6, 6)),
Some(ImageSampler::linear()),
&mut textures,
);
let atlas_linear_padded_handle = texture_atlases.add(texture_atlas_linear_padded.clone());

let (texture_atlas_nearest_padded, nearest_padded_texture) = create_texture_atlas(
loaded_folder,
Some(UVec2::new(6, 6)),
Some(ImageSampler::nearest()),
&mut textures,
);
let (texture_atlas_nearest_padded, nearest_padded_sources, nearest_padded_texture) =
create_texture_atlas(
loaded_folder,
Some(UVec2::new(6, 6)),
Some(ImageSampler::nearest()),
&mut textures,
);
let atlas_nearest_padded_handle = texture_atlases.add(texture_atlas_nearest_padded);

// setup 2d scene
Expand Down Expand Up @@ -145,25 +147,39 @@ fn setup(
.get_handle("textures/rpg/chars/vendor/generic-rpg-vendor.png")
.unwrap();

// get index of the sprite in the texture atlas, this is used to render the sprite
// the index is the same for all the texture atlases, since they are created from the same folder
let vendor_index = texture_atlas_linear
.get_texture_index(&vendor_handle)
.unwrap();

// configuration array to render sprites through iteration
let configurations: [(&str, Handle<TextureAtlasLayout>, Handle<Image>, f32); 4] = [
("Linear", atlas_linear_handle, linear_texture, -350.0),
("Nearest", atlas_nearest_handle, nearest_texture, -150.0),
let configurations: [(
&str,
Handle<TextureAtlasLayout>,
TextureAtlasSources,
Handle<Image>,
f32,
); 4] = [
(
"Linear",
atlas_linear_handle,
linear_sources,
linear_texture,
-350.0,
),
(
"Nearest",
atlas_nearest_handle,
nearest_sources,
nearest_texture,
-150.0,
),
(
"Linear",
atlas_linear_padded_handle,
linear_padded_sources,
linear_padded_texture,
150.0,
),
(
"Nearest",
atlas_nearest_padded_handle,
nearest_padded_sources,
nearest_padded_texture,
350.0,
),
Expand All @@ -178,14 +194,15 @@ fn setup(

let base_y = 170.0; // y position of the sprites

for (sampling, atlas_handle, image_handle, x) in configurations {
for (sampling, atlas_handle, atlas_sources, atlas_texture, x) in configurations {
// render a sprite from the texture_atlas
create_sprite_from_atlas(
&mut commands,
(x, base_y, 0.0),
vendor_index,
atlas_texture,
atlas_sources,
atlas_handle,
image_handle,
&vendor_handle,
);

// render a label to indicate the sampling setting
Expand All @@ -205,7 +222,7 @@ fn create_texture_atlas(
padding: Option<UVec2>,
sampling: Option<ImageSampler>,
textures: &mut ResMut<Assets<Image>>,
) -> (TextureAtlasLayout, Handle<Image>) {
) -> (TextureAtlasLayout, TextureAtlasSources, Handle<Image>) {
// Build a texture atlas using the individual sprites
let mut texture_atlas_builder = TextureAtlasBuilder::default();
texture_atlas_builder.padding(padding.unwrap_or_default());
Expand All @@ -222,23 +239,25 @@ fn create_texture_atlas(
texture_atlas_builder.add_texture(Some(id), texture);
}

let (texture_atlas_layout, texture) = texture_atlas_builder.build().unwrap();
let (texture_atlas_layout, texture_atlas_sources, texture) =
texture_atlas_builder.build().unwrap();
let texture = textures.add(texture);

// Update the sampling settings of the texture atlas
let image = textures.get_mut(&texture).unwrap();
image.sampler = sampling.unwrap_or_default();

(texture_atlas_layout, texture)
(texture_atlas_layout, texture_atlas_sources, texture)
}

/// Create and spawn a sprite from a texture atlas
fn create_sprite_from_atlas(
commands: &mut Commands,
translation: (f32, f32, f32),
sprite_index: usize,
atlas_texture: Handle<Image>,
atlas_sources: TextureAtlasSources,
atlas_handle: Handle<TextureAtlasLayout>,
texture: Handle<Image>,
vendor_handle: &Handle<Image>,
) {
commands.spawn((
SpriteBundle {
Expand All @@ -247,13 +266,10 @@ fn create_sprite_from_atlas(
scale: Vec3::splat(3.0),
..default()
},
texture,
texture: atlas_texture,
..default()
},
TextureAtlas {
layout: atlas_handle,
index: sprite_index,
},
atlas_sources.handle(atlas_handle, vendor_handle).unwrap(),
));
}

Expand Down