5-6 回転・拡大縮小

ここでは画像の回転や拡大縮小の方法を説明します。

拡大縮小に関しては誰でも思いつくことは出来ますが、回転は数学知識を必要とします。
ただし、ここでは知識というよりはその方法ズバリを関数化してしまうため、
回転の計算に必要な加法定理について覚えておく必要はありません。

■原点

拡大縮小や回転を行うにはどの点を中心に行うかというのが重要となりますが、
この点のことを原点と言います。

たとえば拡大縮小は通常は左上を中心に行えば良さそうに見えますが、
この場合では右下のみにしか拡縮されません。



例えば画面の中心から画像が迫ってくるようにしたい場合はその画像の中心を原点にします。


回転も同じく左上を中心とするとあまり使いようがありません。


例えば真ん中を中心にすると車のタイヤのような表現が可能になります。


これを踏まえて原点を利用した拡大縮小の方法を考えてみましょう。

■回転

実は回転を行うための公式が存在します。
ここではその公式を利用して回転を行うことにします。

さて、まずは公式を紹介する前に回転を行うとはどういうことか説明します。
画像の回転を行うということはここでは頂点を動かすということになります。

板ポリの頂点は4つあり、それぞれ左上、右上、左下、右下の順に定義されていると説明しました。
この頂点4つをそれぞれ原点を中心に同じ角度だけ回すことで画像を回転させたことになります。

なお、回転を行うのはあくまでも頂点座標だけであり、頂点に指定してあるUV値は動かす必要はありません。
これは描画先とテクスチャはまったく関連していないためで、UVとはテクスチャ上での色を取ってくるための位置情報であり、
その色を最終的に描画したい座標に打つだけなので、UVは描画先の位置とは直接関係無いからです。

さて、ここで回転の公式を紹介します。
要素としては元のXY座標と回転したい角度だけです。

r : 角度(ラジアン)
x : 元のX座標
y : 元のY座標

  X = x * cos( r ) - y * sin( r )
  Y = x * sin( r ) + y * cos( r )

ここで重要なのは元の座標は原点からの距離であり、画面上の座標はまったく関係ありません。
計算は常にその画像の原点を中心としますが、これをローカル座標系と言います。


例えば画像が1枚ありこれを画像の中心から30度回したいとします。

画像を100x100で作成しその中心位置を(50,50)とした場合、
この中心位置を原点、つまり(0,0)として計算します。

各頂点を原点からの距離にするのは実は簡単で、単にそれぞれの頂点を原点の位置だけ引きます。
例えば四角形の左上が(0,0)だったとすると、中心が(50,50)なのでそれぞれを引けば、
結果的に左上のローカル座標は(-50,-50)ということになります。

以下はこれを視覚的に表したものです。




そしてこれを原点を中心に30度回転をさせると以下のようになります。
※Y座標はスクリーン座標で下に伸びているため、プラス方向の回転は右回りとなります


上記の計算式で求めた各頂点はこの画像上のローカル座標系の位置となるので、
最終的に画面上に表示するには、原点を画面上に移動させるイメージでその原点分を各頂点に加算します。

この座標を頂点構造体に設定して描画を行うことで、
最終的に以下のように回転した画像を表示することが出来ます。


■拡大縮小

拡大縮小は回転より簡単で、単純に倍率を座標に掛けるだけです。

ただし1つ問題があり、拡大縮小は回転より前に行っておく必要があります。
これは特に縦と横で拡縮率が異なる場合で問題となり、
回転を先に行った後に拡縮をすると元の画像の比率と異なる描画になってしまいます。

拡大縮小も原点を中心としたローカル座標系で行う必要があります。

また拡大縮小ではX軸とY軸別々に倍率を指定出来るため、
Xの倍率をxscale、Yの倍率をyscaleとした場合の計算式は以下のようになります。

x      : 元のX座標
y      : 元のY座標
xscale : X方向の倍率
yscale : Y方向の倍率

X = x * xscale
Y = y * yscale

同じくこれも4頂点分計算しますが、例えば上の100x100、原点を(50,50)とした画像に対して、
Xスケールを1.5倍、Yスケールを0.5倍すると以下のような細長い画像が出来ます。

これで原点からの拡大縮小が出来たため、そのあとにこの座標を回転処理することで、
拡大縮小+回転された画像を画面に表示することが出来ます。

■まとめ

ここでは上記の回転と拡縮処理をまとめて1つの関数にしてみます。
例として以下のようにすれば良いでしょう。

// 指定のベクトルに拡大縮小・回転を行う(回転角はラジアン)
void CalcScaleRotate( float *vx,float *vy,float xscale,float yscale,float rot )
{
    float lx = *vx * xscale;
    float ly = *vy * yscale;
    *vx = (float)(lx * cos(rot) - ly * sin(rot));
    *vy = (float)(lx * sin(rot) + ly * cos(rot));
}

この関数は変換したい座標の入った変数のポインタ(vx,vy)を渡すことで、結果を同じ変数に返すようになっています。

また、何度も書きますが与える値はローカル座標です。
間違ってスクリーン座標を与えてしまうととんでもないことになってしまうので、
描画が意図したところに出ないと思ったらまずこの変換に渡している値を確認しましょう。


次は今まで説明した描画ルーチンをまとめてもっと簡単したクラスライブラリを紹介します。

このライブラリでは上記の計算式をそのまま使用しているので、使い方の参考にもなると思います。