//
// Flump - Copyright 2013 Flump Authors

package flump.display {

import flash.utils.ByteArray;

import flump.executor.Executor;
import flump.executor.Future;

/**
 * Loads zip files created by the flump exporter and parses them into Library instances.
 */
public class LibraryLoader
{
    /**
     * Loads a Library from the zip in the given bytes.
     *
     * @param bytes The bytes containing the zip
     *
     * @param executor The executor on which the loading should run. If not specified, it'll run on
     * a new single-use executor.
     *
     * @param scaleFactor the desired scale factor of the textures to load. If the Library contains
     * textures with multiple scale factors, loader will load the textures with the scale factor
     * closest to this value. If scaleFactor <= 0 (the default), Starling.contentScaleFactor will be
     * used.
     * @param generateMipMaps If true (defaults to false), flump will instruct Starling to generate
     * mip maps for all loaded textures. Scaling will look better if mipmaps are enabled, but there
     * is a loading time and memory usage penalty.
     *
     * @return a Future to use to track the success or failure of loading the resources out of the
     * bytes. If the loading succeeds, the Future's onSuccess will fire with an instance of
     * Library. If it fails, the Future's onFail will fire with the Error that caused the
     * loading failure.
     */
    public static function loadBytes (bytes :ByteArray, executor :Executor=null,
        scaleFactor :Number=-1, generateMipMaps :Boolean=false) :Future {
        return (executor || new Executor(1)).submit(
            new Loader(bytes, scaleFactor, generateMipMaps).load);
    }

    /**
     * Loads a Library from the zip at the given url.
     *
     * @param bytes The url where the zip can be found
     *
     * @param executor The executor on which the loading should run. If not specified, it'll run on
     * a new single-use executor.
     *
     * @param scaleFactor the desired scale factor of the textures to load. If the Library contains
     * textures with multiple scale factors, loader will load the textures with the scale factor
     * closest to this value. If scaleFactor <= 0 (the default), Starling.contentScaleFactor will be
     * used.
     * @param generateMipMaps If true (defaults to false), flump will instruct Starling to generate
     * mip maps for all loaded textures. Scaling will look better if mipmaps are enabled, but there
     * is a loading time and memory usage penalty.
     *
     * @return a Future to use to track the success or failure of loading the resources from the
     * url. If the loading succeeds, the Future's onSuccess will fire with an instance of
     * Library. If it fails, the Future's onFail will fire with the Error that caused the
     * loading failure.
     */
    public static function loadURL (url :String, executor :Executor=null,
        scaleFactor :Number=-1, generateMipMaps :Boolean=false) :Future {
        return (executor || new Executor(1)).submit(
            new Loader(url, scaleFactor, generateMipMaps).load);
    }

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

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

}

import deng.fzip.FZip;
import deng.fzip.FZipErrorEvent;
import deng.fzip.FZipEvent;
import deng.fzip.FZipFile;

import flash.events.Event;
import flash.events.IOErrorEvent;
import flash.geom.Point;
import flash.geom.Rectangle;
import flash.net.URLRequest;
import flash.utils.ByteArray;
import flash.utils.Dictionary;

import flump.display.Library;
import flump.display.LibraryLoader;
import flump.display.Movie;
import flump.executor.Executor;
import flump.executor.Future;
import flump.executor.FutureTask;
import flump.executor.load.ImageLoader;
import flump.executor.load.LoadedImage;
import flump.mold.AtlasMold;
import flump.mold.AtlasTextureMold;
import flump.mold.LibraryMold;
import flump.mold.MovieMold;
import flump.mold.TextureGroupMold;

import starling.core.Starling;
import starling.display.DisplayObject;
import starling.display.Image;
import starling.textures.Texture;

interface SymbolCreator
{
    function create (library :Library) :DisplayObject;
}

class LibraryImpl
    implements Library
{
    public function LibraryImpl (baseTextures :Vector.<Texture>, creators :Dictionary) {
        _baseTextures = baseTextures;
        _creators = creators;
    }

    public function createMovie (symbol :String) :Movie {
        return Movie(createDisplayObject(symbol));
    }

    public function createImage (symbol :String) :Image {
        const disp :DisplayObject = createDisplayObject(symbol);
        if (disp is Movie) throw new Error(symbol + " is not an Image");
        return Image(disp);
    }

    public function getImageTexture (symbol :String) :Texture {
        checkNotDisposed();
        var creator :SymbolCreator = requireSymbolCreator(symbol);
        if (!(creator is ImageCreator)) throw new Error(symbol + " is not an Image");
        return ImageCreator(creator).texture;
    }

    public function get movieSymbols () :Vector.<String> {
        checkNotDisposed();
        const names :Vector.<String> = new <String>[];
        for (var creatorName :String in _creators) {
            if (_creators[creatorName] is MovieCreator) names.push(creatorName);
        }
        return names;
    }

    public function get imageSymbols () :Vector.<String> {
        checkNotDisposed();
        const names :Vector.<String> = new <String>[];
        for (var creatorName :String in _creators) {
            if (_creators[creatorName] is ImageCreator) names.push(creatorName);
        }
        return names;
    }

    public function createDisplayObject (name :String) :DisplayObject {
        checkNotDisposed();
        return requireSymbolCreator(name).create(this);
    }

    public function dispose () :void {
        checkNotDisposed();
        for each (var tex :Texture in _baseTextures) {
            tex.dispose();
        }
        _baseTextures = null;
        _creators = null;
    }

    protected function requireSymbolCreator (name :String) :SymbolCreator {
        var creator :SymbolCreator = _creators[name];
        if (creator == null) throw new Error("No such id '" + name + "'");
        return creator;
    }

    protected function checkNotDisposed () :void {
        if (_baseTextures == null) {
            throw new Error("This Library has been disposed");
        }
    }

    protected var _creators :Dictionary;
    protected var _baseTextures :Vector.<Texture>;
}

class Loader
{
    public function Loader (toLoad :Object, scaleFactor :Number, generateMipMaps :Boolean) {
        _scaleFactor = (scaleFactor > 0 ? scaleFactor : Starling.contentScaleFactor);
        _generateMipMaps = generateMipMaps;
        _toLoad = toLoad;
    }

    public function load (future :FutureTask) :void {
        _future = future;

        _zip.addEventListener(Event.COMPLETE, _future.monitoredCallback(onZipLoadingComplete));
        _zip.addEventListener(IOErrorEvent.IO_ERROR, _future.fail);
        _zip.addEventListener(FZipErrorEvent.PARSE_ERROR, _future.fail);
        _zip.addEventListener(FZipEvent.FILE_LOADED, _future.monitoredCallback(onFileLoaded));

        if (_toLoad is String) _zip.load(new URLRequest(String(_toLoad)));
        else _zip.loadBytes(ByteArray(_toLoad));
    }

    protected function onFileLoaded (e :FZipEvent) :void {
        const loaded :FZipFile = _zip.removeFileAt(_zip.getFileCount() - 1);
        const name :String = loaded.filename;
        if (name == LibraryLoader.LIBRARY_LOCATION) {
            const jsonString :String = loaded.content.readUTFBytes(loaded.content.length);
            _lib = LibraryMold.fromJSON(JSON.parse(jsonString));
        } else if (name.indexOf(PNG, name.length - PNG.length) != -1) {
            _atlasBytes[name] = loaded.content;
        } else if (name.indexOf(ATF, name.length - ATF.length) != -1) {
            _atlasBytes[name] = loaded.content;
        } else if (name == LibraryLoader.VERSION_LOCATION) {
            const zipVersion :String = loaded.content.readUTFBytes(loaded.content.length)
            if (zipVersion != LibraryLoader.VERSION) {
                throw new Error("Zip is version " + zipVersion + " but the code needs " + LibraryLoader.VERSION);
            }
            _versionChecked = true;
        } else if (name == LibraryLoader.MD5_LOCATION ) { // Nothing to verify
        } else {} // ignore unknown files
    }

    protected function onZipLoadingComplete (..._) :void {
        _zip = null;
        if (_lib == null) throw new Error(LibraryLoader.LIBRARY_LOCATION + " missing from zip");
        if (!_versionChecked) throw new Error(LibraryLoader.VERSION_LOCATION + " missing from zip");
        const loader :ImageLoader = _lib.textureFormat == "atf" ? null : new ImageLoader();
        _pngLoaders.terminated.add(_future.monitoredCallback(onPngLoadingComplete));

        // Determine the scale factor we want to use
        var textureGroup :TextureGroupMold = _lib.bestTextureGroupForScaleFactor(_scaleFactor);
        if (textureGroup != null) {
            for each (var atlas :AtlasMold in textureGroup.atlases) {
                loadAtlas(loader, atlas);
            }
        }
        _pngLoaders.shutdown();
    }

    protected function loadAtlas (loader :ImageLoader, atlas :AtlasMold) :void {
        const bytes :* = _atlasBytes[atlas.file];
        if (bytes === undefined) {
            throw new Error("Expected an atlas '" + atlas.file + "', but it wasn't in the zip");
        }

        var scale :Number = atlas.scaleFactor;
        if (_lib.textureFormat == "atf") {
            baseTextureLoaded(Texture.fromAtfData(bytes, scale), atlas);
        } else {
            const atlasFuture :Future = loader.loadFromBytes(bytes, _pngLoaders);
            atlasFuture.failed.add(onPngLoadingFailed);
            atlasFuture.succeeded.add(function (img :LoadedImage) :void {
                baseTextureLoaded(Texture.fromBitmapData(
                    img.bitmapData,
                    _generateMipMaps,
                    false,  // optimizeForRenderToTexture
                    scale), atlas);
                if (!Starling.handleLostContext) {
                    img.bitmapData.dispose();
                }
            });

        }
    }

    protected function baseTextureLoaded (baseTexture :Texture, atlas :AtlasMold) :void {
        _baseTextures.push(baseTexture);

        var scale :Number = atlas.scaleFactor;
        for each (var atlasTexture :AtlasTextureMold in atlas.textures) {
            var bounds :Rectangle = atlasTexture.bounds;
            var offset :Point = atlasTexture.origin;

            // Starling expects subtexture bounds to be unscaled
            if (scale != 1) {
                bounds = bounds.clone();
                bounds.x /= scale;
                bounds.y /= scale;
                bounds.width /= scale;
                bounds.height /= scale;

                offset = offset.clone();
                offset.x /= scale;
                offset.y /= scale;
            }

            _creators[atlasTexture.symbol] = new ImageCreator(
                Texture.fromTexture(baseTexture, bounds),
                offset,
                atlasTexture.symbol);
        }
    }

    protected function onPngLoadingComplete (..._) :void {
        for each (var movie :MovieMold in _lib.movies) {
            movie.fillLabels();
            _creators[movie.id] = new MovieCreator(movie, _lib.frameRate);
        }
        _future.succeed(new LibraryImpl(_baseTextures, _creators));
    }

    protected function onPngLoadingFailed (e :*) :void {
        if (_future.isComplete) return;
        _future.fail(e);
        _pngLoaders.shutdownNow();
    }

    protected var _toLoad :Object;
    protected var _scaleFactor :Number;
    protected var _generateMipMaps :Boolean;
    protected var _future :FutureTask;
    protected var _versionChecked :Boolean;

    protected var _zip :FZip = new FZip();
    protected var _lib :LibraryMold;

    protected const _baseTextures :Vector.<Texture> = new <Texture>[];
    protected const _creators :Dictionary = new Dictionary();//<name, ImageCreator/MovieCreator>
    protected const _atlasBytes :Dictionary = new Dictionary();//<String name, ByteArray>
    protected const _pngLoaders :Executor = new Executor(1);

    protected static const PNG :String = ".png";
    protected static const ATF :String = ".atf"
}

class ImageCreator
    implements SymbolCreator
{
    public var texture :Texture;
    public var origin :Point;
    public var symbol :String;

    public function ImageCreator (texture :Texture, origin :Point, symbol :String) {
        this.texture = texture;
        this.origin = origin;
        this.symbol = symbol;
    }

    public function create (library :Library) :DisplayObject {
        const image :Image = new Image(texture);
        image.pivotX = origin.x;
        image.pivotY = origin.y;
        image.name = symbol;
        return image;
    }
}

class MovieCreator
    implements SymbolCreator
{
    public var mold :MovieMold;
    public var frameRate :Number;

    public function MovieCreator (mold :MovieMold, frameRate :Number) {
        this.mold = mold;
        this.frameRate = frameRate;
    }

    public function create (library :Library) :DisplayObject {
        return new Movie(mold, frameRate, library);
    }
}

//
// Flump - Copyright 2013 Flump Authors

package flump.display {

import flash.utils.ByteArray;

import flump.executor.Executor;
import flump.executor.Future;

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

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

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

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

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

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

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

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

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

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

}

import deng.fzip.FZip;
import deng.fzip.FZipErrorEvent;
import deng.fzip.FZipEvent;
import deng.fzip.FZipFile;

import flash.events.Event;
import flash.events.IOErrorEvent;
import flash.geom.Point;
import flash.geom.Rectangle;
import flash.net.URLRequest;
import flash.utils.ByteArray;
import flash.utils.Dictionary;

import flump.display.Library;
import flump.display.LibraryLoader;
import flump.display.Movie;
import flump.executor.Executor;
import flump.executor.Future;
import flump.executor.FutureTask;
import flump.executor.load.ImageLoader;
import flump.executor.load.LoadedImage;
import flump.mold.AtlasMold;
import flump.mold.AtlasTextureMold;
import flump.mold.LibraryMold;
import flump.mold.MovieMold;
import flump.mold.TextureGroupMold;

import starling.core.Starling;
import starling.display.DisplayObject;
import starling.display.Image;
import starling.textures.Texture;

interface SymbolCreator
{
    function create (library :Library) :DisplayObject;
}

class LibraryImpl
    implements Library
{
    public function LibraryImpl (baseTextures :Vector.<Texture>, creators :Dictionary) {
        _baseTextures = baseTextures;
        _creators = creators;
    }

    public function createMovie (symbol :String) :Movie {
        return Movie(createDisplayObject(symbol));
    }

    public function createImage (symbol :String) :Image {
        const disp :DisplayObject = createDisplayObject(symbol);
        if (disp is Movie) throw new Error(symbol + " is not an Image");
        return Image(disp);
    }

    public function getImageTexture (symbol :String) :Texture {
        checkNotDisposed();
        var creator :SymbolCreator = requireSymbolCreator(symbol);
        if (!(creator is ImageCreator)) throw new Error(symbol + " is not an Image");
        return ImageCreator(creator).texture;
    }

    public function get movieSymbols () :Vector.<String> {
        checkNotDisposed();
        const names :Vector.<String> = new <String>[];
        for (var creatorName :String in _creators) {
            if (_creators[creatorName] is MovieCreator) names.push(creatorName);
        }
        return names;
    }

    public function get imageSymbols () :Vector.<String> {
        checkNotDisposed();
        const names :Vector.<String> = new <String>[];
        for (var creatorName :String in _creators) {
            if (_creators[creatorName] is ImageCreator) names.push(creatorName);
        }
        return names;
    }

    public function createDisplayObject (name :String) :DisplayObject {
        checkNotDisposed();
        return requireSymbolCreator(name).create(this);
    }

    public function dispose () :void {
        checkNotDisposed();
        for each (var tex :Texture in _baseTextures) {
            tex.dispose();
        }
        _baseTextures = null;
        _creators = null;
    }

    protected function requireSymbolCreator (name :String) :SymbolCreator {
        var creator :SymbolCreator = _creators[name];
        if (creator == null) throw new Error("No such id '" + name + "'");
        return creator;
    }

    protected function checkNotDisposed () :void {
        if (_baseTextures == null) {
            throw new Error("This Library has been disposed");
        }
    }

    protected var _creators :Dictionary;
    protected var _baseTextures :Vector.<Texture>;
}

class Loader
{
    public function Loader (toLoad :Object, scaleFactor :Number, generateMipMaps :Boolean) {
        _scaleFactor = (scaleFactor > 0 ? scaleFactor : Starling.contentScaleFactor);
        _generateMipMaps = generateMipMaps;
        _toLoad = toLoad;
    }

    public function load (future :FutureTask) :void {
        _future = future;

        _zip.addEventListener(Event.COMPLETE, _future.monitoredCallback(onZipLoadingComplete));
        _zip.addEventListener(IOErrorEvent.IO_ERROR, _future.fail);
        _zip.addEventListener(FZipErrorEvent.PARSE_ERROR, _future.fail);
        _zip.addEventListener(FZipEvent.FILE_LOADED, _future.monitoredCallback(onFileLoaded));

        if (_toLoad is String) _zip.load(new URLRequest(String(_toLoad)));
        else _zip.loadBytes(ByteArray(_toLoad));
    }

    protected function onFileLoaded (e :FZipEvent) :void {
        const loaded :FZipFile = _zip.removeFileAt(_zip.getFileCount() - 1);
        const name :String = loaded.filename;
        if (name == LibraryLoader.LIBRARY_LOCATION) {
            const jsonString :String = loaded.content.readUTFBytes(loaded.content.length);
            _lib = LibraryMold.fromJSON(JSON.parse(jsonString));
        } else if (name.indexOf(PNG, name.length - PNG.length) != -1) {
            _atlasBytes[name] = loaded.content;
        } else if (name.indexOf(ATF, name.length - ATF.length) != -1) {
            _atlasBytes[name] = loaded.content;
        } else if (name == LibraryLoader.VERSION_LOCATION) {
            const zipVersion :String = loaded.content.readUTFBytes(loaded.content.length)
            if (zipVersion != LibraryLoader.VERSION) {
                throw new Error("Zip is version " + zipVersion + " but the code needs " + LibraryLoader.VERSION);
            }
            _versionChecked = true;
        } else if (name == LibraryLoader.MD5_LOCATION ) { // Nothing to verify
        } else {} // ignore unknown files
    }

    protected function onZipLoadingComplete (..._) :void {
        _zip = null;
        if (_lib == null) throw new Error(LibraryLoader.LIBRARY_LOCATION + " missing from zip");
        if (!_versionChecked) throw new Error(LibraryLoader.VERSION_LOCATION + " missing from zip");
        const loader :ImageLoader = _lib.textureFormat == "atf" ? null : new ImageLoader();
        _pngLoaders.terminated.add(_future.monitoredCallback(onPngLoadingComplete));

        // Determine the scale factor we want to use
        var textureGroup :TextureGroupMold = _lib.bestTextureGroupForScaleFactor(_scaleFactor);
        if (textureGroup != null) {
            for each (var atlas :AtlasMold in textureGroup.atlases) {
                loadAtlas(loader, atlas);
            }
        }
        _pngLoaders.shutdown();
    }

    protected function loadAtlas (loader :ImageLoader, atlas :AtlasMold) :void {
        const bytes :* = _atlasBytes[atlas.file];
        if (bytes === undefined) {
            throw new Error("Expected an atlas '" + atlas.file + "', but it wasn't in the zip");
        }

        var scale :Number = atlas.scaleFactor;
        if (_lib.textureFormat == "atf") {
            baseTextureLoaded(Texture.fromAtfData(bytes, scale), atlas);
        } else {
            const atlasFuture :Future = loader.loadFromBytes(bytes, _pngLoaders);
            atlasFuture.failed.add(onPngLoadingFailed);
            atlasFuture.succeeded.add(function (img :LoadedImage) :void {
                baseTextureLoaded(Texture.fromBitmapData(
                    img.bitmapData,
                    _generateMipMaps,
                    false,  // optimizeForRenderToTexture
                    scale), atlas);
                if (!Starling.handleLostContext) {
                    img.bitmapData.dispose();
                }
            });

        }
    }

    protected function baseTextureLoaded (baseTexture :Texture, atlas :AtlasMold) :void {
        _baseTextures.push(baseTexture);

        var scale :Number = atlas.scaleFactor;
        for each (var atlasTexture :AtlasTextureMold in atlas.textures) {
            var bounds :Rectangle = atlasTexture.bounds;
            var offset :Point = atlasTexture.origin;

            // Starling expects subtexture bounds to be unscaled
            if (scale != 1) {
                bounds = bounds.clone();
                bounds.x /= scale;
                bounds.y /= scale;
                bounds.width /= scale;
                bounds.height /= scale;

                offset = offset.clone();
                offset.x /= scale;
                offset.y /= scale;
            }

            _creators[atlasTexture.symbol] = new ImageCreator(
                Texture.fromTexture(baseTexture, bounds),
                offset,
                atlasTexture.symbol);
        }
    }

    protected function onPngLoadingComplete (..._) :void {
        for each (var movie :MovieMold in _lib.movies) {
            movie.fillLabels();
            _creators[movie.id] = new MovieCreator(movie, _lib.frameRate);
        }
        _future.succeed(new LibraryImpl(_baseTextures, _creators));
    }

    protected function onPngLoadingFailed (e :*) :void {
        if (_future.isComplete) return;
        _future.fail(e);
        _pngLoaders.shutdownNow();
    }

    protected var _toLoad :Object;
    protected var _scaleFactor :Number;
    protected var _generateMipMaps :Boolean;
    protected var _future :FutureTask;
    protected var _versionChecked :Boolean;

    protected var _zip :FZip = new FZip();
    protected var _lib :LibraryMold;

    protected const _baseTextures :Vector.<Texture> = new <Texture>[];
    protected const _creators :Dictionary = new Dictionary();//<name, ImageCreator/MovieCreator>
    protected const _atlasBytes :Dictionary = new Dictionary();//<String name, ByteArray>
    protected const _pngLoaders :Executor = new Executor(1);

    protected static const PNG :String = ".png";
    protected static const ATF :String = ".atf"
}

class ImageCreator
    implements SymbolCreator
{
    public var texture :Texture;
    public var origin :Point;
    public var symbol :String;

    public function ImageCreator (texture :Texture, origin :Point, symbol :String) {
        this.texture = texture;
        this.origin = origin;
        this.symbol = symbol;
    }

    public function create (library :Library) :DisplayObject {
        const image :Image = new Image(texture);
        image.pivotX = origin.x;
        image.pivotY = origin.y;
        image.name = symbol;
        return image;
    }
}

class MovieCreator
    implements SymbolCreator
{
    public var mold :MovieMold;
    public var frameRate :Number;

    public function MovieCreator (mold :MovieMold, frameRate :Number) {
        this.mold = mold;
        this.frameRate = frameRate;
    }

    public function create (library :Library) :DisplayObject {
        return new Movie(mold, frameRate, library);
    }
}

Move LibraryLoader to a builder pattern.

I made the old static methods deprecated and removed the new generateMipMaps parameter from them.
Presumably if anybody other than me is already using it, they're paying enough attention to adapt
to this change too.


//
// $Id: PlaceViewUtil.java 3098 2004-08-27 02:12:55Z mdb $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 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.crowd.client {

import flash.display.DisplayObjectContainer;

import com.threerings.crowd.data.PlaceObject;

/**
 * Provides a mechanism for dispatching notifications to all user
 * interface elements in a hierarchy that implement the {@link PlaceView}
 * interface. Look at the documentation for {@link PlaceView} for more
 * explanation.
 */
public class PlaceViewUtil
{
    /**
     * Dispatches a call to {@link PlaceView#willEnterPlace} to all UI
     * elements in the hierarchy rooted at the component provided via the
     * root parameter.
     *
     * @param root the component at which to start traversing the UI
     * hierarchy.
     * @param plobj the place object that is about to be entered.
     */
    public static function dispatchWillEnterPlace (
            root :Object, plobj :PlaceObject) :void
    {
        dispatch(root, plobj, "willEnterPlace");
    }

    /**
     * Dispatches a call to {@link PlaceView#didLeavePlace} to all UI
     * elements in the hierarchy rooted at the component provided via the
     * root parameter.
     *
     * @param root the component at which to start traversing the UI
     * hierarchy.
     * @param plobj the place object that is about to be entered.
     */
    public static function dispatchDidLeavePlace (
            root :Object, plobj :PlaceObject) :void
    {
        dispatch(root, plobj, "didLeavePlace");
    }

    private static function dispatch (
            root :Object, plobj :PlaceObject, funct :String) :void
    {
        // dispatch the call on this component if it implements PlaceView
        if (root is PlaceView) {
            try {
                (root as PlaceView)[funct](plobj);
            } catch (e :Error) {
                var log :Log = Log.getLog(PlaceViewUtil);
                log.warning("Component choked on " + funct + "() " +
                            "[component=" + root + ", plobj=" + plobj + "].");
                log.logStackTrace(e);
            }
        }

        // now traverse all of this component's children
        if (root is DisplayObjectContainer) {
            var cont :DisplayObjectContainer = (root as DisplayObjectContainer);
            for (var ii :int = 0; ii < cont.numChildren; ii++) {
                dispatch(cont.getChildAt(ii), plobj, funct);
            }
        }
    }
}
}

