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.


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-- Feb 12 '12 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\$ – David Gouveia Feb 12 '12 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-- Feb 12 '12 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-- Feb 12 '12 at 14:09
  • \$\begingroup\$ @David XNA isn't open-source? \$\endgroup\$ – Martin. Feb 12 '12 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-- Feb 12 '12 at 14:24
  • \$\begingroup\$ @Martin, XNA is free to use, but XNA itself is not open-source. \$\endgroup\$ – Nic Foster Feb 12 '12 at 18:23

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.