views:

3258

answers:

4

Okay, this is something that should be a simple matrix question, but my understanding of matrices is somewhat limited. Here's the scenario: I have a 1px by 1px sprite that I want to scale by some amount x and y (different amounts on each side), and then I want to rotate that sprite by some angle, and then I want to be able to precisely position the whole thing (from the top left or the center, makes no difference to me).

So far my code is vaguely close, but it tends to be off by some random amount depending on the angle I pass in.

I would think that this would do it:

        Point center = new Point( 50, 50 );
        float width = 60;
        float height = 100;
        float angle = 0.5;
        Vector3 axis = new Vector3( center.X, center.Y, 0 );
        axis.Normalize();
        Matrix m = Matrix.Scaling( width, height, 0 ) *
            Matrix.RotationAxis( axis, angle ) *
            Matrix.Translation( center.X, center.Y, 0 );

But it tends to shrink the scale of the rotated line way down, even though I think it's positioning it sort of right.

I've also tried this:

  Matrix m = Matrix.Transformation2D( new Vector2( center.X, center.Y ), 0f,
      new Vector2( width, height ), new Vector2( center.X, center.Y ),
      angle, Vector2.Zero );

The line looks exactly right, with the exact right size and shape, but I can't position it correctly at all. If I use the translation vector at the end of the call above, or if I set a position using Sprite.Draw, neither works right.

This is all in SlimDX. What am I doing wrong?

+3  A: 

I just went through the pain of learning matrix transformation, but using XNA.

I found these articles to be very helpful in understanding what happens. The method calls are very similar in XNA and the theory behind it all should apply to you, even with SlimDX.

From glancing at your code, I think you should be translating at the start, to the origin, and then translating again at the end, to the final position, though I'm still a little bit of a newbie at this as well.

The order I would do it in is:

  • Translate to origin
  • Scale
  • Rotate
  • Translate to desired location

The reason for translating to the origin first is that rotations are based from the origin. Therefore to rotate something about a certain point, place that point on the origin before rotating.

Ben S
Hmm... this seems very close. The translate to origin, scale, and rotate steps work great, but then when I try to translate to the desired location it acts really strangely -- I change the x coodinate, and it moves in both x and y. I'm thinking that it is translating in the rotated space...
x4000
Another thing that is not yet clear to me is how to translate to the origin. I have two points, plus a third that is in the center of them. I've been translating the center to the origin, but should I be translating in a different way?
x4000
Rather than translate back to where you want, can you just change the position of where you draw?
Ben S
Change the position of where I draw... you mean adjust the world coordinates over, and then adjust them back? I'm hesitant to do that, especially since I'm already messing with world coordinates for simulated 2d zoom, etc. I think I'm close to having this working.
x4000
+1  A: 

What you need to do is do each transformation one step at a time. Do the scaling first draw it. Is it where you expect it. Then throw in the the rotate, and then the translate. This will help uncover incorrect assumptions.

You can also draw in temporary lines to help figure where the coordinates are. For example if you do your stretching and rotation and expect your end point to be at 0,0. Drawing a another line with one endpoint at 0,0 will serve as a double check to see if that really the case.

For your specific problem the problem may be with the rotation. After you have scaled and rotate the line is now off center causing you problems when you are translating.

The general solution is to move the line back to the origin do any operations that involve changing it shape or orientation. Afterward because you are at a known good location you can translate to your final destination.

RESPONSE TO COMMENTS

If translation is an issue and you are transforming the coordinate system then you will need to write a conversion function to convert between the old system and the new.

For example if you are rotating 45 degrees. Before the rotation you could translate by 0,1 to move up 1 inch. After rotation you will have to translate by roughly -.70707, .070707 to move up 1 inch relative to the original coordinate system.

RS Conley
I've already tried doing each part separately -- I should have mentioned that. Scaling and translation work perfectly. But as you say, when I add rotation it gets off. And yes I do have to icons at the reference points so I can see how close I am. I'm going to try the offsetting now.
x4000
Rotation messes up since it's rotated about the origin.
Ben S
I've now tried moving back to the origin and then translating back out to where I want. That seems close, but the translation is off because it is translating inside the rotated coordinate space, it seems.
x4000
transforming the coordinate space of the drawing object rather than the object itself would cause many of the strange behaviors you see.
RS Conley
+1  A: 

Are you rotating around the correct axis? I'm a newbie at matrix stuff, but it seems to me that since the sprite exists in the x, y space, the rotation axis should be the Z-Axis - i.e. (0,0,1).

Aric TenEyck
Hmm, that's a good point, but my rotation seems to be working fine when I just do the scaling and it -- the problem comes when I then try to position it as well. I can make any two out of these three work, but there's something I'm doing wrong when putting it all together!
x4000
Hey, good catch with the Z axis -- with my first example above, that is quite relevant.
x4000
+1  A: 

Okay, this is now working. Here's my working code for this, in case someone else needs it:

    Point sourceLoc = new Point ( 50, 50 );
    float length = 60;
    float thickness = 2;
    float angle = 0.5;
    Matrix m = Matrix.Scaling( length, thickness, 0 ) *
            Matrix.RotationZ( angle ) *
            Matrix.Translation( sourceLoc.X, sourceLoc.Y, 0 );
    sprite.Transform = m;

    sprite.Draw( this.tx, Vector3.Zero, Vector3.Zero, Color.Red );

This will draw an angled line of your chosen length, with a thickness equal to your chosen thickness (presuming your texture is a 1x1 pixel white image). The source location is where the line will emit from, with whatever angle you specify (in radians). So if you start at zero and increment by something like 0.1 until you hit 2PI, and then reset to 0, you'll have a line that rotates around a center like a clock hand or radar sweep. This is what I was looking for -- thanks to all who contributed!

x4000