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:
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);
Calculate the rotation angle using the dot-product
float rotAngle = acos(vector::dot(avatarForwardUnit, newForwardUnit));
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:
If the forward vectors are equal. Solution here is simply return the identity quaternion
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);
}
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. \$\endgroup\$