//
// $Id: PlaceViewUtil.java 3098 2004-08-27 02:12:55Z mdb $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 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.crowd.client {

import flash.display.DisplayObject;

import mx.utils.DisplayUtil;

import com.threerings.crowd.data.PlaceObject;

/**
 * Provides a mechanism for dispatching notifications to all user
 * interface elements in a hierarchy that implement the {@link PlaceView}
 * interface. Look at the documentation for {@link PlaceView} for more
 * explanation.
 */
public class PlaceViewUtil
{
    /**
     * Dispatches a call to {@link PlaceView#willEnterPlace} to all UI
     * elements in the hierarchy rooted at the component provided via the
     * root parameter.
     *
     * @param root the component at which to start traversing the UI
     * hierarchy.
     * @param plobj the place object that is about to be entered.
     */
    public static function dispatchWillEnterPlace (
            root :Object, plobj :PlaceObject) :void
    {
        dispatch(root, plobj, "willEnterPlace");
    }

    /**
     * Dispatches a call to {@link PlaceView#didLeavePlace} to all UI
     * elements in the hierarchy rooted at the component provided via the
     * root parameter.
     *
     * @param root the component at which to start traversing the UI
     * hierarchy.
     * @param plobj the place object that is about to be entered.
     */
    public static function dispatchDidLeavePlace (
            root :Object, plobj :PlaceObject) :void
    {
        dispatch(root, plobj, "didLeavePlace");
    }

