Difference between revisions of "TileMerging"

m (Other Languages)
Line 7: Line 7:
 
-- is_wall_f checks if a tile is a wall
 
-- is_wall_f checks if a tile is a wall
  
local rectangles = {} -- Each rectangle covers a grid of wall tiles
+
local function tilesMerging (grid, tileSize, is_wall_f) -- tilesMerging_v1
 +
local function newRect (start_x, start_y, end_x, end_y)
 +
local new_rect = {
 +
start_x = start_x,
 +
start_y = start_y,
 +
end_x = end_x,
 +
end_y = end_y,
 +
}
 +
return new_rect
 +
end
  
for x = 0, map_width - 1 do
+
local sortStart_y = function (a, b)
    local start_y
+
return a.start_y < b.start_y
    local end_y
+
end
  
    for y = 0, map_height - 1 do
+
local function rectOverlaps (r, x, start_y, end_y)
        if is_wall_f(x, y) then
+
return r.end_x == x - 1
            if not start_y then
+
and start_y <= r.start_y
                start_y = y
+
and end_y >= r.end_y
            end
+
end
            end_y = y
 
        elseif start_y then
 
            local overlaps = {}
 
            for _, r in ipairs(rectangles) do
 
                if (r.end_x == x - 1)
 
                  and (start_y <= r.start_y)
 
                  and (end_y >= r.end_y) then
 
                    table.insert(overlaps, r)
 
                end
 
            end
 
            table.sort(
 
                overlaps,
 
                function (a, b)
 
                    return a.start_y < b.start_y
 
                end
 
            )
 
  
            for _, r in ipairs(overlaps) do
+
local map_width = #grid[1]
                if start_y < r.start_y then
+
local map_height = #grid
                    local new_rect = {
+
local rectangles = {}
                        start_x = x,
 
                        start_y = start_y,
 
                        end_x = x,
 
                        end_y = r.start_y - 1
 
                    }
 
                    table.insert(rectangles, new_rect)
 
                    start_y = r.start_y
 
                end
 
  
                if start_y == r.start_y then
+
for x = 0, map_width - 1 do
                    r.end_x = r.end_x + 1
+
local start_y
 +
local end_y
 +
for y = 0, map_height - 1 do
 +
if is_wall_f(grid, x, y) then
 +
if not start_y then
 +
start_y = y
 +
end
 +
end_y = y
 +
elseif start_y then
 +
local overlaps = {}
 +
for _, r in ipairs(rectangles) do
 +
if rectOverlaps (r, x, start_y, end_y) then
 +
table.insert(overlaps, r)
 +
end
 +
end
 +
table.sort(overlaps, sortStart_y)
 +
for _, r in ipairs(overlaps) do
 +
if start_y < r.start_y then
 +
local new_rect = newRect (x, start_y, x, r.start_y - 1)
 +
table.insert(rectangles, new_rect)
 +
start_y = r.start_y
 +
end
 +
if start_y == r.start_y then
 +
r.end_x = r.end_x + 1
 +
if end_y == r.end_y then
 +
start_y = nil
 +
end_y = nil
 +
elseif end_y > r.end_y then
 +
start_y = r.end_y + 1
 +
end
 +
end
 +
end
  
                    if end_y == r.end_y then
+
if start_y or (y == map_height - 1) then
                        start_y = nil
+
local new_rect = newRect (x, start_y, x, end_y)
                        end_y = nil
+
table.insert(rectangles, new_rect)
                    elseif end_y > r.end_y then
+
start_y = nil
                        start_y = r.end_y + 1
+
end_y = nil
                    end
+
end
                end
+
end
            end
+
end
 +
if start_y then
 +
local new_rect = newRect (x, start_y, x, end_y)
 +
table.insert(rectangles, new_rect)
 +
start_y = nil
 +
end_y = nil
 +
end
 +
end
  
            if start_y then
