/*
#
# Copyright (c) 2008 rPath, Inc.
#
# This program is distributed under the terms of the MIT License as found
# in a file called LICENSE. If it is not present, the license
# is always available at http://www.opensource.org/licenses/mit-license.php.
#
# This program is distributed in the hope that it will be useful, but
# without any waranty; without even the implied warranty of merchantability
# or fitness for a particular purpose. See the MIT License for full details.
*/

package com.rpath.xobj
{
    import mx.collections.ArrayCollection;
    import mx.utils.DescribeTypeCache;
    import mx.utils.ObjectProxy;

    import flash.utils.getQualifiedClassName;
    import flash.utils.getDefinitionByName;
    import flash.xml.XMLNode;

    import mx.utils.object_proxy;
    use namespace object_proxy;

    public class XObjUtils
    {

        public static const DEFAULT_NAMESPACE_PREFIX:String = "_default_";

        /**
         * @private
         */
        private static var CLASS_INFO_CACHE:Object = {};

        /**
         * Returns the local name of an XMLNode.
         *
         * @return The local name of an XMLNode.
         */
        public static function getLocalName(xmlNode:XMLNode):String
        {
            return getNCName(xmlNode.nodeName);
        }

        public static function getNCName(name:String):String
        {
            var myPrefixIndex:int = name.indexOf(":");
            if (myPrefixIndex != -1)
            {
                name = name.substring(myPrefixIndex+1);
            }
            return name;
        }

        public static function encodeElementTag(qname:XObjQName, node:XMLNode):String
        {
            var elementTag:String = XObjUtils.getNCName(qname.localName);

            var prefix:String = node.getPrefixForNamespace(qname.uri);

            if (prefix)
                elementTag =  prefix + ":" + elementTag;

            return elementTag;
        }

        /**
         * Return a Class instance based on a string class name
         * @private
          */
        public static function getClassByName(className:String):Class
        {
            var classReference:Class = null;

            try
            {
                 classReference = getDefinitionByName(className) as Class;
            }
            catch (e:ReferenceError)
            {
                trace("Request for unknown class "+className);
            }

            return classReference;
         }

        private static var typePropertyCache:Object = {};

        public static function isTypeArray(type:Class):Boolean
        {
            if (type == null)
                return false;

            var foo:* = new type();
            return (foo is Array);
        }

        public static function isTypeArrayCollection(type:Class):Boolean
        {
            if (type == null)
                return false;

            var foo:* = new type();
            return (foo is ArrayCollection);
        }

        public static function typeInfoForProperty(object:*, className:String, propName:String):Object
        {
            var isArray:Boolean = false;
            var result:Object = {typeName: null, isArray: false, isArrayCollection: false};

            if (className == "Object" || className == "mx.utils::ObjectProxy")
                return result;

            var propertyCacheKey:String = className + "." + propName;
            var arrayElementType:String;

            result = typePropertyCache[propertyCacheKey];

            if (result == null)
            {
                result = {typeName: null, isArray: false, isArrayCollection: false};

                // go look it up (expensive)
                // very important to use the instance object here, not the classname
                // using the classname results in the typeInfo cache
                // returning class not instance info later on! Bad cache!
                var typeDesc:* = DescribeTypeCache.describeType(object);
                var typeInfo:XML = typeDesc.typeDescription;

                result.typeName = typeInfo..accessor.(@name == propName).@type.toString().replace( /::/, "." );
                if (result.typeName == null || result.typeName == "")
                {
                    result.typeName = typeInfo..variable.(@name == propName).@type.toString().replace( /::/, "." );
                    arrayElementType = typeInfo..variable.(@name == propName).metadata.(@name == 'ArrayElementType').arg.@value.toString().replace( /::/, "." );
                }
                else
                    arrayElementType = typeInfo..accessor.(@name == propName).metadata.(@name == 'ArrayElementType').arg.@value.toString().replace( /::/, "." );

                if (result.typeName == "Array")
                {
                    result.isArray = true;
                    result.typeName = null; // assume generic object unless told otherwise
                }
                else if (result.typeName == "ArrayCollection")
                {
                    result.isArrayCollection = true;
                    result.typeName = null; // assume generic object unless told otherwise
                }

                if (arrayElementType != "")
                {
                    // use type specified
                    result.typeName = arrayElementType;
                }

                if (result.typeName == "Object"
                    || result.typeName == "mx.utils::ObjectProxy"
                    || result.typeName == "Undefined"
                    || result.typeName == "*"
                    || result.typeName == "")
                {
                    result.typeName = null;
                }

                // cache the result for next time
                typePropertyCache[propertyCacheKey] = result;
            }

            return result;
        }

        /**
         * Use our own version of getClassInfo to support various metadata
         * tags we use.  See ObjectUtil.getClassInfo for more info about
         * the baisc functionality of this method.
         */
        public static function getClassInfo(obj:Object,
                                            excludes:Array = null,
                                            options:Object = null):Object
        {
            var n:int;
            var i:int;

            if (obj is ObjectProxy)
                obj = ObjectProxy(obj).object_proxy::object;

            if (options == null)
                options = { includeReadOnly: true, uris: null, includeTransient: true };

            var result:Object;
            var propertyNames:Array = [];
            var cacheKey:String;

            var className:String;
            var classAlias:String;
            var properties:XMLList;
            var prop:XML;
            var dynamic:Boolean = false;
            var metadataInfo:Object;

            if (typeof(obj) == "xml")
            {
                className = "XML";
                properties = obj.text();
                if (properties.length())
                    propertyNames.push("*");
                properties = obj.attributes();
            }
            else
            {
                var classInfo:XML = DescribeTypeCache.describeType(obj).typeDescription;
                className = classInfo.@name.toString();
                classAlias = classInfo.@alias.toString();
                dynamic = (classInfo.@isDynamic.toString() == "true");

                if (options.includeReadOnly)
                    properties = classInfo..accessor.(@access != "writeonly") + classInfo..variable;
                else
                    properties = classInfo..accessor.(@access == "readwrite") + classInfo..variable;

                var numericIndex:Boolean = false;
            }

            // If type is not dynamic, check our cache for class info...
            if (!dynamic)
            {
                cacheKey = getCacheKey(obj, excludes, options);
                result = CLASS_INFO_CACHE[cacheKey];
                if (result != null)
                    return result;
            }

            result = {};
            result["name"] = className;
            result["alias"] = classAlias;
            result["properties"] = propertyNames;
            result["dynamic"] = dynamic;
            result["metadata"] = metadataInfo = recordMetadata(properties);

            var excludeObject:Object = {};
            if (excludes)
            {
                n = excludes.length;
                for (i = 0; i < n; i++)
                {
                    excludeObject[excludes[i]] = 1;
                }
            }

            //TODO this seems slightly fragile, why not use the 'is' operator?
            var isArray:Boolean = (className == "Array");
            var isDict:Boolean  = (className == "flash.utils::Dictionary");

            if (isDict)
            {
                // dictionaries can have multiple keys of the same type,
                // (they can index by reference rather than QName, String, or number),
                // which cannot be looked up by QName, so use references to the actual key
                for (var key:* in obj)
                {
                    propertyNames.push(key);
                }
            }
            else if (dynamic)
            {
                for (var p:String in obj)
                {
                    if (excludeObject[p] != 1)
                    {
                        if (isArray)
                        {
                             var pi:Number = parseInt(p);
                             if (isNaN(pi))
                                propertyNames.push(new QName("", p));
                             else
                                propertyNames.push(pi);
                        }
                        else
                        {
                            propertyNames.push(new QName("", p));
                        }
                    }
                }
                numericIndex = isArray && !isNaN(Number(p));
            }

            if (isArray || isDict || className == "Object")
            {
                // Do nothing since we've already got the dynamic members
            }
            else if (className == "XML")
            {
                n = properties.length();
                for (i = 0; i < n; i++)
                {
                    p = properties[i].name();
                    if (excludeObject[p] != 1)
                        propertyNames.push(new QName("", "@" + p));
                }
            }
            else
            {
                n = properties.length();
                var uris:Array = options.uris;
                var uri:String;
                var qName:QName;
                for (i = 0; i < n; i++)
                {
                    prop = properties[i];
                    p = prop.@name.toString();
                    uri = prop.@uri.toString();

                    if (excludeObject[p] == 1)
                        continue;

                    if (!options.includeTransient && internalHasMetadata(metadataInfo, p, "Transient"))
                        continue;

                    if (internalHasMetadata(metadataInfo, p, "xobjTransient"))
                        continue;

                    if (uris != null)
                    {
                        if (uris.length == 1 && uris[0] == "*")
                        {
                            qName = new QName(uri, p);
                            try
                            {
                                obj[qName]; // access the property to ensure it is supported
                                propertyNames.push();
                            }
                            catch(e:Error)
                            {
                                // don't keep property name
                            }
                        }
                        else
                        {
                            for (var j:int = 0; j < uris.length; j++)
                            {
                                uri = uris[j];
                                if (prop.@uri.toString() == uri)
                                {
                                    qName = new QName(uri, p);
                                    try
                                    {
                                        obj[qName];
                                        propertyNames.push(qName);
                                    }
                                    catch(e:Error)
                                    {
                                        // don't keep property name
                                    }
                                }
                            }
                        }
                    }
                    else if (uri.length == 0)
                    {
                        qName = new QName(uri, p);
                        try
                        {
                            obj[qName];
                            propertyNames.push(qName);
                        }
                        catch(e:Error)
                        {
                            // don't keep property name
                        }
                    }
                }
            }

            propertyNames.sort(Array.CASEINSENSITIVE |
                               (numericIndex ? Array.NUMERIC : 0));

            // dictionary keys can be indexed by an object reference
            // there's a possibility that two keys will have the same toString()
            // so we don't want to remove dupes
            if (!isDict)
            {
                // for Arrays, etc., on the other hand...
                // remove any duplicates, i.e. any items that can't be distingushed by toString()
                for (i = 0; i < propertyNames.length - 1; i++)
                {
                    // the list is sorted so any duplicates should be adjacent
                    // two properties are only equal if both the uri and local name are identical
                    if (propertyNames[i].toString() == propertyNames[i + 1].toString())
                    {
                        propertyNames.splice(i, 1);
                        i--; // back up
                    }
                }
            }

            // For normal, non-dynamic classes we cache the class info
            if (!dynamic)
            {
                cacheKey = getCacheKey(obj, excludes, options);
                CLASS_INFO_CACHE[cacheKey] = result;
            }

            return result;
        }

        /**
         *  @private
         */
        private static function internalHasMetadata(metadataInfo:Object, propName:String, metadataName:String):Boolean
        {
            if (metadataInfo != null)
            {
                var metadata:Object = metadataInfo[propName];
                if (metadata != null)
                {
                    if (metadata[metadataName] != null)
                        return true;
                }
            }
            return false;
        }

        /**
         *  @private
         */
        private static function recordMetadata(properties:XMLList):Object
        {
            var result:Object = null;

            try
            {
                for each (var prop:XML in properties)
                {
                    var propName:String = prop.attribute("name").toString();
                    var metadataList:XMLList = prop.metadata;

                    if (metadataList.length() > 0)
                    {
                        if (result == null)
                            result = {};

                        var metadata:Object = {};
                        result[propName] = metadata;

                        for each (var md:XML in metadataList)
                        {
                            var mdName:String = md.attribute("name").toString();

                            var argsList:XMLList = md.arg;
                            var value:Object = {};

                            for each (var arg:XML in argsList)
                            {
                                var argKey:String = arg.attribute("key").toString();
                                if (argKey != null)
                                {
                                    var argValue:String = arg.attribute("value").toString();
                                    value[argKey] = argValue;
                                }
                            }

                            var existing:Object = metadata[mdName];
                            if (existing != null)
                            {
                                var existingArray:Array;
                                if (existing is Array)
                                    existingArray = existing as Array;
                                else
                                    existingArray = [];
                                existingArray.push(value);
                                existing = existingArray;
                            }
                            else
                            {
                                existing = value;
                            }
                            metadata[mdName] = existing;
                        }
                    }
                }
            }
            catch(e:Error)
            {
            }

            return result;
        }

        /**
         *  @private
         */
        private static function getCacheKey(o:Object, excludes:Array = null, options:Object = null):String
        {
            var key:String = getQualifiedClassName(o);

            if (excludes != null)
            {
                for (var i:uint = 0; i < excludes.length; i++)
                {
                    var excl:String = excludes[i] as String;
                    if (excl != null)
                        key += excl;
                }
            }

            if (options != null)
            {
                for (var flag:String in options)
                {
                    key += flag;
                    var value:String = options[flag] as String;
                    if (value != null)
                        key += value;
                }
            }
            return key;
        }
    }
}

