Search code examples
algorithmgeometryrectangles

Expand rectangles as much as possible to cover another rectangle, minimizing overlap


Given a tiled, x- and y-aligned rectangle and (potentially) a starting set of other rectangles which may overlap, I'd like to find a set of rectangles so that:

  • if no starting rectangle exists, one might be created; otherwise do not create additional rectangles
  • each of the rectangles in the starting set are expanded as much as possible
  • the overlap is minimal
  • the whole tiled rectangle's area is covered.

This smells a lot like a set cover problem, but it still is... different.

The key is that each starting rectangle's area has to be maximized while still minimizing general overlap. A good solution keeps a balance between necessary overlaps and high initial rectangles sizes.

I'd propose a rating function such as that: \left( \left( \sum\limits_{n=0}^{initial_boxes_count-1}{{box_area}_n} \right) \times extended_initial_rectangles_count \right) - \left( \sum\limits_{n=0}^{{overlapping_boxes_count}-1}{number_of_overlaps} \right)

Higher is better.

Examples (assumes a rectangle tiled into a 4x4 grid; numbers in tiles denote starting rectangle "ID"):

  • easiest case: no starting rectangles provided, can just create one and expand it fully:

    .---------------.      .---------------.
    |   |   |   |   |      | 1 | 1 | 1 | 1 |
    |---|---|---|---|      |---|---|---|---|
    |   |   |   |   |      | 1 | 1 | 1 | 1 |
    |---|---|---|---|  =>  |---|---|---|---|
    |   |   |   |   |      | 1 | 1 | 1 | 1 |
    |---|---|---|---|      |---|---|---|---|
    |   |   |   |   |      | 1 | 1 | 1 | 1 |
    ·---------------·      ·---------------·
                           rating: 16 * 1 - 0 = 16
    
  • more sophisticated:

    .---------------.      .---------------.      .---------------.
    | 1 | 1 |   |   |      | 1 | 1 | 1 | 1 |      | 1 | 1 | 2 | 2 |
    |---|---|---|---|      |---|---|---|---|      |---|---|---|---|
    | 1 | 1 |   |   |      | 1 | 1 | 1 | 1 |      | 1 | 1 | 2 | 2 |
    |---|---|---|---|  =>  |---|---|---|---|  or  |---|---|---|---|
    |   |   | 2 | 2 |      | 2 | 2 | 2 | 2 |      | 1 | 1 | 2 | 2 |
    |---|---|---|---|      |---|---|---|---|      |---|---|---|---|
    |   |   | 2 | 2 |      | 2 | 2 | 2 | 2 |      | 1 | 1 | 2 | 2 |
    ·---------------·      ·---------------·      ·---------------·
             ratings:     (4 + 4) * 2 - 0 = 16   (4 + 4) * 2 - 0 = 16
    
  • pretty bad situation, with initial overlap:

    .-----------------.      .-----------------------.
    | 1   |   |   |   |      |  1  |  1  |  1  |  1  |
    |-----|---|---|---|      |-----|-----|-----|-----|
    | 1,2 | 2 |   |   |      | 1,2 | 1,2 | 1,2 | 1,2 |
    |-----|---|---|---|  =>  |-----|-----|-----|-----|
    |     |   |   |   |      |  2  |  2  |  2  |  2  |
    |-----|---|---|---|      |-----|-----|-----|-----|
    |     |   |   |   |      |  2  |  2  |  2  |  2  |
    ·-----------------·      ·-----------------------·
        rating: (8 + 12) * 2 - (2 + 2 + 2 + 2) = 40 - 8 = 36
    
        covering with 1 only:
                             .-----------------------.
                             |  1  |  1  |  1  |  1  |
                             |-----|-----|-----|-----|
                             | 1,2 | 1,2 |  1  |  1  |
                         =>  |-----|-----|-----|-----|
                             |  1  |  1  |  1  |  1  |
                             |-----|-----|-----|-----|
                             |  1  |  1  |  1  |  1  |
                             ·-----------------------·
        rating: (16 + 2) * 1 - (2 + 2) = 18 - 4 = 16
    
  • more starting rectangles, also overlap:

    .-----------------.      .---------------------.
    | 1 | 1,2 | 2 |   |      | 1 | 1,2 | 1,2 | 1,2 |
    |---|-----|---|---|      |---|-----|-----|-----|
    | 1 |  1  |   |   |      | 1 |  1  |  1  |  1  |
    |---|-----|---|---|  =>  |---|-----|-----|-----|
    | 3 |     |   |   |      | 3 |  3  |  3  |  3  |
    |---|-----|---|---|      |---|-----|-----|-----|
    |   |     |   |   |      | 3 |  3  |  3  |  3  |
    ·-----------------·      ·---------------------·
        rating: (8 + 3 + 8) * 3 - (2 + 2 + 2) = 57 - 6 = 51
    

