var ObjectRenderer = require('../../renderers/webgl/utils/ObjectRenderer'), WebGLRenderer = require('../../renderers/webgl/WebGLRenderer'), ParticleShader = require('./ParticleShader'), ParticleBuffer = require('./ParticleBuffer'), math = require('../../math'); /** * @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 ParticleRenderer: * https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/graphics/g2d/ParticleRenderer.java */ /** * * @class * @private * @namespace PIXI * @param renderer {WebGLRenderer} The renderer this sprite batch works for. */ function ParticleRenderer(renderer) { ObjectRenderer.call(this, renderer); /** * The number of images in the Particle before it flushes. * * @member {number} */ this.size = 15000;//CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop var numIndices = this.size * 6; /** * Holds the indices * * @member {Uint16Array} */ this.indices = new Uint16Array(numIndices); for (var i=0, j=0; i < numIndices; i += 6, j += 4) { this.indices[i + 0] = j + 0; this.indices[i + 1] = j + 1; this.indices[i + 2] = j + 2; this.indices[i + 3] = j + 0; this.indices[i + 4] = j + 2; this.indices[i + 5] = j + 3; } /** * The default shader that is used if a sprite doesn't have a more specific one. * * @member {Shader} */ this.shader = null; this.tempMatrix = new math.Matrix(); } ParticleRenderer.prototype = Object.create(ObjectRenderer.prototype); ParticleRenderer.prototype.constructor = ParticleRenderer; module.exports = ParticleRenderer; WebGLRenderer.registerPlugin('particle', ParticleRenderer); /** * Sets up the renderer context and necessary buffers. * * @private * @param gl {WebGLRenderingContext} the current WebGL drawing context */ ParticleRenderer.prototype.onContextChange = function () { var gl = this.renderer.gl; // setup default shader this.shader = new ParticleShader(this.renderer.shaderManager); this.indexBuffer = gl.createBuffer(); // 65535 is max index, so 65535 / 6 = 10922. //upload the index data gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.indices, gl.STATIC_DRAW); this.properties = [ //verticiesData { attribute:this.shader.attributes.aVertexPosition, dynamic:false, size:2, uploadFunction:this.uploadVerticies, offset:0 }, // positionData { attribute:this.shader.attributes.aPositionCoord, dynamic:true, size:2, uploadFunction:this.uploadPosition, offset:0 }, // rotationData { attribute:this.shader.attributes.aRotation, dynamic:false, size:1, uploadFunction:this.uploadRotation, offset:0 }, //u vsData { attribute:this.shader.attributes.aTextureCoord, dynamic:false, size:2, uploadFunction:this.uploadUvs, offset:0 }, // alphaData { attribute:this.shader.attributes.aColor, dynamic:false, size:1, uploadFunction:this.uploadAlpha, offset:0 }]; }; /** * Starts a new sprite batch. * */ ParticleRenderer.prototype.start = function () { var gl = this.renderer.gl; // bind the main texture gl.activeTexture(gl.TEXTURE0); // bind the buffers gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); var shader = this.shader; this.renderer.shaderManager.setShader(shader); }; /** * Renders the sprite object. * * @param sprite {Sprite} the sprite to render when using this Particle */ ParticleRenderer.prototype.render = function ( container ) { var children = container.children, totalChildren = children.length, maxSize = container._size; if(totalChildren === 0) { return; } else if(totalChildren > maxSize) { totalChildren = maxSize; } if(!container._buffers) { container._buffers = this.generateBuffers( container ); } // if the uvs have not updated then no point rendering just yet! //this.renderer.blendModeManager.setBlendMode(sprite.blendMode); var gl = this.renderer.gl; var m = container.worldTransform.copy( this.tempMatrix ); m.prepend( this.renderer.currentRenderTarget.projectionMatrix ); gl.uniformMatrix3fv(this.shader.uniforms.projectionMatrix._location, false, m.toArray(true)); // if this variable is true then we will upload the static contents as well as the dynamic contens var uploadStatic = container._updateStatic; // make sure the texture is bound.. var baseTexture = children[0]._texture.baseTexture; if (!baseTexture._glTextures[gl.id]) { this.renderer.updateTexture(baseTexture); if(!this.properties[0].dynamic || !this.properties[3].dynamic) { uploadStatic = true; } } else { gl.bindTexture(gl.TEXTURE_2D, baseTexture._glTextures[gl.id]); } // now lets upload and render the buffers.. var j = 0; for (var i = 0; i < totalChildren; i+=this.size) { var amount = ( totalChildren - i); if(amount > this.size) { amount = this.size; } var buffer = container._buffers[j++]; // we always upload the dynamic buffer.uploadDynamic(children, i, amount); // we only upload the static content when we have to! if(uploadStatic) { buffer.uploadStatic(children, i, amount); } // bind the buffer buffer.bind( this.shader ); // now draw those suckas! gl.drawElements(gl.TRIANGLES, amount * 6, gl.UNSIGNED_SHORT, 0); this.renderer.drawCount++; } container._updateStatic = false; }; ParticleRenderer.prototype.generateBuffers = function ( container ) { var gl = this.renderer.gl, buffers = [], size = container._size, i; // update the properties to match the state of the container.. for (i = 0; i < container._properties.length; i++) { this.properties[i].dynamic = container._properties[i]; } for (i = 0; i < size; i += this.size) { buffers.push( new ParticleBuffer(gl, this.properties, this.size, this.shader) ); } return buffers; }; ParticleRenderer.prototype.uploadVerticies = function (children, startIndex, amount, array, stride, offset) { 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; } 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; } }; ParticleRenderer.prototype.uploadPosition = function (children,startIndex, amount, array, stride, offset) { for (var i = 0; i < amount; i++) { var 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; } }; ParticleRenderer.prototype.uploadRotation = function (children,startIndex, amount, array, stride, offset) { for (var i = 0; i < amount; i++) { var 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; } }; ParticleRenderer.prototype.uploadUvs = function (children,startIndex, amount, array, stride, offset) { for (var i = 0; i < amount; i++) { var 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; } } }; ParticleRenderer.prototype.uploadAlpha = function (children,startIndex, amount, array, stride, offset) { for (var i = 0; i < amount; i++) { var 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; } }; /** * Destroys the Particle. * */ ParticleRenderer.prototype.destroy = function () { this.shader.destroy(); //TODO implement this! };