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}