Phaser Source: src/gameobjects/Creature.js

# Source: src/gameobjects/Creature.js

    /**
    * @author       Richard Davey <rich@photonstorm.com>
    * @author       Kestrel Moon Studios <creature@kestrelmoon.com>
    * @copyright    2016 Photon Storm Ltd and Kestrel Moon Studios
    * @license      {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
    */

    /**
    * Creature is a custom Game Object used in conjunction with the Creature Runtime libraries by Kestrel Moon Studios.
    *
    * It allows you to display animated Game Objects that were created with the [Creature Automated Animation Tool](http://www.kestrelmoon.com/creature/).
    *
    * Note 1: You can only use Phaser.Creature objects in WebGL enabled games. They do not work in Canvas mode games.
    *
    * Note 2: You must use a build of Phaser that includes the CreatureMeshBone.js runtime and gl-matrix.js, or have them
    * loaded before your Phaser game boots.
    *
    * See the Phaser custom build process for more details.
    *
    * By default the Creature runtimes are NOT included in any pre-configured version of Phaser.
    *
    * So you'll need to do `grunt custom` to create a build that includes them.
    *
    * @class Phaser.Creature
    * @extends PIXI.DisplayObjectContainer
    * @extends Phaser.Component.Core
    * @extends Phaser.Component.Angle
    * @extends Phaser.Component.AutoCull
    * @extends Phaser.Component.BringToTop
    * @extends Phaser.Component.Destroy
    * @extends Phaser.Component.FixedToCamera
    * @extends Phaser.Component.LifeSpan
    * @extends Phaser.Component.Reset
    * @constructor
    * @param {Phaser.Game} game - A reference to the currently running game.
    * @param {number} x - The x coordinate of the Game Object. The coordinate is relative to any parent container this Game Object may be in.
    * @param {number} y - The y coordinate of the Game Object. The coordinate is relative to any parent container this Game Object may be in.
    * @param {string|PIXI.Texture} key - The texture used by the Creature Object during rendering. It can be a string which is a reference to the Cache entry, or an instance of a PIXI.Texture.
    * @param {string} mesh - The mesh data for the Creature Object. It should be a string which is a reference to the Cache JSON entry.
    * @param {string} [animation='default'] - The animation within the mesh data  to play.
    */
    Phaser.Creature = function (game, x, y, key, mesh, animation) {

        if (animation === undefined) { animation = 'default'; }

        /**
        * @property {number} type - The const type of this object.
        * @readonly
        */
        this.type = Phaser.CREATURE;

        if (!game.cache.checkJSONKey(mesh))
        {
            console.warn('Phaser.Creature: Invalid mesh key given. Not found in Phaser.Cache');
            return;
        }

        var meshData = game.cache.getJSON(mesh);

        /**
        * @property {Creature} _creature - The Creature instance.
        * @private
        */
        this._creature = new Creature(meshData);

        /**
        * @property {CreatureAnimation} animation - The CreatureAnimation instance.
        */
        this.animation = new CreatureAnimation(meshData, animation, this._creature);

        /**
        * @property {CreatureManager} manager - The CreatureManager instance for this object.
        */
        this.manager = new CreatureManager(this._creature);

        /**
        * @property {number} timeDelta - How quickly the animation advances.
        * @default
        */
        this.timeDelta = 0.05;

        if (typeof key === 'string')
        {
            var texture = new PIXI.Texture(game.cache.getBaseTexture(key));
        }
        else
        {
            var texture = key;
        }

        /**
        * @property {PIXI.Texture} texture - The texture the animation is using.
        */
        this.texture = texture;

        PIXI.DisplayObjectContainer.call(this);

        this.dirty = true;
        this.blendMode = PIXI.blendModes.NORMAL;

        /**
        * @property {Phaser.Point} creatureBoundsMin - The minimum bounds point.
        * @protected
        */
        this.creatureBoundsMin = new Phaser.Point();

        /**
        * @property {Phaser.Point} creatureBoundsMax - The maximum bounds point.
        * @protected
        */
        this.creatureBoundsMax = new Phaser.Point();

        var target = this.manager.target_creature;

        /**
        * @property {PIXI.Float32Array} vertices - The vertices data.
        * @protected
        */
        this.vertices = new PIXI.Float32Array(target.total_num_pts * 2);

        /**
        * @property {PIXI.Float32Array} uvs - The UV data.
        * @protected
        */
        this.uvs = new PIXI.Float32Array(target.total_num_pts * 2);

        /**
        * @property {PIXI.Uint16Array} indices
        * @protected
        */
        this.indices = new PIXI.Uint16Array(target.global_indices.length);

        for (var i = 0; i < this.indices.length; i++)
        {
            this.indices[i] = target.global_indices[i];
        }

        /**
        * @property {PIXI.Uint16Array} colors - The vertices colors
        * @protected
        */
        this.colors = new PIXI.Float32Array([1, 1, 1, 1]);

        this.updateRenderData(target.global_pts, target.global_uvs);

        this.manager.AddAnimation(this.animation);
        this.manager.SetActiveAnimationName(animation, false);

        Phaser.Component.Core.init.call(this, game, x, y);

    };

    Phaser.Creature.prototype = Object.create(PIXI.DisplayObjectContainer.prototype);
    Phaser.Creature.prototype.constructor = Phaser.Creature;

    Phaser.Component.Core.install.call(Phaser.Creature.prototype, [
        'Angle',
        'AutoCull',
        'BringToTop',
        'Destroy',
        'FixedToCamera',
        'LifeSpan',
        'Reset'
    ]);

    Phaser.Creature.prototype.preUpdateInWorld = Phaser.Component.InWorld.preUpdate;
    Phaser.Creature.prototype.preUpdateCore = Phaser.Component.Core.preUpdate;

    /**
    * Automatically called by World.preUpdate.
    *
    * @method Phaser.Creature#preUpdate
    * @memberof Phaser.Creature
    */
    Phaser.Creature.prototype.preUpdate = function () {

        if (!this.preUpdateInWorld())
        {
            return false;
        }

        this.manager.Update(this.timeDelta);

        this.updateData();

        return this.preUpdateCore();

    };

    /**
    *
    *
    * @method Phaser.Creature#_initWebGL
    * @memberof Phaser.Creature
    * @private
    */
    Phaser.Creature.prototype._initWebGL = function (renderSession) {

        // build the strip!
        var gl = renderSession.gl;

        this._vertexBuffer = gl.createBuffer();
        this._indexBuffer = gl.createBuffer();
        this._uvBuffer = gl.createBuffer();
        this._colorBuffer = gl.createBuffer();

        gl.bindBuffer(gl.ARRAY_BUFFER, this._vertexBuffer);
        gl.bufferData(gl.ARRAY_BUFFER, this.vertices, gl.DYNAMIC_DRAW);

        gl.bindBuffer(gl.ARRAY_BUFFER, this._uvBuffer);
        gl.bufferData(gl.ARRAY_BUFFER,  this.uvs, gl.DYNAMIC_DRAW);

        gl.bindBuffer(gl.ARRAY_BUFFER, this._colorBuffer);
        gl.bufferData(gl.ARRAY_BUFFER, this.colors, gl.STATIC_DRAW);

        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this._indexBuffer);
        gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.indices, gl.STATIC_DRAW);

    };

    /**
    * @method Phaser.Creature#_renderWebGL
    * @memberof Phaser.Creature
    * @private
    */
    Phaser.Creature.prototype._renderWebGL = function (renderSession) {

        //  If the sprite is not visible or the alpha is 0 then no need to render this element
        if (!this.visible || this.alpha <= 0)
        {
            return;
        }

        renderSession.spriteBatch.stop();

        // init! init!
        if (!this._vertexBuffer)
        {
            this._initWebGL(renderSession);
        }

        renderSession.shaderManager.setShader(renderSession.shaderManager.stripShader);

        this._renderCreature(renderSession);

        renderSession.spriteBatch.start();

    };

    /**
    * @method Phaser.Creature#_renderCreature
    * @memberof Phaser.Creature
    * @private
    */
    Phaser.Creature.prototype._renderCreature = function (renderSession) {

        var gl = renderSession.gl;

        var projection = renderSession.projection;
        var offset = renderSession.offset;
        var shader = renderSession.shaderManager.stripShader;

        renderSession.blendModeManager.setBlendMode(this.blendMode);

        //  Set uniforms
        gl.uniformMatrix3fv(shader.translationMatrix, false, this.worldTransform.toArray(true));
        gl.uniform2f(shader.projectionVector, projection.x, -projection.y);
        gl.uniform2f(shader.offsetVector, -offset.x, -offset.y);
        gl.uniform1f(shader.alpha, this.worldAlpha);

        if (!this.dirty)
        {
            gl.bindBuffer(gl.ARRAY_BUFFER, this._vertexBuffer);
            gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertices);
            gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 0, 0);

            //  Update the uvs
            gl.bindBuffer(gl.ARRAY_BUFFER, this._uvBuffer);
            gl.vertexAttribPointer(shader.aTextureCoord, 2, gl.FLOAT, false, 0, 0);

            gl.activeTexture(gl.TEXTURE0);

            //  Check if a texture is dirty..
            if (this.texture.baseTexture._dirty[gl.id])
            {
                renderSession.renderer.updateTexture(this.texture.baseTexture);
            }
            else
            {
                //  Bind the current texture
                gl.bindTexture(gl.TEXTURE_2D, this.texture.baseTexture._glTextures[gl.id]);
            }

            //  Don't need to upload!
            gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this._indexBuffer);
        }
        else
        {
            this.dirty = false;

            gl.bindBuffer(gl.ARRAY_BUFFER, this._vertexBuffer);
            gl.bufferData(gl.ARRAY_BUFFER, this.vertices, gl.DYNAMIC_DRAW);
            gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 0, 0);

            //  Update the uvs
            gl.bindBuffer(gl.ARRAY_BUFFER, this._uvBuffer);
            gl.bufferData(gl.ARRAY_BUFFER, this.uvs, gl.DYNAMIC_DRAW);
            gl.vertexAttribPointer(shader.aTextureCoord, 2, gl.FLOAT, false, 0, 0);

            gl.activeTexture(gl.TEXTURE0);

            //  Check if a texture is dirty
            if (this.texture.baseTexture._dirty[gl.id])
            {
                renderSession.renderer.updateTexture(this.texture.baseTexture);
            }
            else
            {
                gl.bindTexture(gl.TEXTURE_2D, this.texture.baseTexture._glTextures[gl.id]);
            }

            //  Don't need to upload!
            gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this._indexBuffer);
            gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.indices, gl.STATIC_DRAW);
        }

        gl.drawElements(gl.TRIANGLES, this.indices.length, gl.UNSIGNED_SHORT, 0);

    };

    /**
    * @method Phaser.Creature#updateCreatureBounds
    * @memberof Phaser.Creature
    * @private
    */
    Phaser.Creature.prototype.updateCreatureBounds = function () {

        //  Update bounds based off world transform matrix
        var target = this.manager.target_creature;

        target.ComputeBoundaryMinMax();

        this.creatureBoundsMin.set(target.boundary_min[0], -target.boundary_min[1]);
        this.creatureBoundsMax.set(target.boundary_max[0], -target.boundary_max[1]);

        this.worldTransform.apply(this.creatureBoundsMin, this.creatureBoundsMin);
        this.worldTransform.apply(this.creatureBoundsMax, this.creatureBoundsMax);

    };

    /**
    * @method Phaser.Creature#updateData
    * @memberof Phaser.Creature
    * @private
    */
    Phaser.Creature.prototype.updateData = function () {

        var target = this.manager.target_creature;

        var read_pts = target.render_pts;
        var read_uvs = target.global_uvs;

        this.updateRenderData(read_pts, read_uvs);
        this.updateCreatureBounds();

        this.dirty = true;

    };

    /**
    * @method Phaser.Creature#updateRenderData
    * @memberof Phaser.Creature
    * @private
    */
    Phaser.Creature.prototype.updateRenderData = function (verts, uvs) {

        var target = this.manager.target_creature;

        var pt_index = 0;
        var uv_index = 0;

        var write_pt_index = 0;

        for (var i = 0; i < target.total_num_pts; i++)
        {
            this.vertices[write_pt_index] = verts[pt_index];
            this.vertices[write_pt_index + 1] = -verts[pt_index + 1];

            this.uvs[uv_index] = uvs[uv_index];
            this.uvs[uv_index + 1] = uvs[uv_index + 1];

            pt_index += 3;
            uv_index += 2;

            write_pt_index += 2;
        }

    };

    /**
    * Sets the Animation this Creature object will play, as defined in the mesh data.
    *
    * @method Phaser.Creature#setAnimation
    * @memberof Phaser.Creature
    * @param {string} key - The key of the animation to set, as defined in the mesh data.
    */
    Phaser.Creature.prototype.setAnimation = function (key) {

        this.manager.SetActiveAnimationName(key, true);

    };

    /**
    * Plays the currently set animation.
    *
    * @method Phaser.Creature#play
    * @memberof Phaser.Creature
    * @param {boolean} [loop=false] - Should the animation loop?
    */
    Phaser.Creature.prototype.play = function (loop) {

        if (loop === undefined) { loop = false; }

        this.loop = loop;

        this.manager.SetIsPlaying(true);
        this.manager.RunAtTime(0);

    };

    /**
    * Stops the currently playing animation.
    *
    * @method Phaser.Creature#stop
    * @memberof Phaser.Creature
    */
    Phaser.Creature.prototype.stop = function () {

        this.manager.SetIsPlaying(false);

    };

    /**
    * @name Phaser.Creature#isPlaying
    * @property {boolean} isPlaying - Is the _current_ animation playing?
    */
    Object.defineProperty(Phaser.Creature.prototype, 'isPlaying', {

        get: function() {

            return this.manager.GetIsPlaying();

        },

        set: function(value) {

            this.manager.SetIsPlaying(value);

        }

    });

    /**
    * @name Phaser.Creature#loop
    * @property {boolean} loop - Should the _current_ animation loop or not?
    */
    Object.defineProperty(Phaser.Creature.prototype, 'loop', {

        get: function() {

            return this.manager.should_loop;

        },

        set: function(value) {

            this.manager.SetShouldLoop(value);

        }

    });