user16095902
user16095902

Reputation:

How to set the pitch, yaw, roll of a quaternion relative to the world

So I have a quaternion that stores the orientation of an object and I have functions to change the pitch, yaw and roll of the quaternion like so:

void pitch(float amount)
{
    orientation *= glm::angleAxis(glm::radians(amount), glm::vec3(1, 0, 0));
}

void yaw(float amount)
{
    orientation *= glm::angleAxis(glm::radians(-amount), glm::vec3(0, 1, 0));
}

void roll(float amount)
{
    orientation *= glm::angleAxis(glm::radians(amount), glm::vec3(0, 0, -1));
}

What I want to do is set the pitch, yaw and roll relative to the world instead of adding to the current orientation. What I mean by that is right now, say I have a roll of 90*. Each time I use my pitch function and pass 45* into it, the roll will turn into 135* then 180* and so on. What I want the set pitch function to do is when I pass 45* into it, it will set the roll of the quaternion to 45* and still keep the pitch and yaw.

I made a better explanation in my comment below.

So to picture what I'm trying to do, imagine 4 walls around you with pictures hanging of an arrow pointing to the world up. What I want to do is no matter what the pitch, yaw and roll of the quaternion, if I'm looking at any of the hanging pictures (any yaw) but with a little roll (so the arrow will point anywhere but up). If I use the setRoll function and pass 0*, the object should see the arrow pointing up. If I pass 90* to the setRoll function, no matter how many times I call the function it should set the object's rotation to 90* so the arrow will point to the left.


Thanks to Thomas' answer, I now have this code, but sadly it still doesn't work correctly:

void Camera::setPitch(float amount)
{
    glm::vec3 globalUp = glm::vec3(0.0f, 1.0f, 0.0f);
    glm::vec3 globalRight = glm::vec3(1.0f, 0.0f, 0.0f);
    // "The pitch is the angle between the front vector and the horizontal plane. This is pi/2 minus the angle between the front vector and the global up vector."
    float currentPitch = 3.14f / 2.0f -  glm::acos(glm::dot(direction, globalRight));
    // "To find the angle over which you need to rotate, compute the current pitch, and subtract this from the target pitch."
    amount = currentPitch - amount;
    // "To set the pitch without affecting roll and yaw, you'll want to rotate around an axis that lies in the horizontal plane and is orthogonal to the front vector. For a vector to lie in a plane, it means that it's orthogonal to the plane's normal, so we're looking for a vector that is orthogonal to both the global up vector and the front vector. That's just the cross product of the two."
    glm::quat rotation = glm::angleAxis(glm::radians(amount), glm::cross(globalUp, direction));
    orientation *= rotation;


    updateCameraVectors();
    updateViewMatrix();
}

void Camera::setYaw(float amount)
{
    glm::vec3 globalUp = glm::vec3(0.0f, 1.0f, 0.0f);
    glm::vec3 globalRight = glm::vec3(1.0f, 0.0f, 0.0f);
    // "The yaw is the angle between the projection of the front vector onto the global horizontal plane, and some global "zero yaw" vector that also lies in the global horizontal plane. To project the front vector onto the global horizontal plane, simply set its vertical coordinate to zero."
    float currentYaw = 3.14f / 2.0f -  glm::acos(glm::dot(direction, globalRight));
    // "To find the angle over which to rotate, compute the current yaw and subtract this from the target yaw."
    amount = currentYaw - amount;
    // "To change the yaw, simply rotate around the global up vector."
    glm::quat rotation = glm::angleAxis(glm::radians(amount), globalUp);
    orientation *= rotation;
    // "Note that yaw is ill-defined when looking straight up; in that case, you can maybe set the roll instead, because the two are essentially the same."
    // TODO...


    updateCameraVectors();
    updateViewMatrix();
}

void Camera::setRoll(float amount)
{

    glm::vec3 globalUp = glm::vec3(0.0f, 1.0f, 0.0f);
    // "The roll is the angle between the local right vector and the global up vector, minus pi/2"
    float currentRoll =  glm::acos(glm::dot((right, globalUp)) - 3.14f / 2.0f;
    // "To find the angle over which to rotate, compute the current roll and subtract this from the target roll."
    amount = currentRoll - amount;
    // "To change the roll, you'll want to rotate around the local front vector."
    glm::quat rotation = glm::angleAxis(glm::radians(amount), direction);
    orientation *= rotation;

    updateCameraVectors();
    updateViewMatrix();
}

Upvotes: 0

Views: 1798

Answers (1)

Thomas
Thomas

Reputation: 182000

So here's my understanding of what you're trying to do. You have a unit quaternion orientation. You can picture this as a "front" vector pointing out from your eyes, and an "up" vector pointing out of the top of your head. From these follows a "right" vector pointing out your right ear.

You didn't explain your coordinate system, so I'll keep this in pretty generic terms. And this post is more of a "how to think about the problem" rather than working code, but I hope that's at least as useful in the long term.

Pitch

The pitch is the angle between the front vector and the horizontal plane. This is pi/2 minus the angle between the front vector and the global up vector.

To set the pitch without affecting roll and yaw, you'll want to rotate around an axis that lies in the horizontal plane and is orthogonal to the front vector. For a vector to lie in a plane, it means that it's orthogonal to the plane's normal, so we're looking for a vector that is orthogonal to both the global up vector and the front vector. That's just the cross product of the two.

To find the angle over which you need to rotate, compute the current pitch, and subtract this from the target pitch.

Yaw

The yaw is the angle between the projection of the front vector onto the global horizontal plane, and some global "zero yaw" vector that also lies in the global horizontal plane. To project the front vector onto the global horizontal plane, simply set its vertical coordinate to zero.

To change the yaw, simply rotate around the global up vector.

To find the angle over which to rotate, compute the current yaw and subtract this from the target yaw.

Note that yaw is ill-defined when looking straight up; in that case, you can maybe set the roll instead, because the two are essentially the same.

Roll

The roll is the angle between the local right vector and the global up vector, minus pi/2.

To change the roll, you'll want to rotate around the local front vector.

To find the angle over which to rotate, compute the current rol and subtract this from the target roll.

Upvotes: 1

Related Questions