package core.utils
{
	import flash.geom.Rectangle;

	/**
	 * For place texture
	 */
	public final class TextureUtil
	{
		private static const HIGHEST:uint = 0xFFFFFFFF;

		/**
		 * Place textures by textureAtlasXML data
		 */
		public static function packTextures(widthDefault:uint, padding:uint, rectMap:Object, verticalSide:Boolean = false, isNearest2N:Boolean = true):Rectangle
		{
			for each(var rect:Rectangle in rectMap)
			{
				break;
			}
			if(!rect)
			{
				return null;
			}

			var dimensions:uint = 0;
			var maxWidth:Number = 0;
			var rectList:Vector.<Rectangle> = new Vector.<Rectangle>;
			for each(rect in rectMap)
			{
				dimensions += rect.width * rect.height;
				rectList.push(rect);

				maxWidth = Math.max(rect.width, maxWidth);
			}

			//sort texture by size
			rectList.sort(sortRectList);

			if(widthDefault == 0)
			{
				//calculate width for Auto size
				widthDefault = Math.sqrt(dimensions);
			}

			widthDefault = Math.max(maxWidth + padding, widthDefault);
			if (isNearest2N)
			{
				widthDefault = getNearest2N(widthDefault);
			}

			var heightMax:uint = HIGHEST;
			var remainAreaList:Vector.<Rectangle> = new Vector.<Rectangle>;
			remainAreaList.push(new Rectangle(0, 0, widthDefault, heightMax));

			var isFit:Boolean;
			var width:int;
			var height:int;

			var area:Rectangle;
			var areaPrev:Rectangle;
			var areaNext:Rectangle;
			var areaID:int;
			var rectID:int;
			do
			{
				//Find highest blank area
				area = getHighestArea(remainAreaList);
				areaID = remainAreaList.indexOf(area);
				isFit = false;
				rectID = 0;
				for each(rect in rectList)
				{
					//check if the area is fit
					width = int(rect.width) + padding;
					height = int(rect.height) + padding;
					if (area.width >= width && area.height >= height)
					{
						//place portrait texture
						if(
							verticalSide?
							(
								height > width * 4?
								(
									areaID > 0?
									(area.height - height >= remainAreaList[areaID - 1].height):
									true
								):
								true
							):
							true
						)
						{
							isFit = true;
							break;
						}
					}
					rectID ++;
				}

				if(isFit)
				{
					//place texture if size is fit
					rect.x = area.x;
					rect.y = area.y;
					rectList.splice(rectID, 1);
					remainAreaList.splice(
						areaID + 1,
						0,
						new Rectangle(area.x + width, area.y, area.width - width, area.height)
					);
					area.y += height;
					area.width = width;
					area.height -= height;
				}
				else
				{
					//not fit, don't place it, merge blank area to others toghther
					if(areaID == 0)
					{
						areaNext = remainAreaList[areaID + 1];
					}
					else if(areaID == remainAreaList.length - 1)
					{
						areaNext = remainAreaList[areaID - 1];
					}
					else
					{
						areaPrev = remainAreaList[areaID - 1];
						areaNext = remainAreaList[areaID + 1];
						areaNext = areaPrev.height <= areaNext.height?areaNext:areaPrev;
					}
					if(area.x < areaNext.x)
					{
						areaNext.x = area.x;
					}
					areaNext.width = area.width + areaNext.width;
					remainAreaList.splice(areaID, 1);
				}
			}
			while (rectList.length > 0);

			heightMax = heightMax - (getLowestArea(remainAreaList).height + padding);

			if (isNearest2N)
			{
				getNearest2N(heightMax);
			}

			return new Rectangle(0, 0, widthDefault, heightMax);
		}

		private static function sortRectList(rect1:Rectangle, rect2:Rectangle):int
		{
			var v1:uint = rect1.width + rect1.height;
			var v2:uint = rect2.width + rect2.height;
			if (v1 == v2)
			{
				return rect1.width > rect2.width?-1:1;
			}
			return v1 > v2?-1:1;
		}

		private static function getNearest2N(_n:uint):uint
		{
			return _n & _n - 1?1 << _n.toString(2).length:_n;
		}

		private static function getHighestArea(areaList:Vector.<Rectangle>):Rectangle
		{
			var height:uint = 0;
			var areaHighest:Rectangle;
			for each(var area:Rectangle in areaList)
			{
				if (area.height > height)
				{
					height = area.height;
					areaHighest = area;
				}
			}
			return areaHighest;
		}

		private static function getLowestArea(areaList:Vector.<Rectangle>):Rectangle
		{
			var height:uint = HIGHEST;
			var areaLowest:Rectangle;
			for each(var area:Rectangle in areaList)
			{
				if (area.height < height)
				{
					height = area.height;
					areaLowest = area;
				}
			}
			return areaLowest;
		}
	}
}

package core.utils
{
	import flash.geom.Rectangle;

	/**
	 * For place texture
	 */
	public final class TextureUtil
	{
		private static const HIGHEST:uint = 0xFFFFFFFF;

		/**
		 * Place textures by textureAtlasXML data
		 */
		public static function packTextures(widthDefault:uint, padding:uint, rectMap:Object, verticalSide:Boolean = false, isNearest2N:Boolean = true):Rectangle
		{
			for each(var rect:Rectangle in rectMap)
			{
				break;
			}
			if(!rect)
			{
				return null;
			}

			var dimensions:uint = 0;
			var maxWidth:Number = 0;
			var rectList:Vector.<Rectangle> = new Vector.<Rectangle>;
			for each(rect in rectMap)
			{
				dimensions += rect.width * rect.height;
				rectList.push(rect);

				maxWidth = Math.max(rect.width, maxWidth);
			}

			//sort texture by size
			rectList.sort(sortRectList);

			if(widthDefault == 0)
			{
				//calculate width for Auto size
				widthDefault = Math.sqrt(dimensions);
			}

			widthDefault = Math.max(maxWidth + padding, widthDefault);
			if (isNearest2N)
			{
				widthDefault = getNearest2N(widthDefault);
			}

			var heightMax:uint = HIGHEST;
			var remainAreaList:Vector.<Rectangle> = new Vector.<Rectangle>;
			remainAreaList.push(new Rectangle(0, 0, widthDefault, heightMax));

			var isFit:Boolean;
			var width:int;
			var height:int;

			var area:Rectangle;
			var areaPrev:Rectangle;
			var areaNext:Rectangle;
			var areaID:int;
			var rectID:int;
			do
			{
				//Find highest blank area
				area = getHighestArea(remainAreaList);
				areaID = remainAreaList.indexOf(area);
				isFit = false;
				rectID = 0;
				for each(rect in rectList)
				{
					//check if the area is fit
					width = int(rect.width) + padding;
					height = int(rect.height) + padding;
					if (area.width >= width && area.height >= height)
					{
						//place portrait texture
						if(
							verticalSide?
							(
								height > width * 4?
								(
									areaID > 0?
									(area.height - height >= remainAreaList[areaID - 1].height):
									true
								):
								true
							):
							true
						)
						{
							isFit = true;
							break;
						}
					}
					rectID ++;
				}

				if(isFit)
				{
					//place texture if size is fit
					rect.x = area.x;
					rect.y = area.y;
					rectList.splice(rectID, 1);
					remainAreaList.splice(
						areaID + 1,
						0,
						new Rectangle(area.x + width, area.y, area.width - width, area.height)
					);
					area.y += height;
					area.width = width;
					area.height -= height;
				}
				else
				{
					//not fit, don't place it, merge blank area to others toghther
					if(areaID == 0)
					{
						areaNext = remainAreaList[areaID + 1];
					}
					else if(areaID == remainAreaList.length - 1)
					{
						areaNext = remainAreaList[areaID - 1];
					}
					else
					{
						areaPrev = remainAreaList[areaID - 1];
						areaNext = remainAreaList[areaID + 1];
						areaNext = areaPrev.height <= areaNext.height?areaNext:areaPrev;
					}
					if(area.x < areaNext.x)
					{
						areaNext.x = area.x;
					}
					areaNext.width = area.width + areaNext.width;
					remainAreaList.splice(areaID, 1);
				}
			}
			while (rectList.length > 0);

			heightMax = heightMax - (getLowestArea(remainAreaList).height + padding);

			if (isNearest2N)
			{
				heightMax = getNearest2N(heightMax);
			}

			return new Rectangle(0, 0, widthDefault, heightMax);
		}

		private static function sortRectList(rect1:Rectangle, rect2:Rectangle):int
		{
			var v1:uint = rect1.width + rect1.height;
			var v2:uint = rect2.width + rect2.height;
			if (v1 == v2)
			{
				return rect1.width > rect2.width?-1:1;
			}
			return v1 > v2?-1:1;
		}

		private static function getNearest2N(_n:uint):uint
		{
			return _n & _n - 1?1 << _n.toString(2).length:_n;
		}

		private static function getHighestArea(areaList:Vector.<Rectangle>):Rectangle
		{
			var height:uint = 0;
			var areaHighest:Rectangle;
			for each(var area:Rectangle in areaList)
			{
				if (area.height > height)
				{
					height = area.height;
					areaHighest = area;
				}
			}
			return areaHighest;
		}

		private static function getLowestArea(areaList:Vector.<Rectangle>):Rectangle
		{
			var height:uint = HIGHEST;
			var areaLowest:Rectangle;
			for each(var area:Rectangle in areaList)
			{
				if (area.height < height)
				{
					height = area.height;
					areaLowest = area;
				}
			}
			return areaLowest;
		}
	}
}

fix export bug change Export Scale makes not power of 2 image bug


/**
 * Copyright (c) 2009 Digital Primates IT Consulting Group
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 *
 * @author     Michael Labriola
 * @version
 **/