/*
#
# Copyright (c) 2008 rPath, Inc.
#
# This program is distributed under the terms of the MIT License as found
# in a file called LICENSE. If it is not present, the license
# is always available at http://www.opensource.org/licenses/mit-license.php.
#
# This program is distributed in the hope that it will be useful, but
# without any waranty; without even the implied warranty of merchantability
# or fitness for a particular purpose. See the MIT License for full details.
*/

package com.rpath.xobj
{
    import mx.collections.ArrayCollection;
    import mx.utils.DescribeTypeCache;
    import mx.utils.ObjectProxy;

    import flash.utils.getQualifiedClassName;
    import flash.utils.getDefinitionByName;
    import flash.xml.XMLNode;

    import mx.utils.object_proxy;
    use namespace object_proxy;

    public class XObjUtils
    {

        public static const DEFAULT_NAMESPACE_PREFIX:String = "_default_";

        /**
         * @private
         */
        private static var CLASS_INFO_CACHE:Object = {};

        /**
         * Returns the local name of an XMLNode.
         *
         * @return The local name of an XMLNode.
         */
        public static function getLocalName(xmlNode:XMLNode):String
        {
            return getNCName(xmlNode.nodeName);
        }

        public static function getNCName(name:String):String
        {
            var myPrefixIndex:int = name.indexOf(":");
            if (myPrefixIndex != -1)
            {
                name = name.substring(myPrefixIndex+1);
            }
            return name;
        }

        public static function encodeElementTag(qname:XObjQName, node:XMLNode):String
        {
            var elementTag:String = XObjUtils.getNCName(qname.localName);

            var prefix:String = node.getPrefixForNamespace(qname.uri);

            if (prefix)
                elementTag =  prefix + ":" + elementTag;

            return elementTag;
        }

        /**
         * Return a Class instance based on a string class name
         * @private
          */
        public static function getClassByName(className:String):Class
        {
            var classReference:Class = null;

            try
            {
                 classReference = getDefinitionByName(className) as Class;
            }
            catch (e:ReferenceError)
            {
                trace("Request for unknown class "+className);
            }

            return classReference;
         }

        private static var typePropertyCache:Object = {};

        public static function isTypeArray(type:Class):Boolean
        {
            if (type == null)
                return false;

            var foo:* = new type();
            return (foo is Array);
        }

        public static function isTypeArrayCollection(type:Class):Boolean
        {
            if (type == null)
                return false;

            var foo:* = new type();
            return (foo is ArrayCollection);
        }

        public static function typeInfoForProperty(object:*, className:String, propName:String):Object
        {
            var isArray:Boolean = false;
            var result:Object = {typeName: null, isArray: false, isArrayCollection: false};

            if (propName == "label")
                trace("stop");

            if (className == "Object" || className == "mx.utils::ObjectProxy")
                return result;

            var propertyCacheKey:String = className + "." + propName;
            var arrayElementType:String;

            result = typePropertyCache[propertyCacheKey];

            if (result == null)
            {
                result = {typeName: null, isArray: false, isArrayCollection: false};

                // go look it up (expensive)
                // very important to use the instance object here, not the classname
                // using the classname results in the typeInfo cache
                // returning class not instance info later on! Bad cache!
                var typeDesc:* = DescribeTypeCache.describeType(object);
                var typeInfo:XML = typeDesc.typeDescription;

                result.typeName = typeInfo..accessor.(@name == propName).@type.toString().replace( /::/, "." );
                if (result.typeName == null || result.typeName == "")
                {
                    result.typeName = typeInfo..variable.(@name == propName).@type.toString().replace( /::/, "." );
                    arrayElementType = typeInfo..variable.(@name == propName).metadata.(@name == 'ArrayElementType').arg.@value.toString().replace( /::/, "." );
                }
                else
                    arrayElementType = typeInfo..accessor.(@name == propName).metadata.(@name == 'ArrayElementType').arg.@value.toString().replace( /::/, "." );

                if (result.typeName == "Array")
                {
                    result.isArray = true;
                    result.typeName = null; // assume generic object unless told otherwise
                }
                else if (result.typeName == "mx.collections.ArrayCollection")
                {
                    result.isArrayCollection = true;
                    result.typeName = null; // assume generic object unless told otherwise
                }

                if (arrayElementType != "")
                {
                    // use type specified
                    result.typeName = arrayElementType;
                }

                if (result.typeName == "Object"
                    || result.typeName == "mx.utils::ObjectProxy"
                    || result.typeName == "Undefined"
                    || result.typeName == "*"
                    || result.typeName == "")
                {
                    result.typeName = null;
                }

                // cache the result for next time
                typePropertyCache[propertyCacheKey] = result;
            }

            return result;
        }

        /**
         * Use our own version of getClassInfo to support various metadata
         * tags we use.  See ObjectUtil.getClassInfo for more info about
         * the baisc functionality of this method.
         */
        public static function getClassInfo(obj:Object,
                                            excludes:Array = null,
                                            options:Object = null):Object
        {
            var n:int;
            var i:int;

            if (obj is ObjectProxy)
                obj = ObjectProxy(obj).object_proxy::object;

            if (options == null)
                options = { includeReadOnly: true, uris: null, includeTransient: true };

            var result:Object;
            var propertyNames:Array = [];
            var cacheKey:String;

            var className:String;
            var classAlias:String;
            var properties:XMLList;
            var prop:XML;
            var dynamic:Boolean = false;
            var metadataInfo:Object;

            if (typeof(obj) == "xml")
            {
                className = "XML";
                properties = obj.text();
                if (properties.length())
                    propertyNames.push("*");
                properties = obj.attributes();
            }
            else
            {
                var classInfo:XML = DescribeTypeCache.describeType(obj).typeDescription;
                className = classInfo.@name.toString();
                classAlias = classInfo.@alias.toString();
                dynamic = (classInfo.@isDynamic.toString() == "true");

                if (options.includeReadOnly)
                    properties = classInfo..accessor.(@access != "writeonly") + classInfo..variable;
                else
                    properties = classInfo..accessor.(@access == "readwrite") + classInfo..variable;

                var numericIndex:Boolean = false;
            }

            // If type is not dynamic, check our cache for class info...
            if (!dynamic)
            {
                cacheKey = getCacheKey(obj, excludes, options);
                result = CLASS_INFO_CACHE[cacheKey];
                if (result != null)
                    return result;
            }

            result = {};
            result["name"] = className;
            result["alias"] = classAlias;
            result["properties"] = propertyNames;
            result["dynamic"] = dynamic;
            result["metadata"] = metadataInfo = recordMetadata(properties);

            var excludeObject:Object = {};
            if (excludes)
            {
                n = excludes.length;
                for (i = 0; i < n; i++)
                {
                    excludeObject[excludes[i]] = 1;
                }
            }

            //TODO this seems slightly fragile, why not use the 'is' operator?
            var isArray:Boolean = (className == "Array");
            var isDict:Boolean  = (className == "flash.utils::Dictionary");

            if (isDict)
            {
                // dictionaries can have multiple keys of the same type,
                // (they can index by reference rather than QName, String, or number),
                // which cannot be looked up by QName, so use references to the actual key
                for (var key:* in obj)
                {
                    propertyNames.push(key);
                }
            }
            else if (dynamic)
            {
                for (var p:String in obj)
                {
                    if (excludeObject[p] != 1)
                    {
                        if (isArray)
                        {
                             var pi:Number = parseInt(p);
                             if (isNaN(pi))
                                propertyNames.push(new QName("", p));
                             else
                                propertyNames.push(pi);
                        }
                        else
                        {
                            propertyNames.push(new QName("", p));
                        }
                    }
                }
                numericIndex = isArray && !isNaN(Number(p));
            }

            if (isArray || isDict || className == "Object")
            {
                // Do nothing since we've already got the dynamic members
            }
            else if (className == "XML")
            {
                n = properties.length();
                for (i = 0; i < n; i++)
                {
                    p = properties[i].name();
                    if (excludeObject[p] != 1)
                        propertyNames.push(new QName("", "@" + p));
                }
            }
            else
            {
                n = properties.length();
                var uris:Array = options.uris;
                var uri:String;
                var qName:QName;
                for (i = 0; i < n; i++)
                {
                    prop = properties[i];
                    p = prop.@name.toString();
                    uri = prop.@uri.toString();

                    if (excludeObject[p] == 1)
                        continue;

                    if (!options.includeTransient && internalHasMetadata(metadataInfo, p, "Transient"))
                        continue;

                    if (internalHasMetadata(metadataInfo, p, "xobjTransient"))
                        continue;

                    if (uris != null)
                    {
                        if (uris.length == 1 && uris[0] == "*")
                        {
                            qName = new QName(uri, p);
                            try
                            {
                                obj[qName]; // access the property to ensure it is supported
                                propertyNames.push();
                            }
                            catch(e:Error)
                            {
                                // don't keep property name
                            }
                        }
                        else
                        {
                            for (var j:int = 0; j < uris.length; j++)
                            {
                                uri = uris[j];
                                if (prop.@uri.toString() == uri)
                                {
                                    qName = new QName(uri, p);
                                    try
                                    {
                                        obj[qName];
                                        propertyNames.push(qName);
                                    }
                                    catch(e:Error)
                                    {
                                        // don't keep property name
                                    }
                                }
                            }
                        }
                    }
                    else if (uri.length == 0)
                    {
                        qName = new QName(uri, p);
                        try
                        {
                            obj[qName];
                            propertyNames.push(qName);
                        }
                        catch(e:Error)
                        {
                            // don't keep property name
                        }
                    }
                }
            }

            propertyNames.sort(Array.CASEINSENSITIVE |
                               (numericIndex ? Array.NUMERIC : 0));

            // dictionary keys can be indexed by an object reference
            // there's a possibility that two keys will have the same toString()
            // so we don't want to remove dupes
            if (!isDict)
            {
                // for Arrays, etc., on the other hand...
                // remove any duplicates, i.e. any items that can't be distingushed by toString()
                for (i = 0; i < propertyNames.length - 1; i++)
                {
                    // the list is sorted so any duplicates should be adjacent
                    // two properties are only equal if both the uri and local name are identical
                    if (propertyNames[i].toString() == propertyNames[i + 1].toString())
                    {
                        propertyNames.splice(i, 1);
                        i--; // back up
                    }
                }
            }

            // For normal, non-dynamic classes we cache the class info
            if (!dynamic)
            {
                cacheKey = getCacheKey(obj, excludes, options);
                CLASS_INFO_CACHE[cacheKey] = result;
            }

            return result;
        }

        /**
         *  @private
         */
        private static function internalHasMetadata(metadataInfo:Object, propName:String, metadataName:String):Boolean
        {
            if (metadataInfo != null)
            {
                var metadata:Object = metadataInfo[propName];
                if (metadata != null)
                {
                    if (metadata[metadataName] != null)
                        return true;
                }
            }
            return false;
        }

        /**
         *  @private
         */
        private static function recordMetadata(properties:XMLList):Object
        {
            var result:Object = null;

            try
            {
                for each (var prop:XML in properties)
                {
                    var propName:String = prop.attribute("name").toString();
                    var metadataList:XMLList = prop.metadata;

                    if (metadataList.length() > 0)
                    {
                        if (result == null)
                            result = {};

                        var metadata:Object = {};
                        result[propName] = metadata;

                        for each (var md:XML in metadataList)
                        {
                            var mdName:String = md.attribute("name").toString();

                            var argsList:XMLList = md.arg;
                            var value:Object = {};

                            for each (var arg:XML in argsList)
                            {
                                var argKey:String = arg.attribute("key").toString();
                                if (argKey != null)
                                {
                                    var argValue:String = arg.attribute("value").toString();
                                    value[argKey] = argValue;
                                }
                            }

                            var existing:Object = metadata[mdName];
                            if (existing != null)
                            {
                                var existingArray:Array;
                                if (existing is Array)
                                    existingArray = existing as Array;
                                else
                                    existingArray = [];
                                existingArray.push(value);
                                existing = existingArray;
                            }
                            else
                            {
                                existing = value;
                            }
                            metadata[mdName] = existing;
                        }
                    }
                }
            }
            catch(e:Error)
            {
            }

            return result;
        }

        /**
         *  @private
         */
        private static function getCacheKey(o:Object, excludes:Array = null, options:Object = null):String
        {
            var key:String = getQualifiedClassName(o);

            if (excludes != null)
            {
                for (var i:uint = 0; i < excludes.length; i++)
                {
                    var excl:String = excludes[i] as String;
                    if (excl != null)
                        key += excl;
                }
            }

            if (options != null)
            {
                for (var flag:String in options)
                {
                    key += flag;
                    var value:String = options[flag] as String;
                    if (value != null)
                        key += value;
                }
            }
            return key;
        }
    }
}

