Sign in to follow this  
h3ro

Rotating sprites in homemade "2d engine"

Recommended Posts

Hallo everyone. Im working on a small project where I am trying to make a simple 2d game using a very limited API. The API only gives you the ability to create a window and return a pointer for all the pixels in the window. Then it is up to me to manipulate that pointer in order to create something useful. So far everything is good, but I am having troubles with getting my sprite rotation to work. The problem is that a screen pixel has a x,y integer value, and the rotated sprite has a x,y float value for all its pixels. Therefor roundoff errors cause some pixels to be on to of each other leaving some blank spaces where I am drawing. Does anyone know of a way to fix this? Here is a picture of the problem [url=http://imageshack.us][img=http://img257.imageshack.us/img257/7503/spritetroublecf2.jpg][/url] And here is the code
void rotatPixelList(std::vector<pixel> &list, float rotation)
{
	float PI =  3.14159265;

	float r = rotation * PI / 180.0;

	for(int i = 0; i < list.size(); i++)
	{
		float x = list[i].xPos;
		float y = list[i].yPos;

		list[i].xPos = x * cos(r) - y * sin(r);
		list[i].yPos = y * cos(r) + x * sin(r);
	}
}

void movePixelList(std::vector<pixel> &list, float x, float y)
{
	for(int i = 0; i < list.size(); i++)
	{
		list[i].xPos += x;
		list[i].yPos += y;
	}
}

void Visualisation::setPixel(std::vector<pixel> list)
{
	for(int i = 0; i < list.size(); i++)
	{
		int offset = 4 * (((int)list[i].yPos * screenRec.getWidth()) + (int)list[i].xPos);

		screenPnt[offset+0] = list[i].b;
		screenPnt[offset+1] = list[i].g;
		screenPnt[offset+2] = list[i].r;
	}
}

Thanks in advance

Share this post


Link to post
Share on other sites
Instead of calculating where the pixels of the sprite image are in the screenbuffer, I'd fill the region on the screenbuffer and look up the sprite image to find what colour each pixel should be filled with.

For sprites I'd probably go for 2 triangles with affine texture mapping and possibly bi linear filtering.

Share this post


Link to post
Share on other sites
Thats sounded like a lot of work.

So basically I have to rewrite my "engine" to a scanline render? As it is now I dont have polygons at all and I am writing directly to the screen (I have a rectangle for each sprite, but the sprite is not really inside it, its just to pass position values around)

Thanks for the replies

Share this post


Link to post
Share on other sites
if dont want a scan line renderer, you could always use this approach, although i don't recommend it - scan line rendering would be much better.

rotate corners of sprite, calculate min-max along x and y directions for an axis aligned bounding box. then run through all the pixels in this box, determine whether pixel is inside the actual sprite rotated box or not, and if it is. look up what is the nearest pixel in the sprite image to this point.

Share this post


Link to post
Share on other sites
Quote:
Original post by luca-deltodesco
if dont want a scan line renderer, you could always use this approach, although i don't recommend it - scan line rendering would be much better.

Its for a introduction to games programming class which im not even taking, so I think that would be a bit over my current skill level. I might look into it though, I find rendering quite interesting.

Quote:
Original post by luca-deltodesco
rotate corners of sprite, calculate min-max along x and y directions for an axis aligned bounding box. then run through all the pixels in this box, determine whether pixel is inside the actual sprite rotated box or not, and if it is. look up what is the nearest pixel in the sprite image to this point.

Not sure if I fully understand what do to here.

1. Rotate the sprite using one of the corners (Does it only work using corners?)
2. Create a new boundingBox (The bounding box should not be rotated, but be aligned to the axis)
3. Not sure what do to here

Thanks for you reply :)

Share this post


Link to post
Share on other sites
heres an image to help describe what i mean:
http://www.ngup.net/view?file=527

the black box is your rotated sprite. the red box is the axis aligned bounding box for it calculated from the min-max values of the sprite's corners. the circles are the pixels.

from here, easiest way is that for each pixel, do the opposite rotation to take you into the sprite's coordinate system - this way its very easy to check whether the pixel is within the black sprite box or not, if it is, then you also have the coordinate to use for the texture look-up.

Share this post


Link to post
Share on other sites
Again, thank you.

Just let me see if I have gotten this right:

1. Rotate the texture
2. Create the bounding box
3. For each pixel(screen pixel, right?) inside the bounding box: Rotate back to get to texture space and find the right pixel in the texture.

Share this post


Link to post
Share on other sites
After fooling around for a while I got it working, but the problem is that its dead slow. I currently get 8 fps (140fps with a blank screen) when I try to rotate a texture.

My problem is that I loop trough the data to many times, but im not sure how to make it faster. Assembly is not an alternative for me :P

Here is the current Code


// Create bounding box
// Rotate the bounding box
// Move the bounding box
// Create a axis aligned bounding box
// Transform the bounding box so that one of the vertex are at 0,0

