package aerys.minko.scene.controller.mesh
{
	import aerys.minko.scene.controller.AbstractController;
	import aerys.minko.scene.data.MeshVisibilityDataProvider;
	import aerys.minko.scene.node.Camera;
	import aerys.minko.scene.node.Scene;
	import aerys.minko.scene.node.mesh.Mesh;
	import aerys.minko.type.Signal;
	import aerys.minko.type.bounding.FrustumCulling;
	import aerys.minko.type.data.DataBindings;
	import aerys.minko.type.data.DataProvider;
	import aerys.minko.type.data.IDataProvider;
	import aerys.minko.type.math.Frustum;
	import aerys.minko.type.math.Matrix4x4;

	/**
	 * The CullingController watches the Mesh and the active Camera of a Scene
	 * to determine whether the object is actually inside the view frustum or not.
	 *
	 * @author Jean-Marc Le Roux
	 *
	 */
	public final class MeshVisibilityController extends AbstractController
	{
		private static const STATE_UNDEFINED	: uint	= 0;
		private static const STATE_INSIDE		: uint	= 1;
		private static const STATE_OUTSIDE		: uint	= 2;

		private var _mesh			: Mesh							= null;
		private var _state			: uint							= 0;

		private var _visibilityData	: MeshVisibilityDataProvider	= new MeshVisibilityDataProvider();

		public function get visible() : Boolean
		{
			return _visibilityData.visible;
		}
		public function set visible(value : Boolean) : void
		{
			_visibilityData.visible = value;
		}

		public function get frustumCulling() : uint
		{
			return _visibilityData.frustumCulling;
		}
		public function set frustumCulling(value : uint) : void
		{
			_visibilityData.frustumCulling = value;
		}

		public function get insideFrustum() : Boolean
		{
			return _visibilityData.inFrustum;
		}

		public function MeshVisibilityController()
		{
			super(Mesh);

			initialize();
		}

		private function initialize() : void
		{
			targetAdded.add(targetAddedHandler);
		}

		private function targetAddedHandler(ctrl	: MeshVisibilityController,
											target	: Mesh) : void
		{
			if (_mesh != null)
				throw new Error();

			_mesh = target;

			target.addedToScene.add(meshAddedToSceneHandler);
			target.removedFromScene.add(meshRemovedFromSceneHandler);
			target.bindings.addProvider(_visibilityData);

			if (target.root is Scene)
				meshAddedToSceneHandler(target, target.root as Scene);
		}

		private function targetRemovedHandler(ctrl		: MeshVisibilityController,
											  target	: Mesh) : void
		{
			_mesh = null;

			target.addedToScene.remove(meshAddedToSceneHandler);
			target.removedFromScene.remove(meshRemovedFromSceneHandler);
			target.bindings.removeProvider(_visibilityData);

			if (target.root is Scene)
				meshRemovedFromSceneHandler(target, target.root as Scene);
		}

		private function meshAddedToSceneHandler(mesh	: Mesh,
												 scene	: Scene) : void
		{
			scene.bindings.addCallback('worldToView', worldToViewChangedHandler);
			mesh.localToWorld.changed.add(meshLocalToWorldChangedHandler);
		}

		private function meshRemovedFromSceneHandler(mesh	: Mesh,
													 scene	: Scene) : void
		{
			mesh.localToWorld.changed.remove(meshLocalToWorldChangedHandler);
		}

		private function worldToViewChangedHandler(bindings		: DataBindings,
												   propertyName	: String,
												   newValue		: Matrix4x4) : void
		{
			testCulling();
		}

		private function meshLocalToWorldChangedHandler(transform	: Matrix4x4,
														property	: String) : void
		{
			testCulling();
		}

		private function testCulling() : void
		{
			var culling	: uint	= _visibilityData.frustumCulling;

			if (culling != FrustumCulling.DISABLED)
			{
				var camera : Camera = (_mesh.root as Scene).activeCamera;

				if (!camera)
					return ;

				culling = camera.cameraData.frustum.testBoundingVolume(
					_mesh.geometry,
					_mesh.localToWorld,
					culling
				);

				var inside : Boolean = culling != Frustum.OUTSIDE;

				if (inside && _state != STATE_INSIDE)
				{
					_visibilityData.inFrustum = true;
					_state = STATE_INSIDE;
				}
				else if (!inside && _state != STATE_OUTSIDE)
				{
					_visibilityData.inFrustum = false;
					_state = STATE_OUTSIDE;
				}
			}
		}

		override public function clone() : AbstractController
		{
			var clone : MeshVisibilityController = new MeshVisibilityController();

			clone._visibilityData = _visibilityData.clone() as MeshVisibilityDataProvider;

			return clone;
		}
	}
}

package aerys.minko.scene.controller.mesh
{
	import aerys.minko.scene.controller.AbstractController;
	import aerys.minko.scene.data.MeshVisibilityDataProvider;
	import aerys.minko.scene.node.Camera;
	import aerys.minko.scene.node.Scene;
	import aerys.minko.scene.node.mesh.Mesh;
	import aerys.minko.type.Signal;
	import aerys.minko.type.bounding.FrustumCulling;
	import aerys.minko.type.data.DataBindings;
	import aerys.minko.type.data.DataProvider;
	import aerys.minko.type.data.IDataProvider;
	import aerys.minko.type.math.Frustum;
	import aerys.minko.type.math.Matrix4x4;

	/**
	 * The MeshVisibilityController watches the Mesh and the active Camera of a Scene
	 * to determine whether the object is actually inside the view frustum or not.
	 *
	 * @author Jean-Marc Le Roux
	 *
	 */
	public final class MeshVisibilityController extends AbstractController
	{
		private static const STATE_UNDEFINED	: uint	= 0;
		private static const STATE_INSIDE		: uint	= 1;
		private static const STATE_OUTSIDE		: uint	= 2;

		private var _mesh			: Mesh							= null;
		private var _state			: uint							= 0;

		private var _visibilityData	: MeshVisibilityDataProvider	= new MeshVisibilityDataProvider();

		public function get visible() : Boolean
		{
			return _visibilityData.visible;
		}
		public function set visible(value : Boolean) : void
		{
			_visibilityData.visible = value;
		}

		public function get frustumCulling() : uint
		{
			return _visibilityData.frustumCulling;
		}
		public function set frustumCulling(value : uint) : void
		{
			_visibilityData.frustumCulling = value;
		}

		public function get insideFrustum() : Boolean
		{
			return _visibilityData.inFrustum;
		}

		public function MeshVisibilityController()
		{
			super(Mesh);

			initialize();
		}

		private function initialize() : void
		{
			targetAdded.add(targetAddedHandler);
		}

		private function targetAddedHandler(ctrl	: MeshVisibilityController,
											target	: Mesh) : void
		{
			if (_mesh != null)
				throw new Error();

			_mesh = target;

			target.addedToScene.add(meshAddedToSceneHandler);
			target.removedFromScene.add(meshRemovedFromSceneHandler);
			target.bindings.addProvider(_visibilityData);

			if (target.root is Scene)
				meshAddedToSceneHandler(target, target.root as Scene);
		}

		private function targetRemovedHandler(ctrl		: MeshVisibilityController,
											  target	: Mesh) : void
		{
			_mesh = null;

			target.addedToScene.remove(meshAddedToSceneHandler);
			target.removedFromScene.remove(meshRemovedFromSceneHandler);
			target.bindings.removeProvider(_visibilityData);

			if (target.root is Scene)
				meshRemovedFromSceneHandler(target, target.root as Scene);
		}

		private function meshAddedToSceneHandler(mesh	: Mesh,
												 scene	: Scene) : void
		{
			scene.bindings.addCallback('worldToView', worldToViewChangedHandler);
			mesh.localToWorld.changed.add(meshLocalToWorldChangedHandler);
		}

		private function meshRemovedFromSceneHandler(mesh	: Mesh,
													 scene	: Scene) : void
		{
			mesh.localToWorld.changed.remove(meshLocalToWorldChangedHandler);
		}

		private function worldToViewChangedHandler(bindings		: DataBindings,
												   propertyName	: String,
												   newValue		: Matrix4x4) : void
		{
			testCulling();
		}

		private function meshLocalToWorldChangedHandler(transform	: Matrix4x4,
														property	: String) : void
		{
			testCulling();
		}

		private function testCulling() : void
		{
			var culling	: uint	= _visibilityData.frustumCulling;

			if (culling != FrustumCulling.DISABLED)
			{
				var camera : Camera = (_mesh.root as Scene).activeCamera;

				if (!camera)
					return ;

				culling = camera.cameraData.frustum.testBoundingVolume(
					_mesh.geometry,
					_mesh.localToWorld,
					culling
				);

				var inside : Boolean = culling != Frustum.OUTSIDE;

				if (inside && _state != STATE_INSIDE)
				{
					_visibilityData.inFrustum = true;
					_state = STATE_INSIDE;
				}
				else if (!inside && _state != STATE_OUTSIDE)
				{
					_visibilityData.inFrustum = false;
					_state = STATE_OUTSIDE;
				}
			}
		}

		override public function clone() : AbstractController
		{
			var clone : MeshVisibilityController = new MeshVisibilityController();

			clone._visibilityData = _visibilityData.clone() as MeshVisibilityDataProvider;

			return clone;
		}
	}
}

Fix asdoc.

Fix asdoc.


package com.kaltura.delegates {
	import com.kaltura.commands.MultiRequest;
	import com.kaltura.commands.QueuedRequest;
	import com.kaltura.config.KalturaConfig;
	import com.kaltura.errors.KalturaError;
	import com.kaltura.net.KalturaCall;

	import flash.utils.getDefinitionByName;
	import flash.utils.getQualifiedClassName;

	public class QueuedRequestDelegate extends WebDelegateBase {

		public function QueuedRequestDelegate(call:KalturaCall = null, config:KalturaConfig = null) {
			super(call, config);
		}

		override public function parse( result : XML ) : *
		{
			var resArr : Array = new Array();
			var resInd:int = 0;	// index for scanning result
			for ( var i:int=0; i<(call as QueuedRequest).calls.length; i++ )
			{
				var callClassName : String = getQualifiedClassName( (call as QueuedRequest).calls[i] );
				var commandName : String = callClassName.split("::")[1];
				var packageArr : Array =  (callClassName.split("::")[0]).split(".");
				var importFrom : String = packageArr[packageArr.length-1];

				var clsName : String ;
				var cls : Class;
				var myInst : Object;

				if (commandName == "MultiRequest") {
					clsName = "com.kaltura.delegates.MultiRequestDelegate";
					cls = getDefinitionByName( clsName ) as Class;
					myInst = new cls(null , null);
					// set the call after the constructor, so we don't fire execute()
					myInst.call = (call as QueuedRequest).calls[i];
				}
				else {
					clsName = "com.kaltura.delegates."+importFrom+"."+ commandName +"Delegate"; //'com.kaltura.delegates.session.SessionStartDelegate'
					cls = getDefinitionByName( clsName ) as Class;//(') as Class;
					myInst = new cls(null , null);
				}

				//build the result as a regular result
				var xml : String = "<result><result>";
				if (commandName == "MultiRequest") {
					// add as many items as the multirequest had
					var nActions:int = ((call as QueuedRequest).calls[i] as MultiRequest).actions.length;
					for (var j:int = 0; j<nActions ; j++) {
						xml += result.result.item[j + resInd].toXMLString();
					}
					// skip to the result of the next call that wasn't part of the MR:
					resInd+= nActions-1;
				}
				else {
					xml += result.result.item[resInd].children().toString();
					resInd++;
				}
				xml +="</result></result>";

				// add the item or a matching error:
				var kErr:KalturaError = validateKalturaResponse(xml);

				if (kErr == null) {
					var res : XML = new XML(xml);
					try {
						var obj : Object = (myInst as WebDelegateBase).parse( res );
						resArr.push( obj );
					} catch (e:Error) {
						kErr = new KalturaError();
						kErr.errorCode = String(e.errorID);
						kErr.errorMsg = e.message;
						resArr.push( kErr );
					}
				}
				else {
					resArr.push(kErr);
				}
			}

			return resArr;
		}

	}
}

