AS3 – Rounding corners

Interactive demoA few days ago, I was with a few students from Gobelins talking about their school projects. One group was working on some trip-assistant project where they would display itineraries on a city map.

Map itineraries most of the time are painted as straight lines with square corners and so one student suggested that it would look nicer with rounded corners: easy stuff, I said, just get each segment’s direction vector, multiply by the corner radius you want and lineTo/curveTo the path.

For some reason this didn’t seem to be absolutely obvious, so I launched FlashDevelop and quickly hacked the needed code to automatically round the corners.

Geometry basics

Rounding corners diagram Let’s take a typical and unpleasant square corner. You get your ABC from a database and draw moveTo(A), lineTo(B), lineTo(C).

Now you admit it would please the eye to draw moveTo(A), lineTo(A’), curveTo(B, C’), lineTo(C). But how do you compute A’ and B’ automatically, with a nice R radius?

In the case when BA and BC are perpendicular, BA’ is simply BA (ie. A minus B) normalized to length R (ie. a vector on line BA of length R). Add BA’ to B and you get A’. Repeat with BC to get C’.

If BA and BC aren’t perpendicular you actually get larger/smaller radiuses but I found that it looked good and not too regular so I kept this simple computation in general.

Back to ActionScript 3

Now let’s look at ActionScript 3’s Point class. Really, this class is great, it makes such computations so easy that you don’t even need to now your math.

// provided coordinates and radius
var a:Point;
var b:Point;
var c:Point;
var r:Number;

// compute A'
var ba:Point = a.subtract(b);
ba = ba.normalize(r);
var ap:Point = b.add(ba);

// compute C'
var bc:Point = c.subtract(b);
bc = bc.normalize(r);
var cp:Point = b.add(bc);

// draw
graphics.moveTo(a.x, a.y);
graphics.lineTo(ap.x, ap.y);
graphics.curveTo(b.x, b.y, cp.x, cp.y);
graphics.lineTo(c.x, c.y);

Complete implementation

The final code is certainly trickier than just computing one corner – I’ve decided to give it a little more work to make it robust and efficient:

  • radius should be adaptive because some points could be “too close”,
  • computation should be efficient and minimize array access,
  • provide a nice “closed path” option,
  • use Vector.<T> (let’s get used to it).

And here’s the method 🙂

static public function drawRoundPath(g:Graphics, points:Vector.<Point>, 
		radius:Number = 20, closePath:Boolean = false):void
{
	// code by Philippe / http://philippe.elsass.me
	var count:int = points.length;
	if (count < 2) return;
	if (closePath && count < 3) return;
	
	var p0:Point = points[0];
	var p1:Point = points[1];
	var p2:Point;
	var pp0:Point;
	var pp2:Point;
	
	var last:Point;
	if (!closePath) 
	{
		g.moveTo(p0.x, p0.y);
		last = points[count - 1];
	}
	
	var n:int = (closePath) ? count + 1 : count - 1;
	
	for (var i:int = 1; i < n; i++) 
	{
		p2 = points[(i + 1) % count];
		
		var v0:Point = p0.subtract(p1);
		var v2:Point = p2.subtract(p1);
		var r:Number = Math.max(1, Math.min(radius, 
			Math.min(v0.length / 2, v2.length / 2)));
		v0.normalize(r);
		v2.normalize(r);
		pp0 = p1.add(v0);
		pp2 = p1.add(v2);
		
		if (i == 1 && closePath)
		{
			g.moveTo(pp0.x, pp0.y);
			last = pp0;
		}
		else g.lineTo(pp0.x, pp0.y);
		
		g.curveTo(p1.x, p1.y, pp2.x, pp2.y);
		p0 = p1;
		p1 = p2;
	}
	
	g.lineTo(last.x, last.y);
}

Possible further development:

  • create another helper method to produce a GraphicsPath collection for reusable drawing.

Interactive demo with source

Feel free to try out the little interactive demo – you can add/drag/remove/insert points and enjoy your rounded work of art:

8 thoughts on “AS3 – Rounding corners

  1. Pingback: Philippe.me » Blog Archive » AS3 – Parametric path drawing - C’est vraiment très intéressant

  2. Very useful demo.. wish adobe would integrate something like this in their framework

    Cheers

  3. There is an error in the function “Main:removePoint(e:Event)” problematic when one removes the point which has index 0.
    We must replace
    setLastPoint(_currentPath.lastIndex >= 0 ? _currentPath.xpoints[_currentPath.lastIndex – 1] : null);
    with
    setLastPoint(_currentPath.lastIndex > 0 ? _currentPath.xpoints[_currentPath.lastIndex – 1] : null);

    Thank you for your job!

Comments are closed.