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.

Beginners > GBA Mode 2 Issue: Rotation and Scaling

#84122 - Jwraith - Sat May 20, 2006 5:13 pm

Okay, so I've been working through the "Programming the Gameboy Advance" book upto chapter 6, Dealing with Tiled Backgrounds.

Specifically, rotation backgrounds. Now, my tile data/map/palette are all loaded into memory and can be drawn without transformations (yay).

However, when I try and use the rotate method (as provided in the book). It doesn't work.

I've searched just about ever tutorial and forum on GBA dev and cannot find anywhere that explains what I need to do to rotate the image.

Code provided :

Code:

/*********************************************************************************
 * Includes
 ********************************************************************************/
 #include <math.h>
 #include "tile.raw.c"
 #include "tiles.pal.c"
 #include "tilemap.h"
 
//prototype functions
 void DMAFastCopy(void*,void*,unsigned int,unsigned int);
 void WaitVBlank(void);
 void RotateBackground(int,int,int,int);

 //DMA defines
 #define REG_DMA3SAD *(volatile unsigned int*)0x40000D4
 #define REG_DMA3DAD *(volatile unsigned int*)0x40000D8
 #define REG_DMA3CNT *(volatile unsigned int*)0x40000DC
 #define DMA_ENABLE 0x80000000
 #define DMA_TIMING_IMMEDIATE 0x00000000
 #define DMA_16 0x00000000
 #define DMA_32 0x04000000
 #define DMA_32NOW (DMA_ENABLE | DMA_TIMING_IMMEDIATE | DMA_32)
 #define DMA_16NOW (DMA_ENABLE | DMA_TIMING_IMMEDIATE | DMA_16)

 //scrolling registers for bg_0
 #define REG_BG0HOFS *(volatile unsigned short*)0x4000010
 #define REG_BG0VOFS *(volatile unsigned short*)0x4000012
 
 //bg2 registers
 #define REG_BG2X *(volatile unsigned int*)0x4000028
 #define REG_BG2Y *(volatile unsigned int*)0x400002C
 #define REG_BG2PA *(volatile unsigned int*)0x4000020
 #define REG_BG2PB *(volatile unsigned int*)0x4000022
 #define REG_BG2PC *(volatile unsigned int*)0x4000024
 #define REG_BG2PD *(volatile unsigned int*)0x4000026

 //background setup registers
 #define REG_BG0CNT *(volatile unsigned short*)0x4000008
 #define REG_BG1CNT *(volatile unsigned short*)0x400000A
 #define REG_BG2CNT *(volatile unsigned short*)0x400000C
 #define REG_BG3CNT *(volatile unsigned short*)0x400000E
 #define BG_COLOR256 0x80
 #define CHAR_SHIFT 2
 #define SCREEN_SHIFT 8
 #define WRAPAROUND 0x1
 #define BG_MOSAIC_ENABLE 0x40

 //background tile bitmap sizes
 #define TEXTBG_SIZE_256x256 0x0
 #define TEXTBG_SIZE_256x512 0x8000
 #define TEXTBG_SIZE_512x256 0x4000
 #define TEXTBG_SIZE_512x512 0xC000
 #define ROTBG_SIZE_128x128 0x0
 #define ROTBG_SIZE_256x256 0x4000
 #define ROTBG_SIZE_512x512 0x8000
 #define ROTBG_SIZE_1024x1024 0xC000

 //background memory offset macros
#define CharBaseBlock(n) (((n)*0x4000)+0x6000000)
#define ScreenBaseBlock(n) (((n)*0x800)+0x6000000)
//background mode identifiers
#define BG0_ENABLE 0x100
#define BG1_ENABLE 0x200
#define BG2_ENABLE 0x400
#define BG3_ENABLE 0x800
//video identifiers
#define REG_DISPCNT *(unsigned int*)0x4000000
#define BGPaletteMem ((unsigned short*)0x5000000)
#define SetMode(mode) REG_DISPCNT = (mode)
//vertical refresh register
#define REG_DISPSTAT *(volatile unsigned short*)0x4000004
//button identifiers
#define BUTTON_A 1
#define BUTTON_B 2
#define BUTTON_RIGHT 16
#define BUTTON_LEFT 32
#define BUTTON_UP 64
#define BUTTON_DOWN 128
#define BUTTON_R 256
#define BUTTON_L 512
#define BUTTONS (*(volatile unsigned int*)0x04000130)

 //wait for v blank
 void WaitVBlank(void)
 {
     while((REG_DISPSTAT & 1));
 }
 
