Page 2 of 2

Re: Rounded Rectangles

Posted: Thu Feb 20, 2014 3:27 am
by Ref
You could eliminate 3/4 of the trig calls.
If you time one million calls (without the love.graphics.polygon call), the times I get are 7.35 seconds for Robin's function versus 4.57 seconds for the modified code.
Really not too important because most of the time is actually spent filling the rectangle.
Think the code is OK but could contain some unfound bugs.
Plugged it into Robin's demo and got the same figures.

Re: Rounded Rectangles

Posted: Thu Feb 20, 2014 9:07 am
by Robin
I'd like to update my Gist with your changes, is that okay?

Re: Rounded Rectangles

Posted: Thu Feb 20, 2014 3:40 pm
by Ref
I have some reservations about my modifications.
Not overly confident that the code is 100% but you are welcome to do what ever you want with it.
Best

Edit: As expected, an error.

Code: Select all

rx = rx >= w / 2 and w / 2 - 1 or rx
ry = ry >= h / 2 and h / 2 - 1 or ry
Have to add 'equal' to avoid the problem with gaps forming when 'line' mode is used with love.polygon on polygons containing redundant points.
The code now is:

Code: Select all

function radiusRectangle( mode, x, y, w, h, rx, ry )
	ry = ry or rx
	local pts = {}
	local precision = math.floor( 0.2 * ( rx + ry ) )
	local  hP =  math.pi / 2
	rx = rx >= w / 2 and w / 2 - 1 or rx
	ry = ry >= h / 2 and h / 2 - 1 or ry
	local sin, cos = math.sin, math.cos
	for i = 0, precision do	-- upper right
		local a = ( i / precision - 1 ) * hP
		pts[#pts+1] = x + w - rx * ( 1 -  cos(a) )
		pts[#pts+1] = y + ry * ( 1 +  sin(a) )
		end
	for i = 2 * precision + 2 , 1, -2 do	-- lower right
		pts[#pts+1] = pts[i-1]
		pts[#pts+1] = 2 * y - pts[i] + h
		end
	for i = 1, 2 * precision + 2, 2 do	-- lower left
		pts[#pts+1] = -pts[i] + 2 * x + w
		pts[#pts+1] = 2 * y - pts[i+1] + h
		end
	for i = 2 * precision+2 , 1, -2 do	-- upper left
		pts[#pts+1]	= -pts[i-1] + 2 * x + w
		pts[#pts+1]	= pts[i]
		end
	love.graphics.polygon( mode, pts )
	end
(Not really sure why you used clockwise point order???)

Re: Rounded Rectangles

Posted: Mon Nov 10, 2014 9:40 am
by SpotlightKid
Here's my version, which I ported from similar code I had for PyGame.

It draws the rectangle as a polygon to a canvas and returns this, so the rectangle can be reused efficiently. It also allows to set a different foreground (border) and background (fill) color. Both may have alpha ~= 255, but if the foreground color has transparency, it will overlap with the background by half the border width (I deemed this acceptable to avoid having to calculate different polygons for the border and the background).

The code could be optimzed as in the above posts by calculating only one of the rounded corners and transposing/rotating it for the others, but since you only need to calculate the rectangle coordinates once and then just redraw the canvas (unless the shape changes), I didn't think it very important.

Here's a demo .love:
roundedrect.love
(252.86 KiB) Downloaded 135 times
Rounded rectangles screenshot
Rounded rectangles screenshot
screenshot-20141110-085615.png (110.59 KiB) Viewed 1877 times
(In the screenshot I used the DroidSans font instead of the default font, but did not include it in the .love. Kudos to Triplenox for the background pic.)

Code: Select all

--- Draw rectangles with rounded corners with LÖVE (http://love2d.org)
local modname = ...
local M = {}
_G[modname] = M
package.loaded[modname] = M

local ipairs = ipairs
local lg = love.graphics
local PI = math.pi
local sin = math.sin
local cos = math.cos
local unpack = unpack

if _ENV then
    _ENV = M
else
    setfenv(1, M)
end

local TWOPI = PI * 2
local HALFPI = PI * 0.5
local PI32 = PI + HALFPI

function extend(t1, t2)
    for _, v in ipairs(t2) do
        t1[#t1+1] = v
    end
end

--- Return table of coordinates of a circle arc with step resolution
function arc_coords(x, y, radius, start, stop, step)
    step = step or (TWOPI * 0.01)
    if start > stop then
        step = -step
    end

    local coords = {}
    for angle = start, stop, step do
        coords[#coords+1] = x + cos(angle) * radius
        coords[#coords+1] = y + sin(angle) * radius
    end
    return coords
end

--- Draw rectangle with rounded corners to a canvas and return it
function draw_rect(mode, w, h, radius, color, bgcolor, linewidth, aa, step)
    radius = radius or 0
    linewidth = linewidth or 1
    local hlw = linewidth * 0.5
    local x1 = radius + hlw
    local y1 = radius + hlw
    local x2 = w - radius - hlw
    local y2 = h - radius - hlw

    if radius > 0 then
        coords = arc_coords(x1, y1, radius, PI, PI32, step)
        extend(coords, arc_coords(x2, y1, radius, PI32, TWOPI, step))
        extend(coords, arc_coords(x2, y2, radius, 0, HALFPI, step))
        extend(coords, arc_coords(x1, y2, radius, HALFPI, PI, step))
    else
        coords = {x1, hlw, x2, hlw, w - hlw, y2, x1, h - hlw}
    end

    local canvas = lg.newCanvas(w + linewidth, h + linewidth, "normal", aa)
    lg.setCanvas(canvas)
        canvas:clear()
        lg.setBackgroundColor({255, 255, 255, 0})
        lg.setBlendMode('alpha')
        lg.setLineStyle("smooth")

        if bgcolor then
            lg.setLineWidth(0)
            lg.setColor(unpack(bgcolor))
            lg.polygon("fill", coords)
        end

        lg.setColor(unpack(color))
        lg.setLineWidth(linewidth)

        lg.polygon(mode, coords)
    lg.setCanvas()
    return canvas
end
Also available as a Gist here.