Skip to content

Instantly share code, notes, and snippets.

@MasterEx
Created June 21, 2014 14:36
Show Gist options
  • Save MasterEx/85370d53cf3cc0f0eb9b to your computer and use it in GitHub Desktop.
Save MasterEx/85370d53cf3cc0f0eb9b to your computer and use it in GitHub Desktop.
Drawing polygons and polylines in lua/love2d with rounded corners

This is a simple module that can be used to draw polygons or polylines with rounded corners in love2d. The polygons/polylines must consist only from 90/270 degrees corners.

I wanted this functionality to draw pacman like mazes.

Example from a random pacman game:

Pacman

How it works

When there is a 90 or 270 degrees corner then I remove r (radius of the circle that the rounded corner is part of) length from each line that is attached and draw the 1/4 of a circle with radius r which is essentially the rounded corner.

Here it is the output of the main.lua demo that is included in this gist:

Demo

Note that the function as it is working now it gets the points of a polygon/polyline and returns a new set of points that can then be used in love.draw() to draw the shape with rounded corners. The process may be slow in general depending on the values of r and n (where n is the number of points used to draw the corner), so it may not be appropriate in cases where the shapes have to move in real time. It is however OK for cases where we just have to draw static shapes as in the pacman maze example that inspired me to create this.

