Orienting a model to face a target

by Marc   Last Updated August 24, 2018 11:13 AM

I have two objects (target and player), both have Position (Vector3) and Rotation (Quaternion). I want the target to rotate and be facing right at the player. The target, when it shoots something should shoot right at the player.

I've seen plenty of examples of slerping to the player, but I don't want incremental rotation, well, I suppose that would be ok as long as I can make the slerp be 100%, and as long as it actually worked.

FYI - Im able to use the position and rotation to do plenty of other things and it all works great except this last piece I cant figure out.

Code samples run in the Target's class, Position = the targets position, Avatar = the player.

EDIT

Im now using Maik's c# code he has provided and it works great!



Answers 1


There are more than one ways to do it. You can calculate the absolute orientation or the rotation relative to your avatar, that means your new orientation = avatarOrientation * q. Here is the latter one:

  1. Calculate the rotation axis by taking the cross product of your avatar's unit forward vector and the unit vector from avatar to target, the new forward vector:

    vector newForwardUnit = vector::normalize(target - avatarPosition);
    vector rotAxis = vector::cross(avatarForwardUnit, newForwardUnit);
    
  2. Calculate the rotation angle using the dot-product

    float rotAngle = acos(vector::dot(avatarForwardUnit, newForwardUnit));
    
  3. Create the quaternion using rotAxis and rotAngle and multiply it with avatar's current orientation

    quaternion q(rotAxis, rotAngle);
    quaternion newRot = avatarRot * q;
    

If you need help finding the avatar's current forward vector, the input for 1. just shoot :)

EDIT: Calculating the absolute orientation is actually a bit easier, use the forward vector of the identity-matrix instead of avatars forward vector as input for 1) and 2). And don't multiply it in 3), instead use it directly as the new orientation: newRot = q


Important to note: The solution has 2 anomalies caused by the nature of the cross-product:

  1. If the forward vectors are equal. Solution here is simply return the identity quaternion

  2. If the vectors point exactly in the opposite direction. The solution here is to create the quaternion by using avatars up axis as rotation axis and the angle 180.0 degrees.

Here is the implementation in C++ that takes care of those edge cases. Converting it to C# should be easy.

// returns a quaternion that rotates vector a to vector b
quaternion get_rotation(const vector &a, const vector &b, const vector &up)
{   
    ASSERT_VECTOR_NORMALIZED(a);
    ASSERT_VECTOR_NORMALIZED(b);

    float dot = vector::dot(a, b);    
    // test for dot -1
    if(nearly_equal_eps_f(dot, -1.0f, 0.000001f))
    {
        // vector a and b point exactly in the opposite direction, 
        // so it is a 180 degrees turn around the up-axis
        return quaternion(up, gdeg2rad(180.0f));
    }
    // test for dot 1
    else if(nearly_equal_eps_f(dot, 1.0f, 0.000001f))
    {
        // vector a and b point exactly in the same direction
        // so we return the identity quaternion
        return quaternion(0.0f, 0.0f, 0.0f, 1.0f);
    }

    float rotAngle = acos(dot);
    vector rotAxis = vector::cross(a, b);
    rotAxis = vector::normalize(rotAxis);
    return quaternion(rotAxis, rotAngle);
}

EDIT: Corrected version of Marc's XNA code

// the new forward vector, so the avatar faces the target
Vector3 newForward = Vector3.Normalize(Position - GameState.Avatar.Position);
// calc the rotation so the avatar faces the target
Rotation = Helpers.GetRotation(Vector3.Forward, newForward, Vector3.Up);
Cannon.Shoot(Position, Rotation, this);


public static Quaternion GetRotation(Vector3 source, Vector3 dest, Vector3 up)
{
    float dot = Vector3.Dot(source, dest);

    if (Math.Abs(dot - (-1.0f)) < 0.000001f)
    {
        // vector a and b point exactly in the opposite direction, 
        // so it is a 180 degrees turn around the up-axis
        return new Quaternion(up, MathHelper.ToRadians(180.0f));
    }
    if (Math.Abs(dot - (1.0f)) < 0.000001f)
    {
        // vector a and b point exactly in the same direction
        // so we return the identity quaternion
        return Quaternion.Identity;
    }

    float rotAngle = (float)Math.Acos(dot);
    Vector3 rotAxis = Vector3.Cross(source, dest);
    rotAxis = Vector3.Normalize(rotAxis);
    return Quaternion.CreateFromAxisAngle(rotAxis, rotAngle);
}
Maik Semder
Maik Semder
July 21, 2011 09:08 AM

Related Questions


What Programs Are Involved

Updated June 14, 2015 23:05 PM




How are photorealistic models' textures created?

Updated August 26, 2017 00:13 AM