Issues with 3rd person space ship flight algorithm

Started by
41 comments, last by Zakwayda 13 years ago
This probably won't answer all your questions, but I'll try to touch on a few points here.


Moving the ship instead of the world also requires me to move the camera to keep the view the same. The only way I know how to control a camera view is through gluLookAt() but so far all attempts have failed at getting where I want to be. I have a feeling I have to control the eye and Center value manually but I have no clue how to update it properly each frame. The only thing that comes to mind is using the eye as the position and provide a direction unit vector to the center. And since the camera is backed away from the ship, I also need to translate the position opposite of the center and then raise it a bit. This is extremely confusing.

A 'look at' function is just a convenience; there's no requirement that the view matrix has to be constructed that way. You certainly *can* use it (and it can be convenient to do so), but it's not a necessity.

The general idea behind a 'virtual' camera associated with an object in the scene is this: the object in question has a transform, and the inverse of that transform is the view transform.

A simple third-person camera (i.e. with no damping or other animated effects) can be implemented as follows:

1. Compute a transform that represents the camera object's rotation and position in the local space of the target object (e.g. 'behind, slightly above, and looking down').

2. Concatenate that transform with the target object's transform to yield the camera object's world transform.

3. Invert the transform to yield the view transform (which can then be uploaded directly to the graphics API).

Using a 'look at' function is basically a shortcut in that it takes care of some of these steps for you, but at the very least you'll most likely need to transform the local-space camera position by the target object's transform to yield the camera's world-space position (the 'eye' point).

I realized I can achieve a similar outcome rotating and translating the projection view matrix so this is what I am attempting right now without much success.[/quote]
I'm not sure what you mean by that, but typically you wouldn't apply rotations or translations to the projection matrix, and I wouldn't think about it in terms or rotating or translating the view matrix other. As mentioned earlier, just work with the world transforms of the objects in question, and then invert the camera object matrix to yield the view matrix.

Also, I tried to create a matrix that I keep updating frame after frame but it goes awfully wrong when I use rotation and translation. because the matrix contains both rotation and translations into it, when I add further translations and rotations it becomes Screwed up beyond all recognition. Should I keep two separate matrices and update them each frame and put them together only when comes the time to update the view?[/quote]
Yes, typically you'd store the orientation and position separately, or use special-purpose functions that apply rotations to the transform matrix without modifying the translation.

The whole thing can be a little confusing, and it does require some basic understanding of 3-d math and linear algebra. One thing I'll mention is that you're trying to solve two problems at once (moving the ship, and having a camera follow it). One thing you might try is setting up a fixed camera (e.g. using gluLookAt()), and focusing on getting the ship controls working correctly. Then, once that's working, you can move on to implementing a follow-cam.
Advertisement
You WANT translation and rotation in the same matrix, because that way if your ship was modelled with z being forward, translating it by (0,0,1) will always move it forward accoording to its current orientation. It's what makes everything easy instead of a huge mess where you have to constantly figure out how to rotate that ship around the right axis. "Up" or "y" will always be your ships local "up". If you touch any trigonometry except for whatever function creates the rotation matrix I'd blindly say "you're doing it wrong".

A simple example with shipTransform being the ships transformation matrix:

shipTransform = translationMatrix(0,0,1) * shipTransform; // ship moves along it's LOCAL z axis (or "forward")

shipTransform = shipTransform * translationMatrix(0,0,1); // ship moves along the GLOBAL z axis

You want your ship to start shooting and ask "which way do my bullets go" and the answer is
Vector3 bulletDirection(shipTransform[8], shipTransform[9], shipTransform[10]);

You ask "I need to know my ships position in space for collisions" and the answer is
Vector3 shipPosition(shipTransform[12], shipTransform[13], shipTransform[14]);

For convenience you can introduce aliases for the different matrix columns like right, up, forward, position.



For your camera you have the trivial task of taking the ships transformation, add a translation of [0, alittleup, abitback], invert the result and set it as modelview matrix.

cameraMatrix = translationMatrix(0, 2, -10) * shipTransformation;

Now before you say "but inverting a generic matrix is too much math".. you're dealing with a neat and simple kind of matrix, where inversion is just transposing the rotation part and adjusting the translation part (assuming a typical OpenGL matrix with 0-4 being "x", 5-8 "y", 9-12 "z" and 12-15 "origin").

