Beautifl - Flash Gallery

Preview

3D ドーナッツ
muta244 2009年9月1日 All rights reserved
?
      package
{
    import flash.display.*;
    import flash.events.*;
    import flash.text.*;
    import flash.ui.Keyboard;
    import flash.utils.getTimer;
    
    [SWF(width="465", height="465",
        backgroundColor="0x000000", frameRate="30")]
    public class Donuts02 extends Sprite
    {
        private var _donuts:Donuts;
        private var _matrix:Matrix3D;
        private var _render:Render;
        
        private var _textField:TextField;
        
        private var _rx:Number = 0;
        private var _ry:Number = 0;
        private var _rz:Number = 0;
        
        private var _isAutoPlay:Boolean = false;
        
        private const STAGE_WIDTH:Number = stage.stageWidth;
        private const STAGE_HEIGHT:Number = stage.stageHeight;
        
        public function Donuts02()
        {
            configure();
            
            var pi:Number = Math.PI;
            var start:int;
            var i:int;
            
            _donuts = new Donuts(16, 0.7);
            _matrix = new Matrix3D();
            _render = new Render(graphics, _matrix);
            
            addTextField();
            
            mainLoop(null);
            
            stage.addEventListener(MouseEvent.CLICK, changePlayMode);
            
            _textField.text =
                "Partitions : " + _donuts.numPartitions + "\n" +
                "Vertices : " + _donuts.vertices.length + "\n" +
                "Stretch : " + _donuts.stretch;
        }
        
        private function configure():void
        {
            stage.align = StageAlign.TOP_LEFT;
            stage.scaleMode = StageScaleMode.NO_SCALE;
            stage.showDefaultContextMenu = false;
        }
        
        private function addTextField():void
        {
            _textField = new TextField();
            _textField.defaultTextFormat = new TextFormat(
                null, null, 0xFFFFFF, null, null, null, null, null, TextFormatAlign.RIGHT);
            _textField.autoSize = TextFieldAutoSize.RIGHT;
            _textField.background = true;
            _textField.backgroundColor = 0x000000;
            _textField.selectable = false;
            _textField.x = STAGE_WIDTH - _textField.width;
            
            addChild(_textField);
            
            var announce:TextField = new TextField();
            announce.defaultTextFormat = new TextFormat(
                null, null, 0xFFFFFF, null, null, null, null, null, TextFormatAlign.LEFT);
            announce.autoSize = TextFieldAutoSize.LEFT;
            announce.background = true;
            announce.backgroundColor = 0x000000;
            announce.selectable = false;
            announce.text =
                "\"DOWN\" : Partitions -\n" +
                "\"UP\" : Partitions +\n" +
                "\"LEFT\" : Stretch -\n" +
                "\"RIGHT\" : Stretch +\n" +
                "\"Z\" : Polygon on/off\n" +
                "\"X\" : Wireframe on/off";
            announce.y = STAGE_HEIGHT - announce.height;
            
            addChild(announce);
        }
        
        private function mainLoop(event:Event):void
        {
            _matrix.identity();
            _matrix.scale(50, 50, 50);
            _matrix.rotateX(_rx);
            _matrix.rotateY(_ry);
            _matrix.rotateZ(_rz);
            _matrix.translate(STAGE_WIDTH / 2, STAGE_HEIGHT / 2, 0);
            
            graphics.clear();
            
            _render.drawIndexedPrimitive(
                Render.PRIMITIVE_POLYGON, _donuts.vertices, _donuts.indices);
            
            _rx += 0.04;
            _ry += 0.08;
            _rz += 0.02;
        }
        
        private function changePlayMode(event:MouseEvent):void
        {
            if (_isAutoPlay)
            {
                removeEventListener(Event.ENTER_FRAME, mainLoop);
                stage.removeEventListener(KeyboardEvent.KEY_DOWN, changeDonuts);
            }
            else
            {
                addEventListener(Event.ENTER_FRAME, mainLoop);
                stage.addEventListener(KeyboardEvent.KEY_DOWN, changeDonuts);
            }
            
            _isAutoPlay = !_isAutoPlay;
        }
        
        private function changeDonuts(event:KeyboardEvent):void
        {
            var numPartitions:int = _donuts.numPartitions;
            
            switch (event.keyCode)
            {
                case Keyboard.UP:
                    if (numPartitions >= 0)
                    {
                        _donuts.numPartitions += 1;
                    }
                    break;
                case Keyboard.DOWN:
                    if (numPartitions > 0)
                    {
                        _donuts.numPartitions -= 1;
                    }
                    break;
                case Keyboard.LEFT:
                    _donuts.stretch -= 0.02;
                    break;
                case Keyboard.RIGHT:
                    _donuts.stretch += 0.02;
                    break;
                case 88:
                    //trace("X");
                    _render.wireframeEnabled = !_render.wireframeEnabled;
                    break;
                case 90:
                    //trace("Z");
                    _render.polygonEnabled = !_render.polygonEnabled;
                    break;
            }
            
            _textField.text =
                "Partitions : " + _donuts.numPartitions + "\n" +
                "Vertices : " + _donuts.vertices.length + "\n" +
                "Stretch : " + _donuts.stretch.toString().substr(0, 4);
        }
    }
}

