I'm trying to implement an FPS camera using a quaternion and a translation vector. My camera is parsed from a GLTF file from which I get a position, a lookat point and an up vector.
To get a quaternion and a translation vector from that I use glm::lookAt() and glm::decompose():
glm::mat4x4 lookat = glm::lookAt(camera_position, camera_lookat, camera_up);
glm::vec3 scale, skew, translation;
glm::vec4 perspective;
glm::quat rotation;
glm::decompose(lookat, scale, rotation, translation, skew, perspective);
I can then get the view matrix from the quaternion and vector as well as move my camera with the following functions:
glm::mat4x4 get_view_matrix()
{
return glm::mat4_cast(glm::normalize(rotation)) * glm::translate(glm::mat4(1.0f), translation);
}
glm::vec3 get_view_direction()
{
glm::mat4x4 view_mat = get_view_matrix();
return glm::vec3(view_mat[0][2], view_mat[1][2], view_mat[2][2]);
}
void translate_camera_view(glm::vec3 translation)
{
glm::mat4x4 view_mat = get_view_matrix();
translation = translation + glm::vec3(view_mat[0][0], view_mat[1][0], view_mat[2][0]) * translation.x;
translation = translation + glm::vec3(view_mat[0][1], view_mat[1][1], view_mat[2][1]) * translation.y;
}
void rotate_camera_view(glm::vec3 euler_rotation_angles)
{
glm::quat rotation_x = glm::quat(glm::vec3(euler_rotation_angles.y, 0, 0));
glm::quat rotation_y = glm::quat(glm::vec3(0, -euler_rotation_angles.x, 0));
rotation_x = glm::normalize(rotation_x);
rotation_y = glm::normalize(rotation_y);
rotation = rotation_x * rotation * rotation_y;
}
void zoom_camera_view(float offset)
{
translation = translation + get_view_direction() * offset;
}
With a position of (1, 1, 3), a lookat point of (1, 1, 4) and an up vector of (0, 1, 0) computing glm::vec4 transformed = glm::inverse(get_view_matrix()) * glm::vec4(0, 0, 0, 1); gives me (1, -1, 3, 1) where I guess it should be (1, 1, 3), the same as the camera position.
Reversing the translation/rotation order in get_view_direction():
glm::mat4x4 get_view_matrix()
{
// return glm::mat4_cast(glm::normalize(rotation)) * glm::translate(glm::mat4(1.0f), translation);
return glm::translate(glm::mat4(1.0f), translation) * glm::mat4_cast(glm::normalize(rotation));
}
gives me the correct (1, 1, 3) but then all my translate_camera_view(), rotate_camera_view() and zoom_camera_view() functions are pretty much broken and, for the most part, don't follow the axis of the camera anymore (zooming doesn't zoom in the direction the camera is looking at for example).
I tried inversing the rotation quaternion after glm::decompose() or negating the translation in get_view_matrix() but both didn't give the correct result and I'm not sure I understand why this could even work anyway...
Where is the mistake in my implementation?