ShelfBinPack.cpp 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389
  1. /** @file ShelfBinPack.cpp
  2. @author Jukka Jylänki
  3. @brief Implements different bin packer algorithms that use the SHELF data structure.
  4. This work is released to Public Domain, do whatever you want with it.
  5. */
  6. #include "ShelfBinPack.h"
  7. ShelfBinPack::ShelfBinPack()
  8. :binWidth(0),
  9. binHeight(0),
  10. allowRotate(false),
  11. useWasteMap(false),
  12. currentY(0),
  13. usedSurfaceArea(0)
  14. {
  15. }
  16. ShelfBinPack::ShelfBinPack(int width, int height, bool useWasteMap, bool allowRotate)
  17. {
  18. Init(width, height, useWasteMap, allowRotate);
  19. }
  20. void ShelfBinPack::Init(int width, int height, bool useWasteMap_, bool allowRotate_)
  21. {
  22. useWasteMap = useWasteMap_;
  23. allowRotate = allowRotate_;
  24. binWidth = width;
  25. binHeight = height;
  26. currentY = 0;
  27. usedSurfaceArea = 0;
  28. shelves.clear();
  29. StartNewShelf(0);
  30. if (useWasteMap)
  31. {
  32. wasteMap.Init(width, height, allowRotate);
  33. wasteMap.GetFreeRectangles().clear();
  34. }
  35. }
  36. bool ShelfBinPack::CanStartNewShelf(int height) const
  37. {
  38. return shelves.last().startY + shelves.last().height + height <= binHeight;
  39. }
  40. void ShelfBinPack::StartNewShelf(int startingHeight)
  41. {
  42. if (shelves.elms() > 0)
  43. {
  44. assert(shelves.last().height != 0);
  45. currentY += shelves.last().height;
  46. assert(currentY < binHeight);
  47. }
  48. Shelf shelf;
  49. shelf.currentX = 0;
  50. shelf.height = startingHeight;
  51. shelf.startY = currentY;
  52. assert(shelf.startY + shelf.height <= binHeight);
  53. shelves.add(shelf);
  54. }
  55. bool ShelfBinPack::FitsOnShelf(const Shelf &shelf, int width, int height, bool canResize) const
  56. {
  57. const int shelfHeight = canResize ? (binHeight - shelf.startY) : shelf.height;
  58. if ((shelf.currentX + width <= binWidth && height <= shelfHeight) ||
  59. (allowRotate && shelf.currentX + height <= binWidth && width <= shelfHeight))
  60. return true;
  61. else
  62. return false;
  63. }
  64. void ShelfBinPack::RotateToShelf(const Shelf &shelf, int &width, int &height) const
  65. {
  66. // If the width > height and the long edge of the new rectangle fits vertically onto the current shelf,
  67. // flip it. If the short edge is larger than the current shelf height, store
  68. // the short edge vertically.
  69. if ((width > height && width > binWidth - shelf.currentX) ||
  70. (width > height && width < shelf.height) ||
  71. (width < height && height > shelf.height && height <= binWidth - shelf.currentX))
  72. Swap(width, height);
  73. }
  74. void ShelfBinPack::AddToShelf(Shelf &shelf, int width, int height, Rect &newNode)
  75. {
  76. assert(FitsOnShelf(shelf, width, height, true));
  77. // Swap width and height if the rect fits better that way.
  78. if(allowRotate)RotateToShelf(shelf, width, height);
  79. // Add the rectangle to the shelf.
  80. newNode.x = shelf.currentX;
  81. newNode.y = shelf.startY;
  82. newNode.width = width;
  83. newNode.height = height;
  84. shelf.usedRectangles.add(newNode);
  85. // Advance the shelf end position horizontally.
  86. shelf.currentX += width;
  87. assert(shelf.currentX <= binWidth);
  88. // Grow the shelf height.
  89. shelf.height = Max(shelf.height, height);
  90. assert(shelf.height <= binHeight);
  91. usedSurfaceArea += width * height;
  92. }
  93. Rect ShelfBinPack::Insert(int width, int height, ShelfChoiceHeuristic method)
  94. {
  95. Rect newNode;
  96. // First try to pack this rectangle into the waste map, if it fits.
  97. if (useWasteMap)
  98. {
  99. newNode = wasteMap.Insert(width, height, true, GuillotineBinPack::RectBestShortSideFit,
  100. GuillotineBinPack::SplitMaximizeArea);
  101. if (newNode.height != 0)
  102. {
  103. // Track the space we just used.
  104. usedSurfaceArea += width * height;
  105. return newNode;
  106. }
  107. }
  108. switch(method)
  109. {
  110. case ShelfNextFit:
  111. if (FitsOnShelf(shelves.last(), width, height, true))
  112. {
  113. AddToShelf(shelves.last(), width, height, newNode);
  114. return newNode;
  115. }
  116. break;
  117. case ShelfFirstFit:
  118. for(Int i = 0; i < shelves.elms(); ++i)
  119. if (FitsOnShelf(shelves[i], width, height, i == shelves.elms()-1))
  120. {
  121. AddToShelf(shelves[i], width, height, newNode);
  122. return newNode;
  123. }
  124. break;
  125. case ShelfBestAreaFit:
  126. {
  127. // Best Area Fit rule: Choose the shelf with smallest remaining shelf area.
  128. Shelf *bestShelf = 0;
  129. unsigned long bestShelfSurfaceArea = (unsigned long)-1;
  130. for(Int i = 0; i < shelves.elms(); ++i)
  131. {
  132. // Pre-rotate the rect onto the shelf here already so that the area fit computation
  133. // is done correctly.
  134. RotateToShelf(shelves[i], width, height);
  135. if (FitsOnShelf(shelves[i], width, height, i == shelves.elms()-1))
  136. {
  137. unsigned long surfaceArea = (binWidth - shelves[i].currentX) * shelves[i].height;
  138. if (surfaceArea < bestShelfSurfaceArea)
  139. {
  140. bestShelf = &shelves[i];
  141. bestShelfSurfaceArea = surfaceArea;
  142. }
  143. }
  144. }
  145. if (bestShelf)
  146. {
  147. AddToShelf(*bestShelf, width, height, newNode);
  148. return newNode;
  149. }
  150. }
  151. break;
  152. case ShelfWorstAreaFit:
  153. {
  154. // Worst Area Fit rule: Choose the shelf with smallest remaining shelf area.
  155. Shelf *bestShelf = 0;
  156. int bestShelfSurfaceArea = -1;
  157. for(Int i = 0; i < shelves.elms(); ++i)
  158. {
  159. // Pre-rotate the rect onto the shelf here already so that the area fit computation
  160. // is done correctly.
  161. RotateToShelf(shelves[i], width, height);
  162. if (FitsOnShelf(shelves[i], width, height, i == shelves.elms()-1))
  163. {
  164. int surfaceArea = (binWidth - shelves[i].currentX) * shelves[i].height;
  165. if (surfaceArea > bestShelfSurfaceArea)
  166. {
  167. bestShelf = &shelves[i];
  168. bestShelfSurfaceArea = surfaceArea;
  169. }
  170. }
  171. }
  172. if (bestShelf)
  173. {
  174. AddToShelf(*bestShelf, width, height, newNode);
  175. return newNode;
  176. }
  177. }
  178. break;
  179. case ShelfBestHeightFit:
  180. {
  181. // Best Height Fit rule: Choose the shelf with best-matching height.
  182. Shelf *bestShelf = 0;
  183. int bestShelfHeightDifference = 0x7FFFFFFF;
  184. for(Int i = 0; i < shelves.elms(); ++i)
  185. {
  186. // Pre-rotate the rect onto the shelf here already so that the height fit computation
  187. // is done correctly.
  188. RotateToShelf(shelves[i], width, height);
  189. if (FitsOnShelf(shelves[i], width, height, i == shelves.elms()-1))
  190. {
  191. int heightDifference = Max(shelves[i].height - height, 0);
  192. assert(heightDifference >= 0);
  193. if (heightDifference < bestShelfHeightDifference)
  194. {
  195. bestShelf = &shelves[i];
  196. bestShelfHeightDifference = heightDifference;
  197. }
  198. }
  199. }
  200. if (bestShelf)
  201. {
  202. AddToShelf(*bestShelf, width, height, newNode);
  203. return newNode;
  204. }
  205. }
  206. break;
  207. case ShelfBestWidthFit:
  208. {
  209. // Best Width Fit rule: Choose the shelf with smallest remaining shelf width.
  210. Shelf *bestShelf = 0;
  211. int bestShelfWidthDifference = 0x7FFFFFFF;
  212. for(Int i = 0; i < shelves.elms(); ++i)
  213. {
  214. // Pre-rotate the rect onto the shelf here already so that the height fit computation
  215. // is done correctly.
  216. RotateToShelf(shelves[i], width, height);
  217. if (FitsOnShelf(shelves[i], width, height, i == shelves.elms()-1))
  218. {
  219. int widthDifference = binWidth - shelves[i].currentX - width;
  220. assert(widthDifference >= 0);
  221. if (widthDifference < bestShelfWidthDifference)
  222. {
  223. bestShelf = &shelves[i];
  224. bestShelfWidthDifference = widthDifference;
  225. }
  226. }
  227. }
  228. if (bestShelf)
  229. {
  230. AddToShelf(*bestShelf, width, height, newNode);
  231. return newNode;
  232. }
  233. }
  234. break;
  235. case ShelfWorstWidthFit:
  236. {
  237. // Worst Width Fit rule: Choose the shelf with smallest remaining shelf width.
  238. Shelf *bestShelf = 0;
  239. int bestShelfWidthDifference = -1;
  240. for(Int i = 0; i < shelves.elms(); ++i)
  241. {
  242. // Pre-rotate the rect onto the shelf here already so that the height fit computation
  243. // is done correctly.
  244. RotateToShelf(shelves[i], width, height);
  245. if (FitsOnShelf(shelves[i], width, height, i == shelves.elms()-1))
  246. {
  247. int widthDifference = binWidth - shelves[i].currentX - width;
  248. assert(widthDifference >= 0);
  249. if (widthDifference > bestShelfWidthDifference)
  250. {
  251. bestShelf = &shelves[i];
  252. bestShelfWidthDifference = widthDifference;
  253. }
  254. }
  255. }
  256. if (bestShelf)
  257. {
  258. AddToShelf(*bestShelf, width, height, newNode);
  259. return newNode;
  260. }
  261. }
  262. break;
  263. }
  264. // The rectangle did not fit on any of the shelves. Open a new shelf.
  265. // Flip the rectangle so that the long side is horizontal.
  266. if (allowRotate && width < height && height <= binWidth)
  267. Swap(width, height);
  268. if(width<=binWidth && CanStartNewShelf(height))
  269. {
  270. if (useWasteMap)
  271. MoveShelfToWasteMap(shelves.last());
  272. StartNewShelf(height);
  273. if(FitsOnShelf(shelves.last(), width, height, true))
  274. {
  275. AddToShelf(shelves.last(), width, height, newNode);
  276. return newNode;
  277. }
  278. }
  279. /*
  280. ///\todo This is problematic: If we couldn't start a new shelf - should we give up
  281. /// and move all the remaining space of the bin for the waste map to track,
  282. /// or should we just wait if the next rectangle would fit better? For now,
  283. /// don't add the leftover space to the waste map.
  284. else if (useWasteMap)
  285. {
  286. assert(binHeight - shelves.last().startY >= shelves.last().height);
  287. shelves.last().height = binHeight - shelves.last().startY;
  288. if (shelves.last().height > 0)
  289. MoveShelfToWasteMap(shelves.last());
  290. // Try to pack the rectangle again to the waste map.
  291. GuillotineBinPack::Node node = wasteMap.Insert(width, height, true, 1, 3);
  292. if (node.height != 0)
  293. {
  294. newNode.x = node.x;
  295. newNode.y = node.y;
  296. newNode.width = node.width;
  297. newNode.height = node.height;
  298. return newNode;
  299. }
  300. }
  301. */
  302. // The rectangle didn't fit.
  303. memset(&newNode, 0, sizeof(Rect));
  304. return newNode;
  305. }
  306. void ShelfBinPack::MoveShelfToWasteMap(Shelf &shelf)
  307. {
  308. Memc<Rect> &freeRects = wasteMap.GetFreeRectangles();
  309. // Add the gaps between each rect top and shelf ceiling to the waste map.
  310. for(Int i = 0; i < shelf.usedRectangles.elms(); ++i)
  311. {
  312. const Rect &r = shelf.usedRectangles[i];
  313. Rect newNode;
  314. newNode.x = r.x;
  315. newNode.y = r.y + r.height;
  316. newNode.width = r.width;
  317. newNode.height = shelf.height - r.height;
  318. if (newNode.height > 0)
  319. freeRects.add(newNode);
  320. }
  321. shelf.usedRectangles.clear();
  322. // Add the space after the shelf end (right side of the last rect) and the shelf right side.
  323. Rect newNode;
  324. newNode.x = shelf.currentX;
  325. newNode.y = shelf.startY;
  326. newNode.width = binWidth - shelf.currentX;
  327. newNode.height = shelf.height;
  328. if (newNode.width > 0)
  329. freeRects.add(newNode);
  330. // This shelf is DONE.
  331. shelf.currentX = binWidth;
  332. // Perform a rectangle merge step.
  333. wasteMap.MergeFreeList();
  334. }
  335. /// Computes the ratio of used surface area to the bin area.
  336. float ShelfBinPack::Occupancy() const
  337. {
  338. return (float)usedSurfaceArea / (binWidth * binHeight);
  339. }