invertedMatrix = Matrix44(
matrix[0], matrix[4], matrix[8], 0,
matrix[1], matrix[5], matrix[9], 0,
matrix[2], matrix[6], matrix[10], 0,
-(matrix[0]*matrix[12] + matrix[1]*matrix[13] + matrix[2]*matrix[14]),
-(matrix[4]*matrix[12] + matrix[5]*matrix[13] + matrix[6]*matrix[14]),
-(matrix[8]*matrix[12] + matrix[9]*matrix[13] + matrix[10]*matrix[14]), 1);

Ditch gluLookAt and use glLoadMatrix instead.

But don't try to use glMultMatrix from there. When rendering objects you want modelview = object.transformation * modelview, while glMultMatrix would give you modelview = modelview * obj.transformation. So keep track of your matrices, do the multiplication yourself and keep using glLoadMatrix (OpenGL is deprecating everything left and right, so you should eventually handle it yourself anyway).

Understanding transformation matrices and local/global coordinate systems is what I consider the most important part of 3D graphics. There is no magic involved, they are literally a coordinate system with three axes and an origin. It will also avoid weird questions like "how do I need to rotate my billboard to make it face the camera" instead of "ok, "z" must be camera - position, "y" and "origin" is fixed and "x" is just a cross product". Seriously, it would be like asking "I have x = PI, but I want x = 5.325, what calculation do I need to turn x into 5.325".

Also getting used to think in local coordinates instead of sticking to an "outside" perspective in world coordinates will allow you to laugh about silly ideas like "OpenGL applies transformations in reverse order". Seriously, who would deliberately want to write his code with "have to do it backwards" in mind? Object point of view: simple... world point of view: head ache.
f@dzhttp://festini.device-zero.de
I scrapped all the code when I restarted last night.

So far I got the ship at the origin of the scene with the camera behind, slightly above and looking down on the ship using gluLookAt. As I see gluLookAt is generally not the way to go and seems to be deprecated and not recommended for quite a few years I'll definitely need to look for a different way of working.

I don't really understand what inverting the matrix does in terms of actual transformation. If I need to do a transformation to the camera and the invert that transformation to apply to the object, is it the same thing as applying opposite operations in the opposite order meaning. If I translate something by (10,10,10) and rotate the Y axis by 45 degrees then inverting the matrix would yield a transformation of -45 degrees rotation on the Y axis and then a translation of (-10,-10,-10) ? If that's not what it does then I do not understand what results it's giving me. I would like to know though.

jyk, I understand the first numbered point you made but I'm not sure I understand the 2 others but I do have a feeling it comes to something similar as what Trienco is saying right?

Trienco, could you elaborate on the process you're going through regarding the3 lines with the additions and multiplications ? I looked up how OpenGL works with matrices today and figured out its use it transposed as opposed to the way we would use it in math. From there I believe matrix[12],matrix[13],matrix[14] the x,y,z obtained from the transformation and that the others contain rotation information.

I'm not using any trigo anywhere as I learned quickly that things get very messy. I'm trying to stick to matrices and learn how to work from there. I have to say it's quite new to me but I'm slowly getting around to it. I can get my way around understanding math concepts but I've never been good with understanding how to apply certain things and why. I did most of my linear algebra classes a few years ago but never put any of it into actual practice until recently so I basically have to relearn it almost from scratch so I may not understand some concept you might assume basic and trivial so I guess it might be necessary to dumb it down a bit so I can understand better.

Also, regarding jyk saying I'm trying to solve two problems at once. I didn't see it that way but that's an interesting way to put it. I'll see how I can make it work that way. In my head, I thought it was simple to back the camera off a bit from the origin looking down at the ship at the origin and do the rotation transformations both affecting the ship and the camera at the same time in order to make it follow the ship wherever it was going but maybe the approach was wrong.

Thanks again for your help !
I didn't see your updated post Trienco. I'm not sure I understand Local vs global axis.I see that the only difference is in the order of the operations.

I also agree that the backwards operation thinking is giving me serious headaches as well. I don't find it user friendly at all.

