Game Development Community

[Math] 3D Bezier Path orientation interpolation

by Phillip O'Shea · in Torque Game Engine Advanced · 03/30/2009 (3:35 am) · 8 replies

Ouch, nasty title!

I am working on a 3D Quadratic Bezier Path (think TGB's pathing method, only 3D) and all is working well, except the object's orientation.

I basically have no idea how to ensure that the object attached to the path is orientated correctly. Here is a snippet of the math:

static Point3F gBezierAxis( 1.f, 0.f, 0.f );

...

void VPath::advanceBezierPath( const VPathObject &pPathObject )
{
	// Nodes.
	const VPathNode &srcNode = mNodeList[pPathObject.SourceNode];
	const VPathNode &dstNode = mNodeList[pPathObject.DestinationNode];

	// Positions.
	const Point3F &pt0 = srcNode.Position;
	const Point3F &pt3 = dstNode.Position;

	// Fetch Node Rotation Matrices.
	MatrixF mat0, mat1;
	srcNode.Rotation.setMatrix( &mat0 );
	dstNode.Rotation.setMatrix( &mat1 );

	// Determine Tangent Axis.
	Point3F pt1(  gBezierAxis * srcNode.Weight );
	Point3F pt2( -gBezierAxis * dstNode.Weight );

	// Rotate Axis.
	mat0.mulP( pt1 );
	mat1.mulP( pt2 );

	// Offset Points.
	pt1 += pt0;
	pt2 += pt3;

	// Interp Times.
	const F32 t  = getMin( pPathObject.Interp, 1.f );
	const F32 it = ( 1.f - t );

	// Calculate New Position.
	const Point3F newPosition = ( pt0 * it * it * it ) + ( 3 * pt1 * it * it * t ) + ( 3 * pt2 * it * t * t ) + ( pt3 * t * t * t );

	// Apply Position.
	pPathObject.Object->setPosition( newPosition );
}

Each node has its own transform, so at t = 0, the object should have the same transform of the source node, and at t = 1, it should be the same transform of the destination node. In between is the hard part!

I'd appreciate a bit of direction.

About the author

Head of Violent Tulip, a small independent software development company working in Wollongong, Australia. Go to http://www.violent-tulip.com/ to see our latest offerings.


#1
03/30/2009 (4:04 am)
The easiest way is going to be to interpolate one additional tiny step along your bezier path, maybe by using "it * 1.001" to calculate another position, and then orienting your node towards that second point from newPosition.
#2
03/30/2009 (4:07 am)
Thats what I was thinking, but the actual way of orientating it is where I get in trouble. The easiest thing is to just get the current position of the object before you apply it. Then the position delta is just new - old:

void VPath::advanceBezierPath( const VPathObject &pPathObject )
{
	...

	// Grab Old Position.
	const Point3F oldPosition = pPathObject.Object->getPosition();

	// Calculate New Position.
	const Point3F newPosition = ( pt0 * it * it * it ) + ( 3 * pt1 * it * it * t ) + ( 3 * pt2 * it * t * t ) + ( pt3 * t * t * t );

	// Determine Change.
	Point3F deltaPosition = ( newPosition - oldPosition );

	// Apply Position.
	pPathObject.Object->setPosition( newPosition );
}
#3
03/30/2009 (4:45 am)
I'm not able to get into my code at the moment and I'm not sure if I'm remembering the exact functions/methods you'll need, but you can do something along these lines:

//get the normalized direction vector
Point3F dirVector = newPosition - oldPosition;
dirVector.normalize();

//starting direction vector of your node. I think this is default in Torque
Point3F baseDir( 0, 1, 0 ); 

//axis is the cross product of the two vectors
Point3F axis = mCross( baseDir, dirVector );

//angle is the dot product of the two vectors
F32 angle = mDot( baseDir, dirVector );

Now you should have your axis-angle orientation. I'm pretty sure Torque has something for setting orientations with quaternions, probably as part of the MatrixF class, but I haven't a clue what the method would be at the moment.

Hope it helps.

Cheers
#4
03/30/2009 (7:45 am)
Are srcNode.Rotation and dstNode.Rotation quaternions? If so, you can interpolate them exactly as you do with the position, then normalize the quaternion.
#5
03/30/2009 (1:59 pm)
Manoel, I can't do a linear interpolation between the two nodes because Beziers don't always have linear paths between nodes.

Gerald, thanks for that, I think it is close but maybe I am doing something wrong.

I ended up adding a QuatF storing the rotation of the path object and every time he moves over a node it will reset to the source node's rotation.

...
	// Reset Rotation.
	pathObject.Rotation = mNodeList[pathObject.SourceNode].Rotation;

	// Reset Transform.
	MatrixF mat;
	pathObject.Rotation.setMatrix( &mat );
	mat.setPosition( pathObject.Object->getPosition() );
	pathObject.Object->setTransform( mat );
...

I then applied your code:

void VPath::advanceBezierPath( VPathObject &pPathObject )
{
	...

	// get the normalized direction vector
	Point3F dirVector   = newPosition - oldPosition;
	dirVector.normalize();

	// Z-Axis is UP
	Point3F baseDir( 0.f, 0.f, 1.f ); 

	// axis is the cross product of the two vectors
	Point3F axis = mCross( baseDir, dirVector );

	// angle is the dot product of the two vectors
	F32 angle = mDot( baseDir, dirVector ); 

	QuatF rot( axis, angle );

	// Update Rotation.
	pPathObject.Rotation *= rot;

	// Set Transform.
	MatrixF mat;
	pPathObject.Rotation.setMatrix( &mat );
	mat.setPosition( newPosition );

	// Apply.
	pPathObject.Object->setTransform( mat );
}

When I look at the angle that is being produced, it is very small (0x-05) and doesn't do anything to the rotation of the shape.
#6
03/30/2009 (3:49 pm)
you can calc with atan2 too
I'm using this code for aiming my ai turrets.


dir = targerpos-currentpos;
 d = sqrt( dir.x*dir.x + dir.y*dir.y );
 x_angle = atan2( dir.z, dir.x );
 z_angle = atan2( dir.z, d );
 rot_mtx = MatrixCreateFromEuler( x_angle, 0, -z_angle );
#7
03/31/2009 (7:14 am)
Phillip, I meant doing the bezier interpolation on the rotations (since they are actually Point4F vectors), but thinking again that might not give you the results you want (since it'll depends on the rotation of the path nodes, and they aren't guaranteed to orientate along the path).
#8
04/06/2009 (6:17 pm)
I ended up using a "lookAt" method, its pretty simple, yet effective:

// Z-Axis.
VectorF zVec = ( newPosition - oldPosition );
zVec.normalize();

// X-Axis.
VectorF xVec = mCross( Point3F( 0.f, 0.f, 1.f ), zVec );
xVec.normalize();

// Y-Axis.
VectorF yVec = mCross( zVec, xVec );
yVec.normalize();

// Setup Object Transform.
MatrixF mat( true );
mat.setColumn( 0, xVec );
mat.setColumn( 1, yVec );
mat.setColumn( 2, zVec );
mat.setColumn( 3, newPosition );