開発環境 Apache Flex SDK 4.12.1
FlashDevelop 4.6.2.5
実行環境 Microsoft Windows 8.1 (64bit)
プロジェクトの種類 ActionScript 3/AS3 Project
プロジェクト名 PolygonRail2


Main.as
package 
{
    import com.adobe.utils.*;
    import flash.display.*;
    import flash.display3D.*;
    import flash.events.*;
    import flash.geom.*;
    import flash.text.*;
    import flash.ui.*;
    import mx.utils.*;
 
    public class Main extends Sprite 
    {
        // 3D
        private var context3D:Context3D;
        private var vertexBuffer:VertexBuffer3D;
        private var indexBuffer:IndexBuffer3D;
 
        // model
        private var vertexData:Vector.<Number> = new Vector.<Number>;
        private var indexData:Vector.<uint> = new Vector.<uint>;
        private var numVertices:int = 0;
 
        // matrix
        private var view:Matrix3D;
        private var projection:PerspectiveMatrix3D;
 
        // input
        private var keyDown:Array = new Array;
        private var mode:int = 1;
 
        // camera
        private var camPos:Vector3D = new Vector3D(0, 20, -200);
        private var camPitch:int = 0;
        private var camYaw:int = 0;
 
        // display
        private var tfParams:TextField;
 
        // course
        private var courseDist:Number = 0;
        private var courseTriangles:int;
        //private var course:Array = [
            //{gen:genStraight, x0:-100, z0:100, x1:100, z1:100},
            //{gen:genCorner, x0:100, z0:0, r0:100, deg0:0, deg1:180, slice:32},
            //{gen:genStraight, x0:100, z0:-100, x1:-100, z1:-100},
            //{gen:genCorner, x0:-100, z0:0, r0:100, deg0:180, deg1:360, slice:32},
        //];
        private const s45:Number = Math.sin(radians(45)) * 100;
        private var course:Array = [
            {gen:genStraight, x0:-s45, z0:-s45, x1:s45, z1:s45},
            {gen:genCorner, x0:s45 * 2, z0:0, r0:100, deg0:315, deg1:225 + 360, slice:6 * 8},
            {gen:genStraight, x0:s45, z0:-s45, x1:-s45, z1:s45},
            {gen:genCorner, x0:s45 * -2, z0:0, r0:100, deg0:45 + 360, deg1:135, slice:6 * 8},
        ];
 
        // train
        private var trainIndex:int;
        private var trainTriangles:int;
        private var trainDist:Number = 0;
 
        public function Main():void 
        {
            if (stage) init();
            else addEventListener(Event.ADDED_TO_STAGE, init);
        }
 
        private function init(e:Event = null):void 
        {
            removeEventListener(Event.ADDED_TO_STAGE, init);
            // entry point
 
            stage.stage3Ds[0].addEventListener(Event.CONTEXT3D_CREATE, onContext3DCreate);
            stage.stage3Ds[0].requestContext3D();
 
            stage.addEventListener(KeyboardEvent.KEY_DOWN, function(e:KeyboardEvent):void {
                keyDown[e.keyCode] = true;
            });
            stage.addEventListener(KeyboardEvent.KEY_UP, function(e:KeyboardEvent):void {
                keyDown[e.keyCode] = false;
            });
            stage.addEventListener(Event.DEACTIVATE, function(e:Event):void {
                keyDown = new Array;
            });
 
            tfParams = addTextField(0, 0);
        }
 
        private function addTextField(x:Number, y:Number):TextField
        {
            var tf:TextField = new TextField;
            tf.defaultTextFormat = new TextFormat(null, 20, 0xffffff);
            tf.autoSize = TextFieldAutoSize.LEFT;
            tf.x = x;
            tf.y = y;
            addChild(tf);
            return tf;
        }
 
        private function onContext3DCreate(e:Event):void 
        {
            context3D = (e.target as Stage3D).context3D;
            context3D.configureBackBuffer(stage.stageWidth, stage.stageHeight, 2);
            context3D.setCulling(Context3DTriangleFace.BACK);
 
            // course
            for each (var c:Object in course) 
            {
                c.gen(c);
                courseDist += c.dist;
            }
            courseTriangles = indexData.length / 3;
 
            // train
            trainIndex = indexData.length;
            generateTrain();
            trainTriangles = indexData.length / 3 - courseTriangles;
 
            vertexBuffer = context3D.createVertexBuffer(numVertices, 6);
            vertexBuffer.uploadFromVector(vertexData, 0, numVertices);
 
            indexBuffer = context3D.createIndexBuffer(indexData.length);
            indexBuffer.uploadFromVector(indexData, 0, indexData.length);
 
            // program
            var vertexProgram:AGALMiniAssembler = new AGALMiniAssembler;
            vertexProgram.assemble(Context3DProgramType.VERTEX,
                "m44 op va0 vc0\n" +
                "mov v0 va1"
            );
            var fragmentProgram:AGALMiniAssembler = new AGALMiniAssembler;
            fragmentProgram.assemble(Context3DProgramType.FRAGMENT,
                "mov oc v0"
            );
            var program:Program3D = context3D.createProgram();
            program.upload(vertexProgram.agalcode, fragmentProgram.agalcode);
 
            //
            context3D.setVertexBufferAt(0, vertexBuffer, 0, Context3DVertexBufferFormat.FLOAT_3);
            context3D.setVertexBufferAt(1, vertexBuffer, 3, Context3DVertexBufferFormat.FLOAT_3);
            context3D.setProgram(program);
 
            //
            projection = new PerspectiveMatrix3D;
            projection.perspectiveFieldOfViewLH(
                radians(45), stage.stageWidth / stage.stageHeight, 1, 500);
 
            stage.addEventListener(Event.ENTER_FRAME, render);
        }
 
        // 直線の生成
        private function genStraight(c:Object):void 
        {
            const vertices:Vector.<int> = Vector.<int>([
            //  x, y, z, x=start/end z=right/left
                0, 0, 0,
                0, 1, 0,
                0, 1, 1,
                0, 0, 1,
                1, 0, 0,
                1, 1, 0,
                1, 1, 1,
                1, 0, 1,
            ]);
            const indices:Vector.<uint> = Vector.<uint>([
                0, 1, 5, 5, 4, 0, // -z
                1, 2, 6, 6, 5, 1, // +y
                2, 3, 7, 7, 6, 2, // +z
                3, 0, 4, 4, 7, 3, // -y
            ]);
 
            c.x = c.x1 - c.x0;
            c.z = c.z1 - c.z0;
            var rad:Number = Math.atan2(c.x, c.z);
            c.dist = Math.sqrt(c.x * c.x + c.z * c.z);
            c.calc = calcStraight;
 
            for (var i:int = 0; i < 8; i++) 
            {
                var base:int = i * 3;
                var y:int = vertices[base + 1];
                var d:Number = c.dist * vertices[base];
                var w:Number = (vertices[base + 2] - 0.5) * (8 - y * 3);
                var color:Number = (1 + y) / 2;
 
                vertexData.push(c.x0 + Math.sin(rad) * d + Math.sin(rad - Math.PI / 2) * w);
                vertexData.push(y);
                vertexData.push(c.z0 + Math.cos(rad) * d + Math.cos(rad - Math.PI / 2) * w);
                vertexData.push(color, color, color);
            }
            for (i = 0; i < indices.length; i++) 
            {
                indexData.push(numVertices + indices[i]);
            }
            numVertices += 8;
        }
 
        private function calcStraight(c:Object, dist:Number):Vector3D
        {
            var d:Number = dist / c.dist;
            return new Vector3D(c.x0 + c.x * d, 1, c.z0 + c.z * d);
        }
 
        // 曲線の生成
        private function genCorner(c:Object):void 
        {
            const vertices:Vector.<int> = Vector.<int>([
            //  x, y, z, x=start/end z=in/out
                0, 0, 0,
                0, 1, 0,
                0, 1, 1,
                0, 0, 1,
                1, 0, 0,
                1, 1, 0,
                1, 1, 1,
                1, 0, 1,
            ]);
            const indices:Vector.<uint> = Vector.<uint>([
                0, 1, 5, 5, 4, 0, // in
                1, 2, 6, 6, 5, 1, // +y
                2, 3, 7, 7, 6, 2, // out
                3, 0, 4, 4, 7, 3, // -y
            ]);
 
            c.deg = c.deg1 - c.deg0;
            var sign:int = (c.deg < 0) ? -1 : 1;
            c.dist = radians(c.deg) * c.r0 * sign;
            c.calc = calcCorner;
 
            for (var s:int = 0; s < c.slice; s++) 
            {
                for (var i:int = 0; i < 8; i++) 
                {
                    var base:int = i * 3;
                    var y:int = vertices[base + 1];
                    var rad:Number = radians(c.deg0 + c.deg * (s + vertices[base]) / c.slice);
                    var r:Number = c.r0 + (vertices[base + 2] - 0.5) * (8 - y * 3) * sign;
                    var color:Number = (1 + y) / 2;
 
                    vertexData.push(c.x0 + Math.sin(rad) * r);
                    vertexData.push(y);
                    vertexData.push(c.z0 + Math.cos(rad) * r);
                    vertexData.push(color, color, color);
                }
                for (i = 0; i < indices.length; i++) 
                {
                    indexData.push(numVertices + indices[i]);
                }
                numVertices += 8;
            }
        }
 
        private function calcCorner(c:Object, dist:Number):Vector3D
        {
            var rad:Number = radians(c.deg0 + c.deg * dist / c.dist);
            return new Vector3D(c.x0 + Math.sin(rad) * c.r0, 1, c.z0 + Math.cos(rad) * c.r0);
        }
 
        // 車両の生成
        private function generateTrain():void 
        {
            const vertices:Vector.<Number> = Vector.<Number>([
            //  x, y, z, r, g, b,
                1, 0, 0, 1, 0.5, 0.5,
                1, 1, 0, 1, 1, 1,
                0, 1, 0, 1, 1, 1,
                0, 0, 0, 1, 0.5, 0.5,
                1, 0, -1, 0.5, 0.5, 0.5,
                1, 1, -1, 1, 1, 1,
                0, 1, -1, 1, 1, 1,
                0, 0, -1, 0.5, 0.5, 0.5,
            ]);
            const indices:Vector.<uint> = Vector.<uint>([
                0, 4, 5, 5, 1, 0, // +x
                1, 5, 6, 6, 2, 1, // +y
                2, 6, 7, 7, 3, 2, // -x
                3, 7, 4, 4, 0, 3, // -y
                0, 1, 2, 2, 3, 0, // +z
                4, 7, 6, 6, 5, 4, // -z
            ]);
 
            for (var i:int = 0; i < 8; i++) 
            {
                var base:int = i * 6;
                vertexData.push((vertices[base + 0] - 0.5) * 3);
                vertexData.push(vertices[base + 1] * 3 + 1);
                vertexData.push(vertices[base + 2] * 20);
                vertexData.push(vertices[base + 3]);
                vertexData.push(vertices[base + 4]);
                vertexData.push(vertices[base + 5]);
            }
            for (i = 0; i < indices.length; i++) 
            {
                indexData.push(numVertices + indices[i]);
            }
            numVertices += 8;
        }
 
        private function render(e:Event):void 
        {
            updateView();
            updateTrain();
            displayParams();
 
            context3D.clear(0.39, 0.58, 0.93);
 
            // course
            var m:Matrix3D = new Matrix3D;
            m.append(view);
            m.append(projection);
            context3D.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 0, m , true);
            context3D.drawTriangles(indexBuffer, 0, courseTriangles);
 
            // train
            for (var i:int = 0; i < 3; i++) 
            {
                m.identity();
                m.append(calcWorld(i));
                m.append(view);
                m.append(projection);
                context3D.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 0, m , true);
                context3D.drawTriangles(indexBuffer, trainIndex, trainTriangles);
            }
 
            context3D.present();
        }
 
        private function updateView():void 
        {
            var front:int = 0;
            var cross:int = 0;
            var up:int = 0;
 
            if (keyDown[Keyboard.W]) { camPitch += 1; if (89 < camPitch) camPitch = 89; }
            if (keyDown[Keyboard.S]) { camPitch -= 1; if (camPitch < -89) camPitch = -89; }
            if (keyDown[Keyboard.A]) { camYaw -= 1; if (camYaw < 0) camYaw += 360; }
            if (keyDown[Keyboard.D]) { camYaw += 1; if (360 <= camYaw) camYaw -= 360; }
            if (keyDown[Keyboard.UP]) front += 1;
            if (keyDown[Keyboard.DOWN]) front += -1;
            if (keyDown[Keyboard.LEFT]) cross += 1;
            if (keyDown[Keyboard.RIGHT]) cross += -1;
            if (keyDown[Keyboard.PAGE_UP]) up += 1;
            if (keyDown[Keyboard.PAGE_DOWN]) up += -1;
            if (keyDown[Keyboard.NUMPAD_1]) mode = 1;
            if (keyDown[Keyboard.NUMPAD_2]) mode = 2;
 
            switch (mode) 
            {
            case 1:
                var pitch:Number = radians(camPitch);
                var y:Number = Math.sin(pitch);
                var r:Number = Math.cos(pitch);
                var yaw:Number = radians(camYaw);
                var z:Number = Math.cos(yaw) * r;
                var x:Number = Math.sin(yaw) * r;
 
                var camFront:Vector3D = new Vector3D(x, y, z);
                updatePos(camFront, front);
                var camCross:Vector3D = camFront.crossProduct(Vector3D.Y_AXIS);
                updatePos(camCross, cross);
                var camUp:Vector3D = camCross.crossProduct(camFront);
                updatePos(camUp, up);
 
                view = lookAt(camPos, camPos.add(camFront), Vector3D.Y_AXIS);
                break;
 
            case 2:
                var v0:Vector3D = calcPos(trainDist);
                var v1:Vector3D = calcPos(trainDist - 20);
                v0.y = v1.y = 4;
                view = lookAt(v1, v0, Vector3D.Y_AXIS);
                break;
            }
        }
 
        private function updatePos(vector:Vector3D, scale:int):void 
        {
            vector.normalize();
            if (scale == 0) return;
 
            var v:Vector3D = vector.clone();
            v.scaleBy(scale * 0.5);
            camPos = camPos.add(v);
        }
 
        private function updateTrain():void 
        {
            trainDist += 0.5; // 0.5 = 54km/h
            if (courseDist <= trainDist) trainDist -= courseDist;
        }
 
        private function calcWorld(i:int):Matrix3D
        {
            var dist:Number = trainDist - 21 * i;
            var v0:Vector3D = calcPos(dist);
            var v1:Vector3D = calcPos(dist - 20);
            var rad:Number = Math.atan2(v0.x - v1.x, v0.z - v1.z);
 
            var world:Matrix3D = new Matrix3D;
            world.appendRotation(degrees(rad), Vector3D.Y_AXIS);
            world.appendTranslation(v0.x, v0.y, v0.z);
            return world;
        }
 
        private function calcPos(dist:Number):Vector3D
        {
            if (dist < 0) dist += courseDist;
 
            var c:Object;
            for each (c in course) 
            {
                if (dist <= c.dist)
                {
                    return c.calc(c, dist);
                }
                dist -= c.dist;
            }
 
            // 誤差リカバリ
            c = course[0];
            return c.calc(c, 0);
        }
 
        private function displayParams():void 
        {
            tfParams.text = StringUtil.substitute(
                "Pos:{0} {1} {2}\nPitch:{3} Yaw:{4}\nDist:{5}",
                camPos.x.toFixed(0), camPos.y.toFixed(0), camPos.z.toFixed(0),
                camPitch, camYaw, trainDist.toFixed(0));
        }
 
        private function lookAt(
            cameraPosition:Vector3D, cameraTarget:Vector3D, cameraUpVector:Vector3D):Matrix3D
        {
            var zaxis:Vector3D = cameraTarget.subtract(cameraPosition);
            zaxis.normalize();
            var xaxis:Vector3D = cameraUpVector.crossProduct(zaxis);
            xaxis.normalize();
            var yaxis:Vector3D = zaxis.crossProduct(xaxis);
 
            return new Matrix3D(Vector.<Number>([
                xaxis.x, yaxis.x, zaxis.x, 0,
                xaxis.y, yaxis.y, zaxis.y, 0,
                xaxis.z, yaxis.z, zaxis.z, 0,
                -xaxis.dotProduct(cameraPosition), -yaxis.dotProduct(cameraPosition),
                -zaxis.dotProduct(cameraPosition), 1
            ]));
        }
 
        private function radians(x:Number):Number
        {
            return x * Math.PI / 180;
        }
 
        private function degrees(x:Number):Number
        {
            return x * 180 / Math.PI;
        }
 
    }
 
}
 
最終更新:2014年08月05日 21:11