package com.kaltura.delegates {
	import com.kaltura.commands.MultiRequest;
	import com.kaltura.commands.QueuedRequest;
	import com.kaltura.config.KalturaConfig;
	import com.kaltura.errors.KalturaError;
	import com.kaltura.net.KalturaCall;

	import flash.utils.getDefinitionByName;
	import flash.utils.getQualifiedClassName;

	public class QueuedRequestDelegate extends WebDelegateBase {

		public function QueuedRequestDelegate(call:KalturaCall = null, config:KalturaConfig = null) {
			super(call, config);
		}

		override public function parse( result : XML ) : *
		{
			var resArr : Array = new Array();
			var resInd:int = 0;	// index for scanning result
			for ( var i:int=0; i<(call as QueuedRequest).calls.length; i++ )
			{
				var callClassName : String = getQualifiedClassName( (call as QueuedRequest).calls[i] );
				var commandName : String = callClassName.split("::")[1];
				var packageArr : Array =  (callClassName.split("::")[0]).split(".");
				var importFrom : String = packageArr[packageArr.length-1];

				var clsName : String ;
				var cls : Class;
				var myInst : Object;

				if (commandName == "MultiRequest") {
					clsName = "com.kaltura.delegates.MultiRequestDelegate";
					cls = getDefinitionByName( clsName ) as Class;
					myInst = new cls(null , null);
					// set the call after the constructor, so we don't fire execute()
					myInst.call = (call as QueuedRequest).calls[i];
				}
				else {
					clsName = "com.kaltura.delegates."+importFrom+"."+ commandName +"Delegate"; //'com.kaltura.delegates.session.SessionStartDelegate'
					cls = getDefinitionByName( clsName ) as Class;//(') as Class;
					myInst = new cls(null , null);
				}

				//build the result as a regular result
				var xml : String = "<result><result>";
				if (commandName == "MultiRequest") {
					// add as many items as the multirequest had
					var nActions:int = ((call as QueuedRequest).calls[i] as MultiRequest).actions.length;
					for (var j:int = 0; j<nActions ; j++) {
						xml += result.result.item[j + resInd].toXMLString();
					}
					// skip to the result of the next call that wasn't part of the MR:
					resInd+= nActions;
				}
				else {
					xml += result.result.item[resInd].children().toString();
					resInd++;
				}
				xml +="</result></result>";

				// add the item or a matching error:
				var kErr:KalturaError = validateKalturaResponse(xml);

				if (kErr == null) {
					var res : XML = new XML(xml);
					try {
						var obj : Object = (myInst as WebDelegateBase).parse( res );
						resArr.push( obj );
					} catch (e:Error) {
						kErr = new KalturaError();
						kErr.errorCode = String(e.errorID);
						kErr.errorMsg = e.message;
						resArr.push( kErr );
					}
				}
				else {
					resArr.push(kErr);
				}
			}

			return resArr;
		}

	}
}

index fix

index fix

git-svn-id: 8a2ccb88241e16c78017770bc38d91d6d5396a5a@58551 6b8eccd3-e8c5-4e7d-8186-e12b5326b719


package battlecode.client.viewer.render {
    import battlecode.client.viewer.MatchController;
    import battlecode.common.MapLocation;
    import battlecode.common.Team;
    import battlecode.common.TerrainTile;
    import battlecode.events.MatchEvent;
    import battlecode.world.GameMap;

    import flash.events.Event;
    import flash.events.MouseEvent;
    import flash.geom.ColorTransform;
    import flash.geom.Rectangle;

    import mx.containers.Canvas;
    import mx.core.UIComponent;

    public class DrawMap extends Canvas {
        private var controller:MatchController;
        private var origin:MapLocation;

        // various canvases for layering and quick toggling of features
        private var mapCanvas:UIComponent;
        private var gridCanvas:UIComponent;
        private var rubbleCanvas:UIComponent;
        private var partsCanvas:UIComponent;
        private var groundUnitCanvas:UIComponent;
        private var zombieUnitCanvas:UIComponent;

        // optimizations for caching
        private var lastRound:uint = 0;

        public function DrawMap(controller:MatchController) {
            super();

            this.controller = controller;
            this.controller.addEventListener(MatchEvent.ROUND_CHANGE, onRoundChange);
            this.controller.addEventListener(MatchEvent.MATCH_CHANGE, onMatchChange);
            this.addEventListener(Event.ENTER_FRAME, onEnterFrame);

            this.mapCanvas = new UIComponent();
            this.gridCanvas = new UIComponent();
            this.rubbleCanvas = new UIComponent();
            this.partsCanvas = new UIComponent();
            this.groundUnitCanvas = new UIComponent();
            this.zombieUnitCanvas = new UIComponent();

            this.mapCanvas.cacheAsBitmap = true;
            this.gridCanvas.cacheAsBitmap = true;

            this.addChild(mapCanvas);
            this.addChild(rubbleCanvas);
            this.addChild(partsCanvas);
            this.addChild(gridCanvas);
            this.addChild(groundUnitCanvas);
            this.addChild(zombieUnitCanvas);
        }

        ///////////////////////////////////////////////////////
        ////////////////// UTILITY METHODS ////////////////////
        ///////////////////////////////////////////////////////

        public function getMapWidth():uint {
            if (controller.match)
                return controller.match.getMap().getWidth() * RenderConfiguration.GRID_SIZE;
            return 0;
        }

        public function getMapHeight():uint {
            if (controller.match)
                return controller.match.getMap().getHeight() * RenderConfiguration.GRID_SIZE + 5;
            return 0;
        }

        private function getGridSize():Number {
            return RenderConfiguration.getGridSize();
        }

        ///////////////////////////////////////////////////////
        ////////////////// DRAWING METHODS ////////////////////
        ///////////////////////////////////////////////////////

        public function redrawAll():void {
            drawMap();
            drawGridlines();
            drawUnits();
            drawRubble();
            drawParts();

            var o:DrawObject;

            for each (o in controller.currentState.getGroundRobots()) {
                o.draw(true);
            }

            for each (o in controller.currentState.getZombieRobots()) {
                o.draw(true);
            }

            this.scrollRect = new Rectangle(this.mapCanvas.x, this.mapCanvas.y,
                    getMapWidth() * RenderConfiguration.getScalingFactor(),
                    getMapHeight() * RenderConfiguration.getScalingFactor());
        }

        private function drawMap():void {
            var i:uint, j:uint, tile:TerrainTile;
            var map:GameMap = controller.match.getMap();
            var terrain:Array = map.getTerrainTiles();
            var colorTransform:ColorTransform, scalar:uint;
            var g:Number = getGridSize();

            origin = map.getOrigin();

            this.mapCanvas.graphics.clear();

            // draw terrain
            for (i = 0; i < map.getHeight(); i++) {
                for (j = 0; j < map.getWidth(); j++) {
                    tile = terrain[i][j] as TerrainTile;
                    if (tile.getType() == TerrainTile.LAND) {
                        scalar = 0xFF * 0.9;
                        colorTransform = new ColorTransform(0, 0, 0, 1, scalar, scalar, scalar, 0);
                        this.mapCanvas.graphics.beginFill(colorTransform.color, 1.0);
                        this.mapCanvas.graphics.drawRect(j * g, i * g, g, g);
                        this.mapCanvas.graphics.endFill();
                    } else {
                        colorTransform = new ColorTransform(0, 0, 0, 1, 0x00, 0x00, 0x99, 0);
                        this.mapCanvas.graphics.beginFill(colorTransform.color, 1.0);
                        this.mapCanvas.graphics.drawRect(j * g, i * g, g, g);
                        this.mapCanvas.graphics.endFill();
                    }
                }
            }

            // draw void outlines
            for (i = 0; i < map.getHeight(); i++) {
                for (j = 0; j < map.getWidth(); j++) {
                    tile = terrain[i][j] as TerrainTile;
                    if (tile.getType() == TerrainTile.VOID) {
                        this.mapCanvas.graphics.lineStyle(2, 0xFFFFFF);
                        if (i > 0 && terrain[i-1][j].getType() == TerrainTile.LAND) {
                            this.mapCanvas.graphics.moveTo(j * g, i * g);
                            this.mapCanvas.graphics.lineTo((j + 1) * g, i * g);
                        }
                        if (j > 0 && terrain[i][j-1].getType() == TerrainTile.LAND) {
                            this.mapCanvas.graphics.moveTo(j * g, i * g);
                            this.mapCanvas.graphics.lineTo(j * g, (i + 1) * g);
                        }
                        if (i < map.getHeight() - 1 && terrain[i+1][j].getType() == TerrainTile.LAND) {
                            this.mapCanvas.graphics.moveTo(j * g, (i + 1) * g);
                            this.mapCanvas.graphics.lineTo((j + 1) * g, (i + 1) * g);
                        }
                        if (j < map.getWidth() - 1 && terrain[i][j+1].getType() == TerrainTile.LAND) {
                            this.mapCanvas.graphics.moveTo((j + 1) * g, i * g);
                            this.mapCanvas.graphics.lineTo((j + 1) * g, (i + 1) * g);
                        }
                        this.mapCanvas.graphics.lineStyle();
                    }
                }
            }
        }

        private function drawGridlines():void {
            var i:uint, j:uint;
            var map:GameMap = controller.match.getMap();

            this.gridCanvas.graphics.clear();
            for (i = 0; i < map.getHeight(); i++) {
                for (j = 0; j < map.getWidth(); j++) {
                    this.gridCanvas.graphics.lineStyle(.5, 0x999999, 0.3);
                    this.gridCanvas.graphics.drawRect(j * getGridSize(), i * getGridSize(), getGridSize(), getGridSize());
                }
            }
        }

        private function drawRubble():void {
            var terrain:Array = controller.match.getMap().getTerrainTiles();
            var rubble:Array = controller.currentState.getRubble();
            var i:uint, j:uint;

            this.rubbleCanvas.graphics.clear();
            this.rubbleCanvas.graphics.lineStyle();
            var g:Number = getGridSize();
            for (i = 0; i < rubble.length; i++) {
                for (j = 0; j < rubble[i].length; j++) {
                    var tile:TerrainTile = terrain[i][j] as TerrainTile;
                    if (tile.getType() == TerrainTile.VOID) {
                        continue;
                    }

                    var density:Number = Math.min(1, rubble[i][j] / 100);
                    if (density <= .5) {
                        continue;
                    }

                    var color:uint = density < 1 ? 0x999999 : 0x000000;
                    this.rubbleCanvas.graphics.beginFill(color, 1.0);
                    this.rubbleCanvas.graphics.drawRect(j * g, i * g, g, g);
                    this.rubbleCanvas.graphics.endFill();
                }
            }
        }

        private function drawParts():void {
            var terrain:Array = controller.match.getMap().getTerrainTiles();
            var parts:Array = controller.currentState.getParts();
            var i:uint, j:uint;

            this.partsCanvas.graphics.clear();
            this.partsCanvas.graphics.lineStyle();
            var g:Number = getGridSize();
            for (i = 0; i < parts.length; i++) {
                for (j = 0; j < parts[i].length; j++) {
                    var tile:TerrainTile = terrain[i][j] as TerrainTile;
                    if (tile.getType() == TerrainTile.VOID) {
                        continue;
                    }

                    if (parts[i][j] <= 0) {
                        continue;
                    }

                    var density:Number = Math.max(.25, Math.min(1, parts[i][j] / 200));
                    this.partsCanvas.graphics.beginFill(0xCC33CC, 0.7);
                    this.partsCanvas.graphics.drawCircle(j * g + g / 2, i * g + g / 2, density * g / 2);
                    this.partsCanvas.graphics.endFill();
                }
            }
        }

        private function drawUnits():void {
            var loc:MapLocation, i:uint, j:uint, robot:DrawRobot;
            var groundRobots:Object = controller.currentState.getGroundRobots();
            var zombieRobots:Object = controller.currentState.getZombieRobots();
            var g:Number = getGridSize();

            while (groundUnitCanvas.numChildren > 0)
                groundUnitCanvas.removeChildAt(0);

            while (zombieUnitCanvas.numChildren > 0)
                zombieUnitCanvas.removeChildAt(0);

            for each (robot in groundRobots) {
                loc = robot.getLocation();
                j = (loc.getX() - origin.getX());
                i = (loc.getY() - origin.getY());
                robot.x = j * g + g / 2;
                robot.y = i * g + g / 2;
                robot.addEventListener(MouseEvent.CLICK, onRobotSelect, false, 0, true);
                groundUnitCanvas.addChild(robot);
                robot.draw();
                robot.x += robot.getDrawX();
                robot.y += robot.getDrawY();
            }

            for each (robot in zombieRobots) {
                loc = robot.getLocation();
                j = (loc.getX() - origin.getX());
                i = (loc.getY() - origin.getY());
                robot.x = j * g + g / 2;
                robot.y = i * g + g / 2;
                robot.addEventListener(MouseEvent.CLICK, onRobotSelect, false, 0, true);
                zombieUnitCanvas.addChild(robot);
                robot.draw();
                robot.x += robot.getDrawX();
                robot.y += robot.getDrawY();
            }
        }

        private function updateUnits():void {
            var loc:MapLocation, i:uint, j:uint, robot:DrawRobot;
            var groundRobots:Object = controller.currentState.getGroundRobots();
            var zombieRobots:Object = controller.currentState.getZombieRobots();
            var g:Number = getGridSize();

            for each (robot in groundRobots) {
                loc = robot.getLocation();
                j = (loc.getX() - origin.getX());
                i = (loc.getY() - origin.getY());
                robot.x = j * g + g / 2;
                robot.y = i * g + g / 2;
                if (!robot.parent && robot.isAlive()) {
                    robot.addEventListener(MouseEvent.CLICK, onRobotSelect, false, 0, true);
                    groundUnitCanvas.addChild(robot);
                }
                robot.draw();
                robot.x += robot.getDrawX();
                robot.y += robot.getDrawY();
            }

            for each (robot in zombieRobots) {
                loc = robot.getLocation();
                j = (loc.getX() - origin.getX());
                i = (loc.getY() - origin.getY());
                robot.x = j * g + g / 2;
                robot.y = i * g + g / 2;
                if (!robot.parent && robot.isAlive()) {
                    robot.addEventListener(MouseEvent.CLICK, onRobotSelect, false, 0, true);
                    zombieUnitCanvas.addChild(robot);
                }
                robot.draw();
                robot.x += robot.getDrawX();
                robot.y += robot.getDrawY();
            }
        }

        ///////////////////////////////////////////////////////
        //////////////////// EVENT HANDLERS ///////////////////
        ///////////////////////////////////////////////////////

        private function onEnterFrame(e:Event):void {
            gridCanvas.visible = RenderConfiguration.showGridlines();
            groundUnitCanvas.visible = RenderConfiguration.showGround();
            zombieUnitCanvas.visible = RenderConfiguration.showZombies();
            partsCanvas.visible = RenderConfiguration.showParts();
            rubbleCanvas.visible = RenderConfiguration.showRubble();
        }

        private function onRoundChange(e:MatchEvent):void {
            if (e.currentRound < lastRound) {
                drawUnits();
            }
            updateUnits();
            drawRubble();
            drawParts();

            lastRound = e.currentRound;

            this.visible = (e.currentRound != 0);
        }

        private function onMatchChange(e:MatchEvent):void {
            redrawAll();
        }

        private function onRobotSelect(e:MouseEvent):void {
            var x:Number, y:Number;

            if (RenderConfiguration.isTournament())
                return;

            if (controller.selectedRobot && controller.selectedRobot.parent) {
                controller.selectedRobot.setSelected(false);
                x = controller.selectedRobot.x;
                y = controller.selectedRobot.y;
                controller.selectedRobot.draw(true);
                controller.selectedRobot.x = x;
                controller.selectedRobot.y = y;
            }

            var robot:DrawRobot = e.currentTarget as DrawRobot;
            robot.setSelected(true);
            x = robot.x;
            y = robot.y;
            robot.draw(true);
            robot.x = x;
            robot.y = y;
            controller.selectedRobot = robot;
        }

    }

}

