5
\$\begingroup\$

This may have been asked already, but I was unable to find it, because I don't really know what I'm looking for. I drew a picture:

the situation, pictorially

I need theta. I've seen various solutions using Vector.Dot, and Math.Atan2, but they don't seem to work...

BTW, I need the output in degrees. On previous tries, I ran my results through MathHelper.ToDegrees(), but got values over 360, which shouldn't be possible.

This is my current code:

    Vector2 mouse = new Vector2(Mouse.GetState().X, Mouse.GetState().Y);
    Vector2 delta = mouse - Position;
    angle = Math.Atan2(delta.X, -delta.Y);
    angle = MathHelper.ToDegrees((float)angle);

EDIT: A little more info. I'm making a 2D game, and need the angle to 'rotate' one of my sprites (simply changing which sprite I'm displaying). The code for how I'm doing this is below.

public static Direction GetDirection(double radians)
        {
            if (radians > GetRadians(Direction.Up).Lower && radians < GetRadians(Direction.Up).Upper)
            {
                return Direction.Up;
            }
            else if (radians > GetRadians(Direction.UpRight).Lower && radians < GetRadians(Direction.UpRight).Upper)
            {
                return Direction.UpRight;
            }
            else if (radians > GetRadians(Direction.Right).Lower && radians < GetRadians(Direction.Right).Upper)
            {
                return Direction.Right;
            }
            else if (radians > GetRadians(Direction.DownRight).Lower && radians < GetRadians(Direction.DownRight).Upper)
            {
                return Direction.DownRight;
            }
            else if (radians > GetRadians(Direction.Down).Lower && radians < GetRadians(Direction.Down).Upper)
            {
                return Direction.Down;
            }
            else if (radians > GetRadians(Direction.DownLeft).Lower && radians < GetRadians(Direction.DownLeft).Upper)
            {
                return Direction.DownLeft;
            }
            else if (radians > GetRadians(Direction.Left).Lower && radians < GetRadians(Direction.Left).Upper)
            {
                return Direction.Left;
            }
            else
            {
                return Direction.UpLeft;
            }
        }
public static Radians GetRadians(Direction direction)
        {
            Radians r = new Radians(0,0);
            switch (direction)
            {
                case Direction.Up:
                    r = new Radians(15 * increment, increment);
                    break;

                case Direction.UpRight:
                    r = new Radians(1 * increment, 3 * increment);
                    break;

                case Direction.Right:
                    r = new Radians(3 * increment, 5 * increment);
                    break;

                case Direction.DownRight:
                    r = new Radians(5 * increment, 7 * increment);
                    break;

                case Direction.Down:
                    r = new Radians(7 * increment, 9 * increment);
                    break;

                case Direction.DownLeft:
                    r = new Radians(9 * increment, 11 * increment);
                    break;

                case Direction.Left:
                    r = new Radians(11 * increment, 13 * increment);
                    break;

                case Direction.UpLeft:
                    r = new Radians(13 * increment, 15 * increment);
                    break;
            }

            return r;
        }
\$\endgroup\$
2
  • 2
    \$\begingroup\$ A lot of atan2 implementations have X and Y reversed, so the first parameter is y, followed by x. Check a reference to see if that's the case. \$\endgroup\$
    – bummzack
    Apr 20, 2013 at 10:25
  • \$\begingroup\$ But then, atan2() really takes (distance perpendicular from reference line, distance along reference line). With a vertical reference line, we do want atan2(delta_x, delta_y) which may look faulty to the casual onlooker. \$\endgroup\$
    – DarenW
    Apr 26, 2013 at 18:36

2 Answers 2

6
\$\begingroup\$

Two parts: 1. coordinate systems for angles can be...finicky. 2. You don't really need degrees for anything, with the possible exception of outputting their value to the user interface.

Coordinate system angles

So you want an angle from "North", and judging from your example math, that means the -Y direction (in sprite coordinates with the origin in the upper left). Also judging from your example, you want it to grow clockwise. That's fine, although it varies from math convention slightly, which starts the unit circle pointing right and grows counter-clockwise. So instead of using a function that expects normal coordinate systems (and reversing the arguments you pass into it), use plain Math.Atan() instead.

