#### Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

# [RESOLVED] How to improve arc calculation for pie fractions drawing?

edited December 2014 Posts: 57

Hi, I wrote this program to teach my children about fractions. In some fractions, e.g. 1/13 or 1/15 the slices don't quite fill the pie. Can someone suggest how to improve the arc calculation to fix this, please? Thank you.

Main class

``````-- Fractions

function setup()
Pick = 1
Slices = 4
parameter.integer("Pick", 1, 20, Pick, pick)
parameter.integer("Slices", 1, 20, Slices, slice)
end

function draw()
background(40, 40, 50)
for _,p in pairs(pies) do
p:draw()
end
if #pies > 1 then
fontSize(40)
fill(255, 255, 255, 255)
text(Pick.."/"..Slices, WIDTH/2, HEIGHT-50)
end
end

function slice(n)
pick(Pick)
end

function pick(n)
local reqPies = math.ceil(n / Slices)
local cols = math.ceil(math.sqrt(reqPies))
local rows = math.ceil(reqPies / cols)

local radius = HEIGHT / cols / 4

pies = {}
for i = 1,reqPies do
local col = (i - 1) % cols + 1
local row = math.ceil(i / cols)

local thisPick = Slices
if i == reqPies then
thisPick = Pick % Slices
if thisPick == 0 then
thisPick = Slices
end
end
table.insert(pies, Pie(x, y, radius, Slices, thisPick))
end
end
``````

Arc class

``````Arc = class()

local delta = math.pi / 36

-- If the Arc is closed, radius will be drawn
self.startAngle = startAngle
self.endAngle = endAngle
if closed ~= nil then
self.closed = closed
else
self.closed = false
end
self.points = {vec2(0, 0)}

for angle = startAngle, endAngle, delta do
local b = math.cos(angle) * radius
local h = math.sin(angle) * radius
table.insert(self.points, vec2(b, h))
end
end

function Arc:draw()
local n = #self.points

local startPoint = 1
local endPoint = n-2
if self.closed then
startPoint = 0
endPoint = n-1
end

for i = startPoint, endPoint do
local p1 = self.points[i%n + 1]
local p2 = self.points[(i+1)%n + 1]
line(p1.x, p1.y, p2.x, p2.y)
end
end
``````

Slice class

``````Slice = class()

function Slice:init(x, y, startAngle, endAngle, radius, colour)
self.x = x
self.y = y

self.arc = Arc(startAngle, endAngle, radius, true)

