Search code examples
c++openglrotationsfmlquaternions

"Looking At" an object with a Quaternion


So I am currently trying to create a function that will take two 3D points A and B, and provide me with the quaternion representing the rotation required of point A to be "looking at" point B (such that point A's local Z axis passes through point B, if you will).

I originally found this post, the top answer of which seemed to provide me with a good starting point. I went on to implement the following code; instead of assuming a default (0, 0, -1) orientation, as the original answer suggests, I try to extract a unit vector representing the actual orientation of the camera.

void Camera::LookAt(sf::Vector3<float> Target)
{
    ///Derived from pseudocode found here:
    ///https://stackoverflow.com/questions/13014973/quaternion-rotate-to

    //Get the normalized vector from the camera position to Target
    sf::Vector3<float> VectorTo(Target.x - m_Position.x,
                                Target.y - m_Position.y,
                                Target.z - m_Position.z);
    //Get the length of VectorTo
    float VectorLength = sqrt(VectorTo.x*VectorTo.x +
                              VectorTo.y*VectorTo.y +
                              VectorTo.z*VectorTo.z);
    //Normalize VectorTo
    VectorTo.x /= VectorLength;
    VectorTo.y /= VectorLength;
    VectorTo.z /= VectorLength;

    //Straight-ahead vector
    sf::Vector3<float> LocalVector = m_Orientation.MultVect(sf::Vector3<float>(0, 0, -1));

    //Get the cross product as the axis of rotation
    sf::Vector3<float> Axis(VectorTo.y*LocalVector.z - VectorTo.z*LocalVector.y,
                            VectorTo.z*LocalVector.x - VectorTo.x*LocalVector.z,
                            VectorTo.x*LocalVector.y - VectorTo.y*LocalVector.x);

    //Get the dot product to find the angle
    float Angle = acos(VectorTo.x*LocalVector.x +
                       VectorTo.y*LocalVector.y +
                       VectorTo.z*LocalVector.z);

    //Determine whether or not the angle is positive
    //Get the cross product of the axis and the local vector
    sf::Vector3<float> ThirdVect(Axis.y*LocalVector.z - Axis.z*LocalVector.y,
                                 Axis.z*LocalVector.x - Axis.x*LocalVector.z,
                                 Axis.x*LocalVector.y - Axis.y*LocalVector.x);
    //If the dot product of that and the local vector is negative, so is the angle
    if (ThirdVect.x*VectorTo.x + ThirdVect.y*VectorTo.y + ThirdVect.z*VectorTo.z < 0)
    {
        Angle = -Angle;
    }

    //Finally, create a quaternion
    Quaternion AxisAngle;
    AxisAngle.FromAxisAngle(Angle, Axis.x, Axis.y, Axis.z);

    //And multiply it into the current orientation
    m_Orientation = AxisAngle * m_Orientation;
}

This almost works. What happens is that the camera seems to rotate half the distance towards the Target point. If I attempt the rotation again, it performs half the remaining rotation, ad infinitum, such that if I hold down the "Look-At-Button", the camera's orientation gets closer and closer to looking directly at the target, but is also constantly slowing down in its rotation, such that it never quite gets there.

Note that I don't want to resort to gluLookAt(), as I will also eventually need this code to point objects other than the camera at one another, and my objects already use quaternions for their orientations. For example, I might want to create an eyeball that tracks the position of something moving around in front of it, or a projectile that updates its orientation to seek out its target.


Solution

  • Normalize Axis vector before passing it to FromAxisAngle.