Quick Programming Tip: Rotate Actor to Face and the Perp Dot Product

Quick Programming Tip: Rotate Actor to Face and the Perp Dot Product

Here’s a new occasional series: Quick Programming Tips! These we’ll usually be a question for a small problem, and the solution with explanation for it.

The Question:

Here’s the first one! How do I rotate progressively a entity to face another entity? For example, you have two entities Red and Blue. You want Blue to attack Red, but Blue is not facing Red, but the opposite side.

Blue is facing the opposite side.

Blue is facing the opposite side.

Obviously, if Blue attack Red without facing him, it will not look good at all. So he has to look in the direction of Red to properly attack him.

Blue has to look to Red before attacking him

Blue has to look to Red before attacking him

The Naive Answer:

Well, the first answer it comes to the mind is: So let’s just make Blue direction be the direction from Blue to Red!

Here’s the code:

    Vector3 direction = red.position - blue.position;
    direction.normalize();
    blue.direction = direction;

This should work fine, but your character will simply zap from one orientation to the other, which may be a little strange, unpolished, and for some games: out of question, since the time the character will spend turning should get into account before the attack command and the attack itself.

It works, but could be better.

The wanted result:

We (usually) want the character to go smooooothly from one orientation to the other, the previous result didn’t seem to achieve it and this is what we want:

Smoothly nicely!

The Answer:

What come to your mind when you think of smoothly go from one point to other? LERP! When it comes to orientations Quaternion SLERP!

Here’s what it would look like:

Quaternion q1, q2;
float t;
float speed = 50; //in percentage per second
State state;
onAttack()
    q1 = blue.orientation();
    Vector3 direction = red.position - blue.position;
    direction.normalize();
    q2 = Quaternion.FromLookView(Vector3.Up, direction); //most engines have this function, it generated a quaternion from a Up and Front vectors.
    state = State.AttackRotating;
    t = 0;

everyFrame()
    switch(state) {
    //...
    case State.AttackRotating:
        t += speed * deltaTime;
        if(t >= 1.0) {
            state = State.Attacking;
            blue.setOrientation(q2);
        } else {
            blue.setOrientation(Quaternion.slerp(q1, q2, t));
        }
    }

This answer is quite incomplete, it doesn’t account for if the enemy moves while you’re attacking, but the focus here is the interpolation.

Even though this WORKS, it still doesn’t look quite good yet. If I were to enumerate what doesn’t look good on this, the just the fact of using quaternions would be the first thing on the list. First of all, slerping isn’t that costful, but well, it’s not cheap at all. On other news, we’re using a whole quaternion to interpolate a single axis, also the speed constant takes percentages,  what if we wanted the radial speed to be 45 degrees per second instead of go 50% per second?

The better answer:

Here’s the idea, we find out how many degrees are between our current orientation and the target, if it will be faster going clockwise or counter clockwise, and then we start adding speed degrees per second to our current yaw.

Ah, before we implement it: Getting the angle with arcsin(a.dot(b)) is not a good idea. Why? Because it will only give us half of the information we need.

ALL

Three situations, and here are the angles that acos(dot) would return: 90, 180, 90 (you can tell it’s back or front , but can’t tell the circular direction[clockwise or ccw]), respectively. For asin(cross), it would return: 90, 0, -90 (Circular direction, but the 90° give no sense of front/back).

Quick fact: acos(dot) only returns half of the circle information. From 0° to 180°. We need more.

Let’s use the dot product weakness to our advantage. If you get your trigonometry hat and remember the rules, the dot product is the same as the cos of the angle between two vectors:

cos(theta) = dot(a, b);

The cross product, is the sin between two vectors:

sin(theta) = cross(a, b);

And for 2D vectors, the cross can be thought of as the dot product for a perpendicular vector:

sin(theta) = perpdot(a, b) = dot(perp(a), b);
//where perp is:
perp(vec) = Vector2(-a.y, a.x);
//turns down to:
sin(theta) = a.x * b.y - a.y * b.x;

Now, if we wanted to have the full circle information, how would we get it with sin and cos? arctan!

Look at this basic property from trig:

tan(x) = sin(x) / cos(x);
tan(theta) = perp(a, b) / dot(a, b);
theta = atan(perpdot(a,b) / dot(a, b));
theta = atan2(perp(a,b), dot(a, b));

But have anyone ever told you that it would render more information than sin and cos did? Yes it does! Here’s some small proof, look at the C# documentation for Atan2, it says the return value is –π θ π. Now look at the documentation for Acos or Asin, they say the return value is 0 ≤ θ ≤ π and -π/2 ≤ θ ≤ π/2 respectively. Can you believe that? With atan you have both the circular direction (positive or negative angle) and you can tell if it’s on your front or your back (|theta| greater or smaller than 90), this is perfect to rotate something gradually:

ALL

For atan(perp, dot) you get the full circle! the results would be 90, 180, -90!

Here’s a interactive representation of all 3 ways:


 

For the small visualization above, you can drag the red handles on the vectors to move them, you can click on acos, asin and atan to change the calculation method, the result will be shown in an arc from p1 to p2, and at the bottom you can see how the calculation is done and the result in numbers. The line that goes through p2 in asin and acos methods, helps us see how asin and acos are bound to y(sin) and x(cos) axes (relative to p1). You'll see that atan method doesn't have a axis line, that's because it takes into consideration both axes. Btw you can look at the page source code to see how it was done.

Now that we know it, I can even tell you that the atan(perp, dot) is EVEN cheaper than acos(dot) and asin(perp), since you do not have to normalize the vectors, because the result of dot cancels the result of perp, fantastic, we can now rotate cheaply and with customizable parameters!

Here's the final code:

float speed = 45; //in degrees per second
float threshold = 10; //a threshold where it's acceptably facing the target, in degrees
State state;
onAttack()
    state = State.AttackRotating;

everyFrame()
    switch(state) {
    //...
    case State.AttackRotating:
        Vector3 target = red.position - blue.position; //notice we do not normalize it, and it still works!
        float angleBetween = RadToDeg * atan2(perpdot(target, blue.direction), dot(target, blue.direction));
        if(abs(angleBetween) < threshold) {
            state = State.Attacking; //we're done.
        } else {
            float amount = speed * deltaTime;
            amount = clamp(amount, 0.0f, abs(angleBetween)) * sign(angleBetween);
            //amount shouldn't be greater than angleBetween, otherwise we're going too much
            //it should also be the same direction than the closest the atan returned, (maybe you'll need to multiply this by -1)
            blue.yaw = blue.yaw + amount;
       }
    }

C'mon, it wasn't hard! This is it for today guys, I hope this can help anyone, as it helped me. Remember, math is your friend, it doesn't create problems, it help you solve the ones you already have!

Share this: