Got an idea to batch draw as much 3D arrows as possible. It's even possible to squeeze all per-arrow constant values into a single FLOAT4 register: Arrow launch velocity (xyz) + offset (w whole) (ie. dot product of arrow launch velocity and position) + (w fractional) time fractional over MAX_ARROW_TRAVEL_TIME. Then, get the vertex shader to batch draw that those arrows, deriving the offset vertex positions (and resultant projectile orientation) from the given velocities and time of each arrow! Arrows positions can be determined either as a straight-line from launch position or include trajectory (ie. launchPosition.y + .5 * GRAVITY*t*t + velocity.y*t;) The result? You don't need to update individual arrow positions and matrix orientations per frame on the CPU. Just send the existing velocity+offset and time values to GPU and let the GPU do the rest!
This method might not necessarily be better compared to precomputing certain stuff on the CPU. After all, AGAL vertex-shader operations are done per vertex, so, even though the GPU is powerful, the total cost of calculating stuff per vertex might be more heavy compared certain simple per-frame pre-calculations on the CPU, since after all, you're iterating through all arrows anyway to update their "t" variable per frame in this example. (Actually, per-frame arrow iterations on the CPU can be avoided entirely, or at least only be done periodically, by using a global timestamp counter variable. The launch time of an arrow is set to that global timestamp value, and AGAL can determien per arrow current "t" by minusing the launch "t" arrow constant off the globalTimeStamp value . That way, per-arrow vertex constants are literally "constant", but that single globalTimeStamp vertex constant is incrementing per frame.
/**
* Copyright Glidias ( http://wonderfl.net/user/Glidias )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/qeg4
*/
package
{
import alternativa.engine3d.core.Object3D;
import alternativa.engine3d.core.Resource;
import alternativa.engine3d.materials.FillMaterial;
import alternativa.engine3d.materials.TextureMaterial;
import alternativa.engine3d.primitives.Plane;
import flash.display.Bitmap;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.ui.Keyboard;
import flash.utils.ByteArray;
import alternativa.engine3d.resources.BitmapTextureResource;
import flash.display.MovieClip;
import flash.events.KeyboardEvent;
import flash.geom.Vector3D;
import alternativa.engine3d.primitives.*;
/**
* This is just a proof of concept for a rain of "particle-arrow-projectiles" that is set for looping.
* Current code allows batching up to ~120 arrows per draw call!
* THat's a lot of arrows for a single draw call!
*
* All you need to do is launch an arrow with a start-position and
* end position accordingly. Mayabe someone can do an arrow-entity manager or something
* to manage projectile lifecycles and such.
*
* Code will be modified later to support multiple draw calls in ArrowLobMeshSet.collectDraws, so yes,
* you can...have flying thousands of arrows with little performance hit (ie. no max- 120 arrow cap)!
Of course, you can use your own custom meshes for the arrows and all..
*
* @author Glidias
*/
public class RainOfArrowsProofOfConcept extends MovieClip
{
private var _template3D:MainView3D;
private var engine:Engine;
private var ticker:FrameTickProvider;
public function RainOfArrowsProofOfConcept()
{
engine = new Engine();
Wonderfl.disable_capture();
ReflectUtil.registerComponents([ Object3D]);
addChild( _template3D = new MainView3D() );
_template3D.onViewCreate.add(onReady3D);
}
private function onReady3D():void
{
//SpawnerBundle.context3D = _template3D.stage3D.context3D;
stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
//engine.addSystem( new RenderingSystem(_template3D.scene), 1 );
var spectatorPerson:SimpleObjectController =new SimpleObjectController(
stage,
_template3D.camera,
155,
4);
engine.addSystem( spectatorPerson, 2) ;
setupCustomEnvironment();
// Setup floor and some basic parameters
var plane:Plane = new Plane(4000, 4000, 1, 1, false, false, null, new FillMaterial(0x111111));
plane.z = -1;
_template3D.scene.addChild(plane);
_template3D.camera.z = 66;
spectatorPerson.setObjectPosXYZ(0, 0, 66);
// spectatorPerson.lookAtXYZ(0, 500, 0);
uploadResources(_template3D.scene.getResources(true));
_template3D.render();
// Let's go
ticker = new FrameTickProvider(stage);
ticker.add(tick);
ticker.start();
}
// Do whatever custom environment setups here!
private function setupCustomEnvironment():void
{
var box:Box = new Box(26, 3, 3, 1, 1, 1, false, null);
arrows = new ArrowLobMeshSet(box.geometry, new FillMaterial(0xFF0000, 1), 4);
var startPosition:Vector3D = new Vector3D();
var endPosition:Vector3D = new Vector3D();
for (var i:int = 0; i < 120; i++) {
endPosition.x = -300 + Math.random() * 600;
endPosition.y = -300 + Math.random() * 600;
endPosition.z = 0;
arrows.launchNewProjectile(startPosition, endPosition );
}
_template3D.scene.addChild ( arrows);
}
private function onKeyDown(e:KeyboardEvent):void
{
var kc:uint = e.keyCode;
if (kc === Keyboard.F7) {
_scrnie=_template3D.takeScreenshot(screenieMethod2);
}
}
private function screenieMethod2():Boolean
{
stage.addEventListener(MouseEvent.CLICK, removeScreenie);
return false;
}
private var _scrnie:Bitmap;
private function removeScreenie(e:Event=null):void {
if (_scrnie == null) return;
stage.removeEventListener(MouseEvent.CLICK, removeScreenie);
_scrnie.parent.removeChild(_scrnie);
_scrnie = null;
}
public function uploadResources(vec:Vector.<Resource>):void {
var i:int = vec.length;
while (--i > -1) {
vec[i].upload(_template3D.stage3D.context3D);
}
}
private var timePassed:Number = 0;
private var arrows:ArrowLobMeshSet;
private function tick(time:Number):void
{
timePassed += time;
arrows.update(time);
engine.update(time);
_template3D.render();
if (timePassed >= arrows._maxProjectileTravelTime) {
timePassed = 0;
arrows.reset();
arrows.update(0);
}
}
}
}
//package alternativa.engine3d.utils
//{
import alternativa.engine3d.core.VertexStream;
import alternativa.engine3d.materials.Material;
import alternativa.engine3d.objects.Mesh;
import alternativa.engine3d.objects.Surface;
import alternativa.engine3d.resources.Geometry;
import flash.utils.ByteArray;
import flash.utils.Endian;
import alternativa.engine3d.alternativa3d;
use namespace alternativa3d;
/**
* ...
* @author Glenn Ko
*/
//public
class GeometryUtil
{
public static const ATTRIBUTE:uint = 20;
private static function collectAttributes(geom:Geometry, attributesDict:Vector.<int>, attributesLengths:Vector.<int>):void {
for each (var stream:VertexStream in geom._vertexStreams) {
var prev:int = -1;
var attributes:Array = stream.attributes;
for each (var attr:int in attributes) {
attributesLengths[attr]++;
if (attr == prev) continue;
attributesDict[attr]++;
prev = attr;
}
}
}
private static function appendGeometry(geometry:Geometry, geom:Geometry, constantsPerMesh:int):void {
var stream:VertexStream;
var i:int, j:int;
var length:uint = geom._vertexStreams.length;
var numVertices:int = geom._numVertices;
for (i = 0; i < length; i++) {
stream = geom._vertexStreams[i];
var attributes:Array = geometry._vertexStreams[i].attributes;
var attribtuesLength:int = attributes.length;
var destStream:VertexStream = geometry._vertexStreams[i];
var newOffset:int = destStream.data.length;
destStream.data.position = newOffset;
stream.data.position = 0;
var stride:int = stream.attributes.length*4;
var destStride:int = destStream.attributes.length*4;
for (j = 0; j < numVertices; j++) {
var prev:int = -1;
for (var k:int = 0; k < attribtuesLength; k++) {
var attr:int = attributes[k];
if (attr == ATTRIBUTE) {
destStream.data.writeFloat(0);
continue;
}
if (attr != prev) {
stream.data.position = geom._attributesOffsets[attr]*4 + stride*j;
destStream.data.position = newOffset + geometry._attributesOffsets[attr]*4 + destStride*j;
}
destStream.data.writeFloat(stream.data.readFloat());
prev = attr;
}
}
}
geometry._numVertices += geom._numVertices;
}
private static function appendGeom(geometry:Geometry, geom:Geometry, constantsPerMesh:int):void {
var vertexOffset:uint;
var i:int, j:int;
vertexOffset = geometry._numVertices;
appendGeometry(geometry, geom, constantsPerMesh);
// Copy indexes
var indexEnd:uint = geom.numTriangles*3 + 0;
for (j = 0; j < indexEnd; j++) {
geometry._indices.push(geom._indices[j] + vertexOffset);
}
}
private static function getJointGeometry(geom:Geometry, constantsPerMesh:int):Geometry {
var geometry:Geometry = new Geometry(0);
var numAttributes:int = 32;
var attributesDict:Vector.<int> = new Vector.<int>(numAttributes, true);
var attributesLengths:Vector.<int> = new Vector.<int>(numAttributes, true);
collectAttributes(geom, attributesDict, attributesLengths);
var attributes:Array = [];
var i:int;
for (i = 0; i < numAttributes; i++) {
if (attributesDict[i] > 0) {
attributesLengths[i] = attributesLengths[i]/attributesDict[i];
}
}
for (i = 0; i < numAttributes; i++) {
if (Number(attributesDict[i]) == 1) {
for (var j:int = 0; j < attributesLengths[i]; j++) {
attributes.push(i);
}
}
}
attributes.push(ATTRIBUTE);
geometry.addVertexStream(attributes);
//if (root is Mesh) appendMesh(root as Mesh);
//collectMeshes(root);
appendGeom(geometry, geom, constantsPerMesh);
return geometry;
}
// Borrowed from MeshSetClonesContainer (based off MeshSet).
/**
* Allows you to convert a single geometry instance into a single geometry cloned-chain with joint ID indices for draw-batching!
* @param geometry The geometry sample to duplicate
* @param cap The total amount to duplicate! Give a value higher than 1! Usually the predicted maximum amount of instances per draw batch!)
* @param constantsPerMesh The amount of constants being used per mesh clone, used to offset the joint indices accordingly to match the correct starting constant register index.
*/
public static function createDuplicateGeometry(geometry:Geometry, cap:int, constantsPerMesh:int):Geometry {
geometry = getJointGeometry(geometry, constantsPerMesh);
//transformProcedure = calculateTransformProcedure(cap);
//deltaTransformProcedure = calculateDeltaTransformProcedure(cap);
var bytes:ByteArray;
//throw new Error(geometry.getAttributeValues(VertexAttributes.POSITION))
// get samples
var protoJointIndices:Vector.<Number> = geometry.getAttributeValues(ATTRIBUTE);
var protoNumVertices:int = geometry.numVertices;
//_numVertices = protoNumVertices;
var protoByteArrayStreams:Vector.<ByteArray> = new Vector.<ByteArray>();
var len:int = geometry._vertexStreams.length;
// copy all geometry bytearray data samples for all vertex streams
for (var i:int = 0; i < len; i++) {
protoByteArrayStreams[i] = bytes = new ByteArray();
bytes.endian = Endian.LITTLE_ENDIAN;
for (var u:int = 0; u < cap; u++) {
bytes.writeBytes( geometry._vertexStreams[i].data );
}
}
// paste geometry data for all the vertex streams
for (i = 0; i < len; i++) {
bytes =protoByteArrayStreams[i];
for (u = 0; u < cap; u++) {
var data:ByteArray = geometry._vertexStreams[i].data;
data.position = data.length;
data.writeBytes(bytes, data.length);
}
}
// set number of vertices to match new vertex data size
geometry._numVertices = protoNumVertices * cap;
var indices:Vector.<uint> = geometry.indices;
// duplicate indices with offsets
len = indices.length;
for (i = 1; i < cap; i++) {
var indexOffset:int = i * protoNumVertices;
for (u = 0; u < len; u++) {
indices.push(indexOffset+ indices[u]);
}
}
geometry.indices = indices;
// paste joint attribute values with offsets
var jointIndices:Vector.<Number> = geometry.getAttributeValues(ATTRIBUTE);
len = protoJointIndices.length;
var duplicateMultiplier:Number = constantsPerMesh;
var totalLen:int = jointIndices.length;
for (i = len; i < totalLen; i += len) {
for (u = i; u < i+len; u++) {
jointIndices[u] += duplicateMultiplier;
}
duplicateMultiplier+= constantsPerMesh;
}
geometry.setAttributeValues(ATTRIBUTE, jointIndices);
return geometry;
}
}
//}
//package alternativa.a3d.objects
//{
import alternativa.engine3d.core.Camera3D;
import alternativa.engine3d.core.DrawUnit;
import alternativa.engine3d.core.Light3D;
import alternativa.engine3d.core.VertexAttributes;
import alternativa.engine3d.materials.compiler.Linker;
import alternativa.engine3d.materials.compiler.Procedure;
import alternativa.engine3d.materials.Material;
import alternativa.engine3d.objects.Mesh;
import alternativa.engine3d.objects.Surface;
import alternativa.engine3d.resources.Geometry;
//import alternativa.engine3d.utils.GeometryUtil;
import alternativa.engine3d.alternativa3d;
import flash.display3D.Context3DVertexBufferFormat;
import flash.geom.Vector3D;
import flash.utils.Dictionary;
use namespace alternativa3d;
/**
* Yes, ~120 arrows/projectiles per draw call! Let thy arrows blot the sun!!
* We determine arrow position and orientation, from velocity, offset and time...within GPU itself.
* @author Glenn Ko
*/
//public
class ArrowLobMeshSet extends Mesh
{
private var batchAmount:int;
public var _maxProjectileTravelTime:Number;
public var _maxProjectileTravelTimeMult:Number;
private var sampleNumTris:int;
private static var BATCH_AMOUNT:int = 120;
private var _transformProcedures:Dictionary = new Dictionary();
private var _deltaTransformProcedures:Dictionary = new Dictionary();
public var toUpload:Vector.<Number> = new Vector.<Number>();
public var total:int = 0;
private static const ATTRIBUTE:int = GeometryUtil.ATTRIBUTE;
private var constantsPerMesh:int;
private var gravity:Number = 266;
public function setGravity(val:Number):void {
gravity = val;
}
public function ArrowLobMeshSet(arrowGeometry:Geometry, material:Material, maxProjectileTravelTime:Number=10)
{
super();
this.maxProjectileTravelTime = maxProjectileTravelTime;
batchAmount = BATCH_AMOUNT;
sampleNumTris = arrowGeometry.numTriangles;
constantsPerMesh = 1;
geometry = GeometryUtil.createDuplicateGeometry(arrowGeometry, batchAmount, constantsPerMesh);
addSurface( material, 0, sampleNumTris*total );
transformProcedure = calculateTransformProcedure(batchAmount);
// deltaTransformProcedure = calculateDeltaTransformProcedure(batchAmount);
this.maxProjectileTravelTime = maxProjectileTravelTime;
boundBox = null;
}
alternativa3d override function setTransformConstants(drawUnit:DrawUnit, surface:Surface, vertexShader:Linker, camera:Camera3D):void {
drawUnit.setVertexBufferAt(vertexShader.getVariableIndex("joint"), geometry.getVertexBuffer(ATTRIBUTE), geometry._attributesOffsets[ATTRIBUTE], Context3DVertexBufferFormat.FLOAT_1);
drawUnit.setVertexConstantsFromNumbers( vertexShader.getVariableIndex("cVars"), -.5 * gravity, 0, gravity, _maxProjectileTravelTime);
drawUnit.setVertexConstantsFromNumbers( vertexShader.getVariableIndex("cUp"), 0, 0, 1, 0);
//drawUnit.setVertexConstantsFromVector(0, toUploadSpriteData, toUploadNumSprites*NUM_REGISTERS_PER_SPR );
drawUnit.setVertexConstantsFromVector( 0, toUpload, total * constantsPerMesh );
var triCount:int = total * sampleNumTris;
if (triCount != surface.numTriangles) {
surface.numTriangles = triCount;
}
}
// TODO: This should be factored out to a different manager
public function update(time:Number):void {
var dt:Number = time * _maxProjectileTravelTimeMult;
for (var i:int = 0; i < total; i++) {
var c:int = (i << 2);
var w:Number = toUpload[c + 3];
var whole:int = int(w);
var frac:Number = w - whole;
frac += dt;
frac = frac >= 1 ? 0.999999 : frac;
toUpload[c + 3] = frac + whole;
}
}
public function reset():void {
for (var i:int = 0; i < total; i++) {
var c:int = (i << 2);
var w:Number = toUpload[c + 3];
toUpload[c + 3] = int(w);
}
}
public function launchNewProjectile(startPosition:Vector3D, endPosition:Vector3D, speed:Number=144):void {
launchProjectileAtIndex(total, startPosition, endPosition, speed);
}
private var displace:Vector3D = new Vector3D();
private function launchProjectileAtIndex(index:int, startPosition:Vector3D, endPosition:Vector3D, speed:Number):void {
var c:int = (index << 2);
var vx:Number;
var vy:Number;
var vz:Number;
displace.x = endPosition.x - startPosition.x;
displace.y = endPosition.y - startPosition.y;
displace.z = endPosition.z - startPosition.z;
var totalTime:Number = displace.length / speed;
var scaler:Number = 1 / totalTime;
displace.x *= scaler;
displace.y *= scaler;
displace.z *= scaler;
displace.z += gravity * totalTime * 0.5;
displace.w = Math.round(displace.x*startPosition.x + displace.y*startPosition.y + displace.z*startPosition.z);
toUpload[c++] = displace.x;// startPosition.x;// displace.x;
toUpload[c++] = displace.y;
toUpload[c++] = displace.z;// displace.z;
toUpload[c] = displace.w;
// throw new Error(geometry.getAttributeValues(ATTRIBUTE));
total++;
getSurface(0).numTriangles = total * sampleNumTris;
}
override alternativa3d function collectDraws(camera:Camera3D, lights:Vector.<Light3D>, lightsLength:int, useShadow:Boolean):void {
for (var i:int = 0; i < _surfacesLength; i++) {
var surface:Surface = _surfaces[i];
if (surface.material != null) {
surface.material.collectDraws(camera, surface, geometry, lights, lightsLength, useShadow, -1);
}
// Mouse events
//if (listening) camera.view.addSurfaceToMouseEvents(surface, geometry, transformProcedure);
}
}
// -------------------
///*
private function calculateTransformProcedure(numMeshes:int):Procedure {
var res:Procedure = _transformProcedures[numMeshes];
if (res != null) return res;
res = _transformProcedures[numMeshes] = new Procedure(null, "ArrowLobMeshSetTransformProcedure");
res.compileFromArray(["#a0=joint", "#c1=cVars", "#c2=cUp",
// dummy declarations (can remove once done..)
//"mov t0, c1",
//"mov t1, c2",
// "mov t1, a0",
//"mov t1, i0",
// ----
"mov t0, c[a0.x]", // velocity and offset/time
"mov t1, t0",
"frc t0.w, t1.w", // save fractional time into t0.w
"sub t1.w, t1.w, t0.w", // subtract away fractional to get whole number offset into t1.w
"mul t1.xyz, t1.xyz, t1.www", // origin launch position into t1 by multiplying velocity over offset
"mul t0.w, t0.w, c1.w", // now, save actual time t of t0.w from fractional by multiplying against MAX_TIME
"mul t1.w, t0.x, t0.w, ", // save out velocity.x*t offset
"add t1.x, t1.x, t1.w", // and add it to the launch position to get actual x arrow origin position
"mul t1.w, t0.y, t0.w, ", // save out velocity.y*t offset
"add t1.y, t1.y, t1.w", // and add it to the launch position to get actual y arrow origin position
"mul t1.w, c1.x, t0.w", // save out -.5*GRAVITY*t*t + velocity.z*t gravitational offset over time
"mul t1.w, t1.w, t0.w",
"add t1.z, t1.z, t1.w", // and LHS operand done, ADD it in!!
"mul t1.w, t0.z, t0.w",
"add t1.z, t1.z, t1.w", // and RHS operand done, subtract it out to get actual z arrow origin position
///* // if considering orientation
"mul t0.w, c1.z, t0.w", // multiply gravitaitonal offset over time: GRAVITY*t
"sub t0.z, t0.z, t0.w", // subtract this from the z alt component velocity to get final unnormalized velocity vector
"nrm t0.xyz, t0.xyz", // normalize the vector to get forward vector of arrow...along X direction! (2d east)
"mov t3, t0", // no choice, t0 is used, t1 is to hold position, t2 to hold axes! need to maintain reference to foward vector to allow crs later
"mul t2.xyz, i0.xxx, t0.xyz", // extend out position along x offset of vertex
"add t1.xyz, t1.xyz, t2.xyz",
"crs t0.xyz, t0.xyz, c2.xyz", // cross product forward vector with up to get right...along Y direction (2d north)!
"mul t2.xyz, i0.yyy, t0.xyz", // extend out position along y offset of vertex
"add t1.xyz, t1.xyz, t2.xyz",
"crs t0.xyz, t0.xyz, t3.xyz", // cross product right vector with forward vector to get actual UP...along Z direction
"mul t2.xyz, i0.zzz, t0.xyz", // extend out position along z offset of vertex
"add t1.xyz, t1.xyz, t2.xyz",
"mov t1.w, c1.z", // w property of 1 needed?
// */
// if not considering orientation
//"add t1.xyz, t1.xyz, i0.xyz",
"mov t1.w, i0.w",
"mov o0, t1"]);
res.assignConstantsArray(numMeshes * constantsPerMesh);
return res;
}
//*/
/*
private function calculateDeltaTransformProcedure(numMeshes:int):Procedure {
var res:Procedure = _deltaTransformProcedures[numMeshes];
if (res != null) return res;
res = _deltaTransformProcedures[numMeshes] = new Procedure(null, "ArrowLobMeshSetDeltaTransformProcedure");
res.compileFromArray(["#a0=joint", "#c1=cVars", "sub t0, a0.x, c1.x", "m33 o0.xyz, i0, c[t0.x]", "mov o0.w, i0.w"]);
return res;
}
*/
public function get maxProjectileTravelTime():Number
{
return _maxProjectileTravelTime;
}
public function set maxProjectileTravelTime(value:Number):void
{
_maxProjectileTravelTime = value;
_maxProjectileTravelTimeMult = 1/value;
}
}
//}
/**
* Bare bones main view3d class
* @author Glenn Ko
*/
import alternativa.engine3d.core.Camera3D;
import alternativa.engine3d.core.Object3D;
import alternativa.engine3d.core.View;
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Stage;
//import systems.rendering.IRenderable;
//import ash.signals.Signal0;
import alternativa.engine3d.lights.AmbientLight;
import alternativa.engine3d.lights.DirectionalLight;
import flash.display.Sprite;
import flash.display.Stage3D;
import flash.display.StageAlign;
import flash.display.StageQuality;
import flash.display.StageScaleMode;
import flash.events.Event;
import alternativa.engine3d.alternativa3d;
use namespace alternativa3d;
class MainView3D extends Sprite {
private var _stage:Stage;
public var onViewCreate:Signal0 = new Signal0();
public var stage3D:Stage3D
public var camera:Camera3D
public var scene:Object3D
public var directionalLight:DirectionalLight;
public var ambientLight:AmbientLight;
public function MainView3D() {
addEventListener(Event.ADDED_TO_STAGE, init);
}
protected function init(e:Event = null):void
{
_stage = stage;
removeEventListener(Event.ADDED_TO_STAGE, init);
stage.scaleMode = StageScaleMode.NO_SCALE;
stage.align = StageAlign.TOP_LEFT;
stage.quality = StageQuality.HIGH;
//Stage3Dを用意
stage3D = stage.stage3Ds[0];
//Context3Dの生成、呼び出し、初期化
stage3D.addEventListener(Event.CONTEXT3D_CREATE, onContextCreate);
stage3D.requestContext3D();
}
private function onContextCreate(e:Event):void {
stage3D.removeEventListener(Event.CONTEXT3D_CREATE, onContextCreate);
//View3D(表示エリア)の作成
var view:View = new View(stage.stageWidth, stage.stageHeight);
view.antiAlias = 4
addChild(view);
//Scene(コンテナ)の作成
scene = new Object3D();
//Camera(カメラ)の作成
camera = new Camera3D(1, 100000);
camera.view = view;
scene.addChild(camera)
// ..camera.diagram
addChild(camera.diagram);
view.hideLogo();
//Lightを追加
ambientLight = new AmbientLight(0xFFFFFF);
ambientLight.intensity = 0.5;
scene.addChild(ambientLight);
//Lightを追加
directionalLight = new DirectionalLight(0xFFFFFF);
//手前右上から中央へ向けた指向性light
directionalLight.x = 0;
directionalLight.y = -100;
directionalLight.z = -100;
directionalLight.lookAt(0, 0, 0);
scene.addChild(directionalLight);
//directionalLight.visible = false;
onViewCreate.dispatch();
stage.addEventListener(Event.RESIZE, onStageResize);
}
private function onStageResize(e:Event):void
{
camera.view.width = stage.stageWidth;
camera.view.height = stage.stageHeight;
}
public function takeScreenshot( method:Function=null) : Bitmap //width:int, height:int,
{
var view:View = camera.view;
view.renderToBitmap = true;
camera.render(stage3D);
var canvas:BitmapData = view.canvas.clone();
// var bitmapData:BitmapData = view.canvas.clone();
view.renderToBitmap = false;
// view.width = oldWidth;
// view.height = oldHeight;
var child:Bitmap = new Bitmap(canvas);
stage.addChildAt( child,0 );
// take screenshot here
if (method!= null && method() ) {
if (child.parent) child.parent.removeChild(child);
}
return child;
}
/*
public function startRendering():void {
addEventListener(Event.ENTER_FRAME, onRenderTick);
}
private function onRenderTick(e:Event):void
{
render();
}
public function stopRendering():void {
removeEventListener(Event.ENTER_FRAME, onRenderTick);
}
*/
/* INTERFACE systems.rendering.IRenderable */
public function render():void
{
camera.render(stage3D);
}
public function get viewBackgroundColor():uint
{
return camera.view.backgroundColor;
}
public function set viewBackgroundColor(value:uint):void
{
camera.view.backgroundColor = value;
}
}
//}//package {
//import Engine;
//import Node;
//import NodeList;
//import System;
//}
//package {
//import ash.signals.Signal2;
import flash.utils.Dictionary;
import flash.utils.getQualifiedClassName;
/*public*/ class Entity
{
private static var nameCount : int = 0;
private var _name : String;
public var componentAdded : Signal2;
public var componentRemoved : Signal2;
public var nameChanged : Signal2;
public var previous : Entity;
public var next : Entity;
public var components : Dictionary;
public function Entity( name : String = "" )
{
componentAdded = new Signal2( Entity, Class );
componentRemoved = new Signal2( Entity, Class );
nameChanged = new Signal2( Entity, String );
components = new Dictionary();
if( name )
{
_name = name;
}
else
{
_name = "_entity" + (++nameCount);
}
}
public function get name() : String
{
return _name;
}
public function set name( value : String ) : void
{
if( _name != value )
{
var previous : String = _name;
_name = value;
nameChanged.dispatch( this, previous );
}
}
public function add( component : Object, componentClass : Class = null ) : Entity
{
if ( !componentClass )
{
componentClass = Class( component.constructor );
}
if ( components[ componentClass ] )
{
remove( componentClass );
}
components[ componentClass ] = component;
componentAdded.dispatch( this, componentClass );
return this;
}
public function remove( componentClass : Class ) : *
{
var component : * = components[ componentClass ];
if ( component )
{
delete components[ componentClass ];
componentRemoved.dispatch( this, componentClass );
return component;
}
return null;
}
public function get( componentClass : Class ) : *
{
return components[ componentClass ];
}
public function getAll() : Array
{
var componentArray : Array = new Array();
for each( var component : * in components )
{
componentArray.push( component );
}
return componentArray;
}
public function has( componentClass : Class ) : Boolean
{
return components[ componentClass ] != null;
}
}
//}
//package {
/*public*/ class System
{
public var previous : System;
public var next : System;
public var priority : int = 0;
public function addToEngine( engine : Engine ) : void
{
}
public function removeFromEngine( engine : Engine ) : void
{
}
public function update( time : Number ) : void
{
}
}
//}
//package {
/*public*/ class Node
{
public var entity : Entity;
public var previous : *;
public var next : *;
}
//}
//}
class ListenerNodePool
{
private var tail : ListenerNode;
private var cacheTail : ListenerNode;
public function get():ListenerNode
{
if( tail )
{
var node : ListenerNode = tail;
tail = tail.previous;
node.previous = null;
return node;
}
else
{
return new ListenerNode();
}
}
public function dispose( node : ListenerNode ):void
{
node.listener = null;
node.once = false;
node.next = null;
node.previous = tail;
tail = node;
}
public function cache( node : ListenerNode ) : void
{
node.listener = null;
node.previous = cacheTail;
cacheTail = node;
}
public function releaseCache() : void
{
while( cacheTail )
{
var node : ListenerNode = cacheTail;
cacheTail = node.previous;
node.next = null;
node.previous = tail;
tail = node;
}
}
}
class ListenerNode
{
public var previous : ListenerNode;
public var next : ListenerNode;
public var listener : Function;
public var once : Boolean;
}
/*public*/ class ListIteratingSystem extends System
{
protected var nodeList : NodeList;
protected var nodeClass : Class;
protected var nodeUpdateFunction : Function;
protected var nodeAddedFunction : Function;
protected var nodeRemovedFunction : Function;
public function ListIteratingSystem( nodeClass : Class, nodeUpdateFunction : Function, nodeAddedFunction : Function = null, nodeRemovedFunction : Function = null )
{
this.nodeClass = nodeClass;
this.nodeUpdateFunction = nodeUpdateFunction;
this.nodeAddedFunction = nodeAddedFunction;
this.nodeRemovedFunction = nodeRemovedFunction;
}
override public function addToEngine( engine : Engine ) : void
{
nodeList = engine.getNodeList( nodeClass );
if( nodeAddedFunction != null )
{
for( var node : Node = nodeList.head; node; node = node.next )
{
nodeAddedFunction( node );
}
nodeList.nodeAdded.add( nodeAddedFunction );
}
if( nodeRemovedFunction != null )
{
nodeList.nodeRemoved.add( nodeRemovedFunction );
}
}
override public function removeFromEngine( engine : Engine ) : void
{
if( nodeAddedFunction != null )
{
nodeList.nodeAdded.remove( nodeAddedFunction );
}
if( nodeRemovedFunction != null )
{
nodeList.nodeRemoved.remove( nodeRemovedFunction );
}
nodeList = null;
}
override public function update( time : Number ) : void
{
for( var node : Node = nodeList.head; node; node = node.next )
{
nodeUpdateFunction( node, time );
}
}
}
//}
//package {
//import ash.signals.Signal1;
/*public*/ class NodeList
{
public var head : *;
public var tail : *;
public var nodeAdded : Signal1;
public var nodeRemoved : Signal1;
public function NodeList()
{
nodeAdded = new Signal1( Node );
nodeRemoved = new Signal1( Node );
}
public function add( node : Node ) : void
{
if( ! head )
{
head = tail = node;
node.next = node.previous = null;
}
else
{
tail.next = node;
node.previous = tail;
node.next = null;
tail = node;
}
nodeAdded.dispatch( node );
}
public function remove( node : Node ) : void
{
if ( head == node)
{
head = head.next;
}
if ( tail == node)
{
tail = tail.previous;
}
if (node.previous)
{
node.previous.next = node.next;
}
if (node.next)
{
node.next.previous = node.previous;
}
nodeRemoved.dispatch( node );
}
public function removeAll() : void
{
while( head )
{
var node : Node = head;
head = node.next;
node.previous = null;
node.next = null;
nodeRemoved.dispatch( node );
}
tail = null;
}
public function get empty() : Boolean
{
return head == null;
}
public function swap( node1 : Node, node2 : Node ) : void
{
if( node1.previous == node2 )
{
node1.previous = node2.previous;
node2.previous = node1;
node2.next = node1.next;
node1.next = node2;
}
else if( node2.previous == node1 )
{
node2.previous = node1.previous;
node1.previous = node2;
node1.next = node2.next;
node2.next = node1;
}
else
{
var temp : Node = node1.previous;
node1.previous = node2.previous;
node2.previous = temp;
temp = node1.next;
node1.next = node2.next;
node2.next = temp;
}
if( head == node1 )
{
head = node2;
}
else if( head == node2 )
{
head = node1;
}
if( tail == node1 )
{
tail = node2;
}
else if( tail == node2 )
{
tail = node1;
}
if( node1.previous )
{
node1.previous.next = node1;
}
if( node2.previous )
{
node2.previous.next = node2;
}
if( node1.next )
{
node1.next.previous = node1;
}
if( node2.next )
{
node2.next.previous = node2;
}
}
public function insertionSort( sortFunction : Function ) : void
{
if( head == tail )
{
return;
}
var remains : Node = head.next;
for( var node : Node = remains; node; node = remains )
{
remains = node.next;
for( var other : Node = node.previous; other; other = other.previous )
{
if( sortFunction( node, other ) >= 0 )
{
if( node != other.next )
{
if ( tail == node)
{
tail = node.previous;
}
node.previous.next = node.next;
if (node.next)
{
node.next.previous = node.previous;
}
node.next = other.next;
node.previous = other;
node.next.previous = node;
other.next = node;
}
break;
}
}
if( !other )
{
if ( tail == node)
{
tail = node.previous;
}
node.previous.next = node.next;
if (node.next)
{
node.next.previous = node.previous;
}
node.next = head;
head.previous = node;
node.previous = null;
head = node;
}
}
}
public function mergeSort( sortFunction : Function ) : void
{
if( head == tail )
{
return;
}
var lists : Vector.<Node> = new Vector.<Node>;
var start : Node = head;
var end : Node;
while( start )
{
end = start;
while( end.next && sortFunction( end, end.next ) <= 0 )
{
end = end.next;
}
var next : Node = end.next;
start.previous = end.next = null;
lists.push( start );
start = next;
}
while( lists.length > 1 )
{
lists.push( merge( lists.shift(), lists.shift(), sortFunction ) );
}
tail = head = lists[0];
while( tail.next )
{
tail = tail.next;
}
}
private function merge( head1 : Node, head2 : Node, sortFunction : Function ) : Node
{
var node : Node;
var head : Node;
if( sortFunction( head1, head2 ) <= 0 )
{
head = node = head1;
head1 = head1.next;
}
else
{
head = node = head2;
head2 = head2.next;
}
while( head1 && head2 )
{
if( sortFunction( head1, head2 ) <= 0 )
{
node.next = head1;
head1.previous = node;
node = head1;
head1 = head1.next;
}
else
{
node.next = head2;
head2.previous = node;
node = head2;
head2 = head2.next;
}
}
if( head1 )
{
node.next = head1;
head1.previous = node;
}
else
{
node.next = head2;
head2.previous = node;
}
return head;
}
}
//}
//package {
import flash.utils.Dictionary;
/*public*/ class SignalBase
{
public var head : ListenerNode;
public var tail : ListenerNode;
private var nodes : Dictionary;
private var listenerNodePool : ListenerNodePool;
private var toAddHead : ListenerNode;
private var toAddTail : ListenerNode;
private var dispatching : Boolean;
private var _numListeners : int = 0;
public function SignalBase()
{
nodes = new Dictionary( true );
listenerNodePool = new ListenerNodePool();
}
protected function startDispatch() : void
{
dispatching = true;
}
protected function endDispatch() : void
{
dispatching = false;
if( toAddHead )
{
if( !head )
{
head = toAddHead;
tail = toAddTail;
}
else
{
tail.next = toAddHead;
toAddHead.previous = tail;
tail = toAddTail;
}
toAddHead = null;
toAddTail = null;
}
listenerNodePool.releaseCache();
}
public function get numListeners() : int
{
return _numListeners;
}
public function add( listener : Function ) : void
{
if( nodes[ listener ] )
{
return;
}
var node : ListenerNode = listenerNodePool.get();
node.listener = listener;
nodes[ listener ] = node;
addNode( node );
}
public function addOnce( listener : Function ) : void
{
if( nodes[ listener ] )
{
return;
}
var node : ListenerNode = listenerNodePool.get();
node.listener = listener;
node.once = true;
nodes[ listener ] = node;
addNode( node );
}
protected function addNode( node : ListenerNode ) : void
{
if( dispatching )
{
if( !toAddHead )
{
toAddHead = toAddTail = node;
}
else
{
toAddTail.next = node;
node.previous = toAddTail;
toAddTail = node;
}
}
else
{
if ( !head )
{
head = tail = node;
}
else
{
tail.next = node;
node.previous = tail;
tail = node;
}
}
_numListeners++;
}
public function remove( listener : Function ) : void
{
var node : ListenerNode = nodes[ listener ];
if ( node )
{
if ( head == node)
{
head = head.next;
}
if ( tail == node)
{
tail = tail.previous;
}
if ( toAddHead == node)
{
toAddHead = toAddHead.next;
}
if ( toAddTail == node)
{
toAddTail = toAddTail.previous;
}
if (node.previous)
{
node.previous.next = node.next;
}
if (node.next)
{
node.next.previous = node.previous;
}
delete nodes[ listener ];
if( dispatching )
{
listenerNodePool.cache( node );
}
else
{
listenerNodePool.dispose( node );
}
_numListeners--;
}
}
public function removeAll() : void
{
while( head )
{
var node : ListenerNode = head;
head = head.next;
delete nodes[ node.listener ];
listenerNodePool.dispose( node );
node.previous = null;
node.next = null;
}
tail = null;
toAddHead = null;
toAddTail = null;
_numListeners = 0;
}
}
//}
//package {
//import ash.signals.Signal0;
import flash.utils.Dictionary;
/*public*/ class Engine
{
private var entityNames : Dictionary;
private var entityList : EntityList;
private var systemList : SystemList;
private var families : Dictionary;
public var updating : Boolean;
public var updateComplete : Signal0;
public var familyClass : Class = ComponentMatchingFamily;
public function Engine()
{
entityList = new EntityList();
entityNames = new Dictionary();
systemList = new SystemList();
families = new Dictionary();
updateComplete = new Signal0();
}
public function addEntity( entity : Entity ) : void
{
if( entityNames[ entity.name ] )
{
throw new Error( "The entity name " + entity.name + " is already in use by another entity." );
}
entityList.add( entity );
entityNames[ entity.name ] = entity;
entity.componentAdded.add( componentAdded );
entity.componentRemoved.add( componentRemoved );
entity.nameChanged.add( entityNameChanged );
for each( var family : IFamily in families )
{
family.newEntity( entity );
}
}
public function removeEntity( entity : Entity ) : void
{
entity.componentAdded.remove( componentAdded );
entity.componentRemoved.remove( componentRemoved );
entity.nameChanged.remove( entityNameChanged );
for each( var family : IFamily in families )
{
family.removeEntity( entity );
}
delete entityNames[ entity.name ];
entityList.remove( entity );
}
private function entityNameChanged( entity : Entity, oldName : String ) : void
{
if( entityNames[ oldName ] == entity )
{
delete entityNames[ oldName ];
entityNames[ entity.name ] = entity;
}
}
public function getEntityByName( name : String ) : Entity
{
return entityNames[ name ];
}
public function removeAllEntities() : void
{
while( entityList.head )
{
removeEntity( entityList.head );
}
}
public function get entities() : Vector.<Entity>
{
var entities : Vector.<Entity> = new Vector.<Entity>();
for( var entity : Entity = entityList.head; entity; entity = entity.next )
{
entities.push( entity );
}
return entities;
}
private function componentAdded( entity : Entity, componentClass : Class ) : void
{
for each( var family : IFamily in families )
{
family.componentAddedToEntity( entity, componentClass );
}
}
private function componentRemoved( entity : Entity, componentClass : Class ) : void
{
for each( var family : IFamily in families )
{
family.componentRemovedFromEntity( entity, componentClass );
}
}
public function getNodeList( nodeClass : Class ) : NodeList
{
if( families[nodeClass] )
{
return IFamily( families[nodeClass] ).nodeList;
}
var family : IFamily = new familyClass( nodeClass, this );
families[nodeClass] = family;
for( var entity : Entity = entityList.head; entity; entity = entity.next )
{
family.newEntity( entity );
}
return family.nodeList;
}
public function releaseNodeList( nodeClass : Class ) : void
{
if( families[nodeClass] )
{
families[nodeClass].cleanUp();
}
delete families[nodeClass];
}
public function addSystem( system : System, priority : int ) : void
{
system.priority = priority;
system.addToEngine( this );
systemList.add( system );
}
public function getSystem( type : Class ) : System
{
return systemList.get( type );
}
public function get systems() : Vector.<System>
{
var systems : Vector.<System> = new Vector.<System>();
for( var system : System = systemList.head; system; system = system.next )
{
systems.push( system );
}
return systems;
}
public function removeSystem( system : System ) : void
{
systemList.remove( system );
system.removeFromEngine( this );
}
public function removeAllSystems() : void
{
while( systemList.head )
{
removeSystem( systemList.head );
}
}
public function update( time : Number ) : void
{
updating = true;
for( var system : System = systemList.head; system; system = system.next )
{
system.update( time );
}
updating = false;
updateComplete.dispatch();
}
}
//package {
/*public*/ class Signal0 extends SignalBase
{
public function Signal0()
{
}
public function dispatch() : void
{
startDispatch();
var node : ListenerNode;
for ( node = head; node; node = node.next )
{
node.listener();
if( node.once )
{
remove( node.listener );
}
}
endDispatch();
}
}
//}
class ReflectUtil {
private static var CACHE:Dictionary = new Dictionary();
public static var HACHE_COMPONENTS:Dictionary = new Dictionary();
public static function registerComponents(arrClasses:Array):void {
var i:int = arrClasses.length;
while (--i > -1) {
HACHE_COMPONENTS[getQualifiedClassName(arrClasses[i])] = arrClasses[i];
}
}
public static function getFields(nodeClass:Class, arrOfClasses:Array):Dictionary {
if (CACHE[nodeClass]) return CACHE[nodeClass];
var variables : XMLList = describeType( nodeClass ).factory.variable;
var components:Dictionary = new Dictionary();
var i:int = arrOfClasses.length;
var hash:Object = { };
while (--i > -1) {
hash[ getQualifiedClassName(arrOfClasses[i]) ] = arrOfClasses[i];
}
for each ( var atom:XML in variables )
{
if ( atom.@name != "entity" && atom.@name != "previous" && atom.@name != "next" )
{
var componentClass : Class = hash[ atom.@type.toString()] || HACHE_COMPONENTS[atom.@type.toString()];
if (componentClass == null) throw new Error("Could not find component class>" + atom.@type + ", for "+nodeClass);
components[componentClass] = atom.@name.toString();
}
}
CACHE[nodeClass] = components;
return components;
}
}
//package {
import flash.utils.Dictionary;
import flash.utils.describeType;
import flash.utils.getDefinitionByName;
/*public*/ class ComponentMatchingFamily implements IFamily
{
private var nodes : NodeList;
private var entities : Dictionary;
private var nodeClass : Class;
private var components : Dictionary;
private var nodePool : NodePool;
private var engine : Engine;
public function ComponentMatchingFamily( nodeClass : Class, engine : Engine )
{
this.nodeClass = nodeClass;
this.engine = engine;
init();
}
private function init() : void
{
nodes = new NodeList();
entities = new Dictionary();
components = new Dictionary();
nodePool = new NodePool( nodeClass, components );
nodePool.dispose( nodePool.get() );
try {
var dict:Dictionary = nodeClass["_getFields"]();
}
catch (e:Error) {
var variables : XMLList = describeType( nodeClass ).factory.variable;
for each ( var atom:XML in variables )
{
if ( atom.@name != "entity" && atom.@name != "previous" && atom.@name != "next" )
{
var componentClass : Class = ReflectUtil.HACHE_COMPONENTS[ atom.@type.toString()];
if (componentClass == null) throw new Error("Component class is undefined! "+atom.@type);
components[componentClass] = atom.@name.toString();
}
}
dict = new Dictionary();
}
for each(var key:* in dict) {
components[key] = dict[key];
}
}
public function get nodeList() : NodeList
{
return nodes;
}
public function newEntity( entity : Entity ) : void
{
addIfMatch( entity );
}
public function componentAddedToEntity( entity : Entity, componentClass : Class ) : void
{
addIfMatch( entity );
}
public function componentRemovedFromEntity( entity : Entity, componentClass : Class ) : void
{
if( components[componentClass] )
{
removeIfMatch( entity );
}
}
public function removeEntity( entity : Entity ) : void
{
removeIfMatch( entity );
}
private function addIfMatch( entity : Entity ) : void
{
if( !entities[entity] )
{
var componentClass : *;
for ( componentClass in components )
{
if ( !entity.has( componentClass ) )
{
return;
}
}
var node : Node = nodePool.get();
node.entity = entity;
for ( componentClass in components )
{
node[components[componentClass]] = entity.get( componentClass );
}
entities[entity] = node;
nodes.add( node );
}
}
private function removeIfMatch( entity : Entity ) : void
{
if( entities[entity] )
{
var node : Node = entities[entity];
delete entities[entity];
nodes.remove( node );
if( engine.updating )
{
nodePool.cache( node );
engine.updateComplete.add( releaseNodePoolCache );
}
else
{
nodePool.dispose( node );
}
}
}
private function releaseNodePoolCache() : void
{
engine.updateComplete.remove( releaseNodePoolCache );
nodePool.releaseCache();
}
public function cleanUp() : void
{
for( var node : Node = nodes.head; node; node = node.next )
{
delete entities[node.entity];
}
nodes.removeAll();
}
}
//}
//package {
/*public*/ class Signal2 extends SignalBase
{
private var type1 : Class;
private var type2 : Class;
public function Signal2( type1 : Class, type2 : Class )
{
this.type1 = type1;
this.type2 = type2;
}
public function dispatch( object1 : *, object2 : * ) : void
{
startDispatch();
var node : ListenerNode;
for ( node = head; node; node = node.next )
{
node.listener( object1, object2 );
if( node.once )
{
remove( node.listener );
}
}
endDispatch();
}
}
//}
/*
* Based on ideas used in Robert Penner's AS3-signals - https://github.com/robertpenner/as3-signals
*/
class Signal3 extends SignalBase
{
private var type1 : Class;
private var type2 : Class;
private var type3 : Class;
public function Signal3( type1 : Class, type2 : Class, type3 : Class )
{
this.type1 = type1;
this.type2 = type2;
this.type3 = type3;
}
public function dispatch( object1 : *, object2 : *, object3 : * ) : void
{
startDispatch();
var node : ListenerNode;
for ( node = head; node; node = node.next )
{
node.listener( object1, object2, object3 );
if( node.once )
{
remove( node.listener );
}
}
endDispatch();
}
}
class SignalAny extends SignalBase
{
protected var classes : Array;
public function SignalAny( ...classes )
{
this.classes = classes;
}
public function dispatch( ...objects ) : void
{
startDispatch();
var node : ListenerNode;
for ( node = head; node; node = node.next )
{
node.listener.apply( null, objects );
if( node.once )
{
remove( node.listener );
}
}
endDispatch();
}
}
//package {
/*public*/ class Signal1 extends SignalBase
{
private var type : Class;
public function Signal1( type : Class )
{
this.type = type;
}
public function dispatch( object : * ) : void
{
startDispatch();
var node : ListenerNode;
for ( node = head; node; node = node.next )
{
node.listener( object );
if( node.once )
{
remove( node.listener );
}
}
endDispatch();
}
}
//}
interface IFamily {
function get nodeList() : NodeList;
function newEntity( entity : Entity ) : void;
function removeEntity( entity : Entity ) : void;
function componentAddedToEntity( entity : Entity, componentClass : Class ) : void;
function componentRemovedFromEntity( entity : Entity, componentClass : Class ) : void;
function cleanUp() : void;
}
interface ITickProvider
{
function get playing() : Boolean;
function add( listener : Function ) : void;
function remove( listener : Function ) : void;
function start() : void;
function stop() : void;
}
class EntityList
{
public var head : Entity;
public var tail : Entity;
public function add( entity : Entity ) : void
{
if( ! head )
{
head = tail = entity;
entity.next = entity.previous = null;
}
else
{
tail.next = entity;
entity.previous = tail;
entity.next = null;
tail = entity;
}
}
public function remove( entity : Entity ) : void
{
if ( head == entity)
{
head = head.next;
}
if ( tail == entity)
{
tail = tail.previous;
}
if (entity.previous)
{
entity.previous.next = entity.next;
}
if (entity.next)
{
entity.next.previous = entity.previous;
}
// N.B. Don't set node.next and node.previous to null because that will break the list iteration if node is the current node in the iteration.
}
public function removeAll() : void
{
while( head )
{
var entity : Entity = head;
head = head.next;
entity.previous = null;
entity.next = null;
}
tail = null;
}
}
class SystemList
{
public var head : System;
public var tail : System;
public function add( system : System ) : void
{
if( ! head )
{
head = tail = system;
system.next = system.previous = null;
}
else
{
for( var node : System = tail; node; node = node.previous )
{
if( node.priority <= system.priority )
{
break;
}
}
if( node == tail )
{
tail.next = system;
system.previous = tail;
system.next = null;
tail = system;
}
else if( !node )
{
system.next = head;
system.previous = null;
head.previous = system;
head = system;
}
else
{
system.next = node.next;
system.previous = node;
node.next.previous = system;
node.next = system;
}
}
}
public function remove( system : System ) : void
{
if ( head == system)
{
head = head.next;
}
if ( tail == system)
{
tail = tail.previous;
}
if (system.previous)
{
system.previous.next = system.next;
}
if (system.next)
{
system.next.previous = system.previous;
}
// N.B. Don't set system.next and system.previous to null because that will break the list iteration if node is the current node in the iteration.
}
public function removeAll() : void
{
while( head )
{
var system : System = head;
head = head.next;
system.previous = null;
system.next = null;
}
tail = null;
}
public function get( type : Class ) : System
{
for( var system : System = head; system; system = system.next )
{
if ( system is type )
{
return system;
}
}
return null;
}
}
//package
//{
import flash.utils.Dictionary;
class NodePool
{
private var tail : Node;
private var nodeClass : Class;
private var cacheTail : Node;
private var components : Dictionary;
/**
* Creates a pool for the given node class.
*/
public function NodePool( nodeClass : Class, components : Dictionary )
{
this.nodeClass = nodeClass;
this.components = components;
}
/**
* Fetches a node from the pool.
*/
internal function get() : Node
{
if ( tail )
{
var node : Node = tail;
tail = tail.previous;
node.previous = null;
return node;
}
else
{
return new nodeClass();
}
}
/**
* Adds a node to the pool.
*/
internal function dispose( node : Node ) : void
{
for each( var componentName : String in components )
{
node[ componentName ] = null;
}
node.entity = null;
node.next = null;
node.previous = tail;
tail = node;
}
/**
* Adds a node to the cache
*/
internal function cache( node : Node ) : void
{
node.previous = cacheTail;
cacheTail = node;
}
/**
* Releases all nodes from the cache into the pool
*/
internal function releaseCache() : void
{
while( cacheTail )
{
var node : Node = cacheTail;
cacheTail = node.previous;
dispose( node );
}
}
}
//}
//package {
//import ash.signals.Signal1;
import flash.display.DisplayObject;
import flash.events.Event;
import flash.utils.getTimer;
/*public*/ class FrameTickProvider extends Signal1 implements ITickProvider
{
private var displayObject : DisplayObject;
private var previousTime : Number;
private var maximumFrameTime : Number;
private var isPlaying : Boolean = false;
public var timeAdjustment : Number = 1;
public function FrameTickProvider( displayObject : DisplayObject, maximumFrameTime : Number = Number.MAX_VALUE )
{
super( Number );
this.displayObject = displayObject;
this.maximumFrameTime = maximumFrameTime;
}
public function start() : void
{
previousTime = getTimer();
displayObject.addEventListener( Event.ENTER_FRAME, dispatchTick );
isPlaying = true;
}
public function stop() : void
{
isPlaying = false;
displayObject.removeEventListener( Event.ENTER_FRAME, dispatchTick );
}
private function dispatchTick( event : Event ) : void
{
var temp : Number = previousTime;
previousTime = getTimer();
var frameTime : Number = ( previousTime - temp ) / 1000;
if( frameTime > maximumFrameTime )
{
frameTime = maximumFrameTime;
}
dispatch( frameTime * timeAdjustment );
}
public function get playing() : Boolean
{
return isPlaying;
}
}
//}
//package ash.tick
//{
import flash.display.DisplayObject;
import flash.events.Event;
//public
class FixedTickProvider extends Signal1 implements ITickProvider
{
private var displayObject : DisplayObject;
private var frameTime : Number;
private var isPlaying : Boolean = false;
public var timeAdjustment : Number = 1;
public function FixedTickProvider( displayObject : DisplayObject, frameTime : Number )
{
super( Number );
this.displayObject = displayObject;
this.frameTime = frameTime;
}
public function start() : void
{
displayObject.addEventListener( Event.ENTER_FRAME, dispatchTick );
isPlaying = true;
}
public function stop() : void
{
isPlaying = false;
displayObject.removeEventListener( Event.ENTER_FRAME, dispatchTick );
}
private function dispatchTick( event : Event ) : void
{
dispatch( frameTime * timeAdjustment );
}
public function get playing() : Boolean
{
return isPlaying;
}
}
//}
// -- Ash framework ends here
/**
* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
* If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice.
* You may add additional accurate notices of copyright ownership.
*
* It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/
* */
//package alternativa.a3d.controller {
import alternativa.engine3d.core.Camera3D;
import alternativa.engine3d.core.Object3D;
//import ash.core.Engine;
//import ash.core.System;
import flash.display.InteractiveObject;
import flash.events.KeyboardEvent;
import flash.events.MouseEvent;
import flash.geom.Matrix3D;
import flash.geom.Point;
import flash.geom.Vector3D;
import flash.ui.Keyboard;
import flash.utils.getTimer;
/**
* Controller for <code>Object3D</code>. Allow to handle the object with a keyboard and mouse.
*
* @see alternativa.engine3d.core.Object3D //
*/
//public
class SimpleObjectController extends System {
/**
* Name of action for binding "forward" action.
*/
public static const ACTION_FORWARD:String = "ACTION_FORWARD";
/**
* Name of action for binding "back" action.
*/
public static const ACTION_BACK:String = "ACTION_BACK";
/**
* Name of action for binding "left" action.
*/
public static const ACTION_LEFT:String = "ACTION_LEFT";
/**
* Name of action for binding "right" action.
*/
public static const ACTION_RIGHT:String = "ACTION_RIGHT";
/**
* Name of action for binding "up" action.
*/
public static const ACTION_UP:String = "ACTION_UP";
/**
* Name of action for binding "down" action.
*/
public static const ACTION_DOWN:String = "ACTION_DOWN";
/**
* Name of action for binding "pitch up" action.
*/
public static const ACTION_PITCH_UP:String = "ACTION_PITCH_UP";
/**
* Name of action for binding "pitch down" action.
*/
public static const ACTION_PITCH_DOWN:String = "ACTION_PITCH_DOWN";
/**
* Name of action for binding "yaw left" action.
*/
public static const ACTION_YAW_LEFT:String = "ACTION_YAW_LEFT";
/**
* Name of action for binding "yaw right" action.
*/
public static const ACTION_YAW_RIGHT:String = "ACTION_YAW_RIGHT";
/**
* Name of action for binding "accelerate" action.
*/
public static const ACTION_ACCELERATE:String = "ACTION_ACCELERATE";
/**
* ИName of action for binding "mouse look" action.
*/
public static const ACTION_MOUSE_LOOK:String = "ACTION_MOUSE_LOOK";
/**
* Speed.
*/
public var speed:Number;
/**
* Speed multiplier for acceleration mode.
*/
public var speedMultiplier:Number;
/**
* Mouse sensitivity.
*/
public var mouseSensitivity:Number;
/**
* The maximal slope in the vertical plane in radians.
*/
public var maxPitch:Number = 1e+22;
/**
* The minimal slope in the vertical plane in radians.
*/
public var minPitch:Number = -1e+22;
private var eventSource:InteractiveObject;
private var _object:Object3D;
private var _up:Boolean;
private var _down:Boolean;
private var _forward:Boolean;
private var _back:Boolean;
private var _left:Boolean;
private var _right:Boolean;
private var _accelerate:Boolean;
private var displacement:Vector3D = new Vector3D();
private var mousePoint:Point = new Point();
private var mouseLook:Boolean;
private var objectTransform:Vector.<Vector3D>;
/**
* The hash for binding names of action and functions. The functions should be at a form are follows:
* <code>
* function(value:Boolean):void
* </code>
*
* <code>value</code> argument defines if bound key pressed down or up.
*/
private var actionBindings:Object = {};
/**
* The hash for binding key codes and action names.
*/
protected var keyBindings:Object = {};
/**
* Creates a SimpleObjectController object.
* @param eventSource Source for event listening.
* @param speed Speed of movement.
* @param mouseSensitivity Mouse sensitivity, i.e. number of degrees per each pixel of mouse movement.
*/
public function SimpleObjectController(eventSource:InteractiveObject, object:Object3D, speed:Number, speedMultiplier:Number = 3, mouseSensitivity:Number = 1) {
this.eventSource = eventSource;
this.object = object;
this.speed = speed;
this.speedMultiplier = speedMultiplier;
this.mouseSensitivity = mouseSensitivity;
actionBindings[ACTION_FORWARD] = moveForward;
actionBindings[ACTION_BACK] = moveBack;
actionBindings[ACTION_LEFT] = moveLeft;
actionBindings[ACTION_RIGHT] = moveRight;
actionBindings[ACTION_UP] = moveUp;
actionBindings[ACTION_DOWN] = moveDown;
actionBindings[ACTION_ACCELERATE] = accelerate;
setDefaultBindings();
}
override public function addToEngine(engine:Engine):void {
enable();
updateObjectTransform();
}
override public function removeFromEngine(engine:Engine):void {
disable();
}
/**
* Enables the controler.
*/
public function enable():void {
eventSource.addEventListener(KeyboardEvent.KEY_DOWN, onKey);
eventSource.addEventListener(KeyboardEvent.KEY_UP, onKey);
eventSource.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
eventSource.addEventListener(MouseEvent.MOUSE_UP, onMouseUp);
}
/**
* Disables the controller.
*/
public function disable():void {
eventSource.removeEventListener(KeyboardEvent.KEY_DOWN, onKey);
eventSource.removeEventListener(KeyboardEvent.KEY_UP, onKey);
eventSource.removeEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
eventSource.removeEventListener(MouseEvent.MOUSE_UP, onMouseUp);
stopMouseLook();
}
private function onMouseDown(e:MouseEvent):void {
startMouseLook();
}
private function onMouseUp(e:MouseEvent):void {
stopMouseLook();
}
/**
* Enables mouse look mode.
*/
public function startMouseLook():void {
mousePoint.x = eventSource.mouseX;
mousePoint.y = eventSource.mouseY;
mouseLook = true;
}
/**
* Disables mouse look mode.
*/
public function stopMouseLook():void {
mouseLook = false;
}
private function onKey(e:KeyboardEvent):void {
var method:Function = keyBindings[e.keyCode];
if (method != null) method.call(this, e.type == KeyboardEvent.KEY_DOWN);
}
/**
* Target of handling.
*/
public function get object():Object3D {
return _object;
}
/**
* @private
*/
public function set object(value:Object3D):void {
_object = value;
updateObjectTransform();
}
/**
* Refreshes controller state from state of handled object. Should be called if object was moved without the controller (i.e. <code>object.x = 100;</code>).
*/
public function updateObjectTransform():void {
if (_object != null) objectTransform = _object.matrix.decompose();
}
/**
* Calculates and sets new object position.
*/
override public function update(frameTime:Number):void {
if (_object == null) return;
if (frameTime > 0.1) frameTime = 0.1;
var moved:Boolean = false;
if (mouseLook) {
var dx:Number = eventSource.mouseX - mousePoint.x;
var dy:Number = eventSource.mouseY - mousePoint.y;
mousePoint.x = eventSource.mouseX;
mousePoint.y = eventSource.mouseY;
var v:Vector3D = objectTransform[1];
v.x -= dy*Math.PI/180*mouseSensitivity;
if (v.x > maxPitch) v.x = maxPitch;
if (v.x < minPitch) v.x = minPitch;
v.z -= dx*Math.PI/180*mouseSensitivity;
moved = true;
}
displacement.x = _right ? 1 : (_left ? -1 : 0);
displacement.y = _forward ? 1 : (_back ? -1 : 0);
displacement.z = _up ? 1 : (_down ? -1 : 0);
if (displacement.lengthSquared > 0) {
if (_object is Camera3D) {
var tmp:Number = displacement.z;
displacement.z = displacement.y;
displacement.y = -tmp;
}
deltaTransformVector(displacement);
if (_accelerate) displacement.scaleBy(speedMultiplier*speed*frameTime/displacement.length);
else displacement.scaleBy(speed*frameTime/displacement.length);
(objectTransform[0] as Vector3D).incrementBy(displacement);
moved = true;
}
if (moved) {
var m:Matrix3D = new Matrix3D();
m.recompose(objectTransform);
_object.matrix = m;
}
}
/**
* Sets object at given position.
* @param pos The position.
*/
public function setObjectPos(pos:Vector3D):void {
if (_object != null) {
var v:Vector3D = objectTransform[0];
v.x = pos.x;
v.y = pos.y;
v.z = pos.z;
}
}
/**
* Sets object at given position.
* @param x X.
* @param y Y.
* @param z Z.
*/
public function setObjectPosXYZ(x:Number, y:Number, z:Number):void {
if (_object != null) {
var v:Vector3D = objectTransform[0];
v.x = x;
v.y = y;
v.z = z;
}
}
/**
* Sets direction of Z-axis of handled object to pointed at given place. If object is a camera, it will look to this direction.
* @param point Point to look at.
*/
public function lookAt(point:Vector3D):void {
lookAtXYZ(point.x, point.y, point.z);
}
/**
* Sets direction of Z-axis of handled object to pointed at given place. If object is a camera, it will look to this direction.
* @param x X.
* @param y Y.
* @param z Z.
*/
public function lookAtXYZ(x:Number, y:Number, z:Number):void {
if (_object == null) return;
var v:Vector3D = objectTransform[0];
var dx:Number = x - v.x;
var dy:Number = y - v.y;
var dz:Number = z - v.z;
v = objectTransform[1];
v.x = Math.atan2(dz, Math.sqrt(dx*dx + dy*dy));
if (_object is Camera3D) v.x -= 0.5*Math.PI;
v.y = 0;
v.z = -Math.atan2(dx, dy);
var m:Matrix3D = _object.matrix;
m.recompose(objectTransform);
_object.matrix = m;
}
private var _vin:Vector.<Number> = new Vector.<Number>(3);
private var _vout:Vector.<Number> = new Vector.<Number>(3);
private function deltaTransformVector(v:Vector3D):void {
_vin[0] = v.x;
_vin[1] = v.y;
_vin[2] = v.z;
_object.matrix.transformVectors(_vin, _vout);
var c:Vector3D = objectTransform[0];
v.x = _vout[0] - c.x;
v.y = _vout[1] - c.y;
v.z = _vout[2] - c.z;
}
/**
* Starts and stops move forward according to <code>true</code> or <code>false</code> was passed.
* @param value Action switcher.
*/
public function moveForward(value:Boolean):void {
_forward = value;
}
/**
* Starts and stops move backward according to <code>true</code> or <code>false</code> was passed.
* @param value Action switcher.
*/
public function moveBack(value:Boolean):void {
_back = value;
}
/**
* Starts and stops move to left according to <code>true</code> or <code>false</code> was passed.
* @param value Action switcher.
*/
public function moveLeft(value:Boolean):void {
_left = value;
}
/**
* Starts and stops move to right according to <code>true</code> or <code>false</code> was passed.
* @param value Action switcher.
*/
public function moveRight(value:Boolean):void {
_right = value;
}
/**
* Starts and stops move up according to <code>true</code> or <code>false</code> was passed.
* @param value Action switcher.
*/
public function moveUp(value:Boolean):void {
_up = value;
}
/**
* Starts and stops move down according to <code>true</code> or <code>false</code> was passed.
* @param value Action switcher.
*/
public function moveDown(value:Boolean):void {
_down = value;
}
/**
* Switches acceleration mode.
* @param value <code>true</code> turns acceleration on, <code>false</code> turns off.
*/
public function accelerate(value:Boolean):void {
_accelerate = value;
}
/**
* Binds key and action. Only one action can be assigned to one key.
* @param keyCode Key code.
* @param action Action name.
* @see #unbindKey()
* @see #unbindAll()
*/
public function bindKey(keyCode:uint, action:String):void {
var method:Function = actionBindings[action];
if (method != null) keyBindings[keyCode] = method;
}
/**
* Binds keys and actions. Only one action can be assigned to one key.
* @param bindings Array which consists of sequence of couples of key code and action. An example are follows: <code> [ keyCode1, action1, keyCode2, action2 ] </code>.
*/
public function bindKeys(bindings:Array):void {
for (var i:int = 0; i < bindings.length; i += 2) bindKey(bindings[i], bindings[i + 1]);
}
/**
* Clear binding for given keyCode.
* @param keyCode Key code.
* @see #bindKey()
* @see #unbindAll()
*/
public function unbindKey(keyCode:uint):void {
delete keyBindings[keyCode];
}
/**
* Clear binding of all keys.
* @see #bindKey()
* @see #unbindKey()
*/
public function unbindAll():void {
for (var key:String in keyBindings) delete keyBindings[key];
}
/**
* Sets default binding.
* @see #bindKey()
* @see #unbindKey()
* @see #unbindAll()
*/
public function setDefaultBindings():void {
bindKey(87, ACTION_FORWARD);
bindKey(83, ACTION_BACK);
bindKey(65, ACTION_LEFT);
bindKey(68, ACTION_RIGHT);
bindKey(69, ACTION_UP);
bindKey(67, ACTION_DOWN);
bindKey(Keyboard.SHIFT, ACTION_ACCELERATE);
bindKey(Keyboard.UP, ACTION_FORWARD);
bindKey(Keyboard.DOWN, ACTION_BACK);
bindKey(Keyboard.LEFT, ACTION_LEFT);
bindKey(Keyboard.RIGHT, ACTION_RIGHT);
}
}
//}
import flash.utils.ByteArray;
class Base64 {
private static const BASE64_CHARS:String = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
public static const version:String = "1.1.0";
public static function encode(data:String):String {
// Convert string to ByteArray
var bytes:ByteArray = new ByteArray();
bytes.writeUTFBytes(data);
// Return encoded ByteArray
return encodeByteArray(bytes);
}
public static function encodeByteArray(data:ByteArray):String {
// Initialise output
var output:String = "";
// Create data and output buffers
var dataBuffer:Array;
var outputBuffer:Array = new Array(4);
// Rewind ByteArray
data.position = 0;
// while there are still bytes to be processed
while (data.bytesAvailable > 0) {
// Create new data buffer and populate next 3 bytes from data
dataBuffer = new Array();
for (var i:uint = 0; i < 3 && data.bytesAvailable > 0; i++) {
dataBuffer[i] = data.readUnsignedByte();
}
// Convert to data buffer Base64 character positions and
// store in output buffer
outputBuffer[0] = (dataBuffer[0] & 0xfc) >> 2;
outputBuffer[1] = ((dataBuffer[0] & 0x03) << 4) | ((dataBuffer[1]) >> 4);
outputBuffer[2] = ((dataBuffer[1] & 0x0f) << 2) | ((dataBuffer[2]) >> 6);
outputBuffer[3] = dataBuffer[2] & 0x3f;
// If data buffer was short (i.e not 3 characters) then set
// end character indexes in data buffer to index of '=' symbol.
// This is necessary because Base64 data is always a multiple of
// 4 bytes and is basses with '=' symbols.
for (var j:uint = dataBuffer.length; j < 3; j++) {
outputBuffer[j + 1] = 64;
}
// Loop through output buffer and add Base64 characters to
// encoded data string for each character.
for (var k:uint = 0; k < outputBuffer.length; k++) {
output += BASE64_CHARS.charAt(outputBuffer[k]);
}
}
// Return encoded data
return output;
}
public static function decode(data:String):String {
// Decode data to ByteArray
var bytes:ByteArray = decodeToByteArray(data);
// Convert to string and return
return bytes.readUTFBytes(bytes.length);
}
public static function decodeToByteArray(data:String):ByteArray {
// Initialise output ByteArray for decoded data
var output:ByteArray = new ByteArray();
// Create data and output buffers
var dataBuffer:Array = new Array(4);
var outputBuffer:Array = new Array(3);
// While there are data bytes left to be processed
for (var i:uint = 0; i < data.length; i += 4) {
// Populate data buffer with position of Base64 characters for
// next 4 bytes from encoded data
for (var j:uint = 0; j < 4 && i + j < data.length; j++) {
dataBuffer[j] = BASE64_CHARS.indexOf(data.charAt(i + j));
}
// Decode data buffer back into bytes
outputBuffer[0] = (dataBuffer[0] << 2) + ((dataBuffer[1] & 0x30) >> 4);
outputBuffer[1] = ((dataBuffer[1] & 0x0f) << 4) + ((dataBuffer[2] & 0x3c) >> 2);
outputBuffer[2] = ((dataBuffer[2] & 0x03) << 6) + dataBuffer[3];
// Add all non-padded bytes in output buffer to decoded data
for (var k:uint = 0; k < outputBuffer.length; k++) {
if (dataBuffer[k+1] == 64) break;
output.writeByte(outputBuffer[k]);
}
}
// Rewind decoded data ByteArray
output.position = 0;
// Return decoded data
return output;
}
public function Base64() {
throw new Error("Base64 class is static container only");
}
}