There are surely elegant ways to deal with angles in any convention and coordinate system. But I have yet to find one. There always seem to be troubles when angles switch from one sign of angle to another. In order to avoid headaches with different cut-offs between the various ranges of output of trigonometric functions, I suggest you build a logic switch per quadrant. Quadrants can be from North to East, East to South, South to West, and West back to North. Like so:

// assert (delta != Vector2.Zero)
double angle = 0;
if (delta.X >= 0) {
  if (delta.Y > 0) {
    //quadrant 1
    angle = (0 * PI) + Math.Atan(delta.X / delta.Y);
  } else if (delta.Y < 0) {
    //quadrant 2, arctan will yield negative values, so work back from "South"
    angle = (1 * PI) + Math.Atan(delta.X / delta.Y);
  } else {
    // maybe Math.Atan2 handles the case of y=0.  But why leave that to chance?
    // due East
    angle = (1/2 * PI);
  }
} else { // (delta.X < 0)
  if (delta.Y < 0) {
    //quadrant 3, both coordinates negative, arctan will yield positive values
    angle = (1 * PI) + Math.Atan(delta.X / delta.Y);
  } else if (delta.Y > 0) {
    //quadrant 4, arctan gives negative values, angle increases toward 2PI
    angle = (2 * PI) + Math.Atan(delta.X / delta.Y);
  } else {
    // due West
    angle = (3/2 * PI);
  }
}

Using radians like the universe intended

Ok, now you have a useful angle that is guaranteed to range from 0 to 2PI. It is in natural units, so you can apply all kinds of extra math on it quickly and efficiently using normal trigonometry functions. You get this advantage because you didn't stop to prematurely convert your angle into arbitrary, weird, and non-useful units called degrees. But if you really, really must display that unit to the user as degrees, then you can call MathHelper.ToDegrees(angle) at the very end.

\$\endgroup\$
6
  • \$\begingroup\$ Thank you very much for your reply. I was unable to get it working in my solution, though I have switched to using radians. Your assumptions were all correct, though. I've added additional information to my original question. \$\endgroup\$
    – Jack
    Apr 22, 2013 at 16:55
  • \$\begingroup\$ Ok, I have a better idea of what you're trying to do. Can you elaborate on what is going wrong? \$\endgroup\$ Apr 22, 2013 at 17:17
  • \$\begingroup\$ Well, I just copied your code. Just as a test, I have to computer set the sprite's rotation angle to the angle outputted. I seem to be getting values between about 0.5 and 1.2. As I understand it, should I not be getting between -Pi and Pi? \$\endgroup\$
    – Jack
    Apr 28, 2013 at 7:05
  • \$\begingroup\$ @Jack No, I wrote it to range from 0 to 2Pi. \$\endgroup\$ Apr 28, 2013 at 16:17
  • \$\begingroup\$ Ok. But I still just get values around 1 to 1.3... \$\endgroup\$
    – Jack
    Apr 28, 2013 at 16:42
0
\$\begingroup\$

I understand your problem to be this:

You want to know from your Actor where the mouse is so that you can point in roughly that direction. In particular, you are limited to 8 facings.

You can get most of where you want to go with determining the angle from point a to point b using Math.atan2, and then by changing to range from [0..2*PI) to [0..8).

double theOctant=Math.Round(Math.Atan2(y2-y1,x2-x1)*4/Math.PI);

This will give you one of 8 values: -3,-2,-1,0,1,2,3,4, which will map to one of your eight directins.

0 is in the positive x direction, and increases going clockwise.

However, if you simply rotate your original x,y coordinate to the left by 90 degrees, like so:

double theOctant = Math.Round(Math.Atan2(-dx,dy) * 4 / Math.PI);

You wind up with a value of -4 for north, -3 for northeast, and so on, and simply adding 4 will give you 0..7, which is likely all you wanted in the first place:

double theOctant = Math.Round(Math.Atan2(-dx,dy) * 4 / Math.PI) + 4;

From here, you could cast to your enum if the values match up, and you are done.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .