Skip to main content
gamedev.net
🔒 Locked 🎮 Unity

Quaternion Camera Problems

Started by 31337 7 replies 1.5k views
Original Post
31337
31337
I'm trying to make a quaternion camera (based off of the simple quaternion camera article) for a multiplayer space ship game I'm programming. The problem is that it works perfectly when the player changes his viewing slowly, but when he moves it semi-rapidly he loses control of one axis, and then control of another. Its very hard to explain, but everything goes to hell. I've put my code below, and if anybody has any ideas please let me know. This is sort of frustrating. I am tempted to normalize the quaternions, but I doubt it would make a difference. Any help is greatly appreciated, thanks in advance -
[SOURCE]
void CGameScene::RotateCamera(float fAngle, D3DXVECTOR3 vAxis) {
	if(fAngle>0.15f)
		fAngle = 0.15f;
	else if(fAngle<-0.15f)
		fAngle = -0.15f;
	D3DXQUATERNION qTemp, qView, qUp, qResult, qConj;
	qTemp.x = vAxis.x*(float)sin(fAngle/2.0f);
	qTemp.y = vAxis.y*(float)sin(fAngle/2.0f);
	qTemp.z = vAxis.z*(float)sin(fAngle/2.0f);
	qTemp.w = (float)cos(fAngle/2.0f);

	D3DXQuaternionConjugate(&qConj, &qTemp);

	qView.x = m_pvMyShip[VIEWING].x;
	qView.y = m_pvMyShip[VIEWING].y;
	qView.z = m_pvMyShip[VIEWING].z;
	qView.w = 0.0f;

	qUp.x	= m_pvMyShip[UP].x;
	qUp.y	= m_pvMyShip[UP].y;
	qUp.z	= m_pvMyShip[UP].z;
	qUp.w	= 0.0f;

	D3DXQuaternionMultiply(&qResult, &qTemp, &qView);
	D3DXQuaternionMultiply(&qResult, &qResult, &qConj);

	m_pvMyShip[VIEWING].x = qResult.x;
	m_pvMyShip[VIEWING].y = qResult.y;
	m_pvMyShip[VIEWING].z = qResult.z;

	D3DXQuaternionMultiply(&qResult, &qTemp, &qUp);
	D3DXQuaternionMultiply(&qResult, &qResult, &qConj);

	m_pvMyShip[UP].x = qResult.x;
	m_pvMyShip[UP].y = qResult.y;
	m_pvMyShip[UP].z = qResult.z;
}

void CGameScene::ManageViewing() {
	D3DXVECTOR3 vCross, vViewing;
	vViewing = m_pvMyShip[VIEWING] - m_pvMyShip[POSITION];
	D3DXVec3Cross(&vCross, &vViewing, &m_pvMyShip[UP]);
	D3DXVec3Normalize(&vCross, &vCross);
	D3DXVec3Normalize(&vViewing, &vViewing);
	m_pvMyShip[VIEWING] = vViewing;
	RotateCamera(((float)CBase::m_iMouseChangeY)/100.0f, vCross);
	RotateCamera(-((float)CBase::m_iMouseChangeX)/100.0f, m_pvMyShip[UP]);
	CKeyboard *pKey = CWindow::GetKeyboard();
	D3DXVECTOR3 vUnit;
	D3DXVec3Normalize(&vUnit, &m_pvMyShip[VIEWING]);
	if(CBase::m_bAcceleration==false) {
		DWORD dCurrent = GetTickCount();
		if(pKey->CheckKey('W')) {
			m_pvMyShip[POSITION]+=(vUnit*10.0f)*(((float)dCurrent-(float)m_dLastTime)/1000.0f);
		}
		else if(pKey->CheckKey('S')) {
			m_pvMyShip[POSITION]-=(vUnit*1.0f)*(((float)dCurrent-(float)m_dLastTime)/1000.0f);
		}
	}
	m_pvMyShip[VIEWING] = m_pvMyShip[POSITION] + vUnit;
}
[/SOURCE]
OpenGL Revolutions http://students.hightechhigh.org/~jjensen/
Zakwayda
Zakwayda
I don't know enough (i.e. anything) about D3D functions to know whether you're using them right, but there are some things that do look kind of suspect to me. I'd have to look at the code more carefully though to say for sure. So instead let me just make a couple of suggestions about a 'standard' (and effective) way of doing it.

Maintain a quaternion that represents your ship's orientation. To change your ship's orientation, create a quat (as you are doing) that represents the axis you wish to rotate around and the angle by which you wish to rotate.

The result of multiplying this quat with your orientation quat will be your new orientation. I don't know what order D3D does quat rotations in, so you'll have to make sure you get that right.

A quaternion can be converted into a matrix (I assume D3D can do that for you). The first three rows (or columns) of that matrix are the orthnormal vectors corresponding to your orientation, i.e. the 'side', 'up' and 'forward' vectors. You are attempting to maintain these vectors independently and keep them up to date via quaternions. Rather, you should simply extract them from the matrix which is derived from the quaternion.

In turn, it is these axes that you will use to rotate around - 'side' for pitch, 'up' for yaw, and 'forward' for roll.

I'll stop there, but ask if you have any further questions.

P.S. I realize changing to this approach would require rewriting most of your code, but you'll be happier in the long run.
31337
31337
I'll give your plan a try jyk and post if I have any questions, will it handle the same as my current camera? When the person goes upside down will the camera's left/right movement remain not inverted? Thanks for your help you rock.
OpenGL Revolutions http://students.hightechhigh.org/~jjensen/
31337
31337
Alright, after reading through your post a few more times, I have questions.

