bevy/crates/bevy_image/src/texture_atlas.rs
Zachary Harrold 9bc0ae33c3
Move hashbrown and foldhash out of bevy_utils (#17460)
# Objective

- Contributes to #16877

## Solution

- Moved `hashbrown`, `foldhash`, and related types out of `bevy_utils`
and into `bevy_platform_support`
- Refactored the above to match the layout of these types in `std`.
- Updated crates as required.

## Testing

- CI

---

## Migration Guide

- The following items were moved out of `bevy_utils` and into
`bevy_platform_support::hash`:
  - `FixedState`
  - `DefaultHasher`
  - `RandomState`
  - `FixedHasher`
  - `Hashed`
  - `PassHash`
  - `PassHasher`
  - `NoOpHash`
- The following items were moved out of `bevy_utils` and into
`bevy_platform_support::collections`:
  - `HashMap`
  - `HashSet`
- `bevy_utils::hashbrown` has been removed. Instead, import from
`bevy_platform_support::collections` _or_ take a dependency on
`hashbrown` directly.
- `bevy_utils::Entry` has been removed. Instead, import from
`bevy_platform_support::collections::hash_map` or
`bevy_platform_support::collections::hash_set` as appropriate.
- All of the above equally apply to `bevy::utils` and
`bevy::platform_support`.

## Notes

- I left `PreHashMap`, `PreHashMapExt`, and `TypeIdMap` in `bevy_utils`
as they might be candidates for micro-crating. They can always be moved
into `bevy_platform_support` at a later date if desired.
2025-01-23 16:46:08 +00:00

214 lines
7.4 KiB
Rust

use bevy_app::prelude::*;
use bevy_asset::{Asset, AssetApp as _, AssetId, Assets, Handle};
use bevy_math::{URect, UVec2};
use bevy_platform_support::collections::HashMap;
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
#[cfg(feature = "serialize")]
use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
use crate::Image;
/// Adds support for texture atlases.
pub struct TextureAtlasPlugin;
impl Plugin for TextureAtlasPlugin {
fn build(&self, app: &mut App) {
app.init_asset::<TextureAtlasLayout>();
#[cfg(feature = "bevy_reflect")]
app.register_asset_reflect::<TextureAtlasLayout>()
.register_type::<TextureAtlas>();
}
}
/// 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 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.
///
/// Optionally 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 animating sprite in response to an event.](https://github.com/bevyengine/bevy/blob/latest/examples/2d/sprite_animation.rs)
/// [Example usage loading sprite sheet.](https://github.com/bevyengine/bevy/blob/latest/examples/2d/texture_atlas.rs)
///
/// [`TextureAtlasBuilder`]: crate::TextureAtlasBuilder
#[derive(Asset, PartialEq, Eq, Debug, Clone)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
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>,
}
impl TextureAtlasLayout {
/// Create a new empty layout with custom `dimensions`
pub fn new_empty(dimensions: UVec2) -> Self {
Self {
size: dimensions,
textures: Vec::new(),
}
}
/// 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. 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(
tile_size: UVec2,
columns: u32,
rows: u32,
padding: Option<UVec2>,
offset: Option<UVec2>,
) -> Self {
let padding = padding.unwrap_or_default();
let offset = offset.unwrap_or_default();
let mut sprites = Vec::new();
let mut current_padding = UVec2::ZERO;
for y in 0..rows {
if y > 0 {
current_padding.y = padding.y;
}
for x in 0..columns {
if x > 0 {
current_padding.x = padding.x;
}
let cell = UVec2::new(x, y);
let rect_min = (tile_size + current_padding) * cell + offset;
sprites.push(URect {
min: rect_min,
max: rect_min + tile_size,
});
}
}
let grid_size = UVec2::new(columns, rows);
Self {
size: ((tile_size + current_padding) * grid_size) - current_padding,
textures: sprites,
}
}
/// 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 texture to be added
///
/// [`TextureAtlas`]: crate::TextureAtlas
pub fn add_texture(&mut self, rect: URect) -> usize {
self.textures.push(rect);
self.textures.len() - 1
}
/// The number of textures in the [`TextureAtlasLayout`]
pub fn len(&self) -> usize {
self.textures.len()
}
pub fn is_empty(&self) -> bool {
self.textures.is_empty()
}
}
/// An index into a [`TextureAtlasLayout`], which corresponds to 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 display 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)
/// - [`sprite animation event example`](https://github.com/bevyengine/bevy/blob/latest/examples/2d/sprite_animation.rs)
/// - [`texture atlas example`](https://github.com/bevyengine/bevy/blob/latest/examples/2d/texture_atlas.rs)
#[derive(Default, Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Default, Debug, PartialEq, Hash)
)]
pub struct TextureAtlas {
/// Texture atlas layout handle
pub layout: Handle<TextureAtlasLayout>,
/// Texture atlas section index
pub index: usize,
}
impl TextureAtlas {
/// Retrieves the current texture [`URect`] of the sprite sheet according to the section `index`
pub fn texture_rect(&self, texture_atlases: &Assets<TextureAtlasLayout>) -> Option<URect> {
let atlas = texture_atlases.get(&self.layout)?;
atlas.textures.get(self.index).copied()
}
}
impl From<Handle<TextureAtlasLayout>> for TextureAtlas {
fn from(texture_atlas: Handle<TextureAtlasLayout>) -> Self {
Self {
layout: texture_atlas,
index: 0,
}
}
}