    private static function dispatch (
            root :Object, plobj :PlaceObject, funct :String) :void
    {
        if (!(root is DisplayObject)) {
            return;
        }

        DisplayUtil.walkDisplayObjects(root as DisplayObject,
            function (disp :DisplayObject) :void {
                if (disp is PlaceView) {
                    try {
                        (root as PlaceView)[funct](plobj);
                    } catch (e :Error) {
                        var log :Log = Log.getLog(PlaceViewUtil);
                        log.warning("Component choked on " + funct + "() " +
                            "[component=" + root + ", plobj=" + plobj + "].");
                        log.logStackTrace(e);
                    }
                }
            });
    }
}
}

Use mx.utils.DisplayUtil.

Use mx.utils.DisplayUtil.

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


/* ***** BEGIN LICENSE BLOCK *****
 *
 * This file is part of Weave.
 *
 * The Initial Developer of Weave is the Institute for Visualization
 * and Perception Research at the University of Massachusetts Lowell.
 * Portions created by the Initial Developer are Copyright (C) 2008-2015
 * the Initial Developer. All Rights Reserved.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * ***** END LICENSE BLOCK ***** */

package weavejs.core
{
	public class EventCallbackCollection/*/<T>/*/ extends CallbackCollection
	{
		public function EventCallbackCollection(stopPropagation:/*/(event:T)=>void/*/Function = null)
		{
			// specify the preCallback function in super() so data will be set before each callback.
			super(_setEvent);
			this._stopPropagation = stopPropagation;
		}

		// This variable is set before each callback runs
		private var _event:Object = null;
		private var _stopPropagation:Function = null;

		private function _setEvent(event:Object = null):void
		{
			_event = event;
		}

		/**
		 * This is the event object.
		 */
		public function get event():/*/T/*/Object
		{
			return _event;
		}

		/**
		 * This function will run callbacks immediately, setting the event variable before each one.
		 * @param event
		 */
		public function dispatch(event:/*/T/*/Object):void
		{
			// remember previous value so it can be restored in case external code caused us to interrupt something else
			var prevEvent:Object = _event;
			_runCallbacksImmediately(event);
			_setEvent(prevEvent);
		}

		/**
		 * calls the stopPropagation function which was passed to the constructor.
		 */
		public function stopPropagation():void
		{
			if (_stopPropagation != null)
				_stopPropagation(event);
		}
	}
}

