If you have a 2D vector expressed as x and y, what's a good way of transforming that into the closest compass direction?
x:+1, y:+1 => NE x:0, y:+3 => N x:+10, y:-2 => E // closest compass direction
Not sure if it's bad form to answer your own question, but I managed to come up with an alternative solution that doesn't use atan or anything beyond simple arithmetic...
The key was to realise you don't need to know the angle of the vector - only the gradient.
Not sure if it can be written more concisely, but it works.
this seems to work:
You have 8 options (or 16 or more if you want even finer precision).
So x=1, y=0 will result in 0, and it's discontinuous at x=-1, y=0, containing both π and -π.
Now we just need to map the output of
Likely the simplest to implement is a incrementing check of angles. Here's some pseudo code that easily be modified for increased precision:
Now to add more precision, simply add the values to the direction enum.
The algorithm works by checking increasing values around the compass to see if our angle lays somewhere between where we last checked and the new position. That's why we start at -PI + increment/2. We want to offset our checks to include equal space around each direction. Something like this:
West is broken in two because of the return values of
Whenever you're dealing with vectors, consider fundamental vector operations instead of converting to angles in some particular frame.
Given a query vector
This generalizes trivially into more dimensions than two, is extensible with arbitrary directions and doesn't suffer frame-specific problems like infinite gradients.
Implementation-wise, this would boil down to associating from a vector in each cardinal direction with an identifier (enum, string, whatever you need) representing that direction. You would then loop over your set of directions, finding the one with the highest dot product.
One way that hasn't been mentioned here is treating the vectors as complex numbers. They don't require trigonometry and can be pretty intuitive for adding, multiplying or rounding rotations, especially since you're already have your headings represented as pairs of numbers.
In case you're not familiar with them, the directions are expressed in the form of a + b(i) with a being the real component and b(i) is the imaginary. If you imagine the cartesian plane with the X being real and Y being imaginary, 1 would be east (right), i would be north.
Here is the key part: The 8 cardinal directions are represented exclusively with the numbers 1, -1 or 0 for their real and imaginary components. So all you have to do is reduce your X, Y coordinates as a ratio and round both to the closest whole number to get the direction.
For heading-to-nearest diagonal conversion, reduce both X and Y proportionally so the larger value is exactly 1 or -1. Set
Rounding both components of what was originally (10, -2) gives you 1 + 0(i) or 1. So the closest direction is east.
The above doesn't actually require the use of a complex number structure, but thinking of them as such makes it quicker to find the 8 cardinal directions. You can do vector math the usual way if you want to get the net heading of two or more vectors. (As complex numbers, you don't add, but multiply for the result)
The simplest way is probably to get the angle of the vector using
The reason for the
If your language doesn't happen to provide a convenient round-to-nearest function, you can use a truncating integer conversion instead and just add 0.5 to the argument, like this:
Note that, in some languages, the default float-to-integer conversion rounds negative inputs up towards zero rather than down, which is another reason to make sure that the input is always positive.
Of course, you can replace all occurrences of
When you want a string:
This gives you constants by utilizing bitfields:
A slight performance improvement would be to put the