I don't really understand what inverting the matrix does in terms of actual transformation. If I need to do a transformation to the camera and the invert that transformation to apply to the object, is it the same thing as applying opposite operations in the opposite order meaning. If I translate something by (10,10,10) and rotate the Y axis by 45 degrees then inverting the matrix would yield a transformation of -45 degrees rotation on the Y axis and then a translation of (-10,-10,-10) ? If that's not what it does then I do not understand what results it's giving me. I would like to know though.

Yes, in this context inverting a matrix can be thought of as making the transform the 'opposite' of what it was originally. A typical 'model' transform consists of a rotation followed by a translation. To use your example, a model transform might consist of a rotation about the y axis of 45 degrees, followed by a translation of (10, 10, 10). The inverted transform would then consist of a translation of (-10, -10, -10) followed by a rotation about the y axis of -45 degrees (that as, each transformation is converted to its opposite, and the converted transforms are applied in the opposite order).

There are various ways an inverted transform can be realized. The most general way is to use a general matrix inversion function; this is more expensive than the alternatives, but that's unlikely to matter if you're only doing it once per frame. For 'rigid body' transforms, you can also use the shortcut Trienco mentioned. Or, you can build the inverse manually by combining the individual inverted transforms in the opposite order. As for the 'look at' function, it actually rolls two operations into a single function: first, it builds a transform that positions the object and orients it towards a target, and then it inverts that transform.

jyk, I understand the first numbered point you made but I'm not sure I understand the 2 others but I do have a feeling it comes to something similar as what Trienco is saying right?[/quote]
I'm not sure if Trienco and I are talking about the same thing (I'd have to read over the thread again carefully in order to determine that.)

I looked up how OpenGL works with matrices today and figured out its use it transposed as opposed to the way we would use it in math.[/quote]
It's not transposed.

From there I believe matrix[12],matrix[13],matrix[14] the x,y,z obtained from the transformation and that the others contain rotation information.[/quote]
That's generally true. However, the whole 1-d indexing thing is an unfortunate side-effect of how matrices are often represented in memory (that is, as a 1-d array). The 1-d indexing really has no mathematical significance, and (IMO) tends to add to the already existing confusion regarding notational conventions and matrix layout. As such, I generally recommend writing your matrix code so that the 1-d indexing (if any) is hidden behind an interface that allows you to write the actual math code using 2-d indexing.

I also agree that the backwards operation thinking is giving me serious headaches as well. I don't find it user friendly at all.


The first time somebody taught that concept during a lecture I almost started laughing about the masochism involved in that way of looking at it and had to surpress a "uhm, no, it's simply applying them in LOCAL coordinates". Stop looking at the world from the outside and dive into it. Things make so much more sense from in here.

It's easier to play with it and look at the numbers in a debugger, but I'll try.

As a default, you have the identity matrix. I use OpenGLs memory layout a lot, since trying to use mathematical notation on here is annoying. So "each row is a vector" and would be a column in mathematical notation. Also, think about that matrix as 4 vectors and not as a whole.

1,0,0,0 //this is x or "right" and since we didnt do anything it's the same as the worlds "right"
0,1,0,0 //this is y... and so on
0,0,1,0 //no comment
0,0,0,1 //the origin is zero

those confusing 4th coordinates like to be called w. Rule of thumb, if a vector has w=0 it's a direction (for example a normal vector) and if w=1 it's a position. So you notice the three axes are directions and the origin is a position.

What happens if you apply a rotation by say 90° to the left? right turns to forward, forward turns to left and the rest is unaffected

0,0,1,0
0,1,0,0
-1,0,0,0
0,0,0,1

Since this is the first thing we applied to the identity, this also happens to be the actual rotation matrix you can apply to rotate stuff 90° to the left.

Now our local coordinates have changed. You could go and visualize it by drawing three glLines from the last vector to each of the first 3. Since your "local forward" is no the "worlds left", if you apply a translation along [0,0,1] you get

0,0,1,0 //orientation isnt affected by translating
0,1,0,0
-1,0,0,0
-1,0,0,1

The last vector calculated as 0*(0,0,1,0) + 0*(0,1,0,0) + 1*(-1,0,0,0) + 1*(-1,0,0,1)
If the last part is confusing. The translation matrix has the movement in the position part, which has w=1, so strictly speaking you translate by [0,0,1,1].

Now if you multiply the other way around, you first move to

1,0,0,0
0,1,0,0
0,0,1,0
0,0,1,1