/* ***** BEGIN LICENSE BLOCK *****
 *
 * This file is part of Weave.
 *
 * The Initial Developer of Weave is the Institute for Visualization
 * and Perception Research at the University of Massachusetts Lowell.
 * Portions created by the Initial Developer are Copyright (C) 2008-2015
 * the Initial Developer. All Rights Reserved.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * ***** END LICENSE BLOCK ***** */

package weavejs.core
{
	public class EventCallbackCollection/*/<T>/*/ extends CallbackCollection
	{
		public function EventCallbackCollection()
		{
			// specify the preCallback function in super() so data will be set before each callback.
			super(_setEvent);
		}

		// This variable is set before each callback runs
		private var _event:Object = null;

		private function _setEvent(event:Object = null):void
		{
			_event = event;
		}

		/**
		 * This is the event object.
		 */
		public function get event():/*/T/*/Object
		{
			return _event;
		}

		/**
		 * This function will run callbacks immediately, setting the event variable before each one.
		 * @param event
		 */
		public function dispatch(event:/*/T/*/Object):void
		{
			// remember previous value so it can be restored in case external code caused us to interrupt something else
			var prevEvent:Object = _event;
			_runCallbacksImmediately(event);
			_setEvent(prevEvent);
		}
	}
}

Revert "added stopPropagation function to EventCallbackCollection"

