|開発環境|Apache [[Flex]] SDK 4.12.1| ||FlashDevelop 4.6.2.5| |実行環境|Microsoft Windows 8.1 (64bit)| |プロジェクトの種類|ActionScript 3/AS3 Project| |プロジェクト名|PolygonRail3| #table_zebra(project, #fff, #eee) http://www.maroon.dti.ne.jp/lance/flash/polygonrail3.html Project/Properties Output |Flash Player|14.0| |Backgound color|#6495ED| #table_zebra(prop, #fff, #eee) Main.as #highlight(actionscript){{ package { import com.adobe.utils.*; import flash.display.*; import flash.display3D.*; import flash.events.*; import flash.external.ExternalInterface; 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>; private var indexData:Vector.<uint>; private var numVertices:int; // 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; private var courseTriangles:int; private var course:Object; // train private var trainIndex:int; private var trainTriangles:int; private var trainDist:Number; private var velocity:Number; 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(); } private function onContext3DCreate(e:Event):void { context3D = (e.target as Stage3D).context3D; context3D.configureBackBuffer(stage.stageWidth, stage.stageHeight, 2); context3D.setCulling(Context3DTriangleFace.BACK); // 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.setProgram(program); // 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; }); ExternalInterface.addCallback("generate", generate); tfParams = addTextField(0, 0); tfParams.text = "Ready"; } 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 generate(json:String):void { stage.removeEventListener(Event.ENTER_FRAME, render); vertexData = new Vector.<Number>; indexData = new Vector.<uint>; numVertices = 0; try { var data:Object = JSON.parse(json); } catch (err:Error) { tfParams.text = err.getStackTrace(); return; } // course course = data.course; courseDist = 0; for each (var c:Object in course) { if (c.hasOwnProperty("r0")) { genCorner(c); } else { genStraight(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); // context3D.setVertexBufferAt(0, vertexBuffer, 0, Context3DVertexBufferFormat.FLOAT_3); context3D.setVertexBufferAt(1, vertexBuffer, 3, Context3DVertexBufferFormat.FLOAT_3); // projection = new PerspectiveMatrix3D; projection.perspectiveFieldOfViewLH( radians(45), stage.stageWidth / stage.stageHeight, 1, 500); // trainDist = 0; velocity = 0; 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 { updateTrain(); updateView(); 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; if (keyDown[Keyboard.NUMBER_1]) velocity = 0; if (keyDown[Keyboard.NUMBER_2]) velocity = 0.5; if (keyDown[Keyboard.NUMBER_3]) velocity = 1; if (keyDown[Keyboard.NUMBER_4]) velocity = 1.5; 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 += velocity; 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}/{6} Velocity:{7}km/h", camPos.x.toFixed(0), camPos.y.toFixed(0), camPos.z.toFixed(0), camPitch, camYaw, trainDist.toFixed(0), courseDist.toFixed(0), velocity * 108); } 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; } } } }} polygonrail3.html #highlight(javascript){{ <!doctype html> <head> <title>PolygonRail3</title> <script> function generate() { var element = document.getElementById("data"); var data = element.value; window.polyrail.generate(data); } </script> </head> <body> <center> <embed src="PolygonRail3.swf" id="polyrail" width="800" height="600" wmode="direct"></embed><br> WASD:向き ←→:左右 ↑↓:前後 PageUp/PageDown:上下<br> 1-4:速度 Num1/2:モード<br> <br> <textarea id="data" cols="80" rows="10"> {"course":[ {"x0":-100, "z0":100, "x1":100, "z1":100}, {"x0":100, "z0":0, "r0":100, "deg0":0, "deg1":180, "slice":32}, {"x0":100, "z0":-100, "x1":-100, "z1":-100}, {"x0":-100, "z0":0, "r0":100, "deg0":180, "deg1":360, "slice":32} ]} </textarea><br> <br> <button onclick="generate()">generate</button> </center> Sample: <pre> {"course":[ {"x0":100, "z0":0, "r0":100, "deg0":270, "deg1":630, "slice":64}, {"x0":-100, "z0":0, "r0":100, "deg0":450, "deg1":90, "slice":64} ]} </pre> <pre> {"course":[ {"x0":-200, "z0":200, "x1":200, "z1":200}, {"x0":200, "z0":100, "r0":100, "deg0":0, "deg1":90, "slice":16}, {"x0":300, "z0":100, "x1":300, "z1":0}, {"x0":200, "z0":0, "r0":100, "deg0":90, "deg1":270, "slice":32}, {"x0":0, "z0":0, "r0":100, "deg0":90, "deg1":-90, "slice":32}, {"x0":-200, "z0":0, "r0":100, "deg0":90, "deg1":270, "slice":32}, {"x0":-300, "z0":0, "x1":-300, "z1":100}, {"x0":-200, "z0":100, "r0":100, "deg0":270, "deg1":360, "slice":16} ]} </pre> </body> }}