AS3.0 で 3D プログラミングを1から勉強する (1)

3D の原理をあまり知らなかったので、ActionScript 3.0 で1から勉強してみた。1からなのでフレームワークは使わず、自力で実装していく。

Web 上には色んな資料があってありがたいだけど、玉石混交な上に、有用なものでも一本道で幅の狭いものが多い。前提知識のない自分にとっては、資料間の関連性を理解するのが大変だった。

なので、なるべく簡単なところからスタートしつつ、広く浅く体験していくことを目標としてみる。まずは、四面体をワイヤーフレームで表示するところからスタートしよう。

四面体を定義する

まずは、3次元上の点を表現する Point3D クラスを作る。

class Point3D {
    public var x:Number;
    public var y:Number;
    public var z:Number;

    public function Point3D(_x:Number = 0, _y:Number = 0, _z:Number = 0){
        x = _x;
        y = _y;
        z = _z;
    }
}

四面体を作るために、メインのクラスに4つの点を定義した。

public class Study3d1 extends Sprite{
    private var p1:Point3D = new Point3D(  0,   0, 100);
    private var p2:Point3D = new Point3D(100,   0,   0);
    private var p3:Point3D = new Point3D(  0, 100,   0);
    private var p4:Point3D = new Point3D(-50, -50, -50);

2次元空間に表示する

3次元の点を2次元空間に描画する方法を考える。

とはいえ、一番最初のサンプルなので、真上(もしくは真下)から見たところを表示する。つまり、Z の値を無視して表示する。3次元空間上の(0, 0, 100) という点は、(0, 0) に表示する。

プロット先の座標を求めて(といっても、Zの値を無視するだけだけど)、線で結んでやれば四面体を真上から見た図ができた。

回転!

これだとあまりにも退屈なので、回転させてみよう。

カメラが回転するのは難しいので、四面体に回転してもらうことにする。こっちはちょー簡単。

ある点 p を原点周りに rad ラジアンだけ回転させるには

p.x =  Math.cos(rad) * x + Math.sin(rad) * y;
p.y = -Math.sin(rad) * x + Math.cos(rad) * y;

とする。(詳しく知りたい人は「回転行列」あたりのキーワードで検索すべし)

x と y を y と z に置き換えれば X 軸周りの回転になるし、z と x に置き換えれば Y 軸周りの回転になる。

マウスの位置に応じて Y 軸周りと Z 軸の周りに回転させるようにしてみた。回転後の座標を線で繋げば回転する四面体の完成だ。

ソースはたった86行。ローテクだけど、回転し続けていれば立体に見えなくもない。

第2回に続く。

今回のソースは以下に(86行)。

package {
    import flash.display.*;
    import flash.geom.Point;

    [SWF(backgroundColor="0x000000")]
    public class Study3d1 extends Sprite{
        private var p1:Point3D = new Point3D(  0,   0, 100);
        private var p2:Point3D = new Point3D(100,   0,   0);
        private var p3:Point3D = new Point3D(  0, 100,   0);
        private var p4:Point3D = new Point3D(-50, -50, -50);

        private var canvas:Sprite = new Sprite();

        public function Study3d1(){
            stage.scaleMode = "noScale";
            stage.align = "TL";

            addChild(canvas);
            canvas.x = 150;
            canvas.y = 150;

            changeHandler(null);
            addEventListener("enterFrame", changeHandler);
        }

        private function changeHandler(event:Object):void {
            var pp1:Point3D = rotate(p1);
            var pp2:Point3D = rotate(p2);
            var pp3:Point3D = rotate(p3);
            var pp4:Point3D = rotate(p4);

            canvas.graphics.clear();
            drawPoint(pp1); drawPoint(pp2);
            drawPoint(pp3); drawPoint(pp4);
            drawLine(pp1, pp2); drawLine(pp1, pp3); drawLine(pp1, pp4);
            drawLine(pp2, pp3); drawLine(pp2, pp4); drawLine(pp3, pp4);
        }

        private function drawPoint(p:Point3D):void {
            canvas.graphics.beginFill(0xffffff);
            canvas.graphics.drawCircle(p.x, p.y, 10);
            canvas.graphics.endFill();
        }

        private function drawLine(p1:Point3D, p2:Point3D):void {
            canvas.graphics.lineStyle(3, 0xffffff);
            canvas.graphics.moveTo(p1.x, p1.y);
            canvas.graphics.lineTo(p2.x, p2.y);
            canvas.graphics.lineStyle();
        }

        private function rotate(_p:Point3D):Point3D {
            var ret:Point3D = new Point3D(_p.x, _p.y, _p.z);
            var p:Point;

            // y rotate
            p = rotate2d(ret.z, ret.x, stage.mouseX / 180 * Math.PI);
            ret.z = p.x; ret.x = p.y;

            // z rotate
            p = rotate2d(ret.x, ret.y, -stage.mouseY / 180 * Math.PI);
            ret.x = p.x; ret.y = p.y;

            return ret;
        }

        private function rotate2d(x:Number, y:Number, rad:Number):Point {
            var p:Point = new Point();
            p.x =  Math.cos(rad) * x + Math.sin(rad) * y;
            p.y = -Math.sin(rad) * x + Math.cos(rad) * y;
            return p;
        }
    }
}

class Point3D {
    public var x:Number;
    public var y:Number;
    public var z:Number;

    public function Point3D(_x:Number = 0, _y:Number = 0, _z:Number = 0){
        x = _x;
        y = _y;
        z = _z;
    }
}