13

Im struggling with the following problem. Im working with bone animation and I want (ie) the head of the player to follow an another object in space. My up axis is +Z my forward axis is +Y, and the magnitude of the quaternion is in W. I tried to use the mesa code for gluLookAt and use the 3x3 matrix to transform to a quaternion but it doesn't work as expected so I go in another direction...

So far I got the following code that is "almost" working at least the head of the player is rotating (however the X angle seems to affect the Y rotation axis) in the good direction but its looking straight up instead on following an object on the floor at about 65 degree:

qt LookRotation( v3 lookAt, v3 upDirection )
{
qt t;

v3 forward = lookAt;
v3 up = upDirection;

OrthoNormalize( &forward, &up );

v3 right = v3_cross( up, forward );

mat3 m = mat3_make( right.x, up.x, forward.x,
                    right.y, up.y, forward.y,
                    right.z, up.z, forward.z );

t.w = sqrtf( 1.0f +
             m.r[ 0 ].x +
             m.r[ 1 ].y +
             m.r[ 2 ].z ) * 0.5f;

float w4_recip = 1.0f / ( 4.0f * t.w );

t.x = ( m.r[ 2 ].y - m.r[ 1 ].z ) * w4_recip;

t.y = ( m.r[ 0 ].z - m.r[ 2 ].x ) * w4_recip;

t.z = ( m.r[ 1 ].x - m.r[ 0 ].y ) * w4_recip;

t = qt_normalize( t );

return t;
}

... ... ...

v3 v = v3_sub( vec4_to_v3( transform.world.r[ 3 ] /* The object XYZ location in the world */),
           skeleton->final_pose.location[ i ] /* i = The head joint location */ );

v = v3_normalize( v );

qt q = LookRotation( v,
        v3_make( 0.0f, 0.0f, 1.0f ) );

Can someone help me figuring out this problem... Im kinda new with quaternions and don't really know where I could have messed up. After quite some research basically what I want to do is something like the Unity API: http://docs.unity3d.com/Documentation/ScriptReference/Quaternion.LookRotation.html

genpfault
  • 51,148
  • 11
  • 85
  • 139
