123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147 |
- /******************************************************************************
- This is a binary tree based bin packing algorithm that is more complex than
- the simple Packer (packer.js). Instead of starting off with a fixed width and
- height, it starts with the width and height of the first block passed and then
- grows as necessary to accomodate each subsequent block. As it grows it attempts
- to maintain a roughly square ratio by making 'smart' choices about whether to
- grow right or down.
- When growing, the algorithm can only grow to the right OR down. Therefore, if
- the new block is BOTH wider and taller than the current target then it will be
- rejected. This makes it very important to initialize with a sensible starting
- width and height. If you are providing sorted input (largest first) then this
- will not be an issue.
- A potential way to solve this limitation would be to allow growth in BOTH
- directions at once, but this requires maintaining a more complex tree
- with 3 children (down, right and center) and that complexity can be avoided
- by simply chosing a sensible starting block.
- Best results occur when the input blocks are sorted by height, or even better
- when sorted by max(width,height).
- Inputs:
- ------
- blocks: array of any objects that have .w and .h attributes
- Outputs:
- -------
- marks each block that fits with a .fit attribute pointing to a
- node with .x and .y coordinates
- Example:
- -------
- var blocks = [
- { w: 100, h: 100 },
- { w: 100, h: 100 },
- { w: 80, h: 80 },
- { w: 80, h: 80 },
- etc
- etc
- ];
- var packer = new GrowingPacker();
- packer.fit(blocks);
- for(var n = 0 ; n < blocks.length ; n++) {
- var block = blocks[n];
- if (block.fit) {
- Draw(block.fit.x, block.fit.y, block.w, block.h);
- }
- }
- ******************************************************************************/
- GrowingPacker = function() { };
- GrowingPacker.prototype = {
- fit: function(blocks) {
- var n, node, block, len = blocks.length;
- var w = len > 0 ? blocks[0].w : 0;
- var h = len > 0 ? blocks[0].h : 0;
- this.root = { x: 0, y: 0, w: w, h: h };
- for (n = 0; n < len ; n++) {
- block = blocks[n];
- if (node = this.findNode(this.root, block.w, block.h))
- block.fit = this.splitNode(node, block.w, block.h);
- else
- block.fit = this.growNode(block.w, block.h);
- }
- },
- findNode: function(root, w, h) {
- if (root.used)
- return this.findNode(root.right, w, h) || this.findNode(root.down, w, h);
- else if ((w <= root.w) && (h <= root.h))
- return root;
- else
- return null;
- },
- splitNode: function(node, w, h) {
- node.used = true;
- node.down = { x: node.x, y: node.y + h, w: node.w, h: node.h - h };
- node.right = { x: node.x + w, y: node.y, w: node.w - w, h: h };
- return node;
- },
- growNode: function(w, h) {
- var canGrowDown = (w <= this.root.w);
- var canGrowRight = (h <= this.root.h);
- 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
- 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
- if (shouldGrowRight)
- return this.growRight(w, h);
- else if (shouldGrowDown)
- return this.growDown(w, h);
- else if (canGrowRight)
- return this.growRight(w, h);
- else if (canGrowDown)
- return this.growDown(w, h);
- else
- return null; // need to ensure sensible root starting size to avoid this happening
- },
- growRight: function(w, h) {
- this.root = {
- used: true,
- x: 0,
- y: 0,
- w: this.root.w + w,
- h: this.root.h,
- down: this.root,
- right: { x: this.root.w, y: 0, w: w, h: this.root.h }
- };
- if (node = this.findNode(this.root, w, h))
- return this.splitNode(node, w, h);
- else
- return null;
- },
- growDown: function(w, h) {
- this.root = {
- used: true,
- x: 0,
- y: 0,
- w: this.root.w,
- h: this.root.h + h,
- down: { x: 0, y: this.root.h, w: this.root.w, h: h },
- right: this.root
- };
- if (node = this.findNode(this.root, w, h))
- return this.splitNode(node, w, h);
- else
- return null;
- }
- }
|