Take the 2-minute tour ×
Game Development Stack Exchange is a question and answer site for professional and independent game developers. It's 100% free, no registration required.

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!

share|improve this question
4  
If you're doing a 100% slerp, you're not using slerp. You're just setting the rotation to 0*(rotation A) + 1*(rotation B) - in other words, you're just setting the rotation to rotation B the long way. Slerp is only for determining what the rotation should look like (0%<x<100%) of the way in between. –  Jonathan Hobbs Jul 21 '11 at 2:47
    
Ok, makes sense, but, target still isn't rotating fully towards the player..."the long way" with that code. –  Marc Jul 21 '11 at 4:19
add comment

1 Answer

up vote 11 down vote accepted

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 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);
}
share|improve this answer
    
Ok, I gave that a shot as you can see by my edit in the question, code provided. Not really sure what the problem is. a and b inputs are forward vectors, or at least suppose to be. –  Marc Jul 24 '11 at 0:31
    
@Marc see my corrected version of your XNA code in my answer. There were 2 problems: 1) calculation of the new forward vector was wrong, must be normalized AvatarPosition - TargetPosition 2) rotAxis must be normalized after the cross-product in GetRotation –  Maik Semder Jul 24 '11 at 8:17
    
@Marc, 2 minor changes: 3) source and dest are already normalized, no need to normalize them again in GetRotation 4) dont test for absolute 1/-1 in GetRotation, rather use some tolerance, I used 0.000001f –  Maik Semder Jul 24 '11 at 8:17
    
Hmm, thats still not working. The same scaling thing happens with the model and the target doesnt rotate towards the avatar (which I noticed in your comments you are trying to rotate the avatar towards the target....should be the other way around)...basically, trying to get a mob to face the player (avatar in a 3rd person camera). Shouldn't the GetRotation method know something about the current rotations of the target and avatar, as in, do you think the newForward is being created properly? –  Marc Jul 25 '11 at 5:42
    
If the object is scaled, that means the quaternion has not a unit length, that means rotAxis is not normalized. Did you add my last code-change with the normalization of rotAxis? However, please show your current code and for an example case where it doesnt work, please also post the values of: newForward rotAngle rotAxis and the returned quaternion. The code for the mob will be the same, once we made it work with the avatar, it will be easy to change the heading of any object. –  Maik Semder Jul 25 '11 at 17:32
show 4 more comments

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.