Fix for handling ArrayCollections declared directly


package aerys.minko.scene.controller
{
	/**
	 * The SkeletonController aggregates AnimationController objects
	 * and control them in order to start/stop playing animations on
	 * multiple scene nodes at the same time and control a whole
	 * skeleton.
	 *
	 * @author Jean-Marc Le Roux
	 *
	 */
	public final class SkeletonController
	{
		private var _animations	: Vector.<AnimationController>	= null;

		public function SkeletonController(animations : Vector.<AnimationController>)
		{
			_animations = animations.slice();
		}

		public function get animations() : Vector.<AnimationController>
		{
			return _animations;
		}

		public function goto(time : Object) : SkeletonController
		{
			var numAnimations	: uint	= _animations.length;

			for (var animId : uint = 0; animId < numAnimations; ++animId)
				(_animations[animId] as AnimationController).goto(time);

			return this;
		}

		public function stop() : SkeletonController
		{
			var numAnimations	: uint	= _animations.length;

			for (var animId : uint = 0; animId < numAnimations; ++animId)
				(_animations[animId] as AnimationController).stop();

			return this;
		}

		public function play() : SkeletonController
		{
			var numAnimations	: uint	= _animations.length;

			for (var animId : uint = 0; animId < numAnimations; ++animId)
				(_animations[animId] as AnimationController).play();

			return this;
		}

		public function setPlaybackWindow(beginTime	: Object = null,
										  endTime	: Object = null) : SkeletonController
		{
			var numAnimations	: uint	= _animations.length;

			for (var animId : uint = 0; animId < numAnimations; ++animId)
				(_animations[animId] as AnimationController).setPlaybackWindow(beginTime, endTime);

			return this;
		}

		public function resetPlaybackWindow() : SkeletonController
		{
			var numAnimations	: uint	= _animations.length;

			for (var animId : uint = 0; animId < numAnimations; ++animId)
				(_animations[animId] as AnimationController).resetPlaybackWindow();

			return this;
		}
	}
}

