hey,
a different thread prompted me to hunt down and port a game programming gem: a type of curve called a catmull-rom spline.
this curve looks to be quite useful, i wish i had found it earlier ;)
an attempt at an explanation:
a simple "straight line" path works something like this:
you know 2 points, the point behind (where you were) p1 and the point in front of (where you're going) p2. given a parameter u, 0 <= u <= 1, you get a path which moves from p1 to p2 as you increase u from 0 to 1.
the formula is simply:
currentPosition = (1-u)*p1 + u*p2;
this is also known as linear interpolation between two points.
catmull-roms need 2 extra points: the point "behind" p1 (call it p0) and the point "in front of" p2 (call it p3).
given these points and a parameter u, you'll get a nice smoooth path which moves between p1 and p2 as u goes from 0 to 1.
the formula is:
currentPosition = 0.5 *( (2*p1) + (-p0 + p2)*u + (2*p0 - 5*p1 + 4*p2 - p3)*(u*u) + (-p0 + 3*p1 - 3*p2 + p3)*(u*u*u));
i have _no_ idea how this works, it just does. also, it may look scary but it's just a few multiplies.
aaanyway.. here's some code that will produce a test app demonstrating this. use the left and right arrows to move amongst the points:
Code://catmull-rom spline, from GPG1 3.4
function vec2(x,y)
{
this.x = x;
this.y = y;
}
function DrawPlus(mc, x,y, r)
{
mc.moveTo(x+r, y);
mc.lineTo(x-r, y);
mc.moveTo(x, y+r);
mc.lineTo(x, y-r);
}
pList = new Array();
pList.push(new vec2(50, 250));
pList.push(new vec2(100, 300));
pList.push(new vec2(150, 200));
pList.push(new vec2(200, 250));
pList.push(new vec2(250, 300));
pList.push(new vec2(180, 350));
pList.push(new vec2(80, 320));
var sbuf = _root.createEmptyMovieClip("staticBuffer", 100);
sbuf.clear();
sbuf.lineStyle(0, 0x000000, 100);
for(var i in pList)
{
var v = pList[i];
DrawPlus(sbuf, v.x, v.y, 2);
}
var dbuf = _root.createEmptyMovieClip("dynamicBuffer", 200);
P_INDEX = 0;
U_PARAM = 0.4;
function DoStuff()
{
if(Key.isDown(Key.LEFT))
{
U_PARAM -= 0.02;
UpdatePos();
}
else if(Key.isDown(Key.RIGHT))
{
U_PARAM += 0.02;
UpdatePos();
}
}
function UpdatePos()
{
//wrap point indices
if(U_PARAM < 0)
{
//we moved into the next segment, backwards
P_INDEX = ((P_INDEX - 1)+pList.length)%pList.length;
U_PARAM = 1 + U_PARAM;
}
else if(1 < U_PARAM)
{
//we moved into the next segment, forwards
P_INDEX = (P_INDEX + 1)%pList.length;
U_PARAM = U_PARAM - 1;
}
//-----
//get the 4 points to interpolate between
var p0 = pList[P_INDEX];
var p1 = pList[(P_INDEX + 1)%pList.length];
var p2 = pList[(P_INDEX + 2)%pList.length];
var p3 = pList[(P_INDEX + 3)%pList.length];
var u = U_PARAM;//get parametric position between p1 and p2
//calculate weights given u
var uu = u*u;
var uuu = uu*u;
var w0 = -0.5*uuu + uu - 0.5*u;
var w1 = 1.5*uuu - 2.5*uu + 1;
var w2 = -1.5*uuu + 2*uu + 0.5*u;
var w3 = 0.5*uuu - 0.5*uu;
var x = p0.x*w0 + p1.x*w1 + p2.x*w2 + p3.x*w3;
var y = p0.y*w0 + p1.y*w1 + p2.y*w2 + p3.y*w3;
//this is "the long version"
//var x = 0.5 *((2 * p1.x) + (-p0.x + p2.x)*u + (2*p0.x - 5*p1.x + 4*p2.x - p3.x)*uu + (-p0.x + 3*p1.x - 3*p2.x + p3.x)*uuu);
//var y = 0.5 *((2 * p1.y) + (-p0.y + p2.y)*u + (2*p0.y - 5*p1.y + 4*p2.y - p3.y)*uu + (-p0.y + 3*p1.y - 3*p2.y + p3.y)*uuu);
//draw the result
dbuf.clear();
dbuf.lineStyle(0, 0x228822, 100);
DrawPlus(dbuf, x, y, 4);
}
UpdatePos();
_root.onEnterFrame = DoStuff;
this could be used for many things: if you don't want to use it to generate an object's current position, you could (instead) take a very coarse set of points, use the above to generate some nice in-between points, then use your normal linear interpolator to move between the generated points.
the one problem with the above is that you aren't guaranteed constant velocity, like you are with the linear version. however, if the samples are evenly spaced it's not too bad, and the "slowing down at sharp corners" is quite pleasing.
aaaaaaaanyway..
raigan