This reverts commit eec7fe6aa043f6ab507a870b98a1936a1b5a60bc.


package com.google.analytics.utils
{

    public class FakeEnvironment extends Environment
    {
        private var _appName:String;
        private var _appVersion:Version;
        private var _url:String;
        private var _referrer:String;
        private var _documentTitle:String;
        private var _domainName:String;
        private var _locationPath:String;
        private var _locationSearch:String;
        private var _flashVersion:Version;
        private var _language:String;
        private var _languageEncoding:String;
        private var _operatingSystem:String;
        private var _playerType:String;
        private var _platform:String;
        private var _protocol:Protocols;
        private var _screenWidth:Number;
        private var _screenHeight:Number;
        private var _screenColorDepth:String;
        private var _userAgent:UserAgent;
        private var _isInHTML:Boolean;
        private var _isAIR:Boolean;

        public function FakeEnvironment( appName:String = "", appVersion:Version = null,
                                       url:String = "", referrer:String = "",
                                       documentTitle:String = "", domainName:String = "",
                                       locationPath:String = "", locationSearch:String = "",
                                       flashVersion:Version = null, language:String = "",
                                       languageEncoding:String = "", operatingSystem:String = "",
                                       playerType:String = "", platform:String = "",
                                       protocol:Protocols = null, screenWidth:Number = NaN,
                                       screenHeight:Number = NaN, screenColorDepth:String = "",
                                       userAgent:UserAgent = null,
                                       isInHTML:Boolean = false,
                                       isAIR:Boolean = false )
        {
            super("", "", "", null);

            _appName = appName;
            _appVersion = appVersion;
            _url = url;
            _referrer = referrer;
            _documentTitle = documentTitle;
            _domainName = domainName;
            _locationPath = locationPath;
            _locationSearch = locationSearch;
            _flashVersion = flashVersion;
            _language = language;
            _languageEncoding = languageEncoding;
            _operatingSystem = operatingSystem;
            _playerType = playerType;
            _platform = platform;
            _protocol = protocol;
            _screenWidth = screenWidth;
            _screenHeight = screenHeight;
            _screenColorDepth = screenColorDepth;
            _userAgent = userAgent;
            _isInHTML = isInHTML;
            _isAIR = isAIR;
        }

        public override function get appName():String
        {
            return _appName;
        }

        public override function set appName( value:String ):void
        {
            _appName = value;
        }

        public override function get appVersion():Version
        {
            return _appVersion;
        }

        public override function set appVersion( value:Version ):void
        {
            _appVersion = value;
        }

//        ga_internal override function set url( value:String ):void
//        {
//            _url = value;
//        }

        public override function get locationSWFPath():String
        {
            return _url;
        }

        public override function get referrer():String
        {
            return _referrer;
        }

        public override function get documentTitle():String
        {
            return _documentTitle;
        }

        public override function get domainName():String
        {
            return _domainName;
        }

        public override function get locationPath():String
        {
            return _locationPath;
        }

        public override function get locationSearch():String
        {
            return _locationSearch;
        }

        public override function get flashVersion():Version
        {
            return _flashVersion;
        }

        public override function get language():String
        {
            return _language;
        }

        public override function get languageEncoding():String
        {
            return _languageEncoding;
        }

        public override function get operatingSystem():String
        {
            return _operatingSystem;
        }

        public override function get playerType():String
        {
            return _playerType;
        }

        public override function get platform():String
        {
            return _platform;
        }

        public override function get protocol():Protocols
        {
            return _protocol;
        }

        public override function get screenWidth():Number
        {
            return _screenWidth;
        }

        public override function get screenHeight():Number
        {
            return _screenHeight;
        }

        public override function get screenColorDepth():String
        {
            return _screenColorDepth;
        }

        public override function get userAgent():UserAgent
        {
            return _userAgent;
        }

        public override function set userAgent( custom:UserAgent ):void
        {
            _userAgent = custom;
        }

        public override function isInHTML():Boolean
        {
            return _isInHTML;
        }

        public override function isAIR():Boolean
        {
            return _isAIR;
        }
    }
}

package com.google.analytics.utils
{

    public class FakeEnvironment extends Environment
    {
        private var _appName:String;
        private var _appVersion:Version;
        private var _url:String;
        private var _referrer:String;
        private var _documentTitle:String;
        private var _domainName:String;
        private var _locationPath:String;
        private var _locationSearch:String;
        private var _flashVersion:Version;
        private var _language:String;
        private var _languageEncoding:String;
        private var _operatingSystem:String;
        private var _playerType:String;
        private var _platform:String;
        private var _protocol:Protocols;
        private var _screenWidth:Number;
        private var _screenHeight:Number;
        private var _screenColorDepth:String;
        private var _userAgent:String;
        private var _isInHTML:Boolean;
        private var _isAIR:Boolean;

        public function FakeEnvironment( appName:String = "", appVersion:Version = null,
                                       url:String = "", referrer:String = "",
                                       documentTitle:String = "", domainName:String = "",
                                       locationPath:String = "", locationSearch:String = "",
                                       flashVersion:Version = null, language:String = "",
                                       languageEncoding:String = "", operatingSystem:String = "",
                                       playerType:String = "", platform:String = "",
                                       protocol:Protocols = null, screenWidth:Number = NaN,
                                       screenHeight:Number = NaN, screenColorDepth:String = "",
                                       userAgent:String = null,
                                       isInHTML:Boolean = false,
                                       isAIR:Boolean = false )
        {
            super("", "", "", null);

            _appName = appName;
            _appVersion = appVersion;
            _url = url;
            _referrer = referrer;
            _documentTitle = documentTitle;
            _domainName = domainName;
            _locationPath = locationPath;
            _locationSearch = locationSearch;
            _flashVersion = flashVersion;
            _language = language;
            _languageEncoding = languageEncoding;
            _operatingSystem = operatingSystem;
            _playerType = playerType;
            _platform = platform;
            _protocol = protocol;
            _screenWidth = screenWidth;
            _screenHeight = screenHeight;
            _screenColorDepth = screenColorDepth;
            _userAgent = userAgent;
            _isInHTML = isInHTML;
            _isAIR = isAIR;
        }

        public override function get appName():String
        {
            return _appName;
        }

        public override function set appName( value:String ):void
        {
            _appName = value;
        }

        public override function get appVersion():Version
        {
            return _appVersion;
        }

        public override function set appVersion( value:Version ):void
        {
            _appVersion = value;
        }

//        ga_internal override function set url( value:String ):void
//        {
//            _url = value;
//        }

        public override function get locationSWFPath():String
        {
            return _url;
        }

        public override function get referrer():String
        {
            return _referrer;
        }

        public override function get documentTitle():String
        {
            return _documentTitle;
        }

        public override function get domainName():String
        {
            return _domainName;
        }

        public override function get locationPath():String
        {
            return _locationPath;
        }

        public override function get locationSearch():String
        {
            return _locationSearch;
        }

        public override function get flashVersion():Version
        {
            return _flashVersion;
        }

        public override function get language():String
        {
            return _language;
        }

        public override function get languageEncoding():String
        {
            return _languageEncoding;
        }

        public override function get operatingSystem():String
        {
            return _operatingSystem;
        }

        public override function get playerType():String
        {
            return _playerType;
        }

        public override function get platform():String
        {
            return _platform;
        }

        public override function get protocol():Protocols
        {
            return _protocol;
        }

        public override function get screenWidth():Number
        {
            return _screenWidth;
        }

        public override function get screenHeight():Number
        {
            return _screenHeight;
        }

        public override function get screenColorDepth():String
        {
            return _screenColorDepth;
        }

        public override function get userAgent():String
        {
            return _userAgent;
        }

        public override function set userAgent( custom:String ):void
        {
            _userAgent = custom;
        }

        public override function isInHTML():Boolean
        {
            return _isInHTML;
        }

        public override function isAIR():Boolean
        {
            return _isAIR;
        }
    }
}

