15

I'm doing some game programming. FWIW I'm using XNA, but I'm doubtful that this is relevant.

I'd like to convert degrees to a directional vector (ie X and Y) with magnitude 1.

My origin (0,0) is in the upper left.

So I'd like 0 degrees to convert to [0, -1]

I thought the best way to do this was to take my definition of North/Up and rotate it using a matrix, but this does not seem to be working.

Here is the Code...

public class Conversion
{
    public static Vector2 GetDirectionVectorFromDegrees(float Degrees)
    {
        Vector2 North= new Vector2(0, -1);
        float Radians = MathHelper.ToRadians(Degrees);

        var RotationMatrix = Matrix.CreateRotationZ(Radians);
        return Vector2.Transform(North, RotationMatrix);
    }
}

... and here are my unit tests...

[TestFixture]
public class Turning_Tests
{
    [Test]
    public void Degrees0_Tests()
    {
        Vector2 result = Conversion.GetDirectionVectorFromDegrees(0);
        Assert.AreEqual(0, result.X);
        Assert.AreEqual(-1, result.Y);
    }
    [Test]
    public void Degrees90_Tests()
    {
        Vector2 result = Conversion.GetDirectionVectorFromDegrees(90);
        Assert.AreEqual(1, result.X);
        Assert.AreEqual(0, result.Y);
    }
    [Test]
    public void Degrees180_Tests()
    {
        Vector2 result = Conversion.GetDirectionVectorFromDegrees(180);
        Assert.AreEqual(0, result.X);
        Assert.AreEqual(1, result.Y);
    }
    [Test]
    public void Degrees270_Tests()
    {
        Vector2 result = Conversion.GetDirectionVectorFromDegrees(270);
        Assert.AreEqual(-1, result.X);
        Assert.AreEqual(0, result.Y);
    }

}

Am I approaching this all wrong? Should I be using a matrix? Have I screwed up and converted from degrees to radians in the wrong place?

I've seen suggestions that this can be done using code like:

new Vector2((float)Math.Cos(Angle), (float)Math.Sin(Angle));

...or sometimes...

new Vector2((float)Math.Sin(Angle), (float)Math.Cos(Angle));

However these don't seem to work either

Can someone put me on the right path... or better yet give me some code which causes the 4 provided unit tests to path?

Many thanks in advance.

Rory Becker
  • 15,551
  • 16
  • 69
  • 94

1 Answers1

23

Just use:

new Vector2((float)Math.Cos(radians), (float)Math.Sin(radians))

Be sure to convert from degrees to radians with this approach too.

This uses the mathematician's convention of starting from [1, 0] and going in the direction towards [0, 1] (that is counter-clockwise with the orientation that mathematicians use for the two axes).

To use instead your convention (starting from [0, -1] and going in the direction of [1, 0]) you need:

new Vector2((float)Math.Sin(radians), -(float)Math.Cos(radians))

Note that your conversion from degrees to radians can never be exact (it involves something with π). You should allow for some tolerance in your tests. Also, if you use double instead of float for the radians, you will have some extra precision in the intermediate calculation.

Jeppe Stig Nielsen
  • 60,409
  • 11
  • 110
  • 181
  • @RoryBecker See my edit. When working with `Sin` and `Cos` you cannot expect exact values. The values must be very close to the expected value, though. – Jeppe Stig Nielsen Sep 17 '13 at 14:18
  • When I saw my unit tests failing, I assumed that this was bad. As you point out however, this is down to rounding errors, which as it turns out are well within a usable tolerance. Thank you so much :) – Rory Becker Sep 17 '13 at 14:22
  • There are really eight different possible "conventions" for _(a)_ which "corner" (intersection between an axis and the unit circle) to start from (4 possibilities) and _(b)_ which direction (clockwise or counter-clockwise) to use (2 possibilities). That corresponds to the eight possibilities of choosing _(i)_ order of `Cos` and `Sin` (that is `Cos` first (x), then `Sin` (y), or vice versa), _(ii)_ sign (minus or not) on the first coordinate, and _(iii)_ sign on the last coordinate. – Jeppe Stig Nielsen Sep 17 '13 at 14:45
  • FWIW I have upgraded my copy of NUnit and now call new overloads of AreEqual which can take tolerances. In this case I have allowed for tolerances of 0.00001. This appears more than adequate. Thanks once again @Jeppe for your help :) – Rory Becker Sep 17 '13 at 14:59