-- A simple helper module for drawing polygons and polylines with
-- rounded corners in love2d 0.9 (http://love2d.org/)
-- The MIT License (MIT)
-- Copyright (c) 2014 Periklis Ntanasis <[email protected]>
local corner_rounder = {}
--[[
Here are all the possible 90/270 degrees corner cases:
* 1 left -> right -> down
* 2 left -> right -> up
* 3 right -> left -> up
* 4 left -> right -> down
* 5 down -> up -> left
* 6 up -> down -> left
* 7 up -> down -> right
* 8 down -> up -> right
]]
local function get_X(r, y, a, b)
return a + math.sqrt(math.abs(r*r - math.pow((y-b), 2)))
end
local function get_Y(r, x, a, b)
return b + math.sqrt(math.abs(r*r - math.pow((x-a), 2)))
end
-- these functions are needed because graphics system isn't Cartesian
-- and classic circle equation cannot be applied
local function get_inverse_X(r, y, a, b)
return a - math.sqrt(math.abs(r*r - math.pow((y-b), 2)))
end
local function get_inverse_Y(r, x, a, b)
return b - math.sqrt(math.abs(r*r - math.pow((x-a), 2)))
end
local function check_lines(lineA, lineB)
if lineA[2] == lineA[4] then
-- A is horizontal
local Aleft_to_right = true
if lineA[1] > lineA[3] then
Aleft_to_right = false
end
local Bup_to_down = true
if lineB[2] > lineB[4] then
Bup_to_down = false
end
if lineB[1] == lineB[3] then
-- B is vertical
if Aleft_to_right then
if Bup_to_down then
return 1 -- +,+
else
return 2 -- +,-
end
else
if Bup_to_down then
return 4 -- -,+
else
return 3 -- -,-
end
end
end
else
-- A is vertical
local Aup_to_down = true
if lineA[2] > lineA[4] then
Aup_to_down = false
end
local Bleft_to_right = true
if lineB[1] > lineB[3] then
Bleft_to_right = false
end
if lineB[2] == lineB[2] then
-- B is horizontal
if Aup_to_down then
if Bleft_to_right then
return 7 -- +, +
else
return 6 -- -, +
end
else
if Bleft_to_right then
return 8 -- -,-
else
return 5 -- +,-
end
end
end
end
return 0 -- corner of 180 degrees
end
function corner_rounder.get_points(lines, r, n, polyline)
r = r or 8
n = n or 20
local points = {}
local center = {}
for i = 1, #lines do
local next_line = i+1
if next_line == #lines+1 then
next_line = 1
end
center.x = 0
center.y = 0
-- add the lines to the points table
if lines[i][2] == lines[i][4] then
-- vertical line
if math.abs(lines[i][1] - lines[i][3]) <= r then
print("A line should be longer than r")
os.exit()
elseif math.abs(lines[i][1] - lines[i][3]) <= 2*r then
print("Warning: A line is shorter than 2r")
end
if polyline and i == 1 then
table.insert(points , lines[i][1])
else
if lines[i][1] < lines[i][3] then
table.insert(points , lines[i][1]+r)
else
table.insert(points , lines[i][1]-r)
end
end
table.insert(points , lines[i][2])
if polyline and i == #lines then
table.insert(points , lines[i][3])
else
if lines[i][1] < lines[i][3] then
table.insert(points , lines[i][3]-r)
else
table.insert(points , lines[i][3]+r)
end
end
table.insert(points , lines[i][4])
elseif lines[i][1] == lines[i][3] then
-- horizontal line
if math.abs(lines[i][2] - lines[i][4]) <= r then
print("A line should be longer than r")
os.exit()
elseif math.abs(lines[i][2] - lines[i][4]) <= 2*r then
print("Warning: A line is shorter than 2r")
end
table.insert(points , lines[i][1])
if polyline and i == 1 then
table.insert(points , lines[i][2])
else
if lines[i][2] < lines[i][4] then
table.insert(points , lines[i][2]+r)
else
table.insert(points , lines[i][2]-r)
end
end
table.insert(points , lines[i][3])
if polyline and i == #lines then
table.insert(points , lines[i][4])
else
if lines[i][2] < lines[i][4] then
table.insert(points , lines[i][4]-r)
else
table.insert(points , lines[i][4]+r)
end
end
end
local corner_case
if polyline and i == #lines then
corner_case = 0
else
corner_case = check_lines(lines[i], lines[next_line])
end
-- calculate the circle center
if corner_case == 1 then
center.x = lines[i][3] - r
center.y = lines[i][4] + r
elseif corner_case == 2 then
center.x = lines[i][3] - r
center.y = lines[i][4] - r
elseif corner_case == 3 then
center.x = lines[i][3] + r
center.y = lines[i][4] - r
elseif corner_case == 4 then
center.x = lines[i][3] + r
center.y = lines[i][4] + r
elseif corner_case == 5 then
center.x = lines[i][3] - r
center.y = lines[i][4] + r
elseif corner_case == 6 then
center.x = lines[i][3] - r
center.y = lines[i][4] - r
elseif corner_case == 7 then
center.x = lines[i][3] + r
center.y = lines[i][4] - r
elseif corner_case == 8 then
center.x = lines[i][3] + r
center.y = lines[i][4] + r
end
-- if there is a corner of 90/270 degrees
if center.x ~= 0 and center.y ~= 0 then
local y = 0
local x = 0
dist = r / n
for j = 1,n-1 do
if corner_case == 1 then
x = dist * j + center.x
y = get_inverse_Y(r, x, center.x, center.y)
elseif corner_case == 2 then
x = dist * j + center.x
y = get_Y(r, x, center.x, center.y)
elseif corner_case == 3 then
x = center.x - dist * j
y = get_Y(r, x, center.x, center.y)
elseif corner_case == 4 then
x = center.x - dist * j
y = get_inverse_Y(r, x, center.x, center.y)
elseif corner_case == 5 then
x = lines[i][3] - dist * j
-- alternatively can be written like that
-- x = center.x + r - dist*j
y = get_inverse_Y(r, x, center.x, center.y)
elseif corner_case == 6 then
y = center.y + dist * j
x = get_X(r, y, center.x, center.y)
elseif corner_case == 7 then
y = center.y + dist * j
x = get_inverse_X(r, y, center.x, center.y)
elseif corner_case == 8 then
y = center.y - dist * j
x = get_inverse_X(r, y, center.x, center.y)
end
table.insert(points, x)
table.insert(points, y)
end
end
end
return points
end
local function points_to_lines(points, polyline)
local lines = {}
if #points % 2 == 1 then
print("Unexpected number of points!")
os.exit()
end
local i = 1
while i < #points do
if i == #points-1 then
if not polyline then
table.insert(lines, {points[i], points[i+1], points[1], points[2]} )
end
else
table.insert(lines, {points[i], points[i+1], points[i+2], points[i+3]} )
end
i = i + 2
end
return lines
end
function corner_rounder.tranform_polygon_points(points, r, n)
return corner_rounder.get_points(points_to_lines(points, false), r, n)
end
function corner_rounder.tranform_line_points(points, r, n)
return corner_rounder.get_points(points_to_lines(points, true), r, n, true)
end
return corner_rounder
local polygon
local line
local edge
function love.load()
local cr = require "corner-rounder"
local polygon_points = { 50,100 , 150,100 , 150,50 , 250,50 , 250,180, 330,180 , 330,50 ,
410,50, 410,120, 440,120 , 440,65 , 475,65 , 475,120 , 550,120 , 550,50 , 600,50 ,
600,150 , 385,150 , 385,70 , 345,70 , 345,220 , 50,220 }
polygon = cr.tranform_polygon_points(polygon_points)
local line_points = { 50,450 , 100,450 , 100,300 , 200,300 , 200,580 , 550,580 , 550,400 ,
450,400 , 450,500 , 500,500 , 500,450 , 530,450 , 530,550 , 430,550 , 430,350 , 600,350,
600,550 , 650,550 , 650,500 , 700,500 }
line = cr.tranform_line_points(line_points)
local edge_case = {250,400 , 250,250 , 260,250 , 260,400}
--edge = cr.tranform_line_points(edge_case, 4) -- ok
edge = cr.tranform_line_points(edge_case, 5) -- warning
--edge = cr.tranform_line_points(edge_case, 10) -- error
end
function love.draw()
love.graphics.polygon("line", polygon)
love.graphics.line(line)
love.graphics.line(edge)
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment