Jump to content
  • Advertisement
Sign in to follow this  
CDProp

Problems implementing a mouse-look system (euler angles and matrices)

This topic is 3723 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hello. I'm having trouble implementing a mouse-look system for my game. It's not an FPS. It's just a rendering demo that I need to be able to move around in. I suspect that my problem has something to do with matrix multiplication order issues. In general, it seems to make sense that you should perform the rotation first, and then the translation. That way, you're on the origin during the rotation stage, and then you get translated out to the location where the camera should be. If I did it the other way, then I would first translate out to where the camera should be, and then rotate, but I'd rotate around the origin, and thus always face the origin, like some sort of spherical coordinate system. Anyway, as far as rotation is concerned, I heard that I should rotate along the x-axis first (pitch) and then the y-axis (yaw). The z-axis isn't used because I don't want any roll rotation. So here's how I do it in code (DirectX-specific, sorry):
D3DXMATRIX rotXMat, rotYMat, transMat;

D3DXMatrixRotationX(&rotXMat, m_Euler.x);
D3DXMatrixRotationY(&rotYMat, m_Euler.y);
D3DXMatrixTranslation(&transMat, m_Pos.x, m_Pos.y, m_Pos.z);

D3DXMatrixMultiply(&m_Orientation, &rotXMat, &rotYMat);
D3DXMatrixMultiply(&m_Orientation, &m_Orientation, &transMat);

For those who aren't familiar with DirectX, the first line just declares some 4x4 matrices. The next three lines of code create an x-rotation matrix, a y-rotation matrix, and a translation matrix, respectively, using the previously-declared matrices as outputs. The next two lines of code multiply the matrices. According to the DirectX documentation, the first parameter of this function is the output, the second is the left-hand operand, and the third is the right-hand operand. So, those two lines of code should be equivalent to the following, if I have it all straight: m_Orientation = rotXMat * rotYMat m_Orientation = m_Orientation * transMat As far as I can tell, this follows the plan. I'm concatenating the x- and y-rotation matrices, with the x as the first operand, and then I'm taking the resulting concatenated matrix and multiplying it by the translation matrix. However, the result is totally wrong. It behaves somewhat as though I performed the translation before the rotation. In my demo, I use the mouse wheel to translate back and forth along the cameras "at" vector. I can do this fine, but once I rotate, it's as if I'm translating around the origin at a fixed distance, as if I were using spherical coordinates. And then, of course, if I try to use the mouse wheel from that rotated position, the movement is all wrong. If I switch the order of the second multiplication, it seems to fix that particular problem, but the rotation is still all screwy. So, I messed with the matrix multiplication order and this SEEMS to work perfectly:
D3DXMATRIX rotXMat, rotYMat, transMat;

D3DXMatrixRotationX(&rotXMat, m_Euler.x);
D3DXMatrixRotationY(&rotYMat, m_Euler.y);
D3DXMatrixTranslation(&transMat, m_Pos.x, m_Pos.y, m_Pos.z);

D3DXMatrixMultiply(&m_Orientation, &transMat, &rotYMat);
D3DXMatrixMultiply(&m_Orientation, &m_Orientation, &rotXMat);

This FEELS like it works perfectly, but something is odd. I print out the resulting matrix for debugging purposes. When I'm doing the mouse look, I'm not changing the m_Pos vector at all. So, the translation matrix should always be the same. Now, if I'm not mistaken, the 4th row of the orientation matrix is the position. Now, if I'm doing the mouse look, and I'm not changing the position at all (and it certainly SEEMS to be stable while I use it), it seems to me that this 4th row should remain unchanged. However, it's not. What am I misunderstanding. Some recommended to me that I just concatenate the two rotation matrices and then set the position directly. This idea made sense to me, but curiously enough, it had the same exact effect as my first attempt: whenever I try to rotate the camera, I end up orbiting around the origin as though I was doing the translation first.

Share this post


Link to post
Share on other sites
Advertisement
Well, first of, as you're using D3D and directly juggle with the matrices, there is no rule that a transformation happens in local or world coordinates, because that's effectively determined by the order of your parameters when you multiply (A*B is not the same as B*A).

In OpenGL (with predefined rotate/translate) functions (and probably some books) transformations are always applied in a way that they use the local system. So your first statement would be wrong. If you first translate and then rotate, you rotate around the NEW origin (your new position) and NOT the world origin.

My guess after seeing the code is that you simply have a wrong order, not of operations, but of operands.

D3DXMatrixMultiply(&m_Orientation, &rotXMat, &rotYMat);
D3DXMatrixMultiply(&m_Orientation, &m_Orientation, &transMat);