Surprise, it's also the translation matrix that moves you along 0,0,1

then you rotate, resulting in

0,0,1,0
0,1,0,0
-1,0,0,0
0,0,1,1 //rotation doesn't affect position

Whatever transformation you apply first is happening while you still got the identity matrix and hence can be considered happening in global coordinates. But essentially every transformation is always based on your current local coordinates (ie. the current matrix... which is on the right in a multiplication)

For more complex scenarios I'd avoid the headache of going through the numbers, it always works the same anyway. How you store matrices is up to you, a simple way that should be compatible with OpenGLs memory layout would be matrix[vector][coordinate], so matrix[3][1] would be the y coordinate of the position vector and you can directly call glLoad and glGet on it. Behind the scenes the compiler turns it into a 1d array and handles the indexing.
f@dzhttp://festini.device-zero.de
That's very interesting!

Around here it's pretty much morning so I'll play with it after a few hours of sleep. I'll re-read the posts and try to make sense out of them and keep you guys updated during the day.
I've been experimenting a bit with the matrix and displaying the axis on screen and something interesting is going on but I would like to know if it's me who did something wrong or if that's the way it is.

I have my ship rotate on itself around the Y axis and I draw the local axes around it. They rotate twice as fast as the ship.

I also replaced the ship by a Cube and the same thing happens.

I'm simply getting the transformed Model View Matrix to see what's going on.

This is the code I'm using to draw the lines along with a cube at the origin. I amplify the vector magnitude so I can see them properly. The rotation call is the same for both, the lines and the Cube.



glMatrixMode(GL_MODELVIEW);
glLoadIdentity();

glRotatef(Player.GetHeadingAngle(),0.0,1.0,0.0);

DrawMatrixReferenceLines();

glColor3f(1.0,1.0,1.0);

glutWireCube(5);

void DrawMatrixReferenceLines()
{
float* ModelMatrix = new float[16];

glGetFloatv(GL_MODELVIEW_MATRIX,ModelMatrix);

// Draw Origin Axes for reference
glBegin(GL_LINES);
// X axis
glColor3f(1, 0, 0);
glVertex3f(ModelMatrix[12],ModelMatrix[13],ModelMatrix[14]);
glVertex3f(ModelMatrix[0]*500,ModelMatrix[1]*500,ModelMatrix[2]*500);
// Y axis
glColor3f(0, 1, 0);
glVertex3f(ModelMatrix[12],ModelMatrix[13],ModelMatrix[14]);
glVertex3f(ModelMatrix[4]*500,ModelMatrix[5]*500,ModelMatrix[6]*500);
// Z axis
glColor3f(0, 0, 1);
glVertex3f(ModelMatrix[12],ModelMatrix[13],ModelMatrix[14]);
glVertex3f(ModelMatrix[8]*500,ModelMatrix[9]*500,ModelMatrix[10]*500);
glEnd();

delete [] ModelMatrix;
}
I'm not sure if this is what's causing the problem you're seeing, but the second vertex for each of the line segments in DrawMatrixReferenceLines() should be relative to the object's position, rather than absolute. That is, you should be summing what you have now with elements 12, 13, and 14.

Also, you don't need to allocate 'ModelMatrix' dynamically; it can just be a local array on the stack.

I'm not sure if this is what's causing the problem you're seeing, but the second vertex for each of the line segments in DrawMatrixReferenceLines() should be relative to the object's position, rather than absolute. That is, you should be summing what you have now with elements 12, 13, and 14.

Also, you don't need to allocate 'ModelMatrix' dynamically; it can just be a local array on the stack.


That would make sense if the position was different than the origin. Right now, it only makes sense if the object is at the origin but I added the rest of the coordinates to make it compatible for later when I actually move the object.

As for the dynamic array. I'm aware I could just do a local array, I just put the first thing that worked but it's prone to memory leaks so I'll fix those up when I get my base running.

Even with the summation, the axes still rotate twice as fast as the object. The Player.GetHeadingAngle() is basically the Yaw rotation that I change through the keyboard arrows. it currently has a 45 degree limit on each side so when I hit the limit, the axes are rotated by 90 degrees.

Could it be something with the model view matrix I am loading or maybe something else ? I'll keep playing around to see in the mean time.

This topic is closed to new replies.

Advertisement