package battlecode.client.viewer.render {
    import battlecode.client.viewer.MatchController;
    import battlecode.common.MapLocation;
    import battlecode.common.Team;
    import battlecode.common.TerrainTile;
    import battlecode.events.MatchEvent;
    import battlecode.world.GameMap;

    import flash.events.Event;
    import flash.events.MouseEvent;
    import flash.geom.ColorTransform;
    import flash.geom.Rectangle;

    import mx.containers.Canvas;
    import mx.core.UIComponent;

    public class DrawMap extends Canvas {
        private var controller:MatchController;
        private var origin:MapLocation;

        // various canvases for layering and quick toggling of features
        private var mapCanvas:UIComponent;
        private var gridCanvas:UIComponent;
        private var rubbleCanvas:UIComponent;
        private var partsCanvas:UIComponent;
        private var groundUnitCanvas:UIComponent;
        private var zombieUnitCanvas:UIComponent;

        // optimizations for caching
        private var lastRound:uint = 0;

        public function DrawMap(controller:MatchController) {
            super();

            this.controller = controller;
            this.controller.addEventListener(MatchEvent.ROUND_CHANGE, onRoundChange);
            this.controller.addEventListener(MatchEvent.MATCH_CHANGE, onMatchChange);
            this.addEventListener(Event.ENTER_FRAME, onEnterFrame);

            this.mapCanvas = new UIComponent();
            this.gridCanvas = new UIComponent();
            this.rubbleCanvas = new UIComponent();
            this.partsCanvas = new UIComponent();
            this.groundUnitCanvas = new UIComponent();
            this.zombieUnitCanvas = new UIComponent();

            this.mapCanvas.cacheAsBitmap = true;
            this.gridCanvas.cacheAsBitmap = true;

            this.addChild(mapCanvas);
            this.addChild(rubbleCanvas);
            this.addChild(partsCanvas);
            this.addChild(gridCanvas);
            this.addChild(groundUnitCanvas);
            this.addChild(zombieUnitCanvas);
        }

        ///////////////////////////////////////////////////////
        ////////////////// UTILITY METHODS ////////////////////
        ///////////////////////////////////////////////////////

        public function getMapWidth():uint {
            if (controller.match)
                return controller.match.getMap().getWidth() * RenderConfiguration.GRID_SIZE;
            return 0;
        }

        public function getMapHeight():uint {
            if (controller.match)
                return controller.match.getMap().getHeight() * RenderConfiguration.GRID_SIZE + 5;
            return 0;
        }

        private function getGridSize():Number {
            return RenderConfiguration.getGridSize();
        }

        ///////////////////////////////////////////////////////
        ////////////////// DRAWING METHODS ////////////////////
        ///////////////////////////////////////////////////////

        public function redrawAll():void {
            drawMap();
            drawGridlines();
            drawUnits();
            drawRubble();
            drawParts();

            var o:DrawObject;

            for each (o in controller.currentState.getGroundRobots()) {
                o.draw(true);
            }

            for each (o in controller.currentState.getZombieRobots()) {
                o.draw(true);
            }

            this.scrollRect = new Rectangle(this.mapCanvas.x, this.mapCanvas.y,
                    getMapWidth() * RenderConfiguration.getScalingFactor(),
                    getMapHeight() * RenderConfiguration.getScalingFactor());
        }

        private function drawMap():void {
            var i:uint, j:uint, tile:TerrainTile;
            var map:GameMap = controller.match.getMap();
            var terrain:Array = map.getTerrainTiles();
            var colorTransform:ColorTransform, scalar:uint;
            var g:Number = getGridSize();

            origin = map.getOrigin();

            this.mapCanvas.graphics.clear();

            // draw terrain
            for (i = 0; i < map.getHeight(); i++) {
                for (j = 0; j < map.getWidth(); j++) {
                    tile = terrain[i][j] as TerrainTile;
                    if (tile.getType() == TerrainTile.LAND) {
                        scalar = 0xFF * 0.9;
                        colorTransform = new ColorTransform(0, 0, 0, 1, scalar, scalar, scalar, 0);
                        this.mapCanvas.graphics.beginFill(colorTransform.color, 1.0);
                        this.mapCanvas.graphics.drawRect(j * g, i * g, g, g);
                        this.mapCanvas.graphics.endFill();
                    } else {
                        colorTransform = new ColorTransform(0, 0, 0, 1, 0x00, 0x00, 0x99, 0);
                        this.mapCanvas.graphics.beginFill(colorTransform.color, 1.0);
                        this.mapCanvas.graphics.drawRect(j * g, i * g, g, g);
                        this.mapCanvas.graphics.endFill();
                    }
                }
            }

            // draw void outlines
            for (i = 0; i < map.getHeight(); i++) {
                for (j = 0; j < map.getWidth(); j++) {
                    tile = terrain[i][j] as TerrainTile;
                    if (tile.getType() == TerrainTile.VOID) {
                        this.mapCanvas.graphics.lineStyle(2, 0xFFFFFF);
                        if (i > 0 && terrain[i-1][j].getType() == TerrainTile.LAND) {
                            this.mapCanvas.graphics.moveTo(j * g, i * g);
                            this.mapCanvas.graphics.lineTo((j + 1) * g, i * g);
                        }
                        if (j > 0 && terrain[i][j-1].getType() == TerrainTile.LAND) {
                            this.mapCanvas.graphics.moveTo(j * g, i * g);
                            this.mapCanvas.graphics.lineTo(j * g, (i + 1) * g);
                        }
                        if (i < map.getHeight() - 1 && terrain[i+1][j].getType() == TerrainTile.LAND) {
                            this.mapCanvas.graphics.moveTo(j * g, (i + 1) * g);
                            this.mapCanvas.graphics.lineTo((j + 1) * g, (i + 1) * g);
                        }
                        if (j < map.getWidth() - 1 && terrain[i][j+1].getType() == TerrainTile.LAND) {
                            this.mapCanvas.graphics.moveTo((j + 1) * g, i * g);
                            this.mapCanvas.graphics.lineTo((j + 1) * g, (i + 1) * g);
                        }
                        this.mapCanvas.graphics.lineStyle();
                    }
                }
            }
        }

        private function drawGridlines():void {
            var i:uint, j:uint;
            var map:GameMap = controller.match.getMap();

            this.gridCanvas.graphics.clear();
            for (i = 0; i < map.getHeight(); i++) {
                for (j = 0; j < map.getWidth(); j++) {
                    this.gridCanvas.graphics.lineStyle(.5, 0x999999, 0.3);
                    this.gridCanvas.graphics.drawRect(j * getGridSize(), i * getGridSize(), getGridSize(), getGridSize());
                }
            }
        }

        private function drawRubble():void {
            var terrain:Array = controller.match.getMap().getTerrainTiles();
            var rubble:Array = controller.currentState.getRubble();
            var i:uint, j:uint;

            this.rubbleCanvas.graphics.clear();
            this.rubbleCanvas.graphics.lineStyle();
            var g:Number = getGridSize();
            for (i = 0; i < rubble.length; i++) {
                for (j = 0; j < rubble[i].length; j++) {
                    var tile:TerrainTile = terrain[i][j] as TerrainTile;
                    if (tile.getType() == TerrainTile.VOID) {
                        continue;
                    }

                    var density:Number = Math.min(1, rubble[i][j] / 100);
                    if (density <= .5) {
                        continue;
                    }

                    var color:uint = density < 1 ? 0x999999 : 0x000000;
                    this.rubbleCanvas.graphics.beginFill(color, 1.0);
                    this.rubbleCanvas.graphics.drawRect(j * g, i * g, g, g);
                    this.rubbleCanvas.graphics.endFill();
                }
            }
        }

        private function drawParts():void {
            var terrain:Array = controller.match.getMap().getTerrainTiles();
            var parts:Array = controller.currentState.getParts();
            var i:uint, j:uint;

            this.partsCanvas.graphics.clear();
            this.partsCanvas.graphics.lineStyle();
            var g:Number = getGridSize();
            for (i = 0; i < parts.length; i++) {
                for (j = 0; j < parts[i].length; j++) {
                    var tile:TerrainTile = terrain[i][j] as TerrainTile;
                    if (tile.getType() == TerrainTile.VOID) {
                        continue;
                    }

                    if (parts[i][j] <= 0) {
                        continue;
                    }

                    var density:Number = Math.max(.25, Math.min(1, parts[i][j] / 200));
                    this.partsCanvas.graphics.beginFill(0xF48F4B, 1);
                    this.partsCanvas.graphics.drawCircle(j * g + g / 2, i * g + g / 2, density * g / 2);
                    this.partsCanvas.graphics.endFill();
                }
            }
        }

        private function drawUnits():void {
            var loc:MapLocation, i:uint, j:uint, robot:DrawRobot;
            var groundRobots:Object = controller.currentState.getGroundRobots();
            var zombieRobots:Object = controller.currentState.getZombieRobots();
            var g:Number = getGridSize();

            while (groundUnitCanvas.numChildren > 0)
                groundUnitCanvas.removeChildAt(0);

            while (zombieUnitCanvas.numChildren > 0)
                zombieUnitCanvas.removeChildAt(0);

            for each (robot in groundRobots) {
                loc = robot.getLocation();
                j = (loc.getX() - origin.getX());
                i = (loc.getY() - origin.getY());
                robot.x = j * g + g / 2;
                robot.y = i * g + g / 2;
                robot.addEventListener(MouseEvent.CLICK, onRobotSelect, false, 0, true);
                groundUnitCanvas.addChild(robot);
                robot.draw();
                robot.x += robot.getDrawX();
                robot.y += robot.getDrawY();
            }

            for each (robot in zombieRobots) {
                loc = robot.getLocation();
                j = (loc.getX() - origin.getX());
                i = (loc.getY() - origin.getY());
                robot.x = j * g + g / 2;
                robot.y = i * g + g / 2;
                robot.addEventListener(MouseEvent.CLICK, onRobotSelect, false, 0, true);
                zombieUnitCanvas.addChild(robot);
                robot.draw();
                robot.x += robot.getDrawX();
                robot.y += robot.getDrawY();
            }
        }

        private function updateUnits():void {
            var loc:MapLocation, i:uint, j:uint, robot:DrawRobot;
            var groundRobots:Object = controller.currentState.getGroundRobots();
            var zombieRobots:Object = controller.currentState.getZombieRobots();
            var g:Number = getGridSize();

            for each (robot in groundRobots) {
                loc = robot.getLocation();
                j = (loc.getX() - origin.getX());
                i = (loc.getY() - origin.getY());
                robot.x = j * g + g / 2;
                robot.y = i * g + g / 2;
                if (!robot.parent && robot.isAlive()) {
                    robot.addEventListener(MouseEvent.CLICK, onRobotSelect, false, 0, true);
                    groundUnitCanvas.addChild(robot);
                }
                robot.draw();
                robot.x += robot.getDrawX();
                robot.y += robot.getDrawY();
            }

            for each (robot in zombieRobots) {
                loc = robot.getLocation();
                j = (loc.getX() - origin.getX());
                i = (loc.getY() - origin.getY());
                robot.x = j * g + g / 2;
                robot.y = i * g + g / 2;
                if (!robot.parent && robot.isAlive()) {
                    robot.addEventListener(MouseEvent.CLICK, onRobotSelect, false, 0, true);
                    zombieUnitCanvas.addChild(robot);
                }
                robot.draw();
                robot.x += robot.getDrawX();
                robot.y += robot.getDrawY();
            }
        }

        ///////////////////////////////////////////////////////
        //////////////////// EVENT HANDLERS ///////////////////
        ///////////////////////////////////////////////////////

        private function onEnterFrame(e:Event):void {
            gridCanvas.visible = RenderConfiguration.showGridlines();
            groundUnitCanvas.visible = RenderConfiguration.showGround();
            zombieUnitCanvas.visible = RenderConfiguration.showZombies();
            partsCanvas.visible = RenderConfiguration.showParts();
            rubbleCanvas.visible = RenderConfiguration.showRubble();
        }

        private function onRoundChange(e:MatchEvent):void {
            if (e.currentRound < lastRound) {
                drawUnits();
            }
            updateUnits();
            drawRubble();
            drawParts();

            lastRound = e.currentRound;

            this.visible = (e.currentRound != 0);
        }

        private function onMatchChange(e:MatchEvent):void {
            redrawAll();
        }

        private function onRobotSelect(e:MouseEvent):void {
            var x:Number, y:Number;

            if (RenderConfiguration.isTournament())
                return;

            if (controller.selectedRobot && controller.selectedRobot.parent) {
                controller.selectedRobot.setSelected(false);
                x = controller.selectedRobot.x;
                y = controller.selectedRobot.y;
                controller.selectedRobot.draw(true);
                controller.selectedRobot.x = x;
                controller.selectedRobot.y = y;
            }

            var robot:DrawRobot = e.currentTarget as DrawRobot;
            robot.setSelected(true);
            x = robot.x;
            y = robot.y;
            robot.draw(true);
            robot.x = x;
            robot.y = y;
            controller.selectedRobot = robot;
        }

    }

}