package aerys.minko.scene.controller
{
	/**
	 * The SkeletonController aggregates AnimationController objects
	 * and control them in order to start/stop playing animations on
	 * multiple scene nodes at the same time and control a whole
	 * skeleton.
	 *
	 * @author Jean-Marc Le Roux
	 *
	 */
	public final class SkeletonController
	{
		private var _animations	: Vector.<AnimationController>	= null;

		public function SkeletonController(animations : Vector.<AnimationController>)
		{
			_animations = animations.slice();
		}

		public function get animations() : Vector.<AnimationController>
		{
			return _animations;
		}

		public function seek(time : Object) : SkeletonController
		{
			var numAnimations	: uint	= _animations.length;

			for (var animId : uint = 0; animId < numAnimations; ++animId)
				(_animations[animId] as AnimationController).seek(time);

			return this;
		}

		public function stop() : SkeletonController
		{
			var numAnimations	: uint	= _animations.length;

			for (var animId : uint = 0; animId < numAnimations; ++animId)
				(_animations[animId] as AnimationController).stop();

			return this;
		}

		public function play() : SkeletonController
		{
			var numAnimations	: uint	= _animations.length;

			for (var animId : uint = 0; animId < numAnimations; ++animId)
				(_animations[animId] as AnimationController).play();

			return this;
		}

		public function setPlaybackWindow(beginTime	: Object = null,
										  endTime	: Object = null) : SkeletonController
		{
			var numAnimations	: uint	= _animations.length;

			for (var animId : uint = 0; animId < numAnimations; ++animId)
				(_animations[animId] as AnimationController).setPlaybackWindow(beginTime, endTime);

			return this;
		}

		public function resetPlaybackWindow() : SkeletonController
		{
			var numAnimations	: uint	= _animations.length;

			for (var animId : uint = 0; animId < numAnimations; ++animId)
				(_animations[animId] as AnimationController).resetPlaybackWindow();

			return this;
		}
	}
}

fix broken reference to AnimationController.goto()


class LightsOutScript {
    Hub @hub;
    Entity @self;
    Entity @board;
    array<Entity@> buttons(25);
    array<bool> buttonStates(25);
    int numPressedButtons = 0;
    bool gameWon = false;

    LightsOutScript(Entity @entity){
        @hub = Managers();
        @self = @entity;
        @board = GetEntityByGUID(1511530025);

        for (int row = 0; row < 5; ++row) {
            for (int column = 0; column < 5; ++column) {
                Entity @btn = board.GetChild("btn-" + row + "-" + column);
                @buttons[row * 5 + column] = btn.GetChild("btn-" + row + "-" + column).GetChild("button");
                buttonStates[row * 5 + column] = false;
            }
        }

        Toggle(0);
    }

    void Toggle(int index) {
        bool pressed = !buttonStates[index];
        buttonStates[index] = pressed;

        if (pressed) {
            buttons[index].position.x = -0.06f;
            numPressedButtons += 1;
        } else {
            buttons[index].position.x = 0.0f;
            numPressedButtons -= 1;
        }
    }

    bool IsValid(int index) {
        return index >= 0 && index <= 24;
    }

    // Index goes first row 0 -> 4, second row 5 -> 9 etc.
    void ButtonPress(int index) {
        if (gameWon) {
            return;
        }

        print("Pressed button nr `" + index + "`\n");

        int left = index - 1;
        if (IsValid(left)) {
            Toggle(left);
        }

        int right = index + 1;
        if (IsValid(right)) {
            Toggle(right);
        }

        int up = index - 5;
        if (IsValid(up)) {
            Toggle(up);
        }

        int down = index + 5;
        if (IsValid(down)) {
            Toggle(down);
        }

        Toggle(index);

        if (numPressedButtons == 25) {
            gameWon = true;
            //SendMessage(somewhere);
        }
    }

    void b_0_0() {
        ButtonPress(0);
    }

    void b_0_1() {
        ButtonPress(1);
    }

    void b_0_2() {
        ButtonPress(2);
    }

    void b_0_3() {
        ButtonPress(3);
    }

    void b_0_4() {
        ButtonPress(4);
    }

    void b_1_0() {
        ButtonPress(5);
    }

    void b_1_1() {
        ButtonPress(6);
    }

    void b_1_2() {
        ButtonPress(7);
    }

    void b_1_3() {
        ButtonPress(8);
    }

    void b_1_4() {
        ButtonPress(9);
    }

    void b_2_0() {
        ButtonPress(10);
    }

    void b_2_1() {
        ButtonPress(11);
    }

    void b_2_2() {
        ButtonPress(12);
    }

    void b_2_3() {
        ButtonPress(13);
    }

    void b_2_4() {
        ButtonPress(14);
    }

    void b_3_0() {
        ButtonPress(15);
    }

    void b_3_1() {
        ButtonPress(16);
    }

    void b_3_2() {
        ButtonPress(17);
    }

    void b_3_3() {
        ButtonPress(18);
    }

    void b_3_4() {
        ButtonPress(19);
    }

    void b_4_0() {
        ButtonPress(20);
    }

    void b_4_1() {
        ButtonPress(21);
    }

    void b_4_2() {
        ButtonPress(22);
    }

    void b_4_3() {
        ButtonPress(23);
    }

    void b_4_4() {
        ButtonPress(24);
    }
}

class LightsOutScript {
    Hub @hub;
    Entity @self;
    Entity @board;
    array<Entity@> buttons(25);
    array<bool> buttonStates(25);
    int numPressedButtons = 0;
    bool gameWon = false;

    LightsOutScript(Entity @entity){
        @hub = Managers();
        @self = @entity;
        @board = GetEntityByGUID(1511530025);

        for (int row = 0; row < 5; ++row) {
            for (int column = 0; column < 5; ++column) {
                Entity @btn = board.GetChild("btn-" + row + "-" + column);
                @buttons[row * 5 + column] = btn.GetChild("btn-" + row + "-" + column).GetChild("button");
                buttonStates[row * 5 + column] = false;
            }
        }

        Toggle(0);
    }

    void Toggle(int index) {
        bool pressed = !buttonStates[index];
        buttonStates[index] = pressed;

        if (pressed) {
            buttons[index].position.x = -0.06f;
            numPressedButtons += 1;
        } else {
            buttons[index].position.x = 0.0f;
            numPressedButtons -= 1;
        }
    }

    bool IsValid(int index) {
        return index >= 0 && index <= 24;
    }

    // Index goes first row 0 -> 4, second row 5 -> 9 etc.
    void ButtonPress(int index) {
        if (gameWon) {
            return;
        }

        print("Pressed button nr `" + index + "`\n");

        int left = index - 1;
        if (IsValid(left)) {
            Toggle(left);
        }

        int right = index + 1;
        if (IsValid(right)) {
            Toggle(right);
        }

        int up = index - 5;
        if (IsValid(up)) {
            Toggle(up);
        }

        int down = index + 5;
        if (IsValid(down)) {
            Toggle(down);
        }

        Toggle(index);

        if (numPressedButtons == 25) {
            gameWon = true;
            //SendMessage(somewhere);
            print("Won the game of lights out.\n");
        }
    }

    void b_0_0() {
        ButtonPress(0);
    }

    void b_0_1() {
        ButtonPress(1);
    }

    void b_0_2() {
        ButtonPress(2);
    }

    void b_0_3() {
        ButtonPress(3);
    }

    void b_0_4() {
        ButtonPress(4);
    }

    void b_1_0() {
        ButtonPress(5);
    }

    void b_1_1() {
        ButtonPress(6);
    }

    void b_1_2() {
        ButtonPress(7);
    }

    void b_1_3() {
        ButtonPress(8);
    }

    void b_1_4() {
        ButtonPress(9);
    }

    void b_2_0() {
        ButtonPress(10);
    }

    void b_2_1() {
        ButtonPress(11);
    }

    void b_2_2() {
        ButtonPress(12);
    }

    void b_2_3() {
        ButtonPress(13);
    }

    void b_2_4() {
        ButtonPress(14);
    }

    void b_3_0() {
        ButtonPress(15);
    }

    void b_3_1() {
        ButtonPress(16);
    }

    void b_3_2() {
        ButtonPress(17);
    }

    void b_3_3() {
        ButtonPress(18);
    }

    void b_3_4() {
        ButtonPress(19);
    }

    void b_4_0() {
        ButtonPress(20);
    }

    void b_4_1() {
        ButtonPress(21);
    }

    void b_4_2() {
        ButtonPress(22);
    }

    void b_4_3() {
        ButtonPress(23);
    }

    void b_4_4() {
        ButtonPress(24);
    }
}

Print a message when the player won lights out.


//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

package com.threerings.util {

import flash.utils.getQualifiedClassName;

/**
 * A simple logging mechanism.
 *
 * Typical usage for creating a Log to be used by the entire class would be:
 * public class MyClass
 * {
 *     private static const log :Log = Log.getLog(MyClass);
 *     ...
 *
 * OR, if you just need a one-off Log:
 *     protected function doStuff (thingy :Thingy) :void
 *     {
 *          if (thingy == null) {
 *              Log.getLog(this).warn("thiny is null!");
 *              ....
 */
public class Log
{
    /**
     * Retrieve a Log for the specififed class.
     *
     * @param spec can be any Object or Class specifier.
     */
    public static function getLog (spec :*) :Log
    {
        // let's just use the full classname
        var path :String = getQualifiedClassName(spec).replace("::", ".");
        return new Log(path);
    }

    /**
     * A convenience function for quickly and easily inserting printy
     * statements during application development.
     */
    public static function testing (... params) :void
    {
        var log :Log = new Log("testing");
        log.debug.apply(log, params);
    }

    /**
     * A convenience function for quickly printing a stack trace
     * to the log, useful for debugging.
     */
    public static function dumpStack () :void
    {
        testing(new Error("dumpStack").getStackTrace());
    }

    /**
     * Add a logging target.
     */
    public static function addTarget (target :LogTarget) :void
    {
        _targets.push(target);
    }

    /**
     * Remove a logging target.
     */
    public static function removeTarget (target :LogTarget) :void
    {
        var dex :int = _targets.indexOf(target);
        if (dex != -1) {
            _targets.splice(dex, 1);
        }
    }

    /**
     * @private
     */
    public function Log (spec :String)
    {
        _spec = spec;
    }

    /**
     * Log a message with 'debug' priority.
     */
    public function debug (... messages) :void
    {
        doLog("[debug]", messages);
    }

    /**
     * Log a message with 'info' priority.
     */
    public function info (... messages) :void
    {
        doLog("[INFO]", messages);
    }

    /**
     * Log a message with 'debug' priority.
     */
    public function warning (... messages) :void
    {
        doLog("[WARNING]", messages);
    }

    /**
     * Log a message with 'debug' priority.
     */
    public function logStackTrace (error :Error) :void
    {
        warning(error.getStackTrace());
    }

    protected function doLog (level :String, messages :Array) :void
    {
        // TODO: better Date formatting?
        messages.unshift(new Date().toLocaleTimeString(), level, _spec);
        trace.apply(null, messages);

        // possibly also dispatch to any other log targets.
        if (_targets.length > 0) {
            var asOne :String = messages.join(" ");
            for each (var target :LogTarget in _targets) {
                target.log(asOne);
            }
        }
    }

    /** Our log specification. */
    protected var _spec :String;

    protected static var _targets :Array = [];
}

}

//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

package com.threerings.util {

import flash.utils.getQualifiedClassName;

/**
 * A simple logging mechanism.
 *
 * Typical usage for creating a Log to be used by the entire class would be:
 * public class MyClass
 * {
 *     private static const log :Log = Log.getLog(MyClass);
 *     ...
 *
 * OR, if you just need a one-off Log:
 *     protected function doStuff (thingy :Thingy) :void
 *     {
 *          if (thingy == null) {
 *              Log.getLog(this).warn("thiny is null!");
 *              ....
 */
public class Log
{
    /**
     * Retrieve a Log for the specififed class.
     *
     * @param spec can be any Object or Class specifier.
     */
    public static function getLog (spec :*) :Log
    {
        // let's just use the full classname
        var path :String = getQualifiedClassName(spec).replace("::", ".");
        return new Log(path);
    }

    /**
     * A convenience function for quickly and easily inserting printy
     * statements during application development.
     */
    public static function testing (... params) :void
    {
        var log :Log = new Log("testing");
        log.debug.apply(log, params);
    }

    /**
     * A convenience function for quickly printing a stack trace
     * to the log, useful for debugging.
     */
    public static function dumpStack () :void
    {
        testing(new Error("dumpStack").getStackTrace());
    }

    /**
     * Add a logging target.
     */
    public static function addTarget (target :LogTarget) :void
    {
        _targets.push(target);
    }

    /**
     * Remove a logging target.
     */
    public static function removeTarget (target :LogTarget) :void
    {
        var dex :int = _targets.indexOf(target);
        if (dex != -1) {
            _targets.splice(dex, 1);
        }
    }

    /**
     * @private
     */
    public function Log (spec :String)
    {
        _spec = spec;
    }

    /**
     * Log a message with 'debug' priority.
     */
    public function debug (... messages) :void
    {
        doLog("[debug]", messages);
    }

    /**
     * Log a message with 'info' priority.
     */
    public function info (... messages) :void
    {
        doLog("[INFO]", messages);
    }

    /**
     * Log a message with 'debug' priority.
     */
    public function warning (... messages) :void
    {
        doLog("[WARNING]", messages);
    }

    /**
     * Log a message with 'debug' priority.
     */
    public function logStackTrace (error :Error) :void
    {
        warning(error.getStackTrace());
    }

    protected function doLog (level :String, messages :Array) :void
    {
        messages.unshift(getTimeStamp(), level, _spec);
        trace.apply(null, messages);

        // possibly also dispatch to any other log targets.
        if (_targets.length > 0) {
            var asOne :String = messages.join(" ");
            for each (var target :LogTarget in _targets) {
                target.log(asOne);
            }
        }
    }

    protected function getTimeStamp () :String
    {
        var d :Date = new Date();
        // return d.toLocaleTimeString();

        // format it like the date format in our java logs
        return d.fullYear + "/" +
            StringUtil.prepad(String(d.month + 1), 2, "0") + "/" +
            StringUtil.prepad(String(d.date), 2, "0") + " " +
            StringUtil.prepad(String(d.hours), 2, "0") + ":" +
            StringUtil.prepad(String(d.minutes), 2, "0") + ":" +
            StringUtil.prepad(String(d.seconds), 2, "0") + ":" +
            StringUtil.prepad(String(d.milliseconds), 3, "0");
    }

    /** Our log specification. */
    protected var _spec :String;

    protected static var _targets :Array = [];
}

}

Format our timestamp like the timestamp in our Java logs.

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


