But I keep needing shapes with rounded corners. Actionscript has a built-in rounded-corner rectangle class (graphics.drawRoundedRect), but that's the only rounded-cornder method available. I often need rounded-corner triangles, rounded-corner tooltips and rounded-corner circles (just kidding on that last one). So I decided to create a method that allowed me to round the corners of any shape.

I knew I'd need math, which is not my strong point. So I ignored that problem and focused on how I'd create rounded corners with a Bezier-pen tool. Think of the top of a triangle: imaging making points on both of the lines that meet at the top (the apex). If those points are down a little from the top, they can act as start and end points for a Bezier curve. In the illustration, below, I've drawn the start and end points as red dots.

A point at the apex (blue dot) can act as a control point, giving you all the points you need to draw a curve:

Note: Actionscript's Bezier curves have just one control point. The curve is the widest-possible path that Flash can draw without it crossing outside the triangle formed by the start, end and control points.

With this approach, I could make a rounded-cornered shape by starting with a straight-cornered shape and calculating a Bezier curve for each corner.

The first step would be to figure out how to plot points that are a little ways in from the ends of all the lines, as shown by the red dots, above. Generalizing this, I needed a way to find a point on a line. Which is where math reared its ugly head. Luckily, google exists for moments like this. Before long, I found a formula and turned it into a nifty function:

function pointOnLine(t : Number, point1 : Point, point2 : Point ) : Point

{

var xResult : Number = point1.x + ( t * ( point2.x - point1.x ) );

var yResult : Number = point1.y + ( t * ( point2.y - point1.y ) );

return new Point( xResult, yResult );

}

You can use this function by first creating two points that make up a line...

import flash.geom.Point;

var start : Point = new Point( 100, 100 );

var end : Point = new Point( 100, 500 );

... and then handing the points to the pointOnLine function. You also have to hand it t, which is a number between 0 and 1. A t of zero returns the start-point of the line; a t of .5 returns the mid-point of the line. A t of 1 returns the end-point of the line. And so on. So to get a point close to the end of the line, I could call the function like this:

var nearEnd : Point = pointOnLine( .8, start, end );

That out of the way, I could now calculate all the start and end points I needed by calling pointOnLine() twice for each line in my shape, e.g.

//note that I'm using .2 and .8 for t

var nearStart : Point = pointOnLine( .2, start, end );

var nearEnd : Point = pointOnLine( .8, start, end );

Here's an illustration of nearStart and nearEnd for one lines on a triangle:

Now all I had to do was to loop through all the lines on the triangle to find the rest of the start and end points:

After that, it was easy to find the corner points, since they were used to define the triangle in the first place:

I then drew curves through each group of start-end-corner points with the built in curve-to function:

graphics.moveTo( start.x, start.y );

graphics.curveTo( control.x, control.y, end.x, end.y );

In the illustrations here, I've drawn the triangle so you can understand my thought process. In fact, my code never draws the straight-edge version. If it did, it would have to go back later and erase the non-curvy corners. Instead, it draws actual lines from the two inset points of each virtual line: e.g. lines from the .2 t to the .8 t of each line.

If I go through the same process with the t values adjusted to move the pointOnLines in a little, say .4 and .6, I get something like this:

I packaged up my code in a utility class called RoundedShapes. To use it, you call its draw() method as follows:

RounedShapes.draw( target, roundness, points );

The target parameter is the display object on which you want to draw. It can be any object with a graphics property: e.g. Sprite, MovieClip or Shape.

The roundness parameter is essentially t. Roundness must be a number between 0 and 1, 0 being the same as straight.

The points parameter is an array of Points, defining the straight-corner version of the shape.

To draw a rounded-corner triangle, you could use code like this:

import com.grumblebee.ui.drawing.RoundedShapes;

var sprite : Sprite = new Sprite();

var points : Array = [ new Point( 320, 10 ),

new Point( 420, 80 ),

new Point( 320, 160 ),

new Point( 320, 10 ) ];

var roundness : Number = .2;

sprite.graphics.lineStyle( 2, 0x000000 );

sprite.graphics.beginFill( 0xFF0000 );

RoundedShapes.draw( sprite, roundness, points );

sprite.graphics.endFill();

addChild( sprite );

Here's a swf with some example shapes drawn by RoundedShapes.draw(). Just for fun, I animated the roundness parameter of the star:

Here's a link to the RoundedShapes class and an example fla. Happy rounding!

## No comments:

## Post a Comment