change part colors to match java client /cc @kazimuth


////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2008-2013 Esri. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
////////////////////////////////////////////////////////////////////////////////
package com.esri.builder.model
{

import com.esri.ags.portal.Portal;
import com.esri.builder.supportClasses.URLUtil;

import flash.events.Event;
import flash.events.EventDispatcher;

import mx.utils.StringUtil;

public class PortalModel extends EventDispatcher
{
    //--------------------------------------------------------------------------
    //
    //  Constants
    //
    //--------------------------------------------------------------------------

    public static const DEFAULT_PORTAL_URL:String = "https://www.arcgis.com";

    private static var instance:PortalModel;

    //--------------------------------------------------------------------------
    //
    //  Class methods
    //
    //--------------------------------------------------------------------------

    public static function getInstance():PortalModel
    {
        if (!instance)
        {
            instance = new PortalModel(new SingletonEnforcer());
        }

        return instance;
    }

    public function cleanUpPortalURL(url:String):String
    {
        var cleanURL:String = url;
        cleanURL = StringUtil.trim(cleanURL);
        cleanURL = replacePreviousDefaultPortalURL(cleanURL);
        cleanURL = cleanURL.replace(/\/sharing\/content\/items\/?$/i, '');
        cleanURL = URLUtil.encode(cleanURL);
        return cleanURL;
    }

    //--------------------------------------------------------------------------
    //
    //  Constructor
    //
    //--------------------------------------------------------------------------

    public function PortalModel(singletonEnforcer:SingletonEnforcer)
    {
        if (!singletonEnforcer)
        {
            throw new Error("Class should not be instantiated - use getInstance()");
        }
    }

    //--------------------------------------------------------------------------
    //
    //  Properties
    //
    //--------------------------------------------------------------------------

    //----------------------------------
    //  portal
    //----------------------------------

    [Bindable]
    public var portal:Portal = new Portal();

    //----------------------------------
    //  portalURL
    //----------------------------------

    /**
     * Stripped value of the _userDefinedPortalURL
     * (without "/sharing/content/items")
     */
    private var _portalURL:String;

    [Bindable("userDefinedPortalURLChanged")]
    public function get portalURL():String
    {
        return _portalURL;
    }

    public function set portalURL(value:String):void
    {
        if (_userDefinedPortalURL != value)
        {
            _userDefinedPortalURL = cleanUpPortalURL(value);
            _portalURL = _userDefinedPortalURL;
            dispatchEvent(new Event("userDefinedPortalURLChanged"));
        }
    }

    private function replacePreviousDefaultPortalURL(url:String):String
    {
        const previousDefaultPortalURL:String = "http://www.arcgis.com/";
        return url.replace(previousDefaultPortalURL, DEFAULT_PORTAL_URL);
    }

    //----------------------------------
    //  userDefinedPortalURL
    //----------------------------------
    /**
     * The ArcGIS Portal URL set by the user
     */
    private var _userDefinedPortalURL:String;

    [Bindable(event="userDefinedPortalURLChanged")]
    public function get userDefinedPortalURL():String
    {
        return _userDefinedPortalURL;
    }
}
}

class SingletonEnforcer
{
}

////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2008-2013 Esri. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
////////////////////////////////////////////////////////////////////////////////
package com.esri.builder.model
{

import com.esri.ags.portal.Portal;
import com.esri.builder.supportClasses.URLUtil;

import flash.events.Event;
import flash.events.EventDispatcher;

import mx.utils.StringUtil;

public class PortalModel extends EventDispatcher
{
    //--------------------------------------------------------------------------
    //
    //  Constants
    //
    //--------------------------------------------------------------------------

    public static const DEFAULT_PORTAL_URL:String = "https://www.arcgis.com";

    private static var instance:PortalModel;

    //--------------------------------------------------------------------------
    //
    //  Class methods
    //
    //--------------------------------------------------------------------------

    public static function getInstance():PortalModel
    {
        if (!instance)
        {
            instance = new PortalModel(new SingletonEnforcer());
        }

        return instance;
    }

    public function cleanUpPortalURL(url:String):String
    {
        var cleanURL:String = url;
        cleanURL = StringUtil.trim(cleanURL);
        cleanURL = replacePreviousDefaultPortalURL(cleanURL);
        cleanURL = cleanURL.replace(/\/sharing\/content\/items\/?$/i, '');
        cleanURL = cleanURL.charAt(cleanURL.length - 1) == "/" ? cleanURL : cleanURL + "/";
        cleanURL = URLUtil.encode(cleanURL);
        return cleanURL;
    }

    //--------------------------------------------------------------------------
    //
    //  Constructor
    //
    //--------------------------------------------------------------------------

    public function PortalModel(singletonEnforcer:SingletonEnforcer)
    {
        if (!singletonEnforcer)
        {
            throw new Error("Class should not be instantiated - use getInstance()");
        }
    }

    //--------------------------------------------------------------------------
    //
    //  Properties
    //
    //--------------------------------------------------------------------------

    //----------------------------------
    //  portal
    //----------------------------------

    [Bindable]
    public var portal:Portal = new Portal();

    //----------------------------------
    //  portalURL
    //----------------------------------

    /**
     * Stripped value of the _userDefinedPortalURL
     * (without "/sharing/content/items")
     */
    private var _portalURL:String;

    [Bindable("userDefinedPortalURLChanged")]
    public function get portalURL():String
    {
        return _portalURL;
    }

    public function set portalURL(value:String):void
    {
        if (_userDefinedPortalURL != value)
        {
            _userDefinedPortalURL = cleanUpPortalURL(value);
            _portalURL = _userDefinedPortalURL;
            dispatchEvent(new Event("userDefinedPortalURLChanged"));
        }
    }

    private function replacePreviousDefaultPortalURL(url:String):String
    {
        const previousDefaultPortalURL:String = "http://www.arcgis.com/";
        return url.replace(previousDefaultPortalURL, DEFAULT_PORTAL_URL);
    }

    //----------------------------------
    //  userDefinedPortalURL
    //----------------------------------
    /**
     * The ArcGIS Portal URL set by the user
     */
    private var _userDefinedPortalURL:String;

    [Bindable(event="userDefinedPortalURLChanged")]
    public function get userDefinedPortalURL():String
    {
        return _userDefinedPortalURL;
    }
}
}

