Sprite rotation woes

Started by
16 comments, last by Interminable 12 years, 4 months ago
Hello there, I've registered in the hope of resolving some issues I've been having.

I've been working on teach myself how to code with DirectX etc.

To the point, I've been trying to make a 2D game. The time has come where I need to rotate sprites. It appears the way to do this is using D3DXMatrix, ID3DXSprite's SetTransform, with either D3DXMatrixRotationZ or D3DXMatrixTransformation2D.

I have tried both D3DXMatrixRotationZ and D3DXMatrixTransformation2D and have run in to problems with each.

With D3DXMatrixRotationZ, everything appears to rotate. All my walls, along with the player. In addition, it appears to rotate the axis as well; if I say, rotated my player 90 degrees clockwise and hit left, he would move down.

With D3DXMatrixTransformation2D, the above happens, but it also moves all the walls when I move the player as well, which is even worse.


As it was evident that SetTransform applied changes to EVERY sprite I had, I decided that it might be possible to set it to something else for each time I draw a sprite (I believe I 'could' use multiple ID3DXSprite objects, but I from what I've read I gather that would be highly inefficient and the wrong way to go about dealing with this).

So:



g_pD3DXSprite->Begin(D3DXSPRITE_ALPHABLEND);
D3DXMatrixRotationZ(&player01.matrixRotation, player01.rotation);
g_pD3DXSprite->SetTransform(&player01.matrixRotation);
g_pD3DXSprite->Draw(player01.returnTexture(), NULL, new D3DXVECTOR3(0,0,0), &player01.coordinates, 0xffffffff);

if(!walls.empty())
{
for(unsigned int i=0;i<=walls.size()-1;i++)
{
if(g_pD3DXSprite && walls.texture)
{
D3DXMatrixRotationZ(&walls.matrixRotation, 0.0);
g_pD3DXSprite->SetTransform(&walls.matrixRotation);
g_pD3DXSprite->Draw(walls.returnTexture(), NULL, new D3DXVECTOR3(0,0,0), &walls.coordinates, 0xffffffff);
}
}
}
g_pD3DXSprite->End();




Initially it looked promising, it drew the walls orientated in the right direction, and it appeared to draw the player rotated as I wanted it. However, it WAS rotating the wall, just not visibly. At this point I was tearing my hair out as it took me ages to get to that position. :P

So basically, the problems:
  • Rotating just one sprite, not every sprite.
  • Rotating a sprite visibly, but not its axis.


I would greatly appreciate any help and input. I can provide further code that I have if needed.

Thanks in advance. :)
Advertisement
Looks like you are on the right path. The workflow for drawing rotated and non-rotated sprites is typically the following:

Begin Batch
Set Identity Transform (means no transform will take place)
Draw some non-transformed sprites...
End Batch

Begin Batch
Set Transform (matrix generated by RotationX, RotationY, RotationZ or Transformation2D functions in your case)
Draw some rotated or otherwise transformed sprites...
End Batch

Begin Batch
Set Identity Transform
Draw some non-transformed sprites...
End Batch

Here are the things you are missing:

1. Every time you change transform, you need to Begin/End a sprite batch before calling Sprite->SetTransform to set another transform. The way rendering works, a sprite batch will get drawn all at the same time (which is the whole point of sprite batching), with the last transform you set for that batch. Setting transform more than once for the same batch will not have any effect.

2. To "reset" a transform, you pass an identity matrix to SetTransform to cancel out any previous rotation, etc. D3DXMatrixIdentity will generate you an identity matrix. So in your case, call Sprite->Begin, set your transform matrix generated by D3DXMatrixRotationZ, draw player sprite, call Sprite->End, call Sprite->SetTransform passing identity matrix generated by D3DXMatrixIdentity, then Begin, then draw your walls, then End. This will draw the player sprite rotated and walls will be untransformed.

When rotating sprites, you also should be aware of the point you are rotating by, called pivot point or hot spot depending on terminology. If I remember correctly, D3DXSprite will generate its vertices so that any Z rotation applied will automatically rotate the sprite using center as the pivot point. However, if you should want to change this pivot point, you can do so with a Translation matrix generated by D3DXMatrixTranslation and then multiply that with RotationZ matrix to get a rotation about a different pivot point. The translation before rotation will be an offset from current pivot point to the new point.

The last thing I saw in your code is you are using new operator to pass D3DXVECTORs into Sprite->Draw method. If you are using C++ this will create a memory leak, so I recommend allocating the position vars on the stack and taking their address instead. Of course when you progress to the point of having a real game, your data structures for the walls and the player should already use D3DXVECTORs and you can just take an address of those.

