Skip to main content

fastpack_core/algorithms/
basic.rs

1use crate::{
2    error::CoreError,
3    types::rect::{Rect, Size},
4};
5
6use super::packer::{PackInput, PackOutput, Packer, PlacedSprite, Placement};
7
8/// Basic strip packing algorithm.
9///
10/// Sprites are sorted by descending height and placed into rows left-to-right.
11/// A new row starts when the next sprite does not fit in the remaining width of
12/// the current row. Each row's height equals the tallest sprite placed in it.
13///
14/// This algorithm is fast with predictable output but produces larger atlases
15/// than MaxRects. It does not attempt rotation.
16#[derive(Default)]
17pub struct Basic;
18
19impl Packer for Basic {
20    fn pack(&self, input: PackInput) -> Result<PackOutput, CoreError> {
21        if input.sprites.is_empty() {
22            return Err(CoreError::NoSprites);
23        }
24        Ok(pack_basic(input))
25    }
26
27    fn name(&self) -> &'static str {
28        "basic"
29    }
30}
31
32fn pack_basic(input: PackInput) -> PackOutput {
33    let cfg = &input.config;
34    let bp = cfg.border_padding;
35    let sp = cfg.shape_padding;
36
37    let mut sprites = input.sprites;
38    // Tallest-first minimises wasted space at row ends.
39    sprites.sort_unstable_by(|a, b| b.image.height().cmp(&a.image.height()));
40
41    let max_canvas_w = cfg.max_width.saturating_sub(bp * 2);
42    let max_canvas_h = cfg.max_height.saturating_sub(bp * 2);
43
44    let mut placed = Vec::with_capacity(sprites.len());
45    let mut overflow = Vec::new();
46
47    let mut cursor_x: u32 = 0;
48    let mut cursor_y: u32 = 0;
49    let mut row_h: u32 = 0;
50    let mut atlas_w: u32 = 0;
51    let mut atlas_h: u32 = 0;
52
53    for sprite in sprites {
54        let sw = sprite.image.width();
55        let sh = sprite.image.height();
56        let fw = sw + sp;
57        let fh = sh + sp;
58
59        // Wrap to a new row when the sprite doesn't fit horizontally.
60        if cursor_x > 0 && cursor_x + fw > max_canvas_w {
61            cursor_y += row_h;
62            cursor_x = 0;
63            row_h = 0;
64        }
65
66        // Vertical overflow.
67        if cursor_y + sh > max_canvas_h {
68            overflow.push(sprite);
69            continue;
70        }
71
72        let dest_x = bp + cursor_x;
73        let dest_y = bp + cursor_y;
74
75        atlas_w = atlas_w.max(cursor_x + sw);
76        atlas_h = atlas_h.max(cursor_y + sh);
77        row_h = row_h.max(fh);
78
79        placed.push(PlacedSprite {
80            placement: Placement {
81                sprite_id: sprite.id.clone(),
82                dest: Rect::new(dest_x, dest_y, sw, sh),
83                rotated: false,
84            },
85            sprite,
86        });
87
88        cursor_x += fw;
89    }
90
91    let raw_w = if placed.is_empty() {
92        0
93    } else {
94        atlas_w + bp * 2
95    };
96    let raw_h = if placed.is_empty() {
97        0
98    } else {
99        atlas_h + bp * 2
100    };
101
102    let mut w = cfg.size_constraint.apply(raw_w).min(cfg.max_width);
103    let mut h = cfg.size_constraint.apply(raw_h).min(cfg.max_height);
104
105    if cfg.force_square {
106        let side = w.max(h);
107        w = side;
108        h = side;
109    }
110
111    PackOutput {
112        placed,
113        atlas_size: Size { w, h },
114        overflow,
115    }
116}