chuot_packer/lib.rs
1#![forbid(unsafe_code)]
2
3//! 2D texture packer based on [`texture_packer`](https://docs.rs/texture_packer/latest/texture_packer/).
4//!
5//! Removes all features for actually creating the textures and allows inserting already defined rectangles.
6
7/// 2D rectangle packer.
8#[derive(Debug, Clone)]
9pub struct Packer {
10 /// Max width of the output rectangle.
11 max_width: u16,
12 /// Max height of the output rectangle.
13 max_height: u16,
14 /// Skylines for the skyline packing algorithm.
15 skylines: Vec<Skyline>,
16}
17
18impl Packer {
19 /// Setup a new empty packer with a size.
20 ///
21 /// # Arguments
22 ///
23 /// * `(max_width, max_height)` - Tuple of maximum size of the output atlas.
24 #[inline]
25 #[must_use]
26 pub fn new(max_size: impl Into<(u16, u16)>) -> Self {
27 // Reduce compilation times
28 fn inner((max_width, max_height): (u16, u16)) -> Packer {
29 // Start with a single skyline of the full width
30 let skyline = Skyline {
31 x: 0,
32 y: 0,
33 width: max_width,
34 };
35 let skylines = vec![skyline];
36
37 Packer {
38 max_width,
39 max_height,
40 skylines,
41 }
42 }
43
44 inner(max_size.into())
45 }
46
47 /// Fill the packer with already existing rectangles.
48 ///
49 /// The rectangles should be as close to Y = 0 as much as possible, to efficiently add new items.
50 ///
51 /// # Arguments
52 ///
53 /// * `existing_rectangles` - Iterator of pre-set tuple rectangles with `(x, y, width, height)` that should be filled into the positions.
54 ///
55 /// # Panics
56 ///
57 /// - When any rectangle is out of bounds.
58 #[inline]
59 #[must_use]
60 pub fn with_existing_rectangles_iter<R>(
61 mut self,
62 existing_rectangles: impl Iterator<Item = R>,
63 ) -> Self
64 where
65 R: Into<(u16, u16, u16, u16)>,
66 {
67 // Reduce compilation speed
68 fn inner(this: &mut Packer, (x, y, width, height): (u16, u16, u16, u16)) {
69 let y = y + height;
70
71 // Construct the new skyline to check for overlaps and inserts
72 let new_skyline = Skyline { x, y, width };
73
74 // Split overlapping skylines
75 let mut index = 0;
76 let mut index_to_insert = 0;
77 while index < this.skylines.len() {
78 let skyline = this.skylines[index];
79
80 if skyline.y > new_skyline.y || !skyline.overlaps(new_skyline) {
81 // Only take higher skylines that also overlap
82 continue;
83 }
84
85 if skyline.left() >= new_skyline.left() && skyline.right() <= new_skyline.right() {
86 // Skyline is inside the new one
87 this.skylines.remove(index);
88 continue;
89 }
90
91 if skyline.left() < new_skyline.left() && skyline.right() > new_skyline.right() {
92 // Old skyline is inside the new one
93
94 // Insert the slice after
95 this.skylines.insert(index + 1, Skyline {
96 x: new_skyline.right(),
97 y: skyline.y,
98 width: skyline.right() - new_skyline.right(),
99 });
100
101 // Cut the right part of the old skyline
102 this.skylines[index].width = new_skyline.left() - skyline.left();
103
104 // Insert between the recently split one
105 index_to_insert = index + 1;
106 break;
107 }
108
109 if skyline.left() < new_skyline.left() {
110 // Cut the right part of the old skyline
111 this.skylines[index].width = new_skyline.left() - skyline.left();
112
113 // Insert after this skyline
114 index_to_insert = index + 1;
115 }
116
117 if skyline.right() > new_skyline.right() {
118 // Cut the left part of the old skyline
119 this.skylines[index].width = skyline.right() - new_skyline.right();
120 this.skylines[index].x = new_skyline.right();
121
122 // Insert before this skyline
123 index_to_insert = index;
124 break;
125 }
126
127 index += 1;
128 }
129 // Insert the skyline here
130 this.skylines.insert(index_to_insert, new_skyline);
131
132 // Merge the skylines on the same height
133 this.merge();
134 }
135
136 for rect in existing_rectangles {
137 inner(&mut self, rect.into());
138 }
139
140 self
141 }
142
143 /// Insert and pack a rectangle.
144 ///
145 /// # Arguments
146 ///
147 /// * `(width, height)` - Size tuple of the rectangle to place in the atlas.
148 ///
149 /// # Returns
150 ///
151 /// - `None` when there's not enough space to pack the rectangle.
152 /// - Offset tuple `(width, height)` inside the atlas when the rectangle fits.
153 #[inline]
154 pub fn insert(&mut self, rectangle_size: impl Into<(u16, u16)>) -> Option<(u16, u16)> {
155 // Reduce compilation times
156 fn inner(
157 this: &mut Packer,
158 (rectangle_width, rectangle_height): (u16, u16),
159 ) -> Option<(u16, u16)> {
160 // Find the rectangle with the skyline, keep the bottom and width as small as possible
161 let mut bottom = u16::MAX;
162 let mut width = u16::MAX;
163 let mut result = None;
164
165 // Try to find the skyline gap with the smallest Y
166 for (index, skyline) in this.skylines.iter().enumerate() {
167 if let Some((offset_x, offset_y)) =
168 this.can_put(index, rectangle_width, rectangle_height)
169 {
170 let rect_bottom = offset_y + rectangle_height;
171 if rect_bottom < bottom || (rect_bottom == bottom && skyline.width < width) {
172 bottom = rect_bottom;
173 width = skyline.width;
174 result = Some((offset_x, offset_y, index));
175 }
176 }
177 }
178
179 // If no rect is found do nothing
180 let (x, y, index) = result?;
181
182 // Insert the skyline
183 this.split(index, x, y, rectangle_width, rectangle_height);
184
185 // Merge the skylines on the same height
186 this.merge();
187
188 Some((x, y))
189 }
190
191 inner(self, rectangle_size.into())
192 }
193
194 /// Return the rect fitting in a skyline if possible.
195 fn can_put(&self, skyline_index: usize, width: u16, height: u16) -> Option<(u16, u16)> {
196 // Right side of the rectangle, doesn't change because only the Y position will shift in the next loop
197 let x = self.skylines[skyline_index].x;
198 let right = x + width;
199
200 let mut y = 0;
201 let mut width_left = width;
202
203 // Loop over each skyline to the right starting from the current position to try to find a spot where the rectangle can be put
204 self.skylines[skyline_index..].iter().find_map(|skyline| {
205 // Get the highest position of each available skyline
206 y = y.max(skyline.y);
207
208 // Check if the rectangle still fits in the output
209 let bottom = y + height;
210 if right > self.max_width || bottom > self.max_height {
211 return None;
212 }
213
214 if skyline.width >= width_left {
215 // Rectangle fits, return it
216 Some((x, y))
217 } else {
218 width_left -= skyline.width;
219
220 None
221 }
222 })
223 }
224
225 /// Split the skylines at the index.
226 ///
227 /// Will shorten or remove the overlapping skylines to the right.
228 fn split(&mut self, skyline_index: usize, x: u16, y: u16, width: u16, height: u16) {
229 // Add the new skyline
230 let y = y + height;
231 self.skylines.insert(skyline_index, Skyline { x, y, width });
232
233 // Shrink all skylines to the right of the inserted skyline
234 self.shrink(skyline_index);
235 }
236
237 /// Shrink all skylines from the right of the index.
238 fn shrink(&mut self, skyline_index: usize) {
239 let index = skyline_index + 1;
240 while index < self.skylines.len() {
241 let previous = &self.skylines[index - 1];
242 let current = &self.skylines[index];
243
244 assert!(previous.left() <= current.left());
245
246 // Check if the previous overlaps the current
247 if current.left() <= previous.right() {
248 let shrink = previous.right() - current.left();
249 if current.width <= shrink {
250 // Skyline is fully overlapped, remove it and move to the next
251 self.skylines.remove(index);
252 } else {
253 // Skyline is partially overlapped, shorten it
254 self.skylines[index].x += shrink;
255 self.skylines[index].width -= shrink;
256 break;
257 }
258 } else {
259 break;
260 }
261 }
262 }
263
264 /// Merge neighbor skylines on the same height.
265 fn merge(&mut self) {
266 let mut index = 1;
267 while index < self.skylines.len() {
268 let previous = &self.skylines[index - 1];
269 let current = &self.skylines[index];
270
271 if previous.y == current.y {
272 // Merge the skylines
273 self.skylines[index - 1].width += current.width;
274 self.skylines.remove(index);
275 index -= 1;
276 }
277
278 index += 1;
279 }
280 }
281}
282
283/// Single skyline with only a width.
284#[derive(Debug, Clone, Copy)]
285struct Skyline {
286 /// X position on the rectangle.
287 x: u16,
288 /// Y position on the rectangle.
289 y: u16,
290 /// Width of the line.
291 width: u16,
292}
293
294impl Skyline {
295 /// Left split position.
296 #[inline(always)]
297 pub const fn left(self) -> u16 {
298 self.x
299 }
300
301 /// Right split position.
302 #[inline(always)]
303 pub const fn right(self) -> u16 {
304 self.x + self.width
305 }
306
307 /// Whether it overlaps with another skyline.
308 #[inline(always)]
309 pub const fn overlaps(self, other: Self) -> bool {
310 self.right() >= other.left() && other.right() >= self.left()
311 }
312}
313
314#[cfg(test)]
315mod tests {
316 use super::*;
317
318 #[test]
319 fn packer_fill_squares() {
320 // Filling the 32x32 square with 64 equal blocks of 4x4 should fill the box exactly
321 let mut packer = Packer::new((32, 32));
322 for _ in 0..64 {
323 assert!(packer.insert((4, 4)).is_some());
324 }
325
326 // Filling the 32x32 square with 128 equal blocks of 4x2 should fill the box exactly
327 let mut packer = Packer::new((32, 32));
328 for _ in 0..128 {
329 assert!(packer.insert((4, 2)).is_some());
330 }
331 }
332
333 #[test]
334 fn packer_fill_squares_overflow() {
335 // Filling the 32x32 square with 64 + 1 equal blocks of 4x4 should overflow the box
336 let mut packer = Packer::new((32, 32));
337 for _ in 0..64 {
338 assert!(packer.insert((4, 4)).is_some());
339 }
340 assert!(packer.insert((4, 4)).is_none());
341 }
342
343 #[test]
344 fn existing_rects() {
345 // Filling the 32x32 square with 63 + 1 predefined + 1 equal blocks of 4x4 should overflow the box
346 let mut packer =
347 Packer::new((32, 32)).with_existing_rectangles_iter(std::iter::once((0, 0, 4, 4)));
348 for _ in 0..63 {
349 assert!(packer.insert((4, 4)).is_some());
350 }
351 assert!(packer.insert((4, 4)).is_none());
352
353 // Filling the 32x32 square with 63 + 1 predefined + 1 equal blocks of 4x4 should overflow the box
354 let mut packer =
355 Packer::new((32, 32)).with_existing_rectangles_iter(std::iter::once((28, 0, 4, 4)));
356 for _ in 0..63 {
357 assert!(packer.insert((4, 4)).is_some());
358 }
359 assert!(packer.insert((4, 4)).is_none());
360
361 // Filling the 32x32 square with 63 + 1 predefined + 1 equal blocks of 4x4 should overflow the box
362 let mut packer =
363 Packer::new((32, 32)).with_existing_rectangles_iter(std::iter::once((4, 0, 4, 4)));
364 for _ in 0..63 {
365 assert!(packer.insert((4, 4)).is_some());
366 }
367 assert!(packer.insert((4, 4)).is_none());
368 }
369}