gbadev.org forum archive

This is a read-only mirror of the content originally found on forum.gbadev.org (now offline), salvaged from Wayback machine copies. A new forum can be found here.

C/C++ > Non-GBA problem meant to act like GBA sprite

#17069 - DiscoStew - Sun Feb 29, 2004 5:48 pm

I am so frustrated by this problem that I have. I have this function written into a C DLL for use with a VB program (speed reasons), and it does not seem to work correctly. What this function does is takes pixel arrays of my sprite and the output window, and copies the sprite into the output window with scaling and rotation when needed. The result is supposed to be much like on the GBA, with only a specific drawing area unless Double_Flags is on (which is not implemented yet), and any scaling/rotation is always centered in the middle of the drawing area. My problem with the function is that the sprite can't stay centered.
I really don't have much of a problem with equal x-y scaling and rotation, though at every 90 degrees other than 0 I'm loosing a single line of the sprite, but it's when the scaling of the x-y axises are not equal. At 0 degrees, scaling on either axis is ok, but as I rotate, the sprite does not stay centered in the area that is specified. It tends to lean out a bit, and it is very noticable when there is unequal scaling while at a constant rotation value.

Here is the code for this...

Code:
----------------------------------------
   //Initial values which are meant to come through a function's parameters
   int Frame_Width = 32, Frame_Height = 32, Output_Width = 240;
   int Sprite_ScaleX = 128, Sprite_ScaleY = 256, Sprite_RotationVal

   //Get the midpoint of the sprite
   int MidPointX = Frame_Width / 2;
   int MidPointY = Frame_Height / 2;

   //Get the actual scale values
   double ActualScaleX = ((double)256 / Sprite_ScaleX);
   double ActualScaleY = ((double)256 / Sprite_ScaleY);
   double GrabScaleX = MidPointX / ActualScaleX;
   double GrabScaleY = MidPointY / ActualScaleY;

   //Get the distance of the hypotenuse from the center to the upper-left corner of the sprite, and the angle plus add the rotation value of the sprite
   double AngleDist = sqrt((GrabScaleX * GrabScaleX) + (GrabScaleY * GrabScaleY));
   double Angle = Radians(-Sprite_RotationVal) + atan2(-GrabScaleY,-GrabScaleX);

   //Set where on the Sprite Image to begin grabbing pixels
   double GrabStartX = (AngleDist * cos(Angle)) + MidPointX;
   double GrabStartY = (AngleDist * sin(Angle)) + MidPointY;

   //The X-Increments and Y-Increments associated with Scaling and Rotation
   double GrabPixel_X = cos(Radians(-Sprite_RotationVal)) / ActualScaleX;
   double GrabPixel_Y = sin(Radians(-Sprite_RotationVal)) / ActualScaleY;
   double NextLine_X = cos(Radians(90 - Sprite_RotationVal)) / ActualScaleX;
   double NextLine_Y = sin(Radians(90 - Sprite_RotationVal)) / ActualScaleY;

   //Set where on the output array to start placing pixels
   int StartX = Sprite_PositionX;
   int StartY = Sprite_PositionY;
   
   //Loop through the output
   for(int PositionY = 0; PositionY < Frame_Height; PositionY++)
   {
      //Get the location of the next starting place to grab pixels
      double CurrentX = GrabStartX + (NextLine_X * PositionY);
      double CurrentY = GrabStartY + (NextLine_Y * PositionY);

      for(int PositionX = 0; PositionX < Frame_Width; PositionX++)
      {
         //Make sure what is being grabbed is within the sprite image
         if((CurrentY >= 0) && (CurrentY < Frame_Height) && (CurrentX >= 0) && (CurrentX < Frame_Width))
            OutputArray[((StartX + PositionX) + ((StartY + PositionY) * Output_Width))] = SpriteArray[(int)(CurrentX + ((int)CurrentY * Frame_Width))];
         else
            OutputArray[((StartX + PositionX) + ((StartY + PositionY) * Output_Width))] = 0;

         //Add X_Increments to the locations
         CurrentX += GrabPixel_X;
         CurrentY += GrabPixel_Y;
      }
   }
-------------------------------
double Radians(double Degrees)
{
   double Deg2Rad = Degrees * (PI / 180);
   return (Deg2Rad);
}
-------------------------------


...I am using <math.h> for the sin, cos, and atan2 functions, which may be the culprit in the missing pixel line as mentioned earlier. As far as I know, I'm probably missing something that is needed to keep it centered, but I just don't know what. I've stared at this code for hours on end, and tried messing around with it to see if the result will lead up to me getting the problem fixed. Is anyone able to help me resolve this problem? thx for your help.
_________________
DS - It's all about DiscoStew

