Cardinal splines in ActionScript (Updated)

Algorithms, Flex, Source Code,
8/20/07

Here’s the code driving the cardinal spline function I mentioned in the post last week on visual prototyping with Flex. ActionScript already has a bezier drawing method called “curveTo,” but it obliges you to specify a control point for every anchor point you add to the curve. This is great for precision, but sometimes you just want to run a smooth curve through a bunch of points without having to figure out where all the control points should be.

That’s what’s great about the cardinal spline—the next and previous points on the curve function as the control points.  (This means you need to add an extra point to the beginning and end of the curve, as you can see in the diagram accompanying the Wikipedia entry for c-splines.) You also get a “tension” parameter which allows you to set how taut the curve is between points.

I’ve included my current incarnation of this routine below.  Here’s how to use it:

  1. Start with an array of values you want to run the curve through: 3, 7, 2, 6
  2. Duplicate the start and end values: 3, 3, 7, 2, 6, 6
  3. Iterate through each pair of the original values, each of which represents a segment of the curve: 3-7, 7-2, 2-6
  4. For each of these iterations, proceed along the curve n number of steps (more steps = a smoother curve)
  5. Call this function at each step to get the value of the curve at that step

If you’re using the function to set the location of a sprite, then you’ll need to maintain two lists of values (one for x position, one for y position) and call the function once for each list as you’re iterating through each segment of the curve.

Update: Here’s an .fla example that uses the function to draw a curved line through twenty random points.

Here’s the source for the function itself:

/** 
 * Returns a value at the given step on the cardinal spline between two other values. 
 * 
 * @param prevVal  The point just prior the curve segment we are evaluating. 
 * @param startVal     The starting point of the curve segment we are evaluating. 
 * @param endVal       The ending point of the curve segment we are evaluating. 
 * @param nextVal  The point just after the curve segment we are evaluating. 
 * @param numSteps     Number of steps in the curve segment. 
 * @param curStep  The current step in the curve segment. 
 * @param easing       Type of interpolation to be applied to the curve segment. 
 * @param tension  How taut the curve is (0 = straight, 1 = curvy) 
 */ 
function getCardinalSplinePoint(prevVal:Number, startVal:Number, endVal:Number, nextVal:Number, numSteps:Number, curStep:Number, easing:String, tension:Number):Number {

    var t1:Number; 
    var t2:Number;
   
    switch (easing) {
   
        case “easeinout”: 
        t1 = endVal - prevVal; 
        t2 = nextVal - startVal; 
        break;
       
        case “noease”: 
        t1 = 0; 
        t2 = 0; 
        break;
       
        case “easein”: 
        t1 = 0; 
        t2 = nextVal - startVal; 
        break;
       
        case “easeout”: 
        t1 = endVal - prevVal; 
        t2 = 0; 
        break; 
    }
   
    t1 *= tension; 
    t2 *= tension;
   
    var s:Number = curStep / numSteps; 
    var h1:Number = (2 * Math.pow(s,3)) - (3 * Math.pow(s,2)) + 1; 
    var h2:Number = -(2 * Math.pow(s,3)) + (3 * Math.pow(s,2)); 
    var h3:Number = Math.pow(s,3) - (2 * Math.pow(s,2)) + s; 
    var h4:Number = Math.pow(s,3) - Math.pow(s,2);
   
    var value:Number = (h1 * startVal) + (h2 * endVal) + (h3 * t1) + (h4 * t2);
   
    return value; 
}