import flash.display.Graphics;

class Render
{
    public static const PRIMITIVE_POLYGON:int = 1;
    
    private var _graphics:Graphics;
    private var _matrix:Matrix3D;
    private var _mode:int;
    
    private var _polygonEnabled:Boolean = true;
    private var _wireframeEnabled:Boolean = false;
    
    // 光源ベクトル
    private var _light:Vector3D   = new Vector3D( 0.0,  0.0, -1.0);
    
    // アンビエント(環境光)の色
    private var _ambient:Vector3D = new Vector3D( 0.2,  0.2,  0.2);
    // ディフューズ(拡散光)の色
    private var _diffuse:Vector3D = new Vector3D( 0.8,  0.8,  0.8);
    
    /**
    * 新しい Render インスタンスを作成します。
    */
    public function Render(
        graphics:Graphics = null, matrix:Matrix3D = null, mode:int = 1):void
    {
        _graphics = graphics;
        _matrix = matrix;
        _mode = mode;
    }
    
    public function drawIndexedPrimitive(type:int, vertices:Array, indices:Array):void
    {
        var i:int;
        var l:int;
        
        var tv:Array = [];
        
        // 座標を一次変換
        for (i = 0, l = vertices.length; i < l; ++i)
        {
            tv.push(_matrix.transform(vertices[i]));
        }
        
        // ラスタライズ
        switch (type)
        {
            case PRIMITIVE_POLYGON:
                
                var triangles:Array = [];
                
                for (i = 0, l = indices.length; i < l; i += 3)
                {
                    triangles.push(new Triangle3D(
                        tv[indices[i    ]],
                        tv[indices[i + 1]],
                        tv[indices[i + 2]]
                    ));
                }
                
                // 深度ソート
                triangles.sortOn("depth", Array.DESCENDING | Array.NUMERIC);
                
                for each (var triangle:Triangle3D in triangles)
                {
                    drawPolygon(triangle);
                }
                
                break;
        }
    }
    
    private function drawPolygon(triangle:Triangle3D):void
    {
        if (polygonEnabled)
        {
            // 面の法線ベクトルを求める。
            var n:Vector3D = triangle.normal();
            
            // 光源と法線の内積を取る。
            var w:Number = n.dot(_light);
            
            // 背面カリング処理。
            // 内積が負なら背面なので描画しない。(ただしパースをつけた場合は正確な判定方法ではない。)
            if (w < 0)
            {
                return;
            }
            
            var r:int = (_ambient.x + w * _diffuse.x) * 255;
            var g:int = (_ambient.y + w * _diffuse.y) * 255;
            var b:int = (_ambient.z + w * _diffuse.z) * 255;
            
            r = (r < 0) ? 0 : (r > 255) ? 255 : r;
            g = (g < 0) ? 0 : (g > 255) ? 255 : g;
            b = (b < 0) ? 0 : (b > 255) ? 255 : b;
            
            _graphics.beginFill(r << 16 | g << 8 | b);
        }
        
        if (wireframeEnabled)
        {
            _graphics.lineStyle(0, 0xFF0000);
        }
        
        _graphics.moveTo(triangle.v1.x, triangle.v1.y);
        _graphics.lineTo(triangle.v2.x, triangle.v2.y);
        _graphics.lineTo(triangle.v3.x, triangle.v3.y);
        _graphics.lineTo(triangle.v1.x, triangle.v1.y);
        
        _graphics.endFill();
    }
    