//math values needed for rotation
#define PI 3.14159265
#define RADIAN(n) (((float)n) / (float)180 * PI)

//precomputed sine and cosine arrays
signed int SIN[360];
signed int COS[360];
 
 //rotation variables
 int x_scroll=0,y_scroll=0;
 int DX=0,DY=0;
 int PA,PB,PC,PD;
 int zoom = 2;
 int angle = 0;
 int center_y,center_x;
 
int main(void)
{
    int n;
    int charbase = 0;
    int screenbase = 31;
   
    for(n = 0; n < 360; n++)
    {
        SIN[n] = (signed int)(sin(RADIAN(n)) * 256);
        COS[n] = (signed int)(cos(RADIAN(n)) * 256);
    }
   
    unsigned short * bg2map = (unsigned short *)ScreenBaseBlock(screenbase);
   
    //set up bg0
    REG_BG2CNT = BG_COLOR256 | ROTBG_SIZE_128x128 | (charbase << CHAR_SHIFT) | (screenbase << SCREEN_SHIFT);
   
    //set up display mode
    SetMode(2 | BG2_ENABLE);
   
    //set the palette
    DMAFastCopy((void*)tiles_Palette, (void*)BGPaletteMem, 256, DMA_16NOW);
   
    //set the tile images
    DMAFastCopy((void*)tile_Tiles, (void*)CharBaseBlock(0), 64, DMA_32NOW);
   
    //copy the tile map to bg0
    DMAFastCopy((void*)tiles_Map, (void*)bg2map, 64, DMA_32NOW);
   
   while(1)
   {
       WaitVBlank();
       
       //use the hardware to scroll around some
        if(!(BUTTONS & BUTTON_LEFT)) x_scroll--;
        if(!(BUTTONS & BUTTON_RIGHT)) x_scroll++;
        if(!(BUTTONS & BUTTON_UP)) y_scroll--;
        if(!(BUTTONS & BUTTON_DOWN)) y_scroll++;
        if(!(BUTTONS & BUTTON_A)) zoom--;
        if(!(BUTTONS & BUTTON_B)) zoom++;
        if(!(BUTTONS & BUTTON_L)) angle--;
        if(!(BUTTONS & BUTTON_R)) angle++;

    if(angle > 359)
        angle = 0;
    if(angle < 0)
        angle = 359;
    //rotate the background
    RotateBackground(angle,64,64,zoom);
     WaitVBlank();
    //update the background
    REG_BG2X = DX;
    REG_BG2Y = DY;
    REG_BG2PA = PA;
    REG_BG2PB = PB;
    REG_BG2PC = PC;
    REG_BG2PD = PD;
    WaitVBlank();
    for(n = 0; n < 100000; n++);

    }
   return 0;
}

//other methods
void RotateBackground(int ang, int cx, int cy, int zoom)
{

DX= cx - ( (PA*cx + PC*cy)>>8 );
DY= cy - ( (PC*cx + PD*cy)>>8 );

PA = (COS[ang] * zoom) >> 8;
PB = (SIN[ang] * zoom) >> 8;
PC = -PB;
PD = PA;

}

//other functions
void DMAFastCopy(void* source,void* dest,unsigned int count, unsigned int mode)
{
    if(mode == DMA_16NOW || mode == DMA_32NOW)
    {
        REG_DMA3SAD = (unsigned int)source;
        REG_DMA3DAD = (unsigned int)dest;
        REG_DMA3CNT = count | mode;
    }
}

/* END OF FILE */


Please help me! Otherwise I think I'm just going to have to quit because I am too stupid to understand.
_________________
"All Your Megadrive Are Belong to Us"

#84128 - wintermute - Sat May 20, 2006 5:42 pm

Throw away the book, burn it if you have to.

Go Here and start at the beginning, and hopefully forget everything that book taught you.
_________________
devkitPro - professional toolchains at amateur prices
devkitPro IRC support
Personal Blog

#84139 - Jwraith - Sat May 20, 2006 6:32 pm

That was the answer I was afraid of, lol. While I'm sure you find that site easy enough to follow, it confuses the hell out of me on any of the complex sections.
_________________
"All Your Megadrive Are Belong to Us"

#84450 - thegamefreak0134 - Mon May 22, 2006 5:22 pm

Rotation and scailng isn't as hard as people make it out to be. If you have the math down for it, actually putting it into the registers is a breeze. Go to the TONC tutorials for the proper math and stuff you need. He'll even give you the function to do it. The Rotation backgrounds have pa-pd registers just like the OAM registers, with the exception that you have one per BG.

What really threw me (and some other people) is the way the hardware handles the math. Since the GBA has no support for floating point math at all, it uses a number format to compensate. This basically means that 256 in the register is equal to 1. To scale 2x it's 128, to scale .5x it's 512, etc. (I think, can't remember but that might be reversed.) Anyway, starting with 256 in the scaling registers (PA and PD) and 0 in the rotation registers (PB and PC) should get you a plain picture you can play with from there.

-thegamefreak0134
_________________
What if the hokey-pokey really is what it's all about?

[url=http:/www.darknovagames.com/index.php?action=recruit&clanid=1]Support Zeta on DarkNova![/url]

#85028 - Jwraith - Fri May 26, 2006 3:37 pm

Man, I really am stupid. I got back and took a quick look over the rotation
code and figured it out.

I was assigning PA through PD with signed int values rather than u16. The bit shifting was right, but the number format wasn't! Now I just need to figure out why it doesn't scroll horizontally :/
_________________
"All Your Megadrive Are Belong to Us"

#85063 - tepples - Fri May 26, 2006 9:33 pm

Scrolling "text" backgrounds is done with the offset registers (called BGOFFSET[] in libgba).

Scrolling "rotation" (mode 1-2 BG2 and mode 2 BG3) and "extended rotation" (mode 3-5 BG2) backgrounds isn't done with the offset registers. It's done with the affine origin registers (called REG_BG2X and REG_BG2Y in libgba), which specify the (fixed point) texek position in the upper left corner.
_________________
-- Where is he?
-- Who?
-- You know, the human.
-- I think he moved to Tilwick.

#85076 - Cearn - Fri May 26, 2006 10:22 pm

Jwraith wrote:
Man, I really am stupid. I got back and took a quick look over the rotation code and figured it out.

I was assigning PA through PD with signed int values rather than u16. The bit shifting was right, but the number format wasn't! Now I just need to figure out why it doesn't scroll horizontally :/

Actually, the variables PA-PD should be signed ints. Ints because ints are better than the other types, signed int because negative values are to be expected. If you work with unsigned shorts here, the arithmetic will go haywire as soon as there are negative values somewhere, which should be almost immediately.
The IO-registers REG_BG2PA-REG_BG2PD are another matter. These are defined by Nintendo to be shorts, preferably signed shorts but as they are write only that doesn't really matter here. I guess this is what you meant to say anyway, but the distinction between REG_BG2PA and PA is important.
I suppose this could serve as an example of why long identifiers are not always a good thing: with a shorthand like u16 or u32 the mistake is easier to spot because you can just 'see' the datatype immediately instead of having to actually read what it says.

Code:

// Typedefs, yes please.
typedef unsigned char  u8;
typedef unsigned short u16;
typedef unsigned int   u32;

typedef signed char  s8;
typedef signed short s16;
typedef signed int   s32;

#define REG_BG2PA *(volatile s16*)0x04000020
#define REG_BG2PB *(volatile s16*)0x04000022
#define REG_BG2PC *(volatile s16*)0x04000024
#define REG_BG2PD *(volatile s16*)0x04000026
#define REG_BG2X  *(volatile s32*)0x04000028
#define REG_BG2Y  *(volatile s32*)0x0400002C


But even with this fixed, the code shouldn't run properly, because the shifts are the problem. Well, part of it anyway.

Jwraith wrote:
Code:

int x_scroll=0,y_scroll=0;
int zoom = 2;

// <main>
        if(!(BUTTONS & BUTTON_LEFT)) x_scroll--;
        if(!(BUTTONS & BUTTON_RIGHT)) x_scroll++;
        if(!(BUTTONS & BUTTON_UP)) y_scroll--;
        if(!(BUTTONS & BUTTON_DOWN)) y_scroll++;
        if(!(BUTTONS & BUTTON_A)) zoom--;
        if(!(BUTTONS & BUTTON_B)) zoom++;
        RotateBackground(angle,64,64,zoom);
// </main>

void RotateBackground(int ang, int cx, int cy, int zoom)
{

DX= cx - ( (PA*cx + PC*cy)>>8 );
DY= cy - ( (PC*cx + PD*cy)>>8 );

PA = (COS[ang] * zoom) >> 8;
PB = (SIN[ang] * zoom) >> 8;
PC = -PB;
PD = PA;

}


RotateBackground() expects cx, cy, and zoom to be fixed point numbers, but if you look at the rest of the code you'll see that they are not. As a result, they are off by a factor of 256. Something like this would be better:
Code:

// Yes, BUTTONS is a halfword too
#define BUTTONS (*(volatile u16*)0x04000130)

#define DZOOM     2    // zoom speed
#define DSCROLL  64    // scroll speed
int zoom= 256;         // Start with scale of 1 (.8 fixed point)

// <main>
// Load button states once instead of repeatedly due to it being volatile,
//   this allows better code
// Inver bits too to make things easier to code
u32 buttons= ~BUTTONS;
if(buttons & BUTTON_LEFT)    x_scroll -= DSCROLL;
if(buttons & BUTTON_RIGHT)   x_scroll += DSCROLL;
if(buttons & BUTTON_UP)      y_scroll -= DSCROLL;
if(buttons & BUTTON_DOWN)    y_scroll += DSCROLL;
if(buttons & BUTTON_A)       zoom -= DZOOM;
if(buttons & BUTTON_B)       zoom += DZOOM;


But even with these improvement you'd still not be getting a decent picture (at least not if you have any level of compiler optimisations, which you should) because there is no VBlank synchronisation. At least, not anything proper. You may have noticed that there are a lot of different versions of WaitVBlank in the book, and most of them are wrong. This:
JWraith wrote:
Code:

//wait for v blank
void WaitVBlank(void)
{
    while((REG_DISPSTAT & 1));
}

Actually waits not until, but during the VBlank period, meaning that all the update-code is inside the VDraw period. And because there is no waiting while inside the VDraw, the timing of the main loop will be completely snackered. And because the BG registers are now written to at least once every scanline, the resulting picture will make no sense whatsoever.
I think the frequent use of WaitVBlank() and the 100000x loop at the end are supposed to cover for it. It doesn't.

Correcting for this mistake, you'll still get weird results because the RotateBackground function is incorrect. It uses PC twice (the first should be PB), and uses PA-PD before they're set, so you'll use the things from the last scanline.
However, fixing that still won't scroll the background, because its called with constants ( (64,64) in this case, or rather (?,?) because they're supposed to be fixed point numbers). And, more importantly, because the RotateBackground doesn't do scrolling -- all it does is change the rotation point.


A number of these bugs were in the book to begin with, so what you started out with wouldn't work in the first place. That's always a bad place to start learning. But in attempting to fix this and cleaning it up, you also added a few more bugs. Consider starting from scratch and work your way up.