package aerys.minko.render
{
	import aerys.minko.ns.minko_render;
	import aerys.minko.render.resource.Context3DResource;
	import aerys.minko.type.KeyboardManager;
	import aerys.minko.type.MouseManager;
	import aerys.minko.type.Signal;

	import flash.display.DisplayObject;
	import flash.display.Graphics;
	import flash.display.Shape;
	import flash.display.Sprite;
	import flash.display.Stage;
	import flash.display.Stage3D;
	import flash.display.StageAlign;
	import flash.display.StageScaleMode;
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.events.TouchEvent;
	import flash.geom.Point;

	/**
	 * The Viewport is the display area where a 3D scene can be rendered.
	 *
	 * @author Jean-Marc Le Roux
	 *
	 */
	public class Viewport extends Sprite
	{
		private static const ZERO2		: Point				= new Point();

		private var _stage3d			: Stage3D			= null;
		private var _context3d			: Context3DResource	= null;

		private var _width				: uint				= 0;
		private var _height				: uint				= 0;
		private var _autoResize			: Boolean			= false;
		private var _antiAliasing		: uint				= 0;
		private var _backgroundColor	: uint				= 0;
		private var _backBuffer			: RenderTarget		= null;
		private var _invalidBackBuffer	: Boolean			= false;

		private var _alwaysOnTop		: Boolean			= false;
		private var _mask				: Shape				= new Shape();

		private var _mouseManager		: MouseManager		= new MouseManager();
		private var _keyboardManager	: KeyboardManager	= new KeyboardManager();

		private var _resized			: Signal			= new Signal('Viewport.resized');

		minko_render function get context3D() : Context3DResource
		{
			return _context3d;
		}

		minko_render function get backBuffer() : RenderTarget
		{
			var positionOnStage	: Point	= localToGlobal(ZERO2);

			if (_stage3d.x != positionOnStage.x || _stage3d.y != positionOnStage.y)
				updateStage3D()

			return _backBuffer;
		}

		public function get ready() : Boolean
		{
			return _stage3d != null && _stage3d.context3D != null && _backBuffer != null;
		}

		/**
		 * Whether the viewport is visible or not.
		 * @return
		 *
		 */
		override public function get visible() : Boolean
		{
			return _stage3d.visible;
		}
		override public function set visible(value : Boolean) : void
		{
			_stage3d.visible = value;
			super.visible = value;
		}

		override public function set x(value : Number) : void
		{
			super.x = value;
			updateStage3D();
		}

		override public function set y(value : Number) : void
		{
			super.y = value;
			updateStage3D();
		}

		/**
		 * The width of the display area.
		 * @return
		 *
		 */
		override public function get width() : Number
		{
			return _width;
		}
		override public function set width(value : Number) : void
		{
			resize(value, _height);
		}

		/**
		 * The height of the display area.
		 * @return
		 *
		 */
		override public function get height() : Number
		{
			return _height;
		}
		override public function set height(value : Number) : void
		{
			resize(_width, value);
		}

		public function get alwaysOnTop() : Boolean
		{
			return _alwaysOnTop;
		}
		public function set alwaysOnTop(value : Boolean) : void
		{
			_alwaysOnTop = value;
		}

		public function get keyboardManager() : KeyboardManager
		{
			return _keyboardManager;
		}

		public function get mouseManager() : MouseManager
		{
			return _mouseManager;
		}

		/**
		 * The signal executed when the viewport is resized.
		 * Callback functions for this signal should accept the following
		 * arguments:
		 *

		 *
viewport : Viewport, the viewport executing the signal

		 *
width : Number, the new width of the viewport

		 *
height : Number, the new height of the viewport

		 *

		 * @return
		 *
		 */
		public function get resized() : Signal
		{
			return _resized;
		}

		/**
		 * The background color of the display area. This value must use the
		 * RGB format.
		 * @return
		 *
		 */
		public function get backgroundColor() : uint
		{
			return _backgroundColor;
		}
		public function set backgroundColor(value : uint) : void
		{
			_backgroundColor = value;
			updateBackBuffer();
		}

		/**
		 * The anti-aliasing to use (0, 2, 4, 8 or 16). The actual anti-aliasing
		 * used for rendering depends on the hardware capabilities. If the specified
		 * anti-aliasing value is not supported, the value 0 will be used.
		 * @return
		 *
		 */
		public function get antiAliasing() : uint
		{
			return _antiAliasing;
		}
		public function set antiAliasing(value : uint) : void
		{
			_antiAliasing = value;
			updateStage3D();
		}

		/**
		 * The driver informations provided by the Stage3D API.
		 */
		public function get driverInfo() : String
		{
			return _stage3d && _stage3d.context3D
				? _stage3d.context3D.driverInfo
				: null;
		}

		public function Viewport(antiAliasing	: uint	= 0,
								 width			: uint 	= 0,
								 height			: uint	= 0)
		{
			_antiAliasing = antiAliasing;

			if (width == 0 && height == 0)
				_autoResize = true;

			_width = width;
			_height = height;

			initialize();
		}

		private function initialize() : void
		{
			_mouseManager.bind(this);

			addEventListener(Event.ADDED_TO_STAGE, addedToStageHandler);
			addEventListener(Event.REMOVED_FROM_STAGE, removedFromStageHandler);
		}

		private function addedToStageHandler(event : Event) : void
		{
			_keyboardManager.bind(stage);

			parent.addEventListener(Event.RESIZE, parentResizedHandler);

			stage.addEventListener(Event.ADDED_TO_STAGE, displayObjectAddedToStageHandler);
			stage.addEventListener(Event.REMOVED_FROM_STAGE, displayObjectRemovedFromStageHandler);

			setupOnStage(stage);
		}

		private function removedFromStageHandler(event : Event) : void
		{
			_keyboardManager.unbind(stage);

			parent.removeEventListener(Event.RESIZE, parentResizedHandler);

			stage.removeEventListener(Event.ADDED_TO_STAGE, displayObjectAddedToStageHandler);
			stage.removeEventListener(Event.REMOVED_FROM_STAGE, displayObjectRemovedFromStageHandler);

			if (_stage3d != null)
				_stage3d.visible = false;
		}

		private function setupOnStage(stage : Stage, stage3dId : uint = 0) : void
		{
			if (_autoResize)
			{
				_width = stage.stageWidth;
				_height = stage.stageHeight;
			}

			if (!_stage3d)
			{
				_stage3d = stage.stage3Ds[stage3dId];
				_stage3d.addEventListener(Event.CONTEXT3D_CREATE, context3dCreatedHandler);
				_stage3d.requestContext3D();
			}
			else
			{
				_stage3d.visible = true;
			}

			stage.scaleMode = StageScaleMode.NO_SCALE;
			stage.align = StageAlign.TOP_LEFT;
		}

		/**
		 * Dispose the Viewport and all the Stage3D related objects. After this operation,
		 * the Viewport cannot be used anymore and is ready for garbage collection.
		 */
		public function dispose():void
		{
			if (_stage3d != null)
			{
				_stage3d.removeEventListener(Event.CONTEXT3D_CREATE, context3dCreatedHandler);
				_stage3d = null;
			}

			if (_context3d != null)
			{
				_context3d.dispose();
				_context3d = null;
			}

			return ;
		}

		/**
		 * Resize the display area. The "resized" signal is executed when the new width and
		 * height have be set.
		 * @param width
		 * @param height
		 *
		 */
		public function resize(width : Number, height : Number) : void
		{
			_autoResize = false;

			setSize(width, height);
		}

		private function setSize(width : Number, height : Number) : void
		{
			if (width == _width && _height == height)
				return ;

			_width = width;
			_height = height;

			updateStage3D();
			updateBackBuffer();
			_resized.execute(this, width, height);
		}

		private function stageResizedHandler(event : Event) : void
		{
			updateMask();
		}

		private function parentResizedHandler(event : Event) : void
		{
			if (_autoResize)
			{
				if (parent == stage)
					setSize(stage.stageWidth, stage.stageHeight);
				else
					setSize(parent.width, parent.height);
			}
		}

		private function context3dCreatedHandler(event : Event) : void
		{
			_context3d = new Context3DResource(_stage3d.context3D);

			updateStage3D();
			updateBackBuffer();

			dispatchEvent(new Event(Event.INIT));
		}

		private function updateBackBuffer() : void
		{
			if (_width == 0 || _height == 0 || _stage3d == null || _stage3d.context3D == null)
				return ;

			_invalidBackBuffer = false;
			_stage3d.context3D.configureBackBuffer(_width, _height, _antiAliasing, true);
			_backBuffer = new RenderTarget(_width, _height, null, 0, _backgroundColor);

			graphics.clear();
			graphics.beginFill(0, 0);
			graphics.drawRect(0, 0, _width, _height);
		}

		private function updateStage3D() : void
		{
			if (_stage3d == null)
				return ;

			var upperLeft	: Point	= localToGlobal(ZERO2);

			_stage3d.x = upperLeft.x;
			_stage3d.y = upperLeft.y;

			if (_width > 2048)
			{
				_stage3d.x = (_width - 2048) * 0.5;
				_width = 2048;
			}
			else
				_stage3d.x = upperLeft.x;

			if (_height > 2048)
			{
				_stage3d.y = (_height - 2048) * 0.5;
				_height = 2048;
			}
			else
				_stage3d.y = upperLeft.y;

			updateMask();
		}

		private function updateMask() : void
		{
			if (!stage)
				return ;

			var numChildren : uint = stage.numChildren;
			var i 			: uint = 0;

			if (_alwaysOnTop)
			{
				var gfx			: Graphics	= _mask.graphics;
				var stageWidth	: int		= stage.stageWidth;
				var stageHeight	: int		= stage.stageHeight;

				gfx.clear();
				gfx.beginFill(0);
				gfx.moveTo(0, 0);
				gfx.lineTo(stageWidth, 0);
				gfx.lineTo(stageWidth, stageHeight);
				gfx.lineTo(0., stageHeight);
				gfx.lineTo(0, 0);
				gfx.moveTo(_stage3d.x, _stage3d.y);
				gfx.lineTo(_stage3d.x, _stage3d.y + height);
				gfx.lineTo(_stage3d.x + width, _stage3d.y + height);
				gfx.lineTo(_stage3d.x + width, _stage3d.y);
				gfx.lineTo(_stage3d.x, _stage3d.y);
				gfx.endFill();

				for (i = 0; i < numChildren; ++i)
					stage.getChildAt(i).mask = _mask;
			}
			else
			{
				for (i = 0; i < numChildren; ++i)
				{
					var child : DisplayObject = stage.getChildAt(i);

					if (child.mask == _mask)
						child.mask = null;
				}
			}
		}

		private function displayObjectAddedToStageHandler(event : Event) : void
		{
			var displayObject : DisplayObject	= event.target as DisplayObject;

			if (displayObject.parent == stage)
				updateMask();
		}

		private function displayObjectRemovedFromStageHandler(event : Event) : void
		{
			var displayObject : DisplayObject	= event.target as DisplayObject;

			if (_autoResize && displayObject.parent == stage)
				displayObject.mask = null;
		}
	}
}

