"Simple" Camera Problem

Started by
10 comments, last by DrunkenBrit 15 years, 10 months ago
OK, this seems a simple problem yet I just can't see the common logic in solving it for some reason. I want my camera (well the camera code I took from GameTutorials) to be able to rotate 360 around X - axis. The code below shows how the programmer stops this from happening, yet I've commented out all code but:

		// To find the axis we need to rotate around for up and down
		// movements, we need to get a perpendicular vector from the
		// camera's view vector and up vector.  This will be the axis.
		// Before using the axis, it's a good idea to normalize it first.
		CVector3 vAxis = Cross(m_vView - m_vPosition, m_vUpVector);
		vAxis = Normalize(vAxis);
	
		// Rotate around our perpendicular axis
		RotateView(angleZ, vAxis.x, vAxis.y, vAxis.z);
Which techically looks like I should be able to do 360 rotation around X, but it appears as though the behaviour of the camera isn't changing at all and I'm very much still restricted to +-1.0 around X...Here's the original code. Cheers.

void CCamera::SetViewByMouse()
{
	POINT mousePos;									// This is a window structure that holds an X and Y
	int middleX = SCREEN_WIDTH  >> 1;				// This is a binary shift to get half the width
	int middleY = SCREEN_HEIGHT >> 1;				// This is a binary shift to get half the height
	float angleY = 0.0f;							// This is the direction for looking up or down
	float angleZ = 0.0f;							// This will be the value we need to rotate around the Y axis (Left and Right)
	static float currentRotX = 0.0f;
	
	// Get the mouse's current X,Y position
	GetCursorPos(&mousePos);						
	
	// If our cursor is still in the middle, we never moved... so don't update the screen
	if( (mousePos.x == middleX) && (mousePos.y == middleY) ) return;

	// Set the mouse position to the middle of our window
	SetCursorPos(middleX, middleY);							

	// Get the direction the mouse moved in, but bring the number down to a reasonable amount
	angleY = (float)( (middleX - mousePos.x) ) / 500.0f;		
	angleZ = (float)( (middleY - mousePos.y) ) / 500.0f;		

	static float lastRotX = 0.0f; 
 	lastRotX = currentRotX; // We store off the currentRotX and will use it in when the angle is capped
	
	// Here we keep track of the current rotation (for up and down) so that
	// we can restrict the camera from doing a full 360 loop.
	currentRotX += angleZ;
 
	// If the current rotation (in radians) is greater than 1.0, we want to cap it.
	if(currentRotX > 1.0f)     
	{
		currentRotX = 1.0f;
		
		// Rotate by remaining angle if there is any
		if(lastRotX != 1.0f) 
		{
			// To find the axis we need to rotate around for up and down
			// movements, we need to get a perpendicular vector from the
			// camera's view vector and up vector.  This will be the axis.
			// Before using the axis, it's a good idea to normalize it first.
			CVector3 vAxis = Cross(m_vView - m_vPosition, m_vUpVector);
			vAxis = Normalize(vAxis);
				
			// rotate the camera by the remaining angle (1.0f - lastRotX)
			RotateView( 1.0f - lastRotX, vAxis.x, vAxis.y, vAxis.z);
		}
	}
	// Check if the rotation is below -1.0, if so we want to make sure it doesn't continue
	else if(currentRotX < -1.0f)
	{
		currentRotX = -1.0f;
		
		// Rotate by the remaining angle if there is any
		if(lastRotX != -1.0f)
		{
			// To find the axis we need to rotate around for up and down
			// movements, we need to get a perpendicular vector from the
			// camera's view vector and up vector.  This will be the axis.
			// Before using the axis, it's a good idea to normalize it first.
			CVector3 vAxis = Cross(m_vView - m_vPosition, m_vUpVector);
			vAxis = Normalize(vAxis);
			
			// rotate the camera by ( -1.0f - lastRotX)
			RotateView( -1.0f - lastRotX, vAxis.x, vAxis.y, vAxis.z);
		}
	}
	// Otherwise, we can rotate the view around our position
	else 
	{	
		// To find the axis we need to rotate around for up and down
		// movements, we need to get a perpendicular vector from the
		// camera's view vector and up vector.  This will be the axis.
		// Before using the axis, it's a good idea to normalize it first.
		CVector3 vAxis = Cross(m_vView - m_vPosition, m_vUpVector);
		vAxis = Normalize(vAxis);
	
		// Rotate around our perpendicular axis
		RotateView(angleZ, vAxis.x, vAxis.y, vAxis.z);
	}

	// Always rotate the camera around the y-axis
	RotateView(angleY, 0, 1, 0);
}
Show A Man A Program, Frustrate Him For A Day...Teach A Man To Program, Frustrate Him For The Rest Of His Life...
Advertisement
I'm in the process of moving to a new apartment and thus don't have time to look over the code and help you out, but one quick suggestion is to use the source tags and define the correct language like so:

"[" source lang="cpp" "]"
"["/source"]"

Note that I surrounded the square brackets with "" so that it didn't try to interpret them.

Good luck
==============================
A Developers Blog | Dark Rock Studios - My Site
Umm.. if you took out the code that is actually doing the capping:

if(currentRotX > 1.0f)
...
else if(currentRotX < -1.0f)
...

and rebuilt the project, from this code, I don't see how your camera movement will be limited. angleZ should be some small value, dependent on the mouse's vertical position relative to the screen. You then rotate around the camera's right-vector by that angle.

Perhaps the error is somewhere else? Have you tried step-by-step debugging and verifying that all of the values are sane?
Thanks for your replies.

OK, I "think" I've narrowed it down...it appears that no matter what value I to cap the rotation once it goes past about 1.14 radians (and i'm looking at a sky texture in the sky box with clouds) the world appears to judder very fast and invert whatever im looking at i.e. a cloud is in the left hand corner and then gets repositioned at the right hand corner....?

I've capped it to 2.5 rads and it still behaviours the same way after it reaches beyond 1.14(-ish)...So my initial guess is that there is something wrong with the arbitrary axis rotation function which is below:

void CCamera::RotateView(float angle, float x, float y, float z){	CVector3 vNewView;	// Get the view vector (The direction we are facing)	CVector3 vView = m_vView - m_vPosition;			// Calculate the sine and cosine of the angle once	float cosTheta = (float)cos(angle);	float sinTheta = (float)sin(angle);	// Find the new x position for the new rotated point	vNewView.x  = (cosTheta + (1 - cosTheta) * x * x)		* vView.x;	vNewView.x += ((1 - cosTheta) * x * y - z * sinTheta)	* vView.y;	vNewView.x += ((1 - cosTheta) * x * z + y * sinTheta)	* vView.z;	// Find the new y position for the new rotated point	vNewView.y  = ((1 - cosTheta) * x * y + z * sinTheta)	* vView.x;	vNewView.y += (cosTheta + (1 - cosTheta) * y * y)		* vView.y;	vNewView.y += ((1 - cosTheta) * y * z - x * sinTheta)	* vView.z;	// Find the new z position for the new rotated point	vNewView.z  = ((1 - cosTheta) * x * z - y * sinTheta)	* vView.x;	vNewView.z += ((1 - cosTheta) * y * z + x * sinTheta)	* vView.y;	vNewView.z += (cosTheta + (1 - cosTheta) * z * z)		* vView.z;	// Now we just add the newly rotated vector to our position to set	// our new rotated view of our camera.	m_vView = m_vPosition + vNewView;}


Hmmm I'm now also thinking that maybe the author knew there was something wrong with the initial code when they wrote it and hence the cap of 1.0f...

Just re-ran the app now and it is rotating me all the way around the arbitrary - axis just almost instantaneously after it reaches 1.14 rads...

Maybe that function is wrong?

Thanks in advance.
Show A Man A Program, Frustrate Him For A Day...Teach A Man To Program, Frustrate Him For The Rest Of His Life...
Would be a lot more interesting to see the part where you set the view matrix, because I'd almost bet that you're using some lookat function and never considered that once you rotate more then 90° up or down just simply plugging (0,1,0) as "up" into it will be the completely wrong half space. The "real" up at 90° is either (0,0,1) or (0,0,-1) and beyond that it's (0,-y,z) or (0,-y,-z). You do NOT specify what's "up" in the world, but what's up for the viewer. They just happen to usually be in roughly the same direction and lookat happens to fix that with the cross products.

Good reason why just using tutorials and their code isn't enough. At some point you need to learn the background, why things are done that way, how they actually work and most of all, how they work together. In this case, understanding what that black box called "gluLookAt" (or the DX equivalent) actually does.

In fact, everytime there is a topic about rotation/camera problems, it's also a safe bet that there will be Euler angles. Sufficient for simple first person stuff, but somewhat useless for everything beyond that. Quickly checking Google for a camera tutorial, I know whom to blame. Unfortunately, everybody and his dog can put up tutorials on the internet and almost all of them use Euler angles.

Quick and dirty (OGL using a float[16], D3D using the provided matrix class), removed all other stuff (culling, translating, rotation around global axes, etc.):
//Ditch gluLookAt and just do it yourself with less math involvedvoid CameraGL::setView() {	glMatrixMode(GL_MODELVIEW);	float viewmatrix[16]={		Transform[0], Transform[4], Transform[8], 0,		Transform[1], Transform[5], Transform[9], 0,		Transform[2], Transform[6], Transform[10], 0,		-(Transform[0]*Transform[12] + Transform[1]*Transform[13] + Transform[2]*Transform[14]),		-(Transform[4]*Transform[12] + Transform[5]*Transform[13] +	Transform[6]*Transform[14]),						  		-(Transform[8]*Transform[12] + Transform[9]*Transform[13] + Transform[10]*Transform[14]), 1};	glLoadMatrixf(viewmatrix);}//For simplicities sake, we don't use a matrix lib and inefficiently abuse//OpenGL to do the matrix multiplication for usvoid CameraGL::rotate(float deg, float x, float y, float z) {	glPushMatrix();	glLoadMatrixf(Transform);	glRotatef(deg, x,y,z);	glGetFloatv(GL_MODELVIEW_MATRIX, Transform);	glPopMatrix();}//Same for D3D, except we don't manually inverse the matrix//(the generic inverse might be less efficient, but makes it more obvious//what's happening)void CameraD3D::setView() {	D3DXMATRIX ViewMat;	D3DXMatrixInverse(&ViewMat, NULL, &Transform);	device-&gt;SetTransform(D3DTS_VIEW, &ViewMat);}void CameraD3D::rotate(float rad, float x, float y, float z, bool global) {	D3DXMATRIX rot;	D3DXVECTOR3 axis(x, y, z);	D3DXMatrixRotationAxis(&rot, &axis, rad);	D3DXMatrixMultiply(&Transform, &rot, &Transform);	}


The whole point of the above is that moving/rotating objects is exactly the same as moving/rotating the camera, except that the matrix has to be inversed to be used as view matrix.
f@dzhttp://festini.device-zero.de
Hi,

Thanks for your reply and time taken to write your post; I appreciate it mate :)

