|開発環境|Apache [[Flex]] SDK 4.12.1| ||FlashDevelop 4.6.2.5| |実行環境|Microsoft Windows 8.1 (64bit)| |プロジェクトの種類|ActionScript 3/AS3 Project| |プロジェクト名|PolygonRail| #table_zebra(project, #fff, #eee) http://www.maroon.dti.ne.jp/lance/flash/polygonrail.html Main.as #highlight(actionscript){{ 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 const cornerDist:Number = 100 * Math.PI; private const courseDist:Number = 400 + cornerDist * 2; private var courseTriangles:int; // 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 generateStraight( -100, 100, 100, 100); generateStraight( -100, -100, 100, -100); generateCorner(100, 0, 100, 0, 180, 32); generateCorner( -100, 0, 100, 180, 360, 32); generateStraight( -100, 92, 100, 92); generateStraight( -100, -92, 100, -92); generateCorner(100, 0, 92, 0, 180, 32); generateCorner( -100, 0, 92, 180, 360, 32); 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 generateStraight(x0:Number, z0:Number, x1:Number, z1:Number):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 ]); var dx:Number = x1 - x0; var dz:Number = z1 - z0; var len:Number = Math.sqrt(dx * dx + dz * dz); var rad:Number = Math.atan2(dx, dz); for (var i:int = 0; i < 8; i++) { var base:int = i * 3; var y:int = vertices[base + 1]; var l:Number = len * vertices[base]; var w:Number = (vertices[base + 2] - 0.5) * (8 - y * 3); var color:Number = (1 + y) / 2; vertexData.push(x0 + Math.sin(rad) * l - Math.cos(rad) * w); vertexData.push(y); vertexData.push(z0 + Math.cos(rad) * l + Math.sin(rad) * w); vertexData.push(color, color, color); } for (i = 0; i < indices.length; i++) { indexData.push(numVertices + indices[i]); } numVertices += 8; } private function generateCorner( x0:Number, z0:Number, r0:Number, deg0:Number, deg1:Number, slice:Number):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 ]); for (var s:int = 0; s < 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(deg0 + (deg1 - deg0) * (s + vertices[base]) / slice); var r:Number = r0 + (vertices[base + 2] - 0.5) * (8 - y * 3); var color:Number = (1 + y) / 2; vertexData.push(x0 + Math.sin(rad) * r); vertexData.push(y); vertexData.push(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 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); } // train2 var world:Matrix3D = new Matrix3D; world.appendRotation(270, Vector3D.Y_AXIS); world.appendTranslation(0, 1, 92); m.identity(); m.append(world); 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; if (dist <= 200) { return new Vector3D( -100 + dist, 1, 100); } dist -= 200; var rad:Number; if (dist <= cornerDist) { rad = Math.PI * dist / cornerDist; return new Vector3D(100 + Math.sin(rad) * 100, 1, Math.cos(rad) * 100); } dist -= cornerDist; if (dist <= 200) { return new Vector3D(100 - dist, 1, -100); } dist -= 200; rad = Math.PI * (1 + dist / cornerDist); return new Vector3D( -100 + Math.sin(rad) * 100, 1, Math.cos(rad) * 100); } 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; } } } }}