How to properly rotate a quaternion along all axis?

流过昼夜 提交于 2019-11-29 09:48:04

问题


I want to code a first person camera with its rotation stored in a quaternion. Unfortunately there is something wrong with the rotation.

The following function is responsible to rotate the camera. The parameters Mouse and Speed pass the mouse movement and rotation speed. Then the function fetches the rotation quaternion, rotates it and stores the result. By the way, I'm using Bullet Physics that is where the types and functions come from.

void Rotate(vec2 Mouse, float Speed)
{
    btTransform transform = camera->getWorldTransform();
    btQuaternion rotation = transform.getRotation();

    Mouse = Mouse * Speed;                    // apply mouse sensitivity
    btQuaternion change(Mouse.y, Mouse.x, 0); // create quaternion from angles
    rotation = change * rotation;             // rotate camera by that

    transform.setRotation(rotation);
    camera->setWorldTransform(transform);
}

To illustrate the resulting camera rotation when the mouse moves, I show you a hand drawing. On the left side the wrong rotation the camera actually performs is shown. On the right side the desired correct case is shown. The arrows indicate how the camera is rotate when moving the mouse up (in orange) and down (in blue).

As you can see, as long as the yaw is zero, the rotation is correct. But the more yaw it has, the smaller the circles in which the camera rotates become. In contrast, the circles should always run along the whole sphere like a longitude.

I am not very familiar with quaternions, so here I ask how to correctly rotate them.


回答1:


I found out how to properly rotate a quaternion on my own. The key was to find vectors for the axis I want to rotate around. Those are used to create quaternions from axis and angle, when angle is the amount to rotate around the actual axis.

The following code shows what I ended up with. It also allows to roll the camera, which might be useful some time.

void Rotate(btVector3 Amount, float Sensitivity)
{
    // fetch current rotation
    btTransform transform = camera->getWorldTransform();
    btQuaternion rotation = transform.getRotation();

    // apply mouse sensitivity
    Amount *= Sensitivity;

    // create orientation vectors
    btVector3 up(0, 1, 0);
    btVector3 lookat = quatRotate(rotation, btVector3(0, 0, 1));
    btVector3 forward = btVector3(lookat.getX(), 0, lookat.getZ()).normalize();
    btVector3 side = btCross(up, forward);

    // rotate camera with quaternions created from axis and angle
    rotation = btQuaternion(up,      Amount.getY()) * rotation;
    rotation = btQuaternion(side,    Amount.getX()) * rotation;
    rotation = btQuaternion(forward, Amount.getZ()) * rotation;

    // set new rotation
    transform.setRotation(rotation);
    camera->setWorldTransform(transform);
}

Since I rarely found information about quaternion rotation, I'll spend some time further explaining the code above.

Fetching and setting the rotation is specific to the physics engine and isn't related to this question so I won't elaborate on this. The next part, multiplying the amount by a mouse sensitivity should be really clear. Let's continue with the direction vectors.

  • The up vector depends on your own implementation. Most conveniently, the positive Y axis points up, therefore we end up with 0, 1, 0.
  • The lookat vector represents the direction the camera looks at. We simply rotate a unit vector pointing forward by the camera rotation quaternion. Again, the forward pointing vector depends on your conventions. If the Y axis is up, the positive Z axis might point forward, which is 0, 0, 1.
  • Do not mix that up with the next vector. It's named forward which references to the camera rotation. Therefore we just need to project the lookat vector to the ground. In this case, we simply take the lookat vector and ignore the up pointing component. For neatness we normalize that vector.
  • The side vector points leftwards from the camera orientation. Therefore it lies perpendicular to both the up and the forward vector and we can use the cross product to compute it.

Given those vectors, we can correctly rotate the camera quaternion around them. Which you start with, Z, Y or Z, depends on the Euler angle sequence which is, again, a convention varying from application to application. Since I want to rotations to be applied in Y X Z order, I do the following.

  • First, rotate the camera around the up axis by the amount for the Y rotation. This is yaw.
  • Then rotate around the side axis, which points leftwards, by the X amount. It's pitch.
  • And lastly, rotate around the forward vector by the Z amount to apply roll.

To apply those rotations, we need to multiply the quaternions create by axis and angle with the current camera rotation. Lastly we apply the resulted quaternion to the body in the physics simulation.




回答2:


Matrices and pitch/yaw/roll both having their limitations, I do not use them anymore but use instead quaternions. I rotate the view vector and recalculate first the camera vectors, then the view matrix in regard to the rotated view vector.

void Camera::rotateViewVector(glm::quat quat) {

    glm::quat rotatedViewQuat;

    quat = glm::normalize(quat);
    m_viewVector = glm::normalize(m_viewVector);

    glm::quat viewQuat(0.0f,
        m_viewVector.x,
        m_viewVector.y,
        m_viewVector.z);

    viewQuat = glm::normalize(viewQuat);

    rotatedViewQuat = (quat * viewQuat) * glm::conjugate(quat);
    rotatedViewQuat = glm::normalize(rotatedViewQuat);

    m_viewVector = glm::normalize(glm::vec3(rotatedViewQuat.x, rotatedViewQuat.y, rotatedViewQuat.z));
    m_rightVector = glm::normalize(glm::cross(glm::vec3(0.0f, 1.0f, 0.0f), m_viewVector));
    m_upVector = glm::normalize(glm::cross(m_viewVector, m_rightVector));
}


来源:https://stackoverflow.com/questions/16384571/how-to-properly-rotate-a-quaternion-along-all-axis

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!