For your walls you will probably not want to use transform matrices, because it will make collision detection more difficult later. If you represent your walls as AABBs internally (meaning x, y, width, height rectangle structure) drawing them will be more efficient and collision detection will be easier later on. If you still have problems, maybe post a screen or two. I don't know what "rotating the wall, but not visibly" means.

Looks like you are on the right path. The workflow for drawing rotated and non-rotated sprites is typically the following:

Begin Batch
Set Identity Transform (means no transform will take place)
Draw some non-transformed sprites...
End Batch

Begin Batch
Set Transform (matrix generated by RotationX, RotationY, RotationZ or Transformation2D functions in your case)
Draw some rotated or otherwise transformed sprites...
End Batch

Begin Batch
Set Identity Transform
Draw some non-transformed sprites...
End Batch

Here are the things you are missing:

1. Every time you change transform, you need to Begin/End a sprite batch before calling Sprite->SetTransform to set another transform. The way rendering works, a sprite batch will get drawn all at the same time (which is the whole point of sprite batching), with the last transform you set for that batch. Setting transform more than once for the same batch will not have any effect.

2. To "reset" a transform, you pass an identity matrix to SetTransform to cancel out any previous rotation, etc. D3DXMatrixIdentity will generate you an identity matrix. So in your case, call Sprite->Begin, set your transform matrix generated by D3DXMatrixRotationZ, draw player sprite, call Sprite->End, call Sprite->SetTransform passing identity matrix generated by D3DXMatrixIdentity, then Begin, then draw your walls, then End. This will draw the player sprite rotated and walls will be untransformed.

When rotating sprites, you also should be aware of the point you are rotating by, called pivot point or hot spot depending on terminology. If I remember correctly, D3DXSprite will generate its vertices so that any Z rotation applied will automatically rotate the sprite using center as the pivot point. However, if you should want to change this pivot point, you can do so with a Translation matrix generated by D3DXMatrixTranslation and then multiply that with RotationZ matrix to get a rotation about a different pivot point. The translation before rotation will be an offset from current pivot point to the new point.

The last thing I saw in your code is you are using new operator to pass D3DXVECTORs into Sprite->Draw method. If you are using C++ this will create a memory leak, so I recommend allocating the position vars on the stack and taking their address instead. Of course when you progress to the point of having a real game, your data structures for the walls and the player should already use D3DXVECTORs and you can just take an address of those.

For your walls you will probably not want to use transform matrices, because it will make collision detection more difficult later. If you represent your walls as AABBs internally (meaning x, y, width, height rectangle structure) drawing them will be more efficient and collision detection will be easier later on. If you still have problems, maybe post a screen or two. I don't know what "rotating the wall, but not visibly" means.


[size=2]Well the only reason the walls have any transform stuff at all is because I was trying to 'reverse the changes' caused by the first transform functions used on the player. For the wall they don't do anything. I do not use transforms for positioning or movement. I use separate coordinates that are stored in the sprite objects I have.

Isn't spritebatch for XNA? Or is there something else I need to do?

Also are you basically saying that I should have multiple id3dSprite->Begin() and id3dSprite->End() function calls? Isn't that a bad thing to do? I thought I was only supposed to call Begin() and End() once per frame.

Also I was unaware of the memory leak thing. Do you have anything I could use to read up further on this?

Regards.
"Isn't spritebatch for XNA? Or is there something else I need to do?"

ID3DXSprite interface is used to render a sprite batch. This increases efficiency when sprites (each being 4 vertices and 2 triangle primitives) share the same texture, transform and shaders because all sprites that share these render options can be submited to video card in one Draw call (meaning DrawIndexedPrimitive). Sprite batches are also convenient because ID3DXSprite will sort your sprites within a batch by Z order or texture if you specify. For 2D games, you usually want both options on when drawing the world, and none when drawing UI. In your case, the player sprite does NOT share the same transform as the walls, so it cannot be drawn in the same batch since render states have to be set. Chances are, you are also using a different texture, in which case drawing player will break the batch no matter what.

3D games face exactly the same issues, and you start worrying about the number of batches you submit only when you find your performance unacceptable. I personally target 60 FPS with v-sync, and if my game runs at least at 59-60 on debug build I find no reason to worry about optimizing.

"Also are you basically saying that I should have multiple id3dSprite->Begin() and id3dSprite->End() function calls?"