McBob
  • 1,011
  • 2
  • 13
  • 25
  • See also [this answer](https://stackoverflow.com/a/52551983/405017), which considers the world Up axis to keep the quaternion from rolling. – Phrogz Oct 04 '18 at 16:37

3 Answers3

15

I think this function will do what you need:

/// <summary>
/// Evaluates a rotation needed to be applied to an object positioned at sourcePoint to face destPoint
/// </summary>
/// <param name="sourcePoint">Coordinates of source point</param>
/// <param name="destPoint">Coordinates of destionation point</param>
/// <returns></returns>
public static Quaternion LookAt(Vector3 sourcePoint, Vector3 destPoint)
{
    Vector3 forwardVector = Vector3.Normalize(destPoint - sourcePoint);

    float dot = Vector3.Dot(Vector3.forward, forwardVector);

    if (Math.Abs(dot - (-1.0f)) < 0.000001f)
    {
        return new Quaternion(Vector3.up.x, Vector3.up.y, Vector3.up.z, 3.1415926535897932f);
    }
    if (Math.Abs(dot - (1.0f)) < 0.000001f)
    {
        return Quaternion.identity;
    }

    float rotAngle = (float)Math.Acos(dot);
    Vector3 rotAxis = Vector3.Cross(Vector3.forward, forwardVector);
    rotAxis = Vector3.Normalize(rotAxis);
    return CreateFromAxisAngle(rotAxis, rotAngle);
}

// just in case you need that function also
public static Quaternion CreateFromAxisAngle(Vector3 axis, float angle)
{
    float halfAngle = angle * .5f;
    float s = (float)System.Math.Sin(halfAngle);
    Quaternion q;
    q.x = axis.x * s;
    q.y = axis.y * s;
    q.z = axis.z * s;
    q.w = (float)System.Math.Cos(halfAngle);
    return q;
}

This code comes from here: https://gamedev.stackexchange.com/questions/15070/orienting-a-model-to-face-a-target I just slightly modified it to fit my case, which was implementation of the transform.LookAt without using Unity3D.

Phrogz
  • 296,393
  • 112
  • 651
  • 745
nasskov
  • 485
  • 5
  • 13
  • 2
    In my opinion you should also use up vector to fix coordinate system after first rotation - in other case you will get unwanted roll angle on your transform. – majakthecoder Mar 10 '15 at 04:28
  • Why `dot - (-1.0f)` instead of `dot + 1.0f` ? – Andy Ray Dec 28 '15 at 18:46
  • 1
    @AndyRay There's another comparison where it does `dot - (...)`, and the value inside the parentheses can either be `1.0f` or `-1.0f`. It makes sense to type them in a similar fashion, so it looks nice. – tom_mai78101 Aug 08 '16 at 20:15
6

Both of current answers have various problems for edge cases. The accepted answer is not correct for other reasons as well including the fact that it sets w=pi for one of the cases and also it doesn't do proper norms. After looking around quite a bit and testing several cases, I also found out that you need front and up vector to do this computation. So without further ado below is the code I'm using:

Quaternion lookAt(const Vector3f& sourcePoint, const Vector3f& destPoint, const Vector3f& front, const Vector3f& up)
{
    Vector3f toVector = (destPoint - sourcePoint).normalized();

    //compute rotation axis
    Vector3f rotAxis = front.cross(toVector).normalized();
    if (rotAxis.squaredNorm() == 0)
        rotAxis = up;

    //find the angle around rotation axis
    float dot = VectorMath::front().dot(toVector);
    float ang = std::acosf(dot);

    //convert axis angle to quaternion
    return Eigen::AngleAxisf(rotAxis, ang);
}

Bove uses popular Eigen library. If you don't want to use that then you might need following replacement for Eigen::AngleAxisf:

//Angle-Axis to Quaternion
Quaternionr angleAxisf(const Vector3r& axis, float angle) {
    auto s = std::sinf(angle / 2);
    auto u = axis.normalized();
    return Quaternionr(std::cosf(angle / 2), u.x() * s, u.y() * s, u.z() * s);
}

Note that special cases for dot product 0 or 1 or -1 gets automatically handled because normalized() returns 0 for the zero vector in Eigen library.

On the side note, for all your conversions worries, this is a great document to go to.

Shital Shah
  • 63,284
  • 17
  • 238
  • 185
  • 2
    Very useful, except that you don't actually make use of `up`, except for the edge case where `||rotAxis|| == 0`. I'm getting the result upside-down, so I guess there's something small missing. – samvv Jun 25 '20 at 18:50
  • 3
    Quick update .... the upside-down thing had nothing to do with `up` not being used, it's because the `cosf` should go last in my quaternion implementation. – samvv Jun 25 '20 at 19:03
  • 2
    What is the front and the up vector? – Christian Larsson Mar 25 '21 at 21:37
  • What is "front"? – Boann May 23 '23 at 23:38
3

You don't need to use acos and axis angle (which will in turn do 2 more trig functions) to get the quaternion from 2 vectors:

public static Quaternion LookAt(Vector3 sourcePoint, Vector3 destPoint)
{
    Vector3 forwardVector = Vector3.Normalize(destPoint - sourcePoint);

    Vector3 rotAxis = Vector3.Cross(Vector3.forward, forwardVector);
    float dot = Vector3.Dot(Vector3.forward, forwardVector);

    Quaternion q;
    q.x = rotAxis.x;
    q.y = rotAxis.y;
    q.z = rotAxis.z;
    q.w = dot+1;

    return q.normalize();
}

The reason for the dot+1 and subsequent normalize is because if you don't, you'll get the quaternion for the double rotation. Those 2 steps will effectively do slerp(identity, q, 0.5) which will be the proper quaternion.

ratchet freak
  • 47,288
  • 5
  • 68
  • 106