Batched GPU Directional Sprite testing on texture atlas
forked from Sprite3DSet for Alternativa3D v8 (diff: 3941)
Trying to use GPU to automatically determine sprite angle and modify the V coordinate accordingly. (U coordinate is for animation frame). I think using GPU to determine this has disadvantages since it's 4x more work to the GPU per vertex (and uploading 1 more vertex constant, ie. the facing direction of sprite to the GPU, resulting in lower batch count). But, you won't need to manually check/update the angle UV frames for each sprite on the CPU. I could always fall back to a basic TextureAtlasMaterial and manually supply the UV coordinates for each sprite in SpriteSet's data array, if required. Sprites from Daggerfall. Away3D alreadyhas a built-in spritesheet animation system/utility and tutorial. http://away3d.com/tutorials/Using_sprite_sheets_in_your_project In alternativa3d, you would have to come up with your own custom solution/materials/tools like what is done here.
ActionScript3 source code
/**
* Copyright Glidias ( http://wonderfl.net/user/Glidias )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/rffc
*/
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.RenderingSystem;
import alternativa.engine3d.resources.BitmapTextureResource;
//import alternativa.engine3d.spriteset.materials.SpriteSheet8AnimMaterial;
// import alternativa.engine3d.spriteset.SpriteSet;
// import alternativa.engine3d.spriteset.util.SpriteGeometryUtil; //
// import ash.tick.FrameTickProvider;
import flash.display.MovieClip;
import flash.events.KeyboardEvent;
import flash.geom.Vector3D;
//import systems.SystemPriorities;
//import util.SpawnerBundle;
//import views.engine3d.MainView3D;
/**
* ...
* @author Glidias
*/
public class TestDirectionalSprites extends MovieClip
{
private var _template3D:MainView3D;
private var engine:Engine;
private var ticker:FrameTickProvider;
private var sprite8Mat:*;
//[Embed(source="daggerfl/lich.png")]
public static var SHEET_TEST:Class;
public function TestDirectionalSprites()
{
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) ;
var diffuse:BitmapTextureResource = new BitmapTextureResource(SHEET_TEST != null ? new SHEET_TEST().bitmapData : BitmapEncoder.decodeBase64(new Bmp_Tx().str) );
//diffuse.upload( _template3D.stage3D.context3D );
var numSprites:int =1455;
var numRegistersPerSprite:int = 3;
var sprSet:SpriteSet = new SpriteSet(numSprites, false, sprite8Mat = new SpriteSheet8AnimMaterial(diffuse), diffuse.data.width, diffuse.data.height, 35, numRegistersPerSprite, SpriteGeometryUtil.createNormalizedSpriteGeometry(numSprites, 0, 1, 1,0,-1,numRegistersPerSprite) );
sprite8Mat.alphaThreshold = 0.99;
// var bytes:String = BitmapEncoder.encodeBase64(diffuse.data);
//throw new Error(bytes);
//sprSet.alwaysOnTop = true;
//sprSet.setupAxisAlignment(0, 0, 1);
_template3D.scene.addChild(sprSet);
//sprite8Mat.flags = (TextureAtlasMaterial.FLAG_MIPNONE | TextureAtlasMaterial.FLAG_PIXEL_NEAREST);
if (numSprites > 1) sprSet.randomisePositions(4, 0, 4000);
if (sprite8Mat is SpriteSheet8AnimMaterial) {
var spriteMat:SpriteSheet8AnimMaterial = sprite8Mat as SpriteSheet8AnimMaterial;
spriteMat.vSpacing = 128 / 1024;
var ang:Number = Math.PI / 8;
spriteMat.firstSlope = new Vector3D( Math.cos(ang), Math.sin(ang), 0).dotProduct( new Vector3D(1,0,0) );
ang = Math.PI / 3;
spriteMat.secondSlope = new Vector3D( Math.cos(ang), Math.sin(ang), 0).dotProduct( new Vector3D(1, 0, 0) );
//throw new Error( new Vector3D(spriteMat.firstSlope, spriteMat.secondSlope));
}
var data:Vector.<Number> = sprSet.spriteData;
var facingDirection:Vector3D = new Vector3D(1,0);
var len:int = data.length;
for (var i:int = 0; i < len; i+=12) {
var baseI:int = i + 4;
data[baseI] = 0;
data[baseI + 1] = .5;// 0.625;
data[baseI + 2] = .5;
data[baseI + 3] = 128 / 1024;
facingDirection.x = Math.random();
facingDirection.y = Math.random();
facingDirection.normalize();
data[baseI+4] = facingDirection.x;
data[baseI + 5] = facingDirection.y;
data[baseI + 6] = 0;
data[baseI + 7] = 0;
}
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 = 200;
spectatorPerson.setObjectPosXYZ(0, 0, 200);
spectatorPerson.lookAtXYZ(0, 500, 0);
uploadResources(_template3D.scene.getResources(true));
_template3D.render();
// Let's go
ticker = new FrameTickProvider(stage);
ticker.add(tick);
ticker.start();
}
private function onKeyDown(e:KeyboardEvent):void
{
var kc:uint = e.keyCode;
if (kc === Keyboard.F6) {
_template3D.takeScreenshot(screenieMethod);
}
else if (kc === Keyboard.F7) {
_scrnie=_template3D.takeScreenshot(screenieMethod2);
}
}
private function screenieMethod():Boolean
{
// Wonderfl.capture(); //
return true;
}
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 function tick(time:Number):void
{
if (sprite8Mat is SpriteSheet8AnimMaterial) {
var dir:Vector3D = new Vector3D();
_template3D.camera.calculateRay( new Vector3D(), dir, _template3D.camera.view.width * .5, _template3D.camera.view.height * .5);
dir.z = 0;
dir.normalize();
var spriteMat:SpriteSheet8AnimMaterial = sprite8Mat as SpriteSheet8AnimMaterial;
spriteMat.camForward.x = Math.cos(_template3D.camera.rotationZ);
spriteMat.camForward.y = Math.sin(_template3D.camera.rotationZ);
spriteMat.camForward.x = dir.x;
spriteMat.camForward.y = dir.y;
//spriteMat.camRight = spriteMat.camForward.crossProduct(spriteMat.camR
spriteMat.camRight.x = dir.y;
spriteMat.camRight.y = -dir.x;
}
engine.update(time);
_template3D.render();
}
}
}
/**
* 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 alternativa.engine3d.spriteset.materials
//{
import alternativa.engine3d.materials.compiler.Procedure;
import flash.geom.Vector3D;
/**
* ...
* @author Glenn Ko
*/
interface IAtlasVertexMaterial
{
function getAtlasTransformProcedure(maxSprites:int, NUM_REGISTERS_PER_SPR:int, viewAligned:Boolean = true, axis:Vector3D = null):Procedure;
}
//}
/**
* 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.engine3d.spriteset.materials {
import alternativa.engine3d.alternativa3d;
import alternativa.engine3d.core.Camera3D;
import alternativa.engine3d.core.DrawUnit;
import alternativa.engine3d.core.Light3D;
import alternativa.engine3d.core.Object3D;
import alternativa.engine3d.core.Renderer;
import alternativa.engine3d.core.VertexAttributes;
import alternativa.engine3d.materials.A3DUtils;
import alternativa.engine3d.materials.compiler.Linker;
import alternativa.engine3d.materials.compiler.Procedure;
import alternativa.engine3d.materials.compiler.VariableType;
import alternativa.engine3d.materials.Material;
import alternativa.engine3d.objects.Surface;
import alternativa.engine3d.resources.Geometry;
import alternativa.engine3d.resources.TextureResource;
import flash.geom.Vector3D;
import avmplus.getQualifiedClassName;
import flash.display3D.Context3D;
import flash.display3D.Context3DBlendFactor;
import flash.display3D.Context3DProgramType;
import flash.display3D.VertexBuffer3D;
import flash.utils.Dictionary;
import flash.utils.getDefinitionByName;
use namespace alternativa3d;
/**
* The material fills surface with bitmap image in light-independent manner. Mainly used for drawing SpriteSet data.
*
* To be drawn with this material, geometry shoud have UV coordinates.
* @see alternativa.engine3d.objects.Skin#divide()
* @see alternativa.engine3d.core.VertexAttributes#TEXCOORDS
*/
//public
class SpriteSheet8AnimMaterial extends Material implements IAtlasVertexMaterial { //
private static var caches:Dictionary = new Dictionary(true);
private var cachedContext3D:Context3D;
private var programsCache:Dictionary;
private static var _transformProcedures:Dictionary = new Dictionary();
//public static const MIP_LINEAR:String = "miplinear";
//public static const MIP_NONE:String = "nomip";
//public static const PIXEL_NEAREST:String = "nearest";
//public static const PIXEL_LINEAR:String = "miplinear";
//public var pixelSetting:String = PIXEL_LINEAR;
//public var mipSetting:String = MIP_LINEAR;
private static var diffuseProcedures:Vector.<Procedure> = new Vector.<Procedure>(8,true);
public static const FLAG_PIXEL_NEAREST:uint = 2;
public static const FLAG_MIPNONE:uint = 4;
public var flags:uint = 0;
public var camForward:Vector3D = new Vector3D(0,0,1);
public var camRight:Vector3D = new Vector3D(1,0,0);
public var vSpacing:Number = 0;
public var firstSlope:Number = .2;
public var secondSlope:Number = .4;
/**
* @private
* Procedure for diffuse map with alpha channel
*/
alternativa3d function getDiffuseProcedure():Procedure {
var procedure:Procedure;
var key:uint = flags;
procedure = diffuseProcedures[key];
if (procedure!=null) return procedure;
procedure=new Procedure([
"#v0=vUV",
"#s0=sDiffuse",
"#c0=cThresholdAlpha",
"tex t0, v0, s0 <2d,"+((flags & FLAG_PIXEL_NEAREST) ? "nearest" : "linear")+",repeat,"+((flags & FLAG_MIPNONE) ? "mipnone" : "miplinear")+">", //nearest,repeat,nomip //linear,repeat,miplinear
"mul t0.w, t0.w, c0.w",
"mov o0, t0"
], "getDiffuseProcedure");
return procedure;
}
alternativa3d function getDiffuseOpacityProcedure():Procedure {
var procedure:Procedure;
var key:uint = (1 | flags);
procedure = diffuseProcedures[key];
if (procedure!=null) return procedure;
procedure=new Procedure([
"#v0=vUV",
"#s0=sDiffuse",
"#s1=sOpacity",
"#c0=cThresholdAlpha",
"tex t0, v0, s0 <2d,"+((flags & FLAG_PIXEL_NEAREST) ? "nearest" : "linear")+",repeat,"+((flags & FLAG_MIPNONE) ? "mipnone" : "miplinear")+">",
"tex t1, v0, s1 <2d,"+((flags & FLAG_PIXEL_NEAREST) ? "nearest" : "linear")+",repeat,"+((flags & FLAG_MIPNONE) ? "mipnone" : "miplinear")+">",
"mul t0.w, t1.x, c0.w",
"mov o0, t0"
], "getDiffuseOpacityProcedure");
return procedure;
}
/**
* @private
* Alpha-test check procedure.
*/
static alternativa3d const thresholdOpaqueAlphaProcedure:Procedure = new Procedure([
"#c0=cThresholdAlpha",
"sub t0.w, i0.w, c0.x",
"kil t0.w",
"mov o0, i0"
], "thresholdOpaqueAlphaProcedure");
/**
* @private
* Alpha-test check procedure.
*/
static alternativa3d const thresholdTransparentAlphaProcedure:Procedure = new Procedure([
"#c0=cThresholdAlpha",
"slt t0.w, i0.w, c0.x",
"mul i0.w, t0.w, i0.w",
"mov o0, i0"
], "thresholdTransparentAlphaProcedure");
/**
* @private
* Pass UV to the fragment shader procedure
*/
static alternativa3d const _passUVProcedure:Procedure = new Procedure(["#v0=vUV", "#a0=aUV", "#a1=joint",
"#c1=spriteSet", "#c2=cCamForward", "#c3=cCamRight", "#c4=cSlopes",
"add t1.w, c1.w, a1.x", // use offseted t1.w as +1 (c1.w) offset property from joint index to get texture atlas index
"mov t0, a0", // prepare starting a0 uv coordinate variable into t0 // (this step is redudenant for latest AGAL!)
"mov t1, c[t1.w]", // grab texture atlas data for sprite
"mov t0.xy, t1.xy", // use starting UV coordinates of atlas rect's x, and y accordignly
// Start sprite sheet rotation
"add t2.w, c3.w, a1.x", // now get the joint index register for sprite facing direction (assume +2 c3.w offset)
"mov t2, c[t2.w]",
"mov t3, t2", // we'll need the sprite direction vector later to compare against camRight vector, so we save a copy if possible else need to requery!.
"dp3 t2.w, t2, c2", // get slope
"sge t2.x, t2.w, c4.z", // get bit for if slope (dot product result) is higher than zero (to start from back-facing)
"mul t0.y, t0.y, t2.x", // either use starting V offset for back facing, else start from front zero V coordinate
"sub t2.y, c4.w, t2.x", // get flipped bit
"sub t2.x, t2.x, t2.y", // subtract by flipped bit, so 1 will remain 1, but 0 will become -1
"mul t2.z, t2.x, c2.w", // with 1 or -1 direction multiplier, apply it on the constant vSpacing to get -= spacingOffset
"abs t2.w, t2.w", // now we get magnitude of the slope
"slt t2.x, t2.w, c4.y", // does slope magnitude exceed second slope? If yes, subtact by spacingOFfset
"mul t2.y, t2.x, t2.z",
"sub t0.y, t0.y, t2.y",
"slt t2.x, t2.w, c4.x", // does slope magnitude exceed first slope? If yes, subtact by spacingOFfset
"mul t2.y, t2.x, t2.z",
"sub t0.y, t0.y, t2.y",
// now we determine if we need to flip the normalized U coordinate (1 for flip, else 0 for no flip),
// with t2.x exceed first slope as an included factor as well.
"dp3 t3.w, t3, c3",
"sge t3.x, t3.w, c4.z", // if dot product sprite vector against camRIght is higher than zero, than may need to flip
"mul t3.x, t3.x, t2.x", // check if t2.x exceeded as well...finalise by multiplying.
"mov t2, a0", // save out normalized UV coordinate. begin flipping normalized U coordinate if required
"sub t2.x, t3.x, t2.x", // flip using (1- u). If not flipping, it's (0-u)=-u,
"abs t2.x, t2.x", // abs the result, so non flipped-negative case will still get back same U coordinae
// End sprite sheet rotation
"mul t1.xy, t2.xy, t1.zw", // multiple texture atlas rect's width and height against normalized a0 uv coordinates
"add t0.xy, t0.xy, t1.xy", // add width/height offsets if any, to get final result
"mov v0, t0" // that's it!!
],
"passUVProcedure");
/**
* Diffuse map.
*/
public var diffuseMap:TextureResource;
/**
* Opacity map.
*/
public var opacityMap:TextureResource;
/**
* If <code>true</code>, perform transparent pass. Parts of surface, cumulative alpha value of which is below than <code>alphaThreshold</code> will be drawn within transparent pass.
* @see #alphaThreshold
*/
public var transparentPass:Boolean = true;
/**
* If <code>true</code>, perform opaque pass. Parts of surface, cumulative alpha value of which is greater or equal than <code>alphaThreshold</code> will be drawn within opaque pass.
* @see #alphaThreshold
*/
public var opaquePass:Boolean = true;
/**
* alphaThreshold defines starts from which value of alpha a fragment of the surface will get into transparent pass.
* @see #transparentPass
* @see #opaquePass
*/
public var alphaThreshold:Number = 0;
/**
* Transparency.
*/
public var alpha:Number = 1;
/**
* Creates a new SpriteSheet8AnimMaterial instance.
*
* @param diffuseMap Diffuse map.
* @param alpha Transparency.
*/
public function SpriteSheet8AnimMaterial(diffuseMap:TextureResource = null, opacityMap:TextureResource = null, alpha:Number = 1) {
this.diffuseMap = diffuseMap;
this.opacityMap = opacityMap;
this.alpha = alpha;
}
/**
* @private
*/
override alternativa3d function fillResources(resources:Dictionary, resourceType:Class):void {
super.fillResources(resources, resourceType);
if (diffuseMap != null && A3DUtils.checkParent(getDefinitionByName(getQualifiedClassName(diffuseMap)) as Class, resourceType)) {
resources[diffuseMap] = true;
}
if (opacityMap != null && A3DUtils.checkParent(getDefinitionByName(getQualifiedClassName(opacityMap)) as Class, resourceType)) {
resources[opacityMap] = true;
}
}
/**
* @param object
* @param programs
* @param camera
* @param opacityMap
* @param alphaTest 0 - disabled, 1 - opaque, 2 - contours
* @return
*/
private function getProgram(object:Object3D, programs:Vector.<SpriteSheet8AnimMaterialProgram>, camera:Camera3D, opacityMap:TextureResource, alphaTest:int):SpriteSheet8AnimMaterialProgram {
var key:int = (opacityMap != null ? 3 : 0) + alphaTest;
var program:SpriteSheet8AnimMaterialProgram = programs[key];
if (program == null) {
// Make program
// Vertex shader
var vertexLinker:Linker = new Linker(Context3DProgramType.VERTEX);
var positionVar:String = "aPosition";
vertexLinker.declareVariable(positionVar, VariableType.ATTRIBUTE);
if (object.transformProcedure != null) {
positionVar = appendPositionTransformProcedure(object.transformProcedure, vertexLinker);
}
vertexLinker.addProcedure(_projectProcedure);
vertexLinker.setInputParams(_projectProcedure, positionVar);
vertexLinker.addProcedure(_passUVProcedure);
// Pixel shader
var fragmentLinker:Linker = new Linker(Context3DProgramType.FRAGMENT);
var outProcedure:Procedure = (opacityMap != null ? getDiffuseOpacityProcedure() : getDiffuseProcedure());
fragmentLinker.addProcedure(outProcedure);
if (alphaTest > 0) {
fragmentLinker.declareVariable("tColor");
fragmentLinker.setOutputParams(outProcedure, "tColor");
if (alphaTest == 1) {
fragmentLinker.addProcedure(thresholdOpaqueAlphaProcedure, "tColor");
} else {
fragmentLinker.addProcedure(thresholdTransparentAlphaProcedure, "tColor");
}
}
fragmentLinker.varyings = vertexLinker.varyings;
program = new SpriteSheet8AnimMaterialProgram(vertexLinker, fragmentLinker);
program.upload(camera.context3D);
programs[key] = program;
}
return program;
}
public function getAtlasTransformProcedure(maxSprites:int, NUM_REGISTERS_PER_SPR:int, viewAligned:Boolean = true, axis:Vector3D = null):Procedure {
var key:String = maxSprites + "_" + (maxSprites * NUM_REGISTERS_PER_SPR) + (viewAligned ? "_view" : axis!=null ? "_axis" : "_z");
var res:Procedure = _transformProcedures[key];
if (res != null) return res;
res = _transformProcedures[key] = new Procedure(null, "SpriteSetTransformProcedure");
if (viewAligned) {
res.compileFromArray([
"mov t2, c[a0.x].xyz", // origin position in local coordinate space
"mov t1, t2", //dummy not needed if using latest flash player version
"add t1.x, a0.x, c3.w", // CHANGED from original SpriteSet class
"mov t1, c[t1.x]",
"mul t0.xyz, c2.xyz, i0.xxx",
"mul t0.xyz, t0.xyz, c3.xxx", // scale according to spriteset setting (right vector)
"mul t0.xyz, t0.xyz, t1.zzz", // CHANGED from original SpriteSet class (scale by tileAtlas U height)
"add t2.xyz, t2.xyz, t0.xyz",
"mul t0.xyz, c1.xyz, i0.yyy",
"mul t0.xyz, t0.xyz, c3.yyy", // scale according to spriteset setting (up vector)
"mul t0.xyz, t0.xyz, t1.www", // CHANGED from original SpriteSet class (scale by tileAtlas V height)
"add t2.xyz, t2.xyz, t0.xyz",
"mov t2.w, i0.w",
"mov o0, t2",
"#a0=joint",
//"#c0=array",
"#c1=up",
"#c2=right",
"#c3=spriteSet"
]);
}
else if (axis != null) {
res.compileFromArray([
"mov t2, c[a0.x].xyz", // origin position in local coordinate space
"add t3.x, a0.x, c2.w", // CHANGED from original SpriteSet class
"mov t3, c[t3.x]",
"sub t0, c3.xyz, t2.xyz",
//"mov t0.z, c1.w", // #if zAxis
"nrm t0.xyz, t0", // look (no longer needed after cross products)
"crs t1.xyz, c1.xyz, t0.xyz", // right // cross product vs perp dot product for z case
///* #if !zAxis // (doesn't work to face camera, it seems only axis locking works)
"crs t0.xyz, t0.xyz, t1.xyz", // get (non-z) up vector based on look cross with right
"mul t0.xyz, t0.xyz, i0.yyy", // multiple up vector by normalized xyz coodinates
"mul t0.xyz, t0.xyz, c2.yyy",
"mul t0.xyz, t0.xyz, t3.www", // CHANGED from original SpriteSet class (scale by tileAtlas V height)
"add t2.xyz, t2.xyz, t0.xyz",
//*/
"mul t0.xyz, i0.xxx, t1.xyz", // multiple right vector by normalized xyz coodinates
"mul t0.xyz, t0.xyz, c2.xxx", // scale according to spriteset setting (right vector)
"mul t0.xyz, t0.xyz, t3.zzz", // CHANGED from original SpriteSet class (scale by tileAtlas U width)
"add t2.xyz, t2.xyz, t0.xyz",
/* // #if zAxis
"mul t0.z, c2.y, i0.y", // scale according to spriteset setting (fixed axis direction)
"add t2.z, t2.z, t0.z",
*/
"mov t2.w, i0.w",
"mov o0, t2",
"#a0=joint",
//"#c0=array",
"#c1=up", // up
"#c2=spriteSet",
"#c3=cameraPos"
]);
}
else {
res.compileFromArray([
"mov t2, c[a0.x].xyz", // origin position in local coordinate space
"mov t1, t2", //dummy not needed if using latest flash player version
"add t3.x, a0.x, c2.w", // CHANGED from original SpriteSet class
"mov t3, c[t3.x]",
"sub t0, c3.xyz, t2.xyz",
"mov t0.z, c1.w", // #if zAxis
"nrm t0.xyz, t0", // look (no longer needed after cross products)
"crs t1.xyz, c1.xyz, t0.xyz", // right // cross product vs perp dot product for z case
/* #if !zAxis // (doesn't work to face camera, it seems only axis locking works)
"crs t0.xyz, t0.xyz, t1.xyz", // get (non-z) up vector based on look cross with right
"mul t0.xyz, t0.xyz, i0.yyy", // multiple up vector by normalized xyz coodinates
"mul t0.xyz, t0.xyz, c2.yyy",
"add t2.xyz, t2.xyz, t0.xyz",
*/
"mul t0.xyz, i0.xxx, t1.xyz", // multiple right vector by normalized xyz coodinates
"mul t0.xyz, t0.xyz, c2.xxx", // scale according to spriteset setting (right vector)
"mul t0.xyz, t0.xyz, t3.zzz", // CHANGED from original SpriteSet class (scale by tileAtlas U height)
"add t2.xyz, t2.xyz, t0.xyz",
///* // #if zAxis
"mul t0.z, c2.y, i0.y", // scale according to spriteset setting (fixed axis direction)
"mul t0.z, t0.z, t3.w", // CHANGED from original SpriteSet class (scale by tileAtlas V height)
"add t2.z, t2.z, t0.z",
//*/
"mov t2.w, i0.w",
"mov o0, t2",
"#a0=joint",
//"#c0=array",
"#c1=up", // up
"#c2=spriteSet",
"#c3=cameraPos"
]);
}
res.assignConstantsArray(maxSprites*NUM_REGISTERS_PER_SPR);
return res;
}
private function getDrawUnit(program:SpriteSheet8AnimMaterialProgram, camera:Camera3D, surface:Surface, geometry:Geometry, opacityMap:TextureResource):DrawUnit {
var positionBuffer:VertexBuffer3D = geometry.getVertexBuffer(VertexAttributes.POSITION);
var uvBuffer:VertexBuffer3D = geometry.getVertexBuffer(VertexAttributes.TEXCOORDS[0]);
var object:Object3D = surface.object;
// Draw call
var drawUnit:DrawUnit = camera.renderer.createDrawUnit(object, program.program, geometry._indexBuffer, surface.indexBegin, surface.numTriangles, program);
// Streams
drawUnit.setVertexBufferAt(program.aPosition, positionBuffer, geometry._attributesOffsets[VertexAttributes.POSITION], VertexAttributes.FORMATS[VertexAttributes.POSITION]);
drawUnit.setVertexBufferAt(program.aUV, uvBuffer, geometry._attributesOffsets[VertexAttributes.TEXCOORDS[0]], VertexAttributes.FORMATS[VertexAttributes.TEXCOORDS[0]]);
//Constants
object.setTransformConstants(drawUnit, surface, program.vertexShader, camera);
drawUnit.setProjectionConstants(camera, program.cProjMatrix, object.localToCameraTransform);
drawUnit.setVertexConstantsFromNumbers(program.cCamForward, camForward.x, camForward.y, camForward.z, vSpacing);
drawUnit.setVertexConstantsFromNumbers(program.cCamRight, camRight.x, camRight.y, camRight.z, 2);
drawUnit.setVertexConstantsFromNumbers(program.cSlopes, firstSlope, secondSlope, 0, 1);
drawUnit.setFragmentConstantsFromNumbers(program.cThresholdAlpha, alphaThreshold, 0, 0, alpha);
// Textures
drawUnit.setTextureAt(program.sDiffuse, diffuseMap._texture);
if (opacityMap != null) {
drawUnit.setTextureAt(program.sOpacity, opacityMap._texture);
}
return drawUnit;
}
/**
* @private
*/
override alternativa3d function collectDraws(camera:Camera3D, surface:Surface, geometry:Geometry, lights:Vector.<Light3D>, lightsLength:int, useShadow:Boolean, objectRenderPriority:int = -1):void {
var object:Object3D = surface.object;
// Buffers
var positionBuffer:VertexBuffer3D = geometry.getVertexBuffer(VertexAttributes.POSITION);
var uvBuffer:VertexBuffer3D = geometry.getVertexBuffer(VertexAttributes.TEXCOORDS[0]);
// Check validity
if (positionBuffer == null || uvBuffer == null || diffuseMap == null || diffuseMap._texture == null || opacityMap != null && opacityMap._texture == null) return;
// Refresh program cache for this context
if (camera.context3D != cachedContext3D) {
cachedContext3D = camera.context3D;
programsCache = caches[cachedContext3D];
if (programsCache == null) {
programsCache = new Dictionary();
caches[cachedContext3D] = programsCache;
}
}
var optionsPrograms:Vector.<SpriteSheet8AnimMaterialProgram> = programsCache[object.transformProcedure];
if(optionsPrograms == null) {
optionsPrograms = new Vector.<SpriteSheet8AnimMaterialProgram>(6, true);
programsCache[object.transformProcedure] = optionsPrograms;
}
var program:SpriteSheet8AnimMaterialProgram;
var drawUnit:DrawUnit;
// Opaque pass
if (opaquePass && alphaThreshold <= alpha) {
if (alphaThreshold > 0) {
// Alpha test
// use opacityMap if it is presented
program = getProgram(object, optionsPrograms, camera, opacityMap, 1);
drawUnit = getDrawUnit(program, camera, surface, geometry, opacityMap);
} else {
// do not use opacityMap at all
program = getProgram(object, optionsPrograms, camera, null, 0);
drawUnit = getDrawUnit(program, camera, surface, geometry, null);
}
// Use z-buffer within DrawCall, draws without blending
camera.renderer.addDrawUnit(drawUnit, objectRenderPriority >= 0 ? objectRenderPriority : Renderer.OPAQUE);
}
// Transparent pass
if (transparentPass && alphaThreshold > 0 && alpha > 0) {
// use opacityMap if it is presented
if (alphaThreshold <= alpha && !opaquePass) {
// Alpha threshold
program = getProgram(object, optionsPrograms, camera, opacityMap, 2);
drawUnit = getDrawUnit(program, camera, surface, geometry, opacityMap);
} else {
// There is no Alpha threshold or check z-buffer by previous pass
program = getProgram(object, optionsPrograms, camera, opacityMap, 0);
drawUnit = getDrawUnit(program, camera, surface, geometry, opacityMap);
}
// Do not use z-buffer, draws with blending
drawUnit.blendSource = Context3DBlendFactor.SOURCE_ALPHA;
drawUnit.blendDestination = Context3DBlendFactor.ONE_MINUS_SOURCE_ALPHA;
camera.renderer.addDrawUnit(drawUnit, objectRenderPriority >= 0 ? objectRenderPriority : Renderer.TRANSPARENT_SORT);
}
}
/**
* @inheritDoc
*/
override public function clone():Material {
var res:SpriteSheet8AnimMaterial = new SpriteSheet8AnimMaterial(diffuseMap, opacityMap, alpha);
res.clonePropertiesFrom(this);
return res;
}
/**
* @inheritDoc
*/
override protected function clonePropertiesFrom(source:Material):void {
super.clonePropertiesFrom(source);
var tex:SpriteSheet8AnimMaterial = source as SpriteSheet8AnimMaterial;
diffuseMap = tex.diffuseMap;
opacityMap = tex.opacityMap;
opaquePass = tex.opaquePass;
transparentPass = tex.transparentPass;
alphaThreshold = tex.alphaThreshold;
alpha = tex.alpha;
flags = tex.flags;
camForward = tex.camForward.clone();
camRight = tex.camRight.clone();
firstSlope = tex.firstSlope;
secondSlope = tex.secondSlope;
vSpacing = tex.vSpacing;
}
}
//}
import alternativa.engine3d.materials.ShaderProgram;
import alternativa.engine3d.materials.compiler.Linker;
import flash.display3D.Context3D;
class SpriteSheet8AnimMaterialProgram extends ShaderProgram {
public var aPosition:int = -1;
public var aUV:int = -1;
public var cProjMatrix:int = -1;
public var cThresholdAlpha:int = -1;
public var sDiffuse:int = -1;
public var sOpacity:int = -1;
public var cCamForward:int = -1;
public var cCamRight:int = -1;
public var cSlopes:int = -1;
public function SpriteSheet8AnimMaterialProgram(vertex:Linker, fragment:Linker) {
super(vertex, fragment);
}
override public function upload(context3D:Context3D):void {
super.upload(context3D);
aPosition = vertexShader.findVariable("aPosition");
aUV = vertexShader.findVariable("aUV");
cProjMatrix = vertexShader.findVariable("cProjMatrix");
cThresholdAlpha = fragmentShader.findVariable("cThresholdAlpha");
sDiffuse = fragmentShader.findVariable("sDiffuse");
sOpacity = fragmentShader.findVariable("sOpacity");
sDiffuse = fragmentShader.findVariable("sDiffuse");
sOpacity = fragmentShader.findVariable("sOpacity");
cCamForward = vertexShader.findVariable("cCamForward");
cCamRight = vertexShader.findVariable("cCamRight");
cSlopes = vertexShader.findVariable("cSlopes");
if (cCamForward < 0 ) throw new Error("A:" + cCamForward);
if (cCamRight < 0 ) throw new Error("B:" + cCamRight);
if (cSlopes < 0 ) throw new Error("C:"+cSlopes);
}
}
//}//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);
}
}
//}
//package alternativa.engine3d.spriteset
//{
import alternativa.engine3d.core.Camera3D;
import alternativa.engine3d.core.DrawUnit;
import alternativa.engine3d.core.Light3D;
import alternativa.engine3d.core.Object3D;
import alternativa.engine3d.core.Renderer;
import alternativa.engine3d.materials.compiler.Linker;
import alternativa.engine3d.materials.compiler.Procedure;
import alternativa.engine3d.materials.compiler.VariableType;
import alternativa.engine3d.materials.Material;
import alternativa.engine3d.materials.TextureMaterial;
import alternativa.engine3d.alternativa3d;
import alternativa.engine3d.objects.Mesh;
import alternativa.engine3d.objects.Surface;
import alternativa.engine3d.resources.Geometry;
import flash.display3D.Context3D;
import flash.display3D.Context3DVertexBufferFormat;
import flash.geom.Vector3D;
import flash.utils.Dictionary;
use namespace alternativa3d;
/**
* A 3d object to support batch rendering of sprites.
*
* @author Glenn Ko
*/
//public
class SpriteSet extends Object3D
{
/**
* Raw sprite data to upload to GPU if number of renderable sprites is lower than batch
*/
public var spriteData:Vector.<Number>;
/**
* Raw sprite data to upload to GPU in batches if number of renderable sprite is higher than batch amount. (If you called bakeSpriteData(), this is automatically created)
*/
public var staticBatches:Vector.<Vector.<Number>>;
alternativa3d var uploadSpriteData:Vector.<Number>;
private var toUploadSpriteData:Vector.<Number>;
private var toUploadNumSprites:int;
alternativa3d var maxSprites:int;
alternativa3d var _numSprites:int;
public var height:Number;
public var width:Number;
private var material:Material;
private var surface:Surface;
public function setMaterial(mat:Material):void {
this.material = mat;
surface.material = mat;
}
public var alwaysOnTop:Boolean = false;
private static var _transformProcedures:Dictionary = new Dictionary();
public var geometry:Geometry;
/**
* Default maximum batch setting (number of uploadable sprites) per batch.
*/
public static var MAX:int = 80;
alternativa3d var NUM_REGISTERS_PER_SPR:int = 1;
private var viewAligned:Boolean = false;
/**
* An alternative to "z-locking", if viewAligned is enabled, this flag can be used to lock axis along the local up (z) direction, but still keep the rightward-aligned orientation to camera view.
*/
public var viewAlignedLockUp:Boolean = false;
private static var UP:Vector3D = new Vector3D(0, 0, 1);
public var axis:Vector3D;
/**
* Sets an arbituary normalized axis direction vector along a given direction for non-viewAligned option. The default setting is z-locked (0,0,1), but using
* this option will allow alignemnt of sprites along a specific editable axis vector.
* This method automatically disables viewAligned option. and updates the transform procedure to ensure arbituary axis alignment works.
* @param x
* @param y
* @param z
* @return The axis reference which you can change at runtime
*/
public function setupAxisAlignment(x:Number, y:Number, z:Number):Vector3D {
viewAligned = false;
axis = new Vector3D(x, y, z);
if (transformProcedure != null) validateTransformProcedure();
return axis;
}
// TODO:
// create specialised material that uses smallest possible vertex buffer data (2 tuple, quad-corner index and sprite index and spritesheet-animation support) that works with this class.
/**
* Constructor
* @param numSprites The total number of sprites to render in this set
* @param viewAligned (Boolean) Whether to fully align sprites to camera screen orienation, or align to a locked axis (up - z) facing towards camera.
* @param material Material to use for all sprites
* @param width Default width (or scaling factor) of each sprite to use in world coordinate
* @param height Default height (or scaling factor) of each sprite to use in world coordinate
* @param maxSprites (Optional) Default 0 will use static MAX setting. Defines the maximum uploadable batch amount of sprites that can upload at once to GPU for this instance.
* @param numRegistersPerSprite (Optional) Default 1 will only use 1 constant register per sprite (which is the first register assumed to contain xyz position of each sprite).
* Specify more registers if needed depending on material type.
* @param geometry (Optional) Specific custom geometry layout for the spriteset if needed, else, it'll try to create a normalized (1x1 sized geometry sprite batch geometry) to fit according to available material types in Alternativa3D.
*/
public function SpriteSet(numSprites:int, viewAligned:Boolean, material:Material, width:Number, height:Number,maxSprites:int=0, numRegistersPerSprite:int=1, geometry:Geometry=null)
{
super();
this.geometry = geometry;
this.viewAligned = viewAligned;
NUM_REGISTERS_PER_SPR = numRegistersPerSprite;
if (maxSprites <= 0) maxSprites = MAX;
uploadSpriteData = new Vector.<Number>(((maxSprites*NUM_REGISTERS_PER_SPR) << 2),true);
this.material = material;
surface = new Surface();
surface.material = material;
surface.object = this;
surface.indexBegin = 0;
this.width = width;
this.height = height;
this.maxSprites = maxSprites;
_numSprites = numSprites;
spriteData = new Vector.<Number>(((numSprites * NUM_REGISTERS_PER_SPR) << 2), true);
}
/*
alternativa3d override function calculateVisibility(camera:Camera3D):void {
}
*/
alternativa3d override function setTransformConstants(drawUnit:DrawUnit, surface:Surface, vertexShader:Linker, camera:Camera3D):void {
drawUnit.setVertexBufferAt(vertexShader.getVariableIndex("joint"), geometry.getVertexBuffer(SpriteGeometryUtil.ATTRIBUTE), geometry._attributesOffsets[SpriteGeometryUtil.ATTRIBUTE], Context3DVertexBufferFormat.FLOAT_1);
if (!viewAligned) {
drawUnit.setVertexConstantsFromNumbers(vertexShader.getVariableIndex("cameraPos"), cameraToLocalTransform.d, cameraToLocalTransform.h, cameraToLocalTransform.l, 0);
var axis:Vector3D = this.axis || UP;
drawUnit.setVertexConstantsFromNumbers(vertexShader.getVariableIndex("up"), axis.x, axis.y, axis.z, 0);
}
else {
if (!viewAlignedLockUp) drawUnit.setVertexConstantsFromNumbers(vertexShader.getVariableIndex("up"), -cameraToLocalTransform.b, -cameraToLocalTransform.f, -cameraToLocalTransform.j, 0)
else drawUnit.setVertexConstantsFromNumbers(vertexShader.getVariableIndex("up"), 0, 0, 1, 0);
drawUnit.setVertexConstantsFromNumbers(vertexShader.getVariableIndex("right"), cameraToLocalTransform.a, cameraToLocalTransform.e, cameraToLocalTransform.i, 0);
}
drawUnit.setVertexConstantsFromNumbers(vertexShader.getVariableIndex("spriteSet"), width*.5, height*.5, 0, 1);
drawUnit.setVertexConstantsFromVector(0, toUploadSpriteData, toUploadNumSprites*NUM_REGISTERS_PER_SPR );
}
override alternativa3d function collectDraws(camera:Camera3D, lights:Vector.<Light3D>, lightsLength:int, useShadow:Boolean):void {
var spriteDataSize:int;
var i:int;
var numSprites:int = _numSprites; // TODO: use different reference
var objRenderPriority:int = alwaysOnTop ? Renderer.NEXT_LAYER : -1;
// setup defaults if required
if (geometry == null) {
geometry = SpriteGeometryUtil.createNormalizedSpriteGeometry(maxSprites, 0, SpriteGeometryUtil.guessRequirementsAccordingToMaterial(material), 1,0,0, NUM_REGISTERS_PER_SPR);
geometry.upload( camera.context3D );
}
if (transformProcedure == null) validateTransformProcedure();
if (numSprites <= maxSprites) {
toUploadSpriteData = spriteData;
toUploadNumSprites = numSprites;
surface.numTriangles = (toUploadNumSprites << 1);
surface.material.collectDraws(camera, surface, geometry, lights, lightsLength, useShadow, objRenderPriority);
}
else if (staticBatches) {
spriteDataSize = NUM_REGISTERS_PER_SPR * 4;
for (i = 0; i < staticBatches.length; i++) {
toUploadSpriteData = staticBatches[i];
toUploadNumSprites = toUploadSpriteData.length / spriteDataSize;
surface.numTriangles = (toUploadNumSprites << 1);
surface.material.collectDraws(camera, surface, geometry, lights, lightsLength, useShadow, objRenderPriority);
}
}
else {
spriteDataSize = (NUM_REGISTERS_PER_SPR << 2);
toUploadSpriteData = uploadSpriteData;
for (i = 0; i < numSprites; i += maxSprites) {
var limit:int = numSprites - i; // remaining sprites left to iterate
if (limit > maxSprites) limit = maxSprites;
toUploadNumSprites = limit;
limit += i;
var count:int = 0;
for (var u:int = i; u < limit; u++ ) { // start sprite index to ending sprite index
var bu:int = u * spriteDataSize;
var d:int = spriteDataSize;
while (--d > -1) toUploadSpriteData[count++] = spriteData[bu++];
}
surface.numTriangles = (toUploadNumSprites << 1);
surface.material.collectDraws(camera, surface, geometry, lights, lightsLength, useShadow, objRenderPriority);
}
}
// Mouse events
//if (listening) camera.view.addSurfaceToMouseEvents(surface, geometry, transformProcedure);
// }
// Debug
/*
if (camera.debug) {
var debug:int = camera.checkInDebug(this);
if ((debug & Debug.BOUNDS) && boundBox != null) Debug.drawBoundBox(camera, boundBox, localToCameraTransform);
}
*/
}
/**
* Sets up geometry according to settings found in this instance.
* @param context3D
*/
public function setupDefaultGeometry(context3D:Context3D = null):void {
if (geometry != null) {
geometry.dispose();
}
geometry = SpriteGeometryUtil.createNormalizedSpriteGeometry(maxSprites, 0, SpriteGeometryUtil.guessRequirementsAccordingToMaterial(material), 1, 0,0, NUM_REGISTERS_PER_SPR);
if (context3D) geometry.upload(context3D);
}
/**
* Sets up transform procedure according to settings found in this instance.
*/
public function validateTransformProcedure():void {
transformProcedure = material is IAtlasVertexMaterial ? (material as IAtlasVertexMaterial).getAtlasTransformProcedure(maxSprites, NUM_REGISTERS_PER_SPR, viewAligned, axis) : viewAligned ? getViewAlignedTransformProcedure(maxSprites) : axis!= null ? getAxisAlignedTransformProcedure(maxSprites) : getTransformProcedure(maxSprites);
}
/**
* Randomise positions of sprites of spriteData, assuming 1st register of each sprite refers to it's x,y,z position. Good for previewing spriteset.
* @param mask A bitmask to PREVENT randomisation, and assign maskValue instead. Mask bit for 1,2,4 for x,y,z respectively.
* @param maskValue If mask hits, what value to set
* @param range The diameter width range of random spread
* @param offsetX
* @param offsetY
* @param offsetZ
*/
public function randomisePositions(mask:int = 0, maskValue:Number = 0, range:Number = 1200, offsetX:Number = 0, offsetY:Number = 0, offsetZ:Number = 0 ):void {
var multiplier:int = NUM_REGISTERS_PER_SPR * 4;
var hRange:Number = range * .5;
for (var i:int = 0; i < _numSprites; i++ ) {
var baseI:int = i * multiplier;
spriteData[baseI] = (mask & 1) ? maskValue : -hRange + Math.random() * range +offsetX;
spriteData[baseI + 1] = (mask & 2) ? maskValue : -hRange + Math.random() * range+offsetY;
spriteData[baseI + 2] = (mask & 4) ? maskValue : -hRange + Math.random() * range +offsetZ;
}
}
/**
* Adjust number of sprites in spriteData. This would truncate sprites or add more to the list that can be editable.
*/
public function set numSprites(value:int):void
{
spriteData.fixed = false;
spriteData.length = ((value * NUM_REGISTERS_PER_SPR) << 2);
spriteData.fixed = true;
_numSprites = value;
}
/**
* Will permanently render baked static sprite data information into a set of static batches, if total number of sprites to be drawn exceeds the batch size.
* This can improve performance a bit for larger sets since you don't need to re-read data one-by-one from existing spriteData, if spriteData isn't changing,
* or you might wish to use the static batches for your own direct manual editing.
* @param flushOldSpriteDataIfPossible (Boolean) Optional. Whether to null away spriteData reference if it exceeds batch size.
* @return The baked staticBatches reference for the current instance.
*/
public function bakeSpriteData(flushOldSpriteDataIfPossible:Boolean = false):Vector.<Vector.<Number>> {
// setup defaults if required
if (geometry == null) geometry = SpriteGeometryUtil.createNormalizedSpriteGeometry(maxSprites, 0, SpriteGeometryUtil.guessRequirementsAccordingToMaterial(material), 1, 0,0, NUM_REGISTERS_PER_SPR);
if (transformProcedure == null) validateTransformProcedure();
staticBatches = new Vector.<Vector.<Number>>();
if (_numSprites <= maxSprites) {
staticBatches.push(spriteData);
return staticBatches;
}
var batch:Vector.<Number>;
var i:int;
var spriteDataSize:int = NUM_REGISTERS_PER_SPR * 4;
for (i = 0; i < _numSprites; i += maxSprites) {
var limit:int = _numSprites - i; // remaining sprites left to iterate
if (limit > maxSprites) limit = maxSprites;
limit += i;
var count:int = 0;
batch = new Vector.<Number>();
for (var u:int = i; u < limit; u++ ) { // start sprite index to ending sprite index
var bu:int = u * spriteDataSize;
var d:int = spriteDataSize;
while (--d > -1) batch[count++] = spriteData[bu++];
}
batch.fixed = true;
staticBatches.push(batch);
}
staticBatches.fixed = true;
if (flushOldSpriteDataIfPossible) spriteData = null;
return staticBatches;
}
public function getMaxSprites():int
{
return maxSprites;
}
alternativa3d override function fillResources(resources:Dictionary, hierarchy:Boolean = false, resourceType:Class = null):void {
if (geometry != null && (resourceType == null || geometry is resourceType)) resources[geometry] = true;
material.fillResources(resources, resourceType);
super.fillResources(resources, hierarchy, resourceType);
}
private function getTransformProcedure(maxSprites:int):Procedure {
var key:String = maxSprites + "_" + (maxSprites * NUM_REGISTERS_PER_SPR) + "_z";
var res:Procedure = _transformProcedures[key];
if (res != null) return res;
res = _transformProcedures[key] = new Procedure(null, "SpriteSetTransformProcedure");
res.compileFromArray([
"mov t2, c[a0.x].xyz", // origin position in local coordinate space
"sub t0, c3.xyz, t2.xyz",
"mov t0.z, c1.w", // #if zAxis
"nrm t0.xyz, t0", // look (no longer needed after cross products)
"crs t1.xyz, c1.xyz, t0.xyz", // right // cross product vs perp dot product for z case
/* #if !zAxis // (doesn't work to face camera, it seems only axis locking works)
"crs t0.xyz, t0.xyz, t1.xyz", // get (non-z) up vector based on look cross with right
"mul t0.xyz, t0.xyz, i0.yyy", // multiple up vector by normalized xyz coodinates
"mul t0.xyz, t0.xyz, c2.yyy",
"add t2.xyz, t2.xyz, t0.xyz",
*/
"mul t0.xyz, i0.xxx, t1.xyz", // multiple right vector by normalized xyz coodinates
"mul t0.xyz, t0.xyz, c2.xxx", // scale according to spriteset setting (right vector)
"add t2.xyz, t2.xyz, t0.xyz",
///* // #if zAxis
"mul t0.z, c2.y, i0.y", // scale according to spriteset setting (fixed axis direction)
"add t2.z, t2.z, t0.z",
//*/
"mov t2.w, i0.w",
"mov o0, t2",
"#a0=joint",
//"#c0=array",
"#c1=up", // up
"#c2=spriteSet",
"#c3=cameraPos"
]);
res.assignConstantsArray(maxSprites*NUM_REGISTERS_PER_SPR);
return res;
}
private function getAxisAlignedTransformProcedure(maxSprites:int):Procedure {
var key:String = maxSprites + "_" + (maxSprites * NUM_REGISTERS_PER_SPR) + "_axis";
var res:Procedure = _transformProcedures[key];
if (res != null) return res;
res = _transformProcedures[key] = new Procedure(null, "SpriteSetTransformProcedure");
res.compileFromArray([
"mov t2, c[a0.x].xyz", // origin position in local coordinate space
"sub t0, c3.xyz, t2.xyz",
//"mov t0.z, c1.w", // #if zAxis
"nrm t0.xyz, t0", // look (no longer needed after cross products)
"crs t1.xyz, c1.xyz, t0.xyz", // right // cross product vs perp dot product for z case
///* #if !zAxis // (doesn't work to face camera, it seems only axis locking works)
"crs t0.xyz, t0.xyz, t1.xyz", // get (non-z) up vector based on look cross with right
"mul t0.xyz, t0.xyz, i0.yyy", // multiple up vector by normalized xyz coodinates
"mul t0.xyz, t0.xyz, c2.yyy",
"add t2.xyz, t2.xyz, t0.xyz",
//*/
"mul t0.xyz, i0.xxx, t1.xyz", // multiple right vector by normalized xyz coodinates
"mul t0.xyz, t0.xyz, c2.xxx", // scale according to spriteset setting (right vector)
"add t2.xyz, t2.xyz, t0.xyz",
/* // #if zAxis
"mul t0.z, c2.y, i0.y", // scale according to spriteset setting (fixed axis direction)
"add t2.z, t2.z, t0.z",
*/
"mov t2.w, i0.w",
"mov o0, t2",
"#a0=joint",
//"#c0=array",
"#c1=up", // up
"#c2=spriteSet",
"#c3=cameraPos"
]);
res.assignConstantsArray(maxSprites*NUM_REGISTERS_PER_SPR);
return res;
}
private function getViewAlignedTransformProcedure(maxSprites:int):Procedure {
var key:String = maxSprites + "_" + (maxSprites * NUM_REGISTERS_PER_SPR) + "_view";
var res:Procedure = _transformProcedures[key];
if (res != null) return res;
res = _transformProcedures[key] = new Procedure(null, "SpriteSetTransformProcedure");
res.compileFromArray([
"mov t2, c[a0.x].xyz", // origin position in local coordinate space
"mov t1, t2", //dummy not needed later change
"mul t0.xyz, c2.xyz, i0.xxx",
"mul t0.xyz, t0.xyz, c3.xxx", // scale according to spriteset setting (right vector)
"add t2.xyz, t2.xyz, t0.xyz",
"mul t0.xyz, c1.xyz, i0.yyy",
"mul t0.xyz, t0.xyz, c3.yyy", // scale according to spriteset setting (up vector)
"add t2.xyz, t2.xyz, t0.xyz",
"mov t2.w, i0.w",
"mov o0, t2",
"#a0=joint",
//"#c0=array",
"#c1=up",
"#c2=right",
"#c3=spriteSet"
]);
res.assignConstantsArray(maxSprites*NUM_REGISTERS_PER_SPR);
return res;
}
}
//}
//package alternativa.engine3d.spriteset.util
//{
import alternativa.engine3d.core.VertexAttributes;
import alternativa.engine3d.resources.Geometry;
import flash.utils.ByteArray;
import flash.utils.Dictionary;
import flash.utils.Endian;
import alternativa.engine3d.alternativa3d;
import flash.utils.getQualifiedClassName;
use namespace alternativa3d;
/**
* Utility to help work with Sprite3DSet and your own custom sprite materials!
* @author Glenn Ko
*/
//public
class SpriteGeometryUtil
{
public static const REQUIRE_UVs:uint = 1;
public static const REQUIRE_NORMAL:uint = 2;
public static const REQUIRE_TANGENT:uint = 4;
public static const ATTRIBUTE:uint = 20; // same attribute as used in MeshSet
public static var MATERIAL_REQUIREMENTS:Dictionary = new Dictionary();
public static function guessRequirementsAccordingToMaterial(material:*):int {
if (MATERIAL_REQUIREMENTS && MATERIAL_REQUIREMENTS[material.constructor]) return MATERIAL_REQUIREMENTS[material.constructor];
var classeName:String = getQualifiedClassName(material).split("::").pop();
if (MATERIAL_REQUIREMENTS && MATERIAL_REQUIREMENTS[classeName]) return MATERIAL_REQUIREMENTS[classeName];
switch (classeName) {
case "Material":
case "FillMaterial":
return 0;
case "TextureMaterial": return ( REQUIRE_UVs );
case "StandardMaterial": return ( REQUIRE_UVs ); return ( REQUIRE_UVs | REQUIRE_NORMAL | REQUIRE_TANGENT );
default: return ( REQUIRE_UVs | REQUIRE_NORMAL | REQUIRE_TANGENT );
}
}
public static function createNormalizedSpriteGeometry(numSprites:int, indexOffset:int, requirements:uint = 1, scale:Number=1, originX:Number=0, originY:Number=0, indexMultiplier:int=1):Geometry
{
var geometry:Geometry = new Geometry();
var attributes:Array = [];
var i:int = 0;
originX *= scale;
originY *= scale;
var indices:Vector.<uint> = new Vector.<uint>();
var vertices:ByteArray = new ByteArray();
vertices.endian = Endian.LITTLE_ENDIAN;
var requireUV:Boolean = (requirements & REQUIRE_UVs)!=0;
var requireNormal:Boolean = (requirements & REQUIRE_NORMAL)!=0;
var requireTangent:Boolean = (requirements & REQUIRE_TANGENT)!=0;
attributes[i++] = VertexAttributes.POSITION;
attributes[i++] = VertexAttributes.POSITION;
attributes[i++] = VertexAttributes.POSITION;
if ( requireUV) {
attributes[i++] = VertexAttributes.TEXCOORDS[0];
attributes[i++] = VertexAttributes.TEXCOORDS[0];
}
if (requireNormal) {
attributes[i++] = VertexAttributes.NORMAL;
attributes[i++] = VertexAttributes.NORMAL;
attributes[i++] = VertexAttributes.NORMAL;
}
if ( requireTangent) {
attributes[i++] = VertexAttributes.TANGENT4;
attributes[i++] = VertexAttributes.TANGENT4;
attributes[i++] = VertexAttributes.TANGENT4;
attributes[i++] = VertexAttributes.TANGENT4;
}
attributes[i++] = ATTRIBUTE;
for (i = 0; i<numSprites;i++) {
vertices.writeFloat(-1*scale - originX);
vertices.writeFloat(-1*scale - originY);
vertices.writeFloat(0);
if ( requireUV) {
vertices.writeFloat(0);
vertices.writeFloat(1);
}
if ( requireNormal) {
vertices.writeFloat(0);
vertices.writeFloat(0);
vertices.writeFloat(1);
}
if ( requireTangent) {
vertices.writeFloat(1);
vertices.writeFloat(0);
vertices.writeFloat(0);
vertices.writeFloat(-1);
}
vertices.writeFloat(i*indexMultiplier+indexOffset);
vertices.writeFloat(1*scale - originX);
vertices.writeFloat(-1*scale - originY);
vertices.writeFloat(0);
if ( requireUV) {
vertices.writeFloat(1);
vertices.writeFloat(1);
}
if ( requireNormal) {
vertices.writeFloat(0);
vertices.writeFloat(0);
vertices.writeFloat(1);
}
if ( requireTangent) {
vertices.writeFloat(1);
vertices.writeFloat(0);
vertices.writeFloat(0);
vertices.writeFloat(-1);
}
vertices.writeFloat(i*indexMultiplier+indexOffset);
vertices.writeFloat(1*scale - originX);
vertices.writeFloat(1*scale - originY);
vertices.writeFloat(0);
if ( requireUV) {
vertices.writeFloat(1);
vertices.writeFloat(0);
}
if ( requireNormal) {
vertices.writeFloat(0);
vertices.writeFloat(0);
vertices.writeFloat(1);
}
if ( requireTangent) {
vertices.writeFloat(1);
vertices.writeFloat(0);
vertices.writeFloat(0);
vertices.writeFloat(-1);
}
vertices.writeFloat(i*indexMultiplier+indexOffset);
vertices.writeFloat(-1*scale - originX);
vertices.writeFloat(1*scale - originY);
vertices.writeFloat(0);
if ( requireUV) {
vertices.writeFloat(0);
vertices.writeFloat(0);
}
if (requireNormal) {
vertices.writeFloat(0);
vertices.writeFloat(0);
vertices.writeFloat(1);
}
if ( requireTangent) {
vertices.writeFloat(1);
vertices.writeFloat(0);
vertices.writeFloat(0);
vertices.writeFloat(-1);
}
vertices.writeFloat(i*indexMultiplier+indexOffset);
var baseI:int = i * 4;
indices.push(baseI, baseI+1, baseI+3, baseI+1, baseI+2, baseI+3);
}
geometry._indices = indices;
geometry.addVertexStream(attributes);
geometry._vertexStreams[0].data = vertices;
geometry._numVertices = numSprites * 4;
return geometry;
}
}
//}
//package com.foxarc.images {
import flash.display.BitmapData;
import flash.geom.Rectangle;
import flash.utils.ByteArray;
// import com.foxarc.util.Base64;
class BitmapEncoder {
public static function encodeByteArray(data:BitmapData):ByteArray{
if(data == null){
throw new Error("data parameter can not be empty!");
}
var bytes:ByteArray = data.getPixels(data.rect);
bytes.writeShort(data.width);
bytes.writeShort(data.height);
bytes.writeBoolean(data.transparent);
bytes.compress();
return bytes;
}
public static function encodeBase64(data:BitmapData):String{
return Base64.encodeByteArray(encodeByteArray(data));
}
public static function decodeByteArray(bytes:ByteArray):BitmapData{
if(bytes == null){
throw new Error("bytes parameter can not be empty!");
}
bytes.uncompress();
if(bytes.length < 6){
throw new Error("bytes parameter is a invalid value");
}
bytes.position = bytes.length - 1;
var transparent:Boolean = bytes.readBoolean();
bytes.position = bytes.length - 3;
var height:int = bytes.readShort();
bytes.position = bytes.length - 5;
var width:int = bytes.readShort();
bytes.position = 0;
var datas:ByteArray = new ByteArray();
bytes.readBytes(datas,0,bytes.length - 5);
var bmp:BitmapData = new BitmapData(width,height,transparent,0);
bmp.setPixels(new Rectangle(0,0,width,height),datas);
return bmp;
}
public static function decodeBase64(data:String):BitmapData{
return decodeByteArray(Base64.decodeToByteArray(data));
}
public function BitmapEncoder() {
throw new Error("BitmapEncoder is a static class!");
}
}
//}
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");
}
}
class Bmp_Tx {
public var str:String = "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IiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIvuVf/eh/+FD/+7/B5G/WnE="}