class SingletonEnforcer
{
}

Add trailing forward slash if missing from Portal URL.


package aerys.minko.render
{
	import aerys.minko.ns.minko_render;
	import aerys.minko.render.geometry.Geometry;
	import aerys.minko.render.geometry.stream.IVertexStream;
	import aerys.minko.render.geometry.stream.StreamUsage;
	import aerys.minko.render.geometry.stream.VertexStream;
	import aerys.minko.render.geometry.stream.format.VertexComponent;
	import aerys.minko.render.geometry.stream.format.VertexFormat;
	import aerys.minko.render.resource.Context3DResource;
	import aerys.minko.render.resource.IndexBuffer3DResource;
	import aerys.minko.render.resource.Program3DResource;
	import aerys.minko.render.resource.VertexBuffer3DResource;
	import aerys.minko.render.resource.texture.ITextureResource;
	import aerys.minko.render.shader.binding.IBinder;
	import aerys.minko.type.binding.DataBindings;
	import aerys.minko.type.enum.Blending;
	import aerys.minko.type.enum.ColorMask;
	import aerys.minko.type.enum.TriangleCulling;
	import aerys.minko.type.math.Matrix4x4;
	import aerys.minko.type.math.Vector4;

	import flash.display3D.Context3DProgramType;
	import flash.utils.Dictionary;

	/**
	 * DrawCall objects contain all the shader constants and buffer settings required
	 * to perform drawing operations using the Stage3D API.
	 * @author Jean-Marc Le Roux
	 *
	 */
	public final class DrawCall
	{
		use namespace minko_render;

		private static const PROGRAM_TYPE_VERTEX	: String			= Context3DProgramType.VERTEX;
		private static const PROGRAM_TYPE_FRAGMENT	: String			= Context3DProgramType.FRAGMENT;
		private static const NUM_TEXTURES			: uint				= 8;
		private static const NUM_VERTEX_BUFFERS		: uint				= 8;

		private static const TMP_VECTOR4			: Vector4			= new Vector4();
		private static const TMP_NUMBERS			: Vector.<Number>	= new Vector.<Number>(0xffff, true);
		private static const TMP_INTS				: Vector.<int>		= new Vector.<int>(0xffff, true);

		private var _bindings			: Object							= null;

		private var _vsInputComponents	: Vector.<VertexComponent>			= null;
		private var _vsInputIndices		: Vector.<uint>						= null;
		private var _cpuConstants		: Dictionary						= null;
		private var _vsConstants		: Vector.<Number>					= null;
		private var _fsConstants		: Vector.<Number>					= null;
		private var _fsTextures			: Vector.<ITextureResource>			= new Vector.<ITextureResource>(NUM_TEXTURES, true);

		// states
		private var _indexBuffer		: IndexBuffer3DResource				= null;
		private var _firstIndex			: int								= 0;
		private var _numTriangles		: int								= -1;

		private var _vertexBuffers		: Vector.<VertexBuffer3DResource>	= new Vector.<VertexBuffer3DResource>(NUM_VERTEX_BUFFERS, true);
		private var _numVertexComponents: uint								= 0;
		private var _offsets			: Vector.<int>						= new Vector.<int>(8, true);
		private var _formats			: Vector.<String>					= new Vector.<String>(8, true);

		private var _blending			: uint								= 0;
		private var _blendingSource		: String							= null;
		private var _blendingDest		: String							= null;

		private var _triangleCulling	: uint								= 0;
		private var _triangleCullingStr	: String							= null;

		private var _colorMask			: uint								= 0;
		private var _colorMaskR			: Boolean							= true;
		private var _colorMaskG			: Boolean							= true;
		private var _colorMaskB			: Boolean							= true;
		private var _colorMaskA			: Boolean							= true;

		private var _enabled			: Boolean							= true;

		private var _depth				: Number							= 0.;
        private var _center				: Vector4							= null;
		private var _invalidDepth		: Boolean							= false;
		private var _localToWorld		: Matrix4x4							= null;
		private var _worldToScreen		: Matrix4x4							= null;

        private var _changes            : Object                           = {};

		public function get vertexComponents() : Vector.<VertexComponent>
		{
			return _vsInputComponents;
		}

		public function get blending() : uint
		{
			return _blending;
		}
		public function set blending(value : uint) : void
		{
			_blending = value;
			_blendingSource = Blending.STRINGS[int(value & 0xffff)];
			_blendingDest	= Blending.STRINGS[int(value >>> 16)]
		}

		public function get triangleCulling() : uint
		{
			return _triangleCulling;
		}
		public function set triangleCulling(value : uint) : void
		{
			_triangleCulling = value;
			_triangleCullingStr = TriangleCulling.STRINGS[value];
		}

		public function get colorMask() : uint
		{
			return _colorMask;
		}
		public function set colorMask(value : uint) : void
		{
			_colorMask = value;
			_colorMaskR = (value & ColorMask.RED) != 0;
			_colorMaskG = (value & ColorMask.GREEN) != 0;
			_colorMaskB = (value & ColorMask.BLUE) != 0;
			_colorMaskA = (value & ColorMask.ALPHA) != 0;
		}

		public function get enabled() : Boolean
		{
			return _enabled;
		}
		public function set enabled(value : Boolean) : void
		{
			_enabled = value;

            if (value)
                for (var bindingName : String in _changes)
                {
                    setParameter(bindingName, _changes[bindingName]);
                    delete _changes[bindingName];
                }
		}

		public function get depth() : Number
		{
			if (_invalidDepth && _enabled)
			{
				_invalidDepth = false;

				if (_localToWorld != null && _worldToScreen != null)
				{
					var worldSpacePosition 	: Vector4	= _localToWorld.transformVector(
						_center, TMP_VECTOR4
					);
					var screenSpacePosition : Vector4 	= _worldToScreen.transformVector(
						worldSpacePosition, TMP_VECTOR4
					);

					_depth = screenSpacePosition.z / screenSpacePosition.w;
				}
			}

			return _depth;
		}

		public function configure(program		: Program3DResource,
								  geometry		: Geometry,
								  meshBindings	: DataBindings,
								  sceneBindings	: DataBindings,
								  computeDepth	: Boolean) : void
		{
//			if (_bindings != null)
//				unsetBindings(meshBindings, sceneBindings);

			_invalidDepth = computeDepth;

			setProgram(program);
			setGeometry(geometry);
			setBindings(meshBindings, sceneBindings, computeDepth);
		}

		public function unsetBindings(meshBindings	: DataBindings,
									  sceneBindings	: DataBindings) : void
		{
            _changes = {};

			for (var parameter : String in _bindings)
			{
                if (meshBindings.hasCallback(parameter, parameterChangedHandler))
				    meshBindings.removeCallback(parameter, parameterChangedHandler);
                else if (sceneBindings.hasCallback(parameter, parameterChangedHandler))
    				sceneBindings.removeCallback(parameter, parameterChangedHandler);
			}

			if (sceneBindings.hasCallback('worldToScreen', transformChangedHandler))
				sceneBindings.removeCallback('worldToScreen', transformChangedHandler);

			if (meshBindings.hasCallback('localToWorld', transformChangedHandler))
				meshBindings.removeCallback('localToWorld', transformChangedHandler);
		}

		private function setProgram(program : Program3DResource) : void
		{
			_cpuConstants		= new Dictionary();
			_vsConstants		= program._vsConstants.slice();
			_fsConstants		= program._fsConstants.slice();
			_fsTextures			= program._fsTextures.slice();
			_vsInputComponents	= program._vertexInputComponents;
			_vsInputIndices		= program._vertexInputIndices;
			_bindings			= program._bindings;

			triangleCulling		= TriangleCulling.FRONT;
			blending			= Blending.NORMAL;
			colorMask			= ColorMask.RGBA;
		}

		 /**
		  * Ask geometry to compute additional vertex data if needed for this drawcall.
		  */
		public function updateGeometry(geometry : Geometry) : void
		{
			var vertexFormat : VertexFormat	= geometry.format;

			if (_vsInputComponents.indexOf(VertexComponent.TANGENT) >= 0
				&& !vertexFormat.hasComponent(VertexComponent.TANGENT))
			{
				geometry.computeTangentSpace();
			}
			else if (_vsInputComponents.indexOf(VertexComponent.NORMAL) >= 0
                && !vertexFormat.hasComponent(VertexComponent.NORMAL))
			{
				geometry.computeNormals();
			}
		}

		/**
		 * Obtain a reference to each buffer and offset that apply() may possibly need.
		 *
		 */
		public function setGeometry(geometry : Geometry, frame : uint = 0) : void
		{
			if (!_vsInputComponents)
				return ;

			updateGeometry(geometry);

			_center = geometry.boundingSphere
				? geometry.boundingSphere.center
				: Vector4.ZERO;

			_numVertexComponents = _vsInputComponents.length;
			_indexBuffer		 = geometry.indexStream.resource;
			_firstIndex			 = geometry.firstIndex;
			_numTriangles		 = geometry.numTriangles;

			for (var i : uint = 0; i < _numVertexComponents; ++i)
			{
				var component : VertexComponent	= _vsInputComponents[i];
				var index	  : uint			= _vsInputIndices[i];

				if (component)
				{
					var vertexStream	: IVertexStream = geometry.getVertexStream(index + frame);
					var stream	 		: VertexStream	= vertexStream.getStreamByComponent(component);

					if (stream == null)
					{
						throw new Error(
							'Missing vertex component: \'' + component.toString() + '\'.'
						);
					}

					_vertexBuffers[i]	= stream.resource;
					_formats[i]			= component.nativeFormatString;
					_offsets[i]			= stream.format.getOffsetForComponent(component);
				}
			}
		}

		/**
		 * @fixme There is a bug here
		 * @fixme We splitted properties between scene and mesh
		 * @fixme it should be done on the compiler also to avoid this ugly hack
		 */
		private function setBindings(meshBindings	: DataBindings,
									 sceneBindings	: DataBindings,
									 computeDepth	: Boolean) : void
		{
			for (var parameter : String in _bindings)
			{
				meshBindings.addCallback(parameter, parameterChangedHandler);
                if (!sceneBindings.hasCallback(parameter, parameterChangedHandler))
    				sceneBindings.addCallback(parameter, parameterChangedHandler);

				if (meshBindings.propertyExists(parameter))
					setParameter(parameter, meshBindings.getProperty(parameter));
				else if (sceneBindings.propertyExists(parameter))
					setParameter(parameter, sceneBindings.getProperty(parameter));
			}

			if (computeDepth)
			{
				_worldToScreen = sceneBindings.getProperty('worldToScreen') as Matrix4x4;
				_localToWorld = meshBindings.getProperty('localToWorld') as Matrix4x4;

				sceneBindings.addCallback('worldToScreen', transformChangedHandler);
				meshBindings.addCallback('localToWorld', transformChangedHandler);

				_invalidDepth = true;
			}
		}

		public function apply(context : Context3DResource, previous : DrawCall) : uint
		{
			if (!_enabled)
				return 0;

			context.setColorMask(_colorMaskR, _colorMaskG, _colorMaskB, _colorMaskA)
				   .setProgramConstantsFromVector(PROGRAM_TYPE_VERTEX, 0, _vsConstants)
				   .setProgramConstantsFromVector(PROGRAM_TYPE_FRAGMENT, 0, _fsConstants);

			var numTextures	: uint	= _fsTextures.length;
			var maxTextures	: uint	= previous ? previous._fsTextures.length : NUM_TEXTURES;
			var maxBuffers	: uint	= previous ? previous._numVertexComponents : NUM_VERTEX_BUFFERS;
			var i 			: uint 	= 0;

			// setup textures
			for (i = 0; i < numTextures; ++i)
			{
				context.setTextureAt(
					i,
					(_fsTextures[i] as ITextureResource).getTexture(context)
				);
			}

			while (i < maxTextures)
				context.setTextureAt(i++, null);

			// setup buffers
			for (i = 0; i < _numVertexComponents; ++i)
			{
				context.setVertexBufferAt(
					i,
					(_vertexBuffers[i] as VertexBuffer3DResource).getVertexBuffer3D(context),
					_offsets[i],
					_formats[i]
				);
			}

			while (i < maxBuffers)
				context.setVertexBufferAt(i++, null);

			// draw triangles
			context.drawTriangles(
				_indexBuffer.getIndexBuffer3D(context),
				_firstIndex,
				_numTriangles
			);

			return _numTriangles == -1 ? _indexBuffer.numIndices / 3 : _numTriangles;
		}

		public function setParameter(name : String, value : Object) : void
		{
			if (!_enabled)
            {
                _changes[name] = value;

				return ;
            }

			var binding : IBinder = _bindings[name] as IBinder;

			if (binding != null)
				binding.set(_cpuConstants, _vsConstants, _fsConstants, _fsTextures, value);
		}

		private function parameterChangedHandler(dataBindings	: DataBindings,
												 property		: String,
												 oldValue		: Object,
												 newValue		: Object) : void
		{
			newValue !== null && setParameter(property, newValue);
		}

		private function transformChangedHandler(bindings 	: DataBindings,
												 property 	: String,
												 oldValue	: Matrix4x4,
												 newValue 	: Matrix4x4) : void
		{
			if (property == 'worldToScreen')
				_worldToScreen = newValue;
			else if (property == 'localToWorld')
				_localToWorld = newValue;

			_invalidDepth = true;
		}
	}
}