+
-- resize rectangles
                local new_rect = {
+
for _, r in ipairs(rectangles) do
                    start_x = x,
+
r.x = r.start_x * tileSize
                    start_y = start_y,
+
r.y = r.start_y * tileSize
                    end_x = x,
+
r.w = (r.end_x - r.start_x + 1) * tileSize
                    end_y = end_y
+
r.h = (r.end_y - r.start_y + 1) * tileSize
                }
+
end
                table.insert(rectangles, new_rect)
 
  
                start_y = nil
+
return rectangles
                end_y = nil
+
end
            end
+
</source>
        end
+
 
    end
+
Example:
 +
<source lang="lua">
 +
local grid = {
 +
{1,1,1,1,1,1,1,1,1},
 +
{1,0,0,1,1,1,0,0,1},
 +
{1,0,1,1,1,1,1,0,1},
 +
{1,1,1,0,1,0,1,1,1},
 +
{1,1,1,1,0,1,1,1,1},
 +
{1,1,1,0,1,0,1,1,1},
 +
{1,0,1,1,1,1,1,0,1},
 +
{1,0,0,1,1,1,0,0,1},
 +
{1,1,1,1,1,1,1,1,1},
 +
}
 +
 
 +
local tileSize = 30
 +
 
 +
local function is_wall_f(grid, x, y)
 +
if grid[y+1] and grid[y+1][x+1] then
 +
if grid[y+1][x+1] == 1 then
 +
return true -- wall
 +
else
 +
return false -- not wall
 +
end
 +
else
 +
return false -- out of map, also wall
 +
end
 +
end
  
    if start_y then
