Newer
Older
pixi.js / src / core / particles / webgl / ParticleBuffer.js
@mathew groves mathew groves on 28 Jan 2015 10 KB Particle Refactor part 2

/**
 * @author Mat Groves
 *
 * Big thanks to the very clever Matt DesLauriers <mattdesl> https://github.com/mattdesl/
 * for creating the original pixi version!
 * Also a thanks to https://github.com/bchevalier for tweaking the tint and alpha so that they now share 4 bytes on the vertex buffer
 *
 * Heavily inspired by LibGDX's ParticleBuffer:
 * https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/graphics/g2d/ParticleBuffer.java
 */

/**
 *
 * @class
 * @private
 * @namespace PIXI
 * @param renderer {WebGLRenderer} The renderer this sprite batch works for.
 */
function ParticleBuffer(gl, size, shader )
{
    this.gl = gl;

    /**
     *
     *
     * @member {number}
     */
    this.vertSize = 2;

    /**
     *
     *
     * @member {number}
     */
    this.vertByteSize = this.vertSize * 4;

    /**
     * The number of images in the SpriteBatch before it flushes.
     *
     * @member {number}
     */
    this.size = size;

    this.verticiesData = {
        attribute:shader.attributes.aVertexPosition,
        dynamic:true,
        size:2,
        uploadFunction:this.uploadVerticies.bind(this),
        offset:0
    };

    this.positionData = {
        attribute:shader.attributes.aPositionCoord,
        dynamic:true,
        size:2,
        uploadFunction:this.uploadPosition.bind(this),
        offset:0
    };

    this.rotationData = {
        attribute:shader.attributes.aRotation,
        dynamic:true,
        size:1,
        uploadFunction:this.uploadRotation.bind(this),
        offset:0
    };

    this.uvsData = {
        attribute:shader.attributes.aTextureCoord,
        dynamic:true,
        size:2,
        uploadFunction:this.uploadUvs.bind(this),
        offset:0
    };

    this.alphaData = {
        attribute:shader.attributes.aColor,
        dynamic:true,
        size:1,
        uploadFunction:this.uploadAlpha.bind(this),
        offset:0
    };

    this.dynamicProperties = [
       // this.verticiesData,
        this.positionData
       // this.rotationData,
       // this.uvsData
       // this.alphaData
    ];

    this.staticProperties = [
        this.verticiesData,
      //  this.positionData,
        this.rotationData,
        this.uvsData,
        this.alphaData
    ];

    this.staticStride = 0;
    this.staticBuffer = null;
    this.staticData = null;

    this.dynamicStride = 0;
    this.dynamicBuffer = null;
    this.dynamicData = null;

    this.initBuffers();

}

ParticleBuffer.prototype.constructor = ParticleBuffer;
module.exports = ParticleBuffer;

/**
 * Sets up the renderer context and necessary buffers.
 *
 * @private
 * @param gl {WebGLContext} the current WebGL drawing context
 */
ParticleBuffer.prototype.initBuffers = function ()
{
    var gl = this.gl;
    var i;
    var property;

    var dynamicOffset = 0;
    this.dynamicStride = 0;

    for (i = 0; i < this.dynamicProperties.length; i++)
    {
        property = this.dynamicProperties[i];

        if(property.dynamic)
        {
            property.offset = dynamicOffset;
            dynamicOffset += property.size;
            this.dynamicStride += property.size;
        }
    }

    this.dynamicData = new Float32Array( this.size * this.dynamicStride * 4);
    this.dynamicBuffer = gl.createBuffer();

    gl.bindBuffer(gl.ARRAY_BUFFER, this.dynamicBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, this.dynamicData, gl.DYNAMIC_DRAW);


    // static //
    var staticOffset = 0;
    this.staticStride = 0;

    for (i = 0; i < this.staticProperties.length; i++)
    {
        property = this.staticProperties[i];

        if(property.dynamic)
        {
            property.offset = staticOffset;
            staticOffset += property.size;
            this.staticStride += property.size;
        }
    }

    this.staticData = new Float32Array( this.size * this.staticStride * 4);
    this.staticBuffer = gl.createBuffer();

    gl.bindBuffer(gl.ARRAY_BUFFER, this.staticBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, this.staticData, gl.DYNAMIC_DRAW);

};

ParticleBuffer.prototype.upload = function(children, startIndex, amount, uploadStatic)
{
    var i, property;
    var gl = this.gl;

    for (i = 0; i < this.dynamicProperties.length; i++)
    {
        property = this.dynamicProperties[i];
        property.uploadFunction(children, startIndex, amount, this.dynamicData, this.dynamicStride, property.offset);
    }



    gl.bindBuffer(gl.ARRAY_BUFFER, this.dynamicBuffer);
    gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.dynamicData);

    if(uploadStatic)
    {
        for (i = 0; i < this.staticProperties.length; i++)
        {
            property = this.staticProperties[i];
            property.uploadFunction(children, startIndex, amount, this.staticData, this.staticStride, property.offset);
        }


        gl.bindBuffer(gl.ARRAY_BUFFER, this.staticBuffer);
        gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.staticData);
    }
};