local vertices = {}
local colours = {}
for i = 2, (#self.arc.points-1) do
table.insert(vertices, vec2(0, 0))
table.insert(vertices, self.arc.points[i])
table.insert(vertices, self.arc.points[i+1])

table.insert(colours, colour)
table.insert(colours, colour)
table.insert(colours, colour)
end

self.mesh = mesh()
self.mesh.vertices = vertices
self.mesh.colors = colours
end

function Slice:draw()
pushMatrix()
translate(self.x, self.y)
self.mesh:draw()
popMatrix()

strokeWidth(5)
stroke(0, 0, 0, 255)
self.arc:draw()
end
``````

Pie class

``````Pie = class()

function Pie:init(x, y, radius, sliceCount, selected)
self.x = x
self.y = y
self.sliceCount = sliceCount
self.selected = selected
self.label = self.selected.."/"..self.sliceCount

local delta = 2 * math.pi / self.sliceCount
self.slices = {}
local colour
for i = 1,self.sliceCount do
if i <= selected then
colour = color(87, 64, 168, 255)
else
colour = color(127, 127, 127, 255)
end
table.insert(self.slices, Slice(0, 0, delta*(i-1), delta*i, radius, colour))
end
end

function Pie:draw()
pushMatrix()
translate(self.x, self.y)
for i,slice in ipairs(self.slices) do
slice:draw()
end
self.labelW, self.labelH = textSize(self.label)
fill(255, 255, 255, 255)
popMatrix()
end
``````

• edited November 2014 Posts: 10,053

@LightDye Here's how I would do slices.

EDIT: Changed color and size of lines.

``````function setup()
parameter.integer("parts",1,25,4)
w=WIDTH/2
h=HEIGHT/2
end

function draw()
background(0)
fill(0, 198, 255, 255)
noStroke()
ellipse(w,h,405)
stroke(0)
strokeWidth(3)
if parts>1 then
for z=0,360,360/parts do
line(w,h,w+x,h+y)
end
end
end

``````
• edited November 2014 Posts: 57

Thanks @dave1707, that works well when all slices are of the same colour, but I need to draw parts of the circle with different colour and that's why I'm using mesh to build slices out of narrow triangles.

I can improve the fit of slices by drawing narrower triangles in the mesh just by reducing the delta variable in the Arc class:

``````local delta = math.pi / 72
``````

But then I start noticing how the drawing slows down.

• Posts: 10,053

OK, I didn't think about different colors for different slices.

• edited November 2014 Posts: 10,053

@LightDye Is this better.

EDIT: Changed the code to make the lines thicker.

``````function setup()
tab={}
m=mesh()
fontSize(50)
parameter.integer("frac",0,25,2)
parameter.integer("parts",1,25,4)
w=WIDTH/2
h=HEIGHT/2
end

function draw()
background(0, 255, 224, 255)
if frac>parts then
frac=parts
end
fill(0, 198, 255, 255)
ellipse(w,h,408)
text(frac.."/"..parts,w,h+250)
tab={}
for z=1,360*frac/parts do
table.insert(tab,vec2(w,h))
table.insert(tab,vec2(w+x,h+y))
table.insert(tab,vec2(w+x,h+y))
end
m.vertices=tab
m:setColors(255,0,0,255)
m:draw()
stroke(0)
strokeWidth(5)
if parts>1 then
for z=0,360,360/parts do
line(w,h,w+x,h+y)
end
end
end

``````
• Posts: 57

Thanks @dave1707, that certainly looks better. Cheers!

• edited November 2014 Posts: 342

If you replace the 'text()' line in @Dave1707's code with this:

``````    for i = math.min(frac,parts),2,-1 do
if parts%i==0 and frac%i==0 then
text(frac/i.."/"..parts/(i),w,h+300)
break
end
end
text(frac.."/"..parts,w,h+250)
``````

It will reduce the fractions nicely (good for the kiddos!)

• Posts: 10,053

@Monkeyman32123 You're doing extra divisions in the % line of code

``````if (parts/i)%1==0 and (frac/i)%1==0 then
``````

can be changed to

``````if parts%i==0 and frac%i==0 then
``````
• edited November 2014 Posts: 342

Ah, yes, I see that now, how silly of me >_<

Thank you for pointing that out (didn't really go over it very well, just whipped it up)

Edited in the code above.

• Posts: 57

Nice addition to the code @Monkeyman32123, thank you!

• Posts: 342

You're welcome, any time!

• Posts: 57

Codea Fractions

Turns out that what makes my code slow is the drawing of line segments that form each slice border. After adapting @dave1707's code to my code and adding an FPS class this became obvious. I'm sharing the improved code here for future reference.

Main class

``````-- Fractions

function setup()
footerH = 50
Pick = 1
Slices = 4
parameter.integer("Pick", 1, 40, Pick, pick)
parameter.integer("Slices", 1, 40, Slices, slice)
parameter.boolean("ShowFPS", false)
parameter.boolean("OOdrawing", false, OOdrawingChanged)
end

function draw()
background(40, 40, 50)
for _,p in pairs(pies) do
p:draw()
end
if #pies > 1 then
fontSize(40)
fill(255, 232, 0, 255)
text(fraction.label, WIDTH/2, footerH/2)
end
if ShowFPS then
fps:draw()
end
end

function slice(n)
pick(Pick)
end

function pick(n)
fps = FPS()

fraction = Fraction(Pick, Slices)

local reqPies = math.ceil(n / Slices)
local cols = math.ceil(math.sqrt(reqPies))
local rows = math.ceil(reqPies / cols)

local radius = HEIGHT / cols / 3
local colW = WIDTH / cols
local rowH = (HEIGHT - footerH) / rows

pies = {}
for i = 1,reqPies do
local col = (i - 1) % cols + 1
local row = math.ceil(i / cols)

local x = col * colW - colW/2
local y = row * rowH - rowH/2 + footerH

local thisPick = Slices
if i == reqPies then
thisPick = Pick % Slices
if thisPick == 0 then
thisPick = Slices
end
end
table.insert(pies, Pie(x, y, radius, Slices, thisPick))
end
end

function OOdrawingChanged()
fps = FPS()
end
``````

FPS class

``````FPS = class()

function FPS:init()
self.min = 1000
self.max = 0
self.rate = 60
self.count = 0
self.total = 0
end

function FPS:calc()
self.count = self.count + 1
self.rate = math.floor(1 / DeltaTime)
self.min = math.min(self.min, self.rate)
self.max = math.max(self.max, self.rate)
self.total = self.total + self.rate
self.average = math.floor(self.total / self.count)
end

function FPS:draw()
self:calc()
pushStyle()
fontSize(20)
noStroke()
rectMode(CORNER)
textMode(CORNER)
pushMatrix()
local board = self.rate..", "..self.min..".."..self.average..".."..self.max
local w,h = textSize(board)
fill(0, 0, 0, 255)
rect(WIDTH-w, HEIGHT-h, WIDTH, HEIGHT)
fill(255, 0, 0, 255)
text(board, WIDTH-w, HEIGHT-h)
popMatrix()
popStyle()
end
``````

Fraction class

``````Fraction = class()

function Fraction:init(numerator, denominator)
self.numerator = numerator
self.denominator = denominator
self.label = self.numerator.."/"..self.denominator

local n,d = self:reduce()
if d < self.denominator then
self.label = self.label.." = "..n.."/"..d
end
end

function Fraction:reduce()
-- Based on code by Monkeyman32123
for i = math.min(self.numerator, self.denominator),1,-1 do
if self.numerator%i==0 and self.denominator%i==0 then
return self.numerator/i, self.denominator/i
end
end
end
``````

Arc class

``````Arc = class()

local delta = math.pi / 180

-- If the Arc is closed, radius will be drawn
self.startAngle = math.min(startAngle, endAngle)
self.endAngle = math.max(startAngle, endAngle)
if closed ~= nil then
self.closed = closed
else
self.closed = false
end
self.points = {vec2(0, 0)}

local n = (self.endAngle - self.startAngle) / delta
for i = 0, n-1 do
local angle = self.startAngle + delta * i
end
end

local b = math.cos(angle) * self.radius
local h = math.sin(angle) * self.radius
table.insert(self.points, vec2(b, h))
end

-- Very FPS-costly function
function Arc:draw()
local n = #self.points

local startPoint = 1
local endPoint = n-2
if self.closed then
startPoint = 0
endPoint = n-1
end

for i = startPoint, endPoint do
local p1 = self.points[i%n + 1]
local p2 = self.points[(i+1)%n + 1]
line(p1.x, p1.y, p2.x, p2.y)
end
end
``````

Slice class

``````Slice = class()

function Slice:init(x, y, startAngle, endAngle, radius, colour)
self.x = x
self.y = y

self.arc = Arc(startAngle, endAngle, radius, true)

local vertices = {}
local colours = {}
for i = 2, (#self.arc.points-1) do
table.insert(vertices, vec2(0, 0))
table.insert(vertices, self.arc.points[i])
table.insert(vertices, self.arc.points[i+1])

table.insert(colours, colour)
table.insert(colours, colour)
table.insert(colours, colour)
end

self.mesh = mesh()
self.mesh.vertices = vertices
self.mesh.colors = colours
end

function Slice:draw()
pushMatrix()
translate(self.x, self.y)
self.mesh:draw()
popMatrix()

if OOdrawing then
-- This has performance issues
strokeWidth(4)
stroke(0, 0, 0, 255)
self.arc:draw()
end
end
``````

Pie class

``````Pie = class()

function Pie:init(x, y, radius, sliceCount, selected)
self.x = x
self.y = y
self.sliceCount = sliceCount
self.selected = selected

self.fraction = Fraction(selected, sliceCount)

local delta = 2 * math.pi / self.sliceCount
self.slices = {}
local colour
for i = 1,self.sliceCount do
if i <= selected then
colour = color(60, 132, 64, 255)
else
colour = color(127, 127, 127, 255)
end
table.insert(self.slices, Slice(0, 0, delta*(i-1), delta*i, radius, colour))
end
end

function Pie:draw()
pushMatrix()
translate(self.x, self.y)
for i,slice in ipairs(self.slices) do
slice:draw()
end

if not OOdrawing then
-- Draws the circle to solve the performance issue of arc drawing
noFill()
-- Draws all radius here instead of in Arc for performance reasons
stroke(255, 255, 255, 255)
strokeWidth(2)
smooth()
for _,slice in ipairs(self.slices) do
local a = slice.arc.startAngle
local x = math.cos(a) * r
local y = math.sin(a) * r
line(0, 0, x, y)
end
end

self.labelW, self.labelH = textSize(self.fraction.label)
fill(255, 255, 255, 255)
popMatrix()
end
``````
• Posts: 509

There are better ways to draw an arc than as a sequence of lines. Here's my arc code. It's long because it is flexible. It uses a mesh to draw the arc. There's an `arc` function and an `Arc` class depending on how one wants to invoke it.

There's also an `arc` shader in Codea; I don't think it is all that fast.

``````-- Arc path drawing

Arc = class()

local __makeArc = function(nsteps)
-- nsteps doesn't make a huge difference in the range 50,300
nsteps = nsteps or 50
local m = mesh()
//
//

//This is the current model * view * projection matrix
// Codea sets it automatically
uniform mat4 modelViewProjection;
uniform lowp vec4 scolour;
uniform lowp vec4 ecolour;
lowp vec4 mcolour = ecolour - scolour;
//This is the current mesh vertex position, color and tex coord
// Set automatically
attribute vec4 position;
attribute vec4 color;
attribute vec2 texCoord;

varying highp vec2 vTexCoord;
varying lowp vec4 vColour;
varying highp float vWidth;
varying highp float vCore;

uniform float width;
uniform float taper;
uniform float blur;
uniform float cap;
uniform float scap;
uniform float ecap;
float swidth = width + blur;
float ewidth = taper*width - width;
float ecapsw = clamp(cap,0.,1.)*ecap;
float scapsw = clamp(cap,0.,1.)*scap;
uniform vec2 centre;
uniform vec2 xaxis;
uniform vec2 yaxis;
uniform float startAngle;
uniform float deltaAngle;

void main()
{
highp float t = clamp(position.y,0.,1.);
vCore = t;
highp float w = smoothstep(0.,1.,t);
vWidth = w*ewidth + swidth;
highp vec2 bpos = centre + cos(t*deltaAngle + startAngle) * xaxis + sin(t*deltaAngle + startAngle) * yaxis;
highp vec2 bdir = -sin(t*deltaAngle + startAngle) * xaxis + cos(t*deltaAngle + startAngle) * yaxis;
bdir = vec2(bdir.y,-bdir.x);
bdir = vWidth*normalize(bdir);
bpos = bpos + position.x*bdir;
highp vec4 bzpos = vec4(bpos.x,bpos.y,0.,1.);
bzpos.xy += (ecapsw*max(position.y-1.,0.)
+scapsw*min(position.y,0.))*vec2(-bdir.y,bdir.x);
highp float s = clamp(position.y,
scapsw*position.y,1.+ecapsw*(position.y-1.));
vTexCoord = vec2(texCoord.x,s);
vColour = t*mcolour + scolour;
//Multiply the vertex position by our combined transform
gl_Position = modelViewProjection * bzpos;
}
]],[[
//
//

uniform highp float blur;
uniform highp float cap;

varying highp vec2 vTexCoord;
varying highp float vWidth;
varying lowp vec4 vColour;
varying highp float vCore;

void main()
{
lowp vec4 col = vColour;
highp float edge = blur/(vWidth+blur);
col.a = mix( 0., col.a,
(2.-cap)*smoothstep( 0., edge,
min(vTexCoord.x,1. - vTexCoord.x) )
* smoothstep( 0., edge,
min(1.5-vTexCoord.y, .5+vTexCoord.y) )
+ (cap - 1.)*smoothstep( 0., edge,
.5-length(vTexCoord - vec2(.5,vCore)))
);

gl_FragColor = col;
}
]])

