//==============================================================================
// PictureLive2D.js ver.1.03
//==============================================================================

/*:
 * @plugindesc Live2Dアニメーションプラグイン
 * @author 奏ねこま（おとぶきねこま）
 * @url http://makonet.sakura.ne.jp/rpg_tkool
 * @target MZ
 *
 * @param json file
 * @type string[]
 * @default []
 * @desc Live2Dファイル(*.model3.json)
 *
 * @param disable auto loading
 * @type boolean
 * @default false
 * @desc Live2Dの自動読み込みを無効にする
 *
 * @help
 * 本プラグインの利用方法については下記マニュアルをご参照ください。
 * https://makonet.sakura.ne.jp/rpg_tkool/MVMZ/PictureLive2D/document.html
 * 
 * ------------------------------------------------------------------------------
 *   本プラグインの利用はRPGツクール/RPG Makerの正規ユーザーに限られます。
 *   商用、非商用、有償、無償、一般向け、成人向けを問わず利用可能です。
 *   ご利用の際に連絡や報告は必要ありません。また、製作者名の記載等も不要です。
 *   プラグインを導入した作品に同梱する形以外での再配布、転載はご遠慮ください。
 *   本プラグインにより生じたいかなる問題についても一切の責任を負いかねます。
 * ------------------------------------------------------------------------------
 *                                              Copylight (c) 2022 Nekoma Otobuki
 *                                         http://makonet.sakura.ne.jp/rpg_tkool/
 *                                                  https://twitter.com/koma_neko
 */

