package aerys.minko.scene.controller.mesh
{
	import aerys.minko.ns.minko_math;
	import aerys.minko.render.geometry.Geometry;
	import aerys.minko.scene.controller.AbstractController;
	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.scene.node.camera.AbstractCamera;
	import aerys.minko.type.binding.DataBindings;
	import aerys.minko.type.enum.FrustumCulling;
	import aerys.minko.type.math.BoundingBox;
	import aerys.minko.type.math.BoundingSphere;
	import aerys.minko.type.math.Matrix4x4;
	import aerys.minko.type.math.Vector4;

	/**
	 * 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
	{
		use namespace minko_math;

		private static const TMP_VECTOR4		: Vector4	= new Vector4();

		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;
		private var _state			    : uint;
		private var _lastTest		    : int;
		private var _boundingBox	    : BoundingBox;
		private var _boundingSphere	    : BoundingSphere;

        private var _frustumCulling     : uint;
        private var _insideFrustum      : Boolean;
        private var _computedVisibility : Boolean;

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

		public function get insideFrustum() : Boolean
		{
			return _insideFrustum;
		}

        public function get computedVisibility() : Boolean
        {
            return frustumCulling == FrustumCulling.DISABLED || _computedVisibility;
        }

		public function MeshVisibilityController()
		{
			super(Mesh);

			initialize();
		}

		private function initialize() : void
		{
			_boundingBox = new BoundingBox(Vector4.ZERO, Vector4.ONE);
			_boundingSphere = new BoundingSphere(Vector4.ZERO, 0.);

            _state = STATE_UNDEFINED;

			targetAdded.add(targetAddedHandler);
			targetRemoved.add(targetRemovedHandler);
		}

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

			_mesh = target;

			target.added.add(addedHandler);
			target.removed.add(removedHandler);

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

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

			target.added.remove(addedHandler);
			target.removed.remove(removedHandler);

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

		private function addedHandler(mesh : Mesh, ancestor : Group) : void
		{
			var scene : Scene = mesh.scene;

			if (!scene)
				return ;

			scene.bindings.addCallback('worldToScreen', worldToScreenChangedHandler);

            meshLocalToWorldChangedHandler(mesh, mesh.getLocalToWorldTransform());
			mesh.localToWorldTransformChanged.add(meshLocalToWorldChangedHandler);
            mesh.visibilityChanged.add(visiblityChangedHandler);
            mesh.parent.computedVisibilityChanged.add(visiblityChangedHandler);
            mesh.removed.add(meshRemovedHandler);
		}

        private function meshRemovedHandler(mesh : Mesh, ancestor : Group) : void
        {
            if (!mesh.parent)
                ancestor.computedVisibilityChanged.remove(visiblityChangedHandler);
        }

		private function removedHandler(mesh : Mesh, ancestor : Group) : void
		{
			var scene : Scene = ancestor.scene;

			if (!scene)
				return ;

			scene.bindings.removeCallback('worldToScreen', worldToScreenChangedHandler);

			mesh.localToWorldTransformChanged.remove(meshLocalToWorldChangedHandler);
            mesh.visibilityChanged.remove(visiblityChangedHandler);
            mesh.removed.remove(meshRemovedHandler);
			if (mesh.parent)
				mesh.parent.computedVisibilityChanged.remove(visiblityChangedHandler);
		}

        private function visiblityChangedHandler(node : ISceneNode, visibility : Boolean) : void
        {
            _computedVisibility = _mesh.visible && _mesh.parent.computedVisibility
                && _insideFrustum;
            _mesh.computedVisibilityChanged.execute(_mesh, _computedVisibility);
        }

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

		private function meshLocalToWorldChangedHandler(mesh : Mesh, transform : Matrix4x4) : void
		{
			var geom 	: Geometry 	= _mesh.geometry;
			var culling	: uint		= _frustumCulling;

			if (!geom.boundingBox || !geom.boundingSphere || culling == FrustumCulling.DISABLED)
				return ;

			if (culling & FrustumCulling.BOX)
				transform.transformRawVectors(geom.boundingBox._vertices, _boundingBox._vertices);

			if (culling & FrustumCulling.SPHERE)
			{
				var center 	: Vector4 	= transform.transformVector(geom.boundingSphere.center);
				var scale 	: Vector4 	= transform.deltaTransformVector(Vector4.ONE);
				var radius	: Number	= geom.boundingSphere.radius * Math.max(
					Math.abs(scale.x), Math.abs(scale.y), Math.abs(scale.z)
				);

				_boundingSphere.update(center, radius);
			}

			testCulling();
		}

		private function testCulling() : void
		{
			var culling	: uint	= _frustumCulling;

			if (culling != FrustumCulling.DISABLED && _mesh && _mesh.geometry.boundingBox
                && _mesh.visible)
			{
				var camera : AbstractCamera = (_mesh.root as Scene).activeCamera;

				if (!camera)
					return ;

				_lastTest = camera.frustum.testBoundingVolume(
					_boundingSphere,
					_boundingBox,
					null,
					culling,
					_lastTest
				);

				var inside : Boolean = _lastTest == -1;

				if (inside && _state != STATE_INSIDE)
				{
					_insideFrustum = true;
                    _computedVisibility = _mesh.parent.computedVisibility && _mesh.visible;
                    _mesh.computedVisibilityChanged.execute(_mesh, computedVisibility);
					_state = STATE_INSIDE;
				}
				else if (!inside && _state != STATE_OUTSIDE)
				{
					_insideFrustum = false;
                    _computedVisibility = false;
                    _mesh.computedVisibilityChanged.execute(_mesh, false);
					_state = STATE_OUTSIDE;
				}
			}
		}

		override public function clone() : AbstractController
		{
			return new MeshVisibilityController();;
		}
	}
}

package aerys.minko.scene.controller.mesh
{
	import aerys.minko.ns.minko_math;
	import aerys.minko.render.geometry.Geometry;
	import aerys.minko.scene.controller.AbstractController;
	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.scene.node.camera.AbstractCamera;
	import aerys.minko.type.binding.DataBindings;
	import aerys.minko.type.enum.FrustumCulling;
	import aerys.minko.type.math.BoundingBox;
	import aerys.minko.type.math.BoundingSphere;
	import aerys.minko.type.math.Matrix4x4;
	import aerys.minko.type.math.Vector4;

	/**
	 * 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
	{
		use namespace minko_math;

		private static const TMP_VECTOR4		: Vector4	= new Vector4();

		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;
		private var _state			    : uint;
		private var _lastTest		    : int;
		private var _boundingBox	    : BoundingBox;
		private var _boundingSphere	    : BoundingSphere;

        private var _frustumCulling     : uint;
        private var _insideFrustum      : Boolean;
        private var _computedVisibility : Boolean;

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

		public function get insideFrustum() : Boolean
		{
			return _insideFrustum;
		}

        public function get computedVisibility() : Boolean
        {
            return _frustumCulling == FrustumCulling.DISABLED || _computedVisibility;
        }

		public function MeshVisibilityController()
		{
			super(Mesh);

			initialize();
		}

		private function initialize() : void
		{
			_boundingBox = new BoundingBox(Vector4.ZERO, Vector4.ONE);
			_boundingSphere = new BoundingSphere(Vector4.ZERO, 0.);

            _state = STATE_UNDEFINED;

			targetAdded.add(targetAddedHandler);
			targetRemoved.add(targetRemovedHandler);
		}

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

			_mesh = target;

			target.added.add(addedHandler);
			target.removed.add(removedHandler);

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

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

			target.added.remove(addedHandler);
			target.removed.remove(removedHandler);

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

		private function addedHandler(mesh : Mesh, ancestor : Group) : void
		{
			var scene : Scene = mesh.scene;

			if (!scene)
				return ;

			scene.bindings.addCallback('worldToScreen', worldToScreenChangedHandler);

            meshLocalToWorldChangedHandler(mesh, mesh.getLocalToWorldTransform());
			mesh.localToWorldTransformChanged.add(meshLocalToWorldChangedHandler);
            mesh.visibilityChanged.add(visiblityChangedHandler);
            mesh.parent.computedVisibilityChanged.add(visiblityChangedHandler);
            mesh.removed.add(meshRemovedHandler);
		}

        private function meshRemovedHandler(mesh : Mesh, ancestor : Group) : void
        {
            if (!mesh.parent)
                ancestor.computedVisibilityChanged.remove(visiblityChangedHandler);
        }

		private function removedHandler(mesh : Mesh, ancestor : Group) : void
		{
			var scene : Scene = ancestor.scene;

			if (!scene)
				return ;

			scene.bindings.removeCallback('worldToScreen', worldToScreenChangedHandler);

			mesh.localToWorldTransformChanged.remove(meshLocalToWorldChangedHandler);
            mesh.visibilityChanged.remove(visiblityChangedHandler);
            mesh.removed.remove(meshRemovedHandler);
			if (mesh.parent)
				mesh.parent.computedVisibilityChanged.remove(visiblityChangedHandler);
		}

        private function visiblityChangedHandler(node : ISceneNode, visibility : Boolean) : void
        {
            _computedVisibility = _mesh.visible && _mesh.parent.computedVisibility
                && _insideFrustum;
            _mesh.computedVisibilityChanged.execute(_mesh, _computedVisibility);
        }

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

		private function meshLocalToWorldChangedHandler(mesh : Mesh, transform : Matrix4x4) : void
		{
			var geom 	: Geometry 	= _mesh.geometry;
			var culling	: uint		= _frustumCulling;

			if (!geom.boundingBox || !geom.boundingSphere || culling == FrustumCulling.DISABLED)
				return ;

			if (culling & FrustumCulling.BOX)
				transform.transformRawVectors(geom.boundingBox._vertices, _boundingBox._vertices);

			if (culling & FrustumCulling.SPHERE)
			{
				var center 	: Vector4 	= transform.transformVector(geom.boundingSphere.center);
				var scale 	: Vector4 	= transform.deltaTransformVector(Vector4.ONE);
				var radius	: Number	= geom.boundingSphere.radius * Math.max(
					Math.abs(scale.x), Math.abs(scale.y), Math.abs(scale.z)
				);

				_boundingSphere.update(center, radius);
			}

			testCulling();
		}

		private function testCulling() : void
		{
			var culling	: uint	= _frustumCulling;

			if (culling != FrustumCulling.DISABLED && _mesh && _mesh.geometry.boundingBox
                && _mesh.visible)
			{
				var camera : AbstractCamera = (_mesh.root as Scene).activeCamera;

				if (!camera)
					return ;

				_lastTest = camera.frustum.testBoundingVolume(
					_boundingSphere,
					_boundingBox,
					null,
					culling,
					_lastTest
				);

				var inside : Boolean = _lastTest == -1;

				if (inside && _state != STATE_INSIDE)
				{
					_insideFrustum = true;
                    _computedVisibility = _mesh.parent.computedVisibility && _mesh.visible;
                    _mesh.computedVisibilityChanged.execute(_mesh, computedVisibility);
					_state = STATE_INSIDE;
				}
				else if (!inside && _state != STATE_OUTSIDE)
				{
					_insideFrustum = false;
                    _computedVisibility = false;
                    _mesh.computedVisibilityChanged.execute(_mesh, false);
					_state = STATE_OUTSIDE;
				}
			}
		}

		override public function clone() : AbstractController
		{
			return new MeshVisibilityController();;
		}
	}
}

use _frustumCulling private property directly instead of protected frustumCulling protected getter for better performances


//
// Flump - Copyright 2013 Flump Authors

package flump.display {

import flash.events.ProgressEvent;
import flash.utils.ByteArray;

import flump.executor.Executor;
import flump.executor.Future;
import flump.mold.LibraryMold;

import react.Signal;

/**
 * Loads zip files created by the flump exporter and parses them into Library instances.
 */
public class LibraryLoader
{
    /**
     * Loads a Library from the zip in the given bytes.
     *
     * @deprecated Use a new LibraryLoader with the builder pattern instead.
     *
     * @param bytes The bytes containing the zip
     *
     * @param executor The executor on which the loading should run. If not specified, it'll run on
     * a new single-use executor.
     *
     * @param scaleFactor the desired scale factor of the textures to load. If the Library contains
     * textures with multiple scale factors, loader will load the textures with the scale factor
     * closest to this value. If scaleFactor <= 0 (the default), Starling.contentScaleFactor will be
     * used.
     *
     * @return a Future to use to track the success or failure of loading the resources out of the
     * bytes. If the loading succeeds, the Future's onSuccess will fire with an instance of
     * Library. If it fails, the Future's onFail will fire with the Error that caused the
     * loading failure.
     */
    public static function loadBytes (bytes :ByteArray, executor :Executor=null,
        scaleFactor :Number=-1) :Future {
        return new LibraryLoader().setExecutor(executor).setScaleFactor(scaleFactor)
            .loadBytes(bytes);
    }

    /**
     * Loads a Library from the zip at the given url.
     *
     * @deprecated Use a new LibraryLoader with the builder pattern instead.
     *
     * @param url The url where the zip can be found
     *
     * @param executor The executor on which the loading should run. If not specified, it'll run on
     * a new single-use executor.
     *
     * @param scaleFactor the desired scale factor of the textures to load. If the Library contains
     * textures with multiple scale factors, loader will load the textures with the scale factor
     * closest to this value. If scaleFactor <= 0 (the default), Starling.contentScaleFactor will be
     * used.
     *
     * @return a Future to use to track the success or failure of loading the resources from the
     * url. If the loading succeeds, the Future's onSuccess will fire with an instance of
     * Library. If it fails, the Future's onFail will fire with the Error that caused the
     * loading failure.
     */
    public static function loadURL (url :String, executor :Executor=null,
        scaleFactor :Number=-1) :Future {
        return new LibraryLoader().setExecutor(executor).setScaleFactor(scaleFactor)
            .loadURL(url);
    }

    /**
     * Dispatched when a ProgressEvent is received on a URL load of a Zip archive.
     *
     * Signal parameters:
     *  * event :flash.events.ProgressEvent
     */
    public const urlLoadProgressed :Signal = new Signal(ProgressEvent);

    /**
     * Dispatched when a file is found in the Zip archive that is not recognized by Flump.
     *
     * Dispatched Object has the following named properties:
     *  * name :String - the filename in the archive
     *  * bytes :ByteArray - the content of the file
     */
    public const fileLoaded :Signal = new Signal(Object);

    /**
     * Dispatched when the library mold has been read from the archive.
     */
    public const libraryMoldLoaded :Signal = new Signal(LibraryMold);

    /**
     * Dispatched when the bytes for an ATF atlas have been read from the archive.
     *
     * Dispatched Object has the following named properties:
     *  * name :String - the filename of the atlas
     *  * bytes :ByteArray - the content of the atlas
     */
    public const atfAtlasLoaded :Signal = new Signal(Object);

    /**
     * Dispatched when a PNG atlas has been loaded and decoded from the archive. Changes made to
     * the loaded png in a signal listener will affect the final rendered texture.
     *
     * NOTE: If Starling is not configured to handle lost context (Starling.handleLostContext),
     * the Bitmap dispatched to this signal will be disposed immediately after the dispatch, and
     * will become useless.
     *
     * Dispatched Object has the following named properties:
     *  * atlas :AtlasMold - The loaded atlas.
     *  * image :LoadedImage - the decoded image.
     */
    public const pngAtlasLoaded :Signal = new Signal(Object);

    /**
     * Sets the executor instance to use with this loader.
     *
     * @param executor The executor on which the loading should run. If left null (the default),
     * it'll run on a new single-use executor.
     */
    public function setExecutor (executor :Executor) :LibraryLoader {
        _executor = executor;
        return this;
    }

    /**
     * Sets the scale factor value to use with this loader.
     *
     * @param scaleFactor the desired scale factor of the textures to load. If the Library contains
     * textures with multiple scale factors, loader will load the textures with the scale factor
     * closest to this value. If scaleFactor <= 0 (the default), Starling.contentScaleFactor will be
     * used.
     */
    public function setScaleFactor (scaleFactor :Number) :LibraryLoader {
        _scaleFactor = scaleFactor;
        return this;
    }

    public function get scaleFactor () :Number {
        return _scaleFactor;
    }

    /**
     * Sets the mip map generation for this loader.
     *
     * @param generateMipMaps If true (defaults to false), flump will instruct Starling to generate
     * mip maps for all loaded textures. Scaling will look better if mipmaps are enabled, but there
     * is a loading time and memory usage penalty.
     */
    public function setGenerateMipMaps (generateMipMaps :Boolean) :LibraryLoader {
        _generateMipMaps = generateMipMaps;
        return this;
    }

    public function get generateMipMaps () :Boolean {
        return _generateMipMaps;
    }

    /**
     * Sets the CreatorFactory instance used for this loader.
     */
    public function setCreatorFactory (factory :CreatorFactory) :LibraryLoader {
        _creatorFactory = factory;
        return this;
    }

    public function get creatorFactory () :CreatorFactory {
        if (_creatorFactory == null) {
            _creatorFactory = new CreatorFactoryImpl();
        }
        return _creatorFactory;
    }

    /**
     * Loads a Library from the zip in the given bytes, using the settings configured in this
     * loader.
     *
     * @param bytes The bytes containing the zip
     */
    public function loadBytes (bytes :ByteArray) :Future {
        return (_executor || new Executor(1)).submit(
            new Loader(bytes, this).load);
    }

    /**
     * Loads a Library from the zip at the given url, using the settings configured in this
     * loader.
     *
     * @param url The url where the zip can be found
     */
    public function loadURL (url :String) :Future {
        return (_executor || new Executor(1)).submit(
            new Loader(url, this).load);
    }

    /** @private */
    public static const LIBRARY_LOCATION :String = "library.json";
    /** @private */
    public static const MD5_LOCATION :String = "md5";
    /** @private */
    public static const VERSION_LOCATION :String = "version";

    /**
     * @private
     * The version produced and parsable by this version of the code. The version in a resources
     * zip must equal the version compiled into the parsing code for parsing to succeed.
     */
    public static const VERSION :String = "2";

    protected var _executor :Executor;
    protected var _scaleFactor :Number = -1;
    protected var _generateMipMaps :Boolean = false;
    protected var _creatorFactory :CreatorFactory;
}
}

//
// Flump - Copyright 2013 Flump Authors

package flump.display {

import flash.events.ProgressEvent;
import flash.utils.ByteArray;

import flump.executor.Executor;
import flump.executor.Future;
import flump.mold.LibraryMold;

import react.Signal;

/**
 * Loads zip files created by the flump exporter and parses them into Library instances.
 */
public class LibraryLoader
{
    /**
     * Loads a Library from the zip in the given bytes.
     *
     * @deprecated Use a new LibraryLoader with the builder pattern instead.
     *
     * @param bytes The bytes containing the zip
     *
     * @param executor The executor on which the loading should run. If not specified, it'll run on
     * a new single-use executor.
     *
     * @param scaleFactor the desired scale factor of the textures to load. If the Library contains
     * textures with multiple scale factors, loader will load the textures with the scale factor
     * closest to this value. If scaleFactor <= 0 (the default), Starling.contentScaleFactor will be
     * used.
     *
     * @return a Future to use to track the success or failure of loading the resources out of the
     * bytes. If the loading succeeds, the Future's onSuccess will fire with an instance of
     * Library. If it fails, the Future's onFail will fire with the Error that caused the
     * loading failure.
     */
    public static function loadBytes (bytes :ByteArray, executor :Executor=null,
        scaleFactor :Number=-1) :Future {
        return new LibraryLoader().setExecutor(executor).setScaleFactor(scaleFactor)
            .loadBytes(bytes);
    }

    /**
     * Loads a Library from the zip at the given url.
     *
     * @deprecated Use a new LibraryLoader with the builder pattern instead.
     *
     * @param url The url where the zip can be found
     *
     * @param executor The executor on which the loading should run. If not specified, it'll run on
     * a new single-use executor.
     *
     * @param scaleFactor the desired scale factor of the textures to load. If the Library contains
     * textures with multiple scale factors, loader will load the textures with the scale factor
     * closest to this value. If scaleFactor <= 0 (the default), Starling.contentScaleFactor will be
     * used.
     *
     * @return a Future to use to track the success or failure of loading the resources from the
     * url. If the loading succeeds, the Future's onSuccess will fire with an instance of
     * Library. If it fails, the Future's onFail will fire with the Error that caused the
     * loading failure.
     */
    public static function loadURL (url :String, executor :Executor=null,
        scaleFactor :Number=-1) :Future {
        return new LibraryLoader().setExecutor(executor).setScaleFactor(scaleFactor)
            .loadURL(url);
    }

    /**
     * Dispatched when a ProgressEvent is received on a URL load of a Zip archive.
     *
     * Signal parameters:
     *  * event :flash.events.ProgressEvent
     */
    public const urlLoadProgressed :Signal = new Signal(ProgressEvent);

    /**
     * Dispatched when a file is found in the Zip archive that is not recognized by Flump.
     *
     * Dispatched Object has the following named properties:
     *  * name :String - the filename in the archive
     *  * bytes :ByteArray - the content of the file
     */
    public const fileLoaded :Signal = new Signal(Object);

    /**
     * Dispatched when the library mold has been read from the archive.
     */
    public const libraryMoldLoaded :Signal = new Signal(LibraryMold);

    /**
     * Dispatched when the bytes for an ATF atlas have been read from the archive.
     *
     * Dispatched Object has the following named properties:
     *  * name :String - the filename of the atlas
     *  * bytes :ByteArray - the content of the atlas
     */
    public const atfAtlasLoaded :Signal = new Signal(Object);

    /**
     * Dispatched when a PNG atlas has been loaded and decoded from the archive. Changes made to
     * the loaded png in a signal listener will affect the final rendered texture.
     *
     * NOTE: If Starling is not configured to handle lost context (Starling.handleLostContext),
     * the Bitmap dispatched to this signal will be disposed immediately after the dispatch, and
     * will become useless.
     *
     * Dispatched Object has the following named properties:
     *  * atlas :AtlasMold - The loaded atlas.
     *  * image :LoadedImage - the decoded image.
     */
    public const pngAtlasLoaded :Signal = new Signal(Object);

    /**
     * Sets the executor instance to use with this loader.
     *
     * @param executor The executor on which the loading should run. If left null (the default),
     * it'll run on a new single-use executor.
     */
    public function setExecutor (executor :Executor) :LibraryLoader {
        _executor = executor;
        return this;
    }

    /**
     * Sets the scale factor value to use with this loader.
     *
     * @param scaleFactor the desired scale factor of the textures to load. If the Library contains
     * textures with multiple scale factors, loader will load the textures with the scale factor
     * closest to this value. If scaleFactor <= 0 (the default), Starling.contentScaleFactor will
     * be used.
     */
    public function setScaleFactor (scaleFactor :Number) :LibraryLoader {
        _scaleFactor = scaleFactor;
        return this;
    }

    public function get scaleFactor () :Number {
        return _scaleFactor;
    }

    /**
     * Sets the mip map generation for this loader.
     *
     * @param generateMipMaps If true (defaults to false), flump will instruct Starling to generate
     * mip maps for all loaded textures. Scaling will look better if mipmaps are enabled, but there
     * is a loading time and memory usage penalty.
     */
    public function setGenerateMipMaps (generateMipMaps :Boolean) :LibraryLoader {
        _generateMipMaps = generateMipMaps;
        return this;
    }

    public function get generateMipMaps () :Boolean {
        return _generateMipMaps;
    }

    /**
     * Sets the CreatorFactory instance used for this loader.
     */
    public function setCreatorFactory (factory :CreatorFactory) :LibraryLoader {
        _creatorFactory = factory;
        return this;
    }

    public function get creatorFactory () :CreatorFactory {
        if (_creatorFactory == null) {
            _creatorFactory = new CreatorFactoryImpl();
        }
        return _creatorFactory;
    }

    /**
     * Loads a Library from the zip in the given bytes, using the settings configured in this
     * loader.
     *
     * @param bytes The bytes containing the zip
     */
    public function loadBytes (bytes :ByteArray) :Future {
        return (_executor || new Executor(1)).submit(
            new Loader(bytes, this).load);
    }

    /**
     * Loads a Library from the zip at the given url, using the settings configured in this
     * loader.
     *
     * @param url The url where the zip can be found
     */
    public function loadURL (url :String) :Future {
        return (_executor || new Executor(1)).submit(
            new Loader(url, this).load);
    }

    /** @private */
    public static const LIBRARY_LOCATION :String = "library.json";
    /** @private */
    public static const MD5_LOCATION :String = "md5";
    /** @private */
    public static const VERSION_LOCATION :String = "version";

    /**
     * @private
     * The version produced and parsable by this version of the code. The version in a resources
     * zip must equal the version compiled into the parsing code for parsing to succeed.
     */
    public static const VERSION :String = "2";

    protected var _executor :Executor;
    protected var _scaleFactor :Number = -1;
    protected var _generateMipMaps :Boolean = false;
    protected var _creatorFactory :CreatorFactory;
}
}

Fix doc comments.

Fix doc comments.


package jp.promotal.ssplayer.starling {

	import flash.geom.Point;

	import jp.promotal.ssplayer.data.SSModel;
	import jp.promotal.ssplayer.data.SSPartAnime;
	import jp.promotal.ssplayer.data.SSProject;
	import jp.promotal.ssplayer.data.SSAnime;
	import jp.promotal.ssplayer.data.SSPart;

	import starling.display.DisplayObject;

	import starling.display.Sprite;
	import starling.events.Event;

	public class SSSPlayer extends Sprite {

		public static const VERSION:String = "0.0.1";

		private var project:SSProject;

		private var model:SSModel;
		private var _anime:SSAnime;
		public function get anime():SSAnime {
			return this._anime;
		}

		private var _currentFrame:Number;
		public function get currentFrame():Number {
			return this._currentFrame;
		}
		public function set currentFrame(value:Number):void {
			this._currentFrame = value;
			this.updateParts();
		}

		private var _partPlayers:Vector.<SSSPartPlayer>;
		public function get partPlayers():Vector.<SSSPartPlayer> {
			return this._partPlayers.concat();
		}

		private var _isPlaying:Boolean = false;
		public function get isPlaying():Boolean {
			return this._isPlaying;
		}
		public function set isPlaying(value:Boolean):void {
			if (value == this._isPlaying) {
				return;
			}
			if (value) {
				this.addEventListener(Event.ENTER_FRAME, this.onEnterFrame);
			} else {
				this.removeEventListener(Event.ENTER_FRAME, this.onEnterFrame);
			}
			this._isPlaying = value;
		}

		private var _loop:Boolean = true;
		public function get loop():Boolean {
			return this._loop;
		}
		public function set loop(value:Boolean):void {
			this._loop = value;
		}

		private var _withFlatten:Boolean = false;
		public function get withFlatten():Boolean {
			return this._withFlatten;
		}
		public function set withFlatten(value:Boolean):void {
			if (value == this._withFlatten) {
				return;
			}
			this._withFlatten = value;
			if (value) {
				this.flatten();
			} else {
				this.unflatten();
			}
		}

		public function set color(value:uint):void {
			for each (var partPlayer:SSSPartPlayer in this._partPlayers) {
				partPlayer.color = value;
			}
		}

		public function SSSPlayer(project:SSProject) {
			super();
			this.project = project;
		}

		public function setAnime(name:String):SSSPlayer {
			this.model = this.project.modelForAnime(name);
			this._anime = this.project.anime(name);
			this.prepareChildren();
			this._currentFrame = 0;
			this.updateParts();
			return this;
		}

		public function startAnime(name:String):SSSPlayer {
			this.setAnime(name);
			this.isPlaying = true;
			return this;
		}

		public function play():void {
			this.isPlaying = true;
		}

		public function stop():void {
			this.isPlaying = false;
		}

		private function onEnterFrame(event:Event):void {
			if (++this._currentFrame >= this._anime.frameCount) {
				if (this._loop) {
					this._currentFrame = 0;
				} else {
					this._currentFrame--;
					this.stop();
				}
				this.dispatchEventWith(Event.COMPLETE);
			}
			this.updateParts();
		}

		private function prepareChildren():void {
			this.removeChildren();
			this._partPlayers = new Vector.<SSSPartPlayer>();
			for each (var part:SSPart in this.model.parts) {
				var partAnime:SSPartAnime = this._anime.partAnimes[part.name];
				var parentPartPlayer:SSSPartPlayer = null;
				if (part.parentIndex > -1) {
					parentPartPlayer = this._partPlayers[part.parentIndex];
				}
				var partPlayer:SSSPartPlayer = new SSSPartPlayer(this.project, this.model, part, partAnime, parentPartPlayer);
				this.addChild(partPlayer);
				this._partPlayers.push(partPlayer);
			}
		}

		private function updateParts():void {
			for each (var partPlayer:SSSPartPlayer in this._partPlayers) {
				partPlayer.currentFrame = this._currentFrame;
			}
			if (this._withFlatten) {
				this.flatten();
			}
		}

		public function partPlayerAt(x:Number, y:Number, forTouch:Boolean = false):SSSPartPlayer {
			var touchable:Boolean = this.touchable;
			this.touchable = true;
			var hitObject:DisplayObject = this.hitTest(new Point(x, y), forTouch);
			this.touchable = touchable;
			if (!hitObject) {
				return null;
			}
			for each (var partPlayer:SSSPartPlayer in this._partPlayers) {
				if (hitObject.parent == partPlayer) {
					return partPlayer;
				}
			}
			return null;
		}

		public function partNameAt(x:Number, y:Number, forTouch:Boolean = false):String {
			var partPlayer:SSSPartPlayer = this.partPlayerAt(x, y, forTouch);
			return partPlayer ? partPlayer.name : null;
		}

	}
}

package jp.promotal.ssplayer.starling {

	import flash.geom.Point;

	import jp.promotal.ssplayer.data.SSModel;
	import jp.promotal.ssplayer.data.SSPartAnime;
	import jp.promotal.ssplayer.data.SSProject;
	import jp.promotal.ssplayer.data.SSAnime;
	import jp.promotal.ssplayer.data.SSPart;

	import starling.display.DisplayObject;

	import starling.display.Sprite;
	import starling.events.Event;

	public class SSSPlayer extends Sprite {

		public static const VERSION:String = "0.1.0";

		private var project:SSProject;

		private var model:SSModel;
		private var _anime:SSAnime;
		public function get anime():SSAnime {
			return this._anime;
		}

		private var _currentFrame:Number;
		public function get currentFrame():Number {
			return this._currentFrame;
		}
		public function set currentFrame(value:Number):void {
			this._currentFrame = value;
			this.updateParts();
		}

		private var _partPlayers:Vector.<SSSPartPlayer>;
		public function get partPlayers():Vector.<SSSPartPlayer> {
			return this._partPlayers.concat();
		}

		private var _isPlaying:Boolean = false;
		public function get isPlaying():Boolean {
			return this._isPlaying;
		}
		public function set isPlaying(value:Boolean):void {
			if (value == this._isPlaying) {
				return;
			}
			if (value) {
				this.addEventListener(Event.ENTER_FRAME, this.onEnterFrame);
			} else {
				this.removeEventListener(Event.ENTER_FRAME, this.onEnterFrame);
			}
			this._isPlaying = value;
		}

		private var _loop:Boolean = true;
		public function get loop():Boolean {
			return this._loop;
		}
		public function set loop(value:Boolean):void {
			this._loop = value;
		}

		private var _withFlatten:Boolean = false;
		public function get withFlatten():Boolean {
			return this._withFlatten;
		}
		public function set withFlatten(value:Boolean):void {
			if (value == this._withFlatten) {
				return;
			}
			this._withFlatten = value;
			if (value) {
				this.flatten();
			} else {
				this.unflatten();
			}
		}

		public function set color(value:uint):void {
			for each (var partPlayer:SSSPartPlayer in this._partPlayers) {
				partPlayer.color = value;
			}
		}

		public function SSSPlayer(project:SSProject) {
			super();
			this.project = project;
		}

		public function setAnime(name:String):SSSPlayer {
			this.model = this.project.modelForAnime(name);
			this._anime = this.project.anime(name);
			this.prepareChildren();
			this._currentFrame = 0;
			this.updateParts();
			return this;
		}

		public function startAnime(name:String):SSSPlayer {
			this.setAnime(name);
			this.isPlaying = true;
			return this;
		}

		public function play():void {
			this.isPlaying = true;
		}

		public function stop():void {
			this.isPlaying = false;
		}

		private function onEnterFrame(event:Event):void {
			if (++this._currentFrame >= this._anime.frameCount) {
				if (this._loop) {
					this._currentFrame = 0;
				} else {
					this._currentFrame--;
					this.stop();
				}
				this.dispatchEventWith(Event.COMPLETE);
			}
			this.updateParts();
		}

		private function prepareChildren():void {
			this.removeChildren();
			this._partPlayers = new Vector.<SSSPartPlayer>();
			for each (var part:SSPart in this.model.parts) {
				var partAnime:SSPartAnime = this._anime.partAnimes[part.name];
				var parentPartPlayer:SSSPartPlayer = null;
				if (part.parentIndex > -1) {
					parentPartPlayer = this._partPlayers[part.parentIndex];
				}
				var partPlayer:SSSPartPlayer = new SSSPartPlayer(this.project, this.model, part, partAnime, parentPartPlayer);
				this.addChild(partPlayer);
				this._partPlayers.push(partPlayer);
			}
		}

		private function updateParts():void {
			for each (var partPlayer:SSSPartPlayer in this._partPlayers) {
				partPlayer.currentFrame = this._currentFrame;
			}
			if (this._withFlatten) {
				this.flatten();
			}
		}

		public function partPlayerAt(x:Number, y:Number, forTouch:Boolean = false):SSSPartPlayer {
			var touchable:Boolean = this.touchable;
			this.touchable = true;
			var hitObject:DisplayObject = this.hitTest(new Point(x, y), forTouch);
			this.touchable = touchable;
			if (!hitObject) {
				return null;
			}
			for each (var partPlayer:SSSPartPlayer in this._partPlayers) {
				if (hitObject.parent == partPlayer) {
					return partPlayer;
				}
			}
			return null;
		}

		public function partNameAt(x:Number, y:Number, forTouch:Boolean = false):String {
			var partPlayer:SSSPartPlayer = this.partPlayerAt(x, y, forTouch);
			return partPlayer ? partPlayer.name : null;
		}

	}
}

bump version

bump version


package aerys.minko.render.geometry
{
	import aerys.minko.render.shader.compiler.CRC32;

	import flash.utils.ByteArray;
	import flash.utils.Dictionary;
	import flash.utils.Endian;

	/**
	 * The GeometrySanitizer static class provides methods to clean up
	 * 3D geometry loaded from 3D asset files and make sure it is compliant
	 * with the limitations of the Stage3D API.
	 *
	 * @author Romain Gilliotte
	 *
	 */
	public final class GeometrySanitizer
	{
		public static const INDEX_LIMIT		: uint = 524270;
		public static const VERTEX_LIMIT	: uint = 65535;

		public static function isValid(indexData		: ByteArray,
									   vertexData		: ByteArray,
									   bytesPerVertex	: uint = 12) : Boolean
		{
			var startPosition	: uint	= indexData.position;
			var numIndices		: uint 	= indexData.bytesAvailable >> 2;
			var numVertices		: uint 	= vertexData.bytesAvailable / bytesPerVertex;

			for (var indexId : uint = 0; indexId < numIndices; ++indexId)
				if (indexData.readUnsignedShort() >= numVertices)
					break;

			indexData.position = startPosition;

			return indexId >= numIndices;
		}

		/**
		 * Split vertex and index buffers too big the be rendered.
		 * The index stream limit is 524270, the vertex stream limit is 65536.
		 *
		 * @param inVertexData
		 * @param inIndexData
		 * @param outVertexDatas
		 * @param outIndexDatas
		 * @param dwordsPerVertex
		 */
		public static function splitBuffers(inVertices		: ByteArray,
											inIndices		: ByteArray,
											outVertices		: Vector.<ByteArray>,
											outIndices		: Vector.<ByteArray>,
											bytesPerVertex	: uint = 12) : void
		{
			var inVerticesStartPosition	: uint	= inVertices.position;
			var numVertices				: uint	= inVertices.bytesAvailable / bytesPerVertex;
			var inIndicesStartPosition	: uint	= inIndices.position;
			var numIndices				: uint	= inIndices.bytesAvailable >>> 1;

			if (numIndices < INDEX_LIMIT && numVertices < VERTEX_LIMIT)
			{
				outVertices.push(inVertices);
				outIndices.push(inIndices);

				return;
			}

			while (inIndices.bytesAvailable)
			{
				var indexDataLength		: uint				= inIndices.bytesAvailable >> 1;

				// new buffers
				var partialVertexData	: ByteArray			= new ByteArray();
				var partialIndexData	: ByteArray			= new ByteArray();

				// local variables
				var oldVertexIds		: Vector.<uint>		= new Vector.<uint>(3, true);
				var newVertexIds		: Vector.<uint>		= new Vector.<uint>(3, true);
				var newVertexNeeded		: Vector.<Boolean>	= new Vector.<Boolean>(3, true);

				var usedVerticesDic		: Dictionary		= new Dictionary();	// this dictionary maps old and new indices
				var usedVerticesCount	: uint				= 0;				// this counts elements in the dictionary
				var usedIndicesCount	: uint				= 0;				// how many indices have we used?
				var neededVerticesCount	: uint;

				// iterators & limits
				var localVertexId		: uint;

				partialVertexData.endian = Endian.LITTLE_ENDIAN;
				partialIndexData.endian = Endian.LITTLE_ENDIAN;

				while (usedIndicesCount < indexDataLength)
				{
					// check if next triangle fits into the index buffer
					var remainingIndices	: uint		= INDEX_LIMIT - usedIndicesCount;

					if (remainingIndices < 3)
						break ;

					// check if next triangle fits into the vertex buffer
					var remainingVertices	: uint		= VERTEX_LIMIT - usedVerticesCount;

					neededVerticesCount = 0;
					for (localVertexId = 0; localVertexId < 3; ++localVertexId)
					{
						inIndices.position = (usedIndicesCount + localVertexId) << 2;
						oldVertexIds[localVertexId]	= inIndices.readUnsignedShort();

						var tmp : Object = usedVerticesDic[oldVertexIds[localVertexId]];

						newVertexNeeded[localVertexId]	= tmp == null;
						newVertexIds[localVertexId]		= uint(tmp);

						if (newVertexNeeded[localVertexId])
							++neededVerticesCount;
					}

					if (remainingVertices < neededVerticesCount)
						break ;

					// it fills, let insert the triangle
					for (localVertexId = 0; localVertexId < 3; ++localVertexId)
					{

						if (newVertexNeeded[localVertexId])
						{
							// copy current vertex into the new array
							partialVertexData.writeBytes(
								inVertices,
								oldVertexIds[localVertexId] * bytesPerVertex,
								bytesPerVertex
							);

							// updates the id the temporary variable, to allow use filling partial index data later
							newVertexIds[localVertexId] = usedVerticesCount;

							// put its previous id in the dictionary
							usedVerticesDic[oldVertexIds[localVertexId]] = usedVerticesCount++;
						}

						partialIndexData.writeShort(newVertexIds[localVertexId]);
					}

					// ... increment indices counter
					usedIndicesCount += 3;
				}

				partialVertexData.position = 0;
				outVertices.push(partialVertexData);

				partialIndexData.position = 0;
				outIndices.push(partialIndexData);

				inVertices.position = inVerticesStartPosition;
				inIndices.position = inIndicesStartPosition;
			}
		}

		/**
		 * Removes duplicated entries in a vertex buffer, and rewrite the index buffer according to it.
		 * The buffers are modified in place.
		 *
		 * This method simplifies implementing parsers for file-formats that store per-vertex
		 * and per-triangle information in different buffers, such as collada or OBJ:
		 * merging them in a naive way and calling this method will do the job.
		 *
		 * @param vertexData
		 * @param indexData
		 * @param dwordsPerVertex
		 */
		public static function removeDuplicatedVertices(vertexData		: ByteArray,
														indexData		: ByteArray,
														bytesPerVertex	: uint = 12) : void
		{
			var vertexDataStartPosition		: uint			= vertexData.position;
			var numVertices					: uint			= vertexData.bytesAvailable / bytesPerVertex;
			var indexDataStartPosition		: uint			= indexData.position;
			var numIndices					: uint			= indexData.bytesAvailable >>> 1;
			var hashToNewVertexId			: Object		= {};
			var oldVertexIdToNewVertexId	: Vector.<uint>	= new Vector.<uint>(numVertices, true);
			var newVertexCount				: uint			= 0;
			var newLimit					: uint			= 0;

			for (var oldVertexId : uint = 0; oldVertexId < numVertices; ++oldVertexId)
			{
				vertexData.position = oldVertexId * bytesPerVertex;

				var numFloats	: uint		= bytesPerVertex >>> 2;
				var vertexHash	: String	= '';

				while (numFloats)
				{
					vertexHash += vertexData.readFloat() + '|';
					--numFloats;
				}

				var index		: Object	= hashToNewVertexId[vertexHash];
				var newVertexId	: uint		= 0;

				if (index === null)
				{
					newVertexId = newVertexCount++;
					hashToNewVertexId[vertexHash] = newVertexId;

					newLimit = (1 + newVertexId) * bytesPerVertex;

					if (newVertexId != oldVertexId)
					{
						vertexData.position = newVertexId * bytesPerVertex;
						vertexData.writeBytes(vertexData, oldVertexId * bytesPerVertex, bytesPerVertex);
					}
				}
				else
					newVertexId = uint(index);

				oldVertexIdToNewVertexId[oldVertexId] = newVertexId;
			}

			vertexData.position = vertexDataStartPosition;
			vertexData.length = newLimit;

			for (var indexId : int = 0; indexId < numIndices; ++indexId)
			{
				index = indexData.readUnsignedShort();
				indexData.position -= 2;
				indexData.writeShort(oldVertexIdToNewVertexId[index]);
			}
			indexData.position = indexDataStartPosition;
		}

		/**
		 * Removes unused entries in a vertex buffer, and rewrite the index buffer according to it.
		 * The buffers are modified in place.
		 *
		 * @param vertexData
		 * @param indexData
		 * @param dwordsPerVertex
		 */
		public static function removeUnusedVertices(vertexData		: ByteArray,
													indexData		: ByteArray,
													bytesPerVertex	: uint = 12) : void
		{
			var vertexDataStartPosition	: uint	= vertexData.position;
			var oldNumVertices			: uint	= vertexData.bytesAvailable / bytesPerVertex;
			var newNumVertices			: uint	= 0;
			var indexDataStartPosition	: uint	= indexData.position;
			var numIndices				: uint	= indexData.bytesAvailable >>> 1;

			// iterators
			var oldVertexId		: uint;
			var indexId			: uint;

			// flag unused vertices by scanning the index buffer
			var oldVertexIdToUsage : Vector.<Boolean> = new Vector.<Boolean>(oldNumVertices, true);
			for (indexId = 0; indexId < numIndices; ++indexId)
				oldVertexIdToUsage[indexData.readUnsignedShort()] = true;

			// scan the flags, fix vertex buffer, and store old to new vertex id mapping
			var oldVertexIdToNewVertexId : Vector.<uint> = new Vector.<uint>(oldNumVertices, true);
			for (oldVertexId = 0; oldVertexId < oldNumVertices; ++oldVertexId)
				if (oldVertexIdToUsage[oldVertexId])
				{
					var newVertexId	: uint = newNumVertices++;

					// store new index
					oldVertexIdToNewVertexId[oldVertexId] = newVertexId;

					// rewrite vertexbuffer in place
					if (newVertexId != oldVertexId)
					{
						vertexData.position = newVertexId * bytesPerVertex;
						vertexData.writeBytes(vertexData, oldVertexId * bytesPerVertex, bytesPerVertex);
					}
				}

			vertexData.position = vertexDataStartPosition;
			vertexData.length = newNumVertices * bytesPerVertex;

			// rewrite index buffer in place
			indexData.position = indexDataStartPosition;
			for (indexId = 0; indexId < numIndices; ++indexId)
			{
				var index : uint = indexData.readUnsignedShort();

				indexData.position -= 4;
				indexData.writeShort(oldVertexIdToNewVertexId[index]);
			}
			indexData.position = indexDataStartPosition;
		}
	}
}

package aerys.minko.render.geometry
{
	import aerys.minko.render.shader.compiler.CRC32;

	import flash.utils.ByteArray;
	import flash.utils.Dictionary;
	import flash.utils.Endian;

	/**
	 * The GeometrySanitizer static class provides methods to clean up
	 * 3D geometry loaded from 3D asset files and make sure it is compliant
	 * with the limitations of the Stage3D API.
	 *
	 * @author Romain Gilliotte
	 *
	 */
	public final class GeometrySanitizer
	{
//		public static const INDEX_LIMIT		: uint = 524270;
//		public static const VERTEX_LIMIT	: uint = 65535;

		public static const INDEX_LIMIT		: uint = 5242;
		public static const VERTEX_LIMIT	: uint = 6553;

		public static function isValid(indexData		: ByteArray,
									   vertexData		: ByteArray,
									   bytesPerVertex	: uint = 12) : Boolean
		{
			var startPosition	: uint	= indexData.position;
			var numIndices		: uint 	= indexData.bytesAvailable >> 2;
			var numVertices		: uint 	= vertexData.bytesAvailable / bytesPerVertex;

			for (var indexId : uint = 0; indexId < numIndices; ++indexId)
				if (indexData.readUnsignedShort() >= numVertices)
					break;

			indexData.position = startPosition;

			return indexId >= numIndices;
		}

		/**
		 * Split vertex and index buffers too big the be rendered.
		 * The index stream limit is 524270, the vertex stream limit is 65536.
		 *
		 * @param inVertexData
		 * @param inIndexData
		 * @param outVertexDatas
		 * @param outIndexDatas
		 * @param dwordsPerVertex
		 */
		public static function splitBuffers(inVertices		: ByteArray,
											inIndices		: Vector.<uint>,
											outVertices		: Vector.<ByteArray>,
											outIndices		: Vector.<ByteArray>,
											bytesPerVertex	: uint = 12) : void
		{
			var inVerticesStartPosition	: uint	= inVertices.position;
			var numVertices				: uint	= inVertices.bytesAvailable / bytesPerVertex;
			var numIndices				: int	= inIndices.length;

			if (numIndices < INDEX_LIMIT && numVertices < VERTEX_LIMIT)
			{
				var indices : ByteArray = new ByteArray();

				indices.endian = Endian.LITTLE_ENDIAN;
				for (var i : uint = 0; i < numIndices; ++i)
					indices.writeShort(inIndices[i]);
				indices.position = 0;

				outVertices.push(inVertices);
				outIndices.push(indices);

				return;
			}

			var totalUsedIndices : int = 0;

			while (numIndices > 0)
			{
				var indexDataLength		: uint				= numIndices;

				// new buffers
				var partialVertexData	: ByteArray			= new ByteArray();
				var partialIndexData	: ByteArray			= new ByteArray();

				// local variables
				var oldVertexIds		: Vector.<uint>		= new Vector.<uint>(3, true);
				var newVertexIds		: Vector.<uint>		= new Vector.<uint>(3, true);
				var newVertexNeeded		: Vector.<Boolean>	= new Vector.<Boolean>(3, true);

				var usedVerticesDic		: Dictionary		= new Dictionary();	// this dictionary maps old and new indices
				var usedVerticesCount	: uint				= 0;				// this counts elements in the dictionary
				var usedIndicesCount	: uint				= 0;				// how many indices have we used?
				var neededVerticesCount	: uint;

				// iterators & limits
				var localVertexId		: uint;

				partialVertexData.endian = Endian.LITTLE_ENDIAN;
				partialIndexData.endian = Endian.LITTLE_ENDIAN;

				while (usedIndicesCount < indexDataLength)
				{
					// check if next triangle fits into the index buffer
					var remainingIndices	: uint		= INDEX_LIMIT - usedIndicesCount;

					if (remainingIndices < 3)
						break ;

					// check if next triangle fits into the vertex buffer
					var remainingVertices	: uint		= VERTEX_LIMIT - usedVerticesCount;

					neededVerticesCount = 0;
					for (localVertexId = 0; localVertexId < 3; ++localVertexId)
					{
						oldVertexIds[localVertexId]	= inIndices[uint(totalUsedIndices + usedIndicesCount + localVertexId)];

						var tmp : Object = usedVerticesDic[oldVertexIds[localVertexId]];

						newVertexNeeded[localVertexId]	= tmp == null;
						newVertexIds[localVertexId]		= uint(tmp);

						if (newVertexNeeded[localVertexId])
							++neededVerticesCount;
					}

					if (remainingVertices < neededVerticesCount)
						break ;

					// it fills, let insert the triangle
					for (localVertexId = 0; localVertexId < 3; ++localVertexId)
					{
						if (newVertexNeeded[localVertexId])
						{
							// copy current vertex into the new array
							partialVertexData.writeBytes(
								inVertices,
								oldVertexIds[localVertexId] * bytesPerVertex,
								bytesPerVertex
							);

							// updates the id the temporary variable, to allow use filling partial index data later
							newVertexIds[localVertexId] = usedVerticesCount;

							// put its previous id in the dictionary
							usedVerticesDic[oldVertexIds[localVertexId]] = usedVerticesCount++;
						}

						partialIndexData.writeShort(newVertexIds[localVertexId]);
					}

					// ... increment indices counter
					usedIndicesCount += 3;
				}

				totalUsedIndices += usedIndicesCount;
				numIndices -= usedIndicesCount;

				partialVertexData.position = 0;
				outVertices.push(partialVertexData);

				partialIndexData.position = 0;
				outIndices.push(partialIndexData);
			}

			inVertices.position = inVerticesStartPosition;
		}

		/**
		 * Removes duplicated entries in a vertex buffer, and rewrite the index buffer according to it.
		 * The buffers are modified in place.
		 *
		 * This method simplifies implementing parsers for file-formats that store per-vertex
		 * and per-triangle information in different buffers, such as collada or OBJ:
		 * merging them in a naive way and calling this method will do the job.
		 *
		 * @param vertexData
		 * @param indexData
		 * @param dwordsPerVertex
		 */
		public static function removeDuplicatedVertices(vertexData		: ByteArray,
														indexData		: ByteArray,
														bytesPerVertex	: uint = 12) : void
		{
			var vertexDataStartPosition		: uint			= vertexData.position;
			var numVertices					: uint			= vertexData.bytesAvailable / bytesPerVertex;
			var indexDataStartPosition		: uint			= indexData.position;
			var numIndices					: uint			= indexData.bytesAvailable >>> 1;
			var hashToNewVertexId			: Object		= {};
			var oldVertexIdToNewVertexId	: Vector.<uint>	= new Vector.<uint>(numVertices, true);
			var newVertexCount				: uint			= 0;
			var newLimit					: uint			= 0;

			for (var oldVertexId : uint = 0; oldVertexId < numVertices; ++oldVertexId)
			{
				vertexData.position = oldVertexId * bytesPerVertex;

				var numFloats	: uint		= bytesPerVertex >>> 2;
				var vertexHash	: String	= '';

				while (numFloats)
				{
					vertexHash += vertexData.readFloat() + '|';
					--numFloats;
				}

				var index		: Object	= hashToNewVertexId[vertexHash];
				var newVertexId	: uint		= 0;

				if (index === null)
				{
					newVertexId = newVertexCount++;
					hashToNewVertexId[vertexHash] = newVertexId;

					newLimit = (1 + newVertexId) * bytesPerVertex;

					if (newVertexId != oldVertexId)
					{
						vertexData.position = newVertexId * bytesPerVertex;
						vertexData.writeBytes(vertexData, oldVertexId * bytesPerVertex, bytesPerVertex);
					}
				}
				else
					newVertexId = uint(index);

				oldVertexIdToNewVertexId[oldVertexId] = newVertexId;
			}

			vertexData.position = vertexDataStartPosition;
			vertexData.length = newLimit;

			for (var indexId : int = 0; indexId < numIndices; ++indexId)
			{
				index = indexData.readUnsignedShort();
				indexData.position -= 2;
				indexData.writeShort(oldVertexIdToNewVertexId[index]);
			}
			indexData.position = indexDataStartPosition;
		}

		/**
		 * Removes unused entries in a vertex buffer, and rewrite the index buffer according to it.
		 * The buffers are modified in place.
		 *
		 * @param vertexData
		 * @param indexData
		 * @param dwordsPerVertex
		 */
		public static function removeUnusedVertices(vertexData		: ByteArray,
													indexData		: ByteArray,
													bytesPerVertex	: uint = 12) : void
		{
			var vertexDataStartPosition	: uint	= vertexData.position;
			var oldNumVertices			: uint	= vertexData.bytesAvailable / bytesPerVertex;
			var newNumVertices			: uint	= 0;
			var indexDataStartPosition	: uint	= indexData.position;
			var numIndices				: uint	= indexData.bytesAvailable >>> 1;

			// iterators
			var oldVertexId		: uint;
			var indexId			: uint;

			// flag unused vertices by scanning the index buffer
			var oldVertexIdToUsage : Vector.<Boolean> = new Vector.<Boolean>(oldNumVertices, true);
			for (indexId = 0; indexId < numIndices; ++indexId)
				oldVertexIdToUsage[indexData.readUnsignedShort()] = true;

			// scan the flags, fix vertex buffer, and store old to new vertex id mapping
			var oldVertexIdToNewVertexId : Vector.<uint> = new Vector.<uint>(oldNumVertices, true);
			for (oldVertexId = 0; oldVertexId < oldNumVertices; ++oldVertexId)
				if (oldVertexIdToUsage[oldVertexId])
				{
					var newVertexId	: uint = newNumVertices++;

					// store new index
					oldVertexIdToNewVertexId[oldVertexId] = newVertexId;

					// rewrite vertexbuffer in place
					if (newVertexId != oldVertexId)
					{
						vertexData.position = newVertexId * bytesPerVertex;
						vertexData.writeBytes(vertexData, oldVertexId * bytesPerVertex, bytesPerVertex);
					}
				}

			vertexData.position = vertexDataStartPosition;
			vertexData.length = newNumVertices * bytesPerVertex;

			// rewrite index buffer in place
			indexData.position = indexDataStartPosition;
			for (indexId = 0; indexId < numIndices; ++indexId)
			{
				var index : uint = indexData.readUnsignedShort();

				indexData.position -= 2;
				indexData.writeShort(oldVertexIdToNewVertexId[index]);
			}
			indexData.position = indexDataStartPosition;
		}
	}
}

fix GeometrySanitizer.splitBuffers() to work on Vector.<uint> instead of a ByteArray of shorts to allow indices > 65535


/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mangui.hls.controller {
    import org.mangui.hls.constant.HLSLoaderTypes;
    import org.mangui.hls.constant.HLSMaxLevelCappingMode;
    import org.mangui.hls.event.HLSEvent;
    import org.mangui.hls.event.HLSLoadMetrics;
    import org.mangui.hls.HLS;
    import org.mangui.hls.HLSSettings;
    import org.mangui.hls.model.Level;

    CONFIG::LOGGING {
        import org.mangui.hls.utils.Log;
    }
    /** Class that manages auto level selection
     *
     * this is an implementation based on Serial segment fetching method from
     * http://www.cs.tut.fi/~moncef/publications/rate-adaptation-IC-2011.pdf
     */
    public class LevelController {
        /** Reference to the HLS controller. **/
        private var _hls : HLS;
        /** switch up threshold **/
        private var _switchup : Vector.<Number> = null;
        /** switch down threshold **/
        private var _switchdown : Vector.<Number> = null;
        /** bitrate array **/
        private var _bitrate : Vector.<uint> = null;
        /** vector of levels with unique dimension with highest bandwidth **/
        private var _maxUniqueLevels : Vector.<Level> = null;
        /** nb level **/
        private var _nbLevel : int = 0;
        private var _lastSegmentDuration : Number;
        private var _lastFetchDuration : Number;
        private var  lastBandwidth : int;
        private var  _autoLevelCapping : int;
        private var  _startLevel : int = -1;
        private var  _fpsController : FPSController;

        /** Create the loader. **/
        public function LevelController(hls : HLS) : void {
            _hls = hls;
            _fpsController = new FPSController(hls);
            /* low priority listener, so that other listeners with default priority
               could seamlessly set hls.startLevel in their HLSEvent.MANIFEST_PARSED listener */
            _hls.addEventListener(HLSEvent.MANIFEST_PARSED, _manifestParsedHandler, false, int.MIN_VALUE);
            _hls.addEventListener(HLSEvent.MANIFEST_LOADED, _manifestLoadedHandler);
            _hls.addEventListener(HLSEvent.FRAGMENT_LOADED, _fragmentLoadedHandler);
            _hls.addEventListener(HLSEvent.FRAGMENT_LOAD_EMERGENCY_ABORTED, _fragmentLoadedHandler);
        }
        ;

        public function dispose() : void {
            _fpsController.dispose();
            _fpsController = null;
            _hls.removeEventListener(HLSEvent.MANIFEST_PARSED, _manifestParsedHandler);
            _hls.removeEventListener(HLSEvent.MANIFEST_LOADED, _manifestLoadedHandler);
            _hls.removeEventListener(HLSEvent.FRAGMENT_LOADED, _fragmentLoadedHandler);
            _hls.removeEventListener(HLSEvent.FRAGMENT_LOAD_EMERGENCY_ABORTED, _fragmentLoadedHandler);
        }

        private function _fragmentLoadedHandler(event : HLSEvent) : void {
            var metrics : HLSLoadMetrics = event.loadMetrics;
            // only monitor main fragment metrics for level switching
            if(metrics.type == HLSLoaderTypes.FRAGMENT_MAIN) {
                lastBandwidth = metrics.bandwidth;
                _lastSegmentDuration = metrics.duration;
                _lastFetchDuration = metrics.processing_duration;
            }
        }

        private function _manifestParsedHandler(event : HLSEvent) : void {
            if(HLSSettings.autoStartLoad) {
                // upon manifest parsed event, trigger a level switch to load startLevel playlist
                _hls.dispatchEvent(new HLSEvent(HLSEvent.LEVEL_SWITCH, _hls.startLevel));
            }
        }

        private function _manifestLoadedHandler(event : HLSEvent) : void {
            var levels : Vector.<Level> = event.levels;
            var maxswitchup : Number = 0;
            var minswitchdwown : Number = Number.MAX_VALUE;
            _nbLevel = levels.length;
            _bitrate = new Vector.<uint>(_nbLevel, true);
            _switchup = new Vector.<Number>(_nbLevel, true);
            _switchdown = new Vector.<Number>(_nbLevel, true);
            _autoLevelCapping = -1;
            _lastSegmentDuration = 0;
            _lastFetchDuration = 0;
            lastBandwidth = 0;

            var i : int;

            for (i = 0; i < _nbLevel; i++) {
                _bitrate[i] = levels[i].bitrate;
            }

            for (i = 0; i < _nbLevel - 1; i++) {
                _switchup[i] = (_bitrate[i + 1] - _bitrate[i]) / _bitrate[i];
                maxswitchup = Math.max(maxswitchup, _switchup[i]);
            }
            for (i = 0; i < _nbLevel - 1; i++) {
                _switchup[i] = Math.min(maxswitchup, 2 * _switchup[i]);

                CONFIG::LOGGING {
                    Log.debug("_switchup[" + i + "]=" + _switchup[i]);
                }
            }

            for (i = 1; i < _nbLevel; i++) {
                _switchdown[i] = (_bitrate[i] - _bitrate[i - 1]) / _bitrate[i];
                minswitchdwown = Math.min(minswitchdwown, _switchdown[i]);
            }
            for (i = 1; i < _nbLevel; i++) {
                _switchdown[i] = Math.max(2 * minswitchdwown, _switchdown[i]);

                CONFIG::LOGGING {
                    Log.debug("_switchdown[" + i + "]=" + _switchdown[i]);
                }
            }

            if (HLSSettings.capLevelToStage) {
                _maxUniqueLevels = _maxLevelsWithUniqueDimensions;
            }
        }
        ;

        public function getbestlevel(downloadBandwidth : int) : int {
            var max_level : int = _maxLevel;
            for (var i : int = max_level; i >= 0; i--) {
                if (_bitrate[i] <= downloadBandwidth) {
                    return i;
                }
            }
            return 0;
        }

        private function get _maxLevelsWithUniqueDimensions() : Vector.<Level> {
            var filter : Function = function(l : Level, i : int, v : Vector.<Level>) : Boolean {
                if (l.width > 0 && l.height > 0) {
                    if (i + 1 < v.length) {
                        var nextLevel : Level = v[i + 1];
                        if (l.width != nextLevel.width && l.height != nextLevel.height) {
                            return true;
                        }
                    } else {
                        return true;
                    }
                }
                return false;
            };

            return _hls.levels.filter(filter);
        }

        /** Return the capping/max level value that could be used by automatic level selection algorithm **/
        public function get autoLevelCapping() : int {
            return _autoLevelCapping;
        }

        /** set the capping/max level value that could be used by automatic level selection algorithm **/
        public function set autoLevelCapping(newLevel : int) : void {
            _autoLevelCapping = newLevel;
        }

        private function get _maxLevel() : int {
            // if set, _autoLevelCapping takes precedence
            if(_autoLevelCapping >= 0) {
                return Math.min(_nbLevel - 1, _autoLevelCapping);
            } else if (HLSSettings.capLevelToStage) {
                var maxLevelsCount : int = _maxUniqueLevels.length;

                if (_hls.stage && maxLevelsCount) {
                    var maxLevel : Level = this._maxUniqueLevels[0], maxLevelIdx : int = maxLevel.index, sWidth : Number = this._hls.stage.stageWidth, sHeight : Number = this._hls.stage.stageHeight, lWidth : int, lHeight : int, i : int;

                    switch (HLSSettings.maxLevelCappingMode) {
                        case HLSMaxLevelCappingMode.UPSCALE:
                            for (i = maxLevelsCount - 1; i >= 0; i--) {
                                maxLevel = this._maxUniqueLevels[i];
                                maxLevelIdx = maxLevel.index;
                                lWidth = maxLevel.width;
                                lHeight = maxLevel.height;
                                CONFIG::LOGGING {
                                    Log.debug("stage size: " + sWidth + "x" + sHeight + " ,level" + maxLevelIdx + " size: " + lWidth + "x" + lHeight);
                                }
                                if (sWidth >= lWidth || sHeight >= lHeight) {
                                    break;
                                    // from for loop
                                }
                            }
                            break;
                        case HLSMaxLevelCappingMode.DOWNSCALE:
                            for (i = 0; i < maxLevelsCount; i++) {
                                maxLevel = this._maxUniqueLevels[i];
                                maxLevelIdx = maxLevel.index;
                                lWidth = maxLevel.width;
                                lHeight = maxLevel.height;
                                CONFIG::LOGGING {
                                    Log.debug("stage size: " + sWidth + "x" + sHeight + " ,level" + maxLevelIdx + " size: " + lWidth + "x" + lHeight);
                                }
                                if (sWidth <= lWidth || sHeight <= lHeight) {
                                    break;
                                    // from for loop
                                }
                            }
                            break;
                    }
                    CONFIG::LOGGING {
                        Log.debug("max capped level idx: " + maxLevelIdx);
                    }
                }
                return maxLevelIdx;
            } else {
                return _nbLevel - 1;
            }
        }

        /** Update the quality level for the next fragment load. **/
        public function getnextlevel(current_level : int, buffer : Number) : int {
            if (_lastFetchDuration == 0 || _lastSegmentDuration == 0) {
                return 0;
            }

            /* rsft : remaining segment fetch time : available time to fetch next segment
            it depends on the current playback timestamp , the timestamp of the first frame of the next segment
            and TBMT, indicating a desired latency between the time instant to receive the last byte of a
            segment to the playback of the first media frame of a segment
            buffer is start time of next segment
            TBMT is the buffer size we need to ensure (we need at least 2 segments buffered */
            var rsft : Number = 1000 * buffer - 2 * _lastFetchDuration;
            var sftm : Number = Math.min(_lastSegmentDuration, rsft) / _lastFetchDuration;
            var max_level : Number = _maxLevel;
            var switch_to_level : int = current_level;
            // CONFIG::LOGGING {
            // Log.info("rsft:" + rsft);
            // Log.info("sftm:" + sftm);
            // }
            // }
            /* to switch level up :
            rsft should be greater than switch up condition
             */
            if ((current_level < max_level) && (sftm > (1 + _switchup[current_level]))) {
                CONFIG::LOGGING {
                    Log.debug("sftm:> 1+_switchup[_level]=" + (1 + _switchup[current_level]));
                }
                switch_to_level = current_level + 1;
            }

            /* to switch level down :
            rsft should be smaller than switch up condition,
            or the current level is greater than max level
             */ else if ((current_level > max_level && current_level > 0) || (current_level > 0 && (sftm < 1 - _switchdown[current_level]))) {
                CONFIG::LOGGING {
                    Log.debug("sftm < 1-_switchdown[current_level]=" + _switchdown[current_level]);
                }
                var bufferratio : Number = 1000 * buffer / _lastSegmentDuration;
                /* find suitable level matching current bandwidth, starting from current level
                when switching level down, we also need to consider that we might need to load two fragments.
                the condition (bufferratio > 2*_levels[j].bitrate/_lastBandwidth)
                ensures that buffer time is bigger than than the time to download 2 fragments from level j, if we keep same bandwidth.
                 */

                for (var j : int = current_level - 1; j >= 0; j--) {
                    if (_bitrate[j] <= lastBandwidth && (bufferratio > 2 * _bitrate[j] / lastBandwidth)) {
                        switch_to_level = j;
                        break;
                    }
                    if (j == 0) {
                        switch_to_level = 0;
                    }
                }
            }

            // Then we should check if selected level is higher than max_level if so, than take the min of those two
            switch_to_level = Math.min(max_level, switch_to_level);

            CONFIG::LOGGING {
                if (switch_to_level != current_level) {
                    Log.debug("switch to level " + switch_to_level);
                }
            }

            return switch_to_level;
        }

        // get level index of first level appearing in the manifest
        public function get firstLevel() : int {
            var levels : Vector.<Level> = _hls.levels;
            for (var i : int = 0; i < levels.length; i++) {
                if (levels[i].manifest_index == 0) {
                    return i;
                }
            }
            return 0;
        }

        public function isStartLevelSet() : Boolean {
            return (_startLevel >=0);
        }

        /*  set the quality level used when starting a fresh playback */
        public function set startLevel(level : int) : void {
            _startLevel = level;
        };

        public function get startLevel() : int {
            var start_level : int = -1;
            var levels : Vector.<Level> = _hls.levels;
            if (levels) {
                // if set, _startLevel takes precedence
                if(_startLevel >=0) {
                    return Math.min(levels.length-1,_startLevel);
                } else if (HLSSettings.startFromLevel === -2) {
                    // playback will start from the first level appearing in Manifest (not sorted by bitrate)
                    return firstLevel;
                } else if (HLSSettings.startFromLevel === -1 && HLSSettings.startFromBitrate === -1) {
                    /* if startFromLevel is set to -1, it means that effective startup level
                     * will be determined from first segment download bandwidth
                     * let's use lowest bitrate for this download bandwidth assessment
                     * this should speed up playback start time
                     */
                    return 0;
                } else {
                    // set up start level as being the lowest non-audio level.
                    for (var i : int = 0; i < levels.length; i++) {
                        if (!levels[i].audio) {
                            start_level = i;
                            break;
                        }
                    }
                    // in case of audio only playlist, force startLevel to 0
                    if (start_level == -1) {
                        CONFIG::LOGGING {
                            Log.info("playlist is audio-only");
                        }
                        start_level = 0;
                    } else {
                        if (HLSSettings.startFromBitrate > 0) {
                            start_level = findIndexOfClosestLevel(HLSSettings.startFromBitrate);
                        } else if (HLSSettings.startFromLevel > 0) {
                            // adjust start level using a rule by 3
                            start_level += Math.round(HLSSettings.startFromLevel * (levels.length - start_level - 1));
                        }
                    }
                }
                CONFIG::LOGGING {
                    Log.debug("start level :" + start_level);
                }
            }
            return start_level;
        }

        /**
         * @param desiredBitrate
         * @return The index of the level that has a bitrate closest to the desired bitrate.
         */
        private function findIndexOfClosestLevel(desiredBitrate : Number) : int {
            var levelIndex : int = -1;
            var minDistance : Number = Number.MAX_VALUE;
            var levels : Vector.<Level> = _hls.levels;

            for (var index : int = 0; index < levels.length; index++) {
                var level : Level = levels[index];

                var distance : Number = Math.abs(desiredBitrate - level.bitrate);

                if (distance < minDistance) {
                    levelIndex = index;
                    minDistance = distance;
                }
            }
            return levelIndex;
        }

        public function get seekLevel() : int {
            var seek_level : int = -1;
            var levels : Vector.<Level> = _hls.levels;
            if (HLSSettings.seekFromLevel == -1) {
                // keep last level, but don't exceed _maxLevel
                return Math.min(_hls.loadLevel,_maxLevel);
            }

            // set up seek level as being the lowest non-audio level.
            for (var i : int = 0; i < levels.length; i++) {
                if (!levels[i].audio) {
                    seek_level = i;
                    break;
                }
            }
            // in case of audio only playlist, force seek_level to 0
            if (seek_level == -1) {
                seek_level = 0;
            } else {
                if (HLSSettings.seekFromLevel > 0) {
                    // adjust start level using a rule by 3
                    seek_level += Math.round(HLSSettings.seekFromLevel * (levels.length - seek_level - 1));
                }
            }
            CONFIG::LOGGING {
                Log.debug("seek level :" + seek_level);
            }
            return seek_level;
        }
    }
}

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mangui.hls.controller {
    import org.mangui.hls.constant.HLSLoaderTypes;
    import org.mangui.hls.constant.HLSMaxLevelCappingMode;
    import org.mangui.hls.event.HLSEvent;
    import org.mangui.hls.event.HLSLoadMetrics;
    import org.mangui.hls.HLS;
    import org.mangui.hls.HLSSettings;
    import org.mangui.hls.model.Level;

    CONFIG::LOGGING {
        import org.mangui.hls.utils.Log;
    }
    /** Class that manages auto level selection
     *
     * this is an implementation based on Serial segment fetching method from
     * http://www.cs.tut.fi/~moncef/publications/rate-adaptation-IC-2011.pdf
     */
    public class LevelController {
        /** Reference to the HLS controller. **/
        private var _hls : HLS;
        /** switch up threshold **/
        private var _switchup : Vector.<Number> = null;
        /** switch down threshold **/
        private var _switchdown : Vector.<Number> = null;
        /** bitrate array **/
        private var _bitrate : Vector.<uint> = null;
        /** vector of levels with unique dimension with highest bandwidth **/
        private var _maxUniqueLevels : Vector.<Level> = null;
        /** nb level **/
        private var _nbLevel : int = 0;
        private var _lastSegmentDuration : Number;
        private var _lastFetchDuration : Number;
        private var  lastBandwidth : int;
        private var  _autoLevelCapping : int;
        private var  _startLevel : int = -1;
        private var  _fpsController : FPSController;

        /** Create the loader. **/
        public function LevelController(hls : HLS) : void {
            _hls = hls;
            _fpsController = new FPSController(hls);
            /* low priority listener, so that other listeners with default priority
               could seamlessly set hls.startLevel in their HLSEvent.MANIFEST_PARSED listener */
            _hls.addEventListener(HLSEvent.MANIFEST_PARSED, _manifestParsedHandler, false, int.MIN_VALUE);
            _hls.addEventListener(HLSEvent.MANIFEST_LOADED, _manifestLoadedHandler);
            _hls.addEventListener(HLSEvent.FRAGMENT_LOADED, _fragmentLoadedHandler);
            _hls.addEventListener(HLSEvent.FRAGMENT_LOAD_EMERGENCY_ABORTED, _fragmentLoadedHandler);
        }
        ;

        public function dispose() : void {
            _fpsController.dispose();
            _fpsController = null;
            _hls.removeEventListener(HLSEvent.MANIFEST_PARSED, _manifestParsedHandler);
            _hls.removeEventListener(HLSEvent.MANIFEST_LOADED, _manifestLoadedHandler);
            _hls.removeEventListener(HLSEvent.FRAGMENT_LOADED, _fragmentLoadedHandler);
            _hls.removeEventListener(HLSEvent.FRAGMENT_LOAD_EMERGENCY_ABORTED, _fragmentLoadedHandler);
        }

        private function _fragmentLoadedHandler(event : HLSEvent) : void {
            var metrics : HLSLoadMetrics = event.loadMetrics;
            // only monitor main fragment metrics for level switching
            if(metrics.type == HLSLoaderTypes.FRAGMENT_MAIN) {
                lastBandwidth = metrics.bandwidth;
                _lastSegmentDuration = metrics.duration;
                _lastFetchDuration = metrics.processing_duration;
            }
        }

        private function _manifestParsedHandler(event : HLSEvent) : void {
            if(HLSSettings.autoStartLoad) {
                // upon manifest parsed event, trigger a level switch to load startLevel playlist
                _hls.dispatchEvent(new HLSEvent(HLSEvent.LEVEL_SWITCH, startLevel));
            }
        }

        private function _manifestLoadedHandler(event : HLSEvent) : void {
            var levels : Vector.<Level> = event.levels;
            var maxswitchup : Number = 0;
            var minswitchdwown : Number = Number.MAX_VALUE;
            _nbLevel = levels.length;
            _bitrate = new Vector.<uint>(_nbLevel, true);
            _switchup = new Vector.<Number>(_nbLevel, true);
            _switchdown = new Vector.<Number>(_nbLevel, true);
            _autoLevelCapping = -1;
            _lastSegmentDuration = 0;
            _lastFetchDuration = 0;
            lastBandwidth = 0;

            var i : int;

            for (i = 0; i < _nbLevel; i++) {
                _bitrate[i] = levels[i].bitrate;
            }

            for (i = 0; i < _nbLevel - 1; i++) {
                _switchup[i] = (_bitrate[i + 1] - _bitrate[i]) / _bitrate[i];
                maxswitchup = Math.max(maxswitchup, _switchup[i]);
            }
            for (i = 0; i < _nbLevel - 1; i++) {
                _switchup[i] = Math.min(maxswitchup, 2 * _switchup[i]);

                CONFIG::LOGGING {
                    Log.debug("_switchup[" + i + "]=" + _switchup[i]);
                }
            }

            for (i = 1; i < _nbLevel; i++) {
                _switchdown[i] = (_bitrate[i] - _bitrate[i - 1]) / _bitrate[i];
                minswitchdwown = Math.min(minswitchdwown, _switchdown[i]);
            }
            for (i = 1; i < _nbLevel; i++) {
                _switchdown[i] = Math.max(2 * minswitchdwown, _switchdown[i]);

                CONFIG::LOGGING {
                    Log.debug("_switchdown[" + i + "]=" + _switchdown[i]);
                }
            }

            if (HLSSettings.capLevelToStage) {
                _maxUniqueLevels = _maxLevelsWithUniqueDimensions;
            }
        }
        ;

        public function getbestlevel(downloadBandwidth : int) : int {
            var max_level : int = _maxLevel;
            for (var i : int = max_level; i >= 0; i--) {
                if (_bitrate[i] <= downloadBandwidth) {
                    return i;
                }
            }
            return 0;
        }

        private function get _maxLevelsWithUniqueDimensions() : Vector.<Level> {
            var filter : Function = function(l : Level, i : int, v : Vector.<Level>) : Boolean {
                if (l.width > 0 && l.height > 0) {
                    if (i + 1 < v.length) {
                        var nextLevel : Level = v[i + 1];
                        if (l.width != nextLevel.width && l.height != nextLevel.height) {
                            return true;
                        }
                    } else {
                        return true;
                    }
                }
                return false;
            };

            return _hls.levels.filter(filter);
        }

        /** Return the capping/max level value that could be used by automatic level selection algorithm **/
        public function get autoLevelCapping() : int {
            return _autoLevelCapping;
        }

        /** set the capping/max level value that could be used by automatic level selection algorithm **/
        public function set autoLevelCapping(newLevel : int) : void {
            _autoLevelCapping = newLevel;
        }

        private function get _maxLevel() : int {
            // if set, _autoLevelCapping takes precedence
            if(_autoLevelCapping >= 0) {
                return Math.min(_nbLevel - 1, _autoLevelCapping);
            } else if (HLSSettings.capLevelToStage) {
                var maxLevelsCount : int = _maxUniqueLevels.length;

                if (_hls.stage && maxLevelsCount) {
                    var maxLevel : Level = this._maxUniqueLevels[0], maxLevelIdx : int = maxLevel.index, sWidth : Number = this._hls.stage.stageWidth, sHeight : Number = this._hls.stage.stageHeight, lWidth : int, lHeight : int, i : int;

                    switch (HLSSettings.maxLevelCappingMode) {
                        case HLSMaxLevelCappingMode.UPSCALE:
                            for (i = maxLevelsCount - 1; i >= 0; i--) {
                                maxLevel = this._maxUniqueLevels[i];
                                maxLevelIdx = maxLevel.index;
                                lWidth = maxLevel.width;
                                lHeight = maxLevel.height;
                                CONFIG::LOGGING {
                                    Log.debug("stage size: " + sWidth + "x" + sHeight + " ,level" + maxLevelIdx + " size: " + lWidth + "x" + lHeight);
                                }
                                if (sWidth >= lWidth || sHeight >= lHeight) {
                                    break;
                                    // from for loop
                                }
                            }
                            break;
                        case HLSMaxLevelCappingMode.DOWNSCALE:
                            for (i = 0; i < maxLevelsCount; i++) {
                                maxLevel = this._maxUniqueLevels[i];
                                maxLevelIdx = maxLevel.index;
                                lWidth = maxLevel.width;
                                lHeight = maxLevel.height;
                                CONFIG::LOGGING {
                                    Log.debug("stage size: " + sWidth + "x" + sHeight + " ,level" + maxLevelIdx + " size: " + lWidth + "x" + lHeight);
                                }
                                if (sWidth <= lWidth || sHeight <= lHeight) {
                                    break;
                                    // from for loop
                                }
                            }
                            break;
                    }
                    CONFIG::LOGGING {
                        Log.debug("max capped level idx: " + maxLevelIdx);
                    }
                }
                return maxLevelIdx;
            } else {
                return _nbLevel - 1;
            }
        }

        /** Update the quality level for the next fragment load. **/
        public function getnextlevel(current_level : int, buffer : Number) : int {
            if (_lastFetchDuration == 0 || _lastSegmentDuration == 0) {
                return 0;
            }

            /* rsft : remaining segment fetch time : available time to fetch next segment
            it depends on the current playback timestamp , the timestamp of the first frame of the next segment
            and TBMT, indicating a desired latency between the time instant to receive the last byte of a
            segment to the playback of the first media frame of a segment
            buffer is start time of next segment
            TBMT is the buffer size we need to ensure (we need at least 2 segments buffered */
            var rsft : Number = 1000 * buffer - 2 * _lastFetchDuration;
            var sftm : Number = Math.min(_lastSegmentDuration, rsft) / _lastFetchDuration;
            var max_level : Number = _maxLevel;
            var switch_to_level : int = current_level;
            // CONFIG::LOGGING {
            // Log.info("rsft:" + rsft);
            // Log.info("sftm:" + sftm);
            // }
            // }
            /* to switch level up :
            rsft should be greater than switch up condition
             */
            if ((current_level < max_level) && (sftm > (1 + _switchup[current_level]))) {
                CONFIG::LOGGING {
                    Log.debug("sftm:> 1+_switchup[_level]=" + (1 + _switchup[current_level]));
                }
                switch_to_level = current_level + 1;
            }

            /* to switch level down :
            rsft should be smaller than switch up condition,
            or the current level is greater than max level
             */ else if ((current_level > max_level && current_level > 0) || (current_level > 0 && (sftm < 1 - _switchdown[current_level]))) {
                CONFIG::LOGGING {
                    Log.debug("sftm < 1-_switchdown[current_level]=" + _switchdown[current_level]);
                }
                var bufferratio : Number = 1000 * buffer / _lastSegmentDuration;
                /* find suitable level matching current bandwidth, starting from current level
                when switching level down, we also need to consider that we might need to load two fragments.
                the condition (bufferratio > 2*_levels[j].bitrate/_lastBandwidth)
                ensures that buffer time is bigger than than the time to download 2 fragments from level j, if we keep same bandwidth.
                 */

                for (var j : int = current_level - 1; j >= 0; j--) {
                    if (_bitrate[j] <= lastBandwidth && (bufferratio > 2 * _bitrate[j] / lastBandwidth)) {
                        switch_to_level = j;
                        break;
                    }
                    if (j == 0) {
                        switch_to_level = 0;
                    }
                }
            }

            // Then we should check if selected level is higher than max_level if so, than take the min of those two
            switch_to_level = Math.min(max_level, switch_to_level);

            CONFIG::LOGGING {
                if (switch_to_level != current_level) {
                    Log.debug("switch to level " + switch_to_level);
                }
            }

            return switch_to_level;
        }

        // get level index of first level appearing in the manifest
        public function get firstLevel() : int {
            var levels : Vector.<Level> = _hls.levels;
            for (var i : int = 0; i < levels.length; i++) {
                if (levels[i].manifest_index == 0) {
                    return i;
                }
            }
            return 0;
        }

        public function isStartLevelSet() : Boolean {
            return (_startLevel >=0);
        }

        /*  set the quality level used when starting a fresh playback */
        public function set startLevel(level : int) : void {
            _startLevel = level;
        };

        public function get startLevel() : int {
            var start_level : int = -1;
            var levels : Vector.<Level> = _hls.levels;
            if (levels) {
                // if set, _startLevel takes precedence
                if(_startLevel >=0) {
                    return Math.min(levels.length-1,_startLevel);
                } else if (HLSSettings.startFromLevel === -2) {
                    // playback will start from the first level appearing in Manifest (not sorted by bitrate)
                    return firstLevel;
                } else if (HLSSettings.startFromLevel === -1 && HLSSettings.startFromBitrate === -1) {
                    /* if startFromLevel is set to -1, it means that effective startup level
                     * will be determined from first segment download bandwidth
                     * let's use lowest bitrate for this download bandwidth assessment
                     * this should speed up playback start time
                     */
                    return 0;
                } else {
                    // set up start level as being the lowest non-audio level.
                    for (var i : int = 0; i < levels.length; i++) {
                        if (!levels[i].audio) {
                            start_level = i;
                            break;
                        }
                    }
                    // in case of audio only playlist, force startLevel to 0
                    if (start_level == -1) {
                        CONFIG::LOGGING {
                            Log.info("playlist is audio-only");
                        }
                        start_level = 0;
                    } else {
                        if (HLSSettings.startFromBitrate > 0) {
                            start_level = findIndexOfClosestLevel(HLSSettings.startFromBitrate);
                        } else if (HLSSettings.startFromLevel > 0) {
                            // adjust start level using a rule by 3
                            start_level += Math.round(HLSSettings.startFromLevel * (levels.length - start_level - 1));
                        }
                    }
                }
                CONFIG::LOGGING {
                    Log.debug("start level :" + start_level);
                }
            }
            return start_level;
        }

        /**
         * @param desiredBitrate
         * @return The index of the level that has a bitrate closest to the desired bitrate.
         */
        private function findIndexOfClosestLevel(desiredBitrate : Number) : int {
            var levelIndex : int = -1;
            var minDistance : Number = Number.MAX_VALUE;
            var levels : Vector.<Level> = _hls.levels;

            for (var index : int = 0; index < levels.length; index++) {
                var level : Level = levels[index];

                var distance : Number = Math.abs(desiredBitrate - level.bitrate);

                if (distance < minDistance) {
                    levelIndex = index;
                    minDistance = distance;
                }
            }
            return levelIndex;
        }

        public function get seekLevel() : int {
            var seek_level : int = -1;
            var levels : Vector.<Level> = _hls.levels;
            if (HLSSettings.seekFromLevel == -1) {
                // keep last level, but don't exceed _maxLevel
                return Math.min(_hls.loadLevel,_maxLevel);
            }

            // set up seek level as being the lowest non-audio level.
            for (var i : int = 0; i < levels.length; i++) {
                if (!levels[i].audio) {
                    seek_level = i;
                    break;
                }
            }
            // in case of audio only playlist, force seek_level to 0
            if (seek_level == -1) {
                seek_level = 0;
            } else {
                if (HLSSettings.seekFromLevel > 0) {
                    // adjust start level using a rule by 3
                    seek_level += Math.round(HLSSettings.seekFromLevel * (levels.length - seek_level - 1));
                }
            }
            CONFIG::LOGGING {
                Log.debug("seek level :" + seek_level);
            }
            return seek_level;
        }
    }
}

use startLevel getter directly

LevelController: use startLevel getter directly


/*
Copyright 2016-2021 Bowler Hat LLC

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.as3mxml.asconfigc.utils
{
	public function findOutputDirectory(mainFile:String, outputPath:String, isSWF:Boolean):String
	{
		if(!outputPath)
		{
			if(!mainFile)
			{
				return process.cwd();
			}
			var mainPath:String = path.resolve(path.dirname(mainFile));
			if(!isSWF)
			{
				//Royale treats these directory structures as a special case
				if(mainPath.endsWith("/src") ||
					mainPath.endsWith("\\src"))
				{
					return path.resolve(mainPath, "../");
				}
				else if(mainPath.endsWith("/src/main/flex") ||
					mainPath.endsWith("\\src\\main\\flex") ||
					mainPath.endsWith("/src/main/royale") ||
					mainPath.endsWith("\\src\\main\\royale"))
				{
					return path.resolve(mainPath, "../../../");
				}
			}
			return mainPath;
		}
		return path.resolve(path.dirname(outputPath));
	}
}

/*
Copyright 2016-2021 Bowler Hat LLC

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.as3mxml.asconfigc.utils
{
	public function findOutputDirectory(mainFile:String, outputPath:String, isSWF:Boolean):String
	{
		if(!outputPath)
		{
			if(!mainFile)
			{
				return process.cwd();
			}
			var mainPath:String = path.resolve(path.dirname(mainFile));
			if(!isSWF)
			{
				//Royale treats these directory structures as a special case
				if(mainPath.endsWith("/src") ||
					mainPath.endsWith("\\src"))
				{
					return path.resolve(mainPath, "../");
				}
				else if(mainPath.endsWith("/src/main/flex") ||
					mainPath.endsWith("\\src\\main\\flex") ||
					mainPath.endsWith("/src/main/royale") ||
					mainPath.endsWith("\\src\\main\\royale"))
				{
					return path.resolve(mainPath, "../../../");
				}
			}
			return mainPath;
		}
		if (!isSWF)
		{
			return outputPath;
		}
		return path.resolve(path.dirname(outputPath));
	}
}

findOutputDirectory: fix result for Royale when js-output is defined