C++ - Problem With First Person Camera

Started by
8 comments, last by JohnLocke 15 years, 9 months ago
Recently, I've been having some trouble with a first person camera for a game I'm working on. The code for it came from the book DirectX 9 Graphics: The Definitive Guide to Direct 3D by Alan Thorn. Here's a rotating cube when the game first starts and the mouse hasn't been moved: The problem is, when the mouse is moved in a circle... This happens. It doesn't just break with circles, but anything that isn't a straight line. The following are used in the game itself to rotate the camera, calling functions from the included header file. MouseX is a float for the distance the cursor moved from the coordinates of the last calculation to the next on the x-axis, and MouseY is the same but for the y-axis.
TheCamera.RotateDown(InputData->MouseY / 200.0f);
TheCamera.RotateRight(InputData->MouseX / 200.0f);
I guess what I'd like to know is if I've implemented the code right, and if so, how do I fix the problem with it, and if I'm not using the code right, I'd like to know how to use it correctly. Again, I didn't create the code for the camera, it was taken from a book. Any help would be greatly appreciated. Here's the header file for the camera:

extern LPDIRECT3DDEVICE9 d3ddev; // Rendering device/video card.
D3DXMATRIX matTranslate; 
D3DXMATRIX matRotateY;
D3DXMATRIX matProjection; // Bunch of selfexplanatory matrices.

class CXCamera // Just note our camera is called "TheCamera".
{
protected:
	D3DXVECTOR3 m_Position;
	D3DXVECTOR3 m_LookAt;
	D3DXVECTOR3 m_Right;
	D3DXVECTOR3 m_Up;
	
	float m_fRotAboutUp;
    float m_fRotAboutRight;
    float m_fRotAboutFacing;

	D3DXMATRIX m_ViewTransform; // Matrix for view transform.

	LPDIRECT3DDEVICE9 m_pDevice;
	bool m_UpdateRequired;
	// Function prototypes:
	HRESULT UpdateCameraMatrices(); 
public:
	void LookAtPos(D3DXVECTOR3 *Position, D3DXVECTOR3 *LookAt, D3DXVECTOR3 *Up);
	void SetPosition(FLOAT X, FLOAT Y, FLOAT Z);
	D3DXVECTOR3* GetPosition() {return &m_Position;}
	D3DXVECTOR3* GetLookAt() {return &m_LookAt;}
	D3DXVECTOR3* GetRight() {return &m_Right;}
	D3DXVECTOR3* GetUp() {return &m_Up;}
	D3DXMATRIX* GetViewMatrix() {return &m_ViewTransform;}
	CXCamera(LPDIRECT3DDEVICE9 pDevice);
	HRESULT Update();

	void RotateDown(float fAngle);
	void RotateRight(float fAngle);
	void Roll(float fAngle);
	void MoveForward(float fDist);
	void MoveRight(float fDist);
	void MoveUp(float fDist);
	void MoveInDirection(float fDist, D3DXVECTOR3* Dir);
	
};

//---------------------------------------------------------------------------------

CXCamera::CXCamera(LPDIRECT3DDEVICE9 pDevice)
{
	// Set camera to starting positions, etc.
	m_Position = D3DXVECTOR3(0.0f,0.0f,0.0f);
	m_LookAt = D3DXVECTOR3(0.0f,0.0f,1.0f);
	m_Right = D3DXVECTOR3(1.0f,0.0f,0.0f);
	m_Up = D3DXVECTOR3(0.0f,1.0f,0.0f);
	m_UpdateRequired = true;

	m_fRotAboutUp = m_fRotAboutRight = m_fRotAboutFacing = 0.0f;
	D3DXMatrixIdentity(&m_ViewTransform);//Make view transform an identity matrix/initialze it

	m_pDevice = pDevice; // Class gets its own address to d3ddev to use.
}

HRESULT CXCamera::Update()
{
	if(m_pDevice) // If the device/its address is valid...
	{
		return UpdateCameraMatrices(); // See function.
	}

	return E_FAIL; // Or else it fails. This shouldn't happen.
}

