Archived

This topic is now archived and is closed to further replies.

SpazBoy the Mitey

2d Bitmap rotation under DirectDraw

Recommended Posts

I''m sorry to say that I don''t think there are any DirectDraw commands to let you do that with the same bitmap... (If anyone disagrees, please say so.)

You could make several bitmaps that are rotated versions of the original (as animation frames) and then display them consecutively. I don''t think that the Windows Paint application performs rotations, but the GIMP might--I''d have to look into that.

Hope that helps!

Share this post


Link to post
Share on other sites
...Or i could always pre-do them as animation frames yes thats
a good option performance wise.

I wasn''t asking for a DirectDraw function to do the job though,
I was after a bitmap rotator in general (or a texturemapper)

Share this post


Link to post
Share on other sites
oh wait, I have a sprite with 3 animations at 16 frames each
as well as a still idle image

so thats 17,640 frames.

at 4.19 KB per frame, thats approx 72 megabytes just for
this one sprite and I really don''t think its worth it to be
honest.

Share this post


Link to post
Share on other sites
how did you get 17,640 frames
from 3 animations with 16 frames in each animations

thats only 48 frames

at 4.19 KB a frame thats only 201.12 KB total
thats seems pretty low to me

Share this post


Link to post
Share on other sites
I believe the 17,640 came from 49 frames multiplied by 360 degrees.

If you wanted to you could sacrifice on the rotation by doing the rotation in steps. Even if you did just 2 degree steps you would cut the frame count by half. 10 degree steps would knock the frame count to 1,764. Just a thought...

Just had another thought too. I'm pretty sure DDraw7 supports rotating an image on a multiple of 90 degrees. So you could just pre-rotate the first quarter-circle of the sprite. Then when blitting use an algorithm to figure out if you need to mirror the sprite on an axis. That way you get a whole 360 degree rotation. I hope that made sense.

Edited by - doombunny on February 20, 2002 2:36:24 AM

Share this post


Link to post
Share on other sites

for (x,y) = pixel on the target bitmap T
figure out (x0,y0) whence in the source bitmap S
the pixel T(x,y) comes from
( e.g. x0 = cos(-angle)*x,
y0 = sin(-angle)*y,
if you arer centered at (0,0) )
plot the pixel at T(x,y) with the color from S(x0,y0),
possibly with supersampling
(e.g. averaging the neighbouring colors)

Share this post


Link to post
Share on other sites
Well it''s your classical stupid ''copy the pixels from the original image, rotate them, write them in the target image'' algorithm, except that you are iterating over the target pixels instead of the source pixels so as to avoid any holes in the target image.

That''s clear enough I think.

Just remember to precompute sin and cos.

Share this post


Link to post
Share on other sites
Hi SpazBoy,

I used to use DirectDraw and put together a half-decent sprite engine. Then I spent about a year doing very little games programming and have only recently got back into it. Instead of sticking with the old DirectDraw stuff, i''ve decided to take the plunge and learn Direct3D. I''d highly recommend you do the same.

I''m astounded at how easy it has been to learn the basics of Direct3D. DX8 comes with lots of useful helper functions and classes, including a 2D sprite class. There''s no complicated maths involved and you get the benefit of being able to rotate and scale your sprites, as well as being able to so all sorts of nice transparency effects.

Of course, you may WANT to learn how to do all the rotating stuff yourself, but if you would rather get on with writing your game, download the DX8.1 SDK, along with a few tutorials on doing 2D Sprites with D3D (there are lots of these around), and you''ll be up-and-running within days.

Share this post


Link to post
Share on other sites
Doh, doh, doh...

I promised to write an article about this but never did.