package aerys.minko.render
{
	import aerys.minko.ns.minko_render;
	import aerys.minko.render.resource.Context3DResource;
	import aerys.minko.type.KeyboardManager;
	import aerys.minko.type.MouseManager;
	import aerys.minko.type.Signal;

	import flash.display.DisplayObject;
	import flash.display.Graphics;
	import flash.display.Shape;
	import flash.display.Sprite;
	import flash.display.Stage;
	import flash.display.Stage3D;
	import flash.display.StageAlign;
	import flash.display.StageScaleMode;
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.events.TouchEvent;
	import flash.geom.Point;

	/**
	 * The Viewport is the display area where a 3D scene can be rendered.
	 *
	 * @author Jean-Marc Le Roux
	 *
	 */
	public class Viewport extends Sprite
	{
		private static const ZERO2		: Point				= new Point();

		private var _stage3d			: Stage3D			= null;
		private var _context3d			: Context3DResource	= null;

		private var _width				: uint				= 0;
		private var _height				: uint				= 0;
		private var _autoResize			: Boolean			= false;
		private var _antiAliasing		: uint				= 0;
		private var _backgroundColor	: uint				= 0;
		private var _backBuffer			: RenderTarget		= null;
		private var _invalidBackBuffer	: Boolean			= false;

		private var _alwaysOnTop		: Boolean			= false;
		private var _mask				: Shape				= new Shape();

		private var _mouseManager		: MouseManager		= new MouseManager();
		private var _keyboardManager	: KeyboardManager	= new KeyboardManager();

		private var _resized			: Signal			= new Signal('Viewport.resized');

		minko_render function get context3D() : Context3DResource
		{
			return _context3d;
		}

		minko_render function get backBuffer() : RenderTarget
		{
			var positionOnStage	: Point	= localToGlobal(ZERO2);

			if (_stage3d.x != positionOnStage.x || _stage3d.y != positionOnStage.y)
				updateStage3D()

			return _backBuffer;
		}

		public function get ready() : Boolean
		{
			return _stage3d != null && _stage3d.context3D != null && _backBuffer != null;
		}

		/**
		 * Whether the viewport is visible or not.
		 * @return
		 *
		 */
		override public function set visible(value : Boolean) : void
		{
			if (_stage3d)
				_stage3d.visible = value;

			super.visible = value;
		}

		override public function set x(value : Number) : void
		{
			super.x = value;
			updateStage3D();
		}

		override public function set y(value : Number) : void
		{
			super.y = value;
			updateStage3D();
		}

		/**
		 * The width of the display area.
		 * @return
		 *
		 */
		override public function get width() : Number
		{
			return _width;
		}
		override public function set width(value : Number) : void
		{
			resize(value, _height);
		}

		/**
		 * The height of the display area.
		 * @return
		 *
		 */
		override public function get height() : Number
		{
			return _height;
		}
		override public function set height(value : Number) : void
		{
			resize(_width, value);
		}

		public function get alwaysOnTop() : Boolean
		{
			return _alwaysOnTop;
		}
		public function set alwaysOnTop(value : Boolean) : void
		{
			_alwaysOnTop = value;
		}

		public function get keyboardManager() : KeyboardManager
		{
			return _keyboardManager;
		}

		public function get mouseManager() : MouseManager
		{
			return _mouseManager;
		}

		/**
		 * The signal executed when the viewport is resized.
		 * Callback functions for this signal should accept the following
		 * arguments:
		 *

		 *
viewport : Viewport, the viewport executing the signal

		 *
width : Number, the new width of the viewport

		 *
height : Number, the new height of the viewport

		 *

		 * @return
		 *
		 */
		public function get resized() : Signal
		{
			return _resized;
		}

		/**
		 * The background color of the display area. This value must use the
		 * RGB format.
		 * @return
		 *
		 */
		public function get backgroundColor() : uint
		{
			return _backgroundColor;
		}
		public function set backgroundColor(value : uint) : void
		{
			_backgroundColor = value;
			updateBackBuffer();
		}

		/**
		 * The anti-aliasing to use (0, 2, 4, 8 or 16). The actual anti-aliasing
		 * used for rendering depends on the hardware capabilities. If the specified
		 * anti-aliasing value is not supported, the value 0 will be used.
		 * @return
		 *
		 */
		public function get antiAliasing() : uint
		{
			return _antiAliasing;
		}
		public function set antiAliasing(value : uint) : void
		{
			_antiAliasing = value;
			updateStage3D();
		}

		/**
		 * The driver informations provided by the Stage3D API.
		 */
		public function get driverInfo() : String
		{
			return _stage3d && _stage3d.context3D
				? _stage3d.context3D.driverInfo
				: null;
		}

		public function Viewport(antiAliasing	: uint	= 0,
								 width			: uint 	= 0,
								 height			: uint	= 0)
		{
			_antiAliasing = antiAliasing;

			if (width == 0 && height == 0)
				_autoResize = true;

			_width = width;
			_height = height;

			initialize();
		}

		private function initialize() : void
		{
			_mouseManager.bind(this);

			addEventListener(Event.ADDED_TO_STAGE, addedToStageHandler);
			addEventListener(Event.REMOVED_FROM_STAGE, removedFromStageHandler);
		}

		private function addedToStageHandler(event : Event) : void
		{
			_keyboardManager.bind(stage);

			parent.addEventListener(Event.RESIZE, parentResizedHandler);

			stage.addEventListener(Event.ADDED_TO_STAGE, displayObjectAddedToStageHandler);
			stage.addEventListener(Event.REMOVED_FROM_STAGE, displayObjectRemovedFromStageHandler);

			setupOnStage(stage);
		}

		private function removedFromStageHandler(event : Event) : void
		{
			_keyboardManager.unbind(stage);

			parent.removeEventListener(Event.RESIZE, parentResizedHandler);

			stage.removeEventListener(Event.ADDED_TO_STAGE, displayObjectAddedToStageHandler);
			stage.removeEventListener(Event.REMOVED_FROM_STAGE, displayObjectRemovedFromStageHandler);

			if (_stage3d != null)
				_stage3d.visible = false;
		}

		private function setupOnStage(stage : Stage, stage3dId : uint = 0) : void
		{
			if (_autoResize)
			{
				_width = stage.stageWidth;
				_height = stage.stageHeight;
			}

			if (!_stage3d)
			{
				_stage3d = stage.stage3Ds[stage3dId];
				_stage3d.addEventListener(Event.CONTEXT3D_CREATE, context3dCreatedHandler);
				_stage3d.requestContext3D();
			}
			else
			{
				_stage3d.visible = visible;
			}

			stage.scaleMode = StageScaleMode.NO_SCALE;
			stage.align = StageAlign.TOP_LEFT;
		}

		/**
		 * Dispose the Viewport and all the Stage3D related objects. After this operation,
		 * the Viewport cannot be used anymore and is ready for garbage collection.
		 */
		public function dispose():void
		{
			if (_stage3d != null)
			{
				_stage3d.removeEventListener(Event.CONTEXT3D_CREATE, context3dCreatedHandler);
				_stage3d = null;
			}

			if (_context3d != null)
			{
				_context3d.dispose();
				_context3d = null;
			}

			return ;
		}

		/**
		 * Resize the display area. The "resized" signal is executed when the new width and
		 * height have be set.
		 * @param width
		 * @param height
		 *
		 */
		public function resize(width : Number, height : Number) : void
		{
			_autoResize = false;

			setSize(width, height);
		}

		private function setSize(width : Number, height : Number) : void
		{
			if (width == _width && _height == height)
				return ;

			_width = width;
			_height = height;

			updateStage3D();
			updateBackBuffer();
			_resized.execute(this, width, height);
		}

		private function stageResizedHandler(event : Event) : void
		{
			updateMask();
		}

		private function parentResizedHandler(event : Event) : void
		{
			if (_autoResize)
			{
				if (parent == stage)
					setSize(stage.stageWidth, stage.stageHeight);
				else
					setSize(parent.width, parent.height);
			}
		}

		private function context3dCreatedHandler(event : Event) : void
		{
			_context3d = new Context3DResource(_stage3d.context3D);

			updateStage3D();
			updateBackBuffer();

			dispatchEvent(new Event(Event.INIT));
		}

		private function updateBackBuffer() : void
		{
			if (_width == 0 || _height == 0 || _stage3d == null || _stage3d.context3D == null)
				return ;

			_invalidBackBuffer = false;
			_stage3d.context3D.configureBackBuffer(_width, _height, _antiAliasing, true);
			_backBuffer = new RenderTarget(_width, _height, null, 0, _backgroundColor);

			graphics.clear();
			graphics.beginFill(0, 0);
			graphics.drawRect(0, 0, _width, _height);
		}

		private function updateStage3D() : void
		{
			if (_stage3d == null)
				return ;

			var upperLeft	: Point	= localToGlobal(ZERO2);

			_stage3d.x = upperLeft.x;
			_stage3d.y = upperLeft.y;

			if (_width > 2048)
			{
				_stage3d.x = (_width - 2048) * 0.5;
				_width = 2048;
			}
			else
				_stage3d.x = upperLeft.x;

			if (_height > 2048)
			{
				_stage3d.y = (_height - 2048) * 0.5;
				_height = 2048;
			}
			else
				_stage3d.y = upperLeft.y;

			updateMask();
		}

		private function updateMask() : void
		{
			if (!stage)
				return ;

			var numChildren : uint = stage.numChildren;
			var i 			: uint = 0;

			if (_alwaysOnTop)
			{
				var gfx			: Graphics	= _mask.graphics;
				var stageWidth	: int		= stage.stageWidth;
				var stageHeight	: int		= stage.stageHeight;

				gfx.clear();
				gfx.beginFill(0);
				gfx.moveTo(0, 0);
				gfx.lineTo(stageWidth, 0);
				gfx.lineTo(stageWidth, stageHeight);
				gfx.lineTo(0., stageHeight);
				gfx.lineTo(0, 0);
				gfx.moveTo(_stage3d.x, _stage3d.y);
				gfx.lineTo(_stage3d.x, _stage3d.y + height);
				gfx.lineTo(_stage3d.x + width, _stage3d.y + height);
				gfx.lineTo(_stage3d.x + width, _stage3d.y);
				gfx.lineTo(_stage3d.x, _stage3d.y);
				gfx.endFill();

				for (i = 0; i < numChildren; ++i)
					stage.getChildAt(i).mask = _mask;
			}
			else
			{
				for (i = 0; i < numChildren; ++i)
				{
					var child : DisplayObject = stage.getChildAt(i);

					if (child.mask == _mask)
						child.mask = null;
				}
			}
		}

		private function displayObjectAddedToStageHandler(event : Event) : void
		{
			var displayObject : DisplayObject	= event.target as DisplayObject;

			if (displayObject.parent == stage)
				updateMask();
		}

		private function displayObjectRemovedFromStageHandler(event : Event) : void
		{
			var displayObject : DisplayObject	= event.target as DisplayObject;

			if (_autoResize && displayObject.parent == stage)
				displayObject.mask = null;
		}
	}
}

