Newer
Older
pixi.js / packages / particles / src / ParticleBuffer.js
@Ivan Popelyshev Ivan Popelyshev on 9 Jan 2019 7 KB Fix types in documentation (#5345)
import { createIndicesForQuads } from '@pixi/utils';
import { Geometry, Buffer } from '@pixi/core';
import { TYPES } from '@pixi/constants';

/**
 * @author Mat Groves
 *
 * Big thanks to the very clever Matt DesLauriers <mattdesl> https://github.com/mattdesl/
 * for creating the original PixiJS 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
 */

/**
 * The particle buffer manages the static and dynamic buffers for a particle container.
 *
 * @class
 * @private
 * @memberof PIXI
 */
export default class ParticleBuffer
{
    /**
     * @private
     * @param {object} properties - The properties to upload.
     * @param {boolean[]} dynamicPropertyFlags - Flags for which properties are dynamic.
     * @param {number} size - The size of the batch.
     */
    constructor(properties, dynamicPropertyFlags, size)
    {
        this.geometry = new Geometry();

        this.indexBuffer = null;

        /**
         * The number of particles the buffer can hold
         *
         * @private
         * @member {number}
         */
        this.size = size;

        /**
         * A list of the properties that are dynamic.
         *
         * @private
         * @member {object[]}
         */
        this.dynamicProperties = [];

        /**
         * A list of the properties that are static.
         *
         * @private
         * @member {object[]}
         */
        this.staticProperties = [];

        for (let i = 0; i < properties.length; ++i)
        {
            let property = properties[i];

            // Make copy of properties object so that when we edit the offset it doesn't
            // change all other instances of the object literal
            property = {
                attributeName: property.attributeName,
                size: property.size,
                uploadFunction: property.uploadFunction,
                type: property.type || TYPES.FLOAT,
                offset: property.offset,
            };

            if (dynamicPropertyFlags[i])
            {
                this.dynamicProperties.push(property);
            }
            else
            {
                this.staticProperties.push(property);
            }
        }

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

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

        this._updateID = 0;

        this.initBuffers();
    }

    /**
     * Sets up the renderer context and necessary buffers.
     *
     * @private
     */
    initBuffers()
    {
        const geometry = this.geometry;

        let dynamicOffset = 0;

        /**
         * Holds the indices of the geometry (quads) to draw
         *
         * @member {Uint16Array}
         * @private
         */
        this.indexBuffer = new Buffer(createIndicesForQuads(this.size), true, true);
        geometry.addIndex(this.indexBuffer);

        this.dynamicStride = 0;

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

            property.offset = dynamicOffset;
            dynamicOffset += property.size;
            this.dynamicStride += property.size;
        }

        const dynBuffer = new ArrayBuffer(this.size * this.dynamicStride * 4 * 4);

        this.dynamicData = new Float32Array(dynBuffer);
        this.dynamicDataUint32 = new Uint32Array(dynBuffer);
        this.dynamicBuffer = new Buffer(this.dynamicData, false, false);

        // static //
        let staticOffset = 0;

        this.staticStride = 0;

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

            property.offset = staticOffset;
            staticOffset += property.size;
            this.staticStride += property.size;
        }

        const statBuffer = new ArrayBuffer(this.size * this.staticStride * 4 * 4);

        this.staticData = new Float32Array(statBuffer);
        this.staticDataUint32 = new Uint32Array(statBuffer);
        this.staticBuffer = new Buffer(this.staticData, true, false);

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

            geometry.addAttribute(
                property.attributeName,
                this.dynamicBuffer,
                0,
                property.type === TYPES.UNSIGNED_BYTE,
                property.type,
                this.dynamicStride * 4,
                property.offset * 4
            );
        }

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

            geometry.addAttribute(
                property.attributeName,
                this.staticBuffer,
                0,
                property.type === TYPES.UNSIGNED_BYTE,
                property.type,
                this.staticStride * 4,
                property.offset * 4
            );
        }
    }

    /**
     * Uploads the dynamic properties.
     *
     * @private
     * @param {PIXI.DisplayObject[]} children - The children to upload.
     * @param {number} startIndex - The index to start at.
     * @param {number} amount - The number to upload.
     */
    uploadDynamic(children, startIndex, amount)
    {
        for (let i = 0; i < this.dynamicProperties.length; i++)
        {
            const property = this.dynamicProperties[i];

            property.uploadFunction(children, startIndex, amount,
                property.type === TYPES.UNSIGNED_BYTE ? this.dynamicDataUint32 : this.dynamicData,
                this.dynamicStride, property.offset);
        }

        this.dynamicBuffer._updateID++;
    }

    /**
     * Uploads the static properties.
     *
     * @private
     * @param {PIXI.DisplayObject[]} children - The children to upload.
     * @param {number} startIndex - The index to start at.
     * @param {number} amount - The number to upload.
     */
    uploadStatic(children, startIndex, amount)
    {
        for (let i = 0; i < this.staticProperties.length; i++)
        {
            const property = this.staticProperties[i];

            property.uploadFunction(children, startIndex, amount,
                property.type === TYPES.UNSIGNED_BYTE ? this.staticDataUint32 : this.staticData,
                this.staticStride, property.offset);
        }

        this.staticBuffer._updateID++;
    }

    /**
     * Destroys the ParticleBuffer.
     *
     * @private
     */
    destroy()
    {
        this.indexBuffer = null;

        this.dynamicProperties = null;
        // this.dynamicBuffer.destroy();
        this.dynamicBuffer = null;
        this.dynamicData = null;
        this.dynamicDataUint32 = null;

        this.staticProperties = null;
        // this.staticBuffer.destroy();
        this.staticBuffer = null;
        this.staticData = null;
        this.staticDataUint32 = null;
        // all buffers are destroyed inside geometry
        this.geometry.destroy();
    }
}