here's my function. It's not fully optimized but it works quite fast:

    
WorkSurface::RotateBlit(int x, int y, float alpha, SURFACEDATA Scr,
bool ColorKey)
{
DDSURFACEDESC2 SurfaceDesc2;
DDCOLORKEY ddck;
int lPitch2;
USHORT* Buffer2;

int ux,uy,vv;
float hw,hh,x1,y1,x2,y2,x3,y3,mx,my,mx2,my2,ty,ty2,rx,ry,alpha2,r;

alpha = alpha * (2.0f*PI/360.0f);

hw=Scr.Width/2.0f;
hh=Scr.Height/2.0f;


//ColorKey

Scr.Surface->GetColorKey(DDCKEY_SRCBLT, &ddck);

//Locks the destination surface

INITDESC(SurfaceDesc2);
SurfaceData.Surface->Lock(NULL, &SurfaceDesc2, DDLOCK_SURFACEMEMORYPTR |
DDLOCK_WAIT, NULL);
Buffer=(USHORT *)SurfaceDesc2.lpSurface;
lPitch=(int)(SurfaceDesc2.lPitch >> 1);

//Locks the source surface

INITDESC(SurfaceDesc2);
Scr.Surface->Lock(NULL, &SurfaceDesc2, DDLOCK_SURFACEMEMORYPTR |
DDLOCK_WAIT, NULL);
Buffer2=(USHORT *)SurfaceDesc2.lpSurface;
lPitch2=(int)(SurfaceDesc2.lPitch >> 1);

//radien

r=sqrtf(hw*hw+hh*hh);

// #1

/* ux=-hw;
uy=-hh;*/

alpha2=-(acosf(-hw/r))+alpha;
x1=(x+cosf(alpha2)*r);
y1=(y+sinf(alpha2)*r);

// #2

alpha2=-(acosf(-hw/r))+alpha;
x2=(x+cosf(alpha2)*r);
y2=(y+sinf(alpha2)*r);

// #3

alpha2=acosf(hw/r)+alpha;
x3=(x+cosf(alpha2)*r);
y3=(y+sinf(alpha2)*r);

mx=(x2-x1)/(Scr.Width);
my=(y2-y1)/(Scr.Width);
mx2=(x3-x2)/(Scr.Height);
my2=(y3-y2)/(Scr.Height);

for(uy=0;uy<Scr.Height;uy++){
ty=(uy*mx2);
ty2=(uy*my2);
rx=x1;
ry=y1;
for(ux=0;ux<Scr.Width;ux++){
rx+=mx;
ry+=my;

if(Buffer2[ux+uy*lPitch2] != ddck.dwColorSpaceLowValue)
{
vv=int(rx+ty)+int(ry+ty2)*lPitch;
Buffer[vv]=Buffer2[ux+uy*lPitch2];
Buffer[vv+1]=Buffer2[ux+uy*lPitch2];
}
}}


Scr.Surface->Unlock(NULL);
SurfaceData.Surface->Unlock(NULL);
}



It's from my WorkSurface class.

The INITDESC zeromemory:s and stuff for the surfacedesc (the regular + setsize thing-a-ma-bob)

Well... you'll figure it out. This works. I have a demo I could send you if you need proof

/MindWipe

"If it doesn't fit, force it; if it breaks, it needed replacement anyway."

[EDIT] don't mock me for all the varliables, it´s from some old code. I can do better now [EDIT}

Edited by - MindWipe on February 20, 2002 3:03:15 PM

Share this post


Link to post
Share on other sites
you also can use Sprites (that''s DX8 tho) or use primitives with you bmp as texture, I have a rotation routine somewhere (if you want to rotate around the center you''ll need some nasty math stuff) if you want it.

Share this post


Link to post
Share on other sites
The Blt function(directdrawsurface) has a fielsd for angle to rotate by. I think u can use that. I remember seeing that in MSDN. Dont have it right now, so check yourself.

The BltFast function does not have this option though.

------------------------------------

So Long, and Thanks for All the Fish !

Share this post


