Step 3 - マウスを使って回す
このステップでは、マウスを使って三角形を回せるようにします。
ドラッグ動作に合わせて回転させる
先ほどのステップで説明したとおり、X軸は画面の横方向を、Y軸は画面の縦方向を向いています。そのため、X方向にドラッグされたときはY軸を中心に、Y方向にドラッグされたときはX軸を中心に回転させることにします。「○軸を中心に回転」については、以下の図を参照してください。
理科の時間に習った「右ねじの法則」と同じです。右手を握って親指を立てて、そのまま親指を上から見ると、親指以外の指は反時計回りに巻かれています。これと同じように、X軸周りの回転とは、X軸の正の方向から見たときに反時計回りに回ることをいいます。時計回りに回る場合には、マイナス方向の回転となります。
プログラム中では、mouseMoveHandler
メソッドでドラッグ動作による回転を扱っています。
private function mouseMoveHandler(event:MouseEvent):void { if (event.buttonDown){ // カーソルの移動量に合わせて三角形を回転させます rotateY(0.02 * (event.localX - lastPosition.x)); rotateX(0.02 * (event.localY - lastPosition.y)); } // 現在のマウスカーソル位置を記憶します lastPosition.x = event.localX; lastPosition.y = event.localY; }
rotateY
やrotateX
については後で解説しますが、それぞれ三角形をY軸、X軸周りに回転させるメソッドです。引数は回転させる角度(ラジアン単位)です。マウスのボタンが押されているときに、今回と前回のカーソル位置の差分だけ回転させています。
差分に対して0.02をかけていますが、この値は任意です。0.02ぐらいかけておけば、314ピクセル分ドラッグしたときに6.28ラジアン回る(1回転する)ので、ちょうどいいと思います。
さて、先ほどのプログラムではコンストラクタに三角形を描くプログラムを書きましたが、今回のコンストラクタには以下の2行しかありません。
addEventListener(Event.ENTER_FRAME, enterFrameHandler); stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);
1行目では、addEventListener
メソッドを使って、画面が書き換わるときにenterFrameHandler
メソッドを呼びだすように設定しています。2行目も似たような形で、マウスカーソルが動いたときにmouseMoveHandler
を呼び出すように設定しています。
三角形の描画処理はenterFrameHandler
の中で行っています。画面が書き換わるたびに三角形を描き続けるとどんどん上書きされてしまうので、描く前に画面をクリアしておきます。
三角形を回転させる
肝心のrotateX
、rotateY
メソッドの説明です。これは先にプログラムの方を示します。
private function rotateX(angleRadians:Number):void { for each (var vertex:Vector3 in vertices) { var s:Number = Math.sin(angleRadians); var c:Number = Math.cos(angleRadians); var y:Number = c * vertex.y - s * vertex.z; var z:Number = s * vertex.y + c * vertex.z; vertex.y = y; vertex.z = z; } }
Math.sin
やらMath.cos
やら三角関数が出てきて複雑なメソッドですが、3Dプログラミングではよくあることです。各頂点をX軸周りにangleRadians
ラジアン回転させています。ですが、「よくあること」で片付けてしまうのはよくないので、少し説明を加えます。
X軸周りの回転ということは、右ねじの法則で説明したとおり、X軸の正方向から見たときに頂点が反時計回りに回るということです。右手をフレミングにして、親指を手前に、人差し指を右に向けてください。少し手が痛いかもしれませんが、これがX軸を正方向から見た状態です。Y軸(人差し指)は右方向に、Z軸(中指)は上方向に伸びています。回転で変化するのはY、Z座標のみで、X座標は一切変化しません。
金沢工業大学のサイトでは、三角関数の定義と加法定理だけを使って回転に必要な式を導く方法が説明されています。座標軸は横軸がX、縦軸がYとなっていますが、それぞれYとZに置き換えて考えてください。rotateX
で使っているのは、行列が出てくる直前の式です。回転させる角度をa、回転後の座標を(x', y')とすると、以下のような式になります。
y' = y * cos(a) - z * sin(a) z' = y * sin(a) + z * cos(a)
これをプログラムとして書いたのがrotateX
になります。rotateY
については軸が違うだけで、考え方は同様です。
Step 4 - キーボードで移動させる
回転が出来たので、次は移動させます。マウスでの操作は既に使ってしまっているので、方向キーを使って移動できるようにします。
キーボード操作で上下左右に動かす
keyDownHandler
では、方向キーが押されたときに三角形を移動させる処理をします。コンストラクタでは、このメソッドをKeyboardEvent.KEY_DOWN
イベントのハンドラとして登録しておきます。
private function keyDownHandler(event:KeyboardEvent):void { switch (event.keyCode) { case Keyboard.LEFT: translate(-0.1, 0); break; case Keyboard.UP: translate(0, 0.1); break; case Keyboard.RIGHT: translate(0.1, 0); break; case Keyboard.DOWN: translate(0, -0.1); break; } }
方向キーが押されたときに、三角形を移動させるtranslate
メソッドを呼び出しています。translate
メソッドは以下のように単純なもので、引数に従って各頂点の位置を変化させています。
private function translate(dx:Number, dy:Number):void { for each (var vertex:Vector3 in vertices) { vertex.x += dx; vertex.y += dy; } }
引数は、X、Y方向の移動量です。気をつけてほしいのは、単位はピクセルではなく、3D空間内での移動量だということです。今は画面の左上が(-1, -1, z)、右下が(1, 1, z)ということで設定しているので、1回の移動量には0.1という小さな値を設定しています。
回転と移動の順序
これで、マウスで回転、方向キーで移動という2つの操作ができるようになりました。しばらく動かしたり回したりしていると、移動先の場所で回すという操作ができないことに気付くと思います。
例えば、適当に回転させた状態の三角形を右上に置こうとする場合を考えます。三角形が画面中心にあるときに適当に回転させて、それからキーボードで動かせば、うまく移動させることができます。しかし、先にキーボードで移動させた後で回転させるとうまくいきません。三角形が回っているというよりは、空間全体が回っている感覚です。人によってはこれに違和感を覚えると思います。
このように、頂点の操作を行う場合には、個々の操作(回転と移動)が同じものであっても順序が違えば結果が違うということを意識しておかなければなりません。このあたりはプログラムを組むときもそうですが、3Dモデリングソフトを使うときも同じことが言えると思います。
3Dプログラミングでは、一般に回転や移動は行列クラスを使って計算します。そういうプログラムを書けば、操作の順序が違うと結果が変わるのは数学的に考えて明らかだということが分かります。しかし「行列同士の掛け算は逆にすると結果が変わる」という数学的な考え方よりも「回してから動かすのと動かしてから回すのとでは結果が違う」というイメージの方が大事だと思い、あえて行列クラスは使っていません。
まとめ
Flex 3 SDKとFlashDevelopのインストールから、三角形の回転、移動ができるプログラムまでざっと進めてきました。ここから先の課題ですが、より3Dらしく見せるためのシェーディング、途中で話にあがった透視投影、より複雑な形状を表現するためのシーングラフなどがあります。Flashの強みを生かしてインタラクティブなコンテンツを作った方が楽しいと思うので、今後はそういう方向に持っていければいいかなと思います。