I've done some digging around on what you said and found some tutorials that explain how to create a "camera" without using gluLookAt, and also mention how it can be tricky to get the Up Vector correct (for gluLookAt) using some matrix multiplications. Do you know how to do that, if so could explain please for my understanding of it, although I can see how it is easier with the code example you pasted and avoiding gluLookAt.

I found some nice code here that does everything I could pretty much want for a camera: http://www.codecolony.de/docs/camera.htm

Unfortunately, it doesn't explain how it works and I feel like I'm constantly cheating myself and being back to square one with the attitude of "accepting" that it works and get on with making the game rather than be delayed in learning how it works before I progress...I can imagine how this could be a common dilema with a lot of programmers, as 99% of the time you're spoonfed the code without explanations, although I am not ungrateful for the people take their time and effort to help others out with code and tutorials.

Thanks again :)

DB

Show A Man A Program, Frustrate Him For A Day...Teach A Man To Program, Frustrate Him For The Rest Of His Life...
I checked the tutorial and I had RotatedX, RotatedY, RotatedZ flying at me. Euler angles again.

Why do I hate them? Because they look neat and simple. They actually are, but they are also completely useless unless you have very simple requirements.

Try to imagine rotating some amount around one axis (this CHANGES your local coordinate system). Then rotate some amount around the new (other) axis. Do it again in reversed order and you will see a totally different result. You can't just throw away the order in which you rotated and expect the result to be useful, but that's exactly what you do with Euler angles. You just collect sums, losing all info about which axis you rotated around at which point in time.

