Sign in to follow this  
jdub

Help with my First Person Camera Code

Recommended Posts

jdub    459

My control scheme is as followed (with respect to LH coordinate system):

 

W, A, S, D:  Translate camera along standard axes (local to camera space).

Q, E: Rotate camera left, right respectively. 

Mouse Movement: Behave in a manner similar to that of most FPS games.

 

The system seems to work fine at trivial angles.  However, I have noticed that if the camera is rotated, my translations (A,D) do not translate in the correct direction.  In addition, with enough use of the FPS-like mouse control system, rotations begin to break down.  Here is my code:

 

Camera class:

void Camera3D::Translate(Vector3 v)
{
	this->translation += v;
}

void Camera3D::SetRotationAxisAngle(Vector3 v, double angle)
{
	Matrix new_rot = Matrix::CreateFromAxisAngle(v, angle);
	this->rotation = new_rot;
}

void Camera3D::RotateAxisAngle(Vector3 v, double angle)
{
	Matrix new_rot = Matrix::CreateFromAxisAngle(v, angle);
	this->rotation *= new_rot;
}

void Camera3D::buildView(void)
{
	this->view = Matrix::Identity();
	this->view.m[3][0] = -this->translation.x;
	this->view.m[3][1] = -this->translation.y;
	this->view.m[3][2] = -this->translation.z;
	this->view *= this->rotation;
		
	this->invView = this->view.Invert();
}

Input Control code:

void InputController::Update(double ms)
{
	float dist = (ms / 1000.0)*this->cameraMoveSpeed;
	Matrix view = this->renderer->GetCamera().GetView();
	Vector3 axis;

	switch (this->motionState)
	{
	case MOTION_STATE_FORWARD:
		this->renderer->GetCamera().Translate(view.Forward() * dist);
		break;
	case MOTION_STATE_BACK:
		this->renderer->GetCamera().Translate(view.Backward() * dist);
		break;
	case MOTION_STATE_LEFT:
		this->renderer->GetCamera().Translate(view.Left() * dist);
		break;
	case MOTION_STATE_RIGHT:
		axis = view.Right();
		this->renderer->GetCamera().Translate(view.Right() * dist);
		break;
	case MOTION_STATE_ROT_RIGHT:
		this->renderer->GetCamera().RotateAxisAngle(view.Forward(), 0.01f);
		break;
	case MOTION_STATE_ROT_LEFT:
		this->renderer->GetCamera().RotateAxisAngle(view.Forward(), -0.01f);
		break;
	}
}	

void InputController::FPSMouseUpdate(int x, int y)
{
	if(x == 0 && y == 0)
	{
		return;
	}
		
	this->mouseDeltaX = x;
	this->mouseDeltaY = y;

	Vector3 mouseDelta(-this->mouseDeltaX, this->mouseDeltaY, 0.0f);
	Matrix view = this->renderer->GetCamera().GetView();
	
	this->renderer->GetCamera().RotateAxisAngle(view.Right(), mouseDamping*this->mouseDeltaY);
	this->renderer->GetCamera().RotateAxisAngle(view.Up(), mouseDamping*this->mouseDeltaX);
}

Share this post


Link to post
Share on other sites
Irlan    4067
Generally is a good thing to keep track of the rotation axes (right, up, forward) along with the current camera position. Creating a class such CTransform/COrientation, putting the rotation operations such Yaw, Pitch, Row, Rotate Around Axis, etc, and giving the camera one instance of that class it's a good start.

When computing the view-matrix you can pass directly the data to it or create via a LookAt call.

A FPS camera rotate around the world unit up vector, so you can pass that to the rotate around function and work with that.

A x-axis and z-axis rotation it's performed around its local axis, so you pass that axes to the respective function.

Share this post


Link to post
Share on other sites
ericrrichards22    2434

I'll second the suggestion to maintain the position, forward, right and up vectors separately.  It makes updating the camera position when you translate much easier to get right.

Shameless plug, I wrote up a blog post on the FPS camera class I've been using which does about what you're looking to do.

It is in C# and designed for DirectX style matrices.  I based it off of the code from Frank Luna's Introduction to 3D Game Programming with Direct3D 11.0 - you can download the C++ source code for the book there if you don't want to port my code back to the original language :-)

Share this post


Link to post
Share on other sites
haegarr    7372


The system seems to work fine at trivial angles.  However, I have noticed that if the camera is rotated, my translations (A,D) do not translate in the correct direction...

What does "not [...] in the correct direction" mean exactly?

 

Something that is suspect is your use of Camera3D::rotation (R), Camera3D::translation (t), Camera3D::view (V), and Camera3D::invView (V-1). The reference space of Camera3D::rotation and Camera3D::translation seems me to be the world space. Then you generate the view matrix as

   V := T( -t ) * R

Why are you using the negated translation here? I would expect T(t) instead, because the resulting matrix is then in world space. I further assume/hope you're using column vectors!?

 


... In addition, with enough use of the FPS-like mouse control system, rotations begin to break down.

If you use a matrix as 3D orientation representation, then you have 9 values for 3 degrees of freedom. That means that there are 6 constraints needed. These 6 constraints are expressed by the so-called ortho-normality feature of the matrix, meaning that all 3 column/row vectors are of length 1 and are pairwise orthogonal. Due to numerical imprecisions the matrix will deviate from ortho-normality, and this becomes more and more evident the more operations are applied. You seem me to suffer from this problem. To overcome this, you need to apply an operation that restores ortho-normality frequently. In the given case this means to re-construct 2 of the direction vectors by using the cross-product, and to normalize all 3 vectors at the end.

 

Some alternative representations use less values and hence need less constraints, and so are less vulnerable. For example, a unit-quaternion uses 4 values and 1 constraint (just its unit length). Euler angles use 3 values and 0 constraints. However, such representations need to be converted into the matrix form in the end.

 

 

Another issue: Within your InputController class, you have the following lines of code:


this->renderer->GetCamera().RotateAxisAngle(view.Right(), mouseDamping*this->mouseDeltaY);
this->renderer->GetCamera().RotateAxisAngle(view.Up(), mouseDamping*this->mouseDeltaX);

This shows a hapless coupling of the InputController to the Renderer, just for the purpose to locate a Camera instance. An InputController should be agnostic of Renderer.

Share this post


Link to post
Share on other sites
Irlan    4067

this->renderer->GetCamera().RotateAxisAngle(view.Right(), mouseDamping*this->mouseDeltaY);
this->renderer->GetCamera().RotateAxisAngle(view.Up(), mouseDamping*this->mouseDeltaX);

 

 

Assuming you're a beginner/intermediate on the subject, it's not fair to give you such advices, but it's better start seeing which things are correct and which aren't.

 

If your "renderer" has the same functionality of a scene, you're correct about giving it an instance of the camera or camera pointers. But the word "renderer" remind us a software-side renderer—more specifically a interface with your current graphics API or even a in-house rasterizer. For a start step, you can rename it to "scene" or related—to distinguish between things that are high-level and things that are low-level in your application.

 

Your InputController it's flawed. Input in a game it's nothing more than a bunch of data that you request at specific point during the game logical update. This it's too advanced to you—assuming you're a beginning. In your case, the InputController it's a game state, so it's better idea searching for state machines.

 

A camera in that case should be nothing more than a normal entity with a CTransform/COrientation as I said before, and these classes can have functionality such yaw, pitch, and roll operations, etc.—including rotation operations, etc. The camera that will be used in the current frame gets updated each frame before rendering—both view and projection matrices are re-computed if necessary; that is, before you pass its data to the renderer, that passes the matrices to the shader, etc, you need to recompute its data using its CTransform/COrientation. That said, here is an example of hou you can interface with a camera object.

class CTransform {
public :
      //Yaw( float _fRadians );
      //Pitch(...);
      //Roll(...);
      //void RotateAxis( const CVector3& _vAxis, float _fRadians );
protected :
      CVector3 m_vRight;
      CVector3 m_vUp;
      CVector3 m_vForward;
      CVector3 m_vPos;
      CVector3 m_vScale;
};

class CCamera {
public :
      const CTransform& GetTransform() const { return m_tTransform; }
      CTransform& GetTransform() { return m_tTransform; } //Used for movement and rotation.

      void Update() { //This gets called each frame (before rendering).
           m_tTransform.Normalize(); //Normalize all unit vectors.
           m_mView.LookAt( m_tTransform.GetPos(), m_tTransform.GetPos() + m_tTransform.GetForward(), m_tTransform.GetUp() );
           //(...)
      } 
protected :
      CTransform m_tTransform;
      CMatrix4x4 m_mView;
      CMatrix4x4 m_mProj;
      CMatrix4x4 m_mViewProj;
};

class CScene {
public :
      void Render( CCamera* _pcCam ) const {
           _pcCam->Update();
      }
};
Edited by Irlan

Share this post


Link to post
Share on other sites
JohnnyCode    1046

 

case MOTION_STATE_LEFT:
        this->renderer->GetCamera().Translate(view.Left() * dist);
        break;

I do not see what the left() function returns, but you create view matrix by hand

 

this->view = Matrix::Identity();
    this->view.m[3][0] = -this->translation.x;
    this->view.m[3][1] = -this->translation.y;
    this->view.m[3][2] = -this->translation.z;
    this->view *= this->rotation;
        
    
this->invView = this->view.Invert();

, and before you invert it, you can consider the columns of the 3x3 rotation part as unit orthonormal base vectors of the rotated view space. cross product them, ivert them, and so on, to find out all your  movement desired vectors in world space(front/back/left/right/...

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this