update Fake class signature to last change to Environment class


package aerys.minko.type.parser
{
	public final class ParserOptions
	{
		private var _loadTextures				: Boolean	= true;
		private var _textureFilenameFunction	: Function	= null;
		private var _textureFunction			: Function	= null;

//		private var _sceneFunction				: Function	= null;

		private var _loadMeshes					: Boolean	= true;
		private var _mergeMeshes				: Boolean	= false;

		private var _loadSkins					: Boolean	= true;

		public function get loadTextures()				: Boolean	{ return _loadTextures; }
		public function get textureFilenameFunction()	: Function	{ return _textureFilenameFunction; }
		public function get textureFunction()			: Function	{ return _textureFunction; }
//		public function get sceneFunction()				: Function	{ return _sceneFunction; }

		public function get loadMeshes()				: Boolean	{ return _loadMeshes; }
		public function get mergeMeshes()				: Boolean	{ return _mergeMeshes; }

		public function get loadSkins()					: Boolean	{ return _loadSkins; }

		public function set loadTextures(value : Boolean) : void
		{
			_loadTextures = value;
		}

		public function set textureFilenameFunction(value : Function) : void
		{
			_textureFilenameFunction = value;
		}

		public function set textureFunction(value : Function) : void
		{
			_textureFunction = value;
		}

		/*public function set sceneFunction(value : Function) : void
		{
			_sceneFunction = value;
		}*/

		public function set loadMeshes(value : Boolean) : void
		{
			_loadMeshes = value;
		}

		public function set mergeMeshes(value : Boolean) : void
		{
			_mergeMeshes = value;
		}

		public function set loadSkins(value : Boolean) : void
		{
			_loadSkins = value;
		}
	}
}

package aerys.minko.type.parser
{
	public final class ParserOptions
	{
		private var _loadTextures				: Boolean	= true;
		private var _textureFilenameFunction	: Function	= null;
		private var _textureFunction			: Function	= null;

		private var _meshFunction				: Function	= null;

		private var _loadMeshes					: Boolean	= true;
		private var _mergeMeshes				: Boolean	= false;

		private var _loadSkins					: Boolean	= true;

		public function get loadTextures()				: Boolean	{ return _loadTextures; }
		public function get textureFilenameFunction()	: Function	{ return _textureFilenameFunction; }
		public function get textureFunction()			: Function	{ return _textureFunction; }

		public function get loadMeshes()				: Boolean	{ return _loadMeshes; }
		public function get meshFunction()				: Function	{ return _meshFunction; }
		public function get mergeMeshes()				: Boolean	{ return _mergeMeshes; }

		public function get loadSkins()					: Boolean	{ return _loadSkins; }

		public function set loadTextures(value : Boolean) : void
		{
			_loadTextures = value;
		}

		public function set textureFilenameFunction(value : Function) : void
		{
			_textureFilenameFunction = value;
		}

		public function set textureFunction(value : Function) : void
		{
			_textureFunction = value;
		}

		public function set meshFunction(value : Function) : void
		{
			_meshFunction = value;
		}

		public function set loadMeshes(value : Boolean) : void
		{
			_loadMeshes = value;
		}

		public function set mergeMeshes(value : Boolean) : void
		{
			_mergeMeshes = value;
		}

		public function set loadSkins(value : Boolean) : void
		{
			_loadSkins = value;
		}
	}
}

Add meshFunction that enable mesh modifications while loading it.


