package com.threerings.util {

import com.threerings.util.Equalable;

import com.threerings.io.ObjectInputStream;
import com.threerings.io.ObjectOutputStream;
import com.threerings.io.Streamable;

public class Name extends Object
    implements Comparable, Hashable, Streamable
{
    public function Name (name :String = "")
    {
        _name = name;
    }

    public function getNormal () :String
    {
        if (_normal == null) {
            _normal = normalize(_name);
            if (_normal === _name) {
                _normal = _name;
            }
        }
        return _normal;
    }

    public function isValid () :Boolean
    {
        return !isBlank();
    }

    public function isBlank () :Boolean
    {
        return Name.isBlank(this);
    }

    public function toString () :String
    {
        return _name;
    }

    // from interface Hashable
    public function equals (other :Object) :Boolean
    {
        return (ClassUtil.getClass(other) == Name) &&
            (getNormal() === (other as Name).getNormal());
    }

    // from interface Hashable
    public function hashCode () :int
    {
        return StringUtil.hashCode(getNormal());
    }

    // from interface Comparable
    public function compareTo (other :Object) :int
    {
        var thisNormal :String = getNormal();
        var thatNormal :String = (other as Name).getNormal();
        return thisNormal.localeCompare(thatNormal);
    }

    // from interface Streamable
    public function writeObject (out :ObjectOutputStream) :void
        //throws IOError
    {
        out.writeField(_name);
    }

    // from interface Streamable
    public function readObject (ins :ObjectInputStream) :void
        //throws IOError
    {
        _name = (ins.readField(String) as String);
    }

    protected function normalize (txt :String) :String
    {
        return txt.toLowerCase();
    }

    public static function isBlank (name :Name) :Boolean
    {
        return (name == null || "" === name.toString());
    }

    /** The raw name text. */
    protected var _name :String;

    /** The normalized name text. */
    protected var _normal :String;
}
}

package com.threerings.util {

import com.threerings.util.Equalable;

import com.threerings.io.ObjectInputStream;
import com.threerings.io.ObjectOutputStream;
import com.threerings.io.Streamable;

public class Name extends Object
    implements Comparable, Hashable, Streamable
{
    public function Name (name :String = "")
    {
        _name = name;
    }

    public function getNormal () :String
    {
        if (_normal == null) {
            _normal = normalize(_name);
            if (_normal === _name) {
                _normal = _name;
            }
        }
        return _normal;
    }

    public function isValid () :Boolean
    {
        return !isBlank();
    }

    public function isBlank () :Boolean
    {
        return Name.isBlank(this);
    }

    public function toString () :String
    {
        return _name;
    }

    // from interface Hashable
    public function equals (other :Object) :Boolean
    {
        return (other != null) &&
            // this is a check to see if they are the same class
            (other.constructor == Object(this).constructor) &&
            (getNormal() === (other as Name).getNormal());
    }

    // from interface Hashable
    public function hashCode () :int
    {
        return StringUtil.hashCode(getNormal());
    }

    // from interface Comparable
    public function compareTo (other :Object) :int
    {
        var thisNormal :String = getNormal();
        var thatNormal :String = (other as Name).getNormal();
        return thisNormal.localeCompare(thatNormal);
    }

    // from interface Streamable
    public function writeObject (out :ObjectOutputStream) :void
        //throws IOError
    {
        out.writeField(_name);
    }

    // from interface Streamable
    public function readObject (ins :ObjectInputStream) :void
        //throws IOError
    {
        _name = (ins.readField(String) as String);
    }

    protected function normalize (txt :String) :String
    {
        return txt.toLowerCase();
    }

    public static function isBlank (name :Name) :Boolean
    {
        return (name == null || "" === name.toString());
    }

    /** The raw name text. */
    protected var _name :String;

    /** The normalized name text. */
    protected var _normal :String;
}
}

Check that the other name is the same subclass of Name, not just a Name.
(And found a faster way to compare the classes of two objects.)

git-svn-id: a1a4b28b82a3276cc491891159dd9963a0a72fae@4321 542714f4-19e9-0310-aa3c-eee0fc999fb1


package com.rails2u.debug {
    import flash.utils.Dictionary;
    import flash.events.Event;
    import flash.utils.getTimer;

    /**
    * Simple Benchmark class
    *
  * example:
  * <listing version="3.0">
  * Benchmark.start('check');
  * // code
  * Benchmark.end('check');
  * </listing>
  *
  * <listing version="3.0">
  * Benchmark.benchmark('check2', function() {
  *   // code
  * }, this);
  * </listing>
    */
    public class Benchmark {
        private static var dict:Object = {};

        public static function start(name:String):void {
            dict[name] = getTimer();
        }

        public static function benchmark(name:String, callback:Function, bindObject:Object = null):Number {
            start(name);
            callback.call(bindObject);
            return end(name);
        }

        public static function loop(name:String, callback:Function, bindObject:Object = null, times:uint = 100):Number {
            start(name);
            for (var i:uint = 0; i < times; i++)
                callback.call(bindObject);
            return end(name);
        }

        public static function end(name:String):Number {
            var re:uint = getTimer() - dict[name];
            log(name + ':' + ' ' + re + 'ms');
            return re;
        }
    }
}

package com.rails2u.debug {
    import flash.utils.Dictionary;
    import flash.events.Event;
    import flash.utils.getTimer;

    /**
    * Simple Benchmark class
    *
  * example:
  * <listing version="3.0">
  * Benchmark.start('check');
  * // code
  * Benchmark.end('check');
  * </listing>
  *
  * <listing version="3.0">
  * Benchmark.benchmark('check2', function() {
  *   // code
  * }, this);
  * </listing>
    */
    public class Benchmark {
        private static var dict:Object = {};

        public static function start(name:String):void {
            dict[name] = getTimer();
        }

        public static function benchmark(name:String, callback:Function, bindObject:Object = null, times:uint = 1):Number {
            start(name);
            for (var i:uint = 0; i < times; i++)
                callback.call(bindObject);
            return end(name);
        }

        public static function end(name:String):Number {
            var re:uint = getTimer() - dict[name];
            log(name + ':' + ' ' + re + 'ms');
            return re;
        }
    }
}

remove loop method

remove loop method

git-svn-id: 864080e30cc358c5edb906449bbf014f90258007@43 96db6a20-122f-0410-8d9f-89437bbe4005


package org.flashsocket.utils {
	import flash.utils.ByteArray;

	public class SHA1 {
		public static function hash(input:String):String {
			var inputLength:int = input.length;
			// Number of 8-bit bytes to represent the 512-bit blocks
			var numberOfBytes:int = (inputLength + 9) + ((55 - inputLength) & 0x3f);
			// Number of 512-bit blocks required
			var numberOfBlocks:int = numberOfBytes >> 6;
			// Convert input String to ByteArray
			var bytes:ByteArray = StringUtil.toBytes(input);

			// Insert the first padding bit "1" right after the message
			bytes.writeByte(0x80);

			// Insert the message length as the last 8 bytes
			bytes.position = numberOfBytes - 8;
			bytes.writeUnsignedInt(inputLength >>> 29);
			bytes.writeUnsignedInt(inputLength << 3);
			bytes.position = 0;

			// Compute the Message Digest
			// algorithm based on the C code demonstration shown at
			// http://www.faqs.org/rfcs/rfc3174.html
			var H0:int = 0x67452301;
			var H1:int = 0xefcdab89;
			var H2:int = 0x98badcfe;
			var H3:int = 0x10325476;
			var H4:int = 0xc3d2e1f0;
			var W:Array = new Array(80);
			var temp:int;
			var A:int;
			var B:int;
			var C:int;
			var D:int;
			var E:int;
			var t:int;

			while (numberOfBlocks--) {
				A = H0;
				B = H1;
				C = H2;
				D = H3;
				E = H4;

				for (t = 0; t < 16; t++) {
					W[t] = bytes.readUnsignedInt();
				}

				for (t = 16; t < 80; t++) {
					temp = int(W[t - 3]) ^ int(W[t - 8]) ^ int(W[t - 14]) ^ int(W[t - 16]);
					W[t] = (temp << 1) | (temp >>> 31);
				}

				for (t = 0; t < 20; t++) {
					temp =  ((A << 5) | (A >>> 27)) + ((B & C) | ((~B) & D)) + E + int(W[t]) + 0x5a827999;
					E = D;
					D = C;
					C = (B << 30) | (B >>> 2);
					B = A;
					A = temp;
				}

				for (t = 20; t < 40; t++) {
					temp = ((A << 5) | (A >>> 27)) + (B ^ C ^ D) + E + int(W[t]) + 0x6ed9eba1;
					E = D;
					D = C;
					C = (B << 30) | (B >>> 2);
					B = A;
					A = temp;
				}

				for (t = 40; t < 60; t++) {
					temp = ((A << 5) | (A >>> 27)) + ((B & C) | (B & D) | (C & D)) + E + int(W[t]) + 0x8f1bbcdc;
					E = D;
					D = C;
					C = (B << 30) | (B >>> 2);
					B = A;
					A = temp;
				}

				for (t = 60; t < 80; t++) {
					temp = ((A << 5) | (A >>> 27)) + (B ^ C ^ D) + E + int(W[t]) + 0xca62c1d6;
					E = D;
					D = C;
					C = (B << 30) | (B >>> 2);
					B = A;
					A = temp;
				}

				H0 += A;
				H1 += B;
				H2 += C;
				H3 += D;
				H4 += E;
			}

			return String.fromCharCode(
				H0 >> 24 & 0xff,
				H0 >> 16 & 0xff,
				H0 >> 8  & 0xff,
				H0       & 0xff,

				H1 >> 24 & 0xff,
				H1 >> 16 & 0xff,
				H1 >> 8  & 0xff,
				H1       & 0xff,

				H2 >> 24 & 0xff,
				H2 >> 16 & 0xff,
				H2 >> 8  & 0xff,
				H2       & 0xff,

				H3 >> 24 & 0xff,
				H3 >> 16 & 0xff,
				H3 >> 8  & 0xff,
				H3       & 0xff,

				H4 >> 24 & 0xff,
				H4 >> 16 & 0xff,
				H4 >> 8  & 0xff,
				H4       & 0xff
			);
		}

		public static function test():void {
			var tests:Array = [
				"abc",
				"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
				"a",
				"0123456701234567012345670123456701234567012345670123456701234567"
			];
			var results:Array = [
				"A9 99 3E 36 47 06 81 6A BA 3E 25 71 78 50 C2 6C 9C D0 D8 9D",
				"84 98 3E 44 1C 3B D2 6E BA AE 4A A1 F9 51 29 E5 E5 46 70 F1",
				"86 F7 E4 37 FA A5 A7 FC E1 5D 1D DC B9 EA EA EA 37 76 67 B8",
				"E0 C0 94 E8 67 EF 46 C3 50 EF 54 A7 F5 9D D6 0B ED 92 AE 83"
			];

			for (var t:int = 0; t < tests.length; t++) {
				trace("Test " + (t + 1) + ":   " + tests[t]);
				trace("expected: " + results[t]);

				var hashed:String = hash(tests[t]);
				var hex:Array = [];
				for (var h:int = 0; h < hashed.length; h++) {
					hex.push((0x100 + hashed.charCodeAt(h)).toString(16).replace('1', '').toUpperCase());
				}
				var result:String = hex.join(" ");
				trace("result:   " + result);
				trace("correct:  " + (result === results[t]));
				trace();
			}
		}
	}
}

