use crate::packed_location::RotatedBy;
use crate::{BoxSizeHeuristicFn, PackedLocation, RectToInsert, WidthHeightDepth};
use core::{
cmp::Ordering,
fmt::{Debug, Display, Error as FmtError, Formatter},
};
mod overlaps;
pub type ComparePotentialContainersFn =
dyn Fn([WidthHeightDepth; 3], [WidthHeightDepth; 3], &BoxSizeHeuristicFn) -> Ordering;
pub fn contains_smallest_box(
mut container1: [WidthHeightDepth; 3],
mut container2: [WidthHeightDepth; 3],
heuristic: &BoxSizeHeuristicFn,
) -> Ordering {
container1.sort_by(|a, b| heuristic(*a).cmp(&heuristic(*b)));
container2.sort_by(|a, b| heuristic(*a).cmp(&heuristic(*b)));
match heuristic(container2[0]).cmp(&heuristic(container1[0])) {
Ordering::Equal => heuristic(container2[1]).cmp(&heuristic(container1[1])),
o => o,
}
}
#[derive(Debug, Eq, PartialEq, Copy, Clone, Default, Ord, PartialOrd)]
pub struct BinSection {
pub(crate) x: u32,
pub(crate) y: u32,
pub(crate) z: u32,
pub(crate) whd: WidthHeightDepth,
}
#[derive(Debug, Eq, PartialEq)]
#[allow(missing_docs)]
pub enum BinSectionError {
PlacementWiderThanBinSection,
PlacementTallerThanBinSection,
PlacementDeeperThanBinSection,
}
impl Display for BinSectionError {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> {
let err = match self {
BinSectionError::PlacementWiderThanBinSection => {
"Can not place a rectangle inside of a bin that is wider than that rectangle."
}
BinSectionError::PlacementTallerThanBinSection => {
"Can not place a rectangle inside of a bin that is taller than that rectangle."
}
BinSectionError::PlacementDeeperThanBinSection => {
"Can not place a rectangle inside of a bin that is deeper than that rectangle."
}
};
f.write_str(err)
}
}
impl BinSection {
pub fn new(x: u32, y: u32, z: u32, whd: WidthHeightDepth) -> Self {
BinSection { x, y, z, whd }
}
fn new_spread(x: u32, y: u32, z: u32, width: u32, height: u32, depth: u32) -> Self {
BinSection {
x,
y,
z,
whd: WidthHeightDepth {
width,
height,
depth,
},
}
}
}
impl BinSection {
pub fn try_place(
&self,
incoming: &RectToInsert,
container_comparison_fn: &ComparePotentialContainersFn,
heuristic_fn: &BoxSizeHeuristicFn,
) -> Result<(PackedLocation, [BinSection; 3]), BinSectionError> {
self.incoming_can_fit(incoming)?;
let mut all_combinations = [
self.depth_largest_height_second_largest_width_smallest(incoming),
self.depth_largest_width_second_largest_height_smallest(incoming),
self.height_largest_depth_second_largest_width_smallest(incoming),
self.height_largest_width_second_largest_depth_smallest(incoming),
self.width_largest_depth_second_largest_height_smallest(incoming),
self.width_largest_height_second_largest_depth_smallest(incoming),
];
all_combinations.sort_by(|a, b| {
container_comparison_fn(
[a[0].whd, a[1].whd, a[2].whd],
[b[0].whd, b[1].whd, b[2].whd],
heuristic_fn,
)
});
let packed_location = PackedLocation {
x: self.x,
y: self.y,
z: self.z,
whd: WidthHeightDepth {
width: incoming.width(),
height: incoming.height(),
depth: incoming.depth(),
},
x_axis_rotation: RotatedBy::ZeroDegrees,
y_axis_rotation: RotatedBy::ZeroDegrees,
z_axis_rotation: RotatedBy::ZeroDegrees,
};
Ok((packed_location, all_combinations[5]))
}
fn incoming_can_fit(&self, incoming: &RectToInsert) -> Result<(), BinSectionError> {
if incoming.width() > self.whd.width {
return Err(BinSectionError::PlacementWiderThanBinSection);
}
if incoming.height() > self.whd.height {
return Err(BinSectionError::PlacementTallerThanBinSection);
}
if incoming.depth() > self.whd.depth {
return Err(BinSectionError::PlacementDeeperThanBinSection);
}
Ok(())
}
fn width_largest_height_second_largest_depth_smallest(
&self,
incoming: &RectToInsert,
) -> [BinSection; 3] {
[
self.empty_space_directly_right(incoming),
self.all_empty_space_above_excluding_behind(incoming),
self.all_empty_space_behind(incoming),
]
}
fn width_largest_depth_second_largest_height_smallest(
&self,
incoming: &RectToInsert,
) -> [BinSection; 3] {
[
self.empty_space_directly_right(incoming),
self.all_empty_space_above(incoming),
self.all_empty_space_behind_excluding_above(incoming),
]
}
fn height_largest_width_second_largest_depth_smallest(
&self,
incoming: &RectToInsert,
) -> [BinSection; 3] {
[
self.all_empty_space_right_excluding_behind(incoming),
self.empty_space_directly_above(incoming),
self.all_empty_space_behind(incoming),
]
}
fn height_largest_depth_second_largest_width_smallest(
&self,
incoming: &RectToInsert,
) -> [BinSection; 3] {
[
self.all_empty_space_right(incoming),
self.empty_space_directly_above(incoming),
self.all_empty_space_behind_excluding_right(incoming),
]
}
fn depth_largest_width_second_largest_height_smallest(
&self,
incoming: &RectToInsert,
) -> [BinSection; 3] {
[
self.all_empty_space_right_excluding_above(incoming),
self.all_empty_space_above(incoming),
self.empty_space_directly_behind(incoming),
]
}
fn depth_largest_height_second_largest_width_smallest(
&self,
incoming: &RectToInsert,
) -> [BinSection; 3] {
[
self.all_empty_space_right(incoming),
self.all_empty_space_above_excluding_right(incoming),
self.empty_space_directly_behind(incoming),
]
}
fn all_empty_space_above(&self, incoming: &RectToInsert) -> BinSection {
BinSection::new_spread(
self.x,
self.y + incoming.height(),
self.z,
self.whd.width,
self.whd.height - incoming.height(),
self.whd.depth,
)
}
fn all_empty_space_right(&self, incoming: &RectToInsert) -> BinSection {
BinSection::new_spread(
self.x + incoming.width(),
self.y,
self.z,
self.whd.width - incoming.width(),
self.whd.height,
self.whd.depth,
)
}
fn all_empty_space_behind(&self, incoming: &RectToInsert) -> BinSection {
BinSection::new_spread(
self.x,
self.y,
self.z + incoming.depth(),
self.whd.width,
self.whd.height,
self.whd.depth - incoming.depth(),
)
}
fn empty_space_directly_above(&self, incoming: &RectToInsert) -> BinSection {
BinSection::new_spread(
self.x,
self.y + incoming.height(),
self.z,
incoming.width(),
self.whd.height - incoming.height(),
incoming.depth(),
)
}
fn empty_space_directly_right(&self, incoming: &RectToInsert) -> BinSection {
BinSection::new_spread(
self.x + incoming.width(),
self.y,
self.z,
self.whd.width - incoming.width(),
incoming.height(),
incoming.depth(),
)
}
fn empty_space_directly_behind(&self, incoming: &RectToInsert) -> BinSection {
BinSection::new(
self.x,
self.y,
self.z + incoming.depth(),
WidthHeightDepth {
width: incoming.width(),
height: incoming.height(),
depth: self.whd.depth - incoming.depth(),
},
)
}
fn all_empty_space_above_excluding_right(&self, incoming: &RectToInsert) -> BinSection {
BinSection::new(
self.x,
self.y + incoming.height(),
self.z,
WidthHeightDepth {
width: incoming.width(),
height: self.whd.height - incoming.height(),
depth: self.whd.depth,
},
)
}
fn all_empty_space_above_excluding_behind(&self, incoming: &RectToInsert) -> BinSection {
BinSection::new(
self.x,
self.y + incoming.height(),
self.z,
WidthHeightDepth {
width: self.whd.width,
height: self.whd.height - incoming.height(),
depth: incoming.depth(),
},
)
}
fn all_empty_space_right_excluding_above(&self, incoming: &RectToInsert) -> BinSection {
BinSection::new(
self.x + incoming.width(),
self.y,
self.z,
WidthHeightDepth {
width: self.whd.width - incoming.width(),
height: incoming.height(),
depth: self.whd.depth,
},
)
}
fn all_empty_space_right_excluding_behind(&self, incoming: &RectToInsert) -> BinSection {
BinSection::new(
self.x + incoming.width(),
self.y,
self.z,
WidthHeightDepth {
width: self.whd.width - incoming.width(),
height: self.whd.height,
depth: incoming.depth(),
},
)
}
fn all_empty_space_behind_excluding_above(&self, incoming: &RectToInsert) -> BinSection {
BinSection::new(
self.x,
self.y,
self.z + incoming.depth(),
WidthHeightDepth {
width: self.whd.width,
height: incoming.height(),
depth: self.whd.depth - incoming.depth(),
},
)
}
fn all_empty_space_behind_excluding_right(&self, incoming: &RectToInsert) -> BinSection {
BinSection::new(
self.x,
self.y,
self.z + incoming.depth(),
WidthHeightDepth {
width: incoming.width(),
height: self.whd.height,
depth: self.whd.depth - incoming.depth(),
},
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{volume_heuristic, RectToInsert};
const BIGGEST: u32 = 50;
const MIDDLE: u32 = 25;
const SMALLEST: u32 = 10;
const FULL: u32 = 100;
#[test]
fn error_if_placement_is_wider_than_bin_section() {
let bin_section = bin_section_width_height_depth(5, 20, 1);
let placement = RectToInsert::new(6, 20, 1);
assert_eq!(
bin_section
.try_place(&placement, &contains_smallest_box, &volume_heuristic)
.unwrap_err(),
BinSectionError::PlacementWiderThanBinSection
);
}
#[test]
fn error_if_placement_is_taller_than_bin_section() {
let bin_section = bin_section_width_height_depth(5, 20, 1);
let placement = RectToInsert::new(5, 21, 1);
assert_eq!(
bin_section
.try_place(&placement, &contains_smallest_box, &volume_heuristic)
.unwrap_err(),
BinSectionError::PlacementTallerThanBinSection
);
}
#[test]
fn error_if_placement_is_deeper_than_bin_section() {
let bin_section = bin_section_width_height_depth(5, 20, 1);
let placement = RectToInsert::new(5, 20, 2);
assert_eq!(
bin_section
.try_place(&placement, &contains_smallest_box, &volume_heuristic)
.unwrap_err(),
BinSectionError::PlacementDeeperThanBinSection
);
}
fn test_splits(
container_dimensions: u32,
rect_to_place: WidthHeightDepth,
mut expected: [BinSection; 3],
) {
let dim = container_dimensions;
let bin_section = bin_section_width_height_depth(dim, dim, dim);
let whd = rect_to_place;
let placement = RectToInsert::new(whd.width, whd.height, whd.depth);
let mut packed = bin_section
.try_place(&placement, &contains_smallest_box, &volume_heuristic)
.unwrap();
packed.1.sort();
expected.sort();
assert_eq!(packed.1, expected);
}
#[test]
fn width_largest_height_second_largest_depth_smallest() {
let whd = WidthHeightDepth {
width: BIGGEST,
height: MIDDLE,
depth: SMALLEST,
};
test_splits(
FULL,
whd,
[
BinSection::new_spread(whd.width, 0, 0, FULL - whd.width, whd.height, whd.depth),
BinSection::new_spread(0, whd.height, 0, FULL, FULL - whd.height, whd.depth),
BinSection::new_spread(0, 0, whd.depth, FULL, FULL, FULL - whd.depth),
],
);
}
#[test]
fn width_largest_depth_second_largest_height_smallest() {
let whd = WidthHeightDepth {
width: BIGGEST,
height: SMALLEST,
depth: MIDDLE,
};
test_splits(
FULL,
whd,
[
BinSection::new_spread(whd.width, 0, 0, FULL - whd.width, whd.height, whd.depth),
BinSection::new_spread(0, whd.height, 0, FULL, FULL - whd.height, FULL),
BinSection::new_spread(0, 0, whd.depth, FULL, whd.height, FULL - whd.depth),
],
);
}
#[test]
fn height_largest_width_second_largest_depth_smallest() {
let whd = WidthHeightDepth {
width: MIDDLE,
height: BIGGEST,
depth: SMALLEST,
};
test_splits(
FULL,
whd,
[
BinSection::new_spread(whd.width, 0, 0, FULL - whd.width, FULL, whd.depth),
BinSection::new_spread(0, whd.height, 0, whd.width, FULL - whd.height, whd.depth),
BinSection::new_spread(0, 0, whd.depth, FULL, FULL, FULL - whd.depth),
],
);
}
#[test]
fn height_largest_depth_second_largest_width_smallest() {
let whd = WidthHeightDepth {
width: SMALLEST,
height: BIGGEST,
depth: MIDDLE,
};
test_splits(
FULL,
whd,
[
BinSection::new_spread(whd.width, 0, 0, FULL - whd.width, FULL, FULL),
BinSection::new_spread(0, whd.height, 0, whd.width, FULL - whd.height, whd.depth),
BinSection::new_spread(0, 0, whd.depth, whd.width, FULL, FULL - whd.depth),
],
);
}
#[test]
fn depth_largest_width_second_largest_height_smallest() {
let whd = WidthHeightDepth {
width: MIDDLE,
height: SMALLEST,
depth: BIGGEST,
};
test_splits(
FULL,
whd,
[
BinSection::new_spread(whd.width, 0, 0, FULL - whd.width, whd.height, FULL),
BinSection::new_spread(0, whd.height, 0, FULL, FULL - whd.height, FULL),
BinSection::new_spread(0, 0, whd.depth, whd.width, whd.height, FULL - whd.depth),
],
);
}
#[test]
fn depth_largest_height_second_largest_width_smallest() {
let whd = WidthHeightDepth {
width: SMALLEST,
height: MIDDLE,
depth: BIGGEST,
};
test_splits(
FULL,
whd,
[
BinSection::new_spread(whd.width, 0, 0, FULL - whd.width, FULL, FULL),
BinSection::new_spread(0, whd.height, 0, whd.width, FULL - whd.height, FULL),
BinSection::new_spread(0, 0, whd.depth, whd.width, whd.height, FULL - whd.depth),
],
);
}
fn bin_section_width_height_depth(width: u32, height: u32, depth: u32) -> BinSection {
BinSection::new(
0,
0,
0,
WidthHeightDepth {
width,
height,
depth,
},
)
}
}