前回では書きませんでしたが、拡大縮小の4つのパラメータはアフィン変換という方法が使われています。アフィン変換は数学の行列が関係するのでここでは説明をしません。より複雑なパラメータの組み合わせを試したい場合は、アフィン変換について調べると良いと思います。さて画像を回転させるには、4つの16bitパラメータを以下のように設定します。
PA | cos(angle) |
PB | sin(angle) |
PC | -sin(angle) |
PD | cos(angle) |
このように値をセットすれば、それだけで元の画像を回転させた画像が表示されます。ただし、C標準関数のsin, cosは浮動小数点用なのでそのまま使うには問題があります。GBAのCPUでは浮動小数演算は非常に遅いので速度の面で使いづらいです。GBAではあらかじめ配列に整数のデータとして作成しておき、その配列から値を取得するようにすると速度の面で不安がなくなります。s16の配列でSinTab[]、CosTab[]という配列のデータを用意します。この配列は符号付の-256~256の範囲で、360個のデータを持ち、配列の要素1個につき1°になります。また、整数の角度から配列のデータを取得するGetSin()、GetCos()関数を作っておきます。サインテーブルなどはパソコン上で簡単なプログラムを作り、それで出力したデータを使うといいでしょう。
// Example sine lut generator #include <stdio.h> #include <math.h> #define M_PI_ 3.1415926535f #define SIN_SIZE 360 #define SIN_FP 8 int main() { FILE *fp= fopen("sinlut.c", "w"); signed short hw; int i; fprintf(fp, "const short MathSinTab[%d] = \n{", SIN_SIZE); for(i=0; i<SIN_SIZE; i++) { hw = (signed short)(sin(i * 2 * M_PI_ / SIN_SIZE) * (1 << SIN_FP)); if(i % 8 == 0) { fputs("\n\t", fp); } fprintf(fp, "%d, ", hw); } fputs("\n};\n\n\n", fp); fprintf(fp, "const short MathCosTab[%d] = \n{", SIN_SIZE); for(i=0; i<SIN_SIZE; i++) { hw = (signed short)(cos(i * 2 * M_PI_ / SIN_SIZE) * (1 << SIN_FP)); if(i % 8 == 0) { fputs("\n\t", fp); } fprintf(fp, "%d, ", hw); } fputs("\n};\n", fp); fclose(fp); return 0; }
以下のようにするだけで任意の角度(整数)に画像を回転させて表示することができます。適当な数値をかければ拡大縮小も同時に行うことも可能です。
REG_BG2PA = GetCos(angle); REG_BG2PB = GetSin(angle); REG_BG2PC = -GetSin(angle); REG_BG2PD = GetCos(angle);
4000020h - BG2PA - BG2 Rotation/Scaling Parameter A (alias dx) (W) 4000022h - BG2PB - BG2 Rotation/Scaling Parameter B (alias dmx) (W) 4000024h - BG2PC - BG2 Rotation/Scaling Parameter C (alias dy) (W) 4000026h - BG2PD - BG2 Rotation/Scaling Parameter D (alias dmy) (W) Bit Expl. 0-7 Fractional portion (8 bits) 8-14 Integer portion (7 bits) 15 Sign (1 bit)
各パラメータはs16単位であり、上位から1bit符号、7bit整数部分、8bit分数部分となります。
#define REG_BG2X *((vu32 *)(REG_BASE + 0x28)) #define REG_BG2Y *((vu32 *)(REG_BASE + 0x2c)) #define REG_BG3X *((vu32 *)(REG_BASE + 0x38)) #define REG_BG3Y *((vu32 *)(REG_BASE + 0x3c))
さらにBGの回転では、回転の中心を画面の中心になるようにしてみます。画面の中心を回転の中心になるようにするにはBG面全体を動かします。注意してほしいのは、拡大縮小パラメータの4つの数値ではなく、REG_BG2XとREG_BG2Yで行うことです。REG_BG2XとREG_BG2Yは、格納した値がそのまま画面上のBGの移動になるのでなく、拡大縮小回転パラメータに影響されます。BGが1/10に縮小される場合は、REG_BG2Xのセットした値の1/10だけ画面上で移動することになります。また、BGが回転している場合は、回転したあとのBGの上下左右を基準に画像が移動します。REG_BG2XとREG_BG2Yに格納する値の計算式は以下のようになります。計算式の求め方について詳しくは解説しません。
bgx, bgy | BGオフセットレジスタ |
x1, y1 | BG面での回転の中心座標(x1,y1) |
hzoom, vzoom | 横・縦の拡大率 |
θ | 回転の角度 |
α | (0,0)から(x1,y1)までの長さ |
α = sqrt(x1*x1 + y1*y1) | |
β | (0,0)から(x1,y1)までの角度 |
β = atan2(y1,x1) |
こちらも浮動小数点で求めると処理が重くなるので、あらかじめ分かる値は入れておき、sin、cos配列も使います。今回は(120,80)が中心なのでx1 = 120、y1 = 80、α ≒ 144、β ≒ 33としておきます。
s32 bg2x = 120 * 256 - 144 * GetCos(angle-33); s32 bg2y = 80 * 256 - 144 * -GetSin(angle-33);
また、REG_BG2X、REG_BG2Yに格納するときに、値がマイナスの場合は0xFFFFFFFからその値を引くようにします。REG_BG2Xなどは32bit単位として扱われるのですが、レジスタは27bitを符号として扱っているのでbit桁の調整を行います。
4000028h - BG2X_L - BG2 Reference Point X-Coordinate, lower 16 bit (W) 400002Ah - BG2X_H - BG2 Reference Point X-Coordinate, upper 12 bit (W) 400002Ch - BG2Y_L - BG2 Reference Point Y-Coordinate, lower 16 bit (W) 400002Eh - BG2Y_H - BG2 Reference Point Y-Coordinate, upper 12 bit (W) Bit Expl. 0-7 Fractional portion (8 bits) 8-26 Integer portion (19 bits) 27 Sign (1 bit) 28-31 Not used
レジスタは32Bit単位ですが、31,30,29,28bitはNot used、27bitは符号、26-8bit(19bits)は整数部分、0-7bit(8bits)は分数部分となります。
// REG_BGX,Yの28bitフォーマットに調整 if(bg2x < 0) { REG_BG2X = 0xFFFFFFF - abs(bg2x); } else { REG_BG2X = abs(bg2x); } if(bg2y < 0) { REG_BG2Y = 0xFFFFFFF - abs(bg2y); } else { REG_BG2Y = abs(bg2y); }
#include "lib/gba.h" #include "irq.arm.h" #include "math.h" #include "res.h" //--------------------------------------------------------------------------- //BG2の縦・横の拡大率を%で指定 void RotateBG2(s32 angle) { REG_BG2PA = MathCos(angle); REG_BG2PB = MathSin(angle); REG_BG2PC = -MathSin(angle); REG_BG2PD = MathCos(angle); // bgx = x1 - α * cos(θ-β) * hzoom // bgy = y1 - α * -sin(θ-β) * vzoom s32 bg2x = 120 * 256 - 144 * ( MathCos(angle - 33)); s32 bg2y = 80 * 256 - 144 * (-MathSin(angle - 33)); if(bg2x < 0) { REG_BG2X = 0xFFFFFFF - MathAbs(bg2x); } else { REG_BG2X = MathAbs(bg2x); } if(bg2y < 0) { REG_BG2Y = 0xFFFFFFF - MathAbs(bg2y); } else { REG_BG2Y = MathAbs(bg2y); } } //--------------------------------------------------------------------------- int main(void) { REG_WSCNT = 0x4317; REG_DISPCNT = (MODE_3 | BG2_ON); IrqInit(); REG_DMA3SAD = (u32)&imageBitmap; REG_DMA3DAD = (u32)VRAM; REG_DMA3CNT = (u32)(240*160) | (DMA_SRC_INC | DMA_DST_INC | DMA16 | DMA_ENABLE); s32 angle = 0; for(;;) { VBlankIntrWait(); RotateBG2(angle); angle++; if(angle > 360) { angle = 0; } } }