HRESULT CXCamera::UpdateCameraMatrices()
{
	D3DXMATRIX matTotal;
	D3DXMATRIX matRotAboutUp, matRotAboutRight, matRotAboutFacing;

	// Build matrices for rotations around axis:
	D3DXMatrixRotationAxis(&matRotAboutRight,&m_Right,m_fRotAboutRight);
    D3DXMatrixRotationAxis(&matRotAboutUp,&m_Up,m_fRotAboutUp);
    D3DXMatrixRotationAxis(&matRotAboutFacing,&m_LookAt,m_fRotAboutFacing);
	
	// Multiply the matrices into matrix matTotal.
	D3DXMatrixMultiply(&matTotal,&matRotAboutUp,&matRotAboutRight);
    D3DXMatrixMultiply(&matTotal,&matRotAboutFacing,&matTotal);


	// Transform the vectors by a matrix.
	D3DXVec3TransformCoord(&m_Right,&m_Right,&matTotal);   
    D3DXVec3TransformCoord(&m_Up,&m_Up,&matTotal);   
    D3DXVec3Cross(&m_LookAt, &m_Right,&m_Up);

	if(fabs(D3DXVec3Dot(&m_Up,&m_Right)) > 0.01) // If the dot product of the two vectors is
												 // greater than 0.01... (?) 
	{      
		D3DXVec3Cross(&m_Up, &m_LookAt,&m_Right); // Get the cross product of LookAt and Right.
    }

	// Turn vectors into normal vectors.
	D3DXVec3Normalize(&m_Right,&m_Right);
    D3DXVec3Normalize(&m_Up,&m_Up);
    D3DXVec3Normalize(&m_LookAt,&m_LookAt);

	float fView41,fView42,fView43;

	// Set part of the view transform matrix values to the negative dot product of a vector.
    fView41 = -D3DXVec3Dot(&m_Right,&m_Position);
    fView42 = -D3DXVec3Dot(&m_Up,&m_Position);
    fView43 = -D3DXVec3Dot(&m_LookAt,&m_Position);

	// Make the view transform matrix.
	m_ViewTransform = D3DXMATRIX(m_Right.x, m_Up.x, m_LookAt.x, 0.0f,
								 m_Right.y, m_Up.y, m_LookAt.y, 0.0f,
                                 m_Right.z, m_Up.z, m_LookAt.z, 0.0f,
                                 fView41, fView42,  fView43,	1.0f);

	m_fRotAboutUp = m_fRotAboutRight = m_fRotAboutFacing = 0.0f;
	m_UpdateRequired = false;
	static float index = 0.0f; index+=0.05f;
    D3DXMatrixRotationY(&matRotateY, index);
	D3DXMatrixTranslation(&matTranslate, 20.0f, 4.0f, 75.0f);
    D3DXMatrixPerspectiveFovLH(&matProjection,
                               D3DXToRadian(45),    // The horizontal field of view
                               (FLOAT)SCREEN_WIDTH / (FLOAT)SCREEN_HEIGHT, // Aspect ratio
                               1.0f,    // The near view-plane
                               100000.0f);    // The far view-plane
	m_pDevice->SetTransform(D3DTS_PROJECTION, &matProjection);
	m_pDevice->SetTransform(D3DTS_WORLD, &(matRotateY * matTranslate));
	return m_pDevice->SetTransform(D3DTS_VIEW, &m_ViewTransform);
}

void CXCamera::LookAtPos(D3DXVECTOR3 *Position, D3DXVECTOR3 *LookAt, D3DXVECTOR3 *Up)
	// Not for playing, more for cutscenes.
{
	D3DXMatrixLookAtLH(&m_ViewTransform, Position, LookAt, Up);

	m_Position = *(Position);

	m_Right.x = m_ViewTransform._11;
	m_Right.y = m_ViewTransform._21;
	m_Right.z = m_ViewTransform._31;

	m_Up.x = m_ViewTransform._12;
	m_Up.y = m_ViewTransform._22;
	m_Up.z = m_ViewTransform._32;

	m_LookAt.x = m_ViewTransform._13;
	m_LookAt.y = m_ViewTransform._23;
	m_LookAt.z = m_ViewTransform._33;

	m_fRotAboutUp = m_fRotAboutRight = m_fRotAboutFacing = 0.0f;
}

