1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
use bevy_asset::{Asset, AssetId, Assets, Handle};
use bevy_ecs::component::Component;
use bevy_math::{Rect, Vec2};
use bevy_reflect::Reflect;
use bevy_render::texture::Image;
use bevy_utils::HashMap;

/// 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 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 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<Rect>,
    /// 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>>,
}

/// 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 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)
/// - [`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 layout handle
    pub layout: Handle<TextureAtlasLayout>,
    /// Texture atlas section index
    pub index: usize,
}

impl TextureAtlasLayout {
    /// Create a new empty layout with custom `dimensions`
    pub fn new_empty(dimensions: Vec2) -> Self {
        Self {
            size: dimensions,
            texture_handles: None,
            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: Vec2,
        columns: usize,
        rows: usize,
        padding: Option<Vec2>,
        offset: Option<Vec2>,
    ) -> Self {
        let padding = padding.unwrap_or_default();
        let offset = offset.unwrap_or_default();
        let mut sprites = Vec::new();
        let mut current_padding = Vec2::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 = Vec2::new(x as f32, y as f32);

                let rect_min = (tile_size + current_padding) * cell + offset;

                sprites.push(Rect {
                    min: rect_min,
                    max: rect_min + tile_size,
                });
            }
        }

        let grid_size = Vec2::new(columns as f32, rows as f32);

        Self {
            size: ((tile_size + current_padding) * grid_size) - current_padding,
            textures: sprites,
            texture_handles: None,
        }
    }

    /// 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: Rect) -> 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()
    }

    /// 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())
    }
}

impl TextureAtlas {
    /// Retrieves the current texture [`Rect`] of the sprite sheet according to the section `index`
    pub fn texture_rect(&self, texture_atlases: &Assets<TextureAtlasLayout>) -> Option<Rect> {
        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,
        }
    }
}