    public function get graphics():Graphics
    {
        return _graphics;
    }
    
    public function set graphics(value:Graphics):void
    {
        _graphics = value;
    }
    
    public function get matrix():Matrix3D
    {
        return _matrix;
    }
    
    public function set matrix(value:Matrix3D):void
    {
        _matrix = value;
    }
    
    public function get polygonEnabled():Boolean
    {
        return _polygonEnabled;
    }
    
    public function set polygonEnabled(value:Boolean):void
    {
        _polygonEnabled = value;
    }
    
    public function get wireframeEnabled():Boolean
    {
        return _wireframeEnabled;
    }
    
    public function set wireframeEnabled(value:Boolean):void
    {
        _wireframeEnabled = value;
    }
}

class Donuts
{
    private var _vertices:Array;
    private var _indices:Array;
    private var _numPartitions:int;
    private var _stretch:Number;
    
    /**
     * @param numPartitions 分割数
     * @param stretch 拡がり
     */
    public function Donuts(numPartitions:int = 16, stretch:Number = 1):void
    {
        _numPartitions = numPartitions;
        _stretch = stretch;
        
        setup(numPartitions, stretch);
    }
    
    /**
     * @private
     */
    private function setup(numPartitions:int, stretch:Number):void
    {
        var n:int = numPartitions;
        
        var i:int;
        var j:int;
        
        var a:Number = 0;
        var c:Number = 0;
        var addc:Number = Math.PI * 2 / n;
        
        var s:Number;
        var z:Number;
        var x:Number;
        var y:Number;
        
        _vertices = [];
        
        for (i = 0; i < n; ++i)
        {
            s = Math.sin(c) + 2;
            z = Math.cos(c);
            
            for (j = 0; j < n; ++j)
            {
                x = Math.cos(a);
                y = Math.sin(a);
                
                _vertices.push(new Vector3D(
                    x * s * stretch,
                    y * s * stretch,
                    z     * stretch
                ));
                
                a += Math.PI / n * 2;
            }
            
            c += addc;
        }
        
        var m:int = n * n;
        var off:Number;
        
        _indices = [];
        
        for (i = 0; i < n; ++i)
        {
            off = i * n;
            
            for (j = 0; j < n; ++j)
            {
                _indices.push((off + n) % m + (j + 1) % n);
                _indices.push( off          + (j + 1) % n);
                _indices.push( off          +  j         );
                _indices.push((off + n) % m +  j         );
                _indices.push((off + n) % m + (j + 1) % n);
                _indices.push( off          +  j         );
            }
        }
    }
    
    public function get vertices():Array
    {
        return _vertices.concat();
    }
    
    public function get indices():Array
    {
        return _indices.concat();
    }
    
    public function get numPartitions():int
    {
        return _numPartitions;
    }
    
    public function set numPartitions(value:int):void
    {
        _numPartitions = value;
        
        setup(value, stretch);
    }
    
    public function get stretch():Number
    {
        return _stretch;
    }
    
    public function set stretch(value:Number):void
    {
        _stretch = value;
        
        setup(numPartitions, value);
    }
}

class Triangle3D
{
    private var _v1:Vector3D;
    private var _v2:Vector3D;
    private var _v3:Vector3D;
    
    /**
    * 新しい Triangle3D インスタンスを作成します。
    */
    public function Triangle3D(v1:Vector3D, v2:Vector3D, v3:Vector3D):void
    {
        _v1 = v1;
        _v2 = v2;
        _v3 = v3;
    }
    
    public function get v1():Vector3D
    {
        return _v1;
    }
    
    public function set v1(value:Vector3D):void
    {
        _v1 = value;
    }
    
    public function get v2():Vector3D
    {
        return _v2;
    }
    
    public function set v2(value:Vector3D):void
    {
        _v2 = value;
    }
    
    public function get v3():Vector3D
    {
        return _v3;
    }
    
    public function set v3(value:Vector3D):void
    {
        _v3 = value;
    }
    