for n=1,nsteps do
end
return m
end

local m = __makeArc()

-- centre, xaxis, yaxis, startAngle, deltaAngle, taper
function arc(a,b,c,d,e,f)
if type(a) == "table" then
f = b
a,b,c,d,e = unpack(a)
end
if type(b) ~= "userdata" then
b = b*vec2(1,0)
end
if type(c) ~= "userdata" then
c = c*vec2(0,1)
end
m:draw()
end

function Arc:init(...)
self:setParams(...)
end

function Arc:clone()
return Arc(self.params)
end

function Arc:makeDrawable(t)
t = t or {}
local nsteps = t.steps or self.steps
local m = __makeArc(nsteps)
m.shader.taper = t.taper or self.taper or 1
m.shader.blur = t.blur or self.blur or 2
m.shader.cap = t.cap or self.cap or (lineCapMode()-1)%3
m.shader.scap = t.scap or self.scap or 1
m.shader.ecap = t.ecap or self.ecap or 1
m.shader.width = t.width or self.width or strokeWidth()
m.shader.scolour = t.scolour or self.scolour or t.colour or color(stroke())
m.shader.ecolour = t.ecolour or self.ecolour or t.colour or color(stroke())
local a,b,c,d,e = unpack(self.params)
self.curve = m
self.draw = function(self) self.curve:draw() end
end

