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}