package org.flexunit.runners {
	import flex.lang.reflect.Klass;

	import org.flexunit.internals.dependency.IExternalRunnerDependencyWatcher;
	import org.flexunit.internals.runners.InitializationError;
	import org.flexunit.runner.Description;
	import org.flexunit.runner.IDescription;
	import org.flexunit.runner.IRunner;
	import org.flexunit.runner.external.IExternalDependencyRunner;
	import org.flexunit.runner.manipulation.IFilterable;
	import org.flexunit.runner.notification.IRunListener;
	import org.flexunit.runner.notification.IRunNotifier;
	import org.flexunit.runner.notification.StoppedByUserException;
	import org.flexunit.runners.model.IRunnerBuilder;
	import org.flexunit.token.AsyncTestToken;

	/**
 	 * A Suite is an IRunner that contains test cases and other
	 * Suites to be run during the course of a test run.  The Suite is
	 * responsible for locating all non-static classes that it contains and obtaining an array of
	 * IRunners for each child that was found in this manner.  The
	 * IRunnerBuilder to be used to determine the runner for the child classes is
	 * provided to the Suite during its instantiation.

	 *
	 * When a Suite goes to run a child, it is telling another IRunner to
	 * begin running, supplying the IRunner with an IRunNotifier in
	 * order to keep track of the test run.  An AsyncTestToken is also provided to
	 * the child IRunner in order to notify the Suite when the child has
	 * finished.

	 *
	 * In order to declare a class as a suite class, the class must include a [Suite]
	 * and [RunWith("org.flexunit.runners.Suite")] metadata tag.  The
	 * [RunWith] tag will instruct an IRunnerBuilder to use the
	 * Suite IRunner for the class.

	 *
	 *

	 * [Suite]
	 * [RunWith("org.flexunit.runners.Suite")]
	 * public class SuiteToRun
	 * {
	 * 	public var oneTest:OneTest; //A Test
	 * 	public var anotherTest:AnotherTest; //Another Test
	 * 	public var differentSuite:DifferentSuite; //A Suite
	 * }
	 *

	 */
	public class Suite extends ParentRunner implements IFilterable, IExternalDependencyRunner {
		/**
		 * @private
		 */
		private var _runners:Array;

		/**
		 * @private
		 */
		private var _dependencyWatcher:IExternalRunnerDependencyWatcher;

		/**
		 * @inheritDoc
		 */
		override public function pleaseStop():void {
			super.pleaseStop();

			if ( _runners ) {
				for ( var i:int=0; i<_runners.length; i++ ) {
					( _runners[ i ] as IRunner ).pleaseStop();
				}
			}
		}

		/**
		 * @private
		 */
		private var descriptionIsCached:Boolean = false;

		/**
		 * @inheritDoc
		 */
		override public function get description():IDescription {
			var desc:IDescription;

			if ( descriptionIsCached ) {
				desc = super.description;
			} else {
				if ( _dependencyWatcher && _dependencyWatcher.allDependenciesResolved ) {
					//We are good to go, so let it cache this time and from now on we will defer to the super class' copy
					descriptionIsCached = true;
					desc = super.description;
				} else {
					//For some reason we still have unresolved dependencies.. most likey, we have external dependencies
					//but we are being filtered, so, just keep generating new descriptions when asked as we could change
					desc = generateDescription();
				}
			}

			return desc;
		}

		/**
		 * @inheritDoc
		 */
		override protected function get children():Array {
			return _runners;
		}

		/**
		 * @inheritDoc
		 */
		override protected function describeChild( child:* ):IDescription {
			return IRunner( child ).description;
		}

		/**
		 * @inheritDoc
		 */
		override protected function runChild( child:*, notifier:IRunNotifier, childRunnerToken:AsyncTestToken ):void {
			if ( stopRequested ) {
				childRunnerToken.sendResult( new StoppedByUserException() );
				return;
			}

			IRunner( child ).run( notifier, childRunnerToken );
		}

		/**
		 * Setter for a dependency watcher. This is a class that implements IExternalRunnerDependencyWatcher
		 * and watches for any external dependencies (such as loading data) are finalized before execution of
		 * tests is allowed to commence.
		 *
		 * @param value An implementation of IExternalRunnerDependencyWatcher
		 */
		public function set dependencyWatcher( value:IExternalRunnerDependencyWatcher ):void {
			var runner:IRunner;

			_dependencyWatcher = value;

			if ( children ) {
				for ( var i:int=0; i<children.length; i++ ) {
					runner = children[ i ] as IRunner;

					if ( runner is IExternalDependencyRunner ) {
						( runner as IExternalDependencyRunner ).dependencyWatcher = value;
					}
				}
			}
		}

		/**
		 *
		 * Setter to indicate an error occured while attempting to load exteranl dependencies
		 * for this test. It accepts a string to allow the creator of the external dependency
		 * loader to pass a viable error string back to the user.
		 *
		 * @param value The error message
		 *
		 */
		public function set externalDependencyError( value:String ):void {
			//do nothing... suites don't actually have externalDependencies..
			//they just need to pass this along
		}

		/**
		 * Returns an array of non-static class feilds in the provided suite class.
		 *
		 * @param suite The class to check for non-static class fields.
		 *
		 * @return an array of non-static class feilds in the provided suite class.
		 */
		private static function getSuiteClasses( suite:Class ):Array {
			var klassInfo:Klass = new Klass( suite );
			var classRef:Class;
			var classArray:Array = new Array();

			var fields:Array = klassInfo.fields;

			for ( var i:int=0; i<fields.length; i++ ) {
				if ( !fields[ i ].isStatic ) {
					try {
						classRef = fields[i].type;
						classArray.push( classRef );
					} catch ( e:Error ) {
						//Not sure who we should inform here yet. We will need someway of capturing the idea that this
						//is a missing class, but not sure where or how to promote that up the chain....if it is even possible
						//that we could have a missing class, given the way we are linking it
					}
				}
			}

			/***
			  <variable name="two" type="suite.cases::TestTwo"/>
			  <variable name="one" type="suite.cases::TestOne"/>

  			SuiteClasses annotation= klass.getAnnotation(SuiteClasses.class);
			if (annotation == null)
				throw new InitializationError(String.format("class '%s' must have a SuiteClasses annotation", klass.getName()));
			return annotation.value();
			 **/
			 //this needs to return the suiteclasses
			 return classArray;
		}

		/**
		 * This will either be passed a builder, followed by an array of classes... (when there is not root class)
		 * Or it will be passed a root class and a builder.
		 *
		 * So, the two signatures we are supporting are:
		 *
		 * Suite( builder:IRunnerBuilder, classes:Array )
		 * Suite( testClass:Class, builder:IRunnerBuilder )
		 ***/
		public function Suite( arg1:*, arg2:* ) {
			var builder:IRunnerBuilder;
			var testClass:Class;
			var classArray:Array;
			var runnners:Array;
			var error:Boolean = false;

			if ( arg1 is IRunnerBuilder && arg2 is Array ) {
				builder = arg1 as IRunnerBuilder;
				classArray = arg2 as Array;
			} else if ( arg1 is Class && arg2 is IRunnerBuilder ) {
				testClass = arg1 as Class;
				builder = arg2 as IRunnerBuilder;
				classArray = getSuiteClasses(testClass);
			} else {
				error = true;
			}

			super( testClass );

			//Fix for FXU-51
			//Tests to see if suite actually has viable children. If it does not, it is considered an
			//initialization error
			if ( !error && classArray.length > 0) { //a class is specified as a Suite, and has children
				_runners = builder.runners( testClass, classArray );
			} else if ( !error && classArray.length == 0 ) {
				 throw new InitializationError("Empty test Suite!");
			} else {
				throw new Error("Incorrectly formed arguments passed to suite class");
			}
		}
	}
}

/**
 * Copyright (c) 2009 Digital Primates IT Consulting Group
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 *
 * @author     Michael Labriola
 * @version
 **/
package org.flexunit.runners {
	import flex.lang.reflect.Klass;

	import flexunit.framework.TestSuite;

	import org.flexunit.internals.dependency.IExternalRunnerDependencyWatcher;
	import org.flexunit.internals.runners.InitializationError;
	import org.flexunit.runner.Description;
	import org.flexunit.runner.IDescription;
	import org.flexunit.runner.IRunner;
	import org.flexunit.runner.external.IExternalDependencyRunner;
	import org.flexunit.runner.manipulation.IFilterable;
	import org.flexunit.runner.notification.IRunListener;
	import org.flexunit.runner.notification.IRunNotifier;
	import org.flexunit.runner.notification.StoppedByUserException;
	import org.flexunit.runners.model.IRunnerBuilder;
	import org.flexunit.token.AsyncTestToken;

	/**
 	 * A Suite is an IRunner that contains test cases and other
	 * Suites to be run during the course of a test run.  The Suite is
	 * responsible for locating all non-static classes that it contains and obtaining an array of
	 * IRunners for each child that was found in this manner.  The
	 * IRunnerBuilder to be used to determine the runner for the child classes is
	 * provided to the Suite during its instantiation.

	 *
	 * When a Suite goes to run a child, it is telling another IRunner to
	 * begin running, supplying the IRunner with an IRunNotifier in
	 * order to keep track of the test run.  An AsyncTestToken is also provided to
	 * the child IRunner in order to notify the Suite when the child has
	 * finished.

	 *
	 * In order to declare a class as a suite class, the class must include a [Suite]
	 * and [RunWith("org.flexunit.runners.Suite")] metadata tag.  The
	 * [RunWith] tag will instruct an IRunnerBuilder to use the
	 * Suite IRunner for the class.

	 *
	 *

	 * [Suite]
	 * [RunWith("org.flexunit.runners.Suite")]
	 * public class SuiteToRun
	 * {
	 * 	public var oneTest:OneTest; //A Test
	 * 	public var anotherTest:AnotherTest; //Another Test
	 * 	public var differentSuite:DifferentSuite; //A Suite
	 * }
	 *

	 */
	public class Suite extends ParentRunner implements IFilterable, IExternalDependencyRunner {
		/**
		 * @private
		 */
		private var _runners:Array;

		/**
		 * @private
		 */
		private var _dependencyWatcher:IExternalRunnerDependencyWatcher;

		/**
		 * @inheritDoc
		 */
		override public function pleaseStop():void {
			super.pleaseStop();

			if ( _runners ) {
				for ( var i:int=0; i<_runners.length; i++ ) {
					( _runners[ i ] as IRunner ).pleaseStop();
				}
			}
		}

		/**
		 * @private
		 */
		private var descriptionIsCached:Boolean = false;

		/**
		 * @inheritDoc
		 */
		override public function get description():IDescription {
			var desc:IDescription;

			if ( descriptionIsCached ) {
				desc = super.description;
			} else {
				if ( _dependencyWatcher && _dependencyWatcher.allDependenciesResolved ) {
					//We are good to go, so let it cache this time and from now on we will defer to the super class' copy
					descriptionIsCached = true;
					desc = super.description;
				} else {
					//For some reason we still have unresolved dependencies.. most likey, we have external dependencies
					//but we are being filtered, so, just keep generating new descriptions when asked as we could change
					desc = generateDescription();
				}
			}

			return desc;
		}

		/**
		 * @inheritDoc
		 */
		override protected function get children():Array {
			return _runners;
		}

		/**
		 * @inheritDoc
		 */
		override protected function describeChild( child:* ):IDescription {
			return IRunner( child ).description;
		}

		/**
		 * @inheritDoc
		 */
		override protected function runChild( child:*, notifier:IRunNotifier, childRunnerToken:AsyncTestToken ):void {
			if ( stopRequested ) {
				childRunnerToken.sendResult( new StoppedByUserException() );
				return;
			}

			IRunner( child ).run( notifier, childRunnerToken );
		}

		/**
		 * Setter for a dependency watcher. This is a class that implements IExternalRunnerDependencyWatcher
		 * and watches for any external dependencies (such as loading data) are finalized before execution of
		 * tests is allowed to commence.
		 *
		 * @param value An implementation of IExternalRunnerDependencyWatcher
		 */
		public function set dependencyWatcher( value:IExternalRunnerDependencyWatcher ):void {
			var runner:IRunner;

			_dependencyWatcher = value;

			if ( children ) {
				for ( var i:int=0; i<children.length; i++ ) {
					runner = children[ i ] as IRunner;

					if ( runner is IExternalDependencyRunner ) {
						( runner as IExternalDependencyRunner ).dependencyWatcher = value;
					}
				}
			}
		}

		/**
		 *
		 * Setter to indicate an error occured while attempting to load exteranl dependencies
		 * for this test. It accepts a string to allow the creator of the external dependency
		 * loader to pass a viable error string back to the user.
		 *
		 * @param value The error message
		 *
		 */
		public function set externalDependencyError( value:String ):void {
			//do nothing... suites don't actually have externalDependencies..
			//they just need to pass this along
		}

		/**
		 * Returns an array of non-static class feilds in the provided suite class.
		 *
		 * @param suite The class to check for non-static class fields.
		 *
		 * @return an array of non-static class feilds in the provided suite class.
		 */
		private static function getSuiteClasses( suite:Class ):Array {
			var klassInfo:Klass = new Klass( suite );
			var classRef:Class;
			var classArray:Array = new Array();

			var fields:Array = klassInfo.fields;

			//ADD CHECK FOR TESTRUNNER EXTENSION... this will cause an issue where the super class is introspected and attempts to test String and Collection
			if ( klassInfo.descendsFrom( TestSuite ) ) {
				throw new TypeError("This suite both extends from the FlexUnit 1 TestSuite class and has the FlexUnit 4 metada. Please do not extend from TestSuite.");
			}

			for ( var i:int=0; i<fields.length; i++ ) {
				if ( !fields[ i ].isStatic ) {
					try {
						classRef = fields[i].type;
						classArray.push( classRef );
					} catch ( e:Error ) {
						//Not sure who we should inform here yet. We will need someway of capturing the idea that this
						//is a missing class, but not sure where or how to promote that up the chain....if it is even possible
						//that we could have a missing class, given the way we are linking it
					}
				}
			}

			/***
			  <variable name="two" type="suite.cases::TestTwo"/>
			  <variable name="one" type="suite.cases::TestOne"/>

  			SuiteClasses annotation= klass.getAnnotation(SuiteClasses.class);
			if (annotation == null)
				throw new InitializationError(String.format("class '%s' must have a SuiteClasses annotation", klass.getName()));
			return annotation.value();
			 **/
			 //this needs to return the suiteclasses
			 return classArray;
		}

		/**
		 * This will either be passed a builder, followed by an array of classes... (when there is not root class)
		 * Or it will be passed a root class and a builder.
		 *
		 * So, the two signatures we are supporting are:
		 *
		 * Suite( builder:IRunnerBuilder, classes:Array )
		 * Suite( testClass:Class, builder:IRunnerBuilder )
		 ***/
		public function Suite( arg1:*, arg2:* ) {
			var builder:IRunnerBuilder;
			var testClass:Class;
			var classArray:Array;
			var runnners:Array;
			var error:Boolean = false;

			if ( arg1 is IRunnerBuilder && arg2 is Array ) {
				builder = arg1 as IRunnerBuilder;
				classArray = arg2 as Array;
			} else if ( arg1 is Class && arg2 is IRunnerBuilder ) {
				testClass = arg1 as Class;
				builder = arg2 as IRunnerBuilder;
				classArray = getSuiteClasses(testClass);
			} else {
				error = true;
			}

			super( testClass );

			//Fix for FXU-51
			//Tests to see if suite actually has viable children. If it does not, it is considered an
			//initialization error
			if ( !error && classArray.length > 0) { //a class is specified as a Suite, and has children
				_runners = builder.runners( testClass, classArray );
			} else if ( !error && classArray.length == 0 ) {
				 throw new InitializationError("Empty test Suite!");
			} else {
				throw new Error("Incorrectly formed arguments passed to suite class");
			}
		}
	}
}

Fix per Jesse Warden's twitter issue. When using both the RunWith and specifying the Suite runner and descending from the old FlexUnit.9 Suite class, you receive initialization errors as the Suite code finds both a public String and Collection instance in the TestSuite class and attempts to treat them as TestCases


// Urho3D editor node transform gizmo handling

Node@ gizmoNode;
StaticModel@ gizmo;

const float axisMaxD = 0.1;
const float axisMaxT = 1.0;
const float rotSensitivity = 50.0;

EditMode lastGizmoMode;

// For undo
bool previousGizmoDrag;
bool needGizmoUndo;
Array<Transform> oldGizmoTransforms;

class GizmoAxis
{
    Ray axisRay;
    bool selected;
    bool lastSelected;
    float t;
    float d;
    float lastT;
    float lastD;

    GizmoAxis()
    {
        selected = false;
        lastSelected = false;
        t = 0.0;
        d = 0.0;
        lastT = 0.0;
        lastD = 0.0;
    }

    void Update(Ray cameraRay, float scale, bool drag)
    {
        // Do not select when UI has modal element
        if (ui.HasModalElement())
        {
            selected = false;
            return;
        }

        Vector3 closest = cameraRay.ClosestPoint(axisRay);
        Vector3 projected = axisRay.Project(closest);
        d = axisRay.Distance(closest);
        t = (projected - axisRay.origin).DotProduct(axisRay.direction);

        // Determine the sign of d from a plane that goes through the camera position to the axis
        Plane axisPlane(cameraNode.position, axisRay.origin, axisRay.origin + axisRay.direction);
        if (axisPlane.Distance(closest) < 0.0)
            d = -d;

        // Update selected status only when not dragging
        if (!drag)
        {
            selected = Abs(d) < axisMaxD * scale && t >= -axisMaxD * scale && t <= axisMaxT * scale;
            lastT = t;
            lastD = d;
        }
    }

    void Moved()
    {
        lastT = t;
        lastD = d;
    }
}

GizmoAxis gizmoAxisX;
GizmoAxis gizmoAxisY;
GizmoAxis gizmoAxisZ;

void CreateGizmo()
{
    gizmoNode = Node();
    gizmo = gizmoNode.CreateComponent("StaticModel");
    gizmo.model = cache.GetResource("Model", "Models/Editor/Axes.mdl");
    gizmo.materials[0] = cache.GetResource("Material", "Materials/Editor/RedUnlit.xml");
    gizmo.materials[1] = cache.GetResource("Material", "Materials/Editor/GreenUnlit.xml");
    gizmo.materials[2] = cache.GetResource("Material", "Materials/Editor/BlueUnlit.xml");
    gizmo.enabled = false;
    gizmo.viewMask = 0x80000000; // Editor raycasts use viewmask 0x7fffffff
    gizmo.occludee = false;

    gizmoAxisX.lastSelected = false;
    gizmoAxisY.lastSelected = false;
    gizmoAxisZ.lastSelected = false;
    lastGizmoMode = EDIT_MOVE;
}

void HideGizmo()
{
    if (gizmo !is null)
        gizmo.enabled = false;
}

void ShowGizmo()
{
    if (gizmo !is null)
    {
        gizmo.enabled = true;

        // Because setting enabled = false detaches the gizmo from octree,
        // and it is a manually added drawable, must readd to octree when showing
        if (editorScene.octree !is null)
            editorScene.octree.AddManualDrawable(gizmo);
    }
}

void UpdateGizmo()
{
    UseGizmo();
    PositionGizmo();
    ResizeGizmo();
}

void PositionGizmo()
{
    if (gizmo is null)
        return;

    Vector3 center(0, 0, 0);
    bool containsScene = false;

    for (uint i = 0; i < editNodes.length; ++i)
    {
        // Scene's transform should not be edited, so hide gizmo if it is included
        if (editNodes[i] is editorScene)
        {
            containsScene = true;
            break;
        }
        center += editNodes[i].worldPosition;
    }

    if (editNodes.empty || containsScene)
    {
        HideGizmo();
        return;
    }

    center /= editNodes.length;
    gizmoNode.position = center;

    if (axisMode == AXIS_WORLD || editNodes.length > 1)
        gizmoNode.rotation = Quaternion();
    else
        gizmoNode.rotation = editNodes[0].worldRotation;

    if (editMode != lastGizmoMode)
    {
        switch (editMode)
        {
        case EDIT_MOVE:
            gizmo.model = cache.GetResource("Model", "Models/Editor/Axes.mdl");
            break;

        case EDIT_ROTATE:
            gizmo.model = cache.GetResource("Model", "Models/Editor/RotateAxes.mdl");
            break;

        case EDIT_SCALE:
            gizmo.model = cache.GetResource("Model", "Models/Editor/ScaleAxes.mdl");
            break;
        }

        lastGizmoMode = editMode;
    }

    if ((editMode != EDIT_SELECT && !orbiting) && !gizmo.enabled)
        ShowGizmo();
    else if ((editMode == EDIT_SELECT || orbiting) && gizmo.enabled)
        HideGizmo();
}

void ResizeGizmo()
{
    if (gizmo is null || !gizmo.enabled)
        return;

    float c = 0.1;

    if (camera.orthographic)
        gizmoNode.scale = Vector3(c, c, c);
    else
    {
        /// \todo if matrix classes were exposed to script could simply use the camera's inverse world transform
        float z = (cameraNode.worldRotation.Inverse() * (gizmoNode.worldPosition - cameraNode.worldPosition)).z;
        gizmoNode.scale = Vector3(c * z, c * z, c * z);
    }
}

void CalculateGizmoAxes()
{
    gizmoAxisX.axisRay = Ray(gizmoNode.position, gizmoNode.rotation * Vector3(1, 0, 0));
    gizmoAxisY.axisRay = Ray(gizmoNode.position, gizmoNode.rotation * Vector3(0, 1, 0));
    gizmoAxisZ.axisRay = Ray(gizmoNode.position, gizmoNode.rotation * Vector3(0, 0, 1));
}

void GizmoMoved()
{
    gizmoAxisX.Moved();
    gizmoAxisY.Moved();
    gizmoAxisZ.Moved();
}

void UseGizmo()
{
    if (gizmo is null || !gizmo.enabled || editMode == EDIT_SELECT)
    {
        StoreGizmoEditActions();
        previousGizmoDrag = false;
        return;
    }

    IntVector2 pos = ui.cursorPosition;
    if (ui.GetElementAt(pos) !is null)
        return;
    Ray cameraRay = GetActiveViewportCameraRay();
    float scale = gizmoNode.scale.x;

    // Recalculate axes only when not left-dragging
    bool drag = input.mouseButtonDown[MOUSEB_LEFT];
    if (!drag)
        CalculateGizmoAxes();

    gizmoAxisX.Update(cameraRay, scale, drag);
    gizmoAxisY.Update(cameraRay, scale, drag);
    gizmoAxisZ.Update(cameraRay, scale, drag);

    if (gizmoAxisX.selected != gizmoAxisX.lastSelected)
    {
        gizmo.materials[0] = cache.GetResource("Material", gizmoAxisX.selected ? "Materials/Editor/BrightRedUnlit.xml" :
            "Materials/Editor/RedUnlit.xml");
        gizmoAxisX.lastSelected = gizmoAxisX.selected;
    }
    if (gizmoAxisY.selected != gizmoAxisY.lastSelected)
    {
        gizmo.materials[1] = cache.GetResource("Material", gizmoAxisY.selected ? "Materials/Editor/BrightGreenUnlit.xml" :
            "Materials/Editor/GreenUnlit.xml");
        gizmoAxisY.lastSelected = gizmoAxisY.selected;
    }
    if (gizmoAxisZ.selected != gizmoAxisZ.lastSelected)
    {
        gizmo.materials[2] = cache.GetResource("Material", gizmoAxisZ.selected ? "Materials/Editor/BrightBlueUnlit.xml" :
            "Materials/Editor/BlueUnlit.xml");
        gizmoAxisZ.lastSelected = gizmoAxisZ.selected;
    };

    if (drag)
    {
        // Store initial transforms for undo when gizmo drag started
        if (!previousGizmoDrag)
        {
            oldGizmoTransforms.Resize(editNodes.length);
            for (uint i = 0; i < editNodes.length; ++i)
                oldGizmoTransforms[i].Define(editNodes[i]);
        }

        bool moved = false;

        if (editMode == EDIT_MOVE)
        {
            Vector3 adjust(0, 0, 0);
            if (gizmoAxisX.selected)
                adjust += Vector3(1, 0, 0) * (gizmoAxisX.t - gizmoAxisX.lastT);
            if (gizmoAxisY.selected)
                adjust += Vector3(0, 1, 0) * (gizmoAxisY.t - gizmoAxisY.lastT);
            if (gizmoAxisZ.selected)
                adjust += Vector3(0, 0, 1) * (gizmoAxisZ.t - gizmoAxisZ.lastT);

            moved = MoveNodes(adjust);
        }
        else if (editMode == EDIT_ROTATE)
        {
            Vector3 adjust(0, 0, 0);
            if (gizmoAxisX.selected)
                adjust.x = (gizmoAxisX.d - gizmoAxisX.lastD) * rotSensitivity / scale;
            if (gizmoAxisY.selected)
                adjust.y = -(gizmoAxisY.d - gizmoAxisY.lastD) * rotSensitivity / scale;
            if (gizmoAxisZ.selected)
                adjust.z = (gizmoAxisZ.d - gizmoAxisZ.lastD) * rotSensitivity / scale;

            moved = RotateNodes(adjust);
        }
        else if (editMode == EDIT_SCALE)
        {
            Vector3 adjust(0, 0, 0);
            if (gizmoAxisX.selected)
                adjust += Vector3(1, 0, 0) * (gizmoAxisX.t - gizmoAxisX.lastT);
            if (gizmoAxisY.selected)
                adjust += Vector3(0, 1, 0) * (gizmoAxisY.t - gizmoAxisY.lastT);
            if (gizmoAxisZ.selected)
                adjust += Vector3(0, 0, 1) * (gizmoAxisZ.t - gizmoAxisZ.lastT);

            // Special handling for uniform scale: use the unmodified X-axis movement only
            if (editMode == EDIT_SCALE && gizmoAxisX.selected && gizmoAxisY.selected && gizmoAxisZ.selected)
            {
                float x = gizmoAxisX.t - gizmoAxisX.lastT;
                adjust = Vector3(x, x, x);
            }

            moved = ScaleNodes(adjust);
        }

        if (moved)
        {
            GizmoMoved();
            UpdateNodeAttributes();
            needGizmoUndo = true;
        }
    }
    else
    {
        if (previousGizmoDrag)
            StoreGizmoEditActions();
    }

    previousGizmoDrag = drag;
}

bool IsGizmoSelected()
{
    return gizmo !is null && gizmo.enabled && (gizmoAxisX.selected || gizmoAxisY.selected || gizmoAxisZ.selected);
}

bool MoveNodes(Vector3 adjust)
{
    bool moved = false;

    if (adjust.length > M_EPSILON)
    {
        for (uint i = 0; i < editNodes.length; ++i)
        {
            if (moveSnap)
            {
                float moveStepScaled = moveStep * snapScale;
                adjust.x = Floor(adjust.x / moveStepScaled + 0.5) * moveStepScaled;
                adjust.y = Floor(adjust.y / moveStepScaled + 0.5) * moveStepScaled;
                adjust.z = Floor(adjust.z / moveStepScaled + 0.5) * moveStepScaled;
            }

            Node@ node = editNodes[i];
            Vector3 nodeAdjust = adjust;
            if (axisMode == AXIS_LOCAL && editNodes.length == 1)
                nodeAdjust = node.worldRotation * nodeAdjust;

            Vector3 worldPos = node.worldPosition;
            Vector3 oldPos = node.position;

            worldPos += nodeAdjust;

            if (node.parent is null)
                node.position = worldPos;
            else
                node.position = node.parent.WorldToLocal(worldPos);

            if (node.position != oldPos)
                moved = true;
        }
    }

    return moved;
}

bool RotateNodes(Vector3 adjust)
{
    bool moved = false;

    if (rotateSnap)
    {
        float rotateStepScaled = rotateStep * snapScale;
        adjust.x = Floor(adjust.x / rotateStepScaled + 0.5) * rotateStepScaled;
        adjust.y = Floor(adjust.y / rotateStepScaled + 0.5) * rotateStepScaled;
        adjust.z = Floor(adjust.z / rotateStepScaled + 0.5) * rotateStepScaled;
    }

    if (adjust.length > M_EPSILON)
    {
        moved = true;

        for (uint i = 0; i < editNodes.length; ++i)
        {
            Node@ node = editNodes[i];
            Quaternion rotQuat(adjust);
            if (axisMode == AXIS_LOCAL && editNodes.length == 1)
                node.rotation = node.rotation * rotQuat;
            else
            {
                Vector3 offset = node.worldPosition - gizmoAxisX.axisRay.origin;
                if (node.parent !is null && node.parent.worldRotation != Quaternion(1, 0, 0, 0))
                    rotQuat = node.parent.worldRotation.Inverse() * rotQuat * node.parent.worldRotation;
                node.rotation = rotQuat * node.rotation;
                Vector3 newPosition = gizmoAxisX.axisRay.origin + rotQuat * offset;
                if (node.parent !is null)
                    newPosition = node.parent.WorldToLocal(newPosition);
                node.position = newPosition;
            }
        }
    }

    return moved;
}

bool ScaleNodes(Vector3 adjust)
{
    bool moved = false;

    if (adjust.length > M_EPSILON)
    {
        for (uint i = 0; i < editNodes.length; ++i)
        {
            Node@ node = editNodes[i];

            Vector3 scale = node.scale;
            Vector3 oldScale = scale;

            if (!scaleSnap)
                scale += adjust;
            else
            {
                float scaleStepScaled = scaleStep * snapScale;
                if (adjust.x != 0)
                {
                    scale.x += adjust.x * scaleStepScaled;
                    scale.x = Floor(scale.x / scaleStepScaled + 0.5) * scaleStepScaled;
                }
                if (adjust.y != 0)
                {
                    scale.y += adjust.y * scaleStepScaled;
                    scale.y = Floor(scale.y / scaleStepScaled + 0.5) * scaleStepScaled;
                }
                if (adjust.z != 0)
                {
                    scale.z += adjust.z * scaleStepScaled;
                    scale.z = Floor(scale.z / scaleStepScaled + 0.5) * scaleStepScaled;
                }
            }

            if (scale != oldScale)
                moved = true;

            node.scale = scale;
        }
    }

    return moved;
}

void StoreGizmoEditActions()
{
    if (needGizmoUndo && editNodes.length > 0 && oldGizmoTransforms.length == editNodes.length)
    {
        EditActionGroup group;

        for (uint i = 0; i < editNodes.length; ++i)
        {
            EditNodeTransformAction action;
            action.Define(editNodes[i], oldGizmoTransforms[i]);
            group.actions.Push(action);
        }

        SaveEditActionGroup(group);
        SetSceneModified();
    }

    needGizmoUndo = false;
}

// Urho3D editor node transform gizmo handling

Node@ gizmoNode;
StaticModel@ gizmo;

const float axisMaxD = 0.1;
const float axisMaxT = 1.0;
const float rotSensitivity = 50.0;

EditMode lastGizmoMode;

// For undo
bool previousGizmoDrag;
bool needGizmoUndo;
Array<Transform> oldGizmoTransforms;

class GizmoAxis
{
    Ray axisRay;
    bool selected;
    bool lastSelected;
    float t;
    float d;
    float lastT;
    float lastD;

    GizmoAxis()
    {
        selected = false;
        lastSelected = false;
        t = 0.0;
        d = 0.0;
        lastT = 0.0;
        lastD = 0.0;
    }

    void Update(Ray cameraRay, float scale, bool drag)
    {
        // Do not select when UI has modal element
        if (ui.HasModalElement())
        {
            selected = false;
            return;
        }

        Vector3 closest = cameraRay.ClosestPoint(axisRay);
        Vector3 projected = axisRay.Project(closest);
        d = axisRay.Distance(closest);
        t = (projected - axisRay.origin).DotProduct(axisRay.direction);

        // Determine the sign of d from a plane that goes through the camera position to the axis
        Plane axisPlane(cameraNode.position, axisRay.origin, axisRay.origin + axisRay.direction);
        if (axisPlane.Distance(closest) < 0.0)
            d = -d;

        // Update selected status only when not dragging
        if (!drag)
        {
            selected = Abs(d) < axisMaxD * scale && t >= -axisMaxD * scale && t <= axisMaxT * scale;
            lastT = t;
            lastD = d;
        }
    }

    void Moved()
    {
        lastT = t;
        lastD = d;
    }
}

GizmoAxis gizmoAxisX;
GizmoAxis gizmoAxisY;
GizmoAxis gizmoAxisZ;

void CreateGizmo()
{
    gizmoNode = Node();
    gizmo = gizmoNode.CreateComponent("StaticModel");
    gizmo.model = cache.GetResource("Model", "Models/Editor/Axes.mdl");
    gizmo.materials[0] = cache.GetResource("Material", "Materials/Editor/RedUnlit.xml");
    gizmo.materials[1] = cache.GetResource("Material", "Materials/Editor/GreenUnlit.xml");
    gizmo.materials[2] = cache.GetResource("Material", "Materials/Editor/BlueUnlit.xml");
    gizmo.enabled = false;
    gizmo.viewMask = 0x80000000; // Editor raycasts use viewmask 0x7fffffff
    gizmo.occludee = false;

    gizmoAxisX.lastSelected = false;
    gizmoAxisY.lastSelected = false;
    gizmoAxisZ.lastSelected = false;
    lastGizmoMode = EDIT_MOVE;
}

void HideGizmo()
{
    if (gizmo !is null)
        gizmo.enabled = false;
}

void ShowGizmo()
{
    if (gizmo !is null)
    {
        gizmo.enabled = true;

        // Because setting enabled = false detaches the gizmo from octree,
        // and it is a manually added drawable, must readd to octree when showing
        if (editorScene.octree !is null)
            editorScene.octree.AddManualDrawable(gizmo);
    }
}

void UpdateGizmo()
{
    UseGizmo();
    PositionGizmo();
    ResizeGizmo();
}

void PositionGizmo()
{
    if (gizmo is null)
        return;

    Vector3 center(0, 0, 0);
    bool containsScene = false;

    for (uint i = 0; i < editNodes.length; ++i)
    {
        // Scene's transform should not be edited, so hide gizmo if it is included
        if (editNodes[i] is editorScene)
        {
            containsScene = true;
            break;
        }
        center += editNodes[i].worldPosition;
    }

    if (editNodes.empty || containsScene)
    {
        HideGizmo();
        return;
    }

    center /= editNodes.length;
    gizmoNode.position = center;

    if (axisMode == AXIS_WORLD || editNodes.length > 1)
        gizmoNode.rotation = Quaternion();
    else
        gizmoNode.rotation = editNodes[0].worldRotation;

    if (editMode != lastGizmoMode)
    {
        switch (editMode)
        {
        case EDIT_MOVE:
            gizmo.model = cache.GetResource("Model", "Models/Editor/Axes.mdl");
            break;

        case EDIT_ROTATE:
            gizmo.model = cache.GetResource("Model", "Models/Editor/RotateAxes.mdl");
            break;

        case EDIT_SCALE:
            gizmo.model = cache.GetResource("Model", "Models/Editor/ScaleAxes.mdl");
            break;
        }

        lastGizmoMode = editMode;
    }

    if ((editMode != EDIT_SELECT && !orbiting) && !gizmo.enabled)
        ShowGizmo();
    else if ((editMode == EDIT_SELECT || orbiting) && gizmo.enabled)
        HideGizmo();
}