// For each pixel in the aliignedBox go back to the texture and see if there is a pixel there.
// if it is, copy the color of it
for(int y = lowestY; y < higestY; y++)
{
for(int x = lowestX; x < highestX; x++)
{
// Check if the pixel is inside the texture
//---------------------------------------------

// Move the point from screen space to texture space
float newX = x - moveX;
float newY = y - moveY;

// See if the point is inside the texture
Vector3D c = Vector3D(boundingBox[1].x,boundingBox[1].y, 0);
Vector3D p = Vector3D(newX, newY, 0);

Vector3D v = p - c;
Vector3D v1 = Vector3D(boundingBox[0].x,boundingBox[0].y, 0);
Vector3D v2 = Vector3D(boundingBox[3].x,boundingBox[3].y, 0);

if( ( 0 <= Dot(v,v1) && Dot(v,v1) <= Dot(v1,v1) ) &&
( 0 <= Dot(v,v2) && Dot(v,v2) <= Dot(v2,v2) ) )
{
// Find the closest texture pixel
// ----------------------------------

// Rotate the point back, to find the correct pixel
float newXtemp = newX;
float newYtemp = newY;

newX = abs(newXtemp * cos(-r) - newY * sin(-r));
newY = abs(newY * cos(-r) + newXtemp * sin(-r));

// Loop through the pixelList and find the pixel with the same position value
for(int i = 0; i < curSprite.pixelList.size(); i++)
{
if( ((int)newX == (int)curSprite.pixelList[i].xPos) && ((int)newY == (int)curSprite.pixelList[i].yPos) )
{
// Copy the colors of the pixel into the screenPointer
int offset = 4 * (((int)y * screenRec.getWidth()) + (int)x);

screenPnt[offset+0] = curSprite.pixelList[i].b;
screenPnt[offset+1] = curSprite.pixelList[i].g;
screenPnt[offset+2] = curSprite.pixelList[i].r;
}
}
}
}
}



Thanks,

Share this post


Link to post
Share on other sites
I think you're doing unnecessary work there.
All you need to do is perform the opposite of the rotation for every pixel in the bounding box, then use that coordinate to get the colour from the texture. You certainly shouldn't have to loop through the texture pixels. Unless they are in a random order, surely you can just use (y * texwidth + x)

I made a fairly decent 2D engine myself, but I quit before I finished rotation. :P

Share this post


Link to post
Share on other sites
Quote:
Original post by DeathRay2K
I think you're doing unnecessary work there.
All you need to do is perform the opposite of the rotation for every pixel in the bounding box, then use that coordinate to get the colour from the texture. You certainly shouldn't have to loop through the texture pixels. Unless they are in a random order, surely you can just use (y * texwidth + x)

I made a fairly decent 2D engine myself, but I quit before I finished rotation. :P

Thanks for your reply. You mean using (newY * texwidth + newX)? I keep "getting vector out off range" by doing so. Cant really understand why.

Im starting to think that doing rotation with this API is not possible. With your fix I only get 6fps more, which brings me up to 14. 14fps is kind of low considering that I only are trying to draw a single 64x64 sprite.

EDIT:
Changing to release mode gave me a lot better frame rate. I think its the actually part where I copy information into the screen buffer that is the slow part, becasue it seems like the fps relates haveliy to how many pixels im drawing.

[Edited by - h3ro on February 21, 2008 9:49:25 PM]

Share this post


Link to post
Share on other sites
Quote:
Original post by h3ro
Quote:
Original post by DeathRay2K
I think you're doing unnecessary work there.
All you need to do is perform the opposite of the rotation for every pixel in the bounding box, then use that coordinate to get the colour from the texture. You certainly shouldn't have to loop through the texture pixels. Unless they are in a random order, surely you can just use (y * texwidth + x)

I made a fairly decent 2D engine myself, but I quit before I finished rotation. :P

Thanks for your reply. You mean using (newY * texwidth + newX)? I keep "getting vector out off range" by doing so. Cant really understand why.

Im starting to think that doing rotation with this API is not possible. With your fix I only get 6fps more, which brings me up to 14. 14fps is kind of low considering that I only are trying to draw a single 64x64 sprite.

EDIT:
Changing to release mode gave me a lot better frame rate. I think its the actually part where I copy information into the screen buffer that is the slow part, becasue it seems like the fps relates haveliy to how many pixels im drawing.


You would be suprised this is usually the bottleneck in most games, hence running the lowest resolution provides the best increase in performance.

When your in software mode, most games use pre-made sprites and specific angles and use that, like in starcraft and other such games, because rotating and updating per pixel manually is slow and takes too much time.

It's always better to use opengl/directx to do rotations for full 360 degree's.

Share this post


Link to post
Share on other sites
Quote:
Original post by ViperG
You would be suprised this is usually the bottleneck in most games, hence running the lowest resolution provides the best increase in performance.

When your in software mode, most games use pre-made sprites and specific angles and use that, like in starcraft and other such games, because rotating and updating per pixel manually is slow and takes too much time.

It's always better to use opengl/directx to do rotations for full 360 degree's.

Do you know how many rotation angles each sprite normally have?
How would it be saved? One sprite sheet for each rotation or one massive?

I think I will have the character that is controlled by the player fully rotatable and use pre made rotation for enemies.

Share this post


Link to post
Share on other sites
A couple of years ago, I released the source code for Strange Adventures in Infinite Space (click on "files" on the website). You should find the file "sprites.cpp" and function "ik_drsprite" educational. Moving as much code out of the horizontal scanline loop as possible is the key. That code is executed per-pixel, so all you want to do there is a couple of additions. All real math is outside.

Even though the rotated sprite functions aren't as optimized as I could make them now (you can get rid of the branching) the game ran OK on ~300MHz Pentium2 boxes at 640x480. Assuming you want a resolution about four times higher, CPU's 1.2GHz and above should be enough.

Saving multiple frames for rotations is unnecessary unless you're doing it for an isometric perspective like in an RTS game.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this