    /**
     * 各ベクトルの z の値を比較し、最も低い数値を返します。
     * 深度ソート時に Array オブジェクトの sortOn と合わせて使用します。
     */
    public function get depth():Number
    {
        var min:Number = (v1.z < v2.z) ? v1.z : v2.z;
        return (min < v3.z) ? min : v3.z;
    }
    
    /**
     * このトライアングルを面とする法線ベクトルを返します。
     * 
     * @return このトライアングルを面とする法線ベクトル
     */
    public function normal():Vector3D
    {
        var n:Vector3D = new Vector3D();
        n.cross(v1.distance(v2), v1.distance(v3));
        n.normalize();
        
        return n;
    }
    
    /**
     * このトライアングルのストリング表現を返します。
     * 
     * @return このトライアングルのストリング表現
     */
    public function toString():String
    {
        var temp:String = "Triangle3D {\n" +
            "\tv1 : " + v1 + "\n" +
            "\tv2 : " + v2 + "\n" +
            "\tv3 : " + v3 + "\n}";
        
        return temp;
    }
}

class Matrix3D
{
    private var _matrix:Array;
    
    /**
    * 新しい Matrix3D インスタンスを作成します。
    */
    public function Matrix3D():void
    {
        identity();
    }
    
    public function identity():void
    {
        _matrix = [
            1, 0, 0, 0,
            0, 1, 0, 0,
            0, 0, 1, 0,
            0, 0, 0, 1
        ];
    }
    
    public function scale(sx:Number, sy:Number, sz:Number)
    {
        _matrix = _multiply(_matrix, [
            sx,  0,  0,  0,
             0, sy,  0,  0,
             0,  0, sz,  0,
             0,  0,  0,  1
        ]);
    }
    
    public function translate(dx:Number, dy:Number, dz:Number)
    {
        _matrix = _multiply(_matrix, [
             1,  0,  0,  0,
             0,  1,  0,  0,
             0,  0,  1,  0,
            dx, dy, dz,  1
        ]);
    }
    
    public function rotateX(angleRadians:Number)
    {
        var sin = Math.sin(angleRadians);
        var cos = Math.cos(angleRadians);
        
        _matrix = _multiply(_matrix, [
            1,    0,   0, 0,
            0,  cos, sin, 0,
            0, -sin, cos, 0,
            0,    0,   0, 1
        ]);
    }
    
    public function rotateY(angleRadians:Number)
    {
        var sin = Math.sin(angleRadians);
        var cos = Math.cos(angleRadians);
        
        _matrix = _multiply(_matrix, [
            cos, 0, -sin, 0,
              0, 1,    0, 0,
            sin, 0,  cos, 0,
              0, 0,    0, 1
        ]);
    }
    
    public function rotateZ(angleRadians:Number)
    {
        var sin = Math.sin(angleRadians);
        var cos = Math.cos(angleRadians);
        
        _matrix = _multiply(_matrix, [
             cos, sin, 0, 0,
            -sin, cos, 0, 0,
               0,   0, 1, 0,
               0,   0, 0, 1
        ]);
    }
    
    /**
     * このマトリクスで指定のベクトルを一次変換します。
     * 
     * @return 一次変換されたベクトル
     */
    public function transform(v:Vector3D):Vector3D
    {
        var temp:Vector3D = new Vector3D();
        var m:Array = _matrix;
        
        temp.x = m[0] * v.x + m[4] * v.y + m[ 8] * v.z + m[12];
        temp.y = m[1] * v.x + m[5] * v.y + m[ 9] * v.z + m[13];
        temp.z = m[2] * v.x + m[6] * v.y + m[10] * v.z + m[14];
        
        return temp;
    }
    
    /**
     * このマトリクスと指定マトリクスの成分を乗算(合成)します。
     * 
     * @param m 対象の Matrix3D オブジェクト
     */
    public function multiply(m:Matrix3D):void
    {
        _matrix = _multiply(m.toArray(), _matrix);
    }
    
    /**
     * @private
     */
    private function _multiply(m1:Array, m2:Array):Array
    {
        var temp:Array = [];
        var i:int = 0;
        
        for (var y:int = 0; y < 4; ++y)
        {
            for (var x:int = 0; x < 4; ++x)
            {
                temp[x + i] =
                    m1[i    ] * m2[x     ] +
                    m1[i + 1] * m2[x +  4] +
                    m1[i + 2] * m2[x +  8] +
                    m1[i + 3] * m2[x + 12];
            }
            i += 4;
        }
        
        return temp;
    }
    