package aerys.minko.render
{
	import aerys.minko.ns.minko_render;
	import aerys.minko.render.geometry.Geometry;
	import aerys.minko.render.geometry.stream.IVertexStream;
	import aerys.minko.render.geometry.stream.StreamUsage;
	import aerys.minko.render.geometry.stream.VertexStream;
	import aerys.minko.render.geometry.stream.format.VertexComponent;
	import aerys.minko.render.geometry.stream.format.VertexFormat;
	import aerys.minko.render.resource.Context3DResource;
	import aerys.minko.render.resource.IndexBuffer3DResource;
	import aerys.minko.render.resource.Program3DResource;
	import aerys.minko.render.resource.VertexBuffer3DResource;
	import aerys.minko.render.resource.texture.ITextureResource;
	import aerys.minko.render.shader.binding.IBinder;
	import aerys.minko.type.binding.DataBindings;
	import aerys.minko.type.enum.Blending;
	import aerys.minko.type.enum.ColorMask;
	import aerys.minko.type.enum.TriangleCulling;
	import aerys.minko.type.math.Matrix4x4;
	import aerys.minko.type.math.Vector4;

	import flash.display3D.Context3DProgramType;
	import flash.utils.Dictionary;

	/**
	 * DrawCall objects contain all the shader constants and buffer settings required
	 * to perform drawing operations using the Stage3D API.
	 * @author Jean-Marc Le Roux
	 *
	 */
	public final class DrawCall
	{
		use namespace minko_render;

		private static const PROGRAM_TYPE_VERTEX	: String			= Context3DProgramType.VERTEX;
		private static const PROGRAM_TYPE_FRAGMENT	: String			= Context3DProgramType.FRAGMENT;
		private static const NUM_TEXTURES			: uint				= 8;
		private static const NUM_VERTEX_BUFFERS		: uint				= 8;

		private static const TMP_VECTOR4			: Vector4			= new Vector4();
		private static const TMP_NUMBERS			: Vector.<Number>	= new Vector.<Number>(0xffff, true);
		private static const TMP_INTS				: Vector.<int>		= new Vector.<int>(0xffff, true);

		private var _bindings			: Object							= null;

		private var _vsInputComponents	: Vector.<VertexComponent>			= null;
		private var _vsInputIndices		: Vector.<uint>						= null;
		private var _cpuConstants		: Dictionary						= null;
		private var _vsConstants		: Vector.<Number>					= null;
		private var _fsConstants		: Vector.<Number>					= null;
		private var _fsTextures			: Vector.<ITextureResource>			= new Vector.<ITextureResource>(NUM_TEXTURES, true);

		// states
		private var _indexBuffer		: IndexBuffer3DResource				= null;
		private var _firstIndex			: int								= 0;
		private var _numTriangles		: int								= -1;

		private var _vertexBuffers		: Vector.<VertexBuffer3DResource>	= new Vector.<VertexBuffer3DResource>(NUM_VERTEX_BUFFERS, true);
		private var _numVertexComponents: uint								= 0;
		private var _offsets			: Vector.<int>						= new Vector.<int>(8, true);
		private var _formats			: Vector.<String>					= new Vector.<String>(8, true);

		private var _blending			: uint								= 0;
		private var _blendingSource		: String							= null;
		private var _blendingDest		: String							= null;

		private var _triangleCulling	: uint								= 0;
		private var _triangleCullingStr	: String							= null;

		private var _colorMask			: uint								= 0;
		private var _colorMaskR			: Boolean							= true;
		private var _colorMaskG			: Boolean							= true;
		private var _colorMaskB			: Boolean							= true;
		private var _colorMaskA			: Boolean							= true;

		private var _enabled			: Boolean							= true;

		private var _depth				: Number							= 0.;
        private var _center				: Vector4							= null;
		private var _invalidDepth		: Boolean							= false;
		private var _localToWorld		: Matrix4x4							= null;
		private var _worldToScreen		: Matrix4x4							= null;

        private var _changes            : Object                           = {};

		public function get vertexComponents() : Vector.<VertexComponent>
		{
			return _vsInputComponents;
		}

		public function get blending() : uint
		{
			return _blending;
		}
		public function set blending(value : uint) : void
		{
			_blending = value;
			_blendingSource = Blending.STRINGS[int(value & 0xffff)];
			_blendingDest	= Blending.STRINGS[int(value >>> 16)]
		}

		public function get triangleCulling() : uint
		{
			return _triangleCulling;
		}
		public function set triangleCulling(value : uint) : void
		{
			_triangleCulling = value;
			_triangleCullingStr = TriangleCulling.STRINGS[value];
		}

		public function get colorMask() : uint
		{
			return _colorMask;
		}
		public function set colorMask(value : uint) : void
		{
			_colorMask = value;
			_colorMaskR = (value & ColorMask.RED) != 0;
			_colorMaskG = (value & ColorMask.GREEN) != 0;
			_colorMaskB = (value & ColorMask.BLUE) != 0;
			_colorMaskA = (value & ColorMask.ALPHA) != 0;
		}

		public function get enabled() : Boolean
		{
			return _enabled;
		}
		public function set enabled(value : Boolean) : void
		{
			_enabled = value;

            if (value)
                for (var bindingName : String in _changes)
                {
                    setParameter(bindingName, _changes[bindingName]);
                    delete _changes[bindingName];
                }
		}

		public function get depth() : Number
		{
			if (_invalidDepth && _enabled)
			{
				_invalidDepth = false;

				if (_localToWorld != null && _worldToScreen != null)
				{
					var worldSpacePosition 	: Vector4	= _localToWorld.transformVector(
						_center, TMP_VECTOR4
					);
					var screenSpacePosition : Vector4 	= _worldToScreen.transformVector(
						worldSpacePosition, TMP_VECTOR4
					);

					_depth = screenSpacePosition.z / screenSpacePosition.w;
				}
			}

			return _depth;
		}

		public function configure(program		: Program3DResource,
								  geometry		: Geometry,
								  meshBindings	: DataBindings,
								  sceneBindings	: DataBindings,
								  computeDepth	: Boolean) : void
		{
//			if (_bindings != null)
//				unsetBindings(meshBindings, sceneBindings);

			_invalidDepth = computeDepth;

			setProgram(program);
			setGeometry(geometry);
			setBindings(meshBindings, sceneBindings, computeDepth);
		}

		public function unsetBindings(meshBindings	: DataBindings,
									  sceneBindings	: DataBindings) : void
		{
            _changes = {};

			for (var parameter : String in _bindings)
			{
                if (meshBindings.hasCallback(parameter, parameterChangedHandler))
				    meshBindings.removeCallback(parameter, parameterChangedHandler);
                if (sceneBindings.hasCallback(parameter, parameterChangedHandler))
    				sceneBindings.removeCallback(parameter, parameterChangedHandler);
			}

			if (sceneBindings.hasCallback('worldToScreen', transformChangedHandler))
				sceneBindings.removeCallback('worldToScreen', transformChangedHandler);

			if (meshBindings.hasCallback('localToWorld', transformChangedHandler))
				meshBindings.removeCallback('localToWorld', transformChangedHandler);
		}

		private function setProgram(program : Program3DResource) : void
		{
			_cpuConstants		= new Dictionary();
			_vsConstants		= program._vsConstants.slice();
			_fsConstants		= program._fsConstants.slice();
			_fsTextures			= program._fsTextures.slice();
			_vsInputComponents	= program._vertexInputComponents;
			_vsInputIndices		= program._vertexInputIndices;
			_bindings			= program._bindings;

			triangleCulling		= TriangleCulling.FRONT;
			blending			= Blending.NORMAL;
			colorMask			= ColorMask.RGBA;
		}

		 /**
		  * Ask geometry to compute additional vertex data if needed for this drawcall.
		  */
		public function updateGeometry(geometry : Geometry) : void
		{
			var vertexFormat : VertexFormat	= geometry.format;

			if (_vsInputComponents.indexOf(VertexComponent.TANGENT) >= 0
				&& !vertexFormat.hasComponent(VertexComponent.TANGENT))
			{
				geometry.computeTangentSpace();
			}
			else if (_vsInputComponents.indexOf(VertexComponent.NORMAL) >= 0
                && !vertexFormat.hasComponent(VertexComponent.NORMAL))
			{
				geometry.computeNormals();
			}
		}

		/**
		 * Obtain a reference to each buffer and offset that apply() may possibly need.
		 *
		 */
		public function setGeometry(geometry : Geometry, frame : uint = 0) : void
		{
			if (!_vsInputComponents)
				return ;

			updateGeometry(geometry);

			_center = geometry.boundingSphere
				? geometry.boundingSphere.center
				: Vector4.ZERO;

			_numVertexComponents = _vsInputComponents.length;
			_indexBuffer		 = geometry.indexStream.resource;
			_firstIndex			 = geometry.firstIndex;
			_numTriangles		 = geometry.numTriangles;

			for (var i : uint = 0; i < _numVertexComponents; ++i)
			{
				var component : VertexComponent	= _vsInputComponents[i];
				var index	  : uint			= _vsInputIndices[i];

				if (component)
				{
					var vertexStream	: IVertexStream = geometry.getVertexStream(index + frame);
					var stream	 		: VertexStream	= vertexStream.getStreamByComponent(component);

					if (stream == null)
					{
						throw new Error(
							'Missing vertex component: \'' + component.toString() + '\'.'
						);
					}

					_vertexBuffers[i]	= stream.resource;
					_formats[i]			= component.nativeFormatString;
					_offsets[i]			= stream.format.getOffsetForComponent(component);
				}
			}
		}

		/**
		 * @fixme There is a bug here
		 * @fixme We splitted properties between scene and mesh
		 * @fixme it should be done on the compiler also to avoid this ugly hack
		 */
		private function setBindings(meshBindings	: DataBindings,
									 sceneBindings	: DataBindings,
									 computeDepth	: Boolean) : void
		{
			for (var parameter : String in _bindings)
			{
				meshBindings.addCallback(parameter, parameterChangedHandler);
                if (!sceneBindings.hasCallback(parameter, parameterChangedHandler))
    				sceneBindings.addCallback(parameter, parameterChangedHandler);

				if (meshBindings.propertyExists(parameter))
					setParameter(parameter, meshBindings.getProperty(parameter));
				else if (sceneBindings.propertyExists(parameter))
					setParameter(parameter, sceneBindings.getProperty(parameter));
			}

			if (computeDepth)
			{
				_worldToScreen = sceneBindings.getProperty('worldToScreen') as Matrix4x4;
				_localToWorld = meshBindings.getProperty('localToWorld') as Matrix4x4;

				sceneBindings.addCallback('worldToScreen', transformChangedHandler);
				meshBindings.addCallback('localToWorld', transformChangedHandler);

				_invalidDepth = true;
			}
		}

		public function apply(context : Context3DResource, previous : DrawCall) : uint
		{
			if (!_enabled)
				return 0;

			context.setColorMask(_colorMaskR, _colorMaskG, _colorMaskB, _colorMaskA)
				   .setProgramConstantsFromVector(PROGRAM_TYPE_VERTEX, 0, _vsConstants)
				   .setProgramConstantsFromVector(PROGRAM_TYPE_FRAGMENT, 0, _fsConstants);

			var numTextures	: uint	= _fsTextures.length;
			var maxTextures	: uint	= previous ? previous._fsTextures.length : NUM_TEXTURES;
			var maxBuffers	: uint	= previous ? previous._numVertexComponents : NUM_VERTEX_BUFFERS;
			var i 			: uint 	= 0;

			// setup textures
			for (i = 0; i < numTextures; ++i)
			{
				context.setTextureAt(
					i,
					(_fsTextures[i] as ITextureResource).getTexture(context)
				);
			}

			while (i < maxTextures)
				context.setTextureAt(i++, null);

			// setup buffers
			for (i = 0; i < _numVertexComponents; ++i)
			{
				context.setVertexBufferAt(
					i,
					(_vertexBuffers[i] as VertexBuffer3DResource).getVertexBuffer3D(context),
					_offsets[i],
					_formats[i]
				);
			}

			while (i < maxBuffers)
				context.setVertexBufferAt(i++, null);

			// draw triangles
			context.drawTriangles(
				_indexBuffer.getIndexBuffer3D(context),
				_firstIndex,
				_numTriangles
			);

			return _numTriangles == -1 ? _indexBuffer.numIndices / 3 : _numTriangles;
		}

		public function setParameter(name : String, value : Object) : void
		{
			if (!_enabled)
            {
                _changes[name] = value;

				return ;
            }

			var binding : IBinder = _bindings[name] as IBinder;

			if (binding != null)
				binding.set(_cpuConstants, _vsConstants, _fsConstants, _fsTextures, value);
		}

		private function parameterChangedHandler(dataBindings	: DataBindings,
												 property		: String,
												 oldValue		: Object,
												 newValue		: Object) : void
		{
			newValue !== null && setParameter(property, newValue);
		}

		private function transformChangedHandler(bindings 	: DataBindings,
												 property 	: String,
												 oldValue	: Matrix4x4,
												 newValue 	: Matrix4x4) : void
		{
			if (property == 'worldToScreen')
				_worldToScreen = newValue;
			else if (property == 'localToWorld')
				_localToWorld = newValue;

			_invalidDepth = true;
		}
	}
}

fix DrawCall.unsetBindings() to remove callbacks scene on both mesh AND scene bindings: for now there is only one binding table generated by the shader compiler, so both scene and mesh bindings have to be listened too and callbacks need to be removed from both


package aerys.minko.scene.controller.mesh.skinning
{
	import aerys.minko.Minko;
	import aerys.minko.ns.minko_math;
	import aerys.minko.render.Viewport;
	import aerys.minko.scene.controller.AbstractController;
	import aerys.minko.scene.controller.EnterFrameController;
	import aerys.minko.scene.controller.IRebindableController;
	import aerys.minko.scene.node.Group;
	import aerys.minko.scene.node.ISceneNode;
	import aerys.minko.scene.node.Mesh;
	import aerys.minko.scene.node.Scene;
	import aerys.minko.type.animation.SkinningMethod;
	import aerys.minko.type.binding.DataBindings;
	import aerys.minko.type.log.DebugLevel;
	import aerys.minko.type.math.Matrix4x4;

	import flash.display.BitmapData;
	import flash.geom.Matrix3D;
	import flash.utils.Dictionary;

	public final class SkinningController extends EnterFrameController implements IRebindableController
	{
		public static const MAX_NUM_INFLUENCES	: uint	= 8;
		public static const MAX_NUM_JOINTS		: uint	= 51;

		private var _skinningHelper		: AbstractSkinningHelper;
		private var _isDirty			: Boolean;

		private var _flattenSkinning	: Boolean;
		private var _numFps				: uint = 0;
		private var _updateStatic		: Boolean;

		private var _method				: uint;
		private var _bindShapeMatrix	: Matrix3D;
		private var _skeletonRoot		: Group;
		private var _joints				: Vector.<Group>;
		private var _invBindMatrices	: Vector.<Matrix3D>;

		private var _numVisibleTargets	: uint;

		public function get skinningMethod() : uint
		{
			return _method;
		}

		public function get skeletonRoot() : Group
		{
			return _skeletonRoot;
		}

		public function get joints() : Vector.<Group>
		{
			return _joints;
		}

		public function SkinningController(method			: uint,
										   skeletonRoot		: Group,
										   joints			: Vector.<Group>,
										   bindShape		: Matrix4x4,
										   invBindMatrices	: Vector.<Matrix4x4>,
										   flattenSkinning	: Boolean 	= false,
										   numFps			: uint 		= 0)
		{
			super(Mesh);

			if (!skeletonRoot)
				throw new Error('skeletonRoot cannot be null');

			_method				= method;
			_skeletonRoot		= skeletonRoot;
			_bindShapeMatrix	= bindShape.minko_math::_matrix;
			_isDirty			= false;

			_flattenSkinning	= flattenSkinning;
			_numFps				= numFps;
			_updateStatic		= false;

			initialize(joints, invBindMatrices);
		}

		private function initialize(joints			: Vector.<Group>,
									invBindMatrices	: Vector.<Matrix4x4>) : void
		{
			var numJoints : uint = joints.length;

			_joints	= joints.slice();

			_invBindMatrices	= new Vector.<Matrix3D>(numJoints, true);
			for (var jointId : uint = 0; jointId < numJoints; ++jointId)
				_invBindMatrices[jointId] = invBindMatrices[jointId].minko_math::_matrix;
		}

		override protected function targetAddedToScene(target : ISceneNode, scene : Scene) : void
		{
			super.targetAddedToScene(target, scene);

			if (getNumTargetsInScene(scene) == 1)
				subscribeToJoints();

			var mesh : Mesh = target as Mesh;

			mesh.bindings.addCallback('insideFrustum', meshVisibilityChangedHandler);

			if (!_skinningHelper)
			{
				var numInfluences 	: uint = AbstractSkinningHelper.getNumInfluences(
					mesh.geometry.format
				);

				if (_joints.length > MAX_NUM_JOINTS || numInfluences > MAX_NUM_INFLUENCES)
				{
					_method = SkinningMethod.SOFTWARE_MATRIX;
					Minko.log(
						DebugLevel.SKINNING,
						'Too many influences/joints: switching to SkinningMethod.SOFTWARE.'
					);
				}

				if (skinningMethod != SkinningMethod.SOFTWARE_MATRIX)
				{
					try
					{
						_skinningHelper = new HardwareSkinningHelper(
							_method,
							_bindShapeMatrix,
							_invBindMatrices,
							_flattenSkinning,
							_numFps,
							_skeletonRoot,
							_joints
						);
					}
					catch (e : Error)
					{
						Minko.log(
							DebugLevel.SKINNING,
							'Falling back to software skinning: ' + e.message,
							this
						);

						_method = SkinningMethod.SOFTWARE_MATRIX;
					}
				}

				_skinningHelper ||= new SoftwareSkinningHelper(
					_method,
					_bindShapeMatrix,
					_invBindMatrices,
					_flattenSkinning,
					_numFps,
					_skeletonRoot,
					_joints
				);
			}

			_skinningHelper.addMesh(mesh);

			_isDirty = true;
		}

		override protected function targetRemovedFromScene(target : ISceneNode, scene : Scene) : void
		{
			super.targetRemovedFromScene(target, scene);

			if (getNumTargetsInScene(scene) == 0)
				unsubscribeFromJoints();

			var mesh : Mesh = target as Mesh;

			mesh.bindings.removeCallback('insideFrustum', meshVisibilityChangedHandler);
			_skinningHelper.removeMesh(mesh);
		}

		private function jointLocalToWorldChangedHandler(node : Group, emitter : Matrix4x4) : void
		{
			_isDirty = true;
		}

		public function rebindDependencies(nodeMap			: Dictionary,
										   controllerMap	: Dictionary) : void
		{
			var numJoints	: uint = _joints.length;

			if (numTargets != 0)
				unsubscribeFromJoints();

			if (_joints.indexOf(_skeletonRoot) == -1 && nodeMap[_skeletonRoot])
				_skeletonRoot = nodeMap[_skeletonRoot];

			for (var jointId : uint = 0; jointId < numJoints; ++jointId)
				if (nodeMap[_joints[jointId]])
					_joints[jointId] = nodeMap[_joints[jointId]];

			if (numTargets != 0)
				subscribeToJoints();

			_isDirty		= true;
			_updateStatic	= true;
		}

		public function set numFps(value	: uint) : void
		{
			_numFps			= value;
			_updateStatic	= true;
		}

		public function set flattenSkinning(value	: Boolean) : void
		{
			_flattenSkinning = value;
			if (!_flattenSkinning)
			{
				_skinningHelper.clearStaticSkinning();
			}
			_updateStatic	= _flattenSkinning
		}

		private function subscribeToJoints() : void
		{
			var numJoints	: uint	= _joints.length;

			for (var jointId : uint = 0; jointId < numJoints; ++jointId)
				_joints[jointId].localToWorldTransformChanged.add(jointLocalToWorldChangedHandler);

			if (_joints.indexOf(_skeletonRoot) == -1)
				_skeletonRoot.localToWorldTransformChanged.add(jointLocalToWorldChangedHandler);
		}

		private function unsubscribeFromJoints() : void
		{
			var numJoints	: uint	= _joints.length;

			for (var jointId : uint = 0; jointId < numJoints; ++jointId)
				_joints[jointId].localToWorldTransformChanged.remove(jointLocalToWorldChangedHandler);

			if (_joints.indexOf(_skeletonRoot) == -1)
				_skeletonRoot.localToWorldTransformChanged.remove(jointLocalToWorldChangedHandler);
		}

		override protected function sceneEnterFrameHandler(scene		: Scene,
														   viewport		: Viewport,
														   destination	: BitmapData,
														   time			: Number) : void
		{
			if ((_flattenSkinning || _isDirty)
				&& _skinningHelper.numMeshes != 0)
			{
				_skinningHelper.update(
					_skeletonRoot,
					_joints,
					_updateStatic,
					_numFps
				);
				_isDirty		= false;
				_updateStatic	= false;
			}
		}

		override public function clone() : AbstractController
		{
			return new SkinningController(
				_method,
				_skeletonRoot,
				_joints,
				getBindShapeMatrix(),
				getInvBindMatrices(),
				_flattenSkinning,
				_numFps
			);
		}

		public function getInvBindMatrices() : Vector.<Matrix4x4>
		{
			var numMatrices		: uint					= _invBindMatrices.length;
			var invBindMatrices : Vector.<Matrix4x4>	= new Vector.<Matrix4x4>(numMatrices);

			for (var matrixId : uint = 0; matrixId < numMatrices; ++matrixId)
				invBindMatrices[matrixId] = new Matrix4x4().copyFromMatrix3D(
					_invBindMatrices[matrixId]
				);

			return invBindMatrices;
		}

		public function getBindShapeMatrix() : Matrix4x4
		{
			return new Matrix4x4().copyFromMatrix3D(_bindShapeMatrix);
		}

		private function meshVisibilityChangedHandler(bindings 	: DataBindings,
													  property	: String,
													  oldValue	: Boolean,
													  newValue	: Boolean) : void
		{
			if (newValue)
				_skinningHelper.addMesh(bindings.owner as Mesh);
			else
				_skinningHelper.removeMesh(bindings.owner as Mesh);
		}
	}
}

