AS3Graphics#6 Matrix3Dで立体描画

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

Matrix3Dを使って簡単な立体を描画する流れを解説します。

3D(3次元)とは?

前回までに扱ってきた内容では、座標系はx軸とy軸の二次元で表していました。実は Flash では、これに加えてz軸を扱うことができます。z軸は奥行きを示し、xy平面と直交しています。手前が負で奥が正となります。表示オブジェクトに、従来のxyに加えてzの座標値を与える事により、3D表示オブジェクトとなります。ただしここで気を付けなければならないのが、z座標で表す奥行きと、DisplayObjectContainer 内での重なり順は別モノだということです。z座標値では手前にあるはずのものでも、重なり順が下にある場合は、z座標値に関係なく重なり順がより上にあるものの後ろに隠れてしまいます。Flash Player 10 における3Dとは、表示オブジェクトの形を立体的な見え方に変形してくれる機能でしかなく、実際に三次元空間をシミュレートできるわけではありません。

Matrix3Dクラス

表示オブジェクトを3D移動/変形させるために使用するのが Matrix3D クラスです。多くの機能があり、その上難解なものが多いのですが、今回は並行移動と回転のみ使用します。移動/変形を行うメソッドには append 系と prepend 系のものがあり、移動させる内容によっては順序で結果が変わる場合があるのでうまく使い分ける必要があります。

正六面体を描画してみる

それでは、Matrix3D を使って正六面体(立方体)を描画してみましょう。手順としては、正方形の面を六つ作り、原点(0, 0, 0)を中心にして立方体の面を構成する位置へ移動/回転させます。それだけではつまらないので、ついでにマウスポインタの位置に応じて回転させてみます。

package { import flash.display.Sprite; import flash.events.Event; import flash.geom.Point; import flash.geom.Vector3D;

/** * Matrix3Dを利用した立体描画のサンプルです。 * @author ishigami.shintaro */ public class Matrix3DTest extends Sprite {

/** * コンストラクタです。 */ public function Matrix3DTest() { super(); stage.scaleMode = "noScale";

//背景を描画します。 graphics.beginFill(0); graphics.drawRect(0, 0, 400, 400);

//投影の中心をstageの中心に設定します。 transform.perspectiveProjection.projectionCenter = new Point(stage.width / 2, stage.height / 2);

//Cubeインスタンスを作成します。 var cube:Cube = new Cube(); cube.z = 0; cube.x = 200; cube.y = 200; addChild(cube); } } }

import flash.display.Graphics; import flash.display.Shape; import flash.geom.ColorTransform; import flash.geom.Matrix3D; import flash.geom.Vector3D;

/** * Cubeの面を構成するShapeです。 * @author ishigami.shintaro */ class Square extends Shape {

/** * 一辺の長さです。 */ public static const SIZE:int = 100;

/** * コンストラクタです。 */ public function Square() { super(); //矩形を描画します。 var g:Graphics = graphics; g.lineStyle(0, 0xFFFFFF); g.beginFill(0x333333); g.drawRect(-SIZE / 2, -SIZE / 2, SIZE, SIZE); }

/** * stageから見た相対的z座標を返します。 * @return */ public function get relativeZ():Number { if (stage) { var m:Matrix3D = transform.getRelativeMatrix3D(stage); return m ? m.position.z : 0; } else { return 0; } } }

import flash.display.Sprite; import flash.events.Event; import flash.geom.Matrix3D; import flash.geom.Vector3D;

/** * 正六面体です。 * @author ishigami.shintaro * */ class Cube extends Sprite {

/** * 面の配列です。 */ private var surfaces:Vector. = new Vector.(6, true);

/** * コンストラクタです。 */ public function Cube() { super();

const HALF_SIZE:int = Square.SIZE / 2;

//面のインスタンスを作成します。 for (var i:uint = 0; i < 6; i++) { var square:Square; square = new Square(); square.z = 0; //matrix3Dを初期化 surfaces[i] = addChild(square) as Square; } //六つの面を(0, 0, 0)点を中心とした六面体の配置に移動します。 surfaces[0].transform.matrix3D.appendTranslation(0, 0, -HALF_SIZE); //z -50 surfaces[1].transform.matrix3D.appendTranslation(0, 0, -HALF_SIZE); //z -50 surfaces[1].transform.matrix3D.appendRotation(90, Vector3D.X_AXIS); //x軸を軸に90度回転 surfaces[2].transform.matrix3D.appendTranslation(0, 0, -HALF_SIZE); //z -50 surfaces[2].transform.matrix3D.appendRotation(-90, Vector3D.Y_AXIS); //y軸を軸に-90度回転 surfaces[3].transform.matrix3D.appendTranslation(0, 0, HALF_SIZE); //z +50 surfaces[4].transform.matrix3D.appendTranslation(0, 0, HALF_SIZE); //z +50 surfaces[4].transform.matrix3D.appendRotation(90, Vector3D.X_AXIS); //x軸を軸に90度回転 surfaces[5].transform.matrix3D.appendTranslation(0, 0, HALF_SIZE); //z +50 surfaces[5].transform.matrix3D.appendRotation(-90, Vector3D.Y_AXIS); //y軸を軸に-90度回転 addEventListener(Event.ENTER_FRAME, enterFrameHandler); } /** * enterFrameハンドラです。 * @param event */ private function enterFrameHandler(event:Event):void { rotate(); //回転 sortChildIndex(); //重ね順並べ替え } /** * マウス位置に応じてCube全体を回転させます。 */ private function rotate():void { var ry:Number = (stage.mouseX - stage.width / 2) * 0.02; var rx:Number = (stage.mouseY - stage.height / 2) * 0.02; var pivot:Vector3D = new Vector3D(x, y, z); //回転の中心点 transform.matrix3D.appendRotation(rx, Vector3D.X_AXIS, pivot); transform.matrix3D.appendRotation(-ry, Vector3D.Y_AXIS, pivot); } /** * 面のstageから見た相対z座標の順に重ね順を入れ替えます。 */ private function sortChildIndex():void { surfaces.sort(function(item1:Square, item2:Square):int { var z1:Number = item1.relativeZ; var z2:Number = item2.relativeZ; if (z1 < z2) { return 1; } else if (z2 < z1) { return -1; } else { return 0; } }); for (var i:uint = 0; i < surfaces.length; i++) { setChildIndex(surfaces[i], i); } } } [/as3] [SWF]http://public-blog-dev.s3.amazonaws.com/wp-content/uploads/2011/09/Matrix3DTest.swf, 400, 400[/SWF]

このサンプルを見るにはFlash Playerがインストールされている必要があります。

解説

Cube のコンストラクタで正六面体を組み立てるあたりは、特に説明不要かと思います。enterFrame イベントでマウスポインタの位置に応じてCube全体を回転させていますが、ただ回転させるだけでは、z軸上での面の重なり順と実際の重なり順が一致しなくなってしまいます。これを回避するため、transform.getRelativeMatrix3D() でstageから見たそれぞれの面の相対的なz座標を取得し、setChildIndex() でz座標の降順に重なり順を入れ替える処理を行っています。この際に、Squareの基準点が左上隅にあると狙い通りの順序にならないため、矩形の中心に基準点が来るようにしています。

Flash Player 11 では、Stage3D によってもっと強力に3D表現ができるようになるそうです。