If this looks like you rotate before you translate (and I assume you're thinking in world coords here), then why not simply change the second line to

D3DXMatrixMultiply(&m_Orientation, &transMat, &m_Orientation);

and reverse the order of your transformations?

Share this post


Link to post
Share on other sites
Thanks. I did try that, and it did fix the problem of "orbiting the world's origin", but the rotation was still weird. I was rotating in the correct place, but there was a bit of unwanted "roll" rotation that crept in.

If I switch the order of the operands on the first multiplication as well as the second:


D3DXMatrixMultiply(&m_Orientation, &rotYMat, &rotXMat);
D3DXMatrixMultiply(&m_Orientation, &transMat, &m_Orientation);



This seems to work just as well as the order I posted above (in the second set of source tags) but it suffers from the same problem: the 4th row of the matrix doesn't seem to properly represent the position. It changes when I rotate the camera, even if I don't change the actual position of the camera. This makes me think that, even though I appear to be doing the right thing, I'm actually confusing something somewhere.

Share this post


Link to post
Share on other sites
why not use D3DXMatrixRotationYawPitchRoll() which helps you to remove the need of having rotXMat, rotYMat.

D3DXMatrixTranslation() will translate along the world x/y/z axis. For example your camera is looking in a direction of (1.0, 1.0, 1.0). Doing a x-translation with D3DXMatrixTranslation() will still move your camera in the direction of (1.0, 0.0, 0.0) and not (1.0, 1.0, 1.0). I am not sure if that is what you would want.

In my humble opinion, I think operating on the camera local axis is much easier.


class Camera
{
D3DXVECTOR3 Origin; //Camera position
D3DXVECTOR3 xAxis; //Camera x-axis
D3DXVECTOR3 yAxis; //Camera y-axis
D3DXVECTOR3 zAxis; //Camera z-axis

public:
void Translate(float x, float y, float z)
{
Origin = Origin + (xAxis * x) + (yAxis * y) + (zAxis * z);
}

void RotateLocalAxis(float Yaw, float Pitch, float Roll)
{
D3DXMATRIX Rotation, Temp;

D3DXMatrixRotationAxis(&Rotation, &xAxis, Pitch);
D3DXMatrixRotationAxis(&Temp, &yAxis, Yaw);
D3DXMatrixMultiply(&Rotation, &Rotation, &Temp);
D3DXMatrixRotationAxis(&Temp, &zAxis, Roll);
D3DXMatrixMultiply(&Rotation, &Rotation, &Temp);

D3DXVec3TransformNormal(&xAxis, &xAxis, &Rotation);
D3DXVec3TransformNormal(&yAxis, &yAxis, &Rotation);
D3DXVec3TransformNormal(&zAxis, &zAxis, &Rotation);
}

//World Axis are (1.0, 0.0, 0.0), (0.0, 1.0, 0.0), (0.0, 0.0, 1.0)
void RotateWorldAxis(float Yaw, float Pitch, float Roll)
{
D3DXMATRIX Rotation;

D3DXMatrixRotationYawPitchRoll(&Rotation, Yaw, Pitch, Roll);
D3DXVec3TransformNormal(&xAxis, &xAxis, &Rotation);
D3DXVec3TransformNormal(&yAxis, &yAxis, &Rotation);
D3DXVec3TransformNormal(&zAxis, &zAxis, &Rotation);
}

void CalculateViewMatrix(D3DXMATRIX *pViewMatrix)
{
D3DXMatrixLookAtLH(pViewMatrix, &Origin, &(Origin + zAxis), &yAxis);
}
};

Share this post


Link to post
Share on other sites
I guess my question is more of a math-related question. I have a system in place that works the way I want it to; I just don't understand why the 4th row of my matrix changes when I don't change the position at all. So, I could implement your suggestion, and it might work, but then I'd still be left curious as to what I was doing wrong before. So maybe this belongs more in the Math forum?

Also, I don't understand why, when I concatenate the x-rot and y-rot matrices, and then set the position directly by setting the elements in the 4th row, I get the weird "spherical" behavior. I mean, it's not as if I did the translation "first", whatever that means, but it's acting as though I have.

To answer your question about translation, I don't actually just move along the {1, 0, 0} in world space. I actually store my position as a separate vector3 (it's called m_Pos, and you can see its individual elements being passed into the D3DXMatrixTranslate function in my source above). When I want to move along the camera's look-at vector, I do this:

m_Pos.x += (m_Orientation._31 * fAmount);
m_Pos.y += (m_Orientation._32 * fAmount);
m_Pos.z += (m_Orientation._33 * fAmount);


Where fAmount is the amount that I want to move. So, m_Pos gets updated correctly before I call the D3DX function to create the translation matrix.

(btw, the above code might be slightly off because I'm not at home and so I'm doing this from memory, but it gives the general idea).

Share this post


Link to post
Share on other sites
The weird spherical behaviour that you are speaking about. Does it mean that when you move your mouse to rotate your camera, instead of the camera rotatin about the camera's orgin, it rotates about the (0.0, 0.0, 0.0) point?

I think, doing it your way must require that your camera always be at the origin first, meaning if your camera is at some other location: L1, you need to

translate camera back to the orgin,
apply rotation,
translate camera to L1,
apply new translation to new location.

Share this post


Link to post
Share on other sites
A few things:

In your first reply I just noticed that now you mixed up the first multplication:

Quote:

D3DXMatrixMultiply(&m_Orientation, &rotYMat, &rotXMat);
D3DXMatrixMultiply(&m_Orientation, &transMat, &m_Orientation);


