Work out whether to turn clockwise or anticlockwise from two angles
Asked Answered
A

4

5

Im making a game in XNA. I have enemies and player. The enemies should turn gradually towards the player. They should work out whether they need to turn clockwise or anticlockwise, whichever is shorter.

I got the angle the enemy is currently facing and the angle it should be facing (the angle of the line between the enemy and the player) as radians by using Atan2.

I get some weird behavior though. Lets say in the scenario below. the enemy might turn all the way around in the wrong direction.

enter image description here

My code (below) keeps getting longer and I'm still having issues. This code is part of the enemy classes Update method. This must be a common problem to overcome in games. Is there some way of dealing with this?

            //this bit is just in case enemy has rotated more than 360 degrees (gets set back to 0)
            if (Math.Abs(_blocklist[0]._floor.Revolutions) >= 2)
            {
                _blocklist[0]._floor.Rotation = 0.0f;
            }

            //enemy rotation in radians          
            float blockroat = _blocklist[0]._floor.Rotation;
            // vector to player - vector to enemy
            _vectToPlayer = playerpos - _blocklist[0].Centre
            angletoplayer = (float)(Math.Atan2(_vectToPlayer.Y, _vectToPlayer.X));
            diff = blockroat - angletoplayer;

            if (diff < -Math.PI)
            {
                diff += (float) Math.PI;
                diff = -diff;
            }
            else if (diff > Math.PI)
            {
                diff -= (float)Math.PI;
                diff = -diff;
            }

            // if enemy angle if off by a certain amount
            if (Math.Abs(diff) >_maxturn)
            {
                if (diff < 0)
                {
                    //turn clockwise
                    _blocklist[0]._floor.Rotation += _maxturn;
                }
                else
                {
                     //turn anti clockwise
                    _blocklist[0]._floor.Rotation -= _maxturn;
                }
            }

UPDATE

I ended up using method 2 like this.. Works perfectly. Also it is a lot neater than my previous code

            //enemy rotation in radians from farseer (red line)
            float brot = _blocklist[0]._floor.Rotation + ((float)Math.PI/2);
            //vector from enemy to player (blue line)
            Vector2 _vectToPlayer = playerpos - _blocklist[0].Centre;
            //cross product of 2d vectors
            cross = (_vectToPlayer.X * (float)Math.Sin(brot)) - ((float)Math.Cos(brot) * _vectToPlayer.Y);

            //tolerance for how closely enemy must point towards player
            if (Math.Abs(cross) > 5)
            {
                if (cross > 0)
                {
                    //turn anticlockwise
                    _blocklist[0]._floor.Rotation -= _npcstats.maxturnspeed;
                }
                else
                {
                    //turn clockwise
                    _blocklist[0]._floor.Rotation += _npcstats.maxturnspeed;
                } 
            }

I think that my previous code was more or less doing exactly the suggested method 1. But I could not get it to work.. I put this down to the vagaries of farseers coordinate system + how it interacted with my own.

Alamein answered 17/5, 2013 at 16:11 Comment(2)
Looks to me you always rotate the enemy amount of _maxturn and not amount of diff. I can't see definition of _maxturn, but I assume it's some sort of CONST?Cooksey
yeah thats cus the enemies turning speed is limited to a certain amount denoted by maxspeed.. he can only turn that much per frameAlamein
F
12

Technique #1:

You are using a convention that I'm not familiar with. In your convention, east is 0, north is -π/2, west is both π and -π, and south is π/2. All angles are between -π and π.

Normally the angle of a character facing east is zero, north is π/2, west is π, and due south is 3π/2. All angles are between 0 and 2π.

Let's assume the normal convention rather than your convention. Start by getting your red and blue vector angles correct in the normal convention; how you do that is up to you.

Subtract the angle of the red vector from both angles. Now we have the guy at the origin facing due east.

Now normalize the new blue angle; if it is smaller than 0, add 2π. If it is larger than 2π, subtract 2π. Do that until it is between 0 and 2π.

Now we have two angles; the angle of the new red vector is zero and the angle of the new blue vector is between 0 and 2π.

If the angle of the new blue vector is less than π then the character at the origin needs to turn towards its left. If it is greater than π then turn right.

Technique #2:

Take a non-zero point on your blue and red vectors, say (bx, by) and (rx, ry). Now compute bx * ry - by * rx. If it is positive, turn right, if it is negative, turn left. If it is zero then either they are facing directly towards or directly away; in that case you'll have to figure out which case you're in by some other means. (This is essentially Jacek's answer stated more directly.)

Foray answered 17/5, 2013 at 16:56 Comment(3)
Im using farseer physics library for XNA. rotation for a body has that convention in it. I guess cus a thing can rotate in a negative or positive way from its starting point. Also things can keep rotating and be at, like 20 radians. (I am preventing that though)Alamein
@GuyeIncognito: It's not an unreasonable convention, it's just not the one I'm used to thinking about. You could easily adapt my technique to your convention. Essentially my suggestion is to rotate the entire system into an orientation from which it is easier to reason about the correct answer, and then get the answer in that system. This of course works because rigidly rotating the entire thing doesn't change the answer.Foray
@GuyeIncognito: So in your convention, subtract the red angle from both, normalize the new blue angle to between -π and π, and now if the new blue angle is negative, turn left, if it is positive, turn right.Foray
W
5

If you have both blue and red vectors as Vector3, you can do:

Vector3 crossProduct = Vector3.Cross(red, blue)
if (crossProduct.z > 0)
    // Turn Right
else
    // Turn Left
Wallachia answered 17/5, 2013 at 17:36 Comment(0)
Q
2

very easy. say you got 2 angles alpha and beta and you would like to tell shortest movement from alpha to beta is clockwise or anti clockwise. what you should do is this: set alpha to be 0 and add the same offset you gave alpha to beta. now if beta is above 180- movement is anticlockwise, else, movement is clockwise. for example: alpha is 10 deg and beta is 350 deg. so -10 deg offset to both angles sets alpha to be 0 and beta to be 340 and movement is anticlockwise.

def direction_by_2_angles(alpha, beta)
  # true clockwize, false not clockwize
  delta = 360 - alpha
  beta = beta + delta
  beta = beta % 360
  beta < 180 ? true : false
end
Quadruplet answered 10/5, 2018 at 20:56 Comment(1)
This is roughly right, but lacks the finesse of Eric Lippert's similar answer, because after you do the offset that you mention you do need to potentially add or subtract 360 to get it back within the 0-360 range.Crusted
J
1

I searched to find if there is an super-short answer. Didn't found one. Seem like Technique 1 is popular. However i already implemented it so here you are:

//ccw = 1, cw = -1
//refineAngle = normallize angle to range [-PI, PI]

currentAngle = refineAngle(currentAngle);
targetAngle =  refineAngle(targetAngle);

if(targetAngle < 0)
    targetAngle += (PI *2);
if(currentAngle < 0)
    currentAngle += (PI *2);
if(targetAngle < currentAngle)
    targetAngle += (PI *2);

if(targetAngle - currentAngle <= PI)
    return 1;
else
    return -1;
Jorgejorgensen answered 5/3, 2014 at 21:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.