Issues with FPS Camera

Started by
6 comments, last by peter1745 4 years ago

Hello! I'm currently working on a 3D camera, and I have it mostly working but I'm having a small issue with rotations.

Basically, when the I rotate the camera to the left, and move forwards, the camera moves towards the right instead of to the left, and vice versa for the right side.

I'm using glm as a maths library, and their quaternions for representing the rotation.

This is the code I use to move the camera.

const float movementSpeed = 200.0F;
const float mouseSensitivity = 10.0F;

m_Camera.Rotate(glm::vec3(0.0F, -Napalm::Input::GetDeltaX() * mouseSensitivity * (float)ts, 0.0F));

glm::vec3 rotation = m_Camera.GetRotationRadians();

glm::vec3 translation(0.0F);

if (Napalm::Input::IsKeyPressed(KEY_W))
{
	translation.x = glm::sin(rotation.y) * 0.2F;
	translation.z = -glm::cos(rotation.y) * 0.2F;
}
else if (Napalm::Input::IsKeyPressed(KEY_S))
{
	translation.x = -glm::sin(rotation.y) * 0.2F;
	translation.z = glm::cos(rotation.y) * 0.2F;
}

if (Napalm::Input::IsKeyPressed(KEY_D))
{
	translation.x = glm::cos(rotation.y) * 0.2F;
	translation.z = glm::sin(rotation.y) * 0.2F;
}
else if (Napalm::Input::IsKeyPressed(KEY_A))
{
	translation.x = -glm::cos(rotation.y) * 0.2F;
	translation.z = -glm::sin(rotation.y) * 0.2F;
}

m_Camera.Move(translation);

And here's the actual camera code:

void Camera::SetRotation(const glm::vec3& degrees)
{
	m_DegreesRotation = degrees;
	m_Rotation = glm::quat(glm::radians(degrees));
	RecalculateViewMatrix();
}

void Camera::Rotate(const glm::vec3& degrees)
{
	m_DegreesRotation += degrees;
	m_Rotation = glm::quat(glm::radians(m_DegreesRotation));
	RecalculateViewMatrix();
}

void Camera::RecreateProjectionMatrix()
{
	const Window& window = Application::Get().GetWindow();
	m_ProjectionMatrix = glm::perspectiveFov(glm::radians(m_FieldOfView), (float)window.GetWidth(), (float)window.GetHeight(), 0.1F, 1000.0F);
	m_ViewProjectionMatrix = m_ProjectionMatrix * m_ViewMatrix;
}

void Camera::RecalculateViewMatrix()
{
	glm::mat4 transform = glm::translate(glm::mat4(1.0F), m_Position) * glm::toMat4(m_Rotation) * glm::mat4(1.0F);
	m_ViewMatrix = glm::inverse(transform);
	m_ViewProjectionMatrix = m_ProjectionMatrix * m_ViewMatrix;
}

void SetPosition(const glm::vec3& position) { m_Position = position; RecalculateViewMatrix(); }
		void Move(const glm::vec3& position) { m_Position += position; RecalculateViewMatrix(); }

		inline const glm::vec3& GetRotationDegrees() const { return glm::degrees(glm::eulerAngles(m_Rotation)); }
		inline const glm::vec3& GetRotationRadians() const { return glm::eulerAngles(m_Rotation); }

Any help is appreciated!

Advertisement

Sounds you need to inverse the rotation, so it goes camera to world instead world to camera.

In last line of your code:

inline const glm::vec3& GetRotationRadians() const { return glm::eulerAngles( glm::inverse(m_Rotation) ); }

I'm not sure if inverse() works for quaternions, but guess so.

@JoeJ Well, that certainly worked. But you shouldn't have to inverse it, but it works for now. Thanks!

@JoeJ Whilst that solved the immediate problem, my rotation/movement code is still incorrect. When the camera is facing backwards it moves in the completely wrong direction.

@fleabay Most game engines use Quaternions since there's certain advantages to using them, for an example they're easier to interpolate between than just pure euler angles.

Both Unreal and Unity use them, in fact most game engines use them. They're accepted and used by the majority of game developers.

Also, Quaternions are in some cases more space efficient than normal rotations.

Quats are not required for a first person camera, but euler angles are also not required for the movement (only to setup camera orientation). I guess this could cause your incorrectness eventually. It depends on luck if glm uses the same order of rotations you expect it does.

The easier and robuster way is to get movement direction from the camera matrix in world space.

Posting my code, as i also use glm for this stuff:

struct Camera
{
	glm::mat4 world;
	glm::mat4 worldInverse;
	glm::mat4 projection;
	glm::vec3 euler;

	float fov;
	float firstPersonSpeed;
	float firstPersonSensibility;
	float nearClip;
	float farClip;
	float aspect;
	

	void Init (bool fromFile = true)
	{
		fov = 60.0f;
		firstPersonSpeed = 1.0f;
		firstPersonSensibility = 0.1f;
		aspect = 1.0f;
		nearClip = 0.01f;
		farClip = 100.0f;

		world = glm::mat4();
		worldInverse = glm::mat4(); 
		euler = glm::vec3();
		UpdateProjection ();

		UpdateWorld (glm::vec3(1, 0, 7), glm::vec3(0, 0, 0)); // front
	}

	Camera ()
	{
		Init();
		LoadState("camDef.bin");
	}
	
	~Camera ()
	{
		SaveState("camDef.bin");
	}
	void UpdateWorld (const glm::vec3 position, const glm::vec3 orientation)
	{
		glm::vec3 pos = position;
		euler = orientation;
		world  = glm::eulerAngleZ(orientation[2]);
		world *= glm::eulerAngleY(orientation[1]);
		world *= glm::eulerAngleX(orientation[0]);
		world[3] = glm::vec4 (pos, 1.0f);

		worldInverse = glm::inverse (world);
	}

	void MoveFirstPerson (const glm::vec3 translation, glm::vec3 rotation)
	{
		UpdateWorld (
			glm::vec3(world[3] + world * glm::vec4(translation * firstPersonSpeed, 0.0f)), 
			euler + rotation * firstPersonSensibility * 0.01f);
	}

	void UpdateProjection ()
	{
		float fovY = fov / 180.0f * float(M_PI);
		if (fov > 0.0f)
			projection = glm::perspective (fovY, aspect, nearClip, farClip);
		else
			projection = glm::ortho (fov*aspect, -fov*aspect, fov, -fov, -nearClip, farClip);
		projection[1] *= -1.f;
	}

	void SaveState(char *filename)
	{
		FILE* file = fopen(filename, "wb");
		if (file)
		{
			fwrite(this, sizeof(Camera), 1, file);
			fclose(file);
		}
	};
	void LoadState(char *filename)
	{
		FILE* file = fopen(filename, "rb");
		if (file)
		{
			fread(this, sizeof(Camera), 1, file);
			fclose(file);
		}
	};
};

Using it like this:

	void UserInput (float deltaTime)
	{
		glm::vec3 move (0,0,0);
		glm::vec3 look (0,0,0);

		if (application->KeyDown('W') || application->KeyDown(Application::KEY_UP))	move += glm::vec3 (0.0f, 0.0f, -1.0f);
		if (application->KeyDown('S') || application->KeyDown(Application::KEY_DOWN))	move += glm::vec3 (0.0f, 0.0f,  1.0f);
		if (application->KeyDown('A') || application->KeyDown(Application::KEY_LEFT))	move += glm::vec3 (-1.0f, 0.0f, 0.0f);
		if (application->KeyDown('D') || application->KeyDown(Application::KEY_RIGHT))	move += glm::vec3 ( 1.0f, 0.0f, 0.0f);
		if (application->KeyDown('E') || application->KeyDown(Application::KEY_PGUP))	move += glm::vec3 (0.0f, -1.0f, 0.0f);
		if (application->KeyDown('C') || application->KeyDown(Application::KEY_PGDOWN)) move += glm::vec3 (0.0f,  1.0f, 0.0f);

		if (application->KeyDown(Application::KEY_RBUTTON) || application->forceMouseCapture) 
		{
			look[1] = -float(application->mousePosX - application->mousePrevX);
			look[0] = -float(application->mousePosY - application->mousePrevY);
		}
		
		if (application->KeyDown(Application::KEY_CTRL)) move *= 0.1f;
		if (application->KeyDown(Application::KEY_SHIFT)) move *= 10.0f;

		
		camera.MoveFirstPerson (move * deltaTime, look);
	}

My camera behaves like flying, so moving forward always moves in the same direction we look at (also up and down).

To move only on ground plane, you would just project the camera forward axis to this plane and normalize.

@JoeJ I'm aware that Quaternions aren't required for cameras, but they tend to be used quite often because they can be less memory intensive.

Currently at work, but I'll take a look at your method when I get home! Thanks!

This topic is closed to new replies.

Advertisement