!function() {
    'use strict';

    let $p = function parse(param) {
        try {
            param = JSON.parse(param);
        }
        catch (e) {}
        if (Array.isArray(param)) {
            param = param.map(value => parse(value));
        } else if (typeof param == 'object') {
            for (let key in param) {
                param[key] = parse(param[key]);
            }
        }
        return param;
    }({
        ...PluginManager.parameters('PictureLive2D'),
        'live2d data': {}
    });

    function load() {
        let loader = new (PIXI.Loader || PIXI.loaders.Loader)();
        for (let file of $p['json file']) {
            let name = String(file).replace(/\.(?:json|model3.json)$/i, '');
            if (!$p['disable auto loading']) {
                $p['live2d data'][name] = null;
                loader.add(name, `img/live2d/${name}.model3.json`);
            } else {
                $p['live2d data'][name] = {};
            }
        }
        loader.load((loader, resource) => {
            for (let name in $p['live2d data']) {
                if (resource[name]) {
                    $p['live2d data'][name] = resource[name].live2dData;
                }
            }
        });
    }

    if (!window.Live2DCubismCore) {
        let js = null;
        for (let element of document.getElementsByTagName('script')) {
            if (element.src.match('live2dcubismcore.min.js')) {
                js = element;
            }
        }
        if (!js) {
            js = document.createElement('script');
            js.type = 'text/javascript';
            js.src  = 'js/libs/live2dcubismcore.min.js';
            document.body.appendChild(js);
        }
        js.addEventListener('load', load);
    } else {
        load();
    }

    class MosaicFilter extends PIXI.Filter {
        constructor(size = 10) {
            let vertex   = 'attribute vec2 aVertexPosition;attribute vec2 aTextureCoord;uniform mat3 projectionMatrix;varying vec2 vTextureCoord;void main(void){gl_Position=vec4((projectionMatrix*vec3(aVertexPosition,1.0)).xy,0.0,1.0);vTextureCoord=aTextureCoord;}';
            let fragment = 'precision mediump float;varying vec2 vTextureCoord;uniform vec2 size;uniform sampler2D uSampler;uniform vec4 filterArea;vec2 mapCoord(vec2 coord){coord*=filterArea.xy;coord+=filterArea.zw;return coord;}vec2 unmapCoord(vec2 coord){coord-=filterArea.zw;coord/=filterArea.xy;return coord;}vec2 pixelate(vec2 coord, vec2 size){return floor(coord / size) * size;}void main(void){vec2 coord=mapCoord(vTextureCoord);coord=pixelate(coord, size);coord=unmapCoord(coord);gl_FragColor=texture2D(uSampler, coord);}';
            super(vertex, fragment);
            this.uniforms.size = [size, size];
        }
    }

    function convars(obj) {
        if (typeof obj == 'string') {
            let _obj = obj.replace(/\\v\[(\d+)\]/gi, (match, p1) => {
                return $gameVariables.value(Number(p1));
            });
            obj = _obj != obj ? convars(_obj) : _obj;
        } else if (obj instanceof Array) {
            obj = obj.map(value => convars(value));
        } else if (typeof obj == 'object') {
            obj = {...obj};
            for (let key in obj) {
                obj[key] = convars(obj[key]);
            }
        }
        return obj;
    }

    //==============================================================================
    // Game_Live2D
    //==============================================================================

    class Game_Live2D {
        constructor() {
            this.init();
        }

        static live2dData() {
            return $p['live2d data'];
        }

        static fullName(name = '') {
            for (let key in this.live2dData()) {
                if (key.match(`(?:^|/)${name}$`)) {
                    return key;
                }
            }
            return '';
        }

        static loadModel(name) {
            let fullName = this.fullName(name);
            let data = $p['live2d data'][fullName];
            if (!data || 'Moc' in data) return;
            $p['live2d data'][fullName] = null;
            let loader = new (PIXI.Loader || PIXI.loaders.Loader)();
            loader.add(fullName, `img/live2d/${fullName}.model3.json`);
            loader.load((loader, resource) => {
                $p['live2d data'][fullName] = resource[fullName].live2dData;
            });
        }

        get model() { return this._model; }
        get track() { return this._track; }
        get timeScale() { return this._timeScale; }
        get weight() { return this._weight; }
        get mix() { return this._mix; }
        get autoEyeBlink() { return this._autoEyeBlink; }
        get expression() { return this._expression; }
        get customExpression() { return this._customExpression; }
        get color() { return this._color; }
        get mosaic() { return this._mosaic; }
        get offset() { return this._offset; }
        get scale() { return this._scale; }
        get visible() { return this._visible; }
        get snapshot() { return this._snapshot; }

        set snapshot(value) { this._snapshot = value; }

        init() {
            this._model            = '';
            this._track            = {};
            this._timeScale        = 1;
            this._weight           = [];
            this._mix              = {};
            this._autoEyeBlink     = {};
            this._expression       = '';
            this._customExpression = null;
            this._color            = {};
            this._mosaic           = {};
            this._offset           = { x: 0, y: 0 };
            this._scale            = { x: 1, y: 1 };
            this._visible          = true;
            this._snapshot         = null;
        }

        setModel(name = '') {
            name = convars(name);
            this.init();
            this._model = `${name}_${Date.now()}`;
            return this;
        }

        setAnimation(index, animations, ...args) {
            [index, animations, args] = convars([index, animations, args]);
            this._track = {...this._track};
            let list, order, continuance, interrupt;
            if (typeof animations == 'string') {
                list        = [animations];
                order       = 'sequential';
                continuance = args[0] || 'continue';
                interrupt   = typeof args[1] == 'boolean' ? args[1] : args[1] == 'true';
            } else {
                list        = animations;
                order       = args[0] || 'sequential';
                continuance = args[1] || 'continue';
                interrupt   = typeof args[2] == 'boolean' ? args[2] : args[2] == 'true';
            }
            this._track[index] = { list: [], order: order, continuance: continuance, interrupt: interrupt };
            list.forEach(animation => {
                let [name, options] = `${animation}/`.replace(/ +/, '').split(/\//);
                let times           = 1;
                let timeScale       = 1;
                let weight          = 1;
                for (let option of options.replace(/ +/, '').split(/,/)) {
                    if (option.match(/^times=(\d+)$/i)) {
                        times = Number(RegExp.$1);
                    }
                    if (option.match(/^timeScale=(\d+\.?\d*)$/i)) {
                        timeScale = Number(RegExp.$1);
                    }
                    if (option.match(/^weight=(\d+\.?\d*)$/i)) {
                        weight = Number(RegExp.$1);
                    }
                }
                this._track[index].list.push({ name: name, times: times, timeScale: timeScale, weight: weight });
            });
            return this;
        }

        clearAnimation(index, mixTime = 0) {
            [index, mixTime] = convars([index, mixTime]);
            this._track = {...this._track};
            this._track[index] = { clear: true, mixTime: mixTime };
            return this;
        }

        setTimeScale(value) {
            value = convars(value);
            this._timeScale = Number(value);
            return this;
        }

        setWeight(index, value) {
            [index, value] = convars([index, value]);
            this._weight = this._weight.slice();
            this._weight[index] = Number(value);
            return this;
        }

        setMix(...args) {
            args = convars(args);
            this._mix = {...this._mix};
            let from, to, duration;
            if (args.length == 1) {
                from     = '/default';
                to       = '';
                duration = Number(args[0]);
            } else {
                from     = args[0];
                to       = args[1];
                duration = args.length > 2 ? Number(args[2]) : null;
            }
            if (duration !== null) {
                this._mix[`${from}/${to}`] = duration;
            } else {
                delete this._mix[`${from}/${to}`];
            }
            return this;
        }

        setAutoEyeBlink(enabled, options) {
            [enabled, options] = convars([enabled, options]);
            if (options) {
                for (let key in options) {
                    options[key] = Number(options[key]);
                }
            }
            this._autoEyeBlink = {
                enabled: typeof enabled == 'boolean' ? enabled : enabled == 'true',
                options: options
            };
            return this;
        }
        
        setExpression(name) {
            name = convars(name);
            this._expression = name;
            return this;
        }

        setCustomExpression(exp3) {
            exp3 = convars(exp3);
            this._customExpression = { Data: exp3 };
            return this;
        }

        clearExpression() {
            this._expression = '';
            return this;
        }

        setColor(...args) {
            args = convars(args);
            this._color = {...this._color};
            let id, r, g, b, a;
            if (args.length == 4) {
                id = '/default/';
                r  = Number(args[0]);
                g  = Number(args[1]);
                b  = Number(args[2]);
                a  = Number(args[3]);
            } else {
                id = args[0];
                r  = Number(args[1]);
                g  = Number(args[2]);
                b  = Number(args[3]);
                a  = Number(args[4]);
            }
            if (r != 1 || g != 1 || b != 1 || a != 1) {
                this._color[id] = [r, g, b, a];
            } else {
                delete this._color[id];
            }
            return this;
        }

        setMosaic(...args) {
            args = convars(args);
            this._mosaic = {...this._mosaic};
            let id, size;
            if (args.length == 1) {
                id   = '/default/';
                size = Number(args[0]);
            } else {
                id   = args[0];
                size = Number(args[1]);
            }
            if (size > 1) {
                this._mosaic[id] = size;
            } else {
                delete this._mosaic[id];
            }
            return this;
        }

        setOffset(x, y) {
            [x, y] = convars([x, y]);
            this._offset = { x: Number(x), y: Number(y) };
            return this;
        }

        setScale(x, y) {
            [x, y] = convars([x, y]);
            this._scale = { x: Number(x), y: Number(y) };
            return this;
        }

        setVisible(visible) {
            this._visible = !!convars(visible);
            return this;
        }
    }

    window.Game_Live2D = Game_Live2D;

    //==============================================================================
    // Sprite_Live2D
    //==============================================================================

    class Sprite_Live2D extends Sprite {
        constructor(...args) {
            super();
            this._pictureId = typeof args[0] == 'number' ? args[0] : 0;
            this._live2d    = args[0] instanceof Game_Live2D ? args[0] : null;
            this._isRestore = false;
            this.init();
        }

        init() {
            this.removeChild(this._data);
            this._data             = null;
            this._model            = '';
            this._track            = {};
            this._timeScale        = 1;
            this._weight           = [];
            this._mix              = {};
            this._autoEyeBlink     = null;
            this._expression       = '';
            this._customExpression = null;
            this._color            = {};
            this._mosaic           = {};
            this._offset           = null;
            this._scale            = null;
        }

        live2d() {
            if (this._live2d) {
                return this._live2d;
            }
            let picture = $gameScreen.picture(this._pictureId);
            return picture ? picture._live2d : null;
        }

        update() {
            let live2d = this.live2d();
            this.updateModel(live2d);
            if (this._data) {
                this.updateAutoEyeBlink(live2d);
                this.updateColor(live2d);
                this.updateMosaic(live2d);
                this.updateOffset(live2d);
                this.updateScale(live2d);
                this.updateVisible(live2d);
                if (!this._isRestore) {
                    this.updateTimeScale(live2d);
                    this.updateWeight(live2d);
                    this.updateMix(live2d);
                    this.updateExpression(live2d);
                    this.updateCustomExpression(live2d);
                    this.updateAnimation(live2d);
                    this.snapshot(live2d);
                } else {
                    this.restore(live2d);
                }
            }
            super.update();
        }

        updateModel(live2d) {
            if (live2d) {
                if (live2d.model != this._model) {
                    this.init();
                    let model = live2d.model.replace(/_\d+$/, '');
                    if (model) {
                        let fullName = Game_Live2D.fullName(model);
                        let data = Game_Live2D.live2dData()[fullName];
                        if (data) {
                            if (data.loading == 0) {
                                this._data = new PIXI.Live2D(data);
                                this._data.animation.addListener('start', this.onStart.bind(this));
                                this._data.animation.addListener('end', this.onEnd.bind(this));
                                this.addChild(this._data);
                                if (live2d.snapshot) {
                                    this._isRestore = true;
                                }
                            } else if ('Moc' in data == false) {
                                Game_Live2D.loadModel(model);
                            }
                        } else if (fullName in Game_Live2D.live2dData() == false) {
                            throw Error(`'${model}' is unknown model.`);
                        }
                    }
                    if (!model || this._data) {
                        this._model = live2d.model;
                    }
                }
            } else if (this._model) {
                this.init();
            }
        }

        updateTimeScale(live2d) {
            if (live2d.timeScale == this._timeScale) return;
            this._timeScale = live2d.timeScale;
            this._data.animation.setTimeScale(this._timeScale);
        }

        updateWeight(live2d) {
            if (live2d.weight == this._weight) return;
            this._weight = live2d.weight;
            this._weight.forEach((value, index) => {
                this._data.animation.setWeight(index, value);
            });
        }

        updateMix(live2d) {
            if (live2d.mix == this._mix) return;
            for (let key in live2d.mix) {
                if (live2d.mix[key] != this._mix[key]) {
                    let time = live2d.mix[key];
                    if (key == '/default/') {
                        this._data.animation.setDefaultMix(time);
                    } else {
                        let [from, to] = key.split('/');
                        this._data.animation.setMix(from, to, time);
                    }
                }
            }
            for (let key in this._mix) {
                if (key in live2d.mix == false) {
                    if (key == '/default/') {
                        this._data.animation.setDefaultMix(0);
                    } else {
                        let [from, to] = key.split('/');
                        this._data.animation.setMix(from, to, 0);
                    }
                }
            }
            this._mix = live2d.mix;
        }

        updateAutoEyeBlink(live2d) {
            if (live2d.autoEyeBlink == this._autoEyeBlink) return;
            this._autoEyeBlink = live2d.autoEyeBlink;
            this._data.animation.setAutoEyeBlink(this._autoEyeBlink.enabled, this._autoEyeBlink.options);
        }

        updateExpression(live2d) {
            if (live2d.expression == this._expression) return;
            this._expression = live2d.expression;
            if (this._expression) {
                this._data.direction.setExpression(this._expression);
            } else {
                this._data.direction.clearExpression();
            }
        }

        updateCustomExpression(live2d) {
            if (live2d.customExpression == this._customExpression) return;
            this._customExpression = live2d.customExpression;
            if (this._customExpression) {
                this._data.direction.setCustomExpression(this._customExpression);
            }
        }

        updateColor(live2d) {
            if (live2d.color == this._color) return;
            for (let id in live2d.color) {
                if (live2d.color[id] != this._color[id]) {
                    let color  = live2d.color[id];
                    let filter = new PIXI.filters.ColorMatrixFilter();
                    filter.matrix = [
                        color[0], 0, 0, 0, 0,
                        0, color[1], 0, 0, 0,
                        0, 0, color[2], 0, 0,
                        0, 0, 0, color[3], 0
                    ];
                    let target = (id == '/default/') ? this._data : this.getArtMesh(id);
                    let filters = (target.filters || []).filter(filter => {
                        return filter instanceof PIXI.filters.ColorMatrixFilter == false;
                    });
                    filters.push(filter);
                    target.filters = filters;
                }
            }
            for (let id in this._color) {
                if (id in live2d.color == false) {
                    let target = (id == '/default/') ? this._data : this.getArtMesh(id);
                    let filters = (target.filters || []).filter(filter => {
                        return filter instanceof PIXI.filters.ColorMatrixFilter == false;
                    });
                    target.filters = filters.length > 0 ? filters : null;
                }
            }
            this._color = live2d.color;
        }

        updateMosaic(live2d) {
            if (live2d.mosaic == this._mosaic) return;
            for (let id in live2d.mosaic) {
                if (live2d.mosaic[id] != this._mosaic[id]) {
                    let size   = live2d.mosaic[id];
                    let filter = new MosaicFilter(size);
                    let target = (id == '/default/') ? this._data : this.getArtMesh(id);
                    let filters = (target.filters || []).filter(filter => {
                        return filter instanceof MosaicFilter == false;
                    });
                    filters.push(filter);
                    target.filters = filters;
                }
            }
            for (let id in this._mosaic) {
                if (id in live2d.mosaic == false) {
                    let target = (id == '/default/') ? this._data : this.getArtMesh(id);
                    let filters = (target.filters || []).filter(filter => {
                        return filter instanceof MosaicFilter == false;
                    });
                    target.filters = filters.length > 0 ? filters : null;
                }
            }
            this._mosaic = live2d.mosaic;
        }

        updateOffset(live2d) {
            if (live2d.offset == this._offset) return;
            this._offset = live2d.offset;
            this.x = this._offset.x;
            this.y = this._offset.y;
        }

        updateScale(live2d) {
            if (live2d.scale == this._scale) return;
            this._scale = live2d.scale;
            this.scale.x = this._scale.x;
            this.scale.y = this._scale.y;
        }

        updateVisible(live2d) {
            this.visible = live2d.visible;
        }

        updateAnimation(live2d) {
            if (live2d.track == this._track) return;
            for (let id in live2d.track) {
                if (live2d.track[id] != this._track[id]) {
                    let track = live2d.track[id];
                    if (track.clear) {
                        this._data.animation.clearAnimation(Number(id), track.mixTime);
                        continue;
                    }
                    let list  = [];
                    let loop  = track.continuance != 'none';
                    for (let animation of track.list) {
                        for (let j = 0; j < animation.times; j++) {
                            list.push(animation);
                        }
                    }
                    if (track.order == 'shuffle') {
                        let _list = [...list];
                        list = [];
                        while (_list.length > 0) {
                            let index = Math.floor(Math.random() * _list.length);
                            list.push(_list.splice(index, 1)[0]);
                        }
                    } else if (track.order == 'random') {
                        let index = Math.floor(Math.random() * list.length);
                        list = [list[index]];
                    }
                    list.forEach((animation, index) => {
                        let entry;
                        if (index == 0 && track.interrupt) {
                            entry = this._data.animation.setAnimation(Number(id), animation.name, loop);
                        } else {
                            entry = this._data.animation.addAnimation(Number(id), animation.name, loop);
                        }
                        entry.timeScale = animation.timeScale;
                        entry.weight    = animation.weight;
                    });
                }
            }
            this._track = live2d.track;
        }

        getArtMesh(id) {
            let index = this._data.model.drawables.ids.indexOf(id);
            return this._data._meshes[index];
        }

        onStart(trackInfo) {
            if (trackInfo.track.entries.length == 1) {
                this.resetAnimation(trackInfo.index);
            }
        }

        onEnd(trackInfo) {
            this.onStart(trackInfo);
        }

        resetAnimation(index) {
            let live2d = this.live2d();
            if (live2d && live2d.track[index]) {
                if (live2d.track[index] == this._track[index]) {
                    if (this._track[index].continuance == 'reset') {
                        live2d.track[index].interrupt = false;
                        this._track = {...this._track};
                        this._track[index] = null;
                    }
                }
            }
        }

        snapshot(live2d) {
            live2d.snapshot = this._data.snapshot();
        }

        restore(live2d) {
            this._data.restore(live2d.snapshot);
            this._timeScale  = live2d.timeScale;
            this._weight     = live2d.weight;
            this._mix        = live2d.mix;
            this._expression = live2d.expression;
            this._track      = live2d.track;
            this._isRestore = false;
        }
    }

    window.Sprite_Live2D = Sprite_Live2D;

    //==============================================================================
    // Game_Screen
    //==============================================================================

    Game_Screen.prototype.live2d = function(id) {
        let picture = this.picture(id);
        if (!picture._live2d) {
            picture._live2d = new Game_Live2D();
        }
        return picture._live2d;
    };

    //==============================================================================
    // Game_Picture
    //==============================================================================

    (__initialize => {
        Game_Picture.prototype.initialize = function() {
            __initialize.apply(this, arguments);
            this._live2d = null;
        };
    })(Game_Picture.prototype.initialize);

    (__erase => {
        Game_Picture.prototype.erase = function() {
            __erase.apply(this, arguments);
            this._live2d = null;
        };
    })(Game_Picture.prototype.erase);

    //==============================================================================
    // Sprite_Picture
    //==============================================================================

    (__initialize => {
        Sprite_Picture.prototype.initialize = function(pictureId) {
            __initialize.apply(this, arguments);
            this.addChild(new Sprite_Live2D(pictureId))
        };
    })(Sprite_Picture.prototype.initialize);
}();

!function() {
    'use strict';

    //==============================================================================
    // PIXI.mesh.Mesh -> PIXI.SimpleMesh
    //==============================================================================

    if (!PIXI.SimpleMesh) {
        PIXI.SimpleMesh = class extends PIXI.mesh.Mesh {
            constructor(texture, vertices, uvs, indices, drawMode) {
                super(texture, vertices, uvs, indices, drawMode);
                this.state  = {};
                this.shader = {};
                this.texture.baseTexture.resource = {
                    source: this.texture.baseTexture.source
                };
                Object.defineProperty(this.state, 'blendMode', {
                    set: value => this.blendMode = value
                });
            }
        }
    }

    //==============================================================================
    // Mesh
    //==============================================================================

    class Mesh extends PIXI.SimpleMesh {
        constructor(texture, vertices, uvs, indices, drawMode) {
            super(texture, vertices, uvs, indices, drawMode);
            this.uvs     = this.uvs || this.uvBuffer.data;
            this.indices = this.indices || this.geometry.indexBuffer.data;
            this.orderIndex = 0;
        }

        _render(renderer) {
            if (!this.state.culling) {
                super._render(renderer);
                return;
            }
            let gl = renderer.gl;
            gl.frontFace(gl.getParameter(gl.FRAMEBUFFER_BINDING) ? gl.CW : gl.CCW);
            super._render(renderer);
            gl.frontFace(gl.CCW);
        }

        _renderWebGL(renderer) {
            if (!this.state.culling) {
                super._renderWebGL(renderer);
                return;
            }
            let gl = renderer.gl;
            gl.enable(gl.CULL_FACE);
            gl.frontFace(gl.getParameter(gl.FRAMEBUFFER_BINDING) ? gl.CW : gl.CCW);
            super._renderWebGL(renderer);
            gl.frontFace(gl.CCW);
            gl.disable(gl.CULL_FACE);
        }
    }

    //==============================================================================
    // CopyMesh
    //==============================================================================
    
    class CopyMesh extends Mesh {
        constructor(mesh) {
            super(mesh.texture, mesh.vertices, mesh.uvs, mesh.indices, mesh.drawMode);
            this.state  = mesh.state;
            this.shader = mesh.shader;
            this._mesh = mesh;
        }

        update() {
            this.vertices = this._mesh.vertices;
        }
    }

    //==============================================================================
    // MaskMesh
    //==============================================================================

    class MaskMesh extends CopyMesh {
        constructor(mesh) {
            super(mesh);
            this.maskTexture = null;
            this.orderIndex = -Infinity;
            this.setup();
        }

        setup() {
            this.maskTexture = new PIXI.Texture(new PIXI.BaseTexture());
            this.maskTexture.baseTexture.valid = true;
            let filter = new PIXI.Filter(null, PIXI.Filter.defaultFragmentSrc.replace(/\S+uS.+/, 'vec4(0.);'));
            filter.apply = this.apply.bind(this);
            this.filters = [filter];
        }

        copy(gl, uid, width, height) {
            let _texture = gl.getParameter(gl.TEXTURE_BINDING_2D);
            let texture = gl.createTexture();
            gl.bindTexture(gl.TEXTURE_2D, texture);
            gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
            gl.copyTexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 0, 0, width, height, 0);
            gl.bindTexture(gl.TEXTURE_2D, _texture);
            this.maskTexture.baseTexture._glTextures[uid] = { dirtyId: 0, texture: texture };
        }

        apply(filterManager, input, output, clear, currentState) {
            let gl = filterManager.renderer.gl;
            let uid = filterManager.renderer.CONTEXT_UID;
            let frame = {}, size = {};
            if (input.filterFrame) {
                frame.x = input.filterFrame.x;
                frame.y = input.filterFrame.y;
                frame.width = input.filterFrame.width;
                frame.height = input.filterFrame.height;
                size.width  = input.width;
                size.height = input.height;
            } else {
                frame.x = input.sourceFrame.x;
                frame.y = input.sourceFrame.y;
                frame.width = input.sourceFrame.width;
                frame.height = input.sourceFrame.height;
                size.width  = input.size.width;
                size.height = input.size.height;
            }
            this.copy(gl, uid, size.width, size.height);
            this.maskTexture.maskInfo = { frame: frame, size: size };
            filterManager.applyFilter(this.filters[0], input, output, clear, currentState);
        }

        update() {
            super.update();
            this.children.forEach(child => child.update());
        }
    }

    //==============================================================================
    // MeshMaskFilter
    //==============================================================================
    
    class MeshMaskFilter extends PIXI.Filter {
        constructor(mask, invert) {
            let vertexSrc = 'attribute vec2 aVertexPosition;attribute vec2 aTextureCoord;uniform mat3 projectionMatrix;uniform mat3 otherMatrix;uniform vec2 offset;varying vec2 vMaskCoord;varying vec2 vTextureCoord;void main(void){gl_Position=vec4((projectionMatrix*vec3(aVertexPosition,1.)).xy,0.,1.);vTextureCoord=aTextureCoord;vMaskCoord=(otherMatrix*vec3(aTextureCoord,1.)).xy+offset;}';
            let fragmentSrc = 'precision mediump float;varying vec2 vMaskCoord;varying vec2 vTextureCoord;uniform sampler2D uSampler;uniform sampler2D mask;uniform vec2 size;uniform float invert;void main(void){float clip=step(3.5,step(0.,vMaskCoord.x)+step(0.,vMaskCoord.y)+step(vMaskCoord.x,size.x)+step(vMaskCoord.y,size.y));vec4 original=texture2D(uSampler,vTextureCoord);vec4 masky=texture2D(mask,vMaskCoord);gl_FragColor=original*abs(1.*invert-masky.a*clip);}';
            super(vertexSrc, fragmentSrc);
            this._mask = mask;
            this.uniforms.invert = invert ? 1 : 0;
        }

        apply(filterManager, input, output, clear, currentState) {
            let texture = this._mask.maskTexture;
            let inputRect = {};
            if (input.filterFrame) {
                inputRect.x = input.filterFrame.x;
                inputRect.y = input.filterFrame.y;
                inputRect.width  = input.width;
                inputRect.height = input.height;
            } else {
                inputRect.x = input.sourceFrame.x;
                inputRect.y = input.sourceFrame.y;
                inputRect.width  = input.size.width;
                inputRect.height = input.size.height;
            }
            let maskInfo = texture.maskInfo;
            this.uniforms.otherMatrix = new PIXI.Matrix(inputRect.width / maskInfo.size.width, 0, 0, inputRect.height / maskInfo.size.height, 0, 0);
            this.uniforms.offset = [(inputRect.x - maskInfo.frame.x) / maskInfo.size.width, (inputRect.y - maskInfo.frame.y) / maskInfo.size.height];
            this.uniforms.mask = texture;
            this.uniforms.size = [maskInfo.frame.width / maskInfo.size.width, maskInfo.frame.height / maskInfo.size.height];
            filterManager.applyFilter(this, input, output, clear);
        }
    }

    //==============================================================================
    // Expression
    //==============================================================================
    
    class Expression {
        constructor(expression) {
            this._name = expression.Name;
            this._fadeInTime = expression.Data.FadeInTime || 1;
            this._fadeOutTime = expression.Data.FadeOutTime || 1;
            this._parameters = [];
            for (let parameter of expression.Data.Parameters) {
                this._parameters.push({
                    id:    parameter.Id,
                    value: parameter.Value,
                    blend: parameter.Blend
                });
            }
        }

        get name() { return this._name; }
        get fadeInTime() { return this._fadeInTime; }
        get fadeOutTime() { return this._fadeOutTime; }

        fadeWeight(value) {
            return 0.5 - 0.5 * Math.cos(value * Math.PI);
        }

        values(time) {
            let values = {};
            for (let parameter of this._parameters) {
                let fadeInWeight  = time < 0 ? this.fadeWeight(1 + time / this.fadeInTime) : 1;
                let fadeOutWeight = time > 0 ? this.fadeWeight(1 - time / this.fadeOutTime) : 1;
                let id     = parameter.id;
                let value  = parameter.value;
                let blend  = parameter.blend;
                let weight = fadeInWeight * fadeOutWeight;
                values[id] = { value: value, blend: blend, weight: weight };
            }
            return values;
        }
    }

    //==============================================================================
    // Direction
    //==============================================================================
    
    class Direction {
        constructor(expressions, parameters) {
            this._expressions = [];
            this._parameters = parameters;
            this.init(expressions);
        }

        get expressions() { return this._expressions; }

        init(expressions = []) {
            for (let i = 0; i < expressions.length; i++) {
                this._expressions.push({
                    entity:  new Expression(expressions[i]),
                    index:   i,
                    enabled: false,
                    time:    null,
                    custom:  null
                });
            }
        }

        setExpression(name) {
            let lastIndex = this._expressions.reduce((a, b) => Math.max(a, b.index), 0);
            for (let expression of this._expressions) {
                let entity = expression.entity;
                if (entity.name == name) {
                    if (expression.time == null) {
                        expression.time = -entity.fadeInTime;
                    } else if (expression.time > 0) {
                        expression.time = -expression.time;
                    }
                    expression.index = lastIndex + 1;
                    expression.enabled = true;
                } else if (expression.enabled) {
                    expression.time = -expression.time;
                    expression.enabled = false;
                }
            }
            this._expressions.sort((a, b) => a.index - b.index);
        }

        setCustomExpression(expression) {
            this.clearExpression();
            let entity = new Expression(expression);
            let lastIndex = this._expressions.reduce((a, b) => Math.max(a, b.index), 0);
            this._expressions.push({
                entity:  entity,
                index:   lastIndex + 1,
                enabled: true,
                time:    -entity.fadeInTime,
                custom:  expression
            });
        }

        clearExpression() {
            for (let expression of this._expressions) {
                if (expression.enabled) {
                    expression.time = -expression.time;
                    expression.enabled = false;
                }
            }
        }

        apply(delta) {
            let parameters = this._parameters;
            let released = [];
            for (let expression of this._expressions) {
                let time = expression.time;
                if (time == null) {
                    continue;
                }
                time += delta;
                if (expression.enabled && time > 0) {
                    time = 0;
                }
                if (time > expression.entity.fadeOutTime) {
                    if (expression.custom) {
                        released.push(expression);
                    } else {
                        expression.time = null;
                    }
                    continue;
                }
                expression.time = time;
                let values = expression.entity.values(time);
                for (let id in values) {
                    let value  = values[id].value;
                    let blend  = values[id].blend;
                    let weight = values[id].weight;
                    let index  = parameters.ids.indexOf(id);
                    let _value = parameters.values[index];
                    switch (blend) {
                    case 'Add':
                        _value += value * weight;
                        break;
                    case 'Multiply':
                        _value *= 1.0 + (value - 1.0) * weight;
                        break;
                    case 'Overwrite':
                        _value = _value * (1 - weight) + value * weight;
                        break;
                    }
                    parameters.values[index] = _value;
                }
            }
            if (released.length > 0) {
                this._expressions = this._expressions.filter(expression => {
                    return !released.includes(expression);
                });
            }
        }
    }

    //==============================================================================
    // Vector2
    //==============================================================================

    class Vector2 {
        constructor(x, y) {
            this.x = x || 0;
            this.y = y || 0;
        }

        add(vector2) {
            return new Vector2(this.x + vector2.x, this.y + vector2.y);
        }

        substract(vector2) {
            return new Vector2(this.x - vector2.x, this.y - vector2.y);
        }

        multiplyScalar(scalar) {
            return new Vector2(this.x * scalar, this.y * scalar);
        }

        normalize() {
            let length = Math.pow(this.x * this.x + this.y * this.y, 0.5);
            this.x /= length;
            this.y /= length;
            return this;
        }
    }

    //==============================================================================
    // Physics
    //==============================================================================

    class Physics {
        constructor(physics, parameters) {
            this._settings   = {};
            this._parameters = parameters;
            this.init(physics);
        }

        get settings() { return this._settings; }

        init(physics) {
            let parameters = this._parameters;
            let dictionary = physics.Meta.PhysicsDictionary;
            let settings   = JSON.parse(JSON.stringify(physics.PhysicsSettings).replace(/"[^"]+(?=":)/g, m => '"' + m[1].toLowerCase() + m.slice(2)));
            for (let setting of settings) {
                let id = setting.id;
                delete setting.id;
                setting.name = (dictionary.find(item => item.Id == id) || { Name: '' }).Name;
                for (let input of setting.input) {
                    input.source.index = parameters.ids.indexOf(input.source.id);
                }
                for (let output of setting.output) {
                    output.destination.index = parameters.ids.indexOf(output.destination.id);
                }
                for (let vertex of setting.vertices) {
                    vertex.position    = new Vector2(vertex.position.x, vertex.position.y);
                    vertex.lastGravity = new Vector2(0, 1);
                    vertex.velocity    = new Vector2();
                }
                this._settings[id] = setting;
            }
        }

        apply(delta) {
            let values        = this._parameters.values;
            let minimumValues = this._parameters.minimumValues;
            let maximumValues = this._parameters.maximumValues;
            let defaultValues = this._parameters.defaultValues;
            for (let id in this._settings) {
                let setting       = this._settings[id];
                let vertices      = setting.vertices;
                let normalization = setting.normalization;
                let translation   = { x: 0, y: 0 };
                let angle         = 0;
                for (let input of setting.input) {
                    let index = input.source.index;
                    let type  = input.type;
                    let value = this.normalize(
                        values[index], minimumValues[index], maximumValues[index], defaultValues[index],
                        type == 'Angle' ? normalization.angle : normalization.position, input.reflect
                    );
                    value *= input.weight / 100;
                    switch (type) {
                    case 'X':
                        translation.x += value;
                        break;
                    case 'Y':
                        translation.y += value;
                        break;
                    case 'Angle':
                        angle += value;
                        break;
                    }
                }
                angle = -angle / 180 * Math.PI;
                translation = {
                    x: translation.x * Math.cos(angle) - translation.y * Math.sin(angle),
                    y: translation.x * Math.sin(angle) + translation.y * Math.cos(angle)
                };
                this.updateVertices(vertices, translation, -angle, normalization.position.maximum * 0.001, delta);
                for (let output of setting.output) {
                    let index       = output.destination.index;
                    let vertexIndex = output.vertexIndex;
                    if (vertexIndex < 1 || vertexIndex >= vertices.length) {
                        continue;
                    }
                    translation = {
                        x: vertices[vertexIndex].position.x - vertices[vertexIndex - 1].position.x,
                        y: vertices[vertexIndex].position.y - vertices[vertexIndex - 1].position.y
                    };
                    let gravity   = vertexIndex > 1 ? vertices[vertexIndex - 1].position.substract(vertices[vertexIndex - 2].position) : new Vector2(0, 1);
                    let value     = this.directionToRadian(gravity, translation);
                    let outValues = values.slice(index);
                    this.updateValues(outValues, minimumValues[index], maximumValues[index], value * output.scale, output.weight);
                    for (let i = index, j = 0; i < values.length; i++, j++) {
                        values[i] = outValues[j];
                    }
                }
            }
        }

        normalize(value, minimumValue, maximumValue, defaultValue, normalization, reflect) {
            let result = 0;
            value = Math.min(Math.max(value, minimumValue), maximumValue) - defaultValue;
            if (value != 0) {
                let p = (value > 0 ? maximumValue : minimumValue) - defaultValue;
                if (p != 0) {
                    let n = (value > 0 ? normalization.maximum : normalization.minimum) - normalization.default;
                    result = value * n / p + normalization.default;
                }
            } else {
                result = normalization.default;
            }
            return reflect ? result :-result;
        }

        updateVertices(vertices, translation, angle, shreshold, delta) {
            let gravity = new Vector2(Math.sin(angle), Math.cos(angle)).normalize();
            vertices[0].position = new Vector2(translation.x, translation.y);
            for (var i = 1; i < vertices.length; ++i) {
                let delay     = vertices[i].delay * delta * 30;
                let velocity  = vertices[i].velocity.multiplyScalar(delay);
                let force     = gravity.multiplyScalar(vertices[i].acceleration * delay * delay);
                let direction = vertices[i].position.substract(vertices[i - 1].position);
                let radian    = this.directionToRadian(vertices[i].lastGravity, gravity) / 5;
                direction = new Vector2(
                    Math.cos(radian) * direction.x - direction.y * Math.sin(radian),
                    Math.sin(radian) * direction.x + direction.y * Math.cos(radian)
                );
                let lastPosition = new Vector2(vertices[i].position.x, vertices[i].position.y);
                vertices[i].position = vertices[i - 1].position.add(direction).add(velocity).add(force);
                direction = vertices[i].position.substract(vertices[i - 1].position).normalize();
                vertices[i].position = vertices[i - 1].position.add(direction.multiplyScalar(vertices[i].radius));
                if (Math.abs(vertices[i].position.x) < shreshold) {
                    vertices[i].position.x = 0;
                }
                if (delay != 0) {
                    vertices[i].velocity = vertices[i].position.substract(lastPosition).multiplyScalar(vertices[i].mobility / delay);
                }
                vertices[i].lastGravity = new Vector2(gravity.x, gravity.y);
            }
        }

        updateValues(values, minimum, maximum, value, weight) {
            value = Math.min(Math.max(value, minimum), maximum);
            weight /= 100;
            if (weight >= 1) {
                values[0] = value;
            } else {
                values[0] = values[0] * (1 - weight) + value * weight;
            }
        }

        directionToRadian(from, to) {
            let q1 = Math.atan2(to.y, to.x);
            let q2 = Math.atan2(from.y, from.x);
            let ret = q1 - q2;
            while (ret < -Math.PI) {
                ret += Math.PI * 2;
            }
            while (ret > Math.PI) {
                ret -= Math.PI * 2;
            }
            return ret;
        }
    }

    //==============================================================================
    // Curve
    //==============================================================================

    class Curve {
        constructor(curve, beziersRestricted = false) {
            this._beziersRestricted = beziersRestricted;
            this._target   = '';
            this._id       = '';
            this._segments = [];
            this._points   = [];
            this.create(curve);
        }

        get target() { return this._target; }
        get id() { return this._id; }

        create(curve) {
            this._target      = curve.Target;
            this._id          = curve.Id;
            this._fadeInTime  = curve.FadeInTime;
            this._fadeOutTime = curve.FadeOutTime;
            this._segments    = [];
            this._points      = [];
            let basePoint     = 0;
            let endPoint      = 0;
            for (let i = 0; i < curve.Segments.length; i++) {
                let evaluate = null;
                if (i > 0) {
                    let type = curve.Segments[i];
                    basePoint = endPoint;
                    this._points.push({ time: curve.Segments[i + 1], value: curve.Segments[i + 2] });
                    if (type == 1) {
                        this._points.push({ time: curve.Segments[i + 3], value: curve.Segments[i + 4] });
                        this._points.push({ time: curve.Segments[i + 5], value: curve.Segments[i + 6] });
                        endPoint = basePoint + 3;
                        evaluate = this.bezier;
                        i += 6;
                    } else {
                        endPoint = basePoint + 1;
                        evaluate = type == 0 ? this.linear
                                 : type == 2 ? this.stepped
                                 : type == 3 ? this.inverseStepped
                                 : null;
                        i += 2;
                    }
                } else {
                    this._points.push({ time: curve.Segments[0], value: curve.Segments[1] });
                    i++;
                }
                this._segments.push({ basePoint: basePoint, endPoint: endPoint, evaluate: evaluate });
            }
        }

        value(time) {
            let segments = this._segments;
            let points   = this._points;
            for (let i = 0; i < segments.length; i++) {
                if (points[segments[i].endPoint].time > time) {
                    let basePoint = segments[i].basePoint;
                    let endPoint  = segments[i].endPoint;
                    let evaluate  = segments[i].evaluate;
                    if (evaluate) {
                        return evaluate.call(this, points.slice(basePoint, endPoint + 1), time);
                    }
                    break;
                }
            }
            return points.slice(-1)[0].value;
        }

        lerp(a, b, t) {
            let time  = a.time + (b.time - a.time) * t;
            let value = a.value + (b.value - a.value) * t;
            return { time: time, value: value };
        }

        linear(points, time) {
            let t = (time - points[0].time) / (points[1].time - points[0].time);
            return points[0].value + (points[1].value - points[0].value) * Math.max(t, 0);
        }

        bezier(points, time) {
            if (!this._beziersRestricted) {
                return this.cardano(points, time);
            }
            let t  = Math.max((time - points[0].time) / (points[3].time - points[0].time), 0);
            let p0 = this.lerp(points[0], points[1], t);
            let p1 = this.lerp(points[1], points[2], t);
            let p2 = this.lerp(points[2], points[3], t);
            let p3 = this.lerp(p0, p1, t);
            let p4 = this.lerp(p1, p2, t);
            return this.lerp(p3, p4, t).value;
        }

        cardano(points, time) {
            let t0 = points[0].time;
            let t1 = points[1].time;
            let t2 = points[2].time;
            let t3 = points[3].time;
            let t4 = t3 + 3.0 * (t1 - t2) - t0;
            let t5 = 3.0 * (t0 + t2) - 6.0 * t1;
            let t6 = 3.0 * (t1 - t0);
            let t7 = t0 - time;
            let t  = this.cardanoAlgorithm(t4, t5, t6, t7);
            let p0 = this.lerp(points[0], points[1], t);
            let p1 = this.lerp(points[1], points[2], t);
            let p2 = this.lerp(points[2], points[3], t);
            let p3 = this.lerp(p0, p1, t);
            let p4 = this.lerp(p1, p2, t);
            return this.lerp(p3, p4, t).value;
        }

        stepped(points) {
            return points[0].value;
        }

        inverseStepped(points) {
            return points[1].value;
        }

        range(value, min, max) {
            return Math.min(Math.max(value, min), max);
        }

        cbrt(x) {
            if (x == 0) {
                return x;
            }
            var cx = x;
            var isNegativeNumber = cx < 0;
            if (isNegativeNumber) {
                cx = -cx;
            }
            var ret;
            if (cx == Infinity) {
                ret = Infinity;
            } else {
                ret = Math.exp(Math.log(cx) / 3);
                ret = (cx / (ret * ret) + 2 * ret) / 3;
            }
            return isNegativeNumber ? -ret : ret;
        }

        quadratic(a, b, c) {
            if (Math.abs(a) < 0.00001) {
                if (Math.abs(b) < 0.00001) {
                    return -c;
                }
                return -c / b;
            }
            return -(b + Math.sqrt(b * b - 4 * a * c)) / (2 * a);
        }

        cardanoAlgorithm(a, b, c, d) {
            if (Math.sqrt(a) < 0.00001) {
                return this.range(this.quadratic(b, c, d), 0, 1);
            }
            let ba = b / a;
            let ca = c / a;
            let da = d / a;
            let p  = (3 * ca - ba * ba) / 3;
            let p3 = p / 3;
            let q  = (2 * ba * ba * ba - 9 * ba * ca + 27 * da) / 27;
            let q2 = q / 2;
            let discriminant = q2 * q2 + p3 * p3 * p3;
            if (discriminant < 0) {
                let mp3    = -p / 3;
                let mp33   = mp3 * mp3 * mp3;
                let r      = Math.sqrt(mp33);
                let t      = -q / (2 * r);
                let cosphi = this.range(t, -1, 1);
                let phi    = Math.acos(cosphi);
                let crtr   = this.cbrt(r);
                let t1     = 2 * crtr;
                let root1_1 = t1 * Math.cos(phi / 3) - ba / 3;
                if (Math.abs(root1_1 - 0.5) < 0.51) {
                    return this.range(root1_1, 0, 1);
                }
                let root2 = t1 * Math.cos((phi + 2 * Math.PI) / 3) - ba / 3;
                if (Math.abs(root2 - 0.5) < 0.51) {
                    return this.range(root2, 0, 1);
                }
                let root3 = t1 * Math.cos((phi + 4 * Math.PI) / 3) - ba / 3;
                return this.range(root3, 0, 1);
            } else if (discriminant == 0) {
                let u1_1;
                if (q2 < 0) {
                    u1_1 = this.cbrt(-q2);
                } else {
                    u1_1 = -this.cbrt(q2);
                }
                let root1_2 = 2 * u1_1 - ba / 3;
                if (Math.abs(root1_2 - 0.5) < 0.51) {
                    return this.range(root1_2, 0, 1);
                }
                let root2 = -u1_1 - ba / 3;
                return this.range(root2, 0, 1);
            }
            let sd = Math.sqrt(discriminant);
            let u1 = this.cbrt(sd - q2);
            let v1 = this.cbrt(sd + q2);
            let root1 = u1 - v1 - ba / 3;
            return this.range(root1, 0, 1);
        }
    }

    //==============================================================================
    // Motion
    //==============================================================================

    class Motion {
        constructor(motion) {
            if (motion) {
                let meta = motion.Data.Meta;
                this._duration          = meta.Duration;
                this._loop              = meta.Loop;
                this._beziersRestricted = meta.AreBeziersRestricted;
                this._group       = motion.Group;
                this._name        = motion.Name;
                this._fadeInTime  = motion.FadeInTime;
                this._fadeOutTime = motion.FadeOutTime;
                this._sound       = motion.Sound;
                this._curves = [];
                this.createCurve(motion.Data.Curves, this._beziersRestricted);
            } else {
                this._duration          = 0;
                this._loop              = false;
                this._beziersRestricted = false;
                this._group       = '';
                this._name        = '';
                this._fadeInTime  = 0;
                this._fadeOutTime = 0;
                this._sound       = '';
                this._curves = [];
            }
        }

        get duration() { return this._duration; }
        get group() { return this._group; };
        get name() { return this._name; };
        get sound() { return this._sound; }

        createCurve(curves, beziersRestricted) {
            this._curves = curves.map(curve => new Curve(curve, beziersRestricted));
        }

        fadeWeight(value) {
            return 0.5 - 0.5 * Math.cos(value * Math.PI);
        }

        values(time) {
            let remain = Math.max(this._duration - time, 0);
            let values = {};
            for (let curve of this._curves) {
                let fadeInTime    = curve._fadeInTime != null ? curve._fadeInTime : this._fadeInTime;
                let fadeOutTime   = curve._fadeOutTime != null ? curve._fadeOutTime : this._fadeOutTime;
                let fadeInWeight  = time < fadeInTime ? this.fadeWeight(time / fadeInTime) : 1;
                let fadeOutWeight = remain < fadeOutTime ? this.fadeWeight(remain / fadeOutTime) : 1;
                let target = curve.target;
                let id     = curve.id;
                let value  = curve.value(time);
                let weight = fadeInWeight * fadeOutWeight;
                values[target] = values[target] || {};
                values[target][id] = { value: value, weight: weight };
            }
            return values;
        }
    }

    //==============================================================================
    // Entry
    //==============================================================================

    class Entry {
        constructor(motion, elapsed = 0) {
            this._motion = motion;
            this._elapsed = elapsed;
            this.loop = false;
            this.timeScale = 1;
            this.weight    = 1;
            this.mixTime   = 0;
            this.endTime   = 0;
        }

        get name() { return this._motion.name; }
        get duration() { return this.endTime || this._motion.duration; }
        get elapsed() { return this._elapsed; }
        get remain() { return this.duration - this._elapsed; }
        get sound() { return this._motion.sound; }

        values(delta) {
            this._elapsed += (delta * this.timeScale);
            if (this.loop) {
                this._elapsed %= this.duration;
            }
            let values = this._motion.values(this._elapsed);
            for (let target in values) {
                for (let id in values[target]) {
                    values[target][id].weight *= this.weight;
                }
            }
            return values;
        }
    }

    //==============================================================================
    // Track
    //==============================================================================

    class Track {
        constructor(active = false, mixing = false) {
            this._entries = [];
            this._active = active;
            this._mixing = mixing;
            this._history = [];
            this.weight = 1;
        }

        get entries() { return this._entries; }
        get active() { return this._active; }
        get mixing() { return this._mixing; }
        get history() { return this._history.shift(); }

        add(entry) {
            let last = this.entry(-1);
            if (last) {
                last.loop = false;
                if (last.endTime > 0) {
                    last.endTime += entry.mixTime;
                }
            }
            this._entries.push(entry);
        }

        entry(index) {
            return this._entries.slice(index)[0];
        }

        mix(values, next_values, rate) {
            let r0 = 1 - rate;
            let r1 = rate;
            for (let target in next_values) {
                for (let id in next_values[target]) {
                    let value, weight;
                    if (values[target] && values[target][id]) {
                        let v0 = values[target][id];
                        let v1 = next_values[target][id];
                        value  = v0.value * r0 + v1.value * r1;
                        weight = v0.weight * r0 + v1.weight * r1;
                    } else {
                        let v = next_values[target][id];
                        value  = v.value;
                        weight = v.weight * r1;
                    }
                    values[target] = values[target] || {};
                    values[target][id] = { value: value, weight: weight };
                }
            }
            for (let target in values) {
                for (let id in values[target]) {
                    if (!next_values[target] || !next_values[target][id]) {
                        values[target][id].weight *= r0;
                    }
                }
            }
        }

        suspend() {
            if (!this.active) {
                return;
            }
            let entry = this._entries[0];
            entry.endTime = entry.elapsed;
            entry.loop = false;
            this._entries.splice(1);
        }

        values(delta) {
            let entry = this._entries[0];
            let next_entry = this._entries[1];
            if (!entry) {
                return null;
            }
            if (!this._active) {
                delta = 0;
                this._history.push({ start: entry });
            }
            let _remain = entry.remain;
            let mixTime = next_entry ? next_entry.mixTime : 0;
            let values = entry.values(delta);
            let remain = entry.remain;
            if (remain > 0) {
                if (mixTime >= remain) {
                    delta = Math.min(mixTime - remain, _remain - remain);
                    let next_values = next_entry.values(delta);
                    let rate = (mixTime - remain) / mixTime;
                    this.mix(values, next_values, rate);
                    if (!this._mixing) {
                        this._history.push({ start: next_entry });
                        this._mixing = true;
                    }
                }
            } else {
                this._entries.shift();
                this._history.push({ end: entry });
                if (next_entry) {
                    delta = -remain / entry.timeScale;
                    values = next_entry.values(delta);
                    if (!this._mixing) {
                        this._history.push({ start: next_entry });
                    }
                }
                this._mixing = false;
            }
            this._active = remain > 0 || !!next_entry;
            return values;
        }
    }

    //==============================================================================
    // EyeBlink
    //==============================================================================

    class EyeBlink {
        constructor(ids = [], options = {}) {
            this._motionTime = [0, 0.03, 0.01, 0.12];
            this._motionIndex = 0;
            this._baseTime = 0;
            this._baseInterval = 4;
            this._elapsed = 0;
            this._enabled = {};
            this.init(ids, options);
        }

        get interval() { return Math.random() * (2 * this._baseInterval - 1); }

        init(ids, options) {
            this._enabled = {};
            for (let id of ids) {
                this._enabled[id] = true;
            }
            if ('interval' in options) {
                this._baseInterval = options.interval;
            }
            this._motionTime[0] = this.interval;
            let keys = ['closing', 'closed', 'opening'];
            for (let i = 0; i < 3; i++) {
                if (keys[i] in options) {
                    this._motionTime[i + 1] = options[keys[i]];
                }
            }
            this._motionIndex = 0;
            this._baseTime = 0;
            this._elapsed = 0;
        }

        enable(id) {
            for (let key in this._enabled) {
                if (!id || id == key) {
                    this._enabled[key] = true;
                }
            }
        }

        disable(id) {
            for (let key in this._enabled) {
                if (!id || id == key) {
                    this._enabled[key] = false;
                }
            }
        }

        value(timeRate) {
            let value = 1;
            if (this._motionIndex == 1) {
                value = 1 - timeRate;
            } else if (this._motionIndex == 2) {
                value = 0;
            } else if (this._motionIndex == 3) {
                value = timeRate;
            }
            return 0.5 - 0.5 * Math.cos(value * Math.PI);
        }

        values(delta) {
            if (!Object.values(this._enabled).some(value => value)) {
                this._motionIndex = 0;
                this._baseTime = 0;
                this._elapsed = 0;
                return null;
            }
            this._elapsed += delta;
            let motionTime = this._motionTime[this._motionIndex];
            let timeRate = motionTime ? Math.min((this._elapsed - this._baseTime) / motionTime, 1) : 1;
            let value = this.value(timeRate);
            if (timeRate == 1) {
                this._baseTime = this._elapsed;
                this._motionIndex = (this._motionIndex + 1) & 0x3;
                if (this._motionIndex == 0) {
                    this._motionTime[0] = this.interval;
                }
            }
            let values = {};
            for (let id in this._enabled) {
                if (this._enabled[id]) {
                    values[id] = value;
                }
            }
            return values;
        }
    }

    //==============================================================================
    // Animation
    //==============================================================================

    class Animation {
        constructor(motions, parameters, parts, groups) {
            this._motions = [];
            this._parameters = parameters;
            this._parts = parts;
            this._groups = groups;
            this._tracks = [];
            this._eyeBlink = null;
            this._timeScale = 1;
            this._mixTime = {};
            this._listeners = {};
            this.createMotion(motions);
        }

        get motions() { return this._motions; }
        get tracks() { return this._tracks; }
        get timeScale() { return this._timeScale; }
        get mixTime() { return this._mixTime; }

        createMotion(motions) {
            this._motions = motions.map(motion => new Motion(motion));
        }

        track(index) {
            if (!this._tracks[index]) {
                this._tracks[index] = new Track();
            }
            return this._tracks[index];
        }

        setAnimation(index, name, loop) {
            let track = this._tracks[index];
            if (track) {
                track.suspend();
            }
            return this.addAnimation(index, name, loop);
        }

        addAnimation(index, name, loop) {
            let motion = this._motions.find(motion => motion.name == name);
            let entry  = new Entry(motion);
            let track  = this.track(index);
            let last   = track.entry(-1);
            if (last) {
                let key = `${last.name},${name}`;
                entry.mixTime = this._mixTime[key] || this._mixTime.default || 0;
            }
            entry.loop = loop;
            track.add(entry);
            return entry;
        }

        clearAnimation(index, mixTime = 0) {
            let track = this._tracks[index];
            if (!track) {
                return;
            }
            track.suspend();
            let entry = new Entry(new Motion());
            entry.mixTime = mixTime;
            track.add(entry);
        }

        setTimeScale(scale) {
            this._timeScale = scale;
        }

        setWeight(index, value) {
            this.track(index).weight = value;
        }

        setMix(from, to, time) {
            if (time > 0) {
                this._mixTime[`${from},${to}`] = time;
            } else {
                delete this._mixTime[`${from},${to}`];
            }
        }

        setDefaultMix(time) {
            if (time > 0) {
                this._mixTime.default = time;
            } else {
                delete this._mixTime.default;
            }
        }

        setAutoEyeBlink(enabled, options) {
            this._eyeBlink = enabled ? new EyeBlink(this._groups.EyeBlink, options) : null;
        }

        addListener(event, callback) {
            this._listeners[event] = this._listeners[event] || [];
            this._listeners[event].push(callback);
        }

        removeListener(event, callback) {
            if (this._listeners[event]) {
                let index = this._listeners[event].indexOf(callback);
                if (index >= 0) {
                    this._listeners[event].splice(index, 1);
                }
            }
        }

        callListener(event, index, track, entry) {
            if (this._listeners[event]) {
                let trackInfo = { index: index, track: track, entry: entry };
                this._listeners[event].forEach(callback => callback(trackInfo));
            }
        }

        apply(delta) {
            delta *= this._timeScale;
            let parameters = this._parameters;
            let parts      = this._parts;
            if (this._eyeBlink) {
                this._eyeBlink.enable();
            }
            for (let track of this._tracks) {
                let eyeBlink = { value: null, weight: 0, flag: {} };
                let values = track.values(delta);
                if (!values) {
                    continue;
                }
                for (let target in values) {
                    for (let id in values[target]) {
                        let v = values[target][id];
                        let value  = v.value;
                        let weight = v.weight * track.weight;
                        if (target == 'Model') {
                            if (id == 'EyeBlink') {
                                eyeBlink.value = value;
                                eyeBlink.weight = weight;
                            }
                        } else if (target == 'Parameter') {
                            let index  = parameters.ids.indexOf(id);
                            let _value = parameters.values[index];
                            if (this._groups.EyeBlink.includes(id)) {
                                eyeBlink.flag[id] = true;
                                if (eyeBlink.value != null) {
                                    value *= eyeBlink.value;
                                }
                                if (this._eyeBlink) {
                                    this._eyeBlink.disable(id);
                                }
                            }
                            parameters.values[index] = _value * (1 - weight) + value * weight;
                        } else if (target == 'PartOpacity') {
                            let index  = parts.ids.indexOf(id);
                            let _value = parts.opacities[index];
                            parts.opacities[index] = _value * (1 - weight) + value * weight;
                        }
                    }
                }
                if (eyeBlink.value != null) {
                    let value = eyeBlink.value;
                    let weight = eyeBlink.weight;
                    for (let id of this._groups.EyeBlink) {
                        if (!eyeBlink.flag[id]) {
                            let index  = parameters.ids.indexOf(id);
                            let _value = parameters.values[index];
                            parameters.values[index] = _value * (1 - weight) + value * weight;
                        }
                    }
                }
                let index = this._tracks.indexOf(track);
                for (let history; history = track.history;) {
                    let event = history.start ? 'start' : 'end';
                    let entry = history.start || history.end;
                    this.callListener(event, index, track, entry);
                }
            }
            if (this._eyeBlink) {
                let values = this._eyeBlink.values(delta);
                if (values) {
                    for (let id in values) {
                        let index = parameters.ids.indexOf(id);
                        parameters.values[index] = values[id];
                    }
                }
            }
        }
    }

    //==============================================================================
    // Live2D
    //==============================================================================

    class Live2D extends PIXI.Container {
        constructor(live2dData) {
            super();
            this._data       = live2dData;
            this._model      = new Live2DCubismCore.Model(this._data.Moc);
            this._meshes     = [];
            this._masks      = {};
            this._physics    = null;
            this._direction  = null;
            this._animation  = null;
            this._updateTime = 0;
            this.createMesh();
            this.createMask();
            this.createAnimation();
            this.createDirection();
            this.createPhysics();
        }

        get model() { return this._model; }
        get animation() { return this._animation; }
        get direction() { return this._direction; }

        createMesh() {
            let drawables = this._model.drawables;
            for (let i = 0; i < drawables.count; i++) {
                let textureIndex = drawables.textureIndices[i];
                let texture  = this._data.Textures[textureIndex];
                let vertices = this.localVertices(drawables.vertexPositions[i]);
                let uvs      = new Float32Array(drawables.vertexUvs[i]);
                let indices  = new Uint16Array(drawables.indices[i]);
                for (let j = 0; j < drawables.vertexCounts[i]; j++) {
                    uvs[j * 2 + 1] = 1 - uvs[j * 2 + 1];
                }
                let mesh = new Mesh(texture, vertices, uvs, indices, PIXI.DRAW_MODES.TRIANGLES);
                mesh.alpha         = drawables.opacities[i];
                mesh.visible       = drawables.dynamicFlags[i] & 0x1;
                mesh.blendMode     = drawables.constantFlags[i] & 0x3;
                mesh.state.culling = !(drawables.constantFlags[i] & 0x4);
                mesh.orderIndex    = drawables.renderOrders[i];
                if (mesh.state.culling) {
                    mesh.shader.batchable = false;
                }
                this._meshes.push(mesh);
                this.addChild(mesh);
            }
            this.sortMesh();
        }

        createMask() {
            let drawables = this._model.drawables;
            for (let i = 0; i < drawables.count; i++) {
                let mesh = this._meshes[i];
                let invert = !!(drawables.constantFlags[i] & 0x8);
                let masks = [...drawables.masks[i]].sort();
                if (masks.length > 0) {
                    let mask = this._masks[masks];
                    if (!mask) {
                        mask = new MaskMesh(this._meshes[masks[0]]);
                        this.addChildAt(mask, 0);
                        this._masks[masks] = mask;
                        for (let j = 1; j < masks.length; j++) {
                            mask.addChild(new CopyMesh(this._meshes[masks[j]]));
                        }
                    }
                    let filters = mesh.filters || [];
                    filters.push(new MeshMaskFilter(mask, invert));
                    if (mesh.blendMode > 0) {
                        let filter = new PIXI.Filter();
                        filter.blendMode = mesh.blendMode;
                        mesh.blendMode = 0;
                        filters.push(filter);
                    }
                    mesh.filters = filters;
                }
            }
        }

        createPhysics() {
            if (this._data.Physics) {
                this._physics = new Physics(this._data.Physics, this._model.parameters);
            }
        }

        createDirection() {
            if (this._data.Expressions) {
                this._direction = new Direction(this._data.Expressions, this._model.parameters);
            }
        }

        createAnimation() {
            if (this._data.Motions) {
                let motions    = [];
                let parameters = this._model.parameters;
                let parts      = this._model.parts;
                let groups     = {};
                for (let group in this._data.Motions) {
                    motions.push(...this._data.Motions[group]);
                }
                for (let group of this._data.Groups) {
                    groups[group.Name] = [...group.Ids];
                }
                this._animation = new Animation(motions, parameters, parts, groups);
            }
        }

        sortMesh() {
            this.children.sort((a, b) => a.orderIndex - b.orderIndex);
        }

        localVertices(vertices) {
            let canvasinfo = this._model.canvasinfo;
            let cox = canvasinfo.CanvasOriginX;
            let coy = canvasinfo.CanvasOriginY;
            let ppu = canvasinfo.PixelsPerUnit;
            for (let i = 0; i < vertices.length; i++) {
                vertices[i] = i % 2 ?  coy - vertices[i] * ppu : vertices[i] * ppu + cox;
            }
            return vertices;
        }

        update() {
            let time = performance.now() / 1000;
            if (this._updateTime > 0) {
                let delta = time - this._updateTime;
                this.applyAnimation(delta);
                this.applyDirection(delta);
                this.applyPhysics(delta);
            }
            this._model.update();
            this.updateMesh();
            this.updateMask();
            this._model.drawables.resetDynamicFlags();
            this._updateTime = time;
        }

        updateMesh() {
            let drawables = this._model.drawables;
            let vertices     = drawables.vertexPositions;
            let opacities    = drawables.opacities;
            let renderOrders = drawables.renderOrders;
            let dynamicFlags = drawables.dynamicFlags;
            let needsSort = false;
            this._meshes.forEach((mesh, index) => {
                mesh.visible    = !!(dynamicFlags[index] & 0x1);
                mesh.alpha      = opacities[index];
                mesh.orderIndex = renderOrders[index];
                if (dynamicFlags[index] & 0x10) {
                    needsSort = true;
                }
                if (dynamicFlags[index] & 0x20) {
                    mesh.vertices = this.localVertices(vertices[index]);
                }
            });
            if (needsSort) {
                this.sortMesh();
            }
        }

        updateMask() {
            for (let key in this._masks) {
                this._masks[key].update();
            }
        }

        applyPhysics(delta) {
            if (this._physics) {
                this._physics.apply(delta);
            }
        }

        applyDirection(delta) {
            if (this._direction) {
                this._direction.apply(delta);
            }
        }

        applyAnimation(delta) {
            if (this._animation) {
                this._animation.apply(delta);
            }
        }

        snapshot() {
            if (!this._animation) {
                return null;
            }
            let parameters = {
                ids:    [...this._model.parameters.ids],
                values: [...this._model.parameters.values]
            };
            let parts = {
                ids:       [...this._model.parts.ids],
                opacities: [...this._model.parts.opacities]
            };
            let animation = {
                tracks: [],
                timeScale: this._animation.timeScale,
                mixTime: {}
            };
            for (let key in this._animation.mixTime) {
                animation.mixTime[key] = this._animation.mixTime[key];
            }
            this._animation.tracks.forEach((track, index) => {
                let entries = [];
                animation.tracks[index] = {
                    entries: entries,
                    active:  track.active,
                    mixing:  track.mixing,
                    weight:  track.weight
                };
                track.entries.forEach(entry => {
                    entries.push({
                        name:      entry.name,
                        elapsed:   entry.elapsed,
                        loop:      entry.loop,
                        timeScale: entry.timeScale,
                        weight:    entry.weight,
                        mixTime:   entry.mixTime,
                        endTime:   entry.endTime
                    });
                });
            });
            let physics = {};
            if (this._physics) {
                for (let key in this._physics.settings) {
                    physics[key] = [];
                    for (let vertex of this._physics.settings[key].vertices) {
                        physics[key].push({
                            position:    {...vertex.position},
                            lastGravity: {...vertex.lastGravity},
                            velocity:    {...vertex.velocity}
                        });
                    }
                }
            }
            let direction = {};
            if (this._direction) {
                direction.expressions = [];
                for (let expression of this._direction.expressions) {
                    direction.expressions.push({
                        name:    expression.entity.name,
                        index:   expression.index,
                        enabled: expression.enabled,
                        time:    expression.time,
                        custom:  expression.custom
                    });
                }
            }
            return {
                parameters: parameters,
                parts: parts,
                animation: animation,
                physics: physics,
                direction: direction
            };
        }

        restore(snapshot) {
            if (!snapshot) {
                return;
            }
            let parameters = snapshot.parameters;
            let parts      = snapshot.parts;
            let animation  = snapshot.animation;
            let physics    = snapshot.physics;
            let direction  = snapshot.direction;
            parameters.ids.forEach((id, index) => {
                let _index = this._model.parameters.ids.indexOf(id);
                if (_index >= 0) {
                    this._model.parameters.values[_index] = parameters.values[index];
                }
            });
            parts.ids.forEach((id, index) => {
                let _index = this._model.parts.ids.indexOf(id);
                if (_index >= 0) {
                    this._model.parts.opacities[_index] = parts.opacities[index];
                }
            });
            this._animation.setTimeScale(animation.timeScale);
            for (let key in animation.mixTime) {
                if (key == 'default') {
                    this._animation.setDefaultMix(animation.mixTime[key]);
                } else {
                    let [from, to] = key.split(/,(?=[^,]+$)/);
                    this._animation.setMix(from, to, animation.mixTime[key]);
                }
            }
            let motions = this._animation.motions;
            this._animation.tracks.splice(0);
            animation.tracks.forEach((trackData, index) => {
                let track = new Track(trackData.active, trackData.mixing);
                track.weight = trackData.weight;
                for (let entryData of trackData.entries) {
                    let motion = entryData.name ? motions.find(motion => motion.name == entryData.name) : new Motion();
                    let entry = new Entry(motion, entryData.elapsed);
                    entry.loop      = entryData.loop;
                    entry.timeScale = entryData.timeScale;
                    entry.weight    = entryData.weight;
                    entry.mixTime   = entryData.mixTime;
                    entry.endTime   = entryData.endTime;
                    track.entries.push(entry);
                }
                this._animation.tracks[index] = track;
            });
            if (this._physics && physics) {
                for (let key in physics) {
                    if (key in this._physics.settings) {
                        let vertices = this._physics.settings[key].vertices;
                        physics[key].forEach((vertex, index) => {
                            if (vertices[index]) {
                                Object.assign(vertices[index].position, vertex.position);
                                Object.assign(vertices[index].lastGravity, vertex.lastGravity);
                                Object.assign(vertices[index].velocity, vertex.velocity);
                            }
                        });
                    }
                }
            }
            if (this._direction && direction) {
                for (let expressionData of direction.expressions) {
                    if (!expressionData.custom) {
                        let expression = this._direction.expressions.find(expression => expression.entity.name == expressionData.name);
                        if (expression) {
                            expression.index   = expressionData.index;
                            expression.enabled = expressionData.enabled;
                            expression.time    = expressionData.time;
                        }
                    } else {
                        this._direction.expressions.push({
                            entity:  new Expression(expressionData.custom),
                            index:   expressionData.index,
                            enabled: expressionData.enabled,
                            time:    expressionData.time,
                            custom:  expressionData.custom
                        });
                    }
                }
                this._direction.expressions.sort((a, b) => a.index - b.index);
            }
        }
    }

    Live2D.Mesh = Mesh;
    Live2D.CopyMesh = CopyMesh;
    Live2D.MaskMesh = MaskMesh;
    Live2D.MeshMaskFilter = MeshMaskFilter;
    Live2D.Expression = Expression;
    Live2D.Direction = Direction;
    Live2D.Vector2 = Vector2;
    Live2D.Physics = Physics;
    Live2D.Curve = Curve;
    Live2D.Motion = Motion;
    Live2D.Entry = Entry;
    Live2D.Track = Track;
    Live2D.EyeBlink = EyeBlink;
    Live2D.Animation = Animation;

    PIXI.Live2D = Live2D;

    //==============================================================================
    // Model3Parser
    //==============================================================================

    let Model3Parser = (function() {
        let LoaderResource = PIXI.LoaderResource || PIXI.loaders.Resource;
        LoaderResource.setExtensionXhrType('moc3', LoaderResource.XHR_RESPONSE_TYPE.BUFFER);

        function isJson(resource) {
            return resource.type == LoaderResource.TYPE.JSON;
        }

        function Model3Parser() {
            return Model3Parser.use;
        }

        Model3Parser.use = function(resource, next) {
            if (!isJson(resource) || !resource.data.FileReferences) {
                return next();
            }
            let data = resource.data;
            let fileReferences = resource.data.FileReferences;
            let baseUrl = resource.url.replace(/[^/]+$/, '');
            let loadOption = { parentResource: resource };
            let live2dData = {
                Version:     data.Version,
                Moc:         null,
                Textures:    [],
                Physics:     null,
                Pose:        null,
                UserData:    null,
                DisplayInfo: null,
                Expressions: null,
                Motions:     null,
                Groups:      data.Groups,
                HitAreas:    data.HitAreas,
                loading:     0
            };
            for (let index in fileReferences) {
                if (!fileReferences[index]) {
                    continue;
                }
                if (index == 'Moc') {
                    let url = baseUrl + fileReferences.Moc;
                    this.add(`${resource.name}/Moc`, url, loadOption, function(resource) {
                        live2dData.Moc = new Live2DCubismCore.Moc(resource.data);
                        live2dData.loading--;
                        next();
                    });
                    live2dData.loading++;
                } else if (index == 'Textures') {
                    for (let i = 0; i < fileReferences.Textures.length; i++) {
                        let url = baseUrl + fileReferences.Textures[i];
                        this.add(`${resource.name}/Texture_${i}`, url, loadOption, function(resource) {
                            live2dData.Textures[i] = resource.texture;
                            live2dData.loading--;
                            next();
                        });
                        live2dData.loading++;
                    }
                } else if (['Physics', 'Pose', 'UserData', 'DisplayInfo'].includes(index)) {
                    let url = baseUrl + fileReferences[index];
                    this.add(`${resource.name}/${index}`, url, loadOption, function(resource) {
                        live2dData[index] = resource.data;
                        live2dData.loading--;
                        next();
                    });
                    live2dData.loading++;
                } else if (index == 'Expressions') {
                    live2dData.Expressions = [];
                    let expressions = fileReferences.Expressions;
                    for (let i = 0; i < expressions.length; i++) {
                        let expression = expressions[i];
                        let name = expression.Name;
                        live2dData.Expressions[i] = { Name: name };
                        let url = baseUrl + expression.File;
                        this.add(`${resource.name}/Expression/${name}`, url, loadOption, function(resource) {
                            live2dData.Expressions[i].Data = resource.data;
                            live2dData.loading--;
                            next();
                        });
                        live2dData.loading++;
                    }
                } else if (index == 'Motions') {
                    live2dData.Motions = {};
                    for (let group in fileReferences.Motions) {
                        live2dData.Motions[group] = [];
                        let motion = fileReferences.Motions[group];
                        for (let i = 0; i < motion.length; i++) {
                            live2dData.Motions[group][i] = {
                                FadeInTime:  motion[i].FadeInTime,
                                FadeOutTime: motion[i].FadeOutTime,
                                Sound: motion[i].Sound
                            };
                            let url = baseUrl + motion[i].File;
                            this.add(`${resource.name}/Motion/${group}_${i}`, url, loadOption, function(resource) {
                                live2dData.Motions[group][i].Group = group;
                                live2dData.Motions[group][i].Name  = motion[i].Name || group + String(i + 1).padStart(2, '0');
                                live2dData.Motions[group][i].Data  = resource.data;
                                live2dData.loading--;
                                next();
                            });
                            live2dData.loading++;
                        }
                    }
                }
            }
            resource.live2dData = live2dData;
            next();
        };

        return Model3Parser;
    })();

    if (PIXI.Loader) {
        PIXI.Loader.registerPlugin(Model3Parser);
    } else {
        PIXI.loaders.Loader.addPixiMiddleware(Model3Parser);
    }
}();