ParticleBuffer.prototype.uploadVerticies = function (children, startIndex, amount, array, stride, offset)
{
    //console.log(">>>", array)
    //var vertices = this.vertices,
        var sprite,
        texture,
        trim,
        sx,
        sy,
        w0, w1, h0, h1;

    for (var i = 0; i < amount; i++) {

        sprite = children[startIndex + i];
        texture = sprite._texture;
        sx = sprite.scale.x;
        sy = sprite.scale.y;

        if (texture.trim)
        {
            // if the sprite is trimmed then we need to add the extra space before transforming the sprite coords..
            trim = texture.trim;

            w1 = trim.x - sprite.anchor.x * trim.width;
            w0 = w1 + texture.crop.width;

            h1 = trim.y - sprite.anchor.y * trim.height;
            h0 = h1 + texture.crop.height;
        }
        else
        {
            w0 = (texture._frame.width ) * (1-sprite.anchor.x);
            w1 = (texture._frame.width ) * -sprite.anchor.x;

            h0 = texture._frame.height * (1-sprite.anchor.y);
            h1 = texture._frame.height * -sprite.anchor.y;
        }

    //    var pos = stride * i;

        array[offset] = w1 * sx;
        array[offset + 1] = h1 * sy;

        array[offset + stride] = w0 * sx;
        array[offset + stride + 1] = h1 * sy;

        array[offset + stride * 2] = w0 * sx;
        array[offset + stride * 2 + 1] = h0 * sy;

        array[offset + stride * 3] = w1 * sx;
        array[offset + stride * 3 + 1] = h0 * sy;

        offset += stride * 4;
    }

};


ParticleBuffer.prototype.uploadPosition = function (children,startIndex, amount, array, stride, offset)
{
        var spritePosition;

    for (var i = 0; i < amount; i++) {

        spritePosition = children[startIndex + i].position;

        array[offset] = spritePosition.x;
        array[offset + 1] = spritePosition.y;

        array[offset + stride] = spritePosition.x;
        array[offset + stride + 1] = spritePosition.y;

        array[offset + stride * 2] = spritePosition.x;
        array[offset + stride * 2 + 1] = spritePosition.y;

        array[offset + stride * 3] = spritePosition.x;
        array[offset + stride * 3 + 1] = spritePosition.y;

        offset += stride * 4;
    }

};

ParticleBuffer.prototype.uploadRotation = function (children,startIndex, amount, array, stride, offset)
{
        var spriteRotation;

    for (var i = 0; i < amount; i++) {

        spriteRotation = children[startIndex + i].rotation;


         array[offset] = spriteRotation;
         array[offset + stride] = spriteRotation;
         array[offset + stride * 2] = spriteRotation;
         array[offset + stride * 3] = spriteRotation;

        offset += stride * 4;
    }
};

ParticleBuffer.prototype.uploadUvs = function (children,startIndex, amount, array, stride, offset)
{
    var textureUvs;

    for (var i = 0; i < amount; i++) {

        textureUvs = children[startIndex + i]._texture._uvs;

        if (textureUvs)
        {
            array[offset] = textureUvs.x0;
            array[offset + 1] = textureUvs.y0;

            array[offset + stride] = textureUvs.x1;
            array[offset + stride + 1] = textureUvs.y1;

            array[offset + stride * 2] = textureUvs.x2;
            array[offset + stride * 2 + 1] = textureUvs.y2;

            array[offset + stride * 3] = textureUvs.x3;
            array[offset + stride * 3 + 1] = textureUvs.y3;

            offset += stride * 4;
        }
        else
        {
            //TODO you know this can be easier!
            array[offset] = 0;
            array[offset + 1] = 0;

            array[offset + stride] = 0;
            array[offset + stride + 1] = 0;

            array[offset + stride * 2] = 0;
            array[offset + stride * 2 + 1] = 0;

            array[offset + stride * 3] = 0;
            array[offset + stride * 3 + 1] = 0;

            offset += stride * 4;
        }
    }
};

ParticleBuffer.prototype.uploadAlpha = function (children,startIndex, amount, array, stride, offset)
{
    var spriteAlpha;

     for (var i = 0; i < amount; i++) {

        spriteAlpha = children[startIndex + i].alpha;

        array[offset] = spriteAlpha;
        array[offset + stride] = spriteAlpha;
        array[offset + stride * 2] = spriteAlpha;
        array[offset + stride * 3] = spriteAlpha;

        offset += stride * 4;
    }
};

ParticleBuffer.prototype.uploadToBuffer = function (buffer, data, dataSize, amount)
{
    var gl = this.gl;

    gl.bindBuffer(gl.ARRAY_BUFFER, buffer);

    if (this.currentBatchSize > ( this.size * 0.5 ) )
    {
        gl.bufferSubData(gl.ARRAY_BUFFER, 0, data);
    }
    else
    {
        var view = data.subarray(0,  amount * dataSize);
        gl.bufferSubData(gl.ARRAY_BUFFER, 0, view);
    }
};

/**
 * Starts a new sprite batch.
 *
 */
ParticleBuffer.prototype.bind = function ()
{
    var gl = this.gl;
    var i, property;

    gl.bindBuffer(gl.ARRAY_BUFFER, this.dynamicBuffer);

    for (i = 0; i < this.dynamicProperties.length; i++)
    {
        property = this.dynamicProperties[i];
        gl.vertexAttribPointer(property.attribute, property.size, gl.FLOAT, false, this.dynamicStride * 4, property.offset * 4);
    }

    gl.bindBuffer(gl.ARRAY_BUFFER, this.staticBuffer);

    for (i = 0; i < this.staticProperties.length; i++)
    {
        property = this.staticProperties[i];
        gl.vertexAttribPointer(property.attribute, property.size, gl.FLOAT, false, this.staticStride * 4, property.offset * 4);
    }
};

/**
 * Destroys the SpriteBatch.
 *
 */
ParticleBuffer.prototype.destroy = function ()
{
    //TODO implement this :) to busy making the fun bits..
};