Game Development Community

Vehicle and camera angle in 3rd person view?

by Matt Zuhlke · in Torque Game Engine · 04/19/2005 (10:12 pm) · 14 replies

I'm playing with the starter.racing example and I'd like to prevent the camera from rotating to follow the angle of the vehicle's x axis when in 3rd person view. The way it is now, if you drive up a steep hill you're suddenly looking at the sky rather than the roof of the vehicle. Ideally I'd like the camera to just point down at the car at a fixed angle. I am guessing this could be done by modifying Vehicle::getCameraTransform in vehicle.cc, probably by changing this bit: (?)

// Build a transform that points along the eye axis
   // but where the Z axis is always up.
   if (mDataBlock->cameraRoll)
      mat->mul(eye,rot);
   else {
      MatrixF cam(1);
      VectorF x,y,z(0,0,1);
      eye.getColumn(1, &y);
      mCross(y, z, &x);
      x.normalize();
      mCross(x, y, &z);
      z.normalize();
      cam.setColumn(0,x);
      cam.setColumn(1,y);
      cam.setColumn(2,z);
      mat->mul(cam,rot);
   }

Could someone knowlegeable in matrix math help me out? Thanks!

#1
04/20/2005 (12:58 am)
Quick primer:

4 columns in a matrix. First column is the x axis of the "view" of the matrix, second is the y axis of the view, third is the z axis of the view. Fourth is the position.

Make sense? :)
#2
04/27/2005 (10:12 pm)
Hi Ben,

Ok that makes sense, at least from what little I remember of my Matrices and Linear Algebra class.

But I'm confused how this bit works (the part that keeps the camera from rolling with the vehicle):

