Beautifl - Flash Gallery

Thumbnail : Ribbon Road(Native 3D版)
Ribbon Road(Native 3D版)
uwi 2009-12-22 MIT License

再生するにはFlash Playerが必要です。デスクトップのブラウザでご覧ください。

package {
    import flash.display.Shape;
    import flash.display.Sprite;
    import flash.display.Graphics;
    import flash.events.Event;
    import flash.text.TextField;
    import flash.geom.Matrix3D;
    import flash.geom.Vector3D;
    import flash.geom.Point;
    import org.libspark.betweenas3.*;
    import org.libspark.betweenas3.tweens.*;
    import org.libspark.betweenas3.events.*;
    import org.libspark.betweenas3.easing.*;
    import org.papervision3d.core.math.Quaternion;
    import net.hires.debug.Stats;
    import frocessing.color.ColorHSV;
        
    [SWF(backgroundColor="#000000", frameRate=60)]
    public class Test extends Sprite {
        private var _viewport : Shape;
        private var _matWorld : Matrix3D;
        private var _matView : Matrix3D;
        private var _matProj : Matrix3D;
        
        private var _tf : TextField;
        
        private var _vertices : Vector.<Number>; // 道を描く頂点の3D座標
        private var _centers : Array; // 道の中心の3D座標
        
        private const R : Number = 500; // 節点生成時の最大半径
  
        private var _nodes : Array; // Bスプラインの節点
        private var _cor : Array; // Bスプラインの係数
        private var _tween : ITween; // Tweener
        private var _tlap : Object; // t格納用
        
        private var _prevX : Vector3D; // 道路描画時の直前のX方向
        private var _prevY : Vector3D; // 道路描画時の直前のY方向(カメラの上)
        private var _prevZ : Vector3D; // 道路描画時の直前のZ方向(正面)
        
        private var _Ys : Array; // 道の各地点でのカメラの上方向
        
        private var _colors : Array;
        private var _hsv : ColorHSV;
        
        public function Test() {
            _viewport = new Shape();
            _viewport.x = stage.stageWidth / 2;
            _viewport.y = stage.stageHeight / 2;
            addChild(_viewport);
            
            _tf = new TextField();
            _tf.width = 465;
            _tf.height = 465;
            _tf.textColor = 0xffffff;
//            addChild(_tf);
            
            init();
        }
        
        private function init() : void
        {
            _tlap = {};
            _cor = [
                new Vector3D(),
                new Vector3D(),
                new Vector3D(),
                new Vector3D()
                ];
            
            _matWorld = new Matrix3D();
            _matView = new Matrix3D();
            _matProj = new Matrix3D(Vector.<Number>([
                20, 0, 0, 0,
                0, 20, 0, 0,
                0, 0, 1, 0,
                0, 0, 0, 1
                ]));
            
            _nodes = [];
            _nodes.push(new Vector3D(0, 0, 0));
            _nodes.push(new Vector3D(0, 0, 0));
            _nodes.push(new Vector3D(0, 0, 0));
            _nodes.push(new Vector3D(0, 0, R)); // 最初まっすぐ進まないとゆがむ?
            
            _vertices = new Vector.<Number>();
            
            _prevX = Vector3D.X_AXIS;
            _prevY = Vector3D.Y_AXIS;
            _prevZ = Vector3D.Z_AXIS;
            
            _centers = [new Vector3D(), new Vector3D()];
            _Ys = [Vector3D.Y_AXIS, Vector3D.Y_AXIS];
            _colors = [0xffffff, 0xffffff];
            _hsv = new ColorHSV(0, 1, 1, 1);
            
            // 先に道をいくらかつくっておく
            for(var r : uint = 0;r < 12;r++){
                for each(var k : String in ["x", "y", "z"]){
                    _cor[3][k] = (-_nodes[0][k] + 3 * _nodes[1][k] - 3 * _nodes[2][k] + _nodes[3][k]) / 6;
                    _cor[2][k] = (_nodes[0][k] - 2 * _nodes[1][k] + _nodes[2][k]) / 2;
                    _cor[1][k] = (-_nodes[0][k] + _nodes[2][k]) / 2;
                    _cor[0][k] = (_nodes[0][k] + 4 * _nodes[1][k] + _nodes[2][k]) / 6;
                }
                
                _nodes.shift();
                // 20度以下の鋭角カーブを除去
                var prevDir : Vector3D = _nodes[_nodes.length - 1].subtract(_nodes[_nodes.length - 2]);
                var node : Vector3D;
                do{
                    node = makeNode();
                }while(Vector3D.angleBetween(prevDir, node.subtract(_nodes[_nodes.length - 1])) > Math.PI * 160 / 180);
                _nodes.push(node);
                
                for(var t : Number = 0;t < 1.0;t += 0.7 / 25){
                    _tlap.t = t;
                    buildRoad(false);
                }
            }
            
            // 消える前5つぶんの箇所を現在地とするように
            _centers.splice(0, 5);
            _Ys.splice(0, 5);
            _colors.splice(0, 5);
            
            addEventListener(Event.ENTER_FRAME, onEnterFrame);
            addChild(new Stats());
            
            onComplete();
        }
        
        private function onEnterFrame(e : Event) : void
        {
            buildRoad(true);
            rotCamera();
            render();
        }
        
        // 節点を更新する
        private function onComplete() : void
        {
            // 係数を更新
            for each(var k : String in ["x", "y", "z"]){
                _cor[3][k] = (-_nodes[0][k] + 3 * _nodes[1][k] - 3 * _nodes[2][k] + _nodes[3][k]) / 6;
                _cor[2][k] = (_nodes[0][k] - 2 * _nodes[1][k] + _nodes[2][k]) / 2;
                _cor[1][k] = (-_nodes[0][k] + _nodes[2][k]) / 2;
                _cor[0][k] = (_nodes[0][k] + 4 * _nodes[1][k] + _nodes[2][k]) / 6;
            }
            
            // 節点の作成
            _nodes.shift();
            // 20度以下の鋭角カーブを除去
            var prevDir : Vector3D = _nodes[_nodes.length - 1].subtract(_nodes[_nodes.length - 2]);
            var node : Vector3D;
            do{
                node = makeNode();
            }while(Vector3D.angleBetween(prevDir, node.subtract(_nodes[_nodes.length - 1])) > Math.PI * 160 / 180);
            _nodes.push(node);
            
            // 新しいTween
            _tween = BetweenAS3.tween(_tlap, {t : 1.0}, {t : 0.0}, 0.7);
            _tween.onComplete = onComplete;
            _tween.play();
        }
        
        // 節点を作成する
        private function makeNode() : Vector3D
        {
            var theta : Number = Math.random() * Math.PI * 2;
            var phi : Number = Math.random() * Math.PI * 2;
            return new Vector3D(
                    Math.cos(theta) * Math.sin(phi) * R,
                    Math.sin(theta) * Math.sin(phi) * R,
                    Math.cos(phi) * R
                    );
        }
        
        // 道をつくる
        // @param erase 作成と同時に頂点集合を後ろから削るかどうか
        private function buildRoad(erase : Boolean = true) : void
        {
            // 新しい座標を得る
            var t : Number = _tlap.t;
            var curPos : Vector3D = new Vector3D(
                (((_cor[3].x * t + _cor[2].x) * t) + _cor[1].x) * t + _cor[0].x,
                (((_cor[3].y * t + _cor[2].y) * t) + _cor[1].y) * t + _cor[0].y,
                (((_cor[3].z * t + _cor[2].z) * t) + _cor[1].z) * t + _cor[0].z
                );
            var prevPos : Vector3D = _centers[_centers.length - 1];
            var curZ : Vector3D = curPos.subtract(prevPos);
            curZ.normalize();
            
            // 新しい座標に従ってX, Yを回転させる
            var curX : Vector3D, curY : Vector3D;
            var dot : Number = _prevZ.dotProduct(curZ);
            if(dot < 0.999999){
                var n : Vector3D = curZ.crossProduct(_prevZ);
                n.normalize();
                var angle : Number = Math.acos(dot);
                var q : Quaternion = Quaternion.createFromAxisAngle(n.x, n.y, n.z, angle);
                var qconj : Quaternion = Quaternion.conjugate(q);
                
                curX = applyQuaternion(_prevX, q, qconj);
                curY = applyQuaternion(_prevY, q, qconj);
            }else{
                curX = _prevX;
                curY = _prevY;
            }
            
            // 線の座標を格納。
            var W : Number = 10;
            _vertices.push(
                prevPos.x + _prevX.x * W,
                prevPos.y + _prevX.y * W,
                prevPos.z + _prevX.z * W,
                curPos.x + curX.x * W,
                curPos.y + curX.y * W,
                curPos.z + curX.z * W
                );
            _vertices.push(
                prevPos.x - _prevX.x * W,
                prevPos.y - _prevX.y * W,
                prevPos.z - _prevX.z * W,
                curPos.x - curX.x * W,
                curPos.y - curX.y * W,
                curPos.z - curX.z * W
                );
            _vertices.push(
                curPos.x - curX.x * W,
                curPos.y - curX.y * W,
                curPos.z - curX.z * W,
                curPos.x + curX.x * W,
                curPos.y + curX.y * W,
                curPos.z + curX.z * W
                );
            
            _prevX = curX;
            _prevY = curY;
            _prevZ = curZ;
            
            if(erase){
                _vertices.splice(0, 18);
                _colors.shift();
            }
            
            _centers.push(curPos);
            _Ys.push(_prevY.clone());
            _hsv.h += 0.6;
            _colors.push(_hsv.value);
        }
        
        // srcにクォータニオンqを適用して回転させる
        private function applyQuaternion(src : Vector3D, q : Quaternion, qc : Quaternion) : Vector3D
        {
            var qSrc : Quaternion = new Quaternion(src.x, src.y, src.z, 0);
            var qDst : Quaternion = Quaternion.multiply(qc, qSrc);
            qDst.mult(q);
            return new Vector3D(qDst.x, qDst.y, qDst.z);
        }
        
        // 道を描画する
        private function render() : void
        {
            // 変換用行列
            var m : Matrix3D = new Matrix3D();
            m.append(_matWorld);
            m.append(_matView);
            m.append(_matProj);
            
            // 頂点集合を変換
            var vout : Vector.<Number> = new Vector.<Number>(_vertices.length);
            m.transformVectors(_vertices, vout);
            
            var focalLength : Number = 10;
            var g : Graphics = _viewport.graphics;
            var mul2 : Number, mul5 : Number;
            g.clear();
            for(var i : uint = 0;i < vout.length;i += 6){
                // 線分の頂点のどちらも可視の場合
                if(vout[i+2] >= 0 && vout[i+5] >= 0){
                    g.lineStyle(2, _colors[uint(i / 18)], 0.5);
                    mul2 = focalLength / (focalLength + vout[i+2]);
                    mul5 = focalLength / (focalLength + vout[i+5]);
                    g.moveTo(vout[i] * mul2, vout[i+1] * mul2);
                    g.lineTo(vout[i+3] * mul5, vout[i+4] * mul5);
//                    g.moveTo(vout[i] / vout[i+2], vout[i+1] / vout[i+2]);
//                    g.lineTo(vout[i+3] / vout[i+5], vout[i+4] / vout[i+5]);
                    continue;
                }
                // 線分の頂点の一方が可視の場合
                if(vout[i+2] >= 0){
                    g.lineStyle(2, _colors[uint(i / 18)], 0.5);
                    mul2 = focalLength / (focalLength + vout[i+2]);
                    g.moveTo(vout[i] * mul2, vout[i+1] * mul2);
//                    g.moveTo(vout[i] / vout[i+2], vout[i+1] / vout[i+2]);
                    g.lineTo(vout[i] * 10, vout[i+1] * 10);
                    continue;
                }
                if(vout[i+5] >= 0){
                    g.lineStyle(2, _colors[uint(i / 18)], 0.5);
                    mul5 = focalLength / (focalLength + vout[i+5]);
//                    g.moveTo(vout[i+3] / vout[i+5], vout[i+4] / vout[i+5]);
                    g.moveTo(vout[i+3] * mul5, vout[i+4] * mul5);
                    g.lineTo(vout[i+3] * 10, vout[i+4] * 10);
                    continue;
                }
            }
        }
        
        // カメラをまわす
        private function rotCamera() : void
        {
            var pos : Vector3D = _centers.shift();
            var YCamera : Vector3D = _Ys.shift();
            
            // 10個分先を見る
            var curZCamera : Vector3D = _centers[10].subtract(pos);
            curZCamera.normalize();
            
            _matView = lookAt(curZCamera, YCamera);
            // ワールド座標系にはマイナスで足していく
            _matWorld.identity();
            _matWorld.appendTranslation(-pos.x, -pos.y, -pos.z);
            _matWorld.appendTranslation(-YCamera.x * 5, -YCamera.y * 5, -YCamera.z * 5);
            _matWorld.appendTranslation(-curZCamera.x * 5, -curZCamera.y * 5, -curZCamera.z * 5);
        }
        
        // Z・Y=0としたときのlookAt
        private function lookAt(Z : Vector3D, Y : Vector3D) : Matrix3D
        {
            var X : Vector3D = Z.crossProduct(Y);
            X.normalize();
            return new Matrix3D(Vector.<Number>([
                X.x, -Y.x, Z.x, 0,
                X.y, -Y.y, Z.y, 0,
                X.z, -Y.z, Z.z, 0,
                0, 0, 0, 1
                ]));
        }

        private function tr(...o : Array) : void
        {
            _tf.appendText(o + "\n");
        }
    }
}