void ResizeGizmo()
{
    if (gizmo is null || !gizmo.enabled)
        return;

    float scale = 0.1;

    if (camera.orthographic)
        scale *= camera.orthoSize;
    else
        scale *= (camera.view.Inverse() * gizmoNode.position).z;

    gizmoNode.scale = Vector3(scale, scale, scale);
}

void CalculateGizmoAxes()
{
    gizmoAxisX.axisRay = Ray(gizmoNode.position, gizmoNode.rotation * Vector3(1, 0, 0));
    gizmoAxisY.axisRay = Ray(gizmoNode.position, gizmoNode.rotation * Vector3(0, 1, 0));
    gizmoAxisZ.axisRay = Ray(gizmoNode.position, gizmoNode.rotation * Vector3(0, 0, 1));
}

void GizmoMoved()
{
    gizmoAxisX.Moved();
    gizmoAxisY.Moved();
    gizmoAxisZ.Moved();
}

void UseGizmo()
{
    if (gizmo is null || !gizmo.enabled || editMode == EDIT_SELECT)
    {
        StoreGizmoEditActions();
        previousGizmoDrag = false;
        return;
    }

    IntVector2 pos = ui.cursorPosition;
    if (ui.GetElementAt(pos) !is null)
        return;
    Ray cameraRay = GetActiveViewportCameraRay();
    float scale = gizmoNode.scale.x;

    // Recalculate axes only when not left-dragging
    bool drag = input.mouseButtonDown[MOUSEB_LEFT];
    if (!drag)
        CalculateGizmoAxes();

    gizmoAxisX.Update(cameraRay, scale, drag);
    gizmoAxisY.Update(cameraRay, scale, drag);
    gizmoAxisZ.Update(cameraRay, scale, drag);

    if (gizmoAxisX.selected != gizmoAxisX.lastSelected)
    {
        gizmo.materials[0] = cache.GetResource("Material", gizmoAxisX.selected ? "Materials/Editor/BrightRedUnlit.xml" :
            "Materials/Editor/RedUnlit.xml");
        gizmoAxisX.lastSelected = gizmoAxisX.selected;
    }
    if (gizmoAxisY.selected != gizmoAxisY.lastSelected)
    {
        gizmo.materials[1] = cache.GetResource("Material", gizmoAxisY.selected ? "Materials/Editor/BrightGreenUnlit.xml" :
            "Materials/Editor/GreenUnlit.xml");
        gizmoAxisY.lastSelected = gizmoAxisY.selected;
    }
    if (gizmoAxisZ.selected != gizmoAxisZ.lastSelected)
    {
        gizmo.materials[2] = cache.GetResource("Material", gizmoAxisZ.selected ? "Materials/Editor/BrightBlueUnlit.xml" :
            "Materials/Editor/BlueUnlit.xml");
        gizmoAxisZ.lastSelected = gizmoAxisZ.selected;
    };

    if (drag)
    {
        // Store initial transforms for undo when gizmo drag started
        if (!previousGizmoDrag)
        {
            oldGizmoTransforms.Resize(editNodes.length);
            for (uint i = 0; i < editNodes.length; ++i)
                oldGizmoTransforms[i].Define(editNodes[i]);
        }

        bool moved = false;

        if (editMode == EDIT_MOVE)
        {
            Vector3 adjust(0, 0, 0);
            if (gizmoAxisX.selected)
                adjust += Vector3(1, 0, 0) * (gizmoAxisX.t - gizmoAxisX.lastT);
            if (gizmoAxisY.selected)
                adjust += Vector3(0, 1, 0) * (gizmoAxisY.t - gizmoAxisY.lastT);
            if (gizmoAxisZ.selected)
                adjust += Vector3(0, 0, 1) * (gizmoAxisZ.t - gizmoAxisZ.lastT);

            moved = MoveNodes(adjust);
        }
        else if (editMode == EDIT_ROTATE)
        {
            Vector3 adjust(0, 0, 0);
            if (gizmoAxisX.selected)
                adjust.x = (gizmoAxisX.d - gizmoAxisX.lastD) * rotSensitivity / scale;
            if (gizmoAxisY.selected)
                adjust.y = -(gizmoAxisY.d - gizmoAxisY.lastD) * rotSensitivity / scale;
            if (gizmoAxisZ.selected)
                adjust.z = (gizmoAxisZ.d - gizmoAxisZ.lastD) * rotSensitivity / scale;

            moved = RotateNodes(adjust);
        }
        else if (editMode == EDIT_SCALE)
        {
            Vector3 adjust(0, 0, 0);
            if (gizmoAxisX.selected)
                adjust += Vector3(1, 0, 0) * (gizmoAxisX.t - gizmoAxisX.lastT);
            if (gizmoAxisY.selected)
                adjust += Vector3(0, 1, 0) * (gizmoAxisY.t - gizmoAxisY.lastT);
            if (gizmoAxisZ.selected)
                adjust += Vector3(0, 0, 1) * (gizmoAxisZ.t - gizmoAxisZ.lastT);

            // Special handling for uniform scale: use the unmodified X-axis movement only
            if (editMode == EDIT_SCALE && gizmoAxisX.selected && gizmoAxisY.selected && gizmoAxisZ.selected)
            {
                float x = gizmoAxisX.t - gizmoAxisX.lastT;
                adjust = Vector3(x, x, x);
            }

            moved = ScaleNodes(adjust);
        }

        if (moved)
        {
            GizmoMoved();
            UpdateNodeAttributes();
            needGizmoUndo = true;
        }
    }
    else
    {
        if (previousGizmoDrag)
            StoreGizmoEditActions();
    }

    previousGizmoDrag = drag;
}

bool IsGizmoSelected()
{
    return gizmo !is null && gizmo.enabled && (gizmoAxisX.selected || gizmoAxisY.selected || gizmoAxisZ.selected);
}

bool MoveNodes(Vector3 adjust)
{
    bool moved = false;

    if (adjust.length > M_EPSILON)
    {
        for (uint i = 0; i < editNodes.length; ++i)
        {
            if (moveSnap)
            {
                float moveStepScaled = moveStep * snapScale;
                adjust.x = Floor(adjust.x / moveStepScaled + 0.5) * moveStepScaled;
                adjust.y = Floor(adjust.y / moveStepScaled + 0.5) * moveStepScaled;
                adjust.z = Floor(adjust.z / moveStepScaled + 0.5) * moveStepScaled;
            }

            Node@ node = editNodes[i];
            Vector3 nodeAdjust = adjust;
            if (axisMode == AXIS_LOCAL && editNodes.length == 1)
                nodeAdjust = node.worldRotation * nodeAdjust;

            Vector3 worldPos = node.worldPosition;
            Vector3 oldPos = node.position;

            worldPos += nodeAdjust;

            if (node.parent is null)
                node.position = worldPos;
            else
                node.position = node.parent.WorldToLocal(worldPos);

            if (node.position != oldPos)
                moved = true;
        }
    }

    return moved;
}

bool RotateNodes(Vector3 adjust)
{
    bool moved = false;

    if (rotateSnap)
    {
        float rotateStepScaled = rotateStep * snapScale;
        adjust.x = Floor(adjust.x / rotateStepScaled + 0.5) * rotateStepScaled;
        adjust.y = Floor(adjust.y / rotateStepScaled + 0.5) * rotateStepScaled;
        adjust.z = Floor(adjust.z / rotateStepScaled + 0.5) * rotateStepScaled;
    }

    if (adjust.length > M_EPSILON)
    {
        moved = true;

        for (uint i = 0; i < editNodes.length; ++i)
        {
            Node@ node = editNodes[i];
            Quaternion rotQuat(adjust);
            if (axisMode == AXIS_LOCAL && editNodes.length == 1)
                node.rotation = node.rotation * rotQuat;
            else
            {
                Vector3 offset = node.worldPosition - gizmoAxisX.axisRay.origin;
                if (node.parent !is null && node.parent.worldRotation != Quaternion(1, 0, 0, 0))
                    rotQuat = node.parent.worldRotation.Inverse() * rotQuat * node.parent.worldRotation;
                node.rotation = rotQuat * node.rotation;
                Vector3 newPosition = gizmoAxisX.axisRay.origin + rotQuat * offset;
                if (node.parent !is null)
                    newPosition = node.parent.WorldToLocal(newPosition);
                node.position = newPosition;
            }
        }
    }

    return moved;
}

bool ScaleNodes(Vector3 adjust)
{
    bool moved = false;

    if (adjust.length > M_EPSILON)
    {
        for (uint i = 0; i < editNodes.length; ++i)
        {
            Node@ node = editNodes[i];

            Vector3 scale = node.scale;
            Vector3 oldScale = scale;

            if (!scaleSnap)
                scale += adjust;
            else
            {
                float scaleStepScaled = scaleStep * snapScale;
                if (adjust.x != 0)
                {
                    scale.x += adjust.x * scaleStepScaled;
                    scale.x = Floor(scale.x / scaleStepScaled + 0.5) * scaleStepScaled;
                }
                if (adjust.y != 0)
                {
                    scale.y += adjust.y * scaleStepScaled;
                    scale.y = Floor(scale.y / scaleStepScaled + 0.5) * scaleStepScaled;
                }
                if (adjust.z != 0)
                {
                    scale.z += adjust.z * scaleStepScaled;
                    scale.z = Floor(scale.z / scaleStepScaled + 0.5) * scaleStepScaled;
                }
            }

            if (scale != oldScale)
                moved = true;

            node.scale = scale;
        }
    }

    return moved;
}

void StoreGizmoEditActions()
{
    if (needGizmoUndo && editNodes.length > 0 && oldGizmoTransforms.length == editNodes.length)
    {
        EditActionGroup group;

        for (uint i = 0; i < editNodes.length; ++i)
        {
            EditNodeTransformAction action;
            action.Define(editNodes[i], oldGizmoTransforms[i]);
            group.actions.Push(action);
        }

        SaveEditActionGroup(group);
        SetSceneModified();
    }

    needGizmoUndo = false;
}

Fix gizmo scale in orthographic mode (ortho size not taken into account). Cleaned up gizmo scaling code to use camera view matrix. Closes #626.