    /**
     * このマトリクスの成分配列を返します。
     * 
     * @return このマトリクスの成分配列
     */
    public function toArray():Array
    {
        return _matrix.concat();
    }
    
    /**
     * このマトリクスのストリング表現を返します。
     * 
     * @return このマトリクスのストリング表現
     */
    public function toString():String
    {
        var temp:String = "Matrix3D [\n";
        
        for (var i:int = 0, l:int = _matrix.length; i < l; ++i)
        {
            if (i % 4 == 0)
            {
                if (i != 0)
                {
                    temp += "\n";
                }
                temp += "\t";
            }
            if (i != l - 1)
            {
                temp += _matrix[i] + ", ";
            }
            else
            {
                temp += _matrix[i] + "\n]";
            }
        }
        
        return temp;
    }
}

class Vector3D
{
    private var _x:Number;
    private var _y:Number;
    private var _z:Number;
    
    /**
    * 新しい Vector3D インスタンスを作成します。
    */
    public function Vector3D(x:Number = 0, y:Number = 0, z:Number = 0):void
    {
        _x = x;
        _y = y;
        _z = z;
    }
    
    public function get x():Number
    {
        return _x;
    }
    
    public function set x(value:Number):void
    {
        _x = value;
    }
    
    public function get y():Number
    {
        return _y;
    }
    
    public function set y(value:Number):void
    {
        _y = value;
    }
    
    public function get z():Number
    {
        return _z;
    }
    
    public function set z(value:Number):void
    {
        _z = value;
    }
    
    /**
     * このベクトルを正規化して単位ベクトルにします。
     * 単位ベクトルとは大きさが 1 のベクトルのことです。
     */
    public function normalize():void
    {
        var l:Number = 1 / Math.sqrt(x * x + y * y + z * z);
        
        x *= l;
        y *= l;
        z *= l;
    }
    
    /**
     * 指定された 2 つのベクトルの外積をこのベクトルに設定します。
     * 外積は主に、面に垂直な法線ベクトルを求めるために使用します。
     * 
     * @param v1 1 つ目のベクトル
     * @param v1 2 つ目のベクトル
     */
    public function cross(v1:Vector3D, v2:Vector3D):void
    {
        x = v1.y * v2.z - v1.z * v2.y;
        y = v1.z * v2.x - v1.x * v2.z;
        z = v1.x * v2.y - v1.y * v2.x;
    }
    
    /**
     * このベクトルと指定ベクトルの内積を返します。
     * 内積を取るとベクトルとベクトルのなす角度が求まります。
     * 
     * @param v ベクトル
     * @return このベクトルと指定ベクトルの内積
     */
    public function dot(v:Vector3D):Number
    {
        return x * v.x + y * v.y + z * v.z;
    }
    
    /**
     * 指定された 2 つのベクトルの内積を返します。
     * 内積を取るとベクトルとベクトルのなす角度が求まります。
     * 
     * @param v1 1 つ目のベクトル
     * @param v1 2 つ目のベクトル
     * @return 指定された 2 つのベクトルの内積
     */
    public static function dot(v1:Vector3D, v2:Vector3D):Number
    {
        return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z;
    }
    
    /**
     * ベクトルを反転します。
     */
    public function invert():void
    {
        x *= -1;
        y *= -1;
        z *= -1;
    }
    
    /**
     * このベクトルと指定ベクトルとの差分を表すベクトルを返します。
     * 
     * @return 差分を表すベクトル
     */
    public function distance(v:Vector3D):Vector3D
    {
        return new Vector3D(x - v.x, y - v.y, z - v.z);
    }
    
    /**
     * このベクトルのコピーを返します。
     * 
     * @return このベクトルのコピー
     */
    public function clone():Vector3D
    {
        return new Vector3D(x, y, z);
    }
    
    /**
     * このベクトルのストリング表現を返します。
     * 
     * @return このベクトルのストリング表現
     */
    public function toString():String
    {
        return "Vector3D {x:" + x + ", y:" + y + ", z:" + z + "}";
    }
}