+
local rectangles = tilesMerging (grid, tileSize, is_wall_f)
        local new_rect = {
+
print ('amount rectangles:', #rectangles)
            start_x = x,
 
            start_y = start_y,
 
            end_x = x,
 
            end_y = end_y
 
        }
 
        table.insert(rectangles, new_rect)
 
  
        start_y = nil
+
function love.draw ()
        end_y = nil
+
love.graphics.translate (5,5)
    end
+
for i, r in ipairs (rectangles) do
 +
love.graphics.setColor (0.8,0.8,0.8,0.8)
 +
love.graphics.rectangle ('fill', r.x, r.y, r.w, r.h)
 +
love.graphics.setColor (1,1,1)
 +
love.graphics.rectangle ('line', r.x, r.y, r.w, r.h)
 +
end
 
end
 
end
 
</source>
 
</source>
 +
It gives 20 following rectangles:
 +
[[File:tilesMerging_v1.png|280px]]
 +
  
 
Here's how the rectangles would be used for physics.
 
Here's how the rectangles would be used for physics.
Line 94: Line 144:
 
-- phys_world is the world, wall_rects is the list of...
 
-- phys_world is the world, wall_rects is the list of...
 
-- wall rectangles
 
-- wall rectangles
 +
 +
  
 
for _, r in ipairs(rectangles) do
 
for _, r in ipairs(rectangles) do
Line 114: Line 166:
 
</source>
 
</source>
  
== Contributors ==
 
* [[User:AaronWizard|AaronWizard]]
 
  
 
[[Category:Snippets]]
 
[[Category:Snippets]]

Revision as of 08:58, 17 January 2024

This algorithm is for 2D tile maps. The algorithm generates a (hopefully) minimum set of rectangles that cover all tiles of a certain type.

This is useful for physics where you can generate rectangles to cover all wall tiles. You use the rectangles to create physics bodies that can cover multiple wall tiles, instead of create a physics body for every single tile.

-- map_width and map_height are the dimensions of the map
-- is_wall_f checks if a tile is a wall

local function tilesMerging (grid, tileSize, is_wall_f) -- tilesMerging_v1
	local function newRect (start_x, start_y, end_x, end_y)
		local new_rect = {
			start_x = start_x,
			start_y = start_y,
			end_x = end_x,
			end_y = end_y,
		}
		return new_rect
	end

	local sortStart_y = function (a, b)
		return a.start_y < b.start_y
	end

	local function rectOverlaps (r, x, start_y, end_y)
		return r.end_x == x - 1
		and start_y <= r.start_y
		and end_y >= r.end_y
	end

	local	map_width = #grid[1]
	local map_height = #grid
	local rectangles = {}

	for x = 0, map_width - 1 do
		local start_y
		local end_y
		for y = 0, map_height - 1 do
			if is_wall_f(grid, x, y) then
				if not start_y then
					start_y = y
				end
				end_y = y
			elseif start_y then
				local overlaps = {}
				for _, r in ipairs(rectangles) do
					if rectOverlaps (r, x, start_y, end_y) then
						table.insert(overlaps, r)
					end
				end
				table.sort(overlaps, sortStart_y)
				for _, r in ipairs(overlaps) do
					if start_y < r.start_y then
						local new_rect = newRect (x, start_y, x, r.start_y - 1)
						table.insert(rectangles, new_rect)
						start_y = r.start_y
					end
					if start_y == r.start_y then
						r.end_x = r.end_x + 1
						if end_y == r.end_y then
							start_y = nil
							end_y = nil
						elseif end_y > r.end_y then
							start_y = r.end_y + 1
						end
					end
				end

				if start_y or (y == map_height - 1) then
					local new_rect = newRect (x, start_y, x, end_y)
					table.insert(rectangles, new_rect)
					start_y = nil
					end_y = nil
				end
			end
		end
		if start_y then
			local new_rect = newRect (x, start_y, x, end_y)
			table.insert(rectangles, new_rect)
			start_y = nil
			end_y = nil
		end
	end

	-- resize rectangles
	for _, r in ipairs(rectangles) do
		r.x = r.start_x * tileSize
		r.y = r.start_y * tileSize
		r.w = (r.end_x - r.start_x + 1) * tileSize
		r.h = (r.end_y - r.start_y + 1) * tileSize
	end

	return rectangles
end

Example:

local grid = {
	{1,1,1,1,1,1,1,1,1},
	{1,0,0,1,1,1,0,0,1},
	{1,0,1,1,1,1,1,0,1},
	{1,1,1,0,1,0,1,1,1},
	{1,1,1,1,0,1,1,1,1},
	{1,1,1,0,1,0,1,1,1},
	{1,0,1,1,1,1,1,0,1},
	{1,0,0,1,1,1,0,0,1},
	{1,1,1,1,1,1,1,1,1},
}

local tileSize = 30

local function is_wall_f(grid, x, y)
	if grid[y+1] and grid[y+1][x+1] then
		if grid[y+1][x+1] == 1 then
			return true -- wall
		else
			return false -- not wall
		end
	else
		return false -- out of map, also wall
	end
end

local rectangles = tilesMerging (grid, tileSize, is_wall_f)
print ('amount rectangles:', #rectangles)

function love.draw ()
	love.graphics.translate (5,5)
	for i, r in ipairs (rectangles) do
		love.graphics.setColor (0.8,0.8,0.8,0.8)
		love.graphics.rectangle ('fill', r.x, r.y, r.w, r.h)
		love.graphics.setColor (1,1,1)
		love.graphics.rectangle ('line', r.x, r.y, r.w, r.h)
	end
end

It gives 20 following rectangles: tilesMerging v1.png


Here's how the rectangles would be used for physics.

-- Use contents of rectangles to create physics bodies
-- phys_world is the world, wall_rects is the list of...
-- wall rectangles



for _, r in ipairs(rectangles) do
    local start_x = r.start_x * TILE_SIZE
    local start_y = r.start_y * TILE_SIZE
    local width = (r.end_x - r.start_x + 1) * TILE_SIZE
    local height = (r.end_y - r.start_y + 1) * TILE_SIZE

    local x = start_x + (width / 2)
    local y = start_y + (height / 2)

    local body = love.physics.newBody(phys_world, x, y, 0, 0)
    local shape = love.physics.newRectangleShape(body, 0, 0,
      width, height)

    shape:setFriction(0)

    table.insert(wall_rects, {body = body, shape = shape})
end


Other Languages