binpack2d/binpack/
maxrects.rs

1//! A two-dimensional rectangle bin packer using the *MAXRECTS* data structure to keep track of
2//! free space of the bin where rectangles may be placed. A good choice for most use cases.
3//!
4//! # Quick Start
5//!
6//! This example demonstrates the usage of the "MaxRects" bin-packing algorithm. A comparable code
7//! sample for a high-level implementation can be looked up in the [`binpack`] module description.
8//!
9//! [`binpack`]: crate::binpack
10//!
11//! ```rust
12//! use binpack2d::{BinPacker, Dimension};
13//! use binpack2d::maxrects::{Heuristic, MaxRectsBin};
14//!
15//! // Create a number of items to be placed into the bin.
16//! let items_to_place = vec![
17//!     // Items with autogenerated identifiers.
18//!     // Identifiers start at 1 and increment by 1 per call.
19//!     Dimension::new(188, 300),
20//!     Dimension::new(32, 32),
21//!     Dimension::new(420, 512),
22//!     Dimension::new(620, 384),
23//!     // Three more items with explicit identifiers: -1, 300, and 9528 respectively
24//!     Dimension::with_id(-1, 160, 214, 0),
25//!     Dimension::with_id(300, 384, 640, 0),
26//!     Dimension::with_id(9528, 400, 200, 0),
27//! ];
28//!
29//! // Create a bin with the dimensions 1024x1024
30//! let mut bin = MaxRectsBin::new(1024, 1024);
31//!
32//! // Perform the bin packing operation on the list of items, using tetris-style placement rule.
33//! let (inserted, rejected) = bin.insert_list(&items_to_place, Heuristic::BottomLeftRule);
34//!
35//! // Let's see if our item with id=9528 was successfully inserted...
36//! if let Some(rect) = &bin.find_by_id(9528) {
37//!     println!("Item with id {} was placed into the bin at position (x: {}, y: {})",
38//!              rect.id(), rect.x(), rect.y());
39//! } else {
40//!     println!("Item with id 9528 could not be placed into the bin.");
41//! }
42//!
43//! // List all successfully inserted rectangles.
44//! if !inserted.is_empty() {
45//!     inserted.iter().for_each(|rect| println!("Inserted: {}", rect));
46//! } else {
47//!     println!("No rectangles were added to the bin.");
48//! }
49//!
50//! // List all items which could not be inserted into the bin.
51//! if !rejected.is_empty() {
52//!     rejected.iter().for_each(|item| println!("Rejected: {}", item));
53//! } else {
54//!     println!("No items were rejected.");
55//! }
56//!
57//! println!("Occupancy of the bin: {:.1} %", bin.occupancy() * 100.0);
58//! ```
59
60use crate::binpack::BinError;
61use std::fmt::{Display, Formatter};
62use std::slice::Iter;
63
64use super::{visualize_bin, BinPacker};
65use crate::dimension::Dimension;
66use crate::rectangle::Rectangle;
67
68/// List of supported heuristic rules for *MAXRECTS* data structures that can be used when deciding
69/// where to place a new rectangle.
70#[derive(Copy, Clone, Debug, PartialEq)]
71pub enum Heuristic {
72    /// Positions the rectangle against the short side of a free rectangle into which it fits the best.
73    BestShortSideFit,
74    /// Positions the rectangle against the long side of a free rectangle into which it fits the best.
75    BestLongSideFit,
76    /// Positions the rectangle into the smallest free rect into which it fits.
77    BestAreaFit,
78    /// Does the Tetris placement.
79    BottomLeftRule,
80    /// Chooses the placement where the rectangle touches other rectangles as much as possible.
81    ///
82    /// **Note:** Average packing speed of this rule is worse than that of the other rules.
83    ContactPointRule,
84}
85
86/// A two-dimensional rectangle bin packer using the *MAXRECTS* data structure and different
87/// bin packing algorithms that use this structure.
88///
89/// It can be used to pack multiple rectangles of arbitrary size into a "bin" of rectangular shape
90/// with the goal to add as many rectangles as possible into the bin.
91#[derive(Clone, Debug, PartialEq)]
92pub struct MaxRectsBin {
93    /// Horizontal dimension of the bin.
94    bin_width: i32,
95    /// Vertical dimension of the bin.
96    bin_height: i32,
97    /// Keeps track of used areas within the bin.
98    rects_used: Vec<Rectangle>,
99    /// Keeps track of free areas within the bin.
100    rects_free: Vec<Rectangle>,
101
102    // Internally used to speed up packing operations
103    new_rects_free_size: usize,
104    // Internally used to speed up packing operations
105    new_rects_free: Vec<Rectangle>,
106
107    /// Implicitly used for the methods defined by the `BinPacker` trait.
108    default_heuristic: Heuristic,
109}
110
111impl BinPacker for MaxRectsBin {
112    fn width(&self) -> i32 {
113        self.bin_width
114    }
115
116    fn height(&self) -> i32 {
117        self.bin_height
118    }
119
120    fn clear_with(&mut self, capacity: usize) {
121        self.rects_used.clear();
122        self.rects_used.shrink_to(capacity.max(4));
123        self.rects_free.clear();
124        self.rects_free.shrink_to((capacity * 4).max(16));
125        self.rects_free.push(Rectangle::new(
126            0,
127            0,
128            Dimension::with_id(0, self.bin_width, self.bin_height, 0),
129        ));
130    }
131
132    fn grow(&mut self, dw: u32, dh: u32) {
133            if dw > 0 {
134                self.rects_free.push(Rectangle::new(
135                    self.bin_width,
136                    0,
137                    Dimension::with_id(0, dw as i32, self.bin_height, 0)
138                ));
139                self.bin_width += dw as i32;
140            }
141
142            if dh > 0 {
143                self.rects_free.push(Rectangle::new(
144                    0,
145                    self.bin_height,
146                    Dimension::with_id(0, self.bin_width, dh as i32, 0)
147                ));
148                self.bin_height += dh as i32;
149            }
150    }
151
152    fn shrink(&mut self, binary: bool) {
153        if self.rects_used.is_empty() {
154            return;
155        }
156
157        let mut min_x = i32::MAX;
158        let mut min_y = i32::MAX;
159        let mut max_x = i32::MIN;
160        let mut max_y = i32::MIN;
161
162        // finding borders
163        for rect in &self.rects_used {
164            min_x = min_x.min(rect.x_total());
165            min_y = min_y.min(rect.y_total());
166            max_x = max_x.max(rect.x_total() + rect.width_total());
167            max_y = max_y.max(rect.y_total() + rect.height_total());
168        }
169
170        let mut new_width = max_x - min_x;
171        let mut new_height = max_y - min_y;
172
173        if binary {
174            // attempt to shrink to the next lower power of two
175            let mut cur_width = self.bin_width;
176            while new_width <= (cur_width >> 1) {
177                cur_width >>= 1;
178            }
179            new_width = cur_width;
180
181            let mut cur_height = self.bin_height;
182            while new_height <= (cur_height >> 1) {
183                cur_height >>= 1;
184            }
185            new_height = cur_height;
186        }
187
188        // adjusting rectangle positions
189        if new_width != self.bin_width || new_height != self.bin_height {
190            if min_x > 0 || min_y > 0 {
191                for rect in &mut self.rects_used {
192                    rect.set_x_total(rect.x_total() - min_x);
193                    rect.set_y_total(rect.y_total() - min_y);
194                }
195                for rect in &mut self.rects_free {
196                    rect.set_x_total(rect.x_total() - min_x);
197                    rect.set_y_total(rect.y_total() - min_y);
198                }
199                for rect in &mut self.new_rects_free {
200                    rect.set_x_total(rect.x_total() - min_x);
201                    rect.set_y_total(rect.y_total() - min_y);
202                }
203            }
204
205            self.bin_width = new_width;
206            self.bin_height = new_height;
207        }
208    }
209
210    fn insert(&mut self, dim: &Dimension) -> Option<Rectangle> {
211        self.insert(dim, self.default_heuristic)
212    }
213
214    fn insert_list(&mut self, nodes: &[Dimension]) -> (Vec<Rectangle>, Vec<Dimension>) {
215        self.insert_list(nodes, self.default_heuristic)
216    }
217
218    fn occupancy(&self) -> f32 {
219        if self.bin_width == 0 || self.bin_height == 0 {
220            return 0.0;
221        }
222
223        let area: i64 = self.rects_used.iter().map(|r| r.dim().area()).sum();
224
225        area as f32 / (self.bin_width * self.bin_height) as f32
226    }
227
228    fn as_slice(&self) -> &[Rectangle] {
229        &self.rects_used
230    }
231
232    fn is_empty(&self) -> bool {
233        self.rects_used.is_empty()
234    }
235
236    fn len(&self) -> usize {
237        self.rects_used.len()
238    }
239
240    fn iter(&self) -> Iter<'_, Rectangle> {
241        self.rects_used.iter()
242    }
243
244    fn find_by_id(&self, id: isize) -> Option<Rectangle> {
245        self.rects_used
246            .iter()
247            .find(|&n| n.dim().id() == id)
248            .map(|r| r.to_owned())
249    }
250
251    fn visualize(&self) -> String {
252        if let Some(output) = visualize_bin(self.bin_width, self.bin_height, &self.rects_used) {
253            output
254        } else {
255            format!("{self}")
256        }
257    }
258}
259
260impl MaxRectsBin {
261    /// Creates an empty bin of the given size.
262    ///
263    /// Minimum width and height of a bin is 1.
264    pub fn new(width: i32, height: i32) -> Self {
265        Self::with_capacity(width, height, 4)
266    }
267
268    /// Creates an empty bin of the given size and reserves space for at least `capacity` number
269    /// of mapped rectangle to improve performance.
270    ///
271    /// Minimum width and height of a bin is 1.
272    pub fn with_capacity(width: i32, height: i32, capacity: usize) -> Self {
273        let mut result = Self {
274            bin_width: width.max(1),
275            bin_height: height.max(1),
276            rects_used: Vec::with_capacity(capacity.max(4)),
277            rects_free: Vec::with_capacity((capacity * 4).max(4 * 4)),
278            new_rects_free_size: 0,
279            new_rects_free: Vec::new(),
280            default_heuristic: Heuristic::BestShortSideFit,
281        };
282        result.rects_free.push(Rectangle::new(
283            0,
284            0,
285            Dimension::with_id(0, result.bin_width, result.bin_height, 0),
286        ));
287
288        result
289    }
290
291    /// Returns the default [`Heuristic`] rule, which is used by the [`BinPacker`] trait's
292    /// [`insert`] and [`insert_list`] methods.
293    ///
294    /// [`insert`]: BinPacker::insert
295    /// [`insert_list`]: BinPacker::insert_list
296    pub fn default_rule(&self) -> Heuristic {
297        self.default_heuristic
298    }
299
300    /// Can be used to override the default [`Heuristic`] rule, which is used by the [`BinPacker`]
301    /// trait's [`insert`] and [`insert_list`] methods.
302    ///
303    /// [`insert`]: BinPacker::insert
304    /// [`insert_list`]: BinPacker::insert_list
305    pub fn set_default_rule(&mut self, rule: Heuristic) {
306        self.default_heuristic = rule;
307    }
308
309    /// Inserts a single [`Dimension`] object into the bin.
310    ///
311    /// `dim` refers to the object to be packed into the bin.
312    ///
313    /// `rule` specifies the rectangle placement rule to use for the packing operation.
314    ///
315    /// Returns a copy of the packed [`Rectangle`] if the object was inserted successful,
316    /// or `None` otherwise.
317    pub fn insert(&mut self, dim: &Dimension, rule: Heuristic) -> Option<Rectangle> {
318        // Empty or too big dimension objects are always rejected
319        if dim.is_empty()
320            || dim.width_total() > self.bin_width
321            || dim.height_total() > self.bin_height
322        {
323            return None;
324        }
325
326        let (_, _, result) = match rule {
327            Heuristic::BestShortSideFit => self.find_bssf(dim),
328            Heuristic::BestLongSideFit => self.find_blsf(dim),
329            Heuristic::BestAreaFit => self.find_baf(dim),
330            Heuristic::BottomLeftRule => self.find_blr(dim),
331            Heuristic::ContactPointRule => self.find_cpr(dim),
332        };
333
334        if let Some(new_node) = &result {
335            self.place_rect(new_node);
336
337            Some(*new_node)
338        } else {
339            None
340        }
341    }
342
343    /// Attempts to insert the given list of [`Dimension`] objects into the bin.
344    ///
345    /// `nodes` specifies the list of [`Dimension`] objects to insert. All successfully inserted
346    /// objects will be removed from the list in the process.
347    ///
348    /// `rule` specifies the rectangle placement rule to use for the packing operations.
349    ///
350    /// Returns a list with all successfully inserted [`Rectangle`] objects.
351    ///
352    /// This method performs slower than [`insert`], but may result in more tightly
353    /// packed bins for greater numbers of dimension objects.
354    ///
355    /// [`insert`]: MaxRectsBin::insert
356    pub fn insert_list(
357        &mut self,
358        nodes: &[Dimension],
359        rule: Heuristic,
360    ) -> (Vec<Rectangle>, Vec<Dimension>) {
361        let mut inserted = Vec::with_capacity(nodes.len());
362        let mut rejected = nodes.to_vec();
363
364        while !rejected.is_empty() {
365            let mut best_score1 = i32::MAX;
366            let mut best_score2 = i32::MAX;
367            let mut best_index = None;
368            let mut best_node = None;
369
370            for (i, dim) in rejected.iter().enumerate() {
371                let (score1, score2, new_node) = self.score_rect(dim, rule);
372
373                if score1 < best_score1 || (score1 == best_score1 && score2 < best_score2) {
374                    best_score1 = score1;
375                    best_score2 = score2;
376                    best_index = Some(i);
377                    best_node = new_node;
378                }
379            }
380
381            if best_index.is_none() {
382                break;
383            }
384
385            debug_assert!(best_node.is_some());
386
387            self.place_rect(&best_node.unwrap());
388            inserted.push(best_node.unwrap());
389            rejected.swap_remove(best_index.unwrap());
390        }
391
392        (inserted, rejected)
393    }
394
395    /// Computes the placement score for placing the given `Dimension` with the given rule.
396    ///
397    /// Returns a tuple consisting of the primary and secondary placement scores, as well as
398    /// the `Rectangle` structure where the requested `Dimension` can be placed.
399    fn score_rect(&self, dim: &Dimension, rule: Heuristic) -> (i32, i32, Option<Rectangle>) {
400        let (mut score1, mut score2, new_node) = match rule {
401            Heuristic::BestShortSideFit => self.find_bssf(dim),
402            Heuristic::BestLongSideFit => self.find_blsf(dim),
403            Heuristic::BestAreaFit => self.find_baf(dim),
404            Heuristic::BottomLeftRule => self.find_blr(dim),
405            Heuristic::ContactPointRule => self.find_cpr(dim),
406        };
407
408        // Cannot fit the current rectangle.
409        if new_node.is_none() {
410            score1 = i32::MAX;
411            score2 = i32::MAX;
412        }
413
414        (score1, score2, new_node)
415    }
416
417    /// Places the given rectangle into the bin.
418    fn place_rect(&mut self, rect: &Rectangle) {
419        let mut idx = 0usize;
420        while idx < self.rects_free.len() {
421            let node = self.rects_free[idx];
422            if self.split_free_node(&node, rect) {
423                self.rects_free.swap_remove(idx);
424                continue;
425            }
426            idx += 1;
427        }
428
429        self.prune_free_list();
430
431        self.rects_used.push(rect.to_owned());
432    }
433
434    /// Attempts to find the best rectangle position in the bin, using the [`Heuristic::BottomLeftRule`] rule.
435    fn find_blr(&self, dim: &Dimension) -> (i32, i32, Option<Rectangle>) {
436        let mut result = None;
437
438        let mut best_x = i32::MAX;
439        let mut best_y = i32::MAX;
440        for rect in &self.rects_free {
441            if rect.width_total() >= dim.width_total() && rect.height_total() >= dim.height_total()
442            {
443                let top_y = rect.y_total() + dim.height_total();
444
445                if top_y < best_y || (top_y == best_y && rect.x_total() < best_x) {
446                    let best_node = result.get_or_insert_with(|| Rectangle::new(0, 0, *dim));
447                    best_node.set_location_total(rect.x_total(), rect.y_total());
448                    best_node.dim_mut().set_dimension(dim.width(), dim.height());
449                    best_x = rect.x_total();
450                    best_y = top_y;
451                }
452            }
453        }
454
455        (best_y, best_x, result)
456    }
457
458    /// Attempts to find the best rectangle position in the bin, using the [`Heuristic::BestShortSideFit`] rule.
459    fn find_bssf(&self, dim: &Dimension) -> (i32, i32, Option<Rectangle>) {
460        let mut result = None;
461
462        let mut best_short_side_fit = i32::MAX;
463        let mut best_long_size_fit = i32::MAX;
464        for rect in &self.rects_free {
465            if rect.width_total() >= dim.width_total() && rect.height_total() >= dim.height_total()
466            {
467                let leftover_h = rect.width_total().abs_diff(dim.width_total()) as i32;
468                let leftover_v = rect.height_total().abs_diff(dim.height_total()) as i32;
469                let short_side_fit = leftover_h.min(leftover_v);
470                let long_side_fit = leftover_h.max(leftover_v);
471
472                if short_side_fit < best_short_side_fit
473                    || (short_side_fit == best_short_side_fit && long_side_fit < best_long_size_fit)
474                {
475                    let best_node = result.get_or_insert_with(|| Rectangle::new(0, 0, *dim));
476                    best_node.set_location_total(rect.x_total(), rect.y_total());
477                    best_node.dim_mut().set_dimension(dim.width(), dim.height());
478                    best_short_side_fit = short_side_fit;
479                    best_long_size_fit = long_side_fit;
480                }
481            }
482        }
483
484        (best_short_side_fit, best_long_size_fit, result)
485    }
486
487    /// Attempts to find the best rectangle position in the bin, using the [`Heuristic::BestLongSideFit`] rule.
488    fn find_blsf(&self, dim: &Dimension) -> (i32, i32, Option<Rectangle>) {
489        let mut result = None;
490
491        let mut best_short_side_fit = i32::MAX;
492        let mut best_long_size_fit = i32::MAX;
493        for rect in &self.rects_free {
494            if rect.width_total() >= dim.width_total() && rect.height_total() >= dim.height_total()
495            {
496                let leftover_h = rect.width_total().abs_diff(dim.width_total()) as i32;
497                let leftover_v = rect.height_total().abs_diff(dim.height_total()) as i32;
498                let short_side_fit = leftover_h.min(leftover_v);
499                let long_side_fit = leftover_h.max(leftover_v);
500
501                if long_side_fit < best_long_size_fit
502                    || (long_side_fit == best_long_size_fit && short_side_fit < best_short_side_fit)
503                {
504                    let best_node = result.get_or_insert_with(|| Rectangle::new(0, 0, *dim));
505                    best_node.set_location_total(rect.x_total(), rect.y_total());
506                    best_node.dim_mut().set_dimension(dim.width(), dim.height());
507                    best_short_side_fit = short_side_fit;
508                    best_long_size_fit = long_side_fit;
509                }
510            }
511        }
512
513        (best_long_size_fit, best_short_side_fit, result)
514    }
515
516    /// Attempts to find the best rectangle position in the bin, using the [`Heuristic::BestAreaFit`] rule.
517    fn find_baf(&self, dim: &Dimension) -> (i32, i32, Option<Rectangle>) {
518        let mut result = None;
519
520        let mut best_area_fit = i64::MAX;
521        let mut best_short_side_fit = i64::MAX;
522        // let mut best_fit = (u32::MAX, 0u32);
523        for rect in &self.rects_free {
524            if rect.width_total() >= dim.width_total() && rect.height_total() >= dim.height_total()
525            {
526                let leftover_h = rect.width_total().abs_diff(dim.width_total());
527                let leftover_v = rect.height_total().abs_diff(dim.height_total());
528                let short_side_fit = leftover_h.min(leftover_v) as i64;
529
530                let area_fit = rect.dim().area_total() - dim.area_total();
531                if area_fit < best_area_fit
532                    || (area_fit == best_area_fit && short_side_fit < best_short_side_fit)
533                {
534                    let best_node = result.get_or_insert_with(|| Rectangle::new(0, 0, *dim));
535                    best_node.set_location_total(rect.x_total(), rect.y_total());
536                    best_node.dim_mut().set_dimension(dim.width(), dim.height());
537                    best_area_fit = area_fit;
538                    best_short_side_fit = short_side_fit;
539                }
540            }
541        }
542
543        (best_area_fit as i32, best_short_side_fit as i32, result)
544    }
545
546    /// Attempts to find the best rectangle position in the bin, using the [`Heuristic::ContactPointRule`] rule.
547    fn find_cpr(&self, dim: &Dimension) -> (i32, i32, Option<Rectangle>) {
548        let mut result = None;
549
550        let mut best_score = -1;
551        for rect in &self.rects_free {
552            if rect.width_total() >= dim.width_total() && rect.height_total() >= dim.height_total()
553            {
554                let score = self.contact_point_score_node(
555                    rect.x_total(),
556                    rect.y_total(),
557                    dim.width_total(),
558                    dim.height_total(),
559                );
560                if score > best_score {
561                    let best_node = result.get_or_insert_with(|| Rectangle::new(0, 0, *dim));
562                    best_node.set_location_total(rect.x_total(), rect.y_total());
563                    best_node.dim_mut().set_dimension(dim.width(), dim.height());
564                    best_score = score;
565                }
566            }
567        }
568
569        // Reversing score since we are minimizing, but for contact point score, bigger is better.
570        best_score = -best_score;
571
572        // No secondary score needed
573        (best_score, 0, result)
574    }
575
576    /// Computes the placement score for the "CP" variant.
577    fn contact_point_score_node(&self, x: i32, y: i32, width: i32, height: i32) -> i32 {
578        let mut score = 0;
579
580        if x == 0 || x + width == self.bin_width {
581            score += height;
582        }
583        if y == 0 || y + height == self.bin_height {
584            score += width;
585        }
586
587        for rect in &self.rects_used {
588            if rect.x_total() == x + width || rect.x_total() + rect.width_total() == x {
589                score += Self::common_interval_length(
590                    rect.y_total(),
591                    rect.y_total() + rect.height_total(),
592                    y,
593                    y + height,
594                );
595            }
596            if rect.y_total() == y + height || rect.y_total() + rect.height_total() == y {
597                score += Self::common_interval_length(
598                    rect.x_total(),
599                    rect.x_total() + rect.width_total(),
600                    x,
601                    x + width,
602                );
603            }
604        }
605
606        score
607    }
608
609    /// Returns whether the specified free node was split.
610    fn split_free_node(&mut self, free: &Rectangle, used: &Rectangle) -> bool {
611        // Test with SAT if the rectangles even intersect
612        if used.x_total() >= free.x_total() + free.width_total()
613            || used.x_total() + used.width_total() <= free.x_total()
614            || used.y_total() >= free.y_total() + free.height_total()
615            || used.y_total() + used.height_total() <= free.y_total()
616        {
617            return false;
618        }
619
620        // We add up to four new free rectangles to the free rectangles list below. None of these
621        // four newly added free rectangles can overlap any other three, so keep a mark of them
622        // to avoid testing them against each other.
623        self.new_rects_free_size = self.new_rects_free.len();
624
625        if used.x_total() < free.x_total() + free.width_total()
626            && used.x_total() + used.width_total() > free.x_total()
627        {
628            // New node at the top side of the used node
629            if used.y_total() > free.y_total()
630                && used.y_total() < free.y_total() + free.height_total()
631            {
632                let mut new_node = free.to_owned();
633                let new_y = new_node.y_total();
634                new_node.dim_mut().set_height(used.y_total() - new_y);
635                self.insert_new_free_rect(&new_node);
636            }
637
638            // New node at the bottom side of the used node.
639            if used.y_total() + used.height_total() < free.y_total() + free.height_total() {
640                let mut new_node = free.to_owned();
641                new_node.set_y_total(used.y_total() + used.height_total());
642                new_node.dim_mut().set_height(
643                    free.y_total() + free.height_total() - (used.y_total() + used.height_total()),
644                );
645                self.insert_new_free_rect(&new_node);
646            }
647        }
648
649        if used.y_total() < free.y_total() + free.height_total()
650            && used.y_total() + used.height_total() > free.y_total()
651        {
652            // New node at the left side of the used node.
653            if used.x_total() > free.x_total()
654                && used.x_total() < free.x_total() + free.width_total()
655            {
656                let mut new_node = free.to_owned();
657                let new_x = new_node.x_total();
658                new_node.dim_mut().set_width(used.x_total() - new_x);
659                self.insert_new_free_rect(&new_node);
660            }
661
662            // New node at the right side of the used node.
663            if used.x_total() + used.width_total() < free.x_total() + free.width_total() {
664                let mut new_node = free.to_owned();
665                new_node.set_x_total(used.x_total() + used.width_total());
666                new_node.dim_mut().set_width(
667                    free.x_total() + free.width_total() - (used.x_total() + used.width_total()),
668                );
669                self.insert_new_free_rect(&new_node);
670            }
671        }
672
673        true
674    }
675
676    fn insert_new_free_rect(&mut self, new_node: &Rectangle) {
677        debug_assert!(new_node.width_total() > 0);
678        debug_assert!(new_node.height_total() > 0);
679
680        let mut i = 0usize;
681        while i < self.new_rects_free_size {
682            let cur_node = &self.new_rects_free[i];
683
684            // This new free rectangle is already accounted for?
685            if cur_node.contains_total(new_node) {
686                return;
687            }
688
689            // Does this new free rectangle obsolete a previous new free rectangle?
690            if new_node.contains_total(cur_node) {
691                // Remove i'th new free rectangle, but do so by retaining the order
692                // of the older vs newest free rectangles that we may still be placing
693                // in calling function split_free_node().
694                self.new_rects_free_size -= 1;
695                self.new_rects_free[i] = self.new_rects_free[self.new_rects_free_size];
696                self.new_rects_free.swap_remove(self.new_rects_free_size);
697            } else {
698                i += 1;
699            }
700        }
701        self.new_rects_free.push(new_node.to_owned());
702    }
703
704    /// Goes through the free rectangles list and removes any redundant nodes.
705    fn prune_free_list(&mut self) {
706        for rect in &self.rects_free {
707            self.new_rects_free.retain(|r| !rect.contains_total(r));
708        }
709
710        // For testing purposes: comment the block above and uncomment the block below
711        // for rect in &self.rects_free {
712        //     let mut j = 0usize;
713        //     let mut new_size = self.new_rects_free.len();
714        //     while j < new_size {
715        //         if rect.contains_total(&self.new_rects_free[j]) {
716        //             self.new_rects_free.swap_remove(j);
717        //             new_size -= 1;
718        //         } else {
719        //             // The old free rectangles can never be contained in any of the new free
720        //             // rectangles (the new free rectangles keep shrinking in size)
721        //             assert!(!rect.contains_total(&self.new_rects_free[j]));
722        //
723        //             j += 1;
724        //         }
725        //     }
726        // }
727
728        // Merge new and old free rectangles to the group of old free rectangles.
729        self.rects_free.append(&mut self.new_rects_free);
730
731        #[cfg(debug_assertions)]
732        for (i, rect1) in self.rects_free.iter().enumerate() {
733            for rect2 in self.rects_free.iter().skip(i + 1) {
734                debug_assert!(!rect1.contains_total(rect2));
735                debug_assert!(!rect2.contains_total(rect1));
736            }
737        }
738    }
739
740    /// Returns 0 if the two intervals i1 and i2 are disjoint, or the length of their overlap, otherwise.
741    fn common_interval_length(i1start: i32, i1end: i32, i2start: i32, i2end: i32) -> i32 {
742        if i1end < i2start || i2end < i1start {
743            0
744        } else {
745            i1end.min(i2end) - i1start.max(i2start)
746        }
747    }
748}
749
750impl<Idx> std::ops::Index<Idx> for MaxRectsBin
751where
752    Idx: std::slice::SliceIndex<[Rectangle]>,
753{
754    type Output = Idx::Output;
755
756    fn index(&self, index: Idx) -> &Self::Output {
757        &self.rects_used[index]
758    }
759}
760
761impl Display for MaxRectsBin {
762    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
763        write!(
764            f,
765            "Bin(width: {}, height: {}, rectangles: {})",
766            self.bin_width,
767            self.bin_height,
768            self.rects_used.len()
769        )
770    }
771}
772
773/// A convenience function that attempts to insert a given list of `Dimension` objects into a
774/// variable number of bins.
775///
776/// New bins are created on demand, using the given heuristic `rule`.
777///
778/// Specify true for `optimize` to use [`insert_list`] internally, which results in an improved
779/// bin layout but at the cost of a worse processing performance.
780///
781/// [`insert_list`]: MaxRectsBin::insert_list
782///
783/// Returns a list of bins with the packed rectangle nodes as a [`Result`] value.
784///
785/// # Errors
786///
787/// A [`BinError`] is returned for nodes which are either empty or too big for the bin.
788///
789/// # Examples
790/// ```
791/// use binpack2d::binpack::BinPacker;
792/// use binpack2d::binpack::maxrects::{Heuristic, pack_bins};
793/// use binpack2d::dimension::Dimension;
794///
795/// // Defining three items of different size
796/// let nodes = vec![Dimension::new(2, 4), Dimension::new(8, 6), Dimension::new(6, 6)];
797///
798/// // Returned list of bin object contains all nodes, placed according to the given heuristic rule
799/// let bins = pack_bins(&nodes, 16, 12, Heuristic::BestShortSideFit, true)
800///     .expect("Items should not be rejected");
801///
802/// assert_eq!(1, bins.len());
803/// assert_eq!(3, bins[0].len());
804/// ```
805pub fn pack_bins(
806    nodes: &[Dimension],
807    bin_width: i32,
808    bin_height: i32,
809    rule: Heuristic,
810    optimized: bool,
811) -> Result<Vec<MaxRectsBin>, BinError> {
812    if optimized {
813        pack_bins_list(nodes, bin_width, bin_height, rule)
814    } else {
815        pack_bins_single(nodes, bin_width, bin_height, rule)
816    }
817}
818
819/// Inserts nodes via insert_list().
820fn pack_bins_list(
821    nodes: &[Dimension],
822    bin_width: i32,
823    bin_height: i32,
824    rule: Heuristic,
825) -> Result<Vec<MaxRectsBin>, BinError> {
826    let mut bins = Vec::new();
827    if nodes.is_empty() || bin_width == 0 || bin_height == 0 {
828        return Ok(bins);
829    }
830
831    // first pass is done separately to avoid a (potentially) costly clone operation
832    let mut bin = MaxRectsBin::new(bin_width, bin_height);
833    let (inserted, mut rejected) = bin.insert_list(nodes, rule);
834
835    if inserted.is_empty() && !rejected.is_empty() {
836        // remaining nodes are too big and will be silently skipped
837        rejected.clear();
838    }
839
840    if !inserted.is_empty() {
841        bins.push(bin);
842    }
843
844    // subsequent passes are done in a loop
845    let mut nodes_left = rejected;
846    while !nodes_left.is_empty() {
847        let mut bin = MaxRectsBin::new(bin_width, bin_height);
848        let (inserted, mut rejected) = bin.insert_list(&nodes_left, rule);
849
850        if inserted.is_empty() && !rejected.is_empty() {
851            // remaining nodes are too big or too small
852            let result = rejected
853                .iter()
854                .map(|r| {
855                    if r.width_total() == 0 || r.height_total() == 0 {
856                        BinError::ItemTooSmall
857                    } else if r.width_total() > bin_width || r.height_total() > bin_height {
858                        BinError::ItemTooBig
859                    } else {
860                        BinError::Unspecified
861                    }
862                })
863                .next();
864            if let Some(result) = result {
865                return Err(result);
866            } else {
867                // Should not happen
868                eprintln!("pack_bins(): Could not insert remaining items");
869                rejected.clear();
870            }
871        }
872
873        if !inserted.is_empty() {
874            bins.push(bin);
875        }
876
877        // preparing for next iteration
878        nodes_left.clear();
879        nodes_left.append(&mut rejected);
880    }
881
882    Ok(bins)
883}
884
885/// Inserts nodes via insert().
886fn pack_bins_single(
887    nodes: &[Dimension],
888    bin_width: i32,
889    bin_height: i32,
890    rule: Heuristic,
891) -> Result<Vec<MaxRectsBin>, BinError> {
892    let mut bins = Vec::new();
893    if nodes.is_empty() || bin_width == 0 || bin_height == 0 {
894        return Ok(bins);
895    }
896
897    for node in nodes {
898        if node.is_empty() {
899            return Err(BinError::ItemTooSmall);
900        } else if node.width_total() > bin_width || node.height_total() > bin_height {
901            return Err(BinError::ItemTooBig);
902        }
903
904        // try inserting node into existing bins
905        let mut inserted = false;
906        for bin in &mut bins {
907            if bin.insert(node, rule).is_some() {
908                inserted = true;
909                break;
910            }
911        }
912
913        // create new bin if needed
914        if !inserted {
915            bins.push(MaxRectsBin::new(bin_width, bin_height));
916            if let Some(bin) = bins.last_mut() {
917                bin.insert(node, rule)
918                    .expect("Object should fit into the bin");
919            }
920        }
921    }
922
923    Ok(bins)
924}
925
926#[cfg(test)]
927mod tests;