fix Viewport.visible to work even if it's not added on the Stage and no Stage3D is set


package aerys.minko.scene.controller.mesh
{
	import aerys.minko.ns.minko_math;
	import aerys.minko.render.geometry.Geometry;
	import aerys.minko.scene.controller.AbstractController;
	import aerys.minko.scene.data.MeshVisibilityDataProvider;
	import aerys.minko.scene.node.Camera;
	import aerys.minko.scene.node.Mesh;
	import aerys.minko.scene.node.Scene;
	import aerys.minko.type.binding.DataBindings;
	import aerys.minko.type.enum.FrustumCulling;
	import aerys.minko.type.math.BoundingBox;
	import aerys.minko.type.math.BoundingSphere;
	import aerys.minko.type.math.Matrix4x4;
	import aerys.minko.type.math.Vector4;

	/**
	 * The MeshVisibilityController watches the Mesh and the active Camera of a Scene
	 * to determine whether the object is actually inside the view frustum or not.
	 *
	 * @author Jean-Marc Le Roux
	 *
	 */
	public final class VisibilityController extends AbstractController
	{
		use namespace minko_math;

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

		private static const STATE_UNDEFINED	: uint		= 0;
		private static const STATE_INSIDE		: uint		= 1;
		private static const STATE_OUTSIDE		: uint		= 2;

		private var _mesh			: Mesh;
		private var _state			: uint;
		private var _lastTest		: int;
		private var _boundingBox	: BoundingBox;
		private var _boundingSphere	: BoundingSphere;

		private var _visibilityData	: MeshVisibilityDataProvider;

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

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

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

		public function VisibilityController()
		{
			super(Mesh);

			initialize();
		}

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

			_visibilityData = new MeshVisibilityDataProvider();

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

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

			_mesh = target;

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

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

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

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

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

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

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

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

		private function meshLocalToWorldChangedHandler(transform : Matrix4x4) : void
		{
			var geom 	: Geometry 	= _mesh.geometry;
			var culling	: uint		= _visibilityData.frustumCulling;

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

			if (culling & FrustumCulling.SPHERE)
			{
				var center 	: Vector4 = transform.transformVector(geom.boundingSphere.center);
				var scale 	: Vector4 = transform.deltaTransformVector(Vector4.ONE, TMP_VECTOR4);

				_boundingSphere.update(
					center,
					geom.boundingSphere.radius * Math.max(scale.x, scale.y, scale.z)
				);
			}

			testCulling();
		}

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

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

				if (!camera)
					return ;

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

				var inside : Boolean = _lastTest == -1;

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

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

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

			return clone;
		}
	}
}

package aerys.minko.scene.controller.mesh
{
	import aerys.minko.ns.minko_math;
	import aerys.minko.render.geometry.Geometry;
	import aerys.minko.scene.controller.AbstractController;
	import aerys.minko.scene.data.MeshVisibilityDataProvider;
	import aerys.minko.scene.node.Mesh;
	import aerys.minko.scene.node.Scene;
	import aerys.minko.scene.node.camera.AbstractCamera;
	import aerys.minko.type.binding.DataBindings;
	import aerys.minko.type.enum.FrustumCulling;
	import aerys.minko.type.math.BoundingBox;
	import aerys.minko.type.math.BoundingSphere;
	import aerys.minko.type.math.Matrix4x4;
	import aerys.minko.type.math.Vector4;

	/**
	 * The MeshVisibilityController watches the Mesh and the active Camera of a Scene
	 * to determine whether the object is actually inside the view frustum or not.
	 *
	 * @author Jean-Marc Le Roux
	 *
	 */
	public final class VisibilityController extends AbstractController
	{
		use namespace minko_math;

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

		private static const STATE_UNDEFINED	: uint		= 0;
		private static const STATE_INSIDE		: uint		= 1;
		private static const STATE_OUTSIDE		: uint		= 2;

		private var _mesh			: Mesh;
		private var _state			: uint;
		private var _lastTest		: int;
		private var _boundingBox	: BoundingBox;
		private var _boundingSphere	: BoundingSphere;

		private var _visibilityData	: MeshVisibilityDataProvider;

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

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

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

		public function VisibilityController()
		{
			super(Mesh);

			initialize();
		}

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

			_visibilityData = new MeshVisibilityDataProvider();

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

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

			_mesh = target;

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

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

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

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

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

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

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

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

		private function meshLocalToWorldChangedHandler(transform : Matrix4x4) : void
		{
			var geom 	: Geometry 	= _mesh.geometry;
			var culling	: uint		= _visibilityData.frustumCulling;

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

			if (culling & FrustumCulling.SPHERE)
			{
				var center 	: Vector4 = transform.transformVector(geom.boundingSphere.center);
				var scale 	: Vector4 = transform.deltaTransformVector(Vector4.ONE, TMP_VECTOR4);

				_boundingSphere.update(
					center,
					geom.boundingSphere.radius * Math.max(scale.x, scale.y, scale.z)
				);
			}

			testCulling();
		}

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

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

				if (!camera)
					return ;

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

				var inside : Boolean = _lastTest == -1;

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

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

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

			return clone;
		}
	}
}

fix broken reference to Camera in VisibilityController


package sssplayer.display {

	import flash.geom.Point;

	import sssplayer.data.SSSModel;
	import sssplayer.data.SSSPartAnime;
	import sssplayer.data.SSSProject;
	import sssplayer.data.SSSAnime;
	import sssplayer.data.SSSPart;

	import starling.display.DisplayObject;

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

	public class SSSPlayer extends Sprite {

		private var project:SSSProject;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

	}
}

package sssplayer.display {

	import flash.geom.Point;

	import sssplayer.data.SSSModel;
	import sssplayer.data.SSSPartAnime;
	import sssplayer.data.SSSProject;
	import sssplayer.data.SSSAnime;
	import sssplayer.data.SSSPart;

	import starling.display.DisplayObject;

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

	public class SSSPlayer extends Sprite {

		public static const VERSION:String = "0.0.1";

		private var project:SSSProject;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

	}
}

set version

set version


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.apply(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):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;
        }
    }
}

add loop method

add loop method

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