Now you FIRST rotate up/down, so obiously your second rotation (left/right) will be all "wrong", because it's rotating around the cameras up and NOT the world up. Eulers have the annoying tendency to make people forget/ignore that you can't just add rotations in any random order and expect the same result, because every applied rotation changes the coordinate system for the following rotations. Hence YawPitchRoll uses exactly that order, because it's generally the one most useful (plus, assuming very small angles between two frames the difference is very small and you couldn't tell the difference).

If you use those dreaded Eulers, always rotate around Y first, THEN around X:
D3DXMatrixMultiply(&m_Orientation, &rotXMat, &rotYMat);

or do as littlekid suggested and use YawPitchRoll, because it already has the order you want.

Then apply the translation as you did:
D3DXMatrixMultiply(&m_Orientation, &transMat, &m_Orientation);

Quote:
D3DXMatrixTranslation() will translate along the world x/y/z axis.


No it doesn't. a) it doesn't translate anything, it just creates a translation matrix and b) even this matrix doesn't have any reference coordinate system by itself (which indirectly means that setting this directly as transformation does use the world axes, because it's based on the identity matrix).

ONLY how you multiply determines that. If you do it from the left, it's done "after" the other stuff and uses the "local" axes. From the right it's done "first" and still uses the identity (ie. world axes). Meaning that a lot of littlekids code is just reinventing the wheel and overcomplicates things by "emulating" a simple matrix mult with all kinds of operations. Especially since he is storing four vectors that are nothing but the four columns of a matrix (x,y,z,origin), so he has to transform them all seperately.

Then: how do you set your view matrix? You realize that you need to invert the matrix you get from what you do? (that, or do everything the other way and in reverse order).

Last and most of all: you're already using D3DX, anyway. You're already storing a variable to keep track of the position. Why don't you even bother to touch those dreaded Euler angles and make your life harder than necessary? Just store your objects transformation matrix already and you won't have to rewrite everything when you need more than "rotate around up first, then look up/down, because Eulers don't allow much more".


void CameraD3D::setView() {
D3DXMATRIX viewMat;
D3DXMatrixInverse(&viewMat, NULL, &m_transform);
(*device)->SetTransform(D3DTS_VIEW, &viewMat);
}

//You could adjust the last vector in the matrix manually, but for
//demonstration we use matrix multiplication
void CameraD3D::move(float x, float y, float z, bool global) {
D3DMATRIX transMat;
D3DXMatrixTranslation(&transMat, x, y, z);
if (global)
D3DXMatrixMultiply(&m_ransform, &m_transform, &transMat);
else
D3DXMatrixMultiply(&m_ransform, &transMat&, m_transform);
}

void CameraD3D::rotate(float angle, float x, float y, float z, bool global) {
D3DXMATRIX rotMat;
D3DXVECTOR3 axis(x, y, z);
D3DXMatrixRotationAxis(&rotMat, &axis, angle);

if (global) {
D3DXVECTOR3 tmp = m_transform.m[3];
Transform(3,0)=m_transform(3,1)=m_transform(3,2)=0;

D3DXMatrixMultiply(&m_transform, &m_transform, &rotMat);

m_transform(3,0)=tmp.x;
m_transform(3,1)=tmp.y;
m_transform(3,2)=tmp.z;
}
else D3DXMatrixMultiply(&m_transform, &rotMat, &m_transform);
}




Just use setView every frame, throw away m_Pos and stop "collecting" pure angles.

The extra work for rotation around world axes ("global") avoids rotating around the origin, which is exactly what you get if you rotate by multiplying from the right, because now your previously applied translation uses the NEW axes, hence you end up in a different place (and orientation).

Depending on your point of view, just remember that stuff happens "from right to left" and always uses the local/object coordinate system.

Share this post


Link to post
Share on other sites
Trienco, before I reply, I just want to clarify one thing. Out of the two operands that are passed into the multiply function, is the second one the one that is "done first."

I know that question doesn't make sense at first, because a multiply is a single operation on two operands, but a lot of times people will say "rotate on this axis first, THEN that axis." In your case, you are saying that by doing this multiply:

D3DXMatrixMultiply(&m_Orientation, &rotYMat, &rotXMat);

...I am rotating up/down first (rotating on the X axis) and left/right second (which is on the Y axis).

This, I think, might be the crux of my misunderstanding, because I hear people say "do this rotation first, then that rotation" and I know how to concatenate two rotation matrices (that is, by multiplying them) but I don't know which operand is the "first" and which is the "second".

I assumed that the L-operand is the "first" one, but that seems to conflict with what you are telling me.

Share this post


Link to post
Share on other sites
D3D seems to stick with the way it's in math, so yes, the left operand is applied to the second, meaning it's done "after" it. Of course this is just a matter of perspective. If you prefer thinking in world coordinates it's the other way round. Personally I find it less confusing to always think in terms of object space.

Share this post


Link to post
Share on other sites
OK, I'm definitely going to have to ponder this for a bit. In the meantime, thanks for all of your help.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!