use crate::binpack::BinError;
use std::fmt::{Display, Formatter};
use std::mem;
use std::slice::Iter;
use super::{visualize_bin, BinPacker};
use crate::dimension::Dimension;
use crate::rectangle::Rectangle;
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum RectHeuristic {
BestShortSideFit,
BestLongSideFit,
BestAreaFit,
WorstShortSideFit,
WorstLongSideFit,
WorstAreaFit,
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum SplitHeuristic {
ShorterLeftoverAxis,
LongerLeftoverAxis,
MinimizeArea,
MaximizeArea,
ShorterAxis,
LongerAxis,
}
#[derive(Clone, Debug, PartialEq)]
pub struct GuillotineBin {
bin_width: i32,
bin_height: i32,
rects_used: Vec<Rectangle>,
rects_free: Vec<Rectangle>,
default_rect_choice: RectHeuristic,
default_split_method: SplitHeuristic,
default_merge: bool,
}
impl BinPacker for GuillotineBin {
fn width(&self) -> i32 {
self.bin_width
}
fn height(&self) -> i32 {
self.bin_height
}
fn clear_with(&mut self, capacity: usize) {
self.rects_used.clear();
self.rects_used.shrink_to(capacity.max(4));
self.rects_free.clear();
self.rects_free.shrink_to((capacity * 4).max(16));
self.rects_free.push(Rectangle::new(
0,
0,
Dimension::with_id(0, self.bin_width, self.bin_height, 0),
));
}
fn grow(&mut self, dw: u32, dh: u32) {
if dw > 0 {
self.rects_free.push(Rectangle::new(
self.bin_width,
0,
Dimension::with_id(0, dw as i32, self.bin_height, 0)
));
self.bin_width += dw as i32;
}
if dh > 0 {
self.rects_free.push(Rectangle::new(
0,
self.bin_height,
Dimension::with_id(0, self.bin_width, dh as i32, 0)
));
self.bin_height += dh as i32;
}
}
fn shrink(&mut self, binary: bool) {
if self.rects_used.is_empty() {
return;
}
let mut min_x = i32::MAX;
let mut min_y = i32::MAX;
let mut max_x = i32::MIN;
let mut max_y = i32::MIN;
for rect in &self.rects_used {
min_x = min_x.min(rect.x_total());
min_y = min_y.min(rect.y_total());
max_x = max_x.max(rect.x_total() + rect.width_total());
max_y = max_y.max(rect.y_total() + rect.height_total());
}
let mut new_width = max_x - min_x;
let mut new_height = max_y - min_y;
if binary {
let mut cur_width = self.bin_width;
while new_width <= (cur_width >> 1) {
cur_width >>= 1;
}
new_width = cur_width;
let mut cur_height = self.bin_height;
while new_height <= (cur_height >> 1) {
cur_height >>= 1;
}
new_height = cur_height;
}
if new_width != self.bin_width || new_height != self.bin_height {
if min_x > 0 || min_y > 0 {
for rect in &mut self.rects_used {
rect.set_x_total(rect.x_total() - min_x);
rect.set_y_total(rect.y_total() - min_y);
}
for rect in &mut self.rects_free {
rect.set_x_total(rect.x_total() - min_x);
rect.set_y_total(rect.y_total() - min_y);
}
}
self.bin_width = new_width;
self.bin_height = new_height;
}
}
fn insert(&mut self, dim: &Dimension) -> Option<Rectangle> {
self.insert(
dim,
self.default_merge,
self.default_rect_choice,
self.default_split_method,
)
}
fn insert_list(&mut self, nodes: &[Dimension]) -> (Vec<Rectangle>, Vec<Dimension>) {
self.insert_list(
nodes,
self.default_merge,
self.default_rect_choice,
self.default_split_method,
)
}
fn occupancy(&self) -> f32 {
if self.bin_width == 0 || self.bin_height == 0 {
return 0.0;
}
let area: i64 = self.rects_used.iter().map(|r| r.dim().area()).sum();
area as f32 / (self.bin_width * self.bin_height) as f32
}
fn as_slice(&self) -> &[Rectangle] {
&self.rects_used
}
fn is_empty(&self) -> bool {
self.rects_used.is_empty()
}
fn len(&self) -> usize {
self.rects_used.len()
}
fn iter(&self) -> Iter<'_, Rectangle> {
self.rects_used.iter()
}
fn find_by_id(&self, id: isize) -> Option<Rectangle> {
self.rects_used
.iter()
.find(|&n| n.dim().id() == id)
.map(|r| r.to_owned())
}
fn visualize(&self) -> String {
if let Some(output) = visualize_bin(self.bin_width, self.bin_height, &self.rects_used) {
output
} else {
format!("{self}")
}
}
}
impl GuillotineBin {
pub fn new(width: i32, height: i32) -> Self {
Self::with_capacity(width, height, 4)
}
pub fn with_capacity(width: i32, height: i32, capacity: usize) -> Self {
let mut result = Self {
bin_width: width.max(1),
bin_height: height.max(1),
rects_used: Vec::with_capacity(capacity.max(4)),
rects_free: Vec::with_capacity((capacity * 4).max(4 * 4)),
default_rect_choice: RectHeuristic::BestShortSideFit,
default_split_method: SplitHeuristic::ShorterLeftoverAxis,
default_merge: true,
};
result.rects_free.push(Rectangle::new(
0,
0,
Dimension::with_id(0, result.bin_width, result.bin_height, 0),
));
result
}
pub fn default_choice(&self) -> RectHeuristic {
self.default_rect_choice
}
pub fn set_default_choice(&mut self, choice: RectHeuristic) {
self.default_rect_choice = choice;
}
pub fn default_method(&self) -> SplitHeuristic {
self.default_split_method
}
pub fn set_default_method(&mut self, method: SplitHeuristic) {
self.default_split_method = method;
}
pub fn default_merge(&self) -> bool {
self.default_merge
}
pub fn set_default_merge(&mut self, merge: bool) {
self.default_merge = merge;
}
pub fn insert(
&mut self,
dim: &Dimension,
merge: bool,
choice: RectHeuristic,
method: SplitHeuristic,
) -> Option<Rectangle> {
if dim.is_empty()
|| dim.width_total() > self.bin_width
|| dim.height_total() > self.bin_height
{
return None;
}
let (free_node_index, new_rect) = self.find_position_for_new_node(dim, choice);
if let Some(new_rect) = new_rect {
let free_rect = self.rects_free[free_node_index];
self.split_free_rect_by_heuristic(&free_rect, &new_rect, method);
self.rects_free.swap_remove(free_node_index);
if merge {
self.merge_free_list();
}
self.rects_used.push(new_rect.to_owned());
Some(new_rect)
} else {
None
}
}
pub fn insert_list(
&mut self,
nodes: &[Dimension],
merge: bool,
choice: RectHeuristic,
method: SplitHeuristic,
) -> (Vec<Rectangle>, Vec<Dimension>) {
let mut inserted = Vec::with_capacity(nodes.len().max(1));
let mut rejected = nodes.to_vec();
let mut best_free_rect = 0usize;
let mut best_node = 0usize;
let mut best_flipped = false;
while !rejected.is_empty() {
let mut best_score = i32::MAX;
let mut i = 0usize;
let free_size = self.rects_free.len();
'free_loop: while i < free_size {
let free_rect = &self.rects_free[i];
let mut j = 0usize;
let nodes_size = rejected.len();
while j < nodes_size {
let node = &rejected[j];
if node.width_total() == free_rect.width_total()
&& node.height_total() == free_rect.height_total()
{
best_free_rect = i;
best_node = j;
best_flipped = false;
best_score = i32::MIN;
break 'free_loop;
} else if node.height_total() == free_rect.width_total()
&& node.width_total() == free_rect.height_total()
{
best_free_rect = i;
best_node = j;
best_flipped = true;
best_score = i32::MIN;
break 'free_loop;
} else if node.width_total() <= free_rect.width_total()
&& node.height_total() <= free_rect.height_total()
{
let score = self.score_by_heuristic(node, free_rect, choice);
if score < best_score {
best_free_rect = i;
best_node = j;
best_flipped = false;
best_score = score;
}
} else if node.height_total() <= free_rect.width_total()
&& node.width_total() <= free_rect.height_total()
{
let score = self.score_by_heuristic(
&Dimension::with_id(0, node.width_total(), node.height_total(), 0),
free_rect,
choice,
);
if score < best_score {
best_free_rect = i;
best_node = j;
best_flipped = true;
best_score = score;
}
}
j += 1;
}
i += 1;
}
if best_score == i32::MAX {
break;
}
let mut new_node = Rectangle::new(
self.rects_free[best_free_rect].x() + rejected[best_node].padding(),
self.rects_free[best_free_rect].y() + rejected[best_node].padding(),
rejected[best_node].to_owned(),
);
if best_flipped {
new_node.dim_mut().flip();
}
self.split_free_rect_by_heuristic(
&self.rects_free[best_free_rect].to_owned(),
&new_node,
method,
);
self.rects_free.swap_remove(best_free_rect);
rejected.swap_remove(best_node);
if merge {
self.merge_free_list();
}
self.rects_used.push(new_node.to_owned());
inserted.push(new_node);
}
(inserted, rejected)
}
#[allow(dead_code)]
pub(crate) fn get_free_rects(&mut self) -> &mut Vec<Rectangle> {
&mut self.rects_free
}
#[allow(dead_code)]
pub(crate) fn get_used_rects(&mut self) -> &mut Vec<Rectangle> {
&mut self.rects_used
}
fn find_position_for_new_node(
&self,
dim: &Dimension,
choice: RectHeuristic,
) -> (usize, Option<Rectangle>) {
let mut node_index = 0usize;
let mut best_node = None;
let mut best_score = i32::MAX;
for (i, rect) in self.rects_free.iter().enumerate() {
if dim.width_total() == rect.width_total() && dim.height_total() == rect.height_total()
{
let node = best_node.get_or_insert_with(|| Rectangle::new(0, 0, *dim));
node.set_location_total(rect.x_total(), rect.y_total());
node.dim_mut().set_dimension(dim.width(), dim.height());
node_index = i;
break;
} else if dim.height_total() == rect.width_total()
&& dim.width_total() == rect.height_total()
{
let node = best_node.get_or_insert_with(|| Rectangle::new(0, 0, *dim));
node.set_location_total(rect.x_total(), rect.y_total());
node.dim_mut().set_dimension(dim.height(), dim.width());
node_index = i;
break;
} else if dim.width_total() <= rect.width_total()
&& dim.height_total() <= rect.height_total()
{
let score = self.score_by_heuristic(dim, rect, choice);
if score < best_score {
let node = best_node.get_or_insert_with(|| Rectangle::new(0, 0, *dim));
node.set_location_total(rect.x_total(), rect.y_total());
node.dim_mut().set_dimension(dim.width(), dim.height());
best_score = score;
node_index = i;
}
} else if dim.height_total() <= rect.width_total()
&& dim.width_total() <= rect.height_total()
{
let score = self.score_by_heuristic(dim, rect, choice);
if score < best_score {
let node = best_node.get_or_insert_with(|| Rectangle::new(0, 0, *dim));
node.set_location_total(rect.x_total(), rect.y_total());
node.dim_mut().set_dimension(dim.height(), dim.width());
best_score = score;
node_index = i;
}
}
}
(node_index, best_node)
}
fn score_by_heuristic(
&self,
dim: &Dimension,
free_rect: &Rectangle,
choice: RectHeuristic,
) -> i32 {
match choice {
RectHeuristic::BestAreaFit => self.score_baf(dim, free_rect),
RectHeuristic::BestShortSideFit => self.score_bssf(dim, free_rect),
RectHeuristic::BestLongSideFit => self.score_blsf(dim, free_rect),
RectHeuristic::WorstAreaFit => self.score_waf(dim, free_rect),
RectHeuristic::WorstShortSideFit => self.score_wssf(dim, free_rect),
RectHeuristic::WorstLongSideFit => self.score_wlsf(dim, free_rect),
}
}
fn score_baf(&self, dim: &Dimension, free_rect: &Rectangle) -> i32 {
(free_rect.dim().area_total() - dim.area_total()) as i32
}
fn score_bssf(&self, dim: &Dimension, free_rect: &Rectangle) -> i32 {
let leftover_h = free_rect.width_total().abs_diff(dim.width_total());
let leftover_v = free_rect.height_total().abs_diff(dim.height_total());
leftover_v.min(leftover_h) as i32
}
fn score_blsf(&self, dim: &Dimension, free_rect: &Rectangle) -> i32 {
let leftover_h = free_rect.width_total().abs_diff(dim.width_total());
let leftover_v = free_rect.height_total().abs_diff(dim.height_total());
leftover_v.max(leftover_h) as i32
}
fn score_waf(&self, dim: &Dimension, free_rect: &Rectangle) -> i32 {
-self.score_baf(dim, free_rect)
}
fn score_wssf(&self, dim: &Dimension, free_rect: &Rectangle) -> i32 {
-self.score_bssf(dim, free_rect)
}
fn score_wlsf(&self, dim: &Dimension, free_rect: &Rectangle) -> i32 {
-self.score_blsf(dim, free_rect)
}
fn split_free_rect_by_heuristic(
&mut self,
free_rect: &Rectangle,
placed_rect: &Rectangle,
method: SplitHeuristic,
) {
let w = free_rect.width_total() - placed_rect.width_total();
let h = free_rect.height_total() - placed_rect.height_total();
let split_horizontal = match method {
SplitHeuristic::ShorterLeftoverAxis => w <= h,
SplitHeuristic::LongerLeftoverAxis => w > h,
SplitHeuristic::MinimizeArea => {
placed_rect.width_total() * h > w * placed_rect.height_total()
}
SplitHeuristic::MaximizeArea => {
placed_rect.width_total() * h <= w * placed_rect.height_total()
}
SplitHeuristic::ShorterAxis => free_rect.width_total() <= free_rect.height_total(),
SplitHeuristic::LongerAxis => free_rect.width_total() > free_rect.height_total(),
};
self.split_free_rect_along_axis(free_rect, placed_rect, split_horizontal);
}
fn split_free_rect_along_axis(
&mut self,
free_rect: &Rectangle,
placed_rect: &Rectangle,
split_horizontal: bool,
) {
let mut bottom = Rectangle::new(
free_rect.x_total(),
free_rect.y_total() + placed_rect.height_total(),
Dimension::with_id(
0,
0,
free_rect.height_total() - placed_rect.height_total(),
0,
),
);
let mut right = Rectangle::new(
free_rect.x_total() + placed_rect.width_total(),
free_rect.y_total(),
Dimension::with_id(0, free_rect.width_total() - placed_rect.width_total(), 0, 0),
);
if split_horizontal {
bottom.dim_mut().set_width(free_rect.width());
right.dim_mut().set_height(placed_rect.height());
} else {
bottom.dim_mut().set_width(placed_rect.width());
right.dim_mut().set_height(free_rect.height());
}
if !bottom.dim().is_empty_total() {
self.rects_free.push(bottom);
}
if !right.dim().is_empty_total() {
self.rects_free.push(right);
}
}
fn merge_free_list(&mut self) {
let mut i = 0usize;
let mut free_size = self.rects_free.len();
while i < free_size {
let mut rect1 = self.rects_free[i];
let mut j = i + 1;
while j < free_size {
let rect2 = &self.rects_free[j];
if rect1.width_total() == rect2.width_total() && rect1.x_total() == rect2.x_total()
{
if rect1.y_total() == rect2.y_total() + rect2.height_total() {
rect1.set_y_total(rect1.y_total() - rect2.height_total());
let rect1_height = rect1.height();
rect1.dim_mut().set_height(rect1_height + rect2.height());
let _ = mem::replace(&mut self.rects_free[i], rect1);
self.rects_free.swap_remove(j);
free_size -= 1;
} else if rect1.y_total() + rect1.height_total() == rect2.y_total() {
let rect1_height = rect1.height();
rect1.dim_mut().set_height(rect1_height + rect2.height());
let _ = mem::replace(&mut self.rects_free[i], rect1);
self.rects_free.swap_remove(j);
free_size -= 1;
} else {
j += 1;
}
} else if rect1.height_total() == rect2.height_total()
&& rect1.y_total() == rect2.y_total()
{
if rect1.x_total() == rect2.x_total() + rect2.width_total() {
rect1.set_x_total(rect1.x_total() - rect2.width_total());
let rect1_width = rect1.width();
rect1.dim_mut().set_width(rect1_width + rect2.width());
let _ = mem::replace(&mut self.rects_free[i], rect1);
self.rects_free.swap_remove(j);
free_size -= 1;
} else if rect1.x_total() + rect1.width_total() == rect2.x_total() {
let rect1_width = rect1.width();
rect1.dim_mut().set_width(rect1_width + rect2.width());
let _ = mem::replace(&mut self.rects_free[i], rect1);
self.rects_free.swap_remove(j);
free_size -= 1;
} else {
j += 1;
}
} else {
j += 1;
}
}
i += 1;
}
}
}
impl<Idx> std::ops::Index<Idx> for GuillotineBin
where
Idx: std::slice::SliceIndex<[Rectangle]>,
{
type Output = Idx::Output;
fn index(&self, index: Idx) -> &Self::Output {
&self.rects_used[index]
}
}
impl Display for GuillotineBin {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Bin(width: {}, height: {}, rectangles: {})",
self.bin_width,
self.bin_height,
self.rects_used.len()
)
}
}
pub fn pack_bins(
nodes: &[Dimension],
bin_width: i32,
bin_height: i32,
merge: bool,
choice: RectHeuristic,
method: SplitHeuristic,
optimized: bool,
) -> Result<Vec<GuillotineBin>, BinError> {
if optimized {
pack_bins_list(nodes, bin_width, bin_height, merge, choice, method)
} else {
pack_bins_single(nodes, bin_width, bin_height, merge, choice, method)
}
}
fn pack_bins_list(
nodes: &[Dimension],
bin_width: i32,
bin_height: i32,
merge: bool,
choice: RectHeuristic,
method: SplitHeuristic,
) -> Result<Vec<GuillotineBin>, BinError> {
let mut bins = Vec::new();
if nodes.is_empty() || bin_width == 0 || bin_height == 0 {
return Ok(bins);
}
let mut bin = GuillotineBin::new(bin_width, bin_height);
let (inserted, mut rejected) = bin.insert_list(nodes, merge, choice, method);
if inserted.is_empty() && !rejected.is_empty() {
rejected.clear();
}
if !inserted.is_empty() {
bins.push(bin);
}
let mut nodes_left = rejected;
while !nodes_left.is_empty() {
let mut bin = GuillotineBin::new(bin_width, bin_height);
let (inserted, mut rejected) = bin.insert_list(&nodes_left, merge, choice, method);
if inserted.is_empty() && !rejected.is_empty() {
let result = rejected
.iter()
.map(|r| {
if r.width_total() == 0 || r.height_total() == 0 {
BinError::ItemTooSmall
} else if r.width_total() > bin_width || r.height_total() > bin_height {
BinError::ItemTooBig
} else {
BinError::Unspecified
}
})
.next();
if let Some(result) = result {
return Err(result);
} else {
eprintln!("pack_bins(): Could not insert remaining items");
rejected.clear();
}
}
if !inserted.is_empty() {
bins.push(bin);
}
nodes_left.clear();
nodes_left.append(&mut rejected);
}
Ok(bins)
}
fn pack_bins_single(
nodes: &[Dimension],
bin_width: i32,
bin_height: i32,
merge: bool,
choice: RectHeuristic,
method: SplitHeuristic,
) -> Result<Vec<GuillotineBin>, BinError> {
let mut bins = Vec::new();
if nodes.is_empty() || bin_width == 0 || bin_height == 0 {
return Ok(bins);
}
for node in nodes {
if node.is_empty() {
return Err(BinError::ItemTooSmall);
} else if node.width_total() > bin_width || node.height_total() > bin_height {
return Err(BinError::ItemTooBig);
}
let mut inserted = false;
for bin in &mut bins {
if bin.insert(node, merge, choice, method).is_some() {
inserted = true;
break;
}
}
if !inserted {
bins.push(GuillotineBin::new(bin_width, bin_height));
if let Some(bin) = bins.last_mut() {
bin.insert(node, merge, choice, method)
.expect("Object should fit into the bin");
}
}
}
Ok(bins)
}
#[cfg(test)]
mod tests;