World::Create()

AvatarProgramming ramblings with some vague emphasis on Games, Games Tools and Web.

In love with splines

When you're target is to write some AI behaviour that makes a game object move in a realistic, but determinate manner, you really can't beat the spline.

Path finding and steering code is all very well and good, and can provide excellent results, but if you simply want a fish ( say ) to swim over to a point and not look wobble, or stutter, or swim into rocks when the path finding fails, then a spline is the best way.

Some code:


class Hermite
{
public:
Hermite();
Hermite(Vector3_arg start, Vector3_arg end, Vector3_arg t0, Vector3_arg t1);

// Return the position at this point along the spline.
Vector3 PointAt(float t) const;

// Return the tangent at this point on the spline.
Vector3 TangentAt(float t ) const;

// Return an approximation of the length of the spline.
// Calculated by dividing the spline into 10 segments and working out straightline distances between them.
float GetLength();

// Return the distance left on the spline in the forwards direction ( towards m_end )
float GetDistanceForwards(float t);

// Return the distance left on the spline in the backwards direction ( towards m_start )
float GetDistanceBackwards(float t);

Vector3 m_start;
Vector3 m_end;
Vector3 m_t0;
Vector3 m_t1;

float m_length;
};


Note, I've made the contents of this class public and removed the getters and setters to keep the sample size down.

Here's one of the most straightforward splines to use. The Hermite. It's incredibly easy to create, just provide the start point and tangent, and the end point and tangent. And then you can get any point along the spline using the PointAt(t) method and any tangent along the spline using the derivative method TangentAt(t) where t=0 is the start of the spline, and t=1 is the end.


Vector3 Hermite::PointAt(float t) const
{
float t2 = t * t;
float t3 = t * t2;

float h1 = ( 2 * t3 ) - ( 3 * t2 ) + 1;
float h2 = ( -2 * t3 ) + ( 3 * t2 );
float h3 = t3 - ( 2 * t2 ) + t;
float h4 = t3 - t2;

return ( h1 * m_start ) + ( h2 * m_end ) + ( h3 * m_t0 ) + ( h4 * m_t1 );
}

Vector3 Hermite::TangentAt(float t ) const
{
float t2 = t * t;

float dh1 = ( 6 * t2 ) - ( 6 * t );
float dh2 = ( -6 * t2 ) + ( 6 * t );
float dh3 = ( 3 * t2 ) - ( 4 * t ) + 1;
float dh4 = (3 * t2 ) - ( 2 * t );

return ( dh1 * m_start ) + ( dh2 * m_end ) + ( dh3 * m_t0 ) + ( dh4 * m_t1 );
}


It's so easy, now if you need to get your fish smoothly from where it is to another point. You just create a spline.



void CreateFish()
{
m_pHermite = new Hermite( pFish->GetPosition(), pFish->GetHeading(), pTarget->GetPosition(), pTarget->GetHeading() );
}

void UpdateFishPosition(float deltaTime, Fish* pFish, Hermite* pHermite)
{
float newT = pFish->GetCurrentT() + ( deltaTime * pFish->GetSpeed() );
if( newT > 1.0f )
newT = 1.0f;

Vector3 position = pHermite->PointAt( newT );
pFish->SetPosition( position );
}



The sharp-eyed among you will note that increasing t by ( deltaTime * pFish->GetSpeed() ) is not right. t is not a measure of distance, it is a normalised distance along the spline so the units here don't match.

We should be converting the (deltaTime * speed ) to be unitless before we increment t. Giving us this:


void UpdateFishPosition(float deltaTime, Fish* pFish, Hermite* pHermite)
{
float newT = pFish->GetCurrentT() + ( deltaTime * pFish->GetSpeed() / pHermite->GetLength() );
if( newT > 1.0f )
newT = 1.0f;

Vector3 position = pHermite->PointAt( newT );
pFish->SetPosition( position );
}


The one problem remaining is that your point doesn't move along the spline at a constant speed. The interpolation is dependent on the curviness of the spline. There are ways around this, but to be honest, they are probably not worth worrying about. If you keep you splines relatively short and instead use multiple splines linked together, you should move along the total length of each spline in approximately the correct time, even if your movement along the spline is not constant speed.

So, demonstrated using rubbishy pseudo-code and skipping the constant speed issue, there you have it. Hermite splines are a simple and effective way of simulating smooth and natural motion for your in-game objects.