Flying camera using glm is acting weird - c++

I´m working on a Vulkan application and want to implement a "flying camera" which can move around anywhere freely.
I placed a cube in the middle of the scene that I can fly around.
The camera position works perfectly but the rotation acts weirdly as soon as I move around the cube.
When I´m on the opposite side of the cube the UP and DOWN directions that I give via my mouse are inverted and when I´m on either side of the cube they just don´t work at all. Anywhere between that it just does weird circles. Note that this only affects up and down movements not left or right.
Here is a demonstration of how it looks for me where I only move my mouse up and down in this order on every side of the cube (except when moving around it) I apologize for the bad frame rate, i had to convert it to gif and lower the quality:
https://imgur.com/HxknkQV
Quick explanation of the video:
Firstly, while it might have looked like I moved my mouse left and right while not moving looking at the sides, I didn´t. I only went up and down every time except for when I moved into the positions. The other thing is that while for the opposite site of the cube it might have looked like the rotation worked, it was actually inverted.
This is the code for my Camera:
#pragma once
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtx/rotate_vector.hpp>
class Camera {
private:
glm::mat4 _model;
glm::mat4 _view;
glm::mat4 _projection;
glm::vec3 _position;
glm::vec3 _up;
glm::vec3 _moveSpeed = glm::vec3(0.08f);
float _mouseSens = 0.005f;
public:
glm::vec3 _direction;
enum MovementType { FORWARD, BACKWARD, LEFT, RIGHT, UP, DOWN };
Camera(uint32_t width, uint32_t height){
_model = glm::mat4(1.0f);
_projection = glm::perspective(glm::radians(90.0f), width / (float)height, 0.01f, 100.0f);
_direction = glm::vec3(-2, 0, 0);
_up = glm::vec3(0.0f, 0.0f, 1.0f);
_position = glm::vec3(2.0f, 0.0f, 0.0f);
_projection[1][1] *= -1; //Because Vulkan uses different axis, hence the up Vector being different to an OpenGL application for example
}
void rotate(float amount, glm::vec3 axis){
_direction = glm::rotate(_direction, amount * _mouseSens, axis);
}
void move(MovementType movement) {
switch (movement)
{
case FORWARD:
_position += _direction * _moveSpeed;
break;
case BACKWARD:
_position -= _direction * _moveSpeed;
break;
case LEFT:
_position -= glm::normalize(glm::cross(_direction, _up)) * _moveSpeed;
break;
case RIGHT:
_position += glm::normalize(glm::cross(_direction, _up)) * _moveSpeed;
break;
case UP:
_position += _up * _moveSpeed;
break;
case DOWN:
_position -= _up * _moveSpeed;
break;
}
}
glm::mat4 getMVP() {
_view = glm::lookAt(_position, _position + _direction, _up);
return _projection * _view * _model;
}
};
Any help would be gladly appreciated as I am not really that good in Vector and Matrix calculations, and really don´t know how to fix this. Thanks.