void CXCamera::SetPosition(FLOAT X, FLOAT Y, FLOAT Z) //Self-explanatory. 
													  //For cutscenes probably.
{
	m_Position = D3DXVECTOR3(X, Y, Z);
	m_UpdateRequired = true;
}

// All of these functions should be self explanatory:
void CXCamera::RotateDown(float fAngle) //Rotate camera up/down.
{
	m_fRotAboutRight += fAngle;
	m_UpdateRequired = true;
}

void CXCamera::RotateRight(float fAngle) //Rotate camera left/right.
{
	m_fRotAboutUp += fAngle;
	m_UpdateRequired = true;
}

void CXCamera::Roll(float fAngle) //Roll the camera.
{
	m_fRotAboutFacing += fAngle;
	m_UpdateRequired = true;
}

void CXCamera::MoveForward(float fDist) //Move camera forward/back.
{
	m_Position += fDist * m_LookAt;
	m_UpdateRequired = true;
}

void CXCamera::MoveRight(float fDist) //Move camera left/right.
{
	m_Position += fDist * m_Right;
	m_UpdateRequired = true;
}

void CXCamera::MoveUp(float fDist) //Move camera up/down.
{
	m_Position += fDist * m_Up;
	m_UpdateRequired = true;
}

void CXCamera::MoveInDirection(float fDist, D3DXVECTOR3* Dir) //Useless?
{
	D3DXVECTOR3 DirToMove(0,0,0);
	D3DXVec3Normalize(&DirToMove, Dir);
	m_Position += fDist*DirToMove;
	m_UpdateRequired = true;
}

void DebugCamera() //This works for making a camera. Fall back on this if you must ensure
				   //it's the camera causing the problem, not another part of the program.
{
    static float index = 0.0f; index+=0.05f;    // an ever-increasing float value

    D3DXMatrixRotationY(&matRotateY, index);

D3DXMatrixTranslation(&matTranslate, 20.0f, 4.0f, 75.0f);

d3ddev->SetTransform(D3DTS_WORLD, &(matRotateY * matTranslate));


    // the projection transform matrix

    D3DXMatrixPerspectiveFovLH(&matProjection,
                               D3DXToRadian(45),    // the horizontal field of view
                               (FLOAT)SCREEN_WIDTH / (FLOAT)SCREEN_HEIGHT, // aspect ratio
                               1.0f,    // the near view-plane
                               100000.0f);    // the far view-plane

	d3ddev->SetTransform(D3DTS_PROJECTION, &matProjection);
}
Advertisement
This line:
D3DXMatrixRotationAxis(&matRotAboutUp,&m_Up,m_fRotAboutUp);

in
CXCamera::UpdateCameraMatrices

should be using the world up vector rather than the local up vector.
Quote:Original post by bzroom
This line:
D3DXMatrixRotationAxis(&matRotAboutUp,&m_Up,m_fRotAboutUp);

in
CXCamera::UpdateCameraMatrices

should be using the world up vector rather than the local up vector.


Thanks for your response, but could you please elaborate further on what you mean?
I think this line: D3DXMatrixLookAtLH(&m_ViewTransform, Position, LookAt, Up);
Should be: D3DXMatrixLookAtLH(&m_ViewTransform, Position, LookAt, m_Up);
Quote:Original post by Tiffany_Smith
I think this line: D3DXMatrixLookAtLH(&m_ViewTransform, Position, LookAt, Up);
Should be: D3DXMatrixLookAtLH(&m_ViewTransform, Position, LookAt, m_Up);


I've tried this, but I don't seem to see any evident difference...
Actually it looks like you modify m_Up, so you need to create a world Up vector that remains unmodified.
Quote:Original post by Tiffany_Smith
Actually it looks like you modify m_Up, so you need to create a world Up vector that remains unmodified.


Yup, that's what I was refering to. If you prefer I can reword it like she did:

This Line: D3DXMatrixRotationAxis(&matRotAboutUp,&m_Up,m_fRotAboutUp);
should be: D3DXMatrixRotationAxis(&matRotAboutUp,&worldUp,m_fRotAboutUp);