The starting rectangles may be located anywhere in the tiled rectangle and have any size (minimum bound 1 tile).

The starting grid might be as big as 33x33 currently, though potentially bigger in the future.

I haven't been able to reduce this problem instantiation to a well-problem, but this may only be my own inability.


My current approach to solve this in an efficient way would go like this:

if list of starting rects empty:
  create starting rect in tile (0,0)
for each starting rect:
  calculate the distances in x and y direction to the next object (or wall)
sort distances in ascending order
while free space:
  pick rect with lowest distance
  expand it in lowest distance direction

I'm unsure if this gives the optimal solution or really is the most efficient one... and naturally if there are edge cases this approach would fail on.


Solution

  • Proposed attack. Your mileage may vary. Shipping costs higher outside the EU.

    • Make a list of open tiles
    • Make a list of rectangles (dimension & corners)

    We're going to try making +1 growth steps: expand some rectangle one unit in a chosen direction. In each iteration, find the +1 with the highest score. Iterate until the entire room (large rectangle) is covered.

    Scoring suggestions:

    • Count the squares added by the extension: open squares are +1; occupied squares are -1 for each other rectangle overlapped.

    For instance, in this starting position:

    -  -  3  3
    1  1 12  -
    -  -  2  -
    

    ...if we try to extend rectangle 3 down one row, we get +1 for the empty square on the right, but -2 for overlapping both 1 and 2.

    • Divide this score by the current rectangle area. In the example above, we would have (+1 - 2) / (1*2), or -1/2 as the score for that move ... not a good idea, probably.

    The entire first iteration would consider the moves below; directions are Up-Down-Left-Right

    rect  dir  score
      1    U   0.33 = (2-1)/3
      1    D   0.33 = (2-1)/3
      1    R   0.33 = (1-0)/3
      2    U  -0.00 = (0-1)/2
      2    L   0.00 = (1-1)/2
      2    R   0.50 = (2-1)/2
      3    D   0.00 = (1-1)/2
      3    L   0.50 = (1-0)/2
    

    We have a tie for best score: 2 R and 3 L. I'll add a minor criterion of taking the greater expansion, 2 tiles over 1. This gives:

    -  -  3  3
    1  1 12  2
    -  -  2  2
    

    For the second iteration:

    rect  dir  score
      1    U   0.33 = (2-1)/3
      1    D   0.33 = (2-1)/3
      1    R   0.00 = (0-1)/3
      2    U  -0.50 = (0-2)/4
      2    L   0.00 = (1-1)/4
      3    D  -1.00 = (0-2)/2
      3    L   0.50 = (1-0)/2
    

    Naturally, the tie from last time is now the sole top choice, since the two did not conflict:

    -  3  3  3
    1  1 12  2
    -  -  2  2
    

    Possible optimization: If a +1 has no overlap, extend it as far as you can (avoiding overlap) before computing scores.

    In the final two iterations, we will similarly get 3 L and 1 D as our choices, finishing with

    3  3  3  3
    1  1 12  2
    1  1  2  2
    

    Note that this algorithm will not get the same answer for your "pretty bad example": this one will cover the entire room with 2, reducing to only 2 overlap squares. If you'd rather have 1 expand in that case, we'll need a factor for the proportion of another rectangle that you're covering, instead of my constant value of 1.

    Does that look like a tractable starting point for you?