1) Does the order in which I rotate matter? I.E., if I do yaw first then pitch is it different than pitch vs yaw?

2) What should my quaternion be initialized to that represents my orientation? I want my original viewing to be (0, 0, 1) and the up vector to be (0, 1, 0).
OpenGL Revolutions http://students.hightechhigh.org/~jjensen/
Zakwayda
Zakwayda
Hi,

Quote:
1) Does the order in which I rotate matter? I.E., if I do yaw first then pitch is it different than pitch vs yaw?


To honest I'm not sure - I'd have to think about it a little. But it's not like with Euler angles where different orders will give you completely different results - I think the only difference would be that you might 'favor' one axis or another. In any case I'd go yaw-pitch-roll.

Quote:
2) What should my quaternion be initialized to that represents my orientation? I want my original viewing to be (0, 0, 1) and the up vector to be (0, 1, 0).


If you convert a unit quaternion to a matrix, those should be your z and y axes, respectively. So in your case your initial orientation should be an identity quaternion.

Sorry I don't have a better answer about the rotation order. I'll think about that. (Or maybe someone else will answer that question...)
31337
31337
Groovy, I'll start coding the camera with the method you suggested.
OpenGL Revolutions http://students.hightechhigh.org/~jjensen/
31337
31337
I wrote this chunk of code, and it "works" for a little while but once I change the viewing to looking up steeply it spins out of control and dies. I am lost. Any ideas?

[SOURCE]	D3DXVECTOR3 vLook = m_pvMyShip[VIEWING] - m_pvMyShip[POSITION];	D3DXVECTOR3 vLeft;	D3DXVec3Cross(&vLeft, &vLook, &m_pvMyShip[UP]);	D3DXMATRIX mOrientation = D3DXMATRIX(vLeft.x, vLeft.y, vLeft.z, 0.0,										 vLook.x, vLook.y, vLook.z, 0.0,										 m_pvMyShip[UP].x, m_pvMyShip[UP].y, m_pvMyShip[UP].z, 0.0,										 0.0, 0.0, 0.0, 1.0);	D3DXQUATERNION qRot, qYaw, qPitch;	D3DXQuaternionRotationMatrix(&qRot, &mOrientation);	D3DXQuaternionRotationAxis(&qYaw, &m_pvMyShip[UP], ((float)CBase::m_iMouseChangeX)/100.0f);	qRot*=qYaw;	D3DXMatrixRotationQuaternion(&mOrientation, &qRot);	D3DXVECTOR3 vNewLeft = D3DXVECTOR3(mOrientation._11, mOrientation._12, mOrientation._13);	D3DXQuaternionRotationAxis(&qPitch, &vNewLeft, ((float)CBase::m_iMouseChangeY)/100.0f);	qRot*=qPitch;	D3DXMatrixRotationQuaternion(&mOrientation, &qRot);	m_pvMyShip[VIEWING].x = m_pvMyShip[POSITION].x + mOrientation._21;	m_pvMyShip[VIEWING].y = m_pvMyShip[POSITION].y + mOrientation._22;	m_pvMyShip[VIEWING].z = m_pvMyShip[POSITION].z + mOrientation._23;	m_pvMyShip[UP].x = mOrientation._31;	m_pvMyShip[UP].y = mOrientation._32;	m_pvMyShip[UP].z = mOrientation._33;[/SOURCE]
OpenGL Revolutions http://students.hightechhigh.org/~jjensen/
hplus0603
hplus0603
You need to re-normalize the quaternion after a number of changes have been made to it. Normalize it at the end of your RotateCamera function, for example.

Regarding initialization, that depends entirely on what your reference is. Start out with a quaternion that's 0,0,0,1 (which means "identity") -- then, to get the actual "front" and "up" vectors, plug in the vectors as you want them to be in identity (the ones you supplied), and run them through the quaternion transform -- the end result is your front and up as represented by that quaternion.
enum Bool { True, False, FileNotFound };
Zakwayda
Zakwayda
I can't quite follow what your code is doing (I don't know how the D3D functions work), but it doesn't look quite right to me - you're still sort of approaching the problem 'backwards'.

Take a look at this code - it's off the top of my head, so may not be exactly right.

void Object::Rotate(float yaw, float pitch, float roll){    Matrix3 m;    QuaternionToMatrix(m_q, m);    Vector3 xaxis(m[0][0], m[0][1], m[0][2]);    Vector3 yaxis(m[1][0], m[1][1], m[1][2]);    Vector3 zaxis(m[2][0], m[2][1], m[2][2]);        Quaternion rot = Quaternion(yaw, yaxis) * Quaternion(pitch, xaxis) * Quaternion(roll, zaxis);    m_q *= rot;    m_q.Normalize();}


The idea is that you represent your orientation *only* with a quaternion. You can also keep a matrix and/or set of axes around, but they should be derived from the quaternion, not vice versa.

The side, up and forward axes come from the matrix, which comes from the quaternion - again, not vice versa. Everything comes from the quaternion.

You use the axes as the axes of rotation for yaw, pitch and roll, and construct quaternions from that information which you multiply by your orientation quaternion to get your new orientation.

As the other poster pointed out, you should renormalize your quat occasionally, although I don't think that's what's causing your problem.

A couple of caveats. Different libraries do quat multiplication in different orders, so you may have to flip the code around. Also, multiplying the yaw, pitch and roll together like that is not exactly accurate, I don't think. To be more accurate, you could apply yaw, re-extract the x axis, apply pitch, re-extract the z axis, and apply roll.

Also, when you construct your matrix from your axes (which you shouldn't be doing anyway), you're building it from side, forward, up - I think it should be side, up, forward.

'Hope some of that helps.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.