engine/resources/assets/
sprite.rs

1// SPDX-FileCopyrightText: 2024 Jens Pitkänen <jens.pitkanen@helsinki.fi>
2//
3// SPDX-License-Identifier: GPL-3.0-or-later
4
5//! Asset type for individual images that can be rendered as-is.
6//!
7//! Not really suitable for use as a sprite atlas, due to color bleeding issues.
8
9use core::ops::Range;
10
11use arrayvec::ArrayVec;
12
13use super::{gen_asset_handle_code, Asset};
14
15gen_asset_handle_code!(SpriteAsset, SpriteHandle, find_sprite, get_sprite, sprites);
16
17/// The maximum amount of mip levels for a sprite.
18pub const MAX_MIPS: usize = 12;
19
20/// One mipmap level (i.e. a sprite with a specific resolution) of a
21/// [`SpriteAsset`].
22///
23/// Each [`SpriteAsset`] has a maximum of [`MAX_MIPS`] of these, with the first
24/// level being the resolution of the original sprite, and each successive mip
25/// having half the width and height of the previous mip.
26///
27/// The sprites do not use hardware mipmapping. Multiple mip levels (especially
28/// the smaller ones) can share a single sprite chunk.
29#[derive(Debug)]
30pub enum SpriteMipLevel {
31    /// A sprite contained within a single chunk. Unlike the other variant,
32    /// these sprite might not be located in the top-left corner of the chunk,
33    /// so this variant has an offset field.
34    SingleChunkSprite {
35        /// Offset from the topmost and leftmost chunks where the actual sprite
36        /// starts.
37        offset: (u16, u16),
38        /// The dimensions of the sprite in pixels.
39        size: (u16, u16),
40        /// The chunk the sprite's pixels are located in. The subregion to
41        /// render is described by the `offset` and `size` fields.
42        sprite_chunk: u32,
43    },
44    /// A sprite split between multiple sprite chunks.
45    ///
46    /// Chunks are allocated for a multi-chunk sprite starting from the
47    /// top-left, row by row. Each chunk has a 1px border (copied from the edge
48    /// of the sprite, creating a kind of clamp-to-edge effect), inside which is
49    /// the actual sprite. The chunks on the right and bottom edges of the
50    /// sprite are the only ones that don't occupy their sprite chunk entirely,
51    /// they instead occupy only up to the sprite's `width` and `height` plus
52    /// the border, effectively taking up a `width + 2` by `height
53    /// + 2` region from the top left corner of those chunks due to the border.
54    MultiChunkSprite {
55        /// The dimensions of the sprite in pixels.
56        size: (u16, u16),
57        /// The chunks the sprite is made up of.
58        sprite_chunks: Range<u32>,
59    },
60}
61
62/// Drawable image.
63#[derive(Debug)]
64pub struct SpriteAsset {
65    /// Whether the sprite's alpha should be taken into consideration while
66    /// rendering.
67    pub transparent: bool,
68    /// The actual specific-size sprites used for rendering depending on the
69    /// size of the sprite on screen.
70    pub mip_chain: ArrayVec<SpriteMipLevel, MAX_MIPS>,
71}
72
73impl Asset for SpriteAsset {
74    fn get_chunks(&self) -> Option<Range<u32>> {
75        None
76    }
77
78    fn offset_chunks(&mut self, _offset: i32) {}
79
80    fn get_sprite_chunks(&self) -> Option<Range<u32>> {
81        let mut range: Option<Range<u32>> = None;
82        for mip in &self.mip_chain {
83            let mip_range = match mip {
84                SpriteMipLevel::SingleChunkSprite { sprite_chunk, .. } => {
85                    *sprite_chunk..*sprite_chunk + 1
86                }
87                SpriteMipLevel::MultiChunkSprite { sprite_chunks, .. } => sprite_chunks.clone(),
88            };
89            if let Some(range) = &mut range {
90                range.start = range.start.min(mip_range.start);
91                range.end = range.end.max(mip_range.end);
92            } else {
93                range = Some(mip_range);
94            }
95        }
96        range
97    }
98
99    fn offset_sprite_chunks(&mut self, offset: i32) {
100        for mip in &mut self.mip_chain {
101            match mip {
102                SpriteMipLevel::SingleChunkSprite { sprite_chunk, .. } => {
103                    *sprite_chunk = (*sprite_chunk as i32 + offset) as u32;
104                }
105                SpriteMipLevel::MultiChunkSprite { sprite_chunks, .. } => {
106                    sprite_chunks.start = (sprite_chunks.start as i32 + offset) as u32;
107                    sprite_chunks.end = (sprite_chunks.end as i32 + offset) as u32;
108                }
109            };
110        }
111    }
112}