package org.flashsocket.utils {
	import flash.utils.ByteArray;

	public class SHA1 {
		public static function hash(input:String):String {
			var inputLength:int = input.length;
			// Number of 8-bit bytes to represent the 512-bit blocks
			var numberOfBytes:int = (inputLength + 9) + ((55 - inputLength) & 0x3f);
			// Number of 512-bit blocks required
			var numberOfBlocks:int = numberOfBytes >> 6;
			// Convert input String to ByteArray
			var bytes:ByteArray = StringUtil.toBytes(input);

			// Insert the first padding bit "1" right after the message
			bytes.writeByte(0x80);

			// Insert the message length as the last 8 bytes
			bytes.position = numberOfBytes - 8;
			bytes.writeUnsignedInt(inputLength >>> 29);
			bytes.writeUnsignedInt(inputLength << 3);
			bytes.position = 0;

			// Compute the Message Digest
			// algorithm based on the C code demonstration shown at
			// http://www.faqs.org/rfcs/rfc3174.html
			var H0:int = 0x67452301;
			var H1:int = 0xefcdab89;
			var H2:int = 0x98badcfe;
			var H3:int = 0x10325476;
			var H4:int = 0xc3d2e1f0;
			var W:Array = new Array(80);
			var temp:int;
			var A:int;
			var B:int;
			var C:int;
			var D:int;
			var E:int;
			var t:int;

			while (numberOfBlocks--) {
				A = H0;
				B = H1;
				C = H2;
				D = H3;
				E = H4;

				for (t = 0; t < 16; t++) {
					W[t] = bytes.readUnsignedInt();
				}

				for (t = 16; t < 80; t++) {
					temp = int(W[t - 3]) ^ int(W[t - 8]) ^ int(W[t - 14]) ^ int(W[t - 16]);
					W[t] = (temp << 1) | (temp >>> 31);
				}

				for (t = 0; t < 20; t++) {
					temp =  ((A << 5) | (A >>> 27)) + ((B & C) | ((~B) & D)) + E + int(W[t]) + 0x5a827999;
					E = D;
					D = C;
					C = (B << 30) | (B >>> 2);
					B = A;
					A = temp;
				}

				for (t = 20; t < 40; t++) {
					temp = ((A << 5) | (A >>> 27)) + (B ^ C ^ D) + E + int(W[t]) + 0x6ed9eba1;
					E = D;
					D = C;
					C = (B << 30) | (B >>> 2);
					B = A;
					A = temp;
				}

				for (t = 40; t < 60; t++) {
					temp = ((A << 5) | (A >>> 27)) + ((B & C) | (B & D) | (C & D)) + E + int(W[t]) + 0x8f1bbcdc;
					E = D;
					D = C;
					C = (B << 30) | (B >>> 2);
					B = A;
					A = temp;
				}

				for (t = 60; t < 80; t++) {
					temp = ((A << 5) | (A >>> 27)) + (B ^ C ^ D) + E + int(W[t]) + 0xca62c1d6;
					E = D;
					D = C;
					C = (B << 30) | (B >>> 2);
					B = A;
					A = temp;
				}

				H0 += A;
				H1 += B;
				H2 += C;
				H3 += D;
				H4 += E;
			}

			return String.fromCharCode(
				H0 >> 24 & 0xff,
				H0 >> 16 & 0xff,
				H0 >> 8  & 0xff,
				H0       & 0xff,

				H1 >> 24 & 0xff,
				H1 >> 16 & 0xff,
				H1 >> 8  & 0xff,
				H1       & 0xff,

				H2 >> 24 & 0xff,
				H2 >> 16 & 0xff,
				H2 >> 8  & 0xff,
				H2       & 0xff,

				H3 >> 24 & 0xff,
				H3 >> 16 & 0xff,
				H3 >> 8  & 0xff,
				H3       & 0xff,

				H4 >> 24 & 0xff,
				H4 >> 16 & 0xff,
				H4 >> 8  & 0xff,
				H4       & 0xff
			);
		}

		CONFIG::debug {
			public static function test():void {
				var tests:Array = [
					"abc",
					"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
					"a",
					"0123456701234567012345670123456701234567012345670123456701234567"
				];
				var results:Array = [
					"A9 99 3E 36 47 06 81 6A BA 3E 25 71 78 50 C2 6C 9C D0 D8 9D",
					"84 98 3E 44 1C 3B D2 6E BA AE 4A A1 F9 51 29 E5 E5 46 70 F1",
					"86 F7 E4 37 FA A5 A7 FC E1 5D 1D DC B9 EA EA EA 37 76 67 B8",
					"E0 C0 94 E8 67 EF 46 C3 50 EF 54 A7 F5 9D D6 0B ED 92 AE 83"
				];

				for (var t:int = 0; t < tests.length; t++) {
					trace("Test " + (t + 1) + ":   " + tests[t]);
					trace("expected: " + results[t]);

					var hashed:String = hash(tests[t]);
					var hex:Array = [];
					for (var h:int = 0; h < hashed.length; h++) {
						hex.push((0x100 + hashed.charCodeAt(h)).toString(16).replace('1', '').toUpperCase());
					}
					var result:String = hex.join(" ");
					trace("result:   " + result);
					trace("correct:  " + (result === results[t]));
					trace();
				}
			}
		}
	}
}

hide debugging code

hide debugging code


/*
 * FLINT PARTICLE SYSTEM
 * .....................
 *
 * Author: Richard Lord
 * Copyright (c) Richard Lord 2008-2010
 * http://flintparticles.org/
 *
 * Licence Agreement
 *
 * 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.
 */

package org.flintparticles.common.easing
{
	/**
	 * A set of easing equations for modifying the particle energy such that it starts
	 * and ends at zero and peaks at one half way through the particle's lifetime.
	 *
	 * @see org.flintparticles.common.actions.Age
	 */
	public class TwoWay
	{
		/**
		 * Gives a linear increase and decrease in energy either side of the centre point.
		 */
		public static function linear( t : Number, b : Number, c : Number, d : Number ):Number
		{
			if( ( t = 2 * t / d ) <= 1 )
			{
				return t * c + b;
			}
			return ( 2 - t ) * c + b;
		}

		/**
		 * Energy increases and then decreases as if following the top half of a circle.
		 */
		public static function circular( t : Number, b : Number, c : Number, d : Number ):Number
		{
			t = 1 - (2 * t / d);
			return Math.sqrt( 1 - t * t ) * c + b;
		}

		/**
		 * Energy follows the first half of a sine wave.
		 */
		public static function sine( t : Number, b : Number, c : Number, d : Number ):Number
		{
			return Math.sin( Math.PI * t / d ) * c + b;
		}

		/**
		 * Eases towards the middle using a quadratic curve.
		 */
		public static function quadratic( t : Number, b : Number, c : Number, d : Number ):Number
		{
			t = 1 - (2 * t / d);
			return ( 1 - t * t ) * c + b;
		}

		/**
		 * Eases towards the middle using a cubic curve.
		 */
		public static function cubic( t : Number, b : Number, c : Number, d : Number ):Number
		{
			t = 1 - (2 * t / d);
			if( t < 0 ) t = -t;
			return ( 1 - t * t * t ) * c + b;
		}

		/**
		 * Eases towards the middle using a quartic curve.
		 */
		public static function quartic( t : Number, b : Number, c : Number, d : Number ):Number
		{
			t = 1 - (2 * t / d);
			return ( 1 - t * t * t * t ) * c + b;
		}

		/**
		 * Eases towards the middle using a quintic curve.
		 */
		public static function quintic( t : Number, b : Number, c : Number, d : Number ):Number
		{
			t = 1 - (2 * t / d);
			if( t < 0 ) t = -t;
			return ( 1 - t * t * t * t * t ) * c + b;
		}
	}
}

/*
 * FLINT PARTICLE SYSTEM
 * .....................
 *
 * Author: Richard Lord
 * Copyright (c) Richard Lord 2008-2010
 * http://flintparticles.org/
 *
 * Licence Agreement
 *
 * 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.
 */

