tex_packer_core/packer/
maxrects.rs

1use super::Packer;
2use crate::config::{MaxRectsHeuristic, PackerConfig};
3use crate::model::{Frame, Rect};
4
5pub struct MaxRectsPacker {
6    config: PackerConfig,
7    border: Rect,
8    free: Vec<Rect>,
9    used: Vec<Rect>,
10    heuristic: MaxRectsHeuristic,
11}
12
13impl MaxRectsPacker {
14    pub fn new(config: PackerConfig, heuristic: MaxRectsHeuristic) -> Self {
15        let pad = config.border_padding;
16        let w = config.max_width.saturating_sub(pad.saturating_mul(2));
17        let h = config.max_height.saturating_sub(pad.saturating_mul(2));
18        let border = Rect::new(pad, pad, w, h);
19        Self {
20            config,
21            border,
22            free: vec![border],
23            used: Vec::new(),
24            heuristic,
25        }
26    }
27
28    fn rect_right_ex(r: &Rect) -> u32 {
29        r.x + r.w
30    }
31    fn rect_bottom_ex(r: &Rect) -> u32 {
32        r.y + r.h
33    }
34
35    fn intersects(a: &Rect, b: &Rect) -> bool {
36        !(a.x >= Self::rect_right_ex(b)
37            || b.x >= Self::rect_right_ex(a)
38            || a.y >= Self::rect_bottom_ex(b)
39            || b.y >= Self::rect_bottom_ex(a))
40    }
41
42    fn place_rect(&mut self, node: &Rect) {
43        if self.config.mr_reference {
44            return self.place_rect_ref(node);
45        }
46        // split all free rectangles that intersect with node
47        let mut new_free: Vec<Rect> = Vec::new();
48        for fr in self.free.iter() {
49            if !Self::intersects(fr, node) {
50                new_free.push(*fr);
51                continue;
52            }
53            let fr_x2 = fr.x + fr.w;
54            let fr_y2 = fr.y + fr.h;
55            let n_x2 = node.x + node.w;
56            let n_y2 = node.y + node.h;
57
58            let ix1 = fr.x.max(node.x);
59            let iy1 = fr.y.max(node.y);
60            let ix2 = fr_x2.min(n_x2);
61            let iy2 = fr_y2.min(n_y2);
62
63            // above
64            if iy1 > fr.y {
65                let h = iy1 - fr.y;
66                new_free.push(Rect::new(fr.x, fr.y, fr.w, h));
67            }
68            // below
69            if iy2 < fr_y2 {
70                let h = fr_y2 - iy2;
71                new_free.push(Rect::new(fr.x, iy2, fr.w, h));
72            }
73            // left
74            if ix1 > fr.x {
75                let w = ix1 - fr.x;
76                let y = iy1;
77                let h = iy2.saturating_sub(iy1);
78                if h > 0 {
79                    new_free.push(Rect::new(fr.x, y, w, h));
80                }
81            }
82            // right
83            if ix2 < fr_x2 {
84                let w = fr_x2 - ix2;
85                let x = ix2;
86                let y = iy1;
87                let h = iy2.saturating_sub(iy1);
88                if h > 0 {
89                    new_free.push(Rect::new(x, y, w, h));
90                }
91            }
92        }
93
94        self.free = new_free;
95        self.prune_free_list();
96        self.used.push(*node);
97    }
98
99    fn place_rect_ref(&mut self, node: &Rect) {
100        let mut new_free: Vec<Rect> = Vec::new();
101        let mut i = 0usize;
102        while i < self.free.len() {
103            let fr = self.free[i];
104            if Self::intersects(&fr, node) {
105                // remove this free rect; split into parts added to new_free
106                self.free.swap_remove(i);
107                self.split_free_node_ref(fr, node, &mut new_free);
108            } else {
109                i += 1;
110            }
111        }
112        // Prune new_free against existing free; and remove dominated among new_free
113        self.prune_new_vs_old(&mut new_free);
114        self.prune_within(&mut new_free);
115        // Merge new into free, then final prune pass
116        self.free.extend(new_free);
117        self.prune_free_list();
118        self.used.push(*node);
119    }
120
121    fn split_free_node_ref(&self, fr: Rect, node: &Rect, out: &mut Vec<Rect>) {
122        let fr_x2 = fr.x + fr.w;
123        let fr_y2 = fr.y + fr.h;
124        let n_x2 = node.x + node.w;
125        let n_y2 = node.y + node.h;
126
127        // Left
128        if node.x > fr.x && node.x < fr_x2 {
129            let w = node.x - fr.x;
130            out.push(Rect::new(fr.x, fr.y, w, fr.h));
131        }
132        // Right
133        if n_x2 < fr_x2 {
134            let x = n_x2;
135            let w = fr_x2 - n_x2;
136            out.push(Rect::new(x, fr.y, w, fr.h));
137        }
138        // Top
139        if node.y > fr.y && node.y < fr_y2 {
140            let h = node.y - fr.y;
141            out.push(Rect::new(fr.x, fr.y, fr.w, h));
142        }
143        // Bottom
144        if n_y2 < fr_y2 {
145            let y = n_y2;
146            let h = fr_y2 - n_y2;
147            out.push(Rect::new(fr.x, y, fr.w, h));
148        }
149        // filter zero areas handled by prune later
150    }
151
152    fn prune_new_vs_old(&mut self, new_free: &mut Vec<Rect>) {
153        // Remove any new rect fully contained in any existing free rect
154        new_free.retain(|nr| !self.free.iter().any(|of| of.contains(nr)) && nr.w > 0 && nr.h > 0);
155        // Remove any existing free rect fully contained in any remaining new rect
156        let mut i = 0;
157        while i < self.free.len() {
158            if new_free.iter().any(|nr| nr.contains(&self.free[i])) {
159                self.free.swap_remove(i);
160            } else {
161                i += 1;
162            }
163        }
164    }
165
166    fn prune_within(&self, v: &mut Vec<Rect>) {
167        let mut i = 0;
168        while i < v.len() {
169            let a = v[i];
170            let a_x2 = a.x + a.w;
171            let a_y2 = a.y + a.h;
172            let mut remove_i = false;
173            let mut j = 0;
174            while j < v.len() {
175                if i == j {
176                    j += 1;
177                    continue;
178                }
179                let b = v[j];
180                let b_x2 = b.x + b.w;
181                let b_y2 = b.y + b.h;
182                if a.x >= b.x && a.y >= b.y && a_x2 <= b_x2 && a_y2 <= b_y2 {
183                    remove_i = true;
184                    break;
185                }
186                j += 1;
187            }
188            if remove_i {
189                v.swap_remove(i);
190            } else {
191                i += 1;
192            }
193        }
194    }
195
196    fn prune_free_list(&mut self) {
197        let mut i = 0;
198        while i < self.free.len() {
199            let mut j = i + 1;
200            let a = self.free[i];
201            let a_right = Self::rect_right_ex(&a);
202            let a_bottom = Self::rect_bottom_ex(&a);
203            let mut remove_i = false;
204            while j < self.free.len() {
205                let b = self.free[j];
206                let b_right = Self::rect_right_ex(&b);
207                let b_bottom = Self::rect_bottom_ex(&b);
208                // if a inside b
209                if a.x >= b.x && a.y >= b.y && a_right <= b_right && a_bottom <= b_bottom {
210                    remove_i = true;
211                    break;
212                }
213                // if b inside a
214                if b.x >= a.x && b.y >= a.y && b_right <= a_right && b_bottom <= a_bottom {
215                    self.free.remove(j);
216                    continue;
217                }
218                j += 1;
219            }
220            if remove_i {
221                self.free.remove(i);
222            } else {
223                i += 1;
224            }
225        }
226    }
227
228    fn score(&self, fr: &Rect, w: u32, h: u32) -> (i32, i32) {
229        let leftover_h = fr.w as i32 - w as i32;
230        let leftover_v = fr.h as i32 - h as i32;
231        let short_fit = leftover_h.abs().min(leftover_v.abs());
232        let long_fit = leftover_h.abs().max(leftover_v.abs());
233        let area_fit = (fr.w * fr.h) as i32 - (w * h) as i32;
234        match self.heuristic {
235            MaxRectsHeuristic::BestAreaFit => (area_fit, short_fit),
236            MaxRectsHeuristic::BestShortSideFit => (short_fit, long_fit),
237            MaxRectsHeuristic::BestLongSideFit => (long_fit, short_fit),
238            MaxRectsHeuristic::BottomLeft => (fr.y as i32, fr.x as i32),
239            MaxRectsHeuristic::ContactPoint => {
240                // maximize contact score: use negative for minimization
241                let contact = self.contact_point_score(fr.x, fr.y, w, h);
242                (-(contact as i32), area_fit)
243            }
244        }
245    }
246
247    fn find_position(&self, w: u32, h: u32) -> Option<(Rect, bool)> {
248        let mut best_score1 = i32::MAX;
249        let mut best_score2 = i32::MAX;
250        let mut best_rect = Rect::new(0, 0, 0, 0);
251        let mut best_rot = false;
252        let mut best_top = u32::MAX; // tie-break: prefer smaller top side (y + h)
253        let mut best_left = u32::MAX; // then prefer smaller x
254
255        for fr in &self.free {
256            // normal
257            if fr.w >= w && fr.h >= h {
258                let (s1, s2) = self.score(fr, w, h);
259                let top = fr.y.saturating_add(h);
260                if s1 < best_score1
261                    || (s1 == best_score1
262                        && (s2 < best_score2
263                            || (s2 == best_score2
264                                && (top < best_top || (top == best_top && fr.x < best_left)))))
265                {
266                    best_score1 = s1;
267                    best_score2 = s2;
268                    best_top = top;
269                    best_left = fr.x;
270                    best_rect = Rect::new(fr.x, fr.y, w, h);
271                    best_rot = false;
272                }
273                // perfect fit early-out
274                if fr.w == w && fr.h == h {
275                    return Some((Rect::new(fr.x, fr.y, w, h), false));
276                }
277            }
278            // rotated
279            if self.config.allow_rotation && fr.w >= h && fr.h >= w {
280                let (s1, s2) = self.score(fr, h, w);
281                let top = fr.y.saturating_add(w);
282                if s1 < best_score1
283                    || (s1 == best_score1
284                        && (s2 < best_score2
285                            || (s2 == best_score2
286                                && (top < best_top || (top == best_top && fr.x < best_left)))))
287                {
288                    best_score1 = s1;
289                    best_score2 = s2;
290                    best_top = top;
291                    best_left = fr.x;
292                    best_rect = Rect::new(fr.x, fr.y, h, w);
293                    best_rot = true;
294                }
295                // perfect fit early-out (rotated)
296                if fr.w == h && fr.h == w {
297                    return Some((Rect::new(fr.x, fr.y, h, w), true));
298                }
299            }
300        }
301
302        if best_rect.w == 0 || best_rect.h == 0 {
303            None
304        } else {
305            Some((best_rect, best_rot))
306        }
307    }
308
309    fn contact_point_score(&self, x: u32, y: u32, w: u32, h: u32) -> u32 {
310        let node = Rect::new(x, y, w, h);
311        let mut score = 0u32;
312        // contact with borders
313        let border_right = self.border.x + self.border.w;
314        let border_bottom = self.border.y + self.border.h;
315        if node.x == self.border.x {
316            score += node.h;
317        }
318        if node.y == self.border.y {
319            score += node.w;
320        }
321        if node.x + node.w == border_right {
322            score += node.h;
323        }
324        if node.y + node.h == border_bottom {
325            score += node.w;
326        }
327
328        // contact with used rectangles
329        for u in &self.used {
330            // vertical contact (left/right edges)
331            if node.x == u.x + u.w || u.x == node.x + node.w {
332                let overlap = overlap_1d(node.y, node.y + node.h, u.y, u.y + u.h);
333                score += overlap;
334            }
335            // horizontal contact (top/bottom edges)
336            if node.y == u.y + u.h || u.y == node.y + node.h {
337                let overlap = overlap_1d(node.x, node.x + node.w, u.x, u.x + u.w);
338                score += overlap;
339            }
340        }
341        score
342    }
343
344    pub fn free_list_len(&self) -> usize {
345        self.free.len()
346    }
347}
348
349fn overlap_1d(a1: u32, a2: u32, b1: u32, b2: u32) -> u32 {
350    let start = a1.max(b1);
351    let end = a2.min(b2);
352    end.saturating_sub(start)
353}
354
355impl<K: Clone> Packer<K> for MaxRectsPacker {
356    fn can_pack(&self, rect: &Rect) -> bool {
357        let w = rect.w + self.config.texture_padding + self.config.texture_extrusion * 2;
358        let h = rect.h + self.config.texture_padding + self.config.texture_extrusion * 2;
359        self.find_position(w, h).is_some()
360    }
361
362    fn pack(&mut self, key: K, rect: &Rect) -> Option<Frame<K>> {
363        let w = rect.w + self.config.texture_padding + self.config.texture_extrusion * 2;
364        let h = rect.h + self.config.texture_padding + self.config.texture_extrusion * 2;
365        if let Some((place, rotated)) = self.find_position(w, h) {
366            self.place_rect(&place);
367            // Report atlas frame rectangle in stored orientation (post-rotation dimensions),
368            // and offset content inside reserved slot by extrude + half padding (symmetric)
369            let (fw, fh) = if rotated {
370                (rect.h, rect.w)
371            } else {
372                (rect.w, rect.h)
373            };
374            let pad_half = self.config.texture_padding / 2;
375            let off = self.config.texture_extrusion + pad_half;
376            let frame = Rect::new(
377                place.x.saturating_add(off),
378                place.y.saturating_add(off),
379                fw,
380                fh,
381            );
382            Some(Frame {
383                key,
384                frame,
385                rotated,
386                trimmed: false,
387                source: *rect,
388                source_size: (rect.w, rect.h),
389            })
390        } else {
391            None
392        }
393    }
394}