You are rotating the view about it's relative up vector, this is not how a FPS behaves. A FPS always rotates around the world up vector.

Don't move your camera in little circles to test it. Use extreme cases. With your method, look up 90 degrees, then look left 90 degrees, the world should be sideways. This is what you are experiencing, but the way you go about testing it only slowly introduces it.

Also, you said the cube is rotating. How can you test for proper rotation of your camera if the only object in the scene is also rotating?

Quote:Original post by JohnLocke
Quote:Original post by Tiffany_Smith
I think this line: D3DXMatrixLookAtLH(&m_ViewTransform, Position, LookAt, Up);
Should be: D3DXMatrixLookAtLH(&m_ViewTransform, Position, LookAt, m_Up);


I've tried this, but I don't seem to see any evident difference...


The second one is the correct one. You will only see the difference when you look straight up and straight down. The first one is numerically impossible to do that, the second one would work properly.

Also, the first one would never allow you to be upside down, the camera would then just be backwards. The latter version will allow you to view the world upside down.
Thanks for your responses, but I'm still having a bit of trouble with it.


Quote:Original post by Tiffany_Smith
Actually it looks like you modify m_Up, so you need to create a world Up vector that remains unmodified.

Quote:Original post by bzroom
Yup, that's what I was refering to. If you prefer I can reword it like she did:

This Line: D3DXMatrixRotationAxis(&matRotAboutUp,&m_Up,m_fRotAboutUp);
should be: D3DXMatrixRotationAxis(&matRotAboutUp,&worldUp,m_fRotAboutUp);

You are rotating the view about it's relative up vector, this is not how a FPS behaves. A FPS always rotates around the world up vector.


I have done what both have you have said, and, I admit, the effect is lessened, yet it still happens.


Quote:Original post by bzroom
Also, you said the cube is rotating. How can you test for proper rotation of your camera if the only object in the scene is also rotating?


The cube rotates horizontally, while the problem with the camera makes it appear to rotate vertically.


Quote:Original post by bzroom
The second one is the correct one. You will only see the difference when you look straight up and straight down. The first one is numerically impossible to do that, the second one would work properly.

Also, the first one would never allow you to be upside down, the camera would then just be backwards. The latter version will allow you to view the world upside down.


Ahh, okay. Thank you.

Like I said, I'm still having the problem, but to a slightly lesser extent. Although I think I've done what you both have said correctly, I may be wrong. I'll post the updated code:

extern LPDIRECT3DDEVICE9 d3ddev; // Rendering device/video card.D3DXMATRIX matTranslate; D3DXMATRIX matRotateY;D3DXMATRIX matProjection; // Bunch of selfexplanatory matrices.class CXCamera // Just note our camera is called "TheCamera".{protected:	D3DXVECTOR3 m_Position;	D3DXVECTOR3 m_LookAt;	D3DXVECTOR3 m_Right;	D3DXVECTOR3 m_Up;	D3DXVECTOR3 worldUp;		float m_fRotAboutUp;    float m_fRotAboutRight;    float m_fRotAboutFacing;	D3DXMATRIX m_ViewTransform; // Matrix for view transform.	LPDIRECT3DDEVICE9 m_pDevice;	bool m_UpdateRequired;	// Function prototypes:	HRESULT UpdateCameraMatrices(); public:	void LookAtPos(D3DXVECTOR3 *Position, D3DXVECTOR3 *LookAt, D3DXVECTOR3 *Up);	void SetPosition(FLOAT X, FLOAT Y, FLOAT Z);	D3DXVECTOR3* GetPosition() {return &m_Position;}	D3DXVECTOR3* GetLookAt() {return &m_LookAt;}	D3DXVECTOR3* GetRight() {return &m_Right;}	D3DXVECTOR3* GetUp() {return &m_Up;}	D3DXMATRIX* GetViewMatrix() {return &m_ViewTransform;}	CXCamera(LPDIRECT3DDEVICE9 pDevice);	HRESULT Update();	void RotateDown(float fAngle);	void RotateRight(float fAngle);	void Roll(float fAngle);	void MoveForward(float fDist);	void MoveRight(float fDist);	void MoveUp(float fDist);	void MoveInDirection(float fDist, D3DXVECTOR3* Dir);	};//---------------------------------------------------------------------------------CXCamera::CXCamera(LPDIRECT3DDEVICE9 pDevice){	// Set camera to starting positions, etc.	m_Position = D3DXVECTOR3(0.0f,0.0f,0.0f);	m_LookAt = D3DXVECTOR3(0.0f,0.0f,1.0f);	m_Right = D3DXVECTOR3(1.0f,0.0f,0.0f);	m_Up = D3DXVECTOR3(0.0f,1.0f,0.0f);	worldUp = D3DXVECTOR3(0.0f, 1.0f, 0.0f);	m_UpdateRequired = true;	m_fRotAboutUp = m_fRotAboutRight = m_fRotAboutFacing = 0.0f;	D3DXMatrixIdentity(&m_ViewTransform);//Make view transform an identity matrix/initialze it	m_pDevice = pDevice; // Class gets its own address to d3ddev to use.}HRESULT CXCamera::Update(){	if(m_pDevice) // If the device/its address is valid...	{		return UpdateCameraMatrices(); // See function.	}	return E_FAIL; // Or else it fails. This shouldn't happen.}HRESULT CXCamera::UpdateCameraMatrices(){	D3DXMATRIX matTotal;	D3DXMATRIX matRotAboutUp, matRotAboutRight, matRotAboutFacing;	// Build matrices for rotations around axis:	D3DXMatrixRotationAxis(&matRotAboutRight,&m_Right,m_fRotAboutRight);    D3DXMatrixRotationAxis(&matRotAboutUp,&worldUp,m_fRotAboutUp);    D3DXMatrixRotationAxis(&matRotAboutFacing,&m_LookAt,m_fRotAboutFacing);		// Multiply the matrices into matrix matTotal.	D3DXMatrixMultiply(&matTotal,&matRotAboutUp,&matRotAboutRight);    D3DXMatrixMultiply(&matTotal,&matRotAboutFacing,&matTotal);	// Transform the vectors by a matrix.	D3DXVec3TransformCoord(&m_Right,&m_Right,&matTotal);       D3DXVec3TransformCoord(&m_Up,&m_Up,&matTotal);       D3DXVec3Cross(&m_LookAt, &m_Right,&m_Up);	if(fabs(D3DXVec3Dot(&m_Up,&m_Right)) > 0.01) // If the dot product of the two vectors is												 // greater than 0.01... (?) 	{      		D3DXVec3Cross(&m_Up, &m_LookAt,&m_Right); // Get the cross product of LookAt and Right.    }	// Turn vectors into normal vectors.	D3DXVec3Normalize(&m_Right,&m_Right);    D3DXVec3Normalize(&m_Up,&m_Up);    D3DXVec3Normalize(&m_LookAt,&m_LookAt);	float fView41,fView42,fView43;	// Set part of the view transform matrix values to the negative dot product of a vector.    fView41 = -D3DXVec3Dot(&m_Right,&m_Position);    fView42 = -D3DXVec3Dot(&m_Up,&m_Position);    fView43 = -D3DXVec3Dot(&m_LookAt,&m_Position);	// Make the view transform matrix.	m_ViewTransform = D3DXMATRIX(m_Right.x, m_Up.x, m_LookAt.x, 0.0f,								 m_Right.y, m_Up.y, m_LookAt.y, 0.0f,                                 m_Right.z, m_Up.z, m_LookAt.z, 0.0f,                                 fView41, fView42,  fView43,	1.0f);	m_fRotAboutUp = m_fRotAboutRight = m_fRotAboutFacing = 0.0f;	m_UpdateRequired = false;	static float index = 0.0f; index+=0.05f;    D3DXMatrixRotationY(&matRotateY, index);	D3DXMatrixTranslation(&matTranslate, 20.0f, 4.0f, 75.0f);    D3DXMatrixPerspectiveFovLH(&matProjection,                               D3DXToRadian(45),    // The horizontal field of view                               (FLOAT)SCREEN_WIDTH / (FLOAT)SCREEN_HEIGHT, // Aspect ratio                               1.0f,    // The near view-plane                               100000.0f);    // The far view-plane	m_pDevice->SetTransform(D3DTS_PROJECTION, &matProjection);	m_pDevice->SetTransform(D3DTS_WORLD, &(matRotateY * matTranslate));	return m_pDevice->SetTransform(D3DTS_VIEW, &m_ViewTransform);}void CXCamera::LookAtPos(D3DXVECTOR3 *Position, D3DXVECTOR3 *LookAt, D3DXVECTOR3 *Up)	// Not for playing, more for cutscenes.{	D3DXMatrixLookAtLH(&m_ViewTransform, Position, LookAt, &m_Up);	m_Position = *(Position);	m_Right.x = m_ViewTransform._11;	m_Right.y = m_ViewTransform._21;	m_Right.z = m_ViewTransform._31;	m_Up.x = m_ViewTransform._12;	m_Up.y = m_ViewTransform._22;	m_Up.z = m_ViewTransform._32;	m_LookAt.x = m_ViewTransform._13;	m_LookAt.y = m_ViewTransform._23;	m_LookAt.z = m_ViewTransform._33;	m_fRotAboutUp = m_fRotAboutRight = m_fRotAboutFacing = 0.0f;}void CXCamera::SetPosition(FLOAT X, FLOAT Y, FLOAT Z) //Self-explanatory. 													  //For cutscenes probably.{	m_Position = D3DXVECTOR3(X, Y, Z);	m_UpdateRequired = true;}// All of these functions should be self explanatory:void CXCamera::RotateDown(float fAngle) //Rotate camera up/down.{	m_fRotAboutRight += fAngle;	m_UpdateRequired = true;}void CXCamera::RotateRight(float fAngle) //Rotate camera left/right.{	m_fRotAboutUp += fAngle;	m_UpdateRequired = true;}void CXCamera::Roll(float fAngle) //Roll the camera.{	m_fRotAboutFacing += fAngle;	m_UpdateRequired = true;}void CXCamera::MoveForward(float fDist) //Move camera forward/back.{	m_Position += fDist * m_LookAt;	m_UpdateRequired = true;}void CXCamera::MoveRight(float fDist) //Move camera left/right.{	m_Position += fDist * m_Right;	m_UpdateRequired = true;}void CXCamera::MoveUp(float fDist) //Move camera up/down.{	m_Position += fDist * m_Up;	m_UpdateRequired = true;}void CXCamera::MoveInDirection(float fDist, D3DXVECTOR3* Dir) //Useless?{	D3DXVECTOR3 DirToMove(0,0,0);	D3DXVec3Normalize(&DirToMove, Dir);	m_Position += fDist*DirToMove;	m_UpdateRequired = true;}void DebugCamera() //This works for making a camera. Fall back on this if you must ensure				   //it's the camera causing the problem, not another part of the program.{    static float index = 0.0f; index+=0.05f;    // an ever-increasing float value    D3DXMatrixRotationY(&matRotateY, index);D3DXMatrixTranslation(&matTranslate, 20.0f, 4.0f, 75.0f);d3ddev->SetTransform(D3DTS_WORLD, &(matRotateY * matTranslate));    // the projection transform matrix    D3DXMatrixPerspectiveFovLH(&matProjection,                               D3DXToRadian(45),    // the horizontal field of view                               (FLOAT)SCREEN_WIDTH / (FLOAT)SCREEN_HEIGHT, // aspect ratio                               1.0f,    // the near view-plane                               100000.0f);    // the far view-plane	d3ddev->SetTransform(D3DTS_PROJECTION, &matProjection);}
I'm just guessing at this point, but try reversing the order of this multiplication:

D3DXMatrixMultiply(&matTotal,&matRotAboutUp,&matRotAboutRight);

try
D3DXMatrixMultiply(&matTotal,&matRotAboutRight,&matRotAboutUp);

Please use the
[ source ]
..code here..
[ /source ]
tags (without spaces) from now on when dumping code.
Well that fixed it completely! Thank you very much for helping me.

This topic is closed to new replies.

Advertisement