Advertisement

Quaternion-based camera unwanted roll

Started by July 25, 2019 07:48 PM
11 comments, last by congard 5 years, 6 months ago

Hello! I created a camera based on quaternions, but when I turn the camera, an unwanted roll appears. I would not like to lose my freedom of movement using, for example, Euler angles, since there is a need to add roll from time to time. If I use Euler angles, then, as far as I know, I can get a gimbal lock.
Code:


struct FreeCamera : public BaseCamera {
    float pitch = 0, yaw = 0, roll = 0;
    
    void updateView();
    
private:
    glm::quat qCamera;
};

struct FreeCameraController: public BaseCameraController {
    float sensitivityPitch = 0.0025f, sensitivityYaw = 0.0025f, sensitivityRoll = 0.0025f;
    
    void mouseMove(const float x, const float y, const float z = 0);
    
    inline void setMousePos(const float x, const float y, const float z = 0) {
        lastMousePos = glm::vec3(x, y, z);
    }
    
private:
    glm::vec3 lastMousePos = glm::vec3(0.0f);
};

void FreeCamera::updateView() {
    // temporary frame quaternion from pitch, yaw, roll
    glm::quat qPYR = glm::quat(glm::vec3(pitch, yaw, roll));
    // reset values
    pitch = yaw = roll = 0;
 
    // update qCamera
    qCamera = qPYR * qCamera;
    qCamera = glm::normalize(qCamera);
    glm::mat4 rotate = glm::mat4_cast(qCamera);
 
    glm::mat4 translate = glm::mat4(1.0f);
    translate = glm::translate(translate, -pos);
    
    view = rotate * translate;
}

void FreeCameraController::mouseMove(const float x, const float y, const float z) {
    glm::vec3 dCoord = glm::vec3(x, y, z) - lastMousePos;
    
    ((FreeCamera*)camera)->yaw = dCoord.x * sensitivityYaw;
    ((FreeCamera*)camera)->pitch = dCoord.y * sensitivityPitch;
    ((FreeCamera*)camera)->roll = dCoord.z * sensitivityRoll;
 
    lastMousePos = glm::vec3(x, y, z);
}

Is it possible to reset unwanted roll? Thanks in advance for the help!

The order in which the euler angle rotations are composed is not consistent with your expectations of camera behavior. You would need to build the quaternions for each of the 3 euler angle rotations individually, then multiply them in the correct order for your axes/euler conventions. Usually you would use R=(Yaw)*(Pitch)*(Roll) for a first-person camera, where R*x rotates x from camera space to world space.

If you always want the camera to have a consistent orientation, you can also fix the up vector to always point up by first forming the R matrix, then replacing the up column vector (usually either Y or Z) with (0,1,0), then orthonormalizing the matrix.

Advertisement
5 hours ago, congard said:

Hello! I created a camera based on quaternions, but when I turn the camera, an unwanted roll appears. I would not like to lose my freedom of movement using, for example, Euler angles, since there is a need to add roll from time to time. If I use Euler angles, then, as far as I know, I can get a gimbal lock.

Whether gimbal lock is a possibility is orthogonal to whether quaternions are used. You can get gimbal lock using quaternions, and avoid gimbal lock without using quaternions. (I don't know if that's a point of confusion here, but I thought I'd mention it.)

This may be a partial repetition of what Aressera said, but it looks to me like your control system is what's sometimes called 6DOF, meaning (among other things) that there's no fixed up and the object can rotate freely. Assuming you mean what I think you mean by 'unwanted roll', that behavior is natural and expected with this kind of control scheme.

If that's not what you want, then you may want a control scheme based on 'from scratch' Euler angles, where the orientation is created from scratch from Euler angles each update (this may be what Aressera was talking about, although I'm not positive). This does introduce the possibility of gimbal lock, but whether gimbal lock is a problem or not depends on the context.

If the replies you've gotten so far don't provide an adequate answer, maybe you could provide some more info about the context (e.g. what type of object are you controlling? a humanoid? a space vehicle? is there a 'fixed up'? is there a ground or terrain? and so on).

5 hours ago, Zakwayda said:

If the replies you've gotten so far don't provide an adequate answer, maybe you could provide some more info about the context (e.g. what type of object are you controlling? a humanoid? a space vehicle? is there a 'fixed up'? is there a ground or terrain? and so on).

I control, for example, a car that can not only drive, but also fly. Ie, when it is in the air, I need a completely free camera, but when it falls to the ground, I need to “stabilize”, “block” roll

6 hours ago, congard said:

I control, for example, a car that can not only drive, but also fly. Ie, when it is in the air, I need a completely free camera, but when it falls to the ground, I need to “stabilize”, “block” roll

Further specification of the desired behavior might be helpful. Here are some questions that come to mind:

- Are you using a physics engine? If not, how are you handling interaction with the ground/terrain/etc.?

- Do you want full 6DOF motion when flying? That is, is the 'unwanted roll' ok when flying, and do you want to allow the vehicle to be able to e.g. turn upside-down? Or while flying, do you want the vehicle to basically remain upright?

