GuillotineBinPack.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632
  1. /** @file GuillotineBinPack.cpp
  2. @author Jukka Jylänki
  3. @brief Implements different bin packer algorithms that use the GUILLOTINE data structure.
  4. This work is released to Public Domain, do whatever you want with it.
  5. */
  6. #include "GuillotineBinPack.h"
  7. GuillotineBinPack::GuillotineBinPack()
  8. :binWidth(0),
  9. binHeight(0),
  10. allowRotate(false)
  11. {
  12. }
  13. GuillotineBinPack::GuillotineBinPack(int width, int height, bool allowRotate)
  14. {
  15. Init(width, height, allowRotate);
  16. }
  17. void GuillotineBinPack::Init(int width, int height, bool allowRotate_)
  18. {
  19. binWidth = width;
  20. binHeight = height;
  21. allowRotate = allowRotate_;
  22. #if DEBUG
  23. disjointRects.Clear();
  24. #endif
  25. // Clear any memory of previously packed rectangles.
  26. usedRectangles.clear();
  27. // We start with a single big free rectangle that spans the whole bin.
  28. Rect n;
  29. n.x = 0;
  30. n.y = 0;
  31. n.width = width;
  32. n.height = height;
  33. freeRectangles.clear();
  34. freeRectangles.add(n);
  35. }
  36. void GuillotineBinPack::Insert(MemPtr<RectSizeIndex> rects, MemPtr<RectIndex> dst, bool merge,
  37. FreeRectChoiceHeuristic rectChoice, GuillotineSplitHeuristic splitMethod)
  38. {
  39. dst.clear();
  40. // Remember variables about the best packing choice we have made so far during the iteration process.
  41. int bestFreeRect = 0;
  42. int bestRect = 0;
  43. bool bestFlipped = false;
  44. // Pack rectangles one at a time until we have cleared the rects array of all rectangles.
  45. // rects will get destroyed in the process.
  46. while(rects.elms() > 0)
  47. {
  48. // Stores the penalty score of the best rectangle placement - bigger=worse, smaller=better.
  49. int bestScore = INT_MAX;
  50. for(Int i = 0; i < freeRectangles.elms(); ++i)
  51. {
  52. for(Int j = 0; j < rects.elms(); ++j)
  53. {
  54. // If this rectangle is a perfect match, we pick it instantly.
  55. if (rects[j].width == freeRectangles[i].width && rects[j].height == freeRectangles[i].height)
  56. {
  57. bestFreeRect = i;
  58. bestRect = j;
  59. bestFlipped = false;
  60. bestScore = INT_MIN;
  61. i = freeRectangles.elms(); // Force a jump out of the outer loop as well - we got an instant fit.
  62. break;
  63. }
  64. // If flipping this rectangle is a perfect match, pick that then.
  65. else if (allowRotate && rects[j].height == freeRectangles[i].width && rects[j].width == freeRectangles[i].height)
  66. {
  67. bestFreeRect = i;
  68. bestRect = j;
  69. bestFlipped = true;
  70. bestScore = INT_MIN;
  71. i = freeRectangles.elms(); // Force a jump out of the outer loop as well - we got an instant fit.
  72. break;
  73. }
  74. // Try if we can fit the rectangle upright.
  75. else if (rects[j].width <= freeRectangles[i].width && rects[j].height <= freeRectangles[i].height)
  76. {
  77. int score = ScoreByHeuristic(rects[j].width, rects[j].height, freeRectangles[i], rectChoice);
  78. if (score < bestScore)
  79. {
  80. bestFreeRect = i;
  81. bestRect = j;
  82. bestFlipped = false;
  83. bestScore = score;
  84. }
  85. }
  86. // If not, then perhaps flipping sideways will make it fit?
  87. else if (allowRotate && rects[j].height <= freeRectangles[i].width && rects[j].width <= freeRectangles[i].height)
  88. {
  89. int score = ScoreByHeuristic(rects[j].height, rects[j].width, freeRectangles[i], rectChoice);
  90. if (score < bestScore)
  91. {
  92. bestFreeRect = i;
  93. bestRect = j;
  94. bestFlipped = true;
  95. bestScore = score;
  96. }
  97. }
  98. }
  99. }
  100. // If we didn't manage to find any rectangle to pack, abort.
  101. if (bestScore == INT_MAX)
  102. return;
  103. // Otherwise, we're good to go and do the actual packing.
  104. C RectSizeIndex &rect =rects[bestRect];
  105. RectIndex &newNode=dst.New();
  106. newNode.x = freeRectangles[bestFreeRect].x;
  107. newNode.y = freeRectangles[bestFreeRect].y;
  108. newNode.width = rect.width;
  109. newNode.height = rect.height;
  110. newNode.index = rect.index;
  111. if (bestFlipped)
  112. Swap(newNode.width, newNode.height);
  113. // Remove the free space we lost in the bin.
  114. SplitFreeRectByHeuristic(freeRectangles[bestFreeRect], newNode, splitMethod);
  115. freeRectangles.remove(bestFreeRect);
  116. // Remove the rectangle we just packed from the input list.
  117. rects.remove(bestRect);
  118. // Perform a Rectangle Merge step if desired.
  119. if (merge)
  120. MergeFreeList();
  121. // Remember the new used rectangle.
  122. usedRectangles.add(newNode);
  123. // Check that we're really producing correct packings here.
  124. debug_assert(disjointRects.Add(newNode) == true);
  125. }
  126. }
  127. /*
  128. /// @return True if r fits inside freeRect (possibly rotated).
  129. bool Fits(const RectSize &r, const Rect &freeRect, bool allowRotate)
  130. {
  131. return (r.width <= freeRect.width && r.height <= freeRect.height) ||
  132. (allowRotate && r.height <= freeRect.width && r.width <= freeRect.height);
  133. }
  134. /// @return True if r fits perfectly inside freeRect, i.e. the leftover area is 0.
  135. bool FitsPerfectly(const RectSize &r, const Rect &freeRect, bool allowRotate)
  136. {
  137. return (r.width == freeRect.width && r.height == freeRect.height) ||
  138. (allowRotate && r.height == freeRect.width && r.width == freeRect.height);
  139. }
  140. // A helper function for GUILLOTINE-MAXFITTING. Counts how many rectangles fit into the given rectangle
  141. // after it has been split.
  142. void CountNumFitting(const Rect &freeRect, int width, int height, const std::vector<RectSize> &rects,
  143. int usedRectIndex, bool splitHorizontal, int &score1, int &score2)
  144. {
  145. const int w = freeRect.width - width;
  146. const int h = freeRect.height - height;
  147. Rect bottom;
  148. bottom.x = freeRect.x;
  149. bottom.y = freeRect.y + height;
  150. bottom.height = h;
  151. Rect right;
  152. right.x = freeRect.x + width;
  153. right.y = freeRect.y;
  154. right.width = w;
  155. if (splitHorizontal)
  156. {
  157. bottom.width = freeRect.width;
  158. right.height = height;
  159. }
  160. else // Split vertically
  161. {
  162. bottom.width = width;
  163. right.height = freeRect.height;
  164. }
  165. int fitBottom = 0;
  166. int fitRight = 0;
  167. for(Int i = 0; i < rects.elms(); ++i)
  168. if (i != usedRectIndex)
  169. {
  170. if (FitsPerfectly(rects[i], bottom))
  171. fitBottom |= 0x10000000;
  172. if (FitsPerfectly(rects[i], right))
  173. fitRight |= 0x10000000;
  174. if (Fits(rects[i], bottom))
  175. ++fitBottom;
  176. if (Fits(rects[i], right))
  177. ++fitRight;
  178. }
  179. score1 = Min(fitBottom, fitRight);
  180. score2 = Max(fitBottom, fitRight);
  181. }
  182. */
  183. /*
  184. // Implements GUILLOTINE-MAXFITTING, an experimental heuristic that's really cool but didn't quite work in practice.
  185. void GuillotineBinPack::InsertMaxFitting(std::vector<RectSize> &rects, std::vector<Rect> &dst, bool merge,
  186. FreeRectChoiceHeuristic rectChoice, GuillotineSplitHeuristic splitMethod)
  187. {
  188. dst.clear();
  189. int bestRect = 0;
  190. bool bestFlipped = false;
  191. bool bestSplitHorizontal = false;
  192. // Pick rectangles one at a time and pack the one that leaves the most choices still open.
  193. while(rects.elms() > 0 && freeRectangles.elms() > 0)
  194. {
  195. int bestScore1 = -1;
  196. int bestScore2 = -1;
  197. ///\todo Different sort predicates.
  198. clb::sort::QuickSort(&freeRectangles[0], freeRectangles.elms(), CompareRectShortSide);
  199. Rect &freeRect = freeRectangles[0];
  200. for(Int j = 0; j < rects.elms(); ++j)
  201. {
  202. int score1;
  203. int score2;
  204. if (rects[j].width == freeRect.width && rects[j].height == freeRect.height)
  205. {
  206. bestRect = j;
  207. bestFlipped = false;
  208. bestScore1 = bestScore2 = INT_MAX;
  209. break;
  210. }
  211. else if (rects[j].width <= freeRect.width && rects[j].height <= freeRect.height)
  212. {
  213. CountNumFitting(freeRect, rects[j].width, rects[j].height, rects, j, false, score1, score2);
  214. if (score1 > bestScore1 || (score1 == bestScore1 && score2 > bestScore2))
  215. {
  216. bestRect = j;
  217. bestScore1 = score1;
  218. bestScore2 = score2;
  219. bestFlipped = false;
  220. bestSplitHorizontal = false;
  221. }
  222. CountNumFitting(freeRect, rects[j].width, rects[j].height, rects, j, true, score1, score2);
  223. if (score1 > bestScore1 || (score1 == bestScore1 && score2 > bestScore2))
  224. {
  225. bestRect = j;
  226. bestScore1 = score1;
  227. bestScore2 = score2;
  228. bestFlipped = false;
  229. bestSplitHorizontal = true;
  230. }
  231. }
  232. if (rects[j].height == freeRect.width && rects[j].width == freeRect.height)
  233. {
  234. bestRect = j;
  235. bestFlipped = true;
  236. bestScore1 = bestScore2 = INT_MAX;
  237. break;
  238. }
  239. else if (rects[j].height <= freeRect.width && rects[j].width <= freeRect.height)
  240. {
  241. CountNumFitting(freeRect, rects[j].height, rects[j].width, rects, j, false, score1, score2);
  242. if (score1 > bestScore1 || (score1 == bestScore1 && score2 > bestScore2))
  243. {
  244. bestRect = j;
  245. bestScore1 = score1;
  246. bestScore2 = score2;
  247. bestFlipped = true;
  248. bestSplitHorizontal = false;
  249. }
  250. CountNumFitting(freeRect, rects[j].height, rects[j].width, rects, j, true, score1, score2);
  251. if (score1 > bestScore1 || (score1 == bestScore1 && score2 > bestScore2))
  252. {
  253. bestRect = j;
  254. bestScore1 = score1;
  255. bestScore2 = score2;
  256. bestFlipped = true;
  257. bestSplitHorizontal = true;
  258. }
  259. }
  260. }
  261. if (bestScore1 >= 0)
  262. {
  263. Rect newNode;
  264. newNode.x = freeRect.x;
  265. newNode.y = freeRect.y;
  266. newNode.width = rects[bestRect].width;
  267. newNode.height = rects[bestRect].height;
  268. if (bestFlipped)
  269. std::swap(newNode.width, newNode.height);
  270. debug_assert(disjointRects.Disjoint(newNode));
  271. SplitFreeRectAlongAxis(freeRect, newNode, bestSplitHorizontal);
  272. rects.remove(bestRect);
  273. if (merge)
  274. MergeFreeList();
  275. usedRectangles.add(newNode);
  276. #if DEBUG
  277. disjointRects.Add(newNode);
  278. #endif
  279. }
  280. freeRectangles.erase(freeRectangles.begin());
  281. }
  282. }
  283. */
  284. Rect GuillotineBinPack::Insert(int width, int height, bool merge, FreeRectChoiceHeuristic rectChoice,
  285. GuillotineSplitHeuristic splitMethod)
  286. {
  287. // Find where to put the new rectangle.
  288. int freeNodeIndex = 0;
  289. Rect newRect = FindPositionForNewNode(width, height, rectChoice, &freeNodeIndex);
  290. // Abort if we didn't have enough space in the bin.
  291. if (newRect.height == 0)
  292. return newRect;
  293. // Remove the space that was just consumed by the new rectangle.
  294. SplitFreeRectByHeuristic(freeRectangles[freeNodeIndex], newRect, splitMethod);
  295. freeRectangles.remove(freeNodeIndex);
  296. // Perform a Rectangle Merge step if desired.
  297. if (merge)
  298. MergeFreeList();
  299. // Remember the new used rectangle.
  300. usedRectangles.add(newRect);
  301. // Check that we're really producing correct packings here.
  302. debug_assert(disjointRects.Add(newRect) == true);
  303. return newRect;
  304. }
  305. /// Computes the ratio of used surface area to the total bin area.
  306. float GuillotineBinPack::Occupancy() const
  307. {
  308. ///\todo The occupancy rate could be cached/tracked incrementally instead
  309. /// of looping through the list of packed rectangles here.
  310. unsigned long usedSurfaceArea = 0;
  311. for(Int i = 0; i < usedRectangles.elms(); ++i)
  312. usedSurfaceArea += usedRectangles[i].width * usedRectangles[i].height;
  313. return (float)usedSurfaceArea / (binWidth * binHeight);
  314. }
  315. /// Returns the heuristic score value for placing a rectangle of size width*height into freeRect. Does not try to rotate.
  316. int GuillotineBinPack::ScoreByHeuristic(int width, int height, const Rect &freeRect, FreeRectChoiceHeuristic rectChoice)
  317. {
  318. switch(rectChoice)
  319. {
  320. case RectBestAreaFit: return ScoreBestAreaFit(width, height, freeRect);
  321. case RectBestShortSideFit: return ScoreBestShortSideFit(width, height, freeRect);
  322. case RectBestLongSideFit: return ScoreBestLongSideFit(width, height, freeRect);
  323. case RectWorstAreaFit: return ScoreWorstAreaFit(width, height, freeRect);
  324. case RectWorstShortSideFit: return ScoreWorstShortSideFit(width, height, freeRect);
  325. case RectWorstLongSideFit: return ScoreWorstLongSideFit(width, height, freeRect);
  326. default: assert(false); return INT_MAX;
  327. }
  328. }
  329. int GuillotineBinPack::ScoreBestAreaFit(int width, int height, const Rect &freeRect)
  330. {
  331. return freeRect.width * freeRect.height - width * height;
  332. }
  333. int GuillotineBinPack::ScoreBestShortSideFit(int width, int height, const Rect &freeRect)
  334. {
  335. int leftoverHoriz = abs(freeRect.width - width);
  336. int leftoverVert = abs(freeRect.height - height);
  337. int leftover = Min(leftoverHoriz, leftoverVert);
  338. return leftover;
  339. }
  340. int GuillotineBinPack::ScoreBestLongSideFit(int width, int height, const Rect &freeRect)
  341. {
  342. int leftoverHoriz = abs(freeRect.width - width);
  343. int leftoverVert = abs(freeRect.height - height);
  344. int leftover = Max(leftoverHoriz, leftoverVert);
  345. return leftover;
  346. }
  347. int GuillotineBinPack::ScoreWorstAreaFit(int width, int height, const Rect &freeRect)
  348. {
  349. return -ScoreBestAreaFit(width, height, freeRect);
  350. }
  351. int GuillotineBinPack::ScoreWorstShortSideFit(int width, int height, const Rect &freeRect)
  352. {
  353. return -ScoreBestShortSideFit(width, height, freeRect);
  354. }
  355. int GuillotineBinPack::ScoreWorstLongSideFit(int width, int height, const Rect &freeRect)
  356. {
  357. return -ScoreBestLongSideFit(width, height, freeRect);
  358. }
  359. Rect GuillotineBinPack::FindPositionForNewNode(int width, int height, FreeRectChoiceHeuristic rectChoice, int *nodeIndex)
  360. {
  361. Rect bestNode;
  362. memset(&bestNode, 0, sizeof(Rect));
  363. int bestScore = INT_MAX;
  364. /// Try each free rectangle to find the best one for placement.
  365. for(Int i = 0; i < freeRectangles.elms(); ++i)
  366. {
  367. // If this is a perfect fit upright, choose it immediately.
  368. if (width == freeRectangles[i].width && height == freeRectangles[i].height)
  369. {
  370. bestNode.x = freeRectangles[i].x;
  371. bestNode.y = freeRectangles[i].y;
  372. bestNode.width = width;
  373. bestNode.height = height;
  374. bestScore = INT_MIN;
  375. *nodeIndex = i;
  376. debug_assert(disjointRects.Disjoint(bestNode));
  377. break;
  378. }
  379. // If this is a perfect fit sideways, choose it.
  380. else if (allowRotate && height == freeRectangles[i].width && width == freeRectangles[i].height)
  381. {
  382. bestNode.x = freeRectangles[i].x;
  383. bestNode.y = freeRectangles[i].y;
  384. bestNode.width = height;
  385. bestNode.height = width;
  386. bestScore = INT_MIN;
  387. *nodeIndex = i;
  388. debug_assert(disjointRects.Disjoint(bestNode));
  389. break;
  390. }
  391. // Does the rectangle fit upright?
  392. else if (width <= freeRectangles[i].width && height <= freeRectangles[i].height)
  393. {
  394. int score = ScoreByHeuristic(width, height, freeRectangles[i], rectChoice);
  395. if (score < bestScore)
  396. {
  397. bestNode.x = freeRectangles[i].x;
  398. bestNode.y = freeRectangles[i].y;
  399. bestNode.width = width;
  400. bestNode.height = height;
  401. bestScore = score;
  402. *nodeIndex = i;
  403. debug_assert(disjointRects.Disjoint(bestNode));
  404. }
  405. }
  406. // Does the rectangle fit sideways?
  407. else if (allowRotate && height <= freeRectangles[i].width && width <= freeRectangles[i].height)
  408. {
  409. int score = ScoreByHeuristic(height, width, freeRectangles[i], rectChoice);
  410. if (score < bestScore)
  411. {
  412. bestNode.x = freeRectangles[i].x;
  413. bestNode.y = freeRectangles[i].y;
  414. bestNode.width = height;
  415. bestNode.height = width;
  416. bestScore = score;
  417. *nodeIndex = i;
  418. debug_assert(disjointRects.Disjoint(bestNode));
  419. }
  420. }
  421. }
  422. return bestNode;
  423. }
  424. void GuillotineBinPack::SplitFreeRectByHeuristic(const Rect &freeRect, const Rect &placedRect, GuillotineSplitHeuristic method)
  425. {
  426. // Compute the lengths of the leftover area.
  427. const int w = freeRect.width - placedRect.width;
  428. const int h = freeRect.height - placedRect.height;
  429. // Placing placedRect into freeRect results in an L-shaped free area, which must be split into
  430. // two disjoint rectangles. This can be achieved with by splitting the L-shape using a single line.
  431. // We have two choices: horizontal or vertical.
  432. // Use the given heuristic to decide which choice to make.
  433. bool splitHorizontal;
  434. switch(method)
  435. {
  436. case SplitShorterLeftoverAxis:
  437. // Split along the shorter leftover axis.
  438. splitHorizontal = (w <= h);
  439. break;
  440. case SplitLongerLeftoverAxis:
  441. // Split along the longer leftover axis.
  442. splitHorizontal = (w > h);
  443. break;
  444. case SplitMinimizeArea:
  445. // Maximize the larger area == minimize the smaller area.
  446. // Tries to make the single bigger rectangle.
  447. splitHorizontal = (placedRect.width * h > w * placedRect.height);
  448. break;
  449. case SplitMaximizeArea:
  450. // Maximize the smaller area == minimize the larger area.
  451. // Tries to make the rectangles more even-sized.
  452. splitHorizontal = (placedRect.width * h <= w * placedRect.height);
  453. break;
  454. case SplitShorterAxis:
  455. // Split along the shorter total axis.
  456. splitHorizontal = (freeRect.width <= freeRect.height);
  457. break;
  458. case SplitLongerAxis:
  459. // Split along the longer total axis.
  460. splitHorizontal = (freeRect.width > freeRect.height);
  461. break;
  462. default:
  463. splitHorizontal = true;
  464. assert(false);
  465. }
  466. // Perform the actual split.
  467. SplitFreeRectAlongAxis(freeRect, placedRect, splitHorizontal);
  468. }
  469. /// This function will add the two generated rectangles into the freeRectangles array. The caller is expected to
  470. /// remove the original rectangle from the freeRectangles array after that.
  471. void GuillotineBinPack::SplitFreeRectAlongAxis(const Rect &freeRect, const Rect &placedRect, bool splitHorizontal)
  472. {
  473. // Form the two new rectangles.
  474. Rect bottom;
  475. bottom.x = freeRect.x;
  476. bottom.y = freeRect.y + placedRect.height;
  477. bottom.height = freeRect.height - placedRect.height;
  478. Rect right;
  479. right.x = freeRect.x + placedRect.width;
  480. right.y = freeRect.y;
  481. right.width = freeRect.width - placedRect.width;
  482. if (splitHorizontal)
  483. {
  484. bottom.width = freeRect.width;
  485. right.height = placedRect.height;
  486. }
  487. else // Split vertically
  488. {
  489. bottom.width = placedRect.width;
  490. right.height = freeRect.height;
  491. }
  492. // Add the new rectangles into the free rectangle pool if they weren't degenerate.
  493. if (bottom.width > 0 && bottom.height > 0)
  494. freeRectangles.add(bottom);
  495. if (right.width > 0 && right.height > 0)
  496. freeRectangles.add(right);
  497. debug_assert(disjointRects.Disjoint(bottom));
  498. debug_assert(disjointRects.Disjoint(right));
  499. }
  500. void GuillotineBinPack::MergeFreeList()
  501. {
  502. #if DEBUG
  503. DisjointRectCollection test;
  504. for(Int i = 0; i < freeRectangles.elms(); ++i)
  505. debug_assert(test.Add(freeRectangles[i]) == true);
  506. #endif
  507. // Do a Theta(n^2) loop to see if any pair of free rectangles could me merged into one.
  508. // Note that we miss any opportunities to merge three rectangles into one. (should call this function again to detect that)
  509. for(Int i = 0; i < freeRectangles.elms(); ++i)
  510. for(Int j = i+1; j < freeRectangles.elms(); ++j)
  511. {
  512. if (freeRectangles[i].width == freeRectangles[j].width && freeRectangles[i].x == freeRectangles[j].x)
  513. {
  514. if (freeRectangles[i].y == freeRectangles[j].y + freeRectangles[j].height)
  515. {
  516. freeRectangles[i].y -= freeRectangles[j].height;
  517. freeRectangles[i].height += freeRectangles[j].height;
  518. freeRectangles.remove(j);
  519. --j;
  520. }
  521. else if (freeRectangles[i].y + freeRectangles[i].height == freeRectangles[j].y)
  522. {
  523. freeRectangles[i].height += freeRectangles[j].height;
  524. freeRectangles.remove(j);
  525. --j;
  526. }
  527. }
  528. else if (freeRectangles[i].height == freeRectangles[j].height && freeRectangles[i].y == freeRectangles[j].y)
  529. {
  530. if (freeRectangles[i].x == freeRectangles[j].x + freeRectangles[j].width)
  531. {
  532. freeRectangles[i].x -= freeRectangles[j].width;
  533. freeRectangles[i].width += freeRectangles[j].width;
  534. freeRectangles.remove(j);
  535. --j;
  536. }
  537. else if (freeRectangles[i].x + freeRectangles[i].width == freeRectangles[j].x)
  538. {
  539. freeRectangles[i].width += freeRectangles[j].width;
  540. freeRectangles.remove(j);
  541. --j;
  542. }
  543. }
  544. }
  545. #if DEBUG
  546. test.Clear();
  547. for(Int i = 0; i < freeRectangles.elms(); ++i)
  548. debug_assert(test.Add(freeRectangles[i]) == true);
  549. #endif
  550. }