Recently, I found myself in need of a function like circfill or rectfill that draws filled pie slices instead.

I tried a few different implementations (like drawing radial lines or the mid-point and mirror method), but I wasn't happy with their performance. Finally I settled on this:
-- draws a filled pie portion -- x,y - circle center -- r - radius -- c - color -- s,e - start and end arc angle function piefill(x,y,r,c,s,e) -- safety: can't use this method without a valid draw target if(not get_draw_target()) return -- whole circle, no need for calculations if s==e then circfill(x,y,r,c) return end local d = r+r -- math diameter local d_ = d+1 -- pixel diameter local svec,evec = vec(r*cos(s),r*sin(s)),vec(r*cos(e),r*sin(e)) -- arc points of starting and ending angles local rd = function(n) return flr(n+0.5) end -- rounding function for convenience -- linear representation of the arc local offset = (s>0.5 and -svec.x-d or svec.x)+3*r local arclen = ((e>0.5 and -evec.x-d or evec.x)+3*r-offset)%(d+d) local sprite = userdata("u8",d_,d_) get_draw_target():blit(sprite,x-r,y-r,0,0,d_,d_) -- copy background to sprite for transparency sprite:mutate("u8",d_*d_) -- needs to be flat for lerp local y_,dx,sx,ex,x1,x2,chk1,chk2,ps for dy = 1-r, r-1 do y_ = (r+dy)*d_+r -- flat index row offset dx = sqrt(r*r-(dy*dy)) -- x of the circle sx,ex = sin(s)==0 and r+1 or dy*cos(s)/sin(s), sin(e)==0 and r+1 or dy*cos(e)/sin(e) -- x of the angles if(sx<ex) x1,x2,chk1,chk2=sx,ex,svec,evec else x1,x2,chk1,chk2=ex,sx,evec,svec -- determine which points to include ps = {} if(((dy>0 and dx-d or -dx)+3*r-offset)%(d+d) >= arclen) add(ps,rd(-dx)) if(mid(0,x1,chk1.x)==x1 and mid(0,dy,chk1.y)==dy) add(ps,rd(x1)) if(mid(0,x2,chk2.x)==x2 and mid(0,dy,chk2.y)==dy) add(ps,rd(x2)) if(((dy>0 and -dx-d or dx)+3*r-offset)%(d+d) >= arclen) add(ps,rd(dx)) if(dy==0 and #ps==3) deli(ps,2) -- draw row to sprite if #ps>1 then sprite:set(y_+ps[1],c) sprite:set(y_+ps[2],c) sprite:lerp(y_+ps[1],ps[2]-ps[1]) if #ps==4 then sprite:set(y_+ps[3],c) sprite:set(y_+ps[4],c) sprite:lerp(y_+ps[3],ps[4]-ps[3]) end end end sprite:mutate("u8",d_,d_) -- unflatten sprite:blit(get_draw_target(),0,0,x-r,y-r,d_,d_) -- draw sprite to display end |
The s and e parameters use the same units as the regular trigonometric functions, and swapping their values swaps which part of the pie is filled in.
In previous attempts I used atan2 a lot which really tanked performance. By comparison, the single square root per row has been significantly cheaper. Still, there are probably other improvements that can be made.
Below is the same method, but with calls to line instead of the double blitting and lerping of the above implementation. While it's a bit shorter, my limited testing suggests it is actually slightly slower.
function piefill(x,y,r,c,s,e) -- whole circle, no need for calculations if s==e then circfill(x,y,r,c) return end local d = r+r -- math diameter local svec,evec = vec(r*cos(s),r*sin(s)),vec(r*cos(e),r*sin(e)) -- arc points of starting and ending angles local rd = function(n) return flr(n+0.5) end -- rounding function for convenience -- linear representation of the arc local offset = (s>0.5 and -svec.x-d or svec.x)+3*r local arclen = ((e>0.5 and -evec.x-d or evec.x)+3*r-offset)%(d+d) local y_,dx,sx,ex,x1,x2,chk1,chk2,ps for dy = 1-r, r-1 do y_ = (r+dy)*d_+r -- flat index row offset dx = sqrt(r*r-(dy*dy)) -- x of the circle sx,ex = sin(s)==0 and r+1 or dy*cos(s)/sin(s), sin(e)==0 and r+1 or dy*cos(e)/sin(e) -- x of the angles if(sx<ex) x1,x2,chk1,chk2=sx,ex,svec,evec else x1,x2,chk1,chk2=ex,sx,evec,svec -- determine which points to include ps = {} if(((dy>0 and dx-d or -dx)+3*r-offset)%(d+d) >= arclen) add(ps,rd(-dx)) if(mid(0,x1,chk1.x)==x1 and mid(0,dy,chk1.y)==dy) add(ps,rd(x1)) if(mid(0,x2,chk2.x)==x2 and mid(0,dy,chk2.y)==dy) add(ps,rd(x2)) if(((dy>0 and -dx-d or dx)+3*r-offset)%(d+d) >= arclen) add(ps,rd(dx)) if(dy==0 and #ps==3) deli(ps,2) -- draw row with lines if #ps>1 then line(x+ps[1],y+dy,x+ps[2],y+dy,c) if(#ps==4) line(x+ps[3],y+dy,x+ps[4],y+dy,c) end end end |
One thing to keep in mind is that this function starts from the positive x axis and goes counterclockwise. While this is what the other trig functions do, it may not be very convenient. Personally, I'm using a slight variant that goes clockwise instead.
[Please log in to post a comment]