package aerys.minko.type.binding
{
	import aerys.minko.scene.node.ISceneNode;
	import aerys.minko.type.Signal;
	import aerys.minko.type.enum.DataProviderUsage;

	import flash.utils.Dictionary;

	public final class DataBindings
	{
		private var _owner						: ISceneNode;

		private var _providers					: Vector.<IDataProvider>;
		private var _bindingNames				: Vector.<String>;

		private var _bindingNameToValue			: Object;
		private var _bindingNameToChangedSignal	: Object;

		private var _bindingNameToProvider		: Object;
		private var _providerToBindingNames		: Dictionary;

		private var _consumers					: Vector.<IDataBindingsConsumer>;

		public function get owner() : ISceneNode
		{
			return _owner;
		}

		public function get numProviders() : uint
		{
			return _providers.length;
		}

		public function get numProperties() : uint
		{
			return _bindingNames.length;
		}

		public function DataBindings(owner : ISceneNode)
		{
			_owner = owner;

			_providers = new <IDataProvider>[];
			_bindingNames = new <String>[];
			_bindingNameToValue = {};
			_bindingNameToChangedSignal = {};
			_bindingNameToProvider = {};
			_providerToBindingNames = new Dictionary(true);
			_consumers = new <IDataBindingsConsumer>[];
		}

		public function contains(dataProvider : IDataProvider) : Boolean
		{
			return _providers.indexOf(dataProvider) != -1;
		}

		public function addProvider(provider : IDataProvider) : void
		{
			if (_providerToBindingNames[provider])
				throw new Error('This provider is already bound.');

			var dataDescriptor	: Object	= provider.dataDescriptor;

			provider.propertyChanged.add(propertyChangedHandler);
			if (provider is IDynamicDataProvider)
			{
				var dynamicProvider : IDynamicDataProvider = provider as IDynamicDataProvider;

				dynamicProvider.propertyAdded.add(addBinding);
				dynamicProvider.propertyRemoved.add(removeBinding);
			}

			_providerToBindingNames[provider] = new <String>[];
			_providers.push(provider);

			for (var propertyName : String in dataDescriptor)
				addBinding(
					provider,
					propertyName,
					dataDescriptor[propertyName],
					provider[propertyName]
				);
		}

		private function addBinding(provider		: IDataProvider,
									propertyName	: String,
									bindingName		: String,
									value			: Object) : void
		{
			var providerBindingNames	: Vector.<String> 	= _providerToBindingNames[provider];

			if (_bindingNames.indexOf(bindingName) != -1)
				throw new Error(
					'Another data provider is already declaring the \'' + bindingName
					+ '\' property.'
				);

			_bindingNameToProvider[bindingName] = provider;
			_bindingNameToValue[bindingName] = value;

			providerBindingNames.push(bindingName);
			_bindingNames.push(bindingName);

			if (_bindingNameToChangedSignal[bindingName])
				_bindingNameToChangedSignal[bindingName].execute(this, bindingName, null, value);
		}

		public function removeProvider(provider : IDataProvider) : void
		{
			var providerBindingsNames 	: Vector.<String> 	= _providerToBindingNames[provider];

			if (providerBindingsNames == null)
				throw new ArgumentError('Unknown provider.');

			var numProviders : uint = _providers.length - 1;

			provider.propertyChanged.remove(propertyChangedHandler);
			if (provider is IDynamicDataProvider)
			{
				var dynamicProvider : IDynamicDataProvider = provider as IDynamicDataProvider;

				dynamicProvider.propertyAdded.remove(addBinding);
				dynamicProvider.propertyRemoved.remove(removeBinding);
			}

			_providers[_providers.indexOf(provider)] = _providers[numProviders];
			_providers.length = numProviders;
			delete _providerToBindingNames[provider];

			var dataDescriptor : Object = provider.dataDescriptor;

			for (var propertyName : String in dataDescriptor)
				removeBinding(
					provider,
					propertyName,
					dataDescriptor[propertyName],
					provider[propertyName]
				);
		}

		public function removeBinding(provider		: IDataProvider,
									  propertyName	: String,
									  bindingName	: String,
									  value			: Object) : void
		{
			var numBindings 	: uint 		= _bindingNames.length - 1;
			var changedSignal 	: Signal 	= _bindingNameToChangedSignal[bindingName] as Signal;

			delete _bindingNameToValue[bindingName];
			delete _bindingNameToProvider[bindingName];

			_bindingNames[_bindingNames.indexOf(bindingName)] = _bindingNames[numBindings];
			_bindingNames.length = numBindings;

			if (changedSignal != null)
				changedSignal.execute(this, bindingName, value, null);
		}

		public function removeAllProviders() : void
		{
			var numProviders : uint = this.numProviders;

			for (var providerId : int = numProviders - 1; providerId >= 0; --providerId)
				removeProvider(getProviderAt(providerId));
		}

		public function hasCallback(bindingName	: String,
									callback	: Function) : Boolean
		{
			var signal : Signal = _bindingNameToChangedSignal[bindingName];

			return signal != null && signal.hasCallback(callback);
		}

		public function addCallback(bindingName : String,
									callback	: Function) : void
		{
			_bindingNameToChangedSignal[bindingName] ||= new Signal(
				'DataBindings.changed[' + bindingName + ']'
			);

			Signal(_bindingNameToChangedSignal[bindingName]).add(callback);
		}

		public function removeCallback(bindingName	: String,
									   callback		: Function) : void
		{
			var signal : Signal = _bindingNameToChangedSignal[bindingName];

			if (!signal)
				throw new ArgumentError('Unkown property \'' + bindingName + '\'.');

			signal.remove(callback);

			if (signal.numCallbacks == 0)
				delete _bindingNameToChangedSignal[bindingName];
		}

		public function getProviderAt(index : uint) : IDataProvider
		{
			return _providers[index];
		}

		public function getProviderByBindingName(bindingName : String) : IDataProvider
		{
			if (_bindingNameToProvider[bindingName] == null)
				throw new ArgumentError('Unkown property \'' + bindingName + '\'.');

			return _bindingNameToProvider[bindingName];
		}

		public function propertyExists(bindingName : String) : Boolean
		{
			return _bindingNameToValue.hasOwnProperty(bindingName);
		}

		public function getProperty(bindingName : String) : *
		{
			if (_bindingNames.indexOf(bindingName) < 0)
				throw new Error('The property \'' + bindingName + '\' does not exist.');

			return _bindingNameToValue[bindingName];
		}

		public function getPropertyName(bindingIndex : uint) : String
		{
			if (bindingIndex > numProperties)
				throw new ArgumentError('No such binding');

			return _bindingNames[bindingIndex];
		}

		public function copySharedProvidersFrom(source : DataBindings) : void
		{
			var numProviders : uint = source._providers.length;

			for (var providerId : uint = 0; providerId < numProviders; ++providerId)
			{
				var provider : IDataProvider = source._providers[providerId];

				if (provider.usage == DataProviderUsage.SHARED)
					addProvider(provider);
			}
		}

		private function propertyChangedHandler(source 			: IDataProvider,
												propertyName 	: String,
												bindingName		: String,
												value			: Object) : void
		{
			if (propertyName == null)
				throw new Error('DataProviders must change only one property at a time.');

			var oldValue : Object = _bindingNameToValue[bindingName];

			_bindingNameToValue[bindingName] = value;

			var numConsumers : uint = _consumers.length;
			for (var consumerId : uint = 0; consumerId < numConsumers; ++consumerId)
				_consumers[consumerId].setProperty(bindingName, value);

			if (_bindingNameToChangedSignal[bindingName])
				_bindingNameToChangedSignal[bindingName].execute(
					this,
					bindingName,
					oldValue,
					value
				);
		}

		public function addConsumer(consumer : IDataBindingsConsumer) : void
		{
			_consumers.push(consumer);

			var numProperties : uint = this.numProperties;
			for (var propertyId : uint = 0; propertyId < numProperties; ++propertyId)
			{
				var bindingName : String = _bindingNames[propertyId];

				consumer.setProperty(bindingName, _bindingNameToValue[bindingName]);
			}
		}

		public function removeConsumer(consumer : IDataBindingsConsumer) : void
		{
			var numConsumers : uint = _consumers.length - 1;
			var index : int = _consumers.indexOf(consumer);

			if (index < 0)
				throw new Error('This consumer does not exist.');

			_consumers[index] = _consumers[numConsumers];
			_consumers.length = numConsumers;
		}
	}
}

