情報システム実験 K-13 組み込みオペレーティングシステム No.4
前回の move_box で(四角いですが)ボールを動かすことができるようになりました. 上下左右や斜め45度に動かす方法は直感的にわかると思いますが, それ以外の角度に動かすのはどうすればよいでしょうか. ここでは,できるだけ簡単な方法を考えてみましょう.
たとえば,以下のような考え方はどうでしょうか.
(x0, y0)とする.
dxと
y方向の速度dyを決め,
nステップ後の座標を(x0 + n * dx, y0 + n * dy)と計算する.
ここで,dxとdyが整数ならば,かけ算の必要はなくて,次のステップを 単なる足し算で求めることができます. たとえば下図は, 点の (x1, y1) から (x2, y2) への移動を表しています.
(x1, y1)から「次のステップ」(x2, y2)を求めるには,以下の計算をすればよいことになります.
x2 = x1 + dx y2 = y1 + dy
速度が変わっても気にしないなら,dyを1に固定して,dxを変えるだけで いろいろな角度を作り出すことができます. 速度をなるべく変えないためには,dx2+dy2が一定となるようにして, dxとdyの組み合わせを変えればよいことになります.
dxやdyとして,整数よりも細かい値を扱う方法としては,もちろんfloatやdoubleのような 浮動小数点数を用いる方法もありますが,GBAのARMプロセッサには浮動小数点演算を行う ハードウェアがないため,非常に遅い処理になってしまいます.
整数演算と浮動小数点演算の中間的な方法として,固定小数点数を用いるやり方があります. たとえば,C言語のint型の値(GBAでは符号と整数部あわせて32bit)を,「24bitの符号・整数部と8bitの小数部」 とみなして扱うことを考えます. 実際にはint型で表現しますが,分かりやすいように固定小数点数型に別の名前をつけましょう.
typedef int fix; // fix型という名前をつける. fix x, y; // xとyはfix型の変数
x, y座標やdx, dyなどの変化量はこうして固定小数点数で表しておき,最終的にドットの番地を計算する際に小数部を丸めて 整数に変換するようにします.
すると,固定小数点数の扱いは以下のように考えればよいことになります.
int round_fix(fix f) {
return (f + 0x80) >> 8;
}
たとえば,struct boxの中の(x, y)座標はfix型にしておき,draw_boxの際にround_fixするようなやり方が考えられます.
ボールがバーや壁にあたって跳ね返るのは, あたった向きによって, 進む方向を逆転するだけです.
上で説明した固定小数点数は,整数aを使って「a/2kを表している」 とみなして計算を進める方法でした.たとえば 「(x1, y1)から(x2, y2) へ線を引きたい」ような時には固定小数点数を使うより効率がよい方法があります. ここでは,ほんのさわりだけを解説します.
要は,
xn = x0 + n * dx yn = y0 + n * dyで,dxとdyが整数よりきめ細かい(有理数の)場合が扱えればよいわけです. すると,xn, ynも有理数となりますから, 表示に使う座標は整数部分だけですが,整数で表せない有理数の値も正確に計算する必要があります.
いま,
dx = A / Bとします(A, Bは整数).xnを,
xn = qxn + rxn / Bと表すことにします. ここで,qxnは整数部で,rxnは分子を表す 整数(0 ≦ rxn < B)です.
qxn, rxnが整数型変数qx, rxにそれぞれ
入っている時,
xnにA / Bを加えて次のステップの座標xn+1を求めるには,次の
プログラムを実行すればよいことになります.
1 rx += A; /* 分子にAを加える */
2 while (rx >= B) { /* 分子がB以上なら */
3 rx -= B; /* 分子をB減らす */
4 qx++; /* 整数部を1増やす */
5 }
これで,かけ算やわり算を使わずに,有理数の座標を内部的に正確に表現することができました.ynについても1ステップごとに同様の計算を行い,整数部(qx, qy)を使ってボールを表示すれば,ボールの角度を細かく変えることができます.
上のプログラムで,もし A < B に限定すれば,whileでなくif文ですむことになりますし, ボールの座標でなく「画面上に直線を描画する」場合には, dy = 1 あるいは dx = 1に限定して,もっとプログラムを単純・高速にできます.
1 /* (x0, y0)から(xk, yk)まで直線を描画する.*/
2 x = x0;
3 y = y0;
4 A = xk - x0;
5 B = yk - y0;
6 /* ここでは |A| < B と仮定する.*/
7 /* y座標は1ずつ増える.x座標はx + rx / Bと表現し,A/Bずつ増やす. */
8 rx = (B >> 1); /* x座標をx+1/2に初期化しておく */
9 while (y <= yk) {
10 plot(x, y); /* (x, y)に点を描画する */
11 y++; /* y座標は1ずつ動かす */
12 rx += A; /* x座標をA/Bだけ動かす */
13 if (rx >= B) { /* 分子がBを超えた */
14 x++; /* x座標の整数部を1増やす */
15 rx -= B; /* x座標の整数未満の分子をB減らす */
16 }
17 }
これは,コンピュータグラフィクスなどで使われる
DDA(デジタル微分解析器:Digital Differential Analyzer)
という手法を簡単化した例になっています.
これ以上詳しい内容については,この授業の範囲を超えます.
興味のある人は,上記のようなキーワードを使って自分で調べてみてください.