Link to post
Share on other sites
MindWipe - This was very helpful... and it kinda works... When I run it with the code it only is rotating the first column of the bitmap. Here's the code I used (it's edited to fit the game's design):
void bitmap::drawRotate(int iX, int iY, float rotate, USHORT *Buffer)
{
USHORT *Buffer2;
main *m_main;
draw *m_draw;
m_main=m_main->getInstance();
m_draw=&m_main->m_draw;
int ux,uy,vv;
float hw,hh,x1,y1,x2,y2,x3,y3,mx,my,mx2,my2,ty,ty2,rx,ry,alpha2,r;
rotate=(float)(rotate*(2.0f*PI/360.0f));
hw=m_iWidth/2.0f;
hh=m_iHeight/2.0f;
Buffer2=m_p_usBuffer;
r=sqrtf(hw*hw+hh*hh);
alpha2=-(acosf(-hw/r))+rotate;
x1=(iX+cosf(alpha2)*r);
y1=(iY+sinf(alpha2)*r);
alpha2=-(acosf(-hw/r))+rotate;
x2=(iX+cosf(alpha2)*r);
y2=(iY+sinf(alpha2)*r);
alpha2=acosf(hw/r)+rotate;
x3=(iX+cosf(alpha2)*r);
y3=(iY+sinf(alpha2)*r);
mx=(x2-x1)/m_iWidth;
my=(y2-y1)/m_iHeight;
mx2=(x3-x2)/m_iWidth;
my2=(y3-y2)/m_iHeight;
for(uy=0;uy {
ty=(uy*mx2);
ty2=(uy*my2);
rx=x1;
ry=y1;
for(ux=0;ux {
rx+=mx;
ry+=my;
if((Buffer2[ux+uy*m_iWidth])!=(CREATE_16BIT_COLOR(0,255,0)))
{
vv=int(rx+ty)+int(ry+ty2)*640;
Buffer[vv]=Buffer2[ux+uy*m_iWidth];
Buffer[vv+1]=Buffer2[ux+uy*m_iWidth];
}
}
}

And I can't figure out why, because when I do a normal draw of the bitmap the whole thing comes out. See anything wrong?

Edited by - ANTZelda56 on February 23, 2002 1:26:09 PM

Share this post


Link to post
Share on other sites
The problem, to me, seems that mx and my are always 0, because x2-x1 and y2-y1 are always 0. rx and ry then are never incrimented every loop and vv stays the same on every row. Am I correct?

Share this post


Link to post
Share on other sites
DirectDraw does in fact have its own rotation, im not exactly sure how to do it, but if you use Blt with a DDBLTFX structure filled in with the right values for your rotation (look up Blt and DDBLTFX in the docs), it should do it
only problem is 2d rotation hardware isnt too common from what i understand

ByteMe95::~ByteMe95()
My S(h)ite

Share this post


Link to post
Share on other sites
ByteMe95 is right. In TOTWGPG, I read that DirectDraw supports bitmap rotation, but it is only implemented in HAL. This means it only works if your hardware supports it (which supposedly is rare). Apparently, Microsoft attempted to implement it in software, but it was really slow.

Share this post


Link to post
Share on other sites
DirectDraw Rotation = half assed attempt & basically nonexistent.

with the code that MindWipe posted, I found the same problem as ANTZelda56 did, but i found a correction for it:


I rewrote it as a member function for a surface wrapper,
and changed it to 8 bit though. It assumes that the source surface (to which this function belongs) is locked. Buffer8 is a pointer to the source surface, and SurfaceDesc2 is a DDSURFACEDESC2 struct for the source.



void CSurface::RotateBlit8(int x, int y, float alpha, LPDIRECTDRAWSURFACE7 Dst)
{
DDSURFACEDESC2 SurfaceDesc2;
int lPitch2;
unsigned char* Buffer2;
int ux,uy,vv;
float hw,hh,x1,y1,x2,y2,x3,y3,mx,my,mx2,my2,ty,ty2,rx,ry,alpha2,r;

unsigned char *Buffer;
int lPitch;

alpha = alpha * (6.28/360.0f);
hw=m_ddsd.dwWidth/2.0f;
hh=m_ddsd.dwHeight/2.0f;


//Locks the destination surface
ZeroMemory(&SurfaceDesc2,sizeof(SurfaceDesc2));
SurfaceDesc2.dwSize = sizeof(SurfaceDesc2);
Dst->Lock(NULL, &SurfaceDesc2, DDLOCK_SURFACEMEMORYPTR | DDLOCK_WAIT, NULL);

Buffer=(unsigned char *)SurfaceDesc2.lpSurface;
lPitch=(int)SurfaceDesc2.lPitch;

Buffer2=Buffer8;
lPitch2=(int)m_ddsd.lPitch;

//radien
r=sqrtf(hw*hw+hh*hh);
// #1
/* ux=-hw; uy=-hh;*/
alpha2=-(acosf(-hw/r))+alpha;
x1=(x+cosf(alpha2)*r);
y1=(y+sinf(alpha2)*r);

// #2
alpha2=-(acosf(hw/r))+alpha;
x2=(x+cosf(alpha2)*r);
y2=(y+sinf(alpha2)*r);

// #3
alpha2=acosf(hw/r)+alpha;
x3=(x+cosf(alpha2)*r);
y3=(y+sinf(alpha2)*r);

mx=(x2-x1)/(m_ddsd.dwWidth);
my=(y2-y1)/(m_ddsd.dwWidth);
mx2=(x3-x2)/(m_ddsd.dwHeight);
my2=(y3-y2)/(m_ddsd.dwHeight);

for(uy=0;uy {
ty=(uy*mx2);
ty2=(uy*my2);
rx=x1;
ry=y1;

for(ux=0;ux {
rx+=mx;
ry+=my;
if(Buffer2[ux+uy*lPitch2] != 16)
{
vv=(int)(rx+ty)+(int)(ry+ty2)*lPitch;
Buffer[vv]=Buffer2[ux+uy*lPitch2];
Buffer[vv+1]=Buffer2[ux+uy*lPitch2];
}
}
}

Dst->Unlock(NULL);
}


I also dug through the articles here at GameDev.net and found those old Pascal tutorials by Denethor of Asphyxia, and wrote my own simple texture mapper to compare speed:


void inline TraceLine(int x1,int y1,int x2,int y2,int minx[2000],int maxx[2000],int tx1,int ty1,int tx2,int ty2,int tex[2000][2][2])
{
int x,y,deltax,deltay,xinc1,xinc2,yinc1,yinc2,den,num,numadd,numpixels,curpixel;

float tx,ty,txi,tyi;
deltax = abs(x2 - x1); // The difference between the x''s
deltay = abs(y2 - y1); // The difference between the y''s
x = x1; // Start x off at the first pixel
y = y1;
tx = tx1;
ty = ty1;


if (x2 >= x1) // The x-values are increasing
{
xinc1 = 1;
xinc2 = 1;
}
else // The x-values are decreasing
{
xinc1 = -1;
xinc2 = -1;
}

if (y2 >= y1) // The y-values are increasing
{
yinc1 = 1;
yinc2 = 1;
}
else // The y-values are decreasing
{
yinc1 = -1;
yinc2 = -1;
}

if (deltax >= deltay) // There is at least one x-value for every y-value
{
xinc1 = 0; // Don''t change the x when numerator >= denominator
yinc2 = 0; // Don''t change the y for every iteration
den = deltax;
num = deltax / 2;
numadd = deltay;
numpixels = deltax; // There are more x-values than y-values
}
else // There is at least one y-value for every x-value
{
xinc2 = 0; // Don''t change the x for every iteration
yinc1 = 0; // Don''t change the y when numerator >= denominator
den = deltay;
num = deltay / 2;
numadd = deltax;
numpixels = deltay; // There are more y-values than x-values
}

txi = (tx2 - tx1) / (float)numpixels;
tyi = (ty2 - ty1) / (float)numpixels;

for (curpixel = 0; curpixel <= numpixels; curpixel++)
{
//PUTPIXEL(DC,x,y,r,g,b); // Draw the current pixel
if (minx[y] > x)
{
minx[y] = x;
tex[y][0][0] = (int)tx;
tex[y][0][1] = (int)ty;
}

if (maxx[y] < x)
{
maxx[y] = x;
tex[y][1][0] = (int)tx;
tex[y][1][1] = (int)ty;
}
num += numadd; // Increase the numerator by the top of the fraction
if (num >= den) // Check if numerator >= denominator
{
num -= den; // Calculate the new numerator value
x += xinc1; // Change the x as appropriate
y += yinc1; // Change the y as appropriate

}
x += xinc2; // Change the x as appropriate
y += yinc2; // Change the y as appropriate
tx += txi;
ty += tyi;
}

}

void CSurface::RotaBlit8(int x,int y,int angle,unsigned char *tgtBuffer,long tgtPitch)
{
float x1,y1,x2,y2,x3,y3,x4,y4;
float hw,hh;

float stx,sty,tix,tiy;

unsigned char col;

int miny,maxy;
int minx[2000],maxx[2000];
int texpoint[2000][2][2];


//0. Set up min/max arrays
for (int i=0; i<2000; i++)
{
minx = 640;
maxx[i] = 0;
}

//1. Rotate 4 points
hw = m_ddsd.dwWidth >> 1;
hh = m_ddsd.dwHeight >> 1;

Rotate(-hw,-hh,angle,&x1,&y1);
Rotate( hw,-hh,angle,&x2,&y2);
Rotate( hw, hh,angle,&x3,&y3);
Rotate(-hw, hh,angle,&x4,&y4);

x1 += x; x2 += x; x3 += x; x4 += x;
y1 += y; y2 += y; y3 += y; y4 += y;

//2. Determine minimum / maximum Y
miny = y1; if (y2 < miny) miny = y2; if (y3 < miny) miny = y3; if (y4 < miny) miny = y4;
maxy = y1; if (y2 > maxy) maxy = y2; if (y3 > maxy) maxy = y3; if (y4 > maxy) maxy = y4;


//3. Trace through the 4 lines, establish min/max X
TraceLine(x1,y1,x2,y2,minx,maxx,0,0,m_ddsd.dwWidth,0,texpoint);
TraceLine(x2,y2,x3,y3,minx,maxx,m_ddsd.dwWidth,0,m_ddsd.dwWidth,m_ddsd.dwHeight,texpoint);
TraceLine(x3,y3,x4,y4,minx,maxx,m_ddsd.dwWidth,m_ddsd.dwHeight,0,m_ddsd.dwHeight,texpoint);
TraceLine(x4,y4,x1,y1,minx,maxx,0,m_ddsd.dwHeight,0,0,texpoint);

//4. Loop through Y, rendering by scanline
for (i=miny;i<=maxy;i++)
{
stx = texpoint[i][0][0];
sty = texpoint[i][0][1];

if (maxx[i] - minx[i] > 0)
{
tix = (texpoint[i][1][0] - stx) / (float)(maxx[i] - minx[i]);
tiy = (texpoint[i][1][1] - sty) / (float)(maxx[i] - minx[i]);
}
else
{ tix = 0; tiy = 0;}

for (int j=minx[i];j<=maxx[i];j++)
{
col = Buffer8[(int)stx + (int)sty*m_ddsd.lPitch];
if (col != 16)
tgtBuffer[i*tgtPitch+j] = col;
stx += tix;
sty += tiy;
}
}

}


Again, it''s a member function and Buffer8 is the source surface. m_ddsd is the DDSURFACEDESC2 for the source. Unfortunately, these functions really only go well at low resolutions (which is why I coded them for 8 bit

Share this post


Link to post
Share on other sites
Hopefully you''ll get it to work.

I uploaded my demo at GD.net to give you hope
(if it runs) heh

It uses the same rotation function as I posted, altough the
one I posted could include bugs because I tried to use it again but had some troubles. But here''s one that should work I coped from a working backup of my wrapper:

  
//-----------------------------------------------------------------------------

// Name: RotateBlit

// Desc: Rotates and blits a surface

//-----------------------------------------------------------------------------

WorkSurface::RotateBlit(int x, int y, float alpha, SURFACEDATA Scr,
bool ColorKey)
{
DDSURFACEDESC2 SurfaceDesc2;
DDCOLORKEY ddck;
int lPitch2;
USHORT* Buffer2;

int ux,uy,vv;
float hw,hh,x1,y1,x2,y2,x3,y3,mx,my,mx2,my2,ty,ty2,rx,ry,alpha2,r;

alpha = alpha * (2.0f*PI/360.0f);

hw=Scr.Width/2.0f;
hh=Scr.Height/2.0f;


//ColorKey

Scr.Surface->GetColorKey(DDCKEY_SRCBLT, &ddck);

//Locks the destination surface

INITDESC(SurfaceDesc2);
SurfaceData.Surface->Lock(NULL, &SurfaceDesc2, DDLOCK_SURFACEMEMORYPTR |
DDLOCK_WAIT, NULL);
Buffer=(USHORT *)SurfaceDesc2.lpSurface;
lPitch=(int)(SurfaceDesc2.lPitch >> 1);

//Locks the source surface

INITDESC(SurfaceDesc2);
Scr.Surface->Lock(NULL, &SurfaceDesc2, DDLOCK_SURFACEMEMORYPTR |
DDLOCK_WAIT, NULL);
Buffer2=(USHORT *)SurfaceDesc2.lpSurface;
lPitch2=(int)(SurfaceDesc2.lPitch >> 1);

//radien

r=sqrtf(hw*hw+hh*hh);

// #1

/* ux=-hw;
uy=-hh;*/

alpha2=-(acosf(-hw/r))+alpha;
x1=(x+cosf(alpha2)*r);
y1=(y+sinf(alpha2)*r);

// #2

alpha2=-(acosf(-hw/r))+alpha;
x2=(x+cosf(alpha2)*r);
y2=(y+sinf(alpha2)*r);

// #3

alpha2=acosf(hw/r)+alpha;
x3=(x+cosf(alpha2)*r);
y3=(y+sinf(alpha2)*r);

mx=(x2-x1)/(Scr.Width);
my=(y2-y1)/(Scr.Width);
mx2=(x3-x2)/(Scr.Height);
my2=(y3-y2)/(Scr.Height);

for(uy=0;uy<Scr.Height;uy++){
ty=(uy*mx2);
ty2=(uy*my2);
rx=x1;
ry=y1;
for(ux=0;ux<Scr.Width;ux++){
rx+=mx;
ry+=my;

if(Buffer2[ux+uy*lPitch2] != ddck.dwColorSpaceLowValue)
{
vv=int(rx+ty)+int(ry+ty2)*lPitch;
Buffer[vv]=Buffer2[ux+uy*lPitch2];
Buffer[vv+1]=Buffer2[ux+uy*lPitch2];
}
}}


Scr.Surface->Unlock(NULL);
SurfaceData.Surface->Unlock(NULL);
}


And my cool demo is here:
2D SOFTWARE ROOTATION IN DIRECTDRAW DEMO

Good luck all!!

/MindWipe

"If it doesn''t fit, force it; if it breaks, it needed replacement anyway."

Share this post


Link to post
Share on other sites
The demo is here now! What are you waiting for?

/MindWipe

"If it doesn''t fit, force it; if it breaks, it needed replacement anyway."

Share this post


Link to post
Share on other sites
yeah its okies for 1 bitmap rotated at a time, but when you need 30...

nice demo though, heres my stats:

PIII 450
Windows 2000
128MB RAM
INTEL i740 VIDEO CARD

320x240: 169 fps
640x480: 60 fps

I think you should write that tutorial

Share this post


Link to post
Share on other sites
True... but it''s unoptimized and you could speed things up by locking once and lotsa other small optimizations.

And the beginning code of the demo is also a bit dumb. You could play around with vmem/smem and stuff like that. And it also contains a GetDC and TextOut.

But I fear it''s not possible to do too many miracles in ddraw
making a loop that fills the screen is slow only about 50 fps can be received

Still, good luck!



/MindWipe

"If it doesn''t fit, force it; if it breaks, it needed replacement anyway."

Share this post


Link to post
Share on other sites
Do all of these clip?
If found clipping the biggest stumbling block in this and so I stopped my attempt when I couldn''t figure out a nice easy way to do it.

Share this post


Link to post
Share on other sites