#17073 - Miked0801 - Sun Feb 29, 2004 6:10 pm

Oh, oh - PC C code on the GBA forums. Time to get out the disinfectent spray. Pssssssssssst.

1. You don't ever expect to port this to GBA do you? Your use of Float/Double precludes this from the go.

2. Your life will get much easier if you use a rotation matrix instead of geometry fpr your rotation.

3. Turn off the scaling until you get the rotation to work at all. No sense in having 2 different unknowns at the same time.

Just off the top of my head.

#17075 - tepples - Sun Feb 29, 2004 6:17 pm

In fact, it'd be straightforward to implement rot/scale on PC using exactly the same matrices that you use for rot/scale on the GBA.
_________________
-- Where is he?
-- Who?
-- You know, the human.
-- I think he moved to Tilwick.

#17077 - poslundc - Sun Feb 29, 2004 6:32 pm

I don't know if it matters at all to you when it comes to your peecee/windoze programming, but just so you're aware, if you're making a C program then declaring a variable inside a for statement is downright illegal.

It's fine if you're programming in C++, and it's clear enough that your compiler is letting you get away with it, but if you want your code to be at all compatible with other C compilers (including gcc) you should nix that.

Dan.

#17084 - Lupin - Sun Feb 29, 2004 9:19 pm

if you keep coding like that it ain't VBs fault that your code is slow...

There are a lot of ways to implement fast sprite scaling/rotating without c or asm help...

If you want fast code, you should get rid of these floating point stuff in first place :)

#17088 - poslundc - Sun Feb 29, 2004 10:04 pm

Lupin wrote:
If you want fast code, you should get rid of these floating point stuff in first place :)


Almost every type of personal computers since the mid-90's comes equipped with a floating-point unit (FPU) that co-processes any floating-point math in parallel to the main CPU, which in most cases makes floating-point math as fast as fixed-point math, if not faster.

Likewise, you can have an ARM computer with a math co-processor; it just so happens that the GBA doesn't have (or really require) one.

Dan.

#17093 - DiscoStew - Sun Feb 29, 2004 11:28 pm

For one thing, this code is not going to be ported to the GBA in any shape or form. This is part of a PC application written in VC++ 6, not GBA and gcc. The actual interface is written in VB. The program I'm making will ease up some of the frustration with having to do things manually.

The reason why it's in a C DLL and not in the VB program itself is because I plan to call this area of code 60 times per second for each sprite I want displayed.

Now, about using the same matrices as the GBA uses, if I were to use this, I'd need to figure out is how to display a sprite using that. The GBA does it through hardware, while I have to figure it out how to display a sprite through software. Would anyone out there happen to know that is done? If anyone understand how my code works (although it doesn't display a sprite completely correctly), could matrices be incorporated into it without trouble? thx
_________________
DS - It's all about DiscoStew

#17095 - DekuTree64 - Mon Mar 01, 2004 12:02 am

I think the matrix is just the increment values it uses for each x/y pixel increment. So something like
Code:
loop y
{
 x = xOut
 y = yOut
 loop x
 {
  dest = pixel(x, y)
  x += pa
  y += pb
 }
 xOut += pc
 yOut += pd
}

Since you want your sprite center to always stay in the same place, just take the center coords, and subtract multiples of those increments like
Code:
xTopLeft = width/2 - width/2*pa - height/2*pc
yTopLeft = height/2 - width/2*pb - height/2*pd

So after pc gets added height/2 times (half way down the sprite) in the outer loop, and then pa gets added width/2 times in the inner loop, x will be at exactly width/2, which is where you want it.

That's all untested though, but I'm pretty sure that's how it works. The GBA's hardware doesn't actually rotate things, only skews and scales them by adding different x and y increment values each vertical and horizontal pixel on the screen. Lucky for us programmers that's all it needs to do to LOOK like rotation.
Oh, and that will tile the sprite, not just draw one copy and fill the rest with transparent. Although if you zoom out really far on a sprite on GBA you can see that it tiles too, just with a lot of space inbetween. Not sure how/why it does that though, so you can just check and make sure x and y are between 0 and width/height before copying the pixel. Or use some fancy math to trace the edges of the rotated sprite, but then you have to do lots of multiplying/adding the increments and it gets kind of confusing.
_________________
___________
The best optimization is to do nothing at all.
Therefore a fully optimized program doesn't exist.
-Deku

#17098 - DiscoStew - Mon Mar 01, 2004 1:57 am

