/**
* Copyright Nicolas ( http://wonderfl.net/user/Nicolas )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/rHtL
*/
package {
import flash.display.Sprite;
import flash.events.Event;
//本当はBasicViewを継承させる
public class Main extends Sprite
{
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);
//継承できなかったので仕方なく…/////////////////////////////////
var b:BasicView = addChild( new BasicView() ) as BasicView;
var camera:Camera3D = b.camera;
var viewport:Viewport3D = b.viewport;
var scene:Scene3D = b.scene;
var startRendering:Function = b.startRendering;
////////////////////////////////////////////////////////////
camera.x = 0;
camera.y = 100;
camera.z = -1000;
var obj:DisplayObjectContainer3D = new DisplayObjectContainer3D();
var g:Graphics3D = obj.graphics;
g.beginFill(0xCCCCCC);
g.lineStyle(3, 0x000000);
g.moveTo(100, 30, 100);
g.lineTo(100, 30, -100);
g.lineTo(-100, 30, -100);
g.lineTo( -100, 30, 100);
g.lineTo(100, 30, 100);
g.endFill();
var child:DisplayObject3D = new DisplayObject3D();
g = child.graphics;
g.beginFill(0x0000CC);
g.lineStyle(3, 0x000000);
g.moveTo(100, 0, 100);
g.lineTo(100, 0, -100);
g.lineTo(-100, 0, -100);
g.lineTo( -100, 0, 100);
g.lineTo(100, 0, 100);
g.endFill();
obj.addChild(child);
child.x = 200;
child.rotationX = 90;
scene.addChild(obj);
startRendering();
addEventListener(Event.ENTER_FRAME, function(e:Event):void {
child.rotationX++;
var theta:Number = mouseY / 100;
var phi:Number = mouseX / 100;
if (theta <= 0) theta = Math.PI * 0.01;
if (theta > Math.PI) theta = Math.PI * 0.99;
camera.x = -1000 * Math.sin(theta) * Math.cos(phi);
camera.y = -1000 * Math.cos(theta);
camera.z = 1000 * Math.sin(theta) * Math.sin(phi);
});
}
}
}
import flash.display.Sprite;
import flash.display.Graphics;
import flash.events.Event;
import flash.geom.Matrix3D;
import flash.geom.Vector3D;
import flash.geom.Utils3D;
class BasicView extends Sprite
{
public var camera:Camera3D = new Camera3D();
public var renderer:Renderer = new Renderer();
public var scene:Scene3D = new Scene3D();
public var viewport:Viewport3D = new Viewport3D();
public function BasicView()
{
addChild(viewport);
}
public function startRendering():void
{
addEventListener(Event.ENTER_FRAME, singleRender);
}
public function stopRendering():void
{
removeEventListener(Event.ENTER_FRAME, singleRender);
}
public function singleRender(e:Event = null):void
{
renderer.renderScene(scene, camera, viewport);
}
}
class Scene3D implements IContainer
{
private var children:Vector.<IDisplayObject3D> = new Vector.<IDisplayObject3D>();
public var vertices:Vector.<Number> = new Vector.<Number>();
public var commands:Vector.<IGraphicsCommand> = new Vector.<IGraphicsCommand>();
public function Scene3D()
{
}
public function addChild(child:DisplayObject3D):DisplayObject3D
{
child._parent = this;
children.push(child as IDisplayObject3D);
return child;
}
public function removeChild(child:DisplayObject3D):DisplayObject3D
{
child._parent = null;
var index:int = children.indexOf(child as IDisplayObject3D);
if (index != -1)
{
return children.splice(index, 1)[0] as DisplayObject3D;
}
else
{
throw ArgumentError("引数のIDisplayObject3Dは、このIContainerオブジェクトの子ではありません。");
return null;
}
}
public function getChildren():Vector.<IDisplayObject3D>
{
return children.concat();
}
public function get numChildren():int
{
return children.length;
}
}
class Camera3D
{
public var pos:Vector3D;
public var up:Vector3D;
public var at:Vector3D;
public var fov:Number = 60;
public var near:Number = 100;
public var far:Number = 500;
public function get x():Number { return pos.x }
public function set x(value:Number):void { pos.x = value }
public function get y():Number { return pos.y }
public function set y(value:Number):void { pos.y = value }
public function get z():Number { return pos.z }
public function set z(value:Number):void { pos.z = value }
public function Camera3D()
{
pos = new Vector3D(0, 0, -1000);
at = new Vector3D(0, 0, 0);
up = new Vector3D(0, 1, 0);
}
}
class Viewport3D extends Sprite
{
public function get viewportWidth():Number { return _viewportWidth; }
public function set viewportWidth(value:Number):void
{
_viewportWidth = value;
setViewport(_viewportWidth, _viewportHeight);
}
private var _viewportWidth:Number;
public function get viewportHeight():Number { return _viewportHeight; }
public function set viewportHeight(value:Number):void
{
_viewportHeight = value;
setViewport(_viewportWidth, _viewportHeight);
}
private var _viewportHeight:Number;
public function Viewport3D(w:Number = 465, h:Number = 465)
{
_viewportWidth = w;
_viewportHeight = h;
setViewport(w, h);
}
private function setViewport(w:Number, h:Number):void
{
x = w / 2;
y = h / 2;
}
}
class Renderer
{
private var matrix:Matrix3D = new Matrix3D();
private var unitMatrixRawData:Vector.<Number> = Vector.<Number>([
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
]);
private var viewMatrix:Matrix3D;
private var projectionMatrix:Matrix3D;
public function Renderer()
{
}
public function renderScene(scene:Scene3D, camera:Camera3D, viewport:Viewport3D):void
{
viewport.graphics.clear();
//ビュー変換行列
viewMatrix = getViewMatrix(camera);
//プロジェクション変換行列
projectionMatrix = getProjectionMatrix(camera, viewport);
//ワールド変換行列以外の行列の統合
matrix.rawData = unitMatrixRawData;
matrix.append(viewMatrix);
matrix.append(projectionMatrix);
matrix.appendScale(viewport.viewportWidth, viewport.viewportHeight, 1);
//子を探して、最終的な変換行列を計算して、変換後のz座標を計算して、配列に入れる。
//せっかく変換行列を計算したので、射影変換もここでやっとく。
//子がコンテナだったらその子を探す、というように、この関数を再帰的に繰り返す。
var children:Vector.<IDisplayObject3D>;
var mat:Matrix3D;
var objects:Array = [];
searchAndSetDepth(scene);
function searchAndSetDepth(container:IContainer):void
{
children = container.getChildren(); //子の配列を取得
for each (var child:IDisplayObject3D in children)
{
mat = matrix.clone();
var obj:DisplayObject3D = child as DisplayObject3D;
mat.prepend(obj.getWorldMatrix()); //ワールド変換行列を統合
obj.depth = mat.transformVector(new Vector3D(obj.x, obj.y, obj.z)).z; //変換後のz座標を計算
obj.graphics.project(mat); //射影変換
objects.push(obj) //配列に入れる
if (obj is IContainer) searchAndSetDepth(obj as IContainer);
}
}
//Zソート
objects.sortOn("depth", Array.NUMERIC | Array.DESCENDING);
//描画
for each (var o:DisplayObject3D in objects)
{
o.graphics.render(viewport);
}
}
private function getViewMatrix(camera:Camera3D):Matrix3D
{
var xAxis:Vector3D;
var yAxis:Vector3D;
var zAxis:Vector3D;
var eye:Vector3D = camera.pos;
var at:Vector3D = camera.at;
var up:Vector3D = camera.up;
//「カメラの座標→ターゲット」の向きの基本ベクトルがz軸
//zAxis = at.subtract(eye);
zAxis = new Vector3D(at.x - eye.x, at.y - eye.y, at.z - eye.z)
zAxis.normalize();
//「上方向の基準ベクトルとz軸を含む平面」に垂直な向きの基本ベクトルがx軸
xAxis = up.crossProduct(zAxis);
xAxis.normalize();
//x軸とz軸に垂直な向きの基本ベクトルがy軸。(基本ベクトル同士の外積なので長さは1⇒正規化は必要ない)
yAxis = zAxis.crossProduct(xAxis);
yAxis.negate();
var mat:Matrix3D = new Matrix3D();
mat.rawData = 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(eye), -yAxis.dotProduct(eye), -zAxis.dotProduct(eye), 1
])
return mat;
}
private function getProjectionMatrix(camera:Camera3D, viewport:Viewport3D):Matrix3D
{
var mat:Matrix3D = new Matrix3D();
var aspectRatio:Number = viewport.viewportWidth / viewport.viewportHeight;
var sy:Number = 1 / Math.tan(camera.fov * 0.5 * Math.PI / 180);
var sx:Number = sy / aspectRatio;
var sz:Number = camera.far / (camera.far - camera.near);
mat.rawData = Vector.<Number>([
sx, 0, 0, 0,
0, sy, 0, 0,
0, 0, sz, 1,
0, 0, -sz * camera.near, 0
]);
return mat;
}
}
class DisplayObject3D implements IDisplayObject3D
{
public var x:Number = 0;
public var y:Number = 0;
public var z:Number = 0;
public var rotationX:Number = 0;
public var rotationY:Number = 0;
public var rotationZ:Number = 0;
public var scaleX:Number = 1;
public var scaleY:Number = 1;
public var scaleZ:Number = 1;
public var graphics:Graphics3D;
public var depth:Number = 0;
internal var _parent:IContainer;
public function get parent():IContainer { return _parent; }
private var unitMatrixRawData:Vector.<Number> = Vector.<Number>([
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
])
private var matrix:Matrix3D;
public function DisplayObject3D()
{
graphics = new Graphics3D();
matrix = new Matrix3D();
}
public function getWorldMatrix():Matrix3D
{
matrix.rawData = unitMatrixRawData;
matrix.appendScale(scaleX, scaleY, scaleZ);
if (rotationX != 0) matrix.appendRotation(-rotationX, Vector3D.X_AXIS);
if (rotationY != 0) matrix.appendRotation(-rotationY, Vector3D.Y_AXIS);
if (rotationZ != 0) matrix.appendRotation(rotationZ, Vector3D.Z_AXIS);
matrix.appendTranslation(x, y, z);
if (_parent is DisplayObjectContainer3D) matrix.append((_parent as DisplayObjectContainer3D).matrix);
return matrix;
}
}
class DisplayObjectContainer3D extends DisplayObject3D implements IContainer
{
private var children:Vector.<IDisplayObject3D> = new Vector.<IDisplayObject3D>();
public function DisplayObjectContainer3D()
{
}
public function addChild(child:DisplayObject3D):DisplayObject3D
{
children.push(child as IDisplayObject3D);
child._parent = this;
return child;
}
public function removeChild(child:DisplayObject3D):DisplayObject3D
{
child._parent = null;
var index:int = children.indexOf(child as IDisplayObject3D);
if (index != -1)
{
return children.splice(index, 1)[0] as DisplayObject3D;
}
else
{
throw ArgumentError("引数のIDisplayObject3Dは、このIContainerオブジェクトの子ではありません。");
return null;
}
}
public function getChildren():Vector.<IDisplayObject3D>
{
return children.concat();
}
public function get numChildren():int
{
return children.length;
}
}
class Graphics3D
{
public var commands:Vector.<IGraphicsCommand> = new Vector.<IGraphicsCommand>();
public var path3D:Vector.<Number> = new Vector.<Number>();
public var path2D:Vector.<Number> = new Vector.<Number>();
private var path2DIndex:uint = 0;
private var uvts:Vector.<Number> = new Vector.<Number>();
public function Graphics3D()
{
}
public function lineStyle(thickness:Number, color:uint, alpha:Number = 1):void
{
commands.push(new LineStyle(thickness, color, alpha));
}
public function beginFill(color:uint, alpha:Number = 1):void
{
commands.push(new BeginFill(color, alpha));
}
public function endFill():void
{
commands.push(new EndFill());
}
public function moveTo(x:Number, y:Number, z:Number):void
{
path3D.push(x, y, z);
commands.push(new MoveTo(path2DIndex, path2DIndex + 1));//インデックスのみを各コマンドに記憶させておく。lineToなども同様
path2DIndex += 2;
}
public function lineTo(x:Number, y:Number, z:Number):void
{
path3D.push(x, y, z);
commands.push(new LineTo(path2DIndex, path2DIndex + 1));
path2DIndex += 2;
}
public function curveTo(cx:Number, cy:Number, cz:Number, ax:Number, ay:Number, az:Number):void
{
path3D.push(cx, cy, cz, ax, ay, az);
commands.push(new CurveTo(path2DIndex, path2DIndex + 1, path2DIndex + 2, path2DIndex + 3));
path2DIndex += 4;
}
public function clear():void
{
commands = Vector.<IGraphicsCommand>();
path2D = Vector.<Number>();
path3D = Vector.<Number>();
path2DIndex = 0;
}
public function project(mat:Matrix3D):void
{
Utils3D.projectVectors(mat, path3D, path2D, uvts);
}
public function render(viewport:Viewport3D):void
{
for each (var c:GraphicsCommandBase in commands)
{
c.target = viewport.graphics;
if (c is IPath) (c as IPath).getCoordinate(path2D); //インデックスから射影変換後の座標を取得
c.draw();
}
}
}
class GraphicsCommandBase implements IGraphicsCommand
{
internal var target:Graphics;
public function GraphicsCommandBase()
{
}
public function draw():void
{
}
}
class BeginFill extends GraphicsCommandBase
{
public var color:uint;
public var alpha:Number;
public function BeginFill(color:uint, alpha:Number)
{
this.color = color;
this.alpha = alpha;
}
override public function draw():void
{
target.beginFill(color, alpha);
}
}
class EndFill extends GraphicsCommandBase
{
public function EndFill()
{
}
override public function draw():void
{
target.endFill();
}
}
class LineStyle extends GraphicsCommandBase
{
public var thickness:Number;
public var color:uint;
public var alpha:Number;
public function LineStyle(thickness:Number, color:uint, alpha:Number)
{
this.thickness = thickness;
this.color = color;
this.alpha = alpha;
}
override public function draw():void
{
target.lineStyle(thickness, color, alpha);
}
}
class MoveTo extends GraphicsCommandBase implements IPath
{
public var xIndex:uint;
public var yIndex:uint;
private var x:Number;
private var y:Number;
public function MoveTo(xIndex:uint, yIndex:uint)
{
this.xIndex = xIndex;
this.yIndex = yIndex;
}
override public function draw():void
{
target.moveTo(x, y);
}
public function getCoordinate(verts:Vector.<Number>):void
{
x = verts[xIndex];
y = verts[yIndex];
}
}
class LineTo extends GraphicsCommandBase implements IPath
{
public var xIndex:uint;
public var yIndex:uint;
private var x:Number;
private var y:Number;
public function LineTo(xIndex:uint, yIndex:uint)
{
this.xIndex = xIndex;
this.yIndex = yIndex;
}
override public function draw():void
{
target.lineTo(x, y);
}
public function getCoordinate(verts:Vector.<Number>):void
{
x = verts[xIndex];
y = verts[yIndex];
}
}
class CurveTo extends GraphicsCommandBase implements IPath
{
public var controlXIndex:uint;
public var controlYIndex:uint;
public var anchorXIndex:uint;
public var anchorYIndex:uint;
private var controlX:Number;
private var controlY:Number;
private var anchorX:Number;
private var anchorY:Number;
public function CurveTo(controlXIndex:uint, controlYIndex:uint, anchorXIndex:uint, anchorYIndex:uint)
{
this.controlXIndex = controlXIndex;
this.controlYIndex = controlYIndex;
this.anchorXIndex = anchorXIndex;
this.anchorYIndex = anchorYIndex;
}
override public function draw():void
{
target.curveTo(controlX, controlY, anchorX, anchorY);
}
public function getCoordinate(verts:Vector.<Number>):void
{
controlX = verts[controlXIndex];
controlY = verts[controlYIndex];
anchorX = verts[anchorXIndex];
anchorY = verts[anchorYIndex];
}
}
interface IDisplayObject3D {} //同じVectorで扱うための飾り
interface IContainer
{
function addChild(child:DisplayObject3D):DisplayObject3D;
function removeChild(child:DisplayObject3D):DisplayObject3D;
function getChildren():Vector.<IDisplayObject3D>
function get numChildren():int;
}
interface IGraphicsCommand
{
function draw():void;
}
interface IPath
{
function getCoordinate(verts:Vector.<Number>):void;
}