Making「回転」

Home PC-6001mkII Program etc

関連リンク 回転 回転 mkII

画面 画面 更新日:2006/07/30
作成日:2006/07/30

 このページでは、mkII版の「回転mkII」および初代機版の「回転」で実際に行った処理内容を紹介します。

回転表示の処理概要
 回転表示の処理について、まずは P6に特化しないところから説明します。

 基本的な考え方は、仮想画面に表示画面を回転して重ねた時に表示画面の各ドットの位置に仮想画面のどの色があるかを地道に計算して表示するといったものです。以下、概要を説明します。

画面  まず仮想画面を用意します。この仮想画面には、回転させる前の画像が描かれています。

 この仮想画面に、実際回転させる方向とは逆方向に回転させた表示画面を重ね合わせ、表示画面の各ドットに当たる色を調べて行きます。
画面  本来は、表示画面の各ドットの範囲内で色の平均値を出したり、ドットの範囲内で最も多く使用されている色を調べる所ですが、処理速度の問題もあるので、もっと簡易的な方法をとります。それは、

1ドットにつき1点の色のみ調べる

という方法です。ここでは、赤丸に示すように各ドットの左上の色を調べて行きます。
画面
 こうして、表示画面の各ドットに当たる色を調べ、表示画面に反映させます。


 「仮想画面に表示画面を回転させて重ねる」というと、SIN,COSの三角関数や行列を使った座標変換が入るんじゃないか、などなにかと複雑な処理を連想させますが、ここでは極力シンプルに掛け算と足し算のみで実現して行きます。

 最低限必要な情報は、以下の3つのみです。
  1. 回転の基準となる位置が仮想画面のどの座標にあるか
  2. 表示画面で右に1ドット進むときの仮想画面でのベクトル
  3. 表示画面で下に1ドット進むときの仮想画面でのベクトル

 このうち、a.については、その場で回転させる限りでは全く値が変わらないので適当な固定値を設定すれば良く、また b.とc.はあらかじめ回転方向ごとのデータを用意しておけば、回転の方向から単純に求まります。

 上記の3つの値さえわかれば、あとは以下のような手順で処理を行う事ができます。(図中の赤い星部分が回転の基準点とします)

画面

  1. 表示画面の左上の位置に対応する仮想画面の座標を求める。
    表示画面側の基準点から左に移動した分だけ b.の値を、上に移動した分だけ c.の値を それぞれa.の座標から引いていく
  2. この(仮想画面上の)座標を覚えておく。
  3. 仮想画面上の色を読み、表示画面の座標に反映させる。
  4. 仮想画面の座標を右に1つずらす(b.の値を足す)。また、表示画面の座標も右に一つずらす。
  5. 3.〜4. を、表示画面の横幅分だけ繰り返す。
  6. 2. で覚えておいた仮想画面上の座標に戻し、今度は下に一つずらす(c.の値を足す)。また表示画面側の座標も2行目の左端に移す。
  7. 2.〜6. を表示画面の縦幅だけ繰り返す。
 以上で表示画面のできあがりになります。1.以外は、座標の計算は足し算しかしていない事が分かると思います。

決め事
 処理を行う前に、色々決めないとダメな項目があります。回転の細かさや数値の精度,画面サイズなどがこれにあたります。これらを少しの根拠を元にフィーリングで決めていきます。

回転方向数
 最終的に1キャラクタ=1ドットのグラフィックの荒さもあるので、方向数は可能な限り押さえます。マシン語で処理するには2の階乗が都合が良いので、16方向か32方向辺りにします。

 今回はちょっと背伸びして32方向で処理を考えます。
数値の精度
 回転処理では、仮想画面上の座標や回転方向のデータに小数点未満の値を使用します。この精度は高い方が良いのですが、一方で座標等の値で3バイト以上使うと処理の難易度が各段に増すし、その分遅くなるしで良い事がないので、意地でも2バイトに抑えるよう考えます。

 ここでは、整数部分と小数部分は明確に分けた方が処理の都合が良いだろうという憶測の元、整数部分1バイト,小数部分を残りの1バイトに決定します。
仮想画面のサイズ
 仮想画面のサイズは極端な大きさでなければ良いのですが、それだと決まらないのである程度の縛りを入れてその中で選択することにします。

 まず、縛りとしても処理としても都合の良い「2の階乗ルール」を入れます。ただあまり小さすぎるのもなんなので、16以上から候補に入れておきます。

 次に現実的な考慮点として「仮想画面がメモリに入りきる事」があります。BASICも使う事を考えると20KBも確保できれば良い方でしょう。各ドットの色情報を1バイトで表現するとして、縦横141ドット以内までの制限となります。

 この時点で、縦横のサイズが16, 32, 64, 128の4つまで絞り込めました。あとはエイヤで決めます(^_^;)。今回は64x64(4KB)に設定します。

表示画面のサイズ
 表示画面については初めから全画面で試そうと思っていたので40x20(mkIIの場合)にあっさり決まりました。表示画面で回転の中心となる点は、画面の中心よりも若干下に設定するようにしました。

回転方向のデータ
 もう少し前置きが続きます(^^;)。

 一般的に、方向を表すには SINや COSなどの三角関数を使用します。これをリアルタイムで計算していたら流石に処理が重すぎるので、あらかじめ各方向の移動量をデータ化しておきます。

 SINや COSの範囲が -1〜1の範囲なので、基本はこれに沿ってデータ化して行きます。ここでは、-1〜1を下図のように -127〜127の255段階に変換したデータを 32方向分作成します。