Thanks DekuTree64, after looking at what you showed, my GrabPixel and NextLine variables are almost set up the same way as the p matrix on the GBA. However, I had to change my last three p matrix variables to practically be like the GBA's p matrix. Now it is centered correctly, but the scaling is still having problems. It is scaling in relation to the output's x/y axis, not the sprites altered x/y matrix. At 45 degrees and the sprite's x-scale is *2, the sprite is rotated (or made to look like it did) correctly, but the scaling is not going along with the alteration. It is scaling on the output's x-scale as shown below...
Code:

_____*_____
____*_*____
<-_*___*_->
____*_*____
_____*_____


...Is there still something I'm doing wrong?
_________________
DS - It's all about DiscoStew

#17106 - DekuTree64 - Mon Mar 01, 2004 5:08 am

According to my tests, that's exactly how the GBA does it as well, so it sounds to me like it's working. Pretty surprising actually, considering that was just off the top of my head.
_________________
___________
The best optimization is to do nothing at all.
Therefore a fully optimized program doesn't exist.
-Deku

#17107 - DiscoStew - Mon Mar 01, 2004 9:13 am

I still am having trouble with this. My code is the same, but with the following modification...
Code:

//The X-Increments and Y-Increments associated with Scaling and Rotation
   double GrabPixel_X = cos(Radians(-Sprite_RotationVal)) / ActualScaleX;
   double GrabPixel_Y = sin(Radians(-Sprite_RotationVal)) / ActualScaleX;
   double NextLine_X = -sin(Radians(-Sprite_RotationVal)) / ActualScaleY;
   double NextLine_Y = cos(Radians(-Sprite_RotationVal)) / ActualScaleY;


Heck, I even checked my GBA code and my PC code just about matches it, other than the fact that they use different names. I don't know what to do.
For the time being, I have no reason to use un-equal x/y scaling, so I could just continue with the rest of my program. I just wish I was able to figure this out.
On another note, perhaps someone could teach me a few speed tricks in VB (I'm using DX7 or DirectDraw7 for the graphics of the program). The way that my program deals with drawing one or more sprites is that it first locks my output surface so that I can use the GetLockedArray function, locks the first sprite surface for the frame and gets its array, then I send the first sprite and its transformation data into my C DLL function, with my arrays passed ByRef into SAFEARRAY types so that I can get the actual reference for reading and ploting pixels, calculate everything in the C DLL and plot pixels as shown in my code, then after unlocking the SAFEARRAYs, exit the function, unlock the current sprite surface, and start on the next sprite until all have been done. I then unlock the output surface and then use the BltToDC to copy the surface to one of VBs picture objects. This process is done every frame. Now with my current method, as the number of sprites increases (starting at 5 sprites), my program begins to slow down.
Now more than likely I'm doing too many wrong things with this method, and as Lupin said, it doesn't require c or asm to have fast scaling/rotation. I'd like to know how if possible, especially in VB (6.0 preferred). Perhaps direction to a web site would help? I appreciate all comments and suggestions everyone has given, and I hope to help others with their problems as you all are helping me. thx
_________________
DS - It's all about DiscoStew

#17136 - DiscoStew - Tue Mar 02, 2004 2:00 am

ok, now I finally got it working as it should. My GrabPixel_Y variable was supposed to use ActualScaleY, and the NextLine_Y used ActualScaleX. Now I need to find a faster way of drawing than having to continually call Lock functions. It's just so easy having arrays to work with.

*EDIT*
Actually, I tested my VB code for speed, and the whole Locking portion is not the problem. In my C DLL, the speed holdup is right at the center of the whole pixel-plotting function, the array I'm reading, and the array I'm writing. Could it have something to do with the SAFEARRAY? My GetLockedArray in VB gets a DX7 surface's array in BYTE form, yet as my C code shows, it converts access to it by LONG. Could something about that be the problem?
Anyhow, I'm glad I got this working while learning some logic behind it, but because of this odd speed bug, I think I'll give GDI+ a try with a VB wrapper.
_________________
DS - It's all about DiscoStew


Last edited by DiscoStew on Tue Mar 02, 2004 10:08 pm; edited 3 times in total

#17163 - SmileyDude - Tue Mar 02, 2004 7:05 pm

poslundc wrote:
I don't know if it matters at all to you when it comes to your peecee/windoze programming, but just so you're aware, if you're making a C program then declaring a variable inside a for statement is downright illegal.


Not defending the rest of his code, but actually, declaring a variable in a for statement is legal if you are using the C99 standard. I actually use C99 mode for all of my GBA programming now.
_________________
dennis