1#![cfg_attr(not(std), no_std)]
4#![deny(missing_docs)]
5
6#[macro_use]
7extern crate alloc;
8
9#[cfg(not(std))]
10use alloc::collections::BTreeMap as KeyValMap;
11#[cfg(std)]
12use std::collections::HashMap as KeyValMap;
13
14use alloc::{collections::BTreeMap, vec::Vec};
15
16use core::{
17 fmt::{Debug, Display, Error as FmtError, Formatter},
18 hash::Hash,
19};
20
21pub use crate::bin_section::contains_smallest_box;
22pub use crate::bin_section::BinSection;
23pub use crate::bin_section::ComparePotentialContainersFn;
24use crate::grouped_rects_to_place::Group;
25pub use crate::grouped_rects_to_place::GroupedRectsToPlace;
26pub use crate::target_bin::TargetBin;
27use crate::width_height_depth::WidthHeightDepth;
28
29pub use self::box_size_heuristics::{volume_heuristic, BoxSizeHeuristicFn};
30pub use self::rect_to_insert::RectToInsert;
31pub use crate::packed_location::PackedLocation;
32
33mod bin_section;
34mod grouped_rects_to_place;
35
36mod packed_location;
37mod rect_to_insert;
38mod target_bin;
39mod width_height_depth;
40
41mod box_size_heuristics;
42
43pub fn pack_rects<
133 RectToPlaceId: Debug + Hash + PartialEq + Eq + Clone + Ord + PartialOrd,
134 BinId: Debug + Hash + PartialEq + Eq + Clone + Ord + PartialOrd,
135 GroupId: Debug + Hash + PartialEq + Eq + Clone + Ord + PartialOrd,
136>(
137 rects_to_place: &GroupedRectsToPlace<RectToPlaceId, GroupId>,
138 target_bins: &mut BTreeMap<BinId, TargetBin>,
139 box_size_heuristic: &BoxSizeHeuristicFn,
140 more_suitable_containers_fn: &ComparePotentialContainersFn,
141) -> Result<RectanglePackOk<RectToPlaceId, BinId>, RectanglePackError> {
142 let mut packed_locations = KeyValMap::new();
143
144 let mut target_bins: Vec<(&BinId, &mut TargetBin)> = target_bins.iter_mut().collect();
145 sort_bins_smallest_to_largest(&mut target_bins, box_size_heuristic);
146
147 let mut group_id_to_inbound_ids: Vec<(&Group<GroupId, RectToPlaceId>, &Vec<RectToPlaceId>)> =
148 rects_to_place.group_id_to_inbound_ids.iter().collect();
149 sort_groups_largest_to_smallest(
150 &mut group_id_to_inbound_ids,
151 rects_to_place,
152 box_size_heuristic,
153 );
154
155 'group: for (_group_id, rects_to_place_ids) in group_id_to_inbound_ids {
156 for (bin_id, bin) in target_bins.iter_mut() {
157 if !can_fit_entire_group_into_bin(
158 bin.clone(),
159 &rects_to_place_ids[..],
160 rects_to_place,
161 box_size_heuristic,
162 more_suitable_containers_fn,
163 ) {
164 continue;
165 }
166
167 'incoming: for rect_to_place_id in rects_to_place_ids.iter() {
168 if bin.available_bin_sections.len() == 0 {
169 continue;
170 }
171
172 let _bin_clone = bin.clone();
173
174 let mut bin_sections = bin.available_bin_sections.clone();
175
176 let last_section_idx = bin_sections.len() - 1;
177 let mut sections_tried = 0;
178
179 'section: while let Some(remaining_section) = bin_sections.pop() {
180 let rect_to_place = rects_to_place.rects[&rect_to_place_id];
181
182 let placement = remaining_section.try_place(
183 &rect_to_place,
184 more_suitable_containers_fn,
185 box_size_heuristic,
186 );
187
188 if placement.is_err() {
189 sections_tried += 1;
190 continue 'section;
191 }
192
193 let (placement, mut new_sections) = placement.unwrap();
194 sort_by_size_largest_to_smallest(&mut new_sections, box_size_heuristic);
195
196 bin.remove_filled_section(last_section_idx - sections_tried);
197 bin.add_new_sections(new_sections);
198
199 packed_locations.insert(rect_to_place_id.clone(), (bin_id.clone(), placement));
200
201 continue 'incoming;
202 }
203 }
204
205 continue 'group;
206 }
207 return Err(RectanglePackError::NotEnoughBinSpace);
208 }
209
210 Ok(RectanglePackOk { packed_locations })
211}
212
213fn can_fit_entire_group_into_bin<RectToPlaceId, GroupId>(
215 mut bin: TargetBin,
216 group: &[RectToPlaceId],
217 rects_to_place: &GroupedRectsToPlace<RectToPlaceId, GroupId>,
218
219 box_size_heuristic: &BoxSizeHeuristicFn,
220 more_suitable_containers_fn: &ComparePotentialContainersFn,
221) -> bool
222where
223 RectToPlaceId: Debug + Hash + PartialEq + Eq + Clone + Ord + PartialOrd,
224 GroupId: Debug + Hash + PartialEq + Eq + Clone + Ord + PartialOrd,
225{
226 'incoming: for rect_to_place_id in group.iter() {
227 if bin.available_bin_sections.len() == 0 {
228 return false;
229 }
230
231 let mut bin_sections = bin.available_bin_sections.clone();
232
233 let last_section_idx = bin_sections.len() - 1;
234 let mut sections_tried = 0;
235
236 'section: while let Some(remaining_section) = bin_sections.pop() {
237 let rect_to_place = rects_to_place.rects[&rect_to_place_id];
238
239 let placement = remaining_section.try_place(
240 &rect_to_place,
241 more_suitable_containers_fn,
242 box_size_heuristic,
243 );
244
245 if placement.is_err() {
246 sections_tried += 1;
247 continue 'section;
248 }
249
250 let (_placement, mut new_sections) = placement.unwrap();
251 sort_by_size_largest_to_smallest(&mut new_sections, box_size_heuristic);
252
253 bin.remove_filled_section(last_section_idx - sections_tried);
254 bin.add_new_sections(new_sections);
255
256 continue 'incoming;
257 }
258
259 return false;
260 }
261
262 true
263}
264
265#[derive(Debug, PartialEq)]
267pub struct RectanglePackOk<RectToPlaceId: PartialEq + Eq + Hash, BinId: PartialEq + Eq + Hash> {
268 packed_locations: KeyValMap<RectToPlaceId, (BinId, PackedLocation)>,
269 }
272
273impl<RectToPlaceId: PartialEq + Eq + Hash, BinId: PartialEq + Eq + Hash>
274 RectanglePackOk<RectToPlaceId, BinId>
275{
276 pub fn packed_locations(&self) -> &KeyValMap<RectToPlaceId, (BinId, PackedLocation)> {
278 &self.packed_locations
279 }
280}
281
282#[derive(Debug, PartialEq)]
284pub enum RectanglePackError {
285 NotEnoughBinSpace,
287}
288
289#[cfg(std)]
290impl std::error::Error for RectanglePackError {}
291
292impl Display for RectanglePackError {
293 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> {
294 match self {
295 RectanglePackError::NotEnoughBinSpace => {
296 f.write_str("Not enough space to place all of the rectangles.")
297 }
298 }
299 }
300}
301
302fn sort_bins_smallest_to_largest<BinId>(
303 bins: &mut Vec<(&BinId, &mut TargetBin)>,
304 box_size_heuristic: &BoxSizeHeuristicFn,
305) where
306 BinId: Debug + Hash + PartialEq + Eq + Clone,
307{
308 bins.sort_by(|a, b| {
309 box_size_heuristic(WidthHeightDepth {
310 width: a.1.max_width,
311 height: a.1.max_height,
312 depth: a.1.max_depth,
313 })
314 .cmp(&box_size_heuristic(WidthHeightDepth {
315 width: b.1.max_width,
316 height: b.1.max_height,
317 depth: b.1.max_depth,
318 }))
319 });
320}
321
322fn sort_by_size_largest_to_smallest(
323 items: &mut [BinSection; 3],
324 box_size_heuristic: &BoxSizeHeuristicFn,
325) {
326 items.sort_by(|a, b| box_size_heuristic(b.whd).cmp(&box_size_heuristic(a.whd)));
327}
328
329fn sort_groups_largest_to_smallest<GroupId, RectToPlaceId>(
330 group_id_to_inbound_ids: &mut Vec<(&Group<GroupId, RectToPlaceId>, &Vec<RectToPlaceId>)>,
331 incoming_groups: &GroupedRectsToPlace<RectToPlaceId, GroupId>,
332 box_size_heuristic: &BoxSizeHeuristicFn,
333) where
334 RectToPlaceId: Debug + Hash + PartialEq + Eq + Clone + Ord + PartialOrd,
335 GroupId: Debug + Hash + PartialEq + Eq + Clone + Ord + PartialOrd,
336{
337 group_id_to_inbound_ids.sort_by(|a, b| {
338 let a_heuristic =
339 a.1.iter()
340 .map(|inbound| {
341 let rect = incoming_groups.rects[inbound];
342 box_size_heuristic(rect.whd)
343 })
344 .sum();
345
346 let b_heuristic: u128 =
347 b.1.iter()
348 .map(|inbound| {
349 let rect = incoming_groups.rects[inbound];
350 box_size_heuristic(rect.whd)
351 })
352 .sum();
353
354 b_heuristic.cmp(&a_heuristic)
355 });
356}
357
358#[cfg(test)]
359mod tests {
360 use crate::{pack_rects, volume_heuristic, RectToInsert, RectanglePackError, TargetBin};
361
362 use super::*;
363 use crate::packed_location::RotatedBy;
364
365 #[test]
367 fn error_if_the_rectangles_cannot_fit_into_target_bins() {
368 let mut targets = BTreeMap::new();
369 targets.insert(BinId::Three, TargetBin::new(2, 100, 1));
370
371 let mut groups: GroupedRectsToPlace<_, ()> = GroupedRectsToPlace::new();
372 groups.push_rect(RectToPlaceId::One, None, RectToInsert::new(3, 1, 1));
373
374 match pack_rects(
375 &groups,
376 &mut targets,
377 &volume_heuristic,
378 &contains_smallest_box,
379 )
380 .unwrap_err()
381 {
382 RectanglePackError::NotEnoughBinSpace => {}
383 };
384 }
385
386 #[test]
393 fn error_if_cannot_fit_group() {
394 let mut targets = BTreeMap::new();
395 targets.insert(BinId::Three, TargetBin::new(100, 100, 1));
396 targets.insert(BinId::Four, TargetBin::new(100, 100, 1));
397
398 let mut groups = GroupedRectsToPlace::new();
399 groups.push_rect(
400 RectToPlaceId::One,
401 Some(vec!["A Group"]),
402 RectToInsert::new(100, 100, 1),
403 );
404 groups.push_rect(
405 RectToPlaceId::Two,
406 Some(vec!["A Group"]),
407 RectToInsert::new(100, 100, 1),
408 );
409
410 match pack_rects(
411 &groups,
412 &mut targets,
413 &volume_heuristic,
414 &contains_smallest_box,
415 )
416 .unwrap_err()
417 {
418 RectanglePackError::NotEnoughBinSpace => {}
419 };
420 }
421
422 #[test]
425 fn one_inbound_rect_one_bin() {
426 let mut groups: GroupedRectsToPlace<_, ()> = GroupedRectsToPlace::new();
427 groups.push_rect(RectToPlaceId::One, None, RectToInsert::new(1, 2, 1));
428
429 let mut targets = BTreeMap::new();
430 targets.insert(BinId::Three, TargetBin::new(5, 5, 1));
431
432 let packed = pack_rects(
433 &groups,
434 &mut targets,
435 &volume_heuristic,
436 &contains_smallest_box,
437 )
438 .unwrap();
439 let locations = packed.packed_locations;
440
441 assert_eq!(locations.len(), 1);
442
443 assert_eq!(locations[&RectToPlaceId::One].0, BinId::Three,);
444 assert_eq!(
445 locations[&RectToPlaceId::One].1,
446 PackedLocation {
447 x: 0,
448 y: 0,
449 z: 0,
450 whd: WidthHeightDepth {
451 width: 1,
452 height: 2,
453 depth: 1
454 },
455 x_axis_rotation: RotatedBy::ZeroDegrees,
456 y_axis_rotation: RotatedBy::ZeroDegrees,
457 z_axis_rotation: RotatedBy::ZeroDegrees,
458 }
459 )
460 }
461
462 #[test]
464 fn one_inbound_rect_two_bins() {
465 let mut groups: GroupedRectsToPlace<_, ()> = GroupedRectsToPlace::new();
466 groups.push_rect(RectToPlaceId::One, None, RectToInsert::new(2, 2, 1));
467
468 let mut targets = BTreeMap::new();
469 targets.insert(BinId::Three, TargetBin::new(5, 5, 1));
470 targets.insert(BinId::Four, TargetBin::new(5, 5, 2));
471
472 let packed = pack_rects(
473 &groups,
474 &mut targets,
475 &volume_heuristic,
476 &contains_smallest_box,
477 )
478 .unwrap();
479 let locations = packed.packed_locations;
480
481 assert_eq!(locations[&RectToPlaceId::One].0, BinId::Three,);
482
483 assert_eq!(locations.len(), 1);
484 assert_eq!(
485 locations[&RectToPlaceId::One].1,
486 PackedLocation {
487 x: 0,
488 y: 0,
489 z: 0,
490 whd: WidthHeightDepth {
491 width: 2,
492 height: 2,
493 depth: 1
494 },
495 x_axis_rotation: RotatedBy::ZeroDegrees,
496 y_axis_rotation: RotatedBy::ZeroDegrees,
497 z_axis_rotation: RotatedBy::ZeroDegrees,
498 }
499 )
500 }
501
502 #[test]
504 fn places_largest_rectangles_first() {
505 let mut groups: GroupedRectsToPlace<_, ()> = GroupedRectsToPlace::new();
506 groups.push_rect(RectToPlaceId::One, None, RectToInsert::new(10, 10, 1));
507 groups.push_rect(RectToPlaceId::Two, None, RectToInsert::new(5, 5, 1));
508
509 let mut targets = BTreeMap::new();
510 targets.insert(BinId::Three, TargetBin::new(20, 20, 2));
511
512 let packed = pack_rects(
513 &groups,
514 &mut targets,
515 &volume_heuristic,
516 &contains_smallest_box,
517 )
518 .unwrap();
519 let locations = packed.packed_locations;
520
521 assert_eq!(locations.len(), 2);
522
523 assert_eq!(locations[&RectToPlaceId::One].0, BinId::Three,);
524 assert_eq!(locations[&RectToPlaceId::Two].0, BinId::Three,);
525
526 assert_eq!(
527 locations[&RectToPlaceId::One].1,
528 PackedLocation {
529 x: 0,
530 y: 0,
531 z: 0,
532 whd: WidthHeightDepth {
533 width: 10,
534 height: 10,
535 depth: 1
536 },
537 x_axis_rotation: RotatedBy::ZeroDegrees,
538 y_axis_rotation: RotatedBy::ZeroDegrees,
539 z_axis_rotation: RotatedBy::ZeroDegrees,
540 }
541 );
542 assert_eq!(
543 locations[&RectToPlaceId::Two].1,
544 PackedLocation {
545 x: 10,
546 y: 0,
547 z: 0,
548 whd: WidthHeightDepth {
549 width: 5,
550 height: 5,
551 depth: 1
552 },
553 x_axis_rotation: RotatedBy::ZeroDegrees,
554 y_axis_rotation: RotatedBy::ZeroDegrees,
555 z_axis_rotation: RotatedBy::ZeroDegrees,
556 }
557 )
558 }
559
560 #[test]
566 fn two_rects_two_bins() {
567 let mut groups: GroupedRectsToPlace<_, ()> = GroupedRectsToPlace::new();
568 groups.push_rect(RectToPlaceId::One, None, RectToInsert::new(15, 15, 1));
569 groups.push_rect(RectToPlaceId::Two, None, RectToInsert::new(20, 20, 1));
570
571 let mut targets = BTreeMap::new();
572 targets.insert(BinId::Three, TargetBin::new(20, 20, 1));
573 targets.insert(BinId::Four, TargetBin::new(50, 50, 1));
574
575 let packed = pack_rects(
576 &groups,
577 &mut targets,
578 &volume_heuristic,
579 &contains_smallest_box,
580 )
581 .unwrap();
582 let locations = packed.packed_locations;
583
584 assert_eq!(locations.len(), 2);
585
586 assert_eq!(locations[&RectToPlaceId::One].0, BinId::Four,);
587 assert_eq!(locations[&RectToPlaceId::Two].0, BinId::Three,);
588
589 assert_eq!(
590 locations[&RectToPlaceId::One].1,
591 PackedLocation {
592 x: 0,
593 y: 0,
594 z: 0,
595 whd: WidthHeightDepth {
596 width: 15,
597 height: 15,
598 depth: 1
599 },
600 x_axis_rotation: RotatedBy::ZeroDegrees,
601 y_axis_rotation: RotatedBy::ZeroDegrees,
602 z_axis_rotation: RotatedBy::ZeroDegrees,
603 }
604 );
605 assert_eq!(
606 locations[&RectToPlaceId::Two].1,
607 PackedLocation {
608 x: 0,
609 y: 0,
610 z: 0,
611 whd: WidthHeightDepth {
612 width: 20,
613 height: 20,
614 depth: 1
615 },
616 x_axis_rotation: RotatedBy::ZeroDegrees,
617 y_axis_rotation: RotatedBy::ZeroDegrees,
618 z_axis_rotation: RotatedBy::ZeroDegrees,
619 }
620 )
621 }
622
623 #[test]
642 fn fills_small_sections_before_large_ones() {
643 let mut targets = BTreeMap::new();
644 targets.insert(BinId::Three, TargetBin::new(100, 100, 1));
645
646 let mut groups: GroupedRectsToPlace<_, ()> = GroupedRectsToPlace::new();
647
648 groups.push_rect(RectToPlaceId::One, None, RectToInsert::new(50, 90, 1));
649 groups.push_rect(RectToPlaceId::Two, None, RectToInsert::new(1, 1, 1));
650
651 let packed = pack_rects(
652 &groups,
653 &mut targets,
654 &volume_heuristic,
655 &contains_smallest_box,
656 )
657 .unwrap();
658 let locations = packed.packed_locations;
659
660 assert_eq!(locations.len(), 2);
661
662 assert_eq!(locations[&RectToPlaceId::One].0, BinId::Three,);
663 assert_eq!(locations[&RectToPlaceId::Two].0, BinId::Three,);
664
665 assert_eq!(
666 locations[&RectToPlaceId::One].1,
667 PackedLocation {
668 x: 0,
669 y: 0,
670 z: 0,
671 whd: WidthHeightDepth {
672 width: 50,
673 height: 90,
674 depth: 1
675 },
676 x_axis_rotation: RotatedBy::ZeroDegrees,
677 y_axis_rotation: RotatedBy::ZeroDegrees,
678 z_axis_rotation: RotatedBy::ZeroDegrees,
679 }
680 );
681 assert_eq!(
682 locations[&RectToPlaceId::Two].1,
683 PackedLocation {
684 x: 0,
685 y: 90,
686 z: 0,
687 whd: WidthHeightDepth {
688 width: 1,
689 height: 1,
690 depth: 1
691 },
692 x_axis_rotation: RotatedBy::ZeroDegrees,
693 y_axis_rotation: RotatedBy::ZeroDegrees,
694 z_axis_rotation: RotatedBy::ZeroDegrees,
695 }
696 );
697 }
698
699 #[test]
724 fn saves_bin_sections_for_future_use() {
725 let mut targets = BTreeMap::new();
726 targets.insert(BinId::Three, TargetBin::new(100, 100, 1));
727
728 let mut groups: GroupedRectsToPlace<_, ()> = GroupedRectsToPlace::new();
729
730 groups.push_rect(RectToPlaceId::One, None, RectToInsert::new(60, 95, 1));
731 groups.push_rect(RectToPlaceId::Two, None, RectToInsert::new(40, 10, 1));
732 groups.push_rect(RectToPlaceId::Three, None, RectToInsert::new(60, 3, 1));
733
734 let packed = pack_rects(
735 &groups,
736 &mut targets,
737 &volume_heuristic,
738 &contains_smallest_box,
739 )
740 .unwrap();
741 let locations = packed.packed_locations;
742
743 assert_eq!(
744 locations[&RectToPlaceId::One].1,
745 PackedLocation {
746 x: 0,
747 y: 0,
748 z: 0,
749 whd: WidthHeightDepth {
750 width: 60,
751 height: 95,
752 depth: 1
753 },
754 x_axis_rotation: RotatedBy::ZeroDegrees,
755 y_axis_rotation: RotatedBy::ZeroDegrees,
756 z_axis_rotation: RotatedBy::ZeroDegrees,
757 }
758 );
759 assert_eq!(
760 locations[&RectToPlaceId::Two].1,
761 PackedLocation {
762 x: 60,
763 y: 0,
764 z: 0,
765 whd: WidthHeightDepth {
766 width: 40,
767 height: 10,
768 depth: 1
769 },
770 x_axis_rotation: RotatedBy::ZeroDegrees,
771 y_axis_rotation: RotatedBy::ZeroDegrees,
772 z_axis_rotation: RotatedBy::ZeroDegrees,
773 }
774 );
775 assert_eq!(
776 locations[&RectToPlaceId::Three].1,
777 PackedLocation {
778 x: 0,
779 y: 95,
780 z: 0,
781 whd: WidthHeightDepth {
782 width: 60,
783 height: 3,
784 depth: 1
785 },
786 x_axis_rotation: RotatedBy::ZeroDegrees,
787 y_axis_rotation: RotatedBy::ZeroDegrees,
788 z_axis_rotation: RotatedBy::ZeroDegrees,
789 }
790 );
791 }
792
793 #[test]
797 fn deterministic_packing() {
798 let mut previous_packed = None;
799
800 for _ in 0..5 {
801 let mut rects_to_place: GroupedRectsToPlace<&'static str, &str> =
802 GroupedRectsToPlace::new();
803
804 let mut target_bins = BTreeMap::new();
805 for bin_id in 0..5 {
806 target_bins.insert(bin_id, TargetBin::new(8, 8, 1));
807 }
808
809 let rectangles = vec![
810 "some-rectangle-0",
811 "some-rectangle-1",
812 "some-rectangle-2",
813 "some-rectangle-3",
814 "some-rectangle-4",
815 ];
816
817 for rect_id in rectangles.iter() {
818 rects_to_place.push_rect(rect_id, None, RectToInsert::new(4, 4, 1));
819 }
820
821 let packed = pack_rects(
822 &rects_to_place,
823 &mut target_bins.clone(),
824 &volume_heuristic,
825 &contains_smallest_box,
826 )
827 .unwrap();
828
829 if let Some(previous_packed) = previous_packed.as_ref() {
830 assert_eq!(&packed, previous_packed);
831 }
832
833 previous_packed = Some(packed);
834 }
835 }
836
837 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
838 enum RectToPlaceId {
839 One,
840 Two,
841 Three,
842 }
843
844 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
845 enum BinId {
846 Three,
847 Four,
848 }
849}