First read my post Isolating distinct, arbitrary shapes on a 2D grid.
In this post I’ll explain how to break up polygons on a 2D grid into distinct rectangles. This is useful, for example, if you’re aiming to add shapes to Box2D physics engine, as convex shapes cannot be used in Box2D. The easiest way to eliminate concavity is slicing your polygons into smaller rectangles.
Here’s the 2D grid from the previous post, with two polygonal shapes (n = 2) distinguished on the grid:
{0,0,0,0,0,0}
{0,1,1,0,0,0}
{0,1,1,0,0,2}
{1,1,0,2,0,2}
{0,0,0,2,0,2}
{0,0,2,2,2,2}
This algorithm requires you to iterate the grid left-to-right, top-to-bottom. You’ll need to know the how many distinct shapes are already present on the grid (i.e. n = 2, in this example).
The method is to find how long the first row of the shape r1 is, assign each cell in r1 with the value vx,y = n, and then copy down r1 to rows rj, 1 < j < h – j (h = height of grid) below, as many times as it will continue to fit within the shape.
For example:
{0,0,0,0}
{0,1,1,0} <-- r1
{0,1,1,0}
{1,1,0,0}
r1 of the shape currently with v = 1, contains cells (x2,y2) and (x3,y2) and it’s width is 2. Give those cells the value n++ (in this case, 2). r1 can be copied down to r2 successfully, but not r3. This is because r3 has the same width as r1, but it’s left-most cell is obviously not in the same column as r1’s left-most cell.
So the algorithm would identify the first two rows as part of one rectangle, and further on would distinguish the third row as another rectangle, assigning it’s cells the value n++:
{0,0,0,0}
{0,2,2,0}
{0,2,2,0}
{3,3,0,0}
To return to the original example, for each cell (xi, yj) with a value vi where i > 0, and v < n, find out how long it’s row is by checking each tile to it’s right i.e. xi+k where k is iterated between k = 1 and k = w – x (the width of the grid – x). Stop when you reach a cell where vi+k != vi. The length of the row ri will then be l = j-i. Now, iterate yi+k, where 1 < k < h – y (where h is the height of the grid). Continue until you can no longer fit l tiles into the row ri+k. The number of rows you can copy down will be m = k – i.
Your identified rectangle will be of size l x m, starting at point (xi, yj). Each cell in that rectangle is now assigned the value vx,y = n. Once you’ve done that, increment n and continue running the algorithm.
Once you’ve finished you’ll end up with this:
{0,0,0,0,0,0}
{0,3,3,0,0,0}
{0,3,3,0,0,4}
{5,5,0,6,0,4}
{0,0,0,6,0,4}
{0,0,7,6,8,4}
You can then “normalise” the grid the so the lowest shape number is 1:
{0,0,0,0,0,0}
{0,1,1,0,0,0}
{0,1,1,0,0,2}
{3,3,0,4,0,2}
{0,0,0,4,0,2}
{0,0,5,4,6,2}
The algorithm has divided the original 2 polygonal shapes into 6 rectangles.
Here’s a Lua implementation of the algorithm:
-- grid is the 2D grid to input
-- n is the number of distinct polygons on the grid
local function createRectObjects (grid, n)
local max = n
local objNum = max
-- Iterate grid
for y = 1, #grid, 1 do
for x = 1, #grid[y], 1 do
--Subject tile
local subject = {}
subject.val = grid[y][x]
subject.x = x
subject.y = y
if subject.val > 0 and subject.val <= max then
objNum = objNum + 1
--grid[y][x] = objNum
--Determine width
local blockWidth = 1
for i = 1, #grid[y] - x, 1 do
if grid[y][x+i] == subject.val then
blockWidth = blockWidth + 1
else
break
end
end
--Determine height
local blockHeight = 1
for i = 1, #grid - y, 1 do
local invalidRow = false
for j = 1, blockWidth, 1 do
if grid[y+i][x+j-1] ~= subject.val then
invalidRow = true
break
end
end
if invalidRow then
blockHeight = i
break
elseif i == (#grid - y) then
blockHeight = i
break
end
end
--Stamp the new objNum onto the tiles in the block
for i = subject.y, subject.y + blockHeight - 1, 1 do
for j = subject.x, subject.x + blockWidth -1, 1 do
grid[i][j] = objNum
end
end
end
end
end
--Normalise numbers
for y = 1, #grid, 1 do
for x = 1, #grid[y], 1 do
if grid[y][x] ~= 0 then
grid[y][x] = grid[y][x] - max
end
end
end
obj.max = objNum - max
return grid
end
This code is part of my Lua Grid Tools repo on Bitbucket: https://bitbucket.org/anthonygore/luagridtools
Next I’ll explain how to trace these shapes and return a table of points which outline each rectangle. Tools like Box2D take an array of vertices.