11

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.

Community
  • 1
  • 1
GarrickW
  • 2,181
  • 5
  • 31
  • 38
  • This perhaps does not address your question, but why do you want to use a quaternion for this? What you're trying to do is essentially the same as `gluLookAt()`, the code for which is fairly simple if you want to have a version which does not concatenate onto the stack. – JasonD Jan 15 '13 at 12:18
  • As noted in the last sentence, I also want to be able to orient non-camera objects such that they face one another. For instance, I might want to orient a turret such that it is always pointing towards its target; since I store all my object orientations as quaternions, I need to figure out a more general way of doing things. Using the camera as a first step is merely a convenience. – GarrickW Jan 15 '13 at 12:24
  • Ah, I took your comment about `gluLookat()` to mean that the camera was somehow intrinsic to lookAt, where actually it's just that your other objects use quaternions already. – JasonD Jan 15 '13 at 12:32
  • What library are you using for `Quaternion`? – yiding Jan 15 '13 at 13:04
  • @yiding None, I wrote my own Quaternion class for it (and so far it has worked fine for rotating and translating objects in general). – GarrickW Jan 15 '13 at 13:40
  • @JasonD Ah, I see - thanks. I've added concrete examples to the question to, hopefully, reflect what I mean more accurately. – GarrickW Jan 15 '13 at 13:49
  • @GarrickW do you mind to share a copy of the code? I really want to calculate the rotation quaternion to point from (XYZ)1 to (XYZ)2 :$ (I mean the classes this code uses) – Gizmo Jul 28 '13 at 19:25
  • @Gizmo Sure, I can dig it up - here is a dump of the Quaternion source code: http://pastebin.com/9JBMWizm . The function you're looking for is FromLookVector(). Note that this code is poorly optimized, and the comments may be inaccurate (I scanned through them and made a few changes, but I may have missed something); but I use it in my projects, and it works. – GarrickW Jul 31 '13 at 14:39

2 Answers2

4

Normalize Axis vector before passing it to FromAxisAngle.

kerim
  • 2,412
  • 18
  • 16
  • This solved half of the problem; it's always the little things... It worked fine when the looking object's orientation was equal to the identity quaternion, but otherwise, the camera would rotate in a spiral pattern before locking in on the target. However, I was able to circumvent the problem by simply resetting the orientation to the identity quaternion at the start of LookAt() and then deriving a new m_Orientation from the Axis and Angle at the very end; this solution works, regardless of previous rotations. Thank you! – GarrickW Jan 15 '13 at 16:48
  • Oh yes, cross product's length is dependent on the angle between the 2 vectors. – yiding Jan 15 '13 at 22:26
1

Why are you using a quaternion? You're just making things more complex and requiring more computation in this instance. To set up a matrix:-

calculate vector from observer to observed (which you're doing already)
normalise it (again, doing it already) = at
cross product this with the observer's up direction = right
normalise right
cross product at and right to get up

and you're done. The right, up and at vectors are the first, second and third row (or column, depending on how you set things up) of your matrix. The final row/column is the objects position.

But it looks like you want to transform an existing matrix to this new matrix over several frames. SLERPs do this to matricies as well as quaternions (which isn't surprising when you look into the maths). For the transformation, store the initial and target matricies and then SLERP between them, changing the amount to SLERP by each frame (e.g. 0, 0.25, 0.5, 0.75, 1.0 - although a non-linear progression would look nicer).

Don't forget that you're converting a quaternion back into a matrix in order to pass it to the rendering pipeline (unless there's some new features in the shaders to handle quaternions natively). So any efficencies due to quaternion use has to take into account the conversion process as well.

Skizz
  • 69,698
  • 10
  • 71
  • 108
  • I'm not in principle opposed to using matrices, but so far my project is set up to use quaternions at every turn, so it is simpler for the time being to stick to that. Still, thanks for the response; I'll keep that in mind should I start implementing matrices more directly in the future (always a possibility; the flexibility wouldn't hurt). – GarrickW Jan 15 '13 at 16:55