It looks to me as if you were rotating the camera in world space (but I can't tell for sure because the code that invokes Camera::rotate is not included in your question).
If my assumption is correct, rotating in camera space should solve the problem. I.e. assuming that Camera::rotate performs a rotation relative to the axes of the current camera's space, you'll have to transform that back into world space, which can be done with the inverse of _view:
void rotate(float amount, glm::vec3 axis){
auto directionCamSpace = glm::rotate(_direction, amount * _mouseSens, axis);
_directionWorldSpace = glm::mat3(glm::inverse(_view)) * _direction;
}
And then use _directionWorldSpace with glm::lookAt:
glm::mat4 getMVP() {
_view = glm::lookAt(_position, _position + _directionWorldSpace, _up);
return _projection * _view * _model;
}
I am afraid that it might be that this does not lead you to the final solution of your problem yet and that further/other artefacts occur, but it should at least get you one step further.
The best way to implement such a camera would probably be to use quaternions to track the rotation of the camera, rotate the camera's coordinate system with the accumulated quaternion rotations and then use glm::inverse to compute the view matrix from the rotated camera's coordinate system. (You wouldn't need glm::lookAt at all with that approach.)

Related

Camera rotation and orientation in variable gravity

I'm attempting to implement a camera controller for a first person, mouse-look based camera for OpenGL. This is a simple problem when the camera is always oriented normally (camera up vector = world Y axis). However, I'm having real trouble getting everything working properly with a camera that can be used seamlessly for any orientation. The purpose is to allow a player to move around an entire planet. An additional requirement is that the direction remain the same relative to the orientation as the camera's orientation changes. An example would be, if you're walking around a planet, the direction remains the same relative to the ground, so as you go "down" along the side from a pole, the direction is also automatically rotated.
So far, I've attempted a number of different things to get this working, but as I see it, there should be two different ways of doing this. The first is to do regular camera rotation based on yaw and pitch angles from the world axes, and then transform the resulting look direction by the camera orientation to obtain the final look direction. The second approach is to rotate the camera with yaw and pitch angles based on calculated up and right vectors. The up vector is easy here; it's just the orientation. I haven't gotten any right vector I've found to work correctly though.
OK, here's the code for these two approaches.
Common code
// m_orientation calculated from planet center to current position
m_horizontal += horizontal;
m_vertical += vertical;
while (m_horizontal > TWO_PI) {
m_horizontal -= TWO_PI;
}
while (m_horizontal < -TWO_PI) {
m_horizontal += TWO_PI;
}
if (m_vertical > MAX_VERTICAL) {
m_vertical = MAX_VERTICAL;
}
else if (m_vertical < -MAX_VERTICAL) {
m_vertical = -MAX_VERTICAL;
}
// code from either implementation
m_view = glm::lookAt(m_position, m_position + m_direction, m_orientation);
First approach with yaw, pitch about world axes and then transform
// check for m_orientation != WORLD_UP...
glm::vec3 axis = glm::normalize(glm::cross(WORLD_UP, m_orientation));
float angle_degrees = acosf(m_orientation.y) * RADS_TO_DEGREES;
glm::mat4 trans = glm::rotate(glm::mat4(), angle_degrees, axis);
// can also be determined with two rotation matrices about world axes, end result is identical
m_direction = glm::vec3(cosf(m_vertical) * sinf(m_horizontal),
sinf(m_vertical),
cosf(m_vertical) * cosf(m_horizontal));
m_direction = glm::vec3(trans * glm::vec4(m_direction));
Second approach with yaw and pitch about appropriate up and right vectors
m_right = ??? // tried literally everything
glm::mat4 yaw = glm::rotate(glm::mat4(), m_horizontal, m_orientation);
glm::mat4 pitch = glm::rotate(glm::mat4(), m_vertical, m_right);
glm::mat4 trans = yaw * pitch;
m_direction = glm::vec3(trans[2]); // z axis
OK, so here's the problem. The first approach works almost perfectly, but near the south pole of a planet (within ~15 degrees of orientation=(0,-1,0), effect gets stronger closer you are), the camera is automatically rotated toward the south pole as the orientation changes. So if the camera orientation does not change, near the south pole, the camera works perfectly. Any change in orientation results in the camera rotating toward the south pole. The more orientation change, the more the camera rotates. Now I have tried removing either the pitch or yaw from the world axis camera rotation, and this effect appears only with the pitch calculation included. With only yaw, then the camera behaves perfectly (lacking any pitch control ofc). As far as I can tell, my transformation to go from regular up=(0,1,0) to the current orientation is incorrect. Any help on that?
Now the other way to do things appears to work somewhat correctly, but I simply have not found a good right vector. Everything I've tried results in strange behavior of both horizontal and vertical movements. The most obvious solution, cross product of previous frame's direction and current orientation to produce the right vector doesn't work. Any suggestions for a good right vector?
I'm also happy to see completely different solutions to this problem. I know it's possible, but no amount of searching has given me a good solution. Thanks very much in advance.
Edit 1: Tried a few more things in response to Paweł Stawarz
Results in incorrect orientation of camera and weird mouse movement. I made sure my matrix multiplication was in the correct order. I also tried the transpose.
m_view = glm::lookAt(glm::vec3(0.0f, 0.0f, 0.0f), m_direction, m_up);
m_view = trans * m_view; //trans is rotation from orientation=(0,1,0) to orientation=m_orientation
Results in the same problem as previously, with the camera rotating toward the south pole by itself. Also the vertical mouse rotation is not correct, causes camera to go in circles.
m_view = glm::lookAt(glm::vec3(0.0f, 0.0f, 0.0f), m_direction, m_up);
m_view = trans * m_view;
m_direction = glm::vec3(m_view[2]);
m_view = glm::lookAt(m_position, m_direction + m_position, m_orientation);
Edit 2: Using the RIGHT vector method, with no transformation between orientations is working a little better. However, it causes the camera yaw to oscillate wildly with pitch near to vertical (at least 5 degrees away from vertical). In addition, the range of motion for pitch is not adjusted by the orientation, so for example on the side of the planet, vertical motion is restricted to directly in front of you to behind you (~(0,1,0) to ~(0,-1,0)).
glm::mat4 yaw = glm::rotate(glm::mat4(), m_horizontal * ONEEIGHTY_PI, m_orientation);
glm::mat4 pitch = glm::rotate(glm::mat4(), m_vertical * -ONEEIGHTY_PI, m_right);
glm::mat4 cam = pitch * yaw;
m_right = glm::vec3(cam[0]);
m_up = glm::vec3(cam[1]);
m_direction = glm::vec3(cam[2]);
m_view = glm::lookAt(m_position, m_direction + m_position, m_up);
m_vp = m_perspective * m_view;
Solved it. Needed a different transformation. See here for a pretty good explanation.
glm::mat4 trans;
float factor = 1.0f;
float real_vertical = vertical;
m_horizontal += horizontal;
m_vertical += vertical;
while (m_horizontal > TWO_PI) {
m_horizontal -= TWO_PI;
}
while (m_horizontal < -TWO_PI) {
m_horizontal += TWO_PI;
}
if (m_vertical > MAX_VERTICAL) {
m_vertical = MAX_VERTICAL;
}
else if (m_vertical < -MAX_VERTICAL) {
m_vertical = -MAX_VERTICAL;
}
glm::quat world_axes_rotation = glm::angleAxis(m_horizontal * ONEEIGHTY_PI, glm::vec3(0.0f, 1.0f, 0.0f));
world_axes_rotation = glm::normalize(world_axes_rotation);
world_axes_rotation = glm::rotate(world_axes_rotation, m_vertical * ONEEIGHTY_PI, glm::vec3(1.0f, 0.0f, 0.0f));
m_pole = glm::normalize(m_pole - glm::dot(m_orientation, m_pole) * m_orientation);
glm::mat4 local_transform;
local_transform[0] = glm::vec4(m_pole.x, m_pole.y, m_pole.z, 0.0f);
local_transform[1] = glm::vec4(m_orientation.x, m_orientation.y, m_orientation.z, 0.0f);
glm::vec3 tmp = glm::cross(m_pole, m_orientation);
local_transform[2] = glm::vec4(tmp.x, tmp.y, tmp.z, 0.0f);
local_transform[3] = glm::vec4(m_position.x, m_position.y, m_position.z, 1.0f);
world_axes_rotation = glm::normalize(world_axes_rotation);
m_view = local_transform * glm::mat4_cast(world_axes_rotation);
m_direction = -1.0f * glm::vec3(m_view[2]);
m_up = glm::vec3(m_view[1]);
m_right = glm::vec3(m_view[0]);
m_view = glm::inverse(m_view);
If we keep things simple, by using the standard approach:
Move the camera to the current player position
Rotate it towards where the player is looking at
The camera rotation is described by:
The UP vector which is the normalized vector that starts at (p0x,p0y,p0z) (where p0 is the position of the center of planet) and goes thru (p1x,p1y,p1z) (where p1 describes the place the player is currently at),
the RIGHT vector is the vector perpendicular to the UP vector and perpendicular to the direction the player is looking - the LOOK vector (in the case where he's looking straight ahead - perpendicular to his direction).
Since the UP vector can be calulated straight from the player current position, you have to get the LOOK and RIGHT vectors. Both are cross products of corresponding other vectors.
Note also, that allowing the player to look up/down and pan his head, can (and probably will) in fact change the UP vector.

Window coordinates to camera angles?

So I want to use quaternions and angles to control my camera using my mouse.
I accumulate the vertical/horizontal angles like this:
void Camera::RotateCamera(const float offsetHorizontalAngle, const float offsetVerticalAngle)
{
mHorizontalAngle += offsetHorizontalAngle;
mHorizontalAngle = std::fmod(mHorizontalAngle, 360.0f);
mVerticalAngle += offsetVerticalAngle;
mVerticalAngle = std::fmod(mVerticalAngle, 360.0f);
}
and compute my orientation like this:
Mat4 Camera::Orientation() const
{
Quaternion rotation;
rotation = glm::angleAxis(mVerticalAngle, Vec3(1.0f, 0.0f, 0.0f));
rotation = rotation * glm::angleAxis(mHorizontalAngle, Vec3(0.0f, 1.0f, 0.0f));
return glm::toMat4(rotation);
}
and the forward vector, which I need for glm::lookAt, like this:
Vec3 Camera::Forward() const
{
return Vec3(glm::inverse(Orientation()) * Vec4(0.0f, 0.0f, -1.0f, 0.0f));
}
I think that should do the trick, but I do not know how in my example game to get actual angles? All I have is the current and previous mouse location in window coordinates.. how can I get proper angles from that?
EDIT: on a second thought.. my "RotateCamera()" cant be right; I am experiencing rubber-banding effect due to the angles reseting after reaching 360 deegres... so how do I accumulate angles properly? I can just sum them up endlessly
Take a cross section of the viewing frustum (the blue circle is your mouse position):
Theta is half of your FOV
p is your projection plane distance (don't worry - it will cancel out)
From simple ratios it is clear that:
But from simple trignometry
So ...
Just calculate the angle psi for each of your mouse positions and subtract to get the difference.
A similar formula can be found for the vertical angle:
Where A is your aspect ratio (width / height)

GLM Vector Mathematics

I have a cube rendered on the screen which represents a car (or similar).
Using Projection/Model matrices and Glm I am able to move it back and fourth along the axes and rotate it left or right.
I'm having trouble with the vector mathematics to make the cube move forwards no matter which direction it's current orientation is. (ie. if I would like, if it's rotated right 30degrees, when it's move forwards, it travels along the 30degree angle on a new axes).
I hope I've explained that correctly.
This is what I've managed to do so far in terms of using glm to move the cube:
glm::vec3 vel; //velocity vector
void renderMovingCube(){
glUseProgram(movingCubeShader.handle());
GLuint matrixLoc4MovingCube = glGetUniformLocation(movingCubeShader.handle(), "ProjectionMatrix");
glUniformMatrix4fv(matrixLoc4MovingCube, 1, GL_FALSE, &ProjectionMatrix[0][0]);
glm::mat4 viewMatrixMovingCube;
viewMatrixMovingCube = glm::lookAt(camOrigin, camLookingAt, camNormalXYZ);
vel.x = cos(rotX); vel.y=sin(rotX);
vel*=moveCube;
//move cube
ModelViewMatrix = glm::translate(viewMatrixMovingCube,globalPos*vel);
//bring ground and cube to bottom of screen
ModelViewMatrix = glm::translate(ModelViewMatrix, glm::vec3(0,-48,0));
ModelViewMatrix = glm::rotate(ModelViewMatrix, rotX, glm::vec3(0,1,0)); //manually turn
glUniformMatrix4fv(glGetUniformLocation(movingCubeShader.handle(), "ModelViewMatrix"), 1, GL_FALSE, &ModelViewMatrix[0][0]); //pass matrix to shader
movingCube.render(); //draw
glUseProgram(0);
}
keyboard input:
void keyboard()
{
char BACKWARD = keys['S']; char FORWARD = keys['W'];
char ROT_LEFT = keys['A']; char ROT_RIGHT = keys['D'];
if (FORWARD) //W - move forwards
{
globalPos += vel;
//globalPos.z -= moveCube;
BACKWARD = false;
}
if (BACKWARD)//S - move backwards
{
globalPos.z += moveCube;
FORWARD = false;
}
if (ROT_LEFT)//A - turn left
{
rotX +=0.01f;
ROT_LEFT = false;
}
if (ROT_RIGHT)//D - turn right
{
rotX -=0.01f;
ROT_RIGHT = false;
}
Where am I going wrong with my vectors? I would like change the direction of the cube (which it does) but then move forwards in that direction.

Issue with GLM Camera X,Y Rotation introducing Z Rotation

So I've been having trouble with a camera I've implemented in OpenGL and C++ using the GLM library. The type of camera I'm aiming for is a fly around camera which will allow easy exploration of a 3D world. I have managed to get the camera pretty much working, it's nice and smooth, looks around and the movement seems to be nice and correct.
The only problem I seem to have is that the rotation along the camera's X and Y axis (looking up and down) introduces some rotation about it's Z axis. This has the result of causing the world to slightly roll whilst travelling about.
As an example... if I have a square quad in front of the camera and move the camera in a circular motion, so as if looking around in a circle with your head, once the motion is complete the quad will have rolled slightly as if you've tilted your head.
My camera is currently a component which I can attach to an object/entity in my scene. Each entity has a "Frame" which is basically the model matrix for that entity. The Frame contains the following attributes:
glm::mat4 m_Matrix;
glm::vec3 m_Position;
glm::vec3 m_Up;
glm::vec3 m_Forward;
These are then used by the camera to create the appropriate viewMatrix like this:
const glm::mat4& CameraComponent::GetViewMatrix()
{
//Get the transform of the object
const Frame& transform = GetOwnerGO()->GetTransform();
//Update the viewMatrix
m_ViewMatrix = glm::lookAt(transform.GetPosition(), //position of camera
transform.GetPosition() + transform.GetForward(), //position to look at
transform.GetUp()); //up vector
//return reference to the view matrix
return m_ViewMatrix;
}
And now... here are my rotate X and Y methods within the Frame object, which I'm guessing is the place of the problem:
void Frame::RotateX( float delta )
{
glm::vec3 cross = glm::normalize(glm::cross(m_Up, m_Forward)); //calculate x axis
glm::mat4 Rotation = glm::rotate(glm::mat4(1.0f), delta, cross);
m_Forward = glm::normalize(glm::vec3(Rotation * glm::vec4(m_Forward, 0.0f))); //Rotate forward vector by new rotation
m_Up = glm::normalize(glm::vec3(Rotation * glm::vec4(m_Up, 0.0f))); //Rotate up vector by new rotation
}
void Frame::RotateY( float delta )
{
glm::mat4 Rotation = glm::rotate(glm::mat4(1.0f), delta, m_Up);
//Rotate forward vector by new rotation
m_Forward = glm::normalize(glm::vec3(Rotation * glm::vec4(m_Forward, 0.0f)));
}
So somewhere in there, there's a problem which I've been searching around trying to fix. I've been messing with it for a few days now, trying random things but I either get the same result, or the z axis rotation is fixed but other bugs appear such as incorrect X, Y rotation and camera movement.
I had a look at gimbal lock but from what I understood of it, this problem didn't seem quite like gimbal lock to me. But I may be wrong.
Store the current pitch/yaw angles and generate the camera matrix on-the-fly instead of trying to accumulate small changes on the intermediate vectors.
In your RotateY function, change it from this:
glm::mat4 Rotation = glm::rotate(glm::mat4(1.0f), delta, m_Up);
to this:
glm::mat4 Rotation = glm::rotate(glm::mat4(1.0f), delta, glm::vec3(0,1,0));

OpenGL camera - View matrix from quaternion behaves incorrectly and pitch is too restricted

I'm creating the view matrix for my camera using its current orientation (quaternion) and its current position.
void Camera::updateViewMatrix()
{
view = glm::gtx::quaternion::toMat4(orientation);
// Include rotation (Free Look Camera)
view[3][0] = -glm::dot(glm::vec3(view[0][0], view[0][1], view[0][2]), position);
view[3][1] = -glm::dot(glm::vec3(view[1][0], view[1][1], view[1][2]), position);
view[3][2] = -glm::dot(glm::vec3(view[2][0], view[2][1], view[2][2]), position);
// Ignore rotation (FPS Camera)
//view[3][0] = -position.x;
//view[3][1] = -position.y;
//view[3][2] = -position.z;
view[3][3] = 1.0f;
}
There is a problem with this in that I do not believe the quaternion to matrix calculation is giving the correct answer. Translating the camera works as expected but rotating it causes incorrect behavior.
I am rotating the camera using the difference between the current mouse position and the the centre of the screen (resetting the mouse position each frame)
int xPos;
int yPos;
glfwGetMousePos(&xPos, &yPos);
int centreX = 800 / 2;
int centreY = 600 / 2;
rotate(xPos - centreX, yPos - centreY);
// Reset mouse position for next frame
glfwSetMousePos(800 / 2, 600 / 2);
The rotation takes place in this method
void Camera::rotate(float yawDegrees, float pitchDegrees)
{
// Apply rotation speed to the rotation
yawDegrees *= lookSensitivity;
pitchDegrees *= lookSensitivity;
if (isLookInverted)
{
pitchDegrees = -pitchDegrees;
}
pitchAccum += pitchDegrees;
// Stop the camera from looking any higher than 90 degrees
if (pitchAccum > 90.0f)
{
//pitchDegrees = 90.0f - (pitchAccum - pitchDegrees);
pitchAccum = 90.0f;
}
// Stop the camera from looking any lower than 90 degrees
if (pitchAccum < -90.0f)
{
//pitchDegrees = -90.0f - (pitchAccum - pitchDegrees);
pitchAccum = -90.0f;
}
yawAccum += yawDegrees;
if (yawAccum > 360.0f)
{
yawAccum -= 360.0f;
}
if (yawAccum < -360.0f)
{
yawAccum += 360.0f;
}
float yaw = yawDegrees * DEG2RAD;
float pitch = pitchDegrees * DEG2RAD;
glm::quat rotation;
// Rotate the camera about the world Y axis (if mouse has moved in any x direction)
rotation = glm::gtx::quaternion::angleAxis(yaw, 0.0f, 1.0f, 0.0f);
// Concatenate quaterions
orientation = orientation * rotation;
// Rotate the camera about the world X axis (if mouse has moved in any y direction)
rotation = glm::gtx::quaternion::angleAxis(pitch, 1.0f, 0.0f, 0.0f);
// Concatenate quaternions
orientation = orientation * rotation;
}
Am I concatenating the quaternions correctly for the correct orientation?
There is also a problem with the pitch accumulation in that it restricts my view to ~±5 degrees rather than ±90. What could be the cause of that?
EDIT:
I have solved the problem for the pitch accumulation so that its range is [-90, 90]. It turns out that glm uses degrees and not vectors for axis angle and the order of multiplication for the quaternion concatenation was incorrect.
// Rotate the camera about the world Y axis
// N.B. 'angleAxis' method takes angle in degrees (not in radians)
rotation = glm::gtx::quaternion::angleAxis(yawDegrees, 0.0f, 1.0f, 0.0f);
// Concatenate quaterions ('*' operator concatenates)
// C#: Quaternion.Concatenate(ref rotation, ref orientation)
orientation = orientation * rotation;
// Rotate the camera about the world X axis
rotation = glm::gtx::quaternion::angleAxis(pitchDegrees, 1.0f, 0.0f, 0.0f);
// Concatenate quaterions ('*' operator concatenates)
// C#: Quaternion.Concatenate(ref orientation, ref rotation)
orientation = rotation * orientation;
The problem that remains is that the view matrix rotation appears to rotate the drawn object and not look around like a normal FPS camera.
I have uploaded a video to YouTube to demonstrate the problem. I move the mouse around to change the camera's orientation but the triangle appears to rotate instead.
YouTube video demonstrating camera orientation problem
EDIT 2:
void Camera::rotate(float yawDegrees, float pitchDegrees)
{
// Apply rotation speed to the rotation
yawDegrees *= lookSensitivity;
pitchDegrees *= lookSensitivity;
if (isLookInverted)
{
pitchDegrees = -pitchDegrees;
}
pitchAccum += pitchDegrees;
// Stop the camera from looking any higher than 90 degrees
if (pitchAccum > 90.0f)
{
pitchDegrees = 90.0f - (pitchAccum - pitchDegrees);
pitchAccum = 90.0f;
}
// Stop the camera from looking any lower than 90 degrees
else if (pitchAccum < -90.0f)
{
pitchDegrees = -90.0f - (pitchAccum - pitchDegrees);
pitchAccum = -90.0f;
}
// 'pitchAccum' range is [-90, 90]
//printf("pitchAccum %f \n", pitchAccum);
yawAccum += yawDegrees;
if (yawAccum > 360.0f)
{
yawAccum -= 360.0f;
}
else if (yawAccum < -360.0f)
{
yawAccum += 360.0f;
}
orientation =
glm::gtx::quaternion::angleAxis(pitchAccum, 1.0f, 0.0f, 0.0f) *
glm::gtx::quaternion::angleAxis(yawAccum, 0.0f, 1.0f, 0.0f);
}
EDIT3:
The following multiplication order allows the camera to rotate around its own axis but face the wrong direction:
glm::mat4 translation;
translation = glm::translate(translation, position);
view = glm::gtx::quaternion::toMat4(orientation) * translation;
EDIT4:
The following will work (applying the translation matrix based on the position after then rotation)
// Rotation
view = glm::gtx::quaternion::toMat4(orientation);
// Translation
glm::mat4 translation;
translation = glm::translate(translation, -position);
view *= translation;
I can't get the dot product with each orientation axis to work though
// Rotation
view = glm::gtx::quaternion::toMat4(orientation);
glm::vec3 p(
glm::dot(glm::vec3(view[0][0], view[0][1], view[0][2]), position),
glm::dot(glm::vec3(view[1][0], view[1][1], view[1][2]), position),
glm::dot(glm::vec3(view[2][0], view[2][1], view[2][2]), position)
);
// Translation
glm::mat4 translation;
translation = glm::translate(translation, -p);
view *= translation;
In order to give you a definite answer, I think that we would need the code that shows how you're actually supplying the view matrix and vertices to OpenGL. However, the symptom sounds pretty typical of incorrect matrix order.
Consider some variables:
V represents the inverse of the current orientation of the camera (the quaternion).
T represents the translation matrix holding the position of the camera. This should be an identity matrix with negation of the camera's position going down the fourth column (assuming that we're right-multiplying column vectors).
U represents the inverse of the change in orientation.
p represents a vertex in world space.
Note: all of the matrices are inverse matrices because the transformations will be applied to the vertex, not the camera, but the end result is the same.
By default the OpenGL camera is at the origin looking down the negative-z axis. When the view isn't changing (U==I), then the vertex's transformation from world coordinates to camera coordinates should be: p'=TVp. You first orient the camera (by rotating the world in the opposite direction) and then translate the camera into position (by shifting the world in the opposite direction).
Now there are a few places to put U. If we put U to the right of V, then we get the behavior of a first-person view. When you move the mouse up, whatever is currently in view rotates downward around the camera. When you move the mouse right, whatever is in view rotates to the left around the camera.
If we put U between T and V, then the camera turns relative to the world's axes instead of the camera's. This is strange behavior. If V happens to turn the camera off to the side, then moving the mouse up and down will make the world seem to 'roll' instead of 'pitch' or 'yaw'.
If we put U left of T, then the camera rotates around the world's axes around the world's origin. This can be even stranger because it makes the camera fly through world faster the farther the camera is from the origin. However, because the rotation is around the origin, if the camera happens to be looking at the origin, objects there will just appear to be turning around. This is sort of what you're seeing because of the dot-products that you're taking to rotate the camera's position.
You check to make sure that pitchAccum stays within [-90,90], but you've commented out the portion that would make use of that fact. This seems odd to me.
The way that you left-multiply pitch but right-multiply yaw makes it so that your quaternions aren't doing much for you. They're just holding your Euler angles. Unless orientation changes are coming in from other places, you could simply say that orientation = glm::gtx::quaternion::angleAxis(pitchAccum*DEG2RAD, 1.0f, 0.0f, 0.0f) * glm::gtx::quaternion::angleAxis(yawAccum*DEG2RAD, 0.0f, 1.0f, 0.0f); and overwrite the old orientation completely.
From what I understand in this tutorial, there might be a reason why pitch angle is restricted at 90 degrees.
Regardless of using quaternions or a look at matrix, at the end, we give an initial orientation to the Camera. In quaternions, this is the initial value of the orientation, in lookAt, it is the initial value of the up vector.
If the direction facing towards the camera is parallel to this initial vector, then the cross product of these will be zero, which means the camera might have any orientation if pitch is 90 or -90 degrees.
In the internal implementation of toMat4(orientation) this would result in one of your x_dir/y_dir/z_dir vectors to be a zero vector, which would mean that your can have any orientation. This is also discussed in this book, which says that if Y angle is 90 degrees, a degree of freedom is lost (Edward Angel and Dave Shreiner, Interactive Computer Graphics, A Top-Down Approach with WebGL, Seventh Edition, Addison-Wesley 2015.), which is discussed as Gimbal Lock.
I can see that you are aware of this problem, but in your code, the yaw angle is still set to 90 degrees if it overflows 90, leaving your Camera in an invalid state. You should consider something like this instead:
if (pitchAccum > 89.999f && pitchAccum <= 90.0f)
{
pitchAccum = 90.001f;
}
else if (pitchAccum < -89.999f && pitchAccum >= -90.0f)
{
pitchAccum = -90.001f;
}
if (pitchAccum >= 360.0f)
{
pitchAccum = 0.0f;
}
else if (pitchAccum <= -360.0f)
{
pitchAccum = 0.0f;
}
Or you can define another custom action of your choice when pitchAccum is 90 degrees.