アフィン変換とはBGやスプライトに使われている機能です。全体を回転させたり、引き延ばしたような表示をしたり複雑な動きを行います。この解説はToncのThe Affine Transformation Matrixをベースに薄くした内容をお送りします。数学の素養を身に着けたい場合はGoogleで検索するとウェブサイトがでてきますのでそちらをご覧ください。
一言でいうと移動、回転、拡大縮小、せん断を同時に行います。この変換がすごいのはたった6つのパラメータ数値を決定するだけで各状態を実現できる、ということです。もちろん回転だけしたい、移動だけしたい一部の起動だけ使いたい場合も可能です。具体的には画面の120,80に移動して、回転を30度にして、キャラを2倍に拡大して、せん断(詳細は後述)する、ということです。
構成は以下の通りです。mode0で全て16色。bg0:ASCII文字(256x256)、bg1:マスク(256x256)、bg2:ステージ(512x256)となっておりステージのみ512x256です。
REG_BG2HOFS = 120; REG_BG2VOFS = 64; REG_WIN0H = 0; REG_WIN0V = SCREEN_HEIGHT; REG_WININ = WIN_0_BG0 | WIN_0_BG2; REG_WINOUT = WIN_0_BG0 | WIN_0_BG1;
ステージ表示には始点座標を変えています。さらにウィンドウの内部と外部にどれを表示させるかの設定を加えています。
/* Create an array of horizontal offsets for a circular window. * The offsets are to be copied to REG_WINxH each HBlank, either * by HDMA or HBlank isr. Offsets provided by modified * Bresenham's circle routine (of course); the clipping code is not * optional. * * \param x0 X-coord of circle center. * \param y0 Y-coord of circle center. * \param rr Circle radius. */ u16 BgWinh[SCREEN_HEIGHT+1] ALIGN(4); IWRAM_CODE void BgCreateWindowCircleDma(s32 x0, s32 y0, s32 rr) { // Zero clear for(vs32 i=0; i<SCREEN_HEIGHT+1; i++) { BgWinh[i] = 0; } s32 x=0, y=rr, d=1-rr; u32 tmp; while(y >= x) { // Side octs tmp = BgClamp(x0 + y, 0, SCREEN_WIDTH+1); tmp += BgClamp(x0 - y, 0, SCREEN_WIDTH+1) << 8; // o4, o7 if(BgInRange(y0-x, 0, SCREEN_HEIGHT)) { BgWinh[y0 - x]= tmp; } // o0, o3 if(BgInRange(y0+x, 0, SCREEN_HEIGHT)) { BgWinh[y0 + x]= tmp; } // Change in y: top/bottom octs if(d >= 0) { tmp = BgClamp(x0 + x, 0, SCREEN_WIDTH+1); tmp += BgClamp(x0 - x, 0, SCREEN_WIDTH+1) << 8; // o5, o6 if(BgInRange(y0-y, 0, SCREEN_HEIGHT)) { BgWinh[y0 - y]= tmp; } // o1, o2 if(BgInRange(y0+y, 0, SCREEN_HEIGHT)) { BgWinh[y0 + y]= tmp; } d -= 2 * (--y); } d += 2 * (x++) + 3; } /* for(vs32 i=0; i<SCREEN_HEIGHT+1; i++) { TRACE("%d: %4x\n", i, BgWinh[i]); } for(;;){} */ REG_DMA3CNT = 0; REG_DMA3SAD = (u32)&BgWinh[1]; REG_DMA3DAD = (u32)®_WIN0H; REG_DMA3CNT = 1 | (DMA_DST_RELOAD | DMA_REPEAT | DMA_HBLANK | DMA_ENABLE); } //--------------------------------------------------------------------------- IWRAM_CODE s32 BgClamp(s32 val, s32 min, s32 max) { if(val < min) { return min; } if(val > max) { return max; } return val; } //--------------------------------------------------------------------------- IWRAM_CODE bool BgInRange(s32 x, s32 min, s32 max) { return ((x)>=(min)) && ((x)<(max)) ? TRUE : FALSE; }
注目してほしいのは、以下の部分でHBLANK毎にDMAを発生させていることです。GBAの画面の高さは160ですので160回HBLANKを発生させています。1ライン分のウィンドウサイズを2バイト、REG_WIN0Hに格納しています。チラツキもなくハードウェア機能で実現しており非常に芸術点が高いです。
REG_DMA3CNT = 0; REG_DMA3SAD = (u32)&BgWinh[1]; REG_DMA3DAD = (u32)®_WIN0H; REG_DMA3CNT = 1 | (DMA_DST_RELOAD | DMA_REPEAT | DMA_HBLANK | DMA_ENABLE);