Why does this work for your typical first person camera? Because you ALWAYS first rotate left/right (around the unchanged "up") and then up/down (around the changed "right"). You can get away with lookat, because you usually never look so far up or down that the sign of your local "up" becomes negative.

If all you want is that without the limitation on up/down, you can fix it. Just check the up/down rotation and for anything beyond +/- 90° you use (0,-1,0). But don't forget that at +/- 270° it will be back to normal, so I'd suggest limiting the range to +/- 180° before adjusting the value (360 +/- rot, when abs(rot)>180)

If you're going for more degrees of freedom than that, strictly throw away every piece of code or tutorial where you see variables being kept with the word "angle" or "rotation" in their name. Even if they do manage 6DoF, they must be doing it in a pointlessly complicated way just to force an inappropriate approach to work with lots of extra effort. That's like trying to turn crap into dinner. Sure, you can add enough spice to make it taste good, but I still wouldn't want to eat it.
f@dzhttp://festini.device-zero.de
Heh, thanks for your reply...again, I'm making sense of what you're saying :)

I actually found this tutorial on rotation around arbitrary axis that uses gluLookAt (again lol) but uses the cross product and trig to solve the up and right vector problems as you mentioned previously: http://www.codecolony.de/docs/camera.htm

This is using angles and rotations again but it seems to be simple how it all works together with changing the up and right vectors. BUT like you say, it's changing my local co-ord. system and things don't look right at all...hmmm...
Show A Man A Program, Frustrate Him For A Day...Teach A Man To Program, Frustrate Him For The Rest Of His Life...
P.S. I've been learning about quaternions and how they can absolve the gimbal lock problems I've been having. I found the tutorial on NeHe http://nehe.gamedev.net/data/lessons/lesson.asp?lesson=Quaternion_Camera_Class which uses a single matrix to perform arbitrary axis rotations and so far so good. If anyone stumbles across this post with similar problems (no doubt through the years there will be others) with using Euler angles with gluLookAt etc then I suggest you check out that tutorial and also http://www.cprogramming.com/tutorial/3d/quaternions.html for a good explanation of quaternions. I would be nice to learn how to absolve gimbal lock with using Euler rotations and creating a matrix from up, right, view etc vectors if anyone would like to contribute? If I find something on the net I will post it here.
Show A Man A Program, Frustrate Him For A Day...Teach A Man To Program, Frustrate Him For The Rest Of His Life...
Quote:Original post by DrunkenBrit
P.S. I've been learning about quaternions and how they can absolve the gimbal lock problems I've been having. I found the tutorial on NeHe http://nehe.gamedev.net/data/lessons/lesson.asp?lesson=Quaternion_Camera_Class which uses a single matrix to perform arbitrary axis rotations and so far so good. If anyone stumbles across this post with similar problems (no doubt through the years there will be others) with using Euler angles with gluLookAt etc then I suggest you check out that tutorial and also http://www.cprogramming.com/tutorial/3d/quaternions.html for a good explanation of quaternions. I would be nice to learn how to absolve gimbal lock with using Euler rotations and creating a matrix from up, right, view etc vectors if anyone would like to contribute? If I find something on the net I will post it here.
Whether you encounter gimbal lock has nothing to do with whether or not you use quaternions; rather, it depends on how you construct and update the orientation for your object.

I haven't read the thread in its entirety, but if you haven't already, it might be worth clarifying exactly what type of motion you're trying to implement (an easy way to communicate what you're after is to reference a well-known game - e.g. 'like the camera in Quake', or, 'free motion like in Freespace'). Given that information, it will be easier to recommend an appropriate representation.

Another thing I would suggest is not to think in terms of 'camera motion'. The only thing that makes an object a 'camera' is that prior to rendering, its transform is inverted and used as the 'view' transform for the scene. Prior to that, the camera can be thought of (and treated like) any other object. What you're really interested in here is how to represent and manipulate an object's orientation to achieve a desired behavior; the 'camera' part of it is really just an afterthought and should be thought of as such.

This topic is closed to new replies.

Advertisement