package org.flintparticles.common.easing
{
	/**
	 * A set of easing equations that start and end at the end value and reach the start value
	 * at the half-time point. They are designed for modifying the particle energy such that it
	 * starts and ends at zero and peaks at half way through the particle's lifetime.
	 *
	 * @see org.flintparticles.common.actions.Age
	 */
	public class TwoWay
	{
		/**
		 * Gives a linear increase and decrease in energy either side of the centre point.
		 */
		public static function linear( t : Number, b : Number, c : Number, d : Number ):Number
		{
			if( ( t = 2 * t / d ) <= 1 )
			{
				return ( 1 - t ) * c + b;
			}
			return ( t - 1 ) * c + b;
		}

		/**
		 * Energy increases and then decreases as if following the top half of a circle.
		 */
		public static function circular( t : Number, b : Number, c : Number, d : Number ):Number
		{
			t = 1 - (2 * t / d);
			return ( 1 - Math.sqrt( 1 - t * t ) ) * c + b;
		}

		/**
		 * Energy follows the first half of a sine wave.
		 */
		public static function sine( t : Number, b : Number, c : Number, d : Number ):Number
		{
			return ( 1 - Math.sin( Math.PI * t / d ) ) * c + b;
		}

		/**
		 * Eases towards the middle using a quadratic curve.
		 */
		public static function quadratic( t : Number, b : Number, c : Number, d : Number ):Number
		{
			t = 1 - (2 * t / d);
			return t * t * c + b;
		}

		/**
		 * Eases towards the middle using a cubic curve.
		 */
		public static function cubic( t : Number, b : Number, c : Number, d : Number ):Number
		{
			t = 1 - (2 * t / d);
			if( t < 0 ) t = -t;
			return t * t * t * c + b;
		}

		/**
		 * Eases towards the middle using a quartic curve.
		 */
		public static function quartic( t : Number, b : Number, c : Number, d : Number ):Number
		{
			t = 1 - (2 * t / d);
			return t * t * t * t * c + b;
		}

		/**
		 * Eases towards the middle using a quintic curve.
		 */
		public static function quintic( t : Number, b : Number, c : Number, d : Number ):Number
		{
			t = 1 - (2 * t / d);
			if( t < 0 ) t = -t;
			return t * t * t * t * t * c + b;
		}
	}
}

Fix error in TwoWay easing functions

Fix error in TwoWay easing functions


