Tutorial.19 回転(1)

回転

今回は回転機能を扱います。前回のは書きませんでしたが、
拡大縮小の4つのパラメータは、アフィン変換という方法が使われています。
アフィン変換は数学の行列が関係するのでここでは説明しません。
より複雑なパラメータの組み合わせを試したい場合は、
アフィン変換について調べるとわかりやすくなると思います。

画像を回転させるには、4つのパラメータを以下のように設定します。

PAcos(angle)
PBsin(angle)
PC-sin(angle)
PDcos(angle)

このように値をセットすればそれだけで元の画像を回転させた画像が表示されますが、
C標準関数のsin,cosは浮動小数点用なのでそのまま使うには問題があります。
GBAのCPUでは浮動小数演算は非常に遅いので、処理速度の面で使いづらいです。
GBAでsin、cosの値を利用したいときは、あらかじめ配列に整数のデータとして作成しておき、
その配列から値を取得するようにすると速度の面で不安がなくなります。

s16の配列でSinTab[]、CosTab[]という配列のデータを使います。
この配列は符号付の-256〜256の範囲のデータで、360個のデータを持ち、 配列の要素1個につき1°になります。

また整数の角度から配列のデータを取得するGetSin()、GetCos()関数を作っておきます。
サインテーブルなどはパソコン上で簡単なプログラムを作り、それで出力したデータを使うといいでしょう。
以上の用意が終われば、以下のようにするだけで任意の角度(整数)に画像を回転させて表示することができます。
適当な数値をかければ拡大縮小も同時に行うことも可能です。

REG_BG2PA =  GetCos(angle);
REG_BG2PB =  GetSin(angle);
REG_BG2PC = -GetSin(angle);
REG_BG2PD =  GetCos(angle);

これに加えて、BGの回転では、回転の中心を画面の中心になるようにしてみます。
画面の中心を回転の中心になるようにするには、BG面全体を動かします。
これは拡大縮小パラメータの4つの数値ではなく、REG_BG2XとREG_BG2Yで行います。
これらのパラメータは28bitの数値で、下位8bitが小数部、上位1bitが符号です。

REG_BGxX、REG_BGxY

#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))

REG_BG2XとREG_BG2Yは、格納した値がそのまま画面上のBGの移動になるのでなく、
拡大縮小回転パラメータに影響されます。
BGが1/10に縮小される場合は、REG_BG2Xのセットした値の1/10だけ画面上で移動することになります。
また、BGが回転している場合は、回転したあとのBGの上下左右を基準に画像が移動します。
REG_BG2XとREG_BG2Yに格納する値の計算式は以下のようになります。 計算式の求め方について詳しくは解説しません。

bgx = x1 - α* cos(θ-β)*hzoom
bgy = y1 - α*-sin(θ-β)*vzoom
bgx, bgyBGオフセットレジスタ
x1, y1BG面での回転の中心座標(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の変数として扱われているので、28bitとして格納したいときに符号の位置がおかしくなる為)

	//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 <stdlib.h>

#include "lib/gba.h"
#include "sintbl.h"
#include "res.h"

//---------------------------------------------------------------------------
void WaitForVsync(void)
{
	while (*(volatile u16*)0x4000006 >= 160) {};
	while (*(volatile u16*)0x4000006 <  160) {};
}
//---------------------------------------------------------------------------
//BG2の縦・横の拡大率を%で指定
void RotateBG2(s32 angle)
{
	REG_BG2PA =  GetCos(angle);
	REG_BG2PB =  GetSin(angle);
	REG_BG2PC = -GetSin(angle);
	REG_BG2PD =  GetCos(angle);

	// bgx = x1 - α *  cos(θ-β) * hzoom
	// bgy = y1 - α * -sin(θ-β) * vzoom
	s32 bg2x = 120 * 256 - 144 *  GetCos(angle-33);
	s32 bg2y =  80 * 256 - 144 * -GetSin(angle-33);

	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);
	}
}
//---------------------------------------------------------------------------
int main(void)
{
	// モード設定
	SetMode(MODE_3 | BG2_ENABLE);

	// 画像の読み込み
	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(;;)
	{
		WaitForVsync();

		if(++angle >= 360)
		{
			angle = 0;
		}

		RotateBG2(angle);
	}
}

動作画面

clip_1.png

履歴

  • 2015/10/07
  • 2007/09/08

添付ファイル: fileclip_1.png 202件 [詳細]

Last-modified: 2015-10-07 (水) 23:24:02 (2206d)