1 // flow-rbp: A library for packing rectangles into two-dimensional finite bins.
2 // zlib License (see LICENSE)
4 /// Specifies the different heuristic rules that can be used when deciding where to place a new
6 #[derive(Clone, Debug)]
7 pub enum FreeRectHeuristic {
8 /// Choose to pack `R` into such `Fi` that `min(wf - w, hf - h)` is the smallest. In other words, we
9 /// minimize the length of the shorter leftover side.
12 /// Pack `R` into an `Fi` such that `max(wf - w, hf - h)` is the smallest. That is, we minimize
13 /// the length of the longer leftover side.
16 /// Pick the `Fi ∈ F` that is smallest in area to place the next rectangle `R` into. If there is a
17 /// tie, we use the [`ShortSideFit`](crate::rbp::FreeRectHeuristic::ShortSideFit) rule to break it.
20 /// Orient and place each rectangle to the position where the y-coordinate of the top side of the
21 /// rectangle is the smallest and if there are several such valid positions, pick the one that has
22 /// the smallest x-coordinate value.
25 /// Place `R` into a position where the length of the perimeter of `R` that is touched by the bin
26 /// edge or by a previously packed rectangle is maximized.
30 /// Specifies the different error types that can occur.
31 #[derive(PartialEq, Clone, Debug)]
32 pub enum RectsBinPackError {
37 /// Specifies the properties of a 2D rectangle.
38 #[derive(Clone, Debug)]
54 /// Instantiates a 2D rectangle of size (0, 0, 0, 0).
59 /// use flow_rbp::Rect2D;
61 /// let rect = Rect2D::new();
62 /// assert_eq!(rect.x, 0);
63 /// assert_eq!(rect.y, 0);
64 /// assert_eq!(rect.width, 0);
65 /// assert_eq!(rect.height, 0);
67 pub fn new() -> Self {
76 /// Instantiates a 2D rectangle with given size properties.
80 /// * `x` - is the x offset.
81 /// * `y` - is the y offset.
82 /// * `width` - is the width.
83 /// * `height` - is the height.
87 /// [`InvalidArg`](crate::rbp::RectsBinPackError::InvalidArg)
88 /// is returned if `x < 0 || y < 0 || width <= 0 || height <= 0`.
93 /// use flow_rbp::Rect2D;
95 /// let rect = Rect2D::with_details(0, 0, 32, 16).unwrap();
96 /// assert_eq!(rect.x, 0);
97 /// assert_eq!(rect.y, 0);
98 /// assert_eq!(rect.width, 32);
99 /// assert_eq!(rect.height, 16);
101 /// // this should fail:
102 /// assert_eq!(Rect2D::with_details(0, 0, 0, 0).is_err(), true);
109 ) -> Result<Self, RectsBinPackError> {
110 if x >= 0 && y >= 0 && width > 0 && height > 0 {
118 Err(RectsBinPackError::InvalidArg)
123 /// Specifies the properties of a rectangle bin.
124 #[derive(Clone, Debug)]
125 pub struct RectsBinPack {
126 /// is the width of the bin
129 /// is the height of the bin
132 /// is the flag indicating whether rotation is allowed or not
135 /// is the vector holding the used rects
136 used_rects: Vec<Rect2D>,
138 /// is the vector holding the free rects
139 free_rects: Vec<Rect2D>,
143 /// Instantiates a empty bin of given size.
147 /// * `width` - is the width of the bin
148 /// * `height` - is the height of the bin
149 /// * `allow_flip` - is the flag indicating whether the packing algorithm is allowed to rotate
150 /// the input rectangle 90 degrees clockwise to consider a better placement.
154 /// [`RectsBinPackError::InvalidArg`](crate::rbp::RectsBinPackError)
155 /// is returned if `width <= 0 || height <= 0`.
160 /// use flow_rbp::RectsBinPack;
161 /// use flow_rbp::FreeRectHeuristic;
163 /// let mut rbp = RectsBinPack::new(32, 32, false).unwrap();
164 /// assert_eq!(rbp.insert(16, 16, FreeRectHeuristic::BottomLeft).is_some(), true);
165 /// assert_eq!(rbp.insert(33, 33, FreeRectHeuristic::BottomLeft).is_none(), true);
167 pub fn new(width: i32, height: i32, allow_flip: bool) -> Result<Self, RectsBinPackError> {
168 if width > 0 && height > 0 {
173 used_rects: Vec::new(),
174 free_rects: vec![Rect2D::with_details(0, 0, width, height).unwrap()],
177 Err(RectsBinPackError::InvalidArg)
181 /// Insert a single rectangle into the bin, possibly rotated.
185 /// * `width` - is the rectangle width
186 /// * `height` - is the rectangle height
187 /// * `heuristic` - is the heuristic method to use when packing
192 /// use flow_rbp::RectsBinPack;
193 /// use flow_rbp::FreeRectHeuristic;
195 /// let mut rbp = RectsBinPack::new(32, 32, false).unwrap();
196 /// assert_eq!(rbp.insert(16, 16, FreeRectHeuristic::BottomLeft).is_some(), true);
197 /// assert_eq!(rbp.insert(33, 33, FreeRectHeuristic::BottomLeft).is_none(), true);
203 heuristic: FreeRectHeuristic,
204 ) -> Option<Rect2D> {
205 let output = match heuristic {
206 FreeRectHeuristic::ShortSideFit => self.get_rect_for_best_short_side_fit(width, height),
207 FreeRectHeuristic::BottomLeft => self.get_rect_for_bottom_left(width, height),
208 FreeRectHeuristic::ContactPoint => self.get_rect_for_contact_point(width, height),
209 FreeRectHeuristic::LongSideFit => self.get_rect_for_best_long_side_fit(width, height),
210 FreeRectHeuristic::AreaFit => self.get_rect_for_best_area_fit(width, height),
213 if let Some(new_rect) = output {
214 let mut i: usize = 0;
215 while i < self.free_rects.len() {
216 if let Some(free_rect) = self.free_rects.get(i) {
217 if self.is_split_free_node(&free_rect.clone(), &new_rect) {
218 self.free_rects.remove(i);
226 self.prune_free_list();
227 self.used_rects.push(new_rect.clone());
229 return Some(new_rect);
235 /// Computes the ratio of used surface area to the total bin area.
240 /// use flow_rbp::RectsBinPack;
241 /// use flow_rbp::FreeRectHeuristic;
243 /// let mut rbp = RectsBinPack::new(32, 32, false).unwrap();
245 /// // occupancy should be 0.0 initially:
246 /// assert_eq!(rbp.get_occupancy(), 0.0);
248 /// assert_eq!(rbp.insert(16, 16, FreeRectHeuristic::BottomLeft).is_some(), true);
249 /// assert_eq!(rbp.get_occupancy(), 0.25);
250 /// assert_eq!(rbp.insert(16, 16, FreeRectHeuristic::BottomLeft).is_some(), true);
251 /// assert_eq!(rbp.get_occupancy(), 0.5);
252 /// assert_eq!(rbp.insert(16, 16, FreeRectHeuristic::BottomLeft).is_some(), true);
253 /// assert_eq!(rbp.get_occupancy(), 0.75);
254 /// assert_eq!(rbp.insert(16, 16, FreeRectHeuristic::BottomLeft).is_some(), true);
256 /// // occupancy should now be full as in 1.0:
257 /// assert_eq!(rbp.get_occupancy(), 1.0);
259 pub fn get_occupancy(&self) -> f32 {
260 let mut used_surface_area: i32 = 0;
262 for i in 0..self.used_rects.len() {
263 if let Some(rect) = self.used_rects.get(i) {
264 used_surface_area += rect.width * rect.height;
269 return used_surface_area as f32 / (self.width * self.height) as f32;
272 /// Computes the placement score for the contact point variant.
273 fn get_score_for_contact_point(&self, x: i32, y: i32, width: i32, height: i32) -> i32 {
274 let mut score: i32 = 0;
276 if x == 0 || x + width == self.width {
280 if y == 0 || y + height == self.height {
284 for i in 0..self.used_rects.len() {
285 if let Some(used_rect) = self.used_rects.get(i) {
286 if used_rect.x == x + width || used_rect.x + used_rect.width == x {
287 score += self.get_common_interval_len(
289 used_rect.y + used_rect.height,
295 if used_rect.y == y + height || used_rect.y + used_rect.height == y {
296 score += self.get_common_interval_len(
298 used_rect.x + used_rect.width,
309 /// Computes the rect for bottom left placement variant.
310 fn get_rect_for_bottom_left(&self, width: i32, height: i32) -> Option<Rect2D> {
311 let mut new_rect = Rect2D::new();
313 let mut best_x = std::i32::MAX;
314 let mut best_y = std::i32::MAX;
316 for i in 0..self.free_rects.len() {
317 if let Some(free_rect) = self.free_rects.get(i) {
318 // true to place the rect in upright (non-flipped) orientation:
319 if free_rect.width >= width && free_rect.height >= height {
320 let top_side_y = free_rect.y + height;
322 if top_side_y < best_y || (top_side_y == best_y && free_rect.x < best_x) {
323 new_rect.x = free_rect.x;
324 new_rect.y = free_rect.y;
325 new_rect.width = width;
326 new_rect.height = height;
327 best_x = free_rect.x;
332 if self.allow_flip && free_rect.width >= height && free_rect.height >= width {
333 let top_side_y = free_rect.y + width;
335 if top_side_y < best_y || (top_side_y == best_y && free_rect.x < best_x) {
336 new_rect.x = free_rect.x;
337 new_rect.y = free_rect.y;
338 new_rect.width = height;
339 new_rect.height = width;
340 best_x = free_rect.x;
349 if new_rect.height == 0 || new_rect.width == 0 {
353 return Some(new_rect);
356 /// Computes the rect for short side fit variant.
357 fn get_rect_for_best_short_side_fit(&self, width: i32, height: i32) -> Option<Rect2D> {
358 let mut new_rect = Rect2D::new();
360 let mut best_short_side_fit = std::i32::MAX;
361 let mut best_long_side_fit = std::i32::MAX;
363 for i in 0..self.free_rects.len() {
364 if let Some(free_rect) = self.free_rects.get(i) {
365 // try to place the rect in upright (non-flipped) orientation:
366 if free_rect.width >= width && free_rect.height >= height {
367 let left_over_horiz = free_rect.width - width;
368 let left_over_vert = free_rect.height - height;
369 let short_side_fit = std::cmp::min(left_over_horiz, left_over_vert);
370 let long_side_fit = std::cmp::max(left_over_horiz, left_over_vert);
372 if short_side_fit < best_short_side_fit
373 || (short_side_fit == best_short_side_fit
374 && long_side_fit < best_long_side_fit)
376 new_rect.x = free_rect.x;
377 new_rect.y = free_rect.y;
378 new_rect.width = width;
379 new_rect.height = height;
380 best_short_side_fit = short_side_fit;
381 best_long_side_fit = long_side_fit;
385 if self.allow_flip && free_rect.width >= height && free_rect.height >= width {
386 let flipped_left_over_horiz = free_rect.width - height;
387 let flipped_left_over_vert = free_rect.height - width;
388 let flipped_short_side_fit =
389 std::cmp::min(flipped_left_over_horiz, flipped_left_over_vert);
390 let flipped_long_side_fit =
391 std::cmp::max(flipped_left_over_horiz, flipped_left_over_vert);
393 if flipped_short_side_fit < best_short_side_fit
394 || (flipped_short_side_fit == best_short_side_fit
395 && flipped_long_side_fit < best_long_side_fit)
397 new_rect.x = free_rect.x;
398 new_rect.y = free_rect.y;
399 new_rect.width = height;
400 new_rect.height = width;
401 best_short_side_fit = flipped_short_side_fit;
402 best_long_side_fit = flipped_long_side_fit;
410 if new_rect.height == 0 || new_rect.width == 0 {
414 return Some(new_rect);
417 /// Computes the rect for long side fit variant.
418 fn get_rect_for_best_long_side_fit(&self, width: i32, height: i32) -> Option<Rect2D> {
419 let mut new_rect = Rect2D::new();
421 let mut best_short_side_fit = std::i32::MAX;
422 let mut best_long_side_fit = std::i32::MAX;
424 for i in 0..self.free_rects.len() {
425 if let Some(free_rect) = self.free_rects.get(i) {
426 // try to place the rect in upright (non-flipped) orientation:
427 if free_rect.width >= width && free_rect.height >= height {
428 let left_over_horiz = free_rect.width - width;
429 let left_over_vert = free_rect.height - height;
430 let short_side_fit = std::cmp::min(left_over_horiz, left_over_vert);
431 let long_side_fit = std::cmp::max(left_over_horiz, left_over_vert);
433 if long_side_fit < best_long_side_fit
434 || (long_side_fit == best_long_side_fit
435 && short_side_fit < best_short_side_fit)
437 new_rect.x = free_rect.x;
438 new_rect.y = free_rect.y;
439 new_rect.width = width;
440 new_rect.height = height;
441 best_short_side_fit = short_side_fit;
442 best_long_side_fit = long_side_fit;
446 if self.allow_flip && free_rect.width >= height && free_rect.height >= width {
447 let left_over_horiz = free_rect.width - height;
448 let left_over_vert = free_rect.height - width;
449 let short_side_fit = std::cmp::min(left_over_horiz, left_over_vert);
450 let long_side_fit = std::cmp::max(left_over_horiz, left_over_vert);
452 if long_side_fit < best_long_side_fit
453 || (long_side_fit == best_long_side_fit
454 && short_side_fit < best_short_side_fit)
456 new_rect.x = free_rect.x;
457 new_rect.y = free_rect.y;
458 new_rect.width = height;
459 new_rect.height = width;
460 best_short_side_fit = short_side_fit;
461 best_long_side_fit = long_side_fit;
469 if new_rect.height == 0 || new_rect.width == 0 {
473 return Some(new_rect);
476 /// Computes the rect for best area fit variant.
477 fn get_rect_for_best_area_fit(&self, width: i32, height: i32) -> Option<Rect2D> {
478 let mut new_rect = Rect2D::new();
480 let mut best_area_fit = std::i32::MAX;
481 let mut best_short_side_fit = std::i32::MAX;
483 for i in 0..self.free_rects.len() {
484 if let Some(free_rect) = self.free_rects.get(i) {
485 let area_fit = free_rect.width * free_rect.height - width * height;
487 // try to place rect in upright (non-flipped) orientation:
488 if free_rect.width >= width && free_rect.height >= height {
489 let left_over_horiz = free_rect.width - width;
490 let left_over_vert = free_rect.height - height;
491 let short_side_fit = std::cmp::min(left_over_horiz, left_over_vert);
493 if area_fit < best_area_fit
494 || (area_fit == best_area_fit && short_side_fit < best_short_side_fit)
496 new_rect.x = free_rect.x;
497 new_rect.y = free_rect.y;
498 new_rect.width = width;
499 new_rect.height = height;
500 best_short_side_fit = short_side_fit;
501 best_area_fit = area_fit;
505 if self.allow_flip && free_rect.width >= height && free_rect.height >= width {
506 let left_over_horiz = free_rect.width - height;
507 let left_over_vert = free_rect.height - width;
508 let short_side_fit = std::cmp::min(left_over_horiz, left_over_vert);
510 if area_fit < best_area_fit
511 || (area_fit == best_area_fit && short_side_fit < best_short_side_fit)
513 new_rect.x = free_rect.x;
514 new_rect.y = free_rect.y;
515 new_rect.width = height;
516 new_rect.height = width;
517 best_short_side_fit = short_side_fit;
518 best_area_fit = area_fit;
526 if new_rect.height == 0 || new_rect.width == 0 {
530 return Some(new_rect);
533 /// Computes the rect for contact point variant.
534 fn get_rect_for_contact_point(&self, width: i32, height: i32) -> Option<Rect2D> {
535 let mut new_rect = Rect2D::new();
536 let mut best_contact_score = -1;
538 for i in 0..self.free_rects.len() {
539 if let Some(free_rect) = self.free_rects.get(i) {
540 // try to place the rect in upright (non-flipped) orientation:
541 if free_rect.width >= width && free_rect.height >= height {
543 self.get_score_for_contact_point(free_rect.x, free_rect.y, width, height);
545 if contact_score > best_contact_score {
546 new_rect.x = free_rect.x;
547 new_rect.y = free_rect.y;
548 new_rect.width = width;
549 new_rect.height = height;
550 best_contact_score = contact_score;
554 if self.allow_flip && free_rect.width >= height && free_rect.height >= width {
556 self.get_score_for_contact_point(free_rect.x, free_rect.y, height, width);
558 if contact_score > best_contact_score {
559 new_rect.x = free_rect.x;
560 new_rect.y = free_rect.y;
561 new_rect.width = height;
562 new_rect.height = width;
563 best_contact_score = contact_score;
571 if new_rect.height == 0 || new_rect.width == 0 {
575 return Some(new_rect);
578 /// returns true if the free rect was split
579 fn is_split_free_node(&mut self, free_rect: &Rect2D, used_rect: &Rect2D) -> bool {
580 // test with SAT if the rects even intersect:
581 if used_rect.x >= free_rect.x + free_rect.width
582 || used_rect.x + used_rect.width <= free_rect.x
583 || used_rect.y >= free_rect.y + free_rect.height
584 || used_rect.y + used_rect.height <= free_rect.y
589 if used_rect.x < free_rect.x + free_rect.width
590 && used_rect.x + used_rect.width > free_rect.x
592 // new node at the top side of the used node:
593 if used_rect.y > free_rect.y && used_rect.y < free_rect.y + free_rect.height {
594 let mut new_rect = free_rect.clone();
595 new_rect.height = used_rect.y - new_rect.y;
596 self.free_rects.push(new_rect);
599 // new node at the bottom side of the used node:
600 if used_rect.y + used_rect.height < free_rect.y + free_rect.height {
601 let mut new_rect = free_rect.clone();
602 new_rect.y = used_rect.y + used_rect.height;
603 new_rect.height = free_rect.y + free_rect.height - (used_rect.y + used_rect.height);
604 self.free_rects.push(new_rect);
608 if used_rect.y < free_rect.y + free_rect.height
609 && used_rect.y + used_rect.height > free_rect.y
611 // new node at the left side of the used node:
612 if used_rect.x > free_rect.x && used_rect.x < free_rect.x + free_rect.width {
613 let mut new_rect = free_rect.clone();
614 new_rect.width = used_rect.x - new_rect.x;
615 self.free_rects.push(new_rect);
618 // new node at the right side of the used node:
619 if used_rect.x + used_rect.width < free_rect.x + free_rect.width {
620 let mut new_rect = free_rect.clone();
621 new_rect.x = used_rect.x + used_rect.width;
622 new_rect.width = free_rect.x + free_rect.width - (used_rect.x + used_rect.width);
623 self.free_rects.push(new_rect);
630 /// goes through the free rect list and removes any redundant entries
631 fn prune_free_list(&mut self) {
632 // go through each pair and remove any rects that are redundant:
633 let mut keep: Vec<bool> = vec![true; self.free_rects.len()];
634 for i in 0..self.free_rects.len() {
635 for j in i + 1..self.free_rects.len() {
636 if let Some(free_rect_i) = self.free_rects.get(i)
637 && let Some(free_rect_j) = self.free_rects.get(j)
639 if self.is_contained_on(free_rect_i, free_rect_j) {
640 if let Some(value) = keep.get_mut(i) {
646 if self.is_contained_on(free_rect_j, free_rect_i) {
647 if let Some(value) = keep.get_mut(j) {
655 // remove all items marked false:
656 let mut iter = keep.iter();
657 self.free_rects.retain(|_| *iter.next().unwrap());
660 /// determine whether rect A is contained on rect B
661 fn is_contained_on(&self, a: &Rect2D, b: &Rect2D) -> bool {
664 && a.x + a.width <= b.x + b.width
665 && a.y + a.height <= b.y + b.height;
668 /// returns 0 if the two intervals i1 and i2 are disjoint, or the length of their
669 /// overlap otherwise.
670 fn get_common_interval_len(
677 if i1_end < i2_start || i2_end < i1_start {
681 return std::cmp::min(i1_end, i2_end) - std::cmp::max(i1_start, i2_start);
683 } // impl RectsBinPack
692 let rect = Rect2D::new();
694 assert_eq!(rect.x, 0);
695 assert_eq!(rect.y, 0);
696 assert_eq!(rect.width, 0);
697 assert_eq!(rect.height, 0);
699 let rect = Rect2D::with_details(2, 4, 16, 32).unwrap();
701 assert_eq!(rect.x, 2);
702 assert_eq!(rect.y, 4);
703 assert_eq!(rect.width, 16);
704 assert_eq!(rect.height, 32);
707 Rect2D::with_details(-1, 4, 16, 32).unwrap_err(),
708 RectsBinPackError::InvalidArg
711 Rect2D::with_details(0, -1, 16, 32).unwrap_err(),
712 RectsBinPackError::InvalidArg
715 Rect2D::with_details(0, 0, 0, 32).unwrap_err(),
716 RectsBinPackError::InvalidArg
719 Rect2D::with_details(2, 4, 16, 0).unwrap_err(),
720 RectsBinPackError::InvalidArg
723 Rect2D::with_details(-1, -1, 0, 0).unwrap_err(),
724 RectsBinPackError::InvalidArg
729 fn rbp_invalid_arg() {
731 RectsBinPack::new(0, 0, false).unwrap_err(),
732 RectsBinPackError::InvalidArg
735 RectsBinPack::new(32, 0, false).unwrap_err(),
736 RectsBinPackError::InvalidArg
739 RectsBinPack::new(0, 32, false).unwrap_err(),
740 RectsBinPackError::InvalidArg
744 RectsBinPack::new(0, 0, true).unwrap_err(),
745 RectsBinPackError::InvalidArg
748 RectsBinPack::new(32, 0, true).unwrap_err(),
749 RectsBinPackError::InvalidArg
752 RectsBinPack::new(0, 32, true).unwrap_err(),
753 RectsBinPackError::InvalidArg
759 let mut rbp = RectsBinPack::new(32, 32, false).unwrap();
760 assert_eq!(rbp.get_occupancy(), 0.0);
763 rbp.insert(16, 16, FreeRectHeuristic::ShortSideFit)
768 rbp.insert(16, 16, FreeRectHeuristic::LongSideFit).is_some(),
772 rbp.insert(16, 16, FreeRectHeuristic::AreaFit).is_some(),
776 rbp.insert(16, 16, FreeRectHeuristic::BottomLeft).is_some(),
779 assert_eq!(rbp.get_occupancy(), 1.0);
782 rbp.insert(1, 1, FreeRectHeuristic::ContactPoint).is_none(),