package aerys.minko.type.binding
{
	import aerys.minko.scene.node.ISceneNode;
	import aerys.minko.type.Signal;
	import aerys.minko.type.enum.DataProviderUsage;

	import flash.utils.Dictionary;

	public final class DataBindings
	{
		private var _owner						: ISceneNode;

		private var _providers					: Vector.<IDataProvider>;
		private var _bindingNames				: Vector.<String>;

		private var _bindingNameToValue			: Object;
		private var _bindingNameToChangedSignal	: Object;

		private var _bindingNameToProvider		: Object;
		private var _providerToBindingNames		: Dictionary;

		private var _consumers					: Vector.<IDataBindingsConsumer>;

		public function get owner() : ISceneNode
		{
			return _owner;
		}

		public function get numProviders() : uint
		{
			return _providers.length;
		}

		public function get numProperties() : uint
		{
			return _bindingNames.length;
		}

		public function DataBindings(owner : ISceneNode)
		{
			_owner = owner;

			_providers = new <IDataProvider>[];
			_bindingNames = new <String>[];
			_bindingNameToValue = {};
			_bindingNameToChangedSignal = {};
			_bindingNameToProvider = {};
			_providerToBindingNames = new Dictionary(true);
			_consumers = new <IDataBindingsConsumer>[];
		}

		public function contains(dataProvider : IDataProvider) : Boolean
		{
			return _providers.indexOf(dataProvider) != -1;
		}

		public function addProvider(provider : IDataProvider) : void
		{
			if (_providerToBindingNames[provider])
				throw new Error('This provider is already bound.');

			var dataDescriptor	: Object	= provider.dataDescriptor;

			provider.propertyChanged.add(propertyChangedHandler);
			if (provider is IDynamicDataProvider)
			{
				var dynamicProvider : IDynamicDataProvider = provider as IDynamicDataProvider;

				dynamicProvider.propertyAdded.add(addBinding);
				dynamicProvider.propertyRemoved.add(removeBinding);
			}

			_providerToBindingNames[provider] = new <String>[];
			_providers.push(provider);

			for (var propertyName : String in dataDescriptor)
				addBinding(
					provider,
					propertyName,
					dataDescriptor[propertyName],
					provider[propertyName]
				);
		}

		private function addBinding(provider		: IDataProvider,
									propertyName	: String,
									bindingName		: String,
									value			: Object) : void
		{
			var providerBindingNames	: Vector.<String> 	= _providerToBindingNames[provider];

			if (_bindingNames.indexOf(bindingName) != -1)
				throw new Error(
					'Another data provider is already declaring the \'' + bindingName
					+ '\' property.'
				);

			_bindingNameToProvider[bindingName] = provider;
			_bindingNameToValue[bindingName] = value;

			providerBindingNames.push(bindingName);
			_bindingNames.push(bindingName);

            var numConsumers : uint = _consumers.length;
            for (var consumerId : uint = 0; consumerId < numConsumers; ++consumerId)
                _consumers[consumerId].setProperty(bindingName, value);

			if (_bindingNameToChangedSignal[bindingName])
				_bindingNameToChangedSignal[bindingName].execute(this, bindingName, null, value);
		}

		public function removeProvider(provider : IDataProvider) : void
		{
			var providerBindingsNames 	: Vector.<String> 	= _providerToBindingNames[provider];

			if (providerBindingsNames == null)
				throw new ArgumentError('Unknown provider.');

			var numProviders : uint = _providers.length - 1;

			provider.propertyChanged.remove(propertyChangedHandler);
			if (provider is IDynamicDataProvider)
			{
				var dynamicProvider : IDynamicDataProvider = provider as IDynamicDataProvider;

				dynamicProvider.propertyAdded.remove(addBinding);
				dynamicProvider.propertyRemoved.remove(removeBinding);
			}

			_providers[_providers.indexOf(provider)] = _providers[numProviders];
			_providers.length = numProviders;
			delete _providerToBindingNames[provider];

			var dataDescriptor : Object = provider.dataDescriptor;

			for (var propertyName : String in dataDescriptor)
				removeBinding(
					provider,
					propertyName,
					dataDescriptor[propertyName],
					provider[propertyName]
				);
		}

		public function removeBinding(provider		: IDataProvider,
									  propertyName	: String,
									  bindingName	: String,
									  value			: Object) : void
		{
			var numBindings 	: uint 		= _bindingNames.length - 1;
			var changedSignal 	: Signal 	= _bindingNameToChangedSignal[bindingName] as Signal;

			delete _bindingNameToValue[bindingName];
			delete _bindingNameToProvider[bindingName];

			_bindingNames[_bindingNames.indexOf(bindingName)] = _bindingNames[numBindings];
			_bindingNames.length = numBindings;

            var numConsumers : uint = _consumers.length;
            for (var consumerId : uint = 0; consumerId < numConsumers; ++consumerId)
                _consumers[consumerId].setProperty(bindingName, null);

			if (changedSignal != null)
				changedSignal.execute(this, bindingName, value, null);
		}

		public function removeAllProviders() : void
		{
			var numProviders : uint = this.numProviders;

			for (var providerId : int = numProviders - 1; providerId >= 0; --providerId)
				removeProvider(getProviderAt(providerId));
		}

		public function hasCallback(bindingName	: String,
									callback	: Function) : Boolean
		{
			var signal : Signal = _bindingNameToChangedSignal[bindingName];

			return signal != null && signal.hasCallback(callback);
		}

		public function addCallback(bindingName : String,
									callback	: Function) : void
		{
			_bindingNameToChangedSignal[bindingName] ||= new Signal(
				'DataBindings.changed[' + bindingName + ']'
			);

			Signal(_bindingNameToChangedSignal[bindingName]).add(callback);
		}

		public function removeCallback(bindingName	: String,
									   callback		: Function) : void
		{
			var signal : Signal = _bindingNameToChangedSignal[bindingName];

			if (!signal)
				throw new ArgumentError('Unkown property \'' + bindingName + '\'.');

			signal.remove(callback);

			if (signal.numCallbacks == 0)
				delete _bindingNameToChangedSignal[bindingName];
		}

		public function getProviderAt(index : uint) : IDataProvider
		{
			return _providers[index];
		}

		public function getProviderByBindingName(bindingName : String) : IDataProvider
		{
			if (_bindingNameToProvider[bindingName] == null)
				throw new ArgumentError('Unkown property \'' + bindingName + '\'.');

			return _bindingNameToProvider[bindingName];
		}

		public function propertyExists(bindingName : String) : Boolean
		{
			return _bindingNameToValue.hasOwnProperty(bindingName);
		}

		public function getProperty(bindingName : String) : *
		{
			if (_bindingNames.indexOf(bindingName) < 0)
				throw new Error('The property \'' + bindingName + '\' does not exist.');

			return _bindingNameToValue[bindingName];
		}

		public function getPropertyName(bindingIndex : uint) : String
		{
			if (bindingIndex > numProperties)
				throw new ArgumentError('No such binding');

			return _bindingNames[bindingIndex];
		}

		public function copySharedProvidersFrom(source : DataBindings) : void
		{
			var numProviders : uint = source._providers.length;

			for (var providerId : uint = 0; providerId < numProviders; ++providerId)
			{
				var provider : IDataProvider = source._providers[providerId];

				if (provider.usage == DataProviderUsage.SHARED)
					addProvider(provider);
			}
		}

		private function propertyChangedHandler(source 			: IDataProvider,
												propertyName 	: String,
												bindingName		: String,
												value			: Object) : void
		{
			if (propertyName == null)
				throw new Error('DataProviders must change only one property at a time.');

			var oldValue : Object = _bindingNameToValue[bindingName];

			_bindingNameToValue[bindingName] = value;

			var numConsumers : uint = _consumers.length;
			for (var consumerId : uint = 0; consumerId < numConsumers; ++consumerId)
				_consumers[consumerId].setProperty(bindingName, value);

			if (_bindingNameToChangedSignal[bindingName])
				_bindingNameToChangedSignal[bindingName].execute(
					this,
					bindingName,
					oldValue,
					value
				);
		}

		public function addConsumer(consumer : IDataBindingsConsumer) : void
		{
			_consumers.push(consumer);

			var numProperties : uint = this.numProperties;
			for (var propertyId : uint = 0; propertyId < numProperties; ++propertyId)
			{
				var bindingName : String = _bindingNames[propertyId];

				consumer.setProperty(bindingName, _bindingNameToValue[bindingName]);
			}
		}

		public function removeConsumer(consumer : IDataBindingsConsumer) : void
		{
			var numConsumers : uint = _consumers.length - 1;
			var index : int = _consumers.indexOf(consumer);

			if (index < 0)
				throw new Error('This consumer does not exist.');

			_consumers[index] = _consumers[numConsumers];
			_consumers.length = numConsumers;
		}
	}
}

fix missing calls to IDataBindingsConsumer.setProperty() when a binding is added/removed


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

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

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

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

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

			indexData.position = startPosition;

			return indexId >= numIndices;
		}

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

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

				return;
			}

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

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

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

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

				// iterators & limits
				var localVertexId		: uint;

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

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

					if (remainingIndices < 3)
						break ;

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

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

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

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

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

					if (remainingVertices < neededVerticesCount)
						break ;

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

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

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

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

						partialIndexData.writeShort(newVertexIds[localVertexId]);
					}

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

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

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

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

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

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

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

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

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

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

					newLimit = (1 + newVertexId) * bytesPerVertex;

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

				oldVertexIdToNewVertexId[oldVertexId] = newVertexId;
			}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

			indexData.position = startPosition;

			return indexId >= numIndices;
		}

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

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

				return;
			}

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

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

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

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

				// iterators & limits
				var localVertexId		: uint;

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

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

					if (remainingIndices < 3)
						break ;

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

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

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

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

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

					if (remainingVertices < neededVerticesCount)
						break ;

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

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

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

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

						partialIndexData.writeShort(newVertexIds[localVertexId]);
					}

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

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

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

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

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

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

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

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

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

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

					newLimit = (1 + newVertexId) * bytesPerVertex;

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

				oldVertexIdToNewVertexId[oldVertexId] = newVertexId;
			}

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

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

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

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

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

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

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

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

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

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

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

update GeometrySanitizer methods to use ByteArray.readUnsignerShort() to read indices