Ribbon Road(Native 3D版)
/**
* Copyright uwi ( http://wonderfl.net/user/uwi )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/aUJI
*/
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");
}
}
}