I've been working on some steering behaviors and ran into trouble with my logic for converting points in world space into points in local space. I had this (it's not optimized for multiple points yet, but that is not the point of my question):

public Vector2 WorldPointToLocal(Vector2 point)
    var tx = -Vector2.Dot(this.Location, this.Heading);
    var ty = -Vector2.Dot(this.Location, this.HeadingPerpendicular);
    var rotationMatrix = new Matrix
        M11 = this.Heading.X,
        M12 = this.HeadingPerpendicular.X,
        M21 = this.Heading.Y,
        M22 = this.HeadingPerpendicular.Y,
        M31 = tx,
        M32 = ty

    return Vector2.Transform(point, rotationMatrix);

This did not work. The returned point was always well outside the expected range. I had collated most of the mathematics from various sources and began to suspect that Vector2.Transform wasn't doing what I thought it was supposed to do. I found an alternative implementation in C and translated it into C#:

private Vector2 VectorTransform(Vector2 vector, Matrix matrix)
    var tempX = (matrix.M11 * vector.X) + (matrix.M21 * vector.Y) + matrix.M31;
    var tempY = (matrix.M12 * vector.X) + (matrix.M22 * vector.Y) + matrix.M32;

    return new Vector2(tempX, tempY);

When I used this implementation instead of Vector2.Transform, everything worked perfectly.

However, I don't want to leave it at that. I'd like to understand why Vector2.Transform does not do the same thing, and whether there's anything I can do to leverage it instead of writing my own. The API documentation doesn't help at all and I'm a bit clueless when it comes to matrix mathematics.


2 Answers 2


Your translation component is in the wrong basis vector. Vector2.Transform transforms by (x,y,0,1), so the translations should be in the fourth basis to correctly translate.

  • \$\begingroup\$ Thanks Lars. Can you explain further please? Are you saying I should only have to set M41 to tx and M42 to ty (and leave M31 and M32 as is)? I just tried that and it still didn't yield the correct result. \$\endgroup\$
    – me--
    Commented Feb 12, 2012 at 12:18
  • \$\begingroup\$ @user13414 By leaving M31and M32as is, do you mean leaving them like your example, or leaving them at zero? Should be the second. \$\endgroup\$ Commented Feb 12, 2012 at 12:37
  • \$\begingroup\$ My apologies. Yes, I had left M31 and M32 as zero, but I had called my transform method instead of Vector2.Transform. blush. Thanks Lars. \$\endgroup\$
    – me--
    Commented Feb 12, 2012 at 14:07

When the documentation is not enough, I like using a decompiler such as dotPeek to see what's really happening behind the scenes. Running dotPeek on Vector2.Transform gave me this implementation:

/// <summary>
/// Transforms the vector (x, y, 0, 1) by the specified matrix.
/// </summary>
/// <param name="position">The source vector.</param><param name="matrix">The transformation matrix.</param>
public static Vector2 Transform(Vector2 position, Matrix matrix)
  float num1 = (float) ((double) position.X * (double) matrix.M11 + (double) position.Y * (double) matrix.M21) + matrix.M41;
  float num2 = (float) ((double) position.X * (double) matrix.M12 + (double) position.Y * (double) matrix.M22) + matrix.M42;
  Vector2 vector2;
  vector2.X = num1;
  vector2.Y = num2;
  return vector2;

From this you can see that the vector that is being transformed is actually (x, y, 0, 1) and the difference from your code is that you're doing:

... + matrix.M31;
... + matrix.M32;

While Vector2.Translate is doing:

... + matrix.M41;
... + matrix.M42;

So moving txand ty to M41 and M42 should give you the same results as your custom method.

  • \$\begingroup\$ Thanks for the answer. Actually, I had indeed fired up DotPeek to have a look at the implementation. However, all I get in the decompilation is // Stub method ('ret' instruction only). I assumed it was some kind of internal native implementation, but maybe there's something wrong with my DotPeek. Version says Build on 2011-12-27T15:25:00 - same as yours? \$\endgroup\$
    – me--
    Commented Feb 12, 2012 at 14:09
  • \$\begingroup\$ @David XNA isn't open-source? \$\endgroup\$
    – Martin.
    Commented Feb 12, 2012 at 14:20
  • \$\begingroup\$ Oh damn it. I had the DLL from Winphone 7 open instead of the full-blown XNA. Somehow, that DLL consists of stubs - through a mechanism I don't understand - whereas the full XNA successfully decompiles. Many thanks - this will be helpful with other problems too. \$\endgroup\$
    – me--
    Commented Feb 12, 2012 at 14:24
  • \$\begingroup\$ @Martin, XNA is free to use, but XNA itself is not open-source. \$\endgroup\$
    – Nic Foster
    Commented Feb 12, 2012 at 18:23

You must log in to answer this question.

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