|
|
|
#1 |
|
Senior Member
Join Date: Jul 2004
Posts: 153
|
nice smooth paths
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 Last edited by metanet; 12-04-2004 at 03:31 PM. |
|
|
|
|
|
#2 |
|
Script kiddie
Join Date: Jun 2004
Location: England
Posts: 2,559
|
It can be scary how much you know sometimes. I practically fell into a coma when I played N.
|
|
|
|
|
|
#3 |
|
Run for your life!
Join Date: Jul 2003
Location: Vancouver, Canada
Posts: 679
|
Are you psychic?! i was just contemplating last night what i could do to make a smooth path
|
|
|
|
|
|
#4 | |
|
Senior Member
Join Date: Jul 2004
Posts: 153
|
Quote:
![]() i just found it in a book and copied it verbatim into actionscript. but.. having said that, i think it's important for people to consider researching/finding/understanding existing tools/solutions as an important skill, possibly more important than coming up with solutions from scratch, because in terms of games in actionscript, all the problems you're likely to face have already been faced in the past 10 years by people programming for pc, snes, etc.. whoever mr/mrs. catmull and rom are, THEY are crazy smart. it's amazing to me that you just plunk the number in and you get a smooth curve that ALWAYS passes through the points. it makes me wish i had payed attention more to some math classes ![]() raigan |
|
|
|
|
|
|
#5 |
|
Senior Member
Join Date: Jul 2004
Location: In a later dimension
Posts: 201
|
Thats amazing. It doesnt seem to be completely perfect though. If you slow it down a little and zoom in, you can clearly see that at certain points, it will jump a pixel or two left and right over and over before it reaches the point. I guess that would just be because flash is rounding all the floating point numbers a tiny bit.
Maybe one day I will understand what that formula does |
|
|
|
|
|
#6 |
|
Senior Member
Join Date: Jul 2004
Posts: 153
|
i think it might just be problems with my code, not the formula in particular but the logic for when the parameter goes <0 or >1.. at least, there were some bugs in that stuff that i fixed before posting.
raigan |
|
|
|
|
|
#8 |
|
·»¤«·
Join Date: Jun 2003
Posts: 745
|
That's very nice! you can consider yourself the 1337 actionscipter
![]() Now, while not nearly as elegant as this, I have in the past just used rotation and standard accel/decay formulas for manuvering curves around points... but here is the real issue with 'smooth paths' of any time ive always wondered about: in a game, say one that uses some node based path finding, when an AI player is going from point to point using 'smooth paths', how do you predict(and adjust to avoid) the fact that in tight areas, overshooting may result in collisions? It might sounds confusing, but what I mean is going through a node at a curve may result in colliding with a wall(unless the corridor happens to perfectly match the curve). This can be especially bad since the AI may get stuck and not be able to proceed to the next node. The only way I can think of to avoid this is to make 'fat' node paths, then you could easily calculate, based on the players manuevering abilities(rotation speed, max speed, decel, accel) when approaching a node when it will have to slow down, and when it can begin to turn, in order to keep it within the fat paths radius. But what about when using a formula like the above? I suppose could calculate on the fly the projected path, then check the projected path for collisions, but that seems pretty heavy, and what do you do when a collision is detected? Maybe this is all overkill for Flash... but it sure is fun to think about
__________________
>flashl!ght< All the normal names were taken. Ron Paul 2012! http://campaignforliberty.com Last edited by >flashl!ght<; 12-04-2004 at 11:01 PM. |
|
|
|
|
|
#9 |
|
Senior Member
Join Date: Jul 2004
Posts: 153
|
i know what you mean.. the only solution i've ever heard of was to use something like your "fat" nodes: pathfinding nodes are never placed closer than a certain distance "r" from a wall, and objects can always turn in a radius smaller than "r".
i don't think that the above curves are great for pathfinding, it was mostly just to demonstrate that there are tons of cool solutions out there.. raigan |
|
|
|
|
|
#10 | |
|
Filthy Gorgeous
Join Date: Sep 2003
Location: Sunny Australia.
Posts: 478
|
Quote:
I'm almost tempted to start a new project to see how it goes... |
|
|
|
|
|
|
#11 |
|
Senior Member
Join Date: Jul 2004
Posts: 153
|
just thought you might be interested in this:
http://www.cubic.org/~submissive/sourcerer/hermite.htm raigan |
|
|
|
|
|
#12 |
|
Senior Member
Join Date: Jul 2004
Posts: 153
|
hey,
i just added variable "tension" (straight from the site); if anyone wants to muck about with adding bias/etc., please do.. i'm too bit busy+lazy to finish porting the rest of the ideas on that site.. 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
var uu = u*u;
var uuu = uu*u;
/*
//calculate weights given 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"; more than one .x and .y lookup per vector
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);
*/
//lets' try using a generic cardinal spline with tension
//(from http://www.cubic.org/~submissive/sourcerer/hermite.htm)
var h0 = 2*uuu - 3*uu + 1; // calculate basis function 1
var h1 = -2*uuu + 3*uu; // calculate basis function 2
var h2 = uuu - 2*uu + u; // calculate basis function 3
var h3 = uuu - uu; // calculate basis function 4
//use p0 and p3 to calculate tangents at p1 and p2
var a = 0.5;//tension; 0 is linear, 0.5 is catmull-rom, >1 is a bit weird..
var t1x = a*(p2.x - p0.x);
var t1y = a*(p2.y - p0.y);
var t2x = a*(p3.x - p1.x);
var t2y = a*(p3.y - p1.y);
var x = h0*p1.x + h1*p2.x + h2*t1x + h3*t2x;
var y = h0*p1.y + h1*p2.y + h2*t1y + h3*t2y;
//draw the result
dbuf.clear();
dbuf.lineStyle(0, 0x228822, 100);
DrawPlus(dbuf, x, y, 4);
}
UpdatePos();
_root.onEnterFrame = DoStuff;
|
|
|
|
|
|
#13 |
|
Senior Member
Join Date: Jul 2004
Posts: 153
|
hey,
um.. i'm not sure about the angles thing. if you need constant velocity then my suggestion above of "use this to generate some points, then use your normal constant-velocity straight-line pathing" is probably the best thing -- it's simple and you know it will work: just start at the first point in your original list of points, then (stepping along the parameter at a constant rate) get a point on the curve and store it in a list of "points on the curve". then, use this second list with your straight-line algo instead of the first "coarse" list when moving people around. trying to figure out how to move along a curve at constant velocity sounds like a huge headache to me. if you must, there are papers on this -- specifically, many 3D games use splines to move the camera around during cutscenes, and i'm positive i've read at least one paper on how to get constant-velocity movement over splines in realtime. but it's not likely to be cheap or easy.. raigan |
|
|
|
|
|
#14 | |
|
Untitled-1.fla
Join Date: Mar 2001
Location: Sweden
Posts: 1,626
|
Interesting thread metanet.
Quote:
__________________
AS 3.0 Water Effect :: ActionScript 3.0 Raytracer :: The Curse of Sylvaniah :: Tutorials |
|
|
|
|
![]() |
|
||||||
| Thread Tools | Search this Thread |
| Display Modes | |
|
|