Algorithm to find the angle of a direction

algorithmsgeometryvectors

I need to translate the distance between two points into an angle from 0 to 359. To do this I use the new position coordinates which is defined by a vector and subtract the original position. This generates a difference which I can then use to define the slopes absolute value (I know it can be done with out the absolute value if I change my code up).
enter image description here
When the movement is within the NW_North section the angle will be 135 when the absolute value of the slope is 1 and as the slope converges on infinity the angle will be 180. The challenging thing is I need to break up all possible values into 45 separate angles but I have no idea what the correct algorithm is since it will need to be rounded to the nearest integer. Also I dont think the center is actually 2 but its close to it and it appears that as the distance between each red line doubles the slope gets squared but I could be wrong.

 public static int getNormalizedMovementAngle(Vector3d vector, double x, double z) {
    final double zDifference = vector.getZ() - z;
    final double xDifference = vector.getX() - x;
    final double slope = Math.abs(zDifference / xDifference);

    if(vector.getX() < x) {
        if(vector.getZ() == z) return 90;
        else if(vector.getZ() < z) {
            if(slope == 1) return 135;
            else if(slope > 1) {
                //Formula needs to go here
            }
        }
    }
}

Anyways if anyone knows how I can use the slope to generate 45 different angles between NW and North please let met know thank you!!

Edit: I can use the following to get the angle for NW_North however I noticed that its a bit off for West_NW why is that exactly? I divided the side up into 45 sections which then allowed me to decrement the slope by 0.02222 and it appears that my equation does not assign each angle an equal width section. This means multiple sections get assigned the same angle (max of 2) for the first half and then it starts skipping an angle till it finally converges on 90 degrees.

 return Math.toIntExact(Math.round(90 * (2 - Math.pow(2, -slope))));

Does anyone know how to get atan2 to use the domain of 0 to 360 degrees? I can also swap the x and z difference values for my atan2 equation depending on which quadrant we are in if that makes the calculations easier (for example SW_West could go from 1 to infinity while South_SW can go from 1 to 0).

final int angle = Math.toIntExact(Math.round(Math.atan2(zDifference, xDifference) * (180.0 / Math.PI)));

Best Answer

Based on my reading of your diagram and code, you want the following angles (measured in degrees) as results from various input values of xDifference and zDifference:

\begin{array}{ccc} \text{xDifference} & \text{zDifference} & \text{angle} \\ \hline 0 & 1 & 0 \\ -1 & 1 & 45 \\ -1 & 0 & 90 \\ -1 & -1 & 135 \\ 0 & -1 & 180 \\ 1 & -1 & 225 \\ 1 & 0 & 270 \\ 1 & 1 & 315 \end{array}

Obviously you also want to accept values other than $0$ and $\pm1,$ but the cases above are more than enough test cases to figure out the correct use of $\operatorname{atan2}$ in your program.

Now here are some results you could get using the Math.atan2 function on two variables $v_1$ and $v_2$:

\begin{array}{cccc} v_1 & v_2 & \operatorname{atan2}(v_1,v_2) \times \dfrac{180}{\pi} & \operatorname{atan2}(v_1,v_2) \times \dfrac{180}{\pi} + 360 \\ \hline 0 & 1 & 0 \\ 1 & 1 & 45 \\ 1 & 0 & 90 \\ 1 & -1 & 135 \\ 0 & -1 & 180 \\ -1 & -1 & -135 & 225 \\ -1 & 0 & -90 & 270 \\ -1 & 1 & -45 & 315 \end{array}

Compare this table with the first one. Notice that the first five lines of the third column are identical in the two tables. The last three lines of that column are different, but if you add $360$ in those cases -- as shown in the fourth column -- you get the numbers you wanted. This works because of the principle that if you turn in a certain direction (clockwise, in your case) either by the angle $\theta$ or by the angle $\theta + 360^\circ$, in both cases you end up facing in the same direction.

The other difference between the two tables is that the signs in the first column of the second table are opposite the signs in the first table. The second columns, on the other hand, are the same. These facts tell you that zDifference is the correct variable to pass as the second argument of $\operatorname{atan2}$ (not the first) and that you can get the correct first argument by reversing the sign of xDifference.

So that gives us something like this:

angle = Math.atan2(-xDifference, zDifference) * 180.0 / Math.PI);
if (angle < 0) angle += 360;

You can round the result to an integer either before or after the statement if (angle < 0) angle += 360;. The difference is that if you round after adding, you will sometimes round up to $360,$ whereas if you round before adding you will always get an integer in the range from $0$ to $359$ inclusive.


More generally, in case you want to use these ideas in other situations where the axes may be named differently and the angle may be measured differently, whichever axis points in the "angle $0$" direction is the one you want to use for the second argument of Math.atan2, because that's where you would use the $x$ coordinate if you wanted the $x$ axis to point in the direction at "angle $0$". The axis that points in the "angle $90$" direction should be the first argument.

You also need to get the signs of the arguments correct for the kind of output you want. If one of your coordinate axes points in exactly the opposite direction from the "angle $0$" direction or the "angle $90$" direction, you change sign on that coordinate before passing it to $\operatorname{atan2}$.

In any case, when in doubt, map out the results you want and the results $\operatorname{atan2}$ gives you in a two tables as shown above, and match up the columns to figure out which variables get passed to $\operatorname{atan2}$ in what ways.

Related Question