package krewfw.starling_utility {

    import flash.geom.Point;
    import starling.display.Image;
    import starling.textures.Texture;

    /**
     * Tiled Map Editor (http://www.mapeditor.org/) $B$N(B tmx $B%U%!%$%k$+$i(B
     * $B=PNO$7$?(B json $B$r$b$H$K3F%^%9$N(B Image $B$rJV$9%f!<%F%#%j%F%#(B
     */
    //------------------------------------------------------------
    public class TileMapHelper {

        // avoid instantiation cost
        private static var _point:Point = new Point(0, 0);

        /**
         * Tiled Map Editor $B$G$O6u%?%$%k$O(B 0 $B$HI=8=$5$l$k!#(B
         * $B%=!<%9$N%?%$%k2hA|$N0lHV:8>e$O(B 1 $B$+$i;O$^$k!#(B
         * $B;XDj$7$?%^%9$,(B 0 $B$N>l9g$O(B null $B$rJV$9(B.
         *
         * [Note] $B0J2<$N%?%$%k2hA|$N%U%)!<%^%C%H$G%F%9%H!'(B
         * <pre>
         *     - Canvas size: 512 x 512
         *     - Tile size: 32 x 32
         *     - spacing: 2
         * </pre>
         */
        public static function getTileImage(tileMapInfo:Object, tileLayer:Object, tileSet:Object,
                                            tilesTexture:Texture, col:uint, row:uint):Image
        {
            // calculate UV coord
            var numMapCol:uint = tileLayer.width;
            var tileIndex:int  = tileLayer.data[(row * numMapCol) + col] - 1;
            if (tileIndex < 0) { return null; }

            // * consider spacing
            var tileWidth :Number = (tileSet.tilewidth  + tileSet.spacing);
            var tileHeight:Number = (tileSet.tileheight + tileSet.spacing);

            var numTileImageCol:uint = tileSet.imagewidth  / tileWidth;
            var numTileImageRow:uint = tileSet.imageheight / tileHeight;
            var tileImageCol:uint = tileIndex % numTileImageCol;
            var tileImageRow:uint = tileIndex / numTileImageCol;

            var uvLeft:Number = (tileWidth  * tileImageCol) / tileSet.imagewidth;
            var uvTop :Number = (tileHeight * tileImageRow) / tileSet.imageheight;
            var uvSize:Number = tileSet.tilewidth / tileSet.imagewidth;

            // make Image with UV
            var image:Image = new Image(tilesTexture);
            image.width  = tileMapInfo.tilewidth;
            image.height = tileMapInfo.tileheight;

            _point.setTo(uvLeft,          uvTop         );  image.setTexCoords(0, _point);
            _point.setTo(uvLeft + uvSize, uvTop         );  image.setTexCoords(1, _point);
            _point.setTo(uvLeft,          uvTop + uvSize);  image.setTexCoords(2, _point);
            _point.setTo(uvLeft + uvSize, uvTop + uvSize);  image.setTexCoords(3, _point);

            var padding:Number = 0.0005;  // $B$=$N$^$^(B UV $B;XDj$9$k$H%?%$%k4V$K$o$:$+$J7d4V$,8+$($F$7$^$C$?$N$G(B
            _setUv(image, 0, uvLeft         , uvTop         ,  padding,  padding);
            _setUv(image, 1, uvLeft + uvSize, uvTop         , -padding,  padding);
            _setUv(image, 2, uvLeft         , uvTop + uvSize,  padding, -padding);
            _setUv(image, 3, uvLeft + uvSize, uvTop + uvSize, -padding, -padding);

            return image;
        }

        /**
         * vertices index:
         *   0 - 1
         *   | / |
         *   2 - 3
         */
        private static function _setUv(image:Image, vertexId:int, x:Number, y:Number,
                                       paddingX:Number=0, paddingY:Number=0):void
        {
            _point.setTo(x + paddingX, y + paddingY);
            image.setTexCoords(vertexId, _point);
        }

    }
}

package krewfw.starling_utility {

    import flash.geom.Point;
    import starling.display.Image;
    import starling.textures.Texture;

    import krewfw.utility.KrewUtil;

    /**
     * Tiled Map Editor (http://www.mapeditor.org/) の tmx ファイルから
     * 出力した json をもとに各マスの Image を返すユーティリティ
     */
    //------------------------------------------------------------
    public class TileMapHelper {

        // avoid instantiation cost
        private static var _point:Point = new Point(0, 0);

        /**
         * Tiled Map Editor で出力した json の Object から、
         * 名前でレイヤーのデータを取得する。名前がヒットしなかった場合は null を返す
         */
        public static function getLayerByName(tileMapInfo:Object, layerName:String):Object {
            for each (var layerData:Object in tileMapInfo.layers) {
                if (layerData.name == layerName) {
                    return layerData;
                }
            }
            KrewUtil.fwlog("[TileMapHelpr] Layer not found: " + layerName);
            return null;
        }

        /**
         * Tiled Map Editor では空タイルは 0 と表現される。
         * ソースのタイル画像の一番左上は 1 から始まる。
         * 指定したマスが 0 の場合は null を返す.
         *
         * [Note] 以下のタイル画像のフォーマットでテスト：
         * <pre>
         *     - Canvas size: 512 x 512
         *     - Tile size: 32 x 32
         *     - spacing: 2
         * </pre>
         */
        public static function getTileImage(tileMapInfo:Object, tileLayer:Object, tileSet:Object,
                                            tilesTexture:Texture, col:uint, row:uint):Image
        {
            // calculate UV coord
            var numMapCol:uint = tileLayer.width;
            var tileIndex:int  = tileLayer.data[(row * numMapCol) + col] - 1;
            if (tileIndex < 0) { return null; }

            // * consider spacing
            var tileWidth :Number = (tileSet.tilewidth  + tileSet.spacing);
            var tileHeight:Number = (tileSet.tileheight + tileSet.spacing);

            var numTileImageCol:uint = tileSet.imagewidth  / tileWidth;
            var numTileImageRow:uint = tileSet.imageheight / tileHeight;
            var tileImageCol:uint = tileIndex % numTileImageCol;
            var tileImageRow:uint = tileIndex / numTileImageCol;

            var uvLeft:Number = (tileWidth  * tileImageCol) / tileSet.imagewidth;
            var uvTop :Number = (tileHeight * tileImageRow) / tileSet.imageheight;
            var uvSize:Number = tileSet.tilewidth / tileSet.imagewidth;

            // make Image with UV
            var image:Image = new Image(tilesTexture);
            image.width  = tileMapInfo.tilewidth;
            image.height = tileMapInfo.tileheight;

            _point.setTo(uvLeft,          uvTop         );  image.setTexCoords(0, _point);
            _point.setTo(uvLeft + uvSize, uvTop         );  image.setTexCoords(1, _point);
            _point.setTo(uvLeft,          uvTop + uvSize);  image.setTexCoords(2, _point);
            _point.setTo(uvLeft + uvSize, uvTop + uvSize);  image.setTexCoords(3, _point);

            var padding:Number = 0.0005;  // そのまま UV 指定するとタイル間にわずかな隙間が見えてしまったので
            _setUv(image, 0, uvLeft         , uvTop         ,  padding,  padding);
            _setUv(image, 1, uvLeft + uvSize, uvTop         , -padding,  padding);
            _setUv(image, 2, uvLeft         , uvTop + uvSize,  padding, -padding);
            _setUv(image, 3, uvLeft + uvSize, uvTop + uvSize, -padding, -padding);

            return image;
        }

        /**
         * vertices index:
         *   0 - 1
         *   | / |
         *   2 - 3
         */
        private static function _setUv(image:Image, vertexId:int, x:Number, y:Number,
                                       paddingX:Number=0, paddingY:Number=0):void
        {
            _point.setTo(x + paddingX, y + paddingY);
            image.setTexCoords(vertexId, _point);
        }

    }
}

Update TileMapHelper

Update TileMapHelper


/* 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.model {
    CONFIG::LOGGING {
        import org.mangui.hls.utils.Log;
    }
    import org.mangui.hls.utils.PTS;

    /** HLS streaming quality level. **/
    public class Level {
        /** audio only Level ? **/
        public var audio : Boolean;
        /** AAC codec signaled ? **/
        public var codec_aac : Boolean;
        /** MP3 codec signaled ? **/
        public var codec_mp3 : Boolean;
        /** H264 codec signaled ? **/
        public var codec_h264 : Boolean;
        /** Level Bitrate. **/
        public var bitrate : uint;
        /** Level Name. **/
        public var name : String;
        /** level index (sorted by bitrate) **/
        public var index : int = 0;
        /** level index (manifest order) **/
        public var manifest_index : int = 0;
        /** video width (from playlist) **/
        public var width : int;
        /** video height (from playlist) **/
        public var height : int;
        /** URL of this bitrate level (for M3U8). (it is a vector so that we can store redundant streams in same level) **/
        public var urls : Vector.<String>;
        // index of used url (non 0 if we switch to a redundant stream)
        private var _redundantStreamId : int = 0;
        /** Level fragments **/
        public var fragments : Vector.<Fragment>;
        /** min sequence number from M3U8. **/
        public var start_seqnum : int;
        /** max sequence number from M3U8. **/
        public var end_seqnum : int;
        /** target fragment duration from M3U8 **/
        public var targetduration : Number;
        /** average fragment duration **/
        public var averageduration : Number;
        /** Total duration **/
        public var duration : Number;
        /**  Audio Identifier **/
        public var audio_stream_id : String;

        /** Create the quality level. **/
        public function Level() : void {
            this.fragments = new Vector.<Fragment>();
        };

        public function get url() : String {
            return urls[_redundantStreamId];
        }

        public function get redundantStreamsNb() : int {
            if(urls && urls.length) {
                return urls.length-1;
            } else {
                return 0;
            }
        }

        public function get redundantStreamId() : int {
            return _redundantStreamId;
        }

        // when switching to a redundant stream, reset fragments. they will be retrieved from new playlist
        public function set redundantStreamId(id) : void {
            if(id < urls.length && id != _redundantStreamId) {
                _redundantStreamId = id;
                fragments = new Vector.<Fragment>();
                start_seqnum = end_seqnum = NaN;
            }
        }

        /** Return the Fragment before a given time position. **/
        public function getFragmentBeforePosition(position : Number) : Fragment {
            if (fragments[0].data.valid && position < fragments[0].start_time)
                return fragments[0];

            var len : int = fragments.length;
            for (var i : int = 0; i < len; i++) {
                /* check whether fragment contains current position */
                if (fragments[i].data.valid && fragments[i].start_time <= position && fragments[i].start_time + fragments[i].duration > position) {
                    return fragments[i];
                }
            }
            return fragments[len - 1];
        };

        /** Return the sequence number from a given program date **/
        public function getSeqNumFromProgramDate(program_date : Number) : int {
            if (program_date < fragments[0].program_date)
                return -1;

            var len : int = fragments.length;
            for (var i : int = 0; i < len; i++) {
                /* check whether fragment contains current position */
                if (fragments[i].data.valid && fragments[i].program_date <= program_date && fragments[i].program_date + 1000 * fragments[i].duration > program_date) {
                    return (start_seqnum + i);
                }
            }
            return -1;
        };

        /** Return the sequence number nearest a PTS **/
        public function getSeqNumNearestPTS(pts : Number, continuity : int) : Number {
            if (fragments.length == 0)
                return -1;
            var firstIndex : Number = getFirstIndexfromContinuity(continuity);
            if (firstIndex == -1 || isNaN(fragments[firstIndex].data.pts_start_computed))
                return -1;
            var lastIndex : Number = getLastIndexfromContinuity(continuity);

            for (var i : int = firstIndex; i <= lastIndex; i++) {
                var frag : Fragment = fragments[i];
                /* check nearest fragment */
                if ( frag.data.valid && (frag.duration >= 0) && (Math.abs(frag.data.pts_start_computed - pts) < Math.abs(frag.data.pts_start_computed + 1000 * frag.duration - pts))) {
                    return frag.seqnum;
                }
            }
            // requested PTS above max PTS of this level
            return Number.POSITIVE_INFINITY;
        };

        public function getLevelstartPTS() : Number {
            if (fragments.length)
                return fragments[0].data.pts_start_computed;
            else
                return NaN;
        }

        /** Return the fragment index from fragment sequence number **/
        public function getFragmentfromSeqNum(seqnum : Number) : Fragment {
            var index : int = getIndexfromSeqNum(seqnum);
            if (index != -1) {
                return fragments[index];
            } else {
                return null;
            }
        }

        /** Return the fragment index from fragment sequence number **/
        private function getIndexfromSeqNum(seqnum : int) : int {
            if (seqnum >= start_seqnum && seqnum <= end_seqnum) {
                return (fragments.length - 1 - (end_seqnum - seqnum));
            } else {
                return -1;
            }
        }

        /** Return the first index matching with given continuity counter **/
        private function getFirstIndexfromContinuity(continuity : int) : int {
            // look for first fragment matching with given continuity index
            var len : int = fragments.length;
            for (var i : int = 0; i < len; i++) {
                if (fragments[i].continuity == continuity)
                    return i;
            }
            return -1;
        }

        /** Return the first seqnum matching with given continuity counter **/
        public function getFirstSeqNumfromContinuity(continuity : int) : Number {
            var index : int = getFirstIndexfromContinuity(continuity);
            if (index == -1) {
                return Number.NEGATIVE_INFINITY;
            }
            return fragments[index].seqnum;
        }

        /** Return the last seqnum matching with given continuity counter **/
        public function getLastSeqNumfromContinuity(continuity : int) : Number {
            var index : int = getLastIndexfromContinuity(continuity);
            if (index == -1) {
                return Number.NEGATIVE_INFINITY;
            }
            return fragments[index].seqnum;
        }

        /** Return the last index matching with given continuity counter **/
        private function getLastIndexfromContinuity(continuity : Number) : int {
            var firstIndex : int = getFirstIndexfromContinuity(continuity);
            if (firstIndex == -1)
                return -1;

            var lastIndex : int = firstIndex;
            // look for first fragment matching with given continuity index
            for (var i : int = firstIndex; i < fragments.length; i++) {
                if (fragments[i].continuity == continuity)
                    lastIndex = i;
                else
                    break;
            }
            return lastIndex;
        }

        /** set Fragments **/
        public function updateFragments(_fragments : Vector.<Fragment>) : void {
            var idx_with_metrics : int = -1;
            var len : int = _fragments.length;
            var continuity_offset : int;
            var frag : Fragment;
            // update PTS from previous fragments
            for (var i : int = 0; i < len; i++) {
                frag = getFragmentfromSeqNum(_fragments[i].seqnum);
                if (frag != null) {
                    continuity_offset = frag.continuity - _fragments[i].continuity;
                    if(!isNaN(frag.data.pts_start)) {
                    _fragments[i].data = frag.data;
                    idx_with_metrics = i;
                    }
                }
            }
            if(continuity_offset) {
                CONFIG::LOGGING {
                    Log.debug("updateFragments: discontinuity sliding from live playlist,take into account discontinuity drift:" + continuity_offset);
                }
                for (i = 0; i < len; i++) {
                     _fragments[i].continuity+= continuity_offset;
                }
            }
            updateFragmentsProgramDate(_fragments);
            fragments = _fragments;

            if (len > 0) {
                start_seqnum = _fragments[0].seqnum;
                end_seqnum = _fragments[len - 1].seqnum;

                if (idx_with_metrics != -1) {
                    frag = fragments[idx_with_metrics];
                    // if at least one fragment contains PTS info, recompute PTS information for all fragments
                    CONFIG::LOGGING {
                        Log.debug("updateFragments: found PTS info from previous playlist,seqnum/PTS:" + frag.seqnum + "/" + frag.data.pts_start);
                    }
                    updateFragment(frag.seqnum, true, frag.data.pts_start, frag.data.pts_start + 1000 * frag.duration);
                } else {
                    CONFIG::LOGGING {
                        Log.debug("updateFragments: unknown PTS info for this level");
                    }
                    duration = _fragments[len - 1].start_time + _fragments[len - 1].duration;
                }
                averageduration = duration / len;
            } else {
                duration = 0;
                averageduration = 0;
            }
        }

        private function updateFragmentsProgramDate(_fragments : Vector.<Fragment>) : void {
            var len : int = _fragments.length;
            var continuity : int;
            var program_date : Number;
            var frag : Fragment;
            for (var i : int = 0; i < len; i++) {
                frag = _fragments[i];
                if (frag.continuity != continuity) {
                    continuity = frag.continuity;
                    program_date = 0;
                }
                if (frag.program_date) {
                    program_date = frag.program_date + 1000 * frag.duration;
                } else if (program_date) {
                    frag.program_date = program_date;
                }
            }
        }

        private function _updatePTS(from_index : int, to_index : int) : void {
            // CONFIG::LOGGING {
            // Log.info("updateFragmentPTS from/to:" + from_index + "/" + to_index);
            // }
            var frag_from : Fragment = fragments[from_index];
            var frag_to : Fragment = fragments[to_index];

            if (frag_from.data.valid && frag_to.data.valid) {
                if (!isNaN(frag_to.data.pts_start)) {
                    // we know PTS[to_index]
                    frag_to.data.pts_start_computed = frag_to.data.pts_start;
                    /* normalize computed PTS value based on known PTS value.
                     * this is to avoid computing wrong fragment duration in case of PTS looping */
                    var from_pts : Number = PTS.normalize(frag_to.data.pts_start, frag_from.data.pts_start_computed);
                    /* update fragment duration.
                    it helps to fix drifts between playlist reported duration and fragment real duration */
                    if (to_index > from_index) {
                        frag_from.duration = (frag_to.data.pts_start - from_pts) / 1000;
                        CONFIG::LOGGING {
                            if (frag_from.duration < 0) {
                                Log.error("negative duration computed for " + frag_from + ", there should be some duration drift between playlist and fragment!");
                            }
                        }
                    } else {
                        frag_to.duration = ( from_pts - frag_to.data.pts_start) / 1000;
                        CONFIG::LOGGING {
                            if (frag_to.duration < 0) {
                                Log.error("negative duration computed for " + frag_to + ", there should be some duration drift between playlist and fragment!");
                            }
                        }
                    }
                } else {
                    // we dont know PTS[to_index]
                    if (to_index > from_index)
                        frag_to.data.pts_start_computed = frag_from.data.pts_start_computed + 1000 * frag_from.duration;
                    else
                        frag_to.data.pts_start_computed = frag_from.data.pts_start_computed - 1000 * frag_to.duration;
                }
            }
        }

        public function updateFragment(seqnum : Number, valid : Boolean, min_pts : Number = 0, max_pts : Number = 0) : void {
            // CONFIG::LOGGING {
            // Log.info("updatePTS : seqnum/min/max:" + seqnum + '/' + min_pts + '/' + max_pts);
            // }
            // get fragment from seqnum
            var fragIdx : int = getIndexfromSeqNum(seqnum);
            if (fragIdx != -1) {
                var frag : Fragment = fragments[fragIdx];
                // update fragment start PTS + duration
                if (valid) {
                    frag.data.pts_start = min_pts;
                    frag.data.pts_start_computed = min_pts;
                    frag.duration = (max_pts - min_pts) / 1000;
                } else {
                    frag.duration = 0;
                }
                frag.data.valid = valid;
                // CONFIG::LOGGING {
                // Log.info("SN["+fragments[fragIdx].seqnum+"]:pts/duration:" + fragments[fragIdx].start_pts_computed + "/" + fragments[fragIdx].duration);
                // }

                // adjust fragment PTS/duration from seqnum-1 to frag 0
                for (var i : int = fragIdx; i > 0 && fragments[i - 1].continuity == frag.continuity; i--) {
                    _updatePTS(i, i - 1);
                    // CONFIG::LOGGING {
                    // Log.info("SN["+fragments[i-1].seqnum+"]:pts/duration:" + fragments[i-1].start_pts_computed + "/" + fragments[i-1].duration);
                    // }
                }

                // adjust fragment PTS/duration from seqnum to last frag
                for (i = fragIdx; i < fragments.length - 1 && fragments[i + 1].continuity == frag.continuity; i++) {
                    _updatePTS(i, i + 1);
                    // CONFIG::LOGGING {
                    // Log.info("SN["+fragments[i+1].seqnum+"]:pts/duration:" + fragments[i+1].start_pts_computed + "/" + fragments[i+1].duration);
                    // }
                }

                // second, adjust fragment offset
                var start_time_offset : Number = fragments[0].start_time;
                var len : int = fragments.length;
                for (i = 0; i < len; i++) {
                    fragments[i].start_time = start_time_offset;
                    start_time_offset += fragments[i].duration;
                    // CONFIG::LOGGING {
                    // Log.info("SN["+fragments[i].seqnum+"]:start_time/continuity/pts/duration:" + fragments[i].start_time + "/" + fragments[i].continuity + "/"+ fragments[i].start_pts_computed + "/" + fragments[i].duration);
                    // }
                }
                duration = start_time_offset;
            } else {
                CONFIG::LOGGING {
                    Log.error("updateFragment:seqnum " + seqnum + " not found!");
                }
            }
        }
    }
}