package aerys.minko.scene.controller.mesh.skinning
{
	import flash.display.BitmapData;
	import flash.geom.Matrix3D;
	import flash.utils.Dictionary;

	import aerys.minko.Minko;
	import aerys.minko.ns.minko_math;
	import aerys.minko.render.Viewport;
	import aerys.minko.scene.controller.AbstractController;
	import aerys.minko.scene.controller.EnterFrameController;
	import aerys.minko.scene.controller.IRebindableController;
	import aerys.minko.scene.node.Group;
	import aerys.minko.scene.node.ISceneNode;
	import aerys.minko.scene.node.Mesh;
	import aerys.minko.scene.node.Scene;
	import aerys.minko.type.animation.SkinningMethod;
	import aerys.minko.type.binding.DataBindings;
	import aerys.minko.type.log.DebugLevel;
	import aerys.minko.type.math.Matrix4x4;

	public final class SkinningController extends EnterFrameController implements IRebindableController
	{
		public static const MAX_NUM_INFLUENCES	: uint	= 8;
		public static const MAX_NUM_JOINTS		: uint	= 51;

		private var _skinningHelper		: AbstractSkinningHelper;
		private var _isDirty			: Boolean;

		private var _flattenSkinning	: Boolean;
		private var _numFps				: uint = 0;
		private var _updateStatic		: Boolean;

		private var _method				: uint;
		private var _bindShapeMatrix	: Matrix3D;
		private var _skeletonRoot		: Group;
		private var _joints				: Vector.<Group>;
		private var _invBindMatrices	: Vector.<Matrix3D>;

		private var _numVisibleTargets	: uint;

		public function get skinningMethod() : uint
		{
			return _method;
		}

		public function get skeletonRoot() : Group
		{
			return _skeletonRoot;
		}

		public function set skinningMethod(value : uint) : void
		{
			_method = value;

			for (var targetId : uint = 0; targetId < numTargets; targetId++)
			{
				var target : ISceneNode = getTarget(targetId);
				targetRemovedFromScene(target, target.scene);
				targetAddedToScene(target, target.scene);
			}
		}

		public function get joints() : Vector.<Group>
		{
			return _joints;
		}

		public function get numJoints() : uint
		{
			return _joints.length;
		}

		public function SkinningController(method			: uint,
										   skeletonRoot		: Group,
										   joints			: Vector.<Group>,
										   bindShape		: Matrix4x4,
										   invBindMatrices	: Vector.<Matrix4x4>,
										   flattenSkinning	: Boolean 	= false,
										   numFps			: uint 		= 0)
		{
			super(Mesh);

			if (!skeletonRoot)
				throw new Error('skeletonRoot cannot be null');

			_method				= method;
			_skeletonRoot		= skeletonRoot;
			_bindShapeMatrix	= bindShape.minko_math::_matrix;
			_isDirty			= false;

			_flattenSkinning	= flattenSkinning;
			_numFps				= numFps;
			_updateStatic		= false;

			initialize(joints, invBindMatrices);
		}

		private function initialize(joints			: Vector.<Group>,
									invBindMatrices	: Vector.<Matrix4x4>) : void
		{
			var numJoints : uint = joints.length;

			_joints	= joints.slice();

			_invBindMatrices	= new Vector.<Matrix3D>(numJoints, true);
			for (var jointId : uint = 0; jointId < numJoints; ++jointId)
				_invBindMatrices[jointId] = invBindMatrices[jointId].minko_math::_matrix;
		}

		override protected function targetAddedToScene(target : ISceneNode, scene : Scene) : void
		{
			super.targetAddedToScene(target, scene);

			if (getNumTargetsInScene(scene) == 1)
				subscribeToJoints();

			var mesh : Mesh = target as Mesh;

			mesh.bindings.addCallback('insideFrustum', meshVisibilityChangedHandler);

			if (!_skinningHelper)
			{
				var numInfluences 	: uint = AbstractSkinningHelper.getNumInfluences(
					mesh.geometry.format
				);

				if (_joints.length > MAX_NUM_JOINTS || numInfluences > MAX_NUM_INFLUENCES)
				{
					_method = SkinningMethod.SOFTWARE_MATRIX;
					Minko.log(
						DebugLevel.SKINNING,
						'Too many influences/joints: switching to SkinningMethod.SOFTWARE.'
					);
				}

				if (skinningMethod != SkinningMethod.SOFTWARE_MATRIX)
				{
					try
					{
						_skinningHelper = new HardwareSkinningHelper(
							_method,
							_bindShapeMatrix,
							_invBindMatrices,
							_flattenSkinning,
							_numFps,
							_skeletonRoot,
							_joints
						);
					}
					catch (e : Error)
					{
						Minko.log(
							DebugLevel.SKINNING,
							'Falling back to software skinning: ' + e.message,
							this
						);

						_method = SkinningMethod.SOFTWARE_MATRIX;
					}
				}

				_skinningHelper ||= new SoftwareSkinningHelper(
					_method,
					_bindShapeMatrix,
					_invBindMatrices,
					_flattenSkinning,
					_numFps,
					_skeletonRoot,
					_joints
				);
			}

			_skinningHelper.addMesh(mesh);

			_isDirty = true;
		}

		override protected function targetRemovedFromScene(target : ISceneNode, scene : Scene) : void
		{
			super.targetRemovedFromScene(target, scene);

			if (getNumTargetsInScene(scene) == 0)
				unsubscribeFromJoints();

			var mesh : Mesh = target as Mesh;

			mesh.bindings.removeCallback('insideFrustum', meshVisibilityChangedHandler);
			_skinningHelper.removeMesh(mesh);
		}

		private function jointLocalToWorldChangedHandler(node : Group, emitter : Matrix4x4) : void
		{
			_isDirty = true;
		}

		public function rebindDependencies(nodeMap			: Dictionary,
										   controllerMap	: Dictionary) : void
		{
			var numJoints	: uint = _joints.length;

			if (numTargets != 0)
				unsubscribeFromJoints();

			if (_joints.indexOf(_skeletonRoot) == -1 && nodeMap[_skeletonRoot])
				_skeletonRoot = nodeMap[_skeletonRoot];

			for (var jointId : uint = 0; jointId < numJoints; ++jointId)
				if (nodeMap[_joints[jointId]])
					_joints[jointId] = nodeMap[_joints[jointId]];

			if (numTargets != 0)
				subscribeToJoints();

			_isDirty		= true;
			_updateStatic	= true;
		}

		public function set numFps(value	: uint) : void
		{
			_numFps			= value;
			_updateStatic	= true;
		}

		public function set flattenSkinning(value	: Boolean) : void
		{
			_flattenSkinning = value;
			if (!_flattenSkinning)
			{
				_skinningHelper.clearStaticSkinning();
			}
			_updateStatic	= _flattenSkinning
		}

		private function subscribeToJoints() : void
		{
			var numJoints	: uint	= _joints.length;

			for (var jointId : uint = 0; jointId < numJoints; ++jointId)
				_joints[jointId].localToWorldTransformChanged.add(jointLocalToWorldChangedHandler);

			if (_joints.indexOf(_skeletonRoot) == -1)
				_skeletonRoot.localToWorldTransformChanged.add(jointLocalToWorldChangedHandler);
		}

		private function unsubscribeFromJoints() : void
		{
			var numJoints	: uint	= _joints.length;

			for (var jointId : uint = 0; jointId < numJoints; ++jointId)
				_joints[jointId].localToWorldTransformChanged.remove(jointLocalToWorldChangedHandler);

			if (_joints.indexOf(_skeletonRoot) == -1)
				_skeletonRoot.localToWorldTransformChanged.remove(jointLocalToWorldChangedHandler);
		}

		override protected function sceneEnterFrameHandler(scene		: Scene,
														   viewport		: Viewport,
														   destination	: BitmapData,
														   time			: Number) : void
		{
			if ((_flattenSkinning || _isDirty)
				&& _skinningHelper.numMeshes != 0)
			{
				_skinningHelper.update(
					_skeletonRoot,
					_joints,
					_updateStatic,
					_numFps
				);
				_isDirty		= false;
				_updateStatic	= false;
			}
		}

		override public function clone() : AbstractController
		{
			return new SkinningController(
				_method,
				_skeletonRoot,
				_joints,
				getBindShapeMatrix(),
				getInvBindMatrices(),
				_flattenSkinning,
				_numFps
			);
		}

		public function getInvBindMatrices() : Vector.<Matrix4x4>
		{
			var numMatrices		: uint					= _invBindMatrices.length;
			var invBindMatrices : Vector.<Matrix4x4>	= new Vector.<Matrix4x4>(numMatrices);

			for (var matrixId : uint = 0; matrixId < numMatrices; ++matrixId)
				invBindMatrices[matrixId] = new Matrix4x4().copyFromMatrix3D(
					_invBindMatrices[matrixId]
				);

			return invBindMatrices;
		}

		public function getBindShapeMatrix() : Matrix4x4
		{
			return new Matrix4x4().copyFromMatrix3D(_bindShapeMatrix);
		}

		private function meshVisibilityChangedHandler(bindings 	: DataBindings,
													  property	: String,
													  oldValue	: Boolean,
													  newValue	: Boolean) : void
		{
			if (newValue)
				_skinningHelper.addMesh(bindings.owner as Mesh);
			else
				_skinningHelper.removeMesh(bindings.owner as Mesh);
		}
	}
}

Add setter on skinningMethod

Add setter on skinningMethod