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;
}
}
}
<!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>