/* 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.model {
    CONFIG::LOGGING {
        import org.mangui.hls.utils.Log;
    }
    import org.mangui.hls.utils.PTS;

    /** HLS streaming quality level. **/
    public class Level {
        /** audio only Level ? **/
        public var audio : Boolean;
        /** AAC codec signaled ? **/
        public var codec_aac : Boolean;
        /** MP3 codec signaled ? **/
        public var codec_mp3 : Boolean;
        /** H264 codec signaled ? **/
        public var codec_h264 : Boolean;
        /** Level Bitrate. **/
        public var bitrate : uint;
        /** Level Name. **/
        public var name : String;
        /** level index (sorted by bitrate) **/
        public var index : int = 0;
        /** level index (manifest order) **/
        public var manifest_index : int = 0;
        /** video width (from playlist) **/
        public var width : int;
        /** video height (from playlist) **/
        public var height : int;
        /** URL of this bitrate level (for M3U8). (it is a vector so that we can store redundant streams in same level) **/
        public var urls : Vector.<String>;
        // index of used url (non 0 if we switch to a redundant stream)
        private var _redundantStreamId : int = 0;
        /** Level fragments **/
        public var fragments : Vector.<Fragment>;
        /** min sequence number from M3U8. **/
        public var start_seqnum : int;
        /** max sequence number from M3U8. **/
        public var end_seqnum : int;
        /** target fragment duration from M3U8 **/
        public var targetduration : Number;
        /** average fragment duration **/
        public var averageduration : Number;
        /** Total duration **/
        public var duration : Number;
        /**  Audio Identifier **/
        public var audio_stream_id : String;

        /** Create the quality level. **/
        public function Level() : void {
            this.fragments = new Vector.<Fragment>();
        };

        public function get url() : String {
            return urls[_redundantStreamId];
        }

        public function get redundantStreamsNb() : int {
            if(urls && urls.length) {
                return urls.length-1;
            } else {
                return 0;
            }
        }

        public function get redundantStreamId() : int {
            return _redundantStreamId;
        }

        // when switching to a redundant stream, reset fragments. they will be retrieved from new playlist
        public function set redundantStreamId(id : int) : void {
            if(id < urls.length && id != _redundantStreamId) {
                _redundantStreamId = id;
                fragments = new Vector.<Fragment>();
                start_seqnum = end_seqnum = NaN;
            }
        }

        /** Return the Fragment before a given time position. **/
        public function getFragmentBeforePosition(position : Number) : Fragment {
            if (fragments[0].data.valid && position < fragments[0].start_time)
                return fragments[0];

            var len : int = fragments.length;
            for (var i : int = 0; i < len; i++) {
                /* check whether fragment contains current position */
                if (fragments[i].data.valid && fragments[i].start_time <= position && fragments[i].start_time + fragments[i].duration > position) {
                    return fragments[i];
                }
            }
            return fragments[len - 1];
        };

        /** Return the sequence number from a given program date **/
        public function getSeqNumFromProgramDate(program_date : Number) : int {
            if (program_date < fragments[0].program_date)
                return -1;

            var len : int = fragments.length;
            for (var i : int = 0; i < len; i++) {
                /* check whether fragment contains current position */
                if (fragments[i].data.valid && fragments[i].program_date <= program_date && fragments[i].program_date + 1000 * fragments[i].duration > program_date) {
                    return (start_seqnum + i);
                }
            }
            return -1;
        };

        /** Return the sequence number nearest a PTS **/
        public function getSeqNumNearestPTS(pts : Number, continuity : int) : Number {
            if (fragments.length == 0)
                return -1;
            var firstIndex : Number = getFirstIndexfromContinuity(continuity);
            if (firstIndex == -1 || isNaN(fragments[firstIndex].data.pts_start_computed))
                return -1;
            var lastIndex : Number = getLastIndexfromContinuity(continuity);

            for (var i : int = firstIndex; i <= lastIndex; i++) {
                var frag : Fragment = fragments[i];
                /* check nearest fragment */
                if ( frag.data.valid && (frag.duration >= 0) && (Math.abs(frag.data.pts_start_computed - pts) < Math.abs(frag.data.pts_start_computed + 1000 * frag.duration - pts))) {
                    return frag.seqnum;
                }
            }
            // requested PTS above max PTS of this level
            return Number.POSITIVE_INFINITY;
        };

        public function getLevelstartPTS() : Number {
            if (fragments.length)
                return fragments[0].data.pts_start_computed;
            else
                return NaN;
        }

        /** Return the fragment index from fragment sequence number **/
        public function getFragmentfromSeqNum(seqnum : Number) : Fragment {
            var index : int = getIndexfromSeqNum(seqnum);
            if (index != -1) {
                return fragments[index];
            } else {
                return null;
            }
        }

        /** Return the fragment index from fragment sequence number **/
        private function getIndexfromSeqNum(seqnum : int) : int {
            if (seqnum >= start_seqnum && seqnum <= end_seqnum) {
                return (fragments.length - 1 - (end_seqnum - seqnum));
            } else {
                return -1;
            }
        }

        /** Return the first index matching with given continuity counter **/
        private function getFirstIndexfromContinuity(continuity : int) : int {
            // look for first fragment matching with given continuity index
            var len : int = fragments.length;
            for (var i : int = 0; i < len; i++) {
                if (fragments[i].continuity == continuity)
                    return i;
            }
            return -1;
        }

        /** Return the first seqnum matching with given continuity counter **/
        public function getFirstSeqNumfromContinuity(continuity : int) : Number {
            var index : int = getFirstIndexfromContinuity(continuity);
            if (index == -1) {
                return Number.NEGATIVE_INFINITY;
            }
            return fragments[index].seqnum;
        }

        /** Return the last seqnum matching with given continuity counter **/
        public function getLastSeqNumfromContinuity(continuity : int) : Number {
            var index : int = getLastIndexfromContinuity(continuity);
            if (index == -1) {
                return Number.NEGATIVE_INFINITY;
            }
            return fragments[index].seqnum;
        }

        /** Return the last index matching with given continuity counter **/
        private function getLastIndexfromContinuity(continuity : Number) : int {
            var firstIndex : int = getFirstIndexfromContinuity(continuity);
            if (firstIndex == -1)
                return -1;

            var lastIndex : int = firstIndex;
            // look for first fragment matching with given continuity index
            for (var i : int = firstIndex; i < fragments.length; i++) {
                if (fragments[i].continuity == continuity)
                    lastIndex = i;
                else
                    break;
            }
            return lastIndex;
        }

        /** set Fragments **/
        public function updateFragments(_fragments : Vector.<Fragment>) : void {
            var idx_with_metrics : int = -1;
            var len : int = _fragments.length;
            var continuity_offset : int;
            var frag : Fragment;
            // update PTS from previous fragments
            for (var i : int = 0; i < len; i++) {
                frag = getFragmentfromSeqNum(_fragments[i].seqnum);
                if (frag != null) {
                    continuity_offset = frag.continuity - _fragments[i].continuity;
                    if(!isNaN(frag.data.pts_start)) {
                    _fragments[i].data = frag.data;
                    idx_with_metrics = i;
                    }
                }
            }
            if(continuity_offset) {
                CONFIG::LOGGING {
                    Log.debug("updateFragments: discontinuity sliding from live playlist,take into account discontinuity drift:" + continuity_offset);
                }
                for (i = 0; i < len; i++) {
                     _fragments[i].continuity+= continuity_offset;
                }
            }
            updateFragmentsProgramDate(_fragments);
            fragments = _fragments;

            if (len > 0) {
                start_seqnum = _fragments[0].seqnum;
                end_seqnum = _fragments[len - 1].seqnum;

                if (idx_with_metrics != -1) {
                    frag = fragments[idx_with_metrics];
                    // if at least one fragment contains PTS info, recompute PTS information for all fragments
                    CONFIG::LOGGING {
                        Log.debug("updateFragments: found PTS info from previous playlist,seqnum/PTS:" + frag.seqnum + "/" + frag.data.pts_start);
                    }
                    updateFragment(frag.seqnum, true, frag.data.pts_start, frag.data.pts_start + 1000 * frag.duration);
                } else {
                    CONFIG::LOGGING {
                        Log.debug("updateFragments: unknown PTS info for this level");
                    }
                    duration = _fragments[len - 1].start_time + _fragments[len - 1].duration;
                }
                averageduration = duration / len;
            } else {
                duration = 0;
                averageduration = 0;
            }
        }

        private function updateFragmentsProgramDate(_fragments : Vector.<Fragment>) : void {
            var len : int = _fragments.length;
            var continuity : int;
            var program_date : Number;
            var frag : Fragment;
            for (var i : int = 0; i < len; i++) {
                frag = _fragments[i];
                if (frag.continuity != continuity) {
                    continuity = frag.continuity;
                    program_date = 0;
                }
                if (frag.program_date) {
                    program_date = frag.program_date + 1000 * frag.duration;
                } else if (program_date) {
                    frag.program_date = program_date;
                }
            }
        }

        private function _updatePTS(from_index : int, to_index : int) : void {
            // CONFIG::LOGGING {
            // Log.info("updateFragmentPTS from/to:" + from_index + "/" + to_index);
            // }
            var frag_from : Fragment = fragments[from_index];
            var frag_to : Fragment = fragments[to_index];

            if (frag_from.data.valid && frag_to.data.valid) {
                if (!isNaN(frag_to.data.pts_start)) {
                    // we know PTS[to_index]
                    frag_to.data.pts_start_computed = frag_to.data.pts_start;
                    /* normalize computed PTS value based on known PTS value.
                     * this is to avoid computing wrong fragment duration in case of PTS looping */
                    var from_pts : Number = PTS.normalize(frag_to.data.pts_start, frag_from.data.pts_start_computed);
                    /* update fragment duration.
                    it helps to fix drifts between playlist reported duration and fragment real duration */
                    if (to_index > from_index) {
                        frag_from.duration = (frag_to.data.pts_start - from_pts) / 1000;
                        CONFIG::LOGGING {
                            if (frag_from.duration < 0) {
                                Log.error("negative duration computed for " + frag_from + ", there should be some duration drift between playlist and fragment!");
                            }
                        }
                    } else {
                        frag_to.duration = ( from_pts - frag_to.data.pts_start) / 1000;
                        CONFIG::LOGGING {
                            if (frag_to.duration < 0) {
                                Log.error("negative duration computed for " + frag_to + ", there should be some duration drift between playlist and fragment!");
                            }
                        }
                    }
                } else {
                    // we dont know PTS[to_index]
                    if (to_index > from_index)
                        frag_to.data.pts_start_computed = frag_from.data.pts_start_computed + 1000 * frag_from.duration;
                    else
                        frag_to.data.pts_start_computed = frag_from.data.pts_start_computed - 1000 * frag_to.duration;
                }
            }
        }

        public function updateFragment(seqnum : Number, valid : Boolean, min_pts : Number = 0, max_pts : Number = 0) : void {
            // CONFIG::LOGGING {
            // Log.info("updatePTS : seqnum/min/max:" + seqnum + '/' + min_pts + '/' + max_pts);
            // }
            // get fragment from seqnum
            var fragIdx : int = getIndexfromSeqNum(seqnum);
            if (fragIdx != -1) {
                var frag : Fragment = fragments[fragIdx];
                // update fragment start PTS + duration
                if (valid) {
                    frag.data.pts_start = min_pts;
                    frag.data.pts_start_computed = min_pts;
                    frag.duration = (max_pts - min_pts) / 1000;
                } else {
                    frag.duration = 0;
                }
                frag.data.valid = valid;
                // CONFIG::LOGGING {
                // Log.info("SN["+fragments[fragIdx].seqnum+"]:pts/duration:" + fragments[fragIdx].start_pts_computed + "/" + fragments[fragIdx].duration);
                // }

                // adjust fragment PTS/duration from seqnum-1 to frag 0
                for (var i : int = fragIdx; i > 0 && fragments[i - 1].continuity == frag.continuity; i--) {
                    _updatePTS(i, i - 1);
                    // CONFIG::LOGGING {
                    // Log.info("SN["+fragments[i-1].seqnum+"]:pts/duration:" + fragments[i-1].start_pts_computed + "/" + fragments[i-1].duration);
                    // }
                }

                // adjust fragment PTS/duration from seqnum to last frag
                for (i = fragIdx; i < fragments.length - 1 && fragments[i + 1].continuity == frag.continuity; i++) {
                    _updatePTS(i, i + 1);
                    // CONFIG::LOGGING {
                    // Log.info("SN["+fragments[i+1].seqnum+"]:pts/duration:" + fragments[i+1].start_pts_computed + "/" + fragments[i+1].duration);
                    // }
                }

                // second, adjust fragment offset
                var start_time_offset : Number = fragments[0].start_time;
                var len : int = fragments.length;
                for (i = 0; i < len; i++) {
                    fragments[i].start_time = start_time_offset;
                    start_time_offset += fragments[i].duration;
                    // CONFIG::LOGGING {
                    // Log.info("SN["+fragments[i].seqnum+"]:start_time/continuity/pts/duration:" + fragments[i].start_time + "/" + fragments[i].continuity + "/"+ fragments[i].start_pts_computed + "/" + fragments[i].duration);
                    // }
                }
                duration = start_time_offset;
            } else {
                CONFIG::LOGGING {
                    Log.error("updateFragment:seqnum " + seqnum + " not found!");
                }
            }
        }
    }
}

Set redundantStreamId() setter argument as an int

It fixes compilation warnings when Flashls is imported with sources.
Occurs when using mxmlc strict mode (-strict).