Sunday, March 2, 2008

Using Vectors to Calculate Orientation

I've got a game character moving around my 3D world but how do I make him face the direction he is moving? This is a common question I see on game development message boards. The answer can be found by using Trigonometry and the arc tangent function or vector mathematics. While both techniques work equally well I am going to demonstrate how to solve this problem using vectors with examples in XNA.



In order to calculate the orientation of an object moving through 3D space, we need to build a matrix for the object's up, right, and forward (also called look vector) vectors. This matrix will be used to calculate the rotation around the Y axis which, is what we need to properly orientate an object in the direction it is moving. The object is already moving in 3D space which, gives us our look vector. In most game engines the Y axis is used for up and down. This means we should be able to use vector 0,1,0 to represent the up vector. Now, that we have our up and forward vectors we should calculate our "right" vector. To find the "right" vector we need to find the cross product of our up and look vectors. The cross product is an operation on two vectors which, results in a new vector that is perpendicular to the two input vectors. This is exactly what we need to find our "right" vector.

Here is some XNA code which, demonstrates what I've discussed so far:

//Look Vector (We know direction of movement)
Vector3 v3L = v3Direction;

v3L.Normalize();

//Up Vector
Vector3 v3U = new Vector3(0.0f, 1.0f, 0.0f);
v3U.Normalize();

//Right Vector
Vector3 v3R = Vector3.Cross(v3U, v3L);
v3R.Normalize();


Notice that we must normalize the vectors after each calculation. Since these vectors represent orientation we need to factor out the magnitude component. The Normalize method of the vector class (in XNA) helps us do just that. To make this more clear, the vector 0,3,0 gives us an orientation pointing up the Y axis with a magnitude of 3. For this example, all we care about is orientation so there is no difference between 0,3,0 and 0,1,0. However, 0,1,0 makes mathematical operations on orientation vectors easier. By normalizing the vector we factor out the magnitude component so we can focus on orientation.

The next step is to plug these vectors into a Matrix so that we can use this matrix in calculating the object's final world position. Here is the XNA code to accomplish this:

Matrix YRot = new Matrix();
YRot.M11 = vR.X; YRot.M12 = vR.Y; YRot.M13 = vR.Z; YRot.M14 = 0.0f; //Right
YRot.M21 = vU.X; YRot.M22 = vU.Y; YRot.M23 = vU.Z; YRot.M24 = 0.0f; //UpYRot.M31 = vL.X; YRot.M32 = vL.Y; YRot.M33 = vL.Z; YRot.M34 = 0.0f; //LookYRot.M41 = 0.0f; YRot.M42 = 0.0f; YRot.M43 = 0.0f; YRot.M44 = 1.0f;


We now have a matrix which, represents our objects orientation in 3D space. This orientation matrix should match the direction of movement. The next step is to include this matrix in the object's world position calculation. The code example below assumes that the objectPosition variable is being constantly updated in the game loop. Our goal now is to calculate the object's final world position, scale, and orientation.

Matrix matTranslation = Matrix.CreateTranslation(objectPosition);
Matrix matYRotation = YRot

Matrix worldMatrix = matYRotation * matTranslation;

The order of operations is important when concatenating matrices. The correct order should be rotation, scale, and translation. This is because all operations take place at the world's origin. If the object is translated before it is rotated you will get an orbit effect around the world's origin rather than the rotation we are trying to achieve.

We have almost achieved our goal. The worldMatrix should now hold the rotation, scale, and position of our object in world space. Additionally, the orientation of our object should match the direction the object is moving. All we have to do now is project our object to the computer screen. The first step in this process is to translate the object from world space to view space. Remember that we have a camera in our scene and objects need to be translated in reference to the camera's position. This is called translating the object from the world coordinate system to the view coordinate system. Once this is complete, we need a way to project our 3D scene to a 2D screen. To do this, we should translate objects from the view coordinate system to the screen coordinate system.

Vector3 cameraPosition = new Vector3(0.0f, 0.0f, 10.0f);
Vector3 cameraLookAt = new Vector3(0.0f, 0.0f, 0.0f);


Matrix viewMatrix = Matrix.CreateLookAt(cameraPosition, cameraLookAt, Vector3.Up);

Matrix projectionMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45.0f), (float)GraphicsDevice.Viewport.Width / (float)GraphicsDevice.Viewport.Height, 0.0005f, 10000.0f);

At this point we have the worldMatrix variable which holds the rotation, scale, and position of our object in world coordinate space. We also have the viewMatrix variable which represents view coordinate space and the projectionMatrix variable which, holds the information required to project our 3D scene to a 2D screen. All we have to do, at this point, is concatenate our matrices together to project our object to the screen.

Matrix worldViewProjection = worldMatrix * viewMatrix * projectionMatrix

The worldViewProjection matrix now holds all the information required to get your object from 3D world space to your computer screen. What you do next depends on the game engine you're using. In XNA you would pass the worldViewProjection matrix into a pixel/vertex shader for rendering.



No comments: