graphics/texture_packer.rs
1//! Texture packing.
2
3use crate::ImageSize;
4
5/// A texture packer using a skyline heuristic.
6///
7/// For offline texture packing, see [texture_packer](https://github.com/pistondevelopers/texture_packer).
8///
9/// Designed for adding textures one by one to current texture atlas.
10/// Packs tiles without backtracking or knowledge about future tiles.
11///
12/// - Perfect at packing tiles of same size
13/// - Good at packing tiles of some unit size
14/// - Decent at packing tiles of similar sizes
15/// - Can be used with pre-sorted tile sizes for better packing
16///
17/// Can also be used as storage for textures.
18///
19/// ### Design
20///
21/// A skyline is a list of non-hole atlas offsets,
22/// used to efficiently determine a good place to put the next tile.
23///
24/// In this texture packer,
25/// only a single skyline is kept track of,
26/// since new texture atlases are created by need.
27///
28/// This texture packer has runtime complexity `O(N^2)` for inserting a new tile,
29/// where `N` is the number of points in the skyline.
30/// Since `N` is usually a low number, the packing is pretty fast.
31///
32/// The algorithm was designed by Sven Nilsen (2019) for Piston-Graphics.
33pub struct TexturePacker<T> {
34 /// Stores current texture atlas and previously created ones.
35 pub textures: Vec<T>,
36 /// The index to the current texture atlas.
37 pub atlas: usize,
38 /// Texture atlas offsets from left to right.
39 ///
40 /// When a new tile is added with same offset,
41 /// it updates the atlas offsets that it overlaps.
42 /// This means that "holes" get filled in over time.
43 pub skyline: Vec<[u32; 2]>,
44}
45
46impl<T: ImageSize> TexturePacker<T> {
47 /// Returns a new `TexturePacker`.
48 pub fn new() -> TexturePacker<T> {
49 TexturePacker {
50 textures: vec![],
51 atlas: 0,
52 skyline: vec![],
53 }
54 }
55
56 /// Create a new texture atlas with an initial tile.
57 ///
58 /// The new texture atlas is made the current one.
59 pub fn create(&mut self, size: [u32; 2], texture: T) -> usize {
60 let id = self.textures.len();
61 if !self.textures.is_empty() {
62 self.atlas += 1;
63 }
64 self.skyline = vec![[0, size[1]], [size[0], 0]];
65 self.textures.push(texture);
66 id
67 }
68
69 /// Update current texture atlas.
70 ///
71 /// - ind: index of atlas offset in the skyline
72 /// - size: size of new tile
73 ///
74 /// Returns the index of the current texture atlas and
75 /// the atlas offset of the new tile.
76 pub fn update(&mut self, ind: usize, size: [u32; 2]) -> (usize, [u32; 2]) {
77 let texture = self.atlas;
78 let offset = self.skyline[ind];
79
80 // Increase y-value of atlas offsets that are matched.
81 let mut w = 0;
82 for i in ind..self.skyline.len() {
83 if self.skyline[i][1] <= offset[1] {
84 self.skyline[i][1] = offset[1] + size[1];
85 }
86 w = self.skyline[i][0] - offset[0];
87 if w >= size[1] {
88 break;
89 }
90 }
91 if w == 0 {
92 // There is no end-point atlas offset.
93 // Add new atlas offset point.
94 self.skyline.push([offset[0] + size[0], offset[1]]);
95 self.skyline.sort();
96 }
97
98 (texture, offset)
99 }
100
101 /// Returns the index of atlas offset in skyline with room for a new tile.
102 ///
103 /// Returns `None` if no room was found in the current texture atlas.
104 pub fn find_space(&self, size: [u32; 2]) -> Option<usize> {
105 if self.textures.is_empty() {
106 return None;
107 };
108
109 let texture = &self.textures[self.atlas];
110 let mut min: Option<(usize, u32)> = None;
111 for i in 0..self.skyline.len() {
112 let a = self.skyline[i];
113 let mut nxt = [texture.get_width(), texture.get_height()];
114 // Ignore next atlas offsets that have smaller y-value,
115 // because they do not interfer.
116 for j in i + 1..self.skyline.len() {
117 let b = self.skyline[j];
118 nxt[0] = b[0];
119 if b[1] > a[1] {
120 break;
121 };
122 }
123 if nxt[0] - a[0] >= size[0] && nxt[1] - a[1] >= size[1] {
124 // There is room for the glyph.
125 if min.is_none()
126 || min.unwrap().1 > nxt[0] - a[0]
127 || self.skyline[min.unwrap().0][1] > a[1]
128 {
129 // Pick the space with smallest y-value.
130 min = Some((i, nxt[0] - a[0]));
131 }
132 }
133 }
134 min.map(|n| n.0)
135 }
136}
137
138impl<T: ImageSize> Default for TexturePacker<T> {
139 fn default() -> TexturePacker<T> {
140 TexturePacker::new()
141 }
142}