Yes, let me correct my previous post - there is no need to end the batch by calling End() after every batch. End() will attempt to restore all render states on the device to values captured at the time of Begin() call, which is unnecessary until you finish all drawing for the frame. Call Flush() instead to submit current batch and change render states for the next batch. SetTransform may cause Flush() to be called automatically - I use a custom sprite class in my engine so I don't remember the nuts and bolts of D3DXSprite as well as I used to. In any case, make sure to set transform to identity matrix before drawing things that aren't supposed to be transformed, as opposed to using D3DXMatrixRotateZ with angle of 0.

"Also I was unaware of the memory leak thing. Do you have anything I could use to read up further on this?"

new operator in C++ will allocate an object on the heap. This object will remain allocated until you explicitly deallocate it with delete. If the object is never deallocated, this creates a memory leak - your program bit off a chunk of memory and never returned it back. This is especially problematic inside loops, such as this render loop. Every time the loop is run, a new D3DXVECTOR3 object is allocated to pass to Draw method, and never released. So every frame, you loose at least 24 bytes of heap memory (D3DXVECTOR3 takes at least 12 bytes and you have two heap allocations with new). With time, this will make your program run slower and then crash. Read about heap memory allocation and new/delete operators in C++.
Brief question, how I've set my D3DXVECTOR3 value in the Draw calls below is different, is it a right way to do things or is it still wrong (ie leaky)?



g_pD3DXSprite->Begin(D3DXSPRITE_ALPHABLEND);
D3DXMatrixRotationZ(&player01.matrixRotation, player01.rotation);
g_pD3DXSprite->SetTransform(&player01.matrixRotation);
g_pD3DXSprite->Draw(player01.returnTexture(), NULL, &D3DXVECTOR3(0,0,0), &player01.coordinates, 0xffffffff);

if(!walls.empty())
{
for(unsigned int i=0;i<=walls.size()-1;i++)
{
if(g_pD3DXSprite && walls.texture)
{
D3DXMatrixIdentity(&walls.matrixRotation);
g_pD3DXSprite->SetTransform(&walls.matrixRotation);
g_pD3DXSprite->Draw(walls.returnTexture(), NULL, &D3DXVECTOR3(0,0,0), &walls.coordinates, 0xffffffff);
}
}
}
g_pD3DXSprite->End();



Calling D3DXMatrixIdentity before the drawing of each wall appears to draw them in the right place, but my collisions still don't work properly, it treats the walls as though they HAVE rotated, but visibly they appear normal.

Also is there any way to rotate a texture so that it doesn't affect the axis of the thing rotating?

EDIT: It might be possible the walls haven't rotated, but the axis of the world have!? I've realised that my collisions are currently broken the way they are because axis are not what they are supposed to be. ie y is no longer up or down. Is there any way I can preserve or restore axis after rotating like this?
Set identity transform only once before drawing all the walls, no need to set it for each wall.

How does your collision work?

What do you mean by rotating the thing and not its axes? You are not using a camera transform in your program, so no axis should be affected.

Set identity transform only once before drawing all the walls, no need to set it for each wall.

How does your collision work?

What do you mean by rotating the thing and not its axes? You are not using a camera transform in your program, so no axis should be affected.


Well, basically the way I move my player is, the sprite class stores its coordinates as members. I increment these when I move it, and then I draw the sprite at the appropriate location like this:


g_pD3DXSprite->Draw(player01.returnTexture(), NULL, &D3DXVECTOR3(0,0,0), &player01.coordinates, 0xffffffff);



When the sprite is rotated very slightly clockwise, if I want to move along the x axis in a positive direction, ie right, the sprite moves diagonally right and down, rather than just right. It's like what the sprite uses for its axis have changed. x is no longer a horizontal line.

My collision detection works using the size of the walls and player. ie the sprite's x coordinate plus its width, and its y coordinate plus its height. If I have a vertical wall, but its y axis is no longer vertical, then its collisions will be at an angle.
Sorry for the bump but I am very interested in resolving this, if anyone can help.
i think this will solve your problem

Never say Never, Because Never comes too soon. - ryan20fun

Disclaimer: Each post of mine is intended as an attempt of helping and/or bringing some meaningfull insight to the topic at hand. Due to my nature, my good intentions will not always be plainly visible. I apologise in advance and assure you I mean no harm and do not intend to insult anyone.


i think this will solve your problem


Thanks for the reply. Unfortunately I've already read this, and it doesn't explain how to stop the sprite's axis from being transformed as well. Or rather, how to rotate the image of the sprite without altering how it interprets the world.

This topic is closed to new replies.

Advertisement