package aerys.minko.scene.controller
{
    import aerys.minko.ns.minko_math;
    import aerys.minko.render.Viewport;
    import aerys.minko.scene.node.Group;
    import aerys.minko.scene.node.ISceneNode;
    import aerys.minko.scene.node.Scene;
    import aerys.minko.type.math.Matrix4x4;

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

    use namespace minko_math;

    /**
     * The TransformController handles the batched update of all the local to world matrices
     * of a sub-scene. As such, it will only be active on the root node of a sub-scene and will
     * automatically disable itself on other nodes.
     *
     * @author Jean-Marc Le Roux
     *
     */
    public final class TransformController extends AbstractController
    {
        private static const INIT_NONE                  : uint  = 0;
        private static const INIT_LOCAL_TO_WORLD        : uint  = 1;
        private static const INIT_WORLD_TO_LOCAL        : uint  = 2;

        private var _target								: ISceneNode;

        private var _invalidList						: Boolean;

        private var _nodeToId							: Dictionary;
        private var _idToNode							: Vector.<ISceneNode>
        private var _transforms							: Vector.<Matrix4x4>;
        private var _initialized	                    : Vector.<uint>;
        private var _localToWorldTransforms 			: Vector.<Matrix4x4>;
        private var _worldToLocalTransforms 			: Vector.<Matrix4x4>;
        private var _numChildren						: Vector.<uint>;
        private var _firstChildId						: Vector.<uint>;
        private var _parentId							: Vector.<int>;

        public function TransformController()
        {
            super();

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

        private function renderingBeginHandler(scene			: Scene,
                                               viewport		: Viewport,
                                               destination	: BitmapData,
                                               time			: Number) : void
        {
            if (_invalidList)
                updateTransformsList();

            if (_transforms.length)
                updateLocalToWorld();
        }

        private function updateLocalToWorld(nodeId : uint = 0) : void
        {
            var numNodes 			: uint 			= _transforms.length;
            var childrenOffset		: uint			= 1;
            var rootLocalToWorld	: Matrix4x4		= _localToWorldTransforms[nodeId];
            var rootTransform		: Matrix4x4		= _transforms[nodeId];
            var root				: ISceneNode	= _idToNode[childId];

            if (rootTransform._hasChanged || _initialized[nodeId] == INIT_NONE)
            {
                rootLocalToWorld.copyFrom(rootTransform);

                if (nodeId != 0)
                    rootLocalToWorld.append(_localToWorldTransforms[_parentId[nodeId]]);

                rootTransform._hasChanged = false;
                _initialized[nodeId] = INIT_LOCAL_TO_WORLD;
                root.localToWorldTransformChanged.execute(root, rootLocalToWorld);
            }

            for (; nodeId < numNodes; ++nodeId)
            {
                var localToWorld 	: Matrix4x4	= _localToWorldTransforms[nodeId];
                var numChildren		: uint		= _numChildren[nodeId];
                var firstChildId	: uint		= _firstChildId[nodeId];
                var lastChildId		: uint		= firstChildId + numChildren;
                var isDirty			: Boolean	= localToWorld._hasChanged;

                localToWorld._hasChanged = false;

                for (var childId : uint = firstChildId; childId < lastChildId; ++childId)
                {
                    var childTransform		: Matrix4x4		= _transforms[childId];
                    var childLocalToWorld	: Matrix4x4		= _localToWorldTransforms[childId];
                    var childIsDirty		: Boolean		= isDirty || childTransform._hasChanged
                        || !_initialized[childId];

                    if (childIsDirty)
                    {
                        var child	: ISceneNode	= _idToNode[childId];

                        childLocalToWorld
                        .copyFrom(childTransform)
                            .append(localToWorld);

                        childTransform._hasChanged = false;
                        _initialized[childId] = INIT_LOCAL_TO_WORLD;
                        child.localToWorldTransformChanged.execute(child, childLocalToWorld);
                    }
                }
            }
        }

        private function updateAncestorsAndSelfLocalToWorld(nodeId : int) : void
        {
            var dirtyRoot : int = -1;

            while (nodeId >= 0)
            {
                if ((_transforms[nodeId] as Matrix4x4)._hasChanged ||
                    !_initialized[nodeId])
                    dirtyRoot = nodeId;

                nodeId = _parentId[nodeId];
            }

            if (dirtyRoot >= 0)
                updateLocalToWorld(dirtyRoot);
        }

        private function targetAddedHandler(ctrl	: TransformController,
                                            target	: ISceneNode) : void
        {
            if (_target)
                throw new Error('The TransformController cannot have more than one target.');

            _target = target;
            _invalidList = true;

            if (target is Scene)
            {
                (target as Scene).renderingBegin.add(renderingBeginHandler);
                return;
            }

            if (target is Group)
            {
                var targetGroup : Group = target as Group;

                targetGroup.descendantAdded.add(descendantAddedHandler);
                targetGroup.descendantRemoved.add(descendantRemovedHandler);
            }

            target.added.add(addedHandler);
        }

        private function targetRemovedHandler(ctrl		: TransformController,
                                              target	: ISceneNode) : void
        {
            target.added.remove(addedHandler);

            if (target is Group)
            {
                var targetGroup : Group = target as Group;

                targetGroup.descendantAdded.remove(descendantAddedHandler);
                targetGroup.descendantRemoved.remove(descendantRemovedHandler);
            }

            _invalidList = false;
            _target = null;

            _nodeToId = null;
            _transforms = null;
            _initialized = null;
            _localToWorldTransforms = null;
            _worldToLocalTransforms = null;
            _numChildren = null;
            _idToNode = null;
            _parentId = null;
        }

        private function addedHandler(target : ISceneNode, ancestor : Group) : void
        {
            // the controller will remove itself from the node when it's not its own root anymore
            // but it will watch for the 'removed' signal to add itself back if the node becomes
            // its own root again
            _target.removed.add(removedHandler);
            _target.removeController(this);
        }

        private function removedHandler(target : ISceneNode, ancestor : Group) : void
        {
            if (target.root == target)
            {
                target.removed.remove(removedHandler);
                target.addController(this);
            }
        }

        private function descendantAddedHandler(root		: Group,
                                                descendant	: ISceneNode) : void
        {
            _invalidList = true;
        }

        private function descendantRemovedHandler(root			: Group,
                                                  descendant	: ISceneNode) : void
        {
            _invalidList = true;
        }

        private function updateTransformsList() : void
        {
            var root    	                : ISceneNode 			= _target.root;
            var nodes   	                : Vector.<ISceneNode> 	= new <ISceneNode>[root];
            var nodeId  	                : uint 					= 0;
            var oldNodeToId                 : Dictionary            = _nodeToId;
            var oldInitialized              : Vector.<uint>         = _initialized;
            var oldLocalToWorldTransforms   : Vector.<Matrix4x4>    = _localToWorldTransforms;
            var oldWorldToLocalTransform    : Vector.<Matrix4x4>    = _worldToLocalTransforms;

            _nodeToId = new Dictionary(true);
            _transforms = new <Matrix4x4>[];
            _initialized = new <uint>[];
            _localToWorldTransforms = new <Matrix4x4>[];
            _worldToLocalTransforms = new <Matrix4x4>[];
            _numChildren = new <uint>[];
            _firstChildId = new <uint>[];
            _idToNode = new <ISceneNode>[];
            _parentId = new <int>[-1];

            while (nodes.length)
            {
                var node 	: ISceneNode 	= nodes.shift();
                var group   : Group 		= node as Group;

                _nodeToId[node] = nodeId;
                _idToNode[nodeId] = node;
                _transforms[nodeId] = node.transform;

                if (oldNodeToId && node in oldNodeToId)
                {
                    var oldNodeId   : uint  = oldNodeToId[node];

                    _localToWorldTransforms[nodeId] = oldLocalToWorldTransforms[oldNodeId];
                    _worldToLocalTransforms[nodeId] = oldWorldToLocalTransform[oldNodeId];
                    _initialized[nodeId] = oldInitialized[oldNodeId];
                }
                else
                {
                    _localToWorldTransforms[nodeId] = new Matrix4x4().lock();
                    _worldToLocalTransforms[nodeId] = null;
                    _initialized[nodeId] = INIT_NONE;
                }

                if (group)
                {
                    var numChildren 	: uint = group.numChildren;
                    var firstChildId 	: uint = nodeId + nodes.length + 1;

                    _numChildren[nodeId] = numChildren;
                    _firstChildId[nodeId] = firstChildId;
                    for (var childId : uint = 0; childId < numChildren; ++childId)
                    {
                        _parentId[uint(firstChildId + childId)] = nodeId;
                        nodes.push(group.getChildAt(childId));
                    }
                }
                else
                {
                    _numChildren[nodeId] = 0;
                    _firstChildId[nodeId] = 0;
                }

                ++nodeId;
            }

            _worldToLocalTransforms.length = _localToWorldTransforms.length;
            _invalidList = false;
        }

        public function getLocalToWorldTransform(node 			: ISceneNode,
                                                 forceUpdate 	: Boolean = false) : Matrix4x4
        {
            if (_invalidList || _nodeToId[node] == undefined)
                updateTransformsList();

            var nodeId : uint = _nodeToId[node];

            if (forceUpdate)
                updateAncestorsAndSelfLocalToWorld(nodeId);

            return _localToWorldTransforms[nodeId];
        }

        public function getWorldToLocalTransform(node           : ISceneNode,
                                                 forceUpdate    : Boolean   = false) : Matrix4x4
        {
            if (_invalidList || _nodeToId[node] == undefined)
                updateTransformsList();

            var nodeId                  : uint      = _nodeToId[node];
            var worldToLocalTransform   : Matrix4x4 = _worldToLocalTransforms[nodeId];

            if (!worldToLocalTransform)
            {
                _worldToLocalTransforms[nodeId] = worldToLocalTransform = new Matrix4x4();
                if (!forceUpdate)
                {
                    worldToLocalTransform.copyFrom(_localToWorldTransforms[nodeId]).invert();
                    _initialized[nodeId] |= INIT_WORLD_TO_LOCAL;
                }
            }

            if (forceUpdate)
                updateAncestorsAndSelfLocalToWorld(nodeId);

            if (!(_initialized[nodeId] & INIT_WORLD_TO_LOCAL))
            {
                _initialized[nodeId] |= INIT_WORLD_TO_LOCAL;

                worldToLocalTransform
                    .copyFrom(_localToWorldTransforms[nodeId])
                    .invert();
            }

            return worldToLocalTransform;
        }
    }
}

package aerys.minko.scene.controller
{
    import aerys.minko.ns.minko_math;
    import aerys.minko.render.Viewport;
    import aerys.minko.scene.node.Group;
    import aerys.minko.scene.node.ISceneNode;
    import aerys.minko.scene.node.Scene;
    import aerys.minko.type.math.Matrix4x4;

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

    import mx.olap.aggregators.MaxAggregator;

    use namespace minko_math;

    /**
     * The TransformController handles the batched update of all the local to world matrices
     * of a sub-scene. As such, it will only be active on the root node of a sub-scene and will
     * automatically disable itself on other nodes.
     *
     * @author Jean-Marc Le Roux
     *
     */
    public final class TransformController extends AbstractController
    {
        private static const INIT_NONE                  : uint  = 0;
        private static const INIT_LOCAL_TO_WORLD        : uint  = 1;
        private static const INIT_WORLD_TO_LOCAL        : uint  = 2;

        private var _target								: ISceneNode;

        private var _invalidList						: Boolean;

        private var _nodeToId							: Dictionary;
        private var _idToNode							: Vector.<ISceneNode>
        private var _transforms							: Vector.<Matrix4x4>;
        private var _initialized	                    : Vector.<uint>;
        private var _localToWorldTransforms 			: Vector.<Matrix4x4>;
        private var _worldToLocalTransforms 			: Vector.<Matrix4x4>;
        private var _numChildren						: Vector.<uint>;
        private var _firstChildId						: Vector.<uint>;
        private var _parentId							: Vector.<int>;

        public function TransformController()
        {
            super();

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

        private function renderingBeginHandler(scene			: Scene,
                                               viewport		: Viewport,
                                               destination	: BitmapData,
                                               time			: Number) : void
        {
            if (_invalidList)
                updateTransformsList();

            if (_transforms.length)
                updateLocalToWorld();
        }

        private function updateLocalToWorld(nodeId : uint = 0, subtreeOnly : Boolean = false) : void
        {
            var numNodes 			: uint 			= _transforms.length;
            var childrenOffset		: uint			= 1;
            var rootLocalToWorld	: Matrix4x4		= _localToWorldTransforms[nodeId];
            var rootTransform		: Matrix4x4		= _transforms[nodeId];
            var root				: ISceneNode	= _idToNode[childId];
            var subtreeMax          : uint          = nodeId;

            if (rootTransform._hasChanged || _initialized[nodeId] == INIT_NONE)
            {
                rootLocalToWorld.copyFrom(rootTransform);

                if (nodeId != 0)
                    rootLocalToWorld.append(_localToWorldTransforms[_parentId[nodeId]]);

                rootTransform._hasChanged = false;
                _initialized[nodeId] = INIT_LOCAL_TO_WORLD;
                root.localToWorldTransformChanged.execute(root, rootLocalToWorld);
            }

            if (lastChildId > subtreeMax)
                subtreeMax = lastChildId;

            while (nodeId < numNodes)
            {
                var localToWorld 	: Matrix4x4	= _localToWorldTransforms[nodeId];
                var numChildren		: uint		= _numChildren[nodeId];
                var firstChildId	: uint		= _firstChildId[nodeId];
                var lastChildId		: uint		= firstChildId + numChildren;
                var isDirty			: Boolean	= localToWorld._hasChanged;

                localToWorld._hasChanged = false;

                for (var childId : uint = firstChildId; childId < lastChildId; ++childId)
                {
                    var childTransform		: Matrix4x4		= _transforms[childId];
                    var childLocalToWorld	: Matrix4x4		= _localToWorldTransforms[childId];
                    var childIsDirty		: Boolean		= isDirty || childTransform._hasChanged
                        || !_initialized[childId];

                    if (childIsDirty)
                    {
                        var child	: ISceneNode	= _idToNode[childId];

                        childLocalToWorld
                            .copyFrom(childTransform)
                            .append(localToWorld);

                        childTransform._hasChanged = false;
                        _initialized[childId] = INIT_LOCAL_TO_WORLD;
                        child.localToWorldTransformChanged.execute(child, childLocalToWorld);
                    }
                }

                if (subtreeOnly && nodeId && nodeId >= subtreeMax)
                {
                    var parentId : uint = _parentId[nodeId];

                    nodeId = _firstChildId[parentId];
                    while (!_numChildren[nodeId] && nodeId < subtreeMax)
                        ++nodeId;

                    if (nodeId >= subtreeMax)
                        return ;

                    nodeId = _firstChildId[nodeId];
                }
                else
                    ++nodeId;
            }
        }

        private function updateAncestorsAndSelfLocalToWorld(nodeId : int) : void
        {
            var dirtyRoot : int = -1;

            while (nodeId >= 0)
            {
                if ((_transforms[nodeId] as Matrix4x4)._hasChanged || !_initialized[nodeId])
                    dirtyRoot = nodeId;

                nodeId = _parentId[nodeId];
            }

            if (dirtyRoot >= 0)
                updateLocalToWorld(dirtyRoot, true);
        }

        private function targetAddedHandler(ctrl	: TransformController,
                                            target	: ISceneNode) : void
        {
            if (_target)
                throw new Error('The TransformController cannot have more than one target.');

            _target = target;
            _invalidList = true;

            if (target is Scene)
            {
                (target as Scene).renderingBegin.add(renderingBeginHandler);
                return;
            }

            if (target is Group)
            {
                var targetGroup : Group = target as Group;

                targetGroup.descendantAdded.add(descendantAddedHandler);
                targetGroup.descendantRemoved.add(descendantRemovedHandler);
            }

            target.added.add(addedHandler);
        }

        private function targetRemovedHandler(ctrl		: TransformController,
                                              target	: ISceneNode) : void
        {
            target.added.remove(addedHandler);

            if (target is Group)
            {
                var targetGroup : Group = target as Group;

                targetGroup.descendantAdded.remove(descendantAddedHandler);
                targetGroup.descendantRemoved.remove(descendantRemovedHandler);
            }

            _invalidList = false;
            _target = null;

            _nodeToId = null;
            _transforms = null;
            _initialized = null;
            _localToWorldTransforms = null;
            _worldToLocalTransforms = null;
            _numChildren = null;
            _idToNode = null;
            _parentId = null;
        }

        private function addedHandler(target : ISceneNode, ancestor : Group) : void
        {
            // the controller will remove itself from the node when it's not its own root anymore
            // but it will watch for the 'removed' signal to add itself back if the node becomes
            // its own root again
            _target.removed.add(removedHandler);
            _target.removeController(this);
        }

        private function removedHandler(target : ISceneNode, ancestor : Group) : void
        {
            if (target.root == target)
            {
                target.removed.remove(removedHandler);
                target.addController(this);
            }
        }

        private function descendantAddedHandler(root		: Group,
                                                descendant	: ISceneNode) : void
        {
            _invalidList = true;
        }

        private function descendantRemovedHandler(root			: Group,
                                                  descendant	: ISceneNode) : void
        {
            _invalidList = true;
        }

        private function updateTransformsList() : void
        {
            var root    	                : ISceneNode 			= _target.root;
            var nodes   	                : Vector.<ISceneNode> 	= new <ISceneNode>[root];
            var nodeId  	                : uint 					= 0;
            var oldNodeToId                 : Dictionary            = _nodeToId;
            var oldInitialized              : Vector.<uint>         = _initialized;
            var oldLocalToWorldTransforms   : Vector.<Matrix4x4>    = _localToWorldTransforms;
            var oldWorldToLocalTransform    : Vector.<Matrix4x4>    = _worldToLocalTransforms;

            _nodeToId = new Dictionary(true);
            _transforms = new <Matrix4x4>[];
            _initialized = new <uint>[];
            _localToWorldTransforms = new <Matrix4x4>[];
            _worldToLocalTransforms = new <Matrix4x4>[];
            _numChildren = new <uint>[];
            _firstChildId = new <uint>[];
            _idToNode = new <ISceneNode>[];
            _parentId = new <int>[-1];

            while (nodes.length)
            {
                var node 	: ISceneNode 	= nodes.shift();
                var group   : Group 		= node as Group;

                _nodeToId[node] = nodeId;
                _idToNode[nodeId] = node;
                _transforms[nodeId] = node.transform;

                if (oldNodeToId && node in oldNodeToId)
                {
                    var oldNodeId   : uint  = oldNodeToId[node];

                    _localToWorldTransforms[nodeId] = oldLocalToWorldTransforms[oldNodeId];
                    _worldToLocalTransforms[nodeId] = oldWorldToLocalTransform[oldNodeId];
                    _initialized[nodeId] = oldInitialized[oldNodeId];
                }
                else
                {
                    _localToWorldTransforms[nodeId] = new Matrix4x4().lock();
                    _worldToLocalTransforms[nodeId] = null;
                    _initialized[nodeId] = INIT_NONE;
                }

                if (group)
                {
                    var numChildren 	: uint = group.numChildren;
                    var firstChildId 	: uint = nodeId + nodes.length + 1;

                    _numChildren[nodeId] = numChildren;
                    _firstChildId[nodeId] = firstChildId;
                    for (var childId : uint = 0; childId < numChildren; ++childId)
                    {
                        _parentId[uint(firstChildId + childId)] = nodeId;
                        nodes.push(group.getChildAt(childId));
                    }
                }
                else
                {
                    _numChildren[nodeId] = 0;
                    _firstChildId[nodeId] = 0;
                }

                ++nodeId;
            }

            _worldToLocalTransforms.length = _localToWorldTransforms.length;
            _invalidList = false;
        }

        public function getLocalToWorldTransform(node 			: ISceneNode,
                                                 forceUpdate 	: Boolean = false) : Matrix4x4
        {
            if (_invalidList || _nodeToId[node] == undefined)
                updateTransformsList();

            var nodeId : uint = _nodeToId[node];

            if (forceUpdate)
                updateAncestorsAndSelfLocalToWorld(nodeId);

            return _localToWorldTransforms[nodeId];
        }

        public function getWorldToLocalTransform(node           : ISceneNode,
                                                 forceUpdate    : Boolean   = false) : Matrix4x4
        {
            if (_invalidList || _nodeToId[node] == undefined)
                updateTransformsList();

            var nodeId                  : uint      = _nodeToId[node];
            var worldToLocalTransform   : Matrix4x4 = _worldToLocalTransforms[nodeId];

            if (!worldToLocalTransform)
            {
                _worldToLocalTransforms[nodeId] = worldToLocalTransform = new Matrix4x4();
                if (!forceUpdate)
                {
                    worldToLocalTransform.copyFrom(_localToWorldTransforms[nodeId]).invert();
                    _initialized[nodeId] |= INIT_WORLD_TO_LOCAL;
                }
            }

            if (forceUpdate)
                updateAncestorsAndSelfLocalToWorld(nodeId);

            if (!(_initialized[nodeId] & INIT_WORLD_TO_LOCAL))
            {
                _initialized[nodeId] |= INIT_WORLD_TO_LOCAL;

                worldToLocalTransform
                    .copyFrom(_localToWorldTransforms[nodeId])
                    .invert();
            }

            return worldToLocalTransform;
        }
    }
}

optimize TransformController.updateLocalToWorld() to update only the nodes of the subtree when subtreeOnly = true


package aerys.minko.scene.controller
{
    import aerys.minko.ns.minko_math;
    import aerys.minko.render.Viewport;
    import aerys.minko.scene.node.Group;
    import aerys.minko.scene.node.ISceneNode;
    import aerys.minko.scene.node.Scene;
    import aerys.minko.type.math.Matrix4x4;

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

    import mx.olap.aggregators.MaxAggregator;

    use namespace minko_math;

    /**
     * The TransformController handles the batched update of all the local to world matrices
     * of a sub-scene. As such, it will only be active on the root node of a sub-scene and will
     * automatically disable itself on other nodes.
     *
     * @author Jean-Marc Le Roux
     *
     */
    public final class TransformController extends AbstractController
    {

        private static const FLAG_NONE                      : uint  = 0;
        private static const FLAG_INIT_LOCAL_TO_WORLD       : uint  = 1;
        private static const FLAG_INIT_WORLD_TO_LOCAL       : uint  = 2;
        private static const FLAG_SYNCHRONIZE_TRANSFORMS    : uint  = 4;
        private static const FLAG_LOCK_TRANSFORMS           : uint  = 8;

        private var _target					: ISceneNode;

        private var _invalidList			: Boolean;

        private var _nodeToId				: Dictionary;
        private var _idToNode				: Vector.<ISceneNode>
        private var _transforms				: Vector.<Matrix4x4>;
        private var _flags	                : Vector.<uint>;
        private var _localToWorldTransforms : Vector.<Matrix4x4>;
        private var _worldToLocalTransforms : Vector.<Matrix4x4>;
        private var _numChildren			: Vector.<uint>;
        private var _firstChildId			: Vector.<uint>;
        private var _parentId				: Vector.<int>;

        public function TransformController()
        {
            super();

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

        private function renderingBeginHandler(scene		: Scene,
                                               viewport		: Viewport,
                                               destination	: BitmapData,
                                               time			: Number) : void
        {
            if (_invalidList)
                updateTransformsList();

            if (_transforms.length)
                updateLocalToWorld();
        }

        private function updateRootLocalToWorld(nodeId : uint = 0) : void
        {
            var rootLocalToWorld	: Matrix4x4		= _localToWorldTransforms[nodeId];
            var rootTransform		: Matrix4x4		= _transforms[nodeId];
            var root				: ISceneNode	= _idToNode[nodeId];
            var rootFlags           : uint          = _flags[nodeId];

            if (rootTransform._hasChanged || !(rootFlags & FLAG_INIT_LOCAL_TO_WORLD))
            {
                if (rootFlags & FLAG_LOCK_TRANSFORMS)
                    rootLocalToWorld.lock();

                rootLocalToWorld.copyFrom(rootTransform);

                if (nodeId != 0)
                    rootLocalToWorld.append(_localToWorldTransforms[_parentId[nodeId]]);

                if (rootFlags & FLAG_LOCK_TRANSFORMS)
                    rootLocalToWorld.unlock();

                rootTransform._hasChanged = false;
                rootFlags = (rootFlags | FLAG_INIT_LOCAL_TO_WORLD) & ~FLAG_INIT_WORLD_TO_LOCAL;

                if (rootFlags & FLAG_SYNCHRONIZE_TRANSFORMS)
                {
                    var rootWorldToLocal : Matrix4x4 = _worldToLocalTransforms[nodeId]
                        || (_worldToLocalTransforms[nodeId] = new Matrix4x4());

                    if (rootFlags & FLAG_LOCK_TRANSFORMS)
                        rootWorldToLocal.lock();

                    rootWorldToLocal.copyFrom(rootLocalToWorld).invert();
                    rootFlags |= FLAG_INIT_WORLD_TO_LOCAL;

                    if (rootFlags & FLAG_LOCK_TRANSFORMS)
                        rootWorldToLocal.unlock();
                }

                _flags[nodeId] = rootFlags;

                root.localToWorldTransformChanged.execute(root, rootLocalToWorld);
            }
        }

        private function updateLocalToWorld(nodeId : uint = 0, targetNodeId : int = -1) : void
        {
            var numNodes 	: uint 	= _transforms.length;
            var subtreeMax  : uint  = nodeId;

            updateRootLocalToWorld(nodeId);

            if (nodeId == targetNodeId)
                return;

            while (nodeId < numNodes)
            {
                var localToWorld 	: Matrix4x4	= _localToWorldTransforms[nodeId];
                var numChildren		: uint		= _numChildren[nodeId];
                var firstChildId	: uint		= _firstChildId[nodeId];
                var lastChildId		: uint		= firstChildId + numChildren;
                var isDirty			: Boolean	= localToWorld._hasChanged;

                localToWorld._hasChanged = false;

                if (lastChildId > subtreeMax)
                    subtreeMax = lastChildId;

                for (var childId : uint = firstChildId; childId < lastChildId; ++childId)
                {
                    var childTransform		: Matrix4x4		= _transforms[childId];
                    var childLocalToWorld	: Matrix4x4		= _localToWorldTransforms[childId];
                    var childFlags          : uint          = _flags[childId];
                    var childIsDirty		: Boolean		= isDirty || childTransform._hasChanged
                        || !(childFlags & FLAG_INIT_LOCAL_TO_WORLD);

                    if (childIsDirty)
                    {
                        var child	: ISceneNode	= _idToNode[childId];

                        if (childFlags & FLAG_LOCK_TRANSFORMS)
                            childLocalToWorld.lock();

                        childLocalToWorld
                            .copyFrom(childTransform)
                            .append(localToWorld);

                        if (childFlags & FLAG_LOCK_TRANSFORMS)
                            childLocalToWorld.unlock();

                        childTransform._hasChanged = false;
                        childFlags = (childFlags | FLAG_INIT_LOCAL_TO_WORLD)
                            & ~FLAG_INIT_WORLD_TO_LOCAL;

                        if (childFlags & FLAG_SYNCHRONIZE_TRANSFORMS)
                        {
                            var childWorldToLocal : Matrix4x4 = _worldToLocalTransforms[childId]
                                || (_worldToLocalTransforms[childId] = new Matrix4x4());

                            if (childFlags & FLAG_LOCK_TRANSFORMS)
                                childWorldToLocal.lock();

                            childWorldToLocal.copyFrom(childLocalToWorld).invert();
                            childFlags |= FLAG_INIT_WORLD_TO_LOCAL;

                            if (childFlags & FLAG_LOCK_TRANSFORMS)
                                childWorldToLocal.unlock();
                        }

                        _flags[childId] = childFlags;

                        child.localToWorldTransformChanged.execute(child, childLocalToWorld);

                        if (childId == targetNodeId)
                            return;
                    }
                }

                if (targetNodeId >= 0 && nodeId && nodeId >= subtreeMax)
                {
                    // jump to the first brother who has children
                    var parentId : uint = _parentId[nodeId];

                    nodeId = _firstChildId[parentId];
                    while (!_numChildren[nodeId] && nodeId < subtreeMax)
                        ++nodeId;

                    if (nodeId >= subtreeMax)
                        return ;

                    nodeId = _firstChildId[nodeId];
                }
                else
                    ++nodeId;
            }
        }

        private function updateAncestorsAndSelfLocalToWorld(nodeId : uint) : void
        {
            var dirtyRoot : int = -1;
            var tmpNodeId : int = nodeId;

            while (tmpNodeId >= 0)
            {
                if ((_transforms[tmpNodeId] as Matrix4x4)._hasChanged
                    || !(_flags[nodeId] & FLAG_INIT_LOCAL_TO_WORLD))
                    dirtyRoot = tmpNodeId;

                tmpNodeId = _parentId[tmpNodeId];
            }

            if (dirtyRoot >= 0)
                updateLocalToWorld(dirtyRoot, nodeId);
        }

        private function targetAddedHandler(ctrl	: TransformController,
                                            target	: ISceneNode) : void
        {
            if (_target)
                throw new Error('The TransformController cannot have more than one target.');

            _target = target;
            _invalidList = true;

            if (target is Scene)
            {
                (target as Scene).renderingBegin.add(renderingBeginHandler);
                return;
            }

            if (target is Group)
            {
                var targetGroup : Group = target as Group;

                targetGroup.descendantAdded.add(descendantAddedHandler);
                targetGroup.descendantRemoved.add(descendantRemovedHandler);
            }

            target.added.add(addedHandler);
        }

        private function targetRemovedHandler(ctrl		: TransformController,
                                              target	: ISceneNode) : void
        {
            target.added.remove(addedHandler);

            if (target is Group)
            {
                var targetGroup : Group = target as Group;

                targetGroup.descendantAdded.remove(descendantAddedHandler);
                targetGroup.descendantRemoved.remove(descendantRemovedHandler);
            }

            _invalidList = false;
            _target = null;

            _nodeToId = null;
            _transforms = null;
            _flags = null;
            _localToWorldTransforms = null;
            _worldToLocalTransforms = null;
            _numChildren = null;
            _idToNode = null;
            _parentId = null;
        }

        private function addedHandler(target : ISceneNode, ancestor : Group) : void
        {
            // the controller will remove itself from the node when it's not its own root anymore
            // but it will watch for the 'removed' signal to add itself back if the node becomes
            // its own root again
            _target.removed.add(removedHandler);
            _target.removeController(this);
        }

        private function removedHandler(target : ISceneNode, ancestor : Group) : void
        {
            if (target.root == target)
            {
                target.removed.remove(removedHandler);
                target.addController(this);
            }
        }

        private function descendantAddedHandler(root		: Group,
                                                descendant	: ISceneNode) : void
        {
            _invalidList = true;
        }

        private function descendantRemovedHandler(root			: Group,
                                                  descendant	: ISceneNode) : void
        {
            _invalidList = true;
        }

        private function getNodeId(node : ISceneNode) : uint
        {
            if (_invalidList || !(node in _nodeToId))
                updateTransformsList();

            return _nodeToId[node];
        }

        private function updateTransformsList() : void
        {
            var root    	                : ISceneNode 			= _target.root;
            var nodes   	                : Vector.<ISceneNode> 	= new <ISceneNode>[root];
            var nodeId  	                : uint 					= 0;
            var oldNodeToId                 : Dictionary            = _nodeToId;
            var oldInitialized              : Vector.<uint>         = _flags;
            var oldLocalToWorldTransforms   : Vector.<Matrix4x4>    = _localToWorldTransforms;
            var oldWorldToLocalTransform    : Vector.<Matrix4x4>    = _worldToLocalTransforms;

            _nodeToId = new Dictionary(true);
            _transforms = new <Matrix4x4>[];
            _flags = new <uint>[];
            _localToWorldTransforms = new <Matrix4x4>[];
            _worldToLocalTransforms = new <Matrix4x4>[];
            _numChildren = new <uint>[];
            _firstChildId = new <uint>[];
            _idToNode = new <ISceneNode>[];
            _parentId = new <int>[-1];

            while (nodes.length)
            {
                var node 	: ISceneNode 	= nodes.shift();
                var group   : Group 		= node as Group;

                _nodeToId[node] = nodeId;
                _idToNode[nodeId] = node;
                _transforms[nodeId] = node.transform;

                if (oldNodeToId && node in oldNodeToId)
                {
                    var oldNodeId   : uint  = oldNodeToId[node];

                    _localToWorldTransforms[nodeId] = oldLocalToWorldTransforms[oldNodeId];
                    _worldToLocalTransforms[nodeId] = oldWorldToLocalTransform[oldNodeId];
                    _flags[nodeId] = oldInitialized[oldNodeId];
                }
                else
                {
                    _localToWorldTransforms[nodeId] = new Matrix4x4().lock();
                    _worldToLocalTransforms[nodeId] = null;
                    _flags[nodeId] = FLAG_NONE;
                }

                if (group)
                {
                    var numChildren 	: uint = group.numChildren;
                    var firstChildId 	: uint = nodeId + nodes.length + 1;

                    _numChildren[nodeId] = numChildren;
                    _firstChildId[nodeId] = firstChildId;
                    for (var childId : uint = 0; childId < numChildren; ++childId)
                    {
                        _parentId[uint(firstChildId + childId)] = nodeId;
                        nodes.push(group.getChildAt(childId));
                    }
                }
                else
                {
                    _numChildren[nodeId] = 0;
                    _firstChildId[nodeId] = 0;
                }

                ++nodeId;
            }

            _worldToLocalTransforms.length = _localToWorldTransforms.length;
            _invalidList = false;
        }

        public function getLocalToWorldTransform(node 			: ISceneNode,
                                                 forceUpdate 	: Boolean = false) : Matrix4x4
        {
            var nodeId : uint = getNodeId(node);

            if (forceUpdate)
                updateAncestorsAndSelfLocalToWorld(nodeId);

            return _localToWorldTransforms[nodeId];
        }

        public function getWorldToLocalTransform(node           : ISceneNode,
                                                 forceUpdate    : Boolean   = false) : Matrix4x4
        {
            var nodeId                  : uint      = getNodeId(node);
            var worldToLocalTransform   : Matrix4x4 = _worldToLocalTransforms[nodeId];

            if (!worldToLocalTransform)
            {
                _worldToLocalTransforms[nodeId] = worldToLocalTransform = new Matrix4x4();
                if (!forceUpdate)
                {
                    worldToLocalTransform.copyFrom(_localToWorldTransforms[nodeId]).invert();
                    _flags[nodeId] |= FLAG_INIT_WORLD_TO_LOCAL;
                }
            }

            if (forceUpdate)
                updateAncestorsAndSelfLocalToWorld(nodeId);

            var flags : uint = _flags[nodeId];

            if (!(flags & FLAG_INIT_WORLD_TO_LOCAL))
            {
                _flags[nodeId] |= FLAG_INIT_WORLD_TO_LOCAL;

                if (flags & FLAG_LOCK_TRANSFORMS)
                    worldToLocalTransform.lock();

                worldToLocalTransform
                    .copyFrom(_localToWorldTransforms[nodeId])
                    .invert();

                if (flags & FLAG_LOCK_TRANSFORMS)
                    worldToLocalTransform.unlock();
            }

            return worldToLocalTransform;
        }

        public function setSharedLocalToWorldTransformReference(node      : ISceneNode,
                                                                matrix    : Matrix4x4) : void
        {
            var nodeId : uint = getNodeId(node);

            if (_flags[nodeId] & FLAG_INIT_LOCAL_TO_WORLD)
                matrix.copyFrom(_localToWorldTransforms[nodeId]);
            _localToWorldTransforms[nodeId] = matrix;
        }

        public function setSharedWorldToLocalTransformReference(node      : ISceneNode,
                                                                matrix    : Matrix4x4) : void
        {
            var nodeId : uint = getNodeId(node);

            if (_flags[nodeId] & FLAG_INIT_WORLD_TO_LOCAL)
                matrix.copyFrom(_worldToLocalTransforms[nodeId]);
            _worldToLocalTransforms[nodeId] = matrix;
        }

        public function synchronizeTransforms(node : ISceneNode, enabled : Boolean) : void
        {
            var nodeId : uint = getNodeId(node);

            _flags[nodeId] = enabled
                ? _flags[nodeId] | FLAG_SYNCHRONIZE_TRANSFORMS
                : _flags[nodeId] & ~FLAG_SYNCHRONIZE_TRANSFORMS;
        }

        public function lockTransformsBeforeUpdate(node : ISceneNode, enabled : Boolean) : void
        {
            var nodeId : uint = getNodeId(node);

            _flags[nodeId] = enabled
                ? _flags[nodeId] | FLAG_LOCK_TRANSFORMS
                : _flags[nodeId] & ~FLAG_LOCK_TRANSFORMS;
        }
    }
}

package aerys.minko.scene.controller
{
    import aerys.minko.ns.minko_math;
    import aerys.minko.render.Viewport;
    import aerys.minko.scene.node.Group;
    import aerys.minko.scene.node.ISceneNode;
    import aerys.minko.scene.node.Scene;
    import aerys.minko.type.math.Matrix4x4;

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

    import mx.olap.aggregators.MaxAggregator;

    use namespace minko_math;

    /**
     * The TransformController handles the batched update of all the local to world matrices
     * of a sub-scene. As such, it will only be active on the root node of a sub-scene and will
     * automatically disable itself on other nodes.
     *
     * @author Jean-Marc Le Roux
     *
     */
    public final class TransformController extends AbstractController
    {

        private static const FLAG_NONE                      : uint  = 0;
        private static const FLAG_INIT_LOCAL_TO_WORLD       : uint  = 1;
        private static const FLAG_INIT_WORLD_TO_LOCAL       : uint  = 2;
        private static const FLAG_SYNCHRONIZE_TRANSFORMS    : uint  = 4;
        private static const FLAG_LOCK_TRANSFORMS           : uint  = 8;

        private var _target					: ISceneNode;

        private var _invalidList			: Boolean;

        private var _nodeToId				: Dictionary;
        private var _idToNode				: Vector.<ISceneNode>
        private var _transforms				: Vector.<Matrix4x4>;
        private var _flags	                : Vector.<uint>;
        private var _localToWorldTransforms : Vector.<Matrix4x4>;
        private var _worldToLocalTransforms : Vector.<Matrix4x4>;
        private var _numChildren			: Vector.<uint>;
        private var _firstChildId			: Vector.<uint>;
        private var _parentId				: Vector.<int>;

        public function TransformController()
        {
            super();

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

        private function renderingBeginHandler(scene		: Scene,
                                               viewport		: Viewport,
                                               destination	: BitmapData,
                                               time			: Number) : void
        {
            if (_invalidList)
                updateTransformsList();

            if (_transforms.length)
                updateLocalToWorld();
        }

        private function updateRootLocalToWorld(nodeId : uint = 0) : void
        {
            var rootLocalToWorld	: Matrix4x4		= _localToWorldTransforms[nodeId];
            var rootTransform		: Matrix4x4		= _transforms[nodeId];
            var root				: ISceneNode	= _idToNode[nodeId];
            var rootFlags           : uint          = _flags[nodeId];

            if (rootTransform._hasChanged || !(rootFlags & FLAG_INIT_LOCAL_TO_WORLD))
            {
                if (rootFlags & FLAG_LOCK_TRANSFORMS)
                    rootLocalToWorld.lock();

                rootLocalToWorld.copyFrom(rootTransform);

                if (nodeId != 0)
                    rootLocalToWorld.append(_localToWorldTransforms[_parentId[nodeId]]);

                if (rootFlags & FLAG_LOCK_TRANSFORMS)
                    rootLocalToWorld.unlock();

                rootTransform._hasChanged = false;
                rootFlags = (rootFlags | FLAG_INIT_LOCAL_TO_WORLD) & ~FLAG_INIT_WORLD_TO_LOCAL;

                if (rootFlags & FLAG_SYNCHRONIZE_TRANSFORMS)
                {
                    var rootWorldToLocal : Matrix4x4 = _worldToLocalTransforms[nodeId]
                        || (_worldToLocalTransforms[nodeId] = new Matrix4x4());

                    if (rootFlags & FLAG_LOCK_TRANSFORMS)
                        rootWorldToLocal.lock();

                    rootWorldToLocal.copyFrom(rootLocalToWorld).invert();
                    rootFlags |= FLAG_INIT_WORLD_TO_LOCAL;

                    if (rootFlags & FLAG_LOCK_TRANSFORMS)
                        rootWorldToLocal.unlock();
                }

                _flags[nodeId] = rootFlags;

                root.localToWorldTransformChanged.execute(root, rootLocalToWorld);
            }
        }

        private function updateLocalToWorld(nodeId : uint = 0, targetNodeId : int = -1) : void
        {
            var numNodes 	: uint 	= _transforms.length;
            var subtreeMax  : uint  = nodeId;

            updateRootLocalToWorld(nodeId);

            if (nodeId == targetNodeId)
                return;

            while (nodeId < numNodes)
            {
                var localToWorld 	: Matrix4x4	= _localToWorldTransforms[nodeId];
                var numChildren		: uint		= _numChildren[nodeId];
                var firstChildId	: uint		= _firstChildId[nodeId];
                var lastChildId		: uint		= firstChildId + numChildren;
                var isDirty			: Boolean	= localToWorld._hasChanged;

                localToWorld._hasChanged = false;

                if (lastChildId > subtreeMax)
                    subtreeMax = lastChildId;

                for (var childId : uint = firstChildId; childId < lastChildId; ++childId)
                {
                    var childTransform		: Matrix4x4		= _transforms[childId];
                    var childLocalToWorld	: Matrix4x4		= _localToWorldTransforms[childId];
                    var childFlags          : uint          = _flags[childId];
                    var childIsDirty		: Boolean		= isDirty || childTransform._hasChanged
                        || !(childFlags & FLAG_INIT_LOCAL_TO_WORLD);

                    if (childIsDirty)
                    {
                        var child	: ISceneNode	= _idToNode[childId];

                        if (childFlags & FLAG_LOCK_TRANSFORMS)
                            childLocalToWorld.lock();

                        childLocalToWorld
                            .copyFrom(childTransform)
                            .append(localToWorld);

                        if (childFlags & FLAG_LOCK_TRANSFORMS)
                            childLocalToWorld.unlock();

                        childTransform._hasChanged = false;
                        childFlags = (childFlags | FLAG_INIT_LOCAL_TO_WORLD)
                            & ~FLAG_INIT_WORLD_TO_LOCAL;

                        if (childFlags & FLAG_SYNCHRONIZE_TRANSFORMS)
                        {
                            var childWorldToLocal : Matrix4x4 = _worldToLocalTransforms[childId]
                                || (_worldToLocalTransforms[childId] = new Matrix4x4());

                            if (childFlags & FLAG_LOCK_TRANSFORMS)
                                childWorldToLocal.lock();

                            childWorldToLocal.copyFrom(childLocalToWorld).invert();
                            childFlags |= FLAG_INIT_WORLD_TO_LOCAL;

                            if (childFlags & FLAG_LOCK_TRANSFORMS)
                                childWorldToLocal.unlock();
                        }

                        _flags[childId] = childFlags;

                        child.localToWorldTransformChanged.execute(child, childLocalToWorld);

                        if (childId == targetNodeId)
                            return;
                    }
                }

                if (targetNodeId >= 0 && nodeId && nodeId >= subtreeMax)
                {
                    // jump to the first brother who has children
                    var parentId : uint = _parentId[nodeId];

                    nodeId = _firstChildId[parentId];
                    while (!_numChildren[nodeId] && nodeId < subtreeMax)
                        ++nodeId;

                    if (nodeId >= subtreeMax)
                        return ;

                    nodeId = _firstChildId[nodeId];
                }
                else
                    ++nodeId;
            }
        }

        private function updateLocalToWorldPath(path : Vector.<uint> = null) : void
        {
            var numNodes 	    : uint 	    = path.length;
            var nodeId          : uint      = path[uint(numNodes - 1)];
            var localToWorld 	: Matrix4x4	= _localToWorldTransforms[nodeId];

            updateRootLocalToWorld(nodeId);

            for (var i : int = numNodes - 2; i >= 0; --i)
            {
                var childId             : uint          = path[i];
                var childTransform		: Matrix4x4		= _transforms[childId];
                var childLocalToWorld	: Matrix4x4		= _localToWorldTransforms[childId];
                var childFlags          : uint          = _flags[childId];
                var child	            : ISceneNode	= _idToNode[childId];

                if (childFlags & FLAG_LOCK_TRANSFORMS)
                    childLocalToWorld.lock();

                childLocalToWorld
                    .copyFrom(childTransform)
                    .append(localToWorld);

                if (childFlags & FLAG_LOCK_TRANSFORMS)
                    childLocalToWorld.unlock();

                childTransform._hasChanged = false;
                childFlags = (childFlags | FLAG_INIT_LOCAL_TO_WORLD)
                    & ~FLAG_INIT_WORLD_TO_LOCAL;

                if (childFlags & FLAG_SYNCHRONIZE_TRANSFORMS)
                {
                    var childWorldToLocal : Matrix4x4 = _worldToLocalTransforms[childId]
                        || (_worldToLocalTransforms[childId] = new Matrix4x4());

                    if (childFlags & FLAG_LOCK_TRANSFORMS)
                        childWorldToLocal.lock();

                    childWorldToLocal.copyFrom(childLocalToWorld).invert();
                    childFlags |= FLAG_INIT_WORLD_TO_LOCAL;

                    if (childFlags & FLAG_LOCK_TRANSFORMS)
                        childWorldToLocal.unlock();
                }

                _flags[childId] = childFlags;

                child.localToWorldTransformChanged.execute(child, childLocalToWorld);

                localToWorld = childLocalToWorld;
            }
        }

        private function updateAncestorsAndSelfLocalToWorld(nodeId : uint) : void
        {
            var dirtyRoot   : int           = -1;
            var tmpNodeId   : int           = nodeId;
            var path        : Vector.<uint> = new <uint>[];
            var numNodes    : uint          = 0;

            while (tmpNodeId >= 0)
            {
                if ((_transforms[tmpNodeId] as Matrix4x4)._hasChanged
                    || !(_flags[nodeId] & FLAG_INIT_LOCAL_TO_WORLD))
                    dirtyRoot = tmpNodeId;

                path[numNodes] = tmpNodeId;
                ++numNodes;

                tmpNodeId = _parentId[tmpNodeId];
            }

            if (dirtyRoot >= 0)
                updateLocalToWorldPath(path);
//                updateLocalToWorld(dirtyRoot, nodeId);
        }

        private function targetAddedHandler(ctrl	: TransformController,
                                            target	: ISceneNode) : void
        {
            if (_target)
                throw new Error('The TransformController cannot have more than one target.');

            _target = target;
            _invalidList = true;

            if (target is Scene)
            {
                (target as Scene).renderingBegin.add(renderingBeginHandler);
                return;
            }

            if (target is Group)
            {
                var targetGroup : Group = target as Group;

                targetGroup.descendantAdded.add(descendantAddedHandler);
                targetGroup.descendantRemoved.add(descendantRemovedHandler);
            }

            target.added.add(addedHandler);
        }

        private function targetRemovedHandler(ctrl		: TransformController,
                                              target	: ISceneNode) : void
        {
            target.added.remove(addedHandler);

            if (target is Group)
            {
                var targetGroup : Group = target as Group;

                targetGroup.descendantAdded.remove(descendantAddedHandler);
                targetGroup.descendantRemoved.remove(descendantRemovedHandler);
            }

            _invalidList = false;
            _target = null;

            _nodeToId = null;
            _transforms = null;
            _flags = null;
            _localToWorldTransforms = null;
            _worldToLocalTransforms = null;
            _numChildren = null;
            _idToNode = null;
            _parentId = null;
        }

        private function addedHandler(target : ISceneNode, ancestor : Group) : void
        {
            // the controller will remove itself from the node when it's not its own root anymore
            // but it will watch for the 'removed' signal to add itself back if the node becomes
            // its own root again
            _target.removed.add(removedHandler);
            _target.removeController(this);
        }

        private function removedHandler(target : ISceneNode, ancestor : Group) : void
        {
            if (target.root == target)
            {
                target.removed.remove(removedHandler);
                target.addController(this);
            }
        }

        private function descendantAddedHandler(root		: Group,
                                                descendant	: ISceneNode) : void
        {
            _invalidList = true;
        }

        private function descendantRemovedHandler(root			: Group,
                                                  descendant	: ISceneNode) : void
        {
            _invalidList = true;
        }

        private function getNodeId(node : ISceneNode) : uint
        {
            if (_invalidList || !(node in _nodeToId))
                updateTransformsList();

            return _nodeToId[node];
        }

        private function updateTransformsList() : void
        {
            var root    	                : ISceneNode 			= _target.root;
            var nodes   	                : Vector.<ISceneNode> 	= new <ISceneNode>[root];
            var nodeId  	                : uint 					= 0;
            var oldNodeToId                 : Dictionary            = _nodeToId;
            var oldInitialized              : Vector.<uint>         = _flags;
            var oldLocalToWorldTransforms   : Vector.<Matrix4x4>    = _localToWorldTransforms;
            var oldWorldToLocalTransform    : Vector.<Matrix4x4>    = _worldToLocalTransforms;

            _nodeToId = new Dictionary(true);
            _transforms = new <Matrix4x4>[];
            _flags = new <uint>[];
            _localToWorldTransforms = new <Matrix4x4>[];
            _worldToLocalTransforms = new <Matrix4x4>[];
            _numChildren = new <uint>[];
            _firstChildId = new <uint>[];
            _idToNode = new <ISceneNode>[];
            _parentId = new <int>[-1];

            while (nodes.length)
            {
                var node 	: ISceneNode 	= nodes.shift();
                var group   : Group 		= node as Group;

                _nodeToId[node] = nodeId;
                _idToNode[nodeId] = node;
                _transforms[nodeId] = node.transform;

                if (oldNodeToId && node in oldNodeToId)
                {
                    var oldNodeId   : uint  = oldNodeToId[node];

                    _localToWorldTransforms[nodeId] = oldLocalToWorldTransforms[oldNodeId];
                    _worldToLocalTransforms[nodeId] = oldWorldToLocalTransform[oldNodeId];
                    _flags[nodeId] = oldInitialized[oldNodeId];
                }
                else
                {
                    _localToWorldTransforms[nodeId] = new Matrix4x4().lock();
                    _worldToLocalTransforms[nodeId] = null;
                    _flags[nodeId] = FLAG_NONE;
                }

                if (group)
                {
                    var numChildren 	: uint = group.numChildren;
                    var firstChildId 	: uint = nodeId + nodes.length + 1;

                    _numChildren[nodeId] = numChildren;
                    _firstChildId[nodeId] = firstChildId;
                    for (var childId : uint = 0; childId < numChildren; ++childId)
                    {
                        _parentId[uint(firstChildId + childId)] = nodeId;
                        nodes.push(group.getChildAt(childId));
                    }
                }
                else
                {
                    _numChildren[nodeId] = 0;
                    _firstChildId[nodeId] = 0;
                }

                ++nodeId;
            }

            _worldToLocalTransforms.length = _localToWorldTransforms.length;
            _invalidList = false;
        }

        public function getLocalToWorldTransform(node 			: ISceneNode,
                                                 forceUpdate 	: Boolean = false) : Matrix4x4
        {
            var nodeId : uint = getNodeId(node);

            if (forceUpdate)
                updateAncestorsAndSelfLocalToWorld(nodeId);

            return _localToWorldTransforms[nodeId];
        }

        public function getWorldToLocalTransform(node           : ISceneNode,
                                                 forceUpdate    : Boolean   = false) : Matrix4x4
        {
            var nodeId                  : uint      = getNodeId(node);
            var worldToLocalTransform   : Matrix4x4 = _worldToLocalTransforms[nodeId];

            if (!worldToLocalTransform)
            {
                _worldToLocalTransforms[nodeId] = worldToLocalTransform = new Matrix4x4();
                if (!forceUpdate)
                {
                    worldToLocalTransform.copyFrom(_localToWorldTransforms[nodeId]).invert();
                    _flags[nodeId] |= FLAG_INIT_WORLD_TO_LOCAL;
                }
            }

            if (forceUpdate)
                updateAncestorsAndSelfLocalToWorld(nodeId);

            var flags : uint = _flags[nodeId];

            if (!(flags & FLAG_INIT_WORLD_TO_LOCAL))
            {
                _flags[nodeId] |= FLAG_INIT_WORLD_TO_LOCAL;

                if (flags & FLAG_LOCK_TRANSFORMS)
                    worldToLocalTransform.lock();

                worldToLocalTransform
                    .copyFrom(_localToWorldTransforms[nodeId])
                    .invert();

                if (flags & FLAG_LOCK_TRANSFORMS)
                    worldToLocalTransform.unlock();
            }

            return worldToLocalTransform;
        }

        public function getSharedLocalToWorldTransformReference(node    : ISceneNode) : Matrix4x4
        {
            return _localToWorldTransforms[getNodeId(node)];
        }

        public function setSharedLocalToWorldTransformReference(node      : ISceneNode,
                                                                matrix    : Matrix4x4) : void
        {
            var nodeId : uint = getNodeId(node);

            if (_flags[nodeId] & FLAG_INIT_LOCAL_TO_WORLD)
                matrix.copyFrom(_localToWorldTransforms[nodeId]);
            _localToWorldTransforms[nodeId] = matrix;
        }

        public function getSharedWorldToLocalTransformReference(node : ISceneNode) : Matrix4x4
        {
            var nodeId : uint = getNodeId(node);

            return _worldToLocalTransforms[nodeId]
                || (_worldToLocalTransforms[nodeId] = new Matrix4x4());
        }

        public function setSharedWorldToLocalTransformReference(node      : ISceneNode,
                                                                matrix    : Matrix4x4) : void
        {
            var nodeId : uint = getNodeId(node);

            if (_flags[nodeId] & FLAG_INIT_WORLD_TO_LOCAL)
                matrix.copyFrom(_worldToLocalTransforms[nodeId]);
            _worldToLocalTransforms[nodeId] = matrix;
        }

        public function synchronizeTransforms(node : ISceneNode, enabled : Boolean) : void
        {
            var nodeId : uint = getNodeId(node);

            _flags[nodeId] = enabled
                ? _flags[nodeId] | FLAG_SYNCHRONIZE_TRANSFORMS
                : _flags[nodeId] & ~FLAG_SYNCHRONIZE_TRANSFORMS;
        }

        public function lockTransformsBeforeUpdate(node : ISceneNode, enabled : Boolean) : void
        {
            var nodeId : uint = getNodeId(node);

            _flags[nodeId] = enabled
                ? _flags[nodeId] | FLAG_LOCK_TRANSFORMS
                : _flags[nodeId] & ~FLAG_LOCK_TRANSFORMS;
        }
    }
}

add TransformController.getSharedWorldToLocalTransformReference() and TransformController.getSharedLocalToWorldTransformReference() methods