packer.growing.js 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. /******************************************************************************
  2. This is a binary tree based bin packing algorithm that is more complex than
  3. the simple Packer (packer.js). Instead of starting off with a fixed width and
  4. height, it starts with the width and height of the first block passed and then
  5. grows as necessary to accomodate each subsequent block. As it grows it attempts
  6. to maintain a roughly square ratio by making 'smart' choices about whether to
  7. grow right or down.
  8. When growing, the algorithm can only grow to the right OR down. Therefore, if
  9. the new block is BOTH wider and taller than the current target then it will be
  10. rejected. This makes it very important to initialize with a sensible starting
  11. width and height. If you are providing sorted input (largest first) then this
  12. will not be an issue.
  13. A potential way to solve this limitation would be to allow growth in BOTH
  14. directions at once, but this requires maintaining a more complex tree
  15. with 3 children (down, right and center) and that complexity can be avoided
  16. by simply chosing a sensible starting block.
  17. Best results occur when the input blocks are sorted by height, or even better
  18. when sorted by max(width,height).
  19. Inputs:
  20. ------
  21. blocks: array of any objects that have .w and .h attributes
  22. Outputs:
  23. -------
  24. marks each block that fits with a .fit attribute pointing to a
  25. node with .x and .y coordinates
  26. Example:
  27. -------
  28. var blocks = [
  29. { w: 100, h: 100 },
  30. { w: 100, h: 100 },
  31. { w: 80, h: 80 },
  32. { w: 80, h: 80 },
  33. etc
  34. etc
  35. ];
  36. var packer = new GrowingPacker();
  37. packer.fit(blocks);
  38. for(var n = 0 ; n < blocks.length ; n++) {
  39. var block = blocks[n];
  40. if (block.fit) {
  41. Draw(block.fit.x, block.fit.y, block.w, block.h);
  42. }
  43. }
  44. ******************************************************************************/
  45. GrowingPacker = function() { };
  46. GrowingPacker.prototype = {
  47. fit: function(blocks) {
  48. var n, node, block, len = blocks.length;
  49. var w = len > 0 ? blocks[0].w : 0;
  50. var h = len > 0 ? blocks[0].h : 0;
  51. this.root = { x: 0, y: 0, w: w, h: h };
  52. for (n = 0; n < len ; n++) {
  53. block = blocks[n];
  54. if (node = this.findNode(this.root, block.w, block.h))
  55. block.fit = this.splitNode(node, block.w, block.h);
  56. else
  57. block.fit = this.growNode(block.w, block.h);
  58. }
  59. },
  60. findNode: function(root, w, h) {
  61. if (root.used)
  62. return this.findNode(root.right, w, h) || this.findNode(root.down, w, h);
  63. else if ((w <= root.w) && (h <= root.h))
  64. return root;
  65. else
  66. return null;
  67. },
  68. splitNode: function(node, w, h) {
  69. node.used = true;
  70. node.down = { x: node.x, y: node.y + h, w: node.w, h: node.h - h };
  71. node.right = { x: node.x + w, y: node.y, w: node.w - w, h: h };
  72. return node;
  73. },
  74. growNode: function(w, h) {
  75. var canGrowDown = (w <= this.root.w);
  76. var canGrowRight = (h <= this.root.h);
  77. var shouldGrowRight = canGrowRight && (this.root.h >= (this.root.w + w)); // attempt to keep square-ish by growing right when height is much greater than width
  78. var shouldGrowDown = canGrowDown && (this.root.w >= (this.root.h + h)); // attempt to keep square-ish by growing down when width is much greater than height
  79. if (shouldGrowRight)
  80. return this.growRight(w, h);
  81. else if (shouldGrowDown)
  82. return this.growDown(w, h);
  83. else if (canGrowRight)
  84. return this.growRight(w, h);
  85. else if (canGrowDown)
  86. return this.growDown(w, h);
  87. else
  88. return null; // need to ensure sensible root starting size to avoid this happening
  89. },
  90. growRight: function(w, h) {
  91. this.root = {
  92. used: true,
  93. x: 0,
  94. y: 0,
  95. w: this.root.w + w,
  96. h: this.root.h,
  97. down: this.root,
  98. right: { x: this.root.w, y: 0, w: w, h: this.root.h }
  99. };
  100. if (node = this.findNode(this.root, w, h))
  101. return this.splitNode(node, w, h);
  102. else
  103. return null;
  104. },
  105. growDown: function(w, h) {
  106. this.root = {
  107. used: true,
  108. x: 0,
  109. y: 0,
  110. w: this.root.w,
  111. h: this.root.h + h,
  112. down: { x: 0, y: this.root.h, w: this.root.w, h: h },
  113. right: this.root
  114. };
  115. if (node = this.findNode(this.root, w, h))
  116. return this.splitNode(node, w, h);
  117. else
  118. return null;
  119. }
  120. }