- How do you want it to be handled if the vehicle tries to land while at some arbitrary non-upright orientation (e.g. upside-down, nose-first, etc.)?

2 hours ago, Zakwayda said:

Further specification of the desired behavior might be helpful

Yes, during the flight "unwanted roll" is ok. But when the car lands, I would like to reset it, and use something like an FPS camera. I created a simple FPS camera like this:


void FPSCamera::updateView() {
    glm::quat qPitch = glm::angleAxis(pitch, glm::vec3(1, 0, 0));
    glm::quat qYaw = glm::angleAxis(yaw, glm::vec3(0, 1, 0));
    glm::quat qRoll = glm::angleAxis(roll,glm::vec3(0, 0, 1));
    
    glm::quat orientation = qPitch * qYaw * qRoll;
    orientation = glm::normalize(orientation);
    glm::mat4 rotate = glm::mat4_cast(orientation);
 
    glm::mat4 translate = glm::mat4(1.0f);
    translate = glm::translate(translate, -pos);
 
    view = rotate * translate;
}

where pitch, yaw and roll are "global"

2 hours ago, Zakwayda said:

How do you want it to be handled if the vehicle tries to land while at some arbitrary non-upright orientation (e.g. upside-down, nose-first, etc.)?

When approaching the ground, the camera should reset roll and become similar to the FPS camera. Ie no matters how vehicle lands

Advertisement

It sounds to me like it would make sense for flying and ground-based movement to just use separate control schemes. Although you could maybe wrangle the 6DOF scheme to work on the ground as well, it doesn't seem like the most intuitive solution to me. (You could do it that way if you wanted - it just might be a little awkward.)

With respect to landing:

- When switching from the flying control scheme to the ground-based control scheme, you may need to find a way to compute an initial Euler-angle triple (or whatever) that 'sort of' matches the previous orientation. I say 'sort of' because it seems unlikely you'll want to just carry over the 'flying' orientation as is (for example, if the vehicle lands upside-down, I'm guessing you'd want to transition to being upside-up).

- You also might want to do a visual interpolation between the 'flying' and ground-based orientation when landing to get a smoother effect.

There are various ways you can approach all this, but rather than offer a bunch of specific suggestions here, I'll just suggest posting back here if you have more questions.

18 hours ago, Zakwayda said:

It sounds to me like it would make sense for flying and ground-based movement to just use separate control schemes.

I decided to do so. It is not difficult to switch from the "ground" position to the "flying" one - I need just apply glm::quat_cast to FPSCamera orientation matrix, but I have no idea how to go back and reset roll.

19 hours ago, Zakwayda said:

You also might want to do a visual interpolation between the 'flying' and ground-based orientation when landing to get a smoother effect.

Right you are. But the main problem is that I have no idea how I can "extract" pitch, yaw and roll angles from the FreeCamera's quaternion.

38 minutes ago, congard said:

But the main problem is that I have no idea how I can "extract" pitch, yaw and roll angles from the FreeCamera's quaternion.

I don't think you need to. And, you probably wouldn't want to anyway because then you'd have to interpolate Euler angles, which can be difficult to do effectively.

Here's how I'd probably do it:

- When the vehicle (or whatever) lands, store the quaternion representing its current orientation (the last 'flying' orientation), and compute the new Euler angles for the ground-based orientation (how to do that is something we haven't covered here - not sure if you've tackled that part yet).

- Continue from there with the ground-based control system as usual, using Euler angles.

- Superimposed on this, you want to introduce a purely visual effect. Over some time interval after landing (probably relatively short, e.g. 0.5 seconds), for each update, create a quaternion from the current Euler angles, and then interpolate between the stored initial quaternion and that quaternion (using e.g. slerp), with the interpolation parameter increasing from 0 to 1 over the time interval. While this interpolation is going on, use the interpolated quaternion for the orientation of the vehicle rather than the Euler angles. After the interpolation is complete, you can switch back to using the Euler angles. (This approach is fairly crude, but I don't think I have enough information at this point to recommend a better alternative.)

That may seem a little complicated (or maybe not). In any case, you might want to get everything else working the way you want it before tackling that (since this is a purely cosmetic effect).

Quote

It is not difficult to switch from the "ground" position to the "flying" one - I need just apply glm::quat_cast to FPSCamera orientation matrix, but I have no idea how to go back and reset roll.

I'm not sure what you mean by 'reset roll'. Are you talking about landing? Anyway, if there are still some 'missing puzzle pieces' here, you can always ask about them.

11 hours ago, Zakwayda said:

When the vehicle (or whatever) lands, store the quaternion representing its current orientation (the last 'flying' orientation), and compute the new Euler angles for the ground-based orientation (how to do that is something we haven't covered here - not sure if you've tackled that part yet).

There was a problem with this. In a free camera, if I rotate it around the Y-axis (as it would if I changed the yaw of the FPS camera), the rotation around the Y-axis will not change... The pitch and roll will change. Here is the result of rotating the camera by ~180 degrees around the Y-axis:
3.05986, -0.109744, 3.07922 (in degrees ~175.3, ~-6.3, ~176.4)
I got these values using glm::eulerAngles.

This topic is closed to new replies.

Advertisement