2

I have used the bin packing js implementation here https://github.com/jakesgordon/bin-packing

When I specify the frame size as 800x600

and Blocks size as 150x700,150x700 it would say that, it cant accommodate However, there is ample space. The same when 700x150, 700x150 is made, it would fit it.

How Do I adapt the code, so that it can dynamically rotate the block size and fits in to the frame.

The js packer used here is,

    Packer = function(w, h) {
  this.init(w, h);
};

Packer.prototype = {

  init: function(w, h) {
    this.root = { x: 0, y: 0, w: w, h: h };
  },

  fit: function(blocks) {
    var n, node, block;
    for (n = 0; n < blocks.length; n++) {
      block = blocks[n];
      if (node = this.findNode(this.root, block.w, block.h))
        block.fit = this.splitNode(node, 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;
  }

}
Code Guy
  • 2,205
  • 21
  • 49

2 Answers2

3

Am adding a second answer, as this is a radical departure from the first answer, and attempts to resolve the more central issue with the 2D Bin Packing algorithm presented in the question. With that particular algorithm, the splitNode routine generates down and right nodes available for fitting blocks, but does not take into account the possibility that as the available nodes accumulate, that the union of adjoining nodes can accommodate larger blocks...

For example, in the proposed algorithm below, given an 800x600 initial heap, placing a 500x300 block will result in the heap containing two heapBlocks of (0,300)-(800,600) and (500,0)-(800,600). These two heapBlocks will overlap in the area of (500,300)-(800,600) to ensure that the maximal heapBlock areas are represented when searching for a location to fit a block. Whereas in the 2D Bin Packing algorithm, there is no intersecting area in down or right, but rather, favors the potential overlapping space to one or the other node...

The algorithm below attempts to remedy this shortcoming by implementing an array of available heapBlocks that represent the largest available blocks, even if these heapBlocks overlap each other. The drawback is that this introduces an O(n^2) algorithm (unionAll) to manage the heap, on top of the O(n) loop (fit) of walking through the blocks to fit. Thus, this algorithm might approach O(n^3) in performance, although this might be a worse case scenario...

Packer = function(w, h) {
  this.init(w, h);
};

Packer.prototype = {

  init: function(w, h) {
    this._root = { x: 0, y: 0, w: w, h: h }
  },

  intersect: function(block0, block1) {
    //
    // Returns the intersecting block of
    // block0 and block1.
    //
    let ix0 = Math.max(block0.x0, block1.x0);
    let ix1 = Math.min(block0.x1, block1.x1);
    let iy0 = Math.max(block0.y0, block1.y0);
    let iy1 = Math.min(block0.y1, block1.y1);

    if (ix0 <= ix1 && iy0 <= iy1) {
      return {x0: ix0, y0: iy0, x1: ix1, y1: iy1};
    } else {
      return null;
    }
  },

  chunkContains:  function(heapBlock0, heapBlock1) {
    //
    // Determine whether heapBlock0 totally encompasses (ie, contains) heapBlock1.
    //
    return heapBlock0.x0 <= heapBlock1.x0 && heapBlock0.y0 <= heapBlock1.y0 && heapBlock1.x1 <= heapBlock0.x1 && heapBlock1.y1 <= heapBlock0.y1;
  },

  expand: function(heapBlock0, heapBlock1) {
    //
    // Extend heapBlock0 and heapBlock1 if they are
    // adjoining or overlapping.
    //
    if (heapBlock0.x0 <= heapBlock1.x0 && heapBlock1.x1 <= heapBlock0.x1 && heapBlock1.y0 <= heapBlock0.y1) {
      heapBlock1.y0 = Math.min(heapBlock0.y0, heapBlock1.y0);
      heapBlock1.y1 = Math.max(heapBlock0.y1, heapBlock1.y1);
    }

    if (heapBlock0.y0 <= heapBlock1.y0 && heapBlock1.y1 <= heapBlock0.y1 && heapBlock1.x0 <= heapBlock0.x1) {
      heapBlock1.x0 = Math.min(heapBlock0.x0, heapBlock1.x0);
      heapBlock1.x1 = Math.max(heapBlock0.x1, heapBlock1.x1);
    }
  },

  unionMax: function(heapBlock0, heapBlock1) {
    //
    // Given two heap blocks, determine whether...
    //
    if (heapBlock0 && heapBlock1) {
      // ...heapBlock0 and heapBlock1 intersect, and if so...
      let i = this.intersect(heapBlock0, heapBlock1);
      if (i) {
        if (this.chunkContains(heapBlock0, heapBlock1)) {
          // ...if heapBlock1 is contained by heapBlock0...
          heapBlock1 = null;
        } else if (this.chunkContains(heapBlock1, heapBlock0)) {
          // ...or if heapBlock0 is contained by heapBlock1...
          heapBlock0 = null;
        } else {
          // ...otherwise, let's expand both heapBlock0 and
          // heapBlock1 to encompass as much of the intersected
          // space as possible.  In this instance, both heapBlock0
          // and heapBlock1 will overlap.
          this.expand(heapBlock0, heapBlock1);
          this.expand(heapBlock1, heapBlock0);
        }
      }
    }
  },

  unionAll: function() {
    //
    // Loop through the entire heap, looking to eliminate duplicative
    // heapBlocks, and to extend adjoining or intersecting heapBlocks,
    // despite this introducing overlapping heapBlocks.
    //
    for (let i = 0; i < this.heap.length; i++) {
      for (let j = 0; j < this.heap.length; j++) {
        if (i !== j) {
          this.unionMax(this.heap[i],this.heap[j]);
          if (this.heap[i] && this.heap[j]) {
            if (this.chunkContains(this.heap[j], this.heap[i])) {
              this.heap[i] = null;
            } else if (this.chunkContains(this.heap[i], this.heap[j])) {
              this.heap[j] = null;
            }
          }
        }
      }
    }
    // Eliminate the duplicative (ie, nulled) heapBlocks.
    let onlyBlocks = [];
    for (let i = 0; i < this.heap.length; i++) {
      if (this.heap[i]) {
        onlyBlocks.push(this.heap[i]);
      }
    }
    this.heap = onlyBlocks;
  },

  fit: function(blocks) {
    //
    // Loop through all the blocks, looking for a heapBlock
    // that it can fit into.
    //
    this.heap = [{x0:0,y0:0,x1:this._root.w, y1: this._root.h}];
    var n, node, block;
    for (n = 0; n < blocks.length; n++) {
      block = blocks[n];
      block.rotate = false;
      if (this.findInHeap(block)) {  
        this.adjustHeap(block);
      } else {
        // If the block didn't fit in its current orientation,
        // rotate its dimensions and look again.
        block.w = block.h + (block.h = block.w, 0);
        block.rotate = true;
        if (this.findInHeap(block)) {
          this.adjustHeap(block);
        }
      }
    }  
  },

  findInHeap: function(block) {
    //
    // Find a heapBlock that can contain the block.
    //
    for (let i = 0; i < this.heap.length; i++) {
      let heapBlock = this.heap[i];
      if (heapBlock && block.w <= heapBlock.x1 - heapBlock.x0 && block.h <= heapBlock.y1 - heapBlock.y0) {
        block.x0 = heapBlock.x0;
        block.y0 = heapBlock.y0;
        block.x1 = heapBlock.x0 + block.w;
        block.y1 = heapBlock.y0 + block.h;
        return true;
      }
    }
    return false;
  },

  adjustHeap:  function(block) {
    //
    // Find all heap entries that intersect with block,
    // and adjust the heap by breaking up the heapBlock
    // into the possible 4 blocks that remain after
    // removing the intersecting portion.
    //
    let n = this.heap.length;
    for (let i = 0; i < n; i++) {
      let heapBlock = this.heap[i];
      let overlap = this.intersect(heapBlock, block);
      if (overlap) {

        // Top
        if (overlap.y1 !== heapBlock.y1) {
          this.heap.push({
            x0: heapBlock.x0,
            y0: overlap.y1,
            x1: heapBlock.x1,
            y1: heapBlock.y1
          });
        }

        // Right
        if (overlap.x1 !== heapBlock.x1) {
          this.heap.push({
            x0: overlap.x1,
            y0: heapBlock.y0,
            x1: heapBlock.x1,
            y1: heapBlock.y1
          });
        }

        // Bottom
        if (heapBlock.y0 !== overlap.y0) {
          this.heap.push({
            x0: heapBlock.x0,
            y0: heapBlock.y0,
            x1: heapBlock.x1,
            y1: overlap.y0
          });
        }

        // Left
        if (heapBlock.x0 != overlap.x0) {
          this.heap.push({
            x0: heapBlock.x0,
            y0: heapBlock.y0,
            x1: overlap.x0,
            y1: heapBlock.y1
          });
        }       

        this.heap[i] = null;
      }
    }

    this.unionAll();
  }

}

The fit algorithm will leave the heap and the incoming blocks array with the results. For example...

p = new Packer(2400,1200);
blocks = [{w:2100,h:600},{w:2100,h:600},{w:150,h:200},{w:740,h:200},{w:500,h:100}];
p.fit(blocks);

...will leave p.heap and blocks as follows...

The final HEAP
[{"x0":2100,"y0":940,"x1":2400,"y1":1200},
{"x0":2300,"y0":500,"x1":2400,"y1":1200},
{"x0":2250,"y0":0,"x1":2300,"y1":200}]

The final BLOCKS
[{"w":2100,"h":600,"rotate":false,"x0":0,"y0":0,"x1":2100,"y1":600},
{"w":2100,"h":600,"rotate":false,"x0":0,"y0":600,"x1":2100,"y1":1200},
{"w":150,"h":200,"rotate":false,"x0":2100,"y0":0,"x1":2250,"y1":200},
{"w":200,"h":740,"rotate":true,"x0":2100,"y0":200,"x1":2300,"y1":940},
{"w":100,"h":500,"rotate":true,"x0":2300,"y0":0,"x1":2400,"y1":500}]

Note that this algorithm is not optimized. Ie, it does not sort the incoming blocks (ie, by width, height, or area, etc), nor does it sort the heap after performing a unionAll which reduces the heap to the list of maximum sized heapBlocks. (Ie, after each call to unionAll, the opportunity exists to sort the heap by width, height, or area, etc so that when searching the heap for the next available block to place, if the heap is sorted largest to smallest, the algorithm will place the block in the largest available heapBlock, or if sorted smallest to largest, the block will be placed in the heapBlock that's just big enough...) In any event, will leave these types of optimizations as an exercise.

Also, please view this algorithm with some skepticism, and test appropriately.

Trentium
  • 1,542
  • 1
  • 7
  • 12
  • Genuinely, you are a great person. Unfortunately, I am not able to adapt this because the format of the blocks (properties) are all different. My project is very huge that, I am unable to adapt this answers output to be same as first answer. If you could do I am glad. Please, don't mind. Ignore my request if you are busy and without time, or my request doesn't make any sense! – Code Guy Jun 21 '19 at 13:15
  • Are you referring specifically to the block array, and the "fit" property? Eg, `fit: {x: 2100, y: 600, w: 300, h: 600, used: true}`, as you should be able to construct this property by looping through the results of the new proposed algorithm. If, though, you are referring to the `down` and `right` properties, that will introduce code that reverse engineers the results of the new proposed algorithm into the old format, which adds unnecessary complexity. Ie, the time is better spent changing the calling program to deal with the results of the new proposed algorithm. – Trentium Jun 21 '19 at 13:37
  • No I am actually referring the output of the second algo differs from first. The first one output has properties like .fit etc but the second one do not have a property called fit and other properties unlike. – Code Guy Jun 21 '19 at 13:43
  • If all you need is `fit:{ x, y, w, h, & used}`, then loop through the results of the 2nd algo to create this property from the results. If you need `down` and `right`, these are not available because the 2nd algo doesn't work the way the 1st algo does, and you will need to adapt your project to the 2nd algo. – Trentium Jun 21 '19 at 13:48
  • I am actually working on the existing code, I was instructed to optimise the features. God bless you. Thanks a lot. – Code Guy Jun 21 '19 at 13:50
1

I think the code below might do the trick...?! (Ie, I did limited testing, but for what I tested, it appears to work.)

I essentially added another option in the findnode routine to rotate the block (ie, switch the width and height dimensions) as an option if it doesn't fit in it's predefined orientation. This involved adding another property to block dubbed rotate as an indicator that the dimensions were swapped. (And the introduction of swapping and the rotate property necessitated, of course, passing block to findnode rather than w and h as in the previous code.)

Packer = function(w, h) {
  this.init(w, h);
};

Packer.prototype = {

  init: function(w, h) {
    this.root = { x: 0, y: 0, w: w, h: h };
  },

  fit: function(blocks) {
    var n, node, block;
    for (n = 0; n < blocks.length; n++) {
      block = blocks[n];
      block.rotate = false;
      if (node = this.findNode(this.root, block))
        block.fit = this.splitNode(node, block);
    }  
  },

  findNode: function(root, block) {
    if (root.used) {
      return this.findNode(root.right, block) || this.findNode(root.down, block);
    } else if ((block.w <= root.w) && (block.h <= root.h)) {
      return root;
    } else if ((block.h <= root.w) && (block.w <= root.h)) {
        let temp = block.w;
        block.w = block.h;
        block.h = temp;
        block.rotate = !block.rotate;
        return root;
    } else
      return null;
  },

  splitNode: function(node, block) {
    node.used = true;
    node.down  = { x: node.x,           y: node.y + block.h, w: node.w,           h: node.h - block.h };
    node.right = { x: node.x + block.w, y: node.y,           w: node.w - block.w, h: block.h          };
    return node;
  }
}

Hopefully this does the trick for you.

Trentium
  • 1,542
  • 1
  • 7
  • 12
  • Thank you very much, But I have tested, It seems to work when 800x600 frame size, If I reverse the frame dimensions as 600x800, It wouldnt work and would say, It wouldnt fit. For a 2400x1200, I have tested with blocks of 2100 x 600,2100 x 600,150 x 200,200 x 740 it wouldn't work. If I change the panel dimensions ro 1200x2400 it would work perfect. Any suggestions – Code Guy Jun 19 '19 at 05:23
  • I ran all four combinations of 800x600 and 600x800 with 700x150 and 150x700, and they all resulted in a fit. What might be causing the issue is that before you re-run another fit, if you're using the same Packer variable, you need to re-initialize it. Ie, `myPackerVar.init(800,600)` before performing the `myPackerVar.fit(...)`. The issue being that if you perform two `myPackerVar.fit` runs in a row, the `this.root` variable has residual information from the previous run. – Trentium Jun 19 '19 at 12:09
  • I think I see the issue. In your second example of 2400x1200, the algorithm fills in the space with the 2 blocks of 2100 x 600, leaving two areas of 300 x 600. Although contiguous, there is nothing in the algorithm that looks to combine these into a 600x1200 block, and therefore the block of 200x740 has nowhere to go. That's a different problem than rotating blocks(!). – Trentium Jun 19 '19 at 22:57
  • So you mean to tell that, this issue cannot be solved? Where do I need to introduce the code, if the issue needs a fix? – Code Guy Jun 21 '19 at 03:46
  • Solving the issue with the posted algorithm will involve (imho) traversing the tree of nodes for every execution of `findNode` to determine adjoining blocks, which introduces another layer of complexity. It's an interesting problem, somewhat related to garbage collection, so took my own crack at it, and since it's such a departure from the original response surrounding the fitting of blocks simply by rotation, included my proposed algorithm in a separate answer, . – Trentium Jun 21 '19 at 11:41