# snapping an angle to the closest cardinal direction

I'm developing a 2D sprite-based game, and I'm finding that I'm having trouble with making the sprites rotate correctly. In a nutshell, I've got spritesheets for each of 5 directions (the other 3 come from just flipping the sprite horizontally), and I need to clamp the velocity/rotation of the sprite to one of those directions. My sprite class has a pre-computed list of radians corresponding to the cardinal directions like this:

protected readonly List<float> CardinalDirections = new List<float>
{
MathHelper.PiOver4,
MathHelper.PiOver2,
MathHelper.PiOver2 + MathHelper.PiOver4,
MathHelper.Pi,
-MathHelper.PiOver4,
-MathHelper.PiOver2,
-MathHelper.PiOver2 + -MathHelper.PiOver4,
-MathHelper.Pi,
};


Here's the positional update code:

if (velocity == Vector2.Zero)
return;

var rot = ((float)Math.Atan2(velocity.Y, velocity.X));
TurretRotation = SnapPositionToGrid(rot);

var snappedX = (float)Math.Cos(TurretRotation);
var snappedY = (float)Math.Sin(TurretRotation);
var rotVector = new Vector2(snappedX, snappedY);

velocity *= rotVector;
//...snip

private float SnapPositionToGrid(float rotationToSnap)
{
if (rotationToSnap == 0)
return 0.0f;

var targetRotation = CardinalDirections.First(x =>
(x - rotationToSnap >= -0.01 && x - rotationToSnap <= 0.01));
return (float)Math.Round(targetRotation, 3);

}


What am I doing wrong here? I know that the SnapPositionToGrid method is far from what it needs to be - the .First(..) call is on purpose so that it throws on no match, but I have no idea how I would go about accomplishing this, and unfortunately, Google hasn't helped too much either. Am I thinking about this the wrong way, or is the answer staring at me in the face?

EDIT: Thanks Gregory for the answer. Only thing I really had to modify from the answer was to re-construct the velocity vector to the new snapped rotation (I'm sure there are further optimizations that I can do, but for now it's enough that it works!):

        var snappedX = (float)Math.Round(Math.Cos(TurretRotation), 3) *
velocity.Length();
var snappedY = (float)Math.Round(Math.Sin(TurretRotation), 3) *
velocity.Length();
var rotVector = new Vector2(snappedX, snappedY);


FOLLOW-UP EDIT: The initial answer worked very well as a naive implementation, but suffered from inaccuracies of up to 45 degrees when dealing with negative angles; -3.14 snapped to -2.356 for instance. To correct that, I added a check to account for those situations. Below is a complete implementation of a SnapToGrid method. Some optimizations have been made, like pre-computing the various divisors of Pi to avoid the division. Further optimization could possibly be achieved by looking for short-circuit scenarios, i.e. if rotationToSnap ~=0, but if anyone has further optimizations, I'd be curious to see them!

public static float SnapPositionToGrid(float rotationToSnap)
{
if (rotationToSnap == 0)
return 0.0f;

var modRot = rotationToSnap  % MathHelper.PiOver4;

float finalRot;

if (modRot < RoundedPiOver8)
{
if (modRot < -RoundedPiOver8)
finalRot = rotationToSnap + -MathHelper.PiOver4 - modRot;
else
finalRot = rotationToSnap - modRot;
}
else
finalRot = rotationToSnap + MathHelper.PiOver4 - modRot;

return (float)Math.Round(finalRot, 3);
}


I'd do this mathematically, assuming I understand your question. You want your rotation to be an integer multiple of PiOver4, right? So divide by your direction increment, round to the nearest integer, and multiply by the increment again. An unoptimized version would be:

return PiOver4 * (float) Math.Round(rotationToSnap / PiOver4);


I'm not too familiar with optimization in XNA; if modulos and comparisons are cheaper than multiplications and divisions (as I suspect), it'll be faster to do something like:

var modRot = rotationToSnap % PiOver4;
if(modRot < PiOver8) return rotationToSnap - modRot;
else return rotationToSnap + PiOver4 - modRot;


I haven't tested that code or anything, but it or a similar approach should work.

• Anyone reading this answer later - Gregory's answer is a great approach, but can be very inaccurate (up to 45d/0.785r!) when dealing with negative angles. Please see my edit for a corrected, semi-optimized answer Jan 27 '11 at 15:46
• You're totally right; modulos don't work as you'd intuitively expect when dealing with negative numbers. Sorry for the omission, and I'm glad you found it! Jan 27 '11 at 21:33
• If you're calling Math.Round anyway, I would expect the first algorithm to be faster, and you wouldn't have to deal with the negative modulo crap at all.
– user744
Jan 27 '11 at 22:45

Gregory's answer can be amended to handle negative rotation by just wrapping and unwrapping by a full rotation:

return PiOver4 * (float) (Math.Round(TwoPi + rotationToSnap / PiOver4) - TwoPi);

• That won't work right at all, you're adding an unscaled 2pi to the scaled rotation. For example, it will round pi/4 to 0.56. (In fact, I'm not sure what you think in Gregory's answer won't work for negative rotations.)
– user744
Jan 28 '11 at 23:55
• Ah, you're right. For some reason I thought modulo's negative number behavior was involved, but of course it isn't. This is what I get for posting before coffee. Jan 31 '11 at 18:26