情報システム実験 K-13 組み込みオペレーティングシステム
No.4

ボールを斜めに動かす

前回の move_box で(四角いですが)ボールを動かすことができるようになりました. 上下左右や斜め45度に動かす方法は直感的にわかると思いますが, それ以外の角度に動かすのはどうすればよいでしょうか. ここでは,できるだけ簡単な方法を考えてみましょう.

たとえば,以下のような考え方はどうでしょうか.


ここで,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などの変化量はこうして固定小数点数で表しておき,最終的にドットの番地を計算する際に小数部を丸めて 整数に変換するようにします.

すると,固定小数点数の扱いは以下のように考えればよいことになります.

たとえば,struct boxの中の(x, y)座標はfix型にしておき,draw_boxの際にround_fixするようなやり方が考えられます.

ボールの跳ね返り

ボールがバーや壁にあたって跳ね返るのは, あたった向きによって, 進む方向を逆転するだけです.


別の方法(DDA)

上で説明した固定小数点数は,整数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) という手法を簡単化した例になっています. これ以上詳しい内容については,この授業の範囲を超えます. 興味のある人は,上記のようなキーワードを使って自分で調べてみてください.