VectorF x,y,z(0,0,1);
eye.getColumn(1, &y);
mCross(y, z, &x);
x.normalize();
mCross(x, y, &z);
z.normalize();
#3
04/28/2005 (11:23 am)
How that works: (Note that in Torque, X is "right, Y is "forward", and Z is "up".)

// Start with 3 vectors, all pointing straight up (that's
// "up" relative to the world).
VectorF x,y,z(0,0,1);

// Get column 1 from the eye matrix (that is the second, 
// or y column, because numbering starts at 0), and store
// it as y. 
eye.getColumn(1, &y);

// y is now a vector that points "forward" from the 
// vehicle's "eye" point of view.

// Cross y (forward) with z (still world up) to 
// produce x which is level to the world and perpendicular
// to y.
mCross(y, z, &x);

// Make length of x == 1, but we all knew that right?
x.normalize();

// Now we have a "forward" vector, a "world up" vector,
// and a "right" vector. Problem is, "forward" and 
// "world up" are not necessarily perpendicular because 
// "forward" may not be level to the world. In a rotational
// matrix such as this, all 3 vectors must be perpendicular.
// So we must fix this.

// Cross x (right) with y (forward) to get z (vehicle up).
mCross(x, y, &z);

// Normalize z, and we're done.
z.normalize();

Now, if you simply want to keep the camera from changing pitch with the vehicle, change the second cross to read:

VectorF x,y,z(0,0,1);
eye.getColumn(1, &y);
mCross(y, z, &x);
x.normalize();
[b]mCross(z, x, &y);[/b]
z.normalize();

Now the second cross is "world up" with "level right" which will produce a "world level forward". That will keep the camera's pitch level.

(Heh. First time I've hit a post lenght limit. Continued below...)
#4
04/28/2005 (11:23 am)
Level, but not actually pointing at the vehicle. It will be looking above the vehicle in much the same way that it does now when driving on level ground. That may be acceptable behavior. If however, you want the camera to actually point at the vehicle, try this:

void Vehicle::getCameraTransform(F32* pos,MatrixF* mat)
{
   // Returns camera to world space transform
   // Handles first person / third person camera position
   if (isServerObject() && mShapeInstance)
      mShapeInstance->animateNodeSubtrees(true);

   if (*pos == 0) {
      getRenderEyeTransform(mat);
      return;
   }

   // Get the shape's camera parameters.
   F32 min,max;
   MatrixF rot;
   Point3F offset;
   getCameraParameters(&min,&max,&offset,&rot);

   // Start with the current eye position
   MatrixF eye;
   getRenderEyeTransform(&eye);
   
   [b]/* Rotation code from here was moved down below */[/b]

   // Camera is positioned straight back along the eye's -Y axis.
   // A ray is cast to make sure the camera doesn't go through
   // anything solid.
   VectorF vp,vec;
   vp.x = vp.z = 0;
   vp.y = -(max - min) * *pos;
   eye.mulV(vp,&vec);

   // Use the camera node as the starting position if it exists.
   Point3F osp,sp;
   if (mDataBlock->cameraNode != -1) {
      mShapeInstance->mNodeTransforms[mDataBlock->cameraNode].getColumn(3,&osp);
      getRenderTransform().mulP(osp,&sp);
   }
   else
      eye.getColumn(3,&sp);

   // Make sure we don't hit ourself...
   disableCollision();
   if (isMounted())
      getObjectMount()->disableCollision();

   // Cast the ray into the container database to see if we're going
   // to hit anything.
   RayInfo collision;
   Point3F ep = sp + vec + offset + mCameraOffset;
   if (mContainer->castRay(sp, ep,
         ~(WaterObjectType | GameBaseObjectType | DefaultObjectType),
         &collision) == true) {

      // Shift the collision point back a little to try and
      // avoid clipping against the front camera plane.
      F32 t = collision.t - (-mDot(vec, collision.normal) / vec.len()) * 0.1;
      if (t > 0.0f)
         ep = sp + offset + mCameraOffset + (vec * t);
      else
         eye.getColumn(3,&ep);
   }
   [b]//mat->setColumn(3,ep);// This needs to be moved down too.[/b]

   // Re-enable our collision.
   if (isMounted())
      getObjectMount()->enableCollision();
   enableCollision();

   [b]/* Rotation code moved to here. Because we'll need the camera position for this. */[/b]
   
   // Build a transform that [b] points directly at the vehicle, [/b]
   // but where the Z axis is always up.
   if (mDataBlock->cameraRoll)
      mat->mul(eye,rot);
   else {
      MatrixF cam(1);
      VectorF x,y,z(0,0,1);
      [b]Point3F vp;
      getRenderTransform().getColumn(3, &vp);
      y = vp - ep;
      y.normalize();[/b]
      mCross(y, z, &x);
      x.normalize();
      mCross(x, y, &z);
      z.normalize();
      cam.setColumn(0,x);
      cam.setColumn(1,y);
      cam.setColumn(2,z);
      mat->mul(cam,rot);
   }

   [b]// Now set mat column 3 to the camera position. 
   mat->setColumn(3,ep);[/b]
}

Disclaimer: Although I'm pretty sure I got it right, I haven't actually tested any of the changes I'm suggesting. Test it at your own risk. ;)

Update: Fixed a bug (I hope). See Below, 4 posts down.
#5
04/28/2005 (12:01 pm)
Nice explanation, Scott!

I think one could save one normalize by changing it to this,
because the result of crossing two normalized vectors is still a normalized vector:

VectorF x,y,z(0,0,1);
eye.getColumn(1, &y);
y.normalize();
mCross(y, z, &x);
mCross(x, y, &z);

edit: this is wrong! see below.
#6
04/28/2005 (12:15 pm)
@Orion: Same thought crossed my mind while I was writting that, but then I remembered: Crossing two normalized and perpendicular vectors will result in a vector of length 1. But just crossing two normalized vectors is not guaranteed to result in a vector of length 1, because the lenght of the resulting vector is determined by the length of the two vectors crossed and the angle between them.

Since y and z may not be perpendicular, we do need to normalize x.
#7
04/28/2005 (12:21 pm)
Doh!
you're right.
#8
04/28/2005 (9:54 pm)
Hey guys thanks for your comments.

Scott, thanks for posting such a detailed explanation, it's a big help :^) I tried your first suggestion and it works like a charm (however as you mentioned the camera is not pointing directly at the vehicle.) I also tried your second bit of code but it seems as if the camera is now positioned below the terrain...maybe because it's now doing mat->mul after mat->setColumn (?)
#9
04/28/2005 (10:29 pm)
Whoops. Yeah, I think you're correct. I'm going to change that in the code above.

Edit: Fixed, I hope. Thank you for pointing that out.
#10
04/29/2005 (10:10 am)
Yep, that did the trick! Thanks again...I've been doing some reading on matrices and vectors, but 3D math is not my forte and your comments/code helped fill in the gaps.
#11
09/11/2006 (2:48 am)
Using this code im having a problem with camera lag, if the vehicle rotates quickly the camera also rotates but still lags so for a number of seconds all the player can see is the landscape in the direction the vehicle is pointing untill hte cam catches up with the vehicle. I want camera lag in my game but i also want the camera to always point at the vehicle rather than trying to match the vehicles rotation.

Any ideas how this can be accomplished?.

thanks
#12
10/01/2007 (1:47 pm)
Hi , I changed the code to what you suggested but the camera is still not pointing at the car. I don't really know what else to do since I'm new to do this, so if anyone could help I'd appreciate.

Thanks!
#13
10/01/2007 (2:57 pm)
I haven't looked at this in a long time, but I'll hazard a guess that you both have cameraRoll=true in your vehicle datablock.

I notice that I didn't address that condition in the code above, so with little thought and absolutely no testing I will offer this suggestion:

// Build a transform that  [b]points directly at the vehicle[/b], 
   if (mDataBlock->cameraRoll)
   {
      //mat->mul(eye,rot);
      [b]MatrixF cam(1);
      VectorF x,y,z(0,0,1);
      eye.getColumn(0, &x);
      Point3F vp;
      getRenderTransform().getColumn(3, &vp);
      y = vp - ep;
      y.normalize();
      mCross(x, y, &z);
      z.normalize();
      mCross(y, z, &x);
      cam.setColumn(0,x);
      cam.setColumn(1,y);
      cam.setColumn(2,z);
      mat->mul(cam,rot);[/b]
   }
   else // but where the Z axis is always up.
   {
      MatrixF cam(1);
      VectorF x,y,z(0,0,1);
      Point3F vp;
      getRenderTransform().getColumn(3, &vp);
      y = vp - ep;
      y.normalize();
      mCross(y, z, &x);
      x.normalize();
      mCross(x, y, &z);
      //z.normalize(); <-- I think we can loose this line too
      cam.setColumn(0,x);
      cam.setColumn(1,y);
      cam.setColumn(2,z);
      mat->mul(cam,rot);
   }

This would replace the similar conditional block towards the very end of getCameraTransform()
#14
10/02/2007 (10:41 am)
Thanks a lot Scott , it worked perfectly now !!!!!