speedy2d/
texture_packer.rs

1/*
2 *  Copyright 2021 QuantumBadger
3 *
4 *  Licensed under the Apache License, Version 2.0 (the "License");
5 *  you may not use this file except in compliance with the License.
6 *  You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 *  Unless required by applicable law or agreed to in writing, software
11 *  distributed under the License is distributed on an "AS IS" BASIS,
12 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 *  See the License for the specific language governing permissions and
14 *  limitations under the License.
15 */
16
17use crate::dimen::UVec2;
18use crate::shape::Rectangle;
19use crate::texture_packer::TexturePackerError::NotEnoughSpace;
20
21#[derive(Debug)]
22struct FreeRegion
23{
24    rect: Rectangle<u32>
25}
26
27impl FreeRegion
28{
29    #[inline]
30    fn from_rectangle(rect: Rectangle<u32>) -> Self
31    {
32        FreeRegion { rect }
33    }
34
35    #[inline]
36    fn new(width: u32, height: u32) -> Self
37    {
38        FreeRegion::from_rectangle(Rectangle::new(UVec2::ZERO, UVec2::new(width, height)))
39    }
40}
41
42#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
43pub(crate) enum TexturePackerError
44{
45    NotEnoughSpace
46}
47
48#[derive(Debug)]
49pub(crate) struct TexturePacker
50{
51    areas: Vec<FreeRegion>
52}
53
54impl TexturePacker
55{
56    pub(crate) fn new(width: u32, height: u32) -> Self
57    {
58        TexturePacker {
59            areas: vec![FreeRegion::new(width, height)]
60        }
61    }
62
63    pub(crate) fn try_allocate(
64        &mut self,
65        size: UVec2
66    ) -> Result<Rectangle<u32>, TexturePackerError>
67    {
68        if size.x == 0 || size.y == 0 {
69            return Ok(Rectangle::new(UVec2::ZERO, size));
70        }
71
72        let size = size + UVec2::new(2, 2);
73
74        // Add a one-pixel border around each texture
75        let width = size.x;
76        let height = size.y;
77
78        let mut best_area: Option<&mut FreeRegion> = None;
79
80        for area in &mut self.areas {
81            let area_width = area.rect.width();
82            let area_height = area.rect.height();
83
84            if width > area.rect.width() || height > area.rect.height() {
85                continue;
86            }
87
88            let update_best = if let Some(current_best) = &best_area {
89                current_best.rect.width() >= area_width
90                    && current_best.rect.height() >= area_height
91            } else {
92                true
93            };
94
95            if update_best {
96                best_area = Some(area);
97            }
98        }
99
100        let best_area = best_area.ok_or(NotEnoughSpace)?;
101
102        let alloc_area_with_border =
103            Rectangle::new(*best_area.rect.top_left(), best_area.rect.top_left() + size);
104
105        let space_underneath = Rectangle::new(
106            UVec2::new(
107                best_area.rect.top_left().x,
108                alloc_area_with_border.bottom_right().y
109            ),
110            *best_area.rect.bottom_right()
111        );
112
113        let space_right = Rectangle::new(
114            UVec2::new(
115                alloc_area_with_border.bottom_right().x,
116                best_area.rect.top_left().y
117            ),
118            space_underneath.top_right()
119        );
120
121        if space_right.is_zero_area() {
122            best_area.rect = space_underneath
123        } else {
124            best_area.rect = space_right;
125
126            if !space_underneath.is_zero_area() {
127                self.areas
128                    .push(FreeRegion::from_rectangle(space_underneath));
129            }
130        }
131
132        Ok(Rectangle::new(
133            alloc_area_with_border.top_left() + UVec2::new(1, 1),
134            alloc_area_with_border.bottom_right() - UVec2::new(1, 1)
135        ))
136    }
137}
138
139#[cfg(test)]
140mod test
141{
142
143    use super::*;
144
145    #[test]
146    fn pack_test_fill_four_squares()
147    {
148        let mut packer = TexturePacker::new(64, 64);
149
150        assert_eq!(
151            Ok(Rectangle::from_tuples((1, 1), (31, 31))),
152            packer.try_allocate(UVec2::new(30, 30))
153        );
154
155        assert_eq!(
156            Ok(Rectangle::from_tuples((33, 1), (63, 31))),
157            packer.try_allocate(UVec2::new(30, 30))
158        );
159
160        assert_eq!(
161            Ok(Rectangle::from_tuples((1, 33), (31, 63))),
162            packer.try_allocate(UVec2::new(30, 30))
163        );
164
165        assert_eq!(
166            Ok(Rectangle::from_tuples((33, 33), (63, 63))),
167            packer.try_allocate(UVec2::new(30, 30))
168        );
169
170        assert_eq!(Err(NotEnoughSpace), packer.try_allocate(UVec2::new(30, 30)));
171    }
172
173    #[test]
174    fn pack_test_nonfill_four_squares()
175    {
176        let mut packer = TexturePacker::new(64, 64);
177
178        assert_eq!(
179            Ok(Rectangle::from_tuples((1, 1), (29, 29))),
180            packer.try_allocate(UVec2::new(28, 28))
181        );
182
183        assert_eq!(
184            Ok(Rectangle::from_tuples((31, 1), (59, 29))),
185            packer.try_allocate(UVec2::new(28, 28))
186        );
187
188        assert_eq!(
189            Ok(Rectangle::from_tuples((1, 31), (29, 59))),
190            packer.try_allocate(UVec2::new(28, 28))
191        );
192
193        assert_eq!(
194            Ok(Rectangle::from_tuples((31, 31), (59, 59))),
195            packer.try_allocate(UVec2::new(28, 28))
196        );
197
198        assert_eq!(Err(NotEnoughSpace), packer.try_allocate(UVec2::new(30, 30)));
199    }
200
201    #[test]
202    fn pack_test_uneven_squares()
203    {
204        let mut packer = TexturePacker::new(64, 64);
205
206        assert_eq!(
207            Ok(Rectangle::from_tuples((1, 1), (15, 15))),
208            packer.try_allocate(UVec2::new(14, 14))
209        );
210
211        assert_eq!(
212            Ok(Rectangle::from_tuples((1, 17), (15, 47))),
213            packer.try_allocate(UVec2::new(14, 30))
214        );
215
216        assert_eq!(
217            Ok(Rectangle::from_tuples((17, 17), (47, 47))),
218            packer.try_allocate(UVec2::new(30, 30))
219        );
220
221        assert_eq!(
222            Ok(Rectangle::from_tuples((17, 1), (31, 15))),
223            packer.try_allocate(UVec2::new(14, 14))
224        );
225    }
226}