2D Shapes / Outlines / Lines with thickness

Monkey Forums/Monkey Code/2D Shapes / Outlines / Lines with thickness

NoOdle(Posted 2012) [#1]
I became slightly distracted from what I was meant to be doing and ended up writing some shape functions. There are two versions provided for drawing a thick line, one uses sin cos, the other uses vector math. Haven't done a speed test yet so I have left them both in there. Use as you wish, enjoy :)

Could do with some optimising but I need to get back to what I was meant to be doing!

[edit] You may notice that after some DrawPoly commands there is a DrawLine command. This is to hide the seams that you get on HTLM5 target so it can be deleted for other targets.




NoOdle(Posted 2012) [#2]
[edit] Added a Star and StarOutline to the code above


slenkar(Posted 2012) [#3]
thanks the star could be useful


NoOdle(Posted 2012) [#4]
no problem, I'm taking shape requests if you have any? This is a lot more fun that what I should be doing lol


slenkar(Posted 2012) [#5]
I just noticed there is a demo in the code, it looks great, cant think of any shapes at the moment


NoOdle(Posted 2012) [#6]
cheers, yea I though it would be good to show what the functions can do. Im tempted to add an Arrow... but I've just had another idea I want to try so maybe later..


DruggedBunny(Posted 2012) [#7]
Ooh, nice. I'm also thinking the star could be useful!

Perhaps a spiral with options like line thickness, number of rotations and spread of "rings" would be an idea?


NoOdle(Posted 2012) [#8]
Perhaps a spiral with options like line thickness, number of rotations and spread of "rings" would be an idea?



Yea that sounds good!

[edit] Just fixed an error in the DrawPie function.


Nobuyuki(Posted 2012) [#9]
hi!

The pie functions' start and end points base themselves on the screen coordinate system, not the mathematical coordinate system. Therefore, Y is inverted! DrawArcOutline doesn't do this, so if you're drawing a pie and an arc directly afterwards with the same arguments, the pieces won't line up.

Thanks for the code, I'm borrowing those two functions and probably will extend it to draw rounded rectangles.


Nobuyuki(Posted 2012) [#10]
Whew. Okay, I changed some routines around so that they start from 3 O'clock and run counter-clockwise, which is how I expect these routines should work. I swapped Cos/Sin in noodle's code in order to achieve this. In the following code, DrawArc and DrawPie will draw to the same place when given the same arguments.

Furthermore, included are 2 rounded rectangle routines, DrawRoundedRect, and FillRoundedRect. Their usage should be fairly obvious, however, they will not draw correctly if the absolute value of the width or heights is less than r*2. There are multiple ways to solve this; I plan on adding in code to automatically reduce the radius size of the corners if W or H is too small, but I haven't yet because I don't know whether Floats are passed to the function by value or by reference, and I don't want to waste an extra Local variable if I don't have to.

[monkeycode]
Import mojo

Function DrawRectOutline:Void(x:Float, y:Float, w:Float, h:Float)
DrawLine(x,y,x+w,y)
DrawLine(x,y,x,y+h)
DrawLine(x,y+h,x+w,y+h)
DrawLine(x+w,y,x+w,y+h)
End Function

Function DrawArc:Void(x:Float, y:Float, xRad:Float, yRad:Float, aStart:Float=0.0, aEnd:Float = 360.0, segments:Int=8)
Local x1:Float = x + ( Cos( aStart ) * xRad )
Local y1:Float = y - ( Sin( aStart ) * yRad )
Local x2:Float
Local y2:Float
Local div:Float = ( aEnd - aStart ) / segments
For Local i:Int = 1 To segments
x2 = x + ( Cos( aStart + ( i * div )) * xRad )
y2 = y - ( Sin( aStart + ( i * div )) * yRad )
DrawLine(x1, y1, x2, y2)
x1 = x2
y1 = y2
Next
End Function

'/ Draws a circle with sides or pie segment
Function DrawPie:Void(x:Float, y:Float, xRadius:Float, yRadius:Float, aStart:Float=0.0, aEnd:Float=360.0, segments:Int=8 )
Local points:Float[( segments * 2 ) + 4 ]
points[0] = x
points[1] = y
points[2] = x + Cos(aStart) * xRadius
points[3] = y - Sin(aStart) * yRadius
Local div:Float = (aEnd - aStart) / segments
For Local i:Int = 1 To segments
points[( i + 1 ) * 2 ] = x + Cos( aStart + ( i * div )) * xRadius
points[(( i + 1 ) * 2 ) + 1 ] = y - Sin( aStart + ( i * div )) * yRadius
Next
DrawPoly( points )
End Function

Function DrawRoundedRect:Void (x:Float, y:Float, w:Float, h:Float, r:Float=8, segments:Int=8)
DrawLine(x,y+r,x,y+h-r) 'Left
DrawLine(x+r,y,x+w-r,y) 'Top
DrawLine(x+r,y+h,x+w-r,y+h) 'Bottom
DrawLine(x+w,y+r,x+w,y+h-r) 'Right

DrawArc(x+r,y+r,r,r,90,180,segments) 'UL
DrawArc(x+w-r,y+r,r,r,0,90,segments) 'UR
DrawArc(x+r,y+h-r,r,r,180,270,segments) 'LL
DrawArc(x+w-r,y+h-r,r,r,270,360,segments) 'LR
End Function

Function FillRoundedRect:Void(x:Float, y:Float, w:Float, h:Float, r:Float=8, segments:Int=8)
DrawPie(x+r,y+r,r,r,90,180,segments) 'UL
DrawPie(x+w-r,y+r,r,r,0,90,segments) 'UR
DrawPie(x+r,y+h-r,r,r,180,270,segments) 'LL
DrawPie(x+w-r,y+h-r,r,r,270,360,segments) 'LR

'We could do this with just 2 calls, but the rects would overlap and create a dark box in the center if alpha were to be <1
DrawRect(x+r,y,w-r-r,r) 'Top
DrawRect(x+r,y+h-r,w-r-r,r) 'Bottom
DrawRect(x,y+r,r,h-r-r) 'Left
DrawRect(x+w-r,y+r,r,h-r-r) 'Right
DrawRect(x+r,y+r,w-r-r,h-r-r) 'Center
End Function
[/monkeycode]

Someone else, feel free to modify this code to work with the line thickness stuff. I'm not sure how slow that would be, so I left it out.


NoOdle(Posted 2012) [#11]
thanks nice job, I had been meaning to revisit this and improve it, now I don't need to :)

I hope you don't mind, I made a small change to your FillRoundedRect:

[monkeycode]
Function FillRoundedRect:Void(x:Float, y:Float, w:Float, h:Float, r:Float=8, segments:Int=8)
DrawPie(x+r,y+r,r,r,90,180,segments) 'UL
DrawPie(x+w-r,y+r,r,r,0,90,segments) 'UR
DrawPie(x+r,y+h-r,r,r,180,270,segments) 'LL
DrawPie(x+w-r,y+h-r,r,r,270,360,segments) 'LR

DrawRect( x, y + r, w, h - r - r ) 'Centre + Left/Right
DrawRect( x + r, y, w - r - r, r ) 'Top
DrawRect( x + r, y + h - r, w - r - r, r ) 'Bottom
End Function
[/monkeycode]

and you can add this to the top of the RoundedRect Functions to reduce the radius to prevent errors.

[monkeycode]
If h < w
If r > ( h / 2.0 ) Then r = ( h / 2.0 )
Else
If r > ( w / 2.0 ) Then r = ( w / 2.0 )
Endif
[/monkeycode]


Nobuyuki(Posted 2012) [#12]
cool! I was holding off on setting "r" to a new value within these routines until I knew whether or not it would automatically copy the value back to the caller (see http://www.monkeycoder.co.nz/Community/posts.php?topic=3282 ). Now, everything's fantastic.

Just FYI, the checks for radius size fail if any of the values are negative. To keep it relatively sane (ie, the shape still stays within bounds even though the corners invert), only change the radius from checking absolute value, like so:

[monkeycode]
If h < w
If r > Abs( h / 2.0 ) Then r = ( h / 2.0 )
Else
If r > Abs( w / 2.0 ) Then r = ( w / 2.0 )
End If
[/monkeycode]


NoOdle(Posted 2012) [#13]
Yes, I didn't consider negative values.

I quickly wrote a Draw Heart Function with outline, might prove useful. (Not extensively tested yet)

[monkeycode]
Function DrawHeart : Void( x : Float, y : Float, w : Float, h : Float, segments : Int = 8 )
'Draws 4 Sided Pie. Diamond, hearts centre.
DrawPie( x, y, w, h, 0, 360.0, 4 )
'Draws 2 Pies for the hearts ears. Radius = 1 / Sqrt( 2 ) = 0.707. Angles are larger than 180 to prevent seams.
DrawPie( x - ( 0.5 * w ), y - ( 0.5 * h ), 0.707 * w, 0.707 * h, 0, 270, segments )
DrawPie( x + ( 0.5 * w ), y - ( 0.5 * h ), 0.707 * w, 0.707 * h, -90, 180, segments )
End Function

Function DrawHeartOutline : Void( x : Float, y : Float, w : Float, h : Float, segments : Int = 8 )
'Draws 2 Sided Arc. Bottom of Diamond, hearts centre.
DrawArc( x, y, w, h, 180.0, 360.0, 2 )
'Draws 2 Arcs for the hearts ears. 1 / Sqrt( 2 ) = 0.707
DrawArc( x - ( 0.5 * w ), y - ( 0.5 * h ), 0.707 * w, 0.707 * h, 45, 225, segments )
DrawArc( x + ( 0.5 * w ), y - ( 0.5 * h ), 0.707 * w, 0.707 * h, -45, 135, segments )
End Function
[/monkeycode]

I have also just written a Draw Spiral Function, outline only at the moment and still a WIP: (increasing the passed aEnd value loops the spiral more than once)

[monkeycode]
Function DrawSpiralOutline : Void( x : Float, y : Float, xRad : Float, yRad : Float, aStart : Float=0.0, aEnd : Float = 360.0, segments : Int = 8 )
Local x1 : Float = x
Local y1 : Float = y
Local x2 : Float
Local y2 : Float
Local div : Float = ( aEnd - aStart ) / segments
For Local i : Int = 1 To segments
x2 = x + ( Cos( aStart + ( i * div )) * ( xRad * ( Float( i ) / segments )))
y2 = y - ( Sin( aStart + ( i * div )) * ( yRad * ( Float( i ) / segments )))
DrawLine( x1, y1, x2, y2)
x1 = x2
y1 = y2
Next
End Function
[/monkeycode]


chrisc2977(Posted 2014) [#14]
NoOdle! You are my most favorite person of the week! Or maybe even month!
I was picking my brains on how to render a pie chart and i diddnt even know the 'DrawPoly()' existed. This makes my task alot easier, Thank you!


Supertino(Posted 2014) [#15]
Yes these are very handy functions, I use them to help render my custom GUI module. +1