画面

 これは(仮想画面上の)X方向のみなので、Y方向に関しても同様に作成します。こうして、32方向分の移動量データが揃いました...と書きたいところですが、実はまだ足りないのです。

 今回はドットをキャラクタで表現しているのですが、ここで問題になるのが「ドットの縦横の長さが同じではない」ことです。縦方向の移動量と横方向の移動量が異なるので、データも縦方向用と横方向用で分ける必要があります。
 この時の基準となる縦横比ですが、家庭用TVの縦:横=3:4で画面内のキャラクタ数が縦:横=20:40(初代機では16:32)なので、ドット(キャラクタ)あたりの縦横比3÷20:4÷40 = 3:2 となります。という事で、横方向用のデータは縦方向用の2/3のサイズで作成します。

 これでようやくデータが揃いました。ちなみにその後の試行錯誤により、データは以下の順序となっています。
(縦X方向,縦Y方向,90度回転させた横X方向,90度回転させた横Y方向) x 32

マシン語処理
 前置きが長くなりましたが、ようやく処理の記述に入ります。といっても、マシン語の処理概要は既に上で書いた通りなので、ここでは処理時間を少しでも短くするために行った事を書きとめておきます。

 マシン語に限りませんが、プログラムの実行時間を短くするには、繰り返し実行される回数が多い部分を高速化するのがポイントとなります。例えば、100回実行される部分を 0.1msだけでも縮めた方が、1回だけ実行する部分を 1ms縮めるより全体的には速くなるという理屈です。

 ここで、もう一度前の図に登場してもらいます。

画面

 この図で使用回数の一番多い部分は5番、すなわち仮想画面の色を表示画面に反映し、仮想画面上の座標を右へ1つずらす処理である事がわかると思います。
 実際、この処理はmkIIの場合800回(初代機だと512回)繰り返されます。という事で、この部分を重点的に高速化して行きます。

 ここから先はZ80系のマシン語に特化した説明になるので、興味がある人のみどうぞ。

 高速化で念頭に置いたのは、レジスタとメモリとの値の受け渡し部分を最小にする事です。(正確にはレジスタに値を入れる場合も実際にはメモリに入ってますが、ここでは説明のため別扱いにします)

 一例として、Aレジスタの値を一時的に別の場所に退避して別処理後にAレジスタに戻す場合で説明します。大まかに、以下の方法が考えられます。
項番方法コードステート数注意点
1PUSHで退避 PUSH AF
 〜別処理〜
POP AF
11
-
10
PUSHで入れた逆順にPOPで取り出す必要あり
2特定のアドレスに格納 LD (0D000H), A
 〜別処理〜
LD A, (0D000H)
13
-
13
特になし
3レジスタの示すアドレスに格納 LD HL, 0D000H
LD (HL), A
 〜別処理〜
LD A, (HL)
10
7
-
7
別処理を行う間、HLレジスタの内容が変わってはいけない
4別のレジスタに格納 LD C, A
 〜別処理〜
LD A, C
4
-
4
別処理を行う間、格納先のCレジスタの内容が変わってはいけない
 ステート数(処理時間)の数値は電波新聞社の「PC-8801 8001マシン語入門」を参考にしています。

 実行時のステート数を見れば一目瞭然ですが、高速化にこだわるならば4番の方法となります。つまり、レジスタ間だけで値の受け渡しを行えば高速になるのです。

 という事で、できるだけレジスタ内だけで解決するよう、処理内で使用する値を振り分けて行きます

 処理内で使用している値は、以下のようにたくさんあります。

  1. 仮想画面上のX座標 (2バイト)
  2. 仮想画面上のX座標移動量 (2バイト)
  3. 仮想画面上のY座標 (2バイト)
  4. 仮想画面上のY座標移動量 (2バイト)
  5. 表示画面上の描画先アドレス (2バイト)
  6. 1行分ループ用のカウンタ (1バイト)
 ...実に11バイト分です。この他に、仮想画面上のX座標とY座標から抽出元アドレスを求める必要があるので、全てをレジスタ内だけで処理するには最低13のレジスタが必要な事になります。

 このため、今回は俗に言う「裏レジスタ」をフル活用しています。裏レジスタは「表」のレジスタA,B,C,D,E,H,Lと対になるもの(A',B' のようにダッシュをつけたレジスタ名で区別する)で、EXX や EX AF AF'といった命令でこの裏レジスタを「表」にする事で初めて使用できます。(この間は「表」だったレジスタは「裏」になり使えない状態になる)

 表と裏の概念がややこしいですが、これでレジスタが14個分使用できる訳です。(といってもギリギリですが)

 ちなみにレジスタはこの他にも IX, IY, F, SP, PC がありますが、IX, IYレジスタは速度の面で使いどころが分からないし、F, SP, PC は通常システムが使う部分なので値の格納目的では使ってません。

 マシン語処理の説明をするのは、やっぱり難しいですね。あとは、マシン語のソースをそのまま載せておきます(手抜きですが)。注釈の多さが、作成中の試行錯誤ぶりや混乱ぶりを物語っていると思います。

 マシン語部分ソース(mkII用)  マシン語部分ソース(初代機用)

以下、余談
  • 書く前からある程度わかっていましたが、結構文章が長くなってしまいました。まあこれでもまだ分かりにくいような気もしますが。

  • 一応シリーズ化も念頭においてはいますが、1つ目がこれだとちょっと続きにくいかも?


PC-6001mkII