function Arc:draw(t)
self:makeDrawable(t)
self.curve:draw()
end

function Arc:setParams(a,b,c,d,e)
if type(a) == "table" then
a,b,c,d,e = unpack(a)
end
if type(b) ~= "userdata" then
b = b*vec2(1,0)
end
if type(c) ~= "userdata" then
c = c*vec2(0,1)
end
self.params = {a,b,c,d,e}
if self.curve then
end
end

function Arc:setStyle(t)
self.scolour = t.scolour or t.colour or self.scolour
self.ecolour = t.ecolour or t.colour or self.ecolour
self.width = t.width or self.width
self.taper = t.taper or self.taper
self.blur = t.blur or self.blur
self.cap = t.cap or self.cap
self.scap = t.scap or self.scap
self.ecap = t.ecap or self.ecap
if not self.curve then
return
end
t = t or {}
if t.colour then
end
if t.scolour then
end
if t.ecolour then
end
if t.width then
end
if t.taper then
end
if t.blur then
end
if t.cap then
end
if t.scap then
end
if t.ecap then
end
end

function Arc:point(t)
local a,b,c,d,e = unpack(self.params)
return a + math.cos(t*e + d)*b + math.sin(t*e + d)*c
end

function Arc:tangent(t)
local a,b,c,d,e = unpack(self.params)
return -math.sin(t*e + d)*b + math.cos(t*e + d)*c
end

function Arc:normal(t)
return self:tangent(t):rotate90()
end

function Arc:unitNormal(t)
local pt = self:normal(t)
local l = pt:len()
if l == 0 then
return vec2(0,0)
else
return pt/l
end
end

function Arc:unitTangent(t)
local pt = self:tangent